diff --git a/packages/builder/src/components/common/Inputs/InputGroup.svelte b/packages/builder/src/components/common/Inputs/InputGroup.svelte index 3be9f17b16..385623ca19 100644 --- a/packages/builder/src/components/common/Inputs/InputGroup.svelte +++ b/packages/builder/src/components/common/Inputs/InputGroup.svelte @@ -10,18 +10,19 @@ export let onChange = () => {} function handleChange(val, idx) { - value.splice(idx, 1, val !== "auto" ? val + suffix : val) + value.splice(idx, 1, val !== "auto" && suffix ? val + suffix : val) value = value let _value = value.map(v => - !v.endsWith(suffix) && v !== "auto" ? v + suffix : v + suffix && !v.endsWith(suffix) && v !== "auto" ? v + suffix : v ) onChange(_value) } - $: displayValues = value - ? value.map(v => v.replace(new RegExp(`${suffix}$`), "")) - : [] + $: displayValues = + value && suffix + ? value.map(v => v.replace(new RegExp(`${suffix}$`), "")) + : value || []
diff --git a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte index a7288c8c91..00d7e6c171 100644 --- a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte @@ -21,14 +21,47 @@ return componentName || "element" } - $: iframe && - console.log( - iframe.contentDocument.head.insertAdjacentHTML( - "beforeend", - `<\style>` - ) - ) + const screenPlaceholder = { + name: "Screen Placeholder", + route: "*", + props: { + _component: "@budibase/standard-components/container", + type: "div", + _children: [ + { + _component: "@budibase/standard-components/container", + _styles: { normal: {}, hover: {}, active: {}, selected: {} }, + _id: "__screenslot__text", + _code: "", + className: "", + onLoad: [], + type: "div", + _children: [ + { + _component: "@budibase/standard-components/text", + _styles: { + normal: {}, + hover: {}, + active: {}, + selected: {}, + }, + _id: "__screenslot__text_2", + _code: "", + text: "content", + font: "", + color: "", + textAlign: "inline", + verticalAlign: "inline", + formattingTag: "none", + }, + ], + }, + ], + }, + } + $: hasComponent = !!$store.currentPreviewItem + $: { styles = "" // Apply the CSS from the currently selected page and its screens @@ -52,49 +85,12 @@ $: frontendDefinition = { appId: $store.appId, libraries: $store.libraries, - page: $store.currentPreviewItem, - screens: screensExist - ? $store.currentPreviewItem._screens - : [ - { - name: "Screen Placeholder", - route: "*", - props: { - _component: "@budibase/standard-components/container", - type: "div", - _children: [ - { - _component: "@budibase/standard-components/container", - _styles: { normal: {}, hover: {}, active: {}, selected: {} }, - _id: "__screenslot__text", - _code: "", - className: "", - onLoad: [], - type: "div", - _children: [ - { - _component: "@budibase/standard-components/text", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - _id: "__screenslot__text_2", - _code: "", - text: "content", - font: "", - color: "", - textAlign: "inline", - verticalAlign: "inline", - formattingTag: "none", - }, - ], - }, - ], - }, - }, - ], + page: $store.pages[$store.currentPageName], + screens: [ + $store.currentFrontEndType === "page" + ? screenPlaceholder + : $store.currentPreviewItem, + ], appRootPath: "", } @@ -103,6 +99,27 @@ $: selectedComponentId = $store.currentComponentInfo ? $store.currentComponentInfo._id : "" + + const refreshContent = () => { + iframe.contentWindow.postMessage( + JSON.stringify({ + styles, + stylesheetLinks, + selectedComponentType, + selectedComponentId, + frontendDefinition, + }) + ) + } + + $: if (iframe) + iframe.contentWindow.addEventListener("bb-ready", refreshContent, { + once: true, + }) + + $: if (iframe && frontendDefinition) { + refreshContent() + }
@@ -111,13 +128,7 @@ style="height: 100%; width: 100%" title="componentPreview" bind:this={iframe} - srcdoc={iframeTemplate({ - styles, - stylesheetLinks, - selectedComponentType, - selectedComponentId, - frontendDefinition: JSON.stringify(frontendDefinition), - })} /> + srcdoc={iframeTemplate} /> {/if}
diff --git a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js index 462becb32f..3d305f1c1d 100644 --- a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js @@ -1,20 +1,6 @@ -export default ({ - styles, - stylesheetLinks, - selectedComponentType, - selectedComponentId, - frontendDefinition, -}) => ` +export default ` - ${stylesheetLinks} - diff --git a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte index 60279aa9a1..7d33b9c963 100644 --- a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte +++ b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte @@ -38,15 +38,30 @@ c => c._component === componentInstance._component ) || {} - $: panelDefinition = componentPropDefinition.properties - ? componentPropDefinition.properties[selectedCategory.value] - : {} + let panelDefinition = {} - // SCREEN PROPS ============================================= - $: screen_props = - $store.currentFrontEndType === "page" - ? getProps($store.currentPreviewItem, ["name", "favicon"]) - : getProps($store.currentPreviewItem, ["name", "description", "route"]) + $: { + if (componentPropDefinition.properties) { + if (selectedCategory.value === "design") { + panelDefinition = componentPropDefinition.properties["design"] + } else { + let panelDef = componentPropDefinition.properties["settings"] + if ( + $store.currentFrontEndType === "page" && + $store.currentView !== "component" + ) { + panelDefinition = [...page, ...panelDef] + } else if ( + $store.currentFrontEndType === "screen" && + $store.currentView !== "component" + ) { + panelDefinition = [...screen, ...panelDef] + } else { + panelDefinition = panelDef + } + } + } + } const onStyleChanged = store.setComponentStyle const onPropChanged = store.setComponentProp @@ -106,12 +121,10 @@ height: 100%; display: flex; flex-direction: column; - /* Merge Check */ overflow-x: hidden; overflow-y: hidden; padding: 20px; box-sizing: border-box; - /* Merge Check */ } .title > div:nth-child(1) { diff --git a/packages/builder/src/components/userInterface/propertyCategories.js b/packages/builder/src/components/userInterface/propertyCategories.js index 73764404dd..c0485304ce 100644 --- a/packages/builder/src/components/userInterface/propertyCategories.js +++ b/packages/builder/src/components/userInterface/propertyCategories.js @@ -12,8 +12,9 @@ export const layout = [ label: "Display", key: "display", control: OptionSelect, - initialValue: "Flex", + initialValue: "", options: [ + { label: "", value: "" }, { label: "Flex", value: "flex" }, { label: "Inline Flex", value: "inline-flex" }, ], @@ -39,6 +40,7 @@ export const layout = [ control: OptionSelect, initialValue: "Flex Start", options: [ + { label: "", value: "" }, { label: "Flex Start", value: "flex-start" }, { label: "Flex End", value: "flex-end" }, { label: "Center", value: "center" }, @@ -84,7 +86,6 @@ export const spacing = [ key: "margin", control: InputGroup, meta: spacingMeta, - suffix: "px", defaultValue: ["0", "0", "0", "0"], }, { @@ -92,7 +93,6 @@ export const spacing = [ key: "padding", control: InputGroup, meta: spacingMeta, - suffix: "px", defaultValue: ["0", "0", "0", "0"], }, ] @@ -103,7 +103,6 @@ export const size = [ key: "width", control: Input, placeholder: "px", - suffix: "px", width: "48px", textAlign: "center", }, @@ -112,7 +111,6 @@ export const size = [ key: "height", control: Input, placeholder: "px", - suffix: "px", width: "48px", textAlign: "center", }, @@ -121,7 +119,6 @@ export const size = [ key: "min-width", control: Input, placeholder: "px", - suffix: "px", width: "48px", textAlign: "center", }, @@ -129,7 +126,6 @@ export const size = [ label: "Min H", key: "min-height", control: Input, - suffix: "px", placeholder: "px", width: "48px", textAlign: "center", @@ -139,7 +135,6 @@ export const size = [ key: "max-width", control: Input, placeholder: "px", - suffix: "px", width: "48px", textAlign: "center", }, @@ -148,7 +143,6 @@ export const size = [ key: "max-height", control: Input, placeholder: "px", - suffix: "px", width: "48px", textAlign: "center", }, @@ -325,19 +319,31 @@ export const border = [ { label: "Radius", key: "border-radius", - control: Input, - width: "48px", - placeholder: "px", - textAlign: "center", + control: OptionSelect, + defaultValue: "None", + options: [ + { label: "None", value: "0" }, + { label: "small", value: "0.125rem" }, + { label: "Medium", value: "0.25rem;" }, + { label: "Large", value: "0.375rem" }, + { label: "Extra large", value: "0.5rem" }, + { label: "Full", value: "5678px" }, + ], }, { label: "Width", key: "border-width", - control: Input, - width: "48px", - placeholder: "px", - textAlign: "center", - }, //custom + control: OptionSelect, + defaultValue: "None", + options: [ + { label: "None", value: "0" }, + { label: "Extra small", value: "0.5px" }, + { label: "Small", value: "1px" }, + { label: "Medium", value: "2px" }, + { label: "Large", value: "4px" }, + { label: "Extra large", value: "8px" }, + ], + }, { label: "Color", key: "border-color", @@ -347,6 +353,7 @@ export const border = [ label: "Style", key: "border-style", control: OptionSelect, + defaultValue: "None", options: [ "none", "hidden", @@ -373,17 +380,50 @@ export const effects = [ }, { label: "Rotate", - key: "transform", - control: Input, - width: "48px", - textAlign: "center", - placeholder: "deg", + key: "transform-rotate", + control: OptionSelect, + defaultValue: "0", + options: [ + "0", + "45deg", + "90deg", + "90deg", + "135deg", + "180deg", + "225deg", + "270deg", + "315dev", + ], }, //needs special control { label: "Shadow", key: "box-shadow", - control: InputGroup, - meta: [{ placeholder: "X" }, { placeholder: "Y" }, { placeholder: "B" }], + control: OptionSelect, + defaultValue: "None", + options: [ + { label: "None", value: "none" }, + { label: "Extra small", value: "0 1px 2px 0 rgba(0, 0, 0, 0.05)" }, + { + label: "Small", + value: + "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)", + }, + { + label: "Medium", + value: + "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", + }, + { + label: "Large", + value: + "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)", + }, + { + label: "Extra large", + value: + "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", + }, + ], }, ] diff --git a/packages/builder/src/components/userInterface/temporaryPanelStructure.js b/packages/builder/src/components/userInterface/temporaryPanelStructure.js index 603e348e18..afd3a06694 100644 --- a/packages/builder/src/components/userInterface/temporaryPanelStructure.js +++ b/packages/builder/src/components/userInterface/temporaryPanelStructure.js @@ -187,6 +187,17 @@ export default { ], }, }, + { + _component: "@budibase/standard-components/image", + name: "Image", + description: "A basic component for displaying images", + icon: "ri-image-fill", + children: [], + properties: { + design: { ...all }, + settings: [{ label: "URL", key: "url", control: Input }], + }, + }, { _component: "@budibase/standard-components/icon", name: "Icon", diff --git a/packages/builder/src/pages/[application]/_reset.svelte b/packages/builder/src/pages/[application]/_reset.svelte index 2eec749c8f..4d2a708f1e 100644 --- a/packages/builder/src/pages/[application]/_reset.svelte +++ b/packages/builder/src/pages/[application]/_reset.svelte @@ -62,7 +62,7 @@ (location = `/${application}`)}> + on:click={() => window.open(`/${application}`)}>
diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 9bd015c86a..0f604be0e7 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -11,7 +11,7 @@ const { exec } = require("child_process") const sqrl = require("squirrelly") exports.fetch = async function(ctx) { - const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) + const db = new CouchDB(ClientDb.name(getClientId(ctx))) const body = await db.query("client/by_type", { include_docs: true, key: ["app"], @@ -21,16 +21,30 @@ exports.fetch = async function(ctx) { } exports.fetchAppPackage = async function(ctx) { - const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) + const clientId = await lookupClientId(ctx.params.applicationId) + const db = new CouchDB(ClientDb.name(clientId)) const application = await db.get(ctx.params.applicationId) ctx.body = await getPackageForBuilder(ctx.config, application) } exports.create = async function(ctx) { - const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) + const clientId = + (ctx.request.body && ctx.request.body.clientId) || env.CLIENT_ID + + if (!clientId) { + ctx.throw(400, "ClientId not suplied") + } + const appId = newid() + // insert an appId -> clientId lookup + const masterDb = new CouchDB("clientAppLookup") + await masterDb.put({ + _id: appId, + clientId, + }) + const db = new CouchDB(ClientDb.name(clientId)) const newApplication = { - _id: newid(), + _id: appId, type: "app", instances: [], userInstanceMap: {}, @@ -47,11 +61,10 @@ exports.create = async function(ctx) { const createInstCtx = { params: { - clientId: env.CLIENT_ID, applicationId: newApplication._id, }, request: { - body: { name: `dev-${env.CLIENT_ID}` }, + body: { name: `dev-${clientId}` }, }, } await instanceController.create(createInstCtx) @@ -61,6 +74,7 @@ exports.create = async function(ctx) { await runNpmInstall(newAppFolder) } + ctx.status = 200 ctx.body = newApplication ctx.message = `Application ${ctx.request.body.name} created successfully` } @@ -79,7 +93,6 @@ const createEmptyAppPackage = async (ctx, app) => { if (await exists(newAppFolder)) { ctx.throw(400, "App folder already exists for this application") - return } await copy(templateFolder, newAppFolder) @@ -99,6 +112,24 @@ const createEmptyAppPackage = async (ctx, app) => { return newAppFolder } +const lookupClientId = async appId => { + const masterDb = new CouchDB("clientAppLookup") + const { clientId } = await masterDb.get(appId) + return clientId +} + +const getClientId = ctx => { + const clientId = + (ctx.request.body && ctx.request.body.clientId) || + (ctx.query && ctx.query.clientId) || + env.CLIENT_ID + + if (!clientId) { + ctx.throw(400, "ClientId not suplied") + } + return clientId +} + const updateJsonFile = async (filePath, app) => { const json = await readFile(filePath, "utf8") const newJson = sqrl.Render(json, app) diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index de88c29643..5ec3aea617 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -2,7 +2,6 @@ const jwt = require("jsonwebtoken") const CouchDB = require("../../db") const ClientDb = require("../../db/clientDb") const bcrypt = require("../../utilities/bcrypt") -const env = require("../../environment") exports.authenticate = async ctx => { const { username, password } = ctx.request.body @@ -10,8 +9,14 @@ exports.authenticate = async ctx => { if (!username) ctx.throw(400, "Username Required.") if (!password) ctx.throw(400, "Password Required") + const masterDb = new CouchDB("clientAppLookup") + const { clientId } = await masterDb.get(ctx.params.appId) + + if (!clientId) { + ctx.throw(400, "ClientId not suplied") + } // find the instance that the user is associated with - const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) + const db = new CouchDB(ClientDb.name(clientId)) const appId = ctx.params.appId const app = await db.get(appId) const instanceId = app.userInstanceMap[username] diff --git a/packages/server/src/api/controllers/client.js b/packages/server/src/api/controllers/client.js index 433bf58c3b..400ccde358 100644 --- a/packages/server/src/api/controllers/client.js +++ b/packages/server/src/api/controllers/client.js @@ -6,13 +6,26 @@ exports.getClientId = async function(ctx) { } exports.create = async function(ctx) { - await create(env.CLIENT_ID) + const clientId = getClientId(ctx) + await create(clientId) ctx.status = 200 - ctx.message = `Client Database ${env.CLIENT_ID} successfully provisioned.` + ctx.message = `Client Database ${clientId} successfully provisioned.` } exports.destroy = async function(ctx) { - await destroy(env.CLIENT_ID) + const clientId = getClientId(ctx) + await destroy(clientId) ctx.status = 200 - ctx.message = `Client Database ${env.CLIENT_ID} successfully deleted.` + ctx.message = `Client Database ${clientId} successfully deleted.` +} + +const getClientId = ctx => { + const clientId = + (ctx.query && ctx.query.clientId) || + (ctx.body && ctx.body.clientId) || + env.CLIENT_ID + if (!clientId) { + ctx.throw(400, "ClientId not suplied") + } + return clientId } diff --git a/packages/server/src/api/controllers/component.js b/packages/server/src/api/controllers/component.js index 0b07826555..a3b4d9c2bb 100644 --- a/packages/server/src/api/controllers/component.js +++ b/packages/server/src/api/controllers/component.js @@ -5,10 +5,11 @@ const { budibaseTempDir, budibaseAppsDir, } = require("../../utilities/budibaseDir") -const env = require("../../environment") exports.fetchAppComponentDefinitions = async function(ctx) { - const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) + const masterDb = new CouchDB("clientAppLookup") + const { clientId } = await masterDb.get(ctx.params.appId) + const db = new CouchDB(ClientDb.name(clientId)) const app = await db.get(ctx.params.appId) const componentDefinitions = app.componentLibraries.reduce( diff --git a/packages/server/src/api/controllers/instance.js b/packages/server/src/api/controllers/instance.js index fe40cf9fb4..155437371f 100644 --- a/packages/server/src/api/controllers/instance.js +++ b/packages/server/src/api/controllers/instance.js @@ -1,14 +1,16 @@ const CouchDB = require("../../db") const client = require("../../db/clientDb") const newid = require("../../db/newid") -const env = require("../../environment") exports.create = async function(ctx) { const instanceName = ctx.request.body.name const appShortId = ctx.params.applicationId.substring(0, 7) const instanceId = `inst_${appShortId}_${newid()}` const { applicationId } = ctx.params - const clientId = env.CLIENT_ID + + const masterDb = new CouchDB("clientAppLookup") + const { clientId } = await masterDb.get(applicationId) + const db = new CouchDB(instanceId) await db.put({ _id: "_design/database", diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 5810872989..1b66cf1cca 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -1,7 +1,6 @@ const CouchDB = require("../../db") const clientDb = require("../../db/clientDb") const bcrypt = require("../../utilities/bcrypt") -const env = require("../../environment") const getUserId = userName => `user_${userName}` const { POWERUSER_LEVEL_ID, @@ -42,8 +41,11 @@ exports.create = async function(ctx) { const response = await database.post(user) + const masterDb = new CouchDB("clientAppLookup") + const { clientId } = await masterDb.get(appId) + // the clientDB needs to store a map of users against the app - const db = new CouchDB(clientDb.name(env.CLIENT_ID)) + const db = new CouchDB(clientDb.name(clientId)) const app = await db.get(appId) app.userInstanceMap = { diff --git a/packages/server/src/api/routes/tests/application.spec.js b/packages/server/src/api/routes/tests/application.spec.js index 4c193613fd..7f64419778 100644 --- a/packages/server/src/api/routes/tests/application.spec.js +++ b/packages/server/src/api/routes/tests/application.spec.js @@ -5,6 +5,7 @@ const { destroyClientDatabase, builderEndpointShouldBlockNormalUsers, supertest, + TEST_CLIENT_ID, defaultHeaders, } = require("./couchTestUtils") @@ -51,6 +52,7 @@ describe("/applications", () => { body: { name: "My App" } }) }) + }) describe("fetch", () => { @@ -68,6 +70,37 @@ describe("/applications", () => { expect(res.body.length).toBe(2) }) + it("lists only applications in requested client databse", async () => { + await createApplication(request, "app1") + await createClientDatabase("new_client") + + const blah = await request + .post("/api/applications") + .send({ name: "app2", clientId: "new_client"}) + .set(defaultHeaders) + .expect('Content-Type', /json/) + //.expect(200) + + const client1Res = await request + .get(`/api/applications?clientId=${TEST_CLIENT_ID}`) + .set(defaultHeaders) + .expect('Content-Type', /json/) + .expect(200) + + expect(client1Res.body.length).toBe(1) + expect(client1Res.body[0].name).toBe("app1") + + const client2Res = await request + .get(`/api/applications?clientId=new_client`) + .set(defaultHeaders) + .expect('Content-Type', /json/) + .expect(200) + + expect(client2Res.body.length).toBe(1) + expect(client2Res.body[0].name).toBe("app2") + + }) + it("should apply authorization to endpoint", async () => { const otherApplication = await createApplication(request) const instance = await createInstance(request, otherApplication._id) diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index f70fc0ced1..6029e080cc 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -9,6 +9,7 @@ const { const TEST_CLIENT_ID = "test-client-id" +exports.TEST_CLIENT_ID = TEST_CLIENT_ID exports.supertest = async () => { let request let port = 4002 @@ -59,7 +60,7 @@ exports.createView = async (request, instanceId, view) => { return res.body } -exports.createClientDatabase = async () => await create(TEST_CLIENT_ID) +exports.createClientDatabase = async id => await create(id || TEST_CLIENT_ID) exports.createApplication = async (request, name = "test_application") => { const res = await request diff --git a/packages/standard-components/src/DataForm.svelte b/packages/standard-components/src/DataForm.svelte index ab515551dd..6a9788458e 100644 --- a/packages/standard-components/src/DataForm.svelte +++ b/packages/standard-components/src/DataForm.svelte @@ -87,7 +87,6 @@ .form-content { margin-bottom: 20px; - } .input { diff --git a/packages/standard-components/src/DataTable.svelte b/packages/standard-components/src/DataTable.svelte index c0db923d58..90716383da 100644 --- a/packages/standard-components/src/DataTable.svelte +++ b/packages/standard-components/src/DataTable.svelte @@ -65,7 +65,7 @@ } thead { - background: #393C44; + background: #393c44; border: 1px solid #ccc; height: 40px; text-align: left; @@ -87,7 +87,7 @@ tbody tr { border-bottom: 1px solid #ccc; transition: 0.3s background-color; - color: #393C44; + color: #393c44; font-size: 14px; height: 40px; } diff --git a/packages/standard-components/src/Image.svelte b/packages/standard-components/src/Image.svelte index 84f515b287..cd204f5fc2 100644 --- a/packages/standard-components/src/Image.svelte +++ b/packages/standard-components/src/Image.svelte @@ -7,6 +7,8 @@ export let height export let width + export let _bb + $: style = buildStyle({ height, width })