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:
parent
8a80d8801a
commit
34b957f331
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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,27 +22534,60 @@ var app = (function (exports) {
|
|||
if (isFunction(event)) event(context);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = (hydrate, treeNode) => ({
|
||||
bb,
|
||||
let routeTo;
|
||||
let currentScreenStore;
|
||||
let currentScreenUbsubscribe;
|
||||
let currentUrl;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
routeTo = screenRouter(screens, onScreenSelected);
|
||||
routeTo(currentUrl || window.location.pathname);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = store => {
|
||||
let currentState = null;
|
||||
const unsubscribe = store.subscribe(s => {
|
||||
currentState = s;
|
||||
});
|
||||
|
||||
const getInitialiseParams = (hydrate, treeNode) => ({
|
||||
bb: getBbClientApi,
|
||||
coreApi,
|
||||
store,
|
||||
document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
frontendDefinition,
|
||||
hydrate,
|
||||
uiFunctions,
|
||||
treeNode,
|
||||
onScreenSlotRendered,
|
||||
});
|
||||
|
||||
const bb = (treeNode, componentProps) => ({
|
||||
hydrateChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(true, treeNode)
|
||||
const getBbClientApi = (treeNode, componentProps) => {
|
||||
return {
|
||||
hydrateChildren: initialiseChildren(
|
||||
getInitialiseParams(true, treeNode)
|
||||
),
|
||||
appendChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(false, treeNode)
|
||||
appendChildren: initialiseChildren(
|
||||
getInitialiseParams(false, treeNode)
|
||||
),
|
||||
insertChildren: (props, htmlElement, anchor) =>
|
||||
_initialiseChildren(initialiseChildrenParams(false, treeNode))(
|
||||
initialiseChildren(getInitialiseParams(false, treeNode))(
|
||||
props,
|
||||
htmlElement,
|
||||
anchor
|
||||
|
@ -22345,26 +22599,50 @@ var app = (function (exports) {
|
|||
setStateFromBinding(store, binding, value),
|
||||
setState: (path, value) => setState(store, path, value),
|
||||
getStateOrValue: (prop, currentContext) =>
|
||||
getStateOrValue(globalState, prop, currentContext),
|
||||
getStateOrValue(currentState, prop, currentContext),
|
||||
store,
|
||||
relativeUrl,
|
||||
api,
|
||||
isBound,
|
||||
parent,
|
||||
});
|
||||
}
|
||||
};
|
||||
return { getInitialiseParams, unsubscribe }
|
||||
};
|
||||
|
||||
return bb(createTreeNode())
|
||||
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,
|
||||
};
|
||||
|
||||
if (!componentLibraries) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
@ -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();
|
||||
|
|
|
@ -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,27 +22534,60 @@ var app = (function (exports) {
|
|||
if (isFunction(event)) event(context);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = (hydrate, treeNode) => ({
|
||||
bb,
|
||||
let routeTo;
|
||||
let currentScreenStore;
|
||||
let currentScreenUbsubscribe;
|
||||
let currentUrl;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
routeTo = screenRouter(screens, onScreenSelected);
|
||||
routeTo(currentUrl || window.location.pathname);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = store => {
|
||||
let currentState = null;
|
||||
const unsubscribe = store.subscribe(s => {
|
||||
currentState = s;
|
||||
});
|
||||
|
||||
const getInitialiseParams = (hydrate, treeNode) => ({
|
||||
bb: getBbClientApi,
|
||||
coreApi,
|
||||
store,
|
||||
document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
frontendDefinition,
|
||||
hydrate,
|
||||
uiFunctions,
|
||||
treeNode,
|
||||
onScreenSlotRendered,
|
||||
});
|
||||
|
||||
const bb = (treeNode, componentProps) => ({
|
||||
hydrateChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(true, treeNode)
|
||||
const getBbClientApi = (treeNode, componentProps) => {
|
||||
return {
|
||||
hydrateChildren: initialiseChildren(
|
||||
getInitialiseParams(true, treeNode)
|
||||
),
|
||||
appendChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(false, treeNode)
|
||||
appendChildren: initialiseChildren(
|
||||
getInitialiseParams(false, treeNode)
|
||||
),
|
||||
insertChildren: (props, htmlElement, anchor) =>
|
||||
_initialiseChildren(initialiseChildrenParams(false, treeNode))(
|
||||
initialiseChildren(getInitialiseParams(false, treeNode))(
|
||||
props,
|
||||
htmlElement,
|
||||
anchor
|
||||
|
@ -22345,26 +22599,50 @@ var app = (function (exports) {
|
|||
setStateFromBinding(store, binding, value),
|
||||
setState: (path, value) => setState(store, path, value),
|
||||
getStateOrValue: (prop, currentContext) =>
|
||||
getStateOrValue(globalState, prop, currentContext),
|
||||
getStateOrValue(currentState, prop, currentContext),
|
||||
store,
|
||||
relativeUrl,
|
||||
api,
|
||||
isBound,
|
||||
parent,
|
||||
});
|
||||
}
|
||||
};
|
||||
return { getInitialiseParams, unsubscribe }
|
||||
};
|
||||
|
||||
return bb(createTreeNode())
|
||||
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,
|
||||
};
|
||||
|
||||
if (!componentLibraries) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
@ -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 it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -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();
|
||||
|
|
|
@ -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,27 +22534,60 @@ var app = (function (exports) {
|
|||
if (isFunction(event)) event(context);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = (hydrate, treeNode) => ({
|
||||
bb,
|
||||
let routeTo;
|
||||
let currentScreenStore;
|
||||
let currentScreenUbsubscribe;
|
||||
let currentUrl;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
routeTo = screenRouter(screens, onScreenSelected);
|
||||
routeTo(currentUrl || window.location.pathname);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = store => {
|
||||
let currentState = null;
|
||||
const unsubscribe = store.subscribe(s => {
|
||||
currentState = s;
|
||||
});
|
||||
|
||||
const getInitialiseParams = (hydrate, treeNode) => ({
|
||||
bb: getBbClientApi,
|
||||
coreApi,
|
||||
store,
|
||||
document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
frontendDefinition,
|
||||
hydrate,
|
||||
uiFunctions,
|
||||
treeNode,
|
||||
onScreenSlotRendered,
|
||||
});
|
||||
|
||||
const bb = (treeNode, componentProps) => ({
|
||||
hydrateChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(true, treeNode)
|
||||
const getBbClientApi = (treeNode, componentProps) => {
|
||||
return {
|
||||
hydrateChildren: initialiseChildren(
|
||||
getInitialiseParams(true, treeNode)
|
||||
),
|
||||
appendChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(false, treeNode)
|
||||
appendChildren: initialiseChildren(
|
||||
getInitialiseParams(false, treeNode)
|
||||
),
|
||||
insertChildren: (props, htmlElement, anchor) =>
|
||||
_initialiseChildren(initialiseChildrenParams(false, treeNode))(
|
||||
initialiseChildren(getInitialiseParams(false, treeNode))(
|
||||
props,
|
||||
htmlElement,
|
||||
anchor
|
||||
|
@ -22345,26 +22599,50 @@ var app = (function (exports) {
|
|||
setStateFromBinding(store, binding, value),
|
||||
setState: (path, value) => setState(store, path, value),
|
||||
getStateOrValue: (prop, currentContext) =>
|
||||
getStateOrValue(globalState, prop, currentContext),
|
||||
getStateOrValue(currentState, prop, currentContext),
|
||||
store,
|
||||
relativeUrl,
|
||||
api,
|
||||
isBound,
|
||||
parent,
|
||||
});
|
||||
}
|
||||
};
|
||||
return { getInitialiseParams, unsubscribe }
|
||||
};
|
||||
|
||||
return bb(createTreeNode())
|
||||
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,
|
||||
};
|
||||
|
||||
if (!componentLibraries) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
@ -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();
|
||||
|
|
|
@ -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,27 +22534,60 @@ var app = (function (exports) {
|
|||
if (isFunction(event)) event(context);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = (hydrate, treeNode) => ({
|
||||
bb,
|
||||
let routeTo;
|
||||
let currentScreenStore;
|
||||
let currentScreenUbsubscribe;
|
||||
let currentUrl;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
routeTo = screenRouter(screens, onScreenSelected);
|
||||
routeTo(currentUrl || window.location.pathname);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = store => {
|
||||
let currentState = null;
|
||||
const unsubscribe = store.subscribe(s => {
|
||||
currentState = s;
|
||||
});
|
||||
|
||||
const getInitialiseParams = (hydrate, treeNode) => ({
|
||||
bb: getBbClientApi,
|
||||
coreApi,
|
||||
store,
|
||||
document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
frontendDefinition,
|
||||
hydrate,
|
||||
uiFunctions,
|
||||
treeNode,
|
||||
onScreenSlotRendered,
|
||||
});
|
||||
|
||||
const bb = (treeNode, componentProps) => ({
|
||||
hydrateChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(true, treeNode)
|
||||
const getBbClientApi = (treeNode, componentProps) => {
|
||||
return {
|
||||
hydrateChildren: initialiseChildren(
|
||||
getInitialiseParams(true, treeNode)
|
||||
),
|
||||
appendChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(false, treeNode)
|
||||
appendChildren: initialiseChildren(
|
||||
getInitialiseParams(false, treeNode)
|
||||
),
|
||||
insertChildren: (props, htmlElement, anchor) =>
|
||||
_initialiseChildren(initialiseChildrenParams(false, treeNode))(
|
||||
initialiseChildren(getInitialiseParams(false, treeNode))(
|
||||
props,
|
||||
htmlElement,
|
||||
anchor
|
||||
|
@ -22345,26 +22599,50 @@ var app = (function (exports) {
|
|||
setStateFromBinding(store, binding, value),
|
||||
setState: (path, value) => setState(store, path, value),
|
||||
getStateOrValue: (prop, currentContext) =>
|
||||
getStateOrValue(globalState, prop, currentContext),
|
||||
getStateOrValue(currentState, prop, currentContext),
|
||||
store,
|
||||
relativeUrl,
|
||||
api,
|
||||
isBound,
|
||||
parent,
|
||||
});
|
||||
}
|
||||
};
|
||||
return { getInitialiseParams, unsubscribe }
|
||||
};
|
||||
|
||||
return bb(createTreeNode())
|
||||
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,
|
||||
};
|
||||
|
||||
if (!componentLibraries) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
@ -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();
|
||||
|
|
|
@ -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,27 +22534,60 @@ var app = (function (exports) {
|
|||
if (isFunction(event)) event(context);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = (hydrate, treeNode) => ({
|
||||
bb,
|
||||
let routeTo;
|
||||
let currentScreenStore;
|
||||
let currentScreenUbsubscribe;
|
||||
let currentUrl;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
routeTo = screenRouter(screens, onScreenSelected);
|
||||
routeTo(currentUrl || window.location.pathname);
|
||||
};
|
||||
|
||||
const initialiseChildrenParams = store => {
|
||||
let currentState = null;
|
||||
const unsubscribe = store.subscribe(s => {
|
||||
currentState = s;
|
||||
});
|
||||
|
||||
const getInitialiseParams = (hydrate, treeNode) => ({
|
||||
bb: getBbClientApi,
|
||||
coreApi,
|
||||
store,
|
||||
document,
|
||||
componentLibraries,
|
||||
appDefinition,
|
||||
frontendDefinition,
|
||||
hydrate,
|
||||
uiFunctions,
|
||||
treeNode,
|
||||
onScreenSlotRendered,
|
||||
});
|
||||
|
||||
const bb = (treeNode, componentProps) => ({
|
||||
hydrateChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(true, treeNode)
|
||||
const getBbClientApi = (treeNode, componentProps) => {
|
||||
return {
|
||||
hydrateChildren: initialiseChildren(
|
||||
getInitialiseParams(true, treeNode)
|
||||
),
|
||||
appendChildren: _initialiseChildren(
|
||||
initialiseChildrenParams(false, treeNode)
|
||||
appendChildren: initialiseChildren(
|
||||
getInitialiseParams(false, treeNode)
|
||||
),
|
||||
insertChildren: (props, htmlElement, anchor) =>
|
||||
_initialiseChildren(initialiseChildrenParams(false, treeNode))(
|
||||
initialiseChildren(getInitialiseParams(false, treeNode))(
|
||||
props,
|
||||
htmlElement,
|
||||
anchor
|
||||
|
@ -22345,26 +22599,50 @@ var app = (function (exports) {
|
|||
setStateFromBinding(store, binding, value),
|
||||
setState: (path, value) => setState(store, path, value),
|
||||
getStateOrValue: (prop, currentContext) =>
|
||||
getStateOrValue(globalState, prop, currentContext),
|
||||
getStateOrValue(currentState, prop, currentContext),
|
||||
store,
|
||||
relativeUrl,
|
||||
api,
|
||||
isBound,
|
||||
parent,
|
||||
});
|
||||
}
|
||||
};
|
||||
return { getInitialiseParams, unsubscribe }
|
||||
};
|
||||
|
||||
return bb(createTreeNode())
|
||||
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,
|
||||
};
|
||||
|
||||
if (!componentLibraries) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
const { join } = require("path")
|
||||
|
||||
module.exports = (appPath, pageName) => join(appPath, "public", pageName)
|
|
@ -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};`
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue