merge
This commit is contained in:
commit
89615e58a7
|
@ -186,7 +186,7 @@ Or if you are in the builder you can run `yarn cy:test`.
|
||||||
|
|
||||||
### Other Useful Information
|
### Other Useful Information
|
||||||
|
|
||||||
* The contributors are listed in [AUTHORS.md](https://github.com/budibase/server/blob/master/AUTHORS.md) (add yourself).
|
* The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself).
|
||||||
|
|
||||||
* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE).
|
* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE).
|
||||||
|
|
|
@ -116,7 +116,7 @@ You can also follow a quick tutorial on [how to build a CRM with Budibase](https
|
||||||
|
|
||||||
## ❗ Code of Conduct
|
## ❗ Code of Conduct
|
||||||
|
|
||||||
Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/master/CODE_OF_CONDUCT.md). Please read it.
|
Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/master/.github/CODE_OF_CONDUCT.md). Please read it.
|
||||||
|
|
||||||
## 🙌 Contributing to Budibase
|
## 🙌 Contributing to Budibase
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ Budibase is a monorepo managed by lerna. Lerna manages the building and publishi
|
||||||
|
|
||||||
- [packages/server](https://github.com/Budibase/budibase/tree/master/packages/server) - The budibase server. This Koa app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
- [packages/server](https://github.com/Budibase/budibase/tree/master/packages/server) - The budibase server. This Koa app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
||||||
|
|
||||||
For more information, see [CONTRIBUTING.md](./CONTRIBUTING.md)
|
For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
|
||||||
## 📝 License
|
## 📝 License
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"prettier-plugin-svelte": "^1.4.0",
|
"prettier-plugin-svelte": "^1.4.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup-plugin-replace": "^2.2.0",
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
"svelte": "^3.28.0"
|
"svelte": "^3.30.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
"nuke": "rimraf ~/.budibase && npm run restore",
|
"nuke": "rimraf ~/.budibase && npm run restore",
|
||||||
"clean": "lerna clean",
|
"clean": "lerna clean",
|
||||||
"kill-port": "kill-port 4001",
|
"kill-port": "kill-port 4001",
|
||||||
"dev": "npm run kill-port && node ./scripts/symlinkDev.js && lerna run --parallel dev:builder --concurrency 1",
|
"dev": "yarn run kill-port && node ./scripts/symlinkDev.js && lerna run --parallel dev:builder --concurrency 1",
|
||||||
"test": "lerna run test",
|
"test": "lerna run test",
|
||||||
"lint": "eslint packages",
|
"lint": "eslint packages",
|
||||||
"lint:fix": "eslint --fix packages",
|
"lint:fix": "eslint --fix packages",
|
||||||
|
|
|
@ -60,7 +60,7 @@ context("Create a Table", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("deletes a table", () => {
|
it("deletes a table", () => {
|
||||||
cy.contains(".nav-item", "dog").get(".actions").invoke("show").click()
|
cy.get(".actions").first().invoke("show").click()
|
||||||
cy.get("[data-cy=delete-table]").click()
|
cy.get("[data-cy=delete-table]").click()
|
||||||
cy.contains("Delete Table").click()
|
cy.contains("Delete Table").click()
|
||||||
cy.contains("dog").should("not.exist")
|
cy.contains("dog").should("not.exist")
|
||||||
|
|
|
@ -9,9 +9,9 @@ context('Create a User', () => {
|
||||||
|
|
||||||
// https://on.cypress.io/interacting-with-elements
|
// https://on.cypress.io/interacting-with-elements
|
||||||
it('should create a user', () => {
|
it('should create a user', () => {
|
||||||
cy.createUser('bbuser', 'test', 'POWER_USER')
|
cy.createUser("bbuser@test.com", "test", "ADMIN")
|
||||||
|
|
||||||
// Check to make sure user was created!
|
// // Check to make sure user was created!
|
||||||
cy.get("input[disabled]").should('have.value', 'bbuser')
|
cy.contains("bbuser").should('be.visible')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,9 @@ process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE"
|
||||||
process.env.NODE_ENV = "cypress"
|
process.env.NODE_ENV = "cypress"
|
||||||
process.env.ENABLE_ANALYTICS = "false"
|
process.env.ENABLE_ANALYTICS = "false"
|
||||||
|
|
||||||
|
// Stop info logs polluting test outputs
|
||||||
|
process.env.LOG_LEVEL = "error"
|
||||||
|
|
||||||
async function run(dir) {
|
async function run(dir) {
|
||||||
process.env.BUDIBASE_DIR = resolve(dir)
|
process.env.BUDIBASE_DIR = resolve(dir)
|
||||||
require("dotenv").config({ path: resolve(dir, ".env") })
|
require("dotenv").config({ path: resolve(dir, ".env") })
|
||||||
|
|
|
@ -44,9 +44,9 @@ Cypress.Commands.add("createApp", name => {
|
||||||
|
|
||||||
cy.contains("Next").click()
|
cy.contains("Next").click()
|
||||||
|
|
||||||
cy.get("input[name=username]")
|
cy.get("input[name=email]")
|
||||||
.click()
|
.click()
|
||||||
.type("test")
|
.type("test@test.com")
|
||||||
cy.get("input[name=password]")
|
cy.get("input[name=password]")
|
||||||
.click()
|
.click()
|
||||||
.type("test")
|
.type("test")
|
||||||
|
@ -111,26 +111,29 @@ Cypress.Commands.add("addRow", values => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createUser", (username, password, accessLevel) => {
|
Cypress.Commands.add("createUser", (email, password, role) => {
|
||||||
// Create User
|
// Create User
|
||||||
cy.get(".toprightnav > .settings").click()
|
|
||||||
cy.contains("Users").click()
|
cy.contains("Users").click()
|
||||||
|
|
||||||
cy.get("[name=Name]")
|
cy.contains("Create New Row").click()
|
||||||
.first()
|
|
||||||
.type(username)
|
cy.get(".modal").within(() => {
|
||||||
cy.get("[name=Password]")
|
cy.get("input")
|
||||||
.first()
|
.first()
|
||||||
.type(password)
|
.type(password)
|
||||||
|
cy.get("input")
|
||||||
|
.eq(1)
|
||||||
|
.type(email)
|
||||||
cy.get("select")
|
cy.get("select")
|
||||||
.first()
|
.first()
|
||||||
.select(accessLevel)
|
.select(role)
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
cy.get(".inputs")
|
cy.get(".buttons")
|
||||||
.contains("Create")
|
.contains("Create Row")
|
||||||
.click()
|
.click()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("addHeadlineComponent", text => {
|
Cypress.Commands.add("addHeadlineComponent", text => {
|
||||||
cy.get(".switcher > :nth-child(2)").click()
|
cy.get(".switcher > :nth-child(2)").click()
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.50.2",
|
"@budibase/bbui": "^1.52.2",
|
||||||
"@budibase/client": "^0.3.8",
|
"@budibase/client": "^0.3.8",
|
||||||
"@budibase/colorpicker": "^1.0.1",
|
"@budibase/colorpicker": "^1.0.1",
|
||||||
"@budibase/svelte-ag-grid": "^0.0.16",
|
"@budibase/svelte-ag-grid": "^0.0.16",
|
||||||
|
@ -81,8 +81,8 @@
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-loading-spinners": "^0.1.1",
|
"svelte-loading-spinners": "^0.1.1",
|
||||||
"svelte-portal": "^0.1.0",
|
"svelte-portal": "^0.1.0",
|
||||||
"yup": "^0.29.2",
|
"uuid": "^8.3.1",
|
||||||
"uuid": "^8.3.1"
|
"yup": "^0.29.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.5",
|
"@babel/core": "^7.5.5",
|
||||||
|
@ -90,6 +90,7 @@
|
||||||
"@babel/preset-env": "^7.5.5",
|
"@babel/preset-env": "^7.5.5",
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"@rollup/plugin-alias": "^3.0.1",
|
"@rollup/plugin-alias": "^3.0.1",
|
||||||
|
"@rollup/plugin-commonjs": "^16.0.0",
|
||||||
"@rollup/plugin-json": "^4.0.3",
|
"@rollup/plugin-json": "^4.0.3",
|
||||||
"@sveltech/routify": "1.7.11",
|
"@sveltech/routify": "1.7.11",
|
||||||
"@testing-library/jest-dom": "^5.11.0",
|
"@testing-library/jest-dom": "^5.11.0",
|
||||||
|
@ -104,9 +105,9 @@
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^2.11.2",
|
"rollup": "^2.11.2",
|
||||||
"rollup-plugin-alias": "^1.5.2",
|
"rollup-plugin-alias": "^1.5.2",
|
||||||
"rollup-plugin-commonjs": "^10.0.0",
|
|
||||||
"rollup-plugin-copy": "^3.0.0",
|
"rollup-plugin-copy": "^3.0.0",
|
||||||
"rollup-plugin-css-only": "^2.1.0",
|
"rollup-plugin-css-only": "^2.1.0",
|
||||||
|
"rollup-plugin-html": "^0.2.1",
|
||||||
"rollup-plugin-livereload": "^1.0.0",
|
"rollup-plugin-livereload": "^1.0.0",
|
||||||
"rollup-plugin-node-builtins": "^2.1.2",
|
"rollup-plugin-node-builtins": "^2.1.2",
|
||||||
"rollup-plugin-node-globals": "^1.4.0",
|
"rollup-plugin-node-globals": "^1.4.0",
|
||||||
|
@ -115,7 +116,7 @@
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"rollup-plugin-url": "^2.2.2",
|
"rollup-plugin-url": "^2.2.2",
|
||||||
"start-server-and-test": "^1.11.0",
|
"start-server-and-test": "^1.11.0",
|
||||||
"svelte": "^3.29.0",
|
"svelte": "^3.30.0",
|
||||||
"svelte-jester": "^1.0.6"
|
"svelte-jester": "^1.0.6"
|
||||||
},
|
},
|
||||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import alias from "@rollup/plugin-alias"
|
import alias from "@rollup/plugin-alias"
|
||||||
import svelte from "rollup-plugin-svelte"
|
import svelte from "rollup-plugin-svelte"
|
||||||
import resolve from "rollup-plugin-node-resolve"
|
import resolve from "rollup-plugin-node-resolve"
|
||||||
import commonjs from "rollup-plugin-commonjs"
|
import commonjs from "@rollup/plugin-commonjs"
|
||||||
import url from "rollup-plugin-url"
|
import url from "rollup-plugin-url"
|
||||||
import livereload from "rollup-plugin-livereload"
|
import livereload from "rollup-plugin-livereload"
|
||||||
import { terser } from "rollup-plugin-terser"
|
import { terser } from "rollup-plugin-terser"
|
||||||
|
@ -11,106 +11,12 @@ import copy from "rollup-plugin-copy"
|
||||||
import css from "rollup-plugin-css-only"
|
import css from "rollup-plugin-css-only"
|
||||||
import replace from "rollup-plugin-replace"
|
import replace from "rollup-plugin-replace"
|
||||||
import json from "@rollup/plugin-json"
|
import json from "@rollup/plugin-json"
|
||||||
|
import html from "rollup-plugin-html"
|
||||||
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
const production = !process.env.ROLLUP_WATCH
|
const production = !process.env.ROLLUP_WATCH
|
||||||
|
|
||||||
const lodash_fp_exports = [
|
|
||||||
"flow",
|
|
||||||
"pipe",
|
|
||||||
"union",
|
|
||||||
"reduce",
|
|
||||||
"isUndefined",
|
|
||||||
"cloneDeep",
|
|
||||||
"split",
|
|
||||||
"some",
|
|
||||||
"map",
|
|
||||||
"filter",
|
|
||||||
"isEmpty",
|
|
||||||
"countBy",
|
|
||||||
"includes",
|
|
||||||
"last",
|
|
||||||
"find",
|
|
||||||
"constant",
|
|
||||||
"take",
|
|
||||||
"first",
|
|
||||||
"intersection",
|
|
||||||
"mapValues",
|
|
||||||
"isNull",
|
|
||||||
"has",
|
|
||||||
"isInteger",
|
|
||||||
"isNumber",
|
|
||||||
"isString",
|
|
||||||
"isBoolean",
|
|
||||||
"isDate",
|
|
||||||
"isArray",
|
|
||||||
"isObject",
|
|
||||||
"clone",
|
|
||||||
"values",
|
|
||||||
"keyBy",
|
|
||||||
"isNaN",
|
|
||||||
"keys",
|
|
||||||
"orderBy",
|
|
||||||
"concat",
|
|
||||||
"reverse",
|
|
||||||
"difference",
|
|
||||||
"merge",
|
|
||||||
"flatten",
|
|
||||||
"each",
|
|
||||||
"pull",
|
|
||||||
"join",
|
|
||||||
"defaultCase",
|
|
||||||
"uniqBy",
|
|
||||||
"every",
|
|
||||||
"uniqWith",
|
|
||||||
"isFunction",
|
|
||||||
"groupBy",
|
|
||||||
"differenceBy",
|
|
||||||
"intersectionBy",
|
|
||||||
"isEqual",
|
|
||||||
"max",
|
|
||||||
"sortBy",
|
|
||||||
"assign",
|
|
||||||
"uniq",
|
|
||||||
"trimChars",
|
|
||||||
"trimCharsStart",
|
|
||||||
"isObjectLike",
|
|
||||||
"flattenDeep",
|
|
||||||
"indexOf",
|
|
||||||
"isPlainObject",
|
|
||||||
"toNumber",
|
|
||||||
"takeRight",
|
|
||||||
"toPairs",
|
|
||||||
"remove",
|
|
||||||
"findIndex",
|
|
||||||
"compose",
|
|
||||||
"get",
|
|
||||||
"tap",
|
|
||||||
]
|
|
||||||
|
|
||||||
const lodash_exports = [
|
|
||||||
"flow",
|
|
||||||
"join",
|
|
||||||
"replace",
|
|
||||||
"trim",
|
|
||||||
"dropRight",
|
|
||||||
"takeRight",
|
|
||||||
"head",
|
|
||||||
"reduce",
|
|
||||||
"tail",
|
|
||||||
"startsWith",
|
|
||||||
"findIndex",
|
|
||||||
"merge",
|
|
||||||
"assign",
|
|
||||||
"each",
|
|
||||||
"find",
|
|
||||||
"orderBy",
|
|
||||||
"union",
|
|
||||||
]
|
|
||||||
|
|
||||||
const outputpath = "../server/builder"
|
const outputpath = "../server/builder"
|
||||||
|
|
||||||
const coreExternal = [
|
const coreExternal = [
|
||||||
"lodash",
|
"lodash",
|
||||||
"lodash/fp",
|
"lodash/fp",
|
||||||
|
@ -170,10 +76,6 @@ export default {
|
||||||
{ src: "src/index.html", dest: outputpath },
|
{ src: "src/index.html", dest: outputpath },
|
||||||
{ src: "src/favicon.png", dest: outputpath },
|
{ src: "src/favicon.png", dest: outputpath },
|
||||||
{ src: "assets", dest: outputpath },
|
{ src: "assets", dest: outputpath },
|
||||||
{
|
|
||||||
src: "node_modules/@budibase/client/dist/budibase-client.esm.mjs",
|
|
||||||
dest: outputpath,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
src: "node_modules/@budibase/bbui/dist/bbui.css",
|
src: "node_modules/@budibase/bbui/dist/bbui.css",
|
||||||
dest: outputpath,
|
dest: outputpath,
|
||||||
|
@ -224,13 +126,7 @@ export default {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
commonjs({
|
commonjs(),
|
||||||
namedExports: {
|
|
||||||
"lodash/fp": lodash_fp_exports,
|
|
||||||
lodash: lodash_exports,
|
|
||||||
shortid: ["generate"],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
url({
|
url({
|
||||||
limit: 0,
|
limit: 0,
|
||||||
include: ["**/*.woff2", "**/*.png"],
|
include: ["**/*.woff2", "**/*.png"],
|
||||||
|
@ -248,5 +144,6 @@ export default {
|
||||||
// instead of npm run dev), minify
|
// instead of npm run dev), minify
|
||||||
production && terser(),
|
production && terser(),
|
||||||
json(),
|
json(),
|
||||||
|
html(),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { cloneDeep, difference } from "lodash/fp"
|
||||||
* @returns {Array.<BindableProperty>}
|
* @returns {Array.<BindableProperty>}
|
||||||
*/
|
*/
|
||||||
export default function({ componentInstanceId, screen, components, tables }) {
|
export default function({ componentInstanceId, screen, components, tables }) {
|
||||||
const walkResult = walk({
|
const result = walk({
|
||||||
// cloning so we are free to mutate props (e.g. by adding _contexts)
|
// cloning so we are free to mutate props (e.g. by adding _contexts)
|
||||||
instance: cloneDeep(screen.props),
|
instance: cloneDeep(screen.props),
|
||||||
targetId: componentInstanceId,
|
targetId: componentInstanceId,
|
||||||
|
@ -33,13 +33,10 @@ export default function({ componentInstanceId, screen, components, tables }) {
|
||||||
})
|
})
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...walkResult.bindableInstances
|
...result.bindableInstances
|
||||||
.filter(isInstanceInSharedContext(walkResult))
|
.filter(isInstanceInSharedContext(result))
|
||||||
.map(componentInstanceToBindable(walkResult)),
|
.map(componentInstanceToBindable),
|
||||||
|
...(result.target?._contexts.map(contextToBindables(tables)).flat() ?? []),
|
||||||
...(walkResult.target?._contexts
|
|
||||||
.map(contextToBindables(tables, walkResult))
|
|
||||||
.flat() ?? []),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,26 +50,18 @@ const isInstanceInSharedContext = walkResult => i =>
|
||||||
|
|
||||||
// turns a component instance prop into binding expressions
|
// turns a component instance prop into binding expressions
|
||||||
// used by the UI
|
// used by the UI
|
||||||
const componentInstanceToBindable = walkResult => i => {
|
const componentInstanceToBindable = i => {
|
||||||
const lastContext =
|
|
||||||
i.instance._contexts.length &&
|
|
||||||
i.instance._contexts[i.instance._contexts.length - 1]
|
|
||||||
const contextParentPath = lastContext
|
|
||||||
? getParentPath(walkResult, lastContext)
|
|
||||||
: ""
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "instance",
|
type: "instance",
|
||||||
instance: i.instance,
|
instance: i.instance,
|
||||||
// how the binding expression persists, and is used in the app at runtime
|
// how the binding expression persists, and is used in the app at runtime
|
||||||
runtimeBinding: `${contextParentPath}${i.instance._id}.${i.prop}`,
|
runtimeBinding: `${i.instance._id}`,
|
||||||
// how the binding exressions looks to the user of the builder
|
// how the binding exressions looks to the user of the builder
|
||||||
readableBinding: `${i.instance._instanceName}`,
|
readableBinding: `${i.instance._instanceName}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextToBindables = (tables, walkResult) => context => {
|
const contextToBindables = tables => context => {
|
||||||
const contextParentPath = getParentPath(walkResult, context)
|
|
||||||
const tableId = context.table?.tableId ?? context.table
|
const tableId = context.table?.tableId ?? context.table
|
||||||
const table = tables.find(table => table._id === tableId)
|
const table = tables.find(table => table._id === tableId)
|
||||||
let schema =
|
let schema =
|
||||||
|
@ -98,7 +87,7 @@ const contextToBindables = (tables, walkResult) => context => {
|
||||||
fieldSchema,
|
fieldSchema,
|
||||||
instance: context.instance,
|
instance: context.instance,
|
||||||
// how the binding expression persists, and is used in the app at runtime
|
// how the binding expression persists, and is used in the app at runtime
|
||||||
runtimeBinding: `${contextParentPath}data.${runtimeBoundKey}`,
|
runtimeBinding: `${context.instance._id}.${runtimeBoundKey}`,
|
||||||
// how the binding expressions looks to the user of the builder
|
// how the binding expressions looks to the user of the builder
|
||||||
readableBinding: `${context.instance._instanceName}.${table.name}.${key}`,
|
readableBinding: `${context.instance._instanceName}.${table.name}.${key}`,
|
||||||
// table / view info
|
// table / view info
|
||||||
|
@ -118,20 +107,6 @@ const contextToBindables = (tables, walkResult) => context => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getParentPath = (walkResult, context) => {
|
|
||||||
// describes the number of "parent" in the path
|
|
||||||
// clone array first so original array is not mtated
|
|
||||||
const contextParentNumber = [...walkResult.target._contexts]
|
|
||||||
.reverse()
|
|
||||||
.indexOf(context)
|
|
||||||
|
|
||||||
return (
|
|
||||||
new Array(contextParentNumber).fill("parent").join(".") +
|
|
||||||
// trailing . if has parents
|
|
||||||
(contextParentNumber ? "." : "")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const walk = ({ instance, targetId, components, tables, result }) => {
|
const walk = ({ instance, targetId, components, tables, result }) => {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = {
|
result = {
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { walkProps } from "./storeUtils"
|
||||||
import { get_capitalised_name } from "../helpers"
|
import { get_capitalised_name } from "../helpers"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { allScreens } from "builderStore"
|
import { allScreens } from "builderStore"
|
||||||
|
import { FrontendTypes } from "../constants"
|
||||||
|
import { currentAsset } from "."
|
||||||
|
|
||||||
export default function(component, state) {
|
export default function(component, state) {
|
||||||
const capitalised = get_capitalised_name(
|
const capitalised = get_capitalised_name(
|
||||||
|
@ -19,14 +21,16 @@ export default function(component, state) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// check page first
|
// check layouts first
|
||||||
findMatches(state.pages[state.currentPageName].props)
|
for (let layout of state.layouts) {
|
||||||
|
findMatches(layout.props)
|
||||||
|
}
|
||||||
|
|
||||||
// if viewing screen, check current screen for duplicate
|
// if viewing screen, check current screen for duplicate
|
||||||
if (state.currentFrontEndType === "screen") {
|
if (state.currentFrontEndType === FrontendTypes.SCREEN) {
|
||||||
findMatches(state.currentPreviewItem.props)
|
findMatches(get(currentAsset).props)
|
||||||
} else {
|
} else {
|
||||||
// viewing master page - need to find against all screens
|
// viewing a layout - need to find against all screens
|
||||||
for (let screen of get(allScreens)) {
|
for (let screen of get(allScreens)) {
|
||||||
findMatches(screen.props)
|
findMatches(screen.props)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,37 +4,74 @@ import { getAutomationStore } from "./store/automation/"
|
||||||
import { getThemeStore } from "./store/theme"
|
import { getThemeStore } from "./store/theme"
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
import { LAYOUT_NAMES } from "../constants"
|
||||||
|
import { makePropsSafe } from "components/userInterface/assetParsing/createProps"
|
||||||
|
|
||||||
export const store = getFrontendStore()
|
export const store = getFrontendStore()
|
||||||
export const backendUiStore = getBackendUiStore()
|
export const backendUiStore = getBackendUiStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
export const themeStore = getThemeStore()
|
export const themeStore = getThemeStore()
|
||||||
|
|
||||||
|
export const currentAsset = derived(store, $store => {
|
||||||
|
const layout = $store.layouts
|
||||||
|
? $store.layouts.find(layout => layout._id === $store.currentAssetId)
|
||||||
|
: null
|
||||||
|
|
||||||
|
if (layout) return layout
|
||||||
|
|
||||||
|
const screen = $store.screens
|
||||||
|
? $store.screens.find(screen => screen._id === $store.currentAssetId)
|
||||||
|
: null
|
||||||
|
|
||||||
|
if (screen) return screen
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
export const selectedComponent = derived(
|
||||||
|
[store, currentAsset],
|
||||||
|
([$store, $currentAsset]) => {
|
||||||
|
if (!$currentAsset || !$store.selectedComponentId) return null
|
||||||
|
|
||||||
|
function traverse(node, callback) {
|
||||||
|
if (node._id === $store.selectedComponentId) return callback(node)
|
||||||
|
|
||||||
|
if (node._children) {
|
||||||
|
node._children.forEach(child => traverse(child, callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.props) {
|
||||||
|
traverse(node.props, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let component
|
||||||
|
traverse($currentAsset, found => {
|
||||||
|
const componentIdentifier = found._component ?? found.props._component
|
||||||
|
const componentDef = componentIdentifier.startsWith("##")
|
||||||
|
? found
|
||||||
|
: $store.components[componentIdentifier]
|
||||||
|
|
||||||
|
component = makePropsSafe(componentDef, found)
|
||||||
|
})
|
||||||
|
|
||||||
|
return component
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const currentAssetName = derived(store, () => {
|
||||||
|
return currentAsset.name
|
||||||
|
})
|
||||||
|
|
||||||
|
// leave this as before for consistency
|
||||||
export const allScreens = derived(store, $store => {
|
export const allScreens = derived(store, $store => {
|
||||||
let screens = []
|
return $store.screens
|
||||||
if ($store.pages == null) {
|
|
||||||
return screens
|
|
||||||
}
|
|
||||||
for (let page of Object.values($store.pages)) {
|
|
||||||
screens = screens.concat(page._screens)
|
|
||||||
}
|
|
||||||
return screens
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const currentScreens = derived(store, $store => {
|
export const mainLayout = derived(store, $store => {
|
||||||
const currentScreens = $store.pages[$store.currentPageName]?._screens
|
return $store.layouts?.find(
|
||||||
if (currentScreens == null) {
|
layout => layout._id === LAYOUT_NAMES.MASTER.PRIVATE
|
||||||
return []
|
)
|
||||||
}
|
|
||||||
return Array.isArray(currentScreens)
|
|
||||||
? currentScreens
|
|
||||||
: Object.values(currentScreens)
|
|
||||||
})
|
|
||||||
|
|
||||||
export const selectedPage = derived(store, $store => {
|
|
||||||
if (!$store.pages) return null
|
|
||||||
|
|
||||||
return $store.pages[$store.currentPageName || "main"]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const initialise = async () => {
|
export const initialise = async () => {
|
||||||
|
|
|
@ -12,10 +12,7 @@ export function readableToRuntimeBinding(bindableProperties, textWithBindings) {
|
||||||
return boundValue === `{{ ${readableBinding} }}`
|
return boundValue === `{{ ${readableBinding} }}`
|
||||||
})
|
})
|
||||||
if (binding) {
|
if (binding) {
|
||||||
result = textWithBindings.replace(
|
result = result.replace(boundValue, `{{ ${binding.runtimeBinding} }}`)
|
||||||
boundValue,
|
|
||||||
`{{ ${binding.runtimeBinding} }}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -4,34 +4,36 @@ import {
|
||||||
createProps,
|
createProps,
|
||||||
getBuiltin,
|
getBuiltin,
|
||||||
makePropsSafe,
|
makePropsSafe,
|
||||||
} from "components/userInterface/pagesParsing/createProps"
|
} from "components/userInterface/assetParsing/createProps"
|
||||||
import { allScreens, backendUiStore, selectedPage } from "builderStore"
|
import {
|
||||||
import { generate_screen_css } from "../generate_css"
|
allScreens,
|
||||||
|
backendUiStore,
|
||||||
|
currentAsset,
|
||||||
|
mainLayout,
|
||||||
|
selectedComponent,
|
||||||
|
} from "builderStore"
|
||||||
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
|
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
|
||||||
import api from "../api"
|
import api from "../api"
|
||||||
import { DEFAULT_PAGES_OBJECT } from "../../constants"
|
import { FrontendTypes } from "../../constants"
|
||||||
import getNewComponentName from "../getNewComponentName"
|
import getNewComponentName from "../getNewComponentName"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
import {
|
import {
|
||||||
findChildComponentType,
|
findChildComponentType,
|
||||||
generateNewIdsForComponent,
|
generateNewIdsForComponent,
|
||||||
getComponentDefinition,
|
getComponentDefinition,
|
||||||
getParent,
|
findParent,
|
||||||
} from "../storeUtils"
|
} from "../storeUtils"
|
||||||
|
|
||||||
const INITIAL_FRONTEND_STATE = {
|
const INITIAL_FRONTEND_STATE = {
|
||||||
apps: [],
|
apps: [],
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
pages: DEFAULT_PAGES_OBJECT,
|
layouts: [],
|
||||||
mainUi: {},
|
screens: [],
|
||||||
unauthenticatedUi: {},
|
|
||||||
components: [],
|
components: [],
|
||||||
currentPreviewItem: null,
|
|
||||||
currentComponentInfo: null,
|
|
||||||
currentFrontEndType: "none",
|
currentFrontEndType: "none",
|
||||||
currentPageName: "",
|
currentAssetId: "",
|
||||||
currentComponentProps: null,
|
selectedComponentId: "",
|
||||||
errors: [],
|
errors: [],
|
||||||
hasAppPackage: false,
|
hasAppPackage: false,
|
||||||
libraries: null,
|
libraries: null,
|
||||||
|
@ -43,52 +45,13 @@ export const getFrontendStore = () => {
|
||||||
const store = writable({ ...INITIAL_FRONTEND_STATE })
|
const store = writable({ ...INITIAL_FRONTEND_STATE })
|
||||||
|
|
||||||
store.actions = {
|
store.actions = {
|
||||||
// TODO: REFACTOR
|
|
||||||
initialise: async pkg => {
|
initialise: async pkg => {
|
||||||
|
const { layouts, screens, application } = pkg
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.appId = pkg.application._id
|
state.appId = application._id
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
const screens = await api.get("/api/screens").then(r => r.json())
|
|
||||||
|
|
||||||
const mainScreens = screens.filter(screen =>
|
|
||||||
screen._id.includes(pkg.pages.main._id)
|
|
||||||
),
|
|
||||||
unauthScreens = screens.filter(screen =>
|
|
||||||
screen._id.includes(pkg.pages.unauthenticated._id)
|
|
||||||
)
|
|
||||||
pkg.pages = {
|
|
||||||
main: {
|
|
||||||
...pkg.pages.main,
|
|
||||||
_screens: mainScreens,
|
|
||||||
},
|
|
||||||
unauthenticated: {
|
|
||||||
...pkg.pages.unauthenticated,
|
|
||||||
_screens: unauthScreens,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the app has just been created
|
|
||||||
// we need to build the CSS and save
|
|
||||||
if (pkg.justCreated) {
|
|
||||||
for (let pageName of ["main", "unauthenticated"]) {
|
|
||||||
const page = pkg.pages[pageName]
|
|
||||||
store.actions.screens.regenerateCss(page)
|
|
||||||
for (let screen of page._screens) {
|
|
||||||
store.actions.screens.regenerateCss(screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
await api.post(`/api/pages/${page._id}`, {
|
|
||||||
page: {
|
|
||||||
componentLibraries: pkg.application.componentLibraries,
|
|
||||||
...page,
|
|
||||||
},
|
|
||||||
screens: page._screens,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg.justCreated = false
|
|
||||||
|
|
||||||
const components = await fetchComponentLibDefinitions(pkg.application._id)
|
const components = await fetchComponentLibDefinitions(pkg.application._id)
|
||||||
|
|
||||||
|
@ -99,7 +62,8 @@ export const getFrontendStore = () => {
|
||||||
name: pkg.application.name,
|
name: pkg.application.name,
|
||||||
description: pkg.application.description,
|
description: pkg.application.description,
|
||||||
appId: pkg.application._id,
|
appId: pkg.application._id,
|
||||||
pages: pkg.pages,
|
layouts,
|
||||||
|
screens,
|
||||||
hasAppPackage: true,
|
hasAppPackage: true,
|
||||||
builtins: [getBuiltin("##builtin/screenslot")],
|
builtins: [getBuiltin("##builtin/screenslot")],
|
||||||
appInstance: pkg.application.instance,
|
appInstance: pkg.application.instance,
|
||||||
|
@ -107,20 +71,6 @@ export const getFrontendStore = () => {
|
||||||
|
|
||||||
await backendUiStore.actions.database.select(pkg.application.instance)
|
await backendUiStore.actions.database.select(pkg.application.instance)
|
||||||
},
|
},
|
||||||
selectPageOrScreen: type => {
|
|
||||||
store.update(state => {
|
|
||||||
state.currentFrontEndType = type
|
|
||||||
|
|
||||||
const page = get(selectedPage)
|
|
||||||
|
|
||||||
const pageOrScreen = type === "page" ? page : page._screens[0]
|
|
||||||
|
|
||||||
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
|
|
||||||
state.currentPreviewItem = pageOrScreen
|
|
||||||
state.currentView = "detail"
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
routing: {
|
routing: {
|
||||||
fetch: async () => {
|
fetch: async () => {
|
||||||
const response = await api.get("/api/routing")
|
const response = await api.get("/api/routing")
|
||||||
|
@ -133,167 +83,170 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
select: screenId => {
|
select: async screenId => {
|
||||||
|
let promise
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const screen = get(allScreens).find(screen => screen._id === screenId)
|
const screen = get(allScreens).find(screen => screen._id === screenId)
|
||||||
state.currentPreviewItem = screen
|
if (!screen) return state
|
||||||
state.currentFrontEndType = "screen"
|
|
||||||
|
state.currentFrontEndType = FrontendTypes.SCREEN
|
||||||
|
state.currentAssetId = screenId
|
||||||
state.currentView = "detail"
|
state.currentView = "detail"
|
||||||
|
|
||||||
store.actions.screens.regenerateCssForCurrentScreen()
|
promise = store.actions.screens.regenerateCss(screen)
|
||||||
const safeProps = makePropsSafe(
|
state.selectedComponentId = screen.props?._id
|
||||||
state.components[screen.props._component],
|
|
||||||
screen.props
|
|
||||||
)
|
|
||||||
screen.props = safeProps
|
|
||||||
state.currentComponentInfo = safeProps
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
await promise
|
||||||
},
|
},
|
||||||
create: async screen => {
|
create: async screen => {
|
||||||
let savePromise
|
screen = await store.actions.screens.save(screen)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.currentPreviewItem = screen
|
state.currentAssetId = screen._id
|
||||||
state.currentComponentInfo = screen.props
|
state.selectedComponentId = screen.props._id
|
||||||
state.currentFrontEndType = "screen"
|
state.currentFrontEndType = FrontendTypes.SCREEN
|
||||||
|
|
||||||
if (state.currentPreviewItem) {
|
|
||||||
store.actions.screens.regenerateCss(state.currentPreviewItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
savePromise = store.actions.screens.save(screen)
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
return screen
|
||||||
await savePromise
|
|
||||||
},
|
},
|
||||||
save: async screen => {
|
save: async screen => {
|
||||||
const page = get(selectedPage)
|
|
||||||
const currentPageScreens = page._screens
|
|
||||||
|
|
||||||
const creatingNewScreen = screen._id === undefined
|
const creatingNewScreen = screen._id === undefined
|
||||||
|
const response = await api.post(`/api/screens`, screen)
|
||||||
|
screen = await response.json()
|
||||||
|
|
||||||
let savePromise
|
|
||||||
const response = await api.post(`/api/screens/${page._id}`, screen)
|
|
||||||
const json = await response.json()
|
|
||||||
screen._rev = json.rev
|
|
||||||
screen._id = json.id
|
|
||||||
const foundScreen = page._screens.findIndex(el => el._id === screen._id)
|
|
||||||
if (foundScreen !== -1) {
|
|
||||||
page._screens.splice(foundScreen, 1)
|
|
||||||
}
|
|
||||||
page._screens.push(screen)
|
|
||||||
|
|
||||||
// TODO: should carry out all server updates to screen in a single call
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
page._screens = currentPageScreens
|
const foundScreen = state.screens.findIndex(
|
||||||
|
el => el._id === screen._id
|
||||||
|
)
|
||||||
|
if (foundScreen !== -1) {
|
||||||
|
state.screens.splice(foundScreen, 1)
|
||||||
|
}
|
||||||
|
state.screens.push(screen)
|
||||||
|
|
||||||
if (creatingNewScreen) {
|
if (creatingNewScreen) {
|
||||||
state.currentPreviewItem = screen
|
|
||||||
const safeProps = makePropsSafe(
|
const safeProps = makePropsSafe(
|
||||||
state.components[screen.props._component],
|
state.components[screen.props._component],
|
||||||
screen.props
|
screen.props
|
||||||
)
|
)
|
||||||
state.currentComponentInfo = safeProps
|
state.selectedComponentId = safeProps._id
|
||||||
screen.props = safeProps
|
screen.props = safeProps
|
||||||
}
|
}
|
||||||
savePromise = store.actions.pages.save()
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
if (savePromise) await savePromise
|
return screen
|
||||||
},
|
},
|
||||||
regenerateCss: screen => {
|
regenerateCss: async asset => {
|
||||||
screen._css = generate_screen_css([screen.props])
|
const response = await api.post("/api/css/generate", asset)
|
||||||
|
asset._css = (await response.json())?.css
|
||||||
},
|
},
|
||||||
regenerateCssForCurrentScreen: () => {
|
regenerateCssForCurrentScreen: async () => {
|
||||||
const { currentPreviewItem } = get(store)
|
const asset = get(currentAsset)
|
||||||
if (currentPreviewItem) {
|
if (asset) {
|
||||||
store.actions.screens.regenerateCss(currentPreviewItem)
|
await store.actions.screens.regenerateCss(asset)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
delete: async screens => {
|
delete: async screens => {
|
||||||
let deletePromise
|
|
||||||
|
|
||||||
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
||||||
|
|
||||||
|
const screenDeletePromises = []
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const currentPage = get(selectedPage)
|
|
||||||
|
|
||||||
for (let screenToDelete of screensToDelete) {
|
for (let screenToDelete of screensToDelete) {
|
||||||
// Remove screen from current page as well
|
state.screens = state.screens.filter(
|
||||||
// TODO: Should be done server side
|
screen => screen._id !== screenToDelete._id
|
||||||
currentPage._screens = currentPage._screens.filter(
|
|
||||||
scr => scr._id !== screenToDelete._id
|
|
||||||
)
|
)
|
||||||
|
screenDeletePromises.push(
|
||||||
deletePromise = api.delete(
|
api.delete(
|
||||||
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
|
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
if (screenToDelete._id === state.currentAssetId) {
|
||||||
|
state.currentAssetId = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
await deletePromise
|
await Promise.all(screenDeletePromises)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
saveSelected: async () => {
|
saveSelected: async () => {
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
if (state.currentFrontEndType !== "page") {
|
const selectedAsset = get(currentAsset)
|
||||||
await store.actions.screens.save(state.currentPreviewItem)
|
|
||||||
|
if (state.currentFrontEndType !== FrontendTypes.LAYOUT) {
|
||||||
|
await store.actions.screens.save(selectedAsset)
|
||||||
|
} else {
|
||||||
|
await store.actions.layouts.save(selectedAsset)
|
||||||
}
|
}
|
||||||
await store.actions.pages.save()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pages: {
|
layouts: {
|
||||||
select: pageName => {
|
select: async layoutId => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const currentPage = state.pages[pageName]
|
const layout = store.actions.layouts.find(layoutId)
|
||||||
|
|
||||||
state.currentFrontEndType = "page"
|
state.currentFrontEndType = FrontendTypes.LAYOUT
|
||||||
state.currentView = "detail"
|
state.currentView = "detail"
|
||||||
state.currentPageName = pageName
|
|
||||||
|
|
||||||
// This is the root of many problems.
|
state.currentAssetId = layout._id
|
||||||
// Uncaught (in promise) TypeError: Cannot read property '_component' of undefined
|
state.selectedComponentId = layout.props?._id
|
||||||
// it appears that the currentPage sometimes has _props instead of props
|
|
||||||
// why
|
|
||||||
const safeProps = makePropsSafe(
|
|
||||||
state.components[currentPage.props._component],
|
|
||||||
currentPage.props
|
|
||||||
)
|
|
||||||
state.currentComponentInfo = safeProps
|
|
||||||
currentPage.props = safeProps
|
|
||||||
state.currentPreviewItem = state.pages[pageName]
|
|
||||||
store.actions.screens.regenerateCssForCurrentScreen()
|
|
||||||
|
|
||||||
for (let screen of get(allScreens)) {
|
|
||||||
screen._css = generate_screen_css([screen.props])
|
|
||||||
}
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
let cssPromises = []
|
||||||
save: async page => {
|
cssPromises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
||||||
const storeContents = get(store)
|
|
||||||
const pageName = storeContents.currentPageName || "main"
|
|
||||||
const pageToSave = page || storeContents.pages[pageName]
|
|
||||||
|
|
||||||
// TODO: revisit. This sends down a very weird payload
|
for (let screen of get(allScreens)) {
|
||||||
const response = await api.post(`/api/pages/${pageToSave._id}`, {
|
cssPromises.push(store.actions.screens.regenerateCss(screen))
|
||||||
page: {
|
}
|
||||||
componentLibraries: storeContents.pages.componentLibraries,
|
await Promise.all(cssPromises)
|
||||||
...pageToSave,
|
|
||||||
},
|
},
|
||||||
screens: pageToSave._screens,
|
save: async layout => {
|
||||||
})
|
const layoutToSave = cloneDeep(layout)
|
||||||
|
delete layoutToSave._css
|
||||||
|
|
||||||
|
const response = await api.post(`/api/layouts`, layoutToSave)
|
||||||
|
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
|
|
||||||
if (!json.ok) throw new Error("Error updating page")
|
store.update(state => {
|
||||||
|
const layoutIdx = state.layouts.findIndex(
|
||||||
|
stateLayout => stateLayout._id === json._id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (layoutIdx >= 0) {
|
||||||
|
// update existing layout
|
||||||
|
state.layouts.splice(layoutIdx, 1, json)
|
||||||
|
} else {
|
||||||
|
// save new layout
|
||||||
|
state.layouts.push(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentAssetId = json._id
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
find: layoutId => {
|
||||||
|
if (!layoutId) {
|
||||||
|
return get(mainLayout)
|
||||||
|
}
|
||||||
|
const storeContents = get(store)
|
||||||
|
return storeContents.layouts.find(layout => layout._id === layoutId)
|
||||||
|
},
|
||||||
|
delete: async layoutToDelete => {
|
||||||
|
const response = await api.delete(
|
||||||
|
`/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
const json = await response.json()
|
||||||
|
throw new Error(json.message)
|
||||||
|
}
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.pages[pageName]._rev = json.rev
|
state.layouts = state.layouts.filter(
|
||||||
|
layout => layout._id !== layoutToDelete._id
|
||||||
|
)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -301,17 +254,19 @@ export const getFrontendStore = () => {
|
||||||
components: {
|
components: {
|
||||||
select: component => {
|
select: component => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const componentDef = component._component.startsWith("##")
|
state.selectedComponentId = component._id
|
||||||
? component
|
|
||||||
: state.components[component._component]
|
|
||||||
state.currentComponentInfo = makePropsSafe(componentDef, component)
|
|
||||||
state.currentView = "component"
|
state.currentView = "component"
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
create: (componentToAdd, presetProps) => {
|
create: (componentToAdd, presetProps) => {
|
||||||
|
const selectedAsset = get(currentAsset)
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
function findSlot(component_array) {
|
function findSlot(component_array) {
|
||||||
|
if (!component_array) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for (let component of component_array) {
|
for (let component of component_array) {
|
||||||
if (component._component === "##builtin/screenslot") {
|
if (component._component === "##builtin/screenslot") {
|
||||||
return true
|
return true
|
||||||
|
@ -324,7 +279,7 @@ export const getFrontendStore = () => {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
componentToAdd.startsWith("##") &&
|
componentToAdd.startsWith("##") &&
|
||||||
findSlot(state.pages[state.currentPageName].props._children)
|
findSlot(selectedAsset?.props._children)
|
||||||
) {
|
) {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
@ -340,29 +295,34 @@ export const getFrontendStore = () => {
|
||||||
_instanceName: instanceName,
|
_instanceName: instanceName,
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentComponent =
|
const selected = get(selectedComponent)
|
||||||
state.components[state.currentComponentInfo._component]
|
|
||||||
|
|
||||||
const targetParent = currentComponent.children
|
const currentComponentDefinition =
|
||||||
? state.currentComponentInfo
|
state.components[selected._component]
|
||||||
: getParent(
|
|
||||||
state.currentPreviewItem.props,
|
|
||||||
state.currentComponentInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
// Don't continue if there's no parent
|
const allowsChildren = currentComponentDefinition.children
|
||||||
if (!targetParent) {
|
|
||||||
return state
|
// Determine where to put the new component.
|
||||||
|
let targetParent
|
||||||
|
if (allowsChildren) {
|
||||||
|
// Child of the selected component
|
||||||
|
targetParent = selected
|
||||||
|
} else {
|
||||||
|
// Sibling of selected component
|
||||||
|
targetParent = findParent(selectedAsset.props, selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
targetParent._children = targetParent._children.concat(
|
// Don't continue if there's no parent
|
||||||
newComponent.props
|
if (!targetParent) return state
|
||||||
)
|
|
||||||
|
// Push the new component
|
||||||
|
targetParent._children.push(newComponent.props)
|
||||||
|
|
||||||
store.actions.preview.saveSelected()
|
store.actions.preview.saveSelected()
|
||||||
|
|
||||||
state.currentView = "component"
|
state.currentView = "component"
|
||||||
state.currentComponentInfo = newComponent.props
|
state.selectedComponentId = newComponent.props._id
|
||||||
|
|
||||||
analytics.captureEvent("Added Component", {
|
analytics.captureEvent("Added Component", {
|
||||||
name: newComponent.props._component,
|
name: newComponent.props._component,
|
||||||
})
|
})
|
||||||
|
@ -370,14 +330,12 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
copy: (component, cut = false) => {
|
copy: (component, cut = false) => {
|
||||||
|
const selectedAsset = get(currentAsset)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.componentToPaste = cloneDeep(component)
|
state.componentToPaste = cloneDeep(component)
|
||||||
state.componentToPaste.isCut = cut
|
state.componentToPaste.isCut = cut
|
||||||
if (cut) {
|
if (cut) {
|
||||||
const parent = getParent(
|
const parent = findParent(selectedAsset.props, component._id)
|
||||||
state.currentPreviewItem.props,
|
|
||||||
component._id
|
|
||||||
)
|
|
||||||
parent._children = parent._children.filter(
|
parent._children = parent._children.filter(
|
||||||
child => child._id !== component._id
|
child => child._id !== component._id
|
||||||
)
|
)
|
||||||
|
@ -387,7 +345,9 @@ export const getFrontendStore = () => {
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
paste: (targetComponent, mode) => {
|
paste: async (targetComponent, mode) => {
|
||||||
|
const selectedAsset = get(currentAsset)
|
||||||
|
let promises = []
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
if (!state.componentToPaste) return state
|
if (!state.componentToPaste) return state
|
||||||
|
|
||||||
|
@ -406,54 +366,56 @@ export const getFrontendStore = () => {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = getParent(
|
const parent = findParent(selectedAsset.props, targetComponent)
|
||||||
state.currentPreviewItem.props,
|
|
||||||
targetComponent
|
|
||||||
)
|
|
||||||
|
|
||||||
const targetIndex = parent._children.indexOf(targetComponent)
|
const targetIndex = parent._children.indexOf(targetComponent)
|
||||||
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))
|
||||||
|
|
||||||
store.actions.screens.regenerateCssForCurrentScreen()
|
promises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
||||||
store.actions.preview.saveSelected()
|
promises.push(store.actions.preview.saveSelected())
|
||||||
store.actions.components.select(componentToPaste)
|
store.actions.components.select(componentToPaste)
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
await Promise.all(promises)
|
||||||
},
|
},
|
||||||
updateStyle: (type, name, value) => {
|
updateStyle: async (type, name, value) => {
|
||||||
store.update(state => {
|
let promises = []
|
||||||
if (!state.currentComponentInfo._styles) {
|
const selected = get(selectedComponent)
|
||||||
state.currentComponentInfo._styles = {}
|
|
||||||
}
|
|
||||||
state.currentComponentInfo._styles[type][name] = value
|
|
||||||
|
|
||||||
store.actions.screens.regenerateCssForCurrentScreen()
|
store.update(state => {
|
||||||
|
if (!selected._styles) {
|
||||||
|
selected._styles = {}
|
||||||
|
}
|
||||||
|
selected._styles[type][name] = value
|
||||||
|
|
||||||
|
promises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
||||||
|
|
||||||
// save without messing with the store
|
// save without messing with the store
|
||||||
store.actions.preview.saveSelected()
|
promises.push(store.actions.preview.saveSelected())
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
await Promise.all(promises)
|
||||||
},
|
},
|
||||||
updateProp: (name, value) => {
|
updateProp: (name, value) => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
let current_component = state.currentComponentInfo
|
let current_component = get(selectedComponent)
|
||||||
current_component[name] = value
|
current_component[name] = value
|
||||||
|
|
||||||
state.currentComponentInfo = current_component
|
state.selectedComponentId = current_component._id
|
||||||
store.actions.preview.saveSelected()
|
store.actions.preview.saveSelected()
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
findRoute: component => {
|
findRoute: component => {
|
||||||
// Gets all the components to needed to construct a path.
|
// Gets all the components to needed to construct a path.
|
||||||
const tempStore = get(store)
|
const selectedAsset = get(currentAsset)
|
||||||
let pathComponents = []
|
let pathComponents = []
|
||||||
let parent = component
|
let parent = component
|
||||||
let root = false
|
let root = false
|
||||||
while (!root) {
|
while (!root) {
|
||||||
parent = getParent(tempStore.currentPreviewItem.props, parent)
|
parent = findParent(selectedAsset.props, parent)
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
root = true
|
root = true
|
||||||
} else {
|
} else {
|
||||||
|
@ -461,7 +423,7 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove root entry since it's the screen or page layout.
|
// Remove root entry since it's the screen or layout.
|
||||||
// Reverse array since we need the correct order of the IDs
|
// Reverse array since we need the correct order of the IDs
|
||||||
const reversedComponents = pathComponents.reverse().slice(1)
|
const reversedComponents = pathComponents.reverse().slice(1)
|
||||||
|
|
||||||
|
@ -476,12 +438,13 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
links: {
|
links: {
|
||||||
save: async (url, title) => {
|
save: async (url, title) => {
|
||||||
let savePromise
|
let promises = []
|
||||||
|
const layout = get(mainLayout)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
// Try to extract a nav component from the master screen
|
// Try to extract a nav component from the master layout
|
||||||
const nav = findChildComponentType(
|
const nav = findChildComponentType(
|
||||||
state.pages.main,
|
layout,
|
||||||
"@budibase/standard-components/Navigation"
|
"@budibase/standard-components/navigation"
|
||||||
)
|
)
|
||||||
if (nav) {
|
if (nav) {
|
||||||
let newLink
|
let newLink
|
||||||
|
@ -513,18 +476,18 @@ export const getFrontendStore = () => {
|
||||||
}).props
|
}).props
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save page and regenerate all CSS because otherwise weird things happen
|
// Save layout and regenerate all CSS because otherwise weird things happen
|
||||||
nav._children = [...nav._children, newLink]
|
nav._children = [...nav._children, newLink]
|
||||||
state.currentPageName = "main"
|
state.currentAssetId = layout._id
|
||||||
store.actions.screens.regenerateCss(state.pages.main)
|
promises.push(store.actions.screens.regenerateCss(layout))
|
||||||
for (let screen of state.pages.main._screens) {
|
for (let screen of get(allScreens)) {
|
||||||
store.actions.screens.regenerateCss(screen)
|
promises.push(store.actions.screens.regenerateCss(screen))
|
||||||
}
|
}
|
||||||
savePromise = store.actions.pages.save()
|
promises.push(store.actions.layouts.save(layout))
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
await savePromise
|
await Promise.all(promises)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default function(tables) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const newRowUrl = table => sanitizeUrl(`/${table.name}/new`)
|
export const newRowUrl = table => sanitizeUrl(`/${table.name}/new/row`)
|
||||||
export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE"
|
export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE"
|
||||||
|
|
||||||
function generateTitleContainer(table) {
|
function generateTitleContainer(table) {
|
||||||
|
|
|
@ -14,9 +14,6 @@ export class Component extends BaseStructure {
|
||||||
active: {},
|
active: {},
|
||||||
selected: {},
|
selected: {},
|
||||||
},
|
},
|
||||||
_code: "",
|
|
||||||
className: "",
|
|
||||||
onLoad: [],
|
|
||||||
type: "",
|
type: "",
|
||||||
_instanceName: "",
|
_instanceName: "",
|
||||||
_children: [],
|
_children: [],
|
||||||
|
|
|
@ -4,6 +4,7 @@ export class Screen extends BaseStructure {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(true)
|
super(true)
|
||||||
this._json = {
|
this._json = {
|
||||||
|
layoutId: "layout_private_master",
|
||||||
props: {
|
props: {
|
||||||
_id: "",
|
_id: "",
|
||||||
_component: "",
|
_component: "",
|
||||||
|
@ -18,7 +19,7 @@ export class Screen extends BaseStructure {
|
||||||
},
|
},
|
||||||
routing: {
|
routing: {
|
||||||
route: "",
|
route: "",
|
||||||
accessLevelId: "",
|
roleId: "BASIC",
|
||||||
},
|
},
|
||||||
name: "screen-id",
|
name: "screen-id",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { getBuiltin } from "components/userInterface/pagesParsing/createProps"
|
import { getBuiltin } from "components/userInterface/assetParsing/createProps"
|
||||||
import { uuid } from "./uuid"
|
import { uuid } from "./uuid"
|
||||||
import getNewComponentName from "./getNewComponentName"
|
import getNewComponentName from "./getNewComponentName"
|
||||||
|
|
||||||
export const getParent = (rootProps, child) => {
|
/**
|
||||||
|
* Find the parent component of the passed in child.
|
||||||
|
* @param {Object} rootProps - props to search for the parent in
|
||||||
|
* @param {String|Object} child - id of the child or the child itself to find the parent of
|
||||||
|
*/
|
||||||
|
export const findParent = (rootProps, child) => {
|
||||||
let parent
|
let parent
|
||||||
walkProps(rootProps, (p, breakWalk) => {
|
walkProps(rootProps, (props, breakWalk) => {
|
||||||
if (
|
if (
|
||||||
p._children &&
|
props._children &&
|
||||||
(p._children.includes(child) || p._children.some(c => c._id === child))
|
(props._children.includes(child) ||
|
||||||
|
props._children.some(c => c._id === child))
|
||||||
) {
|
) {
|
||||||
parent = p
|
parent = props
|
||||||
breakWalk()
|
breakWalk()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -62,6 +62,8 @@
|
||||||
</Select>
|
</Select>
|
||||||
{:else if value.customType === 'password'}
|
{:else if value.customType === 'password'}
|
||||||
<Input type="password" extraThin bind:value={block.inputs[key]} />
|
<Input type="password" extraThin bind:value={block.inputs[key]} />
|
||||||
|
{:else if value.customType === 'email'}
|
||||||
|
<Input type="email" extraThin bind:value={block.inputs[key]} />
|
||||||
{:else if value.customType === 'table'}
|
{:else if value.customType === 'table'}
|
||||||
<TableSelector bind:value={block.inputs[key]} />
|
<TableSelector bind:value={block.inputs[key]} />
|
||||||
{:else if value.customType === 'row'}
|
{:else if value.customType === 'row'}
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui"
|
import {
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
Label,
|
||||||
|
DatePicker,
|
||||||
|
Toggle,
|
||||||
|
RichText,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { TableNames } from "constants"
|
||||||
import Dropzone from "components/common/Dropzone.svelte"
|
import Dropzone from "components/common/Dropzone.svelte"
|
||||||
import { capitalise } from "../../../helpers"
|
import { capitalise } from "../../../helpers"
|
||||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||||
|
|
||||||
export let meta
|
export let meta
|
||||||
|
export let creating
|
||||||
export let value = meta.type === "boolean" ? false : ""
|
export let value = meta.type === "boolean" ? false : ""
|
||||||
|
|
||||||
$: type = meta.type
|
$: type = meta.type
|
||||||
$: label = capitalise(meta.name)
|
$: label = capitalise(meta.name)
|
||||||
|
$: editingUser =
|
||||||
|
!creating && $backendUiStore.selectedTable?._id === TableNames.USERS
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === 'options'}
|
{#if type === 'options'}
|
||||||
|
@ -29,6 +41,17 @@
|
||||||
<Toggle text={label} bind:checked={value} data-cy="{meta.name}-input" />
|
<Toggle text={label} bind:checked={value} data-cy="{meta.name}-input" />
|
||||||
{:else if type === 'link'}
|
{:else if type === 'link'}
|
||||||
<LinkedRowSelector bind:linkedRows={value} schema={meta} />
|
<LinkedRowSelector bind:linkedRows={value} schema={meta} />
|
||||||
|
{:else if type === 'longform'}
|
||||||
|
<div>
|
||||||
|
<Label extraSmall grey>{label}</Label>
|
||||||
|
<RichText bind:value />
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Input thin {label} data-cy="{meta.name}-input" {type} bind:value />
|
<Input
|
||||||
|
thin
|
||||||
|
{label}
|
||||||
|
data-cy="{meta.name}-input"
|
||||||
|
{type}
|
||||||
|
bind:value
|
||||||
|
disabled={editingUser} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -7,8 +7,8 @@ export async function createUser(user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveRow(row, tableId) {
|
export async function saveRow(row, tableId) {
|
||||||
const SAVE_ROWS_URL = `/api/${tableId}/rows`
|
const SAVE_ROW_URL = `/api/${tableId}/rows`
|
||||||
const response = await api.post(SAVE_ROWS_URL, row)
|
const response = await api.post(SAVE_ROW_URL, row)
|
||||||
|
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { Input, Button, TextButton, Select, Toggle } from "@budibase/bbui"
|
import { Input, Button, TextButton, Select, Toggle } from "@budibase/bbui"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ValuesList from "components/common/ValuesList.svelte"
|
import ValuesList from "components/common/ValuesList.svelte"
|
||||||
|
@ -30,6 +31,9 @@
|
||||||
table => table._id !== $backendUiStore.draftTable._id
|
table => table._id !== $backendUiStore.draftTable._id
|
||||||
)
|
)
|
||||||
$: required = !!field?.constraints?.presence || primaryDisplay
|
$: required = !!field?.constraints?.presence || primaryDisplay
|
||||||
|
$: uneditable =
|
||||||
|
$backendUiStore.selectedTable?._id === TableNames.USERS &&
|
||||||
|
UNEDITABLE_USER_FIELDS.includes(field.name)
|
||||||
|
|
||||||
async function saveColumn() {
|
async function saveColumn() {
|
||||||
backendUiStore.update(state => {
|
backendUiStore.update(state => {
|
||||||
|
@ -87,7 +91,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="actions" class:hidden={deletion}>
|
<div class="actions" class:hidden={deletion}>
|
||||||
<Input label="Name" thin bind:value={field.name} />
|
<Input label="Name" thin bind:value={field.name} disabled={uneditable} />
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
disabled={originalName}
|
disabled={originalName}
|
||||||
|
@ -101,7 +105,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{#if field.type !== 'link'}
|
{#if field.type !== 'link' && !uneditable}
|
||||||
<Toggle
|
<Toggle
|
||||||
checked={required}
|
checked={required}
|
||||||
on:change={onChangeRequired}
|
on:change={onChangeRequired}
|
||||||
|
@ -157,7 +161,7 @@
|
||||||
bind:value={field.fieldName} />
|
bind:value={field.fieldName} />
|
||||||
{/if}
|
{/if}
|
||||||
<footer class="create-column-options">
|
<footer class="create-column-options">
|
||||||
{#if originalName}
|
{#if !uneditable && originalName}
|
||||||
<TextButton text on:click={confirmDelete}>Delete Column</TextButton>
|
<TextButton text on:click={confirmDelete}>Delete Column</TextButton>
|
||||||
{/if}
|
{/if}
|
||||||
<Button secondary on:click={onClosed}>Cancel</Button>
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { TableNames } from "constants"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import RowFieldControl from "../RowFieldControl.svelte"
|
import RowFieldControl from "../RowFieldControl.svelte"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
|
@ -21,9 +22,10 @@
|
||||||
{ ...row, tableId: table._id },
|
{ ...row, tableId: table._id },
|
||||||
table._id
|
table._id
|
||||||
)
|
)
|
||||||
|
|
||||||
if (rowResponse.errors) {
|
if (rowResponse.errors) {
|
||||||
errors = Object.keys(rowResponse.errors)
|
errors = Object.entries(rowResponse.errors)
|
||||||
.map(k => ({ dataPath: k, message: rowResponse.errors[k] }))
|
.map(([key, error]) => ({ dataPath: key, message: error }))
|
||||||
.flat()
|
.flat()
|
||||||
// Prevent modal closing if there were errors
|
// Prevent modal closing if there were errors
|
||||||
return false
|
return false
|
||||||
|
@ -38,9 +40,15 @@
|
||||||
confirmText={creating ? 'Create Row' : 'Save Row'}
|
confirmText={creating ? 'Create Row' : 'Save Row'}
|
||||||
onConfirm={saveRow}>
|
onConfirm={saveRow}>
|
||||||
<ErrorsBox {errors} />
|
<ErrorsBox {errors} />
|
||||||
|
{#if creating && table._id === TableNames.USERS}
|
||||||
|
<RowFieldControl
|
||||||
|
{creating}
|
||||||
|
meta={{ name: 'password', type: 'password' }}
|
||||||
|
bind:value={row.password} />
|
||||||
|
{/if}
|
||||||
{#each tableSchema as [key, meta]}
|
{#each tableSchema as [key, meta]}
|
||||||
<div>
|
<div>
|
||||||
<RowFieldControl {meta} bind:value={row[key]} />
|
<RowFieldControl {meta} bind:value={row[key]} {creating} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { TableNames } from "constants"
|
||||||
import ListItem from "./ListItem.svelte"
|
import ListItem from "./ListItem.svelte"
|
||||||
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
||||||
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||||
|
@ -43,7 +44,7 @@
|
||||||
{#each $backendUiStore.tables as table, idx}
|
{#each $backendUiStore.tables as table, idx}
|
||||||
<NavItem
|
<NavItem
|
||||||
border={idx > 0}
|
border={idx > 0}
|
||||||
icon={table.integration?.type ? 'ri-database-2-line' : 'ri-table-line'}
|
icon={table.integration?.type ? 'ri-database-2-line' : `ri-${table._id === TableNames.USERS ? 'user' : 'table'}-line`}
|
||||||
text={table.name}
|
text={table.name}
|
||||||
selected={selectedView === `all_${table._id}`}
|
selected={selectedView === `all_${table._id}`}
|
||||||
on:click={() => selectTable(table)}>
|
on:click={() => selectTable(table)}>
|
||||||
|
|
|
@ -54,14 +54,13 @@
|
||||||
const screens = screenTemplates($store, [table])
|
const screens = screenTemplates($store, [table])
|
||||||
.filter(template => defaultScreens.includes(template.id))
|
.filter(template => defaultScreens.includes(template.id))
|
||||||
.map(template => template.create())
|
.map(template => template.create())
|
||||||
store.actions.pages.select("main")
|
|
||||||
for (let screen of screens) {
|
for (let screen of screens) {
|
||||||
// Record the table that created this screen so we can link it later
|
// Record the table that created this screen so we can link it later
|
||||||
screen.autoTableId = table._id
|
screen.autoTableId = table._id
|
||||||
await store.actions.screens.create(screen)
|
await store.actions.screens.create(screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create autolink to newly created list page
|
// Create autolink to newly created list screen
|
||||||
const listScreen = screens.find(screen =>
|
const listScreen = screens.find(screen =>
|
||||||
screen.props._instanceName.endsWith("List")
|
screen.props._instanceName.endsWith("List")
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { General, Users, DangerZone, APIKeys } from "./tabs"
|
import { General, DangerZone, APIKeys } from "./tabs"
|
||||||
import { Switcher, ModalContent } from "@budibase/bbui"
|
import { Switcher, ModalContent } from "@budibase/bbui"
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
|
@ -8,11 +8,6 @@
|
||||||
key: "GENERAL",
|
key: "GENERAL",
|
||||||
component: General,
|
component: General,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Users",
|
|
||||||
key: "USERS",
|
|
||||||
component: Users,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "API Keys",
|
title: "API Keys",
|
||||||
key: "API_KEYS",
|
key: "API_KEYS",
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
<script>
|
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
import { Input, Select, Button } from "@budibase/bbui"
|
|
||||||
export let user
|
|
||||||
|
|
||||||
let editMode = false
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="inputs">
|
|
||||||
<Input
|
|
||||||
disabled
|
|
||||||
thin
|
|
||||||
bind:value={user.username}
|
|
||||||
name="Name"
|
|
||||||
placeholder="Username" />
|
|
||||||
<Select disabled={!editMode} bind:value={user.accessLevelId} thin secondary>
|
|
||||||
<option value="">Choose an option</option>
|
|
||||||
<option value="ADMIN">Admin</option>
|
|
||||||
<option value="POWER_USER">Power User</option>
|
|
||||||
</Select>
|
|
||||||
{#if editMode}
|
|
||||||
<Button
|
|
||||||
blue
|
|
||||||
on:click={() => {
|
|
||||||
dispatch('save', user)
|
|
||||||
editMode = false
|
|
||||||
}}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
{:else}
|
|
||||||
<Button secondary on:click={() => (editMode = true)}>Edit</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.inputs {
|
|
||||||
display: grid;
|
|
||||||
justify-items: stretch;
|
|
||||||
grid-gap: var(--spacing-m);
|
|
||||||
grid-template-columns: 1fr 1fr 140px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,114 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Input, Select, Button, Label } from "@budibase/bbui"
|
|
||||||
import UserRow from "../UserRow.svelte"
|
|
||||||
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
// import * as api from "../api"
|
|
||||||
|
|
||||||
let username = ""
|
|
||||||
let password = ""
|
|
||||||
let accessLevelId = "ADMIN"
|
|
||||||
|
|
||||||
$: valid = username && password && accessLevelId
|
|
||||||
$: appId = $store.appId
|
|
||||||
|
|
||||||
// Create user!
|
|
||||||
async function createUser() {
|
|
||||||
if (valid) {
|
|
||||||
const user = { name: username, username, password, accessLevelId }
|
|
||||||
const response = await api.post(`/api/users`, user)
|
|
||||||
const json = await response.json()
|
|
||||||
backendUiStore.actions.users.create(json)
|
|
||||||
fetchUsersPromise = fetchUsers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update user!
|
|
||||||
async function updateUser(event) {
|
|
||||||
let data = event.detail
|
|
||||||
delete data.password
|
|
||||||
const response = await api.put(`/api/users`, data)
|
|
||||||
const users = await response.json()
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.users = users
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
fetchUsersPromise = fetchUsers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get users
|
|
||||||
async function fetchUsers() {
|
|
||||||
const response = await api.get(`/api/users`)
|
|
||||||
const users = await response.json()
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.users = users
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return users
|
|
||||||
}
|
|
||||||
|
|
||||||
let fetchUsersPromise = fetchUsers()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div>
|
|
||||||
<Label extraSmall grey>Create New User</Label>
|
|
||||||
<div class="inputs">
|
|
||||||
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
|
||||||
<Input
|
|
||||||
thin
|
|
||||||
type="password"
|
|
||||||
bind:value={password}
|
|
||||||
name="Password"
|
|
||||||
placeholder="Password" />
|
|
||||||
<Select secondary bind:value={accessLevelId} thin>
|
|
||||||
<option value="">Choose an option</option>
|
|
||||||
<option value="ADMIN">Admin</option>
|
|
||||||
<option value="POWER_USER">Power User</option>
|
|
||||||
</Select>
|
|
||||||
<Button on:click={createUser} primary>Create</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label extraSmall grey>Current Users</Label>
|
|
||||||
{#await fetchUsersPromise}
|
|
||||||
Loading...
|
|
||||||
{:then users}
|
|
||||||
<ul>
|
|
||||||
{#each users as user}
|
|
||||||
<li>
|
|
||||||
<UserRow {user} on:save={updateUser} />
|
|
||||||
</li>
|
|
||||||
{:else}
|
|
||||||
<li>No Users found</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{:catch err}
|
|
||||||
Something went wrong when trying to fetch users. Please refresh (CMD + R /
|
|
||||||
CTRL + R) the page and try again.
|
|
||||||
{/await}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs {
|
|
||||||
display: grid;
|
|
||||||
justify-items: stretch;
|
|
||||||
grid-gap: var(--spacing-m);
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-m);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,6 +1,5 @@
|
||||||
export { default as General } from "./General.svelte"
|
export { default as General } from "./General.svelte"
|
||||||
export { default as Integrations } from "./Integrations.svelte"
|
export { default as Integrations } from "./Integrations.svelte"
|
||||||
export { default as Permissions } from "./Permissions.svelte"
|
export { default as Permissions } from "./Permissions.svelte"
|
||||||
export { default as Users } from "./Users.svelte"
|
|
||||||
export { default as APIKeys } from "./APIKeys.svelte"
|
export { default as APIKeys } from "./APIKeys.svelte"
|
||||||
export { default as DangerZone } from "./DangerZone.svelte"
|
export { default as DangerZone } from "./DangerZone.svelte"
|
||||||
|
|
|
@ -52,13 +52,13 @@
|
||||||
applicationName: string().required("Your application must have a name."),
|
applicationName: string().required("Your application must have a name."),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
username: string().required("Your application needs a first user."),
|
email: string()
|
||||||
|
.email()
|
||||||
|
.required("Your application needs a first user."),
|
||||||
password: string().required(
|
password: string().required(
|
||||||
"Please enter a password for your first user."
|
"Please enter a password for your first user."
|
||||||
),
|
),
|
||||||
accessLevelId: string().required(
|
roleId: string().required("You need to select a role for your user."),
|
||||||
"You need to select an access level for your user."
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -79,9 +79,7 @@
|
||||||
|
|
||||||
if (hasKey) {
|
if (hasKey) {
|
||||||
validationSchemas.shift()
|
validationSchemas.shift()
|
||||||
validationSchemas = validationSchemas
|
|
||||||
steps.shift()
|
steps.shift()
|
||||||
steps = steps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles form navigation
|
// Handles form navigation
|
||||||
|
@ -155,19 +153,17 @@
|
||||||
const pkg = await applicationPkg.json()
|
const pkg = await applicationPkg.json()
|
||||||
if (applicationPkg.ok) {
|
if (applicationPkg.ok) {
|
||||||
backendUiStore.actions.reset()
|
backendUiStore.actions.reset()
|
||||||
pkg.justCreated = true
|
|
||||||
await store.actions.initialise(pkg)
|
await store.actions.initialise(pkg)
|
||||||
automationStore.actions.fetch()
|
await automationStore.actions.fetch()
|
||||||
} else {
|
} else {
|
||||||
throw new Error(pkg)
|
throw new Error(pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
const user = {
|
const user = {
|
||||||
name: $createAppStore.values.username,
|
email: $createAppStore.values.email,
|
||||||
username: $createAppStore.values.username,
|
|
||||||
password: $createAppStore.values.password,
|
password: $createAppStore.values.password,
|
||||||
accessLevelId: $createAppStore.values.accessLevelId,
|
roleId: $createAppStore.values.roleId,
|
||||||
}
|
}
|
||||||
const userResp = await api.post(`/api/users`, user)
|
const userResp = await api.post(`/api/users`, user)
|
||||||
const json = await userResp.json()
|
const json = await userResp.json()
|
||||||
|
|
|
@ -2,18 +2,18 @@
|
||||||
import { Input, Select } from "@budibase/bbui"
|
import { Input, Select } from "@budibase/bbui"
|
||||||
export let validationErrors
|
export let validationErrors
|
||||||
|
|
||||||
let blurred = { username: false, password: false }
|
let blurred = { email: false, password: false }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h2>Create your first User</h2>
|
<h2>Create your first User</h2>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Input
|
<Input
|
||||||
on:input={() => (blurred.username = true)}
|
on:input={() => (blurred.email = true)}
|
||||||
label="Username"
|
label="Email"
|
||||||
name="username"
|
name="email"
|
||||||
placeholder="Username"
|
placeholder="Email"
|
||||||
type="name"
|
type="email"
|
||||||
error={blurred.username && validationErrors.username} />
|
error={blurred.email && validationErrors.email} />
|
||||||
<Input
|
<Input
|
||||||
on:input={() => (blurred.password = true)}
|
on:input={() => (blurred.password = true)}
|
||||||
label="Password"
|
label="Password"
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
type="password"
|
type="password"
|
||||||
error={blurred.password && validationErrors.password} />
|
error={blurred.password && validationErrors.password} />
|
||||||
<Select label="Access Level" secondary name="accessLevelId">
|
<Select label="Role" secondary name="roleId">
|
||||||
<option value="ADMIN">Admin</option>
|
<option value="ADMIN">Admin</option>
|
||||||
<option value="POWER_USER">Power User</option>
|
<option value="POWER_USER">Power User</option>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
|
@ -1,146 +1,79 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { onMount } from "svelte"
|
||||||
import { map, join } from "lodash/fp"
|
import { store, currentAsset } from "builderStore"
|
||||||
import iframeTemplate from "./iframeTemplate"
|
import iframeTemplate from "./iframeTemplate"
|
||||||
import { pipe } from "../../../helpers"
|
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
|
||||||
import { Screen } from "../../../builderStore/store/screenTemplates/utils/Screen"
|
import { FrontendTypes } from "../../../constants"
|
||||||
import { Component } from "../../../builderStore/store/screenTemplates/utils/Component"
|
|
||||||
|
|
||||||
let iframe
|
let iframe
|
||||||
let styles = ""
|
let layout
|
||||||
|
let screen
|
||||||
|
|
||||||
function transform_component(comp) {
|
// Create screen slot placeholder for use when a page is selected rather
|
||||||
const props = comp.props || comp
|
// than a screen
|
||||||
if (props && props._children && props._children.length) {
|
|
||||||
props._children = props._children.map(transform_component)
|
|
||||||
}
|
|
||||||
|
|
||||||
return props
|
|
||||||
}
|
|
||||||
|
|
||||||
const getComponentTypeName = component => {
|
|
||||||
let [componentName] = component._component.match(/[a-z]*$/)
|
|
||||||
return componentName || "element"
|
|
||||||
}
|
|
||||||
|
|
||||||
const headingStyle = {
|
|
||||||
width: "500px",
|
|
||||||
padding: "8px",
|
|
||||||
}
|
|
||||||
const textStyle = {
|
|
||||||
...headingStyle,
|
|
||||||
"max-width": "",
|
|
||||||
"text-align": "left",
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
|
||||||
.normalStyle(headingStyle)
|
|
||||||
.type("h1")
|
|
||||||
.text("Screen Slot")
|
|
||||||
.instanceName("Heading")
|
|
||||||
const textScreenDisplay = new Component("@budibase/standard-components/text")
|
|
||||||
.normalStyle(textStyle)
|
|
||||||
.instanceName("Text")
|
|
||||||
.type("none")
|
|
||||||
.text(
|
|
||||||
"The screens that you create will be displayed inside this box. This box is just a placeholder, to show you the position of screens."
|
|
||||||
)
|
|
||||||
const container = new Component("@budibase/standard-components/container")
|
|
||||||
.normalStyle({
|
|
||||||
display: "flex",
|
|
||||||
"flex-direction": "column",
|
|
||||||
"align-items": "center",
|
|
||||||
flex: "1 1 auto",
|
|
||||||
})
|
|
||||||
.type("div")
|
|
||||||
.instanceName("Container")
|
|
||||||
.addChild(heading)
|
|
||||||
.addChild(textScreenDisplay)
|
|
||||||
const screenPlaceholder = new Screen()
|
const screenPlaceholder = new Screen()
|
||||||
.name("Screen Placeholder")
|
.name("Screen Placeholder")
|
||||||
.route("*")
|
.route("*")
|
||||||
.component("@budibase/standard-components/container")
|
.component("@budibase/standard-components/screenslotplaceholder")
|
||||||
.mainType("div")
|
|
||||||
.instanceName("Content Placeholder")
|
.instanceName("Content Placeholder")
|
||||||
.normalStyle({
|
|
||||||
flex: "1 1 auto",
|
|
||||||
})
|
|
||||||
.addChild(container)
|
|
||||||
.json()
|
.json()
|
||||||
// TODO: this ID is attached to how the screen slot is rendered, confusing, would be better a type etc
|
|
||||||
screenPlaceholder.props._id = "screenslot-placeholder"
|
|
||||||
|
|
||||||
$: hasComponent = !!$store.currentPreviewItem
|
|
||||||
|
|
||||||
|
// Extract data to pass to the iframe
|
||||||
$: {
|
$: {
|
||||||
styles = ""
|
if ($store.currentFrontEndType === FrontendTypes.LAYOUT) {
|
||||||
// Apply the CSS from the currently selected page and its screens
|
layout = $currentAsset
|
||||||
const currentPage = $store.pages[$store.currentPageName]
|
screen = screenPlaceholder
|
||||||
styles += currentPage._css
|
} else {
|
||||||
for (let screen of currentPage._screens) {
|
screen = $currentAsset
|
||||||
styles += screen._css
|
layout = $store.layouts.find(layout => layout._id === screen?.layoutId)
|
||||||
}
|
}
|
||||||
styles = styles
|
|
||||||
}
|
}
|
||||||
|
$: selectedComponentId = $store.selectedComponentId ?? ""
|
||||||
$: stylesheetLinks = pipe($store.pages.stylesheets, [
|
$: previewData = {
|
||||||
map(s => `<link rel="stylesheet" href="${s}"/>`),
|
layout,
|
||||||
join("\n"),
|
screen,
|
||||||
])
|
|
||||||
|
|
||||||
$: screensExist =
|
|
||||||
$store.currentPreviewItem._screens &&
|
|
||||||
$store.currentPreviewItem._screens.length > 0
|
|
||||||
|
|
||||||
$: frontendDefinition = {
|
|
||||||
appId: $store.appId,
|
|
||||||
libraries: $store.libraries,
|
|
||||||
page: $store.pages[$store.currentPageName],
|
|
||||||
screens: [
|
|
||||||
$store.currentFrontEndType === "page"
|
|
||||||
? screenPlaceholder
|
|
||||||
: $store.currentPreviewItem,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
|
|
||||||
|
|
||||||
$: selectedComponentId = $store.currentComponentInfo
|
|
||||||
? $store.currentComponentInfo._id
|
|
||||||
: ""
|
|
||||||
|
|
||||||
const refreshContent = () => {
|
|
||||||
iframe.contentWindow.postMessage(
|
|
||||||
JSON.stringify({
|
|
||||||
styles,
|
|
||||||
stylesheetLinks,
|
|
||||||
selectedComponentType,
|
|
||||||
selectedComponentId,
|
selectedComponentId,
|
||||||
frontendDefinition,
|
|
||||||
appId: $store.appId,
|
|
||||||
instanceId: $backendUiStore.selectedDatabase._id,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (iframe)
|
// Saving pages and screens to the DB causes them to have _revs.
|
||||||
iframe.contentWindow.addEventListener("bb-ready", refreshContent, {
|
// These revisions change every time a save happens and causes
|
||||||
|
// these reactive statements to fire, even though the actual
|
||||||
|
// definition hasn't changed.
|
||||||
|
// By deleting all _rev properties we can avoid this and increase
|
||||||
|
// performance.
|
||||||
|
$: json = JSON.stringify(previewData)
|
||||||
|
$: strippedJson = json.replaceAll(/"_rev":\s*"[^"]+"/g, `"_rev":""`)
|
||||||
|
|
||||||
|
// Update the iframe with the builder info to render the correct preview
|
||||||
|
const refreshContent = message => {
|
||||||
|
if (iframe) {
|
||||||
|
iframe.contentWindow.postMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the preview when required
|
||||||
|
$: refreshContent(strippedJson)
|
||||||
|
|
||||||
|
// Initialise the app when mounted
|
||||||
|
onMount(() => {
|
||||||
|
iframe.contentWindow.addEventListener(
|
||||||
|
"bb-ready",
|
||||||
|
() => {
|
||||||
|
refreshContent(strippedJson)
|
||||||
|
},
|
||||||
|
{
|
||||||
once: true,
|
once: true,
|
||||||
})
|
|
||||||
|
|
||||||
$: if (iframe && frontendDefinition) {
|
|
||||||
refreshContent()
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="component-container">
|
<div class="component-container">
|
||||||
{#if hasComponent && $store.currentPreviewItem}
|
|
||||||
<iframe
|
<iframe
|
||||||
style="height: 100%; width: 100%"
|
style="height: 100%; width: 100%"
|
||||||
title="componentPreview"
|
title="componentPreview"
|
||||||
bind:this={iframe}
|
bind:this={iframe}
|
||||||
srcdoc={iframeTemplate} />
|
srcdoc={iframeTemplate} />
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -152,7 +85,6 @@
|
||||||
margin: auto;
|
margin: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-container iframe {
|
.component-container iframe {
|
||||||
border: 0;
|
border: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||||
|
<style>
|
||||||
|
body, html {
|
||||||
|
height: 100% !important;
|
||||||
|
font-family: Inter, sans-serif !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src='/assets/budibase-client.js'></script>
|
||||||
|
<script>
|
||||||
|
function receiveMessage(event) {
|
||||||
|
if (!event.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract data from message
|
||||||
|
const { selectedComponentId, layout, screen } = JSON.parse(event.data)
|
||||||
|
|
||||||
|
// Set some flags so the app knows we're in the builder
|
||||||
|
window["##BUDIBASE_IN_BUILDER##"] = true
|
||||||
|
window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout
|
||||||
|
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
|
||||||
|
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
|
||||||
|
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
|
||||||
|
|
||||||
|
// Initialise app
|
||||||
|
if (window.loadBudibase) {
|
||||||
|
loadBudibase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore clicks
|
||||||
|
["click", "mousedown"].forEach(type => {
|
||||||
|
document.addEventListener(type, function(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
return false
|
||||||
|
}, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener("message", receiveMessage)
|
||||||
|
window.dispatchEvent(new Event("bb-ready"))
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,77 +1 @@
|
||||||
export default `<html>
|
export { default } from "./iframeTemplate.html"
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
|
||||||
<style>
|
|
||||||
body, html {
|
|
||||||
height: 100%!important;
|
|
||||||
font-family: Inter !important;
|
|
||||||
margin: 0px!important;
|
|
||||||
}
|
|
||||||
*, *:before, *:after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.container-screenslot-placeholder {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
border-style: dashed !important;
|
|
||||||
border-width: 1px;
|
|
||||||
color: #000000;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.container-screenslot-placeholder span {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src='/assets/budibase-client.js'></script>
|
|
||||||
<script>
|
|
||||||
function receiveMessage(event) {
|
|
||||||
|
|
||||||
if (!event.data) return
|
|
||||||
|
|
||||||
const data = JSON.parse(event.data)
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (styles) document.head.removeChild(styles)
|
|
||||||
} catch(_) { }
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (selectedComponentStyle) document.head.removeChild(selectedComponentStyle)
|
|
||||||
} catch(_) { }
|
|
||||||
|
|
||||||
selectedComponentStyle = document.createElement('style');
|
|
||||||
document.head.appendChild(selectedComponentStyle)
|
|
||||||
var selectedCss = '.' + data.selectedComponentType + '-' + data.selectedComponentId + '{ border: 2px solid #0055ff; }'
|
|
||||||
selectedComponentStyle.appendChild(document.createTextNode(selectedCss))
|
|
||||||
|
|
||||||
styles = document.createElement('style')
|
|
||||||
document.head.appendChild(styles)
|
|
||||||
styles.appendChild(document.createTextNode(data.styles))
|
|
||||||
|
|
||||||
window["##BUDIBASE_FRONTEND_DEFINITION##"] = data.frontendDefinition;
|
|
||||||
if (window.loadBudibase) {
|
|
||||||
loadBudibase({ window, localStorage })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let styles
|
|
||||||
let selectedComponentStyle
|
|
||||||
|
|
||||||
document.addEventListener("click", function(e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
return false;
|
|
||||||
}, true)
|
|
||||||
|
|
||||||
window.addEventListener('message', receiveMessage)
|
|
||||||
window.dispatchEvent(new Event('bb-ready'))
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>`
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store } from "builderStore"
|
import { get } from "svelte/store"
|
||||||
|
import { store, currentAsset } from "builderStore"
|
||||||
import { getComponentDefinition } from "builderStore/storeUtils"
|
import { getComponentDefinition } from "builderStore/storeUtils"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { last } from "lodash/fp"
|
import { last } from "lodash/fp"
|
||||||
import { getParent } from "builderStore/storeUtils"
|
import { findParent } from "builderStore/storeUtils"
|
||||||
import { DropdownMenu } from "@budibase/bbui"
|
import { DropdownMenu } from "@budibase/bbui"
|
||||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||||
|
|
||||||
|
@ -15,7 +16,8 @@
|
||||||
let anchor
|
let anchor
|
||||||
|
|
||||||
$: noChildrenAllowed =
|
$: noChildrenAllowed =
|
||||||
!component || !getComponentDefinition($store, component._component).children
|
!component ||
|
||||||
|
!getComponentDefinition($store, component._component)?.children
|
||||||
$: noPaste = !$store.componentToPaste
|
$: noPaste = !$store.componentToPaste
|
||||||
|
|
||||||
const lastPartOfName = c => (c ? last(c._component.split("/")) : "")
|
const lastPartOfName = c => (c ? last(c._component.split("/")) : "")
|
||||||
|
@ -27,12 +29,13 @@
|
||||||
const selectComponent = component => {
|
const selectComponent = component => {
|
||||||
store.actions.components.select(component)
|
store.actions.components.select(component)
|
||||||
const path = store.actions.components.findRoute(component)
|
const path = store.actions.components.findRoute(component)
|
||||||
$goto(`./:page/:screen/${path}`)
|
$goto(`./${$store.currentFrontEndType}/${path}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveUpComponent = () => {
|
const moveUpComponent = () => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const parent = getParent(state.currentPreviewItem.props, component)
|
const asset = get(currentAsset)
|
||||||
|
const parent = findParent(asset.props, component)
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const currentIndex = parent._children.indexOf(component)
|
const currentIndex = parent._children.indexOf(component)
|
||||||
|
@ -42,7 +45,7 @@
|
||||||
newChildren.splice(currentIndex - 1, 0, component)
|
newChildren.splice(currentIndex - 1, 0, component)
|
||||||
parent._children = newChildren
|
parent._children = newChildren
|
||||||
}
|
}
|
||||||
state.currentComponentInfo = component
|
state.selectedComponentId = component._id
|
||||||
store.actions.preview.saveSelected()
|
store.actions.preview.saveSelected()
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
@ -51,7 +54,8 @@
|
||||||
|
|
||||||
const moveDownComponent = () => {
|
const moveDownComponent = () => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const parent = getParent(state.currentPreviewItem.props, component)
|
const asset = get(currentAsset)
|
||||||
|
const parent = findParent(asset.props, component)
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const currentIndex = parent._children.indexOf(component)
|
const currentIndex = parent._children.indexOf(component)
|
||||||
|
@ -61,7 +65,7 @@
|
||||||
newChildren.splice(currentIndex + 1, 0, component)
|
newChildren.splice(currentIndex + 1, 0, component)
|
||||||
parent._children = newChildren
|
parent._children = newChildren
|
||||||
}
|
}
|
||||||
state.currentComponentInfo = component
|
state.selectedComponentId = component._id
|
||||||
store.actions.preview.saveSelected()
|
store.actions.preview.saveSelected()
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
@ -75,10 +79,11 @@
|
||||||
|
|
||||||
const deleteComponent = () => {
|
const deleteComponent = () => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const parent = getParent(state.currentPreviewItem.props, component)
|
const asset = get(currentAsset)
|
||||||
|
const parent = findParent(asset.props, component)
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent._children = parent._children.filter(c => c !== component)
|
parent._children = parent._children.filter(child => child !== component)
|
||||||
selectComponent(parent)
|
selectComponent(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
import { getComponentDefinition } from "builderStore/storeUtils"
|
import { getComponentDefinition } from "builderStore/storeUtils"
|
||||||
import { DropEffect, DropPosition } from "./dragDropStore"
|
import { DropEffect, DropPosition } from "./dragDropStore"
|
||||||
import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte"
|
import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte"
|
||||||
|
@ -10,7 +10,6 @@
|
||||||
export let currentComponent
|
export let currentComponent
|
||||||
export let onSelect = () => {}
|
export let onSelect = () => {}
|
||||||
export let level = 0
|
export let level = 0
|
||||||
|
|
||||||
export let dragDropStore
|
export let dragDropStore
|
||||||
|
|
||||||
const isScreenslot = name => name === "##builtin/screenslot"
|
const isScreenslot = name => name === "##builtin/screenslot"
|
||||||
|
@ -23,7 +22,7 @@
|
||||||
const path = store.actions.components.findRoute(component)
|
const path = store.actions.components.findRoute(component)
|
||||||
|
|
||||||
// Go to correct URL
|
// Go to correct URL
|
||||||
$goto(`./:page/:screen/${path}`)
|
$goto(`./${$store.currentAssetId}/${path}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dragstart = component => e => {
|
const dragstart = component => e => {
|
||||||
|
@ -73,7 +72,7 @@
|
||||||
text={isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
text={isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
||||||
withArrow
|
withArrow
|
||||||
indentLevel={level + 3}
|
indentLevel={level + 3}
|
||||||
selected={currentComponent === component}>
|
selected={$store.selectedComponentId === component._id}>
|
||||||
<ComponentDropdownMenu {component} />
|
<ComponentDropdownMenu {component} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@sveltech/routify"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import { DropdownMenu, Modal, ModalContent, Input } from "@budibase/bbui"
|
||||||
|
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
|
export let layout
|
||||||
|
|
||||||
|
let confirmDeleteDialog
|
||||||
|
let editLayoutNameModal
|
||||||
|
let dropdown
|
||||||
|
let anchor
|
||||||
|
let name = layout.name
|
||||||
|
|
||||||
|
const deleteLayout = async () => {
|
||||||
|
try {
|
||||||
|
await store.actions.layouts.delete(layout)
|
||||||
|
notifier.success(`Layout ${layout.name} deleted successfully.`)
|
||||||
|
} catch (err) {
|
||||||
|
notifier.danger(`Error deleting layout: ${err.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveLayout = async () => {
|
||||||
|
try {
|
||||||
|
const layoutToSave = cloneDeep(layout)
|
||||||
|
layoutToSave.name = name
|
||||||
|
await store.actions.layouts.save(layoutToSave)
|
||||||
|
notifier.success(`Layout saved successfully.`)
|
||||||
|
} catch (err) {
|
||||||
|
notifier.danger(`Error saving layout: ${err.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor} on:click|stopPropagation>
|
||||||
|
<div class="icon" on:click={() => dropdown.show()}>
|
||||||
|
<i class="ri-more-line" />
|
||||||
|
</div>
|
||||||
|
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
||||||
|
<DropdownContainer>
|
||||||
|
<DropdownItem
|
||||||
|
icon="ri-pencil-line"
|
||||||
|
title="Edit"
|
||||||
|
on:click={() => editLayoutNameModal.show()} />
|
||||||
|
<DropdownItem
|
||||||
|
icon="ri-delete-bin-line"
|
||||||
|
title="Delete"
|
||||||
|
on:click={() => confirmDeleteDialog.show()} />
|
||||||
|
</DropdownContainer>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={confirmDeleteDialog}
|
||||||
|
title="Confirm Deletion"
|
||||||
|
body={'Are you sure you wish to delete this layout?'}
|
||||||
|
okText="Delete Layout"
|
||||||
|
onOk={deleteLayout} />
|
||||||
|
|
||||||
|
<Modal bind:this={editLayoutNameModal}>
|
||||||
|
<ModalContent
|
||||||
|
title="Edit Layout Name"
|
||||||
|
confirmText="Save"
|
||||||
|
onConfirm={saveLayout}
|
||||||
|
disabled={!name}>
|
||||||
|
<Input thin type="text" label="Name" bind:value={name} />
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icon i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,25 +1,32 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store } from "builderStore"
|
import { store, selectedComponent, currentAsset } from "builderStore"
|
||||||
import instantiateStore from "./dragDropStore"
|
import instantiateStore from "./dragDropStore"
|
||||||
|
|
||||||
import ComponentsTree from "./ComponentTree.svelte"
|
import ComponentTree from "./ComponentTree.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||||
|
|
||||||
|
const ROUTE_NAME_MAP = {
|
||||||
|
"/": {
|
||||||
|
BASIC: "Home",
|
||||||
|
PUBLIC: "Login",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const dragDropStore = instantiateStore()
|
const dragDropStore = instantiateStore()
|
||||||
|
|
||||||
export let route
|
export let route
|
||||||
export let path
|
export let path
|
||||||
export let indent
|
export let indent
|
||||||
|
|
||||||
$: selectedScreen = $store.currentPreviewItem
|
$: selectedScreen = $currentAsset
|
||||||
|
|
||||||
const changeScreen = screenId => {
|
const changeScreen = screenId => {
|
||||||
// select the route
|
// select the route
|
||||||
store.actions.screens.select(screenId)
|
store.actions.screens.select(screenId)
|
||||||
$goto(`./:page/${screenId}`)
|
$goto(`./${screenId}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -30,21 +37,21 @@
|
||||||
withArrow={route.subpaths} />
|
withArrow={route.subpaths} />
|
||||||
|
|
||||||
{#each Object.entries(route.subpaths) as [url, subpath]}
|
{#each Object.entries(route.subpaths) as [url, subpath]}
|
||||||
{#each Object.values(subpath.screens) as screenId}
|
{#each Object.entries(subpath.screens) as [role, screenId]}
|
||||||
<NavItem
|
<NavItem
|
||||||
icon="ri-artboard-2-line"
|
icon="ri-artboard-2-line"
|
||||||
indentLevel={indent || 1}
|
indentLevel={indent || 1}
|
||||||
selected={$store.currentPreviewItem._id === screenId}
|
selected={$store.currentAssetId === screenId}
|
||||||
opened={$store.currentPreviewItem._id === screenId}
|
opened={$store.currentAssetId === screenId}
|
||||||
text={url === '/' ? 'Home' : url}
|
text={ROUTE_NAME_MAP[url]?.[role] || url}
|
||||||
withArrow={route.subpaths}
|
withArrow={route.subpaths}
|
||||||
on:click={() => changeScreen(screenId)}>
|
on:click={() => changeScreen(screenId)}>
|
||||||
<ScreenDropdownMenu screen={screenId} />
|
<ScreenDropdownMenu {screenId} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
{#if selectedScreen?._id === screenId}
|
{#if selectedScreen?._id === screenId}
|
||||||
<ComponentsTree
|
<ComponentTree
|
||||||
components={selectedScreen.props._children}
|
components={selectedScreen.props._children}
|
||||||
currentComponent={$store.currentComponentInfo}
|
currentComponent={$selectedComponent}
|
||||||
{dragDropStore} />
|
{dragDropStore} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -3,28 +3,27 @@
|
||||||
import { store, allScreens } from "builderStore"
|
import { store, allScreens } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { DropdownMenu } from "@budibase/bbui"
|
import { DropdownMenu, Modal, ModalContent } from "@budibase/bbui"
|
||||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||||
|
|
||||||
export let screen
|
export let screenId
|
||||||
|
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let dropdown
|
let dropdown
|
||||||
let anchor
|
let anchor
|
||||||
|
|
||||||
|
$: screen = $allScreens.find(screen => screen._id === screenId)
|
||||||
|
|
||||||
const deleteScreen = () => {
|
const deleteScreen = () => {
|
||||||
const screenToDelete = $allScreens.find(scr => scr._id === screen)
|
try {
|
||||||
store.actions.screens.delete(screenToDelete)
|
store.actions.screens.delete(screen)
|
||||||
store.actions.routing.fetch()
|
store.actions.routing.fetch()
|
||||||
// update the page if required
|
confirmDeleteDialog.hide()
|
||||||
store.update(state => {
|
$goto("../")
|
||||||
if (state.currentPreviewItem._id === screen) {
|
notifier.success("Deleted screen successfully.")
|
||||||
store.actions.pages.select($store.currentPageName)
|
} catch (err) {
|
||||||
notifier.success(`Screen ${screenToDelete.name} deleted successfully.`)
|
notifier.danger("Error deleting screen")
|
||||||
$goto(`./:page/page-layout`)
|
|
||||||
}
|
}
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default function() {
|
||||||
if (mousePosition > 0.4 && mousePosition < 0.8) {
|
if (mousePosition > 0.4 && mousePosition < 0.8) {
|
||||||
state.dropPosition = DropPosition.INSIDE
|
state.dropPosition = DropPosition.INSIDE
|
||||||
}
|
}
|
||||||
return
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
// bottom half
|
// bottom half
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
{#each Object.keys($store.routes) as path}
|
{#each Object.keys($store.routes || {}) as path}
|
||||||
<PathTree {path} route={$store.routes[path]} />
|
<PathTree {path} route={$store.routes[path]} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { get } from "svelte/store"
|
||||||
|
import { store, selectedComponent, currentAsset } from "builderStore"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
import panelStructure from "./temporaryPanelStructure.js"
|
import panelStructure from "./temporaryPanelStructure.js"
|
||||||
import CategoryTab from "./CategoryTab.svelte"
|
import CategoryTab from "./CategoryTab.svelte"
|
||||||
import DesignView from "./DesignView.svelte"
|
import DesignView from "./DesignView.svelte"
|
||||||
|
@ -14,8 +16,8 @@
|
||||||
|
|
||||||
$: componentInstance =
|
$: componentInstance =
|
||||||
$store.currentView !== "component"
|
$store.currentView !== "component"
|
||||||
? { ...$store.currentPreviewItem, ...$store.currentComponentInfo }
|
? { ...$currentAsset, ...$selectedComponent }
|
||||||
: $store.currentComponentInfo
|
: $selectedComponent
|
||||||
$: componentDefinition = $store.components[componentInstance._component]
|
$: componentDefinition = $store.components[componentInstance._component]
|
||||||
$: componentPropDefinition =
|
$: componentPropDefinition =
|
||||||
flattenedPanel.find(
|
flattenedPanel.find(
|
||||||
|
@ -31,7 +33,7 @@
|
||||||
|
|
||||||
$: isComponentOrScreen =
|
$: isComponentOrScreen =
|
||||||
$store.currentView === "component" ||
|
$store.currentView === "component" ||
|
||||||
$store.currentFrontEndType === "screen"
|
$store.currentFrontEndType === FrontendTypes.SCREEN
|
||||||
$: isNotScreenslot = componentInstance._component !== "##builtin/screenslot"
|
$: isNotScreenslot = componentInstance._component !== "##builtin/screenslot"
|
||||||
|
|
||||||
$: displayName =
|
$: displayName =
|
||||||
|
@ -58,16 +60,20 @@
|
||||||
return components
|
return components
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPageOrScreenProp(name, value) {
|
function setAssetProps(name, value) {
|
||||||
|
const selectedAsset = get(currentAsset)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
if (name === "_instanceName" && state.currentFrontEndType === "screen") {
|
if (
|
||||||
state.currentPreviewItem.props[name] = value
|
name === "_instanceName" &&
|
||||||
|
state.currentFrontEndType === FrontendTypes.SCREEN
|
||||||
|
) {
|
||||||
|
selectedAsset.props._instanceName = value
|
||||||
} else {
|
} else {
|
||||||
state.currentPreviewItem[name] = value
|
selectedAsset[name] = value
|
||||||
}
|
}
|
||||||
store.actions.preview.saveSelected()
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
store.actions.preview.saveSelected()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProps(obj, keys) {
|
function getProps(obj, keys) {
|
||||||
|
@ -94,21 +100,12 @@
|
||||||
{panelDefinition}
|
{panelDefinition}
|
||||||
displayNameField={displayName}
|
displayNameField={displayName}
|
||||||
onChange={store.actions.components.updateProp}
|
onChange={store.actions.components.updateProp}
|
||||||
onScreenPropChange={setPageOrScreenProp}
|
onScreenPropChange={setAssetProps}
|
||||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
assetInstance={$store.currentView !== 'component' && $currentAsset} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.title > div:nth-child(1) {
|
|
||||||
grid-column-start: name;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title > div:nth-child(2) {
|
|
||||||
grid-column-start: actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
.component-props-container {
|
.component-props-container {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto, url } from "@sveltech/routify"
|
||||||
import { store } from "builderStore"
|
import { store, currentAssetName, selectedComponent } from "builderStore"
|
||||||
import components from "./temporaryPanelStructure.js"
|
import components from "./temporaryPanelStructure.js"
|
||||||
import { DropdownMenu } from "@budibase/bbui"
|
import { DropdownMenu } from "@budibase/bbui"
|
||||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||||
|
@ -26,8 +26,8 @@
|
||||||
|
|
||||||
const onComponentChosen = component => {
|
const onComponentChosen = component => {
|
||||||
store.actions.components.create(component._component, component.presetProps)
|
store.actions.components.create(component._component, component.presetProps)
|
||||||
const path = store.actions.components.findRoute($store.currentComponentInfo)
|
const path = store.actions.components.findRoute($selectedComponent)
|
||||||
$goto(`./:page/:screen/${path}`)
|
$goto(`./${$store.currentAssetId}/${path}`)
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
align="left">
|
align="left">
|
||||||
<DropdownContainer>
|
<DropdownContainer>
|
||||||
{#each categories[selectedIndex].children as item}
|
{#each categories[selectedIndex].children as item}
|
||||||
{#if !item.showOnPages || item.showOnPages.includes($store.currentPageName)}
|
{#if !item.showOnAsset || item.showOnAsset.includes($currentAssetName)}
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
title={item.name}
|
title={item.name}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, allScreens } from "builderStore"
|
import { store, allScreens } from "builderStore"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
import ComponentPropertiesPanel from "./ComponentPropertiesPanel.svelte"
|
import ComponentPropertiesPanel from "./ComponentPropertiesPanel.svelte"
|
||||||
import ComponentSelectionList from "./ComponentSelectionList.svelte"
|
import ComponentSelectionList from "./ComponentSelectionList.svelte"
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
{#if $store.currentFrontEndType === 'page' || $allScreens.length}
|
{#if $store.currentFrontEndType === FrontendTypes.LAYOUT || $allScreens.length}
|
||||||
<div class="switcher">
|
<div class="switcher">
|
||||||
<button
|
<button
|
||||||
class:selected={selected === COMPONENT_SELECTION_TAB}
|
class:selected={selected === COMPONENT_SELECTION_TAB}
|
||||||
|
|
|
@ -12,8 +12,6 @@
|
||||||
let propGroup = null
|
let propGroup = null
|
||||||
let currentGroup
|
let currentGroup
|
||||||
|
|
||||||
const getProperties = name => panelDefinition[name]
|
|
||||||
|
|
||||||
function onChange(category) {
|
function onChange(category) {
|
||||||
selectedCategory = category
|
selectedCategory = category
|
||||||
}
|
}
|
||||||
|
@ -38,7 +36,7 @@
|
||||||
{#each propertyGroupNames as groupName}
|
{#each propertyGroupNames as groupName}
|
||||||
<PropertyGroup
|
<PropertyGroup
|
||||||
name={groupName}
|
name={groupName}
|
||||||
properties={getProperties(groupName)}
|
properties={panelDefinition[groupName]}
|
||||||
styleCategory={selectedCategory}
|
styleCategory={selectedCategory}
|
||||||
{onStyleChanged}
|
{onStyleChanged}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
|
@ -64,9 +62,6 @@
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
.design-view-state-categories {
|
|
||||||
}
|
|
||||||
|
|
||||||
.positioned-wrapper {
|
.positioned-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { TextButton, Body, DropdownMenu, ModalContent } from "@budibase/bbui"
|
import { TextButton, Body, DropdownMenu, ModalContent } from "@budibase/bbui"
|
||||||
import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
|
import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
|
||||||
import { EVENT_TYPE_MEMBER_NAME } from "../../../../../client/src/state/eventHandlers"
|
|
||||||
import actionTypes from "./actions"
|
import actionTypes from "./actions"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
const eventTypeKey = "##eventHandlerType"
|
||||||
|
|
||||||
export let event
|
export let event
|
||||||
|
|
||||||
|
@ -18,8 +18,7 @@
|
||||||
$: actions = event || []
|
$: actions = event || []
|
||||||
$: selectedActionComponent =
|
$: selectedActionComponent =
|
||||||
selectedAction &&
|
selectedAction &&
|
||||||
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_MEMBER_NAME])
|
actionTypes.find(t => t.name === selectedAction[eventTypeKey]).component
|
||||||
.component
|
|
||||||
|
|
||||||
const updateEventHandler = (updatedHandler, index) => {
|
const updateEventHandler = (updatedHandler, index) => {
|
||||||
actions[index] = updatedHandler
|
actions[index] = updatedHandler
|
||||||
|
@ -33,7 +32,7 @@
|
||||||
const addAction = actionType => () => {
|
const addAction = actionType => () => {
|
||||||
const newAction = {
|
const newAction = {
|
||||||
parameters: {},
|
parameters: {},
|
||||||
[EVENT_TYPE_MEMBER_NAME]: actionType.name,
|
[eventTypeKey]: actionType.name,
|
||||||
}
|
}
|
||||||
actions.push(newAction)
|
actions.push(newAction)
|
||||||
selectedAction = newAction
|
selectedAction = newAction
|
||||||
|
@ -79,7 +78,7 @@
|
||||||
{#each actions as action, index}
|
{#each actions as action, index}
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<div class="action-header" on:click={selectAction(action)}>
|
<div class="action-header" on:click={selectAction(action)}>
|
||||||
<Body small lh>{index + 1}. {action[EVENT_TYPE_MEMBER_NAME]}</Body>
|
<Body small lh>{index + 1}. {action[eventTypeKey]}</Body>
|
||||||
<div class="row-expander" class:rotate={action !== selectedAction}>
|
<div class="row-expander" class:rotate={action !== selectedAction}>
|
||||||
<ArrowDownIcon />
|
<ArrowDownIcon />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
<script>
|
|
||||||
import { keys, map, includes, filter } from "lodash/fp"
|
|
||||||
import EventEditorModal from "./EventEditorModal.svelte"
|
|
||||||
import { Modal } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export const EVENT_TYPE = "event"
|
|
||||||
export let component
|
|
||||||
|
|
||||||
let events = []
|
|
||||||
let selectedEvent = null
|
|
||||||
let modal
|
|
||||||
|
|
||||||
$: {
|
|
||||||
events = Object.keys(component)
|
|
||||||
// TODO: use real events
|
|
||||||
.filter(propName => ["onChange", "onClick", "onLoad"].includes(propName))
|
|
||||||
.map(propName => ({
|
|
||||||
name: propName,
|
|
||||||
handlers: component[propName] || [],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const openModal = event => {
|
|
||||||
selectedEvent = event
|
|
||||||
modal.show()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button class="newevent" on:click={() => openModal()}>
|
|
||||||
<i class="icon ri-add-circle-fill" />
|
|
||||||
Create New Event
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
<form on:submit|preventDefault class="form-root">
|
|
||||||
{#each events as event, index}
|
|
||||||
{#if event.handlers.length > 0}
|
|
||||||
<div
|
|
||||||
class:selected={selectedEvent && selectedEvent.index === index}
|
|
||||||
class="handler-container budibase__nav-item"
|
|
||||||
on:click={() => openModal({ ...event, index })}>
|
|
||||||
<span class="event-name">{event.name}</span>
|
|
||||||
<span class="edit-text">EDIT</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Modal bind:this={modal} width="600px">
|
|
||||||
<EventEditorModal eventOptions={events} event={selectedEvent} />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
font-size: 10pt;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newevent {
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid var(--grey-4);
|
|
||||||
border-radius: 3px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px 16px;
|
|
||||||
margin: 0px 0px 12px 0px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 2ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newevent:hover {
|
|
||||||
background: var(--grey-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-root {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.handler-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
border: 2px solid var(--grey-1);
|
|
||||||
height: 80px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.event-name {
|
|
||||||
margin-top: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
color: rgba(22, 48, 87, 0.6);
|
|
||||||
align-self: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-text {
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
font-weight: bold;
|
|
||||||
align-self: end;
|
|
||||||
justify-self: end;
|
|
||||||
font-size: 10px;
|
|
||||||
color: rgba(35, 65, 105, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
color: var(--blue);
|
|
||||||
background: var(--grey-1) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,53 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Input, DataList, Select } from "@budibase/bbui"
|
|
||||||
import { automationStore, allScreens } from "builderStore"
|
|
||||||
|
|
||||||
export let parameter
|
|
||||||
|
|
||||||
let isOpen = false
|
|
||||||
|
|
||||||
const capitalize = s => {
|
|
||||||
if (typeof s !== "string") return ""
|
|
||||||
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="handler-option">
|
|
||||||
{#if parameter.name === 'automation'}<span>{parameter.name}</span>{/if}
|
|
||||||
{#if parameter.name === 'automation'}
|
|
||||||
<Select on:change bind:value={parameter.value}>
|
|
||||||
<option value="" />
|
|
||||||
{#each $automationStore.automations.filter(wf => wf.live) as automation}
|
|
||||||
<option value={automation._id}>{automation.name}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
{:else if parameter.name === 'url'}
|
|
||||||
<DataList on:change bind:value={parameter.value}>
|
|
||||||
<option value="" />
|
|
||||||
{#each $allScreens as screen}
|
|
||||||
<option value={screen.routing.route}>
|
|
||||||
{screen.props._instanceName}
|
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
</DataList>
|
|
||||||
{:else}
|
|
||||||
<Input
|
|
||||||
name={parameter.name}
|
|
||||||
label={capitalize(parameter.name)}
|
|
||||||
on:change
|
|
||||||
value={parameter.value} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.handler-option {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,15 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label } from "@budibase/bbui"
|
import { Select, Label } from "@budibase/bbui"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
|
||||||
$: bindableProperties = fetchBindableProperties({
|
$: bindableProperties = fetchBindableProperties({
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.selectedComponentId,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $currentAsset,
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label } from "@budibase/bbui"
|
import { Select, Label } from "@budibase/bbui"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
@ -8,9 +8,9 @@
|
||||||
let idFields
|
let idFields
|
||||||
|
|
||||||
$: bindableProperties = fetchBindableProperties({
|
$: bindableProperties = fetchBindableProperties({
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.selectedComponentId,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $currentAsset,
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
// accepts an array of field names, and outputs an object of { FieldName: value }
|
// accepts an array of field names, and outputs an object of { FieldName: value }
|
||||||
import { DataList, Label, TextButton, Spacer, Select } from "@budibase/bbui"
|
import { DataList, Label, TextButton, Spacer, Select } from "@budibase/bbui"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
import { CloseCircleIcon, AddIcon } from "components/common/Icons"
|
import { CloseCircleIcon, AddIcon } from "components/common/Icons"
|
||||||
import {
|
import {
|
||||||
|
@ -32,9 +32,9 @@
|
||||||
}))
|
}))
|
||||||
|
|
||||||
$: bindableProperties = fetchBindableProperties({
|
$: bindableProperties = fetchBindableProperties({
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.selectedComponentId,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $currentAsset,
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label } from "@budibase/bbui"
|
import { Select, Label } from "@budibase/bbui"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
import {
|
import {
|
||||||
|
@ -16,9 +16,9 @@
|
||||||
let schemaFields
|
let schemaFields
|
||||||
|
|
||||||
$: bindableProperties = fetchBindableProperties({
|
$: bindableProperties = fetchBindableProperties({
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.selectedComponentId,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $currentAsset,
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label } from "@budibase/bbui"
|
import { Select, Label } from "@budibase/bbui"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
import {
|
import {
|
||||||
|
@ -11,9 +11,9 @@
|
||||||
export let parameters
|
export let parameters
|
||||||
|
|
||||||
$: bindableProperties = fetchBindableProperties({
|
$: bindableProperties = fetchBindableProperties({
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.selectedComponentId,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $currentAsset,
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { buildStyle } from "../../helpers.js"
|
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let text = ""
|
export let text = ""
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let onClick = value => {}
|
export let onClick = () => {}
|
||||||
export let selected = false
|
export let selected = false
|
||||||
|
|
||||||
$: useIcon = !!icon
|
$: useIcon = !!icon
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import FlatButton from "./FlatButton.svelte"
|
import FlatButton from "./FlatButton.svelte"
|
||||||
|
|
||||||
export let buttonProps = []
|
export let buttonProps = []
|
||||||
export let isMultiSelect = false
|
export let isMultiSelect = false
|
||||||
export let value = []
|
export let value = []
|
||||||
|
|
|
@ -1,16 +1,33 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { store, currentScreens } from "builderStore"
|
import { goto, params, url } from "@sveltech/routify"
|
||||||
import api from "builderStore/api"
|
import { store, currentAsset, selectedComponent } from "builderStore"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
import ComponentNavigationTree from "components/userInterface/ComponentNavigationTree/index.svelte"
|
import ComponentNavigationTree from "components/userInterface/ComponentNavigationTree/index.svelte"
|
||||||
import PageLayout from "components/userInterface/PageLayout.svelte"
|
import Layout from "components/userInterface/Layout.svelte"
|
||||||
import PagesList from "components/userInterface/PagesList.svelte"
|
|
||||||
import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
|
import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
|
||||||
import { Modal } from "@budibase/bbui"
|
import NewLayoutModal from "components/userInterface/NewLayoutModal.svelte"
|
||||||
|
import { Modal, Switcher } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: "Screens",
|
||||||
|
key: "screen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Layouts",
|
||||||
|
key: "layout",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
let routes = {}
|
let routes = {}
|
||||||
|
let tab = $params.assetType
|
||||||
|
|
||||||
|
function navigate({ detail }) {
|
||||||
|
if (!detail) return
|
||||||
|
$goto(`../${detail.heading.key}`)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
store.actions.routing.fetch()
|
store.actions.routing.fetch()
|
||||||
|
@ -18,32 +35,48 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h1>Screens</h1>
|
<Switcher headings={tabs} bind:value={tab} on:change={navigate}>
|
||||||
<i on:click={modal.show} data-cy="new-screen" class="ri-add-circle-fill" />
|
{#if tab === FrontendTypes.SCREEN}
|
||||||
</div>
|
<i
|
||||||
<PagesList />
|
on:click={modal.show}
|
||||||
|
data-cy="new-screen"
|
||||||
|
class="ri-add-circle-fill" />
|
||||||
|
{#if $currentAsset}
|
||||||
<div class="nav-items-container">
|
<div class="nav-items-container">
|
||||||
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
|
||||||
<ComponentNavigationTree />
|
<ComponentNavigationTree />
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<NewScreenModal />
|
<NewScreenModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
{:else if tab === FrontendTypes.LAYOUT}
|
||||||
|
<i
|
||||||
|
on:click={modal.show}
|
||||||
|
data-cy="new-layout"
|
||||||
|
class="ri-add-circle-fill" />
|
||||||
|
{#each $store.layouts as layout (layout._id)}
|
||||||
|
<Layout {layout} />
|
||||||
|
{/each}
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<NewLayoutModal />
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
||||||
|
</Switcher>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
}
|
position: relative;
|
||||||
.title h1 {
|
|
||||||
font-size: var(--font-size-m);
|
|
||||||
font-weight: 500;
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
.title i {
|
.title i {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
}
|
}
|
||||||
.title i:hover {
|
.title i:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@sveltech/routify"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
|
import ComponentTree from "./ComponentNavigationTree/ComponentTree.svelte"
|
||||||
|
import LayoutDropdownMenu from "./ComponentNavigationTree/LayoutDropdownMenu.svelte"
|
||||||
|
import initDragDropStore from "./ComponentNavigationTree/dragDropStore"
|
||||||
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
import { last } from "lodash/fp"
|
||||||
|
import { store, currentAsset, selectedComponent } from "builderStore"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
export let layout
|
||||||
|
|
||||||
|
let confirmDeleteDialog
|
||||||
|
let componentToDelete = ""
|
||||||
|
|
||||||
|
const dragDropStore = initDragDropStore()
|
||||||
|
|
||||||
|
const selectLayout = () => {
|
||||||
|
store.actions.layouts.select(layout._id)
|
||||||
|
$goto(`./${layout._id}`)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NavItem
|
||||||
|
border={false}
|
||||||
|
icon="ri-layout-3-line"
|
||||||
|
text={layout.name}
|
||||||
|
withArrow
|
||||||
|
selected={$store.currentAssetId === layout._id}
|
||||||
|
opened={$store.currentAssetId === layout._id}
|
||||||
|
on:click={selectLayout}>
|
||||||
|
<LayoutDropdownMenu {layout} />
|
||||||
|
</NavItem>
|
||||||
|
|
||||||
|
{#if $store.currentAssetId === layout._id && layout.props?._children}
|
||||||
|
<ComponentTree
|
||||||
|
components={layout.props._children}
|
||||||
|
currentComponent={$selectedComponent}
|
||||||
|
{dragDropStore} />
|
||||||
|
{/if}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script>
|
||||||
|
import { store, currentAsset } from "builderStore"
|
||||||
|
import { Select } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select bind:value extraThin secondary on:change>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each $store.layouts as layout}
|
||||||
|
<option value={layout._id}>{layout.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@sveltech/routify"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import { store, backendUiStore, allScreens } from "builderStore"
|
||||||
|
import { Input, ModalContent } from "@budibase/bbui"
|
||||||
|
import analytics from "analytics"
|
||||||
|
|
||||||
|
const CONTAINER = "@budibase/standard-components/container"
|
||||||
|
|
||||||
|
let name = ""
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
try {
|
||||||
|
await store.actions.layouts.save({ name })
|
||||||
|
$goto(`./${$store.currentAssetId}`)
|
||||||
|
notifier.success(`Layout ${name} created successfully`)
|
||||||
|
} catch (err) {
|
||||||
|
notifier.danger(`Error creating layout ${name}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent title="Create Layout" confirmText="Create" onConfirm={save}>
|
||||||
|
<Input thin label="Name" bind:value={name} />
|
||||||
|
</ModalContent>
|
|
@ -70,9 +70,9 @@
|
||||||
draftScreen.props._instanceName = name
|
draftScreen.props._instanceName = name
|
||||||
draftScreen.props._component = baseComponent
|
draftScreen.props._component = baseComponent
|
||||||
// TODO: need to fix this up correctly
|
// TODO: need to fix this up correctly
|
||||||
draftScreen.routing = { route, accessLevelId: "ADMIN" }
|
draftScreen.routing = { route, roleId: "ADMIN" }
|
||||||
|
|
||||||
await store.actions.screens.create(draftScreen)
|
const createdScreen = await store.actions.screens.create(draftScreen)
|
||||||
if (createLink) {
|
if (createLink) {
|
||||||
await store.actions.components.links.save(route, name)
|
await store.actions.components.links.save(route, name)
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$goto(`./:page/${name}`)
|
$goto(`./screen/${createdScreen._id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeNameExists = route => {
|
const routeNameExists = route => {
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
<script>
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import ComponentTree from "./ComponentNavigationTree/ComponentTree.svelte"
|
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
|
||||||
import { last } from "lodash/fp"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { writable } from "svelte/store"
|
|
||||||
|
|
||||||
export let layout
|
|
||||||
|
|
||||||
let confirmDeleteDialog
|
|
||||||
let componentToDelete = ""
|
|
||||||
|
|
||||||
const dragDropStore = writable({})
|
|
||||||
|
|
||||||
const lastPartOfName = c =>
|
|
||||||
c && last(c.name ? c.name.split("/") : c._component.split("/"))
|
|
||||||
|
|
||||||
$: _layout = {
|
|
||||||
component: layout,
|
|
||||||
title: lastPartOfName(layout),
|
|
||||||
}
|
|
||||||
|
|
||||||
const setCurrentScreenToLayout = () => {
|
|
||||||
store.actions.selectPageOrScreen("page")
|
|
||||||
$goto("./:page/page-layout")
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<NavItem
|
|
||||||
border={false}
|
|
||||||
icon="ri-layout-3-line"
|
|
||||||
text="Master Screen"
|
|
||||||
withArrow
|
|
||||||
selected={$store.currentComponentInfo?._id === _layout.component.props._id}
|
|
||||||
opened={$store.currentPreviewItem?.name === _layout.title}
|
|
||||||
on:click={setCurrentScreenToLayout} />
|
|
||||||
|
|
||||||
{#if $store.currentPreviewItem?.name === _layout.title && _layout.component.props._children}
|
|
||||||
<ComponentTree
|
|
||||||
components={_layout.component.props._children}
|
|
||||||
currentComponent={$store.currentComponentInfo}
|
|
||||||
{dragDropStore} />
|
|
||||||
{/if}
|
|
|
@ -1,3 +0,0 @@
|
||||||
<script>
|
|
||||||
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
|
|
||||||
</script>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params, goto } from "@sveltech/routify"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
|
|
||||||
const getPage = (state, name) => {
|
|
||||||
const props = state.pages[name]
|
|
||||||
return { name, props }
|
|
||||||
}
|
|
||||||
|
|
||||||
const pages = [
|
|
||||||
{
|
|
||||||
title: "Private",
|
|
||||||
id: "main",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Public",
|
|
||||||
id: "unauthenticated",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
if (!$store.currentPageName)
|
|
||||||
store.actions.pages.select($params.page ? $params.page : "main")
|
|
||||||
|
|
||||||
const changePage = id => {
|
|
||||||
store.actions.pages.select(id)
|
|
||||||
$goto(`./${id}/page-layout`)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
{#each pages as { title, id }}
|
|
||||||
<button class:active={id === $params.page} on:click={() => changePage(id)}>
|
|
||||||
{title}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 var(--spacing-m);
|
|
||||||
height: 32px;
|
|
||||||
text-align: center;
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--grey-7);
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
border: none !important;
|
|
||||||
outline: none;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
.active {
|
|
||||||
background: var(--grey-2);
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import Input from "./PropertyPanelControls/Input.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
import {
|
import {
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
|
@ -22,22 +22,20 @@
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
|
|
||||||
let temporaryBindableValue = value
|
let temporaryBindableValue = value
|
||||||
|
let bindableProperties = []
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
handleChange(key, temporaryBindableValue)
|
handleChange(key, temporaryBindableValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
let bindableProperties = []
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
|
|
||||||
function getBindableProperties() {
|
function getBindableProperties() {
|
||||||
// Get all bindableProperties
|
// Get all bindableProperties
|
||||||
bindableProperties = fetchBindableProperties({
|
bindableProperties = fetchBindableProperties({
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.selectedComponentId,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $currentAsset,
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -94,7 +92,7 @@
|
||||||
{...props}
|
{...props}
|
||||||
name={key} />
|
name={key} />
|
||||||
</div>
|
</div>
|
||||||
{#if bindable && control === Input && !key.startsWith('_')}
|
{#if bindable && !key.startsWith('_') && control === Input}
|
||||||
<div
|
<div
|
||||||
class="icon"
|
class="icon"
|
||||||
data-cy={`${key}-binding-button`}
|
data-cy={`${key}-binding-button`}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { DataList } from "@budibase/bbui"
|
import { DataList } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { store, allScreens, backendUiStore } from "builderStore"
|
import { store, allScreens, backendUiStore, currentAsset } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -27,9 +27,9 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
const bindableProperties = fetchBindableProperties({
|
const bindableProperties = fetchBindableProperties({
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.selectedComponentId,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $currentAsset,
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
import PropertyControl from "./PropertyControl.svelte"
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
|
import LayoutSelect from "./LayoutSelect.svelte"
|
||||||
import Input from "./PropertyPanelControls/Input.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import { excludeProps } from "./propertyCategories.js"
|
import { excludeProps } from "./propertyCategories.js"
|
||||||
import { store, allScreens } from "builderStore"
|
import { store, allScreens, currentAsset } from "builderStore"
|
||||||
import { walkProps } from "builderStore/storeUtils"
|
import { walkProps } from "builderStore/storeUtils"
|
||||||
|
|
||||||
export let panelDefinition = []
|
export let panelDefinition = []
|
||||||
|
@ -13,13 +14,13 @@
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
export let onScreenPropChange = () => {}
|
export let onScreenPropChange = () => {}
|
||||||
export let displayNameField = false
|
export let displayNameField = false
|
||||||
export let screenOrPageInstance
|
export let assetInstance
|
||||||
|
|
||||||
let pageScreenProps = ["title", "favicon", "description", "route"]
|
let assetProps = ["title", "description", "route", "layoutId"]
|
||||||
let duplicateName = false
|
let duplicateName = false
|
||||||
|
|
||||||
const propExistsOnComponentDef = prop =>
|
const propExistsOnComponentDef = prop =>
|
||||||
pageScreenProps.includes(prop) || prop in componentDefinition.props
|
assetProps.includes(prop) || prop in componentDefinition.props
|
||||||
|
|
||||||
function handleChange(key, data) {
|
function handleChange(key, data) {
|
||||||
data.target ? onChange(key, data.target.value) : onChange(key, data)
|
data.target ? onChange(key, data.target.value) : onChange(key, data)
|
||||||
|
@ -28,12 +29,10 @@
|
||||||
const screenDefinition = [
|
const screenDefinition = [
|
||||||
{ key: "description", label: "Description", control: Input },
|
{ key: "description", label: "Description", control: Input },
|
||||||
{ key: "route", label: "Route", control: Input },
|
{ key: "route", label: "Route", control: Input },
|
||||||
|
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||||
]
|
]
|
||||||
|
|
||||||
const pageDefinition = [
|
const layoutDefinition = [{ key: "title", label: "Title", control: Input }]
|
||||||
{ key: "title", label: "Title", control: Input },
|
|
||||||
{ key: "favicon", label: "Favicon", control: Input },
|
|
||||||
]
|
|
||||||
|
|
||||||
const canRenderControl = (key, dependsOn) => {
|
const canRenderControl = (key, dependsOn) => {
|
||||||
let test = !isEmpty(componentInstance[dependsOn])
|
let test = !isEmpty(componentInstance[dependsOn])
|
||||||
|
@ -44,8 +43,8 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: isPage = screenOrPageInstance && screenOrPageInstance.favicon
|
$: isLayout = assetInstance && assetInstance.favicon
|
||||||
$: screenOrPageDefinition = isPage ? pageDefinition : screenDefinition
|
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
||||||
|
|
||||||
const isDuplicateName = name => {
|
const isDuplicateName = name => {
|
||||||
let duplicate = false
|
let duplicate = false
|
||||||
|
@ -58,15 +57,15 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// check page first
|
// check against layouts
|
||||||
lookForDuplicate($store.pages[$store.currentPageName].props)
|
for (let layout of $store.layouts) {
|
||||||
if (duplicate) return true
|
lookForDuplicate(layout.props)
|
||||||
|
}
|
||||||
// if viewing screen, check current screen for duplicate
|
// if viewing screen, check current screen for duplicate
|
||||||
if ($store.currentFrontEndType === "screen") {
|
if ($store.currentFrontEndType === FrontendTypes.SCREEN) {
|
||||||
lookForDuplicate($store.currentPreviewItem.props)
|
lookForDuplicate($currentAsset.props)
|
||||||
} else {
|
} else {
|
||||||
// viewing master page - need to dedupe against all screens
|
// need to dedupe against all screens
|
||||||
for (let screen of $allScreens) {
|
for (let screen of $allScreens) {
|
||||||
lookForDuplicate(screen.props)
|
lookForDuplicate(screen.props)
|
||||||
}
|
}
|
||||||
|
@ -86,14 +85,14 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="settings-view-container">
|
<div class="settings-view-container">
|
||||||
{#if screenOrPageInstance}
|
{#if assetInstance}
|
||||||
{#each screenOrPageDefinition as def}
|
{#each assetDefinition as def}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
bindable={false}
|
bindable={false}
|
||||||
control={def.control}
|
control={def.control}
|
||||||
label={def.label}
|
label={def.label}
|
||||||
key={def.key}
|
key={def.key}
|
||||||
value={screenOrPageInstance[def.key]}
|
value={assetInstance[def.key]}
|
||||||
onChange={onScreenPropChange}
|
onChange={onScreenPropChange}
|
||||||
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
|
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -32,21 +32,24 @@
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
$: bindableProperties = fetchBindableProperties({
|
$: bindableProperties = fetchBindableProperties({
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.selectedComponentId,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $currentAsset,
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
$: links = bindableProperties
|
$: links = bindableProperties
|
||||||
.filter(x => x.fieldSchema?.type === "link")
|
.filter(x => x.fieldSchema?.type === "link")
|
||||||
.map(property => ({
|
.map(property => {
|
||||||
|
return {
|
||||||
|
providerId: property.instance._id,
|
||||||
label: property.readableBinding,
|
label: property.readableBinding,
|
||||||
fieldName: property.fieldSchema.name,
|
fieldName: property.fieldSchema.name,
|
||||||
name: `all_${property.fieldSchema.tableId}`,
|
name: `all_${property.fieldSchema.tableId}`,
|
||||||
tableId: property.fieldSchema.tableId,
|
tableId: property.fieldSchema.tableId,
|
||||||
type: "link",
|
type: "link",
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -25,7 +25,6 @@ export const createProps = (componentDefinition, derivedFromProps) => {
|
||||||
_id: uuid(),
|
_id: uuid(),
|
||||||
_component: componentDefinition._component,
|
_component: componentDefinition._component,
|
||||||
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
|
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
|
||||||
_code: "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = []
|
const errors = []
|
||||||
|
@ -96,6 +95,3 @@ const parsePropDef = propDef => {
|
||||||
|
|
||||||
return cloneDeep(propDef.default)
|
return cloneDeep(propDef.default)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const arrayElementComponentName = (parentComponentName, arrayPropName) =>
|
|
||||||
`${parentComponentName}:${arrayPropName}`
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { isPlainObject, isArray, cloneDeep } from "lodash/fp"
|
|
||||||
import { getExactComponent } from "./searchComponents"
|
|
||||||
|
|
||||||
export const rename = (pages, screens, oldname, newname) => {
|
|
||||||
pages = cloneDeep(pages)
|
|
||||||
screens = cloneDeep(screens)
|
|
||||||
const changedScreens = []
|
|
||||||
|
|
||||||
const existingWithNewName = getExactComponent(screens, newname, true)
|
|
||||||
if (existingWithNewName)
|
|
||||||
return {
|
|
||||||
components: screens,
|
|
||||||
pages,
|
|
||||||
error: "Component by that name already exists",
|
|
||||||
}
|
|
||||||
|
|
||||||
const traverseProps = props => {
|
|
||||||
let hasEdited = false
|
|
||||||
if (props._component && props._component === oldname) {
|
|
||||||
props._component = newname
|
|
||||||
hasEdited = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let propName in props) {
|
|
||||||
const prop = props[propName]
|
|
||||||
if (isPlainObject(prop) && prop._component) {
|
|
||||||
hasEdited = traverseProps(prop) || hasEdited
|
|
||||||
}
|
|
||||||
if (isArray(prop)) {
|
|
||||||
for (let element of prop) {
|
|
||||||
hasEdited = traverseProps(element) || hasEdited
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasEdited
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let screen of screens) {
|
|
||||||
let hasEdited = false
|
|
||||||
|
|
||||||
if (screen.props.instanceName === oldname) {
|
|
||||||
screen.props.instanceName = newname
|
|
||||||
hasEdited = true
|
|
||||||
}
|
|
||||||
|
|
||||||
hasEdited = traverseProps(screen.props) || hasEdited
|
|
||||||
|
|
||||||
if (hasEdited && screen.props.instanceName !== newname)
|
|
||||||
changedScreens.push(screen.props.instanceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let pageName in pages) {
|
|
||||||
const page = pages[pageName]
|
|
||||||
if (page.appBody === oldname) {
|
|
||||||
page.appBody = newname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { screens, pages, changedScreens }
|
|
||||||
}
|
|
|
@ -310,15 +310,15 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Link Color",
|
label: "Link Color",
|
||||||
key: "color",
|
key: "linkColor",
|
||||||
control: Input,
|
control: Colorpicker,
|
||||||
placeholder: "Link Color",
|
defaultValue: "#000",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Hover Color",
|
label: "Hover Color",
|
||||||
key: "linkHoverColor",
|
key: "linkHoverColor",
|
||||||
control: Input,
|
control: Colorpicker,
|
||||||
placeholder: "Hover Color",
|
defaultValue: "#222",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Image Height",
|
label: "Image Height",
|
||||||
|
@ -385,15 +385,15 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Link Color",
|
label: "Link Color",
|
||||||
key: "color",
|
key: "linkColor",
|
||||||
control: Input,
|
control: Colorpicker,
|
||||||
placeholder: "Link Color",
|
defaultValue: "#000",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Hover Color",
|
label: "Hover Color",
|
||||||
key: "linkHoverColor",
|
key: "linkHoverColor",
|
||||||
control: Input,
|
control: Colorpicker,
|
||||||
placeholder: "Hover Color",
|
defaultValue: "#222",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Card Width",
|
label: "Card Width",
|
||||||
|
@ -427,6 +427,36 @@ export default {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
_component: "@budibase/standard-components/cardstat",
|
||||||
|
name: "Stat",
|
||||||
|
description: "A card component for displaying numbers.",
|
||||||
|
icon: "ri-dual-sim-2-line",
|
||||||
|
children: [],
|
||||||
|
properties: {
|
||||||
|
design: { ...all },
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
label: "Title",
|
||||||
|
key: "title",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Total Revenue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Value",
|
||||||
|
key: "value",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "$1,981,983",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Label",
|
||||||
|
key: "label",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Stripe",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1167,7 +1197,7 @@ export default {
|
||||||
_component: "##builtin/screenslot",
|
_component: "##builtin/screenslot",
|
||||||
name: "Screen Slot",
|
name: "Screen Slot",
|
||||||
description:
|
description:
|
||||||
"This component is a placeholder for the rendering of a screen within a page.",
|
"This component is a placeholder for the rendering of a screen within a layout.",
|
||||||
icon: "ri-crop-2-line",
|
icon: "ri-crop-2-line",
|
||||||
properties: { design: { ...all } },
|
properties: { design: { ...all } },
|
||||||
commonProps: {},
|
commonProps: {},
|
||||||
|
@ -1175,7 +1205,7 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Nav Bar",
|
name: "Nav Bar",
|
||||||
_component: "@budibase/standard-components/Navigation",
|
_component: "@budibase/standard-components/navigation",
|
||||||
description:
|
description:
|
||||||
"A component for handling the navigation within your app.",
|
"A component for handling the navigation within your app.",
|
||||||
icon: "ri-navigation-line",
|
icon: "ri-navigation-line",
|
||||||
|
@ -1192,7 +1222,7 @@ export default {
|
||||||
"A component that automatically generates a login screen for your app.",
|
"A component that automatically generates a login screen for your app.",
|
||||||
icon: "ri-login-box-line",
|
icon: "ri-login-box-line",
|
||||||
children: [],
|
children: [],
|
||||||
showOnPages: ["unauthenticated"],
|
showOnAsset: ["login-screen"],
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [
|
settings: [
|
||||||
|
|
|
@ -9,6 +9,16 @@ export const FIELDS = {
|
||||||
presence: false,
|
presence: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
LONGFORM: {
|
||||||
|
name: "Long Form Text",
|
||||||
|
icon: "ri-file-text-line",
|
||||||
|
type: "longform",
|
||||||
|
constraints: {
|
||||||
|
type: "string",
|
||||||
|
length: {},
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
OPTIONS: {
|
OPTIONS: {
|
||||||
name: "Options",
|
name: "Options",
|
||||||
icon: "ri-list-check-2",
|
icon: "ri-list-check-2",
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
export const DEFAULT_PAGES_OBJECT = {
|
export const TableNames = {
|
||||||
main: {
|
USERS: "ta_users",
|
||||||
props: {
|
}
|
||||||
_component: "@budibase/standard-components/container",
|
|
||||||
},
|
export const FrontendTypes = {
|
||||||
_screens: {},
|
PAGE: "page",
|
||||||
},
|
SCREEN: "screen",
|
||||||
unauthenticated: {
|
LAYOUT: "layout",
|
||||||
props: {
|
NONE: "none",
|
||||||
_component: "@budibase/standard-components/container",
|
}
|
||||||
},
|
|
||||||
_screens: {},
|
// fields on the user table that cannot be edited
|
||||||
},
|
export const UNEDITABLE_USER_FIELDS = ["email", "password", "roleId"]
|
||||||
componentLibraries: [],
|
|
||||||
stylesheets: [],
|
export const LAYOUT_NAMES = {
|
||||||
|
MASTER: {
|
||||||
|
PRIVATE: "layout_private_master",
|
||||||
|
PUBLIC: "layout_private_master",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,16 +68,11 @@
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<ThemeEditor />
|
<ThemeEditor />
|
||||||
<FeedbackNavLink />
|
<FeedbackNavLink />
|
||||||
<div class="topnavitemright">
|
|
||||||
<a target="_blank" href="https://docs.budibase.com">
|
|
||||||
<i class="ri-question-line" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="topnavitemright">
|
<div class="topnavitemright">
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://github.com/Budibase/budibase/discussions">
|
href="https://github.com/Budibase/budibase/discussions">
|
||||||
<i class="ri-discuss-line" />
|
<i class="ri-question-line" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<SettingsLink />
|
<SettingsLink />
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { store } from "builderStore"
|
||||||
import { params } from "@sveltech/routify"
|
import { params } from "@sveltech/routify"
|
||||||
store.actions.pages.select($params.page)
|
|
||||||
|
store.actions.layouts.select($params.layout)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=1 -->
|
<!-- routify:options index=0 -->
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<TableNavigator />
|
<TableNavigator />
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script>
|
||||||
|
import { params, leftover, goto } from "@sveltech/routify"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
|
import { store, allScreens } from "builderStore"
|
||||||
|
|
||||||
|
// Get any leftover params not caught by Routifys params store.
|
||||||
|
const componentIds = $leftover.split("/").filter(id => id !== "")
|
||||||
|
|
||||||
|
const currentAssetId = decodeURI($params.asset)
|
||||||
|
|
||||||
|
let assetList
|
||||||
|
let actions
|
||||||
|
|
||||||
|
// Determine screens or layouts based on the URL
|
||||||
|
if ($params.assetType === FrontendTypes.SCREEN) {
|
||||||
|
assetList = $allScreens
|
||||||
|
actions = store.actions.screens
|
||||||
|
} else {
|
||||||
|
assetList = $store.layouts
|
||||||
|
actions = store.actions.layouts
|
||||||
|
}
|
||||||
|
|
||||||
|
// select the screen or layout in the UI
|
||||||
|
actions.select(currentAssetId)
|
||||||
|
|
||||||
|
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
||||||
|
if ($leftover) {
|
||||||
|
// Get the correct screen children.
|
||||||
|
const assetChildren = assetList.find(
|
||||||
|
asset =>
|
||||||
|
asset._id === $params.asset ||
|
||||||
|
asset._id === decodeURIComponent($params.asset)
|
||||||
|
).props._children
|
||||||
|
findComponent(componentIds, assetChildren)
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Find Component with ID and continue
|
||||||
|
function findComponent(ids, children) {
|
||||||
|
// Setup stuff
|
||||||
|
let componentToSelect
|
||||||
|
let currentChildren = children
|
||||||
|
|
||||||
|
// Loop through each ID
|
||||||
|
ids.forEach(id => {
|
||||||
|
// Find ID
|
||||||
|
const component = currentChildren.find(child => child._id === id)
|
||||||
|
|
||||||
|
// If it does not exist, ignore (use last valid route)
|
||||||
|
if (!component) return
|
||||||
|
|
||||||
|
componentToSelect = component
|
||||||
|
|
||||||
|
// Update childrens array to selected components children
|
||||||
|
currentChildren = componentToSelect._children
|
||||||
|
})
|
||||||
|
|
||||||
|
// Select Component!
|
||||||
|
if (componentToSelect) store.actions.components.select(componentToSelect)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
import CurrentItemPreview from "components/userInterface/AppPreview"
|
import CurrentItemPreview from "components/userInterface/AppPreview"
|
||||||
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
|
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
|
||||||
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
|
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
|
||||||
|
@ -26,8 +27,6 @@
|
||||||
const settings = () => {
|
const settings = () => {
|
||||||
settingsView.show()
|
settingsView.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastPartOfName = c => (c ? last(c.split("/")) : "")
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=1 -->
|
<!-- routify:options index=1 -->
|
||||||
|
@ -37,7 +36,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview-pane">
|
<div class="preview-pane">
|
||||||
{#if $store.currentPageName && $store.currentPageName.length > 0}
|
{#if $store.currentAssetId && $store.currentAssetId.length > 0}
|
||||||
<ComponentSelectionList />
|
<ComponentSelectionList />
|
||||||
<div class="preview-content">
|
<div class="preview-content">
|
||||||
<CurrentItemPreview />
|
<CurrentItemPreview />
|
||||||
|
@ -45,7 +44,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'}
|
{#if $store.currentFrontEndType === FrontendTypes.SCREEN || $store.currentFrontEndType === FrontendTypes.LAYOUT}
|
||||||
<div class="components-pane">
|
<div class="components-pane">
|
||||||
<ComponentPropertiesPanel />
|
<ComponentPropertiesPanel />
|
||||||
</div>
|
</div>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import { store, allScreens } from "builderStore"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
|
import { goto, params } from "@sveltech/routify"
|
||||||
|
|
||||||
|
// Go to first layout
|
||||||
|
if ($params.assetType === FrontendTypes.LAYOUT) {
|
||||||
|
$goto(`../${$store.layouts[0]?._id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to first screen
|
||||||
|
if ($params.assetType === FrontendTypes.SCREEN) {
|
||||||
|
$goto(`../${$allScreens[0]?._id}`)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- routify:options index=false -->
|
|
@ -1,69 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { params, leftover, goto } from "@sveltech/routify"
|
|
||||||
import { store, allScreens } from "builderStore"
|
|
||||||
|
|
||||||
// Get any leftover params not caught by Routifys params store.
|
|
||||||
const componentIds = $leftover.split("/").filter(id => id !== "")
|
|
||||||
|
|
||||||
// It's a screen, set it to that screen
|
|
||||||
if ($params.screen !== "page-layout") {
|
|
||||||
const currentScreenName = decodeURI($params.screen)
|
|
||||||
const validScreen =
|
|
||||||
$allScreens.findIndex(screen => screen._id === currentScreenName) !== -1
|
|
||||||
|
|
||||||
if (!validScreen) {
|
|
||||||
// Go to main layout if URL set to invalid screen
|
|
||||||
store.actions.pages.select("main")
|
|
||||||
$goto("../../main")
|
|
||||||
} else {
|
|
||||||
// Otherwise proceed to set screen
|
|
||||||
store.actions.screens.select(currentScreenName)
|
|
||||||
|
|
||||||
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
|
||||||
if ($leftover) {
|
|
||||||
// Get the correct screen children.
|
|
||||||
const screenChildren = $store.pages[$params.page]._screens.find(
|
|
||||||
screen =>
|
|
||||||
screen._id === $params.screen ||
|
|
||||||
screen._id === decodeURIComponent($params.screen)
|
|
||||||
).props._children
|
|
||||||
findComponent(componentIds, screenChildren)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// It's a page, so set the screentype to page.
|
|
||||||
store.actions.selectPageOrScreen("page")
|
|
||||||
|
|
||||||
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
|
||||||
if ($leftover) {
|
|
||||||
findComponent(componentIds, $store.pages[$params.page].props._children)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find Component with ID and continue
|
|
||||||
function findComponent(ids, children) {
|
|
||||||
// Setup stuff
|
|
||||||
let componentToSelect
|
|
||||||
let currentChildren = children
|
|
||||||
|
|
||||||
// Loop through each ID
|
|
||||||
ids.forEach(id => {
|
|
||||||
// Find ID
|
|
||||||
const component = currentChildren.find(child => child._id === id)
|
|
||||||
|
|
||||||
// If it does not exist, ignore (use last valid route)
|
|
||||||
if (!component) return
|
|
||||||
|
|
||||||
componentToSelect = component
|
|
||||||
|
|
||||||
// Update childrens array to selected components children
|
|
||||||
currentChildren = componentToSelect._children
|
|
||||||
})
|
|
||||||
|
|
||||||
// Select Component!
|
|
||||||
if (componentToSelect) store.actions.components.select(componentToSelect)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -1,8 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params } from "@sveltech/routify"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
|
|
||||||
store.actions.pages.select($params.page)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -1,4 +0,0 @@
|
||||||
<script>
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
$goto("../page-layout")
|
|
||||||
</script>
|
|
|
@ -1,6 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
$goto("../main")
|
import { FrontendTypes } from "constants"
|
||||||
|
|
||||||
|
$goto(`../${FrontendTypes.SCREEN}`)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=false -->
|
<!-- routify:options index=1 -->
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createProps } from "../src/components/userInterface/pagesParsing/createProps"
|
import { createProps } from "../src/components/userInterface/assetParsing/createProps"
|
||||||
import { keys, some } from "lodash/fp"
|
import { keys, some } from "lodash/fp"
|
||||||
import { stripStandardProps } from "./testData"
|
import { stripStandardProps } from "./testData"
|
||||||
|
|
||||||
|
@ -158,8 +158,6 @@ describe("createDefaultProps", () => {
|
||||||
const comp = getcomponent()
|
const comp = getcomponent()
|
||||||
comp.props.fieldName = { type: "string", default: 1 }
|
comp.props.fieldName = { type: "string", default: 1 }
|
||||||
const { props } = createProps(comp)
|
const { props } = createProps(comp)
|
||||||
expect(props._code).toBeDefined()
|
|
||||||
expect(props._styles).toBeDefined()
|
expect(props._styles).toBeDefined()
|
||||||
expect(props._code).toBeDefined()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe("fetch bindable properties", () => {
|
||||||
)
|
)
|
||||||
expect(componentBinding).toBeDefined()
|
expect(componentBinding).toBeDefined()
|
||||||
expect(componentBinding.type).toBe("instance")
|
expect(componentBinding.type).toBe("instance")
|
||||||
expect(componentBinding.runtimeBinding).toBe("search-input-id.value")
|
expect(componentBinding.runtimeBinding).toBe("search-input-id")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not return bindable components when not in their context", () => {
|
it("should not return bindable components when not in their context", () => {
|
||||||
|
@ -37,20 +37,22 @@ describe("fetch bindable properties", () => {
|
||||||
expect(contextBindings.length).toBe(4)
|
expect(contextBindings.length).toBe(4)
|
||||||
|
|
||||||
const namebinding = contextBindings.find(
|
const namebinding = contextBindings.find(
|
||||||
b => b.runtimeBinding === "data.name"
|
b => b.runtimeBinding === "list-id.name"
|
||||||
)
|
)
|
||||||
expect(namebinding).toBeDefined()
|
expect(namebinding).toBeDefined()
|
||||||
expect(namebinding.readableBinding).toBe("list-name.Test Table.name")
|
expect(namebinding.readableBinding).toBe("list-name.Test Table.name")
|
||||||
|
|
||||||
const descriptionbinding = contextBindings.find(
|
const descriptionbinding = contextBindings.find(
|
||||||
b => b.runtimeBinding === "data.description"
|
b => b.runtimeBinding === "list-id.description"
|
||||||
)
|
)
|
||||||
expect(descriptionbinding).toBeDefined()
|
expect(descriptionbinding).toBeDefined()
|
||||||
expect(descriptionbinding.readableBinding).toBe(
|
expect(descriptionbinding.readableBinding).toBe(
|
||||||
"list-name.Test Table.description"
|
"list-name.Test Table.description"
|
||||||
)
|
)
|
||||||
|
|
||||||
const idbinding = contextBindings.find(b => b.runtimeBinding === "data._id")
|
const idbinding = contextBindings.find(
|
||||||
|
b => b.runtimeBinding === "list-id._id"
|
||||||
|
)
|
||||||
expect(idbinding).toBeDefined()
|
expect(idbinding).toBeDefined()
|
||||||
expect(idbinding.readableBinding).toBe("list-name.Test Table._id")
|
expect(idbinding.readableBinding).toBe("list-name.Test Table._id")
|
||||||
})
|
})
|
||||||
|
@ -65,13 +67,13 @@ describe("fetch bindable properties", () => {
|
||||||
expect(contextBindings.length).toBe(8)
|
expect(contextBindings.length).toBe(8)
|
||||||
|
|
||||||
const namebinding_parent = contextBindings.find(
|
const namebinding_parent = contextBindings.find(
|
||||||
b => b.runtimeBinding === "parent.data.name"
|
b => b.runtimeBinding === "list-id.name"
|
||||||
)
|
)
|
||||||
expect(namebinding_parent).toBeDefined()
|
expect(namebinding_parent).toBeDefined()
|
||||||
expect(namebinding_parent.readableBinding).toBe("list-name.Test Table.name")
|
expect(namebinding_parent.readableBinding).toBe("list-name.Test Table.name")
|
||||||
|
|
||||||
const descriptionbinding_parent = contextBindings.find(
|
const descriptionbinding_parent = contextBindings.find(
|
||||||
b => b.runtimeBinding === "parent.data.description"
|
b => b.runtimeBinding === "list-id.description"
|
||||||
)
|
)
|
||||||
expect(descriptionbinding_parent).toBeDefined()
|
expect(descriptionbinding_parent).toBeDefined()
|
||||||
expect(descriptionbinding_parent.readableBinding).toBe(
|
expect(descriptionbinding_parent.readableBinding).toBe(
|
||||||
|
@ -79,7 +81,7 @@ describe("fetch bindable properties", () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const namebinding_own = contextBindings.find(
|
const namebinding_own = contextBindings.find(
|
||||||
b => b.runtimeBinding === "data.name"
|
b => b.runtimeBinding === "child-list-id.name"
|
||||||
)
|
)
|
||||||
expect(namebinding_own).toBeDefined()
|
expect(namebinding_own).toBeDefined()
|
||||||
expect(namebinding_own.readableBinding).toBe(
|
expect(namebinding_own.readableBinding).toBe(
|
||||||
|
@ -87,7 +89,7 @@ describe("fetch bindable properties", () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const descriptionbinding_own = contextBindings.find(
|
const descriptionbinding_own = contextBindings.find(
|
||||||
b => b.runtimeBinding === "data.description"
|
b => b.runtimeBinding === "child-list-id.description"
|
||||||
)
|
)
|
||||||
expect(descriptionbinding_own).toBeDefined()
|
expect(descriptionbinding_own).toBeDefined()
|
||||||
expect(descriptionbinding_own.readableBinding).toBe(
|
expect(descriptionbinding_own.readableBinding).toBe(
|
||||||
|
@ -104,7 +106,7 @@ describe("fetch bindable properties", () => {
|
||||||
r => r.instance._id === "list-item-input-id" && r.type === "instance"
|
r => r.instance._id === "list-item-input-id" && r.type === "instance"
|
||||||
)
|
)
|
||||||
expect(componentBinding).toBeDefined()
|
expect(componentBinding).toBeDefined()
|
||||||
expect(componentBinding.runtimeBinding).toBe("list-item-input-id.value")
|
expect(componentBinding.runtimeBinding).toBe("list-item-input-id")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not return components from child context", () => {
|
it("should not return components from child context", () => {
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import {
|
|
||||||
generate_css,
|
|
||||||
generate_screen_css,
|
|
||||||
} from "../src/builderStore/generate_css.js"
|
|
||||||
|
|
||||||
describe("generate_css", () => {
|
|
||||||
|
|
||||||
|
|
||||||
test("Check how array styles are output", () => {
|
|
||||||
expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Check handling of an array with empty string values", () => {
|
|
||||||
expect(generate_css({ padding: ["", "", "", ""] })).toBe("")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Check handling of an empty array", () => {
|
|
||||||
expect(generate_css({ margin: [] })).toBe("")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Check handling of valid font property", () => {
|
|
||||||
expect(generate_css({ "font-size": "10px" })).toBe("font-size: 10px;")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
describe("generate_screen_css", () => {
|
|
||||||
const normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } }
|
|
||||||
|
|
||||||
test("Test generation of normal css styles", () => {
|
|
||||||
expect(generate_screen_css([normalComponent])).toBe(".header-123-456 {\nfont-size: 16px;\n}")
|
|
||||||
})
|
|
||||||
|
|
||||||
const hoverComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {"font-size": "16px"}, active: {}, selected: {} } }
|
|
||||||
|
|
||||||
test("Test generation of hover css styles", () => {
|
|
||||||
expect(generate_screen_css([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}")
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } }
|
|
||||||
|
|
||||||
test("Test generation of selection css styles", () => {
|
|
||||||
expect(generate_screen_css([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}")
|
|
||||||
})
|
|
||||||
|
|
||||||
const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } }
|
|
||||||
|
|
||||||
test.only("Testing handling of empty component styles", () => {
|
|
||||||
expect(generate_screen_css([emptyComponent])).toBe("")
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
searchAllComponents,
|
searchAllComponents,
|
||||||
getExactComponent,
|
getExactComponent,
|
||||||
getAncestorProps,
|
getAncestorProps,
|
||||||
} from "../src/components/userInterface/pagesParsing/searchComponents"
|
} from "../src/components/userInterface/assetParsing/searchComponents"
|
||||||
import { componentsAndScreens } from "./testData"
|
import { componentsAndScreens } from "./testData"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,6 @@ export const componentsAndScreens = () => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
export const stripStandardProps = props => {
|
export const stripStandardProps = props => {
|
||||||
delete props._code
|
|
||||||
delete props._id
|
delete props._id
|
||||||
delete props._styles
|
delete props._styles
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,5 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
|
||||||
release/
|
release/
|
||||||
dist/
|
dist/
|
|
@ -1,13 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
presets: ["@babel/preset-env"],
|
|
||||||
sourceMaps: "inline",
|
|
||||||
retainLines: true,
|
|
||||||
plugins: [
|
|
||||||
[
|
|
||||||
"@babel/plugin-transform-runtime",
|
|
||||||
{
|
|
||||||
regenerator: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
}
|
|
|
@ -3,59 +3,30 @@
|
||||||
"version": "0.3.8",
|
"version": "0.3.8",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
"module": "dist/budibase-client.esm.mjs",
|
"module": "dist/budibase-client.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c",
|
"build": "rollup -c",
|
||||||
"test": "jest",
|
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"jest": {
|
|
||||||
"globals": {
|
|
||||||
"GLOBALS": {
|
|
||||||
"client": "web"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"testURL": "http://test.com",
|
|
||||||
"moduleNameMapper": {
|
|
||||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/internals/mocks/fileMock.js",
|
|
||||||
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
|
|
||||||
},
|
|
||||||
"moduleFileExtensions": [
|
|
||||||
"js",
|
|
||||||
"svelte"
|
|
||||||
],
|
|
||||||
"moduleDirectories": [
|
|
||||||
"node_modules"
|
|
||||||
],
|
|
||||||
"transform": {
|
|
||||||
"^.+js$": "babel-jest",
|
|
||||||
"^.+.svelte$": "svelte-jester"
|
|
||||||
},
|
|
||||||
"transformIgnorePatterns": [
|
|
||||||
"/node_modules/(?!svelte).+\\.js$"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"deep-equal": "^2.0.1",
|
"deep-equal": "^2.0.1",
|
||||||
"mustache": "^4.0.1",
|
"mustache": "^4.0.1",
|
||||||
"regexparam": "^1.3.0"
|
"regexparam": "^1.3.0",
|
||||||
|
"svelte-spa-router": "^3.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.5",
|
"@budibase/standard-components": "^0.3.8",
|
||||||
"@babel/plugin-transform-runtime": "^7.5.5",
|
"@rollup/plugin-commonjs": "^16.0.0",
|
||||||
"@babel/preset-env": "^7.5.5",
|
"@rollup/plugin-node-resolve": "^10.0.0",
|
||||||
"@babel/runtime": "^7.5.5",
|
|
||||||
"babel-jest": "^24.8.0",
|
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"jest": "^24.8.0",
|
|
||||||
"jsdom": "^16.0.1",
|
"jsdom": "^16.0.1",
|
||||||
"rollup": "^1.12.0",
|
"rollup": "^2.33.2",
|
||||||
"rollup-plugin-commonjs": "^10.0.0",
|
|
||||||
"rollup-plugin-node-builtins": "^2.1.2",
|
"rollup-plugin-node-builtins": "^2.1.2",
|
||||||
"rollup-plugin-node-globals": "^1.4.0",
|
"rollup-plugin-node-globals": "^1.4.0",
|
||||||
|
"rollup-plugin-svelte": "^6.1.1",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"rollup-plugin-terser": "^4.0.4",
|
"rollup-plugin-terser": "^4.0.4",
|
||||||
"svelte": "^3.29.7",
|
"svelte": "^3.30.0",
|
||||||
"svelte-jester": "^1.0.6"
|
"svelte-jester": "^1.0.6"
|
||||||
},
|
},
|
||||||
"gitHead": "e4e053cb6ff9a0ddc7115b44ccaa24b8ec41fb9a"
|
"gitHead": "e4e053cb6ff9a0ddc7115b44ccaa24b8ec41fb9a"
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
import resolve from "rollup-plugin-node-resolve"
|
import commonjs from "@rollup/plugin-commonjs"
|
||||||
import commonjs from "rollup-plugin-commonjs"
|
import resolve from "@rollup/plugin-node-resolve"
|
||||||
import builtins from "rollup-plugin-node-builtins"
|
import builtins from "rollup-plugin-node-builtins"
|
||||||
import nodeglobals from "rollup-plugin-node-globals"
|
import svelte from "rollup-plugin-svelte"
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
input: "src/index.js",
|
input: "src/index.js",
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
format: "iife",
|
|
||||||
name: "app",
|
|
||||||
file: `./dist/budibase-client.js`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: "dist/budibase-client.esm.mjs",
|
|
||||||
format: "esm",
|
format: "esm",
|
||||||
sourcemap: "inline",
|
file: `./dist/budibase-client.js`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
svelte({
|
||||||
|
dev: !production,
|
||||||
|
}),
|
||||||
resolve({
|
resolve({
|
||||||
preferBuiltins: true,
|
preferBuiltins: true,
|
||||||
browser: true,
|
browser: true,
|
||||||
|
dedupe: ["svelte", "svelte/internal"],
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
builtins(),
|
builtins(),
|
||||||
nodeglobals(),
|
|
||||||
],
|
],
|
||||||
watch: {
|
watch: {
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { getAppId } from "../utils/getAppId"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API cache for cached request responses.
|
||||||
|
*/
|
||||||
|
let cache = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a fully formatted URL based on the SDK configuration.
|
||||||
|
*/
|
||||||
|
const makeFullURL = path => {
|
||||||
|
return `/${path}`.replace("//", "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for API errors.
|
||||||
|
*/
|
||||||
|
const handleError = error => {
|
||||||
|
return { error }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an API call to the server.
|
||||||
|
* App ID header is always correctly set.
|
||||||
|
*/
|
||||||
|
const makeApiCall = async ({ method, url, body, json = true }) => {
|
||||||
|
try {
|
||||||
|
const requestBody = json ? JSON.stringify(body) : body
|
||||||
|
let headers = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"x-budibase-app-id": getAppId(),
|
||||||
|
}
|
||||||
|
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
||||||
|
headers["x-budibase-type"] = "client"
|
||||||
|
}
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body: requestBody,
|
||||||
|
credentials: "same-origin",
|
||||||
|
})
|
||||||
|
switch (response.status) {
|
||||||
|
case 200:
|
||||||
|
return response.json()
|
||||||
|
case 404:
|
||||||
|
return handleError(`${url}: Not Found`)
|
||||||
|
case 400:
|
||||||
|
return handleError(`${url}: Bad Request`)
|
||||||
|
case 403:
|
||||||
|
return handleError(`${url}: Forbidden`)
|
||||||
|
default:
|
||||||
|
if (response.status >= 200 && response.status < 400) {
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
return handleError(`${url} - ${response.statusText}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return handleError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an API call to the server and caches the response.
|
||||||
|
* Future invocation for this URL will return the cached result instead of
|
||||||
|
* hitting the server again.
|
||||||
|
*/
|
||||||
|
const makeCachedApiCall = async params => {
|
||||||
|
const identifier = params.url
|
||||||
|
if (!identifier) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!cache[identifier]) {
|
||||||
|
cache[identifier] = makeApiCall(params)
|
||||||
|
cache[identifier] = await cache[identifier]
|
||||||
|
}
|
||||||
|
return await cache[identifier]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an API call function for a particular HTTP method.
|
||||||
|
*/
|
||||||
|
const requestApiCall = method => async params => {
|
||||||
|
const { url, cache = false } = params
|
||||||
|
const fullURL = makeFullURL(url)
|
||||||
|
const enrichedParams = { ...params, method, url: fullURL }
|
||||||
|
return await (cache ? makeCachedApiCall : makeApiCall)(enrichedParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
post: requestApiCall("POST"),
|
||||||
|
get: requestApiCall("GET"),
|
||||||
|
patch: requestApiCall("PATCH"),
|
||||||
|
del: requestApiCall("DELETE"),
|
||||||
|
error: handleError,
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue