diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 8352c5bd6a..1a5270e33b 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -1,115 +1,115 @@ - / *********************************************** - // This example commands.js shows you how to - // create various custom commands and overwrite - // existing commands. - // - // For more comprehensive examples of custom - // commands please read more here: - // https://on.cypress.io/custom-commands - // *********************************************** - // - // - // -- This is a parent command -- - // Cypress.Commands.add("login", (email, password) => { ... }) - // - // - // -- This is a child command -- - // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) - // - // - // -- This is a dual command -- - // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) - // - // - // -- This will overwrite an existing command -- - // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) - Cypress.Commands.add("createApp", name => { - cy.contains("Create New Web App").click() +Cypress.Commands.add("createApp", name => { + cy.contains("Create New Web App").click() - cy.get("body") - .then($body => { - if ($body.find("input[name=apiKey]").length) { - // input was found, do something else here - cy.get("input[name=apiKey]") - .type(name) - .should("have.value", name) - cy.contains("Next").click() - } - }) - .then(() => { - cy.get("input[name=applicationName]") - .type(name) - .should("have.value", name) + cy.get("body") + .then($body => { + if ($body.find("input[name=apiKey]").length) { + // input was found, do something else here + cy.get("input[name=apiKey]") + .type(name) + .should("have.value", name) + cy.contains("Next").click() + } + }) + .then(() => { + cy.get("input[name=applicationName]") + .type(name) + .should("have.value", name) - cy.contains("Next").click() + cy.contains("Next").click() - cy.get("input[name=email]") - .click() - .type("test@test.com") - cy.get("input[name=password]") - .click() - .type("test") - cy.contains("Submit").click() - cy.get("[data-cy=new-table]", { - timeout: 20000, - }).should("be.visible") - }) - }) + cy.get("input[name=email]") + .click() + .type("test@test.com") + cy.get("input[name=password]") + .click() + .type("test") + cy.contains("Submit").click() + cy.get("[data-cy=new-table]", { + timeout: 20000, + }).should("be.visible") + }) +}) - Cypress.Commands.add("createTestTableWithData", () => { - cy.createTable("dog") - cy.addColumn("dog", "name", "Text") - cy.addColumn("dog", "age", "Number") - }) +Cypress.Commands.add("createTestTableWithData", () => { + cy.createTable("dog") + cy.addColumn("dog", "name", "Text") + cy.addColumn("dog", "age", "Number") +}) - Cypress.Commands.add("createTable", tableName => { - // Enter table name - cy.get("[data-cy=new-table]").click() - cy.get(".modal").within(() => { - cy.get("input") - .first() - .type(tableName) - cy.get(".buttons") - .contains("Create") - .click() - }) - cy.contains(tableName).should("be.visible") - }) +Cypress.Commands.add("createTable", tableName => { + // Enter table name + cy.get("[data-cy=new-table]").click() + cy.get(".modal").within(() => { + cy.get("input") + .first() + .type(tableName) + cy.get(".buttons") + .contains("Create") + .click() + }) + cy.contains(tableName).should("be.visible") +}) - Cypress.Commands.add("addColumn", (tableName, columnName, type) => { - // Select Table - cy.contains(".nav-item", tableName).click() - cy.contains("Create New Column").click() +Cypress.Commands.add("addColumn", (tableName, columnName, type) => { + // Select Table + cy.contains(".nav-item", tableName).click() + cy.contains("Create New Column").click() - // Configure column - cy.get(".actions").within(() => { - cy.get("input") - .first() - .type(columnName) - // Unset table display column - cy.contains("display column").click() - cy.get("select").select(type) - cy.contains("Save").click() - }) - }) + // Configure column + cy.get(".actions").within(() => { + cy.get("input") + .first() + .type(columnName) + // Unset table display column + cy.contains("display column").click() + cy.get("select").select(type) + cy.contains("Save").click() + }) +}) - Cypress.Commands.add("addRow", values => { - cy.contains("Create New Row").click() +Cypress.Commands.add("addRow", values => { + cy.contains("Create New Row").click() - cy.get(".modal").within(() => { - for (let i = 0; i < values.length; i++) { - cy.get("input") - .eq(i) - .type(values[i]) - } + cy.get(".modal").within(() => { + for (let i = 0; i < values.length; i++) { + cy.get("input") + .eq(i) + .type(values[i]) + } - // Save - cy.get(".buttons") - .contains("Create") - .click() - }) - }) + // Save + cy.get(".buttons") + .contains("Create") + .click() + }) +}) Cypress.Commands.add("createUser", (email, password, role) => { // Create User @@ -120,17 +120,17 @@ Cypress.Commands.add("createUser", (email, password, role) => { cy.get(".modal").within(() => { cy.get("input") .first() - .type(password) + .type(email) cy.get("input") .eq(1) - .type(email) + .type(password) cy.get("select") .first() .select(role) // Save cy.get(".buttons") - .contains("Create Row") + .contains("Create User") .click() }) }) diff --git a/packages/builder/package.json b/packages/builder/package.json index a4908de3da..542c35205e 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -63,7 +63,7 @@ } }, "dependencies": { - "@budibase/bbui": "^1.51.0", + "@budibase/bbui": "^1.52.2", "@budibase/client": "^0.3.8", "@budibase/colorpicker": "^1.0.1", "@budibase/svelte-ag-grid": "^0.0.16", @@ -107,6 +107,7 @@ "rollup-plugin-alias": "^1.5.2", "rollup-plugin-copy": "^3.0.0", "rollup-plugin-css-only": "^2.1.0", + "rollup-plugin-html": "^0.2.1", "rollup-plugin-livereload": "^1.0.0", "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-globals": "^1.4.0", diff --git a/packages/builder/rollup.config.js b/packages/builder/rollup.config.js index 4afb8084bd..2d5ec52f52 100644 --- a/packages/builder/rollup.config.js +++ b/packages/builder/rollup.config.js @@ -11,6 +11,7 @@ import copy from "rollup-plugin-copy" import css from "rollup-plugin-css-only" import replace from "rollup-plugin-replace" import json from "@rollup/plugin-json" +import html from "rollup-plugin-html" import path from "path" @@ -75,10 +76,6 @@ export default { { src: "src/index.html", dest: outputpath }, { src: "src/favicon.png", dest: outputpath }, { src: "assets", dest: outputpath }, - { - src: "node_modules/@budibase/client/dist/budibase-client.esm.mjs", - dest: outputpath, - }, { src: "node_modules/@budibase/bbui/dist/bbui.css", dest: outputpath, @@ -147,5 +144,6 @@ export default { // instead of npm run dev), minify production && terser(), json(), + html(), ], } diff --git a/packages/builder/src/builderStore/getNewComponentName.js b/packages/builder/src/builderStore/getNewComponentName.js index a69bec21ad..98ca05b827 100644 --- a/packages/builder/src/builderStore/getNewComponentName.js +++ b/packages/builder/src/builderStore/getNewComponentName.js @@ -2,6 +2,8 @@ import { walkProps } from "./storeUtils" import { get_capitalised_name } from "../helpers" import { get } from "svelte/store" import { allScreens } from "builderStore" +import { FrontendTypes } from "../constants" +import { currentAsset } from "." export default function(component, state) { const capitalised = get_capitalised_name( @@ -19,14 +21,16 @@ export default function(component, state) { }) } - // check page first - findMatches(state.pages[state.currentPageName].props) + // check layouts first + for (let layout of state.layouts) { + findMatches(layout.props) + } // if viewing screen, check current screen for duplicate - if (state.currentFrontEndType === "screen") { - findMatches(state.currentPreviewItem.props) + if (state.currentFrontEndType === FrontendTypes.SCREEN) { + findMatches(get(currentAsset).props) } else { - // viewing master page - need to find against all screens + // viewing a layout - need to find against all screens for (let screen of get(allScreens)) { findMatches(screen.props) } diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index ae77889404..503d9b08a7 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -4,37 +4,74 @@ import { getAutomationStore } from "./store/automation/" import { getThemeStore } from "./store/theme" import { derived } from "svelte/store" import analytics from "analytics" +import { LAYOUT_NAMES } from "../constants" +import { makePropsSafe } from "components/userInterface/assetParsing/createProps" export const store = getFrontendStore() export const backendUiStore = getBackendUiStore() export const automationStore = getAutomationStore() export const themeStore = getThemeStore() +export const currentAsset = derived(store, $store => { + const layout = $store.layouts + ? $store.layouts.find(layout => layout._id === $store.currentAssetId) + : null + + if (layout) return layout + + const screen = $store.screens + ? $store.screens.find(screen => screen._id === $store.currentAssetId) + : null + + if (screen) return screen + + return null +}) + +export const selectedComponent = derived( + [store, currentAsset], + ([$store, $currentAsset]) => { + if (!$currentAsset || !$store.selectedComponentId) return null + + function traverse(node, callback) { + if (node._id === $store.selectedComponentId) return callback(node) + + if (node._children) { + node._children.forEach(child => traverse(child, callback)) + } + + if (node.props) { + traverse(node.props, callback) + } + } + + let component + traverse($currentAsset, found => { + const componentIdentifier = found._component ?? found.props._component + const componentDef = componentIdentifier.startsWith("##") + ? found + : $store.components[componentIdentifier] + + component = makePropsSafe(componentDef, found) + }) + + return component + } +) + +export const currentAssetName = derived(store, () => { + return currentAsset.name +}) + +// leave this as before for consistency 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 + return $store.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 selectedPage = derived(store, $store => { - if (!$store.pages) return null - - return $store.pages[$store.currentPageName || "main"] +export const mainLayout = derived(store, $store => { + return $store.layouts?.find( + layout => layout.props?._id === LAYOUT_NAMES.MASTER.PRIVATE + ) }) export const initialise = async () => { diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 50c4347583..7be4f68f25 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -4,34 +4,36 @@ import { createProps, getBuiltin, makePropsSafe, -} from "components/userInterface/pagesParsing/createProps" -import { allScreens, backendUiStore, selectedPage } from "builderStore" -import { generate_screen_css } from "../generate_css" +} from "components/userInterface/assetParsing/createProps" +import { + allScreens, + backendUiStore, + currentAsset, + mainLayout, + selectedComponent, +} from "builderStore" import { fetchComponentLibDefinitions } from "../loadComponentLibraries" import api from "../api" -import { DEFAULT_PAGES_OBJECT } from "../../constants" +import { FrontendTypes } from "../../constants" import getNewComponentName from "../getNewComponentName" import analytics from "analytics" import { findChildComponentType, generateNewIdsForComponent, getComponentDefinition, - getParent, + findParent, } from "../storeUtils" const INITIAL_FRONTEND_STATE = { apps: [], name: "", description: "", - pages: DEFAULT_PAGES_OBJECT, - mainUi: {}, - unauthenticatedUi: {}, + layouts: [], + screens: [], components: [], - currentPreviewItem: null, - currentComponentInfo: null, currentFrontEndType: "none", - currentPageName: "", - currentComponentProps: null, + currentAssetId: "", + selectedComponentId: "", errors: [], hasAppPackage: false, libraries: null, @@ -43,52 +45,13 @@ export const getFrontendStore = () => { const store = writable({ ...INITIAL_FRONTEND_STATE }) store.actions = { - // TODO: REFACTOR initialise: async pkg => { + const { layouts, screens, application } = pkg + store.update(state => { - state.appId = pkg.application._id + state.appId = 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) @@ -99,7 +62,8 @@ export const getFrontendStore = () => { name: pkg.application.name, description: pkg.application.description, appId: pkg.application._id, - pages: pkg.pages, + layouts, + screens, hasAppPackage: true, builtins: [getBuiltin("##builtin/screenslot")], appInstance: pkg.application.instance, @@ -107,20 +71,6 @@ export const getFrontendStore = () => { await backendUiStore.actions.database.select(pkg.application.instance) }, - selectPageOrScreen: type => { - store.update(state => { - state.currentFrontEndType = type - - const page = get(selectedPage) - - const pageOrScreen = type === "page" ? page : page._screens[0] - - state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null - state.currentPreviewItem = pageOrScreen - state.currentView = "detail" - return state - }) - }, routing: { fetch: async () => { const response = await api.get("/api/routing") @@ -133,167 +83,166 @@ export const getFrontendStore = () => { }, }, screens: { - select: screenId => { + select: async screenId => { + let promise store.update(state => { const screen = get(allScreens).find(screen => screen._id === screenId) - state.currentPreviewItem = screen - state.currentFrontEndType = "screen" + state.currentFrontEndType = FrontendTypes.SCREEN + state.currentAssetId = screenId state.currentView = "detail" - store.actions.screens.regenerateCssForCurrentScreen() - const safeProps = makePropsSafe( - state.components[screen.props._component], - screen.props - ) - screen.props = safeProps - state.currentComponentInfo = safeProps + promise = store.actions.screens.regenerateCss(screen) + state.selectedComponentId = screen.props._id return state }) + await promise }, create: async screen => { - let savePromise + screen = await store.actions.screens.save(screen) 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) + state.currentAssetId = screen._id + state.selectedComponentId = screen.props._id + state.currentFrontEndType = FrontendTypes.SCREEN return state }) - - await savePromise + return screen }, save: async screen => { - const page = get(selectedPage) - const currentPageScreens = page._screens - const creatingNewScreen = screen._id === undefined + const response = await api.post(`/api/screens`, screen) + screen = await response.json() - let savePromise - const response = await api.post(`/api/screens/${page._id}`, screen) - const json = await response.json() - screen._rev = json.rev - screen._id = json.id - const foundScreen = page._screens.findIndex(el => el._id === screen._id) - if (foundScreen !== -1) { - page._screens.splice(foundScreen, 1) - } - page._screens.push(screen) - - // TODO: should carry out all server updates to screen in a single call store.update(state => { - page._screens = currentPageScreens + const foundScreen = state.screens.findIndex( + el => el._id === screen._id + ) + if (foundScreen !== -1) { + state.screens.splice(foundScreen, 1) + } + state.screens.push(screen) if (creatingNewScreen) { - state.currentPreviewItem = screen const safeProps = makePropsSafe( state.components[screen.props._component], screen.props ) - state.currentComponentInfo = safeProps + state.selectedComponentId = safeProps._id screen.props = safeProps } - savePromise = store.actions.pages.save() - return state }) - if (savePromise) await savePromise + return screen }, - regenerateCss: screen => { - screen._css = generate_screen_css([screen.props]) + regenerateCss: async asset => { + const response = await api.post("/api/css/generate", asset) + asset._css = (await response.json())?.css }, - regenerateCssForCurrentScreen: () => { - const { currentPreviewItem } = get(store) - if (currentPreviewItem) { - store.actions.screens.regenerateCss(currentPreviewItem) + regenerateCssForCurrentScreen: async () => { + const asset = get(currentAsset) + if (asset) { + await store.actions.screens.regenerateCss(asset) } }, delete: async screens => { - let deletePromise - const screensToDelete = Array.isArray(screens) ? screens : [screens] + const screenDeletePromises = [] store.update(state => { - const currentPage = get(selectedPage) - for (let screenToDelete of screensToDelete) { - // Remove screen from current page as well - // TODO: Should be done server side - currentPage._screens = currentPage._screens.filter( - scr => scr._id !== screenToDelete._id + state.screens = state.screens.filter( + screen => screen._id !== screenToDelete._id ) - - deletePromise = api.delete( - `/api/screens/${screenToDelete._id}/${screenToDelete._rev}` + screenDeletePromises.push( + api.delete( + `/api/screens/${screenToDelete._id}/${screenToDelete._rev}` + ) ) } return state }) - await deletePromise + await Promise.all(screenDeletePromises) }, }, preview: { saveSelected: async () => { const state = get(store) - if (state.currentFrontEndType !== "page") { - await store.actions.screens.save(state.currentPreviewItem) + const selectedAsset = get(currentAsset) + + if (state.currentFrontEndType !== FrontendTypes.LAYOUT) { + await store.actions.screens.save(selectedAsset) + } else { + await store.actions.layouts.save(selectedAsset) } - await store.actions.pages.save() }, }, - pages: { - select: pageName => { + layouts: { + select: async layoutId => { store.update(state => { - const currentPage = state.pages[pageName] + const layout = store.actions.layouts.find(layoutId) - state.currentFrontEndType = "page" + state.currentFrontEndType = FrontendTypes.LAYOUT 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]) - } + state.currentAssetId = layout._id + state.selectedComponentId = layout.props._id return state }) - }, - save: async page => { - const storeContents = get(store) - const pageName = storeContents.currentPageName || "main" - const pageToSave = page || storeContents.pages[pageName] + let cssPromises = [] + cssPromises.push(store.actions.screens.regenerateCssForCurrentScreen()) - // 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, - }) + for (let screen of get(allScreens)) { + cssPromises.push(store.actions.screens.regenerateCss(screen)) + } + await Promise.all(cssPromises) + }, + save: async layout => { + const layoutToSave = cloneDeep(layout) + delete layoutToSave._css + + const response = await api.post(`/api/layouts`, layoutToSave) const json = await response.json() - if (!json.ok) throw new Error("Error updating page") + store.update(state => { + const layoutIdx = state.layouts.findIndex( + stateLayout => stateLayout._id === json._id + ) + + if (layoutIdx >= 0) { + // update existing layout + state.layouts.splice(layoutIdx, 1, json) + } else { + // save new layout + state.layouts.push(json) + } + + state.currentAssetId = json._id + state.selectedComponentId = json.props._id + return state + }) + }, + find: layoutId => { + if (!layoutId) { + return get(mainLayout) + } + const storeContents = get(store) + return storeContents.layouts.find(layout => layout._id === layoutId) + }, + delete: async layoutToDelete => { + const response = await api.delete( + `/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}` + ) + + if (response.status !== 200) { + const json = await response.json() + throw new Error(json.message) + } store.update(state => { - state.pages[pageName]._rev = json.rev + state.layouts = state.layouts.filter( + layout => layout._id !== layoutToDelete._id + ) return state }) }, @@ -301,17 +250,19 @@ export const getFrontendStore = () => { components: { select: component => { store.update(state => { - const componentDef = component._component.startsWith("##") - ? component - : state.components[component._component] - state.currentComponentInfo = makePropsSafe(componentDef, component) + state.selectedComponentId = component._id state.currentView = "component" return state }) }, create: (componentToAdd, presetProps) => { + const selectedAsset = get(currentAsset) + store.update(state => { function findSlot(component_array) { + if (!component_array) { + return false + } for (let component of component_array) { if (component._component === "##builtin/screenslot") { return true @@ -324,7 +275,7 @@ export const getFrontendStore = () => { if ( componentToAdd.startsWith("##") && - findSlot(state.pages[state.currentPageName].props._children) + findSlot(selectedAsset?.props._children) ) { return state } @@ -340,29 +291,34 @@ export const getFrontendStore = () => { _instanceName: instanceName, }) - const currentComponent = - state.components[state.currentComponentInfo._component] + const selected = get(selectedComponent) - const targetParent = currentComponent.children - ? state.currentComponentInfo - : getParent( - state.currentPreviewItem.props, - state.currentComponentInfo - ) + const currentComponentDefinition = + state.components[selected._component] - // Don't continue if there's no parent - if (!targetParent) { - return state + const allowsChildren = currentComponentDefinition.children + + // Determine where to put the new component. + let targetParent + if (allowsChildren) { + // Child of the selected component + targetParent = selected + } else { + // Sibling of selected component + targetParent = findParent(selectedAsset.props, selected) } - targetParent._children = targetParent._children.concat( - newComponent.props - ) + // Don't continue if there's no parent + if (!targetParent) return state + + // Push the new component + targetParent._children.push(newComponent.props) store.actions.preview.saveSelected() state.currentView = "component" - state.currentComponentInfo = newComponent.props + state.selectedComponentId = newComponent.props._id + analytics.captureEvent("Added Component", { name: newComponent.props._component, }) @@ -370,14 +326,12 @@ export const getFrontendStore = () => { }) }, copy: (component, cut = false) => { + const selectedAsset = get(currentAsset) store.update(state => { state.componentToPaste = cloneDeep(component) state.componentToPaste.isCut = cut if (cut) { - const parent = getParent( - state.currentPreviewItem.props, - component._id - ) + const parent = findParent(selectedAsset.props, component._id) parent._children = parent._children.filter( child => child._id !== component._id ) @@ -387,7 +341,9 @@ export const getFrontendStore = () => { return state }) }, - paste: (targetComponent, mode) => { + paste: async (targetComponent, mode) => { + const selectedAsset = get(currentAsset) + let promises = [] store.update(state => { if (!state.componentToPaste) return state @@ -406,54 +362,56 @@ export const getFrontendStore = () => { return state } - const parent = getParent( - state.currentPreviewItem.props, - targetComponent - ) + const parent = findParent(selectedAsset.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() + promises.push(store.actions.screens.regenerateCssForCurrentScreen()) + promises.push(store.actions.preview.saveSelected()) store.actions.components.select(componentToPaste) return state }) + await Promise.all(promises) }, - updateStyle: (type, name, value) => { - store.update(state => { - if (!state.currentComponentInfo._styles) { - state.currentComponentInfo._styles = {} - } - state.currentComponentInfo._styles[type][name] = value + updateStyle: async (type, name, value) => { + let promises = [] + const selected = get(selectedComponent) - store.actions.screens.regenerateCssForCurrentScreen() + store.update(state => { + if (!selected._styles) { + selected._styles = {} + } + selected._styles[type][name] = value + + promises.push(store.actions.screens.regenerateCssForCurrentScreen()) // save without messing with the store - store.actions.preview.saveSelected() + promises.push(store.actions.preview.saveSelected()) return state }) + await Promise.all(promises) }, updateProp: (name, value) => { store.update(state => { - let current_component = state.currentComponentInfo + let current_component = get(selectedComponent) current_component[name] = value - state.currentComponentInfo = current_component + state.selectedComponentId = current_component._id store.actions.preview.saveSelected() return state }) }, findRoute: component => { // Gets all the components to needed to construct a path. - const tempStore = get(store) + const selectedAsset = get(currentAsset) let pathComponents = [] let parent = component let root = false while (!root) { - parent = getParent(tempStore.currentPreviewItem.props, parent) + parent = findParent(selectedAsset.props, parent) if (!parent) { root = true } else { @@ -461,7 +419,7 @@ export const getFrontendStore = () => { } } - // Remove root entry since it's the screen or page layout. + // Remove root entry since it's the screen or layout. // Reverse array since we need the correct order of the IDs const reversedComponents = pathComponents.reverse().slice(1) @@ -476,11 +434,12 @@ export const getFrontendStore = () => { }, links: { save: async (url, title) => { - let savePromise + let promises = [] + const layout = get(mainLayout) store.update(state => { - // Try to extract a nav component from the master screen + // Try to extract a nav component from the master layout const nav = findChildComponentType( - state.pages.main, + layout, "@budibase/standard-components/navigation" ) if (nav) { @@ -513,18 +472,18 @@ export const getFrontendStore = () => { }).props } - // Save page and regenerate all CSS because otherwise weird things happen + // Save layout 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) + state.currentAssetId = layout._id + promises.push(store.actions.screens.regenerateCss(layout)) + for (let screen of get(allScreens)) { + promises.push(store.actions.screens.regenerateCss(screen)) } - savePromise = store.actions.pages.save() + promises.push(store.actions.layouts.save(layout)) } return state }) - await savePromise + await Promise.all(promises) }, }, }, diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js index 84de7e15ea..bd03fc7cdc 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js @@ -14,7 +14,6 @@ export class Component extends BaseStructure { active: {}, selected: {}, }, - _code: "", type: "", _instanceName: "", _children: [], diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js b/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js index 76df96ae0c..00bd43ec2c 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js @@ -4,6 +4,7 @@ export class Screen extends BaseStructure { constructor() { super(true) this._json = { + layoutId: "layout_private_master", props: { _id: "", _component: "", @@ -18,7 +19,7 @@ export class Screen extends BaseStructure { }, routing: { route: "", - roleId: "", + roleId: "BASIC", }, name: "screen-id", } diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js index 9c9d1ef940..4ee2dd7ccc 100644 --- a/packages/builder/src/builderStore/storeUtils.js +++ b/packages/builder/src/builderStore/storeUtils.js @@ -1,15 +1,21 @@ -import { getBuiltin } from "components/userInterface/pagesParsing/createProps" +import { getBuiltin } from "components/userInterface/assetParsing/createProps" import { uuid } from "./uuid" import getNewComponentName from "./getNewComponentName" -export const getParent = (rootProps, child) => { +/** + * Find the parent component of the passed in child. + * @param {Object} rootProps - props to search for the parent in + * @param {String|Object} child - id of the child or the child itself to find the parent of + */ +export const findParent = (rootProps, child) => { let parent - walkProps(rootProps, (p, breakWalk) => { + walkProps(rootProps, (props, breakWalk) => { if ( - p._children && - (p._children.includes(child) || p._children.some(c => c._id === child)) + props._children && + (props._children.includes(child) || + props._children.some(c => c._id === child)) ) { - parent = p + parent = props breakWalk() } }) diff --git a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte index f064ff923c..7c26e236a6 100644 --- a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte +++ b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte @@ -51,14 +51,13 @@ const screens = screenTemplates($store, [table]) .filter(template => defaultScreens.includes(template.id)) .map(template => template.create()) - store.actions.pages.select("main") for (let screen of screens) { // Record the table that created this screen so we can link it later screen.autoTableId = table._id await store.actions.screens.create(screen) } - // Create autolink to newly created list page + // Create autolink to newly created list screen const listScreen = screens.find(screen => screen.props._instanceName.endsWith("List") ) diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index 15d4eb4dc6..b88397b6c4 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -153,9 +153,8 @@ const pkg = await applicationPkg.json() if (applicationPkg.ok) { backendUiStore.actions.reset() - pkg.justCreated = true await store.actions.initialise(pkg) - automationStore.actions.fetch() + await automationStore.actions.fetch() } else { throw new Error(pkg) } diff --git a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte index 535b947f5f..ccb0153e45 100644 --- a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte @@ -1,10 +1,13 @@