budibase/packages/builder/src/userInterface/ComponentSelectionList.svelte

307 lines
7.8 KiB
Svelte
Raw Normal View History

<script>
2020-02-03 10:50:30 +01:00
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { store } from "../builderStore"
import { find, sortBy, groupBy, values, filter, map, uniqBy, flatten } from "lodash/fp"
2020-02-03 10:50:30 +01:00
import { ImageIcon, InputIcon, LayoutIcon } from "../common/Icons/"
import Select from "../common/Select.svelte"
2020-02-18 20:24:18 +01:00
import Button from "../common/PlusButton.svelte"
2020-02-19 22:38:21 +01:00
import ConfirmDialog from "../common/ConfirmDialog.svelte"
import { getRecordNodes, getIndexNodes, getIndexSchema, pipe } from "../common/core"
2020-02-03 10:50:30 +01:00
let componentLibraries = []
let current_view = "text"
let selectedComponent = null
2020-02-19 22:38:21 +01:00
let selectedLib
let selectTemplateDialog
let templateInstances = []
let selectedTemplateInstance
$: templatesByComponent = groupBy(t => t.component)($store.templates)
$: hierarchy = $store.hierarchy
$: libraryModules = $store.libraries
$: standaloneTemplates = pipe(templatesByComponent, [
values,
flatten,
filter(t => !$store.components.some(c => c.name === t.component)),
map(t => ({ name: splitName(t.component), template: t })),
uniqBy(t => t.name)
])
2020-02-03 10:50:30 +01:00
const addRootComponent = (component, allComponents) => {
const { libName } = splitName(component.name)
let group = find(r => r.libName === libName)(allComponents)
2020-02-03 10:50:30 +01:00
if (!group) {
group = {
libName,
components: [],
}
allComponents.push(group)
2020-02-03 10:50:30 +01:00
}
group.components.push(component)
2020-02-03 10:50:30 +01:00
}
const onComponentChosen = component => {
if (component.template) {
onTemplateChosen(component.template)
} else {
store.addChildComponent(component)
}
}
2020-02-19 22:38:21 +01:00
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)
}
2020-02-20 18:11:41 +01:00
function generate_components_list(components) {
return ($store.currentFrontEndType === "page"
2020-02-20 18:11:41 +01:00
? $store.builtins.concat(components)
: components).concat(standaloneTemplates)
2020-02-20 18:11:41 +01:00
}
2020-02-03 10:50:30 +01:00
$: {
const newComponentLibraries = []
2020-02-03 10:50:30 +01:00
for (let comp of sortBy(["name"])($store.components)) {
addRootComponent(comp, newComponentLibraries)
}
2020-02-03 10:50:30 +01:00
componentLibraries = newComponentLibraries
2020-02-19 22:38:21 +01:00
if (!selectedLib) selectedLib = newComponentLibraries[0].libName
2020-02-03 10:50:30 +01:00
}
2020-02-19 22:38:21 +01:00
$: componentLibrary = componentLibraries.find(l => l.libName === selectedLib)
</script>
<div class="root">
2020-02-19 22:38:21 +01:00
<Select on:change={e => selectedLib = e.target.value}>
{#each componentLibraries as lib}
<option value={lib.libName}>
{lib.libName}
</option>
{/each}
</Select>
2020-02-03 10:50:30 +01:00
2020-02-19 22:38:21 +01:00
<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}
2020-02-20 18:11:41 +01:00
{#each generate_components_list(componentLibrary.components) as component}
<div class="component-container">
<div
class="component"
on:click={() => onComponentChosen(component.name)}>
<div class="name">{splitName(component.name).componentName}</div>
2020-02-19 22:38:21 +01:00
{#if (component.presets || templatesByComponent[component.name]) && component.name === selectedComponent}
<ul class="preset-menu">
2020-02-19 22:38:21 +01:00
{#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>
{#if component.presets || templatesByComponent[component.name]}
2020-02-18 20:24:18 +01:00
<Button
on:click={() => {
2020-02-18 20:24:18 +01:00
selectedComponent = selectedComponent ? null : component.name
}}>
<span
class="open-presets"
class:open={selectedComponent === component.name}>
...
</span>
</Button>
{/if}
</div>
2020-02-03 10:50:30 +01:00
{/each}
2020-02-19 22:38:21 +01:00
{/if}
</div>
</div>
2020-02-20 18:11:41 +01:00
<ConfirmDialog
2020-02-19 22:38:21 +01:00
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>
2020-02-03 10:50:30 +01:00
.root {
display: flex;
flex-direction: column;
2020-02-03 10:50:30 +01:00
}
2020-02-03 10:50:30 +01:00
.library-container {
padding: 0 0 10px 10px;
flex: 1 1 auto;
min-height: 0px;
2020-02-03 10:50:30 +01:00
}
.component-container {
display: flex;
align-items: center;
}
2020-02-03 10:50:30 +01:00
.component {
position: relative;
padding: 0 15px;
cursor: pointer;
border: 1px solid #ebebeb;
border-radius: 2px;
margin: 10px 0;
height: 40px;
box-sizing: border-box;
color: #163057;
display: flex;
align-items: center;
flex: 1;
margin-right: 5px;
2020-02-03 10:50:30 +01:00
}
2020-02-03 10:50:30 +01:00
.component:hover {
background-color: var(--lightslate);
2020-02-03 10:50:30 +01:00
}
2020-02-03 10:50:30 +01:00
.component > .name {
color: #163057;
display: inline-block;
font-size: 12px;
font-weight: bold;
opacity: 0.6;
2020-02-03 10:50:30 +01:00
}
2020-02-03 10:50:30 +01:00
ul {
list-style: none;
display: flex;
padding: 0;
2020-02-03 10:50:30 +01:00
}
.preset-menu {
flex-direction: column;
position: absolute;
top: 25px;
left: 0;
right: 0;
z-index: 1;
background: #fafafa;
padding: 10px;
border-radius: 2px;
color: rgba(22, 48, 87, 0.6);
}
.preset-menu > span {
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
2020-02-19 22:38:21 +01:00
margin-top: 5px;
}
.preset-menu li {
font-size: 14px;
margin-top: 13px;
}
.preset-menu li:hover {
font-weight: bold;
}
2020-02-03 10:50:30 +01:00
li {
margin-right: 20px;
background: none;
border-radius: 5px;
2020-02-03 10:50:30 +01:00
}
2020-02-03 10:50:30 +01:00
li button {
width: 100%;
height: 100%;
background: none;
border: none;
border-radius: 5px;
padding: 12px;
outline: none;
cursor: pointer;
2020-02-03 10:50:30 +01:00
}
2020-02-03 10:50:30 +01:00
.selected {
color: var(--button-text);
2020-02-03 10:50:30 +01:00
background: var(--background-button) !important;
}
2020-02-18 20:24:18 +01:00
.open {
color: rgba(0, 85, 255, 1);
}
2020-02-19 22:38:21 +01:00
.template-instance-label {
margin-left: 20px;
}
</style>