diff --git a/packages/builder/package.json b/packages/builder/package.json index 81e240d148..8e426deb45 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -51,7 +51,7 @@ "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": { diff --git a/packages/builder/src/budibase.css b/packages/builder/src/budibase.css index f433b016da..0d74ed7221 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/generate_css.js b/packages/builder/src/builderStore/generate_css.js index cd284994f9..5d811c6f37 100644 --- a/packages/builder/src/builderStore/generate_css.js +++ b/packages/builder/src/builderStore/generate_css.js @@ -1,42 +1,41 @@ -export const generate_screen_css = (component_arr) => { - let styles = ''; - for (const { _styles, _id, _children, _component } of component_arr) { - let [ componentName ] = _component.match(/[a-z]*$/); - Object.keys(_styles).forEach((selector) => { - const cssString = generate_css(_styles[selector]); - if (cssString) { - styles += apply_class(_id, componentName, cssString, selector); - } - }); - if (_children && _children.length) { - styles += generate_screen_css(_children) + '\n'; - } - } - return styles.trim(); -}; +export const generate_screen_css = component_arr => { + let styles = "" + for (const { _styles, _id, _children, _component } of component_arr) { + let [componentName] = _component.match(/[a-z]*$/) + Object.keys(_styles).forEach(selector => { + const cssString = generate_css(_styles[selector]) + if (cssString) { + styles += apply_class(_id, componentName, cssString, selector) + } + }) + if (_children && _children.length) { + styles += generate_screen_css(_children) + "\n" + } + } + return styles.trim() +} -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.join(' ')};\n`); - } - } - }, ''); +export const generate_css = style => { + let cssString = Object.entries(style).reduce((str, [key, value]) => { + 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.join(" ")};\n`) + } + } + }, "") - return (cssString || '').trim(); -}; + return (cssString || "").trim() +} -export const apply_class = (id, name = 'element', styles, selector) => { - if (selector === 'normal') { - return `.${name}-${id} {\n${styles}\n}`; - } else { - let sel = selector === 'selected' ? '::selection' : `:${selector}`; - return `.${name}-${id}${sel} {\n${styles}\n}`; - } -}; +export const apply_class = (id, name = "element", styles, selector) => { + if (selector === "normal") { + return `.${name}-${id} {\n${styles}\n}` + } else { + let sel = selector === "selected" ? "::selection" : `:${selector}` + return `.${name}-${id}${sel} {\n${styles}\n}` + } +} diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js index 2943667033..9ac06a0a0d 100644 --- a/packages/builder/src/builderStore/store/index.js +++ b/packages/builder/src/builderStore/store/index.js @@ -1,107 +1,123 @@ -import { cloneDeep, 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, getBuiltin } from 'components/userInterface/pagesParsing/createProps'; -import { fetchComponentLibDefinitions } from '../loadComponentLibraries'; -import { buildCodeForScreens } from '../buildCodeForScreens'; -import { generate_screen_css } from '../generate_css'; -import { insertCodeMetadata } from '../insertCodeMetadata'; -import { uuid } from '../uuid'; + +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, + getBuiltin, +} from "components/userInterface/pagesParsing/createProps" +import { fetchComponentLibDefinitions } from "../loadComponentLibraries" +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 = { - apps: [], - appname: '', - pages: DEFAULT_PAGES_OBJECT, - mainUi: {}, - unauthenticatedUi: {}, - components: [], - currentPreviewItem: null, - currentComponentInfo: null, - currentFrontEndType: 'none', - currentPageName: '', - currentComponentProps: null, - errors: [], - hasAppPackage: false, - libraries: null, - appId: '' - }; + const initial = { + apps: [], + appname: "", + pages: DEFAULT_PAGES_OBJECT, + mainUi: {}, + unauthenticatedUi: {}, + components: [], + currentPreviewItem: null, + currentComponentInfo: null, + currentFrontEndType: "none", + currentPageName: "", + currentComponentProps: null, + errors: [], + hasAppPackage: false, + libraries: null, + appId: "", + } - const store = writable(initial); + const store = writable(initial) - store.setPackage = setPackage(store, initial); + store.setPackage = setPackage(store, initial) - store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store); + 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); - store.createScreen = createScreen(store); - store.addStylesheet = addStylesheet(store); - store.removeStylesheet = removeStylesheet(store); - store.savePage = savePage(store); - store.addChildComponent = addChildComponent(store); - store.selectComponent = selectComponent(store); - store.setComponentProp = setComponentProp(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); - return store; -}; + store.saveScreen = saveScreen(store) + store.renameScreen = renameScreen(store) + store.deleteScreen = deleteScreen(store) + store.setCurrentScreen = setCurrentScreen(store) + store.setCurrentPage = setCurrentPage(store) + store.createScreen = createScreen(store) + store.addStylesheet = addStylesheet(store) + store.removeStylesheet = removeStylesheet(store) + store.savePage = savePage(store) + store.addChildComponent = addChildComponent(store) + store.selectComponent = selectComponent(store) + store.setComponentProp = setComponentProp(store) + store.setComponentStyle = setComponentStyle(store) + store.setComponentCode = setComponentCode(store) + store.setScreenType = setScreenType(store) + store.getPathToComponent = getPathToComponent(store) + store.addTemplatedComponent = addTemplatedComponent(store) + store.setMetadataProp = setMetadataProp(store) + return store +} -export default getStore; +export default getStore -const setPackage = (store, initial) => async (pkg) => { - const [ main_screens, unauth_screens ] = await Promise.all([ - api.get(`/_builder/api/${pkg.application._id}/pages/main/screens`).then((r) => r.json()), - api.get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`).then((r) => r.json()) - ]); +export const getComponentDefinition = (state, name) => + name.startsWith("##") ? getBuiltin(name) : state.components[name] - pkg.pages = { - main: { - ...pkg.pages.main, - _screens: Object.values(main_screens) - }, - unauthenticated: { - ...pkg.pages.unauthenticated, - _screens: Object.values(unauth_screens) - } - }; +const setPackage = (store, initial) => async pkg => { + const [main_screens, unauth_screens] = await Promise.all([ + api + .get(`/_builder/api/${pkg.application._id}/pages/main/screens`) + .then(r => r.json()), + api + .get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`) + .then(r => r.json()), + ]) - initial.libraries = pkg.application.componentLibraries; - initial.components = await fetchComponentLibDefinitions(pkg.application._id); - initial.appname = pkg.application.name; - initial.appId = pkg.application._id; - initial.pages = pkg.pages; - initial.hasAppPackage = true; - initial.screens = values(pkg.screens); - initial.builtins = [ getBuiltin('##builtin/screenslot') ]; - initial.appInstances = pkg.application.instances; - initial.appId = pkg.application._id; + pkg.pages = { + main: { + ...pkg.pages.main, + _screens: Object.values(main_screens), + }, + unauthenticated: { + ...pkg.pages.unauthenticated, + _screens: Object.values(unauth_screens), + }, + } - store.set(initial); - return initial; -}; + initial.libraries = pkg.application.componentLibraries + initial.components = await fetchComponentLibDefinitions(pkg.application._id) + initial.appname = pkg.application.name + initial.appId = pkg.application._id + initial.pages = pkg.pages + initial.hasAppPackage = true + initial.screens = values(pkg.screens) + initial.builtins = [getBuiltin("##builtin/screenslot")] + initial.appInstances = pkg.application.instances + initial.appId = pkg.application._id -const saveScreen = (store) => (screen) => { - store.update((state) => { - return _saveScreen(store, state, screen); - }); -}; + store.set(initial) + return initial +} + +const saveScreen = store => screen => { + store.update(state => { + return _saveScreen(store, state, screen) + }) +} const _saveScreen = async (store, s, screen) => { const currentPageScreens = s.pages[s.currentPageName]._screens; @@ -127,430 +143,349 @@ 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] -const createScreen = (store) => (screenName, route, layoutComponentName) => { - store.update((state) => { - const rootComponent = state.components[layoutComponentName]; + const newScreen = { + name: screenName || "", + description: "", + url: "", + _css: "", + uiFunctions: "", + props: createProps(rootComponent).props, + } - const newScreen = { - name: screenName || '', - description: '', - url: '', - _css: '', - uiFunctions: '', - props: createProps(rootComponent).props - }; + newScreen.route = route + state.currentPreviewItem = newScreen + state.currentComponentInfo = newScreen.props + state.currentFrontEndType = "screen" - newScreen.route = route; - state.currentPreviewItem = newScreen; - state.currentComponentInfo = newScreen.props; - state.currentFrontEndType = 'screen'; + _saveScreen(store, state, newScreen) - _saveScreen(store, state, newScreen); + return state + }) +} - return state; - }); -}; +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" -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'; + const safeProps = makePropsSafe( + s.components[screen.props._component], + screen.props + ) + screen.props = safeProps + s.currentComponentInfo = safeProps + setCurrentPageFunctions(s) + return s + }) +} - const safeProps = makePropsSafe(s.components[screen.props._component], screen.props); - screen.props = safeProps; - s.currentComponentInfo = safeProps; - setCurrentPageFunctions(s); - return s; - }); -}; +const deleteScreen = store => name => { + store.update(s => { + const components = s.components.filter(c => c.name !== name) + const screens = s.screens.filter(c => c.name !== name) -const deleteScreen = (store) => (name) => { - store.update((s) => { - const components = s.components.filter((c) => c.name !== name); - const screens = s.screens.filter((c) => c.name !== name); + s.components = components + s.screens = screens + if (s.currentPreviewItem.name === name) { + s.currentPreviewItem = null + s.currentFrontEndType = "" + } - s.components = components; - s.screens = screens; - if (s.currentPreviewItem.name === name) { - s.currentPreviewItem = null; - s.currentFrontEndType = ''; - } + api.delete(`/_builder/api/${s.appId}/screen/${name}`) - api.delete(`/_builder/api/${s.appId}/screen/${name}`); + return s + }) +} - return s; - }); -}; +const renameScreen = store => (oldname, newname) => { + store.update(s => { + const { screens, pages, error, changedScreens } = rename( + s.pages, + s.screens, + oldname, + newname + ) -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 + } - 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 - 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) + } + } - 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) + }) - api - .patch(`/_builder/api/${s.appId}/screen`, { - oldname, - newname - }) - .then(() => saveAllChanged()) - .then(() => { - _savePage(s); - }); + return s + }) +} - return s; - }); -}; +const savePage = store => async page => { + store.update(state => { + if (state.currentFrontEndType !== "page" || !state.currentPageName) { + return state + } -const savePage = (store) => async (page) => { - store.update((state) => { - if (state.currentFrontEndType !== 'page' || !state.currentPageName) { - return state; - } + state.pages[state.currentPageName] = page + _savePage(state) + return state + }) +} - state.pages[state.currentPageName] = page; - _savePage(state); - return state; - }); -}; +const addStylesheet = store => stylesheet => { + store.update(s => { + s.pages.stylesheets.push(stylesheet) + _savePage(s) + return s + }) +} -const addStylesheet = (store) => (stylesheet) => { - store.update((s) => { - s.pages.stylesheets.push(stylesheet); - _savePage(s); - return s; - }); -}; +const removeStylesheet = store => stylesheet => { + store.update(state => { + state.pages.stylesheets = state.pages.stylesheets.filter( + s => s !== stylesheet + ) + _savePage(state) + return state + }) +} -const removeStylesheet = (store) => (stylesheet) => { - store.update((state) => { - state.pages.stylesheets = state.pages.stylesheets.filter((s) => s !== stylesheet); - _savePage(state); - return state; - }); -}; +const setCurrentPage = store => pageName => { + store.update(state => { + const current_screens = state.pages[pageName]._screens -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 currentPage = state.pages[pageName] -const setCurrentPage = (store) => (pageName) => { - store.update((state) => { - const current_screens = state.pages[pageName]._screens; + state.currentFrontEndType = "page" + state.currentPageName = pageName + state.screens = Array.isArray(current_screens) + ? current_screens + : Object.values(current_screens) + const safeProps = makePropsSafe( + state.components[currentPage.props._component], + currentPage.props + ) + state.currentComponentInfo = safeProps + currentPage.props = safeProps + state.currentPreviewItem = state.pages[pageName] + state.currentPreviewItem._css = generate_screen_css([ + state.currentPreviewItem.props, + ]) - const currentPage = state.pages[pageName]; + for (let screen of state.screens) { + screen._css = generate_screen_css([screen.props]) + } - state.currentFrontEndType = 'page'; - state.currentPageName = pageName; - state.screens = Array.isArray(current_screens) ? current_screens : Object.values(current_screens); - const safeProps = makePropsSafe(state.components[currentPage.props._component], currentPage.props); - state.currentComponentInfo = safeProps; - currentPage.props = safeProps; - state.currentPreviewItem = state.pages[pageName]; - state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem.props ]); - - for (let screen of state.screens) { - screen._css = generate_screen_css([ screen.props ]); - } - - setCurrentPageFunctions(state); - return state; - }); -}; - -// const getComponentDefinition = (components, name) => components.find(c => c.name === name) + setCurrentPageFunctions(state) + return state + }) +} /** * @param {string} componentToAdd - name of the component to add to the application * @param {string} presetName - name of the component preset if defined */ -const addChildComponent = (store) => (componentToAdd, presetName) => { - store.update((state) => { - function findSlot(component_array) { - for (let i = 0; i < component_array.length; i += 1) { - if (component_array[i]._component === '##builtin/screenslot') { - return true; - } +const addChildComponent = store => (componentToAdd, presetName) => { + store.update(state => { + function findSlot(component_array) { + for (let i = 0; i < component_array.length; i += 1) { + if (component_array[i]._component === "##builtin/screenslot") { + return true + } - if (component_array[i]._children) findSlot(component_array[i]); - } + if (component_array[i]._children) findSlot(component_array[i]) + } - return false; - } + return false + } - if (componentToAdd.startsWith('##') && findSlot(state.pages[state.currentPageName].props._children)) { - return state; - } + if ( + componentToAdd.startsWith("##") && + findSlot(state.pages[state.currentPageName].props._children) + ) { + return state + } - const component = componentToAdd.startsWith('##') - ? getBuiltin(componentToAdd) - : state.components[componentToAdd]; + const component = getComponentDefinition(state, componentToAdd) - const presetProps = presetName ? component.presets[presetName] : {}; + const presetProps = presetName ? component.presets[presetName] : {} - const instanceId = get(backendUiStore).selectedDatabase._id; + const instanceId = get(backendUiStore).selectedDatabase._id - const newComponent = createProps( - component, - { - ...presetProps, - _instanceId: instanceId - }, - state - ); + const newComponent = createProps( + component, + { + ...presetProps, + _instanceId: instanceId, + }, + state + ) - state.currentComponentInfo._children = state.currentComponentInfo._children.concat(newComponent.props); + state.currentComponentInfo._children = state.currentComponentInfo._children.concat( + newComponent.props + ) - state.currentFrontEndType === 'page' ? _savePage(state) : _saveScreenApi(state.currentPreviewItem, state); + state.currentFrontEndType === "page" + ? _savePage(state) + : _saveScreenApi(state.currentPreviewItem, state) - state.currentView = 'component'; - state.currentComponentInfo = newComponent.props; + state.currentView = "component" + state.currentComponentInfo = newComponent.props - return state; - }); -}; + return state + }) +} /** * @param {string} props - props to add, as child of current component */ -const addTemplatedComponent = (store) => (props) => { - store.update((state) => { - walkProps(props, (p) => { - p._id = uuid(); - }); - state.currentComponentInfo._children = state.currentComponentInfo._children.concat(props); - state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem.props ]); - setCurrentPageFunctions(state); - _saveCurrentPreviewItem(state); +const addTemplatedComponent = store => props => { + store.update(state => { + walkProps(props, p => { + p._id = uuid() + }) + state.currentComponentInfo._children = state.currentComponentInfo._children.concat( + props + ) + state.currentPreviewItem._css = generate_screen_css([ + state.currentPreviewItem.props, + ]) - return state; - }); -}; + setCurrentPageFunctions(state) + _saveCurrentPreviewItem(state) -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 state + }) +} -const setComponentProp = (store) => (name, value) => { - store.update((state) => { - const current_component = state.currentComponentInfo; - state.currentComponentInfo[name] = value; +const selectComponent = store => component => { + store.update(state => { + return _selectComponent(state, component) + }) +} - _saveCurrentPreviewItem(state); +const setComponentProp = store => (name, value) => { + store.update(state => { + const current_component = state.currentComponentInfo + state.currentComponentInfo[name] = value - state.currentComponentInfo = current_component; - return state; - }); -}; + _saveCurrentPreviewItem(state) -const setComponentStyle = (store) => (type, name, value) => { - store.update((state) => { - if (!state.currentComponentInfo._styles) { - state.currentComponentInfo._styles = {}; - } - state.currentComponentInfo._styles[type][name] = value; + state.currentComponentInfo = current_component + return state + }) +} - state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem.props ]); +const setComponentStyle = store => (type, name, value) => { + store.update(state => { + if (!state.currentComponentInfo._styles) { + state.currentComponentInfo._styles = {} + } + state.currentComponentInfo._styles[type][name] = value - // save without messing with the store - _saveCurrentPreviewItem(state); - return state; - }); -}; + state.currentPreviewItem._css = generate_screen_css([ + state.currentPreviewItem.props, + ]) -const setComponentCode = (store) => (code) => { - store.update((state) => { - state.currentComponentInfo._code = code; + // save without messing with the store + _saveCurrentPreviewItem(state) + return state + }) +} - setCurrentPageFunctions(state); - // save without messing with the store - _saveScreenApi(state.currentPreviewItem, state); +const setComponentCode = store => code => { + store.update(state => { + state.currentComponentInfo._code = code - return state; - }); -}; + setCurrentPageFunctions(state) + // save without messing with the store + _saveScreenApi(state.currentPreviewItem, state) -const setCurrentPageFunctions = (s) => { - s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName]); - insertCodeMetadata(s.currentPreviewItem.props); -}; + return state + }) +} -const buildPageCode = (screens, page) => buildCodeForScreens([ page, ...screens ]); +const setCurrentPageFunctions = s => { + s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName]) + insertCodeMetadata(s.currentPreviewItem.props) +} -const setScreenType = (store) => (type) => { - store.update((state) => { - state.currentFrontEndType = type; +const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens]) - const pageOrScreen = - type === 'page' ? state.pages[state.currentPageName] : state.pages[state.currentPageName]._screens[0]; +const setScreenType = store => type => { + store.update(state => { + state.currentFrontEndType = type - state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null; - state.currentPreviewItem = pageOrScreen; - return state; - }); -}; + const pageOrScreen = + type === "page" + ? state.pages[state.currentPageName] + : state.pages[state.currentPageName]._screens[0] -const deleteComponent = (store) => (componentName) => { - store.update((state) => { - const parent = getParent(state.currentPreviewItem.props, componentName); + state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null + state.currentPreviewItem = pageOrScreen + return state + }) +} - if (parent) { - parent._children = parent._children.filter((component) => component !== componentName); - } +const getPathToComponent = store => component => { + // Gets all the components to needed to construct a path. + const tempStore = get(store) + let pathComponents = [] + let parent = component + let root = false + while (!root) { + parent = getParent(tempStore.currentPreviewItem.props, parent) + if (!parent) { + root = true + } else { + pathComponents.push(parent) + } + } - _saveCurrentPreviewItem(state); + // Remove root entry since it's the screen or page layout. + // Reverse array since we need the correct order of the IDs + const reversedComponents = pathComponents.reverse().slice(1) - return state; - }); -}; + // Add component + const allComponents = [...reversedComponents, component] -const moveUpComponent = (store) => (component) => { - store.update((s) => { - const parent = getParent(s.currentPreviewItem.props, component); + // Map IDs + const IdList = allComponents.map(c => c._id) - if (parent) { - const currentIndex = parent._children.indexOf(component); - if (currentIndex === 0) return s; + // Construct ID Path: + const path = IdList.join("/") - const newChildren = parent._children.filter((c) => c !== component); - newChildren.splice(currentIndex - 1, 0, component); - parent._children = newChildren; - } - s.currentComponentInfo = component; - _saveCurrentPreviewItem(s); + return path +} - 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); - let pathComponents = []; - let parent = component; - let root = false; - while (!root) { - parent = getParent(tempStore.currentPreviewItem.props, parent); - if (!parent) { - root = true; - } else { - pathComponents.push(parent); - } - } - - // Remove root entry since it's the screen or page layout. - // Reverse array since we need the correct order of the IDs - const reversedComponents = pathComponents.reverse().slice(1); - - // Add component - const allComponents = [ ...reversedComponents, component ]; - - // Map IDs - const IdList = allComponents.map((c) => c._id); - - // Construct ID Path: - const path = IdList.join('/'); - - 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); +const setMetadataProp = store => (name, prop) => { + store.update(s => { + s.currentPreviewItem[name] = prop + return 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/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 69b78421da..4e4b41082d 100644 --- a/packages/builder/src/components/common/Icons/index.js +++ b/packages/builder/src/components/common/Icons/index.js @@ -31,3 +31,4 @@ 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/Input.svelte b/packages/builder/src/components/common/Input.svelte index d53c0e9b1b..04272c1d76 100644 --- a/packages/builder/src/components/common/Input.svelte +++ b/packages/builder/src/components/common/Input.svelte @@ -15,7 +15,7 @@ diff --git a/packages/builder/src/components/common/Switcher.svelte b/packages/builder/src/components/common/Switcher.svelte new file mode 100644 index 0000000000..23ced65692 --- /dev/null +++ b/packages/builder/src/components/common/Switcher.svelte @@ -0,0 +1,77 @@ + + +
+ +
+ + {#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/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 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/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/userInterface/ComponentDropdownMenu.svelte b/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte new file mode 100644 index 0000000000..b7cb74fb3c --- /dev/null +++ b/packages/builder/src/components/userInterface/ComponentDropdownMenu.svelte @@ -0,0 +1,276 @@ + + +
{}}> + + +
+ + + + diff --git a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte index fb4f01578c..18e79ad96c 100644 --- a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte +++ b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte @@ -104,6 +104,12 @@ height: 100%; display: flex; flex-direction: column; + /* Merge Check */ + overflow-x: hidden; + overflow-y: hidden; + padding: 20px; + box-sizing: border-box; + /* Merge Check */ } .title > div:nth-child(1) { @@ -118,5 +124,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 @@ + + + + + + + + + + + + diff --git a/packages/builder/src/components/userInterface/ItemTab/Item.svelte b/packages/builder/src/components/userInterface/ItemTab/Item.svelte index d4576055c6..871501a20c 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 -
+
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/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/UserInterfaceRoot.svelte b/packages/builder/src/components/userInterface/UserInterfaceRoot.svelte index 68f3ff80da..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("/")) : "") @@ -42,7 +37,6 @@
-
- -
-