component templates

This commit is contained in:
Michael Shanks 2020-02-19 21:38:21 +00:00
parent b2d71d070c
commit 99c8814533
8 changed files with 188 additions and 46 deletions

View File

@ -111,6 +111,7 @@ export const getStore = () => {
store.moveUpComponent = moveUpComponent(store) store.moveUpComponent = moveUpComponent(store)
store.moveDownComponent = moveDownComponent(store) store.moveDownComponent = moveDownComponent(store)
store.copyComponent = copyComponent(store) store.copyComponent = copyComponent(store)
store.addTemplatedComponent = addTemplatedComponent(store)
return store return store
} }
@ -161,6 +162,7 @@ const initialise = (store, initial) => async () => {
initial.components = values(pkg.components.components).map( initial.components = values(pkg.components.components).map(
expandComponentDefinition expandComponentDefinition
) )
initial.templates = pkg.components.templates
initial.builtins = [getBuiltin("##builtin/screenslot")] initial.builtins = [getBuiltin("##builtin/screenslot")]
initial.actions = values(pkg.appDefinition.actions) initial.actions = values(pkg.appDefinition.actions)
initial.triggers = pkg.appDefinition.triggers initial.triggers = pkg.appDefinition.triggers
@ -735,6 +737,24 @@ const addChildComponent = store => (componentToAdd, presetName) => {
}) })
} }
/**
* @param {string} props - props to add, as child of current component
*/
const addTemplatedComponent = store => props => {
store.update(state => {
walkProps(props, p => {
p._id = uuid()
})
state.currentComponentInfo._children = state.currentComponentInfo._children.concat(
props
)
_savePage(state)
return state
})
}
const selectComponent = store => component => { const selectComponent = store => component => {
store.update(s => { store.update(s => {
const componentDef = component._component.startsWith("##") const componentDef = component._component.startsWith("##")

View File

@ -39,7 +39,9 @@ const ok = () => {
<div class="uk-modal-header"> <div class="uk-modal-header">
<h2 class="uk-modal-title">{title}</h2> <h2 class="uk-modal-title">{title}</h2>
</div> </div>
<div class="uk-modal-body">{body}</div> <div class="uk-modal-body">
<slot>{body}</slot>
</div>
<div class="uk-modal-footer"> <div class="uk-modal-footer">
<ButtonGroup> <ButtonGroup>
<Button grouped color="primary" on:click={ok}> <Button grouped color="primary" on:click={ok}>

View File

@ -1,14 +1,24 @@
<script> <script>
import { splitName } from "./pagesParsing/splitRootComponentName.js" import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { store } from "../builderStore" import { store } from "../builderStore"
import { find, sortBy } from "lodash/fp" import { find, sortBy, groupBy } from "lodash/fp"
import { ImageIcon, InputIcon, LayoutIcon } from "../common/Icons/" import { ImageIcon, InputIcon, LayoutIcon } from "../common/Icons/"
import Select from "../common/Select.svelte" import Select from "../common/Select.svelte"
import Button from "../common/PlusButton.svelte" import Button from "../common/PlusButton.svelte"
import ConfirmDialog from "../common/ConfirmDialog.svelte"
import { getRecordNodes, getIndexNodes, getIndexSchema } from "../common/core"
let componentLibraries = [] let componentLibraries = []
let current_view = "text" let current_view = "text"
let selectedComponent = null let selectedComponent = null
let selectedLib
let selectTemplateDialog
let templateInstances = []
let selectedTemplateInstance
$: templatesByComponent = groupBy(t => t.component)($store.templates)
$: hierarchy = $store.hierarchy
$: libraryModules = $store.libraries
const addRootComponent = (component, allComponents) => { const addRootComponent = (component, allComponents) => {
const { libName } = splitName(component.name) const { libName } = splitName(component.name)
@ -28,6 +38,28 @@
const onComponentChosen = store.addChildComponent const onComponentChosen = store.addChildComponent
const onTemplateChosen = template => {
selectedComponent = null
const { componentName, libName } = splitName(template.name)
const templateOptions = {
records: getRecordNodes(hierarchy),
indexes: getIndexNodes(hierarchy),
helpers: {
indexSchema: getIndexSchema(hierarchy)
}
}
templateInstances = libraryModules[libName][componentName](templateOptions)
if(!templateInstances || templateInstances.length === 0) return
selectedTemplateInstance = templateInstances[0].name
selectTemplateDialog.show()
}
const onTemplateInstanceChosen = () => {
selectedComponent = null
const instance = templateInstances.find(i => i.name === selectedTemplateInstance)
store.addTemplatedComponent(instance.props)
}
$: { $: {
const newComponentLibraries = [] const newComponentLibraries = []
@ -36,18 +68,22 @@
} }
componentLibraries = newComponentLibraries componentLibraries = newComponentLibraries
if (!selectedLib) selectedLib = newComponentLibraries[0].libName
} }
$: componentLibrary = componentLibraries.find(l => l.libName === selectedLib)
</script> </script>
<div class="root"> <div class="root">
<Select> <Select on:change={e => selectedLib = e.target.value}>
{#each componentLibraries as componentLibrary} {#each componentLibraries as lib}
<option value={componentLibrary.libName}> <option value={lib.libName}>
{componentLibrary.libName} {lib.libName}
</option> </option>
{/each} {/each}
</Select> </Select>
{#each componentLibraries as componentLibrary}
<div class="library-container"> <div class="library-container">
<ul> <ul>
<li> <li>
@ -73,14 +109,16 @@
</li> </li>
</ul> </ul>
{#if componentLibrary}
{#each $store.builtins.concat(componentLibrary.components) as component} {#each $store.builtins.concat(componentLibrary.components) as component}
<div class="component-container"> <div class="component-container">
<div <div
class="component" class="component"
on:click={() => onComponentChosen(component.name)}> on:click={() => onComponentChosen(component.name)}>
<div class="name">{splitName(component.name).componentName}</div> <div class="name">{splitName(component.name).componentName}</div>
{#if component.presets && component.name === selectedComponent} {#if (component.presets || templatesByComponent[component.name]) && component.name === selectedComponent}
<ul class="preset-menu"> <ul class="preset-menu">
{#if component.presets}
<span>{splitName(component.name).componentName} Presets</span> <span>{splitName(component.name).componentName} Presets</span>
{#each Object.keys(component.presets) as preset} {#each Object.keys(component.presets) as preset}
<li <li
@ -88,6 +126,16 @@
{preset} {preset}
</li> </li>
{/each} {/each}
{/if}
{#if templatesByComponent[component.name]}
<span>{splitName(component.name).componentName} Templates</span>
{#each templatesByComponent[component.name] as template}
<li
on:click|stopPropagation={() => onTemplateChosen(template)}>
{template.description}
</li>
{/each}
{/if}
</ul> </ul>
{/if} {/if}
</div> </div>
@ -105,12 +153,27 @@
{/if} {/if}
</div> </div>
{/each} {/each}
{/if}
</div> </div>
{/each}
</div> </div>
<ConfirmDialog
bind:this={selectTemplateDialog}
title="Choose Template"
onCancel={() => selectedComponent = null}
onOk={onTemplateInstanceChosen}>
{#each templateInstances.map(i => i.name) as instance}
<div class="uk-margin uk-grid-small uk-child-width-auto uk-grid">
<label>
<input class="uk-radio" type="radio" bind:group={selectedTemplateInstance} value={instance}>
<span class="template-instance-label">{instance}</span>
</label>
</div>
{/each}
</ConfirmDialog>
<style> <style>
.root { .root {
display: flex; display: flex;
@ -179,6 +242,7 @@
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
text-transform: uppercase; text-transform: uppercase;
margin-top: 5px;
} }
.preset-menu li { .preset-menu li {
@ -215,4 +279,9 @@
.open { .open {
color: rgba(0, 85, 255, 1); color: rgba(0, 85, 255, 1);
} }
.template-instance-label {
margin-left: 20px;
}
</style> </style>

View File

@ -31,7 +31,7 @@ module.exports.componentLibraryInfo = async (appPath, libname) => {
const libDir = getLibDir(appPath, libname) const libDir = getLibDir(appPath, libname)
const componentsPath = getComponentsFilepath(libDir) const componentsPath = getComponentsFilepath(libDir)
const componentDefinitionExists = await exists(componentsPath); const componentDefinitionExists = await exists(componentsPath)
if (!componentDefinitionExists) { if (!componentDefinitionExists) {
const e = new Error( const e = new Error(
@ -41,18 +41,32 @@ module.exports.componentLibraryInfo = async (appPath, libname) => {
throw e throw e
} }
const addNamespace = name => `${libname}/${name}`
try { try {
const componentDefinitions = await readJSON(componentsPath) const componentDefinitions = await readJSON(componentsPath)
const namespacedComponents = { _lib: componentDefinitions._lib } const namespacedComponents = { _lib: componentDefinitions._lib }
for (let componentKey in componentDefinitions) { for (let componentKey in componentDefinitions) {
if (componentKey === "_lib") continue if (componentKey === "_lib" || componentKey === "_templates") continue
const namespacedName = `${libname}/${componentKey}` const namespacedName = addNamespace(componentKey)
componentDefinitions[componentKey].name = namespacedName componentDefinitions[componentKey].name = namespacedName
namespacedComponents[namespacedName] = componentDefinitions[componentKey] namespacedComponents[namespacedName] = componentDefinitions[componentKey]
} }
const namespacedTemplates = { _lib: componentDefinitions._lib }
for (let templateKey in componentDefinitions._templates || {}) {
const template = componentDefinitions._templates[templateKey]
if (template.component)
template.component = addNamespace(template.component)
const namespacedName = addNamespace(templateKey)
template.name = namespacedName
namespacedTemplates[namespacedName] =
componentDefinitions._templates[templateKey]
}
return { return {
components: namespacedComponents, components: namespacedComponents,
templates: namespacedTemplates,
libDir, libDir,
componentsPath, componentsPath,
} }

View File

@ -130,21 +130,28 @@ const getComponentDefinitions = async (appPath, pages, componentLibrary) => {
if (!pages) return [] if (!pages) return []
componentLibraries = $(pages, [values, map(p => p.componentLibraries), flatten]) componentLibraries = $(pages, [
values,
map(p => p.componentLibraries),
flatten,
])
} else { } else {
componentLibraries = [componentLibrary] componentLibraries = [componentLibrary]
} }
const components = {} const components = {}
const templates = {}
for (let library of componentLibraries) { for (let library of componentLibraries) {
const info = await componentLibraryInfo(appPath, library) const info = await componentLibraryInfo(appPath, library)
merge(components, info.components) merge(components, info.components)
merge(templates, info.templates)
} }
if (components._lib) delete components._lib if (components._lib) delete components._lib
if (templates._lib) delete templates._lib
return { components } return { components, templates }
} }
module.exports.getComponentDefinitions = getComponentDefinitions module.exports.getComponentDefinitions = getComponentDefinitions

View File

@ -1,5 +1,11 @@
{ {
"_lib": "./dist/index.js", "_lib": "./dist/index.js",
"_templates" : {
"saveRecordButton" : {
"description": "Save record button",
"component": "button"
}
},
"button" : { "button" : {
"name": "Button", "name": "Button",
"description": "an html <button />", "description": "an html <button />",

View File

@ -0,0 +1,23 @@
export default ({ records }) =>
records.map(r => ({
name: `Save ${r.name} Button`,
props: buttonProps(r),
}))
const buttonProps = record => ({
_component: "@budibase/standard-components/button",
_children: [
{
_component: "@budibase/standard-components/text",
text: `Save ${record.name}`,
},
],
onClick: [
{
"##eventHandlerType": "Save Record",
parameters: {
statePath: `${record.name}`,
},
},
],
})

View File

@ -6,3 +6,4 @@ export { default as select } from "./Select.svelte"
export { default as option } from "./Option.svelte" export { default as option } from "./Option.svelte"
export { default as button } from "./Button.svelte" export { default as button } from "./Button.svelte"
export { default as login } from "./Login.svelte" export { default as login } from "./Login.svelte"
export { default as saveRecordButton } from "./Templates/saveRecordButton"