From 9c4b9a2a25b103dcc6f7d23ea93ac2e81302e7e8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 25 Nov 2020 18:30:09 +0000 Subject: [PATCH] Add fade screen transition and fix navigation component casing --- .../src/builderStore/store/frontend.js | 2 +- .../EventsEditor/EventEditorModal.svelte | 9 +- .../userInterface/temporaryPanelStructure.js | 2 +- packages/client/src/components/Router.svelte | 6 + packages/client/src/components/Screen.svelte | 16 +- packages/client/src/old/createApp.js | 88 ----------- packages/client/src/old/index.js.old | 59 -------- .../client/src/old/render/attachChildren.js | 138 ------------------ .../src/old/render/builtinComponents.js | 10 -- .../src/old/render/prepareRenderComponent.js | 88 ----------- .../client/src/old/render/screenRouter.js | 122 ---------------- .../src/old/render/screenSlotComponent.js | 14 -- .../client/src/old/state/bbComponentApi.js | 34 ----- .../client/src/old/state/eventHandlers.js | 42 ------ .../client/src/old/state/getSetContext.js | 11 -- packages/client/src/old/state/hasBinding.js | 1 - .../src/old/state/renderTemplateString.js | 17 --- .../src/old/state/setBindableComponentProp.js | 13 -- packages/client/src/old/state/stateManager.js | 65 --------- packages/client/src/old/state/store.js | 108 -------------- packages/server/src/constants/pages.js | 2 +- packages/standard-components/components.json | 2 +- packages/standard-components/src/index.js | 2 +- 23 files changed, 30 insertions(+), 821 deletions(-) delete mode 100644 packages/client/src/old/createApp.js delete mode 100644 packages/client/src/old/index.js.old delete mode 100644 packages/client/src/old/render/attachChildren.js delete mode 100644 packages/client/src/old/render/builtinComponents.js delete mode 100644 packages/client/src/old/render/prepareRenderComponent.js delete mode 100644 packages/client/src/old/render/screenRouter.js delete mode 100644 packages/client/src/old/render/screenSlotComponent.js delete mode 100644 packages/client/src/old/state/bbComponentApi.js delete mode 100644 packages/client/src/old/state/eventHandlers.js delete mode 100644 packages/client/src/old/state/getSetContext.js delete mode 100644 packages/client/src/old/state/hasBinding.js delete mode 100644 packages/client/src/old/state/renderTemplateString.js delete mode 100644 packages/client/src/old/state/setBindableComponentProp.js delete mode 100644 packages/client/src/old/state/stateManager.js delete mode 100644 packages/client/src/old/state/store.js diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 31b8dcff22..8cac56df66 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -479,7 +479,7 @@ export const getFrontendStore = () => { // Try to extract a nav component from the master screen const nav = findChildComponentType( state.pages.main, - "@budibase/standard-components/Navigation" + "@budibase/standard-components/navigation" ) if (nav) { let newLink diff --git a/packages/builder/src/components/userInterface/EventsEditor/EventEditorModal.svelte b/packages/builder/src/components/userInterface/EventsEditor/EventEditorModal.svelte index 56485a2b6e..27e91cd29e 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/EventEditorModal.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/EventEditorModal.svelte @@ -1,11 +1,11 @@ {#each screens as screen (screen._id)} - +
+ +
{/each} + + diff --git a/packages/client/src/old/createApp.js b/packages/client/src/old/createApp.js deleted file mode 100644 index f276b8d441..0000000000 --- a/packages/client/src/old/createApp.js +++ /dev/null @@ -1,88 +0,0 @@ -import { attachChildren } from "./render/attachChildren" -import { createTreeNode } from "./render/prepareRenderComponent" -import { screenRouter } from "./render/screenRouter" -import { createStateManager } from "./state/stateManager" -import { getAppId } from "@budibase/component-sdk" - -export const createApp = ({ - componentLibraries, - frontendDefinition, - window, -}) => { - let routeTo - let currentUrl - let screenStateManager - - const onScreenSlotRendered = screenSlotNode => { - const onScreenSelected = (screen, url) => { - const stateManager = createStateManager({ - componentLibraries, - onScreenSlotRendered: () => {}, - routeTo, - }) - const getAttachChildrenParams = attachChildrenParams(stateManager) - screenSlotNode.props._children = [screen.props] - const initialiseChildParams = getAttachChildrenParams(screenSlotNode) - attachChildren(initialiseChildParams)(screenSlotNode.rootElement, { - hydrate: true, - force: true, - }) - if (screenStateManager) screenStateManager.destroy() - screenStateManager = stateManager - currentUrl = url - } - - routeTo = screenRouter({ - screens: frontendDefinition.screens, - onScreenSelected, - window, - }) - const fallbackPath = window.location.pathname.replace(getAppId(), "") - routeTo(currentUrl || fallbackPath) - } - - const attachChildrenParams = stateManager => { - const getInitialiseParams = treeNode => ({ - componentLibraries, - treeNode, - onScreenSlotRendered, - setupState: stateManager.setup, - }) - - return getInitialiseParams - } - - let rootTreeNode - const pageStateManager = createStateManager({ - componentLibraries, - onScreenSlotRendered, - // seems weird, but the routeTo variable may not be available at this point - routeTo: url => routeTo(url), - }) - - const initialisePage = (page, target, urlPath) => { - currentUrl = urlPath - - rootTreeNode = createTreeNode() - rootTreeNode.props = { - _children: [page.props], - } - const getInitialiseParams = attachChildrenParams(pageStateManager) - const initChildParams = getInitialiseParams(rootTreeNode) - - attachChildren(initChildParams)(target, { - hydrate: true, - force: true, - }) - - return rootTreeNode - } - - return { - initialisePage, - screenStore: () => screenStateManager.store, - pageStore: () => pageStateManager.store, - routeTo: () => routeTo, - rootNode: () => rootTreeNode, - } -} diff --git a/packages/client/src/old/index.js.old b/packages/client/src/old/index.js.old deleted file mode 100644 index 5c46c1f6b9..0000000000 --- a/packages/client/src/old/index.js.old +++ /dev/null @@ -1,59 +0,0 @@ -import { createApp } from "./createApp" -import { builtins, builtinLibName } from "./render/builtinComponents" -import { getAppId } from "@budibase/component-sdk" - -/** - * create a web application from static budibase definition files. - * @param {object} opts - configuration options for budibase client libary - */ -export const loadBudibase = async opts => { - const _window = (opts && opts.window) || window - // const _localStorage = (opts && opts.localStorage) || localStorage - const appId = getAppId(window.document.cookie) - const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"] - - const user = {} - - const componentLibraryModules = (opts && opts.componentLibraries) || {} - - const libraries = frontendDefinition.libraries || [] - - for (let library of libraries) { - // fetch the JavaScript for the component libraries from the server - componentLibraryModules[library] = await import( - `/componentlibrary?library=${encodeURI(library)}&appId=${appId}` - ) - } - - componentLibraryModules[builtinLibName] = builtins(_window) - - const { - initialisePage, - screenStore, - pageStore, - routeTo, - rootNode, - } = createApp({ - componentLibraries: componentLibraryModules, - frontendDefinition, - user, - window: _window, - }) - - const route = _window.location - ? _window.location.pathname.replace(`${appId}/`, "").replace(appId, "") - : "" - - initialisePage(frontendDefinition.page, _window.document.body, route) - - return { - screenStore, - pageStore, - routeTo, - rootNode, - } -} - -if (window) { - window.loadBudibase = loadBudibase -} diff --git a/packages/client/src/old/render/attachChildren.js b/packages/client/src/old/render/attachChildren.js deleted file mode 100644 index ee6f313c33..0000000000 --- a/packages/client/src/old/render/attachChildren.js +++ /dev/null @@ -1,138 +0,0 @@ -import { prepareRenderComponent } from "./prepareRenderComponent" -import { isScreenSlot } from "./builtinComponents" -import deepEqual from "deep-equal" -import appStore from "../state/store" - -export const attachChildren = initialiseOpts => (htmlElement, options) => { - const { - componentLibraries, - treeNode, - onScreenSlotRendered, - setupState, - } = initialiseOpts - - const anchor = options && options.anchor ? options.anchor : null - const force = options ? options.force : false - const hydrate = options ? options.hydrate : true - const context = options && options.context - - if (!force && treeNode.children.length > 0) return treeNode.children - - for (let childNode of treeNode.children) { - childNode.destroy() - } - - if (!htmlElement) return - - if (hydrate) { - while (htmlElement.firstChild) { - htmlElement.removeChild(htmlElement.firstChild) - } - } - - const contextStoreKeys = [] - - // create new context if supplied - if (context) { - let childIndex = 0 - // if context is an array, map to new structure - const contextArray = Array.isArray(context) ? context : [context] - for (let ctx of contextArray) { - const key = appStore.create( - ctx, - treeNode.props._id, - childIndex, - treeNode.contextStoreKey - ) - contextStoreKeys.push(key) - childIndex++ - } - } - - const childNodes = [] - - const createChildNodes = contextStoreKey => { - for (let childProps of treeNode.props._children) { - const { componentName, libName } = splitName(childProps._component) - - if (!componentName || !libName) return - - const ComponentConstructor = componentLibraries[libName][componentName] - - const childNode = prepareRenderComponent({ - props: childProps, - parentNode: treeNode, - ComponentConstructor, - htmlElement, - anchor, - // in same context as parent, unless a new one was supplied - contextStoreKey, - }) - - childNodes.push(childNode) - } - } - - if (context) { - // if new context(s) is supplied, then create nodes - // with keys to new context stores - for (let contextStoreKey of contextStoreKeys) { - createChildNodes(contextStoreKey) - } - } else { - // otherwise, use same context store as parent - // which maybe undefined (therfor using the root state) - createChildNodes(treeNode.contextStoreKey) - } - - // if everything is equal, then don't re-render - if (areTreeNodesEqual(treeNode.children, childNodes)) return treeNode.children - - for (let node of childNodes) { - const initialProps = setupState(node) - node.render(initialProps) - } - - const screenSlot = childNodes.find(n => isScreenSlot(n.props._component)) - - if (onScreenSlotRendered && screenSlot) { - // assuming there is only ever one screen slot - onScreenSlotRendered(screenSlot) - } - - treeNode.children = childNodes - - return childNodes -} - -const splitName = fullname => { - const nameParts = fullname.split("/") - - const componentName = nameParts[nameParts.length - 1] - - const libName = fullname.substring( - 0, - fullname.length - componentName.length - 1 - ) - - return { libName, componentName } -} - -const areTreeNodesEqual = (children1, children2) => { - if (children1.length !== children2.length) return false - if (children1 === children2) return true - - let isEqual = false - for (let i = 0; i < children1.length; i++) { - // same context and same children, then nothing has changed - isEqual = - deepEqual(children1[i].context, children2[i].context) && - areTreeNodesEqual(children1[i].children, children2[i].children) - if (!isEqual) return false - if (isScreenSlot(children1[i].parentNode.props._component)) { - isEqual = deepEqual(children1[i].props, children2[i].props) - } - if (!isEqual) return false - } - return true -} diff --git a/packages/client/src/old/render/builtinComponents.js b/packages/client/src/old/render/builtinComponents.js deleted file mode 100644 index f5343234f4..0000000000 --- a/packages/client/src/old/render/builtinComponents.js +++ /dev/null @@ -1,10 +0,0 @@ -import { screenSlotComponent } from "./screenSlotComponent" - -export const builtinLibName = "##builtin" - -export const isScreenSlot = componentName => - componentName === "##builtin/screenslot" - -export const builtins = window => ({ - screenslot: screenSlotComponent(window), -}) diff --git a/packages/client/src/old/render/prepareRenderComponent.js b/packages/client/src/old/render/prepareRenderComponent.js deleted file mode 100644 index 5d0d34d003..0000000000 --- a/packages/client/src/old/render/prepareRenderComponent.js +++ /dev/null @@ -1,88 +0,0 @@ -import renderTemplateString from "../state/renderTemplateString" -import appStore from "../state/store" -import hasBinding from "../state/hasBinding" - -export const prepareRenderComponent = ({ - ComponentConstructor, - htmlElement, - anchor, - props, - parentNode, - contextStoreKey, -}) => { - const thisNode = createTreeNode() - thisNode.parentNode = parentNode - thisNode.props = props - thisNode.contextStoreKey = contextStoreKey - - // the treeNode is first created (above), and then this - // render method is add. The treeNode is returned, and - // render is called later (in attachChildren) - thisNode.render = initialProps => { - thisNode.component = new ComponentConstructor({ - target: htmlElement, - props: initialProps, - hydrate: false, - anchor, - }) - - // finds the root element of the component, which was created by the contructor above - // we use this later to attach a className to. This is how styles - // are applied by the builder - thisNode.rootElement = htmlElement.children[htmlElement.children.length - 1] - - let [componentName] = props._component.match(/[a-z]*$/) - if (props._id && thisNode.rootElement) { - thisNode.rootElement.classList.add(`${componentName}-${props._id}`) - } - - // make this node listen to the store - if (thisNode.stateBound) { - const unsubscribe = appStore.subscribe(state => { - const storeBoundProps = Object.keys(initialProps._bb.props).filter(p => - hasBinding(initialProps._bb.props[p]) - ) - if (storeBoundProps.length > 0) { - const toSet = {} - for (let prop of storeBoundProps) { - const propValue = initialProps._bb.props[prop] - toSet[prop] = renderTemplateString(propValue, state) - } - thisNode.component.$set(toSet) - } - }, thisNode.contextStoreKey) - thisNode.unsubscribe = unsubscribe - } - } - - return thisNode -} - -export const createTreeNode = () => ({ - context: {}, - props: {}, - rootElement: null, - parentNode: null, - children: [], - bindings: [], - component: null, - unsubscribe: () => {}, - render: () => {}, - get destroy() { - const node = this - return () => { - if (node.children) { - // destroy children first - from leaf nodes up - for (let child of node.children) { - child.destroy() - } - } - if (node.unsubscribe) node.unsubscribe() - if (node.component && node.component.$destroy) node.component.$destroy() - for (let onDestroyItem of node.onDestroy) { - onDestroyItem() - } - } - }, - onDestroy: [], -}) diff --git a/packages/client/src/old/render/screenRouter.js b/packages/client/src/old/render/screenRouter.js deleted file mode 100644 index 78dc75b3c4..0000000000 --- a/packages/client/src/old/render/screenRouter.js +++ /dev/null @@ -1,122 +0,0 @@ -import regexparam from "regexparam" -import appStore from "../state/store" -import { getAppId } from "@budibase/component-sdk" - -export const screenRouter = ({ screens, onScreenSelected, window }) => { - function sanitize(url) { - if (!url) return url - return url - .split("/") - .map(part => { - // if parameter, then use as is - if (part.startsWith(":")) return part - return encodeURIComponent(part) - }) - .join("/") - .toLowerCase() - } - - const isRunningLocally = () => { - const hostname = (window.location && window.location.hostname) || "" - return ( - hostname === "localhost" || - hostname === "127.0.0.1" || - hostname.startsWith("192.168") - ) - } - - const makeRootedPath = url => { - if (isRunningLocally()) { - const appId = getAppId() - if (url) { - url = sanitize(url) - if (!url.startsWith("/")) { - url = `/${url}` - } - if (url.startsWith(`/${appId}`)) { - return url - } - return `/${appId}${url}` - } - return `/${appId}` - } - return sanitize(url) - } - - const routes = screens.map(s => makeRootedPath(s.routing?.route)) - let fallback = routes.findIndex(([p]) => p === makeRootedPath("*")) - if (fallback < 0) fallback = 0 - - let current - - function route(url) { - const _url = makeRootedPath(url.state || url) - current = routes.findIndex( - p => - p !== makeRootedPath("*") && - new RegExp("^" + p.toLowerCase() + "$").test(_url.toLowerCase()) - ) - - const params = {} - - if (current === -1) { - routes.forEach((p, i) => { - // ignore home - which matched everything - if (p === makeRootedPath("*")) return - 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 - }) - } - - appStore.update(state => { - state["##routeParams"] = params - return state - }) - - const screenIndex = current !== -1 ? current : fallback - - try { - !url.state && history.pushState(_url, null, _url) - } catch (_) { - // ignoring an exception here as the builder runs an iframe, which does not like this - } - - onScreenSelected(screens[screenIndex], _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 - - const target = (x && x.target) || "_self" - if (!y || target !== "_self" || x.host !== location.host) return - - e.preventDefault() - route(y) - } - - addEventListener("popstate", route) - addEventListener("pushstate", route) - addEventListener("click", click) - - return route -} diff --git a/packages/client/src/old/render/screenSlotComponent.js b/packages/client/src/old/render/screenSlotComponent.js deleted file mode 100644 index 6ec133474a..0000000000 --- a/packages/client/src/old/render/screenSlotComponent.js +++ /dev/null @@ -1,14 +0,0 @@ -export const screenSlotComponent = window => { - return function(opts) { - const node = window.document.createElement("DIV") - const $set = props => { - props._bb.attachChildren(node) - } - const $destroy = () => { - if (opts.target && node) opts.target.removeChild(node) - } - this.$set = $set - this.$destroy = $destroy - opts.target.appendChild(node) - } -} diff --git a/packages/client/src/old/state/bbComponentApi.js b/packages/client/src/old/state/bbComponentApi.js deleted file mode 100644 index 554d1d1df7..0000000000 --- a/packages/client/src/old/state/bbComponentApi.js +++ /dev/null @@ -1,34 +0,0 @@ -import setBindableComponentProp from "./setBindableComponentProp" -import { attachChildren } from "../render/attachChildren" -import store from "./store" - -export const bbFactory = ({ - componentLibraries, - onScreenSlotRendered, - runEventActions, -}) => { - return (treeNode, setupState) => { - const attachParams = { - componentLibraries, - treeNode, - onScreenSlotRendered, - setupState, - } - - return { - attachChildren: attachChildren(attachParams), - props: treeNode.props, - call: async eventName => - eventName && - (await runEventActions( - treeNode.props[eventName], - store.getState(treeNode.contextStoreKey) - )), - setBinding: setBindableComponentProp(treeNode), - parent, - store: store.getStore(treeNode.contextStoreKey), - // these parameters are populated by screenRouter - routeParams: () => store.getState()["##routeParams"], - } - } -} diff --git a/packages/client/src/old/state/eventHandlers.js b/packages/client/src/old/state/eventHandlers.js deleted file mode 100644 index 39aecd388c..0000000000 --- a/packages/client/src/old/state/eventHandlers.js +++ /dev/null @@ -1,42 +0,0 @@ -import renderTemplateString from "./renderTemplateString" -import { updateRow, saveRow, deleteRow } from "@budibase/component-sdk" - -export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType" - -export const eventHandlers = routeTo => { - const handlers = { - "Navigate To": param => routeTo(param && param.url), - "Update Row": updateRow, - "Save Row": saveRow, - "Delete Row": deleteRow, - } - - // when an event is called, this is what gets run - const runEventActions = async (actions, state) => { - if (!actions) return - // calls event handlers sequentially - for (let action of actions) { - const handler = handlers[action[EVENT_TYPE_MEMBER_NAME]] - const parameters = createParameters(action.parameters, state) - if (handler) { - await handler(parameters, state) - } - } - } - - return runEventActions -} - -// this will take a parameters obj, iterate all keys, and do a mustache render -// for every string. It will work recursively if it encounnters an {} -const createParameters = (parameterTemplateObj, state) => { - const parameters = {} - for (let key in parameterTemplateObj) { - if (typeof parameterTemplateObj[key] === "string") { - parameters[key] = renderTemplateString(parameterTemplateObj[key], state) - } else if (typeof parameterTemplateObj[key] === "object") { - parameters[key] = createParameters(parameterTemplateObj[key], state) - } - } - return parameters -} diff --git a/packages/client/src/old/state/getSetContext.js b/packages/client/src/old/state/getSetContext.js deleted file mode 100644 index 58dbb22d2c..0000000000 --- a/packages/client/src/old/state/getSetContext.js +++ /dev/null @@ -1,11 +0,0 @@ -export const setContext = treeNode => (key, value) => - (treeNode.context[key] = value) - -export const getContext = treeNode => key => { - if (treeNode.context && treeNode.context[key] !== undefined) - return treeNode.context[key] - - if (!treeNode.context.$parent) return - - return getContext(treeNode.parentNode)(key) -} diff --git a/packages/client/src/old/state/hasBinding.js b/packages/client/src/old/state/hasBinding.js deleted file mode 100644 index ac6ae79074..0000000000 --- a/packages/client/src/old/state/hasBinding.js +++ /dev/null @@ -1 +0,0 @@ -export default value => typeof value === "string" && value.includes("{{") diff --git a/packages/client/src/old/state/renderTemplateString.js b/packages/client/src/old/state/renderTemplateString.js deleted file mode 100644 index e2d8eba859..0000000000 --- a/packages/client/src/old/state/renderTemplateString.js +++ /dev/null @@ -1,17 +0,0 @@ -import mustache from "mustache" - -// this is a much more liberal version of mustache's escape function -// ...just ignoring < and > to prevent tags from user input -// original version here https://github.com/janl/mustache.js/blob/4b7908f5c9fec469a11cfaed2f2bed23c84e1c5c/mustache.js#L78 - -const entityMap = { - "<": "<", - ">": ">", -} - -mustache.escape = text => - String(text).replace(/[&<>"'`=/]/g, function fromEntityMap(s) { - return entityMap[s] || s - }) - -export default mustache.render diff --git a/packages/client/src/old/state/setBindableComponentProp.js b/packages/client/src/old/state/setBindableComponentProp.js deleted file mode 100644 index 4fe9191fc4..0000000000 --- a/packages/client/src/old/state/setBindableComponentProp.js +++ /dev/null @@ -1,13 +0,0 @@ -import appStore from "./store" - -export default treeNode => (propName, value) => { - if (!propName || propName.length === 0) return - if (!treeNode) return - const componentId = treeNode.props._id - - appStore.update(state => { - state[componentId] = state[componentId] || {} - state[componentId][propName] = value - return state - }, treeNode.contextStoreKey) -} diff --git a/packages/client/src/old/state/stateManager.js b/packages/client/src/old/state/stateManager.js deleted file mode 100644 index b753d2f346..0000000000 --- a/packages/client/src/old/state/stateManager.js +++ /dev/null @@ -1,65 +0,0 @@ -import { eventHandlers } from "./eventHandlers" -import { bbFactory } from "./bbComponentApi" -import renderTemplateString from "./renderTemplateString" -import appStore from "./store" -import hasBinding from "./hasBinding" - -const doNothing = () => {} -doNothing.isPlaceholder = true - -const isMetaProp = propName => - propName === "_component" || - propName === "_children" || - propName === "_id" || - propName === "_style" || - propName === "_code" || - propName === "_codeMeta" || - propName === "_styles" - -export const createStateManager = ({ - componentLibraries, - onScreenSlotRendered, - routeTo, -}) => { - let runEventActions = eventHandlers(routeTo) - - const bb = bbFactory({ - componentLibraries, - onScreenSlotRendered, - runEventActions, - }) - - const setup = _setup(bb) - - return { - setup, - destroy: () => {}, - } -} - -const _setup = bb => node => { - const props = node.props - const initialProps = { ...props } - - for (let propName in props) { - if (isMetaProp(propName)) continue - - const propValue = props[propName] - - const isBound = hasBinding(propValue) - - if (isBound) { - const state = appStore.getState(node.contextStoreKey) - initialProps[propName] = renderTemplateString(propValue, state) - - if (!node.stateBound) { - node.stateBound = true - } - } - } - - const setup = _setup(bb) - initialProps._bb = bb(node, setup) - - return initialProps -} diff --git a/packages/client/src/old/state/store.js b/packages/client/src/old/state/store.js deleted file mode 100644 index cf284ec08e..0000000000 --- a/packages/client/src/old/state/store.js +++ /dev/null @@ -1,108 +0,0 @@ -import { writable } from "svelte/store" - -// we assume that the reference to this state object -// will remain for the life of the application -const rootState = {} -const rootStore = writable(rootState) -const contextStores = {} - -// contextProviderId is the component id that provides the data for the context -const contextStoreKey = (dataProviderId, childIndex) => - `${dataProviderId}${childIndex >= 0 ? ":" + childIndex : ""}` - -// creates a store for a datacontext (e.g. each item in a list component) -// overrides store if already exists -const create = (data, dataProviderId, childIndex, parentContextStoreId) => { - const key = contextStoreKey(dataProviderId, childIndex) - const state = { data } - - // add reference to parent state object, - // so we can use bindings like state.parent.parent - // (if no parent, then parent is rootState ) - state.parent = parentContextStoreId - ? contextStores[parentContextStoreId].state - : rootState - - contextStores[key] = { - store: writable(state), - subscriberCount: 0, - state, - parentContextStoreId, - } - - return key -} - -const subscribe = (subscription, storeKey) => { - if (!storeKey) { - return rootStore.subscribe(subscription) - } - const contextStore = contextStores[storeKey] - - // we are subscribing to multiple stores, - // we dont want to run our listener for every subscription, the first time - // as this could repeatedly run $set on the same component - // ... which already has its initial properties set properly - const ignoreFirstSubscription = () => { - let hasRunOnce = false - return () => { - if (hasRunOnce) subscription(contextStore.state) - hasRunOnce = true - } - } - - const unsubscribes = [rootStore.subscribe(ignoreFirstSubscription())] - - // we subscribe to all stores in the hierarchy - const ancestorSubscribe = ctxStore => { - // unsubscribe func returned by svelte store - const svelteUnsub = ctxStore.store.subscribe(ignoreFirstSubscription()) - - // we wrap the svelte unsubscribe, so we can - // cleanup stores when they are no longer subscribed to - const unsub = () => { - ctxStore.subscriberCount = contextStore.subscriberCount - 1 - // when no subscribers left, we delete the store - if (ctxStore.subscriberCount === 0) { - delete ctxStore[storeKey] - } - svelteUnsub() - } - unsubscribes.push(unsub) - if (ctxStore.parentContextStoreId) { - ancestorSubscribe(contextStores[ctxStore.parentContextStoreId]) - } - } - - ancestorSubscribe(contextStore) - - // our final unsubscribe function calls unsubscribe on all stores - return () => unsubscribes.forEach(u => u()) -} - -const findStore = (dataProviderId, childIndex) => - dataProviderId - ? contextStores[contextStoreKey(dataProviderId, childIndex)].store - : rootStore - -const update = (updatefunc, dataProviderId, childIndex) => - findStore(dataProviderId, childIndex).update(updatefunc) - -const set = (value, dataProviderId, childIndex) => - findStore(dataProviderId, childIndex).set(value) - -const getState = contextStoreKey => - contextStoreKey ? contextStores[contextStoreKey].state : rootState - -const getStore = contextStoreKey => - contextStoreKey ? contextStores[contextStoreKey].store : rootStore - -export default { - subscribe, - update, - set, - getState, - create, - contextStoreKey, - getStore, -} diff --git a/packages/server/src/constants/pages.js b/packages/server/src/constants/pages.js index 5fe74d6123..e017885b49 100644 --- a/packages/server/src/constants/pages.js +++ b/packages/server/src/constants/pages.js @@ -39,7 +39,7 @@ const MAIN = { _children: [ { _id: "49e0e519-9e5e-4127-885a-ee6a0a49e2c1", - _component: "@budibase/standard-components/Navigation", + _component: "@budibase/standard-components/navigation", _styles: { normal: { "max-width": "1400px", diff --git a/packages/standard-components/components.json b/packages/standard-components/components.json index 7eddadb68e..180652688f 100644 --- a/packages/standard-components/components.json +++ b/packages/standard-components/components.json @@ -13,7 +13,7 @@ "embed": "string" } }, - "Navigation": { + "navigation": { "name": "Navigation", "description": "A basic header navigation component", "children": true, diff --git a/packages/standard-components/src/index.js b/packages/standard-components/src/index.js index 51f53956fa..f578dc8f6f 100644 --- a/packages/standard-components/src/index.js +++ b/packages/standard-components/src/index.js @@ -10,7 +10,7 @@ export { default as button } from "./Button.svelte" export { default as login } from "./Login.svelte" export { default as link } from "./Link.svelte" export { default as image } from "./Image.svelte" -export { default as Navigation } from "./Navigation.svelte" +export { default as navigation } from "./Navigation.svelte" export { default as datagrid } from "./grid/Component.svelte" export { default as dataform } from "./DataForm.svelte" export { default as dataformwide } from "./DataFormWide.svelte"