WIP: Changes for property panel
This commit is contained in:
parent
67568cb6f8
commit
06bbc88b3e
7 changed files with 289 additions and 98 deletions
|
@ -0,0 +1,43 @@
|
||||||
|
<script>
|
||||||
|
export let categories = []
|
||||||
|
export let selectedCategory = {}
|
||||||
|
export let onClick = category => {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul class="tabs">
|
||||||
|
{#each categories as category}
|
||||||
|
<li
|
||||||
|
on:click={() => onClick(category)}
|
||||||
|
class:active={selectedCategory === category}>
|
||||||
|
{category.name}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 30px;
|
||||||
|
border-bottom: 1px solid #d8d8d8;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
color: #808192;
|
||||||
|
margin: 0 5px;
|
||||||
|
padding: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border-bottom: solid 3px #0055ff;
|
||||||
|
color: #393c44;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -14,17 +14,33 @@
|
||||||
import LayoutEditor from "./LayoutEditor.svelte"
|
import LayoutEditor from "./LayoutEditor.svelte"
|
||||||
import EventsEditor from "./EventsEditor"
|
import EventsEditor from "./EventsEditor"
|
||||||
|
|
||||||
let current_view = "props"
|
import panelStructure from "./temporaryPanelStructure.js"
|
||||||
let codeEditor
|
import CategoryTab from "./CategoryTab.svelte"
|
||||||
|
import DesignView from "./DesignView.svelte"
|
||||||
|
|
||||||
|
let current_view = "design"
|
||||||
|
let codeEditor
|
||||||
|
let flattenedPanel = flattenComponents(panelStructure.categories)
|
||||||
|
let categories = [
|
||||||
|
{ name: "Design" },
|
||||||
|
{ name: "Settings" },
|
||||||
|
{ name: "Actions" },
|
||||||
|
]
|
||||||
|
let selectedCategory = categories[0]
|
||||||
|
|
||||||
$: component = $store.currentComponentInfo
|
|
||||||
$: originalName = component.name
|
|
||||||
$: name =
|
|
||||||
$store.currentView === "detail"
|
|
||||||
? $store.currentPreviewItem.name
|
|
||||||
: component._component
|
|
||||||
$: description = component.description
|
|
||||||
$: components = $store.components
|
$: components = $store.components
|
||||||
|
$: componentInstance = $store.currentComponentInfo //contains prop values of currently selected component
|
||||||
|
$: componentDefinition = $store.components.find(
|
||||||
|
c => c.name === componentInstance._component
|
||||||
|
)
|
||||||
|
|
||||||
|
$: panelDefinition = flattenedPanel.find(
|
||||||
|
//use for getting controls for each component property
|
||||||
|
c => c._component === componentInstance._component
|
||||||
|
)
|
||||||
|
|
||||||
|
// OLD PROPS =============================================
|
||||||
|
|
||||||
$: screen_props =
|
$: screen_props =
|
||||||
$store.currentFrontEndType === "page"
|
$store.currentFrontEndType === "page"
|
||||||
? getProps($store.currentPreviewItem, ["name", "favicon"])
|
? getProps($store.currentPreviewItem, ["name", "favicon"])
|
||||||
|
@ -33,76 +49,46 @@
|
||||||
const onPropChanged = store.setComponentProp
|
const onPropChanged = store.setComponentProp
|
||||||
const onStyleChanged = store.setComponentStyle
|
const onStyleChanged = store.setComponentStyle
|
||||||
|
|
||||||
|
//May be able to remove some of the nested components in PropsView, PropsControl and StateBindingControl tree
|
||||||
|
|
||||||
|
function walkProps(component, action) {
|
||||||
|
action(component)
|
||||||
|
if (component.children) {
|
||||||
|
for (let child of component.children) {
|
||||||
|
walkProps(child, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flattenComponents(props) {
|
||||||
|
const components = []
|
||||||
|
props.forEach(comp =>
|
||||||
|
walkProps(comp, c => {
|
||||||
|
if ("_component" in c) {
|
||||||
|
components.push(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return components
|
||||||
|
}
|
||||||
|
|
||||||
function getProps(obj, keys) {
|
function getProps(obj, keys) {
|
||||||
return keys.map((k, i) => [k, obj[k], obj.props._id + i])
|
return keys.map((k, i) => [k, obj[k], obj.props._id + i])
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<ul>
|
|
||||||
<li>
|
<CategoryTab
|
||||||
<button
|
onClick={category => (selectedCategory = category)}
|
||||||
class:selected={current_view === 'props'}
|
{categories}
|
||||||
on:click={() => (current_view = 'props')}>
|
{selectedCategory} />
|
||||||
<PaintIcon />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
class:selected={current_view === 'layout'}
|
|
||||||
on:click={() => (current_view = 'layout')}>
|
|
||||||
<LayoutIcon />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{#if !component._component.startsWith('##')}
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
class:selected={current_view === 'code'}
|
|
||||||
on:click={() => codeEditor && codeEditor.show()}>
|
|
||||||
{#if component._code && component._code.trim().length > 0}
|
|
||||||
<div class="button-indicator">
|
|
||||||
<CircleIndicator />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<TerminalIcon />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
class:selected={current_view === 'events'}
|
|
||||||
on:click={() => (current_view = 'events')}>
|
|
||||||
<EventsIcon />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
<div class="component-props-container">
|
<div class="component-props-container">
|
||||||
|
{#if current_view === 'design'}
|
||||||
{#if current_view === 'props'}
|
<!-- <PropsView {component} {components} {onPropChanged} /> -->
|
||||||
{#if $store.currentView === 'detail'}
|
<DesignView {panelDefinition} {componentInstance} {componentDefinition} />
|
||||||
{#each screen_props as [k, v, id] (id)}
|
|
||||||
<div class="detail-prop" for={k}>
|
|
||||||
<label>{k}:</label>
|
|
||||||
<input
|
|
||||||
id={k}
|
|
||||||
value={v}
|
|
||||||
on:input={({ target }) => store.setMetadataProp(k, target.value)} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<PropsView {component} {components} {onPropChanged} />
|
|
||||||
{:else}
|
|
||||||
<PropsView {component} {components} {onPropChanged} />
|
|
||||||
{/if}
|
{/if}
|
||||||
{:else if current_view === 'layout'}
|
|
||||||
<LayoutEditor {onStyleChanged} {component} />
|
|
||||||
{:else if current_view === 'events'}
|
|
||||||
<EventsEditor {component} {components} {onPropChanged} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<CodeEditor
|
|
||||||
bind:this={codeEditor}
|
|
||||||
code={component._code}
|
|
||||||
onCodeChanged={store.setComponentCode} />
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -146,7 +132,6 @@
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
||||||
import components from "./temporaryPanelStructure.js"
|
import components from "./temporaryPanelStructure.js"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import CategoryTab from "./CategoryTab.svelte"
|
||||||
import {
|
import {
|
||||||
find,
|
find,
|
||||||
sortBy,
|
sortBy,
|
||||||
|
@ -85,15 +86,12 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<ul class="tabs">
|
|
||||||
{#each categories as category}
|
<CategoryTab
|
||||||
<li
|
onClick={category => (selectedCategory = category)}
|
||||||
on:click={() => (selectedCategory = category)}
|
{selectedCategory}
|
||||||
class:active={selectedCategory === category}>
|
{categories} />
|
||||||
{category.name}
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<Tab
|
<Tab
|
||||||
list={selectedCategory}
|
list={selectedCategory}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import PropertyGroup from "./PropertyGroup.svelte"
|
||||||
|
export let panelDefinition = {}
|
||||||
|
export let componentInstance = {}
|
||||||
|
export let componentDefinition = {}
|
||||||
|
const getProperties = prop => panelDefinition.props[prop]
|
||||||
|
|
||||||
|
$: props = !!panelDefinition.props && Object.keys(panelDefinition.props)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each props as prop}
|
||||||
|
<PropertyGroup
|
||||||
|
title={prop}
|
||||||
|
content={getProperties(prop)}
|
||||||
|
{componentDefinition}
|
||||||
|
{componentInstance} />
|
||||||
|
{/each}
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script>
|
||||||
|
export let title = ""
|
||||||
|
export let content = {}
|
||||||
|
export let componentInstance = {}
|
||||||
|
export let componentDefinition = {}
|
||||||
|
|
||||||
|
let show = false
|
||||||
|
|
||||||
|
const propExistsOnComponentDef = prop => prop in componentDefinition.props
|
||||||
|
|
||||||
|
const capitalize = title => title[0].toUpperCase() + title.slice(1)
|
||||||
|
|
||||||
|
$: propertyKeys = Object.keys(content)
|
||||||
|
$: icon = show ? "ri-arrow-down-s-fill" : "ri-arrow-right-s-fill"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="property-group-container" on:click={() => (show = !show)}>
|
||||||
|
<div class="property-group-title">
|
||||||
|
<div class="icon">
|
||||||
|
<i class={icon} />
|
||||||
|
</div>
|
||||||
|
<div class="title">{capitalize(title)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="property-panel" class:show>
|
||||||
|
<ul>
|
||||||
|
{#each propertyKeys as key}
|
||||||
|
<!-- {#if propExistsOnComponentDef(key)} -->
|
||||||
|
<li>{content[key].displayName || capitalize(key)}</li>
|
||||||
|
<!-- {/if} -->
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.property-group-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
background: #fbfbfb;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-group-title {
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 0 0 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
flex: 0 0 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-panel {
|
||||||
|
height: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
/* transition: height 2s ease-in-out; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.show {
|
||||||
|
overflow: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: all strings types are really inputs and could be typed as such
|
||||||
|
TODO: Options types need option items
|
||||||
|
TODO: Allow for default values for all properties
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const layout = {
|
||||||
|
flexDirection: { displayName: "Direction", type: "string" },
|
||||||
|
justifyContent: { displayName: "Justify", type: "string" },
|
||||||
|
alignItems: { displayName: "Align", type: "string" },
|
||||||
|
flexWrap: { displayName: "Wrap", type: "options" },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spacing = {
|
||||||
|
padding: { type: "string" },
|
||||||
|
margin: { type: "string" },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const size = {
|
||||||
|
width: { type: "string" },
|
||||||
|
height: { type: "string" },
|
||||||
|
minWidth: { displayName: "Min W", type: "string" },
|
||||||
|
minHeight: { displayName: "Min H", type: "string" },
|
||||||
|
maxWidth: { displayName: "Max W", type: "string" },
|
||||||
|
maxHeight: { displayName: "Max H", type: "string" },
|
||||||
|
overflow: { type: "string" }, //custom
|
||||||
|
}
|
||||||
|
|
||||||
|
export const position = {
|
||||||
|
position: { type: "options" },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const typography = {
|
||||||
|
font: { type: "options" },
|
||||||
|
weight: { type: "options" },
|
||||||
|
size: { type: "string" },
|
||||||
|
lineHeight: { displayName: "Line H", type: "string" },
|
||||||
|
color: { type: "colour" },
|
||||||
|
align: { type: "string" }, //custom
|
||||||
|
transform: { type: "string" }, //custom
|
||||||
|
style: { type: "string" }, //custom
|
||||||
|
}
|
||||||
|
|
||||||
|
export const background = {
|
||||||
|
backgroundColor: { displayName: "Background Color", type: "colour" },
|
||||||
|
image: { type: "string" }, //custom
|
||||||
|
}
|
||||||
|
|
||||||
|
export const border = {
|
||||||
|
radius: { type: "string" },
|
||||||
|
width: { type: "string" }, //custom
|
||||||
|
color: { type: "colour" },
|
||||||
|
style: { type: "options" },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const effects = {
|
||||||
|
opacity: "string",
|
||||||
|
rotate: "string",
|
||||||
|
shadow: "string",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transitions = {
|
||||||
|
property: { type: "options" },
|
||||||
|
duration: { type: "string" },
|
||||||
|
ease: { type: "options" },
|
||||||
|
}
|
||||||
|
|
||||||
|
export function excludeProps(props, propsToExclude) {
|
||||||
|
const modifiedProps = {}
|
||||||
|
for (const prop in props) {
|
||||||
|
if (!propsToExclude.includes(prop)) {
|
||||||
|
modifiedProps[prop] = props[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modifiedProps
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { layout, background } from "./propertyCategories.js"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
categories: [
|
categories: [
|
||||||
{
|
{
|
||||||
|
@ -10,7 +12,8 @@ export default {
|
||||||
description: 'This component contains things within itself',
|
description: 'This component contains things within itself',
|
||||||
icon: 'ri-layout-row-fill',
|
icon: 'ri-layout-row-fill',
|
||||||
commonProps: {},
|
commonProps: {},
|
||||||
children: []
|
children: [],
|
||||||
|
props: { layout, background },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Text',
|
name: 'Text',
|
||||||
|
@ -24,12 +27,8 @@ export default {
|
||||||
description: "A component for displaying heading text",
|
description: "A component for displaying heading text",
|
||||||
icon: "ri-heading",
|
icon: "ri-heading",
|
||||||
props: {
|
props: {
|
||||||
type: {
|
layout,
|
||||||
type: "options",
|
background,
|
||||||
options: ["h1", "h2", "h3", "h4", "h5", "h6"],
|
|
||||||
default: "h1",
|
|
||||||
},
|
|
||||||
text: "string",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -82,15 +81,15 @@ export default {
|
||||||
name: 'Button',
|
name: 'Button',
|
||||||
description: 'A basic html button that is ready for styling',
|
description: 'A basic html button that is ready for styling',
|
||||||
icon: 'ri-radio-button-fill',
|
icon: 'ri-radio-button-fill',
|
||||||
commonProps: {},
|
children: [],
|
||||||
children: []
|
props: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_component: "@budibase/standard-components/icon",
|
_component: "@budibase/standard-components/icon",
|
||||||
name: 'Icon',
|
name: 'Icon',
|
||||||
description: 'A basic component for displaying icons',
|
description: 'A basic component for displaying icons',
|
||||||
icon: 'ri-sun-fill',
|
icon: 'ri-sun-fill',
|
||||||
commonProps: {},
|
props: {},
|
||||||
children: []
|
children: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -98,7 +97,7 @@ export default {
|
||||||
name: 'Link',
|
name: 'Link',
|
||||||
description: 'A basic link component for internal and external links',
|
description: 'A basic link component for internal and external links',
|
||||||
icon: 'ri-link',
|
icon: 'ri-link',
|
||||||
commonProps: {},
|
props: {},
|
||||||
children: []
|
children: []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -112,14 +111,14 @@ export default {
|
||||||
name: 'Card',
|
name: 'Card',
|
||||||
description: 'A basic card component that can contain content and actions.',
|
description: 'A basic card component that can contain content and actions.',
|
||||||
icon: 'ri-layout-bottom-line',
|
icon: 'ri-layout-bottom-line',
|
||||||
commonProps: {},
|
props: {},
|
||||||
children: []
|
children: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
description: 'A component that automatically generates a login screen for your app.',
|
description: 'A component that automatically generates a login screen for your app.',
|
||||||
icon: 'ri-login-box-fill',
|
icon: 'ri-login-box-fill',
|
||||||
commonProps: {},
|
props: {},
|
||||||
children: []
|
children: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -127,7 +126,7 @@ export default {
|
||||||
_component: "@budibase/standard-components/Navigation",
|
_component: "@budibase/standard-components/Navigation",
|
||||||
description: "A component for handling the navigation within your app.",
|
description: "A component for handling the navigation within your app.",
|
||||||
icon: "ri-navigation-fill",
|
icon: "ri-navigation-fill",
|
||||||
commonProps: {},
|
props: {},
|
||||||
children: []
|
children: []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -140,15 +139,15 @@ export default {
|
||||||
name: 'Table',
|
name: 'Table',
|
||||||
description: 'A component that generates a table from your data.',
|
description: 'A component that generates a table from your data.',
|
||||||
icon: 'ri-archive-drawer-fill',
|
icon: 'ri-archive-drawer-fill',
|
||||||
commonProps: {},
|
props: {},
|
||||||
children: []
|
children: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Form',
|
name: 'Form',
|
||||||
description: 'A component that generates a form from your data.',
|
description: 'A component that generates a form from your data.',
|
||||||
icon: 'ri-file-edit-fill',
|
icon: 'ri-file-edit-fill',
|
||||||
commonProps: {},
|
props: {},
|
||||||
component: "@budibase/materialdesign-components/Form",
|
_component: "@budibase/materialdesign-components/Form",
|
||||||
template: {
|
template: {
|
||||||
component: "@budibase/materialdesign-components/Form",
|
component: "@budibase/materialdesign-components/Form",
|
||||||
description: "Form for saving a record",
|
description: "Form for saving a record",
|
||||||
|
|
Loading…
Reference in a new issue