1
0
Fork 0
mirror of synced 2024-07-10 08:46:05 +12:00

Merge pull request #115 from mjashanks/master

New Components, Bug fixes... including PR #114
This commit is contained in:
Michael Shanks 2020-02-20 17:30:30 +00:00 committed by GitHub
commit d57c0ebd6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 729 additions and 259 deletions

View file

@ -55,7 +55,7 @@
flex: 0 0 auto; flex: 0 0 auto;
height: 48px; height: 48px;
background: white; background: white;
padding: 0px 15px; padding: 0px 15px 0 1.8rem;
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
@ -104,7 +104,7 @@
cursor: pointer; cursor: pointer;
outline: none; outline: none;
height: 40px; height: 40px;
padding: 8px 10px; padding: 8px 10px 8px 0;
} }
.home-logo:hover { .home-logo:hover {

View file

@ -4,22 +4,29 @@ import { pipe } from "../common/core"
const self = n => n const self = n => n
const join_with = delimiter => a => a.join(delimiter) const join_with = delimiter => a => a.join(delimiter)
const empty_string_to_unset = s => (s.length ? s : "0") const empty_string_to_unset = s => (s.length ? s : "0")
const add_suffix = suffix => s => s + suffix const add_suffix_if_number = suffix => s => {
try {
if (isNaN(s) || isNaN(parseFloat(s))) return s
} catch (_) {
return s
}
return s + suffix
}
export const make_margin = values => export const make_margin = values =>
pipe(values, [ pipe(values, [
map(empty_string_to_unset), map(empty_string_to_unset),
map(add_suffix("px")), map(add_suffix_if_number("px")),
join_with(" "), join_with(" "),
]) ])
const css_map = { const css_map = {
templaterows: { templaterows: {
name: "grid-template-columns", name: "grid-template-rows",
generate: self, generate: self,
}, },
templatecolumns: { templatecolumns: {
name: "grid-template-rows", name: "grid-template-columns",
generate: self, generate: self,
}, },
gridarea: { gridarea: {
@ -58,6 +65,14 @@ const css_map = {
name: "z-index", name: "z-index",
generate: self, generate: self,
}, },
height: {
name: "height",
generate: self,
},
width: {
name: "width",
generate: self,
},
} }
export const generate_rule = ([name, values]) => export const generate_rule = ([name, values]) =>
@ -97,10 +112,11 @@ const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}`
export const generate_screen_css = component_array => { export const generate_screen_css = component_array => {
let styles = "" let styles = ""
let emptyStyles = { layout: {}, position: {} }
for (let i = 0; i < component_array.length; i += 1) { for (let i = 0; i < component_array.length; i += 1) {
const { _styles, _id, _children } = component_array[i] const { _styles, _id, _children } = component_array[i]
const { layout, position } = generate_css(_styles) const { layout, position } = generate_css(_styles || emptyStyles)
styles += apply_class(_id, "pos", position) + "\n" styles += apply_class(_id, "pos", position) + "\n"
styles += apply_class(_id, "lay", layout) + "\n" styles += apply_class(_id, "lay", layout) + "\n"

View file

@ -721,9 +721,27 @@ const getContainerComponent = components =>
*/ */
const addChildComponent = store => (componentToAdd, presetName) => { const addChildComponent = store => (componentToAdd, presetName) => {
store.update(state => { store.update(state => {
function findSlot(component_array) {
for (let i = 0; i < component_array.length; i += 1) {
if (component_array[i]._component === "##builtin/screenslot")
return true
if (component_array[i]._children) findSlot(component_array[i])
}
return false
}
if (
componentToAdd.startsWith("##") &&
findSlot(state.pages[state.currentPageName].props._children)
) {
return state
}
const component = componentToAdd.startsWith("##") const component = componentToAdd.startsWith("##")
? getBuiltin(componentToAdd) ? getBuiltin(componentToAdd)
: state.components.find(({ name }) => name === componentToAdd) : state.components.find(({ name }) => name === componentToAdd)
const presetProps = presetName ? component.presets[presetName] : {} const presetProps = presetName ? component.presets[presetName] : {}
const newComponent = createProps(component, presetProps) const newComponent = createProps(component, presetProps)
@ -731,7 +749,9 @@ const addChildComponent = store => (componentToAdd, presetName) => {
newComponent.props newComponent.props
) )
_savePage(state) state.currentFrontEndType === "page"
? _savePage(state)
: _saveScreenApi(state.currentPreviewItem, state)
return state return state
}) })
@ -749,7 +769,7 @@ const addTemplatedComponent = store => props => {
props props
) )
_savePage(state) _saveCurrentPreviewItem(state)
return state return state
}) })
@ -792,9 +812,7 @@ const setComponentStyle = store => (type, name, value) => {
]) ])
// save without messing with the store // save without messing with the store
s.currentFrontEndType === "page" _saveCurrentPreviewItem(s)
? _savePage(s)
: _saveScreenApi(s.currentPreviewItem, s)
return s return s
}) })
} }
@ -843,9 +861,7 @@ const deleteComponent = store => component => {
parent._children = parent._children.filter(c => c !== component) parent._children = parent._children.filter(c => c !== component)
} }
s.currentFrontEndType === "page" _saveCurrentPreviewItem(s)
? _savePage(s)
: _saveScreenApi(s.currentPreviewItem, s)
return s return s
}) })
@ -864,9 +880,7 @@ const moveUpComponent = store => component => {
parent._children = newChildren parent._children = newChildren
} }
s.currentComponentInfo = component s.currentComponentInfo = component
s.currentFrontEndType === "page" _saveCurrentPreviewItem(s)
? _savePage(s)
: _saveScreenApi(s.currentPreviewItem, s)
return s return s
}) })
@ -885,9 +899,7 @@ const moveDownComponent = store => component => {
parent._children = newChildren parent._children = newChildren
} }
s.currentComponentInfo = component s.currentComponentInfo = component
s.currentFrontEndType === "page" _saveCurrentPreviewItem(s)
? _savePage(s)
: _saveScreenApi(s.currentPreviewItem, s)
return s return s
}) })
@ -902,9 +914,7 @@ const copyComponent = store => component => {
}) })
parent._children = [...parent._children, copiedComponent] parent._children = [...parent._children, copiedComponent]
s.curren s.curren
s.currentFrontEndType === "page" _saveCurrentPreviewItem(s)
? _savePage(s)
: _saveScreenApi(s.currentPreviewItem, s)
s.currentComponentInfo = copiedComponent s.currentComponentInfo = copiedComponent
return s return s
}) })
@ -913,7 +923,7 @@ const copyComponent = store => component => {
const getParent = (rootProps, child) => { const getParent = (rootProps, child) => {
let parent let parent
walkProps(rootProps, (p, breakWalk) => { walkProps(rootProps, (p, breakWalk) => {
if (p._children.includes(child)) { if (p._children && p._children.includes(child)) {
parent = p parent = p
breakWalk() breakWalk()
} }
@ -934,3 +944,8 @@ const walkProps = (props, action, cancelToken = null) => {
} }
} }
} }
const _saveCurrentPreviewItem = s =>
s.currentFrontEndType === "page"
? _savePage(s)
: _saveScreenApi(s.currentPreviewItem, s)

View file

@ -0,0 +1,8 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="100%"
height="100%">
<path fill="none" d="M0 0h24v24H0z" />
<path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" />
</svg>

After

Width:  |  Height:  |  Size: 213 B

View file

@ -1,10 +1,10 @@
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="100%"
height="24"> height="100%">
<path fill="none" d="M0 0h24v24H0z" /> <path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" fill="currentColor"
d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z" /> d="M12 15l-4.243-4.243 1.415-1.414L12 12.172l2.828-2.829 1.415 1.414z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 254 B

View file

@ -0,0 +1,10 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="100%"
height="100%">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z" />
</svg>

After

Width:  |  Height:  |  Size: 254 B

View file

@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="100%"
height="100%">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M14 10v4h-4v-4h4zm2 0h5v4h-5v-4zm-2 11h-4v-5h4v5zm2 0v-5h5v4a1 1 0 0 1-1
1h-4zM14 3v5h-4V3h4zm2 0h4a1 1 0 0 1 1 1v4h-5V3zm-8 7v4H3v-4h5zm0 11H4a1 1 0
0 1-1-1v-4h5v5zM8 3v5H3V4a1 1 0 0 1 1-1h4z" />
</svg>

After

Width:  |  Height:  |  Size: 388 B

View file

@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="100%"
height="100%">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M5 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm14 0a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0
14a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM5 22a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM9
4h6v2H9V4zm0 14h6v2H9v-2zM4 9h2v6H4V9zm14 0h2v6h-2V9z" />
</svg>

After

Width:  |  Height:  |  Size: 387 B

View file

@ -11,3 +11,7 @@ export { default as XCircleIcon } from "./XCircle.svelte"
export { default as ChevronUpIcon } from "./ChevronUp.svelte" export { default as ChevronUpIcon } from "./ChevronUp.svelte"
export { default as ChevronDownIcon } from "./ChevronDown.svelte" export { default as ChevronDownIcon } from "./ChevronDown.svelte"
export { default as CopyIcon } from "./Copy.svelte" export { default as CopyIcon } from "./Copy.svelte"
export { default as CheckIcon } from "./Check.svelte"
export { default as GridIcon } from "./Grid.svelte"
export { default as ShapeIcon } from "./Shape.svelte"
export { default as AddIcon } from "./Add.svelte"

View file

@ -60,6 +60,12 @@
store.addTemplatedComponent(instance.props) store.addTemplatedComponent(instance.props)
} }
function generate_components_list(components) {
return $store.currentFrontEndType === "page"
? $store.builtins.concat(components)
: components
}
$: { $: {
const newComponentLibraries = [] const newComponentLibraries = []
@ -110,7 +116,7 @@
</ul> </ul>
{#if componentLibrary} {#if componentLibrary}
{#each $store.builtins.concat(componentLibrary.components) as component} {#each generate_components_list(componentLibrary.components) as component}
<div class="component-container"> <div class="component-container">
<div <div
class="component" class="component"
@ -139,7 +145,7 @@
</ul> </ul>
{/if} {/if}
</div> </div>
{#if component.presets} {#if component.presets || templatesByComponent[component.name]}
<Button <Button
on:click={() => { on:click={() => {
selectedComponent = selectedComponent ? null : component.name selectedComponent = selectedComponent ? null : component.name

View file

@ -5,7 +5,7 @@
import ConfirmDialog from "../common/ConfirmDialog.svelte" import ConfirmDialog from "../common/ConfirmDialog.svelte"
import { pipe } from "../common/core" import { pipe } from "../common/core"
import { store } from "../builderStore" import { store } from "../builderStore"
import { ArrowDownIcon } from "../common/Icons/" import { ArrowDownIcon, ShapeIcon } from "../common/Icons/"
export let screens = [] export let screens = []
@ -25,9 +25,12 @@
] ]
) )
const lastPartOfName = c => const lastPartOfName = c => {
c && if (!c) return ""
last(c.name ? c.name.split("/") : c._component.split("/")) const name = c.name ? c.name : c._component ? c._component : c
return last(name.split("/"))
}
const isComponentSelected = (current, comp) => current === comp const isComponentSelected = (current, comp) => current === comp
@ -47,7 +50,6 @@
componentToDelete = component componentToDelete = component
confirmDeleteDialog.show() confirmDeleteDialog.show()
} }
</script> </script>
<div class="root"> <div class="root">
@ -55,17 +57,21 @@
{#each _screens as screen} {#each _screens as screen}
<div <div
class="hierarchy-item component" class="hierarchy-item component"
class:selected={$store.currentPreviewItem.name === screen.title} class:selected={$store.currentComponentInfo._id === screen.component.props._id}
on:click|stopPropagation={() => store.setCurrentScreen(screen.title)}> on:click|stopPropagation={() => store.setCurrentScreen(screen.title)}>
<span <span
class="icon" class="icon"
style="transform: rotate({$store.currentPreviewItem.name === screen.title ? 0 : -90}deg);"> class:rotate={$store.currentPreviewItem.name !== screen.title}>
{#if screen.component.props._children.length} {#if screen.component.props._children.length}
<ArrowDownIcon /> <ArrowDownIcon />
{/if} {/if}
</span> </span>
<span class="icon">
<ShapeIcon />
</span>
<span class="title">{screen.title}</span> <span class="title">{screen.title}</span>
</div> </div>
@ -86,28 +92,29 @@
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
title="Confirm Delete" title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete._component)}' component`} body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component`}
okText="Delete Component" okText="Delete Component"
onOk={() => store.deleteComponent(componentToDelete)} /> onOk={() => store.deleteComponent(componentToDelete)} />
<style> <style>
.root { .root {
font-weight: 500; font-weight: 400;
font-size: 0.9rem; font-size: 0.8rem;
color: #828fa5; color: #333;
} }
.hierarchy-item { .hierarchy-item {
cursor: pointer; cursor: pointer;
padding: 11px 7px; padding: 0 7px 0 3px;
height: 35px;
margin: 5px 0; margin: 5px 0;
border-radius: 5px; border-radius: 0 5px 5px 0;
display: flex; display: flex;
align-items: center; align-items: center;
font-weight: 500;
} }
.hierarchy-item:hover { .hierarchy-item:hover {
/* color: var(--secondary); */
background: #fafafa; background: #fafafa;
} }
@ -118,12 +125,27 @@
.title { .title {
margin-left: 10px; margin-left: 10px;
margin-top: 2px;
} }
.icon { .icon {
display: inline-block; display: inline-block;
transition: 0.2s; transition: 0.2s;
width: 24px; width: 20px;
height: 24px; margin-top: 2px;
color: #333;
}
.icon:nth-of-type(2) {
width: 14px;
margin: 0 0 0 5px;
}
:global(svg) {
transition: 0.2s;
}
.rotate :global(svg) {
transform: rotate(-90deg);
} }
</style> </style>

View file

@ -1,7 +1,12 @@
<script> <script>
import { last } from "lodash/fp" import { last } from "lodash/fp"
import { pipe } from "../common/core" import { pipe } from "../common/core"
import { XCircleIcon, ChevronUpIcon, ChevronDownIcon, CopyIcon } from "../common/Icons" import {
XCircleIcon,
ChevronUpIcon,
ChevronDownIcon,
CopyIcon,
} from "../common/Icons"
export let components = [] export let components = []
export let currentComponent export let currentComponent
@ -12,9 +17,8 @@
export let onMoveDownComponent export let onMoveDownComponent
export let onCopyComponent export let onCopyComponent
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1) const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
const get_name = s => !s ? "" : last(s.split("/")) const get_name = s => (!s ? "" : last(s.split("/")))
const get_capitalised_name = name => const get_capitalised_name = name =>
pipe( pipe(
@ -28,7 +32,6 @@
return onMoveDownComponent(c) return onMoveDownComponent(c)
} }
} }
</script> </script>
<ul> <ul>
@ -37,15 +40,16 @@
<div <div
class="item" class="item"
class:selected={currentComponent === component} class:selected={currentComponent === component}
style="padding-left: {level * 20 + 67}px"> style="padding-left: {level * 20 + 15}px">
<div>{get_capitalised_name(component._component)}</div> <div>{get_capitalised_name(component._component)}</div>
<div class="reorder-buttons"> <div class="reorder-buttons">
{#if index > 0} {#if index > 0}
<button on:click|stopPropagation={() => onMoveUpComponent(component)}> <button
on:click|stopPropagation={() => onMoveUpComponent(component)}>
<ChevronUpIcon /> <ChevronUpIcon />
</button> </button>
{/if} {/if}
{#if index < (components.length - 1)} {#if index < components.length - 1}
<button on:click|stopPropagation={moveDownComponent(component)}> <button on:click|stopPropagation={moveDownComponent(component)}>
<ChevronDownIcon /> <ChevronDownIcon />
</button> </button>
@ -54,8 +58,7 @@
<button on:click|stopPropagation={() => onCopyComponent(component)}> <button on:click|stopPropagation={() => onCopyComponent(component)}>
<CopyIcon /> <CopyIcon />
</button> </button>
<button <button on:click|stopPropagation={() => onDeleteComponent(component)}>
on:click|stopPropagation={() => onDeleteComponent(component)}>
<XCircleIcon /> <XCircleIcon />
</button> </button>
</div> </div>
@ -82,18 +85,16 @@
margin: 0; margin: 0;
} }
li {
background: #fafafa;
}
.item { .item {
display: grid; display: grid;
grid-template-columns: 1fr auto auto auto; grid-template-columns: 1fr auto auto auto;
padding: 0px 5px 0px 67px; padding: 0px 5px 0px 15px;
margin: auto 0px; margin: auto 0px;
border-radius: 3px; border-radius: 3px;
height: 43px; height: 35px;
align-items: center; align-items: center;
font-size: 0.8rem;
font-weight: normal;
} }
.item button { .item button {
@ -122,6 +123,7 @@
.selected { .selected {
color: var(--button-text); color: var(--button-text);
background: var(--background-button) !important; background: var(--background-button) !important;
font-weight: 500;
} }
.reorder-buttons { .reorder-buttons {
@ -135,5 +137,4 @@
height: 17px; height: 17px;
width: 30px; width: 30px;
} }
</style> </style>

View file

@ -35,6 +35,11 @@
padding: ["Padding", tbrl, "small"], padding: ["Padding", tbrl, "small"],
} }
$: size = {
height: ["Height", single],
width: ["Width", single],
}
$: zindex = { $: zindex = {
zindex: ["Z-Index", single], zindex: ["Z-Index", single],
} }
@ -82,6 +87,22 @@
onStyleChanged={_value => onStyleChanged('position', key, _value)} onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)} values={layout[key] || newValue(meta.length)}
{meta} {meta}
{size}
type="text" />
</div>
{/each}
</div>
<h4>Size</h4>
<div class="layout-layer">
{#each Object.entries(size) as [key, [name, meta, size]] (component._id + key)}
<div class="grid">
<h5>{name}:</h5>
<InputGroup
onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
type="text"
{meta}
{size} /> {size} />
</div> </div>
{/each} {/each}

View file

@ -0,0 +1,146 @@
<script>
// import { tick } from "svelte"
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
import { last, sortBy, map, trimCharsStart, trimChars, join } from "lodash/fp"
import ConfirmDialog from "../common/ConfirmDialog.svelte"
import { pipe } from "../common/core"
import { store } from "../builderStore"
import { ArrowDownIcon, GridIcon } from "../common/Icons/"
export let layout
let confirmDeleteDialog
let componentToDelete = ""
const joinPath = join("/")
const normalizedName = name =>
pipe(
name,
[
trimCharsStart("./"),
trimCharsStart("~/"),
trimCharsStart("../"),
trimChars(" "),
]
)
const lastPartOfName = c =>
c && last(c.name ? c.name.split("/") : c._component.split("/"))
const isComponentSelected = (current, comp) => current === comp
const isFolderSelected = (current, folder) => isInSubfolder(current, folder)
$: _layout = pipe(
layout,
[c => ({ component: c, title: lastPartOfName(c) })]
)
const isScreenSelected = component =>
component.component &&
$store.currentPreviewItem &&
component.component.name === $store.currentPreviewItem.name
const confirmDeleteComponent = async component => {
console.log(component)
componentToDelete = component
// await tick()
confirmDeleteDialog.show()
}
</script>
<div class="root">
<div
class="hierarchy-item component"
class:selected={$store.currentComponentInfo._id === _layout.component.props._id}
on:click|stopPropagation={() => store.setScreenType('page')}>
<span
class="icon"
class:rotate={$store.currentPreviewItem.name !== _layout.title}>
{#if _layout.component.props._children.length}
<ArrowDownIcon />
{/if}
</span>
<span class="icon">
<GridIcon />
</span>
<span class="title">Master Layout</span>
</div>
{#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children}
<ComponentsHierarchyChildren
components={_layout.component.props._children}
currentComponent={$store.currentComponentInfo}
onSelect={store.selectComponent}
onDeleteComponent={confirmDeleteComponent}
onMoveUpComponent={store.moveUpComponent}
onMoveDownComponent={store.moveDownComponent}
onCopyComponent={store.copyComponent} />
{/if}
</div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component`}
okText="Delete Component"
onOk={() => store.deleteComponent(componentToDelete)} />
<style>
.root {
font-weight: 400;
font-size: 0.8rem;
color: #333;
}
.hierarchy-item {
cursor: pointer;
padding: 0 7px 0 3px;
height: 35px;
margin: 5px 0;
border-radius: 0 5px 5px 0;
display: flex;
align-items: center;
font-weight: 500;
}
.hierarchy-item:hover {
background: #fafafa;
}
.selected {
color: var(--button-text);
background: var(--background-button) !important;
}
.title {
margin-left: 10px;
margin-top: 2px;
}
.icon {
display: inline-block;
transition: 0.2s;
width: 20px;
margin-top: 2px;
color: #333;
}
.icon:nth-of-type(2) {
width: 14px;
margin: 0 0 0 5px;
}
:global(svg) {
transition: 0.2s;
}
.rotate :global(svg) {
transform: rotate(-90deg);
}
</style>

View file

@ -1,6 +1,7 @@
<script> <script>
import { store } from "../builderStore" import { store } from "../builderStore"
import getIcon from "../common/icon" import getIcon from "../common/icon"
import { CheckIcon } from "../common/Icons"
const getPage = (s, name) => { const getPage = (s, name) => {
const props = s.pages[name] const props = s.pages[name]
@ -22,19 +23,23 @@
</script> </script>
<div class="root"> <div class="root">
<select <ul>
id="page"
name="select"
on:change={({ target }) => store.setCurrentPage(target.value)}>
{#each pages as { title, id }} {#each pages as { title, id }}
<option value={id}>Page: {title}</option> <li>
{/each} <span class="icon">
{#if id === $store.currentPageName}
</select> <CheckIcon />
<span class="arrow"> {/if}
{@html getIcon('chevron-down', '24')}
</span> </span>
<button
class:active={id === $store.currentPageName}
on:click={() => store.setCurrentPage(id)}>
{title}
</button>
</li>
{/each}
</ul>
</div> </div>
<style> <style>
@ -44,37 +49,36 @@
color: var(--secondary50); color: var(--secondary50);
font-weight: bold; font-weight: bold;
position: relative; position: relative;
padding-left: 1.8rem;
} }
select { ul {
display: block;
font-size: 16px;
font-family: sans-serif;
font-weight: 700;
color: #444;
line-height: 1.3;
padding: 1em 2.6em 0.9em 1.4em;
width: 100%;
max-width: 100%;
box-sizing: border-box;
margin: 0; margin: 0;
border: none; padding: 0;
border-radius: 0.5em; list-style: none;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
background-color: #fafafa;
} }
.arrow { li {
position: absolute; margin: 0.5rem 0;
right: 10px; }
top: 0;
bottom: 0; button {
margin: auto; margin: 0 0 0 6px;
width: 30px; padding: 0;
height: 30px; border: none;
pointer-events: none; font-family: Roboto;
color: var(--primary100); font-size: 0.8rem;
outline: none;
cursor: pointer;
}
.active {
font-weight: 500;
}
.icon {
display: inline-block;
width: 14px;
color: #333;
} }
</style> </style>

View file

@ -1,6 +1,7 @@
<script> <script>
import ComponentsHierarchy from "./ComponentsHierarchy.svelte" import ComponentsHierarchy from "./ComponentsHierarchy.svelte"
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte" import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
import MasterLayout from "./MasterLayout.svelte"
import PagesList from "./PagesList.svelte" import PagesList from "./PagesList.svelte"
import { store } from "../builderStore" import { store } from "../builderStore"
import IconButton from "../common/IconButton.svelte" import IconButton from "../common/IconButton.svelte"
@ -12,6 +13,7 @@
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte" import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
import ConfirmDialog from "../common/ConfirmDialog.svelte" import ConfirmDialog from "../common/ConfirmDialog.svelte"
import { last } from "lodash/fp" import { last } from "lodash/fp"
import { AddIcon } from "../common/Icons"
let newComponentPicker let newComponentPicker
let confirmDeleteDialog let confirmDeleteDialog
@ -31,8 +33,7 @@
confirmDeleteDialog.show() confirmDeleteDialog.show()
} }
const lastPartOfName = c => c ? last(c.split("/")) : "" const lastPartOfName = c => (c ? last(c.split("/")) : "")
</script> </script>
<div class="root"> <div class="root">
@ -40,63 +41,32 @@
<div class="ui-nav"> <div class="ui-nav">
<div class="pages-list-container"> <div class="pages-list-container">
<div class="nav-group-header"> <div class="nav-header">
<span class="navigator-title">Navigator</span> <span class="navigator-title">Navigator</span>
<span class="components-nav-header">Pages</span>
</div> </div>
<div class="nav-items-container"> <div class="nav-items-container">
<PagesList /> <PagesList />
</div> </div>
</div> </div>
<div class="components-list-container"> <div class="border-line" />
<div class="nav-group-header">
<span
on:click={() => store.setScreenType('page')}
class="components-nav-header"
class:active={$store.currentFrontEndType === 'page'}>
Page
</span>
</div>
<div class="nav-items-container">
{#if $store.currentFrontEndType === 'page'}
<ComponentsHierarchyChildren
components={$store.currentPreviewItem.props._children}
currentComponent={$store.currentComponentInfo}
onDeleteComponent={confirmDeleteComponent}
onSelect={store.selectComponent}
onMoveUpComponent={store.moveUpComponent}
onMoveDownComponent={store.moveDownComponent}
onCopyComponent={store.copyComponent}
level={-2} />
{/if}
</div>
</div>
<div class="components-list-container"> <div class="components-list-container">
<div class="nav-group-header"> <div class="nav-group-header">
<span class="components-nav-header" style="margin-top: 0;">
<span
on:click={() => store.setScreenType('screen')}
class="components-nav-header"
class:active={$store.currentFrontEndType === 'screen'}>
Screens Screens
</span> </span>
{#if $store.currentFrontEndType === 'screen'}
<div> <div>
<button on:click={newComponent}>+</button> <button on:click={newComponent}>
<AddIcon />
</button>
</div> </div>
{/if}
</div> </div>
<div class="nav-items-container"> <div class="nav-items-container">
{#if $store.currentFrontEndType === 'screen'} <MasterLayout layout={$store.pages[$store.currentPageName]} />
<ComponentsHierarchy screens={$store.screens} /> <ComponentsHierarchy screens={$store.screens} />
{/if}
</div> </div>
</div> </div>
@ -120,7 +90,7 @@
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
title="Confirm Delete" title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete._component)}' component`} body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component`}
okText="Delete Component" okText="Delete Component"
onOk={() => store.deleteComponent(componentToDelete)} /> onOk={() => store.deleteComponent(componentToDelete)} />
@ -130,19 +100,12 @@
outline: none; outline: none;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
background: var(--background-button); width: 20px;
width: 1.8rem;
height: 1.8rem;
padding-bottom: 10px; padding-bottom: 10px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 0;
font-size: 1.2rem;
font-weight: 700;
color: var(--button-text);
} }
.root { .root {
@ -157,7 +120,7 @@
grid-column: 1; grid-column: 1;
background-color: var(--secondary5); background-color: var(--secondary5);
height: 100%; height: 100%;
padding: 0 1.5rem 0rem 1.5rem; padding: 0 1.5rem 0rem 0;
} }
.preview-pane { .preview-pane {
@ -179,6 +142,8 @@
font-size: 0.75rem; font-size: 0.75rem;
color: #999; color: #999;
text-transform: uppercase; text-transform: uppercase;
margin-top: 1rem;
font-weight: 500;
} }
.nav-group-header { .nav-group-header {
@ -186,13 +151,20 @@
padding-left: 1rem; padding-left: 1rem;
} }
.nav-header {
display: flex;
flex-direction: column;
margin-top: 1.5rem;
padding: 0 1.8rem;
}
.nav-items-container { .nav-items-container {
padding: 1rem 0rem 0rem 0rem; padding: 1rem 0rem 0rem 0rem;
} }
.nav-group-header { .nav-group-header {
display: flex; display: flex;
padding: 2rem 0 0 0; padding: 1.5rem 0 0 1.8rem;
font-size: 0.9rem; font-size: 0.9rem;
font-weight: bold; font-weight: bold;
justify-content: space-between; justify-content: space-between;
@ -228,9 +200,12 @@
text-transform: uppercase; text-transform: uppercase;
font-weight: 400; font-weight: 400;
color: #999; color: #999;
font-size: 0.9rem;
} }
.active { .border-line {
color: #333; border-bottom: 1px solid #ddd;
margin-top: 1.5rem;
width: calc(100% + 1.5rem);
} }
</style> </style>

View file

@ -77,6 +77,10 @@ export const makePropsSafe = (componentDefinition, props) => {
} }
} }
if (!props._styles) {
props._styles = { layout: {}, position: {} }
}
return props return props
} }

View file

@ -1,5 +1,11 @@
{ {
"_lib": "./dist/index.js", "_lib": "./dist/index.js",
"_templates": {
"indexDatatable": {
"description": "Datatable based on an Index",
"component": "Datatable"
}
},
"Body1": { "Body1": {
"name": "Body1", "name": "Body1",
"description": "Sets the font properties as Roboto Body 1", "description": "Sets the font properties as Roboto Body 1",
@ -61,6 +67,26 @@
"props": {}, "props": {},
"tags": [] "tags": []
}, },
"DatatableHead": {
"name": "DatatableHead",
"description": "Material Design <thead>.",
"props": {}
},
"DatatableCell": {
"name": "DatatableCell",
"description": "Material Design <td>.",
"props": {}
},
"DatatableBody": {
"name": "DatatableBody",
"description": "Material Design <tbody>.",
"props": {}
},
"DatatableRow": {
"name": "DatatableRow",
"description": "Material Design <tr>.",
"props": {}
},
"H1": { "H1": {
"name": "H1", "name": "H1",
"description": "Sets the font properties as Roboto Headline1", "description": "Sets the font properties as Roboto Headline1",

View file

@ -13,6 +13,7 @@
}, },
"devDependencies": { "devDependencies": {
"@budibase/client": "^0.0.16", "@budibase/client": "^0.0.16",
"@budibase/standard-components": "^0.0.16",
"@material/button": "^4.0.0", "@material/button": "^4.0.0",
"@nx-js/compiler-util": "^2.0.0", "@nx-js/compiler-util": "^2.0.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",

View file

@ -6,14 +6,27 @@
import { Button } from "../Button" import { Button } from "../Button"
import ClassBuilder from "../ClassBuilder.js" import ClassBuilder from "../ClassBuilder.js"
export let _bb
const cb = new ClassBuilder("data-table") const cb = new ClassBuilder("data-table")
setContext("BBMD:data-table:cb", cb) setContext("BBMD:data-table:cb", cb)
let datatable = null let datatable = null
let instance = null let instance = null
let tableElement
let initialied = false
$: {
if(tableElement && datatable && !initialied) {
const children = _bb.attachChildren(tableElement)
if(children.length > 0) {
instance = new MDCDataTable(datatable)
initialied = true
}
}
}
onMount(() => { onMount(() => {
if (!!datatable) instance = new MDCDataTable(datatable)
return () => { return () => {
!!instance && instance.destroy() !!instance && instance.destroy()
instance = null instance = null
@ -22,46 +35,7 @@
</script> </script>
<div bind:this={datatable} class={cb.build()}> <div bind:this={datatable} class={cb.build()}>
<table class={cb.elem`table`} aria-label="Material Design Datatable"> <table class={cb.elem`table`} aria-label="Material Design Datatable" bind:this={tableElement}>
<thead>
<Row isHeader>
<Cell isHeader>Id</Cell>
<Cell isHeader>First Name</Cell>
<Cell isHeader>Second Name</Cell>
<Cell isHeader>Gender</Cell>
<Cell isHeader>Address</Cell>
<Cell isHeader>Actions</Cell>
</Row>
</thead>
<tbody class={cb.elem`content`}>
<Row>
<Cell>123456</Cell>
<Cell>Conor</Cell>
<Cell>McKeown</Cell>
<Cell>Male</Cell>
<Cell>1 Cool Street</Cell>
<Cell>
<Button
text="Select"
variant="unelevated"
colour="secondary"
size="small" />
</Cell>
</Row>
<Row>
<Cell>789101</Cell>
<Cell>Joe</Cell>
<Cell>Bloggs</Cell>
<Cell>Male</Cell>
<Cell>2 Cool Street</Cell>
<Cell>
<Button
text="Select"
variant="unelevated"
colour="secondary"
size="small" />
</Cell>
</Row>
</tbody>
</table> </table>
</div> </div>

View file

@ -0,0 +1,15 @@
<script>
import { getContext } from "svelte"
import ClassBuilder from "../ClassBuilder.js"
export let _bb
const cb = getContext("BBMD:data-table:cb")
let tbody
$: tbody && _bb.attachChildren(tbody)
</script>
<tbody bind:this={tbody} class={cb.elem`content`}></tbody>

View file

@ -3,6 +3,7 @@
export let isHeader = false export let isHeader = false
export let numeric = false export let numeric = false
export let _bb
const cb = getContext("BBMD:data-table:cb") const cb = getContext("BBMD:data-table:cb")
@ -10,14 +11,18 @@
let modifiers = { numeric } let modifiers = { numeric }
let props = { modifiers } let props = { modifiers }
let cellClass = cb.build({ elementName, props }) let cellClass = cb.build({ elementName, props })
let element
$: element && _bb.attachChildren(element)
</script> </script>
{#if isHeader} {#if isHeader}
<th class={cellClass} role="columnheader" scope="col"> <th class={cellClass} role="columnheader" scope="col" bind:this={element}>
<slot /> <slot />
</th> </th>
{:else} {:else}
<td class={cellClass}> <td class={cellClass} bind:this={element}>
<slot /> <slot />
</td> </td>
{/if} {/if}

View file

@ -0,0 +1,11 @@
<script>
export let _bb
let thead
$: thead && _bb.attachChildren(thead)
</script>
<thead bind:this={thead} class=className></thead>

View file

@ -3,6 +3,9 @@
export let onSelect = () => {}; export let onSelect = () => {};
export let isHeader = false; export let isHeader = false;
export let _bb
let row = null; let row = null;
let selected = false; let selected = false;
@ -14,6 +17,7 @@
$: modifiers = { selected }; $: modifiers = { selected };
$: props = { modifiers }; $: props = { modifiers };
$: rowClass = cb.build({ elementName, props }); $: rowClass = cb.build({ elementName, props });
$: row && _bb.attachChildren(row)
function rowSelected() { function rowSelected() {
selected = !selected; selected = !selected;

View file

@ -1,2 +1,6 @@
import "./_style.scss"; import "./_style.scss"
export { default as Datatable } from "./Datatable.svelte"; export { default as Datatable } from "./Datatable.svelte"
export { default as DatatableCell } from "./DatatableCell.svelte"
export { default as DatatableHead } from "./DatatableHead.svelte"
export { default as DatatableBody } from "./DatatableBody.svelte"
export { default as DatatableRow } from "./DatatableRow.svelte"

View file

@ -0,0 +1,69 @@
export default ({ indexes, helpers }) =>
indexes.map(i => ({
name: `Table based on index: ${i.name} `,
props: tableProps(i, helpers.indexSchema(i)),
}))
const tableProps = (index, indexSchema) => ({
_component: "@budibase/materialdesign-components/Datatable",
_children: [
{
_component: "@budibase/materialdesign-components/DatatableHead",
_children: [
{
_component: "@budibase/materialdesign-components/DatatableRow",
isHeader: true,
_children: columnHeaders(indexSchema),
},
],
},
{
_component: "@budibase/materialdesign-components/DatatableBody",
_children: [
{
_code: rowCode(index),
_component: "@budibase/materialdesign-components/DatatableRow",
_children: dataCells(index, indexSchema),
},
],
},
],
})
const columnHeaders = indexSchema =>
indexSchema.map(col => ({
_component: "@budibase/materialdesign-components/DatatableCell",
isHeader: true,
_children: [
{
_component: "@budibase/standard-components/text",
type: "none",
text: col.name,
},
],
}))
const dataCells = (index, indexSchema) =>
indexSchema.map(col => ({
_component: "@budibase/materialdesign-components/DatatableCell",
_children: [
{
_component: "@budibase/standard-components/text",
type: "none",
text: {
"##bbstate": `${dataItem(index)}.${col.name}`,
"##bbstatefallback": "",
"##bbsource": "context",
},
},
],
}))
const dataItem = index => `${index.name}_item`
const dataCollection = index => `store.${index.name}`
const rowCode = index =>
`
if (!${dataCollection(index)}) return
for (let ${dataItem(index)} of ${dataCollection(index)})
render( { ${dataItem(index)}) }`

View file

@ -12,6 +12,7 @@
Radiobutton, Radiobutton,
Radiobuttongroup, Radiobuttongroup,
Datatable, Datatable,
CustomersIndexTable,
} = props } = props
let currentComponent let currentComponent
@ -32,6 +33,7 @@
Radiobutton, Radiobutton,
Radiobuttongroup, Radiobuttongroup,
Datatable, Datatable,
CustomersIndexTable
], ],
}, },
} }

View file

@ -2,23 +2,24 @@ import { createApp } from "@budibase/client/src/createApp"
import components from "./testComponents" import components from "./testComponents"
import packageJson from "../../package.json" import packageJson from "../../package.json"
import { rootComponent } from "./rootComponent" import { rootComponent } from "./rootComponent"
import * as standardcomponents from "@budibase/standard-components/src/index"
export default async props => { export default async props => {
delete components._lib delete components._lib
const componentLibraries = {} const componentLibraries = {}
componentLibraries[packageJson.name] = components componentLibraries[packageJson.name] = components
componentLibraries["testcomponents"] = { componentLibraries["testcomponents"] = {
rootComponent: rootComponent(window) rootComponent: rootComponent(window),
} }
componentLibraries["@budibase/standard-components"] = standardcomponents
const appDef = { hierarchy: {}, actions: {} } const appDef = { hierarchy: {}, actions: {} }
const user = { name: "yeo", permissions: [] } const user = { name: "yeo", permissions: [] }
const { initialisePage } = createApp( const { initialisePage } = createApp(
window.document,
componentLibraries, componentLibraries,
{ appRootPath: "" }, { appRootPath: "" },
appDef, appDef,
user, user,
{}, {}
[]
) )
return initialisePage return initialisePage
} }

View file

@ -1,3 +1,24 @@
import indexDatatable from "../Templates/indexDatatable"
const templateOptions = {
indexes: [
{
name: "customers",
},
],
helpers: {
indexSchema: index => {
const field = name => ({ name })
if (index.name === "customers")
return [
field("id"),
field("surname"),
field("forname"),
field("address"),
]
},
},
}
export const props = { export const props = {
H1: { H1: {
@ -80,4 +101,6 @@ export const props = {
_component: "@budibase/materialdesign-components/Datatable", _component: "@budibase/materialdesign-components/Datatable",
_children: [], _children: [],
}, },
CustomersIndexTable: indexDatatable(templateOptions)[0].props,
} }

View file

@ -1,25 +1,3 @@
import { import * as components from "@BBMD"
H1,
Overline,
Button,
Icon,
Textfield,
Checkbox,
Checkboxgroup,
Radiobutton,
Radiobuttongroup,
Datatable,
} from "@BBMD"
export default { export default components
H1,
Overline,
Button,
Icon,
Textfield,
Checkbox,
Checkboxgroup,
Radiobutton,
Radiobuttongroup,
Datatable,
}

View file

@ -1,4 +1,4 @@
import "@material/theme/mdc-theme.scss"; import "@material/theme/mdc-theme.scss"
export { Button } from "./Button" export { Button } from "./Button"
export { default as Icon } from "./Common/Icon.svelte" export { default as Icon } from "./Common/Icon.svelte"
@ -7,5 +7,11 @@ export * from "./Typography"
export { Checkbox, Checkboxgroup } from "./Checkbox" export { Checkbox, Checkboxgroup } from "./Checkbox"
export { Radiobutton, Radiobuttongroup } from "./Radiobutton" export { Radiobutton, Radiobuttongroup } from "./Radiobutton"
export { default as Label } from "./Common/Label.svelte" export { default as Label } from "./Common/Label.svelte"
export { Datatable } from "./Datatable" export {
Datatable,
DatatableHead,
DatatableBody,
DatatableCell,
DatatableRow,
} from "./Datatable"
export { default as indexDatatable } from "./Templates/indexDatatable"

View file

@ -137,6 +137,24 @@
}, },
"tags": ["div", "container"] "tags": ["div", "container"]
}, },
"link": {
"description": "an HTML anchor <a> tag",
"props": {
"url": "string",
"openInNewTab": "bool",
"text": "string"
}
},
"image": {
"description": "an HTML <img> tag",
"props": {
"url": "string",
"className": "string",
"description": "string",
"height": "string",
"width": "string"
}
},
"container": { "container": {
"name": "Container", "name": "Container",
"description": "An element that contains and lays out other elements. e.g. <div>, <header> etc", "description": "An element that contains and lays out other elements. e.g. <div>, <header> etc",
@ -178,5 +196,19 @@
} }
}, },
"tags": [] "tags": []
},
"thead": {
"name": "TableHead",
"description": "an HTML <thead> tab",
"props" : {
"className":"string"
}
},
"tbody": {
"name": "TableBody",
"description": "an HTML <tbody> tab",
"props" : {
"className":"string"
}
} }
} }

View file

@ -0,0 +1,19 @@
<script>
import { buildStyle } from "./buildStyle"
export let className = "";
export let url = "";
export let description = ""
export let height
export let width
$: style = buildStyle({height, width})
</script>
<img class={className}
style={style}
src={url}
alt={description} >

View file

@ -0,0 +1,18 @@
<script>
export let url = ""
export let text = ""
export let openInNewTab = false
export let _bb
let anchorElement
$: anchorElement && !text && _bb.attachChildren(anchorElement)
$: target = openInNewTab ? "_blank" : "_self"
</script>
<a href={url} bind:this={anchorElement} target={target}>{text}</a>

View file

@ -0,0 +1,12 @@
<script>
export let className=""
export let _bb
let thead
$: _bb.attachChildren(thead)
</script>
<tbody bind:this={thead} class=className></tbody>

View file

@ -0,0 +1,12 @@
<script>
export let className=""
export let _bb
let thead
$: _bb.attachChildren(thead)
</script>
<thead bind:this={thead} class=className></thead>

View file

@ -29,7 +29,7 @@
</script> </script>
{#if isTag("none")} {#if isTag("none")}
{text} <span>{text}</span>
{:else if isTag("<b>")} {:else if isTag("<b>")}
<b class={className} {style}>{text}</b> <b class={className} {style}>{text}</b>
{:else if isTag("<strong>")} {:else if isTag("<strong>")}

View file

@ -7,3 +7,5 @@ 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" export { default as saveRecordButton } from "./Templates/saveRecordButton"
export { default as link } from "./Link.svelte"
export { default as image } from "./Image.svelte"