WIP: Changes for property panel

This commit is contained in:
Conor_Mack 2020-05-04 16:07:04 +01:00
parent e5e100bcb1
commit 7184918a82
7 changed files with 289 additions and 98 deletions

View File

@ -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>

View File

@ -14,17 +14,33 @@
import LayoutEditor from "./LayoutEditor.svelte"
import EventsEditor from "./EventsEditor"
let current_view = "props"
let codeEditor
import panelStructure from "./temporaryPanelStructure.js"
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
$: 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 =
$store.currentFrontEndType === "page"
? getProps($store.currentPreviewItem, ["name", "favicon"])
@ -33,77 +49,47 @@
const onPropChanged = store.setComponentProp
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) {
return keys.map((k, i) => [k, obj[k], obj.props._id + i])
}
</script>
<div class="root">
<ul>
<li>
<button
class:selected={current_view === 'props'}
on:click={() => (current_view = 'props')}>
<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>
<CategoryTab
onClick={category => (selectedCategory = category)}
{categories}
{selectedCategory} />
<div class="component-props-container">
{#if current_view === 'props'}
{#if $store.currentView === 'detail'}
{#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}
{:else if current_view === 'layout'}
<LayoutEditor {onStyleChanged} {component} />
{:else if current_view === 'events'}
<EventsEditor {component} {components} {onPropChanged} />
{#if current_view === 'design'}
<!-- <PropsView {component} {components} {onPropChanged} /> -->
<DesignView {panelDefinition} {componentInstance} {componentDefinition} />
{/if}
<CodeEditor
bind:this={codeEditor}
code={component._code}
onCodeChanged={store.setComponentCode} />
</div>
</div>
@ -146,7 +132,6 @@
.root {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
}

View File

@ -2,6 +2,7 @@
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import components from "./temporaryPanelStructure.js"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import CategoryTab from "./CategoryTab.svelte"
import {
find,
sortBy,
@ -85,15 +86,12 @@
</script>
<div class="root">
<ul class="tabs">
{#each categories as category}
<li
on:click={() => (selectedCategory = category)}
class:active={selectedCategory === category}>
{category.name}
</li>
{/each}
</ul>
<CategoryTab
onClick={category => (selectedCategory = category)}
{selectedCategory}
{categories} />
<div class="panel">
<Tab
list={selectedCategory}

View File

@ -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}

View File

@ -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>

View File

@ -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
}

View File

@ -1,3 +1,5 @@
import { layout, background } from "./propertyCategories.js"
export default {
categories: [
{
@ -10,7 +12,8 @@ export default {
description: 'This component contains things within itself',
icon: 'ri-layout-row-fill',
commonProps: {},
children: []
children: [],
props: { layout, background },
},
{
name: 'Text',
@ -24,12 +27,8 @@ export default {
description: "A component for displaying heading text",
icon: "ri-heading",
props: {
type: {
type: "options",
options: ["h1", "h2", "h3", "h4", "h5", "h6"],
default: "h1",
},
text: "string",
layout,
background,
},
},
{
@ -82,15 +81,15 @@ export default {
name: 'Button',
description: 'A basic html button that is ready for styling',
icon: 'ri-radio-button-fill',
commonProps: {},
children: []
children: [],
props: {},
},
{
_component: "@budibase/standard-components/icon",
name: 'Icon',
description: 'A basic component for displaying icons',
icon: 'ri-sun-fill',
commonProps: {},
props: {},
children: []
},
{
@ -98,7 +97,7 @@ export default {
name: 'Link',
description: 'A basic link component for internal and external links',
icon: 'ri-link',
commonProps: {},
props: {},
children: []
}
]
@ -112,14 +111,14 @@ export default {
name: 'Card',
description: 'A basic card component that can contain content and actions.',
icon: 'ri-layout-bottom-line',
commonProps: {},
props: {},
children: []
},
{
name: 'Login',
description: 'A component that automatically generates a login screen for your app.',
icon: 'ri-login-box-fill',
commonProps: {},
props: {},
children: []
},
{
@ -127,7 +126,7 @@ export default {
_component: "@budibase/standard-components/Navigation",
description: "A component for handling the navigation within your app.",
icon: "ri-navigation-fill",
commonProps: {},
props: {},
children: []
}
]
@ -140,15 +139,15 @@ export default {
name: 'Table',
description: 'A component that generates a table from your data.',
icon: 'ri-archive-drawer-fill',
commonProps: {},
props: {},
children: []
},
{
name: 'Form',
description: 'A component that generates a form from your data.',
icon: 'ri-file-edit-fill',
commonProps: {},
component: "@budibase/materialdesign-components/Form",
props: {},
_component: "@budibase/materialdesign-components/Form",
template: {
component: "@budibase/materialdesign-components/Form",
description: "Form for saving a record",