Page Layout & Screen restructure (#87)
* refactoring server for screens & page layout restructure * Disable API calls, UI placeholders. * buildPropsHierarchy is gone & screen has url * Recent changes. * router * router * updated git-ignore to reinclude server/utilities/builder * modified cli - budi new create new file structure * Fix uuid import. * prettier fixes * prettier fixes * prettier fixes * page/screen restructure.. broken tests * all tests passing at last * screen routing tests * Working screen editor and preview. * Render page previews to the screen. * Key input lists to ensure new array references when updating styles. * Ensure the iframe html and body fills the container. * Save screens via the API. * Get all save APIs almost working. * Write pages.json to disk. * Use correct API endpoint for saving styles. * Differentiate between saving properties of screens and pages. * Add required fields to default pages layouts. * Add _css default property to newly created screens. * Add default code property. * page layout / screens - app output Co-authored-by: pngwn <pnda007@gmail.com>
This commit is contained in:
parent
f59eedc21f
commit
8a80d8801a
File diff suppressed because one or more lines are too long
|
@ -7,7 +7,6 @@ import {
|
|||
last,
|
||||
keys,
|
||||
concat,
|
||||
keyBy,
|
||||
find,
|
||||
isEmpty,
|
||||
values,
|
||||
|
@ -21,7 +20,6 @@ import {
|
|||
} from "../common/core"
|
||||
import { writable } from "svelte/store"
|
||||
import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesObject"
|
||||
import { buildPropsHierarchy } from "../userInterface/pagesParsing/buildPropsHierarchy"
|
||||
import api from "./api"
|
||||
import {
|
||||
isRootComponent,
|
||||
|
@ -29,8 +27,8 @@ import {
|
|||
} from "../userInterface/pagesParsing/searchComponents"
|
||||
import { rename } from "../userInterface/pagesParsing/renameScreen"
|
||||
import {
|
||||
getNewComponentInfo,
|
||||
getScreenInfo,
|
||||
getNewScreen,
|
||||
createProps,
|
||||
} from "../userInterface/pagesParsing/createProps"
|
||||
import {
|
||||
loadLibs,
|
||||
|
@ -38,8 +36,8 @@ import {
|
|||
loadGeneratorLibs,
|
||||
} from "./loadComponentLibraries"
|
||||
import { buildCodeForScreens } from "./buildCodeForScreens"
|
||||
import { uuid } from "./uuid"
|
||||
import { generate_screen_css } from "./generate_css"
|
||||
// import { uuid } from "./uuid"
|
||||
|
||||
let appname = ""
|
||||
|
||||
|
@ -54,7 +52,7 @@ export const getStore = () => {
|
|||
mainUi: {},
|
||||
unauthenticatedUi: {},
|
||||
components: [],
|
||||
currentFrontEndItem: null,
|
||||
currentPreviewItem: null,
|
||||
currentComponentInfo: null,
|
||||
currentFrontEndType: "none",
|
||||
currentPageName: "",
|
||||
|
@ -113,6 +111,7 @@ export const getStore = () => {
|
|||
store.setComponentProp = setComponentProp(store)
|
||||
store.setComponentStyle = setComponentStyle(store)
|
||||
store.setComponentCode = setComponentCode(store)
|
||||
store.setScreenType = setScreenType(store)
|
||||
return store
|
||||
}
|
||||
|
||||
|
@ -134,6 +133,26 @@ const initialise = (store, initial) => async () => {
|
|||
.get(`/_builder/api/${appname}/appPackage`)
|
||||
.then(r => r.json())
|
||||
|
||||
const [main_screens, unauth_screens] = await Promise.all([
|
||||
api.get(`/_builder/api/${appname}/pages/main/screens`).then(r => r.json()),
|
||||
api
|
||||
.get(`/_builder/api/${appname}/pages/unauthenticated/screens`)
|
||||
.then(r => r.json()),
|
||||
])
|
||||
|
||||
pkg.pages = {
|
||||
componentLibraries: ["@budibase/standard-components"],
|
||||
stylesheets: [],
|
||||
main: {
|
||||
...pkg.pages.main,
|
||||
_screens: Object.values(main_screens),
|
||||
},
|
||||
unauthenticated: {
|
||||
...pkg.pages.unauthenticated,
|
||||
_screens: Object.values(unauth_screens),
|
||||
},
|
||||
}
|
||||
|
||||
initial.libraries = await loadLibs(appname, pkg)
|
||||
initial.generatorLibraries = await loadGeneratorLibs(appname, pkg)
|
||||
initial.loadLibraryUrls = () => loadLibUrls(appname, pkg)
|
||||
|
@ -156,20 +175,21 @@ const initialise = (store, initial) => async () => {
|
|||
}
|
||||
|
||||
store.set(initial)
|
||||
|
||||
return initial
|
||||
}
|
||||
|
||||
const generatorsArray = generators =>
|
||||
pipe(generators, [keys, filter(k => k !== "_lib"), map(k => generators[k])])
|
||||
|
||||
const showSettings = store => show => {
|
||||
const showSettings = store => () => {
|
||||
store.update(s => {
|
||||
s.showSettings = !s.showSettings
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
const useAnalytics = store => useAnalytics => {
|
||||
const useAnalytics = store => () => {
|
||||
store.update(s => {
|
||||
s.useAnalytics = !s.useAnalytics
|
||||
return s
|
||||
|
@ -194,7 +214,7 @@ const newRecord = (store, useRoot) => () => {
|
|||
store.update(s => {
|
||||
s.currentNodeIsNew = true
|
||||
const shadowHierarchy = createShadowHierarchy(s.hierarchy)
|
||||
parent = useRoot
|
||||
const parent = useRoot
|
||||
? shadowHierarchy
|
||||
: getNode(shadowHierarchy, s.currentNode.nodeId)
|
||||
s.errors = []
|
||||
|
@ -223,7 +243,7 @@ const newIndex = (store, useRoot) => () => {
|
|||
s.currentNodeIsNew = true
|
||||
s.errors = []
|
||||
const shadowHierarchy = createShadowHierarchy(s.hierarchy)
|
||||
parent = useRoot
|
||||
const parent = useRoot
|
||||
? shadowHierarchy
|
||||
: getNode(shadowHierarchy, s.currentNode.nodeId)
|
||||
|
||||
|
@ -442,17 +462,21 @@ const saveScreen = store => screen => {
|
|||
}
|
||||
|
||||
const _saveScreen = (store, s, screen) => {
|
||||
const screens = pipe(s.screens, [
|
||||
const screens = pipe(s.pages[s.currentPageName]._screens, [
|
||||
filter(c => c.name !== screen.name),
|
||||
concat([screen]),
|
||||
])
|
||||
|
||||
s.screens = screens
|
||||
s.currentFrontEndItem = screen
|
||||
s.currentComponentInfo = getScreenInfo(s.components, screen)
|
||||
// console.log('saveScreen', screens, screen)
|
||||
s.pages[s.currentPageName]._screens = screens
|
||||
s.screens = s.pages[s.currentPageName]._screens
|
||||
// s.currentPreviewItem = screen
|
||||
// s.currentComponentInfo = screen.props
|
||||
|
||||
api
|
||||
.post(`/_builder/api/${s.appname}/screen`, screen)
|
||||
.post(
|
||||
`/_builder/api/${s.appname}/pages/${s.currentPageName}/screen`,
|
||||
screen
|
||||
)
|
||||
.then(() => savePackage(store, s))
|
||||
|
||||
return s
|
||||
|
@ -460,22 +484,39 @@ const _saveScreen = (store, s, screen) => {
|
|||
|
||||
const _save = (appname, screen, store, s) =>
|
||||
api
|
||||
.post(`/_builder/api/${appname}/screen`, screen)
|
||||
.post(
|
||||
`/_builder/api/${s.appname}/pages/${s.currentPageName}/screen`,
|
||||
screen
|
||||
)
|
||||
.then(() => savePackage(store, s))
|
||||
|
||||
const createScreen = store => (screenName, layoutComponentName) => {
|
||||
const createScreen = store => (screenName, route, layoutComponentName) => {
|
||||
store.update(s => {
|
||||
const newComponentInfo = getNewComponentInfo(
|
||||
const newScreen = getNewScreen(
|
||||
s.components,
|
||||
layoutComponentName,
|
||||
screenName
|
||||
)
|
||||
|
||||
s.currentFrontEndItem = newComponentInfo.component
|
||||
s.currentComponentInfo = newComponentInfo
|
||||
newScreen.route = route
|
||||
s.currentPreviewItem = newScreen
|
||||
s.currentComponentInfo = newScreen.props
|
||||
s.currentFrontEndType = "screen"
|
||||
|
||||
return _saveScreen(store, s, newComponentInfo.component)
|
||||
return _saveScreen(store, s, newScreen)
|
||||
})
|
||||
}
|
||||
|
||||
const setCurrentScreen = store => screenName => {
|
||||
store.update(s => {
|
||||
const screen = getExactComponent(s.screens, screenName)
|
||||
|
||||
s.currentPreviewItem = screen
|
||||
s.currentFrontEndType = "screen"
|
||||
s.currentComponentInfo = screen.props
|
||||
|
||||
setCurrentScreenFunctions(s)
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -506,8 +547,8 @@ const deleteScreen = store => name => {
|
|||
|
||||
s.components = components
|
||||
s.screens = screens
|
||||
if (s.currentFrontEndItem.name === name) {
|
||||
s.currentFrontEndItem = null
|
||||
if (s.currentPreviewItem.name === name) {
|
||||
s.currentPreviewItem = null
|
||||
s.currentFrontEndType = ""
|
||||
}
|
||||
|
||||
|
@ -533,8 +574,8 @@ const renameScreen = store => (oldname, newname) => {
|
|||
|
||||
s.screens = screens
|
||||
s.pages = pages
|
||||
if (s.currentFrontEndItem.name === oldname)
|
||||
s.currentFrontEndItem.name = newname
|
||||
if (s.currentPreviewItem.name === oldname)
|
||||
s.currentPreviewItem.name = newname
|
||||
|
||||
const saveAllChanged = async () => {
|
||||
for (let screenName of changedScreens) {
|
||||
|
@ -578,13 +619,6 @@ const addComponentLibrary = store => async lib => {
|
|||
|
||||
const success = response.status === 200
|
||||
|
||||
const error =
|
||||
response.status === 404
|
||||
? `Could not find library ${lib}`
|
||||
: success
|
||||
? ""
|
||||
: response.statusText
|
||||
|
||||
const components = success ? await response.json() : []
|
||||
|
||||
store.update(s => {
|
||||
|
@ -654,89 +688,50 @@ const refreshComponents = store => async () => {
|
|||
})
|
||||
}
|
||||
|
||||
const savePackage = (store, s) => {
|
||||
const appDefinition = {
|
||||
hierarchy: s.hierarchy,
|
||||
triggers: s.triggers,
|
||||
actions: keyBy("name")(s.actions),
|
||||
props: {
|
||||
main: buildPropsHierarchy(s.components, s.screens, s.pages.main.appBody),
|
||||
unauthenticated: buildPropsHierarchy(
|
||||
s.components,
|
||||
s.screens,
|
||||
s.pages.unauthenticated.appBody
|
||||
),
|
||||
const savePackage = async (store, s) => {
|
||||
const page = s.pages[s.currentPageName]
|
||||
|
||||
await api.post(`/_builder/api/${appname}/pages/${s.currentPageName}`, {
|
||||
appDefinition: {
|
||||
hierarchy: s.hierarchy,
|
||||
actions: s.actions,
|
||||
triggers: s.triggers,
|
||||
},
|
||||
uiFunctions: buildCodeForScreens(s.screens),
|
||||
}
|
||||
|
||||
const data = {
|
||||
appDefinition,
|
||||
accessLevels: s.accessLevels,
|
||||
pages: s.pages,
|
||||
}
|
||||
|
||||
return api.post(`/_builder/api/${s.appname}/appPackage`, data)
|
||||
}
|
||||
|
||||
const setCurrentScreen = store => screenName => {
|
||||
store.update(s => {
|
||||
const screen = getExactComponent(s.screens, screenName)
|
||||
s.currentFrontEndItem = screen
|
||||
s.currentFrontEndType = "screen"
|
||||
s.currentComponentInfo = getScreenInfo(s.components, screen)
|
||||
setCurrentScreenFunctions(s)
|
||||
return s
|
||||
page: { componentLibraries: s.pages.componentLibraries, ...page },
|
||||
uiFunctions: "{'1234':() => 'test return'}",
|
||||
props: page.props,
|
||||
screens: page.screens,
|
||||
})
|
||||
}
|
||||
|
||||
const setCurrentPage = store => pageName => {
|
||||
store.update(s => {
|
||||
const current_screens = s.pages[pageName]._screens
|
||||
|
||||
s.currentFrontEndType = "page"
|
||||
s.currentPageName = pageName
|
||||
s.screens = Array.isArray(current_screens)
|
||||
? current_screens
|
||||
: Object.values(current_screens)
|
||||
s.currentComponentInfo = s.pages[pageName].props
|
||||
s.currentPreviewItem = s.pages[pageName]
|
||||
|
||||
setCurrentScreenFunctions(s)
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
const addChildComponent = store => component => {
|
||||
const addChildComponent = store => componentName => {
|
||||
store.update(s => {
|
||||
const newComponent = getNewComponentInfo(s.components, component)
|
||||
const component = s.components.find(c => c.name === componentName)
|
||||
const newComponent = createProps(component)
|
||||
|
||||
let children = s.currentComponentInfo.component
|
||||
? s.currentComponentInfo.component.props._children
|
||||
: s.currentComponentInfo._children
|
||||
|
||||
const component_definition = Object.assign(
|
||||
cloneDeep(newComponent.fullProps),
|
||||
{
|
||||
_component: component,
|
||||
_styles: { position: {}, layout: {} },
|
||||
_id: uuid(),
|
||||
}
|
||||
s.currentComponentInfo._children = s.currentComponentInfo._children.concat(
|
||||
newComponent.props
|
||||
)
|
||||
|
||||
if (children) {
|
||||
if (s.currentComponentInfo.component) {
|
||||
s.currentComponentInfo.component.props._children = children.concat(
|
||||
component_definition
|
||||
)
|
||||
} else {
|
||||
s.currentComponentInfo._children = children.concat(component_definition)
|
||||
}
|
||||
} else {
|
||||
if (s.currentComponentInfo.component) {
|
||||
s.currentComponentInfo.component.props._children = [
|
||||
component_definition,
|
||||
]
|
||||
} else {
|
||||
s.currentComponentInfo._children = [component_definition]
|
||||
}
|
||||
}
|
||||
|
||||
_saveScreen(store, s, s.currentFrontEndItem)
|
||||
|
||||
_saveScreen(store, s, s.currentFrontEndItem)
|
||||
savePackage(store, s)
|
||||
|
||||
return s
|
||||
})
|
||||
|
@ -753,7 +748,11 @@ const setComponentProp = store => (name, value) => {
|
|||
store.update(s => {
|
||||
const current_component = s.currentComponentInfo
|
||||
s.currentComponentInfo[name] = value
|
||||
_saveScreen(store, s, s.currentFrontEndItem)
|
||||
|
||||
s.currentFrontEndType === "page"
|
||||
? savePackage(store, s, s.currentPreviewItem)
|
||||
: _saveScreen(store, s, s.currentPreviewItem)
|
||||
|
||||
s.currentComponentInfo = current_component
|
||||
return s
|
||||
})
|
||||
|
@ -765,13 +764,14 @@ const setComponentStyle = store => (type, name, value) => {
|
|||
s.currentComponentInfo._styles = {}
|
||||
}
|
||||
s.currentComponentInfo._styles[type][name] = value
|
||||
s.currentFrontEndItem._css = generate_screen_css(
|
||||
s.currentFrontEndItem.props._children
|
||||
)
|
||||
s.currentPreviewItem._css = generate_screen_css([
|
||||
s.currentPreviewItem.props,
|
||||
])
|
||||
|
||||
// save without messing with the store
|
||||
_save(s.appname, s.currentFrontEndItem, store, s)
|
||||
|
||||
s.currentFrontEndType === "page"
|
||||
? savePackage(store, s, s.currentPreviewItem)
|
||||
: _save(s.appname, s.currentPreviewItem, store, s)
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
@ -782,7 +782,7 @@ const setComponentCode = store => code => {
|
|||
|
||||
setCurrentScreenFunctions(s)
|
||||
// save without messing with the store
|
||||
_save(s.appname, s.currentFrontEndItem, store, s)
|
||||
_save(s.appname, s.currentPreviewItem, store, s)
|
||||
|
||||
return s
|
||||
})
|
||||
|
@ -790,7 +790,22 @@ const setComponentCode = store => code => {
|
|||
|
||||
const setCurrentScreenFunctions = s => {
|
||||
s.currentScreenFunctions =
|
||||
s.currentFrontEndItem === "screen"
|
||||
? buildCodeForScreens([s.currentFrontEndItem])
|
||||
s.currentPreviewItem === "screen"
|
||||
? buildCodeForScreens([s.currentPreviewItem])
|
||||
: "({});"
|
||||
}
|
||||
|
||||
const setScreenType = store => type => {
|
||||
store.update(s => {
|
||||
s.currentFrontEndType = type
|
||||
|
||||
const pageOrScreen =
|
||||
type === "page"
|
||||
? s.pages[s.currentPageName]
|
||||
: s.pages[s.currentPageName]._screens[0]
|
||||
|
||||
s.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
|
||||
s.currentPreviewItem = pageOrScreen
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let meta = []
|
||||
export let size = ""
|
||||
export let values = []
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
$: originalName = component.name
|
||||
$: name = component.name
|
||||
$: description = component.description
|
||||
$: componentInfo = $store.currentComponentInfo
|
||||
$: components = $store.components
|
||||
|
||||
const onPropChanged = store.setComponentProp
|
||||
|
@ -47,7 +46,7 @@
|
|||
<button
|
||||
class:selected={current_view === 'code'}
|
||||
on:click={() => codeEditor && codeEditor.show()}>
|
||||
{#if componentInfo._code && componentInfo._code.trim().length > 0}
|
||||
{#if component._code && component._code.trim().length > 0}
|
||||
<div class="button-indicator">
|
||||
<CircleIndicator />
|
||||
</div>
|
||||
|
@ -63,27 +62,26 @@
|
|||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
{$store.currentFrontEndType}
|
||||
|
||||
{#if !componentInfo.component}
|
||||
<div class="component-props-container">
|
||||
<div class="component-props-container">
|
||||
|
||||
{#if current_view === 'props'}
|
||||
<PropsView {componentInfo} {components} {onPropChanged} />
|
||||
{:else if current_view === 'layout'}
|
||||
<LayoutEditor {onStyleChanged} {componentInfo} />
|
||||
{:else if current_view === 'events'}
|
||||
<EventsEditor {componentInfo} {components} {onPropChanged} />
|
||||
{/if}
|
||||
{#if current_view === 'props'}
|
||||
<PropsView {component} {components} {onPropChanged} />
|
||||
{:else if current_view === 'layout'}
|
||||
<LayoutEditor {onStyleChanged} {component} />
|
||||
{:else if current_view === 'events'}
|
||||
<EventsEditor {component} {components} {onPropChanged} />
|
||||
{/if}
|
||||
|
||||
<CodeEditor
|
||||
bind:this={codeEditor}
|
||||
code={$store.currentComponentInfo._code}
|
||||
onCodeChanged={store.setComponentCode} />
|
||||
<CodeEditor
|
||||
bind:this={codeEditor}
|
||||
code={component._code}
|
||||
onCodeChanged={store.setComponentCode} />
|
||||
|
||||
</div>
|
||||
{:else}
|
||||
<h1>This is a screen, this will be dealt with later</h1>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,69 +7,65 @@
|
|||
import { store } from "../builderStore"
|
||||
import { ArrowDownIcon } from "../common/Icons/"
|
||||
|
||||
export let components = []
|
||||
export let screens = []
|
||||
|
||||
const joinPath = join("/")
|
||||
|
||||
const normalizedName = name =>
|
||||
pipe(name, [
|
||||
trimCharsStart("./"),
|
||||
trimCharsStart("~/"),
|
||||
trimCharsStart("../"),
|
||||
trimChars(" "),
|
||||
])
|
||||
pipe(
|
||||
name,
|
||||
[
|
||||
trimCharsStart("./"),
|
||||
trimCharsStart("~/"),
|
||||
trimCharsStart("../"),
|
||||
trimChars(" "),
|
||||
]
|
||||
)
|
||||
|
||||
const lastPartOfName = c =>
|
||||
last(c.name ? c.name.split("/") : c._component.split("/"))
|
||||
|
||||
const isComponentSelected = (current, comp) =>
|
||||
current &&
|
||||
current.component &&
|
||||
comp.component &&
|
||||
current.component.name === comp.component.name
|
||||
const isComponentSelected = (current, comp) => current === comp
|
||||
|
||||
const isFolderSelected = (current, folder) => isInSubfolder(current, folder)
|
||||
|
||||
$: _components = pipe(components, [
|
||||
map(c => ({ component: c, title: lastPartOfName(c) })),
|
||||
sortBy("title"),
|
||||
])
|
||||
|
||||
function select_component(screen, component) {
|
||||
store.setCurrentScreen(screen)
|
||||
store.selectComponent(component)
|
||||
}
|
||||
$: _screens = pipe(
|
||||
screens,
|
||||
[map(c => ({ component: c, title: lastPartOfName(c) })), sortBy("title")]
|
||||
)
|
||||
|
||||
const isScreenSelected = component =>
|
||||
component.component &&
|
||||
$store.currentFrontEndItem &&
|
||||
component.component.name === $store.currentFrontEndItem.name
|
||||
$store.currentPreviewItem &&
|
||||
component.component.name === $store.currentPreviewItem.name
|
||||
|
||||
$: console.log(_screens)
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
||||
{#each _components as component}
|
||||
{#each _screens as screen}
|
||||
<div
|
||||
class="hierarchy-item component"
|
||||
class:selected={isComponentSelected($store.currentComponentInfo, component)}
|
||||
on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}>
|
||||
class:selected={$store.currentPreviewItem.name === screen.title}
|
||||
on:click|stopPropagation={() => store.setCurrentScreen(screen.title)}>
|
||||
|
||||
<span
|
||||
class="icon"
|
||||
style="transform: rotate({isScreenSelected(component) ? 0 : -90}deg);">
|
||||
{#if component.component.props && component.component.props._children}
|
||||
style="transform: rotate({$store.currentPreviewItem.name === screen.title ? 0 : -90}deg);">
|
||||
{#if screen.component.props._children.length}
|
||||
<ArrowDownIcon />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="title">{component.title}</span>
|
||||
<span class="title">{screen.title}</span>
|
||||
</div>
|
||||
|
||||
{#if isScreenSelected(component) && component.component.props && component.component.props._children}
|
||||
{#if $store.currentPreviewItem.name === screen.title && screen.component.props._children}
|
||||
<ComponentsHierarchyChildren
|
||||
components={component.component.props._children}
|
||||
components={screen.component.props._children}
|
||||
currentComponent={$store.currentComponentInfo}
|
||||
onSelect={child => select_component(component.component.name, child)} />
|
||||
onSelect={store.selectComponent} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
<script>
|
||||
import { last } from "lodash/fp"
|
||||
import { pipe } from "../common/core"
|
||||
|
||||
export let components = []
|
||||
export let currentComponent
|
||||
export let onSelect = () => {}
|
||||
export let level = 0
|
||||
|
||||
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||
const get_name = s => last(s.split("/"))
|
||||
const get_capitalised_name = name => pipe(name, [get_name, capitalise])
|
||||
|
||||
const get_capitalised_name = name =>
|
||||
pipe(
|
||||
name,
|
||||
[get_name, capitalise]
|
||||
)
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { store } from "../builderStore/"
|
||||
import ComponentPanel from "./ComponentPanel.svelte"
|
||||
import ComponentsList from "./ComponentsList.svelte"
|
||||
|
||||
|
@ -10,32 +11,36 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
{#if $store.currentFrontEndType === 'page' || $store.screens.length}
|
||||
<div class="switcher">
|
||||
|
||||
<div class="switcher">
|
||||
<button
|
||||
class:selected={selected === 'properties'}
|
||||
on:click={() => selectTab('properties')}>
|
||||
Properties
|
||||
</button>
|
||||
|
||||
<button
|
||||
class:selected={selected === 'properties'}
|
||||
on:click={() => selectTab('properties')}>
|
||||
Properties
|
||||
</button>
|
||||
<button
|
||||
class:selected={selected === 'components'}
|
||||
on:click={() => selectTab('components')}>
|
||||
Components
|
||||
</button>
|
||||
|
||||
<button
|
||||
class:selected={selected === 'components'}
|
||||
on:click={() => selectTab('components')}>
|
||||
Components
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="panel">
|
||||
{#if selected === 'properties'}
|
||||
<ComponentPanel />
|
||||
{/if}
|
||||
|
||||
<div class="panel">
|
||||
{#if selected === 'properties'}
|
||||
<ComponentPanel />
|
||||
{/if}
|
||||
{#if selected === 'components'}
|
||||
<ComponentsList />
|
||||
{/if}
|
||||
|
||||
{#if selected === 'components'}
|
||||
<ComponentsList />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Please create a new screen</p>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,50 +2,70 @@
|
|||
import { store } from "../builderStore"
|
||||
import { map, join } from "lodash/fp"
|
||||
import { pipe } from "../common/core"
|
||||
import { buildPropsHierarchy } from "./pagesParsing/buildPropsHierarchy"
|
||||
|
||||
let iframe
|
||||
|
||||
function transform_component(comp) {
|
||||
const props = comp.props || comp
|
||||
if (props && props._children && props._children.length) {
|
||||
props._children = props._children.map(transform_component)
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
$: iframe &&
|
||||
console.log(
|
||||
iframe.contentDocument.head.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
'<style ✂prettier:content✂=""></style>'
|
||||
`<\style></style>`
|
||||
)
|
||||
)
|
||||
$: hasComponent = !!$store.currentFrontEndItem
|
||||
$: styles = hasComponent ? $store.currentFrontEndItem._css : ""
|
||||
$: hasComponent = !!$store.currentPreviewItem
|
||||
$: styles = hasComponent ? $store.currentPreviewItem._css : ""
|
||||
|
||||
$: stylesheetLinks = pipe($store.pages.stylesheets, [
|
||||
map(s => `<link rel="stylesheet" href="${s}"/>`),
|
||||
join("\n"),
|
||||
])
|
||||
$: stylesheetLinks = pipe(
|
||||
$store.pages.stylesheets,
|
||||
[map(s => `<link rel="stylesheet" href="${s}"/>`), join("\n")]
|
||||
)
|
||||
|
||||
$: appDefinition = {
|
||||
componentLibraries: $store.loadLibraryUrls(),
|
||||
props: buildPropsHierarchy(
|
||||
$store.components,
|
||||
$store.screens,
|
||||
$store.currentFrontEndItem
|
||||
),
|
||||
props:
|
||||
$store.currentPreviewItem &&
|
||||
transform_component($store.currentPreviewItem, true),
|
||||
hierarchy: $store.hierarchy,
|
||||
appRootPath: "",
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="component-container">
|
||||
{#if hasComponent}
|
||||
{#if hasComponent && $store.currentPreviewItem}
|
||||
<iframe
|
||||
style="height: 100%; width: 100%"
|
||||
title="componentPreview"
|
||||
bind:this={iframe}
|
||||
srcdoc={`<html>
|
||||
|
||||
<head>
|
||||
<head>
|
||||
${stylesheetLinks}
|
||||
<script ✂prettier:content✂="CiAgICAgICAgd2luZG93WyIjI0JVRElCQVNFX0FQUERFRklOSVRJT04jIyJdID0gJHtKU09OLnN0cmluZ2lmeShhcHBEZWZpbml0aW9uKX07CiAgICAgICAgd2luZG93WyIjI0JVRElCQVNFX1VJRlVOQ1RJT05TIl0gPSAkeyRzdG9yZS5jdXJyZW50U2NyZWVuRnVuY3Rpb25zfTsKICAgICAgICAKICAgICAgICBpbXBvcnQoJy9fYnVpbGRlci9idWRpYmFzZS1jbGllbnQuZXNtLm1qcycpCiAgICAgICAgLnRoZW4obW9kdWxlID0+IHsKICAgICAgICAgICAgbW9kdWxlLmxvYWRCdWRpYmFzZSh7IHdpbmRvdywgbG9jYWxTdG9yYWdlIH0pOwogICAgICAgIH0pCiAgICA=">{}</script> <style ✂prettier:content✂="CgogICAgICAgIGJvZHkgewogICAgICAgICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICAgICAgICBwYWRkaW5nOiAyMHB4OwogICAgICAgIH0KICAgICR7c3R5bGVzfQogICAg"></style></head>
|
||||
<body>
|
||||
</body>
|
||||
<style>
|
||||
${styles || ''}
|
||||
body, html {
|
||||
height: 100%!important;
|
||||
}
|
||||
<\/style>
|
||||
<\script>
|
||||
window["##BUDIBASE_APPDEFINITION##"] = ${JSON.stringify(appDefinition)};
|
||||
window["##BUDIBASE_UIFUNCTIONS"] = ${$store.currentScreenFunctions};
|
||||
|
||||
import('/_builder/budibase-client.esm.mjs')
|
||||
.then(module => {
|
||||
module.loadBudibase({ window, localStorage });
|
||||
})
|
||||
<\/script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>`} />
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import { cloneDeep, join, split, last } from "lodash/fp"
|
||||
import { assign } from "lodash"
|
||||
|
||||
$: component = $store.currentFrontEndItem
|
||||
$: component = $store.currentPreviewItem
|
||||
$: componentInfo = $store.currentComponentInfo
|
||||
$: components = $store.components
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import InputGroup from "../common/Inputs/InputGroup.svelte"
|
||||
|
||||
export let onStyleChanged = () => {}
|
||||
export let componentInfo
|
||||
export let component
|
||||
|
||||
const tbrl = [
|
||||
{ placeholder: "T" },
|
||||
|
@ -16,8 +16,8 @@
|
|||
const single = [{ placeholder: "" }]
|
||||
|
||||
$: layout = {
|
||||
...componentInfo._styles.position,
|
||||
...componentInfo._styles.layout,
|
||||
...component._styles.position,
|
||||
...component._styles.layout,
|
||||
}
|
||||
|
||||
$: layouts = {
|
||||
|
@ -46,7 +46,7 @@
|
|||
|
||||
<h4>Positioning</h4>
|
||||
<div class="layout-pos">
|
||||
{#each Object.entries(layouts) as [key, [name, meta, size]]}
|
||||
{#each Object.entries(layouts) as [key, [name, meta, size]] (component._id + key)}
|
||||
<div class="grid">
|
||||
<h5>{name}:</h5>
|
||||
<InputGroup
|
||||
|
@ -61,7 +61,7 @@
|
|||
|
||||
<h4>Positioning</h4>
|
||||
<div class="layout-pos">
|
||||
{#each Object.entries(positions) as [key, [name, meta, size]]}
|
||||
{#each Object.entries(positions) as [key, [name, meta, size]] (component._id + key)}
|
||||
<div class="grid">
|
||||
<h5>{name}:</h5>
|
||||
<InputGroup
|
||||
|
@ -75,7 +75,7 @@
|
|||
|
||||
<h4>Spacing</h4>
|
||||
<div class="layout-spacing">
|
||||
{#each Object.entries(spacing) as [key, [name, meta, size]]}
|
||||
{#each Object.entries(spacing) as [key, [name, meta, size]] (component._id + key)}
|
||||
<div class="grid">
|
||||
<h5>{name}:</h5>
|
||||
<InputGroup
|
||||
|
@ -89,7 +89,7 @@
|
|||
|
||||
<h4>Z-Index</h4>
|
||||
<div class="layout-layer">
|
||||
{#each Object.entries(zindex) as [key, [name, meta, size]]}
|
||||
{#each Object.entries(zindex) as [key, [name, meta, size]] (component._id + key)}
|
||||
<div class="grid">
|
||||
<h5>{name}:</h5>
|
||||
<InputGroup
|
||||
|
|
|
@ -22,13 +22,17 @@
|
|||
let layoutComponent
|
||||
let screens
|
||||
let name = ""
|
||||
let route = ""
|
||||
let saveAttempted = false
|
||||
|
||||
store.subscribe(s => {
|
||||
layoutComponents = pipe(s.components, [
|
||||
filter(c => c.container),
|
||||
map(c => ({ name: c.name, ...splitName(c.name) })),
|
||||
])
|
||||
layoutComponents = pipe(
|
||||
s.components,
|
||||
[
|
||||
filter(c => c.container),
|
||||
map(c => ({ name: c.name, ...splitName(c.name) })),
|
||||
]
|
||||
)
|
||||
|
||||
layoutComponent = layoutComponent
|
||||
? find(c => c.name === layoutComponent.name)(layoutComponents)
|
||||
|
@ -45,7 +49,7 @@
|
|||
|
||||
if (!isValid) return
|
||||
|
||||
store.createScreen(name, layoutComponent.name)
|
||||
store.createScreen(name, route, layoutComponent.name)
|
||||
UIkit.modal(componentSelectorModal).hide()
|
||||
}
|
||||
|
||||
|
@ -53,8 +57,11 @@
|
|||
UIkit.modal(componentSelectorModal).hide()
|
||||
}
|
||||
|
||||
const screenNameExists = name =>
|
||||
some(s => s.name.toLowerCase() === name.toLowerCase())(screens)
|
||||
const screenNameExists = name => {
|
||||
return some(s => {
|
||||
return s.name.toLowerCase() === name.toLowerCase()
|
||||
})(screens)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={componentSelectorModal} id="new-component-modal" uk-modal>
|
||||
|
@ -73,6 +80,14 @@
|
|||
class:uk-form-danger={saveAttempted && (name.length === 0 || screenNameExists(name))}
|
||||
bind:value={name} />
|
||||
</div>
|
||||
|
||||
<label class="uk-form-label">Route (Url)</label>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
class="uk-input uk-form-small"
|
||||
class:uk-form-danger={saveAttempted && route.length === 0}
|
||||
bind:value={route} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin">
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<script>
|
||||
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
|
||||
</script>
|
|
@ -5,25 +5,25 @@
|
|||
import PropControl from "./PropControl.svelte"
|
||||
import IconButton from "../common/IconButton.svelte"
|
||||
|
||||
export let componentInfo
|
||||
export let component
|
||||
export let onPropChanged = () => {}
|
||||
export let components
|
||||
|
||||
let errors = []
|
||||
let props = {}
|
||||
|
||||
const props_to_ignore = ["_component", "_children", "_styles", "_code", "_id"]
|
||||
|
||||
$: propDefs =
|
||||
componentInfo &&
|
||||
Object.entries(componentInfo).filter(
|
||||
component &&
|
||||
Object.entries(component).filter(
|
||||
([name]) => !props_to_ignore.includes(name)
|
||||
)
|
||||
|
||||
function find_type(prop_name) {
|
||||
if (!componentInfo._component) return
|
||||
return components.find(({ name }) => name === componentInfo._component)
|
||||
.props[prop_name]
|
||||
if (!component._component) return
|
||||
return components.find(({ name }) => name === component._component).props[
|
||||
prop_name
|
||||
]
|
||||
}
|
||||
|
||||
let setProp = (name, value) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import ComponentsHierarchy from "./ComponentsHierarchy.svelte"
|
||||
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
|
||||
import PagesList from "./PagesList.svelte"
|
||||
import { store } from "../builderStore"
|
||||
import IconButton from "../common/IconButton.svelte"
|
||||
|
@ -37,29 +38,59 @@
|
|||
</div>
|
||||
|
||||
<div class="components-list-container">
|
||||
|
||||
<div class="nav-group-header">
|
||||
|
||||
<span class="components-nav-header">Screens</span>
|
||||
<div>
|
||||
<button on:click={newComponent}>+</button>
|
||||
</div>
|
||||
<span
|
||||
on:click={() => store.setScreenType('page')}
|
||||
class="components-nav-header"
|
||||
class:active={$store.currentFrontEndType === 'page'}>
|
||||
Page
|
||||
</span>
|
||||
</div>
|
||||
<div class="nav-items-container">
|
||||
<ComponentsHierarchy components={$store.screens} />
|
||||
{#if $store.currentFrontEndType === 'page'}
|
||||
<ComponentsHierarchyChildren
|
||||
components={$store.currentPreviewItem.props._children}
|
||||
currentComponent={$store.currentComponentInfo}
|
||||
onSelect={store.selectComponent}
|
||||
level={-2} />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="components-list-container">
|
||||
|
||||
<div class="nav-group-header">
|
||||
|
||||
<span
|
||||
on:click={() => store.setScreenType('screen')}
|
||||
class="components-nav-header"
|
||||
class:active={$store.currentFrontEndType === 'screen'}>
|
||||
Screens
|
||||
</span>
|
||||
|
||||
{#if $store.currentFrontEndType === 'screen'}
|
||||
<div>
|
||||
<button on:click={newComponent}>+</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="nav-items-container">
|
||||
{#if $store.currentFrontEndType === 'screen'}
|
||||
<ComponentsHierarchy screens={$store.screens} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="preview-pane">
|
||||
{#if $store.currentFrontEndType === 'screen'}
|
||||
<CurrentItemPreview />
|
||||
{:else if $store.currentFrontEndType === 'page'}
|
||||
<PageView />
|
||||
{/if}
|
||||
<CurrentItemPreview />
|
||||
</div>
|
||||
|
||||
{#if $store.currentFrontEndType === 'screen'}
|
||||
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'}
|
||||
<div class="components-pane">
|
||||
<ComponentsPaneSwitcher />
|
||||
</div>
|
||||
|
@ -152,7 +183,7 @@
|
|||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.nav-group-header > span:nth-child(2) {
|
||||
.nav-group-header > span:nth-child(3) {
|
||||
margin-left: 5px;
|
||||
vertical-align: bottom;
|
||||
grid-column-start: title;
|
||||
|
@ -175,4 +206,8 @@
|
|||
font-weight: 400;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
import { getComponentInfo, createProps, getInstanceProps } from "./createProps"
|
||||
|
||||
export const buildPropsHierarchy = (components, screens, baseComponent) => {
|
||||
const allComponents = [...components, ...screens]
|
||||
|
||||
const buildProps = (componentDefinition, derivedFromProps) => {
|
||||
const { props } = createProps(componentDefinition, derivedFromProps)
|
||||
const propsDefinition = componentDefinition.props
|
||||
props._component = componentDefinition.name
|
||||
for (let propName in props) {
|
||||
if (propName === "_component") continue
|
||||
|
||||
const propDef = propsDefinition[propName]
|
||||
if (!propDef) continue
|
||||
if (propName === "_children") {
|
||||
const childrenProps = props[propName]
|
||||
|
||||
if (!childrenProps || childrenProps.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
props[propName] = []
|
||||
|
||||
for (let child of childrenProps) {
|
||||
const propComponentInfo = getComponentInfo(
|
||||
allComponents,
|
||||
child._component
|
||||
)
|
||||
|
||||
const subComponentInstanceProps = getInstanceProps(
|
||||
propComponentInfo,
|
||||
child
|
||||
)
|
||||
|
||||
props[propName].push(
|
||||
buildProps(
|
||||
propComponentInfo.rootComponent.name,
|
||||
propComponentInfo.propsDefinition,
|
||||
subComponentInstanceProps
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
if (!baseComponent) return {}
|
||||
|
||||
const baseComponentInfo = getComponentInfo(allComponents, baseComponent)
|
||||
|
||||
return buildProps(
|
||||
baseComponentInfo.rootComponent,
|
||||
baseComponentInfo.fullProps
|
||||
)
|
||||
}
|
|
@ -1,86 +1,17 @@
|
|||
import {
|
||||
isString,
|
||||
isUndefined,
|
||||
find,
|
||||
keys,
|
||||
uniq,
|
||||
some,
|
||||
filter,
|
||||
reduce,
|
||||
cloneDeep,
|
||||
includes,
|
||||
last,
|
||||
} from "lodash/fp"
|
||||
import { types, expandComponentDefinition } from "./types"
|
||||
import { isString, isUndefined } from "lodash/fp"
|
||||
import { types } from "./types"
|
||||
import { assign } from "lodash"
|
||||
import { pipe } from "../../common/core"
|
||||
import { isRootComponent } from "./searchComponents"
|
||||
import { ensureShardNameIsInShardMap } from "../../../../core/src/indexing/sharding"
|
||||
import { uuid } from "../../builderStore/uuid"
|
||||
|
||||
export const getInstanceProps = (componentInfo, props) => {
|
||||
const finalProps = cloneDeep(componentInfo.fullProps)
|
||||
|
||||
for (let p in props) {
|
||||
finalProps[p] = props[p]
|
||||
}
|
||||
|
||||
return finalProps
|
||||
}
|
||||
|
||||
export const getNewComponentInfo = (components, rootComponent, name) => {
|
||||
const component = {
|
||||
export const getNewScreen = (components, rootComponentName, name) => {
|
||||
const rootComponent = components.find(c => c.name === rootComponentName)
|
||||
return {
|
||||
name: name || "",
|
||||
description: "",
|
||||
props: {
|
||||
_component: rootComponent,
|
||||
},
|
||||
}
|
||||
return getComponentInfo(components, component)
|
||||
}
|
||||
|
||||
export const getScreenInfo = (components, screen) => {
|
||||
return getComponentInfo(components, screen)
|
||||
}
|
||||
|
||||
export const getComponentInfo = (components, comp) => {
|
||||
const targetComponent = isString(comp)
|
||||
? find(c => c.name === comp)(components)
|
||||
: comp
|
||||
let component
|
||||
let subComponent
|
||||
if (isRootComponent(targetComponent)) {
|
||||
component = targetComponent
|
||||
} else {
|
||||
subComponent = targetComponent
|
||||
component = find(
|
||||
c =>
|
||||
c.name ===
|
||||
(subComponent.props
|
||||
? subComponent.props._component
|
||||
: subComponent._component)
|
||||
)(components)
|
||||
}
|
||||
|
||||
const subComponentProps = subComponent ? subComponent.props : {}
|
||||
const p = createProps(component, subComponentProps)
|
||||
const rootProps = createProps(component)
|
||||
|
||||
const unsetProps = pipe(p.props, [
|
||||
keys,
|
||||
filter(k => !includes(k)(keys(subComponentProps)) && k !== "_component"),
|
||||
])
|
||||
|
||||
const fullProps = cloneDeep(p.props)
|
||||
fullProps._component = targetComponent.name
|
||||
|
||||
return {
|
||||
propsDefinition: expandComponentDefinition(component),
|
||||
rootDefaultProps: rootProps.props,
|
||||
unsetProps,
|
||||
fullProps: fullProps,
|
||||
errors: p.errors,
|
||||
component: targetComponent,
|
||||
rootComponent: component,
|
||||
url: "",
|
||||
_css: "",
|
||||
uiFunctions: "",
|
||||
props: createProps(rootComponent).props,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +20,9 @@ export const createProps = (componentDefinition, derivedFromProps) => {
|
|||
|
||||
const props = {
|
||||
_component: componentDefinition.name,
|
||||
_styles: { position: {}, layout: {} },
|
||||
_id: uuid(),
|
||||
_code: "",
|
||||
}
|
||||
|
||||
const errors = []
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
export const defaultPagesObject = () => ({
|
||||
main: {
|
||||
_props: {},
|
||||
_screens: {},
|
||||
index: {
|
||||
_component: "./components/indexHtml",
|
||||
},
|
||||
appBody: "bbapp.main.json",
|
||||
},
|
||||
unauthenticated: {
|
||||
_props: {},
|
||||
_screens: {},
|
||||
index: {
|
||||
_component: "./components/indexHtml",
|
||||
},
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { componentsAndScreens } from "./testData"
|
||||
import { find } from "lodash/fp"
|
||||
import { buildPropsHierarchy } from "../src/userInterface/pagesParsing/buildPropsHierarchy"
|
||||
|
||||
describe("buildPropsHierarchy", () => {
|
||||
it("should build a complex component children", () => {
|
||||
const { components, screens } = componentsAndScreens()
|
||||
|
||||
const allprops = buildPropsHierarchy(components, screens, "ButtonGroup")
|
||||
|
||||
expect(allprops._component).toEqual("budibase-components/div")
|
||||
|
||||
const primaryButtonProps = () => ({
|
||||
_component: "budibase-components/Button",
|
||||
})
|
||||
|
||||
const button1 = primaryButtonProps()
|
||||
button1.contentText = "Button 1"
|
||||
expect(allprops._children[0]).toEqual(button1)
|
||||
|
||||
const button2 = primaryButtonProps()
|
||||
button2.contentText = "Button 2"
|
||||
expect(allprops._children[1]).toEqual(button2)
|
||||
})
|
||||
})
|
|
@ -22,20 +22,7 @@ describe("component dependencies", () => {
|
|||
)
|
||||
})
|
||||
|
||||
it("should include component that nests", () => {
|
||||
const { components, screens } = componentsAndScreens()
|
||||
|
||||
const result = componentDependencies(
|
||||
{},
|
||||
screens,
|
||||
components,
|
||||
get([...components, ...screens], "budibase-components/Button")
|
||||
)
|
||||
|
||||
expect(contains(result.dependantComponents, "ButtonGroup")).toBe(true)
|
||||
})
|
||||
|
||||
it("should include components n page apbody", () => {
|
||||
it("should include components in page apbody", () => {
|
||||
const { components, screens } = componentsAndScreens()
|
||||
const pages = {
|
||||
main: {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createProps } from "../src/userInterface/pagesParsing/createProps"
|
||||
import { keys, some } from "lodash/fp"
|
||||
import { BB_STATE_BINDINGPATH } from "@budibase/client/src/state/isState"
|
||||
import { stripStandardProps } from "./testData"
|
||||
|
||||
describe("createDefaultProps", () => {
|
||||
const getcomponent = () => ({
|
||||
|
@ -16,6 +17,7 @@ describe("createDefaultProps", () => {
|
|||
expect(errors).toEqual([])
|
||||
expect(props.fieldName).toBeDefined()
|
||||
expect(props.fieldName).toBe("something")
|
||||
stripStandardProps(props)
|
||||
expect(keys(props).length).toBe(3)
|
||||
})
|
||||
|
||||
|
@ -190,11 +192,6 @@ describe("createDefaultProps", () => {
|
|||
})
|
||||
|
||||
it("should merge in derived props", () => {
|
||||
const propDef = {
|
||||
fieldName: "string",
|
||||
fieldLength: { type: "number", default: 500 },
|
||||
}
|
||||
|
||||
const comp = getcomponent()
|
||||
comp.props.fieldName = "string"
|
||||
comp.props.fieldLength = { type: "number", default: 500 }
|
||||
|
@ -209,4 +206,13 @@ describe("createDefaultProps", () => {
|
|||
expect(props.fieldName).toBe("surname")
|
||||
expect(props.fieldLength).toBe(500)
|
||||
})
|
||||
|
||||
it("should create standard props", () => {
|
||||
const comp = getcomponent()
|
||||
comp.props.fieldName = { type: "string", default: 1 }
|
||||
const { props } = createProps(comp)
|
||||
expect(props._code).toBeDefined()
|
||||
expect(props._styles).toBeDefined()
|
||||
expect(props._code).toBeDefined()
|
||||
})
|
||||
})
|
|
@ -1,91 +0,0 @@
|
|||
import {
|
||||
getInstanceProps,
|
||||
getScreenInfo,
|
||||
getComponentInfo,
|
||||
} from "../src/userInterface/pagesParsing/createProps"
|
||||
import { keys, some, find } from "lodash/fp"
|
||||
import { componentsAndScreens } from "./testData"
|
||||
|
||||
describe("getComponentInfo", () => {
|
||||
it("should return default props for root component", () => {
|
||||
const result = getComponentInfo(
|
||||
componentsAndScreens().components,
|
||||
"budibase-components/TextBox"
|
||||
)
|
||||
|
||||
expect(result.errors).toEqual([])
|
||||
expect(result.fullProps).toEqual({
|
||||
_component: "budibase-components/TextBox",
|
||||
size: "",
|
||||
isPassword: false,
|
||||
placeholder: "",
|
||||
label: "",
|
||||
})
|
||||
})
|
||||
|
||||
it("getInstanceProps should set supplied props on top of default props", () => {
|
||||
const result = getInstanceProps(
|
||||
getComponentInfo(
|
||||
componentsAndScreens().components,
|
||||
"budibase-components/TextBox"
|
||||
),
|
||||
{ size: "small" }
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
_component: "budibase-components/TextBox",
|
||||
size: "small",
|
||||
isPassword: false,
|
||||
placeholder: "",
|
||||
label: "",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("getScreenInfo", () => {
|
||||
const getScreen = (screens, name) => find(s => s.name === name)(screens)
|
||||
|
||||
it("should return correct props for screen", () => {
|
||||
const { components, screens } = componentsAndScreens()
|
||||
const result = getScreenInfo(
|
||||
components,
|
||||
getScreen(screens, "common/SmallTextbox")
|
||||
)
|
||||
|
||||
expect(result.errors).toEqual([])
|
||||
expect(result.fullProps).toEqual({
|
||||
_component: "common/SmallTextbox",
|
||||
size: "small",
|
||||
isPassword: false,
|
||||
placeholder: "",
|
||||
label: "",
|
||||
})
|
||||
})
|
||||
|
||||
it("should return correct props for twice derived component", () => {
|
||||
const { components, screens } = componentsAndScreens()
|
||||
const result = getScreenInfo(
|
||||
components,
|
||||
getScreen(screens, "common/PasswordBox")
|
||||
)
|
||||
|
||||
expect(result.errors).toEqual([])
|
||||
expect(result.fullProps).toEqual({
|
||||
_component: "common/PasswordBox",
|
||||
size: "small",
|
||||
isPassword: true,
|
||||
placeholder: "",
|
||||
label: "",
|
||||
})
|
||||
})
|
||||
|
||||
it("should list unset props as those that are only defined in root", () => {
|
||||
const { components, screens } = componentsAndScreens()
|
||||
const result = getScreenInfo(
|
||||
components,
|
||||
getScreen(screens, "common/PasswordBox")
|
||||
)
|
||||
|
||||
expect(result.unsetProps).toEqual(["placeholder", "label"])
|
||||
})
|
||||
})
|
|
@ -0,0 +1,30 @@
|
|||
import { getNewScreen } from "../src/userInterface/pagesParsing/createProps"
|
||||
import { componentsAndScreens, stripStandardProps } from "./testData"
|
||||
|
||||
describe("geNewScreen", () => {
|
||||
it("should return correct props for screen", () => {
|
||||
const { components } = componentsAndScreens()
|
||||
const result = getNewScreen(
|
||||
components,
|
||||
"budibase-components/TextBox",
|
||||
"newscreen"
|
||||
)
|
||||
|
||||
expect(result.props._code).toBeDefined()
|
||||
expect(result.props._id).toBeDefined()
|
||||
expect(result.props._styles).toBeDefined()
|
||||
stripStandardProps(result.props)
|
||||
|
||||
const expectedProps = {
|
||||
_component: "budibase-components/TextBox",
|
||||
size: "",
|
||||
isPassword: false,
|
||||
placeholder: "",
|
||||
label: "",
|
||||
}
|
||||
|
||||
expect(result.props).toEqual(expectedProps)
|
||||
expect(result.name).toBe("newscreen")
|
||||
expect(result.url).toBeDefined()
|
||||
})
|
||||
})
|
|
@ -64,7 +64,8 @@ export const componentsAndScreens = () => ({
|
|||
},
|
||||
|
||||
{
|
||||
name: "ButtonGroup",
|
||||
name: "Screen 1",
|
||||
route: "",
|
||||
props: {
|
||||
_component: "budibase-components/div",
|
||||
width: 100,
|
||||
|
@ -99,3 +100,9 @@ export const componentsAndScreens = () => ({
|
|||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const stripStandardProps = props => {
|
||||
delete props._code
|
||||
delete props._id
|
||||
delete props._styles
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"title": "Test App",
|
||||
"favicon": "./_shared/favicon.png",
|
||||
"stylesheets": [],
|
||||
"componentLibraries": ["@budibase/standard-components"],
|
||||
"props" : {
|
||||
"_component": "@budibase/standard-components/div",
|
||||
"_children": [],
|
||||
"_id": 0,
|
||||
"_styles": {
|
||||
"layout": {},
|
||||
"positions": {}
|
||||
},
|
||||
"_code": ""
|
||||
},
|
||||
"_css": "",
|
||||
"uiFunctions": ""
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"title": "Test App",
|
||||
"favicon": "./_shared/favicon.png",
|
||||
"stylesheets": [],
|
||||
"componentLibraries": ["@budibase/standard-components"],
|
||||
"props" : {
|
||||
"_component": "@budibase/standard-components/div",
|
||||
"_children": [],
|
||||
"_id": 1,
|
||||
"_styles": {
|
||||
"layout": {},
|
||||
"positions": {}
|
||||
},
|
||||
"_code": ""
|
||||
},
|
||||
"_css": "",
|
||||
"uiFunctions": ""
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
whats the craic big lawd ?
|
|
@ -54,7 +54,8 @@ const createEmptyAppPackage = async opts => {
|
|||
await remove(join(destinationFolder, ...args, "placeholder"))
|
||||
}
|
||||
|
||||
await removePlaceholder("components")
|
||||
await removePlaceholder("pages", "main", "screens")
|
||||
await removePlaceholder("pages", "unauthenticated", "screens")
|
||||
await removePlaceholder("public", "shared")
|
||||
await removePlaceholder("public", "main")
|
||||
await removePlaceholder("public", "unauthenticated")
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
"bcryptjs": "^2.4.3",
|
||||
"lodash": "^4.17.15",
|
||||
"lunr": "^2.3.5",
|
||||
"regexparam": "^1.3.0",
|
||||
"shortid": "^2.2.8",
|
||||
"svelte": "^3.9.2"
|
||||
},
|
||||
|
|
|
@ -4,29 +4,26 @@ import { getStateOrValue } from "./state/getState"
|
|||
import { setState, setStateFromBinding } from "./state/setState"
|
||||
import { trimSlash } from "./common/trimSlash"
|
||||
import { isBound } from "./state/isState"
|
||||
import { _initialiseChildren } from "./render/initialiseChildren"
|
||||
import { initialiseChildren } from "./render/initialiseChildren"
|
||||
import { createTreeNode } from "./render/renderComponent"
|
||||
import { screenRouter } from "./render/screenRouter"
|
||||
|
||||
export const createApp = (
|
||||
document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
user,
|
||||
uiFunctions
|
||||
uiFunctions,
|
||||
screens
|
||||
) => {
|
||||
const coreApi = createCoreApi(appDefinition, user)
|
||||
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
|
||||
appDefinition.hierarchy
|
||||
)
|
||||
const store = writable({
|
||||
const pageStore = writable({
|
||||
_bbuser: user,
|
||||
})
|
||||
|
||||
let globalState = null
|
||||
store.subscribe(s => {
|
||||
globalState = s
|
||||
})
|
||||
|
||||
const relativeUrl = url =>
|
||||
appDefinition.appRootPath
|
||||
? appDefinition.appRootPath + "/" + trimSlash(url)
|
||||
|
@ -55,45 +52,100 @@ export const createApp = (
|
|||
if (isFunction(event)) event(context)
|
||||
}
|
||||
|
||||
const initialiseChildrenParams = (hydrate, treeNode) => ({
|
||||
bb,
|
||||
coreApi,
|
||||
store,
|
||||
document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
hydrate,
|
||||
uiFunctions,
|
||||
treeNode,
|
||||
})
|
||||
let routeTo
|
||||
let currentScreenStore
|
||||
let currentScreenUbsubscribe
|
||||
let currentUrl
|
||||
|
||||
const bb = (treeNode, componentProps) => ({
|
||||
hydrateChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(true, treeNode)
|
||||
),
|
||||
appendChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(false, treeNode)
|
||||
),
|
||||
insertChildren: (props, htmlElement, anchor) =>
|
||||
_initialiseChildren(initialiseChildrenParams(false, treeNode))(
|
||||
props,
|
||||
htmlElement,
|
||||
anchor
|
||||
),
|
||||
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(globalState, prop, currentContext),
|
||||
store,
|
||||
relativeUrl,
|
||||
api,
|
||||
isBound,
|
||||
parent,
|
||||
})
|
||||
const onScreenSlotRendered = screenSlotNode => {
|
||||
const onScreenSelected = (screen, store, url) => {
|
||||
const { getInitialiseParams, unsubscribe } = initialiseChildrenParams(
|
||||
store
|
||||
)
|
||||
const initialiseChildParams = getInitialiseParams(true, screenSlotNode)
|
||||
initialiseChildren(initialiseChildParams)(
|
||||
[screen.props],
|
||||
screenSlotNode.rootElement
|
||||
)
|
||||
if (currentScreenUbsubscribe) currentScreenUbsubscribe()
|
||||
currentScreenUbsubscribe = unsubscribe
|
||||
currentScreenStore = store
|
||||
currentUrl = url
|
||||
}
|
||||
|
||||
return bb(createTreeNode())
|
||||
routeTo = screenRouter(screens, onScreenSelected)
|
||||
routeTo(currentUrl || window.location.pathname)
|
||||
}
|
||||
|
||||
const initialiseChildrenParams = store => {
|
||||
let currentState = null
|
||||
const unsubscribe = store.subscribe(s => {
|
||||
currentState = s
|
||||
})
|
||||
|
||||
const getInitialiseParams = (hydrate, treeNode) => ({
|
||||
bb: getBbClientApi,
|
||||
coreApi,
|
||||
store,
|
||||
document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
hydrate,
|
||||
uiFunctions,
|
||||
treeNode,
|
||||
onScreenSlotRendered,
|
||||
})
|
||||
|
||||
const getBbClientApi = (treeNode, componentProps) => {
|
||||
return {
|
||||
hydrateChildren: initialiseChildren(
|
||||
getInitialiseParams(true, treeNode)
|
||||
),
|
||||
appendChildren: initialiseChildren(
|
||||
getInitialiseParams(false, treeNode)
|
||||
),
|
||||
insertChildren: (props, htmlElement, anchor) =>
|
||||
initialiseChildren(getInitialiseParams(false, treeNode))(
|
||||
props,
|
||||
htmlElement,
|
||||
anchor
|
||||
),
|
||||
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
|
||||
|
||||
const initialisePage = (page, target, urlPath) => {
|
||||
currentUrl = urlPath
|
||||
|
||||
rootTreeNode = createTreeNode()
|
||||
const { getInitialiseParams } = initialiseChildrenParams(pageStore)
|
||||
const initChildParams = getInitialiseParams(true, rootTreeNode)
|
||||
|
||||
initialiseChildren(initChildParams)([page.props], target)
|
||||
|
||||
return rootTreeNode
|
||||
}
|
||||
return {
|
||||
initialisePage,
|
||||
screenStore: () => currentScreenStore,
|
||||
pageStore: () => pageStore,
|
||||
routeTo: () => routeTo,
|
||||
rootNode: () => rootTreeNode,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import { createApp } from "./createApp"
|
||||
import { trimSlash } from "./common/trimSlash"
|
||||
import { builtins, builtinLibName } from "./render/builtinComponents"
|
||||
|
||||
export const loadBudibase = async ({
|
||||
componentLibraries,
|
||||
props,
|
||||
page,
|
||||
screens,
|
||||
window,
|
||||
localStorage,
|
||||
uiFunctions,
|
||||
}) => {
|
||||
const appDefinition = window["##BUDIBASE_APPDEFINITION##"]
|
||||
const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"]
|
||||
const uiFunctionsFromWindow = window["##BUDIBASE_UIFUNCTIONS##"]
|
||||
uiFunctions = uiFunctionsFromWindow || uiFunctions
|
||||
|
||||
const userFromStorage = localStorage.getItem("budibase:user")
|
||||
|
@ -23,11 +25,13 @@ export const loadBudibase = async ({
|
|||
temp: false,
|
||||
}
|
||||
|
||||
const rootPath =
|
||||
appDefinition.appRootPath === ""
|
||||
? ""
|
||||
: "/" + trimSlash(appDefinition.appRootPath)
|
||||
|
||||
if (!componentLibraries) {
|
||||
const rootPath =
|
||||
appDefinition.appRootPath === ""
|
||||
? ""
|
||||
: "/" + trimSlash(appDefinition.appRootPath)
|
||||
|
||||
const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib)
|
||||
componentLibraries = {}
|
||||
|
||||
|
@ -38,20 +42,36 @@ export const loadBudibase = async ({
|
|||
}
|
||||
}
|
||||
|
||||
if (!props) {
|
||||
props = appDefinition.props
|
||||
componentLibraries[builtinLibName] = builtins(window)
|
||||
|
||||
if (!page) {
|
||||
page = appDefinition.page
|
||||
}
|
||||
|
||||
const app = createApp(
|
||||
if (!screens) {
|
||||
screens = appDefinition.screens
|
||||
}
|
||||
|
||||
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
|
||||
window.document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
user,
|
||||
uiFunctions || {}
|
||||
uiFunctions || {},
|
||||
screens
|
||||
)
|
||||
app.hydrateChildren([props], window.document.body)
|
||||
|
||||
return app
|
||||
const route = window.location
|
||||
? window.location.pathname.replace(rootPath, "")
|
||||
: "";
|
||||
|
||||
return {
|
||||
rootNode: initialisePage(page, window.document.body, route),
|
||||
screenStore,
|
||||
pageStore,
|
||||
routeTo,
|
||||
rootNode
|
||||
}
|
||||
}
|
||||
|
||||
if (window) {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import { screenSlotComponent } from "./screenSlotComponent"
|
||||
|
||||
export const builtinLibName = "##builtin"
|
||||
|
||||
export const isScreenSlot = componentName =>
|
||||
componentName === "##builtin/screenslot"
|
||||
|
||||
export const builtins = window => ({
|
||||
screenslot: screenSlotComponent(window),
|
||||
})
|
|
@ -2,8 +2,9 @@ import { setupBinding } from "../state/stateBinding"
|
|||
import { split, last } from "lodash/fp"
|
||||
import { $ } from "../core/common"
|
||||
import { renderComponent } from "./renderComponent"
|
||||
import { isScreenSlot } from "./builtinComponents"
|
||||
|
||||
export const _initialiseChildren = initialiseOpts => (
|
||||
export const initialiseChildren = initialiseOpts => (
|
||||
childrenProps,
|
||||
htmlElement,
|
||||
anchor = null
|
||||
|
@ -16,13 +17,12 @@ export const _initialiseChildren = initialiseOpts => (
|
|||
componentLibraries,
|
||||
treeNode,
|
||||
appDefinition,
|
||||
document,
|
||||
hydrate,
|
||||
onScreenSlotRendered,
|
||||
} = initialiseOpts
|
||||
|
||||
for (let childNode of treeNode.children) {
|
||||
if (childNode.unsubscribe) childNode.unsubscribe()
|
||||
if (childNode.component) childNode.component.$destroy()
|
||||
childNode.destroy()
|
||||
}
|
||||
|
||||
if (hydrate) {
|
||||
|
@ -59,6 +59,15 @@ export const _initialiseChildren = initialiseOpts => (
|
|||
bb,
|
||||
})
|
||||
|
||||
if (
|
||||
onScreenSlotRendered &&
|
||||
isScreenSlot(childProps._component) &&
|
||||
renderedComponentsThisIteration.length > 0
|
||||
) {
|
||||
// assuming there is only ever one screen slot
|
||||
onScreenSlotRendered(renderedComponentsThisIteration[0])
|
||||
}
|
||||
|
||||
for (let comp of renderedComponentsThisIteration) {
|
||||
comp.unsubscribe = bind(comp.component)
|
||||
renderedComponents.push(comp)
|
||||
|
|
|
@ -61,4 +61,16 @@ export const createTreeNode = () => ({
|
|||
children: [],
|
||||
component: null,
|
||||
unsubscribe: () => {},
|
||||
get destroy() {
|
||||
const node = this
|
||||
return () => {
|
||||
if (node.unsubscribe) node.unsubscribe()
|
||||
if (node.component && node.component.$destroy) node.component.$destroy()
|
||||
if (node.children) {
|
||||
for (let child of node.children) {
|
||||
child.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import regexparam from "regexparam"
|
||||
import { writable } from "svelte/store"
|
||||
|
||||
export const screenRouter = (screens, onScreenSelected) => {
|
||||
const routes = screens.map(s => s.route)
|
||||
let fallback = routes.findIndex(([p]) => p === "*")
|
||||
if (fallback < 0) fallback = 0
|
||||
|
||||
let current
|
||||
|
||||
function route(url) {
|
||||
const _url = url.state || url
|
||||
current = routes.findIndex(
|
||||
p => p !== "*" && new RegExp("^" + p + "$").test(_url)
|
||||
)
|
||||
|
||||
const params = {}
|
||||
|
||||
if (current === -1) {
|
||||
routes.forEach(([p], i) => {
|
||||
const pm = regexparam(p)
|
||||
const matches = pm.pattern.exec(_url)
|
||||
|
||||
if (!matches) return
|
||||
|
||||
let j = 0
|
||||
while (j < pm.keys.length) {
|
||||
params[pm.keys[j]] = matches[++j] || null
|
||||
}
|
||||
|
||||
current = i
|
||||
})
|
||||
}
|
||||
|
||||
const storeInitial = {}
|
||||
storeInitial["##routeParams"]
|
||||
const store = writable(storeInitial)
|
||||
|
||||
if (current !== -1) {
|
||||
onScreenSelected(screens[current], store, _url)
|
||||
} else if (fallback) {
|
||||
onScreenSelected(screens[fallback], store, _url)
|
||||
}
|
||||
|
||||
!url.state && history.pushState(_url, null, _url)
|
||||
}
|
||||
|
||||
function click(e) {
|
||||
const x = e.target.closest("a")
|
||||
const y = x && x.getAttribute("href")
|
||||
|
||||
if (
|
||||
e.ctrlKey ||
|
||||
e.metaKey ||
|
||||
e.altKey ||
|
||||
e.shiftKey ||
|
||||
e.button ||
|
||||
e.defaultPrevented
|
||||
)
|
||||
return
|
||||
|
||||
if (!y || x.target || x.host !== location.host) return
|
||||
|
||||
e.preventDefault()
|
||||
route(y)
|
||||
}
|
||||
|
||||
addEventListener("popstate", route)
|
||||
addEventListener("pushstate", route)
|
||||
addEventListener("click", click)
|
||||
|
||||
return route
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
export const screenSlotComponent = window => {
|
||||
return function(opts) {
|
||||
const node = window.document.createElement("DIV")
|
||||
const $set = props => {
|
||||
props._bb.hydrateChildren(props._children, node)
|
||||
}
|
||||
const $destroy = () => {
|
||||
if (opts.target && node) opts.target.removeChild(node)
|
||||
}
|
||||
this.$set = $set
|
||||
this.$destroy = $destroy
|
||||
opts.target.appendChild(node)
|
||||
}
|
||||
}
|
|
@ -1,61 +1,67 @@
|
|||
import { load } from "./testAppDef"
|
||||
import { load, makePage, makeScreen } from "./testAppDef"
|
||||
|
||||
describe("initialiseApp", () => {
|
||||
describe("initialiseApp (binding)", () => {
|
||||
it("should populate root element prop from store value", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
className: {
|
||||
"##bbstate": "divClassName",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "default",
|
||||
},
|
||||
})
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
className: {
|
||||
"##bbstate": "divClassName",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "default",
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.className).toBe("default")
|
||||
expect(rootDiv.className.includes("default")).toBe(true)
|
||||
})
|
||||
|
||||
it("should update root element from store", async () => {
|
||||
const { dom, app } = await load({
|
||||
_component: "testlib/div",
|
||||
className: {
|
||||
"##bbstate": "divClassName",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "default",
|
||||
},
|
||||
})
|
||||
const { dom, app } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
className: {
|
||||
"##bbstate": "divClassName",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "default",
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
app.store.update(s => {
|
||||
app.pageStore().update(s => {
|
||||
s.divClassName = "newvalue"
|
||||
return s
|
||||
})
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.className).toBe("newvalue")
|
||||
expect(rootDiv.className.includes("newvalue")).toBe(true)
|
||||
})
|
||||
|
||||
it("should populate child component with store value", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerOneText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header one",
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerOneText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header one",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerTwoText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header two",
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerTwoText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header two",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
|
||||
|
@ -67,29 +73,31 @@ describe("initialiseApp", () => {
|
|||
})
|
||||
|
||||
it("should populate child component with store value", async () => {
|
||||
const { dom, app } = await load({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerOneText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header one",
|
||||
const { dom, app } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerOneText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header one",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerTwoText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header two",
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerTwoText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header two",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
app.store.update(s => {
|
||||
app.pageStore().update(s => {
|
||||
s.headerOneText = "header 1 - new val"
|
||||
s.headerTwoText = "header 2 - new val"
|
||||
return s
|
||||
|
@ -103,4 +111,62 @@ describe("initialiseApp", () => {
|
|||
expect(rootDiv.children[1].tagName).toBe("H1")
|
||||
expect(rootDiv.children[1].innerText).toBe("header 2 - new val")
|
||||
})
|
||||
|
||||
it("should populate screen child with store value", async () => {
|
||||
const { dom, app } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "##builtin/screenslot",
|
||||
text: "header one",
|
||||
},
|
||||
],
|
||||
}),
|
||||
[
|
||||
makeScreen("/", {
|
||||
_component: "testlib/div",
|
||||
className: "screen-class",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerOneText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header one",
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: {
|
||||
"##bbstate": "headerTwoText",
|
||||
"##bbsource": "store",
|
||||
"##bbstatefallback": "header two",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
]
|
||||
)
|
||||
|
||||
app.screenStore().update(s => {
|
||||
s.headerOneText = "header 1 - new val"
|
||||
s.headerTwoText = "header 2 - new val"
|
||||
return s
|
||||
})
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.children.length).toBe(1)
|
||||
|
||||
const screenRoot = rootDiv.children[0]
|
||||
|
||||
expect(screenRoot.children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children.length).toBe(2)
|
||||
expect(screenRoot.children[0].children[0].innerText).toBe(
|
||||
"header 1 - new val"
|
||||
)
|
||||
expect(screenRoot.children[0].children[1].innerText).toBe(
|
||||
"header 2 - new val"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,66 +1,74 @@
|
|||
import { load } from "./testAppDef"
|
||||
import { load, makePage } from "./testAppDef"
|
||||
|
||||
describe("controlFlow", () => {
|
||||
it("should display simple div, with always true render function", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
_id: "always_render",
|
||||
})
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
_id: "always_render",
|
||||
})
|
||||
)
|
||||
|
||||
expect(dom.window.document.body.children.length).toBe(1)
|
||||
const child = dom.window.document.body.children[0]
|
||||
expect(child.className).toBe("my-test-class")
|
||||
expect(child.className.includes("my-test-class")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should not display div, with always false render function", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
_id: "never_render",
|
||||
})
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
_id: "never_render",
|
||||
})
|
||||
)
|
||||
|
||||
expect(dom.window.document.body.children.length).toBe(0)
|
||||
})
|
||||
|
||||
it("should display 3 divs in a looped render function", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
_id: "three_clones",
|
||||
})
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
_id: "three_clones",
|
||||
})
|
||||
)
|
||||
|
||||
expect(dom.window.document.body.children.length).toBe(3)
|
||||
|
||||
const child0 = dom.window.document.body.children[0]
|
||||
expect(child0.className).toBe("my-test-class")
|
||||
expect(child0.className.includes("my-test-class")).toBeTruthy()
|
||||
|
||||
const child1 = dom.window.document.body.children[1]
|
||||
expect(child1.className).toBe("my-test-class")
|
||||
expect(child1.className.includes("my-test-class")).toBeTruthy()
|
||||
|
||||
const child2 = dom.window.document.body.children[2]
|
||||
expect(child2.className).toBe("my-test-class")
|
||||
expect(child2.className.includes("my-test-class")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should display 3 div, in a looped render, as children", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
_id: "three_clones",
|
||||
},
|
||||
],
|
||||
})
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
_id: "three_clones",
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
expect(dom.window.document.body.children.length).toBe(1)
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.children.length).toBe(3)
|
||||
|
||||
expect(rootDiv.children[0].className).toBe("my-test-class")
|
||||
expect(rootDiv.children[1].className).toBe("my-test-class")
|
||||
expect(rootDiv.children[2].className).toBe("my-test-class")
|
||||
expect(rootDiv.children[0].className.includes("my-test-class")).toBeTruthy()
|
||||
expect(rootDiv.children[1].className.includes("my-test-class")).toBeTruthy()
|
||||
expect(rootDiv.children[2].className.includes("my-test-class")).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,31 +1,35 @@
|
|||
import { load } from "./testAppDef"
|
||||
import { load, makePage, makeScreen } from "./testAppDef"
|
||||
|
||||
describe("initialiseApp", () => {
|
||||
it("should populate simple div with initial props", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
})
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
className: "my-test-class",
|
||||
})
|
||||
)
|
||||
|
||||
expect(dom.window.document.body.children.length).toBe(1)
|
||||
const child = dom.window.document.body.children[0]
|
||||
expect(child.className).toBe("my-test-class")
|
||||
expect(child.className.includes("my-test-class")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should populate child component with props", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header one",
|
||||
},
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header two",
|
||||
},
|
||||
],
|
||||
})
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header one",
|
||||
},
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header two",
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
|
||||
|
@ -37,20 +41,22 @@ describe("initialiseApp", () => {
|
|||
})
|
||||
|
||||
it("should append children when told to do so", async () => {
|
||||
const { dom } = await load({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header one",
|
||||
},
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header two",
|
||||
},
|
||||
],
|
||||
append: true,
|
||||
})
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header one",
|
||||
},
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header two",
|
||||
},
|
||||
],
|
||||
append: true,
|
||||
})
|
||||
)
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
|
||||
|
@ -62,4 +68,71 @@ describe("initialiseApp", () => {
|
|||
expect(rootDiv.children[2].tagName).toBe("H1")
|
||||
expect(rootDiv.children[2].innerText).toBe("header two")
|
||||
})
|
||||
|
||||
it("should populate page with correct screen", async () => {
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "##builtin/screenslot",
|
||||
},
|
||||
],
|
||||
}),
|
||||
[
|
||||
makeScreen("/", {
|
||||
_component: "testlib/div",
|
||||
className: "screen-class",
|
||||
}),
|
||||
]
|
||||
)
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
|
||||
expect(rootDiv.children.length).toBe(1)
|
||||
expect(rootDiv.children[0].children.length).toBe(1)
|
||||
expect(
|
||||
rootDiv.children[0].children[0].className.includes("screen-class")
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should populate screen with children", async () => {
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "##builtin/screenslot",
|
||||
text: "header one",
|
||||
},
|
||||
],
|
||||
}),
|
||||
[
|
||||
makeScreen("/", {
|
||||
_component: "testlib/div",
|
||||
className: "screen-class",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header one",
|
||||
},
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "header two",
|
||||
},
|
||||
],
|
||||
}),
|
||||
]
|
||||
)
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.children.length).toBe(1)
|
||||
|
||||
const screenRoot = rootDiv.children[0]
|
||||
|
||||
expect(screenRoot.children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children.length).toBe(2)
|
||||
expect(screenRoot.children[0].children[0].innerText).toBe("header one")
|
||||
expect(screenRoot.children[0].children[1].innerText).toBe("header two")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
import { load, makePage, makeScreen, walkComponentTree } from "./testAppDef"
|
||||
import { isScreenSlot } from "../src/render/builtinComponents"
|
||||
|
||||
describe("screenRouting", () => {
|
||||
it("should load correct screen, for initial URL", async () => {
|
||||
const { page, screens } = pageWith3Screens()
|
||||
const { dom } = await load(page, screens, "/screen2")
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.children.length).toBe(1)
|
||||
|
||||
const screenRoot = rootDiv.children[0]
|
||||
|
||||
expect(screenRoot.children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children[0].innerText).toBe("screen 2")
|
||||
})
|
||||
|
||||
it("should be able to route to the correct screen", async () => {
|
||||
const { page, screens } = pageWith3Screens()
|
||||
const { dom, app } = await load(page, screens, "/screen2")
|
||||
|
||||
app.routeTo()("/screen3")
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.children.length).toBe(1)
|
||||
|
||||
const screenRoot = rootDiv.children[0]
|
||||
|
||||
expect(screenRoot.children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children[0].innerText).toBe("screen 3")
|
||||
})
|
||||
|
||||
it("should destroy and unsubscribe all components on a screen whe screen is changed", async () => {
|
||||
const { page, screens } = pageWith3Screens()
|
||||
const { app } = await load(page, screens, "/screen2")
|
||||
|
||||
const nodes = createTrackerNodes(app)
|
||||
|
||||
app.routeTo()("/screen3")
|
||||
|
||||
expect(nodes.length > 0).toBe(true)
|
||||
expect(
|
||||
nodes.some(n => n.isDestroyed === false && isUnderScreenSlot(n.node))
|
||||
).toBe(false)
|
||||
expect(
|
||||
nodes.some(n => n.isUnsubscribed === false && isUnderScreenSlot(n.node))
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it("should not destroy and unsubscribe page and screenslot components when screen is changed", async () => {
|
||||
const { page, screens } = pageWith3Screens()
|
||||
const { app } = await load(page, screens, "/screen2")
|
||||
|
||||
const nodes = createTrackerNodes(app)
|
||||
|
||||
app.routeTo()("/screen3")
|
||||
|
||||
expect(nodes.length > 0).toBe(true)
|
||||
expect(
|
||||
nodes.some(n => n.isDestroyed === true && !isUnderScreenSlot(n.node))
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
const createTrackerNodes = app => {
|
||||
const nodes = []
|
||||
walkComponentTree(app.rootNode(), n => {
|
||||
if (!n.component) return
|
||||
const tracker = { node: n, isDestroyed: false, isUnsubscribed: false }
|
||||
const _destroy = n.component.$destroy
|
||||
n.component.$destroy = () => {
|
||||
_destroy()
|
||||
tracker.isDestroyed = true
|
||||
}
|
||||
const _unsubscribe = n.unsubscribe
|
||||
if (!_unsubscribe) {
|
||||
tracker.isUnsubscribed = undefined
|
||||
} else {
|
||||
n.unsubscribe = () => {
|
||||
_unsubscribe()
|
||||
tracker.isUnsubscribed = true
|
||||
}
|
||||
}
|
||||
nodes.push(tracker)
|
||||
})
|
||||
return nodes
|
||||
}
|
||||
|
||||
const isUnderScreenSlot = node =>
|
||||
node.parentNode &&
|
||||
(isScreenSlot(node.parentNode.props._component) ||
|
||||
isUnderScreenSlot(node.parentNode))
|
||||
|
||||
const pageWith3Screens = () => ({
|
||||
page: makePage({
|
||||
_component: "testlib/div",
|
||||
_children: [
|
||||
{
|
||||
_component: "##builtin/screenslot",
|
||||
text: "header one",
|
||||
},
|
||||
],
|
||||
}),
|
||||
screens: [
|
||||
makeScreen("/", {
|
||||
_component: "testlib/div",
|
||||
className: "screen-class",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "screen 1",
|
||||
},
|
||||
],
|
||||
}),
|
||||
makeScreen("/screen2", {
|
||||
_component: "testlib/div",
|
||||
className: "screen-class",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "screen 2",
|
||||
},
|
||||
],
|
||||
}),
|
||||
makeScreen("/screen3", {
|
||||
_component: "testlib/div",
|
||||
className: "screen-class",
|
||||
_children: [
|
||||
{
|
||||
_component: "testlib/h1",
|
||||
text: "screen 3",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
|
@ -1,20 +1,41 @@
|
|||
import { JSDOM } from "jsdom"
|
||||
import { loadBudibase } from "../src/index"
|
||||
|
||||
export const load = async props => {
|
||||
const dom = new JSDOM(`<!DOCTYPE html><html><body></body><html>`)
|
||||
autoAssignIds(props)
|
||||
setAppDef(dom.window, props)
|
||||
export const load = async (page, screens = [], url = "/") => {
|
||||
const dom = new JSDOM("<!DOCTYPE html><html><body></body><html>", {
|
||||
url: `http://test${url}`,
|
||||
})
|
||||
autoAssignIds(page.props)
|
||||
for (let s of screens) {
|
||||
autoAssignIds(s.props)
|
||||
}
|
||||
setAppDef(dom.window, page, screens)
|
||||
const app = await loadBudibase({
|
||||
componentLibraries: allLibs(dom.window),
|
||||
window: dom.window,
|
||||
localStorage: createLocalStorage(),
|
||||
props,
|
||||
page,
|
||||
screens,
|
||||
uiFunctions,
|
||||
})
|
||||
return { dom, app }
|
||||
}
|
||||
|
||||
export const makePage = props => ({ props })
|
||||
export const makeScreen = (route, props) => ({ props, route })
|
||||
|
||||
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) {
|
||||
walkComponentTree(child, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this happens for real by the builder...
|
||||
// ..this only assigns _ids when missing
|
||||
const autoAssignIds = (props, count = 0) => {
|
||||
|
@ -29,10 +50,11 @@ const autoAssignIds = (props, count = 0) => {
|
|||
}
|
||||
}
|
||||
|
||||
const setAppDef = (window, props) => {
|
||||
const setAppDef = (window, page, screens) => {
|
||||
window["##BUDIBASE_APPDEFINITION##"] = {
|
||||
componentLibraries: [],
|
||||
props,
|
||||
page,
|
||||
screens,
|
||||
hierarchy: {},
|
||||
appRootPath: "",
|
||||
}
|
||||
|
@ -79,6 +101,8 @@ const maketestlib = window => ({
|
|||
}
|
||||
}
|
||||
|
||||
this.$destroy = () => opts.target.removeChild(node)
|
||||
|
||||
this.$set = set
|
||||
this._element = node
|
||||
set(opts.props)
|
||||
|
@ -97,6 +121,8 @@ const maketestlib = window => ({
|
|||
}
|
||||
}
|
||||
|
||||
this.$destroy = () => opts.target.removeChild(node)
|
||||
|
||||
this.$set = set
|
||||
this._element = node
|
||||
set(opts.props)
|
||||
|
@ -105,13 +131,13 @@ const maketestlib = window => ({
|
|||
})
|
||||
|
||||
const uiFunctions = {
|
||||
never_render: (render, parentContext) => {},
|
||||
never_render: () => {},
|
||||
|
||||
always_render: (render, parentContext) => {
|
||||
always_render: render => {
|
||||
render()
|
||||
},
|
||||
|
||||
three_clones: (render, parentContext) => {
|
||||
three_clones: render => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
render()
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ export default {
|
|||
// we'll extract any component CSS out into
|
||||
// a separate file — better for performance
|
||||
css: css => {
|
||||
css.write("public/build/bundle.css");
|
||||
css.write("public/build/bundle.css")
|
||||
},
|
||||
|
||||
hydratable: true,
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import "./_index.scss";
|
||||
export { default as button } from "./Button.svelte";
|
||||
import "./_index.scss"
|
||||
export { default as button } from "./Button.svelte"
|
||||
|
|
|
@ -1,70 +1,38 @@
|
|||
export default class ClassBuilder {
|
||||
constructor(block, defaultIgnoreList) {
|
||||
this.block = `mdc-${block}`;
|
||||
this.defaultIgnoreList = defaultIgnoreList; //will be ignored when building custom classes
|
||||
constructor(block, customDefaults) {
|
||||
this.block = `mdc-${block}`
|
||||
this.customDefaults = customDefaults //will be ignored when building custom classes
|
||||
}
|
||||
|
||||
/*
|
||||
handles both blocks and elementss (BEM MD Notation)
|
||||
params = {elementName: string, props: {modifiers{}, customs:{}, extras: []}}
|
||||
All are optional
|
||||
*/
|
||||
build(params) {
|
||||
if (!params) return this.block; //return block if nothing passed
|
||||
const { props, elementName } = params;
|
||||
let base = !!elementName ? `${this.block}__${elementName}` : this.block;
|
||||
if (!props) return base;
|
||||
return this._handleProps(base, props);
|
||||
// classParams: {modifiers:[] (mdc), custom:[] (bbmd), extra:[] (any)}
|
||||
blocks(classParams) {
|
||||
let base = this.block
|
||||
if (classParams == undefined) return base
|
||||
return this.buildClass(base, classParams)
|
||||
}
|
||||
|
||||
//Easily grab a simple element class
|
||||
elem(elementName) {
|
||||
return this.build({ elementName });
|
||||
//elementName: string, classParams: {}
|
||||
elements(elementName, classParams) {
|
||||
let base = `${this.block}__${elementName}`
|
||||
if (classParams == undefined) return base
|
||||
return this.buildClass(base, classParams)
|
||||
}
|
||||
|
||||
//use if a different base is needed than whats defined by this.block
|
||||
debase(base, elementProps) {
|
||||
if (!elementProps) return base;
|
||||
return this._handleProps(base, elementProps);
|
||||
}
|
||||
|
||||
//proxies bindProps and checks for which elementProps exist before binding
|
||||
_handleProps(base, elementProps) {
|
||||
let cls = base;
|
||||
const { modifiers, customs, extras } = elementProps;
|
||||
if (!!modifiers) cls += this._bindProps(modifiers, base);
|
||||
if (!!customs) cls += this._bindProps(customs, base, true);
|
||||
if (!!extras) cls += ` ${extras.join(" ")}`;
|
||||
return cls.trim();
|
||||
}
|
||||
|
||||
/*
|
||||
Handles both modifiers and customs. Use property, value or both depending
|
||||
on whether it is passsed props for custom or modifiers
|
||||
if custom uses the following convention for scss mixins:
|
||||
bbmd-{this.block}--{property}-{value}
|
||||
bbmd-mdc-button--size-large
|
||||
*/
|
||||
_bindProps(elementProps, base, isCustom = false) {
|
||||
return Object.entries(elementProps)
|
||||
.map(([property, value]) => {
|
||||
//disregard falsy and values set by defaultIgnoreList constructor param
|
||||
if (
|
||||
!!value &&
|
||||
(!this.defaultIgnoreList || !this.defaultIgnoreList.includes(value))
|
||||
) {
|
||||
let classBase = isCustom ? `bbmd-${base}` : `${base}`;
|
||||
let valueType = typeof value;
|
||||
|
||||
if (valueType == "string" || valueType == "number") {
|
||||
return isCustom
|
||||
? ` ${classBase}--${property}-${value}`
|
||||
: ` ${classBase}--${value}`;
|
||||
} else if (valueType == "boolean") {
|
||||
return ` ${classBase}--${property}`;
|
||||
buildClass(base, classParams) {
|
||||
let cls = base
|
||||
const { modifiers, customs, extras } = classParams
|
||||
if (modifiers) cls += modifiers.map(m => ` ${base}--${m}`).join(" ")
|
||||
if (customs)
|
||||
cls += Object.entries(customs)
|
||||
.map(([property, value]) => {
|
||||
//disregard falsy and values set by customDefaults constructor param
|
||||
if (!!value && !this.customDefaults.includes(value)) {
|
||||
//custom scss name convention = bbmd-[block | element]--[property]-[value]
|
||||
return ` bbmd-${base}--${property}-${value}`
|
||||
}
|
||||
}
|
||||
})
|
||||
.join("");
|
||||
})
|
||||
.join("")
|
||||
if (extras) cls += ` ${extras.join(" ")}`
|
||||
return cls.trim()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import { MDCRipple } from "@material/ripple";
|
||||
import { MDCRipple } from "@material/ripple"
|
||||
|
||||
export default function ripple(
|
||||
node,
|
||||
props = { colour: "primary", unbounded: false }
|
||||
) {
|
||||
node.classList.add("mdc-ripple-surface");
|
||||
const component = new MDCRipple(node);
|
||||
component.unbounded = props.unbounded;
|
||||
node.classList.add("mdc-ripple-surface")
|
||||
const component = new MDCRipple(node)
|
||||
component.unbounded = props.unbounded
|
||||
|
||||
if (props.colour === "secondary") {
|
||||
node.classList.remove("mdc-ripple-surface--primary");
|
||||
node.classList.add("mdc-ripple-surface--accent");
|
||||
node.classList.remove("mdc-ripple-surface--primary")
|
||||
node.classList.add("mdc-ripple-surface--accent")
|
||||
} else {
|
||||
node.classList.add("mdc-ripple-surface--primary");
|
||||
node.classList.remove("mdc-ripple-surface--accent");
|
||||
node.classList.add("mdc-ripple-surface--primary")
|
||||
node.classList.remove("mdc-ripple-surface--accent")
|
||||
}
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
component.destroy();
|
||||
node.classList.remove("mdc-ripple-surface");
|
||||
node.classList.remove("mdc-ripple-surface--primary");
|
||||
node.classList.remove("mdc-ripple-surface--accent");
|
||||
component = null;
|
||||
}
|
||||
};
|
||||
component.destroy()
|
||||
node.classList.remove("mdc-ripple-surface")
|
||||
node.classList.remove("mdc-ripple-surface--primary")
|
||||
node.classList.remove("mdc-ripple-surface--accent")
|
||||
component = null
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,12 @@ export const props = {
|
|||
trailingIcon: true,
|
||||
fullwidth: false,
|
||||
text: "I am button",
|
||||
disabled: false
|
||||
disabled: false,
|
||||
},
|
||||
icon: {
|
||||
_component: "@budibase/materialdesign-components/icon",
|
||||
_children: [],
|
||||
icon: ""
|
||||
icon: "",
|
||||
},
|
||||
textfield: {
|
||||
_component: "@budibase/materialdesign-components/textfield",
|
||||
|
@ -39,6 +39,4 @@ export const props = {
|
|||
fullwidth:true,
|
||||
helperText: "Add Surname",
|
||||
useCharCounter: true
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { button, icon, textfield, H1, Overline } from "@BBMD";
|
||||
export default { H1, Overline, button, icon, textfield };
|
||||
import h1 from "../H1.svelte"
|
||||
import { button, icon } from "@BBMD"
|
||||
|
||||
export default { h1, button, icon }
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
// export { default as h1 } from "./H1.svelte";
|
||||
|
||||
export { default as icon } from "./Icon.svelte";
|
||||
export { button } from "./Button";
|
||||
export { textfield } from "./Textfield";
|
||||
export * from "./Typography"
|
||||
export { default as h1 } from "./H1.svelte"
|
||||
export { default as icon } from "./Icon.svelte"
|
||||
export { button } from "./Button"
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
myapps/
|
||||
config.js
|
||||
<<<<<<< HEAD
|
||||
/builder/*
|
||||
!/builder/assets/
|
||||
=======
|
||||
builder/
|
||||
>>>>>>> ee5a4e8c962b29242152cbbd8065d8f3ccf65eaf
|
||||
!/builder/assets/
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"main": {
|
||||
"index": {},
|
||||
"appBody": "./main.app.json"
|
||||
},
|
||||
"unauthenticated": {
|
||||
"index": {
|
||||
"_component": "budibase-components/indexHtml",
|
||||
"title": "Test App 1 - Login",
|
||||
"customScripts": [
|
||||
"MyCustomComponents.js"
|
||||
]
|
||||
},
|
||||
"appBody": "./unauthenticated.app.json"
|
||||
},
|
||||
"componentLibraries": [
|
||||
"./customComponents",
|
||||
"./moreCustomComponents",
|
||||
"@budibase/standard-components"
|
||||
],
|
||||
"stylesheets": [
|
||||
"https://css-r-us.com/myawesomestyles.css",
|
||||
"/local.css"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"title": "Test App",
|
||||
"favicon": "./_shared/favicon.png",
|
||||
"stylesheets": [
|
||||
"my-styles.css"
|
||||
],
|
||||
"componentLibraries": [
|
||||
"./customComponents",
|
||||
"./moreCustomComponents"
|
||||
],
|
||||
"props": {
|
||||
"_component": "@budibase/standard-components/div"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "screen1",
|
||||
"description": "",
|
||||
"props": {
|
||||
"_component": "@budibase/standard-components/div",
|
||||
"className": ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "screen2",
|
||||
"description": "",
|
||||
"props": {
|
||||
"_component": "@budibase/standard-components/div",
|
||||
"className": ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"title": "Test App",
|
||||
"favicon": "./_shared/favicon.png",
|
||||
"stylesheets": ["my-styles.css"],
|
||||
"componentLibraries": ["./customComponents","./moreCustomComponents"],
|
||||
"props" : {
|
||||
"_component": "@budibase/standard-components/div"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1,79 +1,2 @@
|
|||
window["##BUDIBASE_APPDEFINITION##"] = {
|
||||
hierarchy: {
|
||||
name: "root",
|
||||
type: "root",
|
||||
children: [
|
||||
{
|
||||
name: "customer",
|
||||
type: "record",
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
typeOptions: {
|
||||
maxLength: 1000,
|
||||
values: null,
|
||||
allowDeclaredValuesOnly: false,
|
||||
},
|
||||
label: "name",
|
||||
getInitialValue: "default",
|
||||
getUndefinedValue: "default",
|
||||
},
|
||||
],
|
||||
children: [
|
||||
{
|
||||
name: "invoiceyooo",
|
||||
type: "record",
|
||||
fields: [
|
||||
{
|
||||
name: "amount",
|
||||
type: "number",
|
||||
typeOptions: {
|
||||
minValue: 99999999999,
|
||||
maxValue: 99999999999,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
label: "amount",
|
||||
getInitialValue: "default",
|
||||
getUndefinedValue: "default",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
validationRules: [],
|
||||
nodeId: 2,
|
||||
indexes: [],
|
||||
allidsShardFactor: 1,
|
||||
collectionName: "invoices",
|
||||
isSingle: false,
|
||||
},
|
||||
],
|
||||
validationRules: [],
|
||||
nodeId: 1,
|
||||
indexes: [],
|
||||
allidsShardFactor: 64,
|
||||
collectionName: "customers",
|
||||
isSingle: false,
|
||||
},
|
||||
],
|
||||
pathMaps: [],
|
||||
indexes: [],
|
||||
nodeId: 0,
|
||||
},
|
||||
componentLibraries: [
|
||||
{
|
||||
importPath: "/lib/customComponents/index.js",
|
||||
libName: "./customComponents",
|
||||
},
|
||||
{
|
||||
importPath: "/lib/moreCustomComponents/index.js",
|
||||
libName: "./moreCustomComponents",
|
||||
},
|
||||
{
|
||||
importPath:
|
||||
"/lib/node_modules/@budibase/standard-components/dist/index.js",
|
||||
libName: "@budibase/standard-components",
|
||||
},
|
||||
],
|
||||
appRootPath: "",
|
||||
props: { _component: "some_component" },
|
||||
}
|
||||
window['##BUDIBASE_APPDEFINITION##'] = {"hierarchy":{"name":"root","type":"root","children":[{"name":"customer","type":"record","fields":[{"name":"name","type":"string","typeOptions":{"maxLength":1000,"values":null,"allowDeclaredValuesOnly":false},"label":"name","getInitialValue":"default","getUndefinedValue":"default"}],"children":[{"name":"invoiceyooo","type":"record","fields":[{"name":"amount","type":"number","typeOptions":{"minValue":99999999999,"maxValue":99999999999,"decimalPlaces":2},"label":"amount","getInitialValue":"default","getUndefinedValue":"default"}],"children":[],"validationRules":[],"nodeId":2,"indexes":[],"allidsShardFactor":1,"collectionName":"invoices","isSingle":false}],"validationRules":[],"nodeId":1,"indexes":[],"allidsShardFactor":64,"collectionName":"customers","isSingle":false}],"pathMaps":[],"indexes":[],"nodeId":0},"componentLibraries":[{"importPath":"/lib/customComponents/index.js","libName":"./customComponents"},{"importPath":"/lib/moreCustomComponents/index.js","libName":"./moreCustomComponents"}],"appRootPath":"","page":{"title":"Test App","favicon":"./_shared/favicon.png","stylesheets":["my-styles.css"],"componentLibraries":["./customComponents","./moreCustomComponents"],"props":{"_component":"@budibase/standard-components/div"}},"screens":[{"name":"screen1","description":"","props":{"_component":"@budibase/standard-components/div","className":""},"_css":"/css/d121e1ecc6cf44f433213222e9ff5d40.css"},{"name":"screen2","description":"","props":{"_component":"@budibase/standard-components/div","className":""},"_css":"/css/7b7c05b78e05c06eb8d69475caadfea3.css"}]};
|
||||
window['##BUDIBASE_UIFUNCTIONS##'] = {'1234':() => 'test return'}
|
|
@ -0,0 +1 @@
|
|||
/*screen2 css*/
|
|
@ -0,0 +1 @@
|
|||
/*screen1 css*/
|
|
@ -0,0 +1 @@
|
|||
/*main page css*/
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset='utf8'>
|
||||
<meta name='viewport' content='width=device-width'>
|
||||
|
||||
<title>Budibase App</title>
|
||||
<link rel='icon' type='image/png' href='//_shared/favicon.png'>
|
||||
<title>Test App</title>
|
||||
<link rel='icon' type='image/png' href='/./_shared/favicon.png'>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
|
@ -14,9 +14,21 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<link rel='stylesheet' href='https://css-r-us.com/myawesomestyles.css'>
|
||||
<link rel='stylesheet' href='///local.css'>
|
||||
|
||||
<link rel='stylesheet' href='//my-styles.css'>
|
||||
|
||||
|
||||
|
||||
<link rel='stylesheet' href='/css/d121e1ecc6cf44f433213222e9ff5d40.css'>
|
||||
|
||||
<link rel='stylesheet' href='/css/7b7c05b78e05c06eb8d69475caadfea3.css'>
|
||||
|
||||
|
||||
|
||||
<link rel='stylesheet' href='/css/f66fc2928f7d850c946e619c1a1f3096.css'>
|
||||
|
||||
|
||||
|
||||
<script src='/clientAppDefinition.js'></script>
|
||||
<script src='/budibase-client.js'></script>
|
||||
<script>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -7,12 +7,13 @@ const send = require("koa-send")
|
|||
const {
|
||||
getPackageForBuilder,
|
||||
getComponents,
|
||||
savePackage,
|
||||
getApps,
|
||||
saveScreen,
|
||||
renameScreen,
|
||||
deleteScreen,
|
||||
savePagePackage,
|
||||
componentLibraryInfo,
|
||||
listScreens,
|
||||
} = require("../utilities/builder")
|
||||
|
||||
const builderPath = resolve(__dirname, "../builder")
|
||||
|
@ -20,8 +21,6 @@ const builderPath = resolve(__dirname, "../builder")
|
|||
module.exports = (config, app) => {
|
||||
const router = new Router()
|
||||
|
||||
const prependSlash = path => (path.startsWith("/") ? path : `/${path}`)
|
||||
|
||||
router
|
||||
.use(session(config, app))
|
||||
.use(async (ctx, next) => {
|
||||
|
@ -95,7 +94,7 @@ module.exports = (config, app) => {
|
|||
await send(ctx, path, { root: builderPath })
|
||||
}
|
||||
})
|
||||
.post("/:appname/api/authenticate", async (ctx, next) => {
|
||||
.post("/:appname/api/authenticate", async ctx => {
|
||||
const user = await ctx.master.authenticate(
|
||||
ctx.sessionId,
|
||||
ctx.params.appname,
|
||||
|
@ -149,10 +148,6 @@ module.exports = (config, app) => {
|
|||
ctx.body = await getPackageForBuilder(config, ctx.params.appname)
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.post("/_builder/api/:appname/appPackage", async ctx => {
|
||||
ctx.body = await savePackage(config, ctx.params.appname, ctx.request.body)
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.get("/_builder/api/:appname/components", async ctx => {
|
||||
try {
|
||||
ctx.body = getComponents(config, ctx.params.appname, ctx.query.lib)
|
||||
|
@ -184,26 +179,55 @@ module.exports = (config, app) => {
|
|||
ctx.body = info.generators
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.post("/_builder/api/:appname/screen", async ctx => {
|
||||
await saveScreen(config, ctx.params.appname, ctx.request.body)
|
||||
.post("/_builder/api/:appname/pages/:pageName", async ctx => {
|
||||
await savePagePackage(
|
||||
config,
|
||||
ctx.params.appname,
|
||||
ctx.params.pageName,
|
||||
ctx.request.body
|
||||
)
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.patch("/_builder/api/:appname/screen", async ctx => {
|
||||
.get("/_builder/api/:appname/pages/:pagename/screens", async ctx => {
|
||||
ctx.body = await listScreens(
|
||||
config,
|
||||
ctx.params.appname,
|
||||
ctx.params.pagename
|
||||
)
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.post("/_builder/api/:appname/pages/:pagename/screen", async ctx => {
|
||||
await saveScreen(
|
||||
config,
|
||||
ctx.params.appname,
|
||||
ctx.params.pagename,
|
||||
ctx.request.body
|
||||
)
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.patch("/_builder/api/:appname/pages/:pagename/screen", async ctx => {
|
||||
await renameScreen(
|
||||
config,
|
||||
ctx.params.appname,
|
||||
ctx.params.pagename,
|
||||
ctx.request.body.oldname,
|
||||
ctx.request.body.newname
|
||||
)
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.delete("/_builder/api/:appname/screen/*", async ctx => {
|
||||
.delete("/_builder/api/:appname/pages/:pagename/screen/*", async ctx => {
|
||||
const name = ctx.request.path.replace(
|
||||
`/_builder/api/${ctx.params.appname}/screen/`,
|
||||
`/_builder/api/${ctx.params.appname}/pages/${ctx.params.pagename}/screen/`,
|
||||
""
|
||||
)
|
||||
|
||||
await deleteScreen(config, ctx.params.appname, decodeURI(name))
|
||||
await deleteScreen(
|
||||
config,
|
||||
ctx.params.appname,
|
||||
ctx.params.pagename,
|
||||
decodeURI(name)
|
||||
)
|
||||
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.get("/:appname", async ctx => {
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
const testAppDef = require("../appPackages/testApp/appDefinition.json")
|
||||
const testAccessLevels = require("../appPackages/testApp/access_levels.json")
|
||||
const testPages = require("../appPackages/testApp/pages.json")
|
||||
const mainPage = require("../appPackages/testApp/pages/main/page.json")
|
||||
const unauthenticatedPage = require("../appPackages/testApp/pages/unauthenticated/page.json")
|
||||
const testComponents = require("../appPackages/testApp/customComponents/components.json")
|
||||
const testMoreComponents = require("../appPackages/testApp/moreCustomComponents/components.json")
|
||||
const statusCodes = require("../utilities/statusCodes")
|
||||
const screen1 = require("../appPackages/testApp/components/myTextBox.json")
|
||||
const screen2 = require("../appPackages/testApp/components/subfolder/otherTextBox.json")
|
||||
const { readJSON, pathExists, unlink } = require("fs-extra")
|
||||
const screen1 = require("../appPackages/testApp/pages/main/screens/screen1.json")
|
||||
const screen2 = require("../appPackages/testApp/pages/main/screens/screen2.json")
|
||||
const { readJSON, pathExists, unlink, readFile } = require("fs-extra")
|
||||
const { getHashedCssPaths } = require("../utilities/builder/convertCssToFiles")
|
||||
|
||||
const app = require("./testApp")()
|
||||
testComponents.textbox.name = `./customComponents/textbox`
|
||||
testMoreComponents.textbox.name = `./moreCustomComponents/textbox`
|
||||
|
||||
beforeAll(async () => {
|
||||
const testComponent = "./appPackages/testApp/components/newTextBox.json"
|
||||
const testComponentAfterMove =
|
||||
"./appPackages/testApp/components/anotherSubFolder/newTextBox.json"
|
||||
const testScreen = "./appPackages/testApp/pages/main/screens/newscreen.json"
|
||||
const testScreenAfterMove =
|
||||
"./appPackages/testApp/pages/main/screens/anotherscreen.json"
|
||||
|
||||
if (await pathExists(testComponent)) await unlink(testComponent)
|
||||
if (await pathExists(testComponentAfterMove))
|
||||
await unlink(testComponentAfterMove)
|
||||
if (await pathExists(testScreen)) await unlink(testScreen)
|
||||
if (await pathExists(testScreenAfterMove)) await unlink(testScreenAfterMove)
|
||||
|
||||
await app.start()
|
||||
})
|
||||
|
@ -45,7 +46,10 @@ it("/apppackage should get pages", async () => {
|
|||
const { body } = await app
|
||||
.get("/_builder/api/testApp/appPackage")
|
||||
.expect(statusCodes.OK)
|
||||
expect(body.pages).toEqual(testPages)
|
||||
expect(body.pages).toEqual({
|
||||
main: mainPage,
|
||||
unauthenticated: unauthenticatedPage,
|
||||
})
|
||||
})
|
||||
|
||||
it("/apppackage should get components", async () => {
|
||||
|
@ -53,123 +57,127 @@ it("/apppackage should get components", async () => {
|
|||
.get("/_builder/api/testApp/appPackage")
|
||||
.expect(statusCodes.OK)
|
||||
|
||||
expect(body.components["./customComponents/textbox"]).toBeDefined()
|
||||
expect(body.components["./moreCustomComponents/textbox"]).toBeDefined()
|
||||
expect(body.components.components["./customComponents/textbox"]).toBeDefined()
|
||||
expect(
|
||||
body.components.components["./moreCustomComponents/textbox"]
|
||||
).toBeDefined()
|
||||
|
||||
expect(body.components["./customComponents/textbox"]).toEqual(
|
||||
expect(body.components.components["./customComponents/textbox"]).toEqual(
|
||||
testComponents.textbox
|
||||
)
|
||||
|
||||
expect(body.components["./moreCustomComponents/textbox"]).toEqual(
|
||||
expect(body.components.components["./moreCustomComponents/textbox"]).toEqual(
|
||||
testMoreComponents.textbox
|
||||
)
|
||||
})
|
||||
|
||||
it("/apppackage should get screens", async () => {
|
||||
it("/pages/:pageName/screens should get screens", async () => {
|
||||
const { body } = await app
|
||||
.get("/_builder/api/testApp/appPackage")
|
||||
.get("/_builder/api/testApp/pages/main/screens")
|
||||
.expect(statusCodes.OK)
|
||||
|
||||
const expectedComponents = {
|
||||
myTextBox: { ...screen1, name: "myTextBox" },
|
||||
"subfolder/otherTextBox": { ...screen2, name: "subfolder/otherTextBox" },
|
||||
screen1: { ...screen1, name: "screen1" },
|
||||
screen2: { ...screen2, name: "screen2" },
|
||||
}
|
||||
|
||||
expect(body.screens).toEqual(expectedComponents)
|
||||
expect(body).toEqual(expectedComponents)
|
||||
})
|
||||
|
||||
it("should be able to create new derived component", async () => {
|
||||
it("should be able to create new screen", async () => {
|
||||
const newscreen = {
|
||||
name: "newTextBox",
|
||||
inherits: "./customComponents/textbox",
|
||||
name: "newscreen",
|
||||
props: {
|
||||
label: "something",
|
||||
_component: "@budibase/standard-component/div",
|
||||
className: "something",
|
||||
},
|
||||
}
|
||||
|
||||
await app
|
||||
.post("/_builder/api/testApp/screen", newscreen)
|
||||
.post("/_builder/api/testApp/pages/main/screen", newscreen)
|
||||
.expect(statusCodes.OK)
|
||||
|
||||
const componentFile = "./appPackages/testApp/components/newTextBox.json"
|
||||
expect(await pathExists(componentFile)).toBe(true)
|
||||
expect(await readJSON(componentFile)).toEqual(newscreen)
|
||||
const screenFile = "./appPackages/testApp/pages/main/screens/newscreen.json"
|
||||
expect(await pathExists(screenFile)).toBe(true)
|
||||
expect(await readJSON(screenFile)).toEqual(newscreen)
|
||||
})
|
||||
|
||||
it("should be able to update derived component", async () => {
|
||||
it("should be able to update screen", async () => {
|
||||
const updatedscreen = {
|
||||
name: "newTextBox",
|
||||
inherits: "./customComponents/textbox",
|
||||
name: "newscreen",
|
||||
props: {
|
||||
label: "something else",
|
||||
_component: "@budibase/standard-component/div",
|
||||
className: "something else",
|
||||
},
|
||||
}
|
||||
|
||||
await app
|
||||
.post("/_builder/api/testApp/screen", updatedscreen)
|
||||
.post("/_builder/api/testApp/pages/main/screen", updatedscreen)
|
||||
.expect(statusCodes.OK)
|
||||
|
||||
const componentFile = "./appPackages/testApp/components/newTextBox.json"
|
||||
expect(await readJSON(componentFile)).toEqual(updatedscreen)
|
||||
const screenFile = "./appPackages/testApp/pages/main/screens/newscreen.json"
|
||||
expect(await readJSON(screenFile)).toEqual(updatedscreen)
|
||||
})
|
||||
|
||||
it("should be able to rename derived component", async () => {
|
||||
it("should be able to rename screen", async () => {
|
||||
await app
|
||||
.patch("/_builder/api/testApp/screen", {
|
||||
oldname: "newTextBox",
|
||||
newname: "anotherSubFolder/newTextBox",
|
||||
.patch("/_builder/api/testApp/pages/main/screen", {
|
||||
oldname: "newscreen",
|
||||
newname: "anotherscreen",
|
||||
})
|
||||
.expect(statusCodes.OK)
|
||||
|
||||
const oldcomponentFile = "./appPackages/testApp/components/newTextBox.json"
|
||||
const oldcomponentFile =
|
||||
"./appPackages/testApp/pages/main/screens/newscreen.json"
|
||||
const newcomponentFile =
|
||||
"./appPackages/testApp/components/anotherSubFolder/newTextBox.json"
|
||||
"./appPackages/testApp/pages/main/screens/anotherscreen.json"
|
||||
|
||||
expect(await pathExists(oldcomponentFile)).toBe(false)
|
||||
expect(await pathExists(newcomponentFile)).toBe(true)
|
||||
})
|
||||
|
||||
it("should be able to delete derived component", async () => {
|
||||
it("should be able to delete screen", async () => {
|
||||
await app
|
||||
.delete("/_builder/api/testApp/screen/anotherSubFolder/newTextBox")
|
||||
.delete("/_builder/api/testApp/pages/main/screen/anotherscreen")
|
||||
.expect(statusCodes.OK)
|
||||
|
||||
const componentFile =
|
||||
"./appPackages/testApp/components/anotherSubFolder/newTextBox.json"
|
||||
const componentDir = "./appPackages/testApp/components/anotherSubFolder"
|
||||
"./appPackages/testApp/pages/main/screens/anotherscreen.json"
|
||||
expect(await pathExists(componentFile)).toBe(false)
|
||||
expect(await pathExists(componentDir)).toBe(false)
|
||||
})
|
||||
|
||||
it("/savePackage should prepare all necessary client files", async () => {
|
||||
it("/savePage should prepare all necessary client files", async () => {
|
||||
const mainCss = "/*main page css*/"
|
||||
mainPage._css = mainCss
|
||||
const screen1Css = "/*screen1 css*/"
|
||||
screen1._css = screen1Css
|
||||
const screen2Css = "/*screen2 css*/"
|
||||
screen2._css = screen2Css
|
||||
|
||||
await app
|
||||
.post("/_builder/api/testApp/appPackage", {
|
||||
.post("/_builder/api/testApp/pages/main", {
|
||||
appDefinition: testAppDef,
|
||||
accessLevels: testAccessLevels,
|
||||
pages: testPages,
|
||||
page: mainPage,
|
||||
uiFunctions: "{'1234':() => 'test return'}",
|
||||
screens: [screen1, screen2],
|
||||
})
|
||||
.expect(statusCodes.OK)
|
||||
|
||||
const publicFolderMain = relative =>
|
||||
"./appPackages/testApp/public/main" + relative
|
||||
const publicFolderUnauth = relative =>
|
||||
"./appPackages/testApp/public/unauthenticated" + relative
|
||||
|
||||
const cssDir = publicFolderMain("/css")
|
||||
|
||||
expect(await pathExists(publicFolderMain("/index.html"))).toBe(true)
|
||||
expect(await pathExists(publicFolderUnauth("/index.html"))).toBe(true)
|
||||
|
||||
expect(
|
||||
await pathExists(publicFolderMain("/lib/customComponents/index.js"))
|
||||
).toBe(true)
|
||||
expect(
|
||||
await pathExists(publicFolderUnauth("/lib/customComponents/index.js"))
|
||||
).toBe(true)
|
||||
|
||||
expect(
|
||||
await pathExists(publicFolderMain("/lib/moreCustomComponents/index.js"))
|
||||
).toBe(true)
|
||||
expect(
|
||||
await pathExists(publicFolderUnauth("/lib/moreCustomComponents/index.js"))
|
||||
).toBe(true)
|
||||
|
||||
expect(
|
||||
await pathExists(
|
||||
|
@ -178,16 +186,34 @@ it("/savePackage should prepare all necessary client files", async () => {
|
|||
)
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
await pathExists(
|
||||
publicFolderUnauth(
|
||||
"/lib/node_modules/@budibase/standard-components/dist/index.js"
|
||||
)
|
||||
)
|
||||
).toBe(true)
|
||||
|
||||
expect(await pathExists(publicFolderUnauth("/budibase-client.js"))).toBe(true)
|
||||
expect(await pathExists(publicFolderUnauth("/clientAppDefinition.js"))).toBe(
|
||||
true
|
||||
const indexHtmlMain = await readFile(publicFolderMain("/index.html"), "utf8")
|
||||
|
||||
const pageCssPaths = getHashedCssPaths(cssDir, mainCss)
|
||||
const screen1CssPaths = getHashedCssPaths(cssDir, screen1Css)
|
||||
const screen2CssPaths = getHashedCssPaths(cssDir, screen2Css)
|
||||
|
||||
expect(await pathExists(publicFolderMain(pageCssPaths.url))).toBe(true)
|
||||
const savedPageCss = await readFile(
|
||||
publicFolderMain(pageCssPaths.url),
|
||||
"utf8"
|
||||
)
|
||||
expect(savedPageCss).toEqual(mainCss)
|
||||
expect(indexHtmlMain.includes(pageCssPaths.url)).toBe(true)
|
||||
|
||||
expect(await pathExists(publicFolderMain(screen1CssPaths.url))).toBe(true)
|
||||
const savedScreen1Css = await readFile(
|
||||
publicFolderMain(screen1CssPaths.url),
|
||||
"utf8"
|
||||
)
|
||||
expect(savedScreen1Css).toEqual(screen1Css)
|
||||
expect(indexHtmlMain.includes(screen1CssPaths.url)).toBe(true)
|
||||
|
||||
expect(await pathExists(publicFolderMain(screen2CssPaths.url))).toBe(true)
|
||||
const savedScreen2Css = await readFile(
|
||||
publicFolderMain(screen2CssPaths.url),
|
||||
"utf8"
|
||||
)
|
||||
expect(savedScreen2Css).toEqual(screen2Css)
|
||||
expect(indexHtmlMain.includes(screen2CssPaths.url)).toBe(true)
|
||||
})
|
||||
|
|
|
@ -11,34 +11,18 @@ const {
|
|||
} = require("fs-extra")
|
||||
const { join, resolve, dirname } = require("path")
|
||||
const sqrl = require("squirrelly")
|
||||
const { convertCssToFiles } = require("./convertCssToFiles")
|
||||
|
||||
module.exports = async (config, appname, pages, appdefinition) => {
|
||||
module.exports = async (config, appname, pkg) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
|
||||
await buildClientAppDefinition(
|
||||
config,
|
||||
appname,
|
||||
appdefinition,
|
||||
appPath,
|
||||
pages,
|
||||
"main"
|
||||
)
|
||||
await convertCssToFiles(publicPath(appPath, pkg.pageName), pkg)
|
||||
|
||||
await buildClientAppDefinition(
|
||||
config,
|
||||
appname,
|
||||
appdefinition,
|
||||
appPath,
|
||||
pages,
|
||||
"unauthenticated"
|
||||
)
|
||||
await buildIndexHtml(config, appname, appPath, pkg)
|
||||
|
||||
await buildIndexHtml(config, appname, appPath, pages, "main")
|
||||
await buildClientAppDefinition(config, appname, pkg, appPath)
|
||||
|
||||
await buildIndexHtml(config, appname, appPath, pages, "unauthenticated")
|
||||
|
||||
await copyClientLib(appPath, "main")
|
||||
await copyClientLib(appPath, "unauthenticated")
|
||||
await copyClientLib(appPath, pkg.pageName)
|
||||
}
|
||||
|
||||
const publicPath = (appPath, pageName) => join(appPath, "public", pageName)
|
||||
|
@ -46,10 +30,11 @@ const rootPath = (config, appname) =>
|
|||
config.useAppRootPath ? `/${appname}` : ""
|
||||
|
||||
const copyClientLib = async (appPath, pageName) => {
|
||||
var sourcepath = require.resolve("@budibase/client")
|
||||
var destPath = join(publicPath(appPath, pageName), "budibase-client.js")
|
||||
const sourcepath = require.resolve("@budibase/client")
|
||||
const destPath = join(publicPath(appPath, pageName), "budibase-client.js")
|
||||
|
||||
await copyFile(sourcepath, destPath, constants.COPYFILE_FICLONE)
|
||||
|
||||
await copyFile(
|
||||
sourcepath + ".map",
|
||||
destPath + ".map",
|
||||
|
@ -57,8 +42,8 @@ const copyClientLib = async (appPath, pageName) => {
|
|||
)
|
||||
}
|
||||
|
||||
const buildIndexHtml = async (config, appname, appPath, pages, pageName) => {
|
||||
const appPublicPath = publicPath(appPath, pageName)
|
||||
const buildIndexHtml = async (config, appname, appPath, pkg) => {
|
||||
const appPublicPath = publicPath(appPath, pkg.pageName)
|
||||
const appRootPath = rootPath(config, appname)
|
||||
|
||||
const stylesheetUrl = s =>
|
||||
|
@ -67,10 +52,11 @@ const buildIndexHtml = async (config, appname, appPath, pages, pageName) => {
|
|||
: `/${rootPath(config, appname)}/${s}`
|
||||
|
||||
const templateObj = {
|
||||
title: pages[pageName].index.title || "Budibase App",
|
||||
favicon: `${appRootPath}/${pages[pageName].index.favicon ||
|
||||
"/_shared/favicon.png"}`,
|
||||
stylesheets: (pages.stylesheets || []).map(stylesheetUrl),
|
||||
title: pkg.page.title || "Budibase App",
|
||||
favicon: `${appRootPath}/${pkg.page.favicon || "/_shared/favicon.png"}`,
|
||||
stylesheets: (pkg.page.stylesheets || []).map(stylesheetUrl),
|
||||
screenStyles: pkg.screens.filter(s => s._css).map(s => s._css),
|
||||
pageStyle: pkg.page._css,
|
||||
appRootPath,
|
||||
}
|
||||
|
||||
|
@ -86,20 +72,14 @@ const buildIndexHtml = async (config, appname, appPath, pages, pageName) => {
|
|||
await writeFile(indexHtmlPath, indexHtml, { flag: "w+" })
|
||||
}
|
||||
|
||||
const buildClientAppDefinition = async (
|
||||
config,
|
||||
appname,
|
||||
appdefinition,
|
||||
appPath,
|
||||
pages,
|
||||
pageName
|
||||
) => {
|
||||
const appPublicPath = publicPath(appPath, pageName)
|
||||
const buildClientAppDefinition = async (config, appname, pkg) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
const appPublicPath = publicPath(appPath, pkg.pageName)
|
||||
const appRootPath = rootPath(config, appname)
|
||||
|
||||
const componentLibraries = []
|
||||
|
||||
for (let lib of pages.componentLibraries) {
|
||||
for (let lib of pkg.page.componentLibraries) {
|
||||
const info = await componentLibraryInfo(appPath, lib)
|
||||
const libFile = info.components._lib || "index.js"
|
||||
const source = join(info.libDir, libFile)
|
||||
|
@ -131,16 +111,27 @@ const buildClientAppDefinition = async (
|
|||
|
||||
const filename = join(appPublicPath, "clientAppDefinition.js")
|
||||
|
||||
if (pkg.page._css) {
|
||||
delete pkg.page._css
|
||||
}
|
||||
|
||||
for (let screen of pkg.screens) {
|
||||
if (screen._css) {
|
||||
delete pkg.page._css
|
||||
}
|
||||
}
|
||||
|
||||
const clientAppDefObj = {
|
||||
hierarchy: appdefinition.hierarchy,
|
||||
hierarchy: pkg.appDefinition.hierarchy,
|
||||
componentLibraries: componentLibraries,
|
||||
appRootPath: appRootPath,
|
||||
props: appdefinition.props[pageName],
|
||||
page: pkg.page,
|
||||
screens: pkg.screens,
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
filename,
|
||||
`window['##BUDIBASE_APPDEFINITION##'] = ${JSON.stringify(clientAppDefObj)};
|
||||
window['##BUDIBASE_UIFUNCTIONS##'] = ${appdefinition.uiFunctions}`
|
||||
window['##BUDIBASE_UIFUNCTIONS##'] = ${pkg.uiFunctions}`
|
||||
)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
const crypto = require("crypto")
|
||||
const { ensureDir, emptyDir, writeFile } = require("fs-extra")
|
||||
const { join } = require("path")
|
||||
|
||||
module.exports.convertCssToFiles = async (publicPagePath, pkg) => {
|
||||
const cssDir = join(publicPagePath, "css")
|
||||
await ensureDir(cssDir)
|
||||
await emptyDir(cssDir)
|
||||
|
||||
for (let screen of pkg.screens) {
|
||||
if (!screen._css) continue
|
||||
if (screen._css.trim().length === 0) {
|
||||
delete screen._css
|
||||
continue
|
||||
}
|
||||
screen._css = await createCssFile(cssDir, screen._css)
|
||||
}
|
||||
|
||||
if (pkg.page._css) {
|
||||
pkg.page._css = await createCssFile(cssDir, pkg.page._css)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.getHashedCssPaths = (cssDir, _css) => {
|
||||
const fileName =
|
||||
crypto
|
||||
.createHash("md5")
|
||||
.update(_css)
|
||||
.digest("hex") + ".css"
|
||||
|
||||
const filePath = join(cssDir, fileName)
|
||||
const url = `/css/${fileName}`
|
||||
|
||||
return { filePath, url }
|
||||
}
|
||||
|
||||
const createCssFile = async (cssDir, _css) => {
|
||||
const { filePath, url } = module.exports.getHashedCssPaths(cssDir, _css)
|
||||
|
||||
await writeFile(filePath, _css)
|
||||
|
||||
return url
|
||||
}
|
|
@ -11,16 +11,15 @@ const {
|
|||
} = require("fs-extra")
|
||||
const { join, dirname } = require("path")
|
||||
const { $ } = require("@budibase/core").common
|
||||
const { keyBy, intersection, map } = require("lodash/fp")
|
||||
const { keyBy, intersection, map, values, flatten } = require("lodash/fp")
|
||||
const { merge } = require("lodash")
|
||||
|
||||
const { componentLibraryInfo } = require("./componentLibraryInfo")
|
||||
const savePackage = require("./savePackage")
|
||||
const buildApp = require("./buildApp")
|
||||
const savePagePackage = require("./savePagePackage")
|
||||
const buildPage = require("./buildPage")
|
||||
|
||||
module.exports.savePackage = savePackage
|
||||
module.exports.savePagePackage = savePagePackage
|
||||
|
||||
const getPages = async appPath => await readJSON(`${appPath}/pages.json`)
|
||||
const getAppDefinition = async appPath =>
|
||||
await readJSON(`${appPath}/appDefinition.json`)
|
||||
|
||||
|
@ -37,8 +36,6 @@ module.exports.getPackageForBuilder = async (config, appname) => {
|
|||
pages,
|
||||
|
||||
components: await getComponents(appPath, pages),
|
||||
|
||||
screens: keyBy("name")(await fetchscreens(appPath)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,34 +45,65 @@ module.exports.getApps = async (config, master) => {
|
|||
return $(master.listApplications(), [map(a => a.name), intersection(dirs)])
|
||||
}
|
||||
|
||||
const componentPath = (appPath, name) =>
|
||||
join(appPath, "components", name + ".json")
|
||||
const getPages = async appPath => {
|
||||
const pages = {}
|
||||
|
||||
module.exports.saveScreen = async (config, appname, component) => {
|
||||
const pageFolders = await readdir(join(appPath, "pages"))
|
||||
for (let pageFolder of pageFolders) {
|
||||
try {
|
||||
pages[pageFolder] = await readJSON(
|
||||
join(appPath, "pages", pageFolder, "page.json")
|
||||
)
|
||||
} catch (_) {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
const screenPath = (appPath, pageName, name) =>
|
||||
join(appPath, "pages", pageName, "screens", name + ".json")
|
||||
|
||||
module.exports.listScreens = async (config, appname, pagename) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
const compPath = componentPath(appPath, component.name)
|
||||
return keyBy("name")(await fetchscreens(appPath, pagename))
|
||||
}
|
||||
|
||||
module.exports.saveScreen = async (config, appname, pagename, screen) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
const compPath = screenPath(appPath, pagename, screen.name)
|
||||
await ensureDir(dirname(compPath))
|
||||
await writeJSON(compPath, component, {
|
||||
if (screen._css) {
|
||||
delete screen._css
|
||||
}
|
||||
await writeJSON(compPath, screen, {
|
||||
encoding: "utf8",
|
||||
flag: "w",
|
||||
spaces: 2,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.renameScreen = async (config, appname, oldName, newName) => {
|
||||
module.exports.renameScreen = async (
|
||||
config,
|
||||
appname,
|
||||
pagename,
|
||||
oldName,
|
||||
newName
|
||||
) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
|
||||
const oldComponentPath = componentPath(appPath, oldName)
|
||||
const oldComponentPath = screenPath(appPath, pagename, oldName)
|
||||
|
||||
const newComponentPath = componentPath(appPath, newName)
|
||||
const newComponentPath = screenPath(appPath, pagename, newName)
|
||||
|
||||
await ensureDir(dirname(newComponentPath))
|
||||
await rename(oldComponentPath, newComponentPath)
|
||||
}
|
||||
|
||||
module.exports.deleteScreen = async (config, appname, name) => {
|
||||
module.exports.deleteScreen = async (config, appname, pagename, name) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
const componentFile = componentPath(appPath, name)
|
||||
const componentFile = screenPath(appPath, pagename, name)
|
||||
await unlink(componentFile)
|
||||
|
||||
const dir = dirname(componentFile)
|
||||
|
@ -84,6 +112,20 @@ module.exports.deleteScreen = async (config, appname, name) => {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports.savePage = async (config, appname, pagename, page) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
const pageDir = join(appPath, "pages", pagename)
|
||||
|
||||
await ensureDir(pageDir)
|
||||
await writeJSON(join(pageDir, "page.json"), page, {
|
||||
encoding: "utf8",
|
||||
flag: "w",
|
||||
space: 2,
|
||||
})
|
||||
const appDefinition = await getAppDefinition(appPath)
|
||||
await buildPage(config, appname, appDefinition, pagename, page)
|
||||
}
|
||||
|
||||
module.exports.componentLibraryInfo = async (config, appname, lib) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
return await componentLibraryInfo(appPath, lib)
|
||||
|
@ -92,11 +134,11 @@ module.exports.componentLibraryInfo = async (config, appname, lib) => {
|
|||
const getComponents = async (appPath, pages, lib) => {
|
||||
let libs
|
||||
if (!lib) {
|
||||
pages = pages || (await readJSON(`${appPath}/pages.json`))
|
||||
pages = pages || (await getPages(appPath))
|
||||
|
||||
if (!pages.componentLibraries) return []
|
||||
if (!pages) return []
|
||||
|
||||
libs = pages.componentLibraries
|
||||
libs = $(pages, [values, map(p => p.componentLibraries), flatten])
|
||||
} else {
|
||||
libs = [lib]
|
||||
}
|
||||
|
@ -116,12 +158,12 @@ const getComponents = async (appPath, pages, lib) => {
|
|||
return { components, generators }
|
||||
}
|
||||
|
||||
const fetchscreens = async (appPath, relativePath = "") => {
|
||||
const currentDir = join(appPath, "components", relativePath)
|
||||
const fetchscreens = async (appPath, pagename, relativePath = "") => {
|
||||
const currentDir = join(appPath, "pages", pagename, "screens", relativePath)
|
||||
|
||||
const contents = await readdir(currentDir)
|
||||
|
||||
const components = []
|
||||
const screens = []
|
||||
|
||||
for (let item of contents) {
|
||||
const itemRelativePath = join(relativePath, item)
|
||||
|
@ -139,7 +181,7 @@ const fetchscreens = async (appPath, relativePath = "") => {
|
|||
|
||||
component.props = component.props || {}
|
||||
|
||||
components.push(component)
|
||||
screens.push(component)
|
||||
} else {
|
||||
const childComponents = await fetchscreens(
|
||||
appPath,
|
||||
|
@ -147,12 +189,12 @@ const fetchscreens = async (appPath, relativePath = "") => {
|
|||
)
|
||||
|
||||
for (let c of childComponents) {
|
||||
components.push(c)
|
||||
screens.push(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return components
|
||||
return screens
|
||||
}
|
||||
|
||||
module.exports.getComponents = getComponents
|
||||
|
|
|
@ -18,6 +18,15 @@
|
|||
<link rel='stylesheet' href='{{ @this }}'>
|
||||
{{ /each }}
|
||||
|
||||
{{ each(options.screenStyles) }}
|
||||
<link rel='stylesheet' href='{{ @this }}'>
|
||||
{{ /each }}
|
||||
|
||||
{{ if(options.pageStyle) }}
|
||||
<link rel='stylesheet' href='{{ pageStyle }}'>
|
||||
{{ /if }}
|
||||
|
||||
|
||||
<script src='{{ appRootPath }}/clientAppDefinition.js'></script>
|
||||
<script src='{{ appRootPath }}/budibase-client.js'></script>
|
||||
<script>
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
const { appPackageFolder } = require("../createAppPackage")
|
||||
const { writeJSON } = require("fs-extra")
|
||||
const buildApp = require("./buildApp")
|
||||
|
||||
module.exports = async (config, appname, pkg) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
await writeJSON(`${appPath}/appDefinition.json`, pkg.appDefinition, {
|
||||
spaces: 2,
|
||||
})
|
||||
|
||||
await writeJSON(`${appPath}/access_levels.json`, pkg.accessLevels, {
|
||||
spaces: 2,
|
||||
})
|
||||
|
||||
await writeJSON(`${appPath}/pages.json`, pkg.pages, { spaces: 2 })
|
||||
|
||||
await buildApp(config, appname, pkg.pages, pkg.appDefinition)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
const { appPackageFolder } = require("../createAppPackage")
|
||||
const { writeJSON } = require("fs-extra")
|
||||
const { join } = require("path")
|
||||
|
||||
const buildPage = require("./buildPage")
|
||||
|
||||
module.exports = async (config, appname, pageName, pkg) => {
|
||||
const appPath = appPackageFolder(config, appname)
|
||||
pkg.pageName = pageName
|
||||
|
||||
await writeJSON(`${appPath}/appDefinition.json`, pkg.appDefinition, {
|
||||
spaces: 2,
|
||||
})
|
||||
|
||||
await writeJSON(`${appPath}/access_levels.json`, pkg.accessLevels, {
|
||||
spaces: 2,
|
||||
})
|
||||
|
||||
await buildPage(config, appname, pkg)
|
||||
|
||||
const pageFile = join(appPath, "pages", pageName, "page.json")
|
||||
|
||||
if (pkg.page._css) {
|
||||
delete pkg.page._css
|
||||
}
|
||||
|
||||
await writeJSON(pageFile, pkg.page, {
|
||||
spaces: 2,
|
||||
})
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue