Merge branch 'master' into 98-builtin-slot
This commit is contained in:
commit
83953922d7
|
@ -0,0 +1,17 @@
|
||||||
|
export const insertCodeMetadata = props => {
|
||||||
|
if (props._code && props._code.length > 0) {
|
||||||
|
props._codeMeta = codeMetaData(props._code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props._children || props._children.length === 0) return
|
||||||
|
|
||||||
|
for (let child of props._children) {
|
||||||
|
insertCodeMetadata(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeMetaData = code => {
|
||||||
|
return {
|
||||||
|
dependsOnStore: RegExp(/(store.)/g).test(code),
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import {
|
||||||
} from "./loadComponentLibraries"
|
} from "./loadComponentLibraries"
|
||||||
import { buildCodeForScreens } from "./buildCodeForScreens"
|
import { buildCodeForScreens } from "./buildCodeForScreens"
|
||||||
import { generate_screen_css } from "./generate_css"
|
import { generate_screen_css } from "./generate_css"
|
||||||
|
import { insertCodeMetadata } from "./insertCodeMetadata"
|
||||||
// import { uuid } from "./uuid"
|
// import { uuid } from "./uuid"
|
||||||
|
|
||||||
let appname = ""
|
let appname = ""
|
||||||
|
@ -115,6 +116,7 @@ export const getStore = () => {
|
||||||
store.setComponentStyle = setComponentStyle(store)
|
store.setComponentStyle = setComponentStyle(store)
|
||||||
store.setComponentCode = setComponentCode(store)
|
store.setComponentCode = setComponentCode(store)
|
||||||
store.setScreenType = setScreenType(store)
|
store.setScreenType = setScreenType(store)
|
||||||
|
store.deleteComponent = deleteComponent(store)
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -824,6 +826,8 @@ const setCurrentScreenFunctions = s => {
|
||||||
s.currentPreviewItem === "screen"
|
s.currentPreviewItem === "screen"
|
||||||
? buildCodeForScreens([s.currentPreviewItem])
|
? buildCodeForScreens([s.currentPreviewItem])
|
||||||
: "({});"
|
: "({});"
|
||||||
|
|
||||||
|
insertCodeMetadata(s.currentPreviewItem.props)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setScreenType = store => type => {
|
const setScreenType = store => type => {
|
||||||
|
@ -840,3 +844,39 @@ const setScreenType = store => type => {
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteComponent = store => component => {
|
||||||
|
store.update(s => {
|
||||||
|
let parent
|
||||||
|
walkProps(s.currentPreviewItem.props, (p, breakWalk) => {
|
||||||
|
if (p._children.includes(component)) {
|
||||||
|
parent = p
|
||||||
|
breakWalk()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
parent._children = parent._children.filter(c => c !== component)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.currentFrontEndType === "page"
|
||||||
|
? _savePage(s)
|
||||||
|
: _saveScreenApi(s.currentPreviewItem, s)
|
||||||
|
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const walkProps = (props, action, cancelToken = null) => {
|
||||||
|
cancelToken = cancelToken || { cancelled: false }
|
||||||
|
action(props, () => {
|
||||||
|
cancelToken.cancelled = true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (props._children) {
|
||||||
|
for (let child of props._children) {
|
||||||
|
if (cancelToken.cancelled) return
|
||||||
|
walkProps(child, action, cancelToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<script>
|
||||||
|
import Button from "./Button.svelte"
|
||||||
|
import ButtonGroup from "./ButtonGroup.svelte"
|
||||||
|
import UIkit from "uikit"
|
||||||
|
|
||||||
|
export let title=""
|
||||||
|
export let body=""
|
||||||
|
export let okText = "OK"
|
||||||
|
export let cancelText = "Cancel"
|
||||||
|
export let onOk = ()=> {}
|
||||||
|
export let onCancel = ()=> {}
|
||||||
|
|
||||||
|
export const show = () => {
|
||||||
|
UIkit.modal(theModal).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hide = () => {
|
||||||
|
UIkit.modal(theModal).hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
let theModal;
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
hide()
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = () => {
|
||||||
|
hide()
|
||||||
|
onOk()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="my-id" uk-modal bind:this={theModal}>
|
||||||
|
<div class="uk-modal-dialog">
|
||||||
|
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||||
|
<div class="uk-modal-header">
|
||||||
|
<h2 class="uk-modal-title">{title}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="uk-modal-body">{body}</div>
|
||||||
|
<div class="uk-modal-footer">
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button grouped color="primary" on:click={ok}>
|
||||||
|
{okText}
|
||||||
|
</Button>
|
||||||
|
<Button grouped color="secondary" on:click={cancel}>
|
||||||
|
{cancelText}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="feather feather-x-circle">
|
||||||
|
<circle cx="12" cy="12" r="10"/>
|
||||||
|
<line x1="15" y1="9" x2="9" y2="15"/>
|
||||||
|
<line x1="9" y1="9" x2="15" y2="15"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
svg {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
After Width: | Height: | Size: 454 B |
|
@ -7,3 +7,4 @@ export { default as ArrowDownIcon } from "./ArrowDown.svelte"
|
||||||
export { default as CircleIndicator } from "./CircleIndicator.svelte"
|
export { default as CircleIndicator } from "./CircleIndicator.svelte"
|
||||||
export { default as PencilIcon } from "./Pencil.svelte"
|
export { default as PencilIcon } from "./Pencil.svelte"
|
||||||
export { default as EventsIcon } from "./Events.svelte"
|
export { default as EventsIcon } from "./Events.svelte"
|
||||||
|
export { default as XCircleIcon } from "./XCircle.svelte"
|
||||||
|
|
|
@ -2,13 +2,16 @@
|
||||||
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
|
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
|
||||||
|
|
||||||
import { last, sortBy, map, trimCharsStart, trimChars, join } from "lodash/fp"
|
import { last, sortBy, map, trimCharsStart, trimChars, join } from "lodash/fp"
|
||||||
|
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 } from "../common/Icons/"
|
||||||
|
|
||||||
export let screens = []
|
export let screens = []
|
||||||
|
|
||||||
|
let confirmDeleteDialog
|
||||||
|
let componentToDelete = ""
|
||||||
|
|
||||||
const joinPath = join("/")
|
const joinPath = join("/")
|
||||||
|
|
||||||
const normalizedName = name =>
|
const normalizedName = name =>
|
||||||
|
@ -23,6 +26,7 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
const lastPartOfName = c =>
|
const lastPartOfName = c =>
|
||||||
|
c &&
|
||||||
last(c.name ? c.name.split("/") : c._component.split("/"))
|
last(c.name ? c.name.split("/") : c._component.split("/"))
|
||||||
|
|
||||||
const isComponentSelected = (current, comp) => current === comp
|
const isComponentSelected = (current, comp) => current === comp
|
||||||
|
@ -38,6 +42,12 @@
|
||||||
component.component &&
|
component.component &&
|
||||||
$store.currentPreviewItem &&
|
$store.currentPreviewItem &&
|
||||||
component.component.name === $store.currentPreviewItem.name
|
component.component.name === $store.currentPreviewItem.name
|
||||||
|
|
||||||
|
const confirmDeleteComponent = component => {
|
||||||
|
componentToDelete = component
|
||||||
|
confirmDeleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
@ -63,12 +73,20 @@
|
||||||
<ComponentsHierarchyChildren
|
<ComponentsHierarchyChildren
|
||||||
components={screen.component.props._children}
|
components={screen.component.props._children}
|
||||||
currentComponent={$store.currentComponentInfo}
|
currentComponent={$store.currentComponentInfo}
|
||||||
onSelect={store.selectComponent} />
|
onSelect={store.selectComponent}
|
||||||
|
onDeleteComponent={confirmDeleteComponent}/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={confirmDeleteDialog}
|
||||||
|
title="Confirm Delete"
|
||||||
|
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete._component)}' component`}
|
||||||
|
okText="Delete Component"
|
||||||
|
onOk={() => store.deleteComponent(componentToDelete)}/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
<script>
|
<script>
|
||||||
import { last } from "lodash/fp"
|
import { last } from "lodash/fp"
|
||||||
import { pipe } from "../common/core"
|
import { pipe } from "../common/core"
|
||||||
|
import { XCircleIcon } from "../common/Icons"
|
||||||
|
|
||||||
export let components = []
|
export let components = []
|
||||||
export let currentComponent
|
export let currentComponent
|
||||||
export let onSelect = () => {}
|
export let onSelect = () => {}
|
||||||
export let level = 0
|
export let level = 0
|
||||||
|
export let onDeleteComponent
|
||||||
|
|
||||||
|
|
||||||
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 => last(s.split("/"))
|
const get_name = s => !s ? "" : last(s.split("/"))
|
||||||
|
|
||||||
const get_capitalised_name = name =>
|
const get_capitalised_name = name =>
|
||||||
pipe(
|
pipe(
|
||||||
name,
|
name,
|
||||||
[get_name, capitalise]
|
[get_name, capitalise]
|
||||||
)
|
)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -24,7 +28,12 @@
|
||||||
class="item"
|
class="item"
|
||||||
class:selected={currentComponent === component}
|
class:selected={currentComponent === component}
|
||||||
style="padding-left: {level * 20 + 67}px">
|
style="padding-left: {level * 20 + 67}px">
|
||||||
{get_capitalised_name(component._component)}
|
<span class="item-name">{get_capitalised_name(component._component)}</span>
|
||||||
|
<button
|
||||||
|
class="delete-component"
|
||||||
|
on:click={() => onDeleteComponent(component)}>
|
||||||
|
<XCircleIcon />
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{#if component._children}
|
{#if component._children}
|
||||||
|
@ -32,7 +41,8 @@
|
||||||
components={component._children}
|
components={component._children}
|
||||||
{currentComponent}
|
{currentComponent}
|
||||||
{onSelect}
|
{onSelect}
|
||||||
level={level + 1} />
|
level={level + 1}
|
||||||
|
{onDeleteComponent} />
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -45,14 +55,39 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
display: block;
|
display: flex;
|
||||||
padding: 11px 67px;
|
flex-direction: row;
|
||||||
|
padding: 11px 5px 11px 67px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item > span {
|
||||||
|
width: 1px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item > button {
|
||||||
|
display: none;
|
||||||
|
height: 20px;
|
||||||
|
color: var(--slate)
|
||||||
|
}
|
||||||
|
|
||||||
.item:hover {
|
.item:hover {
|
||||||
|
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.item:hover > button {
|
||||||
|
border-style: none;
|
||||||
|
background: rgba(0,0,0,0);
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover > button:hover {
|
||||||
|
color: var(--button-text);
|
||||||
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
color: var(--button-text);
|
color: var(--button-text);
|
||||||
background: var(--background-button) !important;
|
background: var(--background-button) !important;
|
||||||
|
|
|
@ -11,15 +11,17 @@
|
||||||
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
||||||
|
|
||||||
export let event
|
export let event
|
||||||
export let eventOptions
|
export let eventOptions = []
|
||||||
export let open
|
export let open
|
||||||
export let onClose
|
export let onClose
|
||||||
export let onPropChanged
|
export let onPropChanged
|
||||||
|
|
||||||
let eventType = "onClick"
|
let eventType = ""
|
||||||
let draftEventHandler = { parameters: [] }
|
let draftEventHandler = { parameters: [] }
|
||||||
|
|
||||||
$: eventData = event || { handlers: [] }
|
$: eventData = event || { handlers: [] }
|
||||||
|
$: if (!eventOptions.includes(eventType) && eventOptions.length > 0)
|
||||||
|
eventType = eventOptions[0].name
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
onClose()
|
onClose()
|
||||||
|
@ -74,7 +76,7 @@
|
||||||
<h5>Event Type</h5>
|
<h5>Event Type</h5>
|
||||||
{@html getIcon('info', 20)}
|
{@html getIcon('info', 20)}
|
||||||
</header>
|
</header>
|
||||||
<Select :value={eventType}>
|
<Select bind:value={eventType}>
|
||||||
{#each eventOptions as option}
|
{#each eventOptions as option}
|
||||||
<option value={option.name}>{option.name}</option>
|
<option value={option.name}>{option.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -34,16 +34,10 @@
|
||||||
let selectedEvent = null
|
let selectedEvent = null
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
events = Object.keys(component)
|
const componentDefinition = components.find(c => c.name === component._component)
|
||||||
.filter(key => findType(key) === EVENT_TYPE)
|
events = Object.keys(componentDefinition.props)
|
||||||
.map(key => ({ name: key, handlers: component[key] }))
|
.filter(propName => componentDefinition.props[propName].type === EVENT_TYPE)
|
||||||
}
|
.map(propName => ({ name: propName, handlers: (component[propName] || []) }))
|
||||||
|
|
||||||
function findType(propName) {
|
|
||||||
if (!component._component) return
|
|
||||||
return components.find(({ name }) => name === component._component).props[
|
|
||||||
propName
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const openModal = event => {
|
const openModal = event => {
|
||||||
|
|
|
@ -10,8 +10,12 @@
|
||||||
import SettingsView from "./SettingsView.svelte"
|
import SettingsView from "./SettingsView.svelte"
|
||||||
import PageView from "./PageView.svelte"
|
import PageView from "./PageView.svelte"
|
||||||
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
|
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
|
||||||
|
import ConfirmDialog from "../common/ConfirmDialog.svelte"
|
||||||
|
import { last } from "lodash/fp"
|
||||||
|
|
||||||
let newComponentPicker
|
let newComponentPicker
|
||||||
|
let confirmDeleteDialog
|
||||||
|
let componentToDelete = ""
|
||||||
|
|
||||||
const newComponent = () => {
|
const newComponent = () => {
|
||||||
newComponentPicker.show()
|
newComponentPicker.show()
|
||||||
|
@ -21,6 +25,14 @@
|
||||||
const settings = () => {
|
const settings = () => {
|
||||||
settingsView.show()
|
settingsView.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const confirmDeleteComponent = component => {
|
||||||
|
componentToDelete = component
|
||||||
|
confirmDeleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastPartOfName = c => c ? c.split("/") : ""
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
@ -53,6 +65,7 @@
|
||||||
<ComponentsHierarchyChildren
|
<ComponentsHierarchyChildren
|
||||||
components={$store.currentPreviewItem.props._children}
|
components={$store.currentPreviewItem.props._children}
|
||||||
currentComponent={$store.currentComponentInfo}
|
currentComponent={$store.currentComponentInfo}
|
||||||
|
onDeleteComponent={confirmDeleteComponent}
|
||||||
onSelect={store.selectComponent}
|
onSelect={store.selectComponent}
|
||||||
level={-2} />
|
level={-2} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -101,6 +114,13 @@
|
||||||
<NewComponent bind:this={newComponentPicker} />
|
<NewComponent bind:this={newComponentPicker} />
|
||||||
<SettingsView bind:this={settingsView} />
|
<SettingsView bind:this={settingsView} />
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={confirmDeleteDialog}
|
||||||
|
title="Confirm Delete"
|
||||||
|
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete._component)}' component`}
|
||||||
|
okText="Delete Component"
|
||||||
|
onOk={() => store.deleteComponent(componentToDelete)}/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
button {
|
button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nx-js/compiler-util": "^2.0.0",
|
"@nx-js/compiler-util": "^2.0.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
|
"deep-equal": "^2.0.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lunr": "^2.3.5",
|
"lunr": "^2.3.5",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
|
|
|
@ -1,74 +1,46 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { createCoreApi } from "./core"
|
import { createCoreApi } from "./core"
|
||||||
import { getStateOrValue } from "./state/getState"
|
|
||||||
import { setState, setStateFromBinding } from "./state/setState"
|
|
||||||
import { trimSlash } from "./common/trimSlash"
|
|
||||||
import { isBound } from "./state/isState"
|
|
||||||
import { attachChildren } from "./render/attachChildren"
|
import { attachChildren } from "./render/attachChildren"
|
||||||
import { createTreeNode } from "./render/renderComponent"
|
import { createTreeNode } from "./render/prepareRenderComponent"
|
||||||
import { screenRouter } from "./render/screenRouter"
|
import { screenRouter } from "./render/screenRouter"
|
||||||
|
import { createStateManager } from "./state/stateManager"
|
||||||
|
|
||||||
export const createApp = (
|
export const createApp = (
|
||||||
document,
|
|
||||||
componentLibraries,
|
componentLibraries,
|
||||||
frontendDefinition,
|
frontendDefinition,
|
||||||
backendDefinition,
|
backendDefinition,
|
||||||
user,
|
user,
|
||||||
uiFunctions
|
uiFunctions,
|
||||||
|
window
|
||||||
) => {
|
) => {
|
||||||
const coreApi = createCoreApi(backendDefinition, user)
|
const coreApi = createCoreApi(backendDefinition, user)
|
||||||
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
|
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
|
||||||
backendDefinition.hierarchy
|
backendDefinition.hierarchy
|
||||||
)
|
)
|
||||||
const pageStore = writable({
|
|
||||||
_bbuser: user,
|
|
||||||
})
|
|
||||||
|
|
||||||
const relativeUrl = url =>
|
|
||||||
frontendDefinition.appRootPath
|
|
||||||
? frontendDefinition.appRootPath + "/" + trimSlash(url)
|
|
||||||
: url
|
|
||||||
|
|
||||||
const apiCall = method => (url, body) =>
|
|
||||||
fetch(relativeUrl(url), {
|
|
||||||
method: method,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: body && JSON.stringify(body),
|
|
||||||
})
|
|
||||||
|
|
||||||
const api = {
|
|
||||||
post: apiCall("POST"),
|
|
||||||
get: apiCall("GET"),
|
|
||||||
patch: apiCall("PATCH"),
|
|
||||||
delete: apiCall("DELETE"),
|
|
||||||
}
|
|
||||||
|
|
||||||
const safeCallEvent = (event, context) => {
|
|
||||||
const isFunction = obj =>
|
|
||||||
!!(obj && obj.constructor && obj.call && obj.apply)
|
|
||||||
|
|
||||||
if (isFunction(event)) event(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
let routeTo
|
let routeTo
|
||||||
let currentScreenStore
|
|
||||||
let currentScreenUbsubscribe
|
|
||||||
let currentUrl
|
let currentUrl
|
||||||
|
let screenStateManager
|
||||||
|
|
||||||
const onScreenSlotRendered = screenSlotNode => {
|
const onScreenSlotRendered = screenSlotNode => {
|
||||||
const onScreenSelected = (screen, store, url) => {
|
const onScreenSelected = (screen, store, url) => {
|
||||||
const { getInitialiseParams, unsubscribe } = attachChildrenParams(store)
|
const stateManager = createStateManager({
|
||||||
|
store,
|
||||||
|
coreApi,
|
||||||
|
frontendDefinition,
|
||||||
|
componentLibraries,
|
||||||
|
uiFunctions,
|
||||||
|
onScreenSlotRendered: () => {},
|
||||||
|
})
|
||||||
|
const getAttchChildrenParams = attachChildrenParams(stateManager)
|
||||||
screenSlotNode.props._children = [screen.props]
|
screenSlotNode.props._children = [screen.props]
|
||||||
const initialiseChildParams = getInitialiseParams(screenSlotNode)
|
const initialiseChildParams = getAttchChildrenParams(screenSlotNode)
|
||||||
attachChildren(initialiseChildParams)(screenSlotNode.rootElement, {
|
attachChildren(initialiseChildParams)(screenSlotNode.rootElement, {
|
||||||
hydrate: true,
|
hydrate: true,
|
||||||
force: true,
|
force: true,
|
||||||
})
|
})
|
||||||
if (currentScreenUbsubscribe) currentScreenUbsubscribe()
|
if (screenStateManager) screenStateManager.destroy()
|
||||||
currentScreenUbsubscribe = unsubscribe
|
screenStateManager = stateManager
|
||||||
currentScreenStore = store
|
|
||||||
currentUrl = url
|
currentUrl = url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,46 +48,28 @@ export const createApp = (
|
||||||
routeTo(currentUrl || window.location.pathname)
|
routeTo(currentUrl || window.location.pathname)
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachChildrenParams = store => {
|
const attachChildrenParams = stateManager => {
|
||||||
let currentState = null
|
|
||||||
const unsubscribe = store.subscribe(s => {
|
|
||||||
currentState = s
|
|
||||||
})
|
|
||||||
|
|
||||||
const getInitialiseParams = treeNode => ({
|
const getInitialiseParams = treeNode => ({
|
||||||
bb: getBbClientApi,
|
|
||||||
coreApi,
|
|
||||||
store,
|
|
||||||
document,
|
|
||||||
componentLibraries,
|
componentLibraries,
|
||||||
frontendDefinition,
|
|
||||||
uiFunctions,
|
uiFunctions,
|
||||||
treeNode,
|
treeNode,
|
||||||
onScreenSlotRendered,
|
onScreenSlotRendered,
|
||||||
|
setupState: stateManager.setup,
|
||||||
|
getCurrentState: stateManager.getCurrentState,
|
||||||
})
|
})
|
||||||
|
|
||||||
const getBbClientApi = (treeNode, componentProps) => {
|
return getInitialiseParams
|
||||||
return {
|
|
||||||
attachChildren: attachChildren(getInitialiseParams(treeNode)),
|
|
||||||
context: treeNode.context,
|
|
||||||
props: componentProps,
|
|
||||||
call: safeCallEvent,
|
|
||||||
setStateFromBinding: (binding, value) =>
|
|
||||||
setStateFromBinding(store, binding, value),
|
|
||||||
setState: (path, value) => setState(store, path, value),
|
|
||||||
getStateOrValue: (prop, currentContext) =>
|
|
||||||
getStateOrValue(currentState, prop, currentContext),
|
|
||||||
store,
|
|
||||||
relativeUrl,
|
|
||||||
api,
|
|
||||||
isBound,
|
|
||||||
parent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { getInitialiseParams, unsubscribe }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let rootTreeNode
|
let rootTreeNode
|
||||||
|
const pageStateManager = createStateManager({
|
||||||
|
store: writable({ _bbuser: user }),
|
||||||
|
coreApi,
|
||||||
|
frontendDefinition,
|
||||||
|
componentLibraries,
|
||||||
|
uiFunctions,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
})
|
||||||
|
|
||||||
const initialisePage = (page, target, urlPath) => {
|
const initialisePage = (page, target, urlPath) => {
|
||||||
currentUrl = urlPath
|
currentUrl = urlPath
|
||||||
|
@ -125,7 +79,7 @@ export const createApp = (
|
||||||
_children: [page.props],
|
_children: [page.props],
|
||||||
}
|
}
|
||||||
rootTreeNode.rootElement = target
|
rootTreeNode.rootElement = target
|
||||||
const { getInitialiseParams } = attachChildrenParams(pageStore)
|
const getInitialiseParams = attachChildrenParams(pageStateManager)
|
||||||
const initChildParams = getInitialiseParams(rootTreeNode)
|
const initChildParams = getInitialiseParams(rootTreeNode)
|
||||||
|
|
||||||
attachChildren(initChildParams)(target, {
|
attachChildren(initChildParams)(target, {
|
||||||
|
@ -137,8 +91,8 @@ export const createApp = (
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
initialisePage,
|
initialisePage,
|
||||||
screenStore: () => currentScreenStore,
|
screenStore: () => screenStateManager.store,
|
||||||
pageStore: () => pageStore,
|
pageStore: () => pageStateManager.store,
|
||||||
routeTo: () => routeTo,
|
routeTo: () => routeTo,
|
||||||
rootNode: () => rootTreeNode,
|
rootNode: () => rootTreeNode,
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,12 +43,12 @@ export const loadBudibase = async (opts) => {
|
||||||
componentLibraries[builtinLibName] = builtins(_window)
|
componentLibraries[builtinLibName] = builtins(_window)
|
||||||
|
|
||||||
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
|
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
|
||||||
_window.document,
|
|
||||||
componentLibraries,
|
componentLibraries,
|
||||||
frontendDefinition,
|
frontendDefinition,
|
||||||
backendDefinition,
|
backendDefinition,
|
||||||
user,
|
user,
|
||||||
uiFunctions || {}
|
uiFunctions || {},
|
||||||
|
_window
|
||||||
)
|
)
|
||||||
|
|
||||||
const route = _window.location
|
const route = _window.location
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
import { setupBinding } from "../state/stateBinding"
|
|
||||||
import { split, last } from "lodash/fp"
|
import { split, last } from "lodash/fp"
|
||||||
import { $ } from "../core/common"
|
import { $ } from "../core/common"
|
||||||
import { renderComponent } from "./renderComponent"
|
import { prepareRenderComponent } from "./prepareRenderComponent"
|
||||||
import { isScreenSlot } from "./builtinComponents"
|
import { isScreenSlot } from "./builtinComponents"
|
||||||
|
import deepEqual from "deep-equal"
|
||||||
|
|
||||||
export const attachChildren = initialiseOpts => (htmlElement, options) => {
|
export const attachChildren = initialiseOpts => (htmlElement, options) => {
|
||||||
const {
|
const {
|
||||||
uiFunctions,
|
uiFunctions,
|
||||||
bb,
|
|
||||||
coreApi,
|
|
||||||
store,
|
|
||||||
componentLibraries,
|
componentLibraries,
|
||||||
treeNode,
|
treeNode,
|
||||||
frontendDefinition,
|
|
||||||
onScreenSlotRendered,
|
onScreenSlotRendered,
|
||||||
|
setupState,
|
||||||
|
getCurrentState,
|
||||||
} = initialiseOpts
|
} = initialiseOpts
|
||||||
|
|
||||||
const anchor = options && options.anchor ? options.anchor : null
|
const anchor = options && options.anchor ? options.anchor : null
|
||||||
|
@ -34,50 +32,46 @@ export const attachChildren = initialiseOpts => (htmlElement, options) => {
|
||||||
|
|
||||||
htmlElement.classList.add(`lay-${treeNode.props._id}`)
|
htmlElement.classList.add(`lay-${treeNode.props._id}`)
|
||||||
|
|
||||||
const renderedComponents = []
|
const childNodes = []
|
||||||
for (let childProps of treeNode.props._children) {
|
for (let childProps of treeNode.props._children) {
|
||||||
const { componentName, libName } = splitName(childProps._component)
|
const { componentName, libName } = splitName(childProps._component)
|
||||||
|
|
||||||
if (!componentName || !libName) return
|
if (!componentName || !libName) return
|
||||||
|
|
||||||
const { initialProps, bind } = setupBinding(
|
|
||||||
store,
|
|
||||||
childProps,
|
|
||||||
coreApi,
|
|
||||||
frontendDefinition.appRootPath
|
|
||||||
)
|
|
||||||
|
|
||||||
const componentConstructor = componentLibraries[libName][componentName]
|
const componentConstructor = componentLibraries[libName][componentName]
|
||||||
|
|
||||||
const renderedComponentsThisIteration = renderComponent({
|
const childNodesThisIteration = prepareRenderComponent({
|
||||||
props: childProps,
|
props: childProps,
|
||||||
parentNode: treeNode,
|
parentNode: treeNode,
|
||||||
componentConstructor,
|
componentConstructor,
|
||||||
uiFunctions,
|
uiFunctions,
|
||||||
htmlElement,
|
htmlElement,
|
||||||
anchor,
|
anchor,
|
||||||
initialProps,
|
getCurrentState
|
||||||
bb,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (
|
for (let childNode of childNodesThisIteration) {
|
||||||
onScreenSlotRendered &&
|
childNodes.push(childNode)
|
||||||
isScreenSlot(childProps._component) &&
|
}
|
||||||
renderedComponentsThisIteration.length > 0
|
}
|
||||||
) {
|
|
||||||
|
if (areTreeNodesEqual(treeNode.children, childNodes)) return treeNode.children
|
||||||
|
|
||||||
|
for (let node of childNodes) {
|
||||||
|
const initialProps = setupState(node)
|
||||||
|
node.render(initialProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
const screenSlot = childNodes.find(n => isScreenSlot(n.props._component))
|
||||||
|
|
||||||
|
if (onScreenSlotRendered && screenSlot) {
|
||||||
// assuming there is only ever one screen slot
|
// assuming there is only ever one screen slot
|
||||||
onScreenSlotRendered(renderedComponentsThisIteration[0])
|
onScreenSlotRendered(screenSlot)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let comp of renderedComponentsThisIteration) {
|
treeNode.children = childNodes
|
||||||
comp.unsubscribe = bind(comp.component)
|
|
||||||
renderedComponents.push(comp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
treeNode.children = renderedComponents
|
return childNodes
|
||||||
|
|
||||||
return renderedComponents
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const splitName = fullname => {
|
const splitName = fullname => {
|
||||||
|
@ -90,3 +84,19 @@ const splitName = fullname => {
|
||||||
|
|
||||||
return { libName, componentName }
|
return { libName, componentName }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const areTreeNodesEqual = (children1, children2) => {
|
||||||
|
if (children1.length !== children2.length) return false
|
||||||
|
if (children1 === children2) return true
|
||||||
|
|
||||||
|
let isEqual = false
|
||||||
|
for (let i = 0; i < children1.length; i++) {
|
||||||
|
isEqual = deepEqual(children1[i].context, children2[i].context)
|
||||||
|
if (!isEqual) return false
|
||||||
|
if (isScreenSlot(children1[i].parentNode.props._component)) {
|
||||||
|
isEqual = deepEqual(children1[i].props, children2[i].props)
|
||||||
|
}
|
||||||
|
if (!isEqual) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
export const renderComponent = ({
|
export const prepareRenderComponent = ({
|
||||||
componentConstructor,
|
componentConstructor,
|
||||||
uiFunctions,
|
uiFunctions,
|
||||||
htmlElement,
|
htmlElement,
|
||||||
anchor,
|
anchor,
|
||||||
props,
|
props,
|
||||||
initialProps,
|
|
||||||
bb,
|
|
||||||
parentNode,
|
parentNode,
|
||||||
|
getCurrentState,
|
||||||
}) => {
|
}) => {
|
||||||
const func = initialProps._id ? uiFunctions[initialProps._id] : undefined
|
const func = props._id ? uiFunctions[props._id] : undefined
|
||||||
|
|
||||||
const parentContext = (parentNode && parentNode.context) || {}
|
const parentContext = (parentNode && parentNode.context) || {}
|
||||||
|
|
||||||
let renderedNodes = []
|
let nodesToRender = []
|
||||||
const render = context => {
|
const createNodeAndRender = context => {
|
||||||
let componentContext = parentContext
|
let componentContext = parentContext
|
||||||
if (context) {
|
if (context) {
|
||||||
componentContext = { ...componentContext }
|
componentContext = { ...context }
|
||||||
componentContext.$parent = parentContext
|
componentContext.$parent = parentContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,33 +23,31 @@ export const renderComponent = ({
|
||||||
thisNode.context = componentContext
|
thisNode.context = componentContext
|
||||||
thisNode.parentNode = parentNode
|
thisNode.parentNode = parentNode
|
||||||
thisNode.props = props
|
thisNode.props = props
|
||||||
|
nodesToRender.push(thisNode)
|
||||||
|
|
||||||
parentNode.children.push(thisNode)
|
thisNode.render = initialProps => {
|
||||||
renderedNodes.push(thisNode)
|
|
||||||
|
|
||||||
initialProps._bb = bb(thisNode, props)
|
|
||||||
|
|
||||||
thisNode.component = new componentConstructor({
|
thisNode.component = new componentConstructor({
|
||||||
target: htmlElement,
|
target: htmlElement,
|
||||||
props: initialProps,
|
props: initialProps,
|
||||||
hydrate: false,
|
hydrate: false,
|
||||||
anchor,
|
anchor,
|
||||||
})
|
})
|
||||||
|
thisNode.rootElement =
|
||||||
|
htmlElement.children[htmlElement.children.length - 1]
|
||||||
|
|
||||||
thisNode.rootElement = htmlElement.children[htmlElement.children.length - 1]
|
if (props._id && thisNode.rootElement) {
|
||||||
|
thisNode.rootElement.classList.add(`pos-${props._id}`)
|
||||||
if (initialProps._id && thisNode.rootElement) {
|
}
|
||||||
thisNode.rootElement.classList.add(`pos-${initialProps._id}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (func) {
|
if (func) {
|
||||||
func(render, parentContext)
|
func(createNodeAndRender, parentContext, getCurrentState())
|
||||||
} else {
|
} else {
|
||||||
render()
|
createNodeAndRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderedNodes
|
return nodesToRender
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTreeNode = () => ({
|
export const createTreeNode = () => ({
|
||||||
|
@ -59,8 +56,10 @@ export const createTreeNode = () => ({
|
||||||
rootElement: null,
|
rootElement: null,
|
||||||
parentNode: null,
|
parentNode: null,
|
||||||
children: [],
|
children: [],
|
||||||
|
bindings: [],
|
||||||
component: null,
|
component: null,
|
||||||
unsubscribe: () => {},
|
unsubscribe: () => {},
|
||||||
|
render: () => {},
|
||||||
get destroy() {
|
get destroy() {
|
||||||
const node = this
|
const node = this
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -71,6 +70,10 @@ export const createTreeNode = () => ({
|
||||||
child.destroy()
|
child.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (let onDestroyItem of node.onDestroy) {
|
||||||
|
onDestroyItem()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onDestroy: [],
|
||||||
})
|
})
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { getStateOrValue } from "./getState"
|
||||||
|
import { setState, setStateFromBinding } from "./setState"
|
||||||
|
import { trimSlash } from "../common/trimSlash"
|
||||||
|
import { isBound } from "./isState"
|
||||||
|
import { attachChildren } from "../render/attachChildren"
|
||||||
|
|
||||||
|
export const bbFactory = ({
|
||||||
|
store,
|
||||||
|
getCurrentState,
|
||||||
|
frontendDefinition,
|
||||||
|
componentLibraries,
|
||||||
|
uiFunctions,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
}) => {
|
||||||
|
const relativeUrl = url =>
|
||||||
|
frontendDefinition.appRootPath
|
||||||
|
? frontendDefinition.appRootPath + "/" + trimSlash(url)
|
||||||
|
: url
|
||||||
|
|
||||||
|
const apiCall = method => (url, body) =>
|
||||||
|
fetch(relativeUrl(url), {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: body && JSON.stringify(body),
|
||||||
|
})
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
post: apiCall("POST"),
|
||||||
|
get: apiCall("GET"),
|
||||||
|
patch: apiCall("PATCH"),
|
||||||
|
delete: apiCall("DELETE"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeCallEvent = (event, context) => {
|
||||||
|
const isFunction = obj =>
|
||||||
|
!!(obj && obj.constructor && obj.call && obj.apply)
|
||||||
|
|
||||||
|
if (isFunction(event)) event(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (treeNode, setupState) => {
|
||||||
|
const attachParams = {
|
||||||
|
componentLibraries,
|
||||||
|
uiFunctions,
|
||||||
|
treeNode,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
setupState,
|
||||||
|
getCurrentState,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
attachChildren: attachChildren(attachParams),
|
||||||
|
context: treeNode.context,
|
||||||
|
props: treeNode.props,
|
||||||
|
call: safeCallEvent,
|
||||||
|
setStateFromBinding: (binding, value) =>
|
||||||
|
setStateFromBinding(store, binding, value),
|
||||||
|
setState: (path, value) => setState(store, path, value),
|
||||||
|
getStateOrValue: (prop, currentContext) =>
|
||||||
|
getStateOrValue(getCurrentState(), prop, currentContext),
|
||||||
|
store: store,
|
||||||
|
relativeUrl,
|
||||||
|
api,
|
||||||
|
isBound,
|
||||||
|
parent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,8 @@ const isMetaProp = propName =>
|
||||||
propName === "_component" ||
|
propName === "_component" ||
|
||||||
propName === "_children" ||
|
propName === "_children" ||
|
||||||
propName === "_id" ||
|
propName === "_id" ||
|
||||||
propName === "_style"
|
propName === "_style" ||
|
||||||
|
propName === "_code"
|
||||||
|
|
||||||
export const setupBinding = (store, rootProps, coreApi, context, rootPath) => {
|
export const setupBinding = (store, rootProps, coreApi, context, rootPath) => {
|
||||||
const rootInitialProps = { ...rootProps }
|
const rootInitialProps = { ...rootProps }
|
||||||
|
|
|
@ -0,0 +1,288 @@
|
||||||
|
import {
|
||||||
|
isEventType,
|
||||||
|
eventHandlers,
|
||||||
|
EVENT_TYPE_MEMBER_NAME,
|
||||||
|
} from "./eventHandlers"
|
||||||
|
import { bbFactory } from "./bbComponentApi"
|
||||||
|
import { getState } from "./getState"
|
||||||
|
import { attachChildren } from "../render/attachChildren"
|
||||||
|
|
||||||
|
import {
|
||||||
|
isBound,
|
||||||
|
takeStateFromStore,
|
||||||
|
takeStateFromContext,
|
||||||
|
takeStateFromEventParameters,
|
||||||
|
BB_STATE_FALLBACK,
|
||||||
|
BB_STATE_BINDINGPATH,
|
||||||
|
BB_STATE_BINDINGSOURCE,
|
||||||
|
} from "./isState"
|
||||||
|
|
||||||
|
const doNothing = () => {}
|
||||||
|
doNothing.isPlaceholder = true
|
||||||
|
|
||||||
|
const isMetaProp = propName =>
|
||||||
|
propName === "_component" ||
|
||||||
|
propName === "_children" ||
|
||||||
|
propName === "_id" ||
|
||||||
|
propName === "_style" ||
|
||||||
|
propName === "_code" ||
|
||||||
|
propName === "_codeMeta"
|
||||||
|
|
||||||
|
export const createStateManager = ({
|
||||||
|
store,
|
||||||
|
coreApi,
|
||||||
|
rootPath,
|
||||||
|
frontendDefinition,
|
||||||
|
componentLibraries,
|
||||||
|
uiFunctions,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
}) => {
|
||||||
|
let handlerTypes = eventHandlers(store, coreApi, rootPath)
|
||||||
|
let currentState
|
||||||
|
|
||||||
|
// any nodes that have props that are bound to the store
|
||||||
|
let nodesBoundByProps = []
|
||||||
|
|
||||||
|
// any node whose children depend on code, that uses the store
|
||||||
|
let nodesWithCodeBoundChildren = []
|
||||||
|
|
||||||
|
const getCurrentState = () => currentState
|
||||||
|
const registerBindings = _registerBindings(
|
||||||
|
nodesBoundByProps,
|
||||||
|
nodesWithCodeBoundChildren
|
||||||
|
)
|
||||||
|
const bb = bbFactory({
|
||||||
|
store,
|
||||||
|
getCurrentState,
|
||||||
|
frontendDefinition,
|
||||||
|
componentLibraries,
|
||||||
|
uiFunctions,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
})
|
||||||
|
|
||||||
|
const setup = _setup(handlerTypes, getCurrentState, registerBindings, bb)
|
||||||
|
|
||||||
|
const unsubscribe = store.subscribe(
|
||||||
|
onStoreStateUpdated({
|
||||||
|
setCurrentState: s => (currentState = s),
|
||||||
|
getCurrentState,
|
||||||
|
nodesWithCodeBoundChildren,
|
||||||
|
nodesBoundByProps,
|
||||||
|
uiFunctions,
|
||||||
|
componentLibraries,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
setupState: setup,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
setup,
|
||||||
|
destroy: () => unsubscribe(),
|
||||||
|
getCurrentState,
|
||||||
|
store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onStoreStateUpdated = ({
|
||||||
|
setCurrentState,
|
||||||
|
getCurrentState,
|
||||||
|
nodesWithCodeBoundChildren,
|
||||||
|
nodesBoundByProps,
|
||||||
|
uiFunctions,
|
||||||
|
componentLibraries,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
setupState,
|
||||||
|
}) => s => {
|
||||||
|
setCurrentState(s)
|
||||||
|
|
||||||
|
// the original array gets changed by components' destroy()
|
||||||
|
// so we make a clone and check if they are still in the original
|
||||||
|
const nodesWithBoundChildren_clone = [...nodesWithCodeBoundChildren]
|
||||||
|
for (let node of nodesWithBoundChildren_clone) {
|
||||||
|
if (!nodesWithCodeBoundChildren.includes(node)) continue
|
||||||
|
attachChildren({
|
||||||
|
uiFunctions,
|
||||||
|
componentLibraries,
|
||||||
|
treeNode: node,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
setupState,
|
||||||
|
getCurrentState,
|
||||||
|
})(node.rootElement, { hydrate: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let node of nodesBoundByProps) {
|
||||||
|
setNodeState(s, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _registerBindings = (nodesBoundByProps, nodesWithCodeBoundChildren) => (
|
||||||
|
node,
|
||||||
|
bindings
|
||||||
|
) => {
|
||||||
|
if (bindings.length > 0) {
|
||||||
|
node.bindings = bindings
|
||||||
|
nodesBoundByProps.push(node)
|
||||||
|
const onDestroy = () => {
|
||||||
|
nodesBoundByProps = nodesBoundByProps.filter(n => n === node)
|
||||||
|
node.onDestroy = node.onDestroy.filter(d => d === onDestroy)
|
||||||
|
}
|
||||||
|
node.onDestroy.push(onDestroy)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
node.props._children &&
|
||||||
|
node.props._children.filter(c => c._codeMeta && c._codeMeta.dependsOnStore)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
|
nodesWithCodeBoundChildren.push(node)
|
||||||
|
const onDestroy = () => {
|
||||||
|
nodesWithCodeBoundChildren = nodesWithCodeBoundChildren.filter(
|
||||||
|
n => n === node
|
||||||
|
)
|
||||||
|
node.onDestroy = node.onDestroy.filter(d => d === onDestroy)
|
||||||
|
}
|
||||||
|
node.onDestroy.push(onDestroy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setNodeState = (storeState, node) => {
|
||||||
|
if (!node.component) return
|
||||||
|
const newProps = { ...node.bindings.initialProps }
|
||||||
|
|
||||||
|
for (let binding of node.bindings) {
|
||||||
|
const val = getState(storeState, binding.path, binding.fallback)
|
||||||
|
|
||||||
|
if (val === undefined && newProps[binding.propName] !== undefined) {
|
||||||
|
delete newProps[binding.propName]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val !== undefined) {
|
||||||
|
newProps[binding.propName] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.component.$set(newProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
const _setup = (
|
||||||
|
handlerTypes,
|
||||||
|
getCurrentState,
|
||||||
|
registerBindings,
|
||||||
|
bb
|
||||||
|
) => node => {
|
||||||
|
const props = node.props
|
||||||
|
const context = node.context || {}
|
||||||
|
const initialProps = { ...props }
|
||||||
|
const storeBoundProps = []
|
||||||
|
const currentStoreState = getCurrentState()
|
||||||
|
|
||||||
|
for (let propName in props) {
|
||||||
|
if (isMetaProp(propName)) continue
|
||||||
|
|
||||||
|
const val = props[propName]
|
||||||
|
|
||||||
|
if (isBound(val) && takeStateFromStore(val)) {
|
||||||
|
const path = BindingPath(val)
|
||||||
|
const source = BindingSource(val)
|
||||||
|
const fallback = BindingFallback(val)
|
||||||
|
|
||||||
|
storeBoundProps.push({
|
||||||
|
path,
|
||||||
|
fallback,
|
||||||
|
propName,
|
||||||
|
source,
|
||||||
|
})
|
||||||
|
|
||||||
|
initialProps[propName] = !currentStoreState
|
||||||
|
? fallback
|
||||||
|
: getState(
|
||||||
|
currentStoreState,
|
||||||
|
BindingPath(val),
|
||||||
|
BindingFallback(val),
|
||||||
|
BindingSource(val)
|
||||||
|
)
|
||||||
|
} else if (isBound(val) && takeStateFromContext(val)) {
|
||||||
|
initialProps[propName] = !context
|
||||||
|
? val
|
||||||
|
: getState(
|
||||||
|
context,
|
||||||
|
BindingPath(val),
|
||||||
|
BindingFallback(val),
|
||||||
|
BindingSource(val)
|
||||||
|
)
|
||||||
|
} else if (isEventType(val)) {
|
||||||
|
const handlersInfos = []
|
||||||
|
for (let e of val) {
|
||||||
|
const handlerInfo = {
|
||||||
|
handlerType: e[EVENT_TYPE_MEMBER_NAME],
|
||||||
|
parameters: e.parameters,
|
||||||
|
}
|
||||||
|
const resolvedParams = {}
|
||||||
|
for (let paramName in handlerInfo.parameters) {
|
||||||
|
const paramValue = handlerInfo.parameters[paramName]
|
||||||
|
if (!isBound(paramValue)) {
|
||||||
|
resolvedParams[paramName] = () => paramValue
|
||||||
|
continue
|
||||||
|
} else if (takeStateFromContext(paramValue)) {
|
||||||
|
const val = getState(
|
||||||
|
context,
|
||||||
|
paramValue[BB_STATE_BINDINGPATH],
|
||||||
|
paramValue[BB_STATE_FALLBACK]
|
||||||
|
)
|
||||||
|
resolvedParams[paramName] = () => val
|
||||||
|
} else if (takeStateFromStore(paramValue)) {
|
||||||
|
resolvedParams[paramName] = () =>
|
||||||
|
getState(
|
||||||
|
getCurrentState(),
|
||||||
|
paramValue[BB_STATE_BINDINGPATH],
|
||||||
|
paramValue[BB_STATE_FALLBACK]
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
} else if (takeStateFromEventParameters(paramValue)) {
|
||||||
|
resolvedParams[paramName] = eventContext => {
|
||||||
|
getState(
|
||||||
|
eventContext,
|
||||||
|
paramValue[BB_STATE_BINDINGPATH],
|
||||||
|
paramValue[BB_STATE_FALLBACK]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlerInfo.parameters = resolvedParams
|
||||||
|
handlersInfos.push(handlerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handlersInfos.length === 0) initialProps[propName] = doNothing
|
||||||
|
else {
|
||||||
|
initialProps[propName] = async context => {
|
||||||
|
for (let handlerInfo of handlersInfos) {
|
||||||
|
const handler = makeHandler(handlerTypes, handlerInfo)
|
||||||
|
await handler(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerBindings(node, storeBoundProps)
|
||||||
|
|
||||||
|
const setup = _setup(handlerTypes, getCurrentState, registerBindings, bb)
|
||||||
|
initialProps._bb = bb(node, setup)
|
||||||
|
|
||||||
|
return initialProps
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeHandler = (handlerTypes, handlerInfo) => {
|
||||||
|
const handlerType = handlerTypes[handlerInfo.handlerType]
|
||||||
|
return context => {
|
||||||
|
const parameters = {}
|
||||||
|
for (let p in handlerInfo.parameters) {
|
||||||
|
parameters[p] = handlerInfo.parameters[p](context)
|
||||||
|
}
|
||||||
|
handlerType.execute(parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BindingPath = prop => prop[BB_STATE_BINDINGPATH]
|
||||||
|
const BindingFallback = prop => prop[BB_STATE_FALLBACK]
|
||||||
|
const BindingSource = prop => prop[BB_STATE_BINDINGSOURCE]
|
|
@ -1,4 +1,5 @@
|
||||||
import { load, makePage, makeScreen } from "./testAppDef"
|
import { load, makePage, makeScreen } from "./testAppDef"
|
||||||
|
import { EVENT_TYPE_MEMBER_NAME } from "../src/state/eventHandlers"
|
||||||
|
|
||||||
describe("initialiseApp (binding)", () => {
|
describe("initialiseApp (binding)", () => {
|
||||||
it("should populate root element prop from store value", async () => {
|
it("should populate root element prop from store value", async () => {
|
||||||
|
@ -169,4 +170,177 @@ describe("initialiseApp (binding)", () => {
|
||||||
"header 2 - new val"
|
"header 2 - new val"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should fire events", async () => {
|
||||||
|
const { dom, app } = await load(
|
||||||
|
makePage({
|
||||||
|
_component: "testlib/button",
|
||||||
|
onClick: [
|
||||||
|
event("Set State", {
|
||||||
|
path: "address",
|
||||||
|
value: "123 Main Street",
|
||||||
|
}),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const button = dom.window.document.body.children[0]
|
||||||
|
expect(button.tagName).toBe("BUTTON")
|
||||||
|
|
||||||
|
let storeAddress
|
||||||
|
app.pageStore().subscribe(s => {
|
||||||
|
storeAddress = s.address
|
||||||
|
})
|
||||||
|
button.dispatchEvent(new dom.window.Event("click"))
|
||||||
|
expect(storeAddress).toBe("123 Main Street")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should alter event parameters based on store values", async () => {
|
||||||
|
const { dom, app } = await load(
|
||||||
|
makePage({
|
||||||
|
_component: "testlib/button",
|
||||||
|
onClick: [
|
||||||
|
event("Set State", {
|
||||||
|
path: "address",
|
||||||
|
value: {
|
||||||
|
"##bbstate": "sourceaddress",
|
||||||
|
"##bbsource": "store",
|
||||||
|
"##bbstatefallback": "fallback address",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const button = dom.window.document.body.children[0]
|
||||||
|
expect(button.tagName).toBe("BUTTON")
|
||||||
|
|
||||||
|
let storeAddress
|
||||||
|
app.pageStore().subscribe(s => {
|
||||||
|
storeAddress = s.address
|
||||||
|
})
|
||||||
|
|
||||||
|
button.dispatchEvent(new dom.window.Event("click"))
|
||||||
|
expect(storeAddress).toBe("fallback address")
|
||||||
|
|
||||||
|
app.pageStore().update(s => {
|
||||||
|
s.sourceaddress = "new address"
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
|
||||||
|
button.dispatchEvent(new dom.window.Event("click"))
|
||||||
|
expect(storeAddress).toBe("new address")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should take event parameters from context values", async () => {
|
||||||
|
const { dom, app } = await load(
|
||||||
|
makePage({
|
||||||
|
_component: "testlib/button",
|
||||||
|
_id: "with_context",
|
||||||
|
onClick: [
|
||||||
|
event("Set State", {
|
||||||
|
path: "address",
|
||||||
|
value: {
|
||||||
|
"##bbstate": "testKey",
|
||||||
|
"##bbsource": "context",
|
||||||
|
"##bbstatefallback": "fallback address",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const button = dom.window.document.body.children[0]
|
||||||
|
expect(button.tagName).toBe("BUTTON")
|
||||||
|
|
||||||
|
let storeAddress
|
||||||
|
app.pageStore().subscribe(s => {
|
||||||
|
storeAddress = s.address
|
||||||
|
})
|
||||||
|
|
||||||
|
button.dispatchEvent(new dom.window.Event("click"))
|
||||||
|
expect(storeAddress).toBe("test value")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should rerender components when their code is bound to the store ", async () => {
|
||||||
|
const { dom, app } = await load(
|
||||||
|
makePage({
|
||||||
|
_component: "testlib/div",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_component: "testlib/div",
|
||||||
|
_id: "n_clones_based_on_store",
|
||||||
|
className: "child_div",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootDiv = dom.window.document.body.children[0]
|
||||||
|
expect(rootDiv.tagName).toBe("DIV")
|
||||||
|
expect(rootDiv.children.length).toBe(0)
|
||||||
|
|
||||||
|
app.pageStore().update(s => {
|
||||||
|
s.componentCount = 3
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(rootDiv.children.length).toBe(3)
|
||||||
|
expect(rootDiv.children[0].className.includes("child_div")).toBe(true)
|
||||||
|
|
||||||
|
app.pageStore().update(s => {
|
||||||
|
s.componentCount = 5
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(rootDiv.children.length).toBe(5)
|
||||||
|
expect(rootDiv.children[0].className.includes("child_div")).toBe(true)
|
||||||
|
|
||||||
|
app.pageStore().update(s => {
|
||||||
|
s.componentCount = 0
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(rootDiv.children.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to read value from context, passed fromm parent, through code", async () => {
|
||||||
|
const { dom, app } = await load(
|
||||||
|
makePage({
|
||||||
|
_component: "testlib/div",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_component: "testlib/div",
|
||||||
|
_id: "n_clones_based_on_store",
|
||||||
|
className: {
|
||||||
|
"##bbstate": "index",
|
||||||
|
"##bbsource": "context",
|
||||||
|
"##bbstatefallback": "nothing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootDiv = dom.window.document.body.children[0]
|
||||||
|
expect(rootDiv.tagName).toBe("DIV")
|
||||||
|
expect(rootDiv.children.length).toBe(0)
|
||||||
|
|
||||||
|
app.pageStore().update(s => {
|
||||||
|
s.componentCount = 3
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(rootDiv.children.length).toBe(3)
|
||||||
|
expect(rootDiv.children[0].className.includes("index_0")).toBe(true)
|
||||||
|
expect(rootDiv.children[1].className.includes("index_1")).toBe(true)
|
||||||
|
expect(rootDiv.children[2].className.includes("index_2")).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
const event = (handlerType, parameters) => {
|
||||||
|
const e = {}
|
||||||
|
e[EVENT_TYPE_MEMBER_NAME] = handlerType
|
||||||
|
e.parameters = parameters
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ export const load = async (page, screens = [], url = "/") => {
|
||||||
actions: [],
|
actions: [],
|
||||||
triggers: [],
|
triggers: [],
|
||||||
})
|
})
|
||||||
|
setComponentCodeMeta(page, screens)
|
||||||
const app = await loadBudibase({
|
const app = await loadBudibase({
|
||||||
componentLibraries: allLibs(dom.window),
|
componentLibraries: allLibs(dom.window),
|
||||||
window: dom.window,
|
window: dom.window,
|
||||||
|
@ -47,8 +48,11 @@ export const timeout = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
export const walkComponentTree = (node, action) => {
|
export const walkComponentTree = (node, action) => {
|
||||||
action(node)
|
action(node)
|
||||||
|
|
||||||
if (node.children) {
|
// works for nodes or props
|
||||||
for (let child of node.children) {
|
const children = node.children || node._children
|
||||||
|
|
||||||
|
if (children) {
|
||||||
|
for (let child of children) {
|
||||||
walkComponentTree(child, action)
|
walkComponentTree(child, action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +72,22 @@ const autoAssignIds = (props, count = 0) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// any component with an id that include "based_on_store" is
|
||||||
|
// assumed to have code that depends on store value
|
||||||
|
const setComponentCodeMeta = (page, screens) => {
|
||||||
|
const setComponentCodeMeta_single = props => {
|
||||||
|
walkComponentTree(props, c => {
|
||||||
|
if (c._id.indexOf("based_on_store") >= 0) {
|
||||||
|
c._codeMeta = { dependsOnStore: true }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setComponentCodeMeta_single(page.props)
|
||||||
|
for (let s of screens || []) {
|
||||||
|
setComponentCodeMeta_single(s.props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const setAppDef = (window, page, screens) => {
|
const setAppDef = (window, page, screens) => {
|
||||||
window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
|
window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
|
||||||
componentLibraries: [],
|
componentLibraries: [],
|
||||||
|
@ -148,6 +168,29 @@ const maketestlib = window => ({
|
||||||
set(opts.props)
|
set(opts.props)
|
||||||
opts.target.appendChild(node)
|
opts.target.appendChild(node)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
button: function(opts) {
|
||||||
|
const node = window.document.createElement("BUTTON")
|
||||||
|
|
||||||
|
let currentProps = { ...opts.props }
|
||||||
|
|
||||||
|
const set = props => {
|
||||||
|
currentProps = Object.assign(currentProps, props)
|
||||||
|
if (currentProps.onClick) {
|
||||||
|
node.addEventListener("click", () => {
|
||||||
|
const testText = currentProps.testText || "hello"
|
||||||
|
currentProps._bb.call(props.onClick, { testText })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$destroy = () => opts.target.removeChild(node)
|
||||||
|
|
||||||
|
this.$set = set
|
||||||
|
this._element = node
|
||||||
|
set(opts.props)
|
||||||
|
opts.target.appendChild(node)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const uiFunctions = {
|
const uiFunctions = {
|
||||||
|
@ -162,4 +205,15 @@ const uiFunctions = {
|
||||||
render()
|
render()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
with_context: render => {
|
||||||
|
render({ testKey: "test value" })
|
||||||
|
},
|
||||||
|
|
||||||
|
n_clones_based_on_store: (render, _, state) => {
|
||||||
|
const n = state.componentCount || 0
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
render({ index: `index_${i}` })
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,189 @@
|
||||||
{
|
{
|
||||||
"_lib": "./dist/index.js",
|
"_lib": "./dist/index.js",
|
||||||
"h1": {
|
"_generators": {},
|
||||||
"name": "H1",
|
"Body1": {
|
||||||
"description": "An HTML H1 tag",
|
"name": "Body1",
|
||||||
|
"description": "Sets the font properties as Roboto Body 1",
|
||||||
"props": {
|
"props": {
|
||||||
"text": "string",
|
"text": "string"
|
||||||
"className": "string"
|
|
||||||
},
|
},
|
||||||
"tags": []
|
"tags": []
|
||||||
},
|
},
|
||||||
"button": {
|
"Body2": {
|
||||||
|
"name": "Body2",
|
||||||
|
"description": "Sets the font properties as Roboto Body 2",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Button": {
|
||||||
"name": "Button",
|
"name": "Button",
|
||||||
"description": "A button",
|
"description": "A Material Design button with different variations. It renders as an anchor if href is passed to it.",
|
||||||
"props": {
|
"props": {
|
||||||
"raised": "bool"
|
"onClick": "event",
|
||||||
|
"variant": "string",
|
||||||
|
"colour": "string",
|
||||||
|
"size": "string",
|
||||||
|
"href": "string",
|
||||||
|
"icon": "string",
|
||||||
|
"trailingIcon": "bool",
|
||||||
|
"fullwidth": "bool",
|
||||||
|
"text": "string",
|
||||||
|
"disabled": "bool"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Caption": {
|
||||||
|
"name": "Caption",
|
||||||
|
"description": "Sets the font properties as Roboto Caption",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Checkbox": {
|
||||||
|
"name": "Checkbox",
|
||||||
|
"description": "A Material Design checkbox. Supports aligning label before or after checkbox.",
|
||||||
|
"props": {
|
||||||
|
"onClick": "event",
|
||||||
|
"id": "string",
|
||||||
|
"label": "string",
|
||||||
|
"disabled": "bool",
|
||||||
|
"alignEnd": "bool",
|
||||||
|
"indeterminate": "bool",
|
||||||
|
"checked": "bool"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Datatable": {
|
||||||
|
"name": "Datatable",
|
||||||
|
"description": "A Material Design component to represent tabular data.",
|
||||||
|
"props": {},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"H1": {
|
||||||
|
"name": "H1",
|
||||||
|
"description": "Sets the font properties as Roboto Headline1",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"H2": {
|
||||||
|
"name": "H2",
|
||||||
|
"description": "Sets the font properties as Roboto Headline2",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"H3": {
|
||||||
|
"name": "H3",
|
||||||
|
"description": "Sets the font properties as Roboto Headline3",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"H4": {
|
||||||
|
"name": "H4",
|
||||||
|
"description": "Sets the font properties as Roboto Headline4",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"H5": {
|
||||||
|
"name": "H5",
|
||||||
|
"description": "Sets the font properties as Roboto Headline5",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"H6": {
|
||||||
|
"name": "H6",
|
||||||
|
"description": "Sets the font properties as Roboto Headline6",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Label": {
|
||||||
|
"name": "Label",
|
||||||
|
"description": "A simple label component that displays its text in the standard Roboto Material Design font",
|
||||||
|
"props": {
|
||||||
|
"bold": "bool"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Overline": {
|
||||||
|
"name": "Overline",
|
||||||
|
"description": "Sets the font properties as Roboto Overline",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Radiobutton": {
|
||||||
|
"name": "Radiobutton",
|
||||||
|
"description": "A Material Design radiobutton. Supports aligning label before or after radiobutton.",
|
||||||
|
"props": {
|
||||||
|
"onClick": "event",
|
||||||
|
"id": "string",
|
||||||
|
"label": "string",
|
||||||
|
"names": "string",
|
||||||
|
"name": "string",
|
||||||
|
"checked": "bool",
|
||||||
|
"disabled": "bool",
|
||||||
|
"alignEnd": "bool"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Sub1": {
|
||||||
|
"name": "Sub1",
|
||||||
|
"description": "Sets the font properties as Roboto Subtitle1",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Sub2": {
|
||||||
|
"name": "Sub2",
|
||||||
|
"description": "Sets the font properties as Roboto Subtitle2",
|
||||||
|
"props": {
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"Textfield": {
|
||||||
|
"name": "Textfield",
|
||||||
|
"description": "A Material Design textfield with multiple variants. Can also be converted to a text area / multine text field.",
|
||||||
|
"props": {
|
||||||
|
"onChange": "event",
|
||||||
|
"label": "string",
|
||||||
|
"variant": "string",
|
||||||
|
"disabled": "bool",
|
||||||
|
"fullwidth": "bool",
|
||||||
|
"colour":"string",
|
||||||
|
"size":"string",
|
||||||
|
"type": "string",
|
||||||
|
"required": "bool",
|
||||||
|
"minLength": "number",
|
||||||
|
"maxLength": "number",
|
||||||
|
"helperText": "string",
|
||||||
|
"errorText": "string",
|
||||||
|
"placeholder": "string",
|
||||||
|
"icon": "string",
|
||||||
|
"trailingIcon": "bool",
|
||||||
|
"textarea": "bool",
|
||||||
|
"rows": "number",
|
||||||
|
"cols": "number",
|
||||||
|
"validation": "bool",
|
||||||
|
"persistent": "bool"
|
||||||
},
|
},
|
||||||
"tags": []
|
"tags": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072",
|
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material/checkbox": "^4.0.0",
|
"@material/checkbox": "^4.0.0",
|
||||||
|
"@material/data-table": "4.0.0",
|
||||||
"@material/form-field": "^4.0.0",
|
"@material/form-field": "^4.0.0",
|
||||||
"@material/radio": "^4.0.0",
|
"@material/radio": "^4.0.0",
|
||||||
"@material/textfield": "^4.0.0"
|
"@material/textfield": "^4.0.0"
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
<script>
|
|
||||||
import "@material/button/mdc-button.scss"
|
|
||||||
export let raised = false
|
|
||||||
|
|
||||||
let c = raised ? "mdc-button mdc-button--raised" : "mdc-button"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button class={c}>
|
|
||||||
<div class="mdc-button__ripple" />
|
|
||||||
|
|
||||||
<span class="mdc-button__label">Button</span>
|
|
||||||
</button>
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { setContext, getContext } from "svelte"
|
import { setContext, getContext } from "svelte"
|
||||||
import Icon from "../Icon.svelte"
|
import Icon from "../Common/Icon.svelte"
|
||||||
import ripple from "../Ripple.js"
|
import ripple from "../Common/Ripple.js"
|
||||||
import ClassBuilder from "../ClassBuilder.js"
|
import ClassBuilder from "../ClassBuilder.js"
|
||||||
|
|
||||||
const cb = new ClassBuilder("button", ["primary", "medium"])
|
const cb = new ClassBuilder("button", ["primary", "medium"])
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
import "./_index.scss"
|
import "./_index.scss"
|
||||||
export { default as button } from "./Button.svelte"
|
export { default as Button } from "./Button.svelte"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import "./_style.scss";
|
import "./_style.scss";
|
||||||
export { default as checkbox } from "./Checkbox.svelte";
|
export { default as Checkbox } from "./Checkbox.svelte";
|
||||||
export { default as checkboxgroup } from "./CheckboxGroup.svelte";
|
export { default as Checkboxgroup } from "./CheckboxGroup.svelte";
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
<script>
|
||||||
|
import { onMount, setContext } from "svelte"
|
||||||
|
import { MDCDataTable } from "@material/data-table"
|
||||||
|
import Row from "./DatatableRow.svelte"
|
||||||
|
import Cell from "./DatatableCell.svelte"
|
||||||
|
import { Button } from "../Button"
|
||||||
|
import ClassBuilder from "../ClassBuilder.js"
|
||||||
|
|
||||||
|
const cb = new ClassBuilder("data-table")
|
||||||
|
setContext("BBMD:data-table:cb", cb)
|
||||||
|
|
||||||
|
let datatable = null
|
||||||
|
let instance = null
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!!datatable) instance = new MDCDataTable(datatable)
|
||||||
|
return () => {
|
||||||
|
!!instance && instance.destroy()
|
||||||
|
instance = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={datatable} class={cb.build()}>
|
||||||
|
<table class={cb.elem`table`} aria-label="Material Design Datatable">
|
||||||
|
<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>
|
||||||
|
</div>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
export let isHeader = false
|
||||||
|
export let numeric = false
|
||||||
|
|
||||||
|
const cb = getContext("BBMD:data-table:cb")
|
||||||
|
|
||||||
|
let elementName = isHeader ? "header-cell" : "cell"
|
||||||
|
let modifiers = { numeric }
|
||||||
|
let props = { modifiers }
|
||||||
|
let cellClass = cb.build({ elementName, props })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if isHeader}
|
||||||
|
<th class={cellClass} role="columnheader" scope="col">
|
||||||
|
<slot />
|
||||||
|
</th>
|
||||||
|
{:else}
|
||||||
|
<td class={cellClass}>
|
||||||
|
<slot />
|
||||||
|
</td>
|
||||||
|
{/if}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
export let onSelect = () => {};
|
||||||
|
export let isHeader = false;
|
||||||
|
let row = null;
|
||||||
|
let selected = false;
|
||||||
|
|
||||||
|
const cb = getContext("BBMD:data-table:cb");
|
||||||
|
|
||||||
|
let elementName = isHeader ? "header-row" : "row";
|
||||||
|
let modifiers = {};
|
||||||
|
|
||||||
|
$: modifiers = { selected };
|
||||||
|
$: props = { modifiers };
|
||||||
|
$: rowClass = cb.build({ elementName, props });
|
||||||
|
|
||||||
|
function rowSelected() {
|
||||||
|
selected = !selected;
|
||||||
|
onSelect();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<tr bind:this={row} class={rowClass} on:click={rowSelected}>
|
||||||
|
<slot />
|
||||||
|
</tr>
|
|
@ -0,0 +1 @@
|
||||||
|
@import "@material/data-table/mdc-data-table";
|
|
@ -0,0 +1,2 @@
|
||||||
|
import "./_style.scss";
|
||||||
|
export { default as Datatable } from "./Datatable.svelte";
|
|
@ -1,8 +0,0 @@
|
||||||
<script>
|
|
||||||
export let text = ""
|
|
||||||
export let className = ""
|
|
||||||
|
|
||||||
export let _bb
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1 class={className}>{text}</h1>
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
export let id = ""
|
export let id = ""
|
||||||
export let label = ""
|
export let label = ""
|
||||||
export let names = "radios"
|
export let name = "radios"
|
||||||
export let checked = false
|
export let checked = false
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let alignEnd = false
|
export let alignEnd = false
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
{id}
|
{id}
|
||||||
class={cb.elem`native-control`}
|
class={cb.elem`native-control`}
|
||||||
type="radio"
|
type="radio"
|
||||||
{names}
|
{name}
|
||||||
{checked}
|
{checked}
|
||||||
{disabled}
|
{disabled}
|
||||||
on:click={onClick} />
|
on:click={onClick} />
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import "./_style.scss";
|
import "./_style.scss";
|
||||||
export { default as radiobutton } from "./Radiobutton.svelte";
|
export { default as Radiobutton } from "./Radiobutton.svelte";
|
||||||
export { default as radiobuttongroup } from "./RadiobuttonGroup.svelte";
|
export { default as Radiobuttongroup } from "./RadiobuttonGroup.svelte";
|
||||||
|
|
|
@ -3,14 +3,15 @@
|
||||||
import { props } from "./props"
|
import { props } from "./props"
|
||||||
let _bb
|
let _bb
|
||||||
const {
|
const {
|
||||||
h1,
|
H1,
|
||||||
overline,
|
Overline,
|
||||||
button,
|
Button,
|
||||||
textfield,
|
Textfield,
|
||||||
checkbox,
|
Checkbox,
|
||||||
checkboxgroup,
|
Checkboxgroup,
|
||||||
radiobutton,
|
Radiobutton,
|
||||||
radiobuttongroup,
|
Radiobuttongroup,
|
||||||
|
Datatable,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
let currentComponent
|
let currentComponent
|
||||||
|
@ -22,14 +23,15 @@
|
||||||
props: {
|
props: {
|
||||||
_component: "testcomponents/rootComponent",
|
_component: "testcomponents/rootComponent",
|
||||||
_children: [
|
_children: [
|
||||||
h1,
|
H1,
|
||||||
overline,
|
Overline,
|
||||||
button,
|
Button,
|
||||||
textfield,
|
Textfield,
|
||||||
checkbox,
|
Checkbox,
|
||||||
checkboxgroup,
|
Checkboxgroup,
|
||||||
radiobutton,
|
Radiobutton,
|
||||||
radiobuttongroup,
|
Radiobuttongroup,
|
||||||
|
Datatable,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
h1: {
|
H1: {
|
||||||
_component: "@budibase/materialdesign-components/H1",
|
_component: "@budibase/materialdesign-components/H1",
|
||||||
_children: [],
|
_children: [],
|
||||||
text: "Im a big header",
|
text: "Im a big header",
|
||||||
},
|
},
|
||||||
overline: {
|
Overline: {
|
||||||
_component: "@budibase/materialdesign-components/Overline",
|
_component: "@budibase/materialdesign-components/Overline",
|
||||||
_children: [],
|
_children: [],
|
||||||
text: "Im a wee overline",
|
text: "Im a wee overline",
|
||||||
},
|
},
|
||||||
button: {
|
Button: {
|
||||||
_component: "@budibase/materialdesign-components/button",
|
_component: "@budibase/materialdesign-components/Button",
|
||||||
_children: [],
|
_children: [],
|
||||||
variant: "raised",
|
variant: "raised",
|
||||||
colour: "secondary",
|
colour: "secondary",
|
||||||
|
@ -24,13 +24,13 @@ export const props = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
onClick: () => alert`Button Clicked`,
|
onClick: () => alert`Button Clicked`,
|
||||||
},
|
},
|
||||||
icon: {
|
Icon: {
|
||||||
_component: "@budibase/materialdesign-components/icon",
|
_component: "@budibase/materialdesign-components/Icon",
|
||||||
_children: [],
|
_children: [],
|
||||||
icon: "",
|
icon: "",
|
||||||
},
|
},
|
||||||
textfield: {
|
Textfield: {
|
||||||
_component: "@budibase/materialdesign-components/textfield",
|
_component: "@budibase/materialdesign-components/Textfield",
|
||||||
_children: [],
|
_children: [],
|
||||||
label: "First",
|
label: "First",
|
||||||
colour: "secondary",
|
colour: "secondary",
|
||||||
|
@ -39,15 +39,15 @@ export const props = {
|
||||||
helperText: "Add Surname",
|
helperText: "Add Surname",
|
||||||
onChange: text => console.log("Text: ", text),
|
onChange: text => console.log("Text: ", text),
|
||||||
},
|
},
|
||||||
checkbox: {
|
Checkbox: {
|
||||||
_component: "@budibase/materialdesign-components/checkbox",
|
_component: "@budibase/materialdesign-components/Checkbox",
|
||||||
_children: [],
|
_children: [],
|
||||||
id: "test-check",
|
id: "test-check",
|
||||||
label: "Check Yo Self",
|
label: "Check Yo Self",
|
||||||
onClick: () => alert`Before ya reck yo'self`,
|
onClick: () => alert`Before ya reck yo'self`,
|
||||||
},
|
},
|
||||||
checkboxgroup: {
|
Checkboxgroup: {
|
||||||
_component: "@budibase/materialdesign-components/checkboxgroup",
|
_component: "@budibase/materialdesign-components/Checkboxgroup",
|
||||||
_children: [],
|
_children: [],
|
||||||
label: "Whats your favourite?",
|
label: "Whats your favourite?",
|
||||||
items: [
|
items: [
|
||||||
|
@ -57,15 +57,15 @@ export const props = {
|
||||||
],
|
],
|
||||||
onChange: selectedItems => console.log(selectedItems),
|
onChange: selectedItems => console.log(selectedItems),
|
||||||
},
|
},
|
||||||
radiobutton: {
|
Radiobutton: {
|
||||||
_component: "@budibase/materialdesign-components/radiobutton",
|
_component: "@budibase/materialdesign-components/Radiobutton",
|
||||||
_children: [],
|
_children: [],
|
||||||
label: "Hi radio",
|
label: "Hi radio",
|
||||||
alignEnd: true,
|
alignEnd: true,
|
||||||
onClick: () => alert`Roger That`,
|
onClick: () => alert`Roger That`,
|
||||||
},
|
},
|
||||||
radiobuttongroup: {
|
Radiobuttongroup: {
|
||||||
_component: "@budibase/materialdesign-components/radiobuttongroup",
|
_component: "@budibase/materialdesign-components/Radiobuttongroup",
|
||||||
_children: [],
|
_children: [],
|
||||||
label: "Preferred method of contact: ",
|
label: "Preferred method of contact: ",
|
||||||
orientation: "column",
|
orientation: "column",
|
||||||
|
@ -75,5 +75,9 @@ export const props = {
|
||||||
{ label: "Social Media", value: 3 },
|
{ label: "Social Media", value: 3 },
|
||||||
],
|
],
|
||||||
onChange: selected => console.log(selected),
|
onChange: selected => console.log(selected),
|
||||||
}
|
},
|
||||||
|
Datatable: {
|
||||||
|
_component: "@budibase/materialdesign-components/Datatable",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
import {
|
import {
|
||||||
H1,
|
H1,
|
||||||
Overline,
|
Overline,
|
||||||
button,
|
Button,
|
||||||
icon,
|
Icon,
|
||||||
textfield,
|
Textfield,
|
||||||
checkbox,
|
Checkbox,
|
||||||
checkboxgroup,
|
Checkboxgroup,
|
||||||
radiobutton,
|
Radiobutton,
|
||||||
radiobuttongroup,
|
Radiobuttongroup,
|
||||||
|
Datatable,
|
||||||
} from "@BBMD"
|
} from "@BBMD"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
H1,
|
H1,
|
||||||
Overline,
|
Overline,
|
||||||
button,
|
Button,
|
||||||
icon,
|
Icon,
|
||||||
textfield,
|
Textfield,
|
||||||
checkbox,
|
Checkbox,
|
||||||
checkboxgroup,
|
Checkboxgroup,
|
||||||
radiobutton,
|
Radiobutton,
|
||||||
radiobuttongroup,
|
Radiobuttongroup,
|
||||||
|
Datatable,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import FloatingLabel from "../Common/FloatingLabel.svelte"
|
import FloatingLabel from "../Common/FloatingLabel.svelte"
|
||||||
import HelperText from "./HelperText.svelte"
|
import HelperText from "./HelperText.svelte"
|
||||||
import CharacterCounter from "./CharacterCounter.svelte"
|
import CharacterCounter from "./CharacterCounter.svelte"
|
||||||
import Icon from "../Icon.svelte"
|
import Icon from "../Common/Icon.svelte"
|
||||||
|
|
||||||
const cb = new ClassBuilder("text-field", ["primary", "medium"])
|
const cb = new ClassBuilder("text-field", ["primary", "medium"])
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
import "./_index.scss"
|
import "./_index.scss"
|
||||||
export { default as textfield } from "./Textfield.svelte"
|
export { default as Textfield } from "./Textfield.svelte"
|
|
@ -1,13 +1,13 @@
|
||||||
import "./_style.scss";
|
import "./_style.scss";
|
||||||
|
export { default as Body1 } from "./Body1.svelte";
|
||||||
|
export { default as Body2 } from "./Body2.svelte";
|
||||||
|
export { default as Caption } from "./Caption.svelte";
|
||||||
export { default as H1 } from "./H1.svelte";
|
export { default as H1 } from "./H1.svelte";
|
||||||
export { default as H2 } from "./H2.svelte";
|
export { default as H2 } from "./H2.svelte";
|
||||||
export { default as H3 } from "./H3.svelte";
|
export { default as H3 } from "./H3.svelte";
|
||||||
export { default as H4 } from "./H4.svelte";
|
export { default as H4 } from "./H4.svelte";
|
||||||
export { default as H5 } from "./H5.svelte";
|
export { default as H5 } from "./H5.svelte";
|
||||||
export { default as H6 } from "./H6.svelte";
|
export { default as H6 } from "./H6.svelte";
|
||||||
export { default as Body1 } from "./Body1.svelte";
|
export { default as Overline } from "./Overline.svelte";
|
||||||
export { default as Body2 } from "./Body2.svelte";
|
|
||||||
export { default as Sub1 } from "./Sub1.svelte";
|
export { default as Sub1 } from "./Sub1.svelte";
|
||||||
export { default as Sub2 } from "./Sub2.svelte";
|
export { default as Sub2 } from "./Sub2.svelte";
|
||||||
export { default as Caption } from "./Caption.svelte";
|
|
||||||
export { default as Overline } from "./Overline.svelte";
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
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 "./Icon.svelte"
|
export { default as Icon } from "./Common/Icon.svelte"
|
||||||
export { textfield } from "./Textfield"
|
export { Textfield } from "./Textfield"
|
||||||
export * from "./Typography"
|
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 { Datatable } from "./Datatable"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
const { readdir, stat, copyFile } = require("fs-extra")
|
||||||
|
const { constants } = require("fs")
|
||||||
|
const { join, basename } = require("path")
|
||||||
|
const serverConfig = require("../../../server/config")()
|
||||||
|
|
||||||
|
const packagesFolder = ".."
|
||||||
|
|
||||||
|
const jsFile = dir => join(dir, "index.js")
|
||||||
|
const generatorsFile = dir => join(dir, "generators.js")
|
||||||
|
const jsMapFile = dir => join(dir, "index.js.map")
|
||||||
|
const sourceJs = jsFile("dist")
|
||||||
|
const sourceJsMap = jsMapFile("dist")
|
||||||
|
const componentsFile = "components.json"
|
||||||
|
const sourceGenerators = generatorsFile("dist")
|
||||||
|
|
||||||
|
const appPackages = join(
|
||||||
|
packagesFolder,
|
||||||
|
"server",
|
||||||
|
serverConfig.latestPackagesFolder
|
||||||
|
)
|
||||||
|
|
||||||
|
const publicMain = appName =>
|
||||||
|
join(
|
||||||
|
appPackages,
|
||||||
|
appName,
|
||||||
|
"public",
|
||||||
|
"main",
|
||||||
|
"lib",
|
||||||
|
"node_modules",
|
||||||
|
"@budibase",
|
||||||
|
"standard-components"
|
||||||
|
)
|
||||||
|
const publicUnauth = appName =>
|
||||||
|
join(
|
||||||
|
appPackages,
|
||||||
|
appName,
|
||||||
|
"public",
|
||||||
|
"unauthenticated",
|
||||||
|
"lib",
|
||||||
|
"node_modules",
|
||||||
|
"@budibase",
|
||||||
|
"standard-components"
|
||||||
|
)
|
||||||
|
const nodeModulesDist = appName =>
|
||||||
|
join(
|
||||||
|
appPackages,
|
||||||
|
appName,
|
||||||
|
"node_modules",
|
||||||
|
"@budibase",
|
||||||
|
"standard-components",
|
||||||
|
"dist"
|
||||||
|
)
|
||||||
|
const nodeModules = appName =>
|
||||||
|
join(appPackages, appName, "node_modules", "@budibase", "standard-components")
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
const apps = await readdir(appPackages)
|
||||||
|
|
||||||
|
const copySource = file => async toDir => {
|
||||||
|
const dest = join(toDir, basename(file))
|
||||||
|
try {
|
||||||
|
await copyFile(file, dest, constants.COPYFILE_FICLONE)
|
||||||
|
console.log(`COPIED ${file} to ${dest}`)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`COPY FAILED ${file} to ${dest}: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copySourceJs = copySource(sourceJs)
|
||||||
|
const copySourceJsMap = copySource(sourceJsMap)
|
||||||
|
const copyGenerators = copySource(sourceGenerators)
|
||||||
|
const copyComponentsJson = copySource(componentsFile)
|
||||||
|
|
||||||
|
for (let app of apps) {
|
||||||
|
if (app === ".data") continue
|
||||||
|
if (!(await stat(join(appPackages, app))).isDirectory()) continue
|
||||||
|
|
||||||
|
await copySourceJs(nodeModulesDist(app))
|
||||||
|
await copySourceJsMap(nodeModulesDist(app))
|
||||||
|
await copyGenerators(nodeModulesDist(app))
|
||||||
|
|
||||||
|
await copyComponentsJson(nodeModules(app))
|
||||||
|
|
||||||
|
await copySourceJs(join(publicMain(app), "dist"))
|
||||||
|
await copySourceJsMap(join(publicMain(app), "dist"))
|
||||||
|
await copyGenerators(join(publicMain(app), "dist"))
|
||||||
|
|
||||||
|
await copySourceJs(join(publicUnauth(app), "dist"))
|
||||||
|
await copySourceJsMap(join(publicUnauth(app), "dist"))
|
||||||
|
await copyGenerators(join(publicUnauth(app), "dist"))
|
||||||
|
}
|
||||||
|
})()
|
|
@ -14,6 +14,7 @@ const { join, resolve, dirname } = require("path")
|
||||||
const sqrl = require("squirrelly")
|
const sqrl = require("squirrelly")
|
||||||
const { convertCssToFiles } = require("./convertCssToFiles")
|
const { convertCssToFiles } = require("./convertCssToFiles")
|
||||||
const publicPath = require("./publicPath")
|
const publicPath = require("./publicPath")
|
||||||
|
const deleteCodeMeta = require("./deleteCodeMeta")
|
||||||
|
|
||||||
module.exports = async (config, appname, pageName, pkg) => {
|
module.exports = async (config, appname, pageName, pkg) => {
|
||||||
const appPath = appPackageFolder(config, appname)
|
const appPath = appPackageFolder(config, appname)
|
||||||
|
@ -155,6 +156,8 @@ const savePageJson = async (appPath, pageName, pkg) => {
|
||||||
delete pkg.page._screens
|
delete pkg.page._screens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteCodeMeta(pkg.page.props)
|
||||||
|
|
||||||
await writeJSON(pageFile, pkg.page, {
|
await writeJSON(pageFile, pkg.page, {
|
||||||
spaces: 2,
|
spaces: 2,
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = props => {
|
||||||
|
if (props._codeMeta) {
|
||||||
|
delete props._codeMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let child of props._children || []) {
|
||||||
|
module.exports(child)
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ const buildPage = require("./buildPage")
|
||||||
const getPages = require("./getPages")
|
const getPages = require("./getPages")
|
||||||
const listScreens = require("./listScreens")
|
const listScreens = require("./listScreens")
|
||||||
const saveBackend = require("./saveBackend")
|
const saveBackend = require("./saveBackend")
|
||||||
|
const deleteCodeMeta = require("./deleteCodeMeta")
|
||||||
|
|
||||||
module.exports.buildPage = buildPage
|
module.exports.buildPage = buildPage
|
||||||
module.exports.listScreens = listScreens
|
module.exports.listScreens = listScreens
|
||||||
|
@ -58,12 +59,15 @@ module.exports.saveScreen = async (config, appname, pagename, screen) => {
|
||||||
if (screen._css) {
|
if (screen._css) {
|
||||||
delete screen._css
|
delete screen._css
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteCodeMeta(screen.props)
|
||||||
|
|
||||||
await writeJSON(compPath, screen, {
|
await writeJSON(compPath, screen, {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
flag: "w",
|
flag: "w",
|
||||||
spaces: 2,
|
spaces: 2,
|
||||||
})
|
})
|
||||||
return screen;
|
return screen
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.renameScreen = async (
|
module.exports.renameScreen = async (
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
return all
|
return all
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (_bb.props._children.length > 0)
|
$: if(_bb.props._children && _bb.props._children.length > 0)
|
||||||
theButton && _bb.attachChildren(theButton)
|
theButton && _bb.attachChildren(theButton)
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
disabled={disabled || false}
|
disabled={disabled || false}
|
||||||
on:click={clickHandler}
|
on:click={clickHandler}
|
||||||
style={buttonStyles}>
|
style={buttonStyles}>
|
||||||
{#if _bb.props._children.length === 0}{contentText}{/if}
|
{#if !_bb.props._children || _bb.props._children.length === 0}{contentText}{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
Loading…
Reference in New Issue