diff --git a/.DS_Store b/.DS_Store index 0dfd56565a..363dbb1c4c 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/packages/builder/assets/budibase-emblem-white.svg b/packages/builder/assets/budibase-emblem-white.svg deleted file mode 100644 index 728e859260..0000000000 --- a/packages/builder/assets/budibase-emblem-white.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/packages/builder/assets/budibase-logo-only.png b/packages/builder/assets/budibase-logo-only.png deleted file mode 100644 index 652444e5bd..0000000000 Binary files a/packages/builder/assets/budibase-logo-only.png and /dev/null differ diff --git a/packages/builder/assets/budibase-logo-white.png b/packages/builder/assets/budibase-logo-white.png deleted file mode 100644 index da8440c09e..0000000000 Binary files a/packages/builder/assets/budibase-logo-white.png and /dev/null differ diff --git a/packages/builder/assets/budibase-logo.png b/packages/builder/assets/budibase-logo.png deleted file mode 100644 index 1d6fc3d1cc..0000000000 Binary files a/packages/builder/assets/budibase-logo.png and /dev/null differ diff --git a/packages/builder/assets/budibase-logo.svg b/packages/builder/assets/budibase-logo.svg new file mode 100644 index 0000000000..66b5b3bb39 --- /dev/null +++ b/packages/builder/assets/budibase-logo.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + diff --git a/packages/builder/assets/rocket.jpg b/packages/builder/assets/rocket.jpg new file mode 100644 index 0000000000..cc2edf02a4 Binary files /dev/null and b/packages/builder/assets/rocket.jpg differ diff --git a/packages/builder/assets/spacex.jpg b/packages/builder/assets/spacex.jpg new file mode 100644 index 0000000000..30004eadd9 Binary files /dev/null and b/packages/builder/assets/spacex.jpg differ diff --git a/packages/builder/package.json b/packages/builder/package.json index 81e240d148..8fe9ffd494 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -38,20 +38,23 @@ ] }, "dependencies": { - "@budibase/bbui": "^0.3.5", + "@beyonk/svelte-notifications": "^2.0.3", + "@budibase/bbui": "^1.1.1", "@budibase/client": "^0.0.32", "@nx-js/compiler-util": "^2.0.0", "codemirror": "^5.51.0", "date-fns": "^1.29.0", + "deepmerge": "^4.2.2", "feather-icons": "^4.21.0", "flatpickr": "^4.5.7", "lodash": "^4.17.13", "logrocket": "^1.0.6", "lunr": "^2.3.5", + "mustache": "^4.0.1", "safe-buffer": "^5.1.2", "shortid": "^2.2.8", "string_decoder": "^1.2.0", - "svelte-simple-modal": "^0.3.0", + "svelte-simple-modal": "^0.4.2", "uikit": "^3.1.7" }, "devDependencies": { @@ -83,4 +86,4 @@ "svelte": "3.23.x" }, "gitHead": "115189f72a850bfb52b65ec61d932531bf327072" -} +} \ No newline at end of file diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte index 000c5c0e16..b19b8df5c7 100644 --- a/packages/builder/src/App.svelte +++ b/packages/builder/src/App.svelte @@ -7,6 +7,7 @@ import AppNotification, { showAppNotification, } from "components/common/AppNotification.svelte" + import { NotificationDisplay } from "@beyonk/svelte-notifications" function showErrorBanner() { showAppNotification({ @@ -26,4 +27,7 @@ + + + diff --git a/packages/builder/src/budibase.css b/packages/builder/src/budibase.css index f433b016da..3d880cbfa2 100644 --- a/packages/builder/src/budibase.css +++ b/packages/builder/src/budibase.css @@ -1,8 +1,7 @@ /* Budibase Component Styles */ .header { font-size: 0.75rem; - color: #000333; - opacity: 0.4; + color: var(--ink); text-transform: uppercase; margin-top: 1rem; font-weight: 500; @@ -57,35 +56,34 @@ .budibase__nav-item { cursor: pointer; - padding: 0 7px 0 3px; + padding: 0 4px 0 2px; height: 35px; - margin: 5px 20px 5px 0px; + margin: 5px 0px 4px 0px; border-radius: 0 5px 5px 0; display: flex; align-items: center; - font-weight: 500; - font-size: 13px; + font-size: 14px; + transition: 0.2s; } .budibase__nav-item.selected { - color: var(--button-text); - background: #f1f4fc; + color: var(--ink); + background: var(--blue-light); } .budibase__nav-item:hover { - background: #fafafa; + background: var(--grey-light); } .budibase__input { - width: 250px; height: 35px; + width: 220px; border-radius: 3px; - border: 1px solid #DBDBDB; + border: 1px solid var(--grey-dark); text-align: left; - letter-spacing: 0.7px; - color: #000333; - font-size: 16px; - padding-left: 5px; + color: var(--ink); + font-size: 14px; + padding-left: 12px; } .uk-text-right { @@ -102,27 +100,32 @@ } .budibase__table { - border: 1px solid #ccc; + border: 1px solid var(--grey-dark); background: #fff; border-radius: 2px; } .budibase__table thead { - background: #fafafa; + background: var(--blue-light); } .budibase__table thead > tr > th { - color: var(--button-text); + color: var(--ink); text-transform: capitalize; font-weight: 500; } .budibase__table tr { - border-bottom: 1px solid #ccc; + border-bottom: 1px solid var(--grey-light); } .button--toggled { - background: #fafafa; - color: var(--button-text); - padding: 10px; + background: var(--blue-light); + color: var(--ink-light); + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; } \ No newline at end of file diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index 7440fd7031..3fcd35ce28 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -3,6 +3,7 @@ const apiCall = method => async (url, body) => { method: method, headers: { "Content-Type": "application/json", + "x-user-agent": "Budibase Builder", }, body: body && JSON.stringify(body), }) @@ -14,14 +15,16 @@ const apiCall = method => async (url, body) => { return response } -const post = apiCall("POST") -const get = apiCall("GET") -const patch = apiCall("PATCH") -const del = apiCall("DELETE") +export const post = apiCall("POST") +export const get = apiCall("GET") +export const patch = apiCall("PATCH") +export const del = apiCall("DELETE") +export const put = apiCall("PUT") export default { post, get, patch, delete: del, + put, } diff --git a/packages/builder/src/builderStore/generate_css.js b/packages/builder/src/builderStore/generate_css.js index 5826d4f824..2bb5a3bd2e 100644 --- a/packages/builder/src/builderStore/generate_css.js +++ b/packages/builder/src/builderStore/generate_css.js @@ -17,33 +17,22 @@ export const generate_screen_css = component_arr => { export const generate_css = style => { let cssString = Object.entries(style).reduce((str, [key, value]) => { - //TODO Handle arrays and objects here also if (typeof value === "string") { if (value) { return (str += `${key}: ${value};\n`) } } else if (Array.isArray(value)) { if (value.length > 0 && !value.every(v => v === "")) { - return (str += `${key}: ${value - .map(generate_array_styles) - .join(" ")};\n`) + return (str += `${key}: ${value.join(" ")};\n`) } } + + return str }, "") return (cssString || "").trim() } -export const generate_array_styles = item => { - let safeItem = item === "" ? 0 : item - let hasPx = new RegExp("px$") - if (!hasPx.test(safeItem)) { - return `${safeItem}px` - } else { - return safeItem - } -} - export const apply_class = (id, name = "element", styles, selector) => { if (selector === "normal") { return `.${name}-${id} {\n${styles}\n}` diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index 8ba017a7c8..2af3a66667 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -1,9 +1,11 @@ import { getStore } from "./store" import { getBackendUiStore } from "./store/backend" +import { getWorkflowStore } from "./store/workflow/" import LogRocket from "logrocket" export const store = getStore() export const backendUiStore = getBackendUiStore() +export const workflowStore = getWorkflowStore() export const initialise = async () => { try { diff --git a/packages/builder/src/builderStore/loadComponentLibraries.js b/packages/builder/src/builderStore/loadComponentLibraries.js index ada00134f4..9d534f86fe 100644 --- a/packages/builder/src/builderStore/loadComponentLibraries.js +++ b/packages/builder/src/builderStore/loadComponentLibraries.js @@ -1,3 +1,5 @@ +import { get } from "builderStore/api" + /** * Fetches the definitions for component library components. This includes * their props and other metadata from components.json. @@ -6,7 +8,7 @@ export const fetchComponentLibDefinitions = async appId => { const LIB_DEFINITION_URL = `/${appId}/components/definitions` try { - const libDefinitionResponse = await fetch(LIB_DEFINITION_URL) + const libDefinitionResponse = await get(LIB_DEFINITION_URL) return await libDefinitionResponse.json() } catch (err) { console.error(`Error fetching component definitions for ${appId}`, err) diff --git a/packages/builder/src/builderStore/store/backend.js b/packages/builder/src/builderStore/store/backend.js index 62ebd39682..9a5a7b1fbc 100644 --- a/packages/builder/src/builderStore/store/backend.js +++ b/packages/builder/src/builderStore/store/backend.js @@ -1,5 +1,13 @@ import { writable } from "svelte/store" import api from "../api" +import { getContext } from "svelte" + +/** TODO: DEMO SOLUTION + * this section should not be here, it is a quick fix for a demo + * when we reorg the backend UI, this should disappear + * **/ +import { CreateEditModelModal } from "components/database/ModelDataTable/modals" +/** DEMO SOLUTION END **/ export const getBackendUiStore = () => { const INITIAL_BACKEND_UI_STATE = { @@ -22,11 +30,27 @@ export const getBackendUiStore = () => { const views = await viewsResponse.json() store.update(state => { state.selectedDatabase = db + if (models && models.length > 0) { + state.selectedModel = models[0] + state.selectedView = `all_${models[0]._id}` + } state.breadcrumbs = [db.name] state.models = models state.views = views return state }) + /** TODO: DEMO SOLUTION**/ + if (!models || models.length === 0) { + const { open, close } = getContext("simple-modal") + open( + CreateEditModelModal, + { + onClosed: close, + }, + { styleContent: { padding: "0" } } + ) + } + /** DEMO SOLUTION END **/ }, }, records: { @@ -51,6 +75,8 @@ export const getBackendUiStore = () => { store.update(state => { state.models.push(model) state.models = state.models + state.selectedModel = model + state.selectedView = `all_${model._id}` return state }), }, diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js index f4d47064be..1f3ea9c0ef 100644 --- a/packages/builder/src/builderStore/store/index.js +++ b/packages/builder/src/builderStore/store/index.js @@ -1,11 +1,10 @@ -import { cloneDeep, values } from "lodash/fp" +import { values } from "lodash/fp" import { backendUiStore } from "builderStore" import * as backendStoreActions from "./backend" import { writable, get } from "svelte/store" import api from "../api" import { DEFAULT_PAGES_OBJECT } from "../../constants" import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents" -import { rename } from "components/userInterface/pagesParsing/renameScreen" import { createProps, makePropsSafe, @@ -16,6 +15,16 @@ import { buildCodeForScreens } from "../buildCodeForScreens" import { generate_screen_css } from "../generate_css" import { insertCodeMetadata } from "../insertCodeMetadata" import { uuid } from "../uuid" +import { + selectComponent as _selectComponent, + getParent, + walkProps, + savePage as _savePage, + saveCurrentPreviewItem as _saveCurrentPreviewItem, + saveScreenApi as _saveScreenApi, + regenerateCssForCurrentScreen, + renameCurrentScreen, +} from "../storeUtils" export const getStore = () => { const initial = { @@ -43,7 +52,6 @@ export const getStore = () => { store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store) store.saveScreen = saveScreen(store) - store.renameScreen = renameScreen(store) store.deleteScreen = deleteScreen(store) store.setCurrentScreen = setCurrentScreen(store) store.setCurrentPage = setCurrentPage(store) @@ -54,13 +62,10 @@ export const getStore = () => { store.addChildComponent = addChildComponent(store) store.selectComponent = selectComponent(store) store.setComponentProp = setComponentProp(store) + store.setPageOrScreenProp = setPageOrScreenProp(store) store.setComponentStyle = setComponentStyle(store) store.setComponentCode = setComponentCode(store) store.setScreenType = setScreenType(store) - store.deleteComponent = deleteComponent(store) - store.moveUpComponent = moveUpComponent(store) - store.moveDownComponent = moveDownComponent(store) - store.copyComponent = copyComponent(store) store.getPathToComponent = getPathToComponent(store) store.addTemplatedComponent = addTemplatedComponent(store) store.setMetadataProp = setMetadataProp(store) @@ -69,6 +74,9 @@ export const getStore = () => { export default getStore +export const getComponentDefinition = (state, name) => + name.startsWith("##") ? getBuiltin(name) : state.components[name] + const setPackage = (store, initial) => async pkg => { const [main_screens, unauth_screens] = await Promise.all([ api @@ -140,12 +148,6 @@ const _saveScreen = async (store, s, screen) => { return s } -const _saveScreenApi = (screen, s) => { - api - .post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen) - .then(() => _savePage(s)) -} - const createScreen = store => (screenName, route, layoutComponentName) => { store.update(state => { const rootComponent = state.components[layoutComponentName] @@ -155,7 +157,6 @@ const createScreen = store => (screenName, route, layoutComponentName) => { description: "", url: "", _css: "", - uiFunctions: "", props: createProps(rootComponent).props, } @@ -173,11 +174,10 @@ const createScreen = store => (screenName, route, layoutComponentName) => { const setCurrentScreen = store => screenName => { store.update(s => { const screen = getExactComponent(s.screens, screenName) - screen._css = generate_screen_css([screen.props]) s.currentPreviewItem = screen s.currentFrontEndType = "screen" s.currentView = "detail" - + regenerateCssForCurrentScreen(s) const safeProps = makePropsSafe( s.components[screen.props._component], screen.props @@ -207,46 +207,6 @@ const deleteScreen = store => name => { }) } -const renameScreen = store => (oldname, newname) => { - store.update(s => { - const { screens, pages, error, changedScreens } = rename( - s.pages, - s.screens, - oldname, - newname - ) - - if (error) { - // should really do something with this - return s - } - - s.screens = screens - s.pages = pages - if (s.currentPreviewItem.name === oldname) - s.currentPreviewItem.name = newname - - const saveAllChanged = async () => { - for (let screenName of changedScreens) { - const changedScreen = getExactComponent(screens, screenName) - await api.post(`/_builder/api/${s.appId}/screen`, changedScreen) - } - } - - api - .patch(`/_builder/api/${s.appId}/screen`, { - oldname, - newname, - }) - .then(() => saveAllChanged()) - .then(() => { - _savePage(s) - }) - - return s - }) -} - const savePage = store => async page => { store.update(state => { if (state.currentFrontEndType !== "page" || !state.currentPageName) { @@ -277,15 +237,6 @@ const removeStylesheet = store => stylesheet => { }) } -const _savePage = async s => { - const page = s.pages[s.currentPageName] - await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, { - page: { componentLibraries: s.pages.componentLibraries, ...page }, - uiFunctions: s.currentPageFunctions, - screens: page._screens, - }) -} - const setCurrentPage = store => pageName => { store.update(state => { const current_screens = state.pages[pageName]._screens @@ -304,9 +255,7 @@ const setCurrentPage = store => pageName => { state.currentComponentInfo = safeProps currentPage.props = safeProps state.currentPreviewItem = state.pages[pageName] - state.currentPreviewItem._css = generate_screen_css([ - state.currentPreviewItem.props, - ]) + regenerateCssForCurrentScreen(state) for (let screen of state.screens) { screen._css = generate_screen_css([screen.props]) @@ -317,8 +266,6 @@ const setCurrentPage = store => pageName => { }) } -// const getComponentDefinition = (components, name) => components.find(c => c.name === name) - /** * @param {string} componentToAdd - name of the component to add to the application * @param {string} presetName - name of the component preset if defined @@ -344,9 +291,7 @@ const addChildComponent = store => (componentToAdd, presetName) => { return state } - const component = componentToAdd.startsWith("##") - ? getBuiltin(componentToAdd) - : state.components[componentToAdd] + const component = getComponentDefinition(state, componentToAdd) const presetProps = presetName ? component.presets[presetName] : {} @@ -379,6 +324,7 @@ const addChildComponent = store => (componentToAdd, presetName) => { /** * @param {string} props - props to add, as child of current component */ + const addTemplatedComponent = store => props => { store.update(state => { walkProps(props, p => { @@ -387,9 +333,7 @@ const addTemplatedComponent = store => props => { state.currentComponentInfo._children = state.currentComponentInfo._children.concat( props ) - state.currentPreviewItem._css = generate_screen_css([ - state.currentPreviewItem.props, - ]) + regenerateCssForCurrentScreen(state) setCurrentPageFunctions(state) _saveCurrentPreviewItem(state) @@ -400,12 +344,7 @@ const addTemplatedComponent = store => props => { const selectComponent = store => component => { store.update(state => { - const componentDef = component._component.startsWith("##") - ? component - : state.components[component._component] - state.currentComponentInfo = makePropsSafe(componentDef, component) - state.currentView = "component" - return state + return _selectComponent(state, component) }) } @@ -421,6 +360,18 @@ const setComponentProp = store => (name, value) => { }) } +const setPageOrScreenProp = store => (name, value) => { + store.update(state => { + if (name === "name" && state.currentFrontEndType === "screen") { + state = renameCurrentScreen(value, state) + } else { + state.currentPreviewItem[name] = value + _saveCurrentPreviewItem(state) + } + return state + }) +} + const setComponentStyle = store => (type, name, value) => { store.update(state => { if (!state.currentComponentInfo._styles) { @@ -428,9 +379,7 @@ const setComponentStyle = store => (type, name, value) => { } state.currentComponentInfo._styles[type][name] = value - state.currentPreviewItem._css = generate_screen_css([ - state.currentPreviewItem.props, - ]) + regenerateCssForCurrentScreen(state) // save without messing with the store _saveCurrentPreviewItem(state) @@ -472,75 +421,6 @@ const setScreenType = store => type => { }) } -const deleteComponent = store => componentName => { - store.update(state => { - const parent = getParent(state.currentPreviewItem.props, componentName) - - if (parent) { - parent._children = parent._children.filter( - component => component !== componentName - ) - } - - _saveCurrentPreviewItem(state) - - return state - }) -} - -const moveUpComponent = store => component => { - store.update(s => { - const parent = getParent(s.currentPreviewItem.props, component) - - if (parent) { - const currentIndex = parent._children.indexOf(component) - if (currentIndex === 0) return s - - const newChildren = parent._children.filter(c => c !== component) - newChildren.splice(currentIndex - 1, 0, component) - parent._children = newChildren - } - s.currentComponentInfo = component - _saveCurrentPreviewItem(s) - - return s - }) -} - -const moveDownComponent = store => component => { - store.update(s => { - const parent = getParent(s.currentPreviewItem.props, component) - - if (parent) { - const currentIndex = parent._children.indexOf(component) - if (currentIndex === parent._children.length - 1) return s - - const newChildren = parent._children.filter(c => c !== component) - newChildren.splice(currentIndex + 1, 0, component) - parent._children = newChildren - } - s.currentComponentInfo = component - _saveCurrentPreviewItem(s) - - return s - }) -} - -const copyComponent = store => component => { - store.update(s => { - const parent = getParent(s.currentPreviewItem.props, component) - const copiedComponent = cloneDeep(component) - walkProps(copiedComponent, p => { - p._id = uuid() - }) - parent._children = [...parent._children, copiedComponent] - s.curren - _saveCurrentPreviewItem(s) - s.currentComponentInfo = copiedComponent - return s - }) -} - const getPathToComponent = store => component => { // Gets all the components to needed to construct a path. const tempStore = get(store) @@ -572,39 +452,9 @@ const getPathToComponent = store => component => { return path } -const getParent = (rootProps, child) => { - let parent - walkProps(rootProps, (p, breakWalk) => { - if (p._children && p._children.includes(child)) { - parent = p - breakWalk() - } - }) - return parent -} - -const walkProps = (props, action, cancelToken = null) => { - cancelToken = cancelToken || { cancelled: false } - action(props, () => { - cancelToken.cancelled = true - }) - - if (props._children) { - for (let child of props._children) { - if (cancelToken.cancelled) return - walkProps(child, action, cancelToken) - } - } -} - const setMetadataProp = store => (name, prop) => { store.update(s => { s.currentPreviewItem[name] = prop return s }) } - -const _saveCurrentPreviewItem = s => - s.currentFrontEndType === "page" - ? _savePage(s) - : _saveScreenApi(s.currentPreviewItem, s) diff --git a/packages/builder/src/builderStore/store/workflow/Workflow.js b/packages/builder/src/builderStore/store/workflow/Workflow.js new file mode 100644 index 0000000000..d9c1ee249f --- /dev/null +++ b/packages/builder/src/builderStore/store/workflow/Workflow.js @@ -0,0 +1,95 @@ +import mustache from "mustache" +import blockDefinitions from "components/workflow/WorkflowPanel/blockDefinitions" +import { generate } from "shortid" + +/** + * Class responsible for the traversing of the workflow definition. + * Workflow definitions are stored in linked lists. + */ +export default class Workflow { + constructor(workflow) { + this.workflow = workflow + } + + hasTrigger() { + return this.workflow.definition.trigger + } + + addBlock(block) { + // Make sure to add trigger if doesn't exist + if (!this.hasTrigger() && block.type === "TRIGGER") { + this.workflow.definition.trigger = { id: generate(), ...block } + return + } + + this.workflow.definition.steps.push({ + id: generate(), + ...block, + }) + } + + updateBlock(updatedBlock, id) { + const { steps, trigger } = this.workflow.definition + + if (trigger && trigger.id === id) { + this.workflow.definition.trigger = null + return + } + + const stepIdx = steps.findIndex(step => step.id === id) + if (stepIdx < 0) throw new Error("Block not found.") + steps.splice(stepIdx, 1, updatedBlock) + } + + deleteBlock(id) { + const { steps, trigger } = this.workflow.definition + + if (trigger && trigger.id === id) { + this.workflow.definition.trigger = null + return + } + + const stepIdx = steps.findIndex(step => step.id === id) + if (stepIdx < 0) throw new Error("Block not found.") + steps.splice(stepIdx, 1) + } + + createUiTree() { + if (!this.workflow.definition) return [] + return Workflow.buildUiTree(this.workflow.definition) + } + + static buildUiTree(definition) { + const steps = [] + if (definition.trigger) steps.push(definition.trigger) + + return [...steps, ...definition.steps].map(step => { + // The client side display definition for the block + const definition = blockDefinitions[step.type][step.actionId] + if (!definition) { + throw new Error( + `No block definition exists for the chosen block. Check there's an entry in the block definitions for ${step.actionId}` + ) + } + + if (!definition.params) { + throw new Error( + `Blocks should always have parameters. Ensure that the block definition is correct for ${step.actionId}` + ) + } + + const tagline = definition.tagline || "" + const args = step.args || {} + + return { + id: step.id, + type: step.type, + params: step.params, + args, + heading: step.actionId, + body: mustache.render(tagline, args), + name: definition.name, + } + }) + } +} diff --git a/packages/builder/src/builderStore/store/workflow/index.js b/packages/builder/src/builderStore/store/workflow/index.js new file mode 100644 index 0000000000..8d5e63b197 --- /dev/null +++ b/packages/builder/src/builderStore/store/workflow/index.js @@ -0,0 +1,106 @@ +import { writable } from "svelte/store" +import api from "../../api" +import Workflow from "./Workflow" + +const workflowActions = store => ({ + fetch: async instanceId => { + const WORKFLOWS_URL = `/api/${instanceId}/workflows` + const workflowResponse = await api.get(WORKFLOWS_URL) + const json = await workflowResponse.json() + store.update(state => { + state.workflows = json + return state + }) + }, + create: async ({ instanceId, name }) => { + const workflow = { + name, + definition: { + steps: [], + }, + } + const CREATE_WORKFLOW_URL = `/api/${instanceId}/workflows` + const response = await api.post(CREATE_WORKFLOW_URL, workflow) + const json = await response.json() + store.update(state => { + state.workflows = state.workflows.concat(json.workflow) + state.currentWorkflow = new Workflow(json.workflow) + return state + }) + }, + save: async ({ instanceId, workflow }) => { + const UPDATE_WORKFLOW_URL = `/api/${instanceId}/workflows` + const response = await api.put(UPDATE_WORKFLOW_URL, workflow) + const json = await response.json() + store.update(state => { + const existingIdx = state.workflows.findIndex( + existing => existing._id === workflow._id + ) + state.workflows.splice(existingIdx, 1, json.workflow) + state.workflows = state.workflows + state.currentWorkflow = new Workflow(json.workflow) + return state + }) + }, + update: async ({ instanceId, workflow }) => { + const UPDATE_WORKFLOW_URL = `/api/${instanceId}/workflows` + const response = await api.put(UPDATE_WORKFLOW_URL, workflow) + const json = await response.json() + store.update(state => { + const existingIdx = state.workflows.findIndex( + existing => existing._id === workflow._id + ) + state.workflows.splice(existingIdx, 1, json.workflow) + state.workflows = state.workflows + return state + }) + }, + delete: async ({ instanceId, workflow }) => { + const { _id, _rev } = workflow + const DELETE_WORKFLOW_URL = `/api/${instanceId}/workflows/${_id}/${_rev}` + await api.delete(DELETE_WORKFLOW_URL) + + store.update(state => { + const existingIdx = state.workflows.findIndex( + existing => existing._id === _id + ) + state.workflows.splice(existingIdx, 1) + state.workflows = state.workflows + state.currentWorkflow = null + return state + }) + }, + select: workflow => { + store.update(state => { + state.currentWorkflow = new Workflow(workflow) + state.selectedWorkflowBlock = null + return state + }) + }, + addBlockToWorkflow: block => { + store.update(state => { + state.currentWorkflow.addBlock(block) + state.selectedWorkflowBlock = block + return state + }) + }, + deleteWorkflowBlock: block => { + store.update(state => { + state.currentWorkflow.deleteBlock(block.id) + state.selectedWorkflowBlock = null + return state + }) + }, +}) + +export const getWorkflowStore = () => { + const INITIAL_WORKFLOW_STATE = { + workflows: [], + } + + const store = writable(INITIAL_WORKFLOW_STATE) + + store.actions = workflowActions(store) + + return store +} diff --git a/packages/builder/src/builderStore/store/workflow/tests/Workflow.spec.js b/packages/builder/src/builderStore/store/workflow/tests/Workflow.spec.js new file mode 100644 index 0000000000..fd14404a5f --- /dev/null +++ b/packages/builder/src/builderStore/store/workflow/tests/Workflow.spec.js @@ -0,0 +1,57 @@ +import Workflow from "../Workflow"; +import TEST_WORKFLOW from "./testWorkflow"; + +const TEST_BLOCK = { + id: "VFWeZcIPx", + name: "Update UI State", + tagline: "Update {{path}} to {{value}}", + icon: "ri-refresh-line", + description: "Update your User Interface with some data.", + environment: "CLIENT", + params: { + path: "string", + value: "longText", + }, + args: { + path: "foo", + value: "started...", + }, + actionId: "SET_STATE", + type: "ACTION", +} + +describe("Workflow Data Object", () => { + let workflow + + beforeEach(() => { + workflow = new Workflow({ ...TEST_WORKFLOW }); + }); + + it("adds a workflow block to the workflow", () => { + workflow.addBlock(TEST_BLOCK); + expect(workflow.workflow.definition) + }) + + it("updates a workflow block with new attributes", () => { + const firstBlock = workflow.workflow.definition.steps[0]; + const updatedBlock = { + ...firstBlock, + name: "UPDATED" + }; + workflow.updateBlock(updatedBlock, firstBlock.id); + expect(workflow.workflow.definition.steps[0]).toEqual(updatedBlock) + }) + + it("deletes a workflow block successfully", () => { + const { steps } = workflow.workflow.definition + const originalLength = steps.length + + const lastBlock = steps[steps.length - 1]; + workflow.deleteBlock(lastBlock.id); + expect(workflow.workflow.definition.steps.length).toBeLessThan(originalLength); + }) + + it("builds a tree that gets rendered in the flowchart builder", () => { + expect(Workflow.buildUiTree(TEST_WORKFLOW.definition)).toMatchSnapshot(); + }) +}) diff --git a/packages/builder/src/builderStore/store/workflow/tests/__snapshots__/Workflow.spec.js.snap b/packages/builder/src/builderStore/store/workflow/tests/__snapshots__/Workflow.spec.js.snap new file mode 100644 index 0000000000..732764a082 --- /dev/null +++ b/packages/builder/src/builderStore/store/workflow/tests/__snapshots__/Workflow.spec.js.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Workflow Data Object builds a tree that gets rendered in the flowchart builder 1`] = ` +Array [ + Object { + "args": Object { + "time": 3000, + }, + "body": "Delay for 3000 milliseconds", + "heading": "DELAY", + "id": "zJQcZUgDS", + "name": "Delay", + "params": Object { + "time": "number", + }, + "type": "LOGIC", + }, + Object { + "args": Object { + "path": "foo", + "value": "finished", + }, + "body": "Update foo to finished", + "heading": "SET_STATE", + "id": "3RSTO7BMB", + "name": "Update UI State", + "params": Object { + "path": "string", + "value": "longText", + }, + "type": "ACTION", + }, + Object { + "args": Object { + "path": "foo", + "value": "started...", + }, + "body": "Update foo to started...", + "heading": "SET_STATE", + "id": "VFWeZcIPx", + "name": "Update UI State", + "params": Object { + "path": "string", + "value": "longText", + }, + "type": "ACTION", + }, +] +`; diff --git a/packages/builder/src/builderStore/store/workflow/tests/testWorkflow.js b/packages/builder/src/builderStore/store/workflow/tests/testWorkflow.js new file mode 100644 index 0000000000..90c4b17924 --- /dev/null +++ b/packages/builder/src/builderStore/store/workflow/tests/testWorkflow.js @@ -0,0 +1,63 @@ +export default { + _id: "53b6148c65d1429c987e046852d11611", + _rev: "4-02c6659734934895812fa7be0215ee59", + name: "Test Workflow", + definition: { + steps: [ + { + id: "VFWeZcIPx", + name: "Update UI State", + tagline: "Update {{path}} to {{value}}", + icon: "ri-refresh-line", + description: "Update your User Interface with some data.", + environment: "CLIENT", + params: { + path: "string", + value: "longText", + }, + args: { + path: "foo", + value: "started...", + }, + actionId: "SET_STATE", + type: "ACTION", + }, + { + id: "zJQcZUgDS", + name: "Delay", + icon: "ri-time-fill", + tagline: "Delay for {{time}} milliseconds", + description: "Delay the workflow until an amount of time has passed.", + environment: "CLIENT", + params: { + time: "number", + }, + args: { + time: 3000, + }, + actionId: "DELAY", + type: "LOGIC", + }, + { + id: "3RSTO7BMB", + name: "Update UI State", + tagline: "Update {{path}} to {{value}}", + icon: "ri-refresh-line", + description: "Update your User Interface with some data.", + environment: "CLIENT", + params: { + path: "string", + value: "longText", + }, + args: { + path: "foo", + value: "finished", + }, + actionId: "SET_STATE", + type: "ACTION", + }, + ], + }, + type: "workflow", + live: true, +} diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js new file mode 100644 index 0000000000..ff951e6b6f --- /dev/null +++ b/packages/builder/src/builderStore/storeUtils.js @@ -0,0 +1,80 @@ +import { makePropsSafe } from "components/userInterface/pagesParsing/createProps" +import api from "./api" +import { generate_screen_css } from "./generate_css" + +export const selectComponent = (state, component) => { + const componentDef = component._component.startsWith("##") + ? component + : state.components[component._component] + state.currentComponentInfo = makePropsSafe(componentDef, component) + state.currentView = "component" + return state +} + +export const getParent = (rootProps, child) => { + let parent + walkProps(rootProps, (p, breakWalk) => { + if ( + p._children && + (p._children.includes(child) || p._children.some(c => c._id === child)) + ) { + parent = p + breakWalk() + } + }) + return parent +} + +export const saveCurrentPreviewItem = s => + s.currentFrontEndType === "page" + ? savePage(s) + : saveScreenApi(s.currentPreviewItem, s) + +export const savePage = async s => { + const page = s.pages[s.currentPageName] + await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, { + page: { componentLibraries: s.pages.componentLibraries, ...page }, + uiFunctions: s.currentPageFunctions, + screens: page._screens, + }) +} + +export const saveScreenApi = (screen, s) => { + api + .post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen) + .then(() => savePage(s)) +} + +export const renameCurrentScreen = (newname, state) => { + const oldname = state.currentPreviewItem.name + state.currentPreviewItem.name = newname + api.patch( + `/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`, + { + oldname, + newname, + } + ) + return state +} + +export const walkProps = (props, action, cancelToken = null) => { + cancelToken = cancelToken || { cancelled: false } + action(props, () => { + cancelToken.cancelled = true + }) + + if (props._children) { + for (let child of props._children) { + if (cancelToken.cancelled) return + walkProps(child, action, cancelToken) + } + } +} + +export const regenerateCssForCurrentScreen = state => { + state.currentPreviewItem._css = generate_screen_css([ + state.currentPreviewItem.props, + ]) + return state +} diff --git a/packages/builder/src/components/common/ActionButton.svelte b/packages/builder/src/components/common/ActionButton.svelte index f8eb00f6e3..147737b5c6 100644 --- a/packages/builder/src/components/common/ActionButton.svelte +++ b/packages/builder/src/components/common/ActionButton.svelte @@ -1,6 +1,7 @@ - + handleChange(e.target.value)} /> diff --git a/packages/builder/src/components/common/Inputs/InputGroup.svelte b/packages/builder/src/components/common/Inputs/InputGroup.svelte index ffe6880f51..385623ca19 100644 --- a/packages/builder/src/components/common/Inputs/InputGroup.svelte +++ b/packages/builder/src/components/common/Inputs/InputGroup.svelte @@ -1,74 +1,50 @@
{label}
-
- {#each meta as { placeholder }, i} - handleChange(e.target.value || 0, i)} /> +
+ {#each meta as m, i} + handleChange(value || 0, i)} /> {/each}
diff --git a/packages/builder/src/components/common/Modal.svelte b/packages/builder/src/components/common/Modal.svelte index 807dcee922..c91c30827e 100644 --- a/packages/builder/src/components/common/Modal.svelte +++ b/packages/builder/src/components/common/Modal.svelte @@ -50,10 +50,10 @@ diff --git a/packages/builder/src/components/common/Select.svelte b/packages/builder/src/components/common/Select.svelte index 39dbe6d059..4bdd2a941e 100644 --- a/packages/builder/src/components/common/Select.svelte +++ b/packages/builder/src/components/common/Select.svelte @@ -20,11 +20,8 @@ diff --git a/packages/builder/src/components/common/Switcher.svelte b/packages/builder/src/components/common/Switcher.svelte new file mode 100644 index 0000000000..484d52ee07 --- /dev/null +++ b/packages/builder/src/components/common/Switcher.svelte @@ -0,0 +1,78 @@ + + +
+ +
+ + {#each tabs as tab} + + {/each} + +
+ +
+ {#if selectedIndex === 0} + + {:else if selectedIndex === 1} + + {:else if selectedIndex === 2} + + {:else if selectedIndex === 3} + + {/if} +
+ +
+ + diff --git a/packages/builder/src/components/common/binding.js b/packages/builder/src/components/common/binding.js deleted file mode 100644 index af04397327..0000000000 --- a/packages/builder/src/components/common/binding.js +++ /dev/null @@ -1,31 +0,0 @@ -import { isString } from "lodash/fp" - -import { - BB_STATE_BINDINGPATH, - BB_STATE_FALLBACK, - BB_STATE_BINDINGSOURCE, - isBound, - parseBinding, -} from "@budibase/client/src/state/parseBinding" - -export const isBinding = isBound - -export const setBinding = ({ path, fallback, source }, binding = {}) => { - if (isNonEmptyString(path)) binding[BB_STATE_BINDINGPATH] = path - if (isNonEmptyString(fallback)) binding[BB_STATE_FALLBACK] = fallback - binding[BB_STATE_BINDINGSOURCE] = source || "store" - return binding -} - -export const getBinding = val => { - const binding = parseBinding(val) - return binding - ? binding - : { - path: "", - source: "store", - fallback: "", - } -} - -const isNonEmptyString = s => isString(s) && s.length > 0 diff --git a/packages/builder/src/components/common/eventHandlers.js b/packages/builder/src/components/common/eventHandlers.js index 9dac01eb86..2e7a62ac3e 100644 --- a/packages/builder/src/components/common/eventHandlers.js +++ b/packages/builder/src/components/common/eventHandlers.js @@ -1,13 +1,8 @@ import { eventHandlers } from "../../../../client/src/state/eventHandlers" -import { writable } from "svelte/store" export { EVENT_TYPE_MEMBER_NAME } from "../../../../client/src/state/eventHandlers" -export const allHandlers = user => { - const store = writable({ - _bbuser: user, - }) - - const handlersObj = eventHandlers(store) +export const allHandlers = () => { + const handlersObj = eventHandlers() const handlers = Object.keys(handlersObj).map(name => ({ name, diff --git a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte index 9e6ddc5042..f1d49894a0 100644 --- a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte +++ b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte @@ -43,13 +43,6 @@ ) } - async function selectRecord(record) { - return await api.loadRecord(record.key, { - appname: $store.appname, - instanceId: $backendUiStore.selectedDatabase._id, - }) - } - const ITEMS_PER_PAGE = 10 // Internal headers we want to hide from the user const INTERNAL_HEADERS = ["_id", "_rev", "modelId", "type"] @@ -152,19 +145,19 @@ } table { - border: 1px solid #ccc; + border: 1px solid var(--grey-dark); background: #fff; border-radius: 3px; border-collapse: collapse; } thead { - background: #f9f9f9; - border: 1px solid #ccc; + background: var(--blue-light); + border: 1px solid var(--grey-dark); } thead th { - color: var(--button-text); + color: var(--ink); text-transform: capitalize; font-weight: 500; font-size: 14px; @@ -173,14 +166,14 @@ } tbody tr { - border-bottom: 1px solid #ccc; + border-bottom: 1px solid var(--grey-dark); transition: 0.3s background-color; - color: var(--secondary100); + color: var(--ink); font-size: 14px; } tbody tr:hover { - background: #fafafa; + background: var(--grey-light); } .table-controls { diff --git a/packages/builder/src/components/database/ModelDataTable/api.js b/packages/builder/src/components/database/ModelDataTable/api.js index c723676388..cb98879567 100644 --- a/packages/builder/src/components/database/ModelDataTable/api.js +++ b/packages/builder/src/components/database/ModelDataTable/api.js @@ -1,6 +1,6 @@ import api from "builderStore/api" -export async function createUser(user, appId, instanceId) { +export async function createUser(user, instanceId) { const CREATE_USER_URL = `/api/${instanceId}/users` const response = await api.post(CREATE_USER_URL, user) return await response.json() @@ -28,7 +28,7 @@ export async function saveRecord(record, instanceId, modelId) { } export async function fetchDataForView(viewName, instanceId) { - const FETCH_RECORDS_URL = `/api/${instanceId}/${viewName}/records` + const FETCH_RECORDS_URL = `/api/${instanceId}/views/${viewName}` const response = await api.get(FETCH_RECORDS_URL) return await response.json() diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/CreateEditModel.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/CreateEditModel.svelte index 97bb799c45..6264b74e09 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/CreateEditModel.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/CreateEditModel.svelte @@ -35,7 +35,7 @@ } - +
{#if !showFieldView}

Create / Edit Model

@@ -43,22 +43,20 @@

Create / Edit Field

{/if} - +
{#if !showFieldView}
-

Settings

- {#if $store.errors && $store.errors.length > 0} {/if} - - - +
+ +
- Fields -

(showFieldView = true)}> + Fields +
(showFieldView = true)}> Add new field -

+
@@ -67,7 +65,6 @@ - @@ -90,9 +87,9 @@ {/each}
Edit Name TypeValues
-
+
Save -
+
{:else} .padding { - padding: 20px; + padding-top: 40px; + } + + .label { + font-size: 14px; + font-weight: 500; + } + + .textbox { + margin: 0px 40px 0px 40px; + font-size: 14px; + font-weight: 500; } .new-field { font-size: 16px; font-weight: bold; - color: var(--button-text); + color: var(--blue); } .fields-table { - margin: 1rem 1rem 0rem 0rem; + margin: 8px 40px 0px 40px; border-collapse: collapse; + width: 88%; } tbody > tr:hover { - background-color: var(--primary10); + background-color: var(--grey-light); } .table-controls { display: flex; justify-content: space-between; align-items: center; + margin: 0px 40px; } .ri-more-line:hover { cursor: pointer; } - heading { - padding: 20px 20px 0 20px; + .heading { + padding: 40px 40px 0 40px; display: flex; align-items: center; } h3 { margin: 0 0 0 10px; + color: var(--ink); + } + + footer { + background-color: var(--grey-light); + margin-top: 40px; + padding: 20px 40px 20px 40px; + display: flex; + justify-content: flex-end; } diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/FieldView.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/FieldView.svelte index 65934a032d..c17a0ac9c8 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/FieldView.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/FieldView.svelte @@ -13,17 +13,41 @@ const FIELD_TYPES = ["string", "number", "boolean"] - export let field = { type: "string" } + export let field = { + type: "string", + constraints: { type: "string", presence: false }, + } export let schema export let goBack let errors = [] let draftField = cloneDeep(field) + let type = field.type + let constraints = field.constraints + let required = + field.constraints.presence && !field.constraints.presence.allowEmpty + const save = () => { + constraints.presence = required ? { allowEmpty: false } : false + draftField.constraints = constraints + draftField.type = type schema[field.name] = draftField goBack() } + + $: constraints = + type === "string" + ? { type: "string", length: {}, presence: false } + : type === "number" + ? { type: "number", presence: false, numericality: {} } + : type === "boolean" + ? { type: "boolean", presence: false } + : type === "datetime" + ? { type: "date", datetime: {}, presence: false } + : type.startsWith("array") + ? { type: "array", presence: false } + : { type: "string", presence: false }
@@ -32,49 +56,51 @@
- + - {#if field.type === 'string'} - - - {:else if field.type === 'boolean'} - - - {:else if field.format === 'datetime'} - - - - {:else if field.type === 'number'} - - - {:else if draftField.type.startsWith('array')} - + + + {#if type === 'string'} + + + {:else if type === 'datetime'} + + + {:else if type === 'number'} + label="Min Value" + bind:value={constraints.numericality.greaterThanOrEqualTo} /> + label="Max Value" + bind:value={constraints.numericality.lessThanOrEqualTo} /> {/if}
+
+ Cancel +
Save - Cancel
diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte index 49749a70a1..62688d58e3 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte @@ -8,10 +8,6 @@ import * as api from "../api" import ErrorsBox from "components/common/ErrorsBox.svelte" - const CLASS_NAME_MAP = { - boolean: "uk-checkbox", - } - export let record = {} export let onClosed @@ -28,14 +24,25 @@ onClosed() } + const isSelect = meta => + meta.type === "string" && + meta.constraints && + meta.constraints.inclusion && + meta.constraints.inclusion.length > 0 + function determineInputType(meta) { if (meta.type === "datetime") return "date" if (meta.type === "number") return "number" if (meta.type === "boolean") return "checkbox" + if (isSelect(meta)) return "select" return "text" } + function determineOptions(meta) { + return isSelect(meta) ? meta.constraints.inclusion : [] + } + async function saveRecord() { const recordResponse = await api.saveRecord( { @@ -46,7 +53,9 @@ $backendUiStore.selectedModel._id ) if (recordResponse.errors) { - errors = recordResponse.errors + errors = Object.keys(recordResponse.errors) + .map(k => ({ dataPath: k, message: recordResponse.errors[k] })) + .flat() return } @@ -65,8 +74,8 @@ {#each modelSchema as [key, meta]}
diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditView.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditView.svelte index 606310d44f..4db445e54f 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditView.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditView.svelte @@ -39,51 +39,49 @@ } - +

Create / Edit View

- +
-

Settings

{#if $store.errors && $store.errors.length > 0} {/if} -
-
- +
+
+
+ +
+
+
+ {#each Object.values(SNIPPET_EDITORS) as snippetType} + (currentSnippetEditor = snippetType)}> + {snippetType} + + {/each} + {#if currentSnippetEditor === SNIPPET_EDITORS.MAP} + + {:else if currentSnippetEditor === SNIPPET_EDITORS.FILTER} + + {:else if currentSnippetEditor === SNIPPET_EDITORS.REDUCE} + + {/if}
- -

Snippets

- {#each Object.values(SNIPPET_EDITORS) as snippetType} - (currentSnippetEditor = snippetType)}> - {snippetType} - - {/each} - {#if currentSnippetEditor === SNIPPET_EDITORS.MAP} - - {:else if currentSnippetEditor === SNIPPET_EDITORS.FILTER} - - {:else if currentSnippetEditor === SNIPPET_EDITORS.REDUCE} - - {/if} - - Save - Delete +
+
+ Delete +
+ Save +
diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateUser.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateUser.svelte index 842703af1c..d5c924405f 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateUser.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateUser.svelte @@ -7,21 +7,26 @@ let username let password + let accessLevelId - $: valid = username && password + $: valid = username && password && accessLevelId $: instanceId = $backendUiStore.selectedDatabase._id $: appId = $store.appId async function createUser() { - const user = { name: username, username, password } - const response = await api.createUser(user, appId, instanceId) + const user = { name: username, username, password, accessLevelId } + const response = await api.createUser(user, instanceId) backendUiStore.actions.users.create(response) onClosed() }
-
+
+
+ +
Create User
+
@@ -30,20 +35,50 @@
+
+ + +
- Cancel +
+ Cancel +
Save
diff --git a/packages/builder/src/components/database/ModelDataTable/modals/RecordFieldControl.svelte b/packages/builder/src/components/database/ModelDataTable/modals/RecordFieldControl.svelte index 0839180601..5c308a7abb 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/RecordFieldControl.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/RecordFieldControl.svelte @@ -3,10 +3,16 @@ export let value = "" export let label export let errors = [] - export let className = "uk-input" + export let options = [] let checked = type === "checkbox" ? value : false + const determineClassName = type => { + if (type === "checkbox") return "uk-checkbox" + if (type === "select") return "uk-select" + return "uk-input" + } + const handleInput = event => { if (event.target.type === "checkbox") { value = event.target.checked @@ -23,11 +29,23 @@ - 0} - {checked} - {type} - {value} - on:input={handleInput} - on:change={handleInput} /> + +{#if type === 'select'} + +{:else} + 0} + {checked} + {type} + {value} + on:input={handleInput} + on:change={handleInput} /> +{/if} diff --git a/packages/builder/src/components/nav/BackendNav.svelte b/packages/builder/src/components/nav/BackendNav.svelte index 173e92d98b..9a790ed75d 100644 --- a/packages/builder/src/components/nav/BackendNav.svelte +++ b/packages/builder/src/components/nav/BackendNav.svelte @@ -33,18 +33,7 @@
-
-
- -
- -
- -
-
+
{#if $backendUiStore.selectedDatabase._id}
diff --git a/packages/builder/src/components/nav/SchemaManagementDrawer.svelte b/packages/builder/src/components/nav/SchemaManagementDrawer.svelte index 5fb6ed6c25..cc9e5d86a7 100644 --- a/packages/builder/src/components/nav/SchemaManagementDrawer.svelte +++ b/packages/builder/src/components/nav/SchemaManagementDrawer.svelte @@ -46,7 +46,7 @@ function selectModel(model) { backendUiStore.update(state => { state.selectedModel = model - state.selectedView = `${model._id}` + state.selectedView = `all_${model._id}` return state }) } diff --git a/packages/builder/src/components/start/AppCard.svelte b/packages/builder/src/components/start/AppCard.svelte index 2608bfa370..8a0f89bb5e 100644 --- a/packages/builder/src/components/start/AppCard.svelte +++ b/packages/builder/src/components/start/AppCard.svelte @@ -10,7 +10,6 @@

{name}

{description}

@@ -18,7 +17,7 @@ diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index d2232f7604..f012860194 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -5,6 +5,7 @@ import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/" import { getContext } from "svelte" import { fade } from "svelte/transition" + import { post } from "builderStore/api" const { open, close } = getContext("simple-modal") @@ -33,15 +34,7 @@ const data = { name, description } loading = true try { - const response = await fetch("/api/applications", { - method: "POST", // *GET, POST, PUT, DELETE, etc. - credentials: "same-origin", // include, *same-origin, omit - headers: { - "Content-Type": "application/json", - // 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: JSON.stringify(data), // body data type must match "Content-Type" header - }) + const response = await post("/api/applications", data) const res = await response.json() diff --git a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte index 27a4851c8d..00d7e6c171 100644 --- a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte @@ -21,14 +21,47 @@ return componentName || "element" } - $: iframe && - console.log( - iframe.contentDocument.head.insertAdjacentHTML( - "beforeend", - `<\style>` - ) - ) + const screenPlaceholder = { + name: "Screen Placeholder", + route: "*", + props: { + _component: "@budibase/standard-components/container", + type: "div", + _children: [ + { + _component: "@budibase/standard-components/container", + _styles: { normal: {}, hover: {}, active: {}, selected: {} }, + _id: "__screenslot__text", + _code: "", + className: "", + onLoad: [], + type: "div", + _children: [ + { + _component: "@budibase/standard-components/text", + _styles: { + normal: {}, + hover: {}, + active: {}, + selected: {}, + }, + _id: "__screenslot__text_2", + _code: "", + text: "content", + font: "", + color: "", + textAlign: "inline", + verticalAlign: "inline", + formattingTag: "none", + }, + ], + }, + ], + }, + } + $: hasComponent = !!$store.currentPreviewItem + $: { styles = "" // Apply the CSS from the currently selected page and its screens @@ -52,49 +85,12 @@ $: frontendDefinition = { appId: $store.appId, libraries: $store.libraries, - page: $store.currentPreviewItem, - screens: screensExist - ? $store.currentPreviewItem._screens - : [ - { - name: "Screen Placeholder", - route: "*", - props: { - _component: "@budibase/standard-components/container", - type: "div", - _children: [ - { - _component: "@budibase/standard-components/container", - _styles: { normal: {}, hover: {}, active: {}, selected: {} }, - _id: "__screenslot__text", - _code: "", - className: "", - onLoad: [], - type: "div", - _children: [ - { - _component: "@budibase/standard-components/text", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - _id: "__screenslot__text_2", - _code: "", - text: "content", - font: "", - color: "", - textAlign: "inline", - verticalAlign: "inline", - formattingTag: "none", - }, - ], - }, - ], - }, - }, - ], + page: $store.pages[$store.currentPageName], + screens: [ + $store.currentFrontEndType === "page" + ? screenPlaceholder + : $store.currentPreviewItem, + ], appRootPath: "", } @@ -103,6 +99,27 @@ $: selectedComponentId = $store.currentComponentInfo ? $store.currentComponentInfo._id : "" + + const refreshContent = () => { + iframe.contentWindow.postMessage( + JSON.stringify({ + styles, + stylesheetLinks, + selectedComponentType, + selectedComponentId, + frontendDefinition, + }) + ) + } + + $: if (iframe) + iframe.contentWindow.addEventListener("bb-ready", refreshContent, { + once: true, + }) + + $: if (iframe && frontendDefinition) { + refreshContent() + }
@@ -111,14 +128,7 @@ style="height: 100%; width: 100%" title="componentPreview" bind:this={iframe} - srcdoc={iframeTemplate({ - styles, - stylesheetLinks, - selectedComponentType, - selectedComponentId, - frontendDefinition: JSON.stringify(frontendDefinition), - currentPageFunctions: $store.currentPageFunctions, - })} /> + srcdoc={iframeTemplate} /> {/if}
diff --git a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js index dd2ca69dbb..3d305f1c1d 100644 --- a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js @@ -1,23 +1,9 @@ -export default ({ - styles, - stylesheetLinks, - selectedComponentType, - selectedComponentId, - frontendDefinition, - currentPageFunctions, -}) => ` +export default ` - ${stylesheetLinks} - diff --git a/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte b/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte new file mode 100644 index 0000000000..0b51b962ba --- /dev/null +++ b/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte @@ -0,0 +1,277 @@ + + +
{}}> + + +
+ + + + diff --git a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte index 726fb35aae..bcebb4d2d4 100644 --- a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte +++ b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte @@ -13,7 +13,6 @@ import CodeEditor from "./CodeEditor.svelte" import LayoutEditor from "./LayoutEditor.svelte" import EventsEditor from "./EventsEditor" - import panelStructure from "./temporaryPanelStructure.js" import CategoryTab from "./CategoryTab.svelte" import DesignView from "./DesignView.svelte" @@ -25,7 +24,7 @@ let categories = [ { value: "design", name: "Design" }, { value: "settings", name: "Settings" }, - { value: "actions", name: "Actions" }, + { value: "events", name: "Events" }, ] let selectedCategory = categories[0] @@ -38,15 +37,10 @@ c => c._component === componentInstance._component ) || {} - $: panelDefinition = componentPropDefinition.properties - ? componentPropDefinition.properties[selectedCategory.value] - : {} + let panelDefinition = {} - // SCREEN PROPS ============================================= - $: screen_props = - $store.currentFrontEndType === "page" - ? getProps($store.currentPreviewItem, ["name", "favicon"]) - : getProps($store.currentPreviewItem, ["name", "description", "route"]) + $: panelDefinition = componentPropDefinition.properties && + componentPropDefinition.properties[selectedCategory.value] const onStyleChanged = store.setComponentStyle const onPropChanged = store.setComponentProp @@ -92,7 +86,11 @@ {componentInstance} {componentDefinition} {panelDefinition} - onChange={onPropChanged} /> + onChange={onPropChanged} + onScreenPropChange={store.setPageOrScreenProp} + screenOrPageInstance={$store.currentView !== "component" && $store.currentPreviewItem} /> + {:else if selectedCategory.value === 'events'} + {/if}
@@ -105,6 +103,9 @@ display: flex; flex-direction: column; overflow-x: hidden; + overflow-y: hidden; + padding: 20px; + box-sizing: border-box; } .title > div:nth-child(1) { @@ -119,5 +120,7 @@ .component-props-container { margin-top: 20px; flex: 1 1 auto; + min-height: 0; + overflow-y: auto; } diff --git a/packages/builder/src/components/userInterface/ComponentSelectionList.svelte b/packages/builder/src/components/userInterface/ComponentSelectionList.svelte index 0bf848e4a8..590cf9c985 100644 --- a/packages/builder/src/components/userInterface/ComponentSelectionList.svelte +++ b/packages/builder/src/components/userInterface/ComponentSelectionList.svelte @@ -1,4 +1,5 @@ diff --git a/packages/builder/src/components/userInterface/ComponentsHierarchy.svelte b/packages/builder/src/components/userInterface/ComponentsHierarchy.svelte index 26929fcdce..041377aedc 100644 --- a/packages/builder/src/components/userInterface/ComponentsHierarchy.svelte +++ b/packages/builder/src/components/userInterface/ComponentsHierarchy.svelte @@ -1,7 +1,6 @@ - -

- {eventData.name ? `${eventData.name} Event` : 'Create a New Component Event'} -

- - Click here to learn more about component events - +
+
+
+

+ {eventData.name ? `${eventData.name} Event` : 'Create a New Component Event'} +

+
+
+
+

Event Type

+ +
+
-
-
-
-
Event Type
- {@html getIcon('info', 20)} -
- +
+

Event Action(s)

+ { + createNewEventHandler(draftEventHandler) + draftEventHandler = { parameters: [] } + }} + handler={draftEventHandler} /> +
+ {#if eventData} + {#each eventData.handlers as handler, index} + deleteEventHandler(index)} + {handler} /> + {/each} + {/if} + +
+ - -
-
Event Action(s)
- {@html getIcon('info', 20)} -
- { - createNewEventHandler(draftEventHandler) - draftEventHandler = { parameters: [] } - }} - handler={draftEventHandler} /> - {#if eventData} - {#each eventData.handlers as handler, index} - deleteEventHandler(index)} - {handler} /> - {/each} - {/if} - -
- - - Save - +
+
- +
diff --git a/packages/builder/src/components/userInterface/EventsEditor/EventsEditor.svelte b/packages/builder/src/components/userInterface/EventsEditor/EventsEditor.svelte index c85607bd8f..5a3fde257d 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/EventsEditor.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/EventsEditor.svelte @@ -1,4 +1,5 @@ -
-

Events

- openModal()} /> -
+
@@ -72,26 +84,40 @@ {/each}
- diff --git a/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte b/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte index e3811cc176..69ab73e16c 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte @@ -1,73 +1,52 @@
- {parameter.name} -
- - - {#if isOpen} - { - onChange(option) - isOpen = false - }} /> - {/if} -
+ {#if parameter.name === 'workflow'} + {parameter.name} + {/if} + {#if parameter.name === 'workflow'} + + {:else} + + {/if}
diff --git a/packages/builder/src/components/userInterface/FlatButton.svelte b/packages/builder/src/components/userInterface/FlatButton.svelte index 7fb5742c46..2eff1a6be4 100644 --- a/packages/builder/src/components/userInterface/FlatButton.svelte +++ b/packages/builder/src/components/userInterface/FlatButton.svelte @@ -1,18 +1,28 @@ -
onClick(value || text)}> +
onClick(value || text)}> {#if useIcon} {:else} - {text} + + {@html text} + {/if}
@@ -28,6 +38,7 @@ font-size: 14px; font-weight: 400; transition: all 0.3s; + margin-left: 5px; text-rendering: optimizeLegibility; } @@ -35,4 +46,8 @@ background: var(--ink-light); color: #ffffff; } + + i { + font-size: 20px; + } diff --git a/packages/builder/src/components/userInterface/FlatButtonGroup.svelte b/packages/builder/src/components/userInterface/FlatButtonGroup.svelte index ffa1a98393..c9523f4d5a 100644 --- a/packages/builder/src/components/userInterface/FlatButtonGroup.svelte +++ b/packages/builder/src/components/userInterface/FlatButtonGroup.svelte @@ -27,13 +27,16 @@ } onChange(val) } + + const checkSelected = val => + isMultiSelect ? value.includes(val) : value === val
{#each buttonProps as props}
diff --git a/packages/builder/src/components/userInterface/FrontendNavigatePane.svelte b/packages/builder/src/components/userInterface/FrontendNavigatePane.svelte new file mode 100644 index 0000000000..59fc13bcd1 --- /dev/null +++ b/packages/builder/src/components/userInterface/FrontendNavigatePane.svelte @@ -0,0 +1,57 @@ + + + + + + + + + + + + + diff --git a/packages/builder/src/components/userInterface/ItemTab/Item.svelte b/packages/builder/src/components/userInterface/ItemTab/Item.svelte index d4576055c6..e5b36d0183 100644 --- a/packages/builder/src/components/userInterface/ItemTab/Item.svelte +++ b/packages/builder/src/components/userInterface/ItemTab/Item.svelte @@ -3,7 +3,7 @@ export let item -
+
@@ -19,7 +19,7 @@ cursor: pointer; margin-bottom: 8px; padding: 8px 0px 16px 0px; - width: 120px; + width: 110px; height: 80px; justify-content: center; align-items: center; diff --git a/packages/builder/src/components/userInterface/ItemTab/Tab.svelte b/packages/builder/src/components/userInterface/ItemTab/Tab.svelte index 80f1733cc1..f811a56670 100644 --- a/packages/builder/src/components/userInterface/ItemTab/Tab.svelte +++ b/packages/builder/src/components/userInterface/ItemTab/Tab.svelte @@ -3,7 +3,6 @@ const dispatch = createEventDispatcher() import Item from "./Item.svelte" - import { store } from "builderStore" export let list let category = list diff --git a/packages/builder/src/components/userInterface/ModelSelect.svelte b/packages/builder/src/components/userInterface/ModelSelect.svelte new file mode 100644 index 0000000000..44fa2e51e0 --- /dev/null +++ b/packages/builder/src/components/userInterface/ModelSelect.svelte @@ -0,0 +1,16 @@ + + +
+
+ +
+
diff --git a/packages/builder/src/components/userInterface/OptionSelect.svelte b/packages/builder/src/components/userInterface/OptionSelect.svelte index 4fbedde4ac..8a1e3685f6 100644 --- a/packages/builder/src/components/userInterface/OptionSelect.svelte +++ b/packages/builder/src/components/userInterface/OptionSelect.svelte @@ -1,36 +1,223 @@ - +
toggleSelect(!open)}> +
+ {displayLabel} + +
+
+
    + {#if isOptionsObject} + {#each options as { value: v, label }} +
  • + {label} +
  • + {/each} + {:else} + {#each options as v} +
  • + {v} +
  • + {/each} + {/if} +
+
+
+{#if open} +
toggleSelect(false)} class="overlay" /> +{/if} + + diff --git a/packages/builder/src/components/userInterface/OptionSelectOld.svelte b/packages/builder/src/components/userInterface/OptionSelectOld.svelte new file mode 100644 index 0000000000..db3c19de35 --- /dev/null +++ b/packages/builder/src/components/userInterface/OptionSelectOld.svelte @@ -0,0 +1,33 @@ + + + diff --git a/packages/builder/src/components/userInterface/PageLayout.svelte b/packages/builder/src/components/userInterface/PageLayout.svelte index 2a532f9624..c2ad4b590b 100644 --- a/packages/builder/src/components/userInterface/PageLayout.svelte +++ b/packages/builder/src/components/userInterface/PageLayout.svelte @@ -34,86 +34,51 @@ title: lastPartOfName(layout), } - const confirmDeleteComponent = async component => { - componentToDelete = component - confirmDeleteDialog.show() - } - const setCurrentScreenToLayout = () => { store.setScreenType("page") $goto("./:page/page-layout") } -
-
Page Layout
-
- - - - - - - - - Page Layout -
- - {#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children} - - {/if} +
+ + + + + Master Screen
- store.deleteComponent(componentToDelete)} /> +{#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children} + +{/if} diff --git a/packages/builder/src/components/userInterface/PropertyCascader/PropertyCascader.svelte b/packages/builder/src/components/userInterface/PropertyCascader/PropertyCascader.svelte deleted file mode 100644 index dba1001f8f..0000000000 --- a/packages/builder/src/components/userInterface/PropertyCascader/PropertyCascader.svelte +++ /dev/null @@ -1,121 +0,0 @@ - - -
-
- { - setBindingFallback(e.target.value) - onChanged(e.target.value) - }} /> - -
- {#if isOpen} - { - onChanged(option) - isOpen = false - }} /> - {/if} -
- - diff --git a/packages/builder/src/components/userInterface/PropertyCascader/StateBindingOptions.svelte b/packages/builder/src/components/userInterface/PropertyCascader/StateBindingOptions.svelte deleted file mode 100644 index 06f8289a34..0000000000 --- a/packages/builder/src/components/userInterface/PropertyCascader/StateBindingOptions.svelte +++ /dev/null @@ -1,63 +0,0 @@ - - -
    - {#each options as option} -
  • onSelect(`${option.name}.`)}> - {option.name} - {option.description} -
  • - {/each} -
- - diff --git a/packages/builder/src/components/userInterface/PropertyCascader/index.js b/packages/builder/src/components/userInterface/PropertyCascader/index.js deleted file mode 100644 index 2a2573830a..0000000000 --- a/packages/builder/src/components/userInterface/PropertyCascader/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./PropertyCascader.svelte" diff --git a/packages/builder/src/components/userInterface/PropertyControl.svelte b/packages/builder/src/components/userInterface/PropertyControl.svelte index 82acb8b3f9..a7ea35c014 100644 --- a/packages/builder/src/components/userInterface/PropertyControl.svelte +++ b/packages/builder/src/components/userInterface/PropertyControl.svelte @@ -50,6 +50,9 @@ .label { flex: 0 0 50px; + display: flex; + align-items: center; + padding: 0px 5px; font-size: 12px; font-weight: 400; text-align: left; @@ -60,6 +63,7 @@ .control { flex: 1; + display: flex; padding-left: 2px; max-width: 164px; } diff --git a/packages/builder/src/components/userInterface/PropertyGroup.svelte b/packages/builder/src/components/userInterface/PropertyGroup.svelte index 6d0cf63ab0..b181b406a7 100644 --- a/packages/builder/src/components/userInterface/PropertyGroup.svelte +++ b/packages/builder/src/components/userInterface/PropertyGroup.svelte @@ -1,6 +1,7 @@ -
-
(show = !show)}> -
- -
-
{capitalize(name)}
-
-
- - {#each properties as props} - onStyleChanged(styleCategory, key, value)} - props={{ ...excludeProps(props, ['control', 'label']) }} /> - {/each} -
-
- - + + {#each properties as props} + onStyleChanged(styleCategory, key, value)} + props={{ ...excludeProps(props, ['control', 'label']) }} /> + {/each} + diff --git a/packages/builder/src/components/userInterface/SettingsView.svelte b/packages/builder/src/components/userInterface/SettingsView.svelte index 36b2ab9c75..17e4fe5e71 100644 --- a/packages/builder/src/components/userInterface/SettingsView.svelte +++ b/packages/builder/src/components/userInterface/SettingsView.svelte @@ -2,21 +2,61 @@ import PropertyControl from "./PropertyControl.svelte" import InputGroup from "../common/Inputs/InputGroup.svelte" import Colorpicker from "../common/Colorpicker.svelte" + import { goto } from "@sveltech/routify" import { excludeProps } from "./propertyCategories.js" + import Input from "../common/Input.svelte" export let panelDefinition = [] export let componentDefinition = {} export let componentInstance = {} export let onChange = () => {} + export let onScreenPropChange = () => {} + export let screenOrPageInstance const propExistsOnComponentDef = prop => prop in componentDefinition.props function handleChange(key, data) { data.target ? onChange(key, data.target.value) : onChange(key, data) } + + function handleScreenPropChange (name, value) { + onScreenPropChange(name,value) + if(!isPage && name === "name") { + // screen name is changed... change URL + $goto(`./:page/${value}`) + } + } + + const screenDefinition = [ + { key: "name", label: "Name", control: Input }, + { key: "description", label: "Description", control: Input }, + { key: "route", label: "Route", control: Input }, + ] + + const pageDefinition = [ + { key: "title", label: "Title", control: Input }, + { key: "favicon", label: "Favicon", control: Input }, + ] + + $: isPage = screenOrPageInstance && screenOrPageInstance.favicon + $: screenOrPageDefinition = isPage ? pageDefinition : screenDefinition + -{#if panelDefinition.length > 0} +{#if screenOrPageInstance} + {#each screenOrPageDefinition as def} + + {/each} +
+{/if} + +{#if panelDefinition && panelDefinition.length > 0} {#each panelDefinition as definition} {#if propExistsOnComponentDef(definition.key)} - {:else} - {/if}
diff --git a/packages/builder/src/components/userInterface/UserInterfaceRoot.svelte b/packages/builder/src/components/userInterface/UserInterfaceRoot.svelte index 8b600047d8..d2924675e6 100644 --- a/packages/builder/src/components/userInterface/UserInterfaceRoot.svelte +++ b/packages/builder/src/components/userInterface/UserInterfaceRoot.svelte @@ -27,11 +27,6 @@ settingsView.show() } - const confirmDeleteComponent = component => { - componentToDelete = component - confirmDeleteDialog.show() - } - const lastPartOfName = c => (c ? last(c.split("/")) : "") @@ -86,13 +81,6 @@ - store.deleteComponent(componentToDelete)} /> - diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ComponentSelector.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ComponentSelector.svelte new file mode 100644 index 0000000000..bcf5508643 --- /dev/null +++ b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ComponentSelector.svelte @@ -0,0 +1,45 @@ + + +
+ +
+ +
+ {#if components.length > 0} + +
+ +
+ {/if} +
diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ModelSelector.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ModelSelector.svelte new file mode 100644 index 0000000000..cab1b68ecf --- /dev/null +++ b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ModelSelector.svelte @@ -0,0 +1,16 @@ + + +
+
+ +
+
diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/RecordSelector.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/RecordSelector.svelte new file mode 100644 index 0000000000..125ef2436e --- /dev/null +++ b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/RecordSelector.svelte @@ -0,0 +1,33 @@ + + +
+
+ +
+
+ +{#if value.model} +
+ + {#each Object.keys(value.model.schema) as field} +
+ + +
+ {/each} +
+{/if} + + diff --git a/packages/builder/src/components/workflow/SetupPanel/SetupPanel.svelte b/packages/builder/src/components/workflow/SetupPanel/SetupPanel.svelte new file mode 100644 index 0000000000..456505a4a8 --- /dev/null +++ b/packages/builder/src/components/workflow/SetupPanel/SetupPanel.svelte @@ -0,0 +1,294 @@ + + +
+
+ { + selectedTab = 'SETUP' + testResult = null + }}> + Setup + + {#if !workflowBlock} + (selectedTab = 'TEST')}> + Test + + {/if} +
+ {#if selectedTab === 'TEST'} +
+ {#if testResult} + + {/if} + +
+ {/if} + {#if selectedTab === 'SETUP'} + {#if workflowBlock} + +
+ + +
+ {:else if $workflowStore.currentWorkflow} +
+
+
Workflow: {workflow.name}
+
+ +
+ +
+
+
+ +
+ {#each ACCESS_LEVELS as { name, key }} + + + + + {/each} +
+
+
+
+ +
+
+ {/if} + {/if} +
+ + diff --git a/packages/builder/src/components/workflow/SetupPanel/WorkflowBlockSetup.svelte b/packages/builder/src/components/workflow/SetupPanel/WorkflowBlockSetup.svelte new file mode 100644 index 0000000000..42ebb307fa --- /dev/null +++ b/packages/builder/src/components/workflow/SetupPanel/WorkflowBlockSetup.svelte @@ -0,0 +1,97 @@ + + + +{#each workflowParams as [parameter, type]} +
+ +
+ {#if Array.isArray(type)} + + {:else if type === 'component'} + + {:else if type === 'accessLevel'} + + {:else if type === 'password'} + + {:else if type === 'number'} + + {:else if type === 'longText'} +