From 5eec4d7a4766bd97e894fd54a098e9e05116e001 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 24 Nov 2020 18:11:18 +0000 Subject: [PATCH] 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}` + } +}