forked from tsai/budibase
committed by
GitHub
6 changed files with 289 additions and 253 deletions
@ -1,264 +1,54 @@ |
|||||
<script> |
<script> |
||||
import { splitName } from "./pagesParsing/splitRootComponentName.js" |
// This should be fetched from somewhere in the future, rather than be hardcoded. |
||||
import { store } from "builderStore" |
import components from "./temporaryPanelStructure.js" |
||||
import { |
import Tab from "./ComponentTab/Tab.svelte" |
||||
find, |
|
||||
sortBy, |
|
||||
groupBy, |
|
||||
values, |
|
||||
filter, |
|
||||
map, |
|
||||
uniqBy, |
|
||||
flatten, |
|
||||
} from "lodash/fp" |
|
||||
import { ImageIcon, InputIcon, LayoutIcon } from "components/common/Icons/" |
|
||||
import Select from "components/common/Select.svelte" |
|
||||
import Button from "components/common/PlusButton.svelte" |
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|
||||
import { |
|
||||
getRecordNodes, |
|
||||
getIndexNodes, |
|
||||
getIndexSchema, |
|
||||
pipe, |
|
||||
} from "components/common/core" |
|
||||
import ComponentItem from "./ComponentItem.svelte" |
|
||||
import panelStructure from "./temporaryPanelStructure.json" |
|
||||
export let toggleTab |
|
||||
|
|
||||
let componentLibraries = [] |
const categories = components.categories |
||||
let current_view = "text" |
let selectedCategory = categories[0] |
||||
let selectedComponent = null |
|
||||
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).componentName, template: t })), |
|
||||
uniqBy(t => t.name), |
|
||||
] |
|
||||
) |
|
||||
|
|
||||
const addRootComponent = (component, allComponents) => { |
|
||||
const { libName } = splitName(component.name) |
|
||||
let group = find(r => r.libName === libName)(allComponents) |
|
||||
|
|
||||
if (!group) { |
|
||||
group = { |
|
||||
libName, |
|
||||
components: [], |
|
||||
} |
|
||||
|
|
||||
allComponents.push(group) |
|
||||
} |
|
||||
|
|
||||
group.components.push(component) |
|
||||
} |
|
||||
|
|
||||
const onComponentChosen = component => { |
|
||||
if (component.template) { |
|
||||
onTemplateChosen(component.template) |
|
||||
} else { |
|
||||
store.addChildComponent(component.name) |
|
||||
toggleTab() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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) |
|
||||
toggleTab() |
|
||||
} |
|
||||
|
|
||||
function generate_components_list(components) { |
|
||||
return ($store.currentFrontEndType === "page" |
|
||||
? $store.builtins.concat(components) |
|
||||
: components |
|
||||
).concat(standaloneTemplates) |
|
||||
} |
|
||||
|
|
||||
$: { |
|
||||
const newComponentLibraries = [] |
|
||||
|
|
||||
for (let comp of sortBy(["name"])($store.components)) { |
|
||||
addRootComponent(comp, newComponentLibraries) |
|
||||
} |
|
||||
|
|
||||
componentLibraries = newComponentLibraries |
|
||||
if (!selectedLib) selectedLib = newComponentLibraries[0].libName |
|
||||
} |
|
||||
|
|
||||
$: componentLibrary = componentLibraries.find(l => l.libName === selectedLib) |
|
||||
|
|
||||
$: componentItems = |
|
||||
panelStructure.categories[ |
|
||||
panelStructure.categories.findIndex(i => i.name === "Basic") |
|
||||
].components |
|
||||
</script> |
</script> |
||||
|
|
||||
<div class="root"> |
<div class="root"> |
||||
|
<ul class="tabs"> |
||||
{#each componentItems as item} |
{#each categories as category} |
||||
<ComponentItem |
<li |
||||
name={item.component} |
on:click={() => (selectedCategory = category)} |
||||
description={item.description} |
class:active={selectedCategory === category}> |
||||
icon={item.icon} /> |
{category.name} |
||||
{/each} |
</li> |
||||
|
{/each} |
||||
|
</ul> |
||||
|
<div class="panel"> |
||||
|
<Tab components={selectedCategory.components} /> |
||||
|
</div> |
||||
</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 { |
.tabs { |
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
} |
|
||||
|
|
||||
.library-container { |
|
||||
padding: 0 0 10px 0; |
|
||||
flex: 1 1 auto; |
|
||||
min-height: 0px; |
|
||||
margin-top: 20px; |
|
||||
} |
|
||||
|
|
||||
.component-container { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
.component { |
|
||||
position: relative; |
|
||||
padding: 0 15px; |
|
||||
cursor: pointer; |
|
||||
border: 1px solid #d8d8d8; |
|
||||
border-radius: 2px; |
|
||||
margin: 5px 0; |
|
||||
height: 40px; |
|
||||
box-sizing: border-box; |
|
||||
color: #000333; |
|
||||
display: flex; |
display: flex; |
||||
align-items: center; |
justify-content: center; |
||||
flex: 1; |
|
||||
margin-right: 5px; |
|
||||
} |
|
||||
|
|
||||
.component:hover { |
|
||||
background-color: var(--lightslate); |
|
||||
} |
|
||||
|
|
||||
.component > .name { |
|
||||
color: #000333; |
|
||||
display: inline-block; |
|
||||
font-size: 13px; |
|
||||
opacity: 0.8; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
list-style: none; |
list-style: none; |
||||
display: flex; |
margin: 0 auto; |
||||
padding: 0; |
padding: 0 30px; |
||||
} |
border-bottom: 1px solid #d8d8d8; |
||||
|
|
||||
.preset-menu { |
|
||||
flex-direction: column; |
|
||||
position: absolute; |
|
||||
top: 25px; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
z-index: 1; |
|
||||
background: #fafafa; |
|
||||
padding: 10px; |
|
||||
border-radius: 2px; |
|
||||
color: var(--secondary80); |
|
||||
} |
|
||||
|
|
||||
.preset-menu > span { |
|
||||
font-size: 13px; |
|
||||
text-transform: uppercase; |
|
||||
margin-top: 5px; |
|
||||
} |
|
||||
|
|
||||
.preset-menu li { |
|
||||
font-size: 14px; |
font-size: 14px; |
||||
margin-top: 13px; |
font-weight: 500; |
||||
} |
letter-spacing: 0.14px; |
||||
|
|
||||
.preset-menu li:hover { |
|
||||
font-weight: bold; |
|
||||
} |
} |
||||
|
|
||||
li { |
li { |
||||
margin-right: 20px; |
color: #808192; |
||||
background: none; |
margin: 0 5px; |
||||
border-radius: 5px; |
padding: 0 8px; |
||||
} |
|
||||
|
|
||||
/* li button { |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
background: none; |
|
||||
border: none; |
|
||||
border-radius: 5px; |
|
||||
padding: 13px; |
|
||||
outline: none; |
|
||||
cursor: pointer; |
cursor: pointer; |
||||
} */ |
} |
||||
|
|
||||
/* .selected { |
|
||||
color: var(--button-text); |
|
||||
background: var(--background-button) !important; |
|
||||
} */ |
|
||||
|
|
||||
.open { |
.panel { |
||||
color: rgba(0, 85, 255, 1); |
padding: 20px; |
||||
} |
} |
||||
|
|
||||
.template-instance-label { |
.active { |
||||
margin-left: 20px; |
border-bottom: solid 3px #0055ff; |
||||
|
color: #393c44; |
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -0,0 +1,10 @@ |
|||||
|
<script> |
||||
|
import Item from "./Item.svelte" |
||||
|
export let components |
||||
|
|
||||
|
console.log("Components: ", components) |
||||
|
</script> |
||||
|
|
||||
|
{#each components as { icon, name, description }} |
||||
|
<Item {icon} {name} {description} /> |
||||
|
{/each} |
||||
@ -0,0 +1,244 @@ |
|||||
|
export default { |
||||
|
categories: [ |
||||
|
{ |
||||
|
name: 'Basic', |
||||
|
components: [ |
||||
|
{ |
||||
|
name: 'Container', |
||||
|
description: 'This component contains things within itself', |
||||
|
icon: 'ri-layout-row-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Text', |
||||
|
description: 'This is a simple text component', |
||||
|
icon: 'ri-t-box-fill', |
||||
|
commonProps: {}, |
||||
|
type: [ |
||||
|
{ |
||||
|
_component: '@budibase/standard-components/header', |
||||
|
name: 'Headline', |
||||
|
icon: 'headline', |
||||
|
props: { |
||||
|
type: { |
||||
|
type: 'options', |
||||
|
options: [ |
||||
|
'h1', |
||||
|
'h2' |
||||
|
], |
||||
|
'default': 'h1' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
_component: '@budibase/standard-components/text', |
||||
|
name: 'Paragraph', |
||||
|
icon: 'paragraph', |
||||
|
props: {} |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Button', |
||||
|
description: 'A basic html button that is ready for styling', |
||||
|
icon: 'ri-radio-button-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Icon', |
||||
|
description: 'A basic component for displaying icons', |
||||
|
icon: 'ri-sun-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Avatar', |
||||
|
description: 'A basic component for rendering an avatar', |
||||
|
icon: 'ri-user-smile-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Link', |
||||
|
description: 'A basic link component for internal and external links', |
||||
|
icon: 'ri-link', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Form', |
||||
|
components: [ |
||||
|
{ |
||||
|
name: 'Button', |
||||
|
description: 'A basic html button that is ready for styling', |
||||
|
icon: 'ri-radio-button-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Icon', |
||||
|
description: 'A basic component for displaying icons', |
||||
|
icon: 'ri-sun-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Avatar', |
||||
|
description: 'A basic component for rendering an avatar', |
||||
|
icon: 'ri-user-smile-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Link', |
||||
|
description: 'A basic link component for internal and external links', |
||||
|
icon: 'ri-link', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Blocks', |
||||
|
components: [ |
||||
|
{ |
||||
|
name: 'Container', |
||||
|
description: 'This component contains things within itself', |
||||
|
icon: 'ri-layout-row-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Text', |
||||
|
description: 'This is a simple text component', |
||||
|
icon: 'ri-t-box-fill', |
||||
|
commonProps: {}, |
||||
|
type: [ |
||||
|
{ |
||||
|
_component: '@budibase/standard-components/header', |
||||
|
name: 'Headline', |
||||
|
icon: 'headline', |
||||
|
props: { |
||||
|
type: { |
||||
|
type: 'options', |
||||
|
options: [ |
||||
|
'h1', |
||||
|
'h2' |
||||
|
], |
||||
|
'default': 'h1' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
_component: '@budibase/standard-components/text', |
||||
|
name: 'Paragraph', |
||||
|
icon: 'paragraph', |
||||
|
props: {} |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Button', |
||||
|
description: 'A basic html button that is ready for styling', |
||||
|
icon: 'ri-radio-button-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Icon', |
||||
|
description: 'A basic component for displaying icons', |
||||
|
icon: 'ri-sun-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Avatar', |
||||
|
description: 'A basic component for rendering an avatar', |
||||
|
icon: 'ri-user-smile-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Link', |
||||
|
description: 'A basic link component for internal and external links', |
||||
|
icon: 'ri-link', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Data', |
||||
|
components: [ |
||||
|
{ |
||||
|
name: 'Container', |
||||
|
description: 'This component contains things within itself', |
||||
|
icon: 'ri-layout-row-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Text', |
||||
|
description: 'This is a simple text component', |
||||
|
icon: 'ri-t-box-fill', |
||||
|
commonProps: {}, |
||||
|
type: [ |
||||
|
{ |
||||
|
_component: '@budibase/standard-components/header', |
||||
|
name: 'Headline', |
||||
|
icon: 'headline', |
||||
|
props: { |
||||
|
type: { |
||||
|
type: 'options', |
||||
|
options: [ |
||||
|
'h1', |
||||
|
'h2' |
||||
|
], |
||||
|
'default': 'h1' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
_component: '@budibase/standard-components/text', |
||||
|
name: 'Paragraph', |
||||
|
icon: 'paragraph', |
||||
|
props: {} |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Button', |
||||
|
description: 'A basic html button that is ready for styling', |
||||
|
icon: 'ri-radio-button-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Icon', |
||||
|
description: 'A basic component for displaying icons', |
||||
|
icon: 'ri-sun-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Avatar', |
||||
|
description: 'A basic component for rendering an avatar', |
||||
|
icon: 'ri-user-smile-fill', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'Link', |
||||
|
description: 'A basic link component for internal and external links', |
||||
|
icon: 'ri-link', |
||||
|
commonProps: {}, |
||||
|
type: [] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
] |
||||
|
} |
||||
Loading…
Reference in new issue