diff --git a/packages/builder/package.json b/packages/builder/package.json index 5cd18c3955..7e4e7b9cab 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@beyonk/svelte-notifications": "^2.0.3", + "@budibase/bbui": "^0.3.5", "@budibase/client": "^0.0.32", "@nx-js/compiler-util": "^2.0.0", "codemirror": "^5.51.0", @@ -83,7 +84,7 @@ "rollup-plugin-svelte": "^5.0.3", "rollup-plugin-terser": "^4.0.4", "rollup-plugin-url": "^2.2.2", - "svelte": "^3.0.0" + "svelte": "3.23.x" }, "gitHead": "115189f72a850bfb52b65ec61d932531bf327072" } diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte index 72d287e9cb..b19b8df5c7 100644 --- a/packages/builder/src/App.svelte +++ b/packages/builder/src/App.svelte @@ -30,6 +30,4 @@ - - - + diff --git a/packages/builder/src/budibase.css b/packages/builder/src/budibase.css index 2117926dd2..283a761c3b 100644 --- a/packages/builder/src/budibase.css +++ b/packages/builder/src/budibase.css @@ -57,23 +57,23 @@ .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 { diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js index 2009737254..87694d846f 100644 --- a/packages/builder/src/builderStore/store/index.js +++ b/packages/builder/src/builderStore/store/index.js @@ -1,4 +1,4 @@ -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" @@ -16,6 +16,14 @@ 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, +} from "../storeUtils" export const getStore = () => { const initial = { @@ -57,10 +65,6 @@ export const getStore = () => { 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 +73,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 +147,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] @@ -276,14 +277,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 }, - screens: page._screens, - }) -} - const setCurrentPage = store => pageName => { store.update(state => { const current_screens = state.pages[pageName]._screens @@ -315,8 +308,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 @@ -342,9 +333,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] : {} @@ -398,12 +387,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) }) } @@ -470,75 +454,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) @@ -570,39 +485,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/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js new file mode 100644 index 0000000000..68e20e56b3 --- /dev/null +++ b/packages/builder/src/builderStore/storeUtils.js @@ -0,0 +1,59 @@ +import { makePropsSafe } from "components/userInterface/pagesParsing/createProps" +import api from "./api" + +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 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) + } + } +} diff --git a/packages/builder/src/components/common/AppNotification.svelte b/packages/builder/src/components/common/AppNotification.svelte index e4a43247c9..2e8723ab53 100644 --- a/packages/builder/src/components/common/AppNotification.svelte +++ b/packages/builder/src/components/common/AppNotification.svelte @@ -5,7 +5,7 @@ UIKit.notification({ message: `
- +
🤯
${message} @@ -21,6 +21,7 @@ diff --git a/packages/builder/src/components/common/Icons/Close.svelte b/packages/builder/src/components/common/Icons/Close.svelte new file mode 100644 index 0000000000..b6cacd076c --- /dev/null +++ b/packages/builder/src/components/common/Icons/Close.svelte @@ -0,0 +1,10 @@ + + + + diff --git a/packages/builder/src/components/common/Icons/Info.svelte b/packages/builder/src/components/common/Icons/Info.svelte new file mode 100644 index 0000000000..ea9779858d --- /dev/null +++ b/packages/builder/src/components/common/Icons/Info.svelte @@ -0,0 +1,6 @@ + + + + diff --git a/packages/builder/src/components/common/Icons/More.svelte b/packages/builder/src/components/common/Icons/More.svelte new file mode 100644 index 0000000000..1ec4139eae --- /dev/null +++ b/packages/builder/src/components/common/Icons/More.svelte @@ -0,0 +1,12 @@ + + + + diff --git a/packages/builder/src/components/common/Icons/index.js b/packages/builder/src/components/common/Icons/index.js index c7e4da29da..4e4b41082d 100644 --- a/packages/builder/src/components/common/Icons/index.js +++ b/packages/builder/src/components/common/Icons/index.js @@ -29,3 +29,6 @@ export { default as ContributionIcon } from "./Contribution.svelte" export { default as BugIcon } from "./Bug.svelte" export { default as EmailIcon } from "./Email.svelte" export { default as TwitterIcon } from "./Twitter.svelte" +export { default as InfoIcon } from "./Info.svelte" +export { default as CloseIcon } from "./Close.svelte" +export { default as MoreIcon } from "./More.svelte" diff --git a/packages/builder/src/components/common/Switcher.svelte b/packages/builder/src/components/common/Switcher.svelte new file mode 100644 index 0000000000..b1c84e6eac --- /dev/null +++ b/packages/builder/src/components/common/Switcher.svelte @@ -0,0 +1,69 @@ + + +
+ +
+ + {#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/database/ModelDataTable/api.js b/packages/builder/src/components/database/ModelDataTable/api.js index e9592074bf..cb98879567 100644 --- a/packages/builder/src/components/database/ModelDataTable/api.js +++ b/packages/builder/src/components/database/ModelDataTable/api.js @@ -15,13 +15,13 @@ export async function createDatabase(appname, instanceName) { } export async function deleteRecord(record, instanceId) { - const DELETE_RECORDS_URL = `/api/${instanceId}/${record.modelId}/records/${record._id}/${record._rev}` + const DELETE_RECORDS_URL = `/api/${instanceId}/${record._modelId}/records/${record._id}/${record._rev}` const response = await api.delete(DELETE_RECORDS_URL) return response } -export async function saveRecord(record, instanceId) { - const SAVE_RECORDS_URL = `/api/${instanceId}/${record.modelId}/records` +export async function saveRecord(record, instanceId, modelId) { + const SAVE_RECORDS_URL = `/api/${instanceId}/${modelId}/records` const response = await api.post(SAVE_RECORDS_URL, record) return await response.json() 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..46f8bd9189 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,32 +56,26 @@
- + - {#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}
diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte index d957a99f02..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,24 +24,38 @@ 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( { ...record, modelId: $backendUiStore.selectedModel._id, }, - instanceId + instanceId, + $backendUiStore.selectedModel._id ) if (recordResponse.errors) { - errors = recordResponse.errors + errors = Object.keys(recordResponse.errors) + .map(k => ({ dataPath: k, message: recordResponse.errors[k] })) + .flat() return } @@ -64,8 +74,8 @@ {#each modelSchema as [key, meta]}
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 8a7fd6bb90..173e92d98b 100644 --- a/packages/builder/src/components/nav/BackendNav.svelte +++ b/packages/builder/src/components/nav/BackendNav.svelte @@ -45,7 +45,6 @@
-
{#if $backendUiStore.selectedDatabase._id}
diff --git a/packages/builder/src/components/nav/SchemaManagementDrawer.svelte b/packages/builder/src/components/nav/SchemaManagementDrawer.svelte index cc9e5d86a7..5fb6ed6c25 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 = `all_${model._id}` + state.selectedView = `${model._id}` return state }) } diff --git a/packages/builder/src/components/start/AppCard.svelte b/packages/builder/src/components/start/AppCard.svelte new file mode 100644 index 0000000000..2608bfa370 --- /dev/null +++ b/packages/builder/src/components/start/AppCard.svelte @@ -0,0 +1,68 @@ + + +
+

{name}

+

{description}

+ +
+ + diff --git a/packages/builder/src/components/start/AppList.svelte b/packages/builder/src/components/start/AppList.svelte index 22ba16c45d..a0eacbc470 100644 --- a/packages/builder/src/components/start/AppList.svelte +++ b/packages/builder/src/components/start/AppList.svelte @@ -1,5 +1,5 @@ + +
+
+
+ + + +

Create new web app

+
+ (name = e.target.value)} + on:input={e => (name = e.target.value)} /> + {#if error.name} + You need to enter a name for your application. + {/if} +