diff --git a/packages/builder/src/builderStore/buildCodeForScreens.js b/packages/builder/src/builderStore/buildCodeForScreens.js
deleted file mode 100644
index 69ef0d6e0b..0000000000
--- a/packages/builder/src/builderStore/buildCodeForScreens.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const buildCodeForSingleScreen = screen => {
- let code = ""
- const walkProps = props => {
- if (props._code && props._code.trim().length > 0) {
- code += buildComponentCode(props)
- }
-
- if (!props._children) return
-
- for (let child of props._children) {
- walkProps(child)
- }
- }
-
- walkProps(screen.props)
-
- return code
-}
-
-export const buildCodeForScreens = screens => {
- let allfunctions = ""
- for (let screen of screens) {
- allfunctions += buildCodeForSingleScreen(screen)
- }
-
- return `({ ${allfunctions} });`
-}
-
-const buildComponentCode = componentProps =>
- `"${componentProps._id}" : (render, context, state, route) => {
-${componentProps._code}
-},
-`
diff --git a/packages/builder/src/builderStore/getNewComponentName.js b/packages/builder/src/builderStore/getNewComponentName.js
index a4565c2296..a69bec21ad 100644
--- a/packages/builder/src/builderStore/getNewComponentName.js
+++ b/packages/builder/src/builderStore/getNewComponentName.js
@@ -1,5 +1,7 @@
import { walkProps } from "./storeUtils"
import { get_capitalised_name } from "../helpers"
+import { get } from "svelte/store"
+import { allScreens } from "builderStore"
export default function(component, state) {
const capitalised = get_capitalised_name(
@@ -25,7 +27,7 @@ export default function(component, state) {
findMatches(state.currentPreviewItem.props)
} else {
// viewing master page - need to find against all screens
- for (let screen of state.screens) {
+ for (let screen of get(allScreens)) {
findMatches(screen.props)
}
}
@@ -33,7 +35,7 @@ export default function(component, state) {
let index = 1
let name
while (!name) {
- const tryName = `${capitalised} ${index}`
+ const tryName = `${capitalised || "Copy"} ${index}`
if (!matchingComponents.includes(tryName)) name = tryName
index++
}
diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js
index 6317640955..1bd86480a4 100644
--- a/packages/builder/src/builderStore/index.js
+++ b/packages/builder/src/builderStore/index.js
@@ -1,14 +1,36 @@
-import { getStore } from "./store"
+import { getFrontendStore } from "./store/frontend"
import { getBackendUiStore } from "./store/backend"
import { getAutomationStore } from "./store/automation/"
import { getThemeStore } from "./store/theme"
+import { derived } from "svelte/store"
import analytics from "analytics"
-export const store = getStore()
+export const store = getFrontendStore()
export const backendUiStore = getBackendUiStore()
export const automationStore = getAutomationStore()
export const themeStore = getThemeStore()
+export const allScreens = derived(store, $store => {
+ let screens = []
+ if ($store.pages == null) {
+ return screens
+ }
+ for (let page of Object.values($store.pages)) {
+ screens = screens.concat(page._screens)
+ }
+ return screens
+})
+
+export const currentScreens = derived(store, $store => {
+ const currentScreens = $store.pages[$store.currentPageName]?._screens
+ if (currentScreens == null) {
+ return []
+ }
+ return Array.isArray(currentScreens)
+ ? currentScreens
+ : Object.values(currentScreens)
+})
+
export const initialise = async () => {
try {
await analytics.activate()
diff --git a/packages/builder/src/builderStore/insertCodeMetadata.js b/packages/builder/src/builderStore/insertCodeMetadata.js
deleted file mode 100644
index 9093c4003d..0000000000
--- a/packages/builder/src/builderStore/insertCodeMetadata.js
+++ /dev/null
@@ -1,17 +0,0 @@
-export const insertCodeMetadata = props => {
- if (props._code && props._code.length > 0) {
- props._codeMeta = codeMetaData(props._code)
- }
-
- if (!props._children || props._children.length === 0) return
-
- for (let child of props._children) {
- insertCodeMetadata(child)
- }
-}
-
-const codeMetaData = code => {
- return {
- dependsOnStore: RegExp(/(state.)/g).test(code),
- }
-}
diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
new file mode 100644
index 0000000000..ee0c98ed8d
--- /dev/null
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -0,0 +1,529 @@
+import { get, writable } from "svelte/store"
+import { cloneDeep } from "lodash/fp"
+import {
+ createProps,
+ getBuiltin,
+ makePropsSafe,
+} from "components/userInterface/pagesParsing/createProps"
+import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
+import { allScreens, backendUiStore } from "builderStore"
+import { generate_screen_css } from "../generate_css"
+import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
+import api from "../api"
+import { DEFAULT_PAGES_OBJECT } from "../../constants"
+import getNewComponentName from "../getNewComponentName"
+import analytics from "analytics"
+import {
+ findChildComponentType,
+ generateNewIdsForComponent,
+ getComponentDefinition,
+ getParent,
+} from "../storeUtils"
+
+const INITIAL_FRONTEND_STATE = {
+ apps: [],
+ name: "",
+ description: "",
+ pages: DEFAULT_PAGES_OBJECT,
+ mainUi: {},
+ unauthenticatedUi: {},
+ components: [],
+ currentPreviewItem: null,
+ currentComponentInfo: null,
+ currentFrontEndType: "none",
+ currentPageName: "",
+ currentComponentProps: null,
+ errors: [],
+ hasAppPackage: false,
+ libraries: null,
+ appId: "",
+}
+
+export const getFrontendStore = () => {
+ const store = writable({ ...INITIAL_FRONTEND_STATE })
+
+ store.actions = {
+ // TODO: REFACTOR
+ initialise: async pkg => {
+ store.update(state => {
+ state.appId = pkg.application._id
+ return state
+ })
+ const screens = await api.get("/api/screens").then(r => r.json())
+
+ const mainScreens = screens.filter(screen =>
+ screen._id.includes(pkg.pages.main._id)
+ ),
+ unauthScreens = screens.filter(screen =>
+ screen._id.includes(pkg.pages.unauthenticated._id)
+ )
+ pkg.pages = {
+ main: {
+ ...pkg.pages.main,
+ _screens: mainScreens,
+ },
+ unauthenticated: {
+ ...pkg.pages.unauthenticated,
+ _screens: unauthScreens,
+ },
+ }
+
+ // if the app has just been created
+ // we need to build the CSS and save
+ if (pkg.justCreated) {
+ for (let pageName of ["main", "unauthenticated"]) {
+ const page = pkg.pages[pageName]
+ store.actions.screens.regenerateCss(page)
+ for (let screen of page._screens) {
+ store.actions.screens.regenerateCss(screen)
+ }
+
+ await api.post(`/api/pages/${page._id}`, {
+ page: {
+ componentLibraries: pkg.application.componentLibraries,
+ ...page,
+ },
+ screens: page._screens,
+ })
+ }
+ }
+
+ pkg.justCreated = false
+
+ const components = await fetchComponentLibDefinitions(pkg.application._id)
+
+ store.update(state => ({
+ ...state,
+ libraries: pkg.application.componentLibraries,
+ components,
+ name: pkg.application.name,
+ description: pkg.application.description,
+ appId: pkg.application._id,
+ pages: pkg.pages,
+ hasAppPackage: true,
+ currentScreens: [],
+ builtins: [getBuiltin("##builtin/screenslot")],
+ appInstance: pkg.application.instance,
+ }))
+
+ await backendUiStore.actions.database.select(pkg.application.instance)
+ },
+ selectPageOrScreen: type => {
+ store.update(state => {
+ state.currentFrontEndType = type
+
+ const pageOrScreen =
+ type === "page"
+ ? state.pages[state.currentPageName]
+ : state.pages[state.currentPageName]._screens[0]
+
+ state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
+ state.currentPreviewItem = pageOrScreen
+ state.currentView = "detail"
+ return state
+ })
+ },
+ screens: {
+ select: screenName => {
+ store.update(state => {
+ const screen = getExactComponent(get(allScreens), screenName, true)
+ state.currentPreviewItem = screen
+ state.currentFrontEndType = "screen"
+ state.currentView = "detail"
+
+ store.actions.screens.regenerateCssForCurrentScreen()
+ // this.regenerateCssForCurrentScreen()
+ // regenerateCssForCurrentScreen(s)
+ const safeProps = makePropsSafe(
+ state.components[screen.props._component],
+ screen.props
+ )
+ screen.props = safeProps
+ state.currentComponentInfo = safeProps
+ return state
+ })
+ },
+ create: async screen => {
+ let savePromise
+ store.update(state => {
+ state.currentPreviewItem = screen
+ state.currentComponentInfo = screen.props
+ state.currentFrontEndType = "screen"
+
+ if (state.currentPreviewItem) {
+ store.actions.screens.regenerateCss(state.currentPreviewItem)
+ }
+
+ savePromise = store.actions.screens.save(screen)
+ return state
+ })
+
+ await savePromise
+ },
+ save: async screen => {
+ const storeContents = get(store)
+ const pageName = storeContents.currentPageName || "main"
+ const currentPage = storeContents.pages[pageName]
+ const currentPageScreens = currentPage._screens
+
+ let savePromise
+ const response = await api.post(
+ `/api/screens/${currentPage._id}`,
+ screen
+ )
+ const json = await response.json()
+ screen._rev = json.rev
+ screen._id = json.id
+ const foundScreen = currentPageScreens.findIndex(
+ el => el._id === screen._id
+ )
+ if (currentPageScreens !== -1) {
+ currentPageScreens.splice(foundScreen, 1)
+ }
+ currentPageScreens.push(screen)
+
+ // TODO: should carry out all server updates to screen in a single call
+ store.update(state => {
+ state.pages[pageName]._screens = currentPageScreens
+ state.currentPreviewItem = screen
+ const safeProps = makePropsSafe(
+ state.components[screen.props._component],
+ screen.props
+ )
+ state.currentComponentInfo = safeProps
+ screen.props = safeProps
+ savePromise = store.actions.pages.save()
+ return state
+ })
+ await savePromise
+ },
+ regenerateCss: screen => {
+ screen._css = generate_screen_css([screen.props])
+ },
+ regenerateCssForCurrentScreen: () => {
+ const { currentPreviewItem } = get(store)
+ if (currentPreviewItem) {
+ store.actions.screens.regenerateCss(currentPreviewItem)
+ }
+ },
+ delete: async (screensToDelete, pageName) => {
+ let deletePromise
+
+ store.update(state => {
+ if (pageName == null) {
+ pageName = state.pages.main.name
+ }
+ for (let screenToDelete of Array.isArray(screenToDelete)
+ ? screenToDelete
+ : [screenToDelete]) {
+ // Remove screen from current page as well
+ // TODO: Should be done server side
+ state.pages[pageName]._screens = state.pages[
+ pageName
+ ]._screens.filter(scr => scr.name !== screenToDelete.name)
+ deletePromise = api.delete(
+ `/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
+ )
+ }
+ return state
+ })
+ await deletePromise
+ },
+ },
+ preview: {
+ // _saveCurrentPreviewItem
+ saveSelected: () => {
+ const state = get(store)
+ state.currentFrontEndType === "page"
+ ? store.actions.pages.save()
+ : store.actions.screens.save(state.currentPreviewItem)
+ },
+ },
+ pages: {
+ select: pageName => {
+ store.update(state => {
+ const currentPage = state.pages[pageName]
+
+ state.currentScreens = currentPage._screens
+ state.currentFrontEndType = "page"
+ state.currentView = "detail"
+ state.currentPageName = pageName
+
+ // This is the root of many problems.
+ // Uncaught (in promise) TypeError: Cannot read property '_component' of undefined
+ // it appears that the currentPage sometimes has _props instead of props
+ // why
+ const safeProps = makePropsSafe(
+ state.components[currentPage.props._component],
+ currentPage.props
+ )
+ state.currentComponentInfo = safeProps
+ currentPage.props = safeProps
+ state.currentPreviewItem = state.pages[pageName]
+ store.actions.screens.regenerateCssForCurrentScreen()
+
+ for (let screen of get(allScreens)) {
+ screen._css = generate_screen_css([screen.props])
+ }
+
+ return state
+ })
+ },
+ save: async page => {
+ const storeContents = get(store)
+ const pageName = storeContents.currentPageName || "main"
+ const pageToSave = page || storeContents.pages[pageName]
+
+ // TODO: revisit. This sends down a very weird payload
+ const response = await api
+ .post(`/api/pages/${pageToSave._id}`, {
+ page: {
+ componentLibraries: storeContents.pages.componentLibraries,
+ ...pageToSave,
+ },
+ screens: pageToSave._screens,
+ })
+ .then(response => response.json())
+
+ store.update(state => {
+ state.pages[pageName]._rev = response.rev
+ return state
+ })
+ },
+ },
+ components: {
+ select: component => {
+ store.update(state => {
+ const componentDef = component._component.startsWith("##")
+ ? component
+ : state.components[component._component]
+ state.currentComponentInfo = makePropsSafe(componentDef, component)
+ state.currentView = "component"
+ return state
+ })
+ },
+ // addChildComponent
+ create: (componentToAdd, presetProps) => {
+ store.update(state => {
+ function findSlot(component_array) {
+ for (let i = 0; i < component_array.length; i += 1) {
+ if (component_array[i]._component === "##builtin/screenslot") {
+ return true
+ }
+
+ if (component_array[i]._children) findSlot(component_array[i])
+ }
+
+ return false
+ }
+
+ if (
+ componentToAdd.startsWith("##") &&
+ findSlot(state.pages[state.currentPageName].props._children)
+ ) {
+ return state
+ }
+
+ const component = getComponentDefinition(state, componentToAdd)
+
+ const instanceId = get(backendUiStore).selectedDatabase._id
+ const instanceName = getNewComponentName(component, state)
+
+ const newComponent = createProps(component, {
+ ...presetProps,
+ _instanceId: instanceId,
+ _instanceName: instanceName,
+ })
+
+ const currentComponent =
+ state.components[state.currentComponentInfo._component]
+
+ const targetParent = currentComponent.children
+ ? state.currentComponentInfo
+ : getParent(
+ state.currentPreviewItem.props,
+ state.currentComponentInfo
+ )
+
+ // Don't continue if there's no parent
+ if (!targetParent) {
+ return state
+ }
+
+ targetParent._children = targetParent._children.concat(
+ newComponent.props
+ )
+
+ store.actions.preview.saveSelected()
+
+ state.currentView = "component"
+ state.currentComponentInfo = newComponent.props
+ analytics.captureEvent("Added Component", {
+ name: newComponent.props._component,
+ })
+ return state
+ })
+ },
+ copy: (component, cut = false) => {
+ store.update(state => {
+ state.componentToPaste = cloneDeep(component)
+ state.componentToPaste.isCut = cut
+ if (cut) {
+ const parent = getParent(
+ state.currentPreviewItem.props,
+ component._id
+ )
+ parent._children = parent._children.filter(
+ c => c._id !== component._id
+ )
+ store.actions.components.select(parent)
+ }
+
+ return state
+ })
+ },
+ paste: (targetComponent, mode) => {
+ store.update(state => {
+ if (!state.componentToPaste) return state
+
+ const componentToPaste = cloneDeep(state.componentToPaste)
+ // retain the same ids as things may be referencing this component
+ if (componentToPaste.isCut) {
+ // in case we paste a second time
+ state.componentToPaste.isCut = false
+ } else {
+ generateNewIdsForComponent(componentToPaste, state)
+ }
+ delete componentToPaste.isCut
+
+ if (mode === "inside") {
+ targetComponent._children.push(componentToPaste)
+ return state
+ }
+
+ const parent = getParent(
+ state.currentPreviewItem.props,
+ targetComponent
+ )
+
+ const targetIndex = parent._children.indexOf(targetComponent)
+ const index = mode === "above" ? targetIndex : targetIndex + 1
+ parent._children.splice(index, 0, cloneDeep(componentToPaste))
+
+ store.actions.screens.regenerateCssForCurrentScreen()
+ store.actions.preview.saveSelected()
+ store.actions.components.select(componentToPaste)
+
+ return state
+ })
+ },
+ updateStyle: (type, name, value) => {
+ store.update(state => {
+ if (!state.currentComponentInfo._styles) {
+ state.currentComponentInfo._styles = {}
+ }
+ state.currentComponentInfo._styles[type][name] = value
+
+ store.actions.screens.regenerateCssForCurrentScreen()
+
+ // save without messing with the store
+ store.actions.preview.saveSelected()
+ return state
+ })
+ },
+ updateProp: (name, value) => {
+ store.update(state => {
+ let current_component = state.currentComponentInfo
+ current_component[name] = value
+
+ state.currentComponentInfo = current_component
+ store.actions.preview.saveSelected()
+ return state
+ })
+ },
+ findRoute: component => {
+ // Gets all the components to needed to construct a path.
+ const tempStore = get(store)
+ let pathComponents = []
+ let parent = component
+ let root = false
+ while (!root) {
+ parent = getParent(tempStore.currentPreviewItem.props, parent)
+ if (!parent) {
+ root = true
+ } else {
+ pathComponents.push(parent)
+ }
+ }
+
+ // Remove root entry since it's the screen or page layout.
+ // Reverse array since we need the correct order of the IDs
+ const reversedComponents = pathComponents.reverse().slice(1)
+
+ // Add component
+ const allComponents = [...reversedComponents, component]
+
+ // Map IDs
+ const IdList = allComponents.map(c => c._id)
+
+ // Construct ID Path:
+ return IdList.join("/")
+ },
+ links: {
+ save: async (url, title) => {
+ let savePromise
+ store.update(state => {
+ // Try to extract a nav component from the master screen
+ const nav = findChildComponentType(
+ state.pages.main,
+ "@budibase/standard-components/Navigation"
+ )
+ if (nav) {
+ let newLink
+
+ // Clone an existing link if one exists
+ if (nav._children && nav._children.length) {
+ // Clone existing link style
+ newLink = cloneDeep(nav._children[0])
+
+ // Manipulate IDs to ensure uniqueness
+ generateNewIdsForComponent(newLink, state, false)
+
+ // Set our new props
+ newLink._instanceName = `${title} Link`
+ newLink.url = url
+ newLink.text = title
+ } else {
+ // Otherwise create vanilla new link
+ const component = getComponentDefinition(
+ state,
+ "@budibase/standard-components/link"
+ )
+ const instanceId = get(backendUiStore).selectedDatabase._id
+ newLink = createProps(component, {
+ url,
+ text: title,
+ _instanceName: `${title} Link`,
+ _instanceId: instanceId,
+ }).props
+ }
+
+ // Save page and regenerate all CSS because otherwise weird things happen
+ nav._children = [...nav._children, newLink]
+ state.currentPageName = "main"
+ store.actions.screens.regenerateCss(state.pages.main)
+ for (let screen of state.pages.main._screens) {
+ store.actions.screens.regenerateCss(screen)
+ }
+ savePromise = store.actions.pages.save()
+ }
+ return state
+ })
+ await savePromise
+ },
+ },
+ },
+ }
+
+ return store
+}
diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js
deleted file mode 100644
index 0aa3d69ecd..0000000000
--- a/packages/builder/src/builderStore/store/index.js
+++ /dev/null
@@ -1,607 +0,0 @@
-import { cloneDeep } from "lodash/fp"
-import getNewComponentName from "../getNewComponentName"
-import { backendUiStore } from "builderStore"
-import { writable, get } from "svelte/store"
-import api from "../api"
-import { DEFAULT_PAGES_OBJECT } from "../../constants"
-import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
-import {
- createProps,
- makePropsSafe,
- getBuiltin,
-} from "components/userInterface/pagesParsing/createProps"
-import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
-import { buildCodeForScreens } from "../buildCodeForScreens"
-import { generate_screen_css } from "../generate_css"
-import { insertCodeMetadata } from "../insertCodeMetadata"
-import analytics from "analytics"
-import { uuid } from "../uuid"
-import {
- selectComponent as _selectComponent,
- getParent,
- walkProps,
- savePage as _savePage,
- saveCurrentPreviewItem as _saveCurrentPreviewItem,
- saveScreenApi as _saveScreenApi,
- regenerateCssForCurrentScreen,
- regenerateCssForScreen,
- generateNewIdsForComponent,
- getComponentDefinition,
- findChildComponentType,
-} from "../storeUtils"
-
-export const getStore = () => {
- const initial = {
- apps: [],
- name: "",
- description: "",
- pages: DEFAULT_PAGES_OBJECT,
- mainUi: {},
- unauthenticatedUi: {},
- components: [],
- currentPreviewItem: null,
- currentComponentInfo: null,
- currentFrontEndType: "none",
- currentPageName: "",
- currentComponentProps: null,
- errors: [],
- hasAppPackage: false,
- libraries: null,
- appId: "",
- }
-
- const store = writable(initial)
-
- store.setPackage = setPackage(store, initial)
-
- store.saveScreen = saveScreen(store)
- store.setCurrentScreen = setCurrentScreen(store)
- store.deleteScreens = deleteScreens(store)
- store.setCurrentPage = setCurrentPage(store)
- store.createLink = createLink(store)
- store.createScreen = createScreen(store)
- store.addStylesheet = addStylesheet(store)
- store.removeStylesheet = removeStylesheet(store)
- store.savePage = savePage(store)
- store.addChildComponent = addChildComponent(store)
- store.selectComponent = selectComponent(store)
- store.setComponentProp = setComponentProp(store)
- store.setPageOrScreenProp = setPageOrScreenProp(store)
- store.setComponentStyle = setComponentStyle(store)
- store.setComponentCode = setComponentCode(store)
- store.setScreenType = setScreenType(store)
- store.getPathToComponent = getPathToComponent(store)
- store.addTemplatedComponent = addTemplatedComponent(store)
- store.setMetadataProp = setMetadataProp(store)
- store.editPageOrScreen = editPageOrScreen(store)
- store.pasteComponent = pasteComponent(store)
- store.storeComponentForCopy = storeComponentForCopy(store)
- return store
-}
-
-export default getStore
-
-const setPackage = (store, initial) => async pkg => {
- const [main_screens, unauth_screens] = await Promise.all([
- api
- .get(`/_builder/api/${pkg.application._id}/pages/main/screens`)
- .then(r => r.json()),
- api
- .get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`)
- .then(r => r.json()),
- ])
-
- pkg.pages = {
- main: {
- ...pkg.pages.main,
- _screens: Object.values(main_screens),
- },
- unauthenticated: {
- ...pkg.pages.unauthenticated,
- _screens: Object.values(unauth_screens),
- },
- }
-
- // if the app has just been created
- // we need to build the CSS and save
- if (pkg.justCreated) {
- const generateInitialPageCss = async name => {
- const page = pkg.pages[name]
- regenerateCssForScreen(page)
- for (let screen of page._screens) {
- regenerateCssForScreen(screen)
- }
-
- await api.post(`/_builder/api/${pkg.application._id}/pages/${name}`, {
- page: {
- componentLibraries: pkg.application.componentLibraries,
- ...page,
- },
- screens: page._screens,
- })
- }
- generateInitialPageCss("main")
- generateInitialPageCss("unauthenticated")
- pkg.justCreated = false
- }
-
- initial.libraries = pkg.application.componentLibraries
- initial.components = await fetchComponentLibDefinitions(pkg.application._id)
- initial.name = pkg.application.name
- initial.description = pkg.application.description
- initial.appId = pkg.application._id
- initial.pages = pkg.pages
- initial.hasAppPackage = true
- initial.screens = [
- ...Object.values(main_screens),
- ...Object.values(unauth_screens),
- ]
- initial.builtins = [getBuiltin("##builtin/screenslot")]
- initial.appInstance = pkg.application.instance
- initial.appId = pkg.application._id
- store.set(initial)
- await backendUiStore.actions.database.select(initial.appInstance)
- return initial
-}
-
-const saveScreen = store => screen => {
- store.update(state => {
- return _saveScreen(store, state, screen)
- })
-}
-
-const _saveScreen = async (store, s, screen) => {
- const pageName = s.currentPageName || "main"
- const currentPageScreens = s.pages[pageName]._screens
-
- await api
- .post(`/_builder/api/${s.appId}/pages/${pageName}/screen`, screen)
- .then(() => {
- if (currentPageScreens.includes(screen)) return
-
- const screens = [...currentPageScreens, screen]
-
- store.update(innerState => {
- innerState.pages[pageName]._screens = screens
- innerState.screens = screens
- innerState.currentPreviewItem = screen
- const safeProps = makePropsSafe(
- innerState.components[screen.props._component],
- screen.props
- )
- innerState.currentComponentInfo = safeProps
- screen.props = safeProps
-
- _savePage(innerState)
- return innerState
- })
- })
-
- return s
-}
-
-const createScreen = store => async screen => {
- let savePromise
- store.update(state => {
- state.currentPreviewItem = screen
- state.currentComponentInfo = screen.props
- state.currentFrontEndType = "screen"
- regenerateCssForCurrentScreen(state)
- savePromise = _saveScreen(store, state, screen)
- return state
- })
- await savePromise
-}
-
-const createLink = store => async (url, title) => {
- let savePromise
- store.update(state => {
- // Try to extract a nav component from the master screen
- const nav = findChildComponentType(
- state.pages.main,
- "@budibase/standard-components/Navigation"
- )
- if (nav) {
- let newLink
-
- // Clone an existing link if one exists
- if (nav._children && nav._children.length) {
- // Clone existing link style
- newLink = cloneDeep(nav._children[0])
-
- // Manipulate IDs to ensure uniqueness
- generateNewIdsForComponent(newLink, state, false)
-
- // Set our new props
- newLink._instanceName = `${title} Link`
- newLink.url = url
- newLink.text = title
- } else {
- // Otherwise create vanilla new link
- const component = getComponentDefinition(
- state,
- "@budibase/standard-components/link"
- )
- const instanceId = get(backendUiStore).selectedDatabase._id
- newLink = createProps(component, {
- url,
- text: title,
- _instanceName: `${title} Link`,
- _instanceId: instanceId,
- }).props
- }
-
- // Save page and regenerate all CSS because otherwise weird things happen
- nav._children = [...nav._children, newLink]
- state.currentPageName = "main"
- regenerateCssForScreen(state.pages.main)
- for (let screen of state.pages.main._screens) {
- regenerateCssForScreen(screen)
- }
- savePromise = _savePage(state)
- }
- return state
- })
- await savePromise
-}
-
-const setCurrentScreen = store => screenName => {
- store.update(s => {
- const screen = getExactComponent(s.screens, screenName, true)
- s.currentPreviewItem = screen
- s.currentFrontEndType = "screen"
- s.currentView = "detail"
- regenerateCssForCurrentScreen(s)
- const safeProps = makePropsSafe(
- s.components[screen.props._component],
- screen.props
- )
- screen.props = safeProps
- s.currentComponentInfo = safeProps
- setCurrentPageFunctions(s)
- return s
- })
-}
-
-const deleteScreens = store => (screens, pageName = null) => {
- if (!(screens instanceof Array)) {
- screens = [screens]
- }
- store.update(state => {
- if (pageName == null) {
- pageName = state.pages.main.name
- }
- for (let screen of screens) {
- state.screens = state.screens.filter(c => c.name !== screen.name)
- // Remove screen from current page as well
- state.pages[pageName]._screens = state.pages[pageName]._screens.filter(
- scr => scr.name !== screen.name
- )
- api.delete(`/_builder/api/pages/${pageName}/screens/${screen.name}`)
- }
- return state
- })
-}
-
-const savePage = store => async page => {
- store.update(state => {
- if (state.currentFrontEndType !== "page" || !state.currentPageName) {
- return state
- }
-
- state.pages[state.currentPageName] = page
- _savePage(state)
- return state
- })
-}
-
-const addStylesheet = store => stylesheet => {
- store.update(s => {
- s.pages.stylesheets.push(stylesheet)
- _savePage(s)
- return s
- })
-}
-
-const removeStylesheet = store => stylesheet => {
- store.update(state => {
- state.pages.stylesheets = state.pages.stylesheets.filter(
- s => s !== stylesheet
- )
- _savePage(state)
- return state
- })
-}
-
-const setCurrentPage = store => pageName => {
- store.update(state => {
- const current_screens = state.pages[pageName]._screens
-
- const currentPage = state.pages[pageName]
-
- state.currentFrontEndType = "page"
- state.currentView = "detail"
- state.currentPageName = pageName
- state.screens = Array.isArray(current_screens)
- ? current_screens
- : Object.values(current_screens)
- const safeProps = makePropsSafe(
- state.components[currentPage.props._component],
- currentPage.props
- )
- state.currentComponentInfo = safeProps
- currentPage.props = safeProps
- state.currentPreviewItem = state.pages[pageName]
- regenerateCssForCurrentScreen(state)
-
- for (let screen of state.screens) {
- screen._css = generate_screen_css([screen.props])
- }
-
- setCurrentPageFunctions(state)
- return state
- })
-}
-
-/**
- * @param {string} componentToAdd - name of the component to add to the application
- * @param {string} presetName - name of the component preset if defined
- */
-const addChildComponent = store => (componentToAdd, presetProps = {}) => {
- store.update(state => {
- function findSlot(component_array) {
- for (let i = 0; i < component_array.length; i += 1) {
- if (component_array[i]._component === "##builtin/screenslot") {
- return true
- }
-
- if (component_array[i]._children) findSlot(component_array[i])
- }
-
- return false
- }
-
- if (
- componentToAdd.startsWith("##") &&
- findSlot(state.pages[state.currentPageName].props._children)
- ) {
- return state
- }
-
- const component = getComponentDefinition(state, componentToAdd)
-
- const instanceId = get(backendUiStore).selectedDatabase._id
- const instanceName = getNewComponentName(component, state)
-
- const newComponent = createProps(
- component,
- {
- ...presetProps,
- _instanceId: instanceId,
- _instanceName: instanceName,
- },
- state
- )
-
- const currentComponent =
- state.components[state.currentComponentInfo._component]
-
- const targetParent = currentComponent.children
- ? state.currentComponentInfo
- : getParent(state.currentPreviewItem.props, state.currentComponentInfo)
-
- // Don't continue if there's no parent
- if (!targetParent) {
- return state
- }
-
- targetParent._children = targetParent._children.concat(newComponent.props)
-
- state.currentFrontEndType === "page"
- ? _savePage(state)
- : _saveScreenApi(state.currentPreviewItem, state)
-
- state.currentView = "component"
- state.currentComponentInfo = newComponent.props
- analytics.captureEvent("Added Component", {
- name: newComponent.props._component,
- })
- return state
- })
-}
-
-/**
- * @param {string} props - props to add, as child of current component
- */
-
-const addTemplatedComponent = store => props => {
- store.update(state => {
- walkProps(props, p => {
- p._id = uuid()
- })
- state.currentComponentInfo._children = state.currentComponentInfo._children.concat(
- props
- )
- regenerateCssForCurrentScreen(state)
-
- setCurrentPageFunctions(state)
- _saveCurrentPreviewItem(state)
-
- return state
- })
-}
-
-const selectComponent = store => component => {
- store.update(state => {
- return _selectComponent(state, component)
- })
-}
-
-const setComponentProp = store => (name, value) => {
- store.update(state => {
- let current_component = state.currentComponentInfo
- current_component[name] = value
-
- state.currentComponentInfo = current_component
- _saveCurrentPreviewItem(state)
- return state
- })
-}
-
-const setPageOrScreenProp = store => (name, value) => {
- store.update(state => {
- if (name === "_instanceName" && state.currentFrontEndType === "screen") {
- state.currentPreviewItem.props[name] = value
- } else {
- state.currentPreviewItem[name] = value
- }
- _saveCurrentPreviewItem(state)
- return state
- })
-}
-
-const setComponentStyle = store => (type, name, value) => {
- store.update(state => {
- if (!state.currentComponentInfo._styles) {
- state.currentComponentInfo._styles = {}
- }
- state.currentComponentInfo._styles[type][name] = value
-
- regenerateCssForCurrentScreen(state)
-
- // save without messing with the store
- _saveCurrentPreviewItem(state)
- return state
- })
-}
-
-const setComponentCode = store => code => {
- store.update(state => {
- state.currentComponentInfo._code = code
-
- setCurrentPageFunctions(state)
- // save without messing with the store
- _saveScreenApi(state.currentPreviewItem, state)
-
- return state
- })
-}
-
-const setCurrentPageFunctions = s => {
- s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName])
- insertCodeMetadata(s.currentPreviewItem.props)
-}
-
-const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens])
-
-const setScreenType = store => type => {
- store.update(state => {
- state.currentFrontEndType = type
-
- const pageOrScreen =
- type === "page"
- ? state.pages[state.currentPageName]
- : state.pages[state.currentPageName]._screens[0]
-
- state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
- state.currentPreviewItem = pageOrScreen
- state.currentView = "detail"
- return state
- })
-}
-
-const editPageOrScreen = store => (key, value, setOnComponent = false) => {
- store.update(state => {
- setOnComponent
- ? (state.currentPreviewItem.props[key] = value)
- : (state.currentPreviewItem[key] = value)
- _saveCurrentPreviewItem(state)
-
- return state
- })
-}
-
-const getPathToComponent = store => component => {
- // Gets all the components to needed to construct a path.
- const tempStore = get(store)
- let pathComponents = []
- let parent = component
- let root = false
- while (!root) {
- parent = getParent(tempStore.currentPreviewItem.props, parent)
- if (!parent) {
- root = true
- } else {
- pathComponents.push(parent)
- }
- }
-
- // Remove root entry since it's the screen or page layout.
- // Reverse array since we need the correct order of the IDs
- const reversedComponents = pathComponents.reverse().slice(1)
-
- // Add component
- const allComponents = [...reversedComponents, component]
-
- // Map IDs
- const IdList = allComponents.map(c => c._id)
-
- // Construct ID Path:
- const path = IdList.join("/")
-
- return path
-}
-
-const setMetadataProp = store => (name, prop) => {
- store.update(s => {
- s.currentPreviewItem[name] = prop
- return s
- })
-}
-
-const storeComponentForCopy = store => (component, cut = false) => {
- store.update(s => {
- const copiedComponent = cloneDeep(component)
- s.componentToPaste = copiedComponent
- s.componentToPaste.isCut = cut
- if (cut) {
- const parent = getParent(s.currentPreviewItem.props, component._id)
- parent._children = parent._children.filter(c => c._id !== component._id)
- selectComponent(s, parent)
- }
-
- return s
- })
-}
-
-const pasteComponent = store => (targetComponent, mode) => {
- store.update(s => {
- if (!s.componentToPaste) return s
-
- const componentToPaste = cloneDeep(s.componentToPaste)
- // retain the same ids as things may be referencing this component
- if (componentToPaste.isCut) {
- // in case we paste a second time
- s.componentToPaste.isCut = false
- } else {
- generateNewIdsForComponent(componentToPaste, s)
- }
- delete componentToPaste.isCut
-
- if (mode === "inside") {
- targetComponent._children.push(componentToPaste)
- return s
- }
-
- const parent = getParent(s.currentPreviewItem.props, targetComponent)
-
- const targetIndex = parent._children.indexOf(targetComponent)
- const index = mode === "above" ? targetIndex : targetIndex + 1
- parent._children.splice(index, 0, cloneDeep(componentToPaste))
- regenerateCssForCurrentScreen(s)
- _saveCurrentPreviewItem(s)
- selectComponent(s, componentToPaste)
-
- return s
- })
-}
diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js
index 2c486a9bdb..9c9d1ef940 100644
--- a/packages/builder/src/builderStore/storeUtils.js
+++ b/packages/builder/src/builderStore/storeUtils.js
@@ -1,21 +1,7 @@
-import {
- makePropsSafe,
- getBuiltin,
-} from "components/userInterface/pagesParsing/createProps"
-import api from "./api"
-import { generate_screen_css } from "./generate_css"
+import { getBuiltin } from "components/userInterface/pagesParsing/createProps"
import { uuid } from "./uuid"
import getNewComponentName from "./getNewComponentName"
-export const selectComponent = (state, component) => {
- const componentDef = component._component.startsWith("##")
- ? component
- : state.components[component._component]
- state.currentComponentInfo = makePropsSafe(componentDef, component)
- state.currentView = "component"
- return state
-}
-
export const getParent = (rootProps, child) => {
let parent
walkProps(rootProps, (p, breakWalk) => {
@@ -30,41 +16,6 @@ export const getParent = (rootProps, child) => {
return parent
}
-export const saveCurrentPreviewItem = s =>
- s.currentFrontEndType === "page"
- ? savePage(s)
- : saveScreenApi(s.currentPreviewItem, s)
-
-export const savePage = async s => {
- const pageName = s.currentPageName || "main"
- const page = s.pages[pageName]
- await api.post(`/_builder/api/${s.appId}/pages/${pageName}`, {
- page: { componentLibraries: s.pages.componentLibraries, ...page },
- uiFunctions: s.currentPageFunctions,
- screens: page._screens,
- })
-}
-
-export const saveScreenApi = (screen, s) => {
- api
- .post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
- .then(() => savePage(s))
-}
-
-export const renameCurrentScreen = (newname, state) => {
- const oldname = state.currentPreviewItem.props._instanceName
- state.currentPreviewItem.props._instanceName = newname
-
- api.patch(
- `/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`,
- {
- oldname,
- newname,
- }
- )
- return state
-}
-
export const walkProps = (props, action, cancelToken = null) => {
cancelToken = cancelToken || { cancelled: false }
action(props, () => {
@@ -79,21 +30,14 @@ export const walkProps = (props, action, cancelToken = null) => {
}
}
-export const regenerateCssForScreen = screen => {
- screen._css = generate_screen_css([screen.props])
-}
-
-export const regenerateCssForCurrentScreen = state => {
- if (state.currentPreviewItem) {
- regenerateCssForScreen(state.currentPreviewItem)
- }
- return state
-}
-
-export const generateNewIdsForComponent = (c, state, changeName = true) =>
- walkProps(c, p => {
- p._id = uuid()
- if (changeName) p._instanceName = getNewComponentName(p._component, state)
+export const generateNewIdsForComponent = (
+ component,
+ state,
+ changeName = true
+) =>
+ walkProps(component, prop => {
+ prop._id = uuid()
+ if (changeName) prop._instanceName = getNewComponentName(prop, state)
})
export const getComponentDefinition = (state, name) =>
diff --git a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte
index 146f881b2b..0fe19f9bc0 100644
--- a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte
+++ b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte
@@ -55,7 +55,7 @@
// Record the table that created this screen so we can link it later
screen.autoTableId = table._id
try {
- await store.createScreen(screen)
+ await store.actions.screens.create(screen)
} catch (_) {
// TODO: this is temporary
// a cypress test is failing, because I added the
@@ -70,7 +70,7 @@
const listPage = screens.find(screen =>
screen.props._instanceName.endsWith("List")
)
- await store.createLink(listPage.route, table.name)
+ await store.actions.components.links.save(listPage.route, table.name)
// Navigate to new table
$goto(`./table/${table._id}`)
diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte
index c794999ac7..1fdbe35151 100644
--- a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte
+++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte
@@ -1,5 +1,5 @@
@@ -239,7 +235,7 @@
diff --git a/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte b/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte
index 62eb49145d..999014f0cd 100644
--- a/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte
+++ b/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte
@@ -4,7 +4,7 @@
import { getComponentDefinition } from "builderStore/storeUtils"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last } from "lodash/fp"
- import { getParent, saveCurrentPreviewItem } from "builderStore/storeUtils"
+ import { getParent } from "builderStore/storeUtils"
import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@@ -25,50 +25,50 @@
}
const selectComponent = component => {
- store.selectComponent(component)
- const path = store.getPathToComponent(component)
+ store.actions.components.select(component)
+ const path = store.actions.components.findRoute(component)
$goto(`./:page/:screen/${path}`)
}
const moveUpComponent = () => {
- store.update(s => {
- const parent = getParent(s.currentPreviewItem.props, component)
+ store.update(state => {
+ const parent = getParent(state.currentPreviewItem.props, component)
if (parent) {
const currentIndex = parent._children.indexOf(component)
- if (currentIndex === 0) return s
+ if (currentIndex === 0) return state
const newChildren = parent._children.filter(c => c !== component)
newChildren.splice(currentIndex - 1, 0, component)
parent._children = newChildren
}
- s.currentComponentInfo = component
- saveCurrentPreviewItem(s)
+ state.currentComponentInfo = component
+ store.actions.preview.saveSelected()
- return s
+ return state
})
}
const moveDownComponent = () => {
- store.update(s => {
- const parent = getParent(s.currentPreviewItem.props, component)
+ store.update(state => {
+ const parent = getParent(state.currentPreviewItem.props, component)
if (parent) {
const currentIndex = parent._children.indexOf(component)
- if (currentIndex === parent._children.length - 1) return s
+ if (currentIndex === parent._children.length - 1) return state
const newChildren = parent._children.filter(c => c !== component)
newChildren.splice(currentIndex + 1, 0, component)
parent._children = newChildren
}
- s.currentComponentInfo = component
- saveCurrentPreviewItem(s)
+ state.currentComponentInfo = component
+ store.actions.preview.saveSelected()
- return s
+ return state
})
}
- const copyComponent = () => {
+ const duplicateComponent = () => {
storeComponentForCopy(false)
pasteComponent("below")
}
@@ -82,19 +82,19 @@
selectComponent(parent)
}
- saveCurrentPreviewItem(state)
+ store.actions.preview.saveSelected()
return state
})
}
const storeComponentForCopy = (cut = false) => {
// lives in store - also used by drag drop
- store.storeComponentForCopy(component, cut)
+ store.actions.components.copy(component, cut)
}
const pasteComponent = mode => {
// lives in store - also used by drag drop
- store.pasteComponent(component, mode)
+ store.actions.components.paste(component, mode)
}
@@ -117,7 +117,7 @@
+ on:click={duplicateComponent} />
{
+ if (name === "_instanceName" && state.currentFrontEndType === "screen") {
+ state.currentPreviewItem.props[name] = value
+ } else {
+ state.currentPreviewItem[name] = value
+ }
+ store.actions.preview.saveSelected()
+ return state
+ })
+ }
+
function getProps(obj, keys) {
return keys.map((key, i) => [key, obj[key], obj.props._id + i])
}
@@ -81,8 +93,8 @@
{componentDefinition}
{panelDefinition}
displayNameField={displayName}
- onChange={store.setComponentProp}
- onScreenPropChange={store.setPageOrScreenProp}
+ onChange={store.actions.components.updateProp}
+ onScreenPropChange={setPageOrScreenProp}
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
{/if}
diff --git a/packages/builder/src/components/userInterface/ComponentSelectionList.svelte b/packages/builder/src/components/userInterface/ComponentSelectionList.svelte
index f43faf033d..5dc9c05ea5 100644
--- a/packages/builder/src/components/userInterface/ComponentSelectionList.svelte
+++ b/packages/builder/src/components/userInterface/ComponentSelectionList.svelte
@@ -25,8 +25,8 @@
}
const onComponentChosen = component => {
- store.addChildComponent(component._component, component.presetProps)
- const path = store.getPathToComponent($store.currentComponentInfo)
+ store.actions.components.create(component._component, component.presetProps)
+ const path = store.actions.components.findRoute($store.currentComponentInfo)
$goto(`./:page/:screen/${path}`)
close()
}
diff --git a/packages/builder/src/components/userInterface/ComponentsHierarchy.svelte b/packages/builder/src/components/userInterface/ComponentsHierarchy.svelte
index 48fe72f8d6..a9140e2978 100644
--- a/packages/builder/src/components/userInterface/ComponentsHierarchy.svelte
+++ b/packages/builder/src/components/userInterface/ComponentsHierarchy.svelte
@@ -32,7 +32,7 @@
])
const changeScreen = screen => {
- store.setCurrentScreen(screen.props._instanceName)
+ store.actions.screens.select(screen.props._instanceName)
$goto(`./:page/${screen.props._instanceName}`)
}
diff --git a/packages/builder/src/components/userInterface/ComponentsHierarchyChildren.svelte b/packages/builder/src/components/userInterface/ComponentsHierarchyChildren.svelte
index d854c9b906..7bedf5e6a8 100644
--- a/packages/builder/src/components/userInterface/ComponentsHierarchyChildren.svelte
+++ b/packages/builder/src/components/userInterface/ComponentsHierarchyChildren.svelte
@@ -40,10 +40,10 @@
const selectComponent = component => {
// Set current component
- store.selectComponent(component)
+ store.actions.components.select(component)
// Get ID path
- const path = store.getPathToComponent(component)
+ const path = store.actions.components.findRoute(component)
// Go to correct URL
$goto(`./:page/:screen/${path}`)
@@ -96,8 +96,8 @@
const drop = () => {
if ($dragDropStore.targetComponent !== $dragDropStore.componentToDrop) {
- store.storeComponentForCopy($dragDropStore.componentToDrop, true)
- store.pasteComponent(
+ store.actions.components.copy($dragDropStore.componentToDrop, true)
+ store.actions.components.paste(
$dragDropStore.targetComponent,
$dragDropStore.dropPosition
)
diff --git a/packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte b/packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte
index 5904221596..4e865b4514 100644
--- a/packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte
+++ b/packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte
@@ -1,5 +1,5 @@
- {#if $store.currentFrontEndType === 'page' || $store.screens.length}
+ {#if $store.currentFrontEndType === 'page' || $allScreens.length}
diff --git a/packages/builder/tests/buildCodeForScreen.spec.js b/packages/builder/tests/buildCodeForScreen.spec.js
deleted file mode 100644
index 87f4c02fa3..0000000000
--- a/packages/builder/tests/buildCodeForScreen.spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { buildCodeForScreens } from "../src/builderStore/buildCodeForScreens"
-
-describe("buildCodeForScreen", () => {
- it("should package _code into runnable function, for simple screen props", () => {
- const screen = {
- props: {
- _id: "1234",
- _code: "render('render argument');",
- },
- }
-
- let renderArg
- const render = arg => {
- renderArg = arg
- }
- const uiFunctions = getFunctions(screen)
-
- const targetfunction = uiFunctions[screen.props._id]
- expect(targetfunction).toBeDefined()
-
- targetfunction(render)
-
- expect(renderArg).toBe("render argument")
- })
-
- it("should package _code into runnable function, for _children ", () => {
- const screen = {
- props: {
- _id: "parent",
- _code: "render('parent argument');",
- _children: [
- {
- _id: "child1",
- _code: "render('child 1 argument');",
- },
- {
- _id: "child2",
- _code: "render('child 2 argument');",
- },
- ],
- },
- }
-
- let renderArg
- const render = arg => {
- renderArg = arg
- }
- const uiFunctions = getFunctions(screen)
-
- const targetfunction = uiFunctions["child2"]
- expect(targetfunction).toBeDefined()
-
- targetfunction(render)
-
- expect(renderArg).toBe("child 2 argument")
- })
-})
-
-const getFunctions = screen => {
- const code = buildCodeForScreens([screen])
- const func = new Function(`return ${code}`)()
- return func
-}
diff --git a/packages/server/scripts/exportAppTemplate.js b/packages/server/scripts/exportAppTemplate.js
index 72ad797183..8268b802d4 100755
--- a/packages/server/scripts/exportAppTemplate.js
+++ b/packages/server/scripts/exportAppTemplate.js
@@ -23,6 +23,12 @@ yargs
},
async args => {
console.log("Exporting app..")
+ if (args.name == null || args.appId == null) {
+ console.error(
+ "Unable to export without a name and app ID being specified, check help for more info."
+ )
+ return
+ }
const exportPath = await exportTemplateFromApp({
templateName: args.name,
appId: args.appId,
diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js
index a8596e38ac..2185293352 100644
--- a/packages/server/src/api/controllers/application.js
+++ b/packages/server/src/api/controllers/application.js
@@ -1,21 +1,29 @@
const CouchDB = require("../../db")
-const { getPackageForBuilder, buildPage } = require("../../utilities/builder")
+const compileStaticAssetsForPage = require("../../utilities/builder/compileStaticAssetsForPage")
const env = require("../../environment")
-const { copy, existsSync, readFile, writeFile } = require("fs-extra")
+const { existsSync } = require("fs-extra")
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
-const sqrl = require("squirrelly")
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
const fs = require("fs-extra")
const { join, resolve } = require("../../utilities/centralPath")
-const { promisify } = require("util")
-const chmodr = require("chmodr")
const packageJson = require("../../../package.json")
const { createLinkView } = require("../../db/linkedRows")
const { downloadTemplate } = require("../../utilities/templates")
-const { generateAppID, DocumentTypes, SEPARATOR } = require("../../db/utils")
+const {
+ generateAppID,
+ DocumentTypes,
+ SEPARATOR,
+ getPageParams,
+ generatePageID,
+ generateScreenID,
+} = require("../../db/utils")
const {
downloadExtractComponentLibraries,
} = require("../../utilities/createAppPackage")
+const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages")
+const { HOME_SCREEN } = require("../../constants/screens")
+const { cloneDeep } = require("lodash/fp")
+
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
async function createInstance(template) {
@@ -60,13 +68,31 @@ exports.fetch = async function(ctx) {
exports.fetchAppPackage = async function(ctx) {
const db = new CouchDB(ctx.params.appId)
const application = await db.get(ctx.params.appId)
- ctx.body = await getPackageForBuilder(ctx.config, application)
+
+ let pages = await db.allDocs(
+ getPageParams(null, {
+ include_docs: true,
+ })
+ )
+ pages = pages.rows.map(row => row.doc)
+
+ const mainPage = pages.find(page => page.name === PageTypes.MAIN)
+ const unauthPage = pages.find(page => page.name === PageTypes.UNAUTHENTICATED)
+ ctx.body = {
+ application,
+ pages: {
+ main: mainPage,
+ unauthenticated: unauthPage,
+ },
+ }
+
await setBuilderToken(ctx, ctx.params.appId, application.version)
}
exports.create = async function(ctx) {
const instance = await createInstance(ctx.request.body.template)
const appId = instance._id
+ const version = packageJson.version
const newApplication = {
_id: appId,
type: "app",
@@ -84,6 +110,7 @@ exports.create = async function(ctx) {
await downloadExtractComponentLibraries(newAppFolder)
}
+ await setBuilderToken(ctx, appId, version)
ctx.status = 200
ctx.body = newApplication
ctx.message = `Application ${ctx.request.body.name} created successfully`
@@ -120,99 +147,38 @@ exports.delete = async function(ctx) {
}
const createEmptyAppPackage = async (ctx, app) => {
- const templateFolder = resolve(
- __dirname,
- "..",
- "..",
- "utilities",
- "appDirectoryTemplate"
- )
-
const appsFolder = budibaseAppsDir()
const newAppFolder = resolve(appsFolder, app._id)
+ const db = new CouchDB(app._id)
+
if (existsSync(newAppFolder)) {
ctx.throw(400, "App folder already exists for this application")
}
- await fs.ensureDir(join(newAppFolder, "pages", "main", "screens"), 0o777)
- await fs.ensureDir(
- join(newAppFolder, "pages", "unauthenticated", "screens"),
- 0o777
- )
+ fs.mkdirpSync(newAppFolder)
- await copy(templateFolder, newAppFolder)
+ const mainPage = cloneDeep(MAIN)
+ mainPage._id = generatePageID()
+ mainPage.title = app.name
- // this line allows full permission on copied files
- // we have an unknown problem without this, whereby the
- // files get weird permissions and cant be written to :(
- const chmodrPromise = promisify(chmodr)
- await chmodrPromise(newAppFolder, 0o777)
+ const unauthPage = cloneDeep(UNAUTHENTICATED)
+ unauthPage._id = generatePageID()
+ unauthPage.title = app.name
+ unauthPage.props._children[0].title = `Log in to ${app.name}`
- await updateJsonFile(join(appsFolder, app._id, "package.json"), {
- name: npmFriendlyAppName(app.name),
+ const homeScreen = cloneDeep(HOME_SCREEN)
+ homeScreen._id = generateScreenID(mainPage._id)
+ await db.bulkDocs([mainPage, unauthPage, homeScreen])
+
+ await compileStaticAssetsForPage(app._id, "main", {
+ page: mainPage,
+ screens: [homeScreen],
})
-
- // if this app is being created from a template,
- // copy the frontend page definition files from
- // the template directory.
- if (app.template) {
- const templatePageDefinitions = join(
- appsFolder,
- "templates",
- app.template.key,
- "pages"
- )
- await copy(templatePageDefinitions, join(appsFolder, app._id, "pages"))
- }
-
- const mainJson = await updateJsonFile(
- join(appsFolder, app._id, "pages", "main", "page.json"),
- app
- )
-
- await buildPage(ctx.config, app._id, "main", {
- page: mainJson,
- screens: await loadScreens(newAppFolder, "main"),
- })
-
- const unauthenticatedJson = await updateJsonFile(
- join(appsFolder, app._id, "pages", "unauthenticated", "page.json"),
- app
- )
-
- await buildPage(ctx.config, app._id, "unauthenticated", {
- page: unauthenticatedJson,
- screens: await loadScreens(newAppFolder, "unauthenticated"),
+ await compileStaticAssetsForPage(app._id, "unauthenticated", {
+ page: unauthPage,
+ screens: [],
})
return newAppFolder
}
-
-const loadScreens = async (appFolder, page) => {
- const screensFolder = join(appFolder, "pages", page, "screens")
-
- const screenFiles = (await fs.readdir(screensFolder)).filter(s =>
- s.endsWith(".json")
- )
-
- let screens = []
- for (let file of screenFiles) {
- screens.push(await fs.readJSON(join(screensFolder, file)))
- }
- return screens
-}
-
-const updateJsonFile = async (filePath, app) => {
- const json = await readFile(filePath, "utf8")
- const newJson = sqrl.Render(json, app)
- await writeFile(filePath, newJson, "utf8")
- return JSON.parse(newJson)
-}
-
-const npmFriendlyAppName = name =>
- name
- .replace(/_/g, "")
- .replace(/./g, "")
- .replace(/ /g, "")
- .toLowerCase()
diff --git a/packages/server/src/api/controllers/deploy/aws.js b/packages/server/src/api/controllers/deploy/aws.js
index 3e19812a00..e6cd514cac 100644
--- a/packages/server/src/api/controllers/deploy/aws.js
+++ b/packages/server/src/api/controllers/deploy/aws.js
@@ -42,6 +42,13 @@ exports.isInvalidationComplete = async function(
return resp.Invalidation.Status === "Completed"
}
+/**
+ * Finalises the deployment, updating the quota for the user API key
+ * The verification process returns the levels to update to.
+ * Calls the "deployment-success" lambda.
+ * @param {object} quota The usage quota levels returned from the verifyDeploy
+ * @returns {Promise