component templates
This commit is contained in:
parent
b2d71d070c
commit
99c8814533
|
@ -111,6 +111,7 @@ export const getStore = () => {
|
|||
store.moveUpComponent = moveUpComponent(store)
|
||||
store.moveDownComponent = moveDownComponent(store)
|
||||
store.copyComponent = copyComponent(store)
|
||||
store.addTemplatedComponent = addTemplatedComponent(store)
|
||||
return store
|
||||
}
|
||||
|
||||
|
@ -161,6 +162,7 @@ const initialise = (store, initial) => async () => {
|
|||
initial.components = values(pkg.components.components).map(
|
||||
expandComponentDefinition
|
||||
)
|
||||
initial.templates = pkg.components.templates
|
||||
initial.builtins = [getBuiltin("##builtin/screenslot")]
|
||||
initial.actions = values(pkg.appDefinition.actions)
|
||||
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 => {
|
||||
store.update(s => {
|
||||
const componentDef = component._component.startsWith("##")
|
||||
|
|
|
@ -39,7 +39,9 @@ const ok = () => {
|
|||
<div class="uk-modal-header">
|
||||
<h2 class="uk-modal-title">{title}</h2>
|
||||
</div>
|
||||
<div class="uk-modal-body">{body}</div>
|
||||
<div class="uk-modal-body">
|
||||
<slot>{body}</slot>
|
||||
</div>
|
||||
<div class="uk-modal-footer">
|
||||
<ButtonGroup>
|
||||
<Button grouped color="primary" on:click={ok}>
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
<script>
|
||||
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
||||
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 Select from "../common/Select.svelte"
|
||||
import Button from "../common/PlusButton.svelte"
|
||||
import ConfirmDialog from "../common/ConfirmDialog.svelte"
|
||||
import { getRecordNodes, getIndexNodes, getIndexSchema } from "../common/core"
|
||||
|
||||
let componentLibraries = []
|
||||
let current_view = "text"
|
||||
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 { libName } = splitName(component.name)
|
||||
|
@ -28,6 +38,28 @@
|
|||
|
||||
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 = []
|
||||
|
||||
|
@ -36,58 +68,74 @@
|
|||
}
|
||||
|
||||
componentLibraries = newComponentLibraries
|
||||
if (!selectedLib) selectedLib = newComponentLibraries[0].libName
|
||||
}
|
||||
|
||||
$: componentLibrary = componentLibraries.find(l => l.libName === selectedLib)
|
||||
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<Select>
|
||||
{#each componentLibraries as componentLibrary}
|
||||
<option value={componentLibrary.libName}>
|
||||
{componentLibrary.libName}
|
||||
<Select on:change={e => selectedLib = e.target.value}>
|
||||
{#each componentLibraries as lib}
|
||||
<option value={lib.libName}>
|
||||
{lib.libName}
|
||||
</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{#each componentLibraries as componentLibrary}
|
||||
<div class="library-container">
|
||||
<ul>
|
||||
<li>
|
||||
<button
|
||||
class:selected={current_view === 'text'}
|
||||
on:click={() => (current_view = 'text')}>
|
||||
<InputIcon />
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class:selected={current_view === 'layout'}
|
||||
on:click={() => (current_view = 'layout')}>
|
||||
<LayoutIcon />
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class:selected={current_view === 'media'}
|
||||
on:click={() => (current_view = 'media')}>
|
||||
<ImageIcon />
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="library-container">
|
||||
<ul>
|
||||
<li>
|
||||
<button
|
||||
class:selected={current_view === 'text'}
|
||||
on:click={() => (current_view = 'text')}>
|
||||
<InputIcon />
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class:selected={current_view === 'layout'}
|
||||
on:click={() => (current_view = 'layout')}>
|
||||
<LayoutIcon />
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class:selected={current_view === 'media'}
|
||||
on:click={() => (current_view = 'media')}>
|
||||
<ImageIcon />
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{#if componentLibrary}
|
||||
{#each $store.builtins.concat(componentLibrary.components) as component}
|
||||
<div class="component-container">
|
||||
<div
|
||||
class="component"
|
||||
on:click={() => onComponentChosen(component.name)}>
|
||||
<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">
|
||||
<span>{splitName(component.name).componentName} Presets</span>
|
||||
{#each Object.keys(component.presets) as preset}
|
||||
<li
|
||||
on:click|stopPropagation={() => onComponentChosen(component.name, preset)}>
|
||||
{preset}
|
||||
</li>
|
||||
{/each}
|
||||
{#if component.presets}
|
||||
<span>{splitName(component.name).componentName} Presets</span>
|
||||
{#each Object.keys(component.presets) as preset}
|
||||
<li
|
||||
on:click|stopPropagation={() => onComponentChosen(component.name, preset)}>
|
||||
{preset}
|
||||
</li>
|
||||
{/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>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -105,12 +153,27 @@
|
|||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
</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>
|
||||
.root {
|
||||
display: flex;
|
||||
|
@ -179,6 +242,7 @@
|
|||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.preset-menu li {
|
||||
|
@ -215,4 +279,9 @@
|
|||
.open {
|
||||
color: rgba(0, 85, 255, 1);
|
||||
}
|
||||
|
||||
.template-instance-label {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -31,7 +31,7 @@ module.exports.componentLibraryInfo = async (appPath, libname) => {
|
|||
const libDir = getLibDir(appPath, libname)
|
||||
const componentsPath = getComponentsFilepath(libDir)
|
||||
|
||||
const componentDefinitionExists = await exists(componentsPath);
|
||||
const componentDefinitionExists = await exists(componentsPath)
|
||||
|
||||
if (!componentDefinitionExists) {
|
||||
const e = new Error(
|
||||
|
@ -41,18 +41,32 @@ module.exports.componentLibraryInfo = async (appPath, libname) => {
|
|||
throw e
|
||||
}
|
||||
|
||||
const addNamespace = name => `${libname}/${name}`
|
||||
|
||||
try {
|
||||
const componentDefinitions = await readJSON(componentsPath)
|
||||
const namespacedComponents = { _lib: componentDefinitions._lib }
|
||||
for (let componentKey in componentDefinitions) {
|
||||
if (componentKey === "_lib") continue
|
||||
const namespacedName = `${libname}/${componentKey}`
|
||||
if (componentKey === "_lib" || componentKey === "_templates") continue
|
||||
const namespacedName = addNamespace(componentKey)
|
||||
componentDefinitions[componentKey].name = namespacedName
|
||||
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 {
|
||||
components: namespacedComponents,
|
||||
templates: namespacedTemplates,
|
||||
libDir,
|
||||
componentsPath,
|
||||
}
|
||||
|
|
|
@ -130,21 +130,28 @@ const getComponentDefinitions = async (appPath, pages, componentLibrary) => {
|
|||
|
||||
if (!pages) return []
|
||||
|
||||
componentLibraries = $(pages, [values, map(p => p.componentLibraries), flatten])
|
||||
componentLibraries = $(pages, [
|
||||
values,
|
||||
map(p => p.componentLibraries),
|
||||
flatten,
|
||||
])
|
||||
} else {
|
||||
componentLibraries = [componentLibrary]
|
||||
}
|
||||
|
||||
const components = {}
|
||||
const templates = {}
|
||||
|
||||
for (let library of componentLibraries) {
|
||||
const info = await componentLibraryInfo(appPath, library)
|
||||
merge(components, info.components)
|
||||
merge(templates, info.templates)
|
||||
}
|
||||
|
||||
if (components._lib) delete components._lib
|
||||
if (templates._lib) delete templates._lib
|
||||
|
||||
return { components }
|
||||
return { components, templates }
|
||||
}
|
||||
|
||||
module.exports.getComponentDefinitions = getComponentDefinitions
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
{
|
||||
"_lib": "./dist/index.js",
|
||||
"_templates" : {
|
||||
"saveRecordButton" : {
|
||||
"description": "Save record button",
|
||||
"component": "button"
|
||||
}
|
||||
},
|
||||
"button" : {
|
||||
"name": "Button",
|
||||
"description": "an html <button />",
|
||||
|
|
|
@ -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}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
|
@ -6,3 +6,4 @@ export { default as select } from "./Select.svelte"
|
|||
export { default as option } from "./Option.svelte"
|
||||
export { default as button } from "./Button.svelte"
|
||||
export { default as login } from "./Login.svelte"
|
||||
export { default as saveRecordButton } from "./Templates/saveRecordButton"
|
||||
|
|
Loading…
Reference in New Issue