Builder saves backend and front end seperately (#88)

* 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

* backend and fronend save seperately

Co-authored-by: pngwn <pnda007@gmail.com>
This commit is contained in:
Michael Shanks 2020-02-10 21:35:51 +00:00 committed by GitHub
parent 8a80d8801a
commit 34b957f331
36 changed files with 13522 additions and 11612 deletions

View File

@ -295,7 +295,7 @@ const saveCurrentNode = store => () => {
s.currentNodeIsNew = false
savePackage(store, s)
saveBackend(s)
return s
})
@ -331,7 +331,7 @@ const deleteCurrentNode = store => () => {
)(nodeToDelete.parent().indexes)
}
s.errors = []
savePackage(store, s)
saveBackend(s)
return s
})
}
@ -370,7 +370,7 @@ const saveAction = store => (newAction, isNew, oldAction = null) => {
} else {
s.actions.push(newAction)
}
savePackage(store, s)
saveBackend(s)
return s
})
}
@ -378,7 +378,7 @@ const saveAction = store => (newAction, isNew, oldAction = null) => {
const deleteAction = store => action => {
store.update(s => {
s.actions = filter(a => a.name !== action.name)(s.actions)
savePackage(store, s)
saveBackend(s)
return s
})
}
@ -396,7 +396,7 @@ const saveTrigger = store => (newTrigger, isNew, oldTrigger = null) => {
} else {
s.triggers.push(newTrigger)
}
savePackage(store, s)
saveBackend(s)
return s
})
}
@ -429,7 +429,7 @@ const saveLevel = store => (newLevel, isNew, oldLevel = null) => {
incrementAccessLevelsVersion(s)
savePackage(store, s)
saveBackend(s)
return s
})
}
@ -440,7 +440,7 @@ const deleteLevel = store => level => {
s.accessLevels.levels
)
incrementAccessLevelsVersion(s)
savePackage(store, s)
saveBackend(s)
return s
})
}
@ -477,18 +477,18 @@ const _saveScreen = (store, s, screen) => {
`/_builder/api/${s.appname}/pages/${s.currentPageName}/screen`,
screen
)
.then(() => savePackage(store, s))
.then(() => _savePage(s))
return s
}
const _save = (appname, screen, store, s) =>
const _saveScreenApi = (screen, s) =>
api
.post(
`/_builder/api/${s.appname}/pages/${s.currentPageName}/screen`,
screen
)
.then(() => savePackage(store, s))
.then(() => _savePage(s))
const createScreen = store => (screenName, route, layoutComponentName) => {
store.update(s => {
@ -530,7 +530,7 @@ const createGeneratedComponents = store => components => {
await api.post(`/_builder/api/${s.appname}/screen`, c)
}
await savePackage(store, s)
await _savePage(s)
}
doCreate()
@ -591,7 +591,7 @@ const renameScreen = store => (oldname, newname) => {
})
.then(() => saveAllChanged())
.then(() => {
savePackage(store, s)
_savePage(s)
})
return s
@ -605,7 +605,7 @@ const savePage = store => async page => {
}
s.pages[s.currentPageName] = page
savePackage(store, s)
_savePage(s)
return s
})
}
@ -634,7 +634,7 @@ const addComponentLibrary = store => async lib => {
])
s.pages.componentLibraries.push(lib)
savePackage(store, s)
_savePage(s)
}
return s
@ -646,7 +646,7 @@ const removeComponentLibrary = store => lib => {
s.pages.componentLibraries = filter(l => l !== lib)(
s.pages.componentLibraries
)
savePackage(store, s)
_savePage(s)
return s
})
@ -655,7 +655,7 @@ const removeComponentLibrary = store => lib => {
const addStylesheet = store => stylesheet => {
store.update(s => {
s.pages.stylesheets.push(stylesheet)
savePackage(store, s)
_savePage(s)
return s
})
}
@ -663,14 +663,14 @@ const addStylesheet = store => stylesheet => {
const removeStylesheet = store => stylesheet => {
store.update(s => {
s.pages.stylesheets = filter(s => s !== stylesheet)(s.pages.stylesheets)
savePackage(store, s)
_savePage(s)
return s
})
}
const refreshComponents = store => async () => {
const componentsAndGenerators = await api
.get(`/_builder/api/${db.appname}/components`)
.get(`/_builder/api/${appname}/components`)
.then(r => r.json())
const components = pipe(componentsAndGenerators.components, [
@ -688,20 +688,24 @@ const refreshComponents = store => async () => {
})
}
const savePackage = async (store, s) => {
const _savePage = async s => {
const page = s.pages[s.currentPageName]
await api.post(`/_builder/api/${appname}/pages/${s.currentPageName}`, {
page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: "{'1234':() => 'test return'}",
screens: page.screens,
})
}
const saveBackend = async s => {
await api.post(`/_builder/api/${appname}/backend`, {
appDefinition: {
hierarchy: s.hierarchy,
actions: s.actions,
triggers: s.triggers,
},
accessLevels: s.accessLevels,
page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: "{'1234':() => 'test return'}",
props: page.props,
screens: page.screens,
})
}
@ -731,7 +735,7 @@ const addChildComponent = store => componentName => {
newComponent.props
)
savePackage(store, s)
_savePage(s)
return s
})
@ -750,7 +754,7 @@ const setComponentProp = store => (name, value) => {
s.currentComponentInfo[name] = value
s.currentFrontEndType === "page"
? savePackage(store, s, s.currentPreviewItem)
? _savePage(s, s.currentPreviewItem)
: _saveScreen(store, s, s.currentPreviewItem)
s.currentComponentInfo = current_component
@ -770,8 +774,8 @@ const setComponentStyle = store => (type, name, value) => {
// save without messing with the store
s.currentFrontEndType === "page"
? savePackage(store, s, s.currentPreviewItem)
: _save(s.appname, s.currentPreviewItem, store, s)
? _savePage(s)
: _saveScreenApi(s.currentPreviewItem, s)
return s
})
}
@ -782,7 +786,7 @@ const setComponentCode = store => code => {
setCurrentScreenFunctions(s)
// save without messing with the store
_save(s.appname, s.currentPreviewItem, store, s)
_saveScreenApi(s.currentPreviewItem, s)
return s
})

View File

@ -29,14 +29,17 @@
[map(s => `<link rel="stylesheet" href="${s}"/>`), join("\n")]
)
$: appDefinition = {
$: frontendDefinition = {
componentLibraries: $store.loadLibraryUrls(),
props:
$store.currentPreviewItem &&
transform_component($store.currentPreviewItem, true),
hierarchy: $store.hierarchy,
page: $store.currentPreviewItem,
screens: [],
appRootPath: "",
}
$: backendDefinition = {
hierarchy: $store.hierarchy,
}
</script>
<div class="component-container">
@ -55,8 +58,9 @@
}
<\/style>
<\script>
window["##BUDIBASE_APPDEFINITION##"] = ${JSON.stringify(appDefinition)};
window["##BUDIBASE_UIFUNCTIONS"] = ${$store.currentScreenFunctions};
window["##BUDIBASE_FRONTEND_DEFINITION##"] = ${JSON.stringify(frontendDefinition)};
window["##BUDIBASE_BACKEND_DEFINITION##"] = ${JSON.stringify(backendDefinition)};
window["##BUDIBASE_FRONTEND_FUNCTIONS##"] = ${$store.currentScreenFunctions};
import('/_builder/budibase-client.esm.mjs')
.then(module => {

View File

@ -1,10 +1,10 @@
export const createCoreApp = (appDefinition, user) => {
export const createCoreApp = (backendDefinition, user) => {
const app = {
datastore: null,
crypto: null,
publish: () => {},
hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions,
hierarchy: backendDefinition.hierarchy,
actions: backendDefinition.actions,
user,
}

View File

@ -2,8 +2,8 @@ import { createCoreApp } from "./createCoreApp"
import { getNew, getNewChild } from "../../../core/src/recordApi/getNew"
import { constructHierarchy } from "../../../core/src/templateApi/createNodes"
export const createCoreApi = (appDefinition, user) => {
const app = createCoreApp(appDefinition, user)
export const createCoreApi = (backendDefinition, user) => {
const app = createCoreApp(backendDefinition, user)
return {
recordApi: {

View File

@ -11,22 +11,23 @@ import { screenRouter } from "./render/screenRouter"
export const createApp = (
document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
user,
uiFunctions,
screens
) => {
const coreApi = createCoreApi(appDefinition, user)
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
appDefinition.hierarchy
const coreApi = createCoreApi(backendDefinition, user)
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
backendDefinition.hierarchy
)
const pageStore = writable({
_bbuser: user,
})
const relativeUrl = url =>
appDefinition.appRootPath
? appDefinition.appRootPath + "/" + trimSlash(url)
frontendDefinition.appRootPath
? frontendDefinition.appRootPath + "/" + trimSlash(url)
: url
const apiCall = method => (url, body) =>
@ -89,7 +90,7 @@ export const createApp = (
store,
document,
componentLibraries,
appDefinition,
frontendDefinition,
hydrate,
uiFunctions,
treeNode,

View File

@ -10,8 +10,9 @@ export const loadBudibase = async ({
localStorage,
uiFunctions,
}) => {
const appDefinition = window["##BUDIBASE_APPDEFINITION##"]
const uiFunctionsFromWindow = window["##BUDIBASE_UIFUNCTIONS##"]
const backendDefinition = window["##BUDIBASE_BACKEND_DEFINITION##"]
const frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"]
const uiFunctionsFromWindow = window["##BUDIBASE_FRONTEND_FUNCTIONS##"]
uiFunctions = uiFunctionsFromWindow || uiFunctions
const userFromStorage = localStorage.getItem("budibase:user")
@ -26,16 +27,16 @@ export const loadBudibase = async ({
}
const rootPath =
appDefinition.appRootPath === ""
frontendDefinition.appRootPath === ""
? ""
: "/" + trimSlash(appDefinition.appRootPath)
: "/" + trimSlash(frontendDefinition.appRootPath)
if (!componentLibraries) {
const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib)
componentLibraries = {}
for (let lib of appDefinition.componentLibraries) {
for (let lib of frontendDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath)
)
@ -45,17 +46,18 @@ export const loadBudibase = async ({
componentLibraries[builtinLibName] = builtins(window)
if (!page) {
page = appDefinition.page
page = frontendDefinition.page
}
if (!screens) {
screens = appDefinition.screens
screens = frontendDefinition.screens
}
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
window.document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
user,
uiFunctions || {},
screens

View File

@ -16,7 +16,7 @@ export const initialiseChildren = initialiseOpts => (
store,
componentLibraries,
treeNode,
appDefinition,
frontendDefinition,
hydrate,
onScreenSlotRendered,
} = initialiseOpts
@ -43,7 +43,7 @@ export const initialiseChildren = initialiseOpts => (
store,
childProps,
coreApi,
appDefinition.appRootPath
frontendDefinition.appRootPath
)
const componentConstructor = componentLibraries[libName][componentName]

View File

@ -51,13 +51,16 @@ const autoAssignIds = (props, count = 0) => {
}
const setAppDef = (window, page, screens) => {
window["##BUDIBASE_APPDEFINITION##"] = {
window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
componentLibraries: [],
page,
screens,
hierarchy: {},
appRootPath: "",
}
window["##BUDIBASE_BACKEND_DEFINITION##"] = {
hierarchy: {},
}
}
const allLibs = window => ({

View File

@ -2,9 +2,61 @@ var app = (function (exports) {
'use strict';
function noop() { }
function run(fn) {
return fn();
}
function run_all(fns) {
fns.forEach(run);
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function destroy_component(component, detaching) {
if (component.$$.fragment) {
run_all(component.$$.on_destroy);
component.$$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
component.$$.on_destroy = component.$$.fragment = null;
component.$$.ctx = {};
}
}
let SvelteElement;
if (typeof HTMLElement !== 'undefined') {
SvelteElement = class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
this.appendChild(this.$$.slotted[key]);
}
}
attributeChangedCallback(attr, _oldValue, newValue) {
this[attr] = newValue;
}
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
}
$on(type, callback) {
// TODO should this delegate to addEventListener?
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1)
callbacks.splice(index, 1);
};
}
$set() {
// overridden by instance, if it has props
}
};
}
const subscriber_queue = [];
/**
@ -58,13 +110,13 @@ var app = (function (exports) {
return { set, update, subscribe };
}
const createCoreApp = (appDefinition, user) => {
const createCoreApp = (backendDefinition, user) => {
const app = {
datastore: null,
crypto: null,
publish: () => {},
hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions,
hierarchy: backendDefinition.hierarchy,
actions: backendDefinition.actions,
user,
};
@ -1349,17 +1401,48 @@ var app = (function (exports) {
var randomByteBrowser = randomByte;
var format_browser = function (random, alphabet, size) {
/**
* Secure random string generator with custom alphabet.
*
* Alphabet must contain 256 symbols or less. Otherwise, the generator
* will not be secure.
*
* @param {generator} random The random bytes generator.
* @param {string} alphabet Symbols to be used in new random string.
* @param {size} size The number of symbols in new random string.
*
* @return {string} Random string.
*
* @example
* const format = require('nanoid/format')
*
* function random (size) {
* const result = []
* for (let i = 0; i < size; i++) {
* result.push(randomByte())
* }
* return result
* }
*
* format(random, "abcdef", 5) //=> "fbaef"
*
* @name format
* @function
*/
var format = function (random, alphabet, size) {
var mask = (2 << Math.log(alphabet.length - 1) / Math.LN2) - 1;
var step = -~(1.6 * mask * size / alphabet.length);
var id = '';
var step = Math.ceil(1.6 * mask * size / alphabet.length);
size = +size;
var id = '';
while (true) {
var i = step;
var bytes = random(i);
while (i--) {
id += alphabet[bytes[i] & mask] || '';
if (id.length === +size) return id
var bytes = random(step);
for (var i = 0; i < step; i++) {
var byte = bytes[i] & mask;
if (alphabet[byte]) {
id += alphabet[byte];
if (id.length === size) return id
}
}
}
};
@ -1371,7 +1454,7 @@ var app = (function (exports) {
var str = '';
while (!done) {
str = str + format_browser(randomByteBrowser, alphabet_1.get(), 1);
str = str + format(randomByteBrowser, alphabet_1.get(), 1);
done = number < (Math.pow(16, loopCounter + 1 ) );
loopCounter++;
}
@ -21567,8 +21650,8 @@ var app = (function (exports) {
return node
};
const createCoreApi = (appDefinition, user) => {
const app = createCoreApp(appDefinition, user);
const createCoreApi = (backendDefinition, user) => {
const app = createCoreApp(backendDefinition, user);
return {
recordApi: {
@ -22186,10 +22269,46 @@ var app = (function (exports) {
parentNode: null,
children: [],
component: null,
unsubscribe: () => { },
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();
}
}
}
},
});
const _initialiseChildren = initialiseOpts => (
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);
}
};
const builtinLibName = "##builtin";
const isScreenSlot = componentName =>
componentName === "##builtin/screenslot";
const builtins = window => ({
screenslot: screenSlotComponent(window),
});
const initialiseChildren = initialiseOpts => (
childrenProps,
htmlElement,
anchor = null
@ -22201,14 +22320,13 @@ var app = (function (exports) {
store,
componentLibraries,
treeNode,
appDefinition,
document,
frontendDefinition,
hydrate,
onScreenSlotRendered,
} = initialiseOpts;
for (let childNode of treeNode.children) {
if (childNode.unsubscribe) childNode.unsubscribe();
if (childNode.component) childNode.component.$destroy();
childNode.destroy();
}
if (hydrate) {
@ -22229,7 +22347,7 @@ var app = (function (exports) {
store,
childProps,
coreApi,
appDefinition.appRootPath
frontendDefinition.appRootPath
);
const componentConstructor = componentLibraries[libName][componentName];
@ -22245,6 +22363,15 @@ var app = (function (exports) {
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);
@ -22265,29 +22392,123 @@ var app = (function (exports) {
return { libName, componentName }
};
function regexparam (str, loose) {
if (str instanceof RegExp) return { keys:false, pattern:str };
var c, o, tmp, ext, keys=[], pattern='', arr = str.split('/');
arr[0] || arr.shift();
while (tmp = arr.shift()) {
c = tmp[0];
if (c === '*') {
keys.push('wild');
pattern += '/(.*)';
} else if (c === ':') {
o = tmp.indexOf('?', 1);
ext = tmp.indexOf('.', 1);
keys.push( tmp.substring(1, !!~o ? o : !!~ext ? ext : tmp.length) );
pattern += !!~o && !~ext ? '(?:/([^/]+?))?' : '/([^/]+?)';
if (!!~ext) pattern += (!!~o ? '?' : '') + '\\' + tmp.substring(ext);
} else {
pattern += '/' + tmp;
}
}
return {
keys: keys,
pattern: new RegExp('^' + pattern + (loose ? '(?=$|\/)' : '\/?$'), 'i')
};
}
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 = {};
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
};
const createApp = (
document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
user,
uiFunctions
uiFunctions,
screens
) => {
const coreApi = createCoreApi(appDefinition, user);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
appDefinition.hierarchy
const coreApi = createCoreApi(backendDefinition, user);
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
backendDefinition.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)
frontendDefinition.appRootPath
? frontendDefinition.appRootPath + "/" + trimSlash(url)
: url;
const apiCall = method => (url, body) =>
@ -22313,58 +22534,115 @@ var app = (function (exports) {
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,
frontendDefinition,
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,
}
};
const loadBudibase = async ({
componentLibraries,
props,
page,
screens,
window,
localStorage,
uiFunctions,
}) => {
const appDefinition = window["##BUDIBASE_APPDEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"];
const backendDefinition = window["##BUDIBASE_BACKEND_DEFINITION##"];
const frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_FRONTEND_FUNCTIONS##"];
uiFunctions = uiFunctionsFromWindow || uiFunctions;
const userFromStorage = localStorage.getItem("budibase:user");
@ -22378,35 +22656,54 @@ var app = (function (exports) {
temp: false,
};
const rootPath =
frontendDefinition.appRootPath === ""
? ""
: "/" + trimSlash(frontendDefinition.appRootPath);
if (!componentLibraries) {
const rootPath =
appDefinition.appRootPath === ""
? ""
: "/" + trimSlash(appDefinition.appRootPath);
const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib);
componentLibraries = {};
for (let lib of appDefinition.componentLibraries) {
for (let lib of frontendDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath)
);
}
}
if (!props) {
props = appDefinition.props;
componentLibraries[builtinLibName] = builtins(window);
if (!page) {
page = frontendDefinition.page;
}
const app = createApp(
if (!screens) {
screens = frontendDefinition.screens;
}
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
window.document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
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) {

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,8 @@
</style>
<script src='/_master/clientAppDefinition.js'></script>
<script src='/_master/clientFrontendDefinition.js'></script>
<script src='/_master/clientBackendDefinition.js'></script>
<script src='/_master/budibase-client.js'></script>
<script>
loadBudibase();

View File

@ -2,9 +2,61 @@ var app = (function (exports) {
'use strict';
function noop() { }
function run(fn) {
return fn();
}
function run_all(fns) {
fns.forEach(run);
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function destroy_component(component, detaching) {
if (component.$$.fragment) {
run_all(component.$$.on_destroy);
component.$$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
component.$$.on_destroy = component.$$.fragment = null;
component.$$.ctx = {};
}
}
let SvelteElement;
if (typeof HTMLElement !== 'undefined') {
SvelteElement = class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
this.appendChild(this.$$.slotted[key]);
}
}
attributeChangedCallback(attr, _oldValue, newValue) {
this[attr] = newValue;
}
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
}
$on(type, callback) {
// TODO should this delegate to addEventListener?
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1)
callbacks.splice(index, 1);
};
}
$set() {
// overridden by instance, if it has props
}
};
}
const subscriber_queue = [];
/**
@ -58,13 +110,13 @@ var app = (function (exports) {
return { set, update, subscribe };
}
const createCoreApp = (appDefinition, user) => {
const createCoreApp = (backendDefinition, user) => {
const app = {
datastore: null,
crypto: null,
publish: () => {},
hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions,
hierarchy: backendDefinition.hierarchy,
actions: backendDefinition.actions,
user,
};
@ -1349,17 +1401,48 @@ var app = (function (exports) {
var randomByteBrowser = randomByte;
var format_browser = function (random, alphabet, size) {
/**
* Secure random string generator with custom alphabet.
*
* Alphabet must contain 256 symbols or less. Otherwise, the generator
* will not be secure.
*
* @param {generator} random The random bytes generator.
* @param {string} alphabet Symbols to be used in new random string.
* @param {size} size The number of symbols in new random string.
*
* @return {string} Random string.
*
* @example
* const format = require('nanoid/format')
*
* function random (size) {
* const result = []
* for (let i = 0; i < size; i++) {
* result.push(randomByte())
* }
* return result
* }
*
* format(random, "abcdef", 5) //=> "fbaef"
*
* @name format
* @function
*/
var format = function (random, alphabet, size) {
var mask = (2 << Math.log(alphabet.length - 1) / Math.LN2) - 1;
var step = -~(1.6 * mask * size / alphabet.length);
var id = '';
var step = Math.ceil(1.6 * mask * size / alphabet.length);
size = +size;
var id = '';
while (true) {
var i = step;
var bytes = random(i);
while (i--) {
id += alphabet[bytes[i] & mask] || '';
if (id.length === +size) return id
var bytes = random(step);
for (var i = 0; i < step; i++) {
var byte = bytes[i] & mask;
if (alphabet[byte]) {
id += alphabet[byte];
if (id.length === size) return id
}
}
}
};
@ -1371,7 +1454,7 @@ var app = (function (exports) {
var str = '';
while (!done) {
str = str + format_browser(randomByteBrowser, alphabet_1.get(), 1);
str = str + format(randomByteBrowser, alphabet_1.get(), 1);
done = number < (Math.pow(16, loopCounter + 1 ) );
loopCounter++;
}
@ -21567,8 +21650,8 @@ var app = (function (exports) {
return node
};
const createCoreApi = (appDefinition, user) => {
const app = createCoreApp(appDefinition, user);
const createCoreApi = (backendDefinition, user) => {
const app = createCoreApp(backendDefinition, user);
return {
recordApi: {
@ -22186,10 +22269,46 @@ var app = (function (exports) {
parentNode: null,
children: [],
component: null,
unsubscribe: () => { },
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();
}
}
}
},
});
const _initialiseChildren = initialiseOpts => (
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);
}
};
const builtinLibName = "##builtin";
const isScreenSlot = componentName =>
componentName === "##builtin/screenslot";
const builtins = window => ({
screenslot: screenSlotComponent(window),
});
const initialiseChildren = initialiseOpts => (
childrenProps,
htmlElement,
anchor = null
@ -22201,14 +22320,13 @@ var app = (function (exports) {
store,
componentLibraries,
treeNode,
appDefinition,
document,
frontendDefinition,
hydrate,
onScreenSlotRendered,
} = initialiseOpts;
for (let childNode of treeNode.children) {
if (childNode.unsubscribe) childNode.unsubscribe();
if (childNode.component) childNode.component.$destroy();
childNode.destroy();
}
if (hydrate) {
@ -22229,7 +22347,7 @@ var app = (function (exports) {
store,
childProps,
coreApi,
appDefinition.appRootPath
frontendDefinition.appRootPath
);
const componentConstructor = componentLibraries[libName][componentName];
@ -22245,6 +22363,15 @@ var app = (function (exports) {
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);
@ -22265,29 +22392,123 @@ var app = (function (exports) {
return { libName, componentName }
};
function regexparam (str, loose) {
if (str instanceof RegExp) return { keys:false, pattern:str };
var c, o, tmp, ext, keys=[], pattern='', arr = str.split('/');
arr[0] || arr.shift();
while (tmp = arr.shift()) {
c = tmp[0];
if (c === '*') {
keys.push('wild');
pattern += '/(.*)';
} else if (c === ':') {
o = tmp.indexOf('?', 1);
ext = tmp.indexOf('.', 1);
keys.push( tmp.substring(1, !!~o ? o : !!~ext ? ext : tmp.length) );
pattern += !!~o && !~ext ? '(?:/([^/]+?))?' : '/([^/]+?)';
if (!!~ext) pattern += (!!~o ? '?' : '') + '\\' + tmp.substring(ext);
} else {
pattern += '/' + tmp;
}
}
return {
keys: keys,
pattern: new RegExp('^' + pattern + (loose ? '(?=$|\/)' : '\/?$'), 'i')
};
}
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 = {};
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
};
const createApp = (
document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
user,
uiFunctions
uiFunctions,
screens
) => {
const coreApi = createCoreApi(appDefinition, user);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
appDefinition.hierarchy
const coreApi = createCoreApi(backendDefinition, user);
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
backendDefinition.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)
frontendDefinition.appRootPath
? frontendDefinition.appRootPath + "/" + trimSlash(url)
: url;
const apiCall = method => (url, body) =>
@ -22313,58 +22534,115 @@ var app = (function (exports) {
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,
frontendDefinition,
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,
}
};
const loadBudibase = async ({
componentLibraries,
props,
page,
screens,
window,
localStorage,
uiFunctions,
}) => {
const appDefinition = window["##BUDIBASE_APPDEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"];
const backendDefinition = window["##BUDIBASE_BACKEND_DEFINITION##"];
const frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_FRONTEND_FUNCTIONS##"];
uiFunctions = uiFunctionsFromWindow || uiFunctions;
const userFromStorage = localStorage.getItem("budibase:user");
@ -22378,35 +22656,54 @@ var app = (function (exports) {
temp: false,
};
const rootPath =
frontendDefinition.appRootPath === ""
? ""
: "/" + trimSlash(frontendDefinition.appRootPath);
if (!componentLibraries) {
const rootPath =
appDefinition.appRootPath === ""
? ""
: "/" + trimSlash(appDefinition.appRootPath);
const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib);
componentLibraries = {};
for (let lib of appDefinition.componentLibraries) {
for (let lib of frontendDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath)
);
}
}
if (!props) {
props = appDefinition.props;
componentLibraries[builtinLibName] = builtins(window);
if (!page) {
page = frontendDefinition.page;
}
const app = createApp(
if (!screens) {
screens = frontendDefinition.screens;
}
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
window.document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
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) {

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,8 @@
</style>
<script src='/_master/clientAppDefinition.js'></script>
<script src='/_master/clientFrontendDefinition.js'></script>
<script src='/_master/clientBackendDefinition.js'></script>
<script src='/_master/budibase-client.js'></script>
<script>
loadBudibase();

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,8 @@
<script src='/clientAppDefinition.js'></script>
<script src='/_master/clientFrontendDefinition.js'></script>
<script src='/_master/clientBackendDefinition.js'></script>
<script src='/budibase-client.js'></script>
<script>
loadBudibase();

View File

@ -2,9 +2,61 @@ var app = (function (exports) {
'use strict';
function noop() { }
function run(fn) {
return fn();
}
function run_all(fns) {
fns.forEach(run);
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function destroy_component(component, detaching) {
if (component.$$.fragment) {
run_all(component.$$.on_destroy);
component.$$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
component.$$.on_destroy = component.$$.fragment = null;
component.$$.ctx = {};
}
}
let SvelteElement;
if (typeof HTMLElement !== 'undefined') {
SvelteElement = class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
this.appendChild(this.$$.slotted[key]);
}
}
attributeChangedCallback(attr, _oldValue, newValue) {
this[attr] = newValue;
}
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
}
$on(type, callback) {
// TODO should this delegate to addEventListener?
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1)
callbacks.splice(index, 1);
};
}
$set() {
// overridden by instance, if it has props
}
};
}
const subscriber_queue = [];
/**
@ -58,13 +110,13 @@ var app = (function (exports) {
return { set, update, subscribe };
}
const createCoreApp = (appDefinition, user) => {
const createCoreApp = (backendDefinition, user) => {
const app = {
datastore: null,
crypto: null,
publish: () => {},
hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions,
hierarchy: backendDefinition.hierarchy,
actions: backendDefinition.actions,
user,
};
@ -1349,17 +1401,48 @@ var app = (function (exports) {
var randomByteBrowser = randomByte;
var format_browser = function (random, alphabet, size) {
/**
* Secure random string generator with custom alphabet.
*
* Alphabet must contain 256 symbols or less. Otherwise, the generator
* will not be secure.
*
* @param {generator} random The random bytes generator.
* @param {string} alphabet Symbols to be used in new random string.
* @param {size} size The number of symbols in new random string.
*
* @return {string} Random string.
*
* @example
* const format = require('nanoid/format')
*
* function random (size) {
* const result = []
* for (let i = 0; i < size; i++) {
* result.push(randomByte())
* }
* return result
* }
*
* format(random, "abcdef", 5) //=> "fbaef"
*
* @name format
* @function
*/
var format = function (random, alphabet, size) {
var mask = (2 << Math.log(alphabet.length - 1) / Math.LN2) - 1;
var step = -~(1.6 * mask * size / alphabet.length);
var id = '';
var step = Math.ceil(1.6 * mask * size / alphabet.length);
size = +size;
var id = '';
while (true) {
var i = step;
var bytes = random(i);
while (i--) {
id += alphabet[bytes[i] & mask] || '';
if (id.length === +size) return id
var bytes = random(step);
for (var i = 0; i < step; i++) {
var byte = bytes[i] & mask;
if (alphabet[byte]) {
id += alphabet[byte];
if (id.length === size) return id
}
}
}
};
@ -1371,7 +1454,7 @@ var app = (function (exports) {
var str = '';
while (!done) {
str = str + format_browser(randomByteBrowser, alphabet_1.get(), 1);
str = str + format(randomByteBrowser, alphabet_1.get(), 1);
done = number < (Math.pow(16, loopCounter + 1 ) );
loopCounter++;
}
@ -21567,8 +21650,8 @@ var app = (function (exports) {
return node
};
const createCoreApi = (appDefinition, user) => {
const app = createCoreApp(appDefinition, user);
const createCoreApi = (backendDefinition, user) => {
const app = createCoreApp(backendDefinition, user);
return {
recordApi: {
@ -22186,10 +22269,46 @@ var app = (function (exports) {
parentNode: null,
children: [],
component: null,
unsubscribe: () => { },
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();
}
}
}
},
});
const _initialiseChildren = initialiseOpts => (
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);
}
};
const builtinLibName = "##builtin";
const isScreenSlot = componentName =>
componentName === "##builtin/screenslot";
const builtins = window => ({
screenslot: screenSlotComponent(window),
});
const initialiseChildren = initialiseOpts => (
childrenProps,
htmlElement,
anchor = null
@ -22201,14 +22320,13 @@ var app = (function (exports) {
store,
componentLibraries,
treeNode,
appDefinition,
document,
frontendDefinition,
hydrate,
onScreenSlotRendered,
} = initialiseOpts;
for (let childNode of treeNode.children) {
if (childNode.unsubscribe) childNode.unsubscribe();
if (childNode.component) childNode.component.$destroy();
childNode.destroy();
}
if (hydrate) {
@ -22229,7 +22347,7 @@ var app = (function (exports) {
store,
childProps,
coreApi,
appDefinition.appRootPath
frontendDefinition.appRootPath
);
const componentConstructor = componentLibraries[libName][componentName];
@ -22245,6 +22363,15 @@ var app = (function (exports) {
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);
@ -22265,29 +22392,123 @@ var app = (function (exports) {
return { libName, componentName }
};
function regexparam (str, loose) {
if (str instanceof RegExp) return { keys:false, pattern:str };
var c, o, tmp, ext, keys=[], pattern='', arr = str.split('/');
arr[0] || arr.shift();
while (tmp = arr.shift()) {
c = tmp[0];
if (c === '*') {
keys.push('wild');
pattern += '/(.*)';
} else if (c === ':') {
o = tmp.indexOf('?', 1);
ext = tmp.indexOf('.', 1);
keys.push( tmp.substring(1, !!~o ? o : !!~ext ? ext : tmp.length) );
pattern += !!~o && !~ext ? '(?:/([^/]+?))?' : '/([^/]+?)';
if (!!~ext) pattern += (!!~o ? '?' : '') + '\\' + tmp.substring(ext);
} else {
pattern += '/' + tmp;
}
}
return {
keys: keys,
pattern: new RegExp('^' + pattern + (loose ? '(?=$|\/)' : '\/?$'), 'i')
};
}
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 = {};
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
};
const createApp = (
document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
user,
uiFunctions
uiFunctions,
screens
) => {
const coreApi = createCoreApi(appDefinition, user);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
appDefinition.hierarchy
const coreApi = createCoreApi(backendDefinition, user);
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
backendDefinition.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)
frontendDefinition.appRootPath
? frontendDefinition.appRootPath + "/" + trimSlash(url)
: url;
const apiCall = method => (url, body) =>
@ -22313,58 +22534,115 @@ var app = (function (exports) {
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,
frontendDefinition,
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,
}
};
const loadBudibase = async ({
componentLibraries,
props,
page,
screens,
window,
localStorage,
uiFunctions,
}) => {
const appDefinition = window["##BUDIBASE_APPDEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"];
const backendDefinition = window["##BUDIBASE_BACKEND_DEFINITION##"];
const frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_FRONTEND_FUNCTIONS##"];
uiFunctions = uiFunctionsFromWindow || uiFunctions;
const userFromStorage = localStorage.getItem("budibase:user");
@ -22378,35 +22656,54 @@ var app = (function (exports) {
temp: false,
};
const rootPath =
frontendDefinition.appRootPath === ""
? ""
: "/" + trimSlash(frontendDefinition.appRootPath);
if (!componentLibraries) {
const rootPath =
appDefinition.appRootPath === ""
? ""
: "/" + trimSlash(appDefinition.appRootPath);
const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib);
componentLibraries = {};
for (let lib of appDefinition.componentLibraries) {
for (let lib of frontendDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath)
);
}
}
if (!props) {
props = appDefinition.props;
componentLibraries[builtinLibName] = builtins(window);
if (!page) {
page = frontendDefinition.page;
}
const app = createApp(
if (!screens) {
screens = frontendDefinition.screens;
}
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
window.document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
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) {

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,8 @@
<link rel='stylesheet' href='https://css-r-us.com/myawesomestyles.css'>
<link rel='stylesheet' href='///local.css'>
<script src='/clientAppDefinition.js'></script>
<script src='/_master/clientFrontendDefinition.js'></script>
<script src='/_master/clientBackendDefinition.js'></script>
<script src='/budibase-client.js'></script>
<script>
loadBudibase();

View File

@ -2,9 +2,61 @@ var app = (function (exports) {
'use strict';
function noop() { }
function run(fn) {
return fn();
}
function run_all(fns) {
fns.forEach(run);
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function destroy_component(component, detaching) {
if (component.$$.fragment) {
run_all(component.$$.on_destroy);
component.$$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
component.$$.on_destroy = component.$$.fragment = null;
component.$$.ctx = {};
}
}
let SvelteElement;
if (typeof HTMLElement !== 'undefined') {
SvelteElement = class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
this.appendChild(this.$$.slotted[key]);
}
}
attributeChangedCallback(attr, _oldValue, newValue) {
this[attr] = newValue;
}
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
}
$on(type, callback) {
// TODO should this delegate to addEventListener?
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1)
callbacks.splice(index, 1);
};
}
$set() {
// overridden by instance, if it has props
}
};
}
const subscriber_queue = [];
/**
@ -58,13 +110,13 @@ var app = (function (exports) {
return { set, update, subscribe };
}
const createCoreApp = (appDefinition, user) => {
const createCoreApp = (backendDefinition, user) => {
const app = {
datastore: null,
crypto: null,
publish: () => {},
hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions,
hierarchy: backendDefinition.hierarchy,
actions: backendDefinition.actions,
user,
};
@ -1349,17 +1401,48 @@ var app = (function (exports) {
var randomByteBrowser = randomByte;
var format_browser = function (random, alphabet, size) {
/**
* Secure random string generator with custom alphabet.
*
* Alphabet must contain 256 symbols or less. Otherwise, the generator
* will not be secure.
*
* @param {generator} random The random bytes generator.
* @param {string} alphabet Symbols to be used in new random string.
* @param {size} size The number of symbols in new random string.
*
* @return {string} Random string.
*
* @example
* const format = require('nanoid/format')
*
* function random (size) {
* const result = []
* for (let i = 0; i < size; i++) {
* result.push(randomByte())
* }
* return result
* }
*
* format(random, "abcdef", 5) //=> "fbaef"
*
* @name format
* @function
*/
var format = function (random, alphabet, size) {
var mask = (2 << Math.log(alphabet.length - 1) / Math.LN2) - 1;
var step = -~(1.6 * mask * size / alphabet.length);
var id = '';
var step = Math.ceil(1.6 * mask * size / alphabet.length);
size = +size;
var id = '';
while (true) {
var i = step;
var bytes = random(i);
while (i--) {
id += alphabet[bytes[i] & mask] || '';
if (id.length === +size) return id
var bytes = random(step);
for (var i = 0; i < step; i++) {
var byte = bytes[i] & mask;
if (alphabet[byte]) {
id += alphabet[byte];
if (id.length === size) return id
}
}
}
};
@ -1371,7 +1454,7 @@ var app = (function (exports) {
var str = '';
while (!done) {
str = str + format_browser(randomByteBrowser, alphabet_1.get(), 1);
str = str + format(randomByteBrowser, alphabet_1.get(), 1);
done = number < (Math.pow(16, loopCounter + 1 ) );
loopCounter++;
}
@ -21567,8 +21650,8 @@ var app = (function (exports) {
return node
};
const createCoreApi = (appDefinition, user) => {
const app = createCoreApp(appDefinition, user);
const createCoreApi = (backendDefinition, user) => {
const app = createCoreApp(backendDefinition, user);
return {
recordApi: {
@ -22186,10 +22269,46 @@ var app = (function (exports) {
parentNode: null,
children: [],
component: null,
unsubscribe: () => { },
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();
}
}
}
},
});
const _initialiseChildren = initialiseOpts => (
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);
}
};
const builtinLibName = "##builtin";
const isScreenSlot = componentName =>
componentName === "##builtin/screenslot";
const builtins = window => ({
screenslot: screenSlotComponent(window),
});
const initialiseChildren = initialiseOpts => (
childrenProps,
htmlElement,
anchor = null
@ -22201,14 +22320,13 @@ var app = (function (exports) {
store,
componentLibraries,
treeNode,
appDefinition,
document,
frontendDefinition,
hydrate,
onScreenSlotRendered,
} = initialiseOpts;
for (let childNode of treeNode.children) {
if (childNode.unsubscribe) childNode.unsubscribe();
if (childNode.component) childNode.component.$destroy();
childNode.destroy();
}
if (hydrate) {
@ -22229,7 +22347,7 @@ var app = (function (exports) {
store,
childProps,
coreApi,
appDefinition.appRootPath
frontendDefinition.appRootPath
);
const componentConstructor = componentLibraries[libName][componentName];
@ -22245,6 +22363,15 @@ var app = (function (exports) {
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);
@ -22265,29 +22392,123 @@ var app = (function (exports) {
return { libName, componentName }
};
function regexparam (str, loose) {
if (str instanceof RegExp) return { keys:false, pattern:str };
var c, o, tmp, ext, keys=[], pattern='', arr = str.split('/');
arr[0] || arr.shift();
while (tmp = arr.shift()) {
c = tmp[0];
if (c === '*') {
keys.push('wild');
pattern += '/(.*)';
} else if (c === ':') {
o = tmp.indexOf('?', 1);
ext = tmp.indexOf('.', 1);
keys.push( tmp.substring(1, !!~o ? o : !!~ext ? ext : tmp.length) );
pattern += !!~o && !~ext ? '(?:/([^/]+?))?' : '/([^/]+?)';
if (!!~ext) pattern += (!!~o ? '?' : '') + '\\' + tmp.substring(ext);
} else {
pattern += '/' + tmp;
}
}
return {
keys: keys,
pattern: new RegExp('^' + pattern + (loose ? '(?=$|\/)' : '\/?$'), 'i')
};
}
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 = {};
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
};
const createApp = (
document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
user,
uiFunctions
uiFunctions,
screens
) => {
const coreApi = createCoreApi(appDefinition, user);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
appDefinition.hierarchy
const coreApi = createCoreApi(backendDefinition, user);
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
backendDefinition.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)
frontendDefinition.appRootPath
? frontendDefinition.appRootPath + "/" + trimSlash(url)
: url;
const apiCall = method => (url, body) =>
@ -22313,58 +22534,115 @@ var app = (function (exports) {
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,
frontendDefinition,
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,
}
};
const loadBudibase = async ({
componentLibraries,
props,
page,
screens,
window,
localStorage,
uiFunctions,
}) => {
const appDefinition = window["##BUDIBASE_APPDEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"];
const backendDefinition = window["##BUDIBASE_BACKEND_DEFINITION##"];
const frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_FRONTEND_FUNCTIONS##"];
uiFunctions = uiFunctionsFromWindow || uiFunctions;
const userFromStorage = localStorage.getItem("budibase:user");
@ -22378,35 +22656,54 @@ var app = (function (exports) {
temp: false,
};
const rootPath =
frontendDefinition.appRootPath === ""
? ""
: "/" + trimSlash(frontendDefinition.appRootPath);
if (!componentLibraries) {
const rootPath =
appDefinition.appRootPath === ""
? ""
: "/" + trimSlash(appDefinition.appRootPath);
const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib);
componentLibraries = {};
for (let lib of appDefinition.componentLibraries) {
for (let lib of frontendDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath)
);
}
}
if (!props) {
props = appDefinition.props;
componentLibraries[builtinLibName] = builtins(window);
if (!page) {
page = frontendDefinition.page;
}
const app = createApp(
if (!screens) {
screens = frontendDefinition.screens;
}
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
window.document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
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) {

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,8 @@
<link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'>
<script src='/testApp2/clientAppDefinition.js'></script>
<script src='/_master/clientFrontendDefinition.js'></script>
<script src='/_master/clientBackendDefinition.js'></script>
<script src='/testApp2/budibase-client.js'></script>
<script>
loadBudibase();

View File

@ -2,9 +2,61 @@ var app = (function (exports) {
'use strict';
function noop() { }
function run(fn) {
return fn();
}
function run_all(fns) {
fns.forEach(run);
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function destroy_component(component, detaching) {
if (component.$$.fragment) {
run_all(component.$$.on_destroy);
component.$$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
component.$$.on_destroy = component.$$.fragment = null;
component.$$.ctx = {};
}
}
let SvelteElement;
if (typeof HTMLElement !== 'undefined') {
SvelteElement = class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
this.appendChild(this.$$.slotted[key]);
}
}
attributeChangedCallback(attr, _oldValue, newValue) {
this[attr] = newValue;
}
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
}
$on(type, callback) {
// TODO should this delegate to addEventListener?
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1)
callbacks.splice(index, 1);
};
}
$set() {
// overridden by instance, if it has props
}
};
}
const subscriber_queue = [];
/**
@ -58,13 +110,13 @@ var app = (function (exports) {
return { set, update, subscribe };
}
const createCoreApp = (appDefinition, user) => {
const createCoreApp = (backendDefinition, user) => {
const app = {
datastore: null,
crypto: null,
publish: () => {},
hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions,
hierarchy: backendDefinition.hierarchy,
actions: backendDefinition.actions,
user,
};
@ -1349,17 +1401,48 @@ var app = (function (exports) {
var randomByteBrowser = randomByte;
var format_browser = function (random, alphabet, size) {
/**
* Secure random string generator with custom alphabet.
*
* Alphabet must contain 256 symbols or less. Otherwise, the generator
* will not be secure.
*
* @param {generator} random The random bytes generator.
* @param {string} alphabet Symbols to be used in new random string.
* @param {size} size The number of symbols in new random string.
*
* @return {string} Random string.
*
* @example
* const format = require('nanoid/format')
*
* function random (size) {
* const result = []
* for (let i = 0; i < size; i++) {
* result.push(randomByte())
* }
* return result
* }
*
* format(random, "abcdef", 5) //=> "fbaef"
*
* @name format
* @function
*/
var format = function (random, alphabet, size) {
var mask = (2 << Math.log(alphabet.length - 1) / Math.LN2) - 1;
var step = -~(1.6 * mask * size / alphabet.length);
var id = '';
var step = Math.ceil(1.6 * mask * size / alphabet.length);
size = +size;
var id = '';
while (true) {
var i = step;
var bytes = random(i);
while (i--) {
id += alphabet[bytes[i] & mask] || '';
if (id.length === +size) return id
var bytes = random(step);
for (var i = 0; i < step; i++) {
var byte = bytes[i] & mask;
if (alphabet[byte]) {
id += alphabet[byte];
if (id.length === size) return id
}
}
}
};
@ -1371,7 +1454,7 @@ var app = (function (exports) {
var str = '';
while (!done) {
str = str + format_browser(randomByteBrowser, alphabet_1.get(), 1);
str = str + format(randomByteBrowser, alphabet_1.get(), 1);
done = number < (Math.pow(16, loopCounter + 1 ) );
loopCounter++;
}
@ -21567,8 +21650,8 @@ var app = (function (exports) {
return node
};
const createCoreApi = (appDefinition, user) => {
const app = createCoreApp(appDefinition, user);
const createCoreApi = (backendDefinition, user) => {
const app = createCoreApp(backendDefinition, user);
return {
recordApi: {
@ -22186,10 +22269,46 @@ var app = (function (exports) {
parentNode: null,
children: [],
component: null,
unsubscribe: () => { },
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();
}
}
}
},
});
const _initialiseChildren = initialiseOpts => (
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);
}
};
const builtinLibName = "##builtin";
const isScreenSlot = componentName =>
componentName === "##builtin/screenslot";
const builtins = window => ({
screenslot: screenSlotComponent(window),
});
const initialiseChildren = initialiseOpts => (
childrenProps,
htmlElement,
anchor = null
@ -22201,14 +22320,13 @@ var app = (function (exports) {
store,
componentLibraries,
treeNode,
appDefinition,
document,
frontendDefinition,
hydrate,
onScreenSlotRendered,
} = initialiseOpts;
for (let childNode of treeNode.children) {
if (childNode.unsubscribe) childNode.unsubscribe();
if (childNode.component) childNode.component.$destroy();
childNode.destroy();
}
if (hydrate) {
@ -22229,7 +22347,7 @@ var app = (function (exports) {
store,
childProps,
coreApi,
appDefinition.appRootPath
frontendDefinition.appRootPath
);
const componentConstructor = componentLibraries[libName][componentName];
@ -22245,6 +22363,15 @@ var app = (function (exports) {
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);
@ -22265,29 +22392,123 @@ var app = (function (exports) {
return { libName, componentName }
};
function regexparam (str, loose) {
if (str instanceof RegExp) return { keys:false, pattern:str };
var c, o, tmp, ext, keys=[], pattern='', arr = str.split('/');
arr[0] || arr.shift();
while (tmp = arr.shift()) {
c = tmp[0];
if (c === '*') {
keys.push('wild');
pattern += '/(.*)';
} else if (c === ':') {
o = tmp.indexOf('?', 1);
ext = tmp.indexOf('.', 1);
keys.push( tmp.substring(1, !!~o ? o : !!~ext ? ext : tmp.length) );
pattern += !!~o && !~ext ? '(?:/([^/]+?))?' : '/([^/]+?)';
if (!!~ext) pattern += (!!~o ? '?' : '') + '\\' + tmp.substring(ext);
} else {
pattern += '/' + tmp;
}
}
return {
keys: keys,
pattern: new RegExp('^' + pattern + (loose ? '(?=$|\/)' : '\/?$'), 'i')
};
}
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 = {};
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
};
const createApp = (
document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
user,
uiFunctions
uiFunctions,
screens
) => {
const coreApi = createCoreApi(appDefinition, user);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
appDefinition.hierarchy
const coreApi = createCoreApi(backendDefinition, user);
backendDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
backendDefinition.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)
frontendDefinition.appRootPath
? frontendDefinition.appRootPath + "/" + trimSlash(url)
: url;
const apiCall = method => (url, body) =>
@ -22313,58 +22534,115 @@ var app = (function (exports) {
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,
frontendDefinition,
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,
}
};
const loadBudibase = async ({
componentLibraries,
props,
page,
screens,
window,
localStorage,
uiFunctions,
}) => {
const appDefinition = window["##BUDIBASE_APPDEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"];
const backendDefinition = window["##BUDIBASE_BACKEND_DEFINITION##"];
const frontendDefinition = window["##BUDIBASE_FRONTEND_DEFINITION##"];
const uiFunctionsFromWindow = window["##BUDIBASE_FRONTEND_FUNCTIONS##"];
uiFunctions = uiFunctionsFromWindow || uiFunctions;
const userFromStorage = localStorage.getItem("budibase:user");
@ -22378,35 +22656,54 @@ var app = (function (exports) {
temp: false,
};
const rootPath =
frontendDefinition.appRootPath === ""
? ""
: "/" + trimSlash(frontendDefinition.appRootPath);
if (!componentLibraries) {
const rootPath =
appDefinition.appRootPath === ""
? ""
: "/" + trimSlash(appDefinition.appRootPath);
const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib);
componentLibraries = {};
for (let lib of appDefinition.componentLibraries) {
for (let lib of frontendDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath)
);
}
}
if (!props) {
props = appDefinition.props;
componentLibraries[builtinLibName] = builtins(window);
if (!page) {
page = frontendDefinition.page;
}
const app = createApp(
if (!screens) {
screens = frontendDefinition.screens;
}
const { initialisePage, screenStore, pageStore, routeTo, rootNode } = createApp(
window.document,
componentLibraries,
appDefinition,
frontendDefinition,
backendDefinition,
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) {

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,8 @@
<link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'>
<script src='/testApp2/clientAppDefinition.js'></script>
<script src='/_master/clientFrontendDefinition.js'></script>
<script src='/_master/clientBackendDefinition.js'></script>
<script src='/testApp2/budibase-client.js'></script>
<script>
loadBudibase();

View File

@ -11,9 +11,10 @@ const {
saveScreen,
renameScreen,
deleteScreen,
savePagePackage,
buildPage,
componentLibraryInfo,
listScreens,
saveBackend,
} = require("../utilities/builder")
const builderPath = resolve(__dirname, "../builder")
@ -179,8 +180,17 @@ module.exports = (config, app) => {
ctx.body = info.generators
ctx.response.status = StatusCodes.OK
})
.post("/_builder/api/:appname/backend", async ctx => {
await saveBackend(
config,
ctx.params.appname,
ctx.request.body.appDefinition,
ctx.request.body.accessLevels
)
ctx.response.status = StatusCodes.OK
})
.post("/_builder/api/:appname/pages/:pageName", async ctx => {
await savePagePackage(
await buildPage(
config,
ctx.params.appname,
ctx.params.pageName,

View File

@ -8,24 +8,27 @@ const {
copyFile,
writeFile,
readFile,
writeJSON,
} = require("fs-extra")
const { join, resolve, dirname } = require("path")
const sqrl = require("squirrelly")
const { convertCssToFiles } = require("./convertCssToFiles")
const publicPath = require("./publicPath")
module.exports = async (config, appname, pkg) => {
module.exports = async (config, appname, pageName, pkg) => {
const appPath = appPackageFolder(config, appname)
await convertCssToFiles(publicPath(appPath, pkg.pageName), pkg)
await convertCssToFiles(publicPath(appPath, pageName), pkg)
await buildIndexHtml(config, appname, appPath, pkg)
await buildIndexHtml(config, appname, pageName, appPath, pkg)
await buildClientAppDefinition(config, appname, pkg, appPath)
await buildFrontendAppDefinition(config, appname, pageName, pkg, appPath)
await copyClientLib(appPath, pkg.pageName)
await copyClientLib(appPath, pageName)
await savePageJson(appPath, pageName, pkg)
}
const publicPath = (appPath, pageName) => join(appPath, "public", pageName)
const rootPath = (config, appname) =>
config.useAppRootPath ? `/${appname}` : ""
@ -42,8 +45,8 @@ const copyClientLib = async (appPath, pageName) => {
)
}
const buildIndexHtml = async (config, appname, appPath, pkg) => {
const appPublicPath = publicPath(appPath, pkg.pageName)
const buildIndexHtml = async (config, appname, pageName, appPath, pkg) => {
const appPublicPath = publicPath(appPath, pageName)
const appRootPath = rootPath(config, appname)
const stylesheetUrl = s =>
@ -72,9 +75,9 @@ const buildIndexHtml = async (config, appname, appPath, pkg) => {
await writeFile(indexHtmlPath, indexHtml, { flag: "w+" })
}
const buildClientAppDefinition = async (config, appname, pkg) => {
const buildFrontendAppDefinition = async (config, appname, pageName, pkg) => {
const appPath = appPackageFolder(config, appname)
const appPublicPath = publicPath(appPath, pkg.pageName)
const appPublicPath = publicPath(appPath, pageName)
const appRootPath = rootPath(config, appname)
const componentLibraries = []
@ -109,7 +112,7 @@ const buildClientAppDefinition = async (config, appname, pkg) => {
}
}
const filename = join(appPublicPath, "clientAppDefinition.js")
const filename = join(appPublicPath, "clientFrontendDefinition.js")
if (pkg.page._css) {
delete pkg.page._css
@ -121,17 +124,32 @@ const buildClientAppDefinition = async (config, appname, pkg) => {
}
}
const clientAppDefObj = {
hierarchy: pkg.appDefinition.hierarchy,
const clientUiDefinition = JSON.stringify({
componentLibraries: componentLibraries,
appRootPath: appRootPath,
page: pkg.page,
screens: pkg.screens,
}
})
await writeFile(
filename,
`window['##BUDIBASE_APPDEFINITION##'] = ${JSON.stringify(clientAppDefObj)};
window['##BUDIBASE_UIFUNCTIONS##'] = ${pkg.uiFunctions}`
`window['##BUDIBASE_FRONTEND_DEINITION##'] = ${clientUiDefinition};
window['##BUDIBASE_FRONTEND_FUNCTIONS##'] = ${pkg.uiFunctions}`
)
}
const savePageJson = async (appPath, pageName, pkg) => {
const pageFile = join(appPath, "pages", pageName, "page.json")
if (pkg.page._css) {
delete pkg.page._css
}
if (pkg.page.name) {
delete pkg.page.name
}
await writeJSON(pageFile, pkg.page, {
spaces: 2,
})
}

View File

@ -0,0 +1,20 @@
const { readJSON, readdir } = require("fs-extra")
const { join } = require("path")
module.exports = async appPath => {
const pages = {}
const pageFolders = await readdir(join(appPath, "pages"))
for (let pageFolder of pageFolders) {
try {
pages[pageFolder] = await readJSON(
join(appPath, "pages", pageFolder, "page.json")
)
pages[pageFolder].name = pageFolder
} catch (_) {
// ignore error
}
}
return pages
}

View File

@ -3,7 +3,6 @@ const {
readJSON,
writeJSON,
readdir,
stat,
ensureDir,
rename,
unlink,
@ -11,14 +10,18 @@ const {
} = require("fs-extra")
const { join, dirname } = require("path")
const { $ } = require("@budibase/core").common
const { keyBy, intersection, map, values, flatten } = require("lodash/fp")
const { intersection, map, values, flatten } = require("lodash/fp")
const { merge } = require("lodash")
const { componentLibraryInfo } = require("./componentLibraryInfo")
const savePagePackage = require("./savePagePackage")
const buildPage = require("./buildPage")
const getPages = require("./getPages")
const listScreens = require("./listScreens")
const saveBackend = require("./saveBackend")
module.exports.savePagePackage = savePagePackage
module.exports.buildPage = buildPage
module.exports.listScreens = listScreens
module.exports.saveBackend = saveBackend
const getAppDefinition = async appPath =>
await readJSON(`${appPath}/appDefinition.json`)
@ -45,31 +48,9 @@ module.exports.getApps = async (config, master) => {
return $(master.listApplications(), [map(a => a.name), intersection(dirs)])
}
const getPages = async appPath => {
const pages = {}
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)
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)
@ -158,43 +139,4 @@ const getComponents = async (appPath, pages, lib) => {
return { components, generators }
}
const fetchscreens = async (appPath, pagename, relativePath = "") => {
const currentDir = join(appPath, "pages", pagename, "screens", relativePath)
const contents = await readdir(currentDir)
const screens = []
for (let item of contents) {
const itemRelativePath = join(relativePath, item)
const itemFullPath = join(currentDir, item)
const stats = await stat(itemFullPath)
if (stats.isFile()) {
if (!item.endsWith(".json")) continue
const component = await readJSON(itemFullPath)
component.name = itemRelativePath
.substring(0, itemRelativePath.length - 5)
.replace(/\\/g, "/")
component.props = component.props || {}
screens.push(component)
} else {
const childComponents = await fetchscreens(
appPath,
join(relativePath, item)
)
for (let c of childComponents) {
screens.push(c)
}
}
}
return screens
}
module.exports.getComponents = getComponents

View File

@ -27,7 +27,8 @@
{{ /if }}
<script src='{{ appRootPath }}/clientAppDefinition.js'></script>
<script src='{{ appRootPath }}/clientFrontendDefinition.js'></script>
<script src='{{ appRootPath }}/clientBackendDefinition.js'></script>
<script src='{{ appRootPath }}/budibase-client.js'></script>
<script>
loadBudibase();

View File

@ -0,0 +1,48 @@
const { appPackageFolder } = require("../createAppPackage")
const { readJSON, readdir, stat } = require("fs-extra")
const { join } = require("path")
const { keyBy } = require("lodash/fp")
module.exports = async (config, appname, pagename) => {
const appPath = appPackageFolder(config, appname)
return keyBy("name")(await fetchscreens(appPath, pagename))
}
const fetchscreens = async (appPath, pagename, relativePath = "") => {
const currentDir = join(appPath, "pages", pagename, "screens", relativePath)
const contents = await readdir(currentDir)
const screens = []
for (let item of contents) {
const itemRelativePath = join(relativePath, item)
const itemFullPath = join(currentDir, item)
const stats = await stat(itemFullPath)
if (stats.isFile()) {
if (!item.endsWith(".json")) continue
const component = await readJSON(itemFullPath)
component.name = itemRelativePath
.substring(0, itemRelativePath.length - 5)
.replace(/\\/g, "/")
component.props = component.props || {}
screens.push(component)
} else {
const childComponents = await fetchscreens(
appPath,
join(relativePath, item)
)
for (let c of childComponents) {
screens.push(c)
}
}
}
return screens
}

View File

@ -0,0 +1,3 @@
const { join } = require("path")
module.exports = (appPath, pageName) => join(appPath, "public", pageName)

View File

@ -0,0 +1,28 @@
const getPages = require("./getPages")
const { appPackageFolder } = require("../createAppPackage")
const { writeJSON, writeFile } = require("fs-extra")
const { join } = require("path")
const publicPath = require("./publicPath")
module.exports = async (config, appname, appDefinition, accessLevels) => {
const appPath = appPackageFolder(config, appname)
await writeJSON(`${appPath}/appDefinition.json`, appDefinition, {
spaces: 2,
})
await writeJSON(`${appPath}/access_levels.json`, accessLevels, {
spaces: 2,
})
const pages = await getPages(appPath)
for (let pageName in pages) {
const pagePublicPath = publicPath(appPath, pageName)
const filename = join(pagePublicPath, "clientBackendDefinition.js")
const appDefString = JSON.stringify(appDefinition)
await writeFile(
filename,
`window['##BUDIBASE_FRONTEND_DEINITION##'] = ${appDefString};`
)
}
}

View File

@ -1,30 +0,0 @@
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