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"
|
||||
import { buildCodeForScreens } from "./buildCodeForScreens"
|
||||
import { generate_screen_css } from "./generate_css"
|
||||
import { insertCodeMetadata } from "./insertCodeMetadata"
|
||||
// import { uuid } from "./uuid"
|
||||
|
||||
let appname = ""
|
||||
|
@ -115,6 +116,7 @@ export const getStore = () => {
|
|||
store.setComponentStyle = setComponentStyle(store)
|
||||
store.setComponentCode = setComponentCode(store)
|
||||
store.setScreenType = setScreenType(store)
|
||||
store.deleteComponent = deleteComponent(store)
|
||||
return store
|
||||
}
|
||||
|
||||
|
@ -824,6 +826,8 @@ const setCurrentScreenFunctions = s => {
|
|||
s.currentPreviewItem === "screen"
|
||||
? buildCodeForScreens([s.currentPreviewItem])
|
||||
: "({});"
|
||||
|
||||
insertCodeMetadata(s.currentPreviewItem.props)
|
||||
}
|
||||
|
||||
const setScreenType = store => type => {
|
||||
|
@ -840,3 +844,39 @@ const setScreenType = store => type => {
|
|||
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 PencilIcon } from "./Pencil.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 { 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 } from "../common/Icons/"
|
||||
|
||||
export let screens = []
|
||||
|
||||
let confirmDeleteDialog
|
||||
let componentToDelete = ""
|
||||
|
||||
const joinPath = join("/")
|
||||
|
||||
const normalizedName = name =>
|
||||
|
@ -23,6 +26,7 @@
|
|||
)
|
||||
|
||||
const lastPartOfName = c =>
|
||||
c &&
|
||||
last(c.name ? c.name.split("/") : c._component.split("/"))
|
||||
|
||||
const isComponentSelected = (current, comp) => current === comp
|
||||
|
@ -38,6 +42,12 @@
|
|||
component.component &&
|
||||
$store.currentPreviewItem &&
|
||||
component.component.name === $store.currentPreviewItem.name
|
||||
|
||||
const confirmDeleteComponent = component => {
|
||||
componentToDelete = component
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
@ -63,12 +73,20 @@
|
|||
<ComponentsHierarchyChildren
|
||||
components={screen.component.props._children}
|
||||
currentComponent={$store.currentComponentInfo}
|
||||
onSelect={store.selectComponent} />
|
||||
onSelect={store.selectComponent}
|
||||
onDeleteComponent={confirmDeleteComponent}/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
</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>
|
||||
.root {
|
||||
font-weight: 500;
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
<script>
|
||||
import { last } from "lodash/fp"
|
||||
import { pipe } from "../common/core"
|
||||
import { XCircleIcon } from "../common/Icons"
|
||||
|
||||
export let components = []
|
||||
export let currentComponent
|
||||
export let onSelect = () => {}
|
||||
export let level = 0
|
||||
export let onDeleteComponent
|
||||
|
||||
|
||||
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 =>
|
||||
pipe(
|
||||
name,
|
||||
[get_name, capitalise]
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
|
@ -24,7 +28,12 @@
|
|||
class="item"
|
||||
class:selected={currentComponent === component}
|
||||
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>
|
||||
|
||||
{#if component._children}
|
||||
|
@ -32,7 +41,8 @@
|
|||
components={component._children}
|
||||
{currentComponent}
|
||||
{onSelect}
|
||||
level={level + 1} />
|
||||
level={level + 1}
|
||||
{onDeleteComponent} />
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
|
@ -45,14 +55,39 @@
|
|||
margin: 0;
|
||||
}
|
||||
.item {
|
||||
display: block;
|
||||
padding: 11px 67px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 11px 5px 11px 67px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.item > span {
|
||||
width: 1px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.item > button {
|
||||
display: none;
|
||||
height: 20px;
|
||||
color: var(--slate)
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
|
||||
background: #fafafa;
|
||||
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 {
|
||||
color: var(--button-text);
|
||||
background: var(--background-button) !important;
|
||||
|
|
|
@ -11,15 +11,17 @@
|
|||
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
||||
|
||||
export let event
|
||||
export let eventOptions
|
||||
export let eventOptions = []
|
||||
export let open
|
||||
export let onClose
|
||||
export let onPropChanged
|
||||
|
||||
let eventType = "onClick"
|
||||
let eventType = ""
|
||||
let draftEventHandler = { parameters: [] }
|
||||
|
||||
$: eventData = event || { handlers: [] }
|
||||
$: if (!eventOptions.includes(eventType) && eventOptions.length > 0)
|
||||
eventType = eventOptions[0].name
|
||||
|
||||
const closeModal = () => {
|
||||
onClose()
|
||||
|
@ -74,7 +76,7 @@
|
|||
<h5>Event Type</h5>
|
||||
{@html getIcon('info', 20)}
|
||||
</header>
|
||||
<Select :value={eventType}>
|
||||
<Select bind:value={eventType}>
|
||||
{#each eventOptions as option}
|
||||
<option value={option.name}>{option.name}</option>
|
||||
{/each}
|
||||
|
|
|
@ -34,16 +34,10 @@
|
|||
let selectedEvent = null
|
||||
|
||||
$: {
|
||||
events = Object.keys(component)
|
||||
.filter(key => findType(key) === EVENT_TYPE)
|
||||
.map(key => ({ name: key, handlers: component[key] }))
|
||||
}
|
||||
|
||||
function findType(propName) {
|
||||
if (!component._component) return
|
||||
return components.find(({ name }) => name === component._component).props[
|
||||
propName
|
||||
]
|
||||
const componentDefinition = components.find(c => c.name === component._component)
|
||||
events = Object.keys(componentDefinition.props)
|
||||
.filter(propName => componentDefinition.props[propName].type === EVENT_TYPE)
|
||||
.map(propName => ({ name: propName, handlers: (component[propName] || []) }))
|
||||
}
|
||||
|
||||
const openModal = event => {
|
||||
|
|
|
@ -10,8 +10,12 @@
|
|||
import SettingsView from "./SettingsView.svelte"
|
||||
import PageView from "./PageView.svelte"
|
||||
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
|
||||
import ConfirmDialog from "../common/ConfirmDialog.svelte"
|
||||
import { last } from "lodash/fp"
|
||||
|
||||
let newComponentPicker
|
||||
let confirmDeleteDialog
|
||||
let componentToDelete = ""
|
||||
|
||||
const newComponent = () => {
|
||||
newComponentPicker.show()
|
||||
|
@ -21,6 +25,14 @@
|
|||
const settings = () => {
|
||||
settingsView.show()
|
||||
}
|
||||
|
||||
const confirmDeleteComponent = component => {
|
||||
componentToDelete = component
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
const lastPartOfName = c => c ? c.split("/") : ""
|
||||
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
@ -53,6 +65,7 @@
|
|||
<ComponentsHierarchyChildren
|
||||
components={$store.currentPreviewItem.props._children}
|
||||
currentComponent={$store.currentComponentInfo}
|
||||
onDeleteComponent={confirmDeleteComponent}
|
||||
onSelect={store.selectComponent}
|
||||
level={-2} />
|
||||
{/if}
|
||||
|
@ -101,6 +114,13 @@
|
|||
<NewComponent bind:this={newComponentPicker} />
|
||||
<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>
|
||||
button {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"dependencies": {
|
||||
"@nx-js/compiler-util": "^2.0.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"deep-equal": "^2.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"lunr": "^2.3.5",
|
||||
"regexparam": "^1.3.0",
|
||||
|
|
|
@ -1,74 +1,46 @@
|
|||
import { writable } from "svelte/store"
|
||||
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 { createTreeNode } from "./render/renderComponent"
|
||||
import { createTreeNode } from "./render/prepareRenderComponent"
|
||||
import { screenRouter } from "./render/screenRouter"
|
||||
import { createStateManager } from "./state/stateManager"
|
||||
|
||||
export const createApp = (
|
||||
document,
|
||||
componentLibraries,
|
||||
frontendDefinition,
|
||||
backendDefinition,
|
||||
user,
|
||||
uiFunctions
|
||||
uiFunctions,
|
||||
window
|
||||
) => {
|
||||
const coreApi = createCoreApi(backendDefinition, user)
|
||||
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
|
||||
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 currentScreenStore
|
||||
let currentScreenUbsubscribe
|
||||
let currentUrl
|
||||
let screenStateManager
|
||||
|
||||
const onScreenSlotRendered = screenSlotNode => {
|
||||
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]
|
||||
const initialiseChildParams = getInitialiseParams(screenSlotNode)
|
||||
const initialiseChildParams = getAttchChildrenParams(screenSlotNode)
|
||||
attachChildren(initialiseChildParams)(screenSlotNode.rootElement, {
|
||||
hydrate: true,
|
||||
force: true,
|
||||
})
|
||||
if (currentScreenUbsubscribe) currentScreenUbsubscribe()
|
||||
currentScreenUbsubscribe = unsubscribe
|
||||
currentScreenStore = store
|
||||
if (screenStateManager) screenStateManager.destroy()
|
||||
screenStateManager = stateManager
|
||||
currentUrl = url
|
||||
}
|
||||
|
||||
|
@ -76,46 +48,28 @@ export const createApp = (
|
|||
routeTo(currentUrl || window.location.pathname)
|
||||
}
|
||||
|
||||
const attachChildrenParams = store => {
|
||||
let currentState = null
|
||||
const unsubscribe = store.subscribe(s => {
|
||||
currentState = s
|
||||
})
|
||||
|
||||
const attachChildrenParams = stateManager => {
|
||||
const getInitialiseParams = treeNode => ({
|
||||
bb: getBbClientApi,
|
||||
coreApi,
|
||||
store,
|
||||
document,
|
||||
componentLibraries,
|
||||
frontendDefinition,
|
||||
uiFunctions,
|
||||
treeNode,
|
||||
onScreenSlotRendered,
|
||||
setupState: stateManager.setup,
|
||||
getCurrentState: stateManager.getCurrentState,
|
||||
})
|
||||
|
||||
const getBbClientApi = (treeNode, componentProps) => {
|
||||
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 }
|
||||
return getInitialiseParams
|
||||
}
|
||||
|
||||
let rootTreeNode
|
||||
const pageStateManager = createStateManager({
|
||||
store: writable({ _bbuser: user }),
|
||||
coreApi,
|
||||
frontendDefinition,
|
||||
componentLibraries,
|
||||
uiFunctions,
|
||||
onScreenSlotRendered,
|
||||
})
|
||||
|
||||
const initialisePage = (page, target, urlPath) => {
|
||||
currentUrl = urlPath
|
||||
|
@ -125,7 +79,7 @@ export const createApp = (
|
|||
_children: [page.props],
|
||||
}
|
||||
rootTreeNode.rootElement = target
|
||||
const { getInitialiseParams } = attachChildrenParams(pageStore)
|
||||
const getInitialiseParams = attachChildrenParams(pageStateManager)
|
||||
const initChildParams = getInitialiseParams(rootTreeNode)
|
||||
|
||||
attachChildren(initChildParams)(target, {
|
||||
|
@ -137,8 +91,8 @@ export const createApp = (
|
|||
}
|
||||
return {
|
||||
initialisePage,
|
||||
screenStore: () => currentScreenStore,
|
||||
pageStore: () => pageStore,
|
||||
screenStore: () => screenStateManager.store,
|
||||
pageStore: () => pageStateManager.store,
|
||||
routeTo: () => routeTo,
|
||||
rootNode: () => rootTreeNode,
|
||||
}
|
||||
|
|
|
@ -43,12 +43,12 @@ export const loadBudibase = async (opts) => {
|
|||
componentLibraries[builtinLibName] = builtins(_window)
|
||||
|
||||
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
|
||||
_window.document,
|
||||
componentLibraries,
|
||||
frontendDefinition,
|
||||
backendDefinition,
|
||||
user,
|
||||
uiFunctions || {}
|
||||
uiFunctions || {},
|
||||
_window
|
||||
)
|
||||
|
||||
const route = _window.location
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import { setupBinding } from "../state/stateBinding"
|
||||
import { split, last } from "lodash/fp"
|
||||
import { $ } from "../core/common"
|
||||
import { renderComponent } from "./renderComponent"
|
||||
import { prepareRenderComponent } from "./prepareRenderComponent"
|
||||
import { isScreenSlot } from "./builtinComponents"
|
||||
import deepEqual from "deep-equal"
|
||||
|
||||
export const attachChildren = initialiseOpts => (htmlElement, options) => {
|
||||
const {
|
||||
uiFunctions,
|
||||
bb,
|
||||
coreApi,
|
||||
store,
|
||||
componentLibraries,
|
||||
treeNode,
|
||||
frontendDefinition,
|
||||
onScreenSlotRendered,
|
||||
setupState,
|
||||
getCurrentState,
|
||||
} = initialiseOpts
|
||||
|
||||
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}`)
|
||||
|
||||
const renderedComponents = []
|
||||
const childNodes = []
|
||||
for (let childProps of treeNode.props._children) {
|
||||
const { componentName, libName } = splitName(childProps._component)
|
||||
|
||||
if (!componentName || !libName) return
|
||||
|
||||
const { initialProps, bind } = setupBinding(
|
||||
store,
|
||||
childProps,
|
||||
coreApi,
|
||||
frontendDefinition.appRootPath
|
||||
)
|
||||
|
||||
const componentConstructor = componentLibraries[libName][componentName]
|
||||
|
||||
const renderedComponentsThisIteration = renderComponent({
|
||||
const childNodesThisIteration = prepareRenderComponent({
|
||||
props: childProps,
|
||||
parentNode: treeNode,
|
||||
componentConstructor,
|
||||
uiFunctions,
|
||||
htmlElement,
|
||||
anchor,
|
||||
initialProps,
|
||||
bb,
|
||||
getCurrentState
|
||||
})
|
||||
|
||||
if (
|
||||
onScreenSlotRendered &&
|
||||
isScreenSlot(childProps._component) &&
|
||||
renderedComponentsThisIteration.length > 0
|
||||
) {
|
||||
for (let childNode of childNodesThisIteration) {
|
||||
childNodes.push(childNode)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
onScreenSlotRendered(renderedComponentsThisIteration[0])
|
||||
onScreenSlotRendered(screenSlot)
|
||||
}
|
||||
|
||||
for (let comp of renderedComponentsThisIteration) {
|
||||
comp.unsubscribe = bind(comp.component)
|
||||
renderedComponents.push(comp)
|
||||
}
|
||||
}
|
||||
treeNode.children = childNodes
|
||||
|
||||
treeNode.children = renderedComponents
|
||||
|
||||
return renderedComponents
|
||||
return childNodes
|
||||
}
|
||||
|
||||
const splitName = fullname => {
|
||||
|
@ -90,3 +84,19 @@ const splitName = fullname => {
|
|||
|
||||
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,
|
||||
uiFunctions,
|
||||
htmlElement,
|
||||
anchor,
|
||||
props,
|
||||
initialProps,
|
||||
bb,
|
||||
parentNode,
|
||||
getCurrentState,
|
||||
}) => {
|
||||
const func = initialProps._id ? uiFunctions[initialProps._id] : undefined
|
||||
const func = props._id ? uiFunctions[props._id] : undefined
|
||||
|
||||
const parentContext = (parentNode && parentNode.context) || {}
|
||||
|
||||
let renderedNodes = []
|
||||
const render = context => {
|
||||
let nodesToRender = []
|
||||
const createNodeAndRender = context => {
|
||||
let componentContext = parentContext
|
||||
if (context) {
|
||||
componentContext = { ...componentContext }
|
||||
componentContext = { ...context }
|
||||
componentContext.$parent = parentContext
|
||||
}
|
||||
|
||||
|
@ -24,33 +23,31 @@ export const renderComponent = ({
|
|||
thisNode.context = componentContext
|
||||
thisNode.parentNode = parentNode
|
||||
thisNode.props = props
|
||||
nodesToRender.push(thisNode)
|
||||
|
||||
parentNode.children.push(thisNode)
|
||||
renderedNodes.push(thisNode)
|
||||
|
||||
initialProps._bb = bb(thisNode, props)
|
||||
|
||||
thisNode.render = initialProps => {
|
||||
thisNode.component = new componentConstructor({
|
||||
target: htmlElement,
|
||||
props: initialProps,
|
||||
hydrate: false,
|
||||
anchor,
|
||||
})
|
||||
thisNode.rootElement =
|
||||
htmlElement.children[htmlElement.children.length - 1]
|
||||
|
||||
thisNode.rootElement = htmlElement.children[htmlElement.children.length - 1]
|
||||
|
||||
if (initialProps._id && thisNode.rootElement) {
|
||||
thisNode.rootElement.classList.add(`pos-${initialProps._id}`)
|
||||
if (props._id && thisNode.rootElement) {
|
||||
thisNode.rootElement.classList.add(`pos-${props._id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (func) {
|
||||
func(render, parentContext)
|
||||
func(createNodeAndRender, parentContext, getCurrentState())
|
||||
} else {
|
||||
render()
|
||||
createNodeAndRender()
|
||||
}
|
||||
|
||||
return renderedNodes
|
||||
return nodesToRender
|
||||
}
|
||||
|
||||
export const createTreeNode = () => ({
|
||||
|
@ -59,8 +56,10 @@ export const createTreeNode = () => ({
|
|||
rootElement: null,
|
||||
parentNode: null,
|
||||
children: [],
|
||||
bindings: [],
|
||||
component: null,
|
||||
unsubscribe: () => {},
|
||||
render: () => {},
|
||||
get destroy() {
|
||||
const node = this
|
||||
return () => {
|
||||
|
@ -71,6 +70,10 @@ export const createTreeNode = () => ({
|
|||
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 === "_children" ||
|
||||
propName === "_id" ||
|
||||
propName === "_style"
|
||||
propName === "_style" ||
|
||||
propName === "_code"
|
||||
|
||||
export const setupBinding = (store, rootProps, coreApi, context, rootPath) => {
|
||||
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 { EVENT_TYPE_MEMBER_NAME } from "../src/state/eventHandlers"
|
||||
|
||||
describe("initialiseApp (binding)", () => {
|
||||
it("should populate root element prop from store value", async () => {
|
||||
|
@ -169,4 +170,177 @@ describe("initialiseApp (binding)", () => {
|
|||
"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: [],
|
||||
triggers: [],
|
||||
})
|
||||
setComponentCodeMeta(page, screens)
|
||||
const app = await loadBudibase({
|
||||
componentLibraries: allLibs(dom.window),
|
||||
window: dom.window,
|
||||
|
@ -47,8 +48,11 @@ export const timeout = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|||
export const walkComponentTree = (node, action) => {
|
||||
action(node)
|
||||
|
||||
if (node.children) {
|
||||
for (let child of node.children) {
|
||||
// works for nodes or props
|
||||
const children = node.children || node._children
|
||||
|
||||
if (children) {
|
||||
for (let child of children) {
|
||||
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) => {
|
||||
window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
|
||||
componentLibraries: [],
|
||||
|
@ -148,6 +168,29 @@ const maketestlib = window => ({
|
|||
set(opts.props)
|
||||
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 = {
|
||||
|
@ -162,4 +205,15 @@ const uiFunctions = {
|
|||
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",
|
||||
"h1": {
|
||||
"name": "H1",
|
||||
"description": "An HTML H1 tag",
|
||||
"_generators": {},
|
||||
"Body1": {
|
||||
"name": "Body1",
|
||||
"description": "Sets the font properties as Roboto Body 1",
|
||||
"props": {
|
||||
"text": "string",
|
||||
"className": "string"
|
||||
"text": "string"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
"button": {
|
||||
"name": "Button",
|
||||
"description": "A button",
|
||||
"Body2": {
|
||||
"name": "Body2",
|
||||
"description": "Sets the font properties as Roboto Body 2",
|
||||
"props": {
|
||||
"raised": "bool"
|
||||
"text": "string"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
"Button": {
|
||||
"name": "Button",
|
||||
"description": "A Material Design button with different variations. It renders as an anchor if href is passed to it.",
|
||||
"props": {
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072",
|
||||
"dependencies": {
|
||||
"@material/checkbox": "^4.0.0",
|
||||
"@material/data-table": "4.0.0",
|
||||
"@material/form-field": "^4.0.0",
|
||||
"@material/radio": "^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>
|
||||
import { setContext, getContext } from "svelte"
|
||||
import Icon from "../Icon.svelte"
|
||||
import ripple from "../Ripple.js"
|
||||
import Icon from "../Common/Icon.svelte"
|
||||
import ripple from "../Common/Ripple.js"
|
||||
import ClassBuilder from "../ClassBuilder.js"
|
||||
|
||||
const cb = new ClassBuilder("button", ["primary", "medium"])
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import "./_index.scss"
|
||||
export { default as button } from "./Button.svelte"
|
||||
export { default as Button } from "./Button.svelte"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import "./_style.scss";
|
||||
export { default as checkbox } from "./Checkbox.svelte";
|
||||
export { default as checkboxgroup } from "./CheckboxGroup.svelte";
|
||||
export { default as Checkbox } from "./Checkbox.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 label = ""
|
||||
export let names = "radios"
|
||||
export let name = "radios"
|
||||
export let checked = false
|
||||
export let disabled = false
|
||||
export let alignEnd = false
|
||||
|
@ -37,7 +37,7 @@
|
|||
{id}
|
||||
class={cb.elem`native-control`}
|
||||
type="radio"
|
||||
{names}
|
||||
{name}
|
||||
{checked}
|
||||
{disabled}
|
||||
on:click={onClick} />
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import "./_style.scss";
|
||||
export { default as radiobutton } from "./Radiobutton.svelte";
|
||||
export { default as radiobuttongroup } from "./RadiobuttonGroup.svelte";
|
||||
export { default as Radiobutton } from "./Radiobutton.svelte";
|
||||
export { default as Radiobuttongroup } from "./RadiobuttonGroup.svelte";
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
import { props } from "./props"
|
||||
let _bb
|
||||
const {
|
||||
h1,
|
||||
overline,
|
||||
button,
|
||||
textfield,
|
||||
checkbox,
|
||||
checkboxgroup,
|
||||
radiobutton,
|
||||
radiobuttongroup,
|
||||
H1,
|
||||
Overline,
|
||||
Button,
|
||||
Textfield,
|
||||
Checkbox,
|
||||
Checkboxgroup,
|
||||
Radiobutton,
|
||||
Radiobuttongroup,
|
||||
Datatable,
|
||||
} = props
|
||||
|
||||
let currentComponent
|
||||
|
@ -22,14 +23,15 @@
|
|||
props: {
|
||||
_component: "testcomponents/rootComponent",
|
||||
_children: [
|
||||
h1,
|
||||
overline,
|
||||
button,
|
||||
textfield,
|
||||
checkbox,
|
||||
checkboxgroup,
|
||||
radiobutton,
|
||||
radiobuttongroup,
|
||||
H1,
|
||||
Overline,
|
||||
Button,
|
||||
Textfield,
|
||||
Checkbox,
|
||||
Checkboxgroup,
|
||||
Radiobutton,
|
||||
Radiobuttongroup,
|
||||
Datatable,
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
|
||||
export const props = {
|
||||
h1: {
|
||||
H1: {
|
||||
_component: "@budibase/materialdesign-components/H1",
|
||||
_children: [],
|
||||
text: "Im a big header",
|
||||
},
|
||||
overline: {
|
||||
Overline: {
|
||||
_component: "@budibase/materialdesign-components/Overline",
|
||||
_children: [],
|
||||
text: "Im a wee overline",
|
||||
},
|
||||
button: {
|
||||
_component: "@budibase/materialdesign-components/button",
|
||||
Button: {
|
||||
_component: "@budibase/materialdesign-components/Button",
|
||||
_children: [],
|
||||
variant: "raised",
|
||||
colour: "secondary",
|
||||
|
@ -24,13 +24,13 @@ export const props = {
|
|||
disabled: false,
|
||||
onClick: () => alert`Button Clicked`,
|
||||
},
|
||||
icon: {
|
||||
_component: "@budibase/materialdesign-components/icon",
|
||||
Icon: {
|
||||
_component: "@budibase/materialdesign-components/Icon",
|
||||
_children: [],
|
||||
icon: "",
|
||||
},
|
||||
textfield: {
|
||||
_component: "@budibase/materialdesign-components/textfield",
|
||||
Textfield: {
|
||||
_component: "@budibase/materialdesign-components/Textfield",
|
||||
_children: [],
|
||||
label: "First",
|
||||
colour: "secondary",
|
||||
|
@ -39,15 +39,15 @@ export const props = {
|
|||
helperText: "Add Surname",
|
||||
onChange: text => console.log("Text: ", text),
|
||||
},
|
||||
checkbox: {
|
||||
_component: "@budibase/materialdesign-components/checkbox",
|
||||
Checkbox: {
|
||||
_component: "@budibase/materialdesign-components/Checkbox",
|
||||
_children: [],
|
||||
id: "test-check",
|
||||
label: "Check Yo Self",
|
||||
onClick: () => alert`Before ya reck yo'self`,
|
||||
},
|
||||
checkboxgroup: {
|
||||
_component: "@budibase/materialdesign-components/checkboxgroup",
|
||||
Checkboxgroup: {
|
||||
_component: "@budibase/materialdesign-components/Checkboxgroup",
|
||||
_children: [],
|
||||
label: "Whats your favourite?",
|
||||
items: [
|
||||
|
@ -57,15 +57,15 @@ export const props = {
|
|||
],
|
||||
onChange: selectedItems => console.log(selectedItems),
|
||||
},
|
||||
radiobutton: {
|
||||
_component: "@budibase/materialdesign-components/radiobutton",
|
||||
Radiobutton: {
|
||||
_component: "@budibase/materialdesign-components/Radiobutton",
|
||||
_children: [],
|
||||
label: "Hi radio",
|
||||
alignEnd: true,
|
||||
onClick: () => alert`Roger That`,
|
||||
},
|
||||
radiobuttongroup: {
|
||||
_component: "@budibase/materialdesign-components/radiobuttongroup",
|
||||
Radiobuttongroup: {
|
||||
_component: "@budibase/materialdesign-components/Radiobuttongroup",
|
||||
_children: [],
|
||||
label: "Preferred method of contact: ",
|
||||
orientation: "column",
|
||||
|
@ -75,5 +75,9 @@ export const props = {
|
|||
{ label: "Social Media", value: 3 },
|
||||
],
|
||||
onChange: selected => console.log(selected),
|
||||
}
|
||||
},
|
||||
Datatable: {
|
||||
_component: "@budibase/materialdesign-components/Datatable",
|
||||
_children: [],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
import {
|
||||
H1,
|
||||
Overline,
|
||||
button,
|
||||
icon,
|
||||
textfield,
|
||||
checkbox,
|
||||
checkboxgroup,
|
||||
radiobutton,
|
||||
radiobuttongroup,
|
||||
Button,
|
||||
Icon,
|
||||
Textfield,
|
||||
Checkbox,
|
||||
Checkboxgroup,
|
||||
Radiobutton,
|
||||
Radiobuttongroup,
|
||||
Datatable,
|
||||
} from "@BBMD"
|
||||
|
||||
export default {
|
||||
H1,
|
||||
Overline,
|
||||
button,
|
||||
icon,
|
||||
textfield,
|
||||
checkbox,
|
||||
checkboxgroup,
|
||||
radiobutton,
|
||||
radiobuttongroup,
|
||||
Button,
|
||||
Icon,
|
||||
Textfield,
|
||||
Checkbox,
|
||||
Checkboxgroup,
|
||||
Radiobutton,
|
||||
Radiobuttongroup,
|
||||
Datatable,
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import FloatingLabel from "../Common/FloatingLabel.svelte"
|
||||
import HelperText from "./HelperText.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"])
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import "./_index.scss"
|
||||
export { default as textfield } from "./Textfield.svelte"
|
||||
export { default as Textfield } from "./Textfield.svelte"
|
|
@ -1,13 +1,13 @@
|
|||
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 H2 } from "./H2.svelte";
|
||||
export { default as H3 } from "./H3.svelte";
|
||||
export { default as H4 } from "./H4.svelte";
|
||||
export { default as H5 } from "./H5.svelte";
|
||||
export { default as H6 } from "./H6.svelte";
|
||||
export { default as Body1 } from "./Body1.svelte";
|
||||
export { default as Body2 } from "./Body2.svelte";
|
||||
export { default as Overline } from "./Overline.svelte";
|
||||
export { default as Sub1 } from "./Sub1.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";
|
||||
|
||||
export { button } from "./Button"
|
||||
export { default as icon } from "./Icon.svelte"
|
||||
export { textfield } from "./Textfield"
|
||||
export { Button } from "./Button"
|
||||
export { default as Icon } from "./Common/Icon.svelte"
|
||||
export { Textfield } from "./Textfield"
|
||||
export * from "./Typography"
|
||||
export { checkbox, checkboxgroup } from "./Checkbox"
|
||||
export { radiobutton, radiobuttongroup } from "./Radiobutton"
|
||||
export { Checkbox, Checkboxgroup } from "./Checkbox"
|
||||
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 { convertCssToFiles } = require("./convertCssToFiles")
|
||||
const publicPath = require("./publicPath")
|
||||
const deleteCodeMeta = require("./deleteCodeMeta")
|
||||
|
||||
module.exports = async (config, appname, pageName, pkg) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
|
@ -155,6 +156,8 @@ const savePageJson = async (appPath, pageName, pkg) => {
|
|||
delete pkg.page._screens
|
||||
}
|
||||
|
||||
deleteCodeMeta(pkg.page.props)
|
||||
|
||||
await writeJSON(pageFile, pkg.page, {
|
||||
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 listScreens = require("./listScreens")
|
||||
const saveBackend = require("./saveBackend")
|
||||
const deleteCodeMeta = require("./deleteCodeMeta")
|
||||
|
||||
module.exports.buildPage = buildPage
|
||||
module.exports.listScreens = listScreens
|
||||
|
@ -58,12 +59,15 @@ module.exports.saveScreen = async (config, appname, pagename, screen) => {
|
|||
if (screen._css) {
|
||||
delete screen._css
|
||||
}
|
||||
|
||||
deleteCodeMeta(screen.props)
|
||||
|
||||
await writeJSON(compPath, screen, {
|
||||
encoding: "utf8",
|
||||
flag: "w",
|
||||
spaces: 2,
|
||||
})
|
||||
return screen;
|
||||
return screen
|
||||
}
|
||||
|
||||
module.exports.renameScreen = async (
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
return all
|
||||
}
|
||||
|
||||
$: if (_bb.props._children.length > 0)
|
||||
$: if(_bb.props._children && _bb.props._children.length > 0)
|
||||
theButton && _bb.attachChildren(theButton)
|
||||
|
||||
$: {
|
||||
|
@ -74,7 +74,7 @@
|
|||
disabled={disabled || false}
|
||||
on:click={clickHandler}
|
||||
style={buttonStyles}>
|
||||
{#if _bb.props._children.length === 0}{contentText}{/if}
|
||||
{#if !_bb.props._children || _bb.props._children.length === 0}{contentText}{/if}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
|
|
Loading…
Reference in New Issue