From b2da94c49035e18ed6c0f2795b96d64501fc2d8c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 20 Nov 2020 17:47:13 +0000 Subject: [PATCH 01/56] initial work towards page refactor. --- packages/server/src/api/controllers/screen.js | 18 +------------ packages/server/src/api/routes/screen.js | 3 +-- packages/server/src/db/utils.js | 26 +++++++++---------- 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/packages/server/src/api/controllers/screen.js b/packages/server/src/api/controllers/screen.js index 694d171fff..acf6df22f0 100644 --- a/packages/server/src/api/controllers/screen.js +++ b/packages/server/src/api/controllers/screen.js @@ -20,29 +20,13 @@ exports.fetch = async ctx => { ) } -exports.find = async ctx => { - const appId = ctx.user.appId - const db = new CouchDB(appId) - - const screens = await db.allDocs( - getScreenParams(ctx.params.pageId, { - include_docs: true, - }) - ) - - ctx.body = await new AccessController(appId).checkScreensAccess( - screens, - ctx.user.accessLevel._id - ) -} - exports.save = async ctx => { const appId = ctx.user.appId const db = new CouchDB(appId) const screen = ctx.request.body if (!screen._id) { - screen._id = generateScreenID(ctx.params.pageId) + screen._id = generateScreenID() } delete screen._css const response = await db.put(screen) diff --git a/packages/server/src/api/routes/screen.js b/packages/server/src/api/routes/screen.js index ce49f66043..1b21a58452 100644 --- a/packages/server/src/api/routes/screen.js +++ b/packages/server/src/api/routes/screen.js @@ -30,9 +30,8 @@ function generateSaveValidation() { router .get("/api/screens", authorized(BUILDER), controller.fetch) - .get("/api/screens/:pageId", authorized(BUILDER), controller.find) .post( - "/api/screens/:pageId", + "/api/screens", authorized(BUILDER), generateSaveValidation(), controller.save diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 4edb27a416..c3b5866a2a 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -179,14 +179,6 @@ exports.getAccessLevelParams = (accessLevelId = null, otherProps = {}) => { return getDocParams(DocumentTypes.ACCESS_LEVEL, accessLevelId, otherProps) } -/** - * Generates a new webhook ID. - * @returns {string} The new webhook ID which the webhook doc can be stored under. - */ -exports.generateWebhookID = () => { - return `${DocumentTypes.WEBHOOK}${SEPARATOR}${newid()}` -} - /** * Generates a new page ID. * @returns {string} The new page ID which the page doc can be stored under. @@ -206,15 +198,23 @@ exports.getPageParams = (pageId = null, otherProps = {}) => { * Generates a new screen ID. * @returns {string} The new screen ID which the screen doc can be stored under. */ -exports.generateScreenID = pageId => { - return `${DocumentTypes.SCREEN}${SEPARATOR}${pageId}${SEPARATOR}${newid()}` +exports.generateScreenID = () => { + return `${DocumentTypes.SCREEN}${SEPARATOR}${newid()}` } /** - * Gets parameters for retrieving screens for a particular page, this is a utility function for the getDocParams function. + * Gets parameters for retrieving screens, this is a utility function for the getDocParams function. */ -exports.getScreenParams = (pageId = null, otherProps = {}) => { - return getDocParams(DocumentTypes.SCREEN, pageId, otherProps) +exports.getScreenParams = (screenId = null, otherProps = {}) => { + return getDocParams(DocumentTypes.SCREEN, screenId, otherProps) +} + +/** + * Generates a new webhook ID. + * @returns {string} The new webhook ID which the webhook doc can be stored under. + */ +exports.generateWebhookID = () => { + return `${DocumentTypes.WEBHOOK}${SEPARATOR}${newid()}` } /** From 8ae24a4b30ac0fda19814b500053c8f000751779 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 23 Nov 2020 14:07:18 +0000 Subject: [PATCH 02/56] Storing some work in commit, currently broken, further work needed - application needs cleaned up. --- .../server/src/api/controllers/application.js | 28 ++--- .../server/src/api/controllers/deploy/aws.js | 28 +++-- packages/server/src/api/controllers/layout.js | 15 +++ packages/server/src/api/controllers/page.js | 19 ---- .../src/api/controllers/static/index.js | 4 - .../static/templates/BudibaseApp.svelte | 3 +- packages/server/src/api/routes/index.js | 4 +- .../src/api/routes/{pages.js => layout.js} | 4 +- .../src/api/routes/tests/automation.spec.js | 1 - .../src/constants/{pages.js => layouts.js} | 8 +- packages/server/src/db/utils.js | 16 +-- .../utilities/builder/compileStaticAssets.js | 83 ++++++++++++++ .../builder/compileStaticAssetsForPage.js | 102 ------------------ 13 files changed, 137 insertions(+), 178 deletions(-) create mode 100644 packages/server/src/api/controllers/layout.js delete mode 100644 packages/server/src/api/controllers/page.js rename packages/server/src/api/routes/{pages.js => layout.js} (62%) rename packages/server/src/constants/{pages.js => layouts.js} (97%) create mode 100644 packages/server/src/utilities/builder/compileStaticAssets.js delete mode 100644 packages/server/src/utilities/builder/compileStaticAssetsForPage.js diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 512299c5c8..095cb3b831 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -1,5 +1,5 @@ const CouchDB = require("../../db") -const compileStaticAssetsForPage = require("../../utilities/builder/compileStaticAssetsForPage") +const compileStaticAssets = require("../../utilities/builder/compileStaticAssets") const env = require("../../environment") const { existsSync } = require("fs-extra") const { budibaseAppsDir } = require("../../utilities/budibaseDir") @@ -14,16 +14,16 @@ const { generateAppID, DocumentTypes, SEPARATOR, - getPageParams, + getLayoutParams, getScreenParams, - generatePageID, + generateLayoutID, generateScreenID, } = require("../../db/utils") const { BUILTIN_LEVEL_IDS } = require("../../utilities/security/accessLevels") const { downloadExtractComponentLibraries, } = require("../../utilities/createAppPackage") -const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages") +const { MAIN, UNAUTHENTICATED, LayoutTypes } = require("../../constants/layouts") const { HOME_SCREEN } = require("../../constants/screens") const { cloneDeep } = require("lodash/fp") @@ -32,14 +32,14 @@ const APP_PREFIX = DocumentTypes.APP + SEPARATOR // utility function, need to do away with this async function getMainAndUnauthPage(db) { let pages = await db.allDocs( - getPageParams(null, { + getLayoutParams(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) + const mainPage = pages.find(page => page.name === LayoutTypes.MAIN) + const unauthPage = pages.find(page => page.name === LayoutTypes.UNAUTHENTICATED) return { mainPage, unauthPage } } @@ -194,11 +194,11 @@ const createEmptyAppPackage = async (ctx, app) => { fs.mkdirpSync(newAppFolder) const mainPage = cloneDeep(MAIN) - mainPage._id = generatePageID() + mainPage._id = generateLayoutID() mainPage.title = app.name const unauthPage = cloneDeep(UNAUTHENTICATED) - unauthPage._id = generatePageID() + unauthPage._id = generateLayoutID() unauthPage.title = app.name unauthPage.props._children[0].title = `Log in to ${app.name}` @@ -206,14 +206,6 @@ const createEmptyAppPackage = async (ctx, app) => { homeScreen._id = generateScreenID(mainPage._id) await db.bulkDocs([mainPage, unauthPage, homeScreen]) - await compileStaticAssetsForPage(app._id, "main", { - page: mainPage, - screens: [homeScreen], - }) - await compileStaticAssetsForPage(app._id, "unauthenticated", { - page: unauthPage, - screens: [], - }) - + await compileStaticAssets(app._id) return newAppFolder } diff --git a/packages/server/src/api/controllers/deploy/aws.js b/packages/server/src/api/controllers/deploy/aws.js index d478c4efde..5c788a9dcc 100644 --- a/packages/server/src/api/controllers/deploy/aws.js +++ b/packages/server/src/api/controllers/deploy/aws.js @@ -136,25 +136,21 @@ exports.uploadAppAssets = async function({ appId, bucket, accountId }) { const appAssetsPath = join(budibaseAppsDir(), appId, "public") - const appPages = fs.readdirSync(appAssetsPath) - let uploads = [] - for (let page of appPages) { - // Upload HTML, CSS and JS for each page of the web app - walkDir(join(appAssetsPath, page), function(filePath) { - const appAssetUpload = prepareUploadForS3({ - file: { - path: filePath, - name: [...filePath.split("/")].pop(), - }, - s3Key: filePath.replace(appAssetsPath, `assets/${appId}`), - s3, - metadata: { accountId }, - }) - uploads.push(appAssetUpload) + // Upload HTML, CSS and JS of the web app + walkDir(appAssetsPath, function(filePath) { + const appAssetUpload = prepareUploadForS3({ + file: { + path: filePath, + name: [...filePath.split("/")].pop(), + }, + s3Key: filePath.replace(appAssetsPath, `assets/${appId}`), + s3, + metadata: { accountId }, }) - } + uploads.push(appAssetUpload) + }) // Upload file attachments const db = new PouchDB(appId) diff --git a/packages/server/src/api/controllers/layout.js b/packages/server/src/api/controllers/layout.js new file mode 100644 index 0000000000..eae7b04d49 --- /dev/null +++ b/packages/server/src/api/controllers/layout.js @@ -0,0 +1,15 @@ +const CouchDB = require("../../db/client") +const { generateLayoutID } = require("../../db/utils") +const compileStaticAssets = require("../../utilities/builder/compileStaticAssets") + +exports.save = async function(ctx) { + const db = new CouchDB(ctx.user.appId) + const appPackage = ctx.request.body + + // remove special doc props which couch will complain about + delete appPackage.layout._css + appPackage.layout._id = appPackage.layout._id || generateLayoutID() + ctx.body = await db.put(appPackage.layout) + await compileStaticAssets(ctx.user.appId) + ctx.status = 200 +} diff --git a/packages/server/src/api/controllers/page.js b/packages/server/src/api/controllers/page.js deleted file mode 100644 index 4f3989ae90..0000000000 --- a/packages/server/src/api/controllers/page.js +++ /dev/null @@ -1,19 +0,0 @@ -const CouchDB = require("../../db/client") -const { generatePageID } = require("../../db/utils") -const compileStaticAssetsForPage = require("../../utilities/builder/compileStaticAssetsForPage") - -exports.save = async function(ctx) { - const db = new CouchDB(ctx.user.appId) - - const appPackage = ctx.request.body - - const page = await db.get(ctx.params.pageId) - await compileStaticAssetsForPage(ctx.user.appId, page.name, ctx.request.body) - - // remove special doc props which couch will complain about - delete appPackage.page._css - delete appPackage.page._screens - appPackage.page._id = appPackage.page._id || generatePageID() - ctx.body = await db.put(appPackage.page) - ctx.status = 200 -} diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index b048bbd9fe..5676afb230 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -142,15 +142,11 @@ exports.performLocalFileProcessing = async function(ctx) { exports.serveApp = async function(ctx) { const App = require("./templates/BudibaseApp.svelte").default - const db = new CouchDB(ctx.params.appId) - const appInfo = await db.get(ctx.params.appId) const { head, html, css } = App.render({ title: appInfo.name, - pageName: - ctx.auth.authenticated === AuthTypes.APP ? "main" : "unauthenticated", production: env.CLOUD, appId: ctx.params.appId, }) diff --git a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte index 7d6ad8141c..7668d7c7eb 100644 --- a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte +++ b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte @@ -3,14 +3,13 @@ export let favicon = "" export let appId - export let pageName = "" export let production export const PRODUCTION_ASSETS_URL = `https://${appId}.app.budi.live` function publicPath(path) { if (production) { - return `${PRODUCTION_ASSETS_URL}/assets/${appId}/${pageName}/${path}` + return `${PRODUCTION_ASSETS_URL}/assets/${appId}/${path}` } return `/assets/${path}` diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index 840f968eb3..43732298e1 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -1,5 +1,5 @@ const authRoutes = require("./auth") -const pageRoutes = require("./pages") +const layoutRoutes = require("./layout") const screenRoutes = require("./screen") const userRoutes = require("./user") const applicationRoutes = require("./application") @@ -19,7 +19,7 @@ const routingRoutes = require("./routing") exports.mainRoutes = [ deployRoutes, - pageRoutes, + layoutRoutes, screenRoutes, userRoutes, applicationRoutes, diff --git a/packages/server/src/api/routes/pages.js b/packages/server/src/api/routes/layout.js similarity index 62% rename from packages/server/src/api/routes/pages.js rename to packages/server/src/api/routes/layout.js index 43fb0e764c..b773dc7dae 100644 --- a/packages/server/src/api/routes/pages.js +++ b/packages/server/src/api/routes/layout.js @@ -1,10 +1,10 @@ const Router = require("@koa/router") const authorized = require("../../middleware/authorized") const { BUILDER } = require("../../utilities/security/permissions") -const controller = require("../controllers/page") +const controller = require("../controllers/layout") const router = Router() -router.post("/api/pages/:pageId", authorized(BUILDER), controller.save) +router.post("/api/layouts/:layoutId", authorized(BUILDER), controller.save) module.exports = router diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 226bbad226..4c9bdb67cb 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -17,7 +17,6 @@ const AUTOMATION_ID = generateAutomationID() const TEST_AUTOMATION = { _id: AUTOMATION_ID, name: "My Automation", - pageId: "123123123", screenId: "kasdkfldsafkl", live: true, uiTree: { diff --git a/packages/server/src/constants/pages.js b/packages/server/src/constants/layouts.js similarity index 97% rename from packages/server/src/constants/pages.js rename to packages/server/src/constants/layouts.js index 5fe74d6123..065bcdf261 100644 --- a/packages/server/src/constants/pages.js +++ b/packages/server/src/constants/layouts.js @@ -1,4 +1,4 @@ -const PageTypes = { +const LayoutTypes = { MAIN: "main", UNAUTHENTICATED: "unauthenticated", } @@ -8,7 +8,7 @@ const MAIN = { title: "{{ name }}", favicon: "./_shared/favicon.png", stylesheets: [], - name: PageTypes.MAIN, + name: LayoutTypes.MAIN, props: { _id: "private-master-root", _component: "@budibase/standard-components/container", @@ -153,7 +153,7 @@ const UNAUTHENTICATED = { title: "{{ name }}", favicon: "./_shared/favicon.png", stylesheets: [], - name: PageTypes.UNAUTHENTICATED, + name: LayoutTypes.UNAUTHENTICATED, props: { _id: "public-master-root", _component: "@budibase/standard-components/container", @@ -218,4 +218,4 @@ const UNAUTHENTICATED = { }, } -module.exports = { MAIN, UNAUTHENTICATED, PageTypes } +module.exports = { MAIN, UNAUTHENTICATED, LayoutTypes } diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index c3b5866a2a..9d9e470ec1 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -13,7 +13,7 @@ const DocumentTypes = { ACCESS_LEVEL: "ac", WEBHOOK: "wh", INSTANCE: "inst", - PAGE: "page", + LAYOUT: "layout", SCREEN: "screen", } @@ -180,18 +180,18 @@ exports.getAccessLevelParams = (accessLevelId = null, otherProps = {}) => { } /** - * Generates a new page ID. - * @returns {string} The new page ID which the page doc can be stored under. + * Generates a new layout ID. + * @returns {string} The new layout ID which the layout doc can be stored under. */ -exports.generatePageID = () => { - return `${DocumentTypes.PAGE}${SEPARATOR}${newid()}` +exports.generateLayoutID = () => { + return `${DocumentTypes.LAYOUT}${SEPARATOR}${newid()}` } /** - * Gets parameters for retrieving pages, this is a utility function for the getDocParams function. + * Gets parameters for retrieving layout, this is a utility function for the getDocParams function. */ -exports.getPageParams = (pageId = null, otherProps = {}) => { - return getDocParams(DocumentTypes.PAGE, pageId, otherProps) +exports.getLayoutParams = (layoutId = null, otherProps = {}) => { + return getDocParams(DocumentTypes.LAYOUT, layoutId, otherProps) } /** diff --git a/packages/server/src/utilities/builder/compileStaticAssets.js b/packages/server/src/utilities/builder/compileStaticAssets.js new file mode 100644 index 0000000000..a64c2f0b98 --- /dev/null +++ b/packages/server/src/utilities/builder/compileStaticAssets.js @@ -0,0 +1,83 @@ +const { ensureDir, constants, copyFile, writeFile } = require("fs-extra") +const { join } = require("../centralPath") +const { budibaseAppsDir } = require("../budibaseDir") +const CouchDB = require("../../db") +const { getScreenParams, getLayoutParams } = require("../../db/utils") + +async function getAppPackage(appId) { + const db = new CouchDB(appId) + let params = { + include_docs: true, + } + let [screens, layouts] = await Promise.all([ + db.allDocs(getScreenParams(null, params)), + db.allDocs(getLayoutParams(null, params)), + ]) + screens = screens.rows.map(row => row.doc) + layouts = layouts.rows.map(row => row.doc) + if (!screens) { + screens = [] + } + if (!layouts) { + layouts = [] + } + return { screens, layouts } +} + +/** + * Compile all the non-db static web assets that are required for the running of + * a budibase application. This includes CSS, the JSON structure of the DOM and + * the client library, a script responsible for reading the JSON structure + * and rendering the application. + * @param {string} appId - id of the application we want to compile static assets for + */ +module.exports = async appId => { + const publicPath = join(budibaseAppsDir(), appId, "public") + const pkg = await getAppPackage(appId) + + await ensureDir(publicPath) + await buildCssBundle(publicPath, pkg) + await copyClientLib(publicPath) +} + +/** + * Reads the _css property of all screens and the screen layouts, and creates a singular CSS + * bundle for the app at /public/bundle.css + * @param {String} publicPath - path to the public assets directory of the budibase application + * @param {Object} pkg - app package information + */ +const buildCssBundle = async (publicPath, pkg) => { + let cssString = "" + + for (let screen of pkg.screens || []) { + if (!screen._css) continue + if (screen._css.trim().length === 0) { + delete screen._css + continue + } + cssString += screen._css + } + + if (pkg.layout._css) cssString += pkg.layout._css + + writeFile(join(publicPath, "bundle.css"), cssString) +} + +/** + * Copy the budibase client library and sourcemap from NPM to /public/. + * The client library is then served as a static asset when the budibase application + * is running in preview or prod + * @param {String} publicPath - path to write the client library to + */ +const copyClientLib = async publicPath => { + const sourcepath = require.resolve("@budibase/client") + const destPath = join(publicPath, "budibase-client.js") + + await copyFile(sourcepath, destPath, constants.COPYFILE_FICLONE) + + await copyFile( + sourcepath + ".map", + destPath + ".map", + constants.COPYFILE_FICLONE + ) +} diff --git a/packages/server/src/utilities/builder/compileStaticAssetsForPage.js b/packages/server/src/utilities/builder/compileStaticAssetsForPage.js deleted file mode 100644 index c91ba24bb3..0000000000 --- a/packages/server/src/utilities/builder/compileStaticAssetsForPage.js +++ /dev/null @@ -1,102 +0,0 @@ -const { ensureDir, constants, copyFile, writeFile } = require("fs-extra") -const { join } = require("../centralPath") -const { budibaseAppsDir } = require("../budibaseDir") - -/** - * Compile all the non-db static web assets that are required for the running of - * a budibase application. This includes CSS, the JSON structure of the DOM and - * the client library, a script responsible for reading the JSON structure - * and rendering the application. - * @param {} appId - id of the application we want to compile static assets for - * @param {*} pageName - name of the page that the assets will be served for - * @param {*} pkg - app package information/metadata - */ -module.exports = async (appId, pageName, pkg) => { - const pagePath = join(budibaseAppsDir(), appId, "public", pageName) - - pkg.screens = pkg.screens || [] - - await ensureDir(pagePath) - - await buildPageCssBundle(pagePath, pkg) - - await buildFrontendAppDefinition(pagePath, pkg) - - await copyClientLib(pagePath) -} - -/** - * Reads the _css property of a page and its screens, and creates a singular CSS - * bundle for the page at /public//bundle.css - * @param {String} publicPagePath - path to the public assets directory of the budibase application - * @param {Object} pkg - app package information - * @param {"main" | "unauthenticated"} pageName - the pagename of the page we are compiling CSS for. - */ -const buildPageCssBundle = async (publicPagePath, pkg) => { - let cssString = "" - - for (let screen of pkg.screens || []) { - if (!screen._css) continue - if (screen._css.trim().length === 0) { - delete screen._css - continue - } - cssString += screen._css - } - - if (pkg.page._css) cssString += pkg.page._css - - writeFile(join(publicPagePath, "bundle.css"), cssString) -} - -/** - * Copy the budibase client library and sourcemap from NPM to /public/. - * The client library is then served as a static asset when the budibase application - * is running in preview or prod - * @param {String} pagePath - path to write the client library to - */ -const copyClientLib = async pagePath => { - const sourcepath = require.resolve("@budibase/client") - const destPath = join(pagePath, "budibase-client.js") - - await copyFile(sourcepath, destPath, constants.COPYFILE_FICLONE) - - await copyFile( - sourcepath + ".map", - destPath + ".map", - constants.COPYFILE_FICLONE - ) -} - -/** - * Build the frontend definition for a budibase application. This includes all page and screen information, - * and is injected into the budibase client library to tell it how to start constructing - * the DOM from components defined in the frontendDefinition. - * @param {String} pagePath - path to the public folder of the page where the definition will be written - * @param {Object} pkg - app package information from which the frontendDefinition will be built. - */ -const buildFrontendAppDefinition = async (pagePath, pkg) => { - const filename = join(pagePath, "clientFrontendDefinition.js") - - // Delete CSS code from the page and screens so it's not injected - delete pkg.page._css - - for (let screen of pkg.screens) { - if (screen._css) { - delete pkg.page._css - } - } - - const clientUiDefinition = JSON.stringify({ - page: pkg.page, - screens: pkg.screens, - libraries: ["@budibase/standard-components"], - }) - - await writeFile( - filename, - ` - window['##BUDIBASE_FRONTEND_DEFINITION##'] = ${clientUiDefinition}; - ` - ) -} From 71ca88207d9902f6f94fd6958ce610974171c817 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 23 Nov 2020 15:46:26 +0000 Subject: [PATCH 03/56] Removing all reference to 'pages' in server source code, now to look at builder. --- .../server/src/api/controllers/application.js | 98 ++-- .../server/src/automations/automationUtils.js | 36 -- packages/server/src/automations/thread.js | 22 +- packages/server/src/constants/layouts.js | 420 +++++++++--------- packages/server/src/utilities/mustache.js | 73 +++ 5 files changed, 335 insertions(+), 314 deletions(-) create mode 100644 packages/server/src/utilities/mustache.js diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 095cb3b831..6f5bf7f1e1 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -19,28 +19,45 @@ const { generateLayoutID, generateScreenID, } = require("../../db/utils") -const { BUILTIN_LEVEL_IDS } = require("../../utilities/security/accessLevels") +const { + BUILTIN_LEVEL_IDS, + AccessController, +} = require("../../utilities/security/accessLevels") const { downloadExtractComponentLibraries, } = require("../../utilities/createAppPackage") -const { MAIN, UNAUTHENTICATED, LayoutTypes } = require("../../constants/layouts") +const { BASE_LAYOUTS } = require("../../constants/layouts") const { HOME_SCREEN } = require("../../constants/screens") const { cloneDeep } = require("lodash/fp") +const { recurseMustache } = require("../../utilities/mustache") const APP_PREFIX = DocumentTypes.APP + SEPARATOR // utility function, need to do away with this -async function getMainAndUnauthPage(db) { - let pages = await db.allDocs( - getLayoutParams(null, { - include_docs: true, - }) - ) - pages = pages.rows.map(row => row.doc) +async function getLayouts(db) { + return ( + await db.allDocs( + getLayoutParams(null, { + include_docs: true, + }) + ) + ).rows.map(row => row.doc) +} - const mainPage = pages.find(page => page.name === LayoutTypes.MAIN) - const unauthPage = pages.find(page => page.name === LayoutTypes.UNAUTHENTICATED) - return { mainPage, unauthPage } +async function getScreens(db) { + return ( + await db.allDocs( + getScreenParams(null, { + include_docs: true, + }) + ) + ).rows.map(row => row.doc) +} + +function getUserAccessLevelId(ctx) { + return !ctx.user.accessLevel || !ctx.user.accessLevel._id + ? BUILTIN_LEVEL_IDS.PUBLIC + : ctx.user.accessLevel._id } async function createInstance(template) { @@ -85,25 +102,16 @@ exports.fetch = async function(ctx) { exports.fetchAppDefinition = async function(ctx) { const db = new CouchDB(ctx.params.appId) - // TODO: need to get rid of pages here, they shouldn't be needed anymore - const { mainPage, unauthPage } = await getMainAndUnauthPage(db) - const userAccessLevelId = - !ctx.user.accessLevel || !ctx.user.accessLevel._id - ? BUILTIN_LEVEL_IDS.PUBLIC - : ctx.user.accessLevel._id - const correctPage = - userAccessLevelId === BUILTIN_LEVEL_IDS.PUBLIC ? unauthPage : mainPage - const screens = ( - await db.allDocs( - getScreenParams(correctPage._id, { - include_docs: true, - }) - ) - ).rows.map(row => row.doc) - // TODO: need to handle access control here, limit screens to user access level + const layouts = await getLayouts(db) + const userAccessLevelId = getUserAccessLevelId(ctx) + const accessController = new AccessController(ctx.params.appId) + const screens = accessController.checkScreensAccess( + await getScreens(db), + userAccessLevelId + ) ctx.body = { - page: correctPage, - screens: screens, + layouts, + screens, libraries: ["@budibase/standard-components"], } } @@ -111,16 +119,14 @@ exports.fetchAppDefinition = async function(ctx) { exports.fetchAppPackage = async function(ctx) { const db = new CouchDB(ctx.params.appId) const application = await db.get(ctx.params.appId) + const layouts = await getLayouts(db) + const screens = await getScreens(db) - const { mainPage, unauthPage } = await getMainAndUnauthPage(db) ctx.body = { application, - pages: { - main: mainPage, - unauthenticated: unauthPage, - }, + screens, + layouts, } - await setBuilderToken(ctx, ctx.params.appId, application.version) } @@ -193,18 +199,18 @@ const createEmptyAppPackage = async (ctx, app) => { fs.mkdirpSync(newAppFolder) - const mainPage = cloneDeep(MAIN) - mainPage._id = generateLayoutID() - mainPage.title = app.name - - const unauthPage = cloneDeep(UNAUTHENTICATED) - unauthPage._id = generateLayoutID() - unauthPage.title = app.name - unauthPage.props._children[0].title = `Log in to ${app.name}` + const bulkDocs = [] + for (let layout of BASE_LAYOUTS) { + const cloned = cloneDeep(layout) + cloned._id = generateLayoutID() + cloned.title = app.name + bulkDocs.push(recurseMustache(cloned, app)) + } const homeScreen = cloneDeep(HOME_SCREEN) - homeScreen._id = generateScreenID(mainPage._id) - await db.bulkDocs([mainPage, unauthPage, homeScreen]) + homeScreen._id = generateScreenID() + bulkDocs.push(homeScreen) + await db.bulkDocs(bulkDocs) await compileStaticAssets(app._id) return newAppFolder diff --git a/packages/server/src/automations/automationUtils.js b/packages/server/src/automations/automationUtils.js index 3a66420dd0..ac0d9bf721 100644 --- a/packages/server/src/automations/automationUtils.js +++ b/packages/server/src/automations/automationUtils.js @@ -1,41 +1,5 @@ const CouchDB = require("../db") -/** - * When running mustache statements to execute on the context of the automation it possible user's may input mustache - * in a few different forms, some of which are invalid but are logically valid. An example of this would be the mustache - * statement "{{steps[0].revision}}" here it is obvious the user is attempting to access an array or object using array - * like operators. These are not supported by Mustache and therefore the statement will fail. This function will clean up - * the mustache statement so it instead reads as "{{steps.0.revision}}" which is valid and will work. It may also be expanded - * to include any other mustache statement cleanup that has been deemed necessary for the system. - * - * @param {string} string The string which *may* contain mustache statements, it is OK if it does not contain any. - * @returns {string} The string that was input with cleaned up mustache statements as required. - */ -module.exports.cleanMustache = string => { - let charToReplace = { - "[": ".", - "]": "", - } - let regex = new RegExp(/{{[^}}]*}}/g) - let matches = string.match(regex) - if (matches == null) { - return string - } - for (let match of matches) { - let baseIdx = string.indexOf(match) - for (let key of Object.keys(charToReplace)) { - let idxChar = match.indexOf(key) - if (idxChar !== -1) { - string = - string.slice(baseIdx, baseIdx + idxChar) + - charToReplace[key] + - string.slice(baseIdx + idxChar + 1) - } - } - } - return string -} - /** * When values are input to the system generally they will be of type string as this is required for mustache. This can * generate some odd scenarios as the Schema of the automation requires a number but the builder might supply a string diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index 26ca8f04a3..cc01ec2d4b 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -1,30 +1,10 @@ -const handlebars = require("handlebars") const actions = require("./actions") const logic = require("./logic") const automationUtils = require("./automationUtils") - -handlebars.registerHelper("object", value => { - return new handlebars.SafeString(JSON.stringify(value)) -}) +const { recurseMustache } = require("../utilities/mustache") const FILTER_STEP_ID = logic.BUILTIN_DEFINITIONS.FILTER.stepId -function recurseMustache(inputs, context) { - for (let key of Object.keys(inputs)) { - let val = inputs[key] - if (typeof val === "string") { - val = automationUtils.cleanMustache(inputs[key]) - const template = handlebars.compile(val) - inputs[key] = template(context) - } - // this covers objects and arrays - else if (typeof val === "object") { - inputs[key] = recurseMustache(inputs[key], context) - } - } - return inputs -} - /** * The automation orchestrator is a class responsible for executing automations. * It handles the context of the automation and makes sure each step gets the correct diff --git a/packages/server/src/constants/layouts.js b/packages/server/src/constants/layouts.js index 065bcdf261..85ab7ee69d 100644 --- a/packages/server/src/constants/layouts.js +++ b/packages/server/src/constants/layouts.js @@ -1,221 +1,219 @@ -const LayoutTypes = { - MAIN: "main", - UNAUTHENTICATED: "unauthenticated", -} - -const MAIN = { - componentLibraries: ["@budibase/standard-components"], - title: "{{ name }}", - favicon: "./_shared/favicon.png", - stylesheets: [], - name: LayoutTypes.MAIN, - props: { - _id: "private-master-root", - _component: "@budibase/standard-components/container", - _children: [ - { - _id: "c74f07266980c4b6eafc33e2a6caa783d", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - display: "flex", - "flex-direction": "row", - "justify-content": "flex-start", - "align-items": "flex-start", - background: "#fff", - width: "100%", - "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - type: "div", - _appId: "inst_app_80b_f158d4057d2c4bedb0042d42fda8abaf", - _instanceName: "Header", - _children: [ - { - _id: "49e0e519-9e5e-4127-885a-ee6a0a49e2c1", - _component: "@budibase/standard-components/Navigation", - _styles: { - normal: { - "max-width": "1400px", - "margin-left": "auto", - "margin-right": "auto", - padding: "20px", - color: "#757575", - "font-weight": "400", - "font-size": "16px", - flex: "1 1 auto", - }, - hover: {}, - active: {}, - selected: {}, +const BASE_LAYOUTS = [ + { + componentLibraries: ["@budibase/standard-components"], + title: "{{ name }}", + favicon: "./_shared/favicon.png", + stylesheets: [], + name: "Main", + props: { + _id: "private-master-root", + _component: "@budibase/standard-components/container", + _children: [ + { + _id: "c74f07266980c4b6eafc33e2a6caa783d", + _component: "@budibase/standard-components/container", + _styles: { + normal: { + display: "flex", + "flex-direction": "row", + "justify-content": "flex-start", + "align-items": "flex-start", + background: "#fff", + width: "100%", + "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", }, - _code: "", - logoUrl: - "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg", - title: "", - backgroundColor: "", - color: "", - borderWidth: "", - borderColor: "", - borderStyle: "", - _appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394", - _instanceName: "Navigation", - _children: [ - { - _id: "48b35328-4c91-4343-a6a3-1a1fd77b3386", - _component: "@budibase/standard-components/link", - _styles: { - normal: { - "font-family": "Inter", - "font-weight": "500", - color: "#000000", - "text-decoration-line": "none", - "font-size": "16px", - }, - hover: { - color: "#4285f4", - }, - active: {}, - selected: {}, + hover: {}, + active: {}, + selected: {}, + }, + _code: "", + className: "", + onLoad: [], + type: "div", + _appId: "inst_app_80b_f158d4057d2c4bedb0042d42fda8abaf", + _instanceName: "Header", + _children: [ + { + _id: "49e0e519-9e5e-4127-885a-ee6a0a49e2c1", + _component: "@budibase/standard-components/Navigation", + _styles: { + normal: { + "max-width": "1400px", + "margin-left": "auto", + "margin-right": "auto", + padding: "20px", + color: "#757575", + "font-weight": "400", + "font-size": "16px", + flex: "1 1 auto", }, - _code: "", - url: "/", - openInNewTab: false, - text: "Home", - color: "", - hoverColor: "", - underline: false, - fontSize: "", - fontFamily: "initial", - _appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394", - _instanceName: "Home Link", - _children: [], + hover: {}, + active: {}, + selected: {}, }, - ], - }, - ], - }, - { - _id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967", - _component: "##builtin/screenslot", - _styles: { - normal: { - flex: "1 1 auto", - display: "flex", - "flex-direction": "column", - "justify-content": "flex-start", - "align-items": "stretch", - "max-width": "100%", - "margin-left": "20px", - "margin-right": "20px", - width: "1400px", - padding: "20px", - }, - hover: {}, - active: {}, - selected: {}, + _code: "", + logoUrl: + "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg", + title: "", + backgroundColor: "", + color: "", + borderWidth: "", + borderColor: "", + borderStyle: "", + _appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394", + _instanceName: "Navigation", + _children: [ + { + _id: "48b35328-4c91-4343-a6a3-1a1fd77b3386", + _component: "@budibase/standard-components/link", + _styles: { + normal: { + "font-family": "Inter", + "font-weight": "500", + color: "#000000", + "text-decoration-line": "none", + "font-size": "16px", + }, + hover: { + color: "#4285f4", + }, + active: {}, + selected: {}, + }, + _code: "", + url: "/", + openInNewTab: false, + text: "Home", + color: "", + hoverColor: "", + underline: false, + fontSize: "", + fontFamily: "initial", + _appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394", + _instanceName: "Home Link", + _children: [], + }, + ], + }, + ], }, - _code: "", - _children: [], - }, - ], - type: "div", - _styles: { - active: {}, - hover: {}, - normal: { - display: "flex", - "flex-direction": "column", - "align-items": "center", - "justify-content": "flex-start", - "margin-right": "auto", - "margin-left": "auto", - "min-height": "100%", - "background-image": - "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);", - }, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - }, -} - -const UNAUTHENTICATED = { - componentLibraries: ["@budibase/standard-components"], - title: "{{ name }}", - favicon: "./_shared/favicon.png", - stylesheets: [], - name: LayoutTypes.UNAUTHENTICATED, - props: { - _id: "public-master-root", - _component: "@budibase/standard-components/container", - _children: [ - { - _id: "686c252d-dbf2-4e28-9078-414ba4719759", - _component: "@budibase/standard-components/login", - _styles: { - normal: { - padding: "64px", - background: "rgba(255, 255, 255, 0.4)", - "border-radius": "0.5rem", - "margin-top": "0px", - margin: "0px", - "line-height": "1", - "box-shadow": - "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", - "font-size": "16px", - "font-family": "Inter", - flex: "0 1 auto", - transform: "0", + { + _id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967", + _component: "##builtin/screenslot", + _styles: { + normal: { + flex: "1 1 auto", + display: "flex", + "flex-direction": "column", + "justify-content": "flex-start", + "align-items": "stretch", + "max-width": "100%", + "margin-left": "20px", + "margin-right": "20px", + width: "1400px", + padding: "20px", + }, + hover: {}, + active: {}, + selected: {}, }, - hover: {}, - active: {}, - selected: {}, + _code: "", + _children: [], }, - _code: "", - loginRedirect: "", - usernameLabel: "Username", - passwordLabel: "Password", - loginButtonLabel: "Login", - buttonClass: "", - _instanceName: "Login", - inputClass: "", - _children: [], - title: "Log in to {{ name }}", - buttonText: "Log In", - logo: - "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg", + ], + type: "div", + _styles: { + active: {}, + hover: {}, + normal: { + display: "flex", + "flex-direction": "column", + "align-items": "center", + "justify-content": "flex-start", + "margin-right": "auto", + "margin-left": "auto", + "min-height": "100%", + "background-image": + "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);", + }, + selected: {}, }, - ], - type: "div", - _styles: { - active: {}, - hover: {}, - normal: { - display: "flex", - "flex-direction": "column", - "align-items": "center", - "justify-content": "center", - "margin-right": "auto", - "margin-left": "auto", - "min-height": "100%", - "background-image": - "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);", - }, - selected: {}, + _code: "", + className: "", + onLoad: [], }, - _code: "", - className: "", - onLoad: [], }, -} + { + componentLibraries: ["@budibase/standard-components"], + title: "{{ name }}", + favicon: "./_shared/favicon.png", + stylesheets: [], + name: "Unauthenticated", + props: { + _id: "public-master-root", + _component: "@budibase/standard-components/container", + _children: [ + { + _id: "686c252d-dbf2-4e28-9078-414ba4719759", + _component: "@budibase/standard-components/login", + _styles: { + normal: { + padding: "64px", + background: "rgba(255, 255, 255, 0.4)", + "border-radius": "0.5rem", + "margin-top": "0px", + margin: "0px", + "line-height": "1", + "box-shadow": + "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", + "font-size": "16px", + "font-family": "Inter", + flex: "0 1 auto", + transform: "0", + }, + hover: {}, + active: {}, + selected: {}, + }, + _code: "", + loginRedirect: "", + usernameLabel: "Username", + passwordLabel: "Password", + loginButtonLabel: "Login", + buttonClass: "", + _instanceName: "Login", + inputClass: "", + _children: [], + title: "Log in to {{ name }}", + buttonText: "Log In", + logo: + "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg", + }, + ], + type: "div", + _styles: { + active: {}, + hover: {}, + normal: { + display: "flex", + "flex-direction": "column", + "align-items": "center", + "justify-content": "center", + "margin-right": "auto", + "margin-left": "auto", + "min-height": "100%", + "background-image": + "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);", + }, + selected: {}, + }, + _code: "", + className: "", + onLoad: [], + }, + }, +] -module.exports = { MAIN, UNAUTHENTICATED, LayoutTypes } +module.exports = { + BASE_LAYOUTS, +} diff --git a/packages/server/src/utilities/mustache.js b/packages/server/src/utilities/mustache.js new file mode 100644 index 0000000000..0428bdc03d --- /dev/null +++ b/packages/server/src/utilities/mustache.js @@ -0,0 +1,73 @@ +const handlebars = require("handlebars") + +handlebars.registerHelper("object", value => { + return new handlebars.SafeString(JSON.stringify(value)) +}) + +/** + * When running mustache statements to execute on the context of the automation it possible user's may input mustache + * in a few different forms, some of which are invalid but are logically valid. An example of this would be the mustache + * statement "{{steps[0].revision}}" here it is obvious the user is attempting to access an array or object using array + * like operators. These are not supported by Mustache and therefore the statement will fail. This function will clean up + * the mustache statement so it instead reads as "{{steps.0.revision}}" which is valid and will work. It may also be expanded + * to include any other mustache statement cleanup that has been deemed necessary for the system. + * + * @param {string} string The string which *may* contain mustache statements, it is OK if it does not contain any. + * @returns {string} The string that was input with cleaned up mustache statements as required. + */ +function cleanMustache(string) { + let charToReplace = { + "[": ".", + "]": "", + } + let regex = new RegExp(/{{[^}}]*}}/g) + let matches = string.match(regex) + if (matches == null) { + return string + } + for (let match of matches) { + let baseIdx = string.indexOf(match) + for (let key of Object.keys(charToReplace)) { + let idxChar = match.indexOf(key) + if (idxChar !== -1) { + string = + string.slice(baseIdx, baseIdx + idxChar) + + charToReplace[key] + + string.slice(baseIdx + idxChar + 1) + } + } + } + return string +} + +/** + * Given an input object this will recurse through all props to try and update + * any handlebars/mustache statements within. + * @param {object|array} inputs The input structure which is to be recursed, it is important to note that + * if the structure contains any cycles then this will fail. + * @param {object} context The context that handlebars should fill data from. + * @returns {object|array} The structure input, as fully updated as possible. + */ +function recurseMustache(inputs, context) { + // JSON stringify will fail if there are any cycles, stops infinite recursion + try { + JSON.stringify(inputs) + } catch (err) { + throw "Unable to process inputs to JSON, cannot recurse" + } + for (let key of Object.keys(inputs)) { + let val = inputs[key] + if (typeof val === "string") { + val = cleanMustache(inputs[key]) + const template = handlebars.compile(val) + inputs[key] = template(context) + } + // this covers objects and arrays + else if (typeof val === "object") { + inputs[key] = recurseMustache(inputs[key], context) + } + } + return inputs +} + +exports.recurseMustache = recurseMustache From ddca22245cdf1d007110c2cb08a31a3ecfc7bec7 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 23 Nov 2020 16:56:35 +0000 Subject: [PATCH 04/56] Changing up how the static assets are compiled, making a 'css' directory in which individual assets CSS is written and then bundled together meaning that not all assets need to be sent up at once for css bundle to be built. --- .../server/src/api/controllers/application.js | 10 +-- packages/server/src/api/controllers/layout.js | 10 +-- packages/server/src/api/controllers/screen.js | 5 +- .../utilities/builder/compileStaticAssets.js | 75 +++++++++---------- 4 files changed, 48 insertions(+), 52 deletions(-) diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 6f5bf7f1e1..1bc1be09b7 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -199,19 +199,19 @@ const createEmptyAppPackage = async (ctx, app) => { fs.mkdirpSync(newAppFolder) - const bulkDocs = [] + let screensAndLayouts = [] for (let layout of BASE_LAYOUTS) { const cloned = cloneDeep(layout) cloned._id = generateLayoutID() cloned.title = app.name - bulkDocs.push(recurseMustache(cloned, app)) + screensAndLayouts.push(recurseMustache(cloned, app)) } const homeScreen = cloneDeep(HOME_SCREEN) homeScreen._id = generateScreenID() - bulkDocs.push(homeScreen) - await db.bulkDocs(bulkDocs) + screensAndLayouts.push(homeScreen) - await compileStaticAssets(app._id) + screensAndLayouts = await compileStaticAssets(app._id, screensAndLayouts) + await db.bulkDocs(screensAndLayouts) return newAppFolder } diff --git a/packages/server/src/api/controllers/layout.js b/packages/server/src/api/controllers/layout.js index eae7b04d49..9e22fe027d 100644 --- a/packages/server/src/api/controllers/layout.js +++ b/packages/server/src/api/controllers/layout.js @@ -4,12 +4,10 @@ const compileStaticAssets = require("../../utilities/builder/compileStaticAssets exports.save = async function(ctx) { const db = new CouchDB(ctx.user.appId) - const appPackage = ctx.request.body + let layout = ctx.request.body - // remove special doc props which couch will complain about - delete appPackage.layout._css - appPackage.layout._id = appPackage.layout._id || generateLayoutID() - ctx.body = await db.put(appPackage.layout) - await compileStaticAssets(ctx.user.appId) + layout._id = layout._id || generateLayoutID() + layout = await compileStaticAssets(ctx.user.appId, layout) + ctx.body = await db.put(layout) ctx.status = 200 } diff --git a/packages/server/src/api/controllers/screen.js b/packages/server/src/api/controllers/screen.js index acf6df22f0..588b642156 100644 --- a/packages/server/src/api/controllers/screen.js +++ b/packages/server/src/api/controllers/screen.js @@ -1,5 +1,6 @@ const CouchDB = require("../../db") const { getScreenParams, generateScreenID } = require("../../db/utils") +const compileStaticAssets = require("../../utilities/builder/compileStaticAssets") const { AccessController } = require("../../utilities/security/accessLevels") exports.fetch = async ctx => { @@ -23,12 +24,12 @@ exports.fetch = async ctx => { exports.save = async ctx => { const appId = ctx.user.appId const db = new CouchDB(appId) - const screen = ctx.request.body + let screen = ctx.request.body if (!screen._id) { screen._id = generateScreenID() } - delete screen._css + screen = await compileStaticAssets(ctx.user.appId, screen) const response = await db.put(screen) ctx.message = `Screen ${screen.name} saved.` diff --git a/packages/server/src/utilities/builder/compileStaticAssets.js b/packages/server/src/utilities/builder/compileStaticAssets.js index a64c2f0b98..43c7309f5e 100644 --- a/packages/server/src/utilities/builder/compileStaticAssets.js +++ b/packages/server/src/utilities/builder/compileStaticAssets.js @@ -1,66 +1,63 @@ -const { ensureDir, constants, copyFile, writeFile } = require("fs-extra") +const { + ensureDir, + constants, + copyFile, + writeFile, + readdir, + readFile, +} = require("fs-extra") const { join } = require("../centralPath") const { budibaseAppsDir } = require("../budibaseDir") -const CouchDB = require("../../db") -const { getScreenParams, getLayoutParams } = require("../../db/utils") -async function getAppPackage(appId) { - const db = new CouchDB(appId) - let params = { - include_docs: true, - } - let [screens, layouts] = await Promise.all([ - db.allDocs(getScreenParams(null, params)), - db.allDocs(getLayoutParams(null, params)), - ]) - screens = screens.rows.map(row => row.doc) - layouts = layouts.rows.map(row => row.doc) - if (!screens) { - screens = [] - } - if (!layouts) { - layouts = [] - } - return { screens, layouts } -} +const CSS_DIRECTORY = "css" /** * Compile all the non-db static web assets that are required for the running of * a budibase application. This includes CSS, the JSON structure of the DOM and * the client library, a script responsible for reading the JSON structure * and rendering the application. - * @param {string} appId - id of the application we want to compile static assets for + * @param {string} appId id of the application we want to compile static assets for + * @param {array|object} assets a list of screens or screen layouts for which the CSS should be extracted and stored. */ -module.exports = async appId => { +module.exports = async (appId, assets) => { const publicPath = join(budibaseAppsDir(), appId, "public") - const pkg = await getAppPackage(appId) - await ensureDir(publicPath) - await buildCssBundle(publicPath, pkg) - await copyClientLib(publicPath) + for (let asset of Array.isArray(assets) ? assets : [assets]) { + await buildCssBundle(publicPath, asset) + await copyClientLib(publicPath) + // remove props that shouldn't be present when written to DB + if (asset._css) { + delete asset._css + } + } + return assets } /** * Reads the _css property of all screens and the screen layouts, and creates a singular CSS * bundle for the app at /public/bundle.css * @param {String} publicPath - path to the public assets directory of the budibase application - * @param {Object} pkg - app package information + * @param {Object} asset a single screen or screen layout which is being updated */ -const buildCssBundle = async (publicPath, pkg) => { +const buildCssBundle = async (publicPath, asset) => { + const cssPath = join(publicPath, CSS_DIRECTORY) let cssString = "" - for (let screen of pkg.screens || []) { - if (!screen._css) continue - if (screen._css.trim().length === 0) { - delete screen._css - continue - } - cssString += screen._css + // create a singular CSS file for this asset + const assetCss = asset._css ? asset._css.trim() : "" + if (assetCss.length !== 0) { + await ensureDir(cssPath) + await writeFile(join(cssPath, asset._id), assetCss) } - if (pkg.layout._css) cssString += pkg.layout._css + // bundle up all the CSS in the directory into one top level CSS file + const cssFiles = await readdir(cssPath) + for (let filename of cssFiles) { + const css = await readFile(filename) + cssString += css + } - writeFile(join(publicPath, "bundle.css"), cssString) + await writeFile(join(publicPath, "bundle.css"), cssString) } /** From e26baa6faf35bd558ee0bd7e4fe4118542ed6e2c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 24 Nov 2020 18:11:18 +0000 Subject: [PATCH 05/56] Creating CSS generation capabilities in the server. --- .../server/src/api/controllers/application.js | 10 +++- .../src/api/controllers/static/index.js | 6 +++ packages/server/src/api/routes/static.js | 1 + .../src/api/routes/tests/generateCss.spec.js | 46 +++++++++++++++++++ packages/server/src/constants/screens.js | 28 ++++++++++- .../src/utilities/builder/generateCss.js | 43 +++++++++++++++++ 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 packages/server/src/api/routes/tests/generateCss.spec.js create mode 100644 packages/server/src/utilities/builder/generateCss.js diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 1bc1be09b7..6cbf3891d5 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -30,6 +30,7 @@ const { BASE_LAYOUTS } = require("../../constants/layouts") const { HOME_SCREEN } = require("../../constants/screens") const { cloneDeep } = require("lodash/fp") const { recurseMustache } = require("../../utilities/mustache") +const { generateAssetCss } = require("../../utilities/builder/generateCss") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -119,9 +120,14 @@ exports.fetchAppDefinition = async function(ctx) { exports.fetchAppPackage = async function(ctx) { const db = new CouchDB(ctx.params.appId) const application = await db.get(ctx.params.appId) - const layouts = await getLayouts(db) - const screens = await getScreens(db) + const [layouts, screens] = await Promise.all([getLayouts(db), getScreens(db)]) + for (let layout of layouts) { + layout._css = generateAssetCss([layout.props]) + } + for (let screen of screens) { + screen._css = generateAssetCss([screen.props]) + } ctx.body = { application, screens, diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index 5676afb230..51aef4cd34 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -17,10 +17,16 @@ const setBuilderToken = require("../../../utilities/builder/setBuilderToken") const fileProcessor = require("../../../utilities/fileProcessor") const { AuthTypes } = require("../../../constants") const env = require("../../../environment") +const { generateAssetCss } = require("../../../utilities/builder/generateCss") // this was the version before we started versioning the component library const COMP_LIB_BASE_APP_VERSION = "0.2.5" +exports.generateCss = async function(ctx) { + const structure = ctx.request.body + ctx.body = generateAssetCss(structure) +} + exports.serveBuilder = async function(ctx) { let builderPath = resolve(__dirname, "../../../../builder") if (ctx.file === "index.html") { diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index a519a63781..f8f399a4ea 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -24,6 +24,7 @@ if (env.NODE_ENV !== "production") { } router + .post("/api/css/generate", authorized(BUILDER), controller.generateCss) .post( "/api/attachments/process", authorized(BUILDER), diff --git a/packages/server/src/api/routes/tests/generateCss.spec.js b/packages/server/src/api/routes/tests/generateCss.spec.js new file mode 100644 index 0000000000..84ad2eafc6 --- /dev/null +++ b/packages/server/src/api/routes/tests/generateCss.spec.js @@ -0,0 +1,46 @@ +const { generateAssetCss, generateCss } = require("../../../utilities/builder/generateCss") + +describe("generate_css", () => { + it("Check how array styles are output", () => { + expect(generateCss({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0 10 0 15;") + }) + + it("Check handling of an array with empty string values", () => { + expect(generateCss({ padding: ["", "", "", ""] })).toBe("") + }) + + it("Check handling of an empty array", () => { + expect(generateCss({ margin: [] })).toBe("") + }) + + it("Check handling of valid font property", () => { + expect(generateCss({ "font-size": "10px" })).toBe("font-size: 10px;") + }) +}) + + +describe("generate_screen_css", () => { + const normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } } + + it("Test generation of normal css styles", () => { + expect(generateAssetCss([normalComponent])).toBe(".header-123-456 {\nfont-size: 16px;\n}") + }) + + const hoverComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {"font-size": "16px"}, active: {}, selected: {} } } + + it("Test generation of hover css styles", () => { + expect(generateAssetCss([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}") + }) + + const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } } + + it("Test generation of selection css styles", () => { + expect(generateAssetCss([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}") + }) + + const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } } + + it("Testing handling of empty component styles", () => { + expect(generateAssetCss([emptyComponent])).toBe("") + }) +}) \ No newline at end of file diff --git a/packages/server/src/constants/screens.js b/packages/server/src/constants/screens.js index 5c5a9dfd26..dbdaf2b975 100644 --- a/packages/server/src/constants/screens.js +++ b/packages/server/src/constants/screens.js @@ -104,5 +104,31 @@ exports.HOME_SCREEN = { route: "/", accessLevelId: BUILTIN_LEVEL_IDS.BASIC, }, - name: "d834fea2-1b3e-4320-ab34-f9009f5ecc59", + name: "home-screen", +} + +exports.LOGIN_SCREEN = { + description: "", + url: "", + props: { + _id: "781e497e-2e7c-11eb-adc1-0242ac120002", + _component: "@budibase/standard-components/login", + _styles: { + normal: {}, + hover: {}, + active: {}, + selected: {}, + }, + _code: "", + className: "", + onLoad: [], + type: "div", + _children: [], + _instanceName: "Login", + }, + routing: { + route: "/", + accessLevelId: BUILTIN_LEVEL_IDS.PUBLIC, + }, + name: "login-screen", } diff --git a/packages/server/src/utilities/builder/generateCss.js b/packages/server/src/utilities/builder/generateCss.js new file mode 100644 index 0000000000..c3d72c741f --- /dev/null +++ b/packages/server/src/utilities/builder/generateCss.js @@ -0,0 +1,43 @@ +exports.generateAssetCss = component_arr => { + let styles = "" + for (const { _styles, _id, _children, _component } of component_arr) { + let [componentName] = _component.match(/[a-z]*$/) + Object.keys(_styles).forEach(selector => { + const cssString = exports.generateCss(_styles[selector]) + if (cssString) { + styles += exports.applyClass(_id, componentName, cssString, selector) + } + }) + if (_children && _children.length) { + styles += exports.generateAssetCss(_children) + "\n" + } + } + return styles.trim() +} + +exports.generateCss = style => { + let cssString = Object.entries(style).reduce((str, [key, value]) => { + if (typeof value === "string") { + if (value) { + return (str += `${key}: ${value};\n`) + } + } else if (Array.isArray(value)) { + if (value.length > 0 && !value.every(v => v === "")) { + return (str += `${key}: ${value.join(" ")};\n`) + } + } + + return str + }, "") + + return (cssString || "").trim() +} + +exports.applyClass = (id, name = "element", styles, selector) => { + if (selector === "normal") { + return `.${name}-${id} {\n${styles}\n}` + } else { + let sel = selector === "selected" ? "::selection" : `:${selector}` + return `.${name}-${id}${sel} {\n${styles}\n}` + } +} From e1314b0d8873a90087a98ad2cb5a2d5be1bd852e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 24 Nov 2020 18:11:34 +0000 Subject: [PATCH 06/56] Starting work on builder, very broken. --- .../builder/src/builderStore/generate_css.js | 43 --------- packages/builder/src/builderStore/index.js | 34 ++++--- .../src/builderStore/store/frontend.js | 96 ++++++------------- .../modals/CreateTableModal.svelte | 2 +- .../components/start/CreateAppModal.svelte | 3 +- .../AppPreview/CurrentItemPreview.svelte | 2 +- .../ScreenDropdownMenu.svelte | 2 +- .../ComponentSelectionList.svelte | 4 +- .../userInterface/FrontendNavigatePane.svelte | 4 +- .../{PagesList.svelte => LayoutsList.svelte} | 14 +-- .../userInterface/SettingsView.svelte | 12 +-- .../userInterface/temporaryPanelStructure.js | 3 +- .../automate/[automation]/_layout.svelte | 2 +- .../design/[page]/[screen]/_layout.svelte | 6 +- .../design/[page]/_layout.svelte | 2 +- packages/builder/tests/generate_css.spec.js | 51 ---------- 16 files changed, 76 insertions(+), 204 deletions(-) delete mode 100644 packages/builder/src/builderStore/generate_css.js rename packages/builder/src/components/userInterface/{PagesList.svelte => LayoutsList.svelte} (76%) delete mode 100644 packages/builder/tests/generate_css.spec.js diff --git a/packages/builder/src/builderStore/generate_css.js b/packages/builder/src/builderStore/generate_css.js deleted file mode 100644 index 2bb5a3bd2e..0000000000 --- a/packages/builder/src/builderStore/generate_css.js +++ /dev/null @@ -1,43 +0,0 @@ -export const generate_screen_css = component_arr => { - let styles = "" - for (const { _styles, _id, _children, _component } of component_arr) { - let [componentName] = _component.match(/[a-z]*$/) - Object.keys(_styles).forEach(selector => { - const cssString = generate_css(_styles[selector]) - if (cssString) { - styles += apply_class(_id, componentName, cssString, selector) - } - }) - if (_children && _children.length) { - styles += generate_screen_css(_children) + "\n" - } - } - return styles.trim() -} - -export const generate_css = style => { - let cssString = Object.entries(style).reduce((str, [key, value]) => { - if (typeof value === "string") { - if (value) { - return (str += `${key}: ${value};\n`) - } - } else if (Array.isArray(value)) { - if (value.length > 0 && !value.every(v => v === "")) { - return (str += `${key}: ${value.join(" ")};\n`) - } - } - - return str - }, "") - - return (cssString || "").trim() -} - -export const apply_class = (id, name = "element", styles, selector) => { - if (selector === "normal") { - return `.${name}-${id} {\n${styles}\n}` - } else { - let sel = selector === "selected" ? "::selection" : `:${selector}` - return `.${name}-${id}${sel} {\n${styles}\n}` - } -} diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index ae77889404..aac2437560 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -10,19 +10,29 @@ 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 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 + const currentScreens = $store.layouts[currentAssetName]?._screens if (currentScreens == null) { return [] } @@ -31,12 +41,6 @@ export const currentScreens = derived(store, $store => { : Object.values(currentScreens) }) -export const selectedPage = derived(store, $store => { - if (!$store.pages) return null - - return $store.pages[$store.currentPageName || "main"] -}) - export const initialise = async () => { try { await analytics.activate() diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 778c7f7be5..0523b6693f 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -5,8 +5,7 @@ import { getBuiltin, makePropsSafe, } from "components/userInterface/pagesParsing/createProps" -import { allScreens, backendUiStore, selectedPage } from "builderStore" -import { generate_screen_css } from "../generate_css" +import { allScreens, backendUiStore, currentAsset } from "builderStore" import { fetchComponentLibDefinitions } from "../loadComponentLibraries" import api from "../api" import { DEFAULT_PAGES_OBJECT } from "../../constants" @@ -23,14 +22,15 @@ const INITIAL_FRONTEND_STATE = { apps: [], name: "", description: "", - pages: DEFAULT_PAGES_OBJECT, + layouts: DEFAULT_PAGES_OBJECT, + screens: [], mainUi: {}, unauthenticatedUi: {}, components: [], currentPreviewItem: null, currentComponentInfo: null, currentFrontEndType: "none", - currentPageName: "", + currentAssetId: "", currentComponentProps: null, errors: [], hasAppPackage: false, @@ -43,52 +43,12 @@ export const getFrontendStore = () => { const store = writable({ ...INITIAL_FRONTEND_STATE }) store.actions = { - // TODO: REFACTOR initialise: async pkg => { + const layouts = pkg.layouts, screens = pkg.screens, application = pkg.application 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 +59,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, @@ -111,7 +72,7 @@ export const getFrontendStore = () => { store.update(state => { state.currentFrontEndType = type - const page = get(selectedPage) + const page = get(currentAsset) const pageOrScreen = type === "page" ? page : page._screens[0] @@ -168,7 +129,7 @@ export const getFrontendStore = () => { await savePromise }, save: async screen => { - const page = get(selectedPage) + const page = get(currentAsset) const currentPageScreens = page._screens const creatingNewScreen = screen._id === undefined @@ -197,14 +158,15 @@ export const getFrontendStore = () => { state.currentComponentInfo = safeProps screen.props = safeProps } - savePromise = store.actions.pages.save() + savePromise = store.actions.layouts.save() return state }) if (savePromise) await savePromise }, - 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() }, regenerateCssForCurrentScreen: () => { const { currentPreviewItem } = get(store) @@ -218,7 +180,7 @@ export const getFrontendStore = () => { const screensToDelete = Array.isArray(screens) ? screens : [screens] store.update(state => { - const currentPage = get(selectedPage) + const currentPage = get(currentAsset) for (let screenToDelete of screensToDelete) { // Remove screen from current page as well @@ -242,17 +204,17 @@ export const getFrontendStore = () => { if (state.currentFrontEndType !== "page") { await store.actions.screens.save(state.currentPreviewItem) } - await store.actions.pages.save() + await store.actions.layouts.save() }, }, - pages: { + layouts: { select: pageName => { store.update(state => { - const currentPage = state.pages[pageName] + const currentPage = state.layouts[pageName] state.currentFrontEndType = "page" state.currentView = "detail" - state.currentPageName = pageName + state.currentAssetId = pageName // This is the root of many problems. // Uncaught (in promise) TypeError: Cannot read property '_component' of undefined @@ -264,11 +226,11 @@ export const getFrontendStore = () => { ) state.currentComponentInfo = safeProps currentPage.props = safeProps - state.currentPreviewItem = state.pages[pageName] + state.currentPreviewItem = state.layouts[pageName] store.actions.screens.regenerateCssForCurrentScreen() for (let screen of get(allScreens)) { - screen._css = generate_screen_css([screen.props]) + screen._css = store.actions.screens.regenerateCss(screen) } return state @@ -276,7 +238,7 @@ export const getFrontendStore = () => { }, save: async page => { const storeContents = get(store) - const pageName = storeContents.currentPageName || "main" + const pageName = storeContents.currentAssetId || "main" const pageToSave = page || storeContents.pages[pageName] // TODO: revisit. This sends down a very weird payload @@ -293,7 +255,7 @@ export const getFrontendStore = () => { if (!json.ok) throw new Error("Error updating page") store.update(state => { - state.pages[pageName]._rev = json.rev + state.layouts[pageName]._rev = json.rev return state }) }, @@ -324,7 +286,7 @@ export const getFrontendStore = () => { if ( componentToAdd.startsWith("##") && - findSlot(state.pages[state.currentPageName].props._children) + findSlot(state.layouts[state.currentAssetId].props._children) ) { return state } @@ -480,7 +442,7 @@ export const getFrontendStore = () => { store.update(state => { // Try to extract a nav component from the master screen const nav = findChildComponentType( - state.pages.main, + state.layouts.main, "@budibase/standard-components/Navigation" ) if (nav) { @@ -515,12 +477,12 @@ export const getFrontendStore = () => { // 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) { + state.currentAssetId = "main" + store.actions.screens.regenerateCss(state.layouts.main) + for (let screen of state.layouts.main._screens) { store.actions.screens.regenerateCss(screen) } - savePromise = store.actions.pages.save() + savePromise = store.actions.layouts.save() } return state }) diff --git a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte index f064ff923c..d111821fb3 100644 --- a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte +++ b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte @@ -51,7 +51,7 @@ const screens = screenTemplates($store, [table]) .filter(template => defaultScreens.includes(template.id)) .map(template => template.create()) - store.actions.pages.select("main") + store.actions.layouts.select("main") for (let screen of screens) { // Record the table that created this screen so we can link it later screen.autoTableId = table._id diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index 049b21c995..99539e965f 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -155,9 +155,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 c9fe02e786..2e7d719084 100644 --- a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte @@ -56,7 +56,7 @@ screenPlaceholder.props._id = "screenslot-placeholder" // Extract data to pass to the iframe - $: page = $store.pages[$store.currentPageName] + $: page = $store.layouts[$store.currentPageName] $: screen = $store.currentFrontEndType === "page" ? screenPlaceholder diff --git a/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte b/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte index 3ecd8ce0dc..07f0f042a1 100644 --- a/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte +++ b/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte @@ -19,7 +19,7 @@ // update the page if required store.update(state => { if (state.currentPreviewItem._id === screen) { - store.actions.pages.select($store.currentPageName) + store.actions.layouts.select($store.currentPageName) notifier.success(`Screen ${screenToDelete.name} deleted successfully.`) $goto(`./:page/page-layout`) } diff --git a/packages/builder/src/components/userInterface/ComponentSelectionList.svelte b/packages/builder/src/components/userInterface/ComponentSelectionList.svelte index f099984e5a..edce828f41 100644 --- a/packages/builder/src/components/userInterface/ComponentSelectionList.svelte +++ b/packages/builder/src/components/userInterface/ComponentSelectionList.svelte @@ -1,6 +1,6 @@
- {#each pages as { title, id }} - {/each} diff --git a/packages/builder/src/components/userInterface/SettingsView.svelte b/packages/builder/src/components/userInterface/SettingsView.svelte index 8afdfce7ec..1ceb0f6c06 100644 --- a/packages/builder/src/components/userInterface/SettingsView.svelte +++ b/packages/builder/src/components/userInterface/SettingsView.svelte @@ -4,7 +4,7 @@ import Input from "./PropertyPanelControls/Input.svelte" import { goto } from "@sveltech/routify" import { excludeProps } from "./propertyCategories.js" - import { store, allScreens } from "builderStore" + import { store, allScreens, currentAsset } from "builderStore" import { walkProps } from "builderStore/storeUtils" export let panelDefinition = [] @@ -58,15 +58,15 @@ } }) } - // check page first - lookForDuplicate($store.pages[$store.currentPageName].props) - if (duplicate) return true - + // check against layouts + for (let layout of $store.layouts) { + lookForDuplicate(layout.props) + } // if viewing screen, check current screen for duplicate if ($store.currentFrontEndType === "screen") { lookForDuplicate($store.currentPreviewItem.props) } else { - // viewing master page - need to dedupe against all screens + // need to dedupe against all screens for (let screen of $allScreens) { lookForDuplicate(screen.props) } diff --git a/packages/builder/src/components/userInterface/temporaryPanelStructure.js b/packages/builder/src/components/userInterface/temporaryPanelStructure.js index e18ca6014f..5c18e012b0 100644 --- a/packages/builder/src/components/userInterface/temporaryPanelStructure.js +++ b/packages/builder/src/components/userInterface/temporaryPanelStructure.js @@ -1185,6 +1185,7 @@ export default { settings: [{ label: "Logo URL", key: "logoUrl", control: Input }], }, }, + // TODO: need to deal with this { name: "Login", _component: "@budibase/standard-components/login", @@ -1192,7 +1193,7 @@ export default { "A component that automatically generates a login screen for your app.", icon: "ri-login-box-line", children: [], - showOnPages: ["unauthenticated"], + showOnAsset: ["login-screen"], properties: { design: { ...all }, settings: [ diff --git a/packages/builder/src/pages/[application]/automate/[automation]/_layout.svelte b/packages/builder/src/pages/[application]/automate/[automation]/_layout.svelte index a61df114db..87d314e5bd 100644 --- a/packages/builder/src/pages/[application]/automate/[automation]/_layout.svelte +++ b/packages/builder/src/pages/[application]/automate/[automation]/_layout.svelte @@ -1,4 +1,4 @@ diff --git a/packages/builder/src/pages/[application]/design/[page]/[screen]/_layout.svelte b/packages/builder/src/pages/[application]/design/[page]/[screen]/_layout.svelte index 99434b7cd5..56884a4879 100644 --- a/packages/builder/src/pages/[application]/design/[page]/[screen]/_layout.svelte +++ b/packages/builder/src/pages/[application]/design/[page]/[screen]/_layout.svelte @@ -14,7 +14,7 @@ if (!validScreen) { // Go to main layout if URL set to invalid screen - store.actions.pages.select("main") + store.actions.layouts.select("main") $goto("../../main") } else { // Otherwise proceed to set screen @@ -23,7 +23,7 @@ // There are leftover stuff, like IDs, so navigate the components and find the ID and select it. if ($leftover) { // Get the correct screen children. - const screenChildren = $store.pages[$params.page]._screens.find( + const screenChildren = $store.layouts[$params.page]._screens.find( screen => screen._id === $params.screen || screen._id === decodeURIComponent($params.screen) @@ -37,7 +37,7 @@ // There are leftover stuff, like IDs, so navigate the components and find the ID and select it. if ($leftover) { - findComponent(componentIds, $store.pages[$params.page].props._children) + findComponent(componentIds, $store.layouts[$params.page].props._children) } } diff --git a/packages/builder/src/pages/[application]/design/[page]/_layout.svelte b/packages/builder/src/pages/[application]/design/[page]/_layout.svelte index d07a4c5695..45f577a0a8 100644 --- a/packages/builder/src/pages/[application]/design/[page]/_layout.svelte +++ b/packages/builder/src/pages/[application]/design/[page]/_layout.svelte @@ -2,7 +2,7 @@ import { params } from "@sveltech/routify" import { store } from "builderStore" - store.actions.pages.select($params.page) + store.actions.layouts.select($params.page) diff --git a/packages/builder/tests/generate_css.spec.js b/packages/builder/tests/generate_css.spec.js deleted file mode 100644 index 5fcdef25c1..0000000000 --- a/packages/builder/tests/generate_css.spec.js +++ /dev/null @@ -1,51 +0,0 @@ -import { - generate_css, - generate_screen_css, -} from "../src/builderStore/generate_css.js" - -describe("generate_css", () => { - - - test("Check how array styles are output", () => { - expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;") - }) - - test("Check handling of an array with empty string values", () => { - expect(generate_css({ padding: ["", "", "", ""] })).toBe("") - }) - - test("Check handling of an empty array", () => { - expect(generate_css({ margin: [] })).toBe("") - }) - - test("Check handling of valid font property", () => { - expect(generate_css({ "font-size": "10px" })).toBe("font-size: 10px;") - }) -}) - - -describe("generate_screen_css", () => { - const normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } } - - test("Test generation of normal css styles", () => { - expect(generate_screen_css([normalComponent])).toBe(".header-123-456 {\nfont-size: 16px;\n}") - }) - - const hoverComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {"font-size": "16px"}, active: {}, selected: {} } } - - test("Test generation of hover css styles", () => { - expect(generate_screen_css([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}") - }) - - const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } } - - test("Test generation of selection css styles", () => { - expect(generate_screen_css([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}") - }) - - const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } } - - test.only("Testing handling of empty component styles", () => { - expect(generate_screen_css([emptyComponent])).toBe("") - }) -}) \ No newline at end of file From 8a013c33c3c5c2dc03119b7fcdcae3c5f2c97832 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 25 Nov 2020 17:56:09 +0000 Subject: [PATCH 07/56] Major re-work, client library stills needs some work but it appears layouts and screens are no longer inter-dependent. --- .../src/builderStore/getNewComponentName.js | 8 +- packages/builder/src/builderStore/index.js | 10 +- .../src/builderStore/store/frontend.js | 188 +++++++++--------- .../builder/src/builderStore/storeUtils.js | 2 +- .../modals/CreateTableModal.svelte | 2 +- .../AppPreview/CurrentItemPreview.svelte | 8 +- .../AppPreview/iframeTemplate.js | 5 +- .../ComponentDropdownMenu.svelte | 2 +- .../ComponentTree.svelte | 2 +- .../ComponentNavigationTree/PathTree.svelte | 2 +- .../ScreenDropdownMenu.svelte | 9 - .../ComponentPropertiesPanel.svelte | 6 +- .../ComponentSelectionList.svelte | 2 +- .../ComponentsPaneSwitcher.svelte | 2 +- .../userInterface/FrontendNavigatePane.svelte | 13 +- .../{PageLayout.svelte => Layout.svelte} | 4 +- ...ponents.svelte => LayoutComponents.svelte} | 0 .../userInterface/LayoutsList.svelte | 7 +- .../userInterface/NewScreenModal.svelte | 2 +- .../userInterface/SettingsView.svelte | 21 +- .../createProps.js | 0 .../getRootComponent.js | 0 .../searchComponents.js | 0 .../splitRootComponentName.js | 0 .../{pagesParsing => assetParsing}/types.js | 0 .../pagesParsing/renameScreen.js | 60 ------ .../userInterface/temporaryPanelStructure.js | 2 +- packages/builder/src/constants/index.js | 4 +- .../automate/[automation]/_layout.svelte | 4 +- .../design/[page]/[screen]/_layout.svelte | 12 +- .../design/[page]/_layout.svelte | 2 +- .../[application]/design/[page]/index.svelte | 2 +- .../pages/[application]/design/_layout.svelte | 2 +- packages/builder/tests/createProps.spec.js | 2 +- .../tests/searchComponentsProps.spec.js | 2 +- packages/builder/yarn.lock | 170 +--------------- .../server/src/api/controllers/application.js | 6 +- packages/server/src/api/controllers/layout.js | 2 - packages/server/src/api/controllers/screen.js | 2 - .../src/api/controllers/static/index.js | 5 +- packages/server/src/api/routes/layout.js | 2 +- packages/server/src/constants/layouts.js | 5 +- .../utilities/builder/compileStaticAssets.js | 11 +- 43 files changed, 180 insertions(+), 410 deletions(-) rename packages/builder/src/components/userInterface/{PageLayout.svelte => Layout.svelte} (93%) rename packages/builder/src/components/userInterface/{PagesComponents.svelte => LayoutComponents.svelte} (100%) rename packages/builder/src/components/userInterface/{pagesParsing => assetParsing}/createProps.js (100%) rename packages/builder/src/components/userInterface/{pagesParsing => assetParsing}/getRootComponent.js (100%) rename packages/builder/src/components/userInterface/{pagesParsing => assetParsing}/searchComponents.js (100%) rename packages/builder/src/components/userInterface/{pagesParsing => assetParsing}/splitRootComponentName.js (100%) rename packages/builder/src/components/userInterface/{pagesParsing => assetParsing}/types.js (100%) delete mode 100644 packages/builder/src/components/userInterface/pagesParsing/renameScreen.js diff --git a/packages/builder/src/builderStore/getNewComponentName.js b/packages/builder/src/builderStore/getNewComponentName.js index a69bec21ad..1c156fd9f5 100644 --- a/packages/builder/src/builderStore/getNewComponentName.js +++ b/packages/builder/src/builderStore/getNewComponentName.js @@ -19,14 +19,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) } 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 aac2437560..b29792fa94 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -31,14 +31,8 @@ export const allScreens = derived(store, $store => { return $store.screens }) -export const currentScreens = derived(store, $store => { - const currentScreens = $store.layouts[currentAssetName]?._screens - if (currentScreens == null) { - return [] - } - return Array.isArray(currentScreens) - ? currentScreens - : Object.values(currentScreens) +export const mainLayout = derived(store, $store => { + return $store.layouts?.find(layout => layout.props?._id === "private-master-layout") }) export const initialise = async () => { diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 0523b6693f..37337cb98c 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -4,11 +4,11 @@ import { createProps, getBuiltin, makePropsSafe, -} from "components/userInterface/pagesParsing/createProps" -import { allScreens, backendUiStore, currentAsset } from "builderStore" +} from "components/userInterface/assetParsing/createProps" +import { allScreens, backendUiStore, currentAsset, mainLayout } from "builderStore" import { fetchComponentLibDefinitions } from "../loadComponentLibraries" import api from "../api" -import { DEFAULT_PAGES_OBJECT } from "../../constants" +import { DEFAULT_LAYOUTS } from "../../constants" import getNewComponentName from "../getNewComponentName" import analytics from "analytics" import { @@ -22,7 +22,7 @@ const INITIAL_FRONTEND_STATE = { apps: [], name: "", description: "", - layouts: DEFAULT_PAGES_OBJECT, + layouts: DEFAULT_LAYOUTS, screens: [], mainUi: {}, unauthenticatedUi: {}, @@ -68,16 +68,14 @@ export const getFrontendStore = () => { await backendUiStore.actions.database.select(pkg.application.instance) }, - selectPageOrScreen: type => { + selectAssetType: type => { store.update(state => { state.currentFrontEndType = type - const page = get(currentAsset) + const asset = get(currentAsset) - const pageOrScreen = type === "page" ? page : page._screens[0] - - state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null - state.currentPreviewItem = pageOrScreen + state.currentComponentInfo = asset && asset.props ? asset.props : null + state.currentPreviewItem = asset state.currentView = "detail" return state }) @@ -94,14 +92,16 @@ 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.currentAssetId = screenId state.currentView = "detail" - store.actions.screens.regenerateCssForCurrentScreen() + promise = store.actions.screens.regenerateCssForCurrentScreen() const safeProps = makePropsSafe( state.components[screen.props._component], screen.props @@ -110,44 +110,38 @@ export const getFrontendStore = () => { state.currentComponentInfo = safeProps return state }) + await promise }, create: async screen => { - let savePromise + let promises = [] store.update(state => { state.currentPreviewItem = screen state.currentComponentInfo = screen.props state.currentFrontEndType = "screen" if (state.currentPreviewItem) { - store.actions.screens.regenerateCss(state.currentPreviewItem) + promises.push(store.actions.screens.regenerateCss(state.currentPreviewItem)) } - savePromise = store.actions.screens.save(screen) + promises.push(store.actions.screens.save(screen)) return state }) - await savePromise + await Promise.all(promises) }, save: async screen => { - const page = get(currentAsset) - const currentPageScreens = page._screens - const creatingNewScreen = screen._id === undefined - - let savePromise - const response = await api.post(`/api/screens/${page._id}`, screen) + const response = await api.post(`/api/screens`, 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 @@ -158,107 +152,99 @@ export const getFrontendStore = () => { state.currentComponentInfo = safeProps screen.props = safeProps } - savePromise = store.actions.layouts.save() - return state }) - if (savePromise) await savePromise }, regenerateCss: async asset => { const response = await api.post("/api/css/generate", asset) - asset._css = await response.json() + asset._css = (await response.json())?.css }, - regenerateCssForCurrentScreen: () => { + regenerateCssForCurrentScreen: async () => { const { currentPreviewItem } = get(store) if (currentPreviewItem) { - store.actions.screens.regenerateCss(currentPreviewItem) + await store.actions.screens.regenerateCss(currentPreviewItem) } }, delete: async screens => { - let deletePromise - const screensToDelete = Array.isArray(screens) ? screens : [screens] + const screenDeletePromises = [] store.update(state => { - const currentPage = get(currentAsset) - 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 - ) - - deletePromise = api.delete( + state.screens = state.screens.filter(screen => screen._id !== screenToDelete._id) + 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) + if (state.currentFrontEndType !== "layout") { + await store.actions.screens.save(currentAsset) } - await store.actions.layouts.save() + await store.actions.layouts.save(currentAsset) }, }, layouts: { - select: pageName => { + select: async layoutName => { store.update(state => { - const currentPage = state.layouts[pageName] + const layout = store.actions.layouts.find(layoutName) - state.currentFrontEndType = "page" + state.currentFrontEndType = "layout" state.currentView = "detail" - state.currentAssetId = pageName + state.currentAssetId = layout._id // 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 + // it appears that the currentLayout sometimes has _props instead of props // why const safeProps = makePropsSafe( - state.components[currentPage.props._component], - currentPage.props + state.components[layout.props._component], + layout.props ) state.currentComponentInfo = safeProps - currentPage.props = safeProps - state.currentPreviewItem = state.layouts[pageName] - store.actions.screens.regenerateCssForCurrentScreen() - - for (let screen of get(allScreens)) { - screen._css = store.actions.screens.regenerateCss(screen) - } + layout.props = safeProps + state.currentPreviewItem = store.actions.layouts.find(layoutName) return state }) - }, - save: async page => { - const storeContents = get(store) - const pageName = storeContents.currentAssetId || "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 response = await api.post(`/api/layouts`, { + ...layout, }) const json = await response.json() - if (!json.ok) throw new Error("Error updating page") + if (!json.ok) throw new Error("Error updating layout") store.update(state => { - state.layouts[pageName]._rev = json.rev + const layoutToUpdate = state.layouts.find(stateLayouts => stateLayouts._id === layout._id) + if (layoutToUpdate) { + layoutToUpdate._rev = json.rev + } return state }) }, + find: layoutName => { + if (!layoutName) { + return get(mainLayout) + } + const storeContents = get(store) + return storeContents.layouts.find(layout => layout.name.toLowerCase() === layoutName.toLowerCase()) + }, }, components: { select: component => { @@ -274,6 +260,9 @@ export const getFrontendStore = () => { create: (componentToAdd, presetProps) => { 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 @@ -286,7 +275,7 @@ export const getFrontendStore = () => { if ( componentToAdd.startsWith("##") && - findSlot(state.layouts[state.currentAssetId].props._children) + findSlot(get(currentAsset)?.props._children) ) { return state } @@ -349,7 +338,8 @@ export const getFrontendStore = () => { return state }) }, - paste: (targetComponent, mode) => { + paste: async (targetComponent, mode) => { + let promises = [] store.update(state => { if (!state.componentToPaste) return state @@ -377,26 +367,29 @@ export const getFrontendStore = () => { 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) => { + updateStyle: async (type, name, value) => { + let promises = [] store.update(state => { if (!state.currentComponentInfo._styles) { state.currentComponentInfo._styles = {} } state.currentComponentInfo._styles[type][name] = value - store.actions.screens.regenerateCssForCurrentScreen() + 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 => { @@ -423,7 +416,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) @@ -438,11 +431,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.layouts.main, + layout, "@budibase/standard-components/Navigation" ) if (nav) { @@ -475,18 +469,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.currentAssetId = "main" - store.actions.screens.regenerateCss(state.layouts.main) - for (let screen of state.layouts.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.layouts.save() + promises.push(store.actions.layouts.save(layout)) } return state }) - await savePromise + await Promise.all(promises) }, }, }, diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js index 9c9d1ef940..7833477ff5 100644 --- a/packages/builder/src/builderStore/storeUtils.js +++ b/packages/builder/src/builderStore/storeUtils.js @@ -1,4 +1,4 @@ -import { getBuiltin } from "components/userInterface/pagesParsing/createProps" +import { getBuiltin } from "components/userInterface/assetParsing/createProps" import { uuid } from "./uuid" import getNewComponentName from "./getNewComponentName" diff --git a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte index d111821fb3..75b27f71d3 100644 --- a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte +++ b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte @@ -58,7 +58,7 @@ 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/userInterface/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte index 292718c665..e659edf2a1 100644 --- a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte @@ -1,6 +1,6 @@ diff --git a/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte b/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte index 07f0f042a1..f716b94351 100644 --- a/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte +++ b/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte @@ -16,15 +16,6 @@ const screenToDelete = $allScreens.find(scr => scr._id === screen) store.actions.screens.delete(screenToDelete) store.actions.routing.fetch() - // update the page if required - store.update(state => { - if (state.currentPreviewItem._id === screen) { - store.actions.layouts.select($store.currentPageName) - notifier.success(`Screen ${screenToDelete.name} deleted successfully.`) - $goto(`./:page/page-layout`) - } - return state - }) } diff --git a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte index 831864a89d..009596e0f3 100644 --- a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte +++ b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte @@ -58,7 +58,7 @@ return components } - function setPageOrScreenProp(name, value) { + function setAssetProps(name, value) { store.update(state => { if (name === "_instanceName" && state.currentFrontEndType === "screen") { state.currentPreviewItem.props[name] = value @@ -94,8 +94,8 @@ {panelDefinition} displayNameField={displayName} onChange={store.actions.components.updateProp} - onScreenPropChange={setPageOrScreenProp} - screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} /> + onScreenPropChange={setAssetProps} + assetInstance={$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 edce828f41..a6f1d6fea5 100644 --- a/packages/builder/src/components/userInterface/ComponentSelectionList.svelte +++ b/packages/builder/src/components/userInterface/ComponentSelectionList.svelte @@ -27,7 +27,7 @@ const onComponentChosen = component => { store.actions.components.create(component._component, component.presetProps) const path = store.actions.components.findRoute($store.currentComponentInfo) - $goto(`./:page/:screen/${path}`) + $goto(`./:screen/${path}`) close() } diff --git a/packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte b/packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte index 4e865b4514..8615a6016e 100644 --- a/packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte +++ b/packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte @@ -18,7 +18,7 @@
- {#if $store.currentFrontEndType === 'page' || $allScreens.length} + {#if $store.currentFrontEndType === "layout" || $allScreens.length}
- + + +{#if $store.currentFrontEndType === "layout" && $currentAsset} +{/if} diff --git a/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte b/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte index 0f2c30ff88..0b0cb44fa7 100644 --- a/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte +++ b/packages/builder/src/components/userInterface/ComponentNavigationTree/ScreenDropdownMenu.svelte @@ -3,7 +3,6 @@ import { store, allScreens } from "builderStore" import { notifier } from "builderStore/store/notifications" import ConfirmDialog from "components/common/ConfirmDialog.svelte" - import EditScreenLayoutModal from "./EditScreenLayoutModal.svelte" import { DropdownMenu, Modal, ModalContent } from "@budibase/bbui" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" @@ -19,14 +18,6 @@ store.actions.screens.delete(screen) store.actions.routing.fetch() } - - async function saveScreen() { - try { - await store.actions.screens.save(screen) - } catch (err) { - notifier.danger("Error saving page.") - } - }
diff --git a/packages/builder/src/components/userInterface/Layout.svelte b/packages/builder/src/components/userInterface/Layout.svelte index 7222ffd36a..7473e0bfec 100644 --- a/packages/builder/src/components/userInterface/Layout.svelte +++ b/packages/builder/src/components/userInterface/Layout.svelte @@ -2,6 +2,7 @@ import { goto } from "@sveltech/routify" import { FrontendTypes } from "constants" import ComponentTree from "./ComponentNavigationTree/ComponentTree.svelte" + import LayoutDropdownMenu from "./ComponentNavigationTree/LayoutDropdownMenu.svelte" import initDragDropStore from "./ComponentNavigationTree/dragDropStore" import NavItem from "components/common/NavItem.svelte" import { last } from "lodash/fp" @@ -28,7 +29,9 @@ withArrow selected={$store.currentComponentInfo?._id === layout.props._id} opened={$store.currentAssetId === layout._id} - on:click={selectLayout} /> + on:click={selectLayout}> + + {#if $store.currentAssetId === layout._id && layout.props._children} + + \ No newline at end of file From 40f609319847227fa2bb905d3e491cb1ec12feb4 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Sat, 5 Dec 2020 09:43:00 +0000 Subject: [PATCH 39/56] allow renaming of layouts --- .../src/builderStore/store/frontend.js | 18 ++++++---- .../LayoutDropdownMenu.svelte | 34 ++++++++++++++++--- .../components/userInterface/Layout.svelte | 2 +- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 8d432ae5d2..8ebfececc4 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -238,15 +238,21 @@ export const getFrontendStore = () => { if (!json.ok) throw new Error("Error updating layout") store.update(state => { - const layoutToUpdate = state.layouts.find( - stateLayout => stateLayout._id === layout._id + layoutToSave._rev = json.rev + layoutToSave._id = json.id + + const layoutIdx = state.layouts.findIndex( + stateLayout => stateLayout._id === layoutToSave._id ) - if (layoutToUpdate) { - layoutToUpdate._rev = json.rev + + if (layoutIdx >= 0) { + // update existing layout + state.layouts.splice(layoutIdx, 1, layoutToSave) } else { - // TODO: when a new layout is created - state.layouts.push({}) + // save new layout + state.layouts.push(layoutToSave) } + return state }) }, diff --git a/packages/builder/src/components/userInterface/ComponentNavigationTree/LayoutDropdownMenu.svelte b/packages/builder/src/components/userInterface/ComponentNavigationTree/LayoutDropdownMenu.svelte index d5a0d411a9..1767cae54c 100644 --- a/packages/builder/src/components/userInterface/ComponentNavigationTree/LayoutDropdownMenu.svelte +++ b/packages/builder/src/components/userInterface/ComponentNavigationTree/LayoutDropdownMenu.svelte @@ -3,16 +3,17 @@ import { store } from "builderStore" import { notifier } from "builderStore/store/notifications" import ConfirmDialog from "components/common/ConfirmDialog.svelte" - import { DropdownMenu, Modal, ModalContent } from "@budibase/bbui" + import { DropdownMenu, Modal, ModalContent, Input } from "@budibase/bbui" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" + import { cloneDeep } from "lodash/fp" - export let layoutId + export let layout let confirmDeleteDialog + let editLayoutNameModal let dropdown let anchor - - $: layout = $store.layouts.find(layout => layout._id === layoutId) + let name = layout.name const deleteLayout = async () => { try { @@ -22,6 +23,17 @@ notifier.danger(`Error deleting layout: ${err.message}`) } } + + const saveLayout = async () => { + try { + const layoutToSave = cloneDeep(layout) + layoutToSave.name = name + await store.actions.layouts.save(layoutToSave) + notifier.success(`Layout saved successfully.`) + } catch (err) { + notifier.danger(`Error saving layout: ${err.message}`) + } + }
@@ -34,6 +46,10 @@ icon="ri-delete-bin-line" title="Delete" on:click={() => confirmDeleteDialog.show()} /> + editLayoutNameModal.show()} />
@@ -44,6 +60,16 @@ okText="Delete Layout" onOk={deleteLayout} /> + + + + + +