Merge branch 'feature/security-update' of github.com:Budibase/budibase into feature/self-hosting
This commit is contained in:
commit
3c2ca11d31
|
@ -83,19 +83,19 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
select: async screenId => {
|
select: screenId => {
|
||||||
let promise
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const screen = get(allScreens).find(screen => screen._id === screenId)
|
const screens = get(allScreens)
|
||||||
|
let selectedScreen = screens.find(screen => screen._id === screenId)
|
||||||
|
if (!selectedScreen) {
|
||||||
|
selectedScreen = screens[0]
|
||||||
|
}
|
||||||
state.currentFrontEndType = FrontendTypes.SCREEN
|
state.currentFrontEndType = FrontendTypes.SCREEN
|
||||||
state.currentAssetId = screenId
|
state.currentAssetId = selectedScreen._id
|
||||||
state.currentView = "detail"
|
state.currentView = "detail"
|
||||||
|
state.selectedComponentId = selectedScreen.props._id
|
||||||
promise = store.actions.screens.regenerateCss(screen)
|
|
||||||
state.selectedComponentId = screen.props._id
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
await promise
|
|
||||||
},
|
},
|
||||||
create: async screen => {
|
create: async screen => {
|
||||||
screen = await store.actions.screens.save(screen)
|
screen = await store.actions.screens.save(screen)
|
||||||
|
@ -133,16 +133,6 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
return screen
|
return screen
|
||||||
},
|
},
|
||||||
regenerateCss: async asset => {
|
|
||||||
const response = await api.post("/api/css/generate", asset)
|
|
||||||
asset._css = (await response.json())?.css
|
|
||||||
},
|
|
||||||
regenerateCssForCurrentScreen: async () => {
|
|
||||||
const asset = get(currentAsset)
|
|
||||||
if (asset) {
|
|
||||||
await store.actions.screens.regenerateCss(asset)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
delete: async screens => {
|
delete: async screens => {
|
||||||
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
||||||
|
|
||||||
|
@ -176,29 +166,18 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
layouts: {
|
layouts: {
|
||||||
select: async layoutId => {
|
select: layoutId => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const layout = store.actions.layouts.find(layoutId)
|
const layout = store.actions.layouts.find(layoutId)
|
||||||
|
|
||||||
state.currentFrontEndType = FrontendTypes.LAYOUT
|
state.currentFrontEndType = FrontendTypes.LAYOUT
|
||||||
state.currentView = "detail"
|
state.currentView = "detail"
|
||||||
|
|
||||||
state.currentAssetId = layout._id
|
state.currentAssetId = layout._id
|
||||||
state.selectedComponentId = layout.props._id
|
state.selectedComponentId = layout.props._id
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
let cssPromises = []
|
|
||||||
cssPromises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
|
||||||
|
|
||||||
for (let screen of get(allScreens)) {
|
|
||||||
cssPromises.push(store.actions.screens.regenerateCss(screen))
|
|
||||||
}
|
|
||||||
await Promise.all(cssPromises)
|
|
||||||
},
|
},
|
||||||
save: async layout => {
|
save: async layout => {
|
||||||
const layoutToSave = cloneDeep(layout)
|
const layoutToSave = cloneDeep(layout)
|
||||||
delete layoutToSave._css
|
|
||||||
|
|
||||||
const response = await api.post(`/api/layouts`, layoutToSave)
|
const response = await api.post(`/api/layouts`, layoutToSave)
|
||||||
|
|
||||||
|
@ -368,7 +347,6 @@ export const getFrontendStore = () => {
|
||||||
const index = mode === "above" ? targetIndex : targetIndex + 1
|
const index = mode === "above" ? targetIndex : targetIndex + 1
|
||||||
parent._children.splice(index, 0, cloneDeep(componentToPaste))
|
parent._children.splice(index, 0, cloneDeep(componentToPaste))
|
||||||
|
|
||||||
promises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
|
||||||
promises.push(store.actions.preview.saveSelected())
|
promises.push(store.actions.preview.saveSelected())
|
||||||
store.actions.components.select(componentToPaste)
|
store.actions.components.select(componentToPaste)
|
||||||
|
|
||||||
|
@ -386,8 +364,6 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
selected._styles[type][name] = value
|
selected._styles[type][name] = value
|
||||||
|
|
||||||
promises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
|
||||||
|
|
||||||
// save without messing with the store
|
// save without messing with the store
|
||||||
promises.push(store.actions.preview.saveSelected())
|
promises.push(store.actions.preview.saveSelected())
|
||||||
return state
|
return state
|
||||||
|
@ -472,13 +448,9 @@ export const getFrontendStore = () => {
|
||||||
}).props
|
}).props
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save layout and regenerate all CSS because otherwise weird things happen
|
// Save layout
|
||||||
nav._children = [...nav._children, newLink]
|
nav._children = [...nav._children, newLink]
|
||||||
state.currentAssetId = layout._id
|
state.currentAssetId = layout._id
|
||||||
promises.push(store.actions.screens.regenerateCss(layout))
|
|
||||||
for (let screen of get(allScreens)) {
|
|
||||||
promises.push(store.actions.screens.regenerateCss(screen))
|
|
||||||
}
|
|
||||||
promises.push(store.actions.layouts.save(layout))
|
promises.push(store.actions.layouts.save(layout))
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
|
|
|
@ -56,6 +56,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
// Reset selection every time data changes
|
||||||
|
selectedRows = []
|
||||||
|
|
||||||
let result = []
|
let result = []
|
||||||
if (allowEditing) {
|
if (allowEditing) {
|
||||||
result = [
|
result = [
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import CategoryTab from "./CategoryTab.svelte"
|
import CategoryTab from "./CategoryTab.svelte"
|
||||||
import DesignView from "./DesignView.svelte"
|
import DesignView from "./DesignView.svelte"
|
||||||
import SettingsView from "./SettingsView.svelte"
|
import SettingsView from "./SettingsView.svelte"
|
||||||
|
import { setWith } from "lodash"
|
||||||
|
|
||||||
let flattenedPanel = flattenComponents(panelStructure.categories)
|
let flattenedPanel = flattenComponents(panelStructure.categories)
|
||||||
let categories = [
|
let categories = [
|
||||||
|
@ -69,7 +70,7 @@
|
||||||
) {
|
) {
|
||||||
selectedAsset.props._instanceName = value
|
selectedAsset.props._instanceName = value
|
||||||
} else {
|
} else {
|
||||||
selectedAsset[name] = value
|
setWith(selectedAsset, name.split("."), value, Object)
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { get } from "lodash"
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
import { FrontendTypes } from "constants"
|
import { FrontendTypes } from "constants"
|
||||||
import PropertyControl from "./PropertyControl.svelte"
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
|
@ -16,7 +17,13 @@
|
||||||
export let displayNameField = false
|
export let displayNameField = false
|
||||||
export let assetInstance
|
export let assetInstance
|
||||||
|
|
||||||
let assetProps = ["title", "description", "route", "layoutId"]
|
let assetProps = [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"routing.route",
|
||||||
|
"layoutId",
|
||||||
|
"routing.roleId",
|
||||||
|
]
|
||||||
let duplicateName = false
|
let duplicateName = false
|
||||||
|
|
||||||
const propExistsOnComponentDef = prop =>
|
const propExistsOnComponentDef = prop =>
|
||||||
|
@ -28,8 +35,9 @@
|
||||||
|
|
||||||
const screenDefinition = [
|
const screenDefinition = [
|
||||||
{ key: "description", label: "Description", control: Input },
|
{ key: "description", label: "Description", control: Input },
|
||||||
{ key: "route", label: "Route", control: Input },
|
{ key: "routing.route", label: "Route", control: Input },
|
||||||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||||
|
{ key: "routing.roleId", label: "Role", control: Input },
|
||||||
]
|
]
|
||||||
|
|
||||||
const layoutDefinition = [{ key: "title", label: "Title", control: Input }]
|
const layoutDefinition = [{ key: "title", label: "Title", control: Input }]
|
||||||
|
@ -92,7 +100,7 @@
|
||||||
control={def.control}
|
control={def.control}
|
||||||
label={def.label}
|
label={def.label}
|
||||||
key={def.key}
|
key={def.key}
|
||||||
value={assetInstance[def.key]}
|
value={get(assetInstance, def.key)}
|
||||||
onChange={onScreenPropChange}
|
onChange={onScreenPropChange}
|
||||||
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -32,7 +32,6 @@ const {
|
||||||
} = require("../../constants/screens")
|
} = require("../../constants/screens")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
const { recurseMustache } = require("../../utilities/mustache")
|
const { recurseMustache } = require("../../utilities/mustache")
|
||||||
const { generateAssetCss } = require("../../utilities/builder/generateCss")
|
|
||||||
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
@ -131,12 +130,6 @@ exports.fetchAppPackage = async function(ctx) {
|
||||||
const application = await db.get(ctx.params.appId)
|
const application = await db.get(ctx.params.appId)
|
||||||
const [layouts, screens] = await Promise.all([getLayouts(db), 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 = {
|
ctx.body = {
|
||||||
application,
|
application,
|
||||||
screens,
|
screens,
|
||||||
|
@ -230,10 +223,6 @@ const createEmptyAppPackage = async (ctx, app) => {
|
||||||
screensAndLayouts.push(loginScreen)
|
screensAndLayouts.push(loginScreen)
|
||||||
|
|
||||||
await db.bulkDocs(screensAndLayouts)
|
await db.bulkDocs(screensAndLayouts)
|
||||||
// at the end add CSS to all the structures
|
await compileStaticAssets(app._id)
|
||||||
for (let asset of screensAndLayouts) {
|
|
||||||
asset._css = generateAssetCss([asset.props])
|
|
||||||
}
|
|
||||||
await compileStaticAssets(app._id, screensAndLayouts)
|
|
||||||
return newAppFolder
|
return newAppFolder
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ const { join } = require("../../../utilities/centralPath")
|
||||||
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
||||||
const PouchDB = require("../../../db")
|
const PouchDB = require("../../../db")
|
||||||
|
|
||||||
const EXCLUDED_DIRECTORIES = ["css"]
|
|
||||||
|
|
||||||
const CONTENT_TYPE_MAP = {
|
const CONTENT_TYPE_MAP = {
|
||||||
html: "text/html",
|
html: "text/html",
|
||||||
css: "text/css",
|
css: "text/css",
|
||||||
|
@ -44,12 +42,6 @@ exports.deployToObjectStore = async function(appId, objectClient, metadata) {
|
||||||
// Upload HTML, CSS and JS for each page of the web app
|
// Upload HTML, CSS and JS for each page of the web app
|
||||||
walkDir(appAssetsPath, function(filePath) {
|
walkDir(appAssetsPath, function(filePath) {
|
||||||
const filePathParts = filePath.split("/")
|
const filePathParts = filePath.split("/")
|
||||||
const publicIndex = filePathParts.indexOf("public")
|
|
||||||
const directory = filePathParts[publicIndex + 1]
|
|
||||||
// don't include these top level directories
|
|
||||||
if (EXCLUDED_DIRECTORIES.indexOf(directory) !== -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const appAssetUpload = exports.prepareUpload({
|
const appAssetUpload = exports.prepareUpload({
|
||||||
file: {
|
file: {
|
||||||
path: filePath,
|
path: filePath,
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
||||||
const { AccessController } = require("../../utilities/security/roles")
|
const { AccessController } = require("../../utilities/security/roles")
|
||||||
const { generateAssetCss } = require("../../utilities/builder/generateCss")
|
|
||||||
const compileStaticAssets = require("../../utilities/builder/compileStaticAssets")
|
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
const appId = ctx.user.appId
|
const appId = ctx.user.appId
|
||||||
|
@ -32,10 +30,6 @@ exports.save = async ctx => {
|
||||||
}
|
}
|
||||||
const response = await db.put(screen)
|
const response = await db.put(screen)
|
||||||
|
|
||||||
// update CSS so client doesn't need to make a call directly after
|
|
||||||
screen._css = generateAssetCss([screen.props])
|
|
||||||
await compileStaticAssets(appId, screen)
|
|
||||||
|
|
||||||
ctx.message = `Screen ${screen.name} saved.`
|
ctx.message = `Screen ${screen.name} saved.`
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
...screen,
|
...screen,
|
||||||
|
|
|
@ -16,19 +16,10 @@ const CouchDB = require("../../../db")
|
||||||
const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
|
const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
|
||||||
const fileProcessor = require("../../../utilities/fileProcessor")
|
const fileProcessor = require("../../../utilities/fileProcessor")
|
||||||
const env = require("../../../environment")
|
const env = require("../../../environment")
|
||||||
const { generateAssetCss } = require("../../../utilities/builder/generateCss")
|
|
||||||
const compileStaticAssets = require("../../../utilities/builder/compileStaticAssets")
|
|
||||||
|
|
||||||
// this was the version before we started versioning the component library
|
// this was the version before we started versioning the component library
|
||||||
const COMP_LIB_BASE_APP_VERSION = "0.2.5"
|
const COMP_LIB_BASE_APP_VERSION = "0.2.5"
|
||||||
|
|
||||||
exports.generateCss = async function(ctx) {
|
|
||||||
const structure = ctx.request.body
|
|
||||||
structure._css = generateAssetCss([structure.props])
|
|
||||||
await compileStaticAssets(ctx.appId, structure)
|
|
||||||
ctx.body = { css: structure._css }
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.serveBuilder = async function(ctx) {
|
exports.serveBuilder = async function(ctx) {
|
||||||
let builderPath = resolve(__dirname, "../../../../builder")
|
let builderPath = resolve(__dirname, "../../../../builder")
|
||||||
if (ctx.file === "index.html") {
|
if (ctx.file === "index.html") {
|
||||||
|
|
|
@ -22,13 +22,7 @@
|
||||||
|
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="icon" type="image/png" href={favicon} />
|
<link rel="icon" type="image/png" href={favicon} />
|
||||||
|
|
||||||
<link rel="stylesheet" href={publicPath('bundle.css')} />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Roboto+Mono" />
|
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -10,7 +10,6 @@ const router = Router()
|
||||||
function generateSaveValidation() {
|
function generateSaveValidation() {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return joiValidator.body(Joi.object({
|
return joiValidator.body(Joi.object({
|
||||||
_css: Joi.string().allow(""),
|
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
routing: Joi.object({
|
routing: Joi.object({
|
||||||
route: Joi.string().required(),
|
route: Joi.string().required(),
|
||||||
|
|
|
@ -5,22 +5,6 @@ const env = require("../../environment")
|
||||||
const authorized = require("../../middleware/authorized")
|
const authorized = require("../../middleware/authorized")
|
||||||
const { BUILDER } = require("../../utilities/security/permissions")
|
const { BUILDER } = require("../../utilities/security/permissions")
|
||||||
const usage = require("../../middleware/usageQuota")
|
const usage = require("../../middleware/usageQuota")
|
||||||
const joiValidator = require("../../middleware/joi-validator")
|
|
||||||
const Joi = require("joi")
|
|
||||||
|
|
||||||
function generateCssValidator() {
|
|
||||||
return joiValidator.body(
|
|
||||||
Joi.object({
|
|
||||||
_id: Joi.string().required(),
|
|
||||||
_rev: Joi.string().required(),
|
|
||||||
props: Joi.object()
|
|
||||||
.required()
|
|
||||||
.unknown(true),
|
|
||||||
})
|
|
||||||
.required()
|
|
||||||
.unknown(true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
|
@ -40,12 +24,6 @@ if (env.NODE_ENV !== "production") {
|
||||||
}
|
}
|
||||||
|
|
||||||
router
|
router
|
||||||
.post(
|
|
||||||
"/api/css/generate",
|
|
||||||
authorized(BUILDER),
|
|
||||||
generateCssValidator(),
|
|
||||||
controller.generateCss
|
|
||||||
)
|
|
||||||
.post(
|
.post(
|
||||||
"/api/attachments/process",
|
"/api/attachments/process",
|
||||||
authorized(BUILDER),
|
authorized(BUILDER),
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
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("")
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,66 +1,18 @@
|
||||||
const {
|
const { ensureDir, constants, copyFile } = require("fs-extra")
|
||||||
ensureDir,
|
|
||||||
constants,
|
|
||||||
copyFile,
|
|
||||||
writeFile,
|
|
||||||
readdir,
|
|
||||||
readFile,
|
|
||||||
existsSync,
|
|
||||||
} = require("fs-extra")
|
|
||||||
const { join } = require("../centralPath")
|
const { join } = require("../centralPath")
|
||||||
const { budibaseAppsDir } = require("../budibaseDir")
|
const { budibaseAppsDir } = require("../budibaseDir")
|
||||||
|
|
||||||
const CSS_DIRECTORY = "css"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile all the non-db static web assets that are required for the running of
|
* 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
|
* a budibase application. This includes the JSON structure of the DOM and
|
||||||
* the client library, a script responsible for reading the JSON structure
|
* the client library, a script responsible for reading the JSON structure
|
||||||
* and rendering the application.
|
* 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, assets) => {
|
module.exports = async appId => {
|
||||||
const publicPath = join(budibaseAppsDir(), appId, "public")
|
const publicPath = join(budibaseAppsDir(), appId, "public")
|
||||||
await ensureDir(publicPath)
|
await ensureDir(publicPath)
|
||||||
for (let asset of Array.isArray(assets) ? assets : [assets]) {
|
await copyClientLib(publicPath)
|
||||||
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 <appId>/public/bundle.css
|
|
||||||
* @param {String} publicPath - path to the public assets directory of the budibase application
|
|
||||||
* @param {Object} asset a single screen or screen layout which is being updated
|
|
||||||
*/
|
|
||||||
const buildCssBundle = async (publicPath, asset) => {
|
|
||||||
const cssPath = join(publicPath, CSS_DIRECTORY)
|
|
||||||
let cssString = ""
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bundle up all the CSS in the directory into one top level CSS file
|
|
||||||
if (existsSync(cssPath)) {
|
|
||||||
const cssFiles = await readdir(cssPath)
|
|
||||||
for (let filename of cssFiles) {
|
|
||||||
const css = await readFile(join(cssPath, filename))
|
|
||||||
cssString += css
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile(join(publicPath, "bundle.css"), cssString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
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}`
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue