component templates
This commit is contained in:
parent
b2d71d070c
commit
99c8814533
|
@ -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("##")
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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,58 +68,74 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
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">
|
|
||||||
<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}
|
{#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">
|
||||||
<span>{splitName(component.name).componentName} Presets</span>
|
{#if component.presets}
|
||||||
{#each Object.keys(component.presets) as preset}
|
<span>{splitName(component.name).componentName} Presets</span>
|
||||||
<li
|
{#each Object.keys(component.presets) as preset}
|
||||||
on:click|stopPropagation={() => onComponentChosen(component.name, preset)}>
|
<li
|
||||||
{preset}
|
on:click|stopPropagation={() => onComponentChosen(component.name, preset)}>
|
||||||
</li>
|
{preset}
|
||||||
{/each}
|
</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>
|
</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>
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 />",
|
||||||
|
|
|
@ -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 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"
|
||||||
|
|
Loading…
Reference in New Issue