diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2a57d6f388..bd21123709 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -93,6 +93,8 @@ then `cd ` into your local copy. #### 3. Install and Build +| **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash) + To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed. ##### Quick method diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d1ba2bc046..b4f7739293 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,15 @@ assignees: '' --- +**Hosting** + +- Self + - Method: + - Budibase Version: + - App Version: +- Cloud + - Tenant ID: + **Describe the bug** A clear and concise description of what the bug is. diff --git a/lerna.json b/lerna.json index 157d6fe849..c2e996729b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.105-alpha.10", + "version": "1.0.105-alpha.20", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 9cd8c6bdb3..15b5a9c6ad 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.0.105-alpha.10", + "version": "1.0.105-alpha.20", "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", diff --git a/packages/backend-core/src/constants.js b/packages/backend-core/src/constants.js index 559dc0e6b2..42450190e5 100644 --- a/packages/backend-core/src/constants.js +++ b/packages/backend-core/src/constants.js @@ -16,6 +16,7 @@ exports.Headers = { API_VER: "x-budibase-api-version", APP_ID: "x-budibase-app-id", TYPE: "x-budibase-type", + PREVIEW_ROLE: "x-budibase-role", TENANT_ID: "x-budibase-tenant-id", TOKEN: "x-budibase-token", CSRF_TOKEN: "x-csrf-token", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 21db7d691c..b4757cab93 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "1.0.105-alpha.10", + "version": "1.0.105-alpha.20", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.2.1", - "@budibase/string-templates": "^1.0.105-alpha.10", + "@budibase/string-templates": "^1.0.105-alpha.20", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", diff --git a/packages/bbui/src/Layout/Layout.svelte b/packages/bbui/src/Layout/Layout.svelte index 0dcb1f46ee..c66a409242 100644 --- a/packages/bbui/src/Layout/Layout.svelte +++ b/packages/bbui/src/Layout/Layout.svelte @@ -36,6 +36,10 @@ padding-left: var(--spacing-l); padding-right: var(--spacing-l); } + .paddingX-XL { + padding-left: var(--spacing-xl); + padding-right: var(--spacing-xl); + } .paddingY-S { padding-top: var(--spacing-s); padding-bottom: var(--spacing-s); @@ -48,6 +52,10 @@ padding-top: var(--spacing-l); padding-bottom: var(--spacing-l); } + .paddingY-XL { + padding-top: var(--spacing-xl); + padding-bottom: var(--spacing-xl); + } .gap-XXS { grid-gap: var(--spacing-xs); } diff --git a/packages/bbui/src/Table/InternalRenderer.svelte b/packages/bbui/src/Table/InternalRenderer.svelte index 7e2dd0b2aa..d38fb9f691 100644 --- a/packages/bbui/src/Table/InternalRenderer.svelte +++ b/packages/bbui/src/Table/InternalRenderer.svelte @@ -1,42 +1,21 @@ diff --git a/packages/bbui/src/helpers.js b/packages/bbui/src/helpers.js index cf40e12d74..b02783e0bd 100644 --- a/packages/bbui/src/helpers.js +++ b/packages/bbui/src/helpers.js @@ -106,3 +106,29 @@ export const deepSet = (obj, key, value) => { export const cloneDeep = obj => { return JSON.parse(JSON.stringify(obj)) } + +/** + * Copies a value to the clipboard + * @param value the value to copy + */ +export const copyToClipboard = value => { + return new Promise(res => { + if (navigator.clipboard && window.isSecureContext) { + // Try using the clipboard API first + navigator.clipboard.writeText(value).then(res) + } else { + // Fall back to the textarea hack + let textArea = document.createElement("textarea") + textArea.value = value + textArea.style.position = "fixed" + textArea.style.left = "-9999px" + textArea.style.top = "-9999px" + document.body.appendChild(textArea) + textArea.focus() + textArea.select() + document.execCommand("copy") + textArea.remove() + res() + } + }) +} diff --git a/packages/builder/cypress/integration/createApp.spec.js b/packages/builder/cypress/integration/createApp.spec.js index 4867534241..13c5979df9 100644 --- a/packages/builder/cypress/integration/createApp.spec.js +++ b/packages/builder/cypress/integration/createApp.spec.js @@ -25,9 +25,13 @@ filterTests(['smoke', 'all'], () => { cy.visit(`${Cypress.config().baseUrl}/builder`) cy.wait(500) - if (Cypress.env("TEST_ENV")) { - cy.get(".spectrum-Button").contains("Templates").click({force: true}) - } + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) + .its("body") + .then(val => { + if (val.length > 0) { + cy.get(".spectrum-Button").contains("Templates").click({force: true}) + } + }) cy.get(".template-category-filters").should("exist") cy.get(".template-categories").should("exist") diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js index 81b7c2f045..4600807cbc 100644 --- a/packages/builder/cypress/integration/createTable.spec.js +++ b/packages/builder/cypress/integration/createTable.spec.js @@ -55,13 +55,14 @@ filterTests(["smoke", "all"], () => { if (Cypress.env("TEST_ENV")) { // No Pagination in CI - Test env only for the next two tests - it("Adds 15 rows and checks pagination", () => { + xit("Adds 15 rows and checks pagination", () => { // 10 rows per page, 15 rows should create 2 pages within table const totalRows = 16 for (let i = 1; i < totalRows; i++) { cy.addRow([i]) } - cy.wait(1000) + cy.reload() + cy.wait(2000) cy.get(".spectrum-Pagination").within(() => { cy.get(".spectrum-ActionButton").eq(1).click() }) @@ -70,13 +71,13 @@ filterTests(["smoke", "all"], () => { }) }) - it("Deletes rows and checks pagination", () => { - // Delete rows, removing second page of rows from table - const deleteRows = 5 + xit("Deletes rows and checks pagination", () => { + // Delete rows, removing second page from table cy.get(".spectrum-Checkbox-input").check({ force: true }) - cy.get(".spectrum-Table") - cy.contains("Delete 5 row(s)").click() - cy.get(".spectrum-Modal").contains("Delete").click() + cy.get(".popovers").within(() => { + cy.get(".spectrum-Button").click({ force: true }) + }) + cy.get(".spectrum-Dialog-grid").contains("Delete").click({ force: true }) cy.wait(1000) // Confirm table only has one page diff --git a/packages/builder/cypress/integration/datasources/mySql.spec.js b/packages/builder/cypress/integration/datasources/mySql.spec.js index 03f59a6004..98bb2f2acf 100644 --- a/packages/builder/cypress/integration/datasources/mySql.spec.js +++ b/packages/builder/cypress/integration/datasources/mySql.spec.js @@ -19,6 +19,7 @@ filterTests(["all"], () => { cy.get(".spectrum-Button") .contains("Save and fetch tables") .click({ force: true }) + cy.wait(500) // Intercept Request after button click & apply assertions cy.wait("@datasource") cy.get("@datasource") @@ -31,6 +32,7 @@ filterTests(["all"], () => { cy.get("@datasource") .its("response.body") .should("have.property", "status", 500) + cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true }) }) it("should add MySQL data source and fetch tables", () => { @@ -72,10 +74,13 @@ filterTests(["all"], () => { cy.get(".spectrum-Popover").contains("COUNTRIES").click() cy.get(".spectrum-Picker").eq(4).click() cy.get(".spectrum-Popover").contains("REGION_ID").click() - // Save relationship & reload page - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - cy.reload() }) + // Save relationship & reload page + cy.get(".spectrum-ButtonGroup").within(() => { + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + }) + cy.reload() + // Confirm table length & column name cy.get(".spectrum-Table") .eq(1) @@ -131,7 +136,7 @@ filterTests(["all"], () => { cy.get(".spectrum-Table") .eq(1) .within(() => { - cy.get(".spectrum-Table-row").eq(0).click() + cy.get(".spectrum-Table-row").eq(0).click({ force: true }) cy.wait(500) }) cy.get(".spectrum-Dialog-grid").within(() => { @@ -175,11 +180,12 @@ filterTests(["all"], () => { }) it("should duplicate a query", () => { - // Get last nav item - The query + /// Get query nav item - QueryName cy.get(".nav-item") - .last() + .contains(queryName) + .parent() .within(() => { - cy.get(".icon").eq(1).click({ force: true }) + cy.get(".spectrum-Icon").eq(1).click({ force: true }) }) // Select and confirm duplication cy.get(".spectrum-Menu").contains("Duplicate").click() @@ -199,23 +205,21 @@ filterTests(["all"], () => { }) it("should delete a query", () => { - // Get last nav item - The query - for (let i = 0; i < 2; i++) { - cy.get(".nav-item") - .last() - .within(() => { - cy.get(".icon").eq(1).click({ force: true }) - }) - // Select Delete - cy.get(".spectrum-Menu").contains("Delete").click() - cy.get(".spectrum-Button") - .contains("Delete Query") - .click({ force: true }) - cy.wait(1000) - } + // Get query nav item - QueryName + cy.get(".nav-item") + .contains(queryName) + .parent() + .within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button") + .contains("Delete Query") + .click({ force: true }) + cy.wait(1000) // Confirm deletion cy.get(".nav-item").should("not.contain", queryName) - cy.get(".nav-item").should("not.contain", queryRename) }) } }) diff --git a/packages/builder/cypress/integration/datasources/oracle.spec.js b/packages/builder/cypress/integration/datasources/oracle.spec.js index 73c25001c9..4c4d33d654 100644 --- a/packages/builder/cypress/integration/datasources/oracle.spec.js +++ b/packages/builder/cypress/integration/datasources/oracle.spec.js @@ -46,9 +46,10 @@ filterTests(["all"], () => { cy.get("@datasource") .its("response.body") .should("have.property", "status", 500) + cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true }) }) - it("should add Oracle data source and fetch tables", () => { + xit("should add Oracle data source and fetch tables", () => { // Add & configure Oracle data source cy.selectExternalDatasource(datasource) cy.intercept("**/datasources").as("datasource") @@ -64,7 +65,7 @@ filterTests(["all"], () => { .should("be.gt", 0) }) - it("should define a One relationship type", () => { + xit("should define a One relationship type", () => { // Select relationship type & configure cy.get(".spectrum-Button") .contains("Define relationship") @@ -93,7 +94,7 @@ filterTests(["all"], () => { cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS") }) - it("should define a Many relationship type", () => { + xit("should define a Many relationship type", () => { // Select relationship type & configure cy.get(".spectrum-Button") .contains("Define relationship") @@ -127,7 +128,7 @@ filterTests(["all"], () => { ) }) - it("should delete relationships", () => { + xit("should delete relationships", () => { // Delete both relationships cy.get(".spectrum-Table") .eq(1) @@ -156,7 +157,7 @@ filterTests(["all"], () => { }) }) - it("should add a query", () => { + xit("should add a query", () => { // Add query cy.get(".spectrum-Button").contains("Add query").click({ force: true }) cy.get(".spectrum-Form-item") @@ -181,7 +182,7 @@ filterTests(["all"], () => { cy.get(".nav-item").should("contain", queryName) }) - it("should duplicate a query", () => { + xit("should duplicate a query", () => { // Get query nav item cy.get(".nav-item") .contains(queryName) @@ -194,7 +195,7 @@ filterTests(["all"], () => { cy.get(".nav-item").should("contain", queryName + " (1)") }) - it("should edit a query name", () => { + xit("should edit a query name", () => { // Rename query cy.get(".spectrum-Form-item") .eq(0) @@ -206,7 +207,7 @@ filterTests(["all"], () => { cy.get(".nav-item").should("contain", queryRename) }) - it("should delete a query", () => { + xit("should delete a query", () => { // Get query nav item - QueryName cy.get(".nav-item") .contains(queryName) diff --git a/packages/builder/cypress/integration/datasources/postgreSql.spec.js b/packages/builder/cypress/integration/datasources/postgreSql.spec.js index 3f55636623..c7413bb7d1 100644 --- a/packages/builder/cypress/integration/datasources/postgreSql.spec.js +++ b/packages/builder/cypress/integration/datasources/postgreSql.spec.js @@ -21,16 +21,10 @@ filterTests(["all"], () => { .click({ force: true }) // Intercept Request after button click & apply assertions cy.wait("@datasource") - cy.get("@datasource") - .its("response.body") - .should( - "have.property", - "message", - "connect ECONNREFUSED 127.0.0.1:5432" - ) cy.get("@datasource") .its("response.body") .should("have.property", "status", 500) + cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true }) }) it("should add PostgreSQL data source and fetch tables", () => { @@ -113,13 +107,13 @@ filterTests(["all"], () => { }) it("should delete a relationship", () => { - cy.get(".hierarchy-items-container").contains(datasource).click() + cy.get(".hierarchy-items-container").contains("PostgreSQL-2").click() cy.reload() // Delete one relationship cy.get(".spectrum-Table") .eq(1) .within(() => { - cy.get(".spectrum-Table-row").eq(0).click() + cy.get(".spectrum-Table-row").eq(0).click({ force: true }) cy.wait(500) }) cy.get(".spectrum-Dialog-grid").within(() => { @@ -161,7 +155,7 @@ filterTests(["all"], () => { it("should switch to schema with no tables", () => { // Switch Schema - To one without any tables - cy.get(".hierarchy-items-container").contains(datasource).click() + cy.get(".hierarchy-items-container").contains("PostgreSQL-2").click() switchSchema("randomText") // No tables displayed @@ -208,11 +202,12 @@ filterTests(["all"], () => { }) it("should duplicate a query", () => { - // Get last nav item - The query + // Locate previously created query cy.get(".nav-item") - .last() + .contains(queryName) + .siblings(".actions") .within(() => { - cy.get(".icon").eq(1).click({ force: true }) + cy.get(".icon").click({ force: true }) }) // Select and confirm duplication cy.get(".spectrum-Menu").contains("Duplicate").click() @@ -240,23 +235,21 @@ filterTests(["all"], () => { }) it("should delete a query", () => { - // Get last nav item - The query - for (let i = 0; i < 2; i++) { - cy.get(".nav-item") - .last() - .within(() => { - cy.get(".icon").eq(1).click({ force: true }) - }) - // Select Delete - cy.get(".spectrum-Menu").contains("Delete").click() - cy.get(".spectrum-Button") - .contains("Delete Query") - .click({ force: true }) - cy.wait(1000) - } + // Get query nav item - QueryName + cy.get(".nav-item") + .contains(queryName) + .parent() + .within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button") + .contains("Delete Query") + .click({ force: true }) + cy.wait(1000) // Confirm deletion cy.get(".nav-item").should("not.contain", queryName) - cy.get(".nav-item").should("not.contain", queryRename) }) const switchSchema = schema => { diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index f4ccdcca24..672e9dfc00 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -60,43 +60,48 @@ Cypress.Commands.add("deleteApp", name => { cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) .its("body") .then(val => { - if (val.length > 0) { - if (Cypress.env("TEST_ENV")) { - cy.searchForApplication(name) - cy.get(".appTable").within(() => { - cy.get(".spectrum-Icon").eq(1).click() + const findAppName = val.some(val => val.name == name) + if (findAppName) { + if (val.length > 0) { + if (Cypress.env("TEST_ENV")) { + cy.searchForApplication(name) + cy.get(".appTable").within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + } else { + const appId = val.reduce((acc, app) => { + if (name === app.name) { + acc = app.appId + } + return acc + }, "") + + if (appId == "") { + return + } + + const appIdParsed = appId.split("_").pop() + const actionEleId = `[data-cy=row_actions_${appIdParsed}]` + cy.get(actionEleId).within(() => { + cy.get(".spectrum-Icon").eq(0).click() + }) + } + + cy.get(".spectrum-Menu").then($menu => { + if ($menu.text().includes("Unpublish")) { + cy.get(".spectrum-Menu").contains("Unpublish").click() + cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click() + } else { + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type(name) + }) + cy.get(".spectrum-Button--warning").click() + } }) } else { - const appId = val.reduce((acc, app) => { - if (name === app.name) { - acc = app.appId - } - return acc - }, "") - - if (appId == "") { - return - } - - const appIdParsed = appId.split("_").pop() - const actionEleId = `[data-cy=row_actions_${appIdParsed}]` - cy.get(actionEleId).within(() => { - cy.get(".spectrum-Icon").eq(0).click() - }) + return } - - cy.get(".spectrum-Menu").then($menu => { - if ($menu.text().includes("Unpublish")) { - cy.get(".spectrum-Menu").contains("Unpublish").click() - cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click() - } else { - cy.get(".spectrum-Menu").contains("Delete").click() - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get("input").type(name) - }) - cy.get(".spectrum-Button--warning").click() - } - }) } else { return } @@ -410,7 +415,9 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => { if (datasource == "Oracle") { cy.get("input").clear().type(Cypress.env("oracle").HOST) } else { - cy.get("input").clear().type(Cypress.env("HOST_IP")) + cy.get("input") + .clear({ force: true }) + .type(Cypress.env("mysql").HOST, { force: true }) } }) }) diff --git a/packages/builder/package.json b/packages/builder/package.json index 6dde63596e..bc1c02994d 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "1.0.105-alpha.10", + "version": "1.0.105-alpha.20", "license": "GPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^1.0.105-alpha.10", - "@budibase/client": "^1.0.105-alpha.10", - "@budibase/frontend-core": "^1.0.105-alpha.10", - "@budibase/string-templates": "^1.0.105-alpha.10", + "@budibase/bbui": "^1.0.105-alpha.20", + "@budibase/client": "^1.0.105-alpha.20", + "@budibase/frontend-core": "^1.0.105-alpha.20", + "@budibase/string-templates": "^1.0.105-alpha.20", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 3d857ec9fb..53acb59cd9 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -104,7 +104,10 @@ ) bindings = bindings.concat( outputs.map(([name, value]) => { - const runtime = idx === 0 ? `trigger.${name}` : `steps.${idx}.${name}` + const stepsLabel = block.name.startsWith("JS") + ? `steps[${idx}].${name}` + : `steps.${idx}.${name}` + const runtime = idx === 0 ? `trigger.${name}` : stepsLabel return { label: runtime, type: value.type, diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte index 73554b45db..1e19508744 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte @@ -52,7 +52,6 @@ .map(query => ({ label: query.name, name: query.name, - tableId: query._id, ...query, type: "query", })) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionsEditor/OptionsEditor.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionsEditor/OptionsEditor.svelte index 4d74ea9940..1201edd31e 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionsEditor/OptionsEditor.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionsEditor/OptionsEditor.svelte @@ -10,9 +10,15 @@ let drawer let tempValue = value || [] - const saveFilter = async () => { - // Filter out incomplete options - tempValue = tempValue.filter(option => option.value && option.label) + const saveOptions = async () => { + // Filter out incomplete options, default if needed + tempValue = tempValue.filter(option => option.value || option.label) + for (let i = 0; i < tempValue.length; i++) { + let option = tempValue[i] + option.label = option.label ? option.label : option.value + option.value = option.value ? option.value : option.label + tempValue[i] = option + } dispatch("change", tempValue) drawer.hide() } @@ -23,6 +29,6 @@ Define the options for this picker. - + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SearchFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SearchFieldSelect.svelte index b3387fdd05..41906d3ba1 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SearchFieldSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SearchFieldSelect.svelte @@ -15,16 +15,14 @@ const dispatch = createEventDispatcher() $: datasource = getDatasourceForProvider($currentAsset, componentInstance) - $: schema = getSchemaForDatasource($currentAsset, datasource, { - searchableSchema: true, - }).schema + $: schema = getSchemaForDatasource($currentAsset, datasource).schema $: options = getOptions(datasource, schema || {}) $: boundValue = getSelectedOption(value, options) function getOptions(ds, dsSchema) { let base = Object.values(dsSchema) if (!ds?.tableId) { - return base + return base.map(field => field.name) } const currentTable = $tables.list.find(table => table._id === ds.tableId) return getFields(base, { allowLinks: currentTable?.sql }).map( diff --git a/packages/cli/package.json b/packages/cli/package.json index ccfe05f86e..57cf5bcaf5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "1.0.105-alpha.10", + "version": "1.0.105-alpha.20", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 264aa57fe5..f4e2a78164 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "1.0.105-alpha.10", + "version": "1.0.105-alpha.20", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^1.0.105-alpha.10", - "@budibase/frontend-core": "^1.0.105-alpha.10", - "@budibase/string-templates": "^1.0.105-alpha.10", + "@budibase/bbui": "^1.0.105-alpha.20", + "@budibase/frontend-core": "^1.0.105-alpha.20", + "@budibase/string-templates": "^1.0.105-alpha.20", "@spectrum-css/button": "^3.0.3", "@spectrum-css/card": "^3.0.3", "@spectrum-css/divider": "^1.0.3", diff --git a/packages/client/src/api/api.js b/packages/client/src/api/api.js index 591d4a6782..1d575d9d27 100644 --- a/packages/client/src/api/api.js +++ b/packages/client/src/api/api.js @@ -1,5 +1,5 @@ import { createAPIClient } from "@budibase/frontend-core" -import { notificationStore, authStore } from "../stores" +import { notificationStore, authStore, devToolsStore } from "../stores" import { get } from "svelte/store" export const API = createAPIClient({ @@ -21,6 +21,12 @@ export const API = createAPIClient({ if (auth?.csrfToken) { headers["x-csrf-token"] = auth.csrfToken } + + // Add role header + const role = get(devToolsStore).role + if (role) { + headers["x-budibase-role"] = role + } }, // Show an error notification for all API failures. diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index 27767862ab..6bd0313c75 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -14,6 +14,8 @@ routeStore, builderStore, themeStore, + appStore, + devToolsStore, } from "stores" import NotificationDisplay from "components/overlay/NotificationDisplay.svelte" import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte" @@ -28,6 +30,8 @@ import CustomThemeWrapper from "./CustomThemeWrapper.svelte" import DNDHandler from "components/preview/DNDHandler.svelte" import KeyboardManager from "components/preview/KeyboardManager.svelte" + import DevToolsHeader from "components/devtools/DevToolsHeader.svelte" + import DevTools from "components/devtools/DevTools.svelte" // Provide contexts setContext("sdk", SDK) @@ -55,8 +59,22 @@ if ($authStore) { // There is a logged in user, so handle them if ($screenStore.screens.length) { + let firstRoute + + // If using devtools, find the first screen matching our role + if ($devToolsStore.role) { + const roleRoutes = $screenStore.screens.filter( + screen => screen.routing?.roleId === $devToolsStore.role + ) + firstRoute = roleRoutes[0]?.routing?.route || "/" + } + + // Otherwise just use the first route + else { + firstRoute = $screenStore.screens[0]?.routing?.route ?? "/" + } + // Screens exist so navigate back to the home screen - const firstRoute = $screenStore.screens[0].routing?.route ?? "/" routeStore.actions.navigate(firstRoute) } else { // No screens likely means the user has no permissions to view this app @@ -70,6 +88,8 @@ } } } + + $: isDevPreview = $appStore.isDevApp && !$builderStore.inBuilder {#if dataLoaded} @@ -109,39 +129,49 @@ >
- - {#key `${$screenStore.activeLayout._id}-${$builderStore.previewType}`} - - {/key} + {#if isDevPreview} + + {/if} - -
+
+ + {#key `${$screenStore.activeLayout._id}-${$builderStore.previewType}`} + + {/key} - - @@ -167,6 +197,7 @@ justify-content: center; align-items: center; } + #clip-root { max-width: 100%; max-height: 100%; @@ -176,10 +207,24 @@ overflow: hidden; background-color: transparent; } + #app-root { overflow: hidden; height: 100%; width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: stretch; + } + + #app-body { + flex: 1 1 auto; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: stretch; + overflow: hidden; } .error { @@ -192,19 +237,23 @@ text-align: center; padding: 20px; } + .error :global(svg) { fill: var(--spectrum-global-color-gray-500); width: 80px; height: 80px; } + .error :global(h1), .error :global(p) { color: var(--spectrum-global-color-gray-800); } + .error :global(p) { font-style: italic; margin-top: -0.5em; } + .error :global(h1) { font-weight: 400; } @@ -214,14 +263,17 @@ #clip-root.preview { padding: 2px; } + #clip-root.tablet-preview { width: calc(1024px + 6px); height: calc(768px + 6px); } + #clip-root.mobile-preview { width: calc(390px + 6px); height: calc(844px + 6px); } + .preview #app-root { border: 1px solid var(--spectrum-global-color-gray-300); border-radius: 4px; diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index d9af295108..3c29cb875b 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -9,12 +9,16 @@ {#if constructor && initialSettings && (visible || inSelectedPath)} @@ -419,12 +431,15 @@ .component { display: contents; } + .interactive :global(*:hover) { cursor: pointer; } + .draggable :global(*:hover) { cursor: grab; } + .editing :global(*:hover) { cursor: auto; } diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index a801ea4b46..4df9087904 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -179,6 +179,7 @@ justify-content: flex-start; align-items: stretch; height: 100%; + flex: 1 1 auto; overflow: auto; overflow-x: hidden; position: relative; diff --git a/packages/client/src/components/app/forms/Form.svelte b/packages/client/src/components/app/forms/Form.svelte index 740c5fd9f1..a274fb24f0 100644 --- a/packages/client/src/components/app/forms/Form.svelte +++ b/packages/client/src/components/app/forms/Form.svelte @@ -80,7 +80,7 @@ } const fetchTable = async dataSource => { - if (dataSource?.tableId) { + if (dataSource?.tableId && dataSource?.type !== "query") { try { table = await API.fetchTableDefinition(dataSource.tableId) } catch (error) { diff --git a/packages/client/src/components/app/forms/FormStep.svelte b/packages/client/src/components/app/forms/FormStep.svelte index 58300287a8..4441f515ee 100644 --- a/packages/client/src/components/app/forms/FormStep.svelte +++ b/packages/client/src/components/app/forms/FormStep.svelte @@ -5,7 +5,7 @@ export let step = 1 - const { styleable, builderStore } = getContext("sdk") + const { styleable, builderStore, componentStore } = getContext("sdk") const component = getContext("component") const formContext = getContext("form") @@ -22,7 +22,7 @@ if ( formContext && $builderStore.inBuilder && - $builderStore.selectedComponentPath?.includes($component.id) + $componentStore.selectedComponentPath?.includes($component.id) ) { formContext.formApi.setStep(step) } diff --git a/packages/client/src/components/context/UserBindingsProvider.svelte b/packages/client/src/components/context/UserBindingsProvider.svelte index e788d80dc4..f7605122ae 100644 --- a/packages/client/src/components/context/UserBindingsProvider.svelte +++ b/packages/client/src/components/context/UserBindingsProvider.svelte @@ -1,6 +1,6 @@ - + diff --git a/packages/client/src/components/devtools/DevTools.svelte b/packages/client/src/components/devtools/DevTools.svelte new file mode 100644 index 0000000000..4bb332da2f --- /dev/null +++ b/packages/client/src/components/devtools/DevTools.svelte @@ -0,0 +1,69 @@ + + +
+ {#if $devToolsStore.visible} + +
+ Budibase DevTools + devToolsStore.actions.setVisible(false)} + /> +
+ + +
+ +
+
+ +
+ +
+
+
+
+ {/if} +
+ + diff --git a/packages/client/src/components/devtools/DevToolsComponentContextTab.svelte b/packages/client/src/components/devtools/DevToolsComponentContextTab.svelte new file mode 100644 index 0000000000..3b4c426851 --- /dev/null +++ b/packages/client/src/components/devtools/DevToolsComponentContextTab.svelte @@ -0,0 +1,113 @@ + + + + + Choose a category to see the value of all its available bindings. + + devToolsStore.actions.changeRole(e.detail)} + /> + {#if !$context.device.mobile} + + {/if} +
+ + diff --git a/packages/client/src/components/devtools/DevToolsStat.svelte b/packages/client/src/components/devtools/DevToolsStat.svelte new file mode 100644 index 0000000000..30600737d2 --- /dev/null +++ b/packages/client/src/components/devtools/DevToolsStat.svelte @@ -0,0 +1,75 @@ + + +
+
{prettyLabel}
+
+ {prettyValue} +
+
+ + diff --git a/packages/client/src/components/devtools/DevToolsStatsTab.svelte b/packages/client/src/components/devtools/DevToolsStatsTab.svelte new file mode 100644 index 0000000000..ab029db815 --- /dev/null +++ b/packages/client/src/components/devtools/DevToolsStatsTab.svelte @@ -0,0 +1,27 @@ + + + + + + + {#if $appStore.clientLoadTime} + + {/if} + + + + + + + + diff --git a/packages/client/src/components/preview/IndicatorSet.svelte b/packages/client/src/components/preview/IndicatorSet.svelte index 012aa7e470..6fcf552d21 100644 --- a/packages/client/src/components/preview/IndicatorSet.svelte +++ b/packages/client/src/components/preview/IndicatorSet.svelte @@ -2,6 +2,7 @@ import { onMount, onDestroy } from "svelte" import Indicator from "./Indicator.svelte" import { domDebounce } from "utils/domDebounce" + import { builderStore } from "stores" export let componentId export let color @@ -13,6 +14,7 @@ let interval let text $: visibleIndicators = indicators.filter(x => x.visible) + $: offset = $builderStore.inBuilder ? 0 : 2 let updating = false let observers = [] @@ -88,8 +90,8 @@ const elBounds = child.getBoundingClientRect() nextIndicators.push({ - top: elBounds.top + scrollY - deviceBounds.top, - left: elBounds.left + scrollX - deviceBounds.left, + top: elBounds.top + scrollY - deviceBounds.top - offset, + left: elBounds.left + scrollX - deviceBounds.left - offset, width: elBounds.width + 4, height: elBounds.height + 4, visible: false, diff --git a/packages/client/src/components/preview/SettingsBar.svelte b/packages/client/src/components/preview/SettingsBar.svelte index 05df1ef898..bf0b48250a 100644 --- a/packages/client/src/components/preview/SettingsBar.svelte +++ b/packages/client/src/components/preview/SettingsBar.svelte @@ -3,7 +3,7 @@ import SettingsButton from "./SettingsButton.svelte" import SettingsColorPicker from "./SettingsColorPicker.svelte" import SettingsPicker from "./SettingsPicker.svelte" - import { builderStore } from "stores" + import { builderStore, componentStore } from "stores" import { domDebounce } from "utils/domDebounce" const verticalOffset = 28 @@ -15,7 +15,7 @@ let self let measured = false - $: definition = $builderStore.selectedComponentDefinition + $: definition = $componentStore.selectedComponentDefinition $: showBar = definition?.showSettingsBar && !$builderStore.isDragging $: settings = getBarSettings(definition) @@ -67,7 +67,7 @@ } //If element is at the very top of the screen, put the bar below the element - if (elBounds.top < elBounds.height) { + if (elBounds.top < elBounds.height && elBounds.height < 80) { newTop = elBounds.bottom + verticalOffset } @@ -163,9 +163,7 @@ { - builderStore.actions.deleteComponent( - $builderStore.selectedComponent._id - ) + builderStore.actions.deleteComponent($builderStore.selectedComponentId) }} title="Delete component" /> diff --git a/packages/client/src/components/preview/SettingsButton.svelte b/packages/client/src/components/preview/SettingsButton.svelte index 1490b2c9b7..6f7d95f5ae 100644 --- a/packages/client/src/components/preview/SettingsButton.svelte +++ b/packages/client/src/components/preview/SettingsButton.svelte @@ -1,6 +1,6 @@ diff --git a/packages/client/src/components/preview/SettingsColorPicker.svelte b/packages/client/src/components/preview/SettingsColorPicker.svelte index 68061e0163..b078d048d2 100644 --- a/packages/client/src/components/preview/SettingsColorPicker.svelte +++ b/packages/client/src/components/preview/SettingsColorPicker.svelte @@ -1,10 +1,10 @@
diff --git a/packages/client/src/components/preview/SettingsPicker.svelte b/packages/client/src/components/preview/SettingsPicker.svelte index 8d7129b812..8b83729fde 100644 --- a/packages/client/src/components/preview/SettingsPicker.svelte +++ b/packages/client/src/components/preview/SettingsPicker.svelte @@ -1,12 +1,12 @@
diff --git a/packages/client/src/stores/app.js b/packages/client/src/stores/app.js index a28a4cd9eb..2c2ead66c4 100644 --- a/packages/client/src/stores/app.js +++ b/packages/client/src/stores/app.js @@ -1,8 +1,14 @@ import { API } from "api" import { get, writable } from "svelte/store" +const initialState = { + appId: null, + isDevApp: false, + clientLoadTime: window.INIT_TIME ? Date.now() - window.INIT_TIME : null, +} + const createAppStore = () => { - const store = writable(null) + const store = writable(initialState) // Fetches the app definition including screens, layouts and theme const fetchAppDefinition = async () => { @@ -13,11 +19,13 @@ const createAppStore = () => { try { const appDefinition = await API.fetchAppPackage(appId) store.set({ + ...initialState, ...appDefinition, appId: appDefinition?.application?.appId, + isDevApp: appId.startsWith("app_dev"), }) } catch (error) { - store.set(null) + store.set(initialState) } } diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index 27c8bbe2a2..6d57ce7762 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -1,7 +1,6 @@ -import { writable, derived, get } from "svelte/store" -import Manifest from "manifest.json" -import { findComponentById, findComponentPathById } from "../utils/components" +import { writable, get } from "svelte/store" import { API } from "api" +import { devToolsStore } from "./devTools.js" const dispatchEvent = (type, data = {}) => { window.parent.postMessage({ type, data }) @@ -22,38 +21,18 @@ const createBuilderStore = () => { previewDevice: "desktop", isDragging: false, } - const writableStore = writable(initialState) - const derivedStore = derived(writableStore, $state => { - // Avoid any of this logic if we aren't in the builder preview - if (!$state.inBuilder) { - return $state - } - - // Derive the selected component instance and definition - const { layout, screen, previewType, selectedComponentId } = $state - const asset = previewType === "layout" ? layout : screen - const component = findComponentById(asset?.props, selectedComponentId) - const prefix = "@budibase/standard-components/" - const type = component?._component?.replace(prefix, "") - const definition = type ? Manifest[type] : null - - // Derive the selected component path - const path = findComponentPathById(asset.props, selectedComponentId) || [] - - return { - ...$state, - selectedComponent: component, - selectedComponentDefinition: definition, - selectedComponentPath: path?.map(component => component._id), - } - }) - + const store = writable(initialState) const actions = { selectComponent: id => { - if (id === get(writableStore).selectedComponentId) { + if (id === get(store).selectedComponentId) { return } - writableStore.update(state => ({ ...state, editMode: false })) + store.update(state => ({ + ...state, + editMode: false, + selectedComponentId: id, + })) + devToolsStore.actions.setAllowSelection(false) dispatchEvent("select-component", { id }) }, updateProp: (prop, value) => { @@ -76,7 +55,7 @@ const createBuilderStore = () => { } }, setSelectedPath: path => { - writableStore.update(state => ({ ...state, selectedPath: path })) + store.update(state => ({ ...state, selectedPath: path })) }, moveComponent: (componentId, destinationComponentId, mode) => { dispatchEvent("move-component", { @@ -86,22 +65,21 @@ const createBuilderStore = () => { }) }, setDragging: dragging => { - if (dragging === get(writableStore).isDragging) { + if (dragging === get(store).isDragging) { return } - writableStore.update(state => ({ ...state, isDragging: dragging })) + store.update(state => ({ ...state, isDragging: dragging })) }, setEditMode: enabled => { - if (enabled === get(writableStore).editMode) { + if (enabled === get(store).editMode) { return } - writableStore.update(state => ({ ...state, editMode: enabled })) + store.update(state => ({ ...state, editMode: enabled })) }, } return { - ...writableStore, - set: state => writableStore.set({ ...initialState, ...state }), - subscribe: derivedStore.subscribe, + ...store, + set: state => store.set({ ...initialState, ...state }), actions, } } diff --git a/packages/client/src/stores/components.js b/packages/client/src/stores/components.js new file mode 100644 index 0000000000..4f972b23c7 --- /dev/null +++ b/packages/client/src/stores/components.js @@ -0,0 +1,81 @@ +import { get, writable, derived } from "svelte/store" +import Manifest from "manifest.json" +import { findComponentById, findComponentPathById } from "../utils/components" +import { devToolsStore } from "./devTools" +import { screenStore } from "./screens" +import { builderStore } from "./builder" + +const createComponentStore = () => { + const store = writable({}) + + const derivedStore = derived( + [store, builderStore, devToolsStore, screenStore], + ([$store, $builderState, $devToolsState, $screenState]) => { + // Avoid any of this logic if we aren't in the builder preview + if (!$builderState.inBuilder && !$devToolsState.visible) { + return {} + } + + // Derive the selected component instance and definition + let asset + const { layout, screen, previewType, selectedComponentId } = $builderState + if ($builderState.inBuilder) { + asset = previewType === "layout" ? layout : screen + } else { + asset = $screenState.activeScreen + } + const component = findComponentById(asset?.props, selectedComponentId) + const prefix = "@budibase/standard-components/" + const type = component?._component?.replace(prefix, "") + const definition = type ? Manifest[type] : null + + // Derive the selected component path + const path = + findComponentPathById(asset?.props, selectedComponentId) || [] + + return { + selectedComponentInstance: $store[selectedComponentId], + selectedComponent: component, + selectedComponentDefinition: definition, + selectedComponentPath: path?.map(component => component._id), + mountedComponents: Object.keys($store).length, + currentAsset: asset, + } + } + ) + + const registerInstance = (id, instance) => { + store.update(state => ({ + ...state, + [id]: instance, + })) + } + + const unregisterInstance = id => { + store.update(state => { + delete state[id] + return state + }) + } + + const isComponentRegistered = id => { + return get(store)[id] != null + } + + const getComponentById = id => { + const asset = get(derivedStore).currentAsset + return findComponentById(asset?.props, id) + } + + return { + ...derivedStore, + actions: { + registerInstance, + unregisterInstance, + isComponentRegistered, + getComponentById, + }, + } +} + +export const componentStore = createComponentStore() diff --git a/packages/client/src/stores/devTools.js b/packages/client/src/stores/devTools.js new file mode 100644 index 0000000000..6d631080ab --- /dev/null +++ b/packages/client/src/stores/devTools.js @@ -0,0 +1,47 @@ +import { get } from "svelte/store" +import { createLocalStorageStore } from "@budibase/frontend-core" +import { appStore } from "./app" +import { initialise } from "./initialise" +import { authStore } from "./auth" + +const initialState = { + visible: false, + allowSelection: false, + role: null, +} + +const createDevToolStore = () => { + const localStorageKey = `${get(appStore).appId}.devTools` + const store = createLocalStorageStore(localStorageKey, initialState) + + const setVisible = visible => { + store.update(state => ({ + ...state, + visible: visible, + })) + } + + const setAllowSelection = allowSelection => { + store.update(state => ({ + ...state, + allowSelection, + })) + } + + const changeRole = async role => { + store.update(state => ({ + ...state, + role: role === "self" ? null : role, + })) + // location.reload() + await authStore.actions.fetchUser() + await initialise() + } + + return { + subscribe: store.subscribe, + actions: { setVisible, setAllowSelection, changeRole }, + } +} + +export const devToolsStore = createDevToolStore() diff --git a/packages/client/src/stores/index.js b/packages/client/src/stores/index.js index ddd052fb4e..280a32e069 100644 --- a/packages/client/src/stores/index.js +++ b/packages/client/src/stores/index.js @@ -9,6 +9,8 @@ export { confirmationStore } from "./confirmation" export { peekStore } from "./peek" export { stateStore } from "./state" export { themeStore } from "./theme" +export { devToolsStore } from "./devTools" +export { componentStore } from "./components" export { uploadStore } from "./uploads.js" export { rowSelectionStore } from "./rowSelection.js" // Context stores are layered and duplicated, so it is not a singleton diff --git a/packages/client/src/stores/screens.js b/packages/client/src/stores/screens.js index 702f662f8a..9635f2b5a0 100644 --- a/packages/client/src/stores/screens.js +++ b/packages/client/src/stores/screens.js @@ -66,7 +66,6 @@ const createScreenStore = () => { } let children = [] findChildrenByType(component, type, children) - console.log(children) return children }, } diff --git a/packages/client/src/utils/componentProps.js b/packages/client/src/utils/componentProps.js index 14760252a9..38eaf77885 100644 --- a/packages/client/src/utils/componentProps.js +++ b/packages/client/src/utils/componentProps.js @@ -107,3 +107,21 @@ export const propsUseBinding = (props, bindingKey) => { } return false } + +/** + * Gets the definition of this component's settings from the manifest + */ +export const getSettingsDefinition = definition => { + if (!definition) { + return [] + } + let settings = [] + definition.settings?.forEach(setting => { + if (setting.section) { + settings = settings.concat(setting.settings || []) + } else { + settings.push(setting) + } + }) + return settings +} diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index b37208d97d..f6fcf1696f 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -1,12 +1,12 @@ { "name": "@budibase/frontend-core", - "version": "1.0.105-alpha.10", + "version": "1.0.105-alpha.20", "description": "Budibase frontend core libraries used in builder and client", "author": "Budibase", "license": "MPL-2.0", "svelte": "src/index.js", "dependencies": { - "@budibase/bbui": "^1.0.105-alpha.10", + "@budibase/bbui": "^1.0.105-alpha.20", "lodash": "^4.17.21", "svelte": "^3.46.2" } diff --git a/packages/server/package.json b/packages/server/package.json index 4a97ec6cc8..86168a32a6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "1.0.105-alpha.10", + "version": "1.0.105-alpha.20", "description": "Budibase Web Server", "main": "src/index.ts", "repository": { @@ -9,7 +9,7 @@ "url": "https://github.com/Budibase/budibase.git" }, "scripts": { - "build": "rimraf dist/ && tsc && mv dist/src/* dist/ && rmdir dist/src/ && yarn postbuild", + "build": "rimraf dist/ && tsc && mv dist/src/* dist/ && rimraf dist/src/ && yarn postbuild", "postbuild": "copyfiles -u 1 src/**/*.svelte dist/ && copyfiles -u 1 src/**/*.hbs dist/ && copyfiles -u 1 src/**/*.json dist/", "test": "jest --coverage --maxWorkers=2", "test:watch": "jest --watch", @@ -68,9 +68,9 @@ "license": "GPL-3.0", "dependencies": { "@apidevtools/swagger-parser": "^10.0.3", - "@budibase/backend-core": "^1.0.105-alpha.10", - "@budibase/client": "^1.0.105-alpha.10", - "@budibase/string-templates": "^1.0.105-alpha.10", + "@budibase/backend-core": "^1.0.105-alpha.20", + "@budibase/client": "^1.0.105-alpha.20", + "@budibase/string-templates": "^1.0.105-alpha.20", "@bull-board/api": "^3.7.0", "@bull-board/koa": "^3.7.0", "@elastic/elasticsearch": "7.10.0", diff --git a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte index 761e953ff7..35379ba6d8 100644 --- a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte +++ b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte @@ -78,6 +78,9 @@ app.
+