1
0
Fork 0
mirror of synced 2024-07-09 00:06:05 +12:00

Merge branch 'master' of https://github.com/Budibase/budibase into property-panel/components-from-design

This commit is contained in:
Conor_Mack 2020-06-02 10:51:57 +01:00
commit 98610c60ed
42 changed files with 1813 additions and 1311 deletions

View file

@ -51,7 +51,7 @@
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"shortid": "^2.2.8", "shortid": "^2.2.8",
"string_decoder": "^1.2.0", "string_decoder": "^1.2.0",
"svelte-simple-modal": "^0.3.0", "svelte-simple-modal": "^0.4.2",
"uikit": "^3.1.7" "uikit": "^3.1.7"
}, },
"devDependencies": { "devDependencies": {

View file

@ -57,23 +57,23 @@
.budibase__nav-item { .budibase__nav-item {
cursor: pointer; cursor: pointer;
padding: 0 7px 0 3px; padding: 0 4px 0 2px;
height: 35px; height: 35px;
margin: 5px 20px 5px 0px; margin: 5px 0px 4px 0px;
border-radius: 0 5px 5px 0; border-radius: 0 5px 5px 0;
display: flex; display: flex;
align-items: center; align-items: center;
font-weight: 500; font-size: 14px;
font-size: 13px; transition: 0.2s;
} }
.budibase__nav-item.selected { .budibase__nav-item.selected {
color: var(--button-text); color: var(--ink);
background: #f1f4fc; background: var(--blue-light);
} }
.budibase__nav-item:hover { .budibase__nav-item:hover {
background: #fafafa; background: var(--grey-light);
} }
.budibase__input { .budibase__input {

View file

@ -1,42 +1,41 @@
export const generate_screen_css = (component_arr) => { export const generate_screen_css = component_arr => {
let styles = ''; let styles = ""
for (const { _styles, _id, _children, _component } of component_arr) { for (const { _styles, _id, _children, _component } of component_arr) {
let [ componentName ] = _component.match(/[a-z]*$/); let [componentName] = _component.match(/[a-z]*$/)
Object.keys(_styles).forEach((selector) => { Object.keys(_styles).forEach(selector => {
const cssString = generate_css(_styles[selector]); const cssString = generate_css(_styles[selector])
if (cssString) { if (cssString) {
styles += apply_class(_id, componentName, cssString, selector); styles += apply_class(_id, componentName, cssString, selector)
} }
}); })
if (_children && _children.length) { if (_children && _children.length) {
styles += generate_screen_css(_children) + '\n'; styles += generate_screen_css(_children) + "\n"
} }
} }
return styles.trim(); return styles.trim()
}; }
export const generate_css = (style) => { export const generate_css = style => {
let cssString = Object.entries(style).reduce((str, [key, value]) => { let cssString = Object.entries(style).reduce((str, [key, value]) => {
//TODO Handle arrays and objects here also if (typeof value === "string") {
if (typeof value === 'string') {
if (value) { if (value) {
return (str += `${key}: ${value};\n`); return (str += `${key}: ${value};\n`)
} }
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
if (value.length > 0 && !value.every((v) => v === '')) { if (value.length > 0 && !value.every(v => v === "")) {
return (str += `${key}: ${value.join(' ')};\n`); return (str += `${key}: ${value.join(" ")};\n`)
} }
} }
}, ''); }, "")
return (cssString || '').trim(); return (cssString || "").trim()
}; }
export const apply_class = (id, name = 'element', styles, selector) => { export const apply_class = (id, name = "element", styles, selector) => {
if (selector === 'normal') { if (selector === "normal") {
return `.${name}-${id} {\n${styles}\n}`; return `.${name}-${id} {\n${styles}\n}`
} else { } else {
let sel = selector === 'selected' ? '::selection' : `:${selector}`; let sel = selector === "selected" ? "::selection" : `:${selector}`
return `.${name}-${id}${sel} {\n${styles}\n}`; return `.${name}-${id}${sel} {\n${styles}\n}`
}
} }
};

View file

@ -1,107 +1,123 @@
import { cloneDeep, values } from 'lodash/fp';
import { backendUiStore } from 'builderStore'; import { values } from "lodash/fp"
import * as backendStoreActions from './backend'; import { backendUiStore } from "builderStore"
import { writable, get } from 'svelte/store'; import * as backendStoreActions from "./backend"
import api from '../api'; import { writable, get } from "svelte/store"
import { DEFAULT_PAGES_OBJECT } from '../../constants'; import api from "../api"
import { getExactComponent } from 'components/userInterface/pagesParsing/searchComponents'; import { DEFAULT_PAGES_OBJECT } from "../../constants"
import { rename } from 'components/userInterface/pagesParsing/renameScreen'; import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
import { createProps, makePropsSafe, getBuiltin } from 'components/userInterface/pagesParsing/createProps'; import { rename } from "components/userInterface/pagesParsing/renameScreen"
import { fetchComponentLibDefinitions } from '../loadComponentLibraries'; import {
import { buildCodeForScreens } from '../buildCodeForScreens'; createProps,
import { generate_screen_css } from '../generate_css'; makePropsSafe,
import { insertCodeMetadata } from '../insertCodeMetadata'; getBuiltin,
import { uuid } from '../uuid'; } 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 = () => { export const getStore = () => {
const initial = { const initial = {
apps: [], apps: [],
appname: '', appname: "",
pages: DEFAULT_PAGES_OBJECT, pages: DEFAULT_PAGES_OBJECT,
mainUi: {}, mainUi: {},
unauthenticatedUi: {}, unauthenticatedUi: {},
components: [], components: [],
currentPreviewItem: null, currentPreviewItem: null,
currentComponentInfo: null, currentComponentInfo: null,
currentFrontEndType: 'none', currentFrontEndType: "none",
currentPageName: '', currentPageName: "",
currentComponentProps: null, currentComponentProps: null,
errors: [], errors: [],
hasAppPackage: false, hasAppPackage: false,
libraries: null, libraries: null,
appId: '' 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.saveScreen = saveScreen(store)
store.renameScreen = renameScreen(store); store.renameScreen = renameScreen(store)
store.deleteScreen = deleteScreen(store); store.deleteScreen = deleteScreen(store)
store.setCurrentScreen = setCurrentScreen(store); store.setCurrentScreen = setCurrentScreen(store)
store.setCurrentPage = setCurrentPage(store); store.setCurrentPage = setCurrentPage(store)
store.createScreen = createScreen(store); store.createScreen = createScreen(store)
store.addStylesheet = addStylesheet(store); store.addStylesheet = addStylesheet(store)
store.removeStylesheet = removeStylesheet(store); store.removeStylesheet = removeStylesheet(store)
store.savePage = savePage(store); store.savePage = savePage(store)
store.addChildComponent = addChildComponent(store); store.addChildComponent = addChildComponent(store)
store.selectComponent = selectComponent(store); store.selectComponent = selectComponent(store)
store.setComponentProp = setComponentProp(store); store.setComponentProp = setComponentProp(store)
store.setComponentStyle = setComponentStyle(store); store.setComponentStyle = setComponentStyle(store)
store.setComponentCode = setComponentCode(store); store.setComponentCode = setComponentCode(store)
store.setScreenType = setScreenType(store); store.setScreenType = setScreenType(store)
store.deleteComponent = deleteComponent(store); store.getPathToComponent = getPathToComponent(store)
store.moveUpComponent = moveUpComponent(store); store.addTemplatedComponent = addTemplatedComponent(store)
store.moveDownComponent = moveDownComponent(store); store.setMetadataProp = setMetadataProp(store)
store.copyComponent = copyComponent(store); return 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) => { 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([ const [main_screens, unauth_screens] = await Promise.all([
api.get(`/_builder/api/${pkg.application._id}/pages/main/screens`).then((r) => r.json()), api
api.get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`).then((r) => r.json()) .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()),
])
pkg.pages = { pkg.pages = {
main: { main: {
...pkg.pages.main, ...pkg.pages.main,
_screens: Object.values(main_screens) _screens: Object.values(main_screens),
}, },
unauthenticated: { unauthenticated: {
...pkg.pages.unauthenticated, ...pkg.pages.unauthenticated,
_screens: Object.values(unauth_screens) _screens: Object.values(unauth_screens),
},
} }
};
initial.libraries = pkg.application.componentLibraries; initial.libraries = pkg.application.componentLibraries
initial.components = await fetchComponentLibDefinitions(pkg.application._id); initial.components = await fetchComponentLibDefinitions(pkg.application._id)
initial.appname = pkg.application.name; initial.appname = pkg.application.name
initial.appId = pkg.application._id; initial.appId = pkg.application._id
initial.pages = pkg.pages; initial.pages = pkg.pages
initial.hasAppPackage = true; initial.hasAppPackage = true
initial.screens = values(pkg.screens); initial.screens = values(pkg.screens)
initial.builtins = [ getBuiltin('##builtin/screenslot') ]; initial.builtins = [getBuiltin("##builtin/screenslot")]
initial.appInstances = pkg.application.instances; initial.appInstances = pkg.application.instances
initial.appId = pkg.application._id; initial.appId = pkg.application._id
store.set(initial); store.set(initial)
return initial; return initial
}; }
const saveScreen = (store) => (screen) => { const saveScreen = store => screen => {
store.update((state) => { store.update(state => {
return _saveScreen(store, state, screen); return _saveScreen(store, state, screen)
}); })
}; }
const _saveScreen = async (store, s, screen) => { const _saveScreen = async (store, s, screen) => {
const currentPageScreens = s.pages[s.currentPageName]._screens; const currentPageScreens = s.pages[s.currentPageName]._screens;
@ -127,430 +143,349 @@ const _saveScreen = async (store, s, screen) => {
return s; return s;
}; };
const _saveScreenApi = (screen, s) => { const createScreen = store => (screenName, route, layoutComponentName) => {
api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen).then(() => _savePage(s)); store.update(state => {
}; const rootComponent = state.components[layoutComponentName]
const createScreen = (store) => (screenName, route, layoutComponentName) => {
store.update((state) => {
const rootComponent = state.components[layoutComponentName];
const newScreen = { const newScreen = {
name: screenName || '', name: screenName || "",
description: '', description: "",
url: '', url: "",
_css: '', _css: "",
uiFunctions: '', uiFunctions: "",
props: createProps(rootComponent).props props: createProps(rootComponent).props,
};
newScreen.route = route;
state.currentPreviewItem = newScreen;
state.currentComponentInfo = newScreen.props;
state.currentFrontEndType = 'screen';
_saveScreen(store, state, newScreen);
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 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);
s.components = components;
s.screens = screens;
if (s.currentPreviewItem.name === name) {
s.currentPreviewItem = null;
s.currentFrontEndType = '';
} }
api.delete(`/_builder/api/${s.appId}/screen/${name}`); newScreen.route = route
state.currentPreviewItem = newScreen
state.currentComponentInfo = newScreen.props
state.currentFrontEndType = "screen"
return s; _saveScreen(store, state, newScreen)
});
};
const renameScreen = (store) => (oldname, newname) => { return state
store.update((s) => { })
const { screens, pages, error, changedScreens } = rename(s.pages, s.screens, oldname, newname); }
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 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 = ""
}
api.delete(`/_builder/api/${s.appId}/screen/${name}`)
return s
})
}
const renameScreen = store => (oldname, newname) => {
store.update(s => {
const { screens, pages, error, changedScreens } = rename(
s.pages,
s.screens,
oldname,
newname
)
if (error) { if (error) {
// should really do something with this // should really do something with this
return s; return s
} }
s.screens = screens; s.screens = screens
s.pages = pages; s.pages = pages
if (s.currentPreviewItem.name === oldname) s.currentPreviewItem.name = newname; if (s.currentPreviewItem.name === oldname)
s.currentPreviewItem.name = newname
const saveAllChanged = async () => { const saveAllChanged = async () => {
for (let screenName of changedScreens) { for (let screenName of changedScreens) {
const changedScreen = getExactComponent(screens, screenName); const changedScreen = getExactComponent(screens, screenName)
await api.post(`/_builder/api/${s.appId}/screen`, changedScreen); await api.post(`/_builder/api/${s.appId}/screen`, changedScreen)
}
} }
};
api api
.patch(`/_builder/api/${s.appId}/screen`, { .patch(`/_builder/api/${s.appId}/screen`, {
oldname, oldname,
newname newname,
}) })
.then(() => saveAllChanged()) .then(() => saveAllChanged())
.then(() => { .then(() => {
_savePage(s); _savePage(s)
}); })
return s; return s
}); })
};
const savePage = (store) => async (page) => {
store.update((state) => {
if (state.currentFrontEndType !== 'page' || !state.currentPageName) {
return state;
} }
state.pages[state.currentPageName] = page; const savePage = store => async page => {
_savePage(state); store.update(state => {
return state; if (state.currentFrontEndType !== "page" || !state.currentPageName) {
}); return state
}; }
const addStylesheet = (store) => (stylesheet) => { state.pages[state.currentPageName] = page
store.update((s) => { _savePage(state)
s.pages.stylesheets.push(stylesheet); return state
_savePage(s); })
return s; }
});
};
const removeStylesheet = (store) => (stylesheet) => { const addStylesheet = store => stylesheet => {
store.update((state) => { store.update(s => {
state.pages.stylesheets = state.pages.stylesheets.filter((s) => s !== stylesheet); s.pages.stylesheets.push(stylesheet)
_savePage(state); _savePage(s)
return state; return s
}); })
}; }
const _savePage = async (s) => { const removeStylesheet = store => stylesheet => {
const page = s.pages[s.currentPageName]; store.update(state => {
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, { state.pages.stylesheets = state.pages.stylesheets.filter(
page: { componentLibraries: s.pages.componentLibraries, ...page }, s => s !== stylesheet
uiFunctions: s.currentPageFunctions, )
screens: page._screens _savePage(state)
}); return state
}; })
}
const setCurrentPage = (store) => (pageName) => { const setCurrentPage = store => pageName => {
store.update((state) => { store.update(state => {
const current_screens = state.pages[pageName]._screens; const current_screens = state.pages[pageName]._screens
const currentPage = state.pages[pageName]; const currentPage = state.pages[pageName]
state.currentFrontEndType = 'page'; state.currentFrontEndType = "page"
state.currentPageName = pageName; state.currentPageName = pageName
state.screens = Array.isArray(current_screens) ? current_screens : Object.values(current_screens); state.screens = Array.isArray(current_screens)
const safeProps = makePropsSafe(state.components[currentPage.props._component], currentPage.props); ? current_screens
state.currentComponentInfo = safeProps; : Object.values(current_screens)
currentPage.props = safeProps; const safeProps = makePropsSafe(
state.currentPreviewItem = state.pages[pageName]; state.components[currentPage.props._component],
state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem.props ]); 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) { for (let screen of state.screens) {
screen._css = generate_screen_css([ screen.props ]); screen._css = generate_screen_css([screen.props])
} }
setCurrentPageFunctions(state); setCurrentPageFunctions(state)
return state; return state
}); })
}; }
// const getComponentDefinition = (components, name) => components.find(c => c.name === name)
/** /**
* @param {string} componentToAdd - name of the component to add to the application * @param {string} componentToAdd - name of the component to add to the application
* @param {string} presetName - name of the component preset if defined * @param {string} presetName - name of the component preset if defined
*/ */
const addChildComponent = (store) => (componentToAdd, presetName) => { const addChildComponent = store => (componentToAdd, presetName) => {
store.update((state) => { store.update(state => {
function findSlot(component_array) { function findSlot(component_array) {
for (let i = 0; i < component_array.length; i += 1) { for (let i = 0; i < component_array.length; i += 1) {
if (component_array[i]._component === '##builtin/screenslot') { if (component_array[i]._component === "##builtin/screenslot") {
return true; 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)) { if (
return state; componentToAdd.startsWith("##") &&
findSlot(state.pages[state.currentPageName].props._children)
) {
return state
} }
const component = componentToAdd.startsWith('##') const component = getComponentDefinition(state, componentToAdd)
? getBuiltin(componentToAdd)
: state.components[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( const newComponent = createProps(
component, component,
{ {
...presetProps, ...presetProps,
_instanceId: instanceId _instanceId: instanceId,
}, },
state 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.currentView = "component"
state.currentComponentInfo = newComponent.props; state.currentComponentInfo = newComponent.props
return state; return state
}); })
}; }
/** /**
* @param {string} props - props to add, as child of current component * @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); const addTemplatedComponent = store => props => {
_saveCurrentPreviewItem(state); 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) => { return state
store.update((state) => { })
const componentDef = component._component.startsWith('##') ? component : state.components[component._component];
state.currentComponentInfo = makePropsSafe(componentDef, component);
state.currentView = 'component';
return state;
});
};
const setComponentProp = (store) => (name, value) => {
store.update((state) => {
const current_component = state.currentComponentInfo;
state.currentComponentInfo[name] = value;
_saveCurrentPreviewItem(state);
state.currentComponentInfo = current_component;
return state;
});
};
const setComponentStyle = (store) => (type, name, value) => {
store.update((state) => {
if (!state.currentComponentInfo._styles) {
state.currentComponentInfo._styles = {};
} }
state.currentComponentInfo._styles[type][name] = value;
state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem.props ]); const selectComponent = store => component => {
store.update(state => {
return _selectComponent(state, component)
})
}
const setComponentProp = store => (name, value) => {
store.update(state => {
const current_component = state.currentComponentInfo
state.currentComponentInfo[name] = value
_saveCurrentPreviewItem(state)
state.currentComponentInfo = current_component
return state
})
}
const setComponentStyle = store => (type, name, value) => {
store.update(state => {
if (!state.currentComponentInfo._styles) {
state.currentComponentInfo._styles = {}
}
state.currentComponentInfo._styles[type][name] = value
state.currentPreviewItem._css = generate_screen_css([
state.currentPreviewItem.props,
])
// save without messing with the store // save without messing with the store
_saveCurrentPreviewItem(state); _saveCurrentPreviewItem(state)
return state; return state
}); })
}; }
const setComponentCode = (store) => (code) => { const setComponentCode = store => code => {
store.update((state) => { store.update(state => {
state.currentComponentInfo._code = code; state.currentComponentInfo._code = code
setCurrentPageFunctions(state); setCurrentPageFunctions(state)
// save without messing with the store // save without messing with the store
_saveScreenApi(state.currentPreviewItem, state); _saveScreenApi(state.currentPreviewItem, state)
return state; return state
}); })
}; }
const setCurrentPageFunctions = (s) => { const setCurrentPageFunctions = s => {
s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName]); s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName])
insertCodeMetadata(s.currentPreviewItem.props); insertCodeMetadata(s.currentPreviewItem.props)
}; }
const buildPageCode = (screens, page) => buildCodeForScreens([ page, ...screens ]); const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens])
const setScreenType = (store) => (type) => { const setScreenType = store => type => {
store.update((state) => { store.update(state => {
state.currentFrontEndType = type; state.currentFrontEndType = type
const pageOrScreen = const pageOrScreen =
type === 'page' ? state.pages[state.currentPageName] : state.pages[state.currentPageName]._screens[0]; type === "page"
? state.pages[state.currentPageName]
: state.pages[state.currentPageName]._screens[0]
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null; state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
state.currentPreviewItem = pageOrScreen; state.currentPreviewItem = pageOrScreen
return state; return state
}); })
};
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); const getPathToComponent = store => component => {
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. // Gets all the components to needed to construct a path.
const tempStore = get(store); const tempStore = get(store)
let pathComponents = []; let pathComponents = []
let parent = component; let parent = component
let root = false; let root = false
while (!root) { while (!root) {
parent = getParent(tempStore.currentPreviewItem.props, parent); parent = getParent(tempStore.currentPreviewItem.props, parent)
if (!parent) { if (!parent) {
root = true; root = true
} else { } else {
pathComponents.push(parent); pathComponents.push(parent)
} }
} }
// Remove root entry since it's the screen or page layout. // Remove root entry since it's the screen or page layout.
// Reverse array since we need the correct order of the IDs // Reverse array since we need the correct order of the IDs
const reversedComponents = pathComponents.reverse().slice(1); const reversedComponents = pathComponents.reverse().slice(1)
// Add component // Add component
const allComponents = [ ...reversedComponents, component ]; const allComponents = [...reversedComponents, component]
// Map IDs // Map IDs
const IdList = allComponents.map((c) => c._id); const IdList = allComponents.map(c => c._id)
// Construct ID Path: // Construct ID Path:
const path = IdList.join('/'); const path = IdList.join("/")
return path; 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) => { const setMetadataProp = store => (name, prop) => {
cancelToken = cancelToken || { cancelled: false }; store.update(s => {
action(props, () => { s.currentPreviewItem[name] = prop
cancelToken.cancelled = true; return s
}); })
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);

View file

@ -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)
}
}
}

View file

@ -5,7 +5,7 @@
UIKit.notification({ UIKit.notification({
message: ` message: `
<div class="message-container"> <div class="message-container">
<i class="ri-information-fill information-icon"></i> <div class="information-icon">🤯</div>
<span class="notification-message"> <span class="notification-message">
${message} ${message}
</span> </span>
@ -21,6 +21,7 @@
<style> <style>
:global(.information-icon) { :global(.information-icon) {
font-size: 24px; font-size: 24px;
margin-right: 8px;
} }
:global(.uk-nofi) { :global(.uk-nofi) {
@ -31,10 +32,9 @@
} }
:global(.message-container) { :global(.message-container) {
display: grid; display: flex;
grid-template-columns: 40px 1fr auto;
grid-gap: 5px;
align-items: center; align-items: center;
justify-content: center;
} }
:global(.uk-notification) { :global(.uk-notification) {
@ -44,7 +44,6 @@
margin-right: auto !important; margin-right: auto !important;
margin-left: auto !important; margin-left: auto !important;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 3px 6px #00000029;
} }
:global(.uk-notification-message) { :global(.uk-notification-message) {
@ -56,21 +55,23 @@
} }
:global(.uk-notification-message-danger) { :global(.uk-notification-message-danger) {
background: #f2545b !important; background: var(--ink-light) !important;
color: #fff !important; color: #fff !important;
font-family: Roboto; font-family: Roboto;
font-size: 14px !important; font-size: 16px !important;
} }
:global(.refresh-page-button) { :global(.refresh-page-button) {
font-size: 13px; font-size: 14px;
font-weight: 600; border-radius: 3px;
border-radius: 5px;
border: none; border: none;
padding: 5px; padding: 8px 16px;
width: 91px; color: var(--ink);
height: 28px;
color: #f2545b;
background: #ffffff; background: #ffffff;
margin-left: 20px;
}
:global(.refresh-page-button):hover {
background: var(--grey-light);
} }
</style> </style>

View file

@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6
12s-.675-1.5-1.5-1.5zm15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21
12.825 21 12s-.675-1.5-1.5-1.5zm-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5
1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z" />
</svg>

After

Width:  |  Height:  |  Size: 419 B

View file

@ -31,3 +31,4 @@ export { default as EmailIcon } from "./Email.svelte"
export { default as TwitterIcon } from "./Twitter.svelte" export { default as TwitterIcon } from "./Twitter.svelte"
export { default as InfoIcon } from "./Info.svelte" export { default as InfoIcon } from "./Info.svelte"
export { default as CloseIcon } from "./Close.svelte" export { default as CloseIcon } from "./Close.svelte"
export { default as MoreIcon } from "./More.svelte"

View file

@ -15,7 +15,7 @@
<style> <style>
input { input {
width: 32px; /* width: 32px; */
height: 32px; height: 32px;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
@ -23,6 +23,9 @@
color: var(--ink); color: var(--ink);
opacity: 0.7; opacity: 0.7;
padding: 0px 4px; padding: 0px 4px;
line-height: 1.3;
/* padding: 12px; */
width: 164px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid var(--grey); border: 1px solid var(--grey);
border-radius: 2px; border-radius: 2px;

View file

@ -6,6 +6,7 @@
export let label = "" export let label = ""
export let value = ["0", "0", "0", "0"] export let value = ["0", "0", "0", "0"]
export let suffix = "" export let suffix = ""
export let onChange = () => {} export let onChange = () => {}
function handleChange(val, idx) { function handleChange(val, idx) {
@ -42,4 +43,5 @@
.inputs-group { .inputs-group {
flex: 1; flex: 1;
} }
</style> </style>

View file

@ -0,0 +1,77 @@
<script>
export let tabs = []
export const selectTab = tabName => {
selected = tabName
selectedIndex = tabs.indexOf(selected)
}
let selected = tabs.length > 0 && tabs[0]
let selectedIndex = 0
const isSelected = tab => selected === tab
</script>
<div class="root">
<div class="switcher">
{#each tabs as tab}
<button class:selected={selected === tab} on:click={() => selectTab(tab)}>
{tab}
</button>
{/each}
</div>
<div class="panel">
{#if selectedIndex === 0}
<slot name="0" />
{:else if selectedIndex === 1}
<slot name="1" />
{:else if selectedIndex === 2}
<slot name="2" />
{:else if selectedIndex === 3}
<slot name="3" />
{/if}
</div>
</div>
<style>
.root {
height: 100%;
display: flex;
flex-direction: column;
padding: 20px 20px;
border-left: solid 1px var(--grey);
box-sizing: border-box;
}
.switcher {
display: flex;
margin: 0px 20px 20px 0px;
box-sizing: border-box;
}
.switcher > button {
display: inline-block;
border: none;
margin: 0;
padding: 0;
cursor: pointer;
font-size: 18px;
font-weight: 700;
color: var(--ink-lighter);
margin-right: 20px;
background: none;
}
.switcher > .selected {
color: var(--ink);
}
.panel {
min-height: 0;
overflow-y: auto;
}
</style>

View file

@ -13,17 +13,41 @@
const FIELD_TYPES = ["string", "number", "boolean"] 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 schema
export let goBack export let goBack
let errors = [] let errors = []
let draftField = cloneDeep(field) let draftField = cloneDeep(field)
let type = field.type
let constraints = field.constraints
let required =
field.constraints.presence && !field.constraints.presence.allowEmpty
const save = () => { const save = () => {
constraints.presence = required ? { allowEmpty: false } : false
draftField.constraints = constraints
draftField.type = type
schema[field.name] = draftField schema[field.name] = draftField
goBack() 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 }
</script> </script>
<div class="root"> <div class="root">
@ -32,32 +56,26 @@
<form on:submit|preventDefault class="uk-form-stacked"> <form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={field.name} /> <Textbox label="Name" bind:text={field.name} />
<Dropdown <Dropdown label="Type" bind:selected={type} options={FIELD_TYPES} />
label="Type"
bind:selected={draftField.type}
options={FIELD_TYPES} />
{#if field.type === 'string'} <Checkbox label="Required" bind:checked={required} />
<NumberBox label="Max Length" bind:value={draftField.maxLength} />
<ValuesList label="Categories" bind:values={draftField.values} /> {#if type === 'string'}
{:else if field.type === 'boolean'} <NumberBox label="Max Length" bind:value={constraints.length.maximum} />
<!-- TODO: revisit and fix with JSON schema --> <ValuesList label="Categories" bind:values={constraints.inclusion} />
<Checkbox label="Allow Null" bind:checked={draftField.allowNulls} /> {:else if type === 'datetime'}
{:else if field.format === 'datetime'}
<!-- TODO: revisit and fix with JSON schema -->
<DatePicker label="Min Value" bind:value={draftField.minValue} />
<DatePicker label="Max Value" bind:value={draftField.maxValue} />
{:else if field.type === 'number'}
<NumberBox label="Min Value" bind:value={draftField.minimum} />
<NumberBox label="Max Value" bind:value={draftField.maximum} />
{:else if draftField.type.startsWith('array')}
<!-- TODO: revisit and fix with JSON schema --> <!-- TODO: revisit and fix with JSON schema -->
<DatePicker
label="Min Value"
bind:value={constraints.datetime.earliest} />
<DatePicker label="Max Value" bind:value={constraints.datetime.latest} />
{:else if type === 'number'}
<NumberBox <NumberBox
label="Min Length" label="Min Value"
bind:value={draftField.typeOptions.minLength} /> bind:value={constraints.numericality.greaterThanOrEqualTo} />
<NumberBox <NumberBox
label="Max Length" label="Max Value"
bind:value={draftField.typeOptions.maxLength} /> bind:value={constraints.numericality.lessThanOrEqualTo} />
{/if} {/if}
</form> </form>
</div> </div>

View file

@ -8,10 +8,6 @@
import * as api from "../api" import * as api from "../api"
import ErrorsBox from "components/common/ErrorsBox.svelte" import ErrorsBox from "components/common/ErrorsBox.svelte"
const CLASS_NAME_MAP = {
boolean: "uk-checkbox",
}
export let record = {} export let record = {}
export let onClosed export let onClosed
@ -28,14 +24,25 @@
onClosed() onClosed()
} }
const isSelect = meta =>
meta.type === "string" &&
meta.constraints &&
meta.constraints.inclusion &&
meta.constraints.inclusion.length > 0
function determineInputType(meta) { function determineInputType(meta) {
if (meta.type === "datetime") return "date" if (meta.type === "datetime") return "date"
if (meta.type === "number") return "number" if (meta.type === "number") return "number"
if (meta.type === "boolean") return "checkbox" if (meta.type === "boolean") return "checkbox"
if (isSelect(meta)) return "select"
return "text" return "text"
} }
function determineOptions(meta) {
return isSelect(meta) ? meta.constraints.inclusion : []
}
async function saveRecord() { async function saveRecord() {
const recordResponse = await api.saveRecord( const recordResponse = await api.saveRecord(
{ {
@ -46,7 +53,9 @@
$backendUiStore.selectedModel._id $backendUiStore.selectedModel._id
) )
if (recordResponse.errors) { if (recordResponse.errors) {
errors = recordResponse.errors errors = Object.keys(recordResponse.errors)
.map(k => ({ dataPath: k, message: recordResponse.errors[k] }))
.flat()
return return
} }
@ -65,8 +74,8 @@
{#each modelSchema as [key, meta]} {#each modelSchema as [key, meta]}
<div class="uk-margin"> <div class="uk-margin">
<RecordFieldControl <RecordFieldControl
className={CLASS_NAME_MAP[meta.type]}
type={determineInputType(meta)} type={determineInputType(meta)}
options={determineOptions(meta)}
label={key} label={key}
bind:value={record[key]} /> bind:value={record[key]} />
</div> </div>

View file

@ -3,10 +3,16 @@
export let value = "" export let value = ""
export let label export let label
export let errors = [] export let errors = []
export let className = "uk-input" export let options = []
let checked = type === "checkbox" ? value : false 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 => { const handleInput = event => {
if (event.target.type === "checkbox") { if (event.target.type === "checkbox") {
value = event.target.checked value = event.target.checked
@ -23,11 +29,23 @@
</script> </script>
<label>{label}</label> <label>{label}</label>
{#if type === 'select'}
<select
class={determineClassName(type)}
bind:value
class:uk-form-danger={errors.length > 0}>
{#each options as opt}
<option value={opt}>{opt}</option>
{/each}
</select>
{:else}
<input <input
class={className} class={determineClassName(type)}
class:uk-form-danger={errors.length > 0} class:uk-form-danger={errors.length > 0}
{checked} {checked}
{type} {type}
{value} {value}
on:input={handleInput} on:input={handleInput}
on:change={handleInput} /> on:change={handleInput} />
{/if}

View file

@ -45,7 +45,6 @@
<DatabasesList /> <DatabasesList />
</div> </div>
</div> </div>
<hr />
{#if $backendUiStore.selectedDatabase._id} {#if $backendUiStore.selectedDatabase._id}
<div class="hierarchy"> <div class="hierarchy">
<div class="components-list-container"> <div class="components-list-container">

View file

@ -0,0 +1,276 @@
<script>
import { MoreIcon } from "components/common/Icons"
import { store } from "builderStore"
import { getComponentDefinition } from "builderStore/store"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last, cloneDeep } from "lodash/fp"
import UIkit from "uikit"
import {
selectComponent,
getParent,
walkProps,
saveCurrentPreviewItem,
} from "builderStore/storeUtils"
import { uuid } from "builderStore/uuid"
export let component
let confirmDeleteDialog
let dropdownEl
$: dropdown = UIkit.dropdown(dropdownEl, {
mode: "click",
offset: 0,
pos: "bottom-right",
"delay-hide": 0,
animation: false,
})
$: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false))
$: noChildrenAllowed =
!component ||
getComponentDefinition($store, component._component).children === false
$: noPaste =
!$store.componentToPaste || $store.componentToPaste._id === component._id
const lastPartOfName = c => (c ? last(c._component.split("/")) : "")
const hideDropdown = () => {
dropdown.hide()
}
const moveUpComponent = () => {
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.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.update(s => {
const parent = getParent(s.currentPreviewItem.props, component)
const copiedComponent = cloneDeep(component)
walkProps(copiedComponent, p => {
p._id = uuid()
})
parent._children = [...parent._children, copiedComponent]
saveCurrentPreviewItem(s)
s.currentComponentInfo = copiedComponent
return s
})
}
const deleteComponent = () => {
store.update(state => {
const parent = getParent(state.currentPreviewItem.props, component)
if (parent) {
parent._children = parent._children.filter(c => c !== component)
}
saveCurrentPreviewItem(state)
return state
})
}
const generateNewIdsForComponent = c =>
walkProps(c, p => {
p._id = uuid()
})
const storeComponentForCopy = (cut = false) => {
store.update(s => {
const copiedComponent = cloneDeep(component)
s.componentToPaste = copiedComponent
if (cut) {
const parent = getParent(s.currentPreviewItem.props, component._id)
parent._children = parent._children.filter(c => c._id !== component._id)
selectComponent(s, parent)
}
return s
})
}
const pasteComponent = mode => {
store.update(s => {
if (!s.componentToPaste) return s
const componentToPaste = cloneDeep(s.componentToPaste)
generateNewIdsForComponent(componentToPaste)
delete componentToPaste._cutId
if (mode === "inside") {
component._children.push(componentToPaste)
return s
}
const parent = getParent(s.currentPreviewItem.props, component)
const targetIndex = parent._children.indexOf(component)
const index = mode === "above" ? targetIndex : targetIndex + 1
parent._children.splice(index, 0, cloneDeep(componentToPaste))
saveCurrentPreviewItem(s)
selectComponent(s, componentToPaste)
return s
})
}
</script>
<div class="root" on:click|stopPropagation={() => {}}>
<button>
<MoreIcon />
</button>
<ul class="menu" bind:this={dropdownEl} on:click={hideDropdown}>
<li class="item" on:click={() => confirmDeleteDialog.show()}>
<i class="icon ri-delete-bin-2-line" />
Delete
</li>
<li class="item" on:click={moveUpComponent}>
<i class="icon ri-arrow-up-line" />
Move up
</li>
<li class="item" on:click={moveDownComponent}>
<i class="icon ri-arrow-down-line" />
Move down
</li>
<li class="item" on:click={copyComponent}>
<i class="icon ri-repeat-one-line" />
Duplicate
</li>
<li class="item" on:click={() => storeComponentForCopy(true)}>
<i class="icon ri-scissors-cut-line" />
Cut
</li>
<li class="item" on:click={() => storeComponentForCopy(false)}>
<i class="icon ri-file-copy-line" />
Copy
</li>
<hr class="hr-style" />
<li
class="item"
class:disabled={noPaste}
on:click={() => pasteComponent('above')}>
<i class="icon ri-insert-row-top" />
Paste above
</li>
<li
class="item"
class:disabled={noPaste}
on:click={() => pasteComponent('below')}>
<i class="icon ri-insert-row-bottom" />
Paste below
</li>
<li
class="item"
class:disabled={noPaste || noChildrenAllowed}
on:click={() => pasteComponent('inside')}>
<i class="icon ri-insert-column-right" />
Paste inside
</li>
</ul>
</div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(component)}' component?`}
okText="Delete Component"
onOk={deleteComponent} />
<style>
.root {
overflow: hidden;
z-index: 9;
}
.root button {
border-style: none;
border-radius: 2px;
padding: 5px;
background: transparent;
cursor: pointer;
color: var(--ink);
outline: none;
}
.menu {
z-index: 100000;
overflow: visible;
padding: 12px 0px;
border-radius: 5px;
}
.menu li {
border-style: none;
background-color: transparent;
list-style-type: none;
padding: 4px 16px;
margin: 0;
width: 100%;
box-sizing: border-box;
}
.item {
display: flex;
align-items: center;
font-size: 14px;
}
.icon {
margin-right: 8px;
}
.menu li:not(.disabled) {
cursor: pointer;
color: var(--ink-light);
}
.menu li:not(.disabled):hover {
color: var(--ink);
background-color: var(--grey-light);
}
.disabled {
color: var(--grey-dark);
cursor: default;
}
.hr-style {
margin: 8px 0;
color: var(--grey-dark);
}
</style>

View file

@ -104,6 +104,12 @@
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* Merge Check */
overflow-x: hidden;
overflow-y: hidden;
padding: 20px;
box-sizing: border-box;
/* Merge Check */
} }
.title > div:nth-child(1) { .title > div:nth-child(1) {
@ -118,5 +124,7 @@
.component-props-container { .component-props-container {
margin-top: 20px; margin-top: 20px;
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
} }
</style> </style>

View file

@ -1,4 +1,5 @@
<script> <script>
import { goto } from "@sveltech/routify"
import { splitName } from "./pagesParsing/splitRootComponentName.js" import { splitName } from "./pagesParsing/splitRootComponentName.js"
import components from "./temporaryPanelStructure.js" import components from "./temporaryPanelStructure.js"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@ -32,7 +33,14 @@
const onComponentChosen = component => { const onComponentChosen = component => {
store.addChildComponent(component._component) store.addChildComponent(component._component)
toggleTab()
toggleTab("Navigate")
// Get ID path
const path = store.getPathToComponent($store.currentComponentInfo)
// Go to correct URL
$goto(`./:page/:screen/${path}`)
} }
</script> </script>

View file

@ -1,7 +1,6 @@
<script> <script>
import { params, goto } from "@sveltech/routify" import { params, goto } from "@sveltech/routify"
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte" import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
import { last, sortBy, map, trimCharsStart, trimChars, join } from "lodash/fp" import { last, sortBy, map, trimCharsStart, trimChars, join } from "lodash/fp"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { pipe } from "components/common/core" import { pipe } from "components/common/core"
@ -36,11 +35,6 @@
sortBy("title"), sortBy("title"),
]) ])
const confirmDeleteComponent = component => {
componentToDelete = component
confirmDeleteDialog.show()
}
const changeScreen = screen => { const changeScreen = screen => {
store.setCurrentScreen(screen.title) store.setCurrentScreen(screen.title)
$goto(`./:page/${screen.title}`) $goto(`./:page/${screen.title}`)
@ -62,9 +56,7 @@
{/if} {/if}
</span> </span>
<span class="icon"> <i class="ri-artboard-2-fill icon" />
<ShapeIcon />
</span>
<span class="title">{screen.title}</span> <span class="title">{screen.title}</span>
</div> </div>
@ -72,41 +64,32 @@
{#if $store.currentPreviewItem.name === screen.title && screen.component.props._children} {#if $store.currentPreviewItem.name === screen.title && screen.component.props._children}
<ComponentsHierarchyChildren <ComponentsHierarchyChildren
components={screen.component.props._children} components={screen.component.props._children}
currentComponent={$store.currentComponentInfo} currentComponent={$store.currentComponentInfo} />
onDeleteComponent={confirmDeleteComponent}
onMoveUpComponent={store.moveUpComponent}
onMoveDownComponent={store.moveDownComponent}
onCopyComponent={store.copyComponent} />
{/if} {/if}
{/each} {/each}
</div> </div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component?`}
okText="Delete Component"
onOk={() => store.deleteComponent(componentToDelete)} />
<style> <style>
.root { .root {
font-weight: 400; font-weight: 400;
color: #000333; color: var(--ink);
} }
.title { .title {
margin-left: 10px; margin-left: 10px;
margin-top: 2px; margin-top: 2px;
font-size: 13px; font-size: 14px;
font-weight: 400;
} }
.icon { .icon {
display: inline-block; display: inline-block;
transition: 0.2s; transition: 0.2s;
font-size: 24px;
width: 20px; width: 20px;
margin-top: 2px; margin-top: 2px;
color: #333; color: var(--ink-light);
} }
.icon:nth-of-type(2) { .icon:nth-of-type(2) {

View file

@ -3,6 +3,7 @@
import { store } from "builderStore" import { store } from "builderStore"
import { last } from "lodash/fp" import { last } from "lodash/fp"
import { pipe } from "components/common/core" import { pipe } from "components/common/core"
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
import { import {
XCircleIcon, XCircleIcon,
ChevronUpIcon, ChevronUpIcon,
@ -14,23 +15,12 @@
export let currentComponent export let currentComponent
export let onSelect = () => {} export let onSelect = () => {}
export let level = 0 export let level = 0
export let onDeleteComponent
export let onMoveUpComponent
export let onMoveDownComponent
export let onCopyComponent
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1) const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
const get_name = s => (!s ? "" : last(s.split("/"))) const get_name = s => (!s ? "" : last(s.split("/")))
const get_capitalised_name = name => pipe(name, [get_name, capitalise]) const get_capitalised_name = name => pipe(name, [get_name, capitalise])
const moveDownComponent = component => {
const c = component
return () => {
return onMoveDownComponent(c)
}
}
const selectComponent = component => { const selectComponent = component => {
// Set current component // Set current component
store.selectComponent(component) store.selectComponent(component)
@ -50,31 +40,13 @@
class="budibase__nav-item item" class="budibase__nav-item item"
class:selected={currentComponent === component} class:selected={currentComponent === component}
style="padding-left: {level * 20 + 53}px"> style="padding-left: {level * 20 + 53}px">
<div>{get_capitalised_name(component._component)}</div> <div class="nav-item">
<div class="reorder-buttons"> <i class="icon ri-arrow-right-circle-fill" />
{#if index > 0} {get_capitalised_name(component._component)}
<button </div>
class:solo={index === components.length - 1} <div class="actions">
on:click|stopPropagation={() => onMoveUpComponent(component)}> <ComponentDropdownMenu {component} />
<ChevronUpIcon />
</button>
{/if}
{#if index < components.length - 1}
<button
class:solo={index === 0}
on:click|stopPropagation={moveDownComponent(component)}>
<ChevronDownIcon />
</button>
{/if}
</div> </div>
<button
class="copy"
on:click|stopPropagation={() => onCopyComponent(component)}>
<CopyIcon />
</button>
<button on:click|stopPropagation={() => onDeleteComponent(component)}>
<XCircleIcon />
</button>
</div> </div>
{#if component._children} {#if component._children}
@ -82,11 +54,7 @@
components={component._children} components={component._children}
{currentComponent} {currentComponent}
{onSelect} {onSelect}
level={level + 1} level={level + 1} />
{onDeleteComponent}
{onMoveUpComponent}
{onMoveDownComponent}
{onCopyComponent} />
{/if} {/if}
</li> </li>
{/each} {/each}
@ -107,50 +75,37 @@
border-radius: 3px; border-radius: 3px;
height: 35px; height: 35px;
align-items: center; align-items: center;
font-weight: 400;
font-size: 13px;
} }
.item button { .actions {
display: none; display: none;
height: 20px; height: 24px;
width: 28px; width: 24px;
color: var(--slate); color: var(--ink);
padding: 0px 5px; padding: 0px 5px;
border-style: none; border-style: none;
background: rgba(0, 0, 0, 0); background: rgba(0, 0, 0, 0);
cursor: pointer; cursor: pointer;
} position: relative;
.item button.copy {
width: 26px;
} }
.item:hover { .item:hover {
background: #fafafa; background: var(--grey-light);
cursor: pointer; cursor: pointer;
} }
.item:hover button { .item:hover .actions {
display: block; display: block;
} }
.item:hover button:hover { .nav-item {
color: var(--button-text);
}
.reorder-buttons {
display: flex; display: flex;
flex-direction: column; align-items: center;
height: 100%; font-size: 14px;
color: var(--ink);
} }
.reorder-buttons > button { .icon {
flex: 1 1 auto; color: var(--ink-light);
width: 30px; margin-right: 8px;
height: 15px;
}
.reorder-buttons > button.solo {
padding-top: 2px;
} }
</style> </style>

View file

@ -0,0 +1,57 @@
<script>
import { store, backendUiStore } from "builderStore"
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
import PageLayout from "components/userInterface/PageLayout.svelte"
import PagesList from "components/userInterface/PagesList.svelte"
import NewScreen from "components/userInterface/NewScreen.svelte"
const newScreen = () => {
newScreenPicker.show()
}
let newScreenPicker
</script>
<PagesList />
<button class="newscreen" on:click={newScreen}>
<i class="icon ri-add-circle-fill" />
Create New Screen
</button>
<PageLayout layout={$store.pages[$store.currentPageName]} />
<div class="nav-items-container">
<ComponentsHierarchy screens={$store.screens} />
</div>
<NewScreen bind:this={newScreenPicker} />
<style>
.newscreen {
cursor: pointer;
border: 1px solid var(--grey-dark);
border-radius: 3px;
width: 100%;
padding: 8px 16px;
margin: 20px 0px 12px 0px;
display: flex;
justify-content: center;
align-items: center;
background: white;
color: var(--ink);
font-size: 14px;
font-weight: 500;
transition: all 2ms;
}
.newscreen:hover {
background: var(--grey-light);
}
.icon {
color: var(--ink);
font-size: 16px;
margin-right: 4px;
}
</style>

View file

@ -3,7 +3,7 @@
export let item export let item
</script> </script>
<div class="item-item" transition:fly={{ y: 100, duration: 1000 }} on:click> <div class="item-item" in:fly={{ y: 100, duration: 1000 }} on:click>
<div class="item-icon"> <div class="item-icon">
<i class={item.icon} /> <i class={item.icon} />
</div> </div>

View file

@ -3,7 +3,6 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
import Item from "./Item.svelte" import Item from "./Item.svelte"
import { store } from "builderStore"
export let list export let list
let category = list let category = list

View file

@ -34,19 +34,12 @@
title: lastPartOfName(layout), title: lastPartOfName(layout),
} }
const confirmDeleteComponent = async component => {
componentToDelete = component
confirmDeleteDialog.show()
}
const setCurrentScreenToLayout = () => { const setCurrentScreenToLayout = () => {
store.setScreenType("page") store.setScreenType("page")
$goto("./:page/page-layout") $goto("./:page/page-layout")
} }
</script> </script>
<div class="pagelayoutSection">
<div class="components-nav-page">Page Layout</div>
<div <div
class="budibase__nav-item root" class="budibase__nav-item root"
class:selected={$store.currentComponentInfo._id === _layout.component.props._id} class:selected={$store.currentComponentInfo._id === _layout.component.props._id}
@ -56,64 +49,36 @@
class:rotate={$store.currentPreviewItem.name !== _layout.title}> class:rotate={$store.currentPreviewItem.name !== _layout.title}>
<ArrowDownIcon /> <ArrowDownIcon />
</span> </span>
<i class="ri-layout-3-fill icon-big" />
<span class="icon"> <span class="title">Master Screen</span>
<GridIcon />
</span>
<span class="title">Page Layout</span>
</div> </div>
{#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children} {#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children}
<ComponentsHierarchyChildren <ComponentsHierarchyChildren
thisComponent={_layout.component.props} thisComponent={_layout.component.props}
components={_layout.component.props._children} components={_layout.component.props._children}
currentComponent={$store.currentComponentInfo} currentComponent={$store.currentComponentInfo} />
onDeleteComponent={confirmDeleteComponent}
onMoveUpComponent={store.moveUpComponent}
onMoveDownComponent={store.moveDownComponent}
onCopyComponent={store.copyComponent} />
{/if} {/if}
</div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component?`}
okText="Delete Component"
onOk={() => store.deleteComponent(componentToDelete)} />
<style> <style>
.components-nav-page {
font-size: 13px;
color: #000333;
text-transform: uppercase;
margin-bottom: 10px;
padding-left: 20px;
font-weight: 600;
opacity: 0.4;
letter-spacing: 1px;
}
.pagelayoutSection {
margin: 20px 0px 20px 0px;
}
.title { .title {
margin-left: 10px; margin-left: 10px;
font-size: 13px; font-size: 14px;
font-weight: 400;
color: var(--ink);
} }
.icon { .icon {
width: 24px;
display: inline-block; display: inline-block;
transition: 0.2s; transition: 0.2s;
width: 20px; width: 20px;
margin-top: 2px; color: var(--ink-light);
color: #000333;
} }
.icon:nth-of-type(2) { .icon-big {
width: 14px; font-size: 24px;
margin: 0 0 0 5px; color: var(--ink-light);
} }
:global(svg) { :global(svg) {

View file

@ -1,8 +1,6 @@
<script> <script>
import { params, goto } from "@sveltech/routify" import { params, goto } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
import getIcon from "components/common/icon"
import { CheckIcon } from "components/common/Icons"
const getPage = (s, name) => { const getPage = (s, name) => {
const props = s.pages[name] const props = s.pages[name]
@ -20,6 +18,7 @@
}, },
] ]
if (!$store.currentPageName)
store.setCurrentPage($params.page ? $params.page : "main") store.setCurrentPage($params.page ? $params.page : "main")
const changePage = id => { const changePage = id => {
@ -29,63 +28,37 @@
</script> </script>
<div class="root"> <div class="root">
<ul>
{#each pages as { title, id }} {#each pages as { title, id }}
<li> <button class:active={id === $params.page} on:click={() => changePage(id)}>
<span class="icon">
{#if id === $params.page}
<CheckIcon />
{/if}
</span>
<button
class:active={id === $params.page}
on:click={() => changePage(id)}>
{title} {title}
</button> </button>
</li>
{/each} {/each}
</ul>
</div> </div>
<style> <style>
.root { .root {
padding-bottom: 10px; display: flex;
font-size: 0.9rem; flex-direction: row;
color: #000333;
font-weight: bold;
position: relative;
padding-left: 1.8rem;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
li {
margin: 0.5rem 0;
} }
button { button {
margin: 0 0 0 6px;
padding: 0;
border: none;
font-family: Roboto;
font-size: 13px;
outline: none;
cursor: pointer; cursor: pointer;
background: rgba(0, 0, 0, 0); padding: 8px 16px;
text-align: center;
background: #ffffff;
color: var(--ink-light);
border-radius: 5px;
font-family: Roboto;
font-size: 14px;
font-weight: 400;
transition: all 0.3s;
text-rendering: optimizeLegibility;
border: none !important;
transition: 0.2s;
} }
.active { .active {
font-weight: 500; background: var(--ink-light);
} color: var(--white);
.icon {
display: inline-block;
width: 14px;
color: #000333;
} }
</style> </style>

View file

@ -27,11 +27,6 @@
settingsView.show() settingsView.show()
} }
const confirmDeleteComponent = component => {
componentToDelete = component
confirmDeleteDialog.show()
}
const lastPartOfName = c => (c ? last(c.split("/")) : "") const lastPartOfName = c => (c ? last(c.split("/")) : "")
</script> </script>
@ -42,7 +37,6 @@
<div class="pages-list-container"> <div class="pages-list-container">
<div class="nav-header"> <div class="nav-header">
<span class="navigator-title">Navigator</span> <span class="navigator-title">Navigator</span>
<div class="border-line" />
<span class="components-nav-page">Pages</span> <span class="components-nav-page">Pages</span>
</div> </div>
@ -52,12 +46,8 @@
</div> </div>
</div> </div>
<div class="border-line" />
<PageLayout layout={$store.pages[$store.currentPageName]} /> <PageLayout layout={$store.pages[$store.currentPageName]} />
<div class="border-line" />
<div class="components-list-container"> <div class="components-list-container">
<div class="nav-group-header"> <div class="nav-group-header">
<span class="components-nav-header" style="margin-top: 0;"> <span class="components-nav-header" style="margin-top: 0;">
@ -91,13 +81,6 @@
<NewScreen bind:this={newScreenPicker} /> <NewScreen bind:this={newScreenPicker} />
<SettingsView bind:this={settingsView} /> <SettingsView bind:this={settingsView} />
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component`}
okText="Delete Component"
onOk={() => store.deleteComponent(componentToDelete)} />
<style> <style>
button { button {
cursor: pointer; cursor: pointer;
@ -112,22 +95,12 @@
padding: 0; padding: 0;
} }
.root {
display: grid;
grid-template-columns: 275px 1fr 300px;
height: 100%;
width: 100%;
background: #fafafa;
}
@media only screen and (min-width: 1800px) {
.root { .root {
display: grid; display: grid;
grid-template-columns: 300px 1fr 300px; grid-template-columns: 300px 1fr 300px;
height: 100%; height: 100%;
width: 100%; width: 100%;
background: #fafafa; background: #fbfbfb;
}
} }
.ui-nav { .ui-nav {
@ -135,7 +108,6 @@
background-color: var(--white); background-color: var(--white);
height: calc(100vh - 49px); height: calc(100vh - 49px);
padding: 0; padding: 0;
overflow: scroll;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -229,10 +201,6 @@
letter-spacing: 1px; letter-spacing: 1px;
} }
.border-line {
border-bottom: 1px solid #d8d8d8;
}
.components-list-container { .components-list-container {
padding: 20px 0px 0 0; padding: 20px 0px 0 0;
} }

View file

@ -1,7 +1,7 @@
import Input from '../common/Input.svelte'; import Input from "../common/Input.svelte"
import OptionSelect from './OptionSelect.svelte'; import OptionSelect from "./OptionSelect.svelte"
import InputGroup from '../common/Inputs/InputGroup.svelte'; import InputGroup from "../common/Inputs/InputGroup.svelte"
import FlatButtonGroup from './FlatButtonGroup.svelte'; import FlatButtonGroup from "./FlatButtonGroup.svelte"
// import Colorpicker from "../common/Colorpicker.svelte" // import Colorpicker from "../common/Colorpicker.svelte"
/* /*
TODO: Allow for default values for all properties TODO: Allow for default values for all properties
@ -9,279 +9,290 @@ import FlatButtonGroup from './FlatButtonGroup.svelte';
export const layout = [ export const layout = [
{ {
label: 'Display', label: "Display",
key: 'display', key: "display",
control: OptionSelect, control: OptionSelect,
initialValue: 'Flex', initialValue: "Flex",
options: [ { label: 'Flex', value: 'flex' }, { label: 'Inline Flex', value: 'inline-flex' } ] options: [
{ label: "Flex", value: "flex" },
{ label: "Inline Flex", value: "inline-flex" },
],
}, },
{ {
label: 'Direction', label: "Direction",
key: 'flex-direction', key: "flex-direction",
control: FlatButtonGroup, control: FlatButtonGroup,
buttonProps: [ buttonProps: [
{ icon: 'ri-arrow-right-line', padding: '0px 5px', value: 'row' }, { icon: "ri-arrow-right-line", padding: "0px 5px", value: "row" },
{ icon: 'ri-arrow-left-line', padding: '0px 5px', value: 'rowReverse' }, { icon: "ri-arrow-left-line", padding: "0px 5px", value: "rowReverse" },
{ icon: 'ri-arrow-down-line', padding: '0px 5px', value: 'column' }, { icon: "ri-arrow-down-line", padding: "0px 5px", value: "column" },
{ {
icon: 'ri-arrow-up-line', icon: "ri-arrow-up-line",
padding: '0px 5px', padding: "0px 5px",
value: 'columnReverse' value: "columnReverse",
} },
] ],
}, },
{ {
label: 'Justify', label: "Justify",
key: 'justify-content', key: "justify-content",
control: OptionSelect, control: OptionSelect,
initialValue: 'Flex Start', initialValue: "Flex Start",
options: [ options: [
{ label: 'Flex Start', value: 'flex-start' }, { label: "Flex Start", value: "flex-start" },
{ label: 'Flex End', value: 'flex-end' }, { label: "Flex End", value: "flex-end" },
{ label: 'Center', value: 'center' }, { label: "Center", value: "center" },
{ label: 'Space Between', value: 'space-between' }, { label: "Space Between", value: "space-between" },
{ label: 'Space Around', value: 'space-around' }, { label: "Space Around", value: "space-around" },
{ label: 'Space Evenly', value: 'space-evenly' } { label: "Space Evenly", value: "space-evenly" },
] ],
}, },
{ {
label: 'Align', label: "Align",
key: 'align-items', key: "align-items",
control: OptionSelect, control: OptionSelect,
initialValue: 'Flex Start', initialValue: "Flex Start",
options: [ options: [
{ label: 'Flex Start', value: 'flex-start' }, { label: "Flex Start", value: "flex-start" },
{ label: 'Flex End', value: 'flex-end' }, { label: "Flex End", value: "flex-end" },
{ label: 'Center', value: 'center' }, { label: "Center", value: "center" },
{ label: 'Baseline', value: 'baseline' }, { label: "Baseline", value: "baseline" },
{ label: 'Stretch', value: 'stretch' } { label: "Stretch", value: "stretch" },
] ],
}, },
{ {
label: 'Wrap', label: "Wrap",
key: 'flex-wrap', key: "flex-wrap",
control: OptionSelect, control: OptionSelect,
options: [ { label: 'wrap', value: 'wrap' }, { label: 'no wrap', value: 'noWrap' } ] options: [
} { label: "wrap", value: "wrap" },
]; { label: "no wrap", value: "noWrap" },
],
},
]
const spacingMeta = [ { placeholder: 'T' }, { placeholder: 'R' }, { placeholder: 'B' }, { placeholder: 'L' } ]; const spacingMeta = [
{ placeholder: "T" },
{ placeholder: "R" },
{ placeholder: "B" },
{ placeholder: "L" },
]
export const spacing = [ export const spacing = [
{ {
label: 'Margin', label: "Margin",
key: 'margin', key: "margin",
control: InputGroup, control: InputGroup,
meta: spacingMeta, meta: spacingMeta,
suffix: 'px', suffix: "px",
defaultValue: [ '0', '0', '0', '0' ] defaultValue: ["0", "0", "0", "0"],
}, },
{ {
label: 'Padding', label: "Padding",
key: 'padding', key: "padding",
control: InputGroup, control: InputGroup,
meta: spacingMeta, meta: spacingMeta,
suffix: 'px', suffix: "px",
defaultValue: [ '0', '0', '0', '0' ] defaultValue: ["0", "0", "0", "0"],
} },
]; ]
export const size = [ export const size = [
{ {
label: 'Width', label: "Width",
key: 'width', key: "width",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Height', label: "Height",
key: 'height', key: "height",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Min W', label: "Min W",
key: 'min-width', key: "min-width",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Min H', label: "Min H",
key: 'min-height', key: "min-height",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Max W', label: "Max W",
key: 'max-width', key: "max-width",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Max H', label: "Max H",
key: 'max-height', key: "max-height",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
} },
]; ]
export const position = [ export const position = [
{ {
label: 'Position', label: "Position",
key: 'position', key: "position",
control: OptionSelect, control: OptionSelect,
initialValue: 'Wrap', initialValue: "Wrap",
options: [ options: [
{ label: 'Static', value: 'static' }, { label: "Static", value: "static" },
{ label: 'Relative', value: 'relative' }, { label: "Relative", value: "relative" },
{ label: 'Fixed', value: 'fixed' }, { label: "Fixed", value: "fixed" },
{ label: 'Absolute', value: 'absolute' }, { label: "Absolute", value: "absolute" },
{ label: 'Sticky', value: 'sticky' } { label: "Sticky", value: "sticky" },
],
},
{
label: "Top",
key: "top",
control: Input,
placeholder: "px",
width: "48px",
textAlign: "center",
},
{
label: "Right",
key: "right",
control: Input,
placeholder: "px",
width: "48px",
textAlign: "center",
},
{
label: "Bottom",
key: "bottom",
control: Input,
placeholder: "px",
width: "48px",
textAlign: "center",
},
{
label: "Left",
key: "left",
control: Input,
placeholder: "px",
width: "48px",
textAlign: "center",
},
{
label: "Z-index",
key: "z-index",
control: Input,
placeholder: "num",
width: "48px",
textAlign: "center",
},
] ]
},
{
label: 'Top',
key: 'top',
control: Input,
placeholder: 'px',
width: '48px',
textAlign: 'center'
},
{
label: 'Right',
key: 'right',
control: Input,
placeholder: 'px',
width: '48px',
textAlign: 'center'
},
{
label: 'Bottom',
key: 'bottom',
control: Input,
placeholder: 'px',
width: '48px',
textAlign: 'center'
},
{
label: 'Left',
key: 'left',
control: Input,
placeholder: 'px',
width: '48px',
textAlign: 'center'
},
{
label: 'Z-index',
key: 'z-index',
control: Input,
placeholder: 'num',
width: '48px',
textAlign: 'center'
}
];
export const typography = [ export const typography = [
{ {
label: 'Font', label: "Font",
key: 'font-family', key: "font-family",
control: OptionSelect, control: OptionSelect,
defaultValue: 'initial', defaultValue: "initial",
options: [ options: [
'initial', "initial",
'Arial', "Arial",
'Arial Black', "Arial Black",
'Cursive', "Cursive",
'Courier', "Courier",
'Comic Sans MS', "Comic Sans MS",
'Helvetica', "Helvetica",
'Impact', "Impact",
'Inter', "Inter",
'Lucida Sans Unicode', "Lucida Sans Unicode",
'Open Sans', "Open Sans",
'Playfair', "Playfair",
'Roboto', "Roboto",
'Roboto Mono', "Roboto Mono",
'Times New Roman', "Times New Roman",
'Verdana' "Verdana",
], ],
styleBindingProperty: 'font-family' styleBindingProperty: "font-family",
}, },
{ {
label: 'Weight', label: "Weight",
key: 'font-weight', key: "font-weight",
control: OptionSelect, control: OptionSelect,
options: [ 'normal', 'bold', 'bolder', 'lighter' ] options: ["normal", "bold", "bolder", "lighter"],
}, },
{ {
label: 'size', label: "size",
key: 'font-size', key: "font-size",
defaultValue: '', defaultValue: "",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Line H', label: "Line H",
key: 'line-height', key: "line-height",
control: Input, control: Input,
placeholder: 'lh', placeholder: "lh",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Color', label: "Color",
key: 'color', key: "color",
control: Input, control: Input,
placeholder: "hex", placeholder: "hex",
}, },
{ {
label: 'align', label: "align",
key: 'text-align', key: "text-align",
control: FlatButtonGroup, control: FlatButtonGroup,
buttonProps: [ buttonProps: [
{ icon: 'ri-align-left', padding: '0px 5px', value: 'left' }, { icon: "ri-align-left", padding: "0px 5px", value: "left" },
{ icon: 'ri-align-center', padding: '0px 5px', value: 'center' }, { icon: "ri-align-center", padding: "0px 5px", value: "center" },
{ icon: 'ri-align-right', padding: '0px 5px', value: 'right' }, { icon: "ri-align-right", padding: "0px 5px", value: "right" },
{ icon: 'ri-align-justify', padding: '0px 5px', value: 'justify' } { icon: "ri-align-justify", padding: "0px 5px", value: "justify" },
] ],
}, },
{ {
label: 'transform', label: "transform",
key: 'text-transform', key: "text-transform",
control: FlatButtonGroup, control: FlatButtonGroup,
buttonProps: [ buttonProps: [
{ text: 'BB', padding: '0px 5px', fontWeight: 500, value: 'uppercase' }, { text: "BB", padding: "0px 5px", fontWeight: 500, value: "uppercase" },
{ text: 'Bb', padding: '0px 5px', fontWeight: 500, value: 'capitalize' }, { text: "Bb", padding: "0px 5px", fontWeight: 500, value: "capitalize" },
{ text: 'bb', padding: '0px 5px', fontWeight: 500, value: 'lowercase' }, { text: "bb", padding: "0px 5px", fontWeight: 500, value: "lowercase" },
{ {
text: '&times;', text: "&times;",
padding: '0px 5px', padding: "0px 5px",
fontWeight: 500, fontWeight: 500,
value: 'none' value: "none",
}
]
}, },
{ label: 'style', key: 'font-style', control: Input } ],
]; },
{ label: "style", key: "font-style", control: Input },
]
export const background = [ export const background = [
{ {
label: 'Background', label: "Background",
key: 'background', key: "background",
control: Input, control: Input,
}, },
{ {
@ -290,54 +301,65 @@ export const background = [
control: Input, control: Input,
placeholder: "src", placeholder: "src",
}, },
]; ]
export const border = [ export const border = [
{ {
label: 'Radius', label: "Radius",
key: 'border-radius', key: "border-radius",
control: Input, control: Input,
width: '48px', width: "48px",
placeholder: 'px', placeholder: "px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Width', label: "Width",
key: 'border-width', key: "border-width",
control: Input, control: Input,
width: '48px', width: "48px",
placeholder: 'px', placeholder: "px",
textAlign: 'center' textAlign: "center",
}, //custom }, //custom
{ {
label: 'Color', label: "Color",
key: 'border-color', key: "border-color",
control: Input control: Input,
}, },
{ {
label: 'Style', label: "Style",
key: 'border-style', key: "border-style",
control: OptionSelect, control: OptionSelect,
options: [ 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset' ] options: [
} "none",
]; "hidden",
"dotted",
"dashed",
"solid",
"double",
"groove",
"ridge",
"inset",
"outset",
],
},
]
export const effects = [ export const effects = [
{ {
label: 'Opacity', label: "Opacity",
key: 'opacity', key: "opacity",
control: Input, control: Input,
width: '48px', width: "48px",
textAlign: 'center', textAlign: "center",
placeholder: '%' placeholder: "%",
}, },
{ {
label: 'Rotate', label: "Rotate",
key: 'transform', key: "transform",
control: Input, control: Input,
width: '48px', width: "48px",
textAlign: 'center', textAlign: "center",
placeholder: 'deg' placeholder: "deg",
}, //needs special control }, //needs special control
{ {
label: "Shadow", label: "Shadow",
@ -345,44 +367,44 @@ export const effects = [
control: InputGroup, control: InputGroup,
meta: [{ placeholder: "X" }, { placeholder: "Y" }, { placeholder: "B" }], meta: [{ placeholder: "X" }, { placeholder: "Y" }, { placeholder: "B" }],
}, },
]; ]
export const transitions = [ export const transitions = [
{ {
label: 'Property', label: "Property",
key: 'transition-property', key: "transition-property",
control: OptionSelect, control: OptionSelect,
options: [ options: [
'None', "None",
'All', "All",
'Background Color', "Background Color",
'Color', "Color",
'Font Size', "Font Size",
'Font Weight', "Font Weight",
'Height', "Height",
'Margin', "Margin",
'Opacity', "Opacity",
'Padding', "Padding",
'Rotate', "Rotate",
'Shadow', "Shadow",
'Width' "Width",
] ],
}, },
{ {
label: 'Duration', label: "Duration",
key: 'transition-timing-function', key: "transition-timing-function",
control: Input, control: Input,
width: '48px', width: "48px",
textAlign: 'center', textAlign: "center",
placeholder: 'sec' placeholder: "sec",
}, },
{ {
label: 'Ease', label: "Ease",
key: 'transition-ease', key: "transition-ease",
control: OptionSelect, control: OptionSelect,
options: [ 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out' ] options: ["linear", "ease", "ease-in", "ease-out", "ease-in-out"],
} },
]; ]
export const all = { export const all = {
layout, layout,
@ -393,15 +415,15 @@ export const all = {
background, background,
border, border,
effects, effects,
transitions transitions,
}; }
export function excludeProps(props, propsToExclude) { export function excludeProps(props, propsToExclude) {
const modifiedProps = {}; const modifiedProps = {}
for (const prop in props) { for (const prop in props) {
if (!propsToExclude.includes(prop)) { if (!propsToExclude.includes(prop)) {
modifiedProps[prop] = props[prop]; modifiedProps[prop] = props[prop]
} }
} }
return modifiedProps; return modifiedProps
} }

View file

@ -88,6 +88,8 @@ html, body {
#app { #app {
height: 100%; height: 100%;
box-sizing: border-box;
overflow-y: hidden;
} }
h1 { h1 {

View file

@ -81,7 +81,7 @@
<style> <style>
.root { .root {
height: 100%; min-height: 100%;
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -31,19 +31,10 @@
margin: 20px 40px; margin: 20px 40px;
} }
.nav {
overflow: auto;
flex: 0 1 auto;
width: 275px;
height: 100%;
}
@media only screen and (min-width: 1800px) {
.nav { .nav {
overflow: auto; overflow: auto;
flex: 0 1 auto; flex: 0 1 auto;
width: 300px; width: 300px;
height: 100%; height: 100%;
} }
}
</style> </style>

View file

@ -2,18 +2,17 @@
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { onMount } from "svelte" import { onMount } from "svelte"
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
import ComponentsHierarchyChildren from "components/userInterface/ComponentsHierarchyChildren.svelte" import ComponentsHierarchyChildren from "components/userInterface/ComponentsHierarchyChildren.svelte"
import PageLayout from "components/userInterface/PageLayout.svelte"
import PagesList from "components/userInterface/PagesList.svelte"
import IconButton from "components/common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
import NewScreen from "components/userInterface/NewScreen.svelte"
import CurrentItemPreview from "components/userInterface/AppPreview" import CurrentItemPreview from "components/userInterface/AppPreview"
import PageView from "components/userInterface/PageView.svelte" import PageView from "components/userInterface/PageView.svelte"
import ComponentsPaneSwitcher from "components/userInterface/ComponentsPaneSwitcher.svelte" import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
import Switcher from "components/common/Switcher.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last } from "lodash/fp" import { last } from "lodash/fp"
import { AddIcon } from "components/common/Icons" import { AddIcon } from "components/common/Icons"
import FrontendNavigatePane from "components/userInterface/FrontendNavigatePane.svelte"
$: instances = $store.appInstances $: instances = $store.appInstances
@ -27,23 +26,15 @@
} }
}) })
let newScreenPicker
let confirmDeleteDialog let confirmDeleteDialog
let componentToDelete = "" let componentToDelete = ""
const newScreen = () => {
newScreenPicker.show()
}
let settingsView let settingsView
const settings = () => { const settings = () => {
settingsView.show() settingsView.show()
} }
const confirmDeleteComponent = component => { let leftNavSwitcher
componentToDelete = component
confirmDeleteDialog.show()
}
const lastPartOfName = c => (c ? last(c.split("/")) : "") const lastPartOfName = c => (c ? last(c.split("/")) : "")
</script> </script>
@ -52,92 +43,42 @@
<div class="ui-nav"> <div class="ui-nav">
<div class="pages-list-container"> <Switcher bind:this={leftNavSwitcher} tabs={['Navigate', 'Add']}>
<div class="nav-header"> <div slot="0">
<span class="navigator-title">Navigate</span> <FrontendNavigatePane />
<span class="components-nav-page">Pages</span>
</div>
<div class="nav-items-container">
<PagesList />
</div>
</div>
<div class="border-line" />
<PageLayout layout={$store.pages[$store.currentPageName]} />
<div class="border-line" />
<div class="components-list-container">
<div class="nav-group-header">
<span class="components-nav-header" style="margin-top: 0;">
Screens
</span>
<div>
<button on:click={newScreen}>
<AddIcon />
</button>
</div>
</div>
<div class="nav-items-container">
<ComponentsHierarchy screens={$store.screens} />
</div> </div>
<div slot="1">
<ComponentSelectionList toggleTab={leftNavSwitcher.selectTab} />
</div> </div>
</Switcher>
</div> </div>
<div class="preview-pane"> <div class="preview-pane">
{#if $store.currentPageName && $store.currentPageName.length > 0}
<CurrentItemPreview /> <CurrentItemPreview />
{/if}
</div> </div>
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'} {#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'}
<div class="components-pane"> <div class="components-pane">
<ComponentsPaneSwitcher /> <ComponentPropertiesPanel />
</div> </div>
{/if} {/if}
</div> </div>
<NewScreen bind:this={newScreenPicker} />
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component`}
okText="Delete Component"
onOk={() => store.deleteComponent(componentToDelete)} />
<slot /> <slot />
<style> <style>
button {
cursor: pointer;
outline: none;
border: none;
border-radius: 5px;
width: 20px;
padding-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
.root {
display: grid;
grid-template-columns: 275px 1fr 275px;
width: 100%;
background: var(--grey-light);
}
@media only screen and (min-width: 1800px) {
.root { .root {
display: grid; display: grid;
grid-template-columns: 300px 1fr 300px; grid-template-columns: 300px 1fr 300px;
width: 100%; width: 100%;
background: var(--grey-light); background: var(--grey-light);
} flex: 1;
min-height: 0;
align-items: stretch;
} }
.ui-nav { .ui-nav {
@ -145,9 +86,9 @@
background-color: var(--white); background-color: var(--white);
height: calc(100vh - 69px); height: calc(100vh - 69px);
padding: 0; padding: 0;
overflow: scroll;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
z-index: 5;
} }
.preview-pane { .preview-pane {
@ -201,6 +142,7 @@
font-weight: bold; font-weight: bold;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
min-height: 0;
} }
.nav-group-header > div:nth-child(1) { .nav-group-header > div:nth-child(1) {
@ -210,13 +152,6 @@
margin-right: 5px; margin-right: 5px;
} }
.nav-group-header > span:nth-child(3) {
margin-left: 5px;
vertical-align: bottom;
grid-column-start: title;
margin-top: auto;
}
.nav-group-header > div:nth-child(3) { .nav-group-header > div:nth-child(3) {
vertical-align: bottom; vertical-align: bottom;
grid-column-start: button; grid-column-start: button;
@ -227,19 +162,4 @@
.nav-group-header > div:nth-child(3):hover { .nav-group-header > div:nth-child(3):hover {
color: var(--primary75); color: var(--primary75);
} }
.navigator-title {
font-size: 18px;
color: var(--ink);
font-weight: bold;
padding: 0 20px 20px 20px;
}
.border-line {
border-bottom: 1px solid #d8d8d8;
}
.components-list-container {
padding: 20px 0px 0 0;
}
</style> </style>

View file

@ -117,15 +117,6 @@
</Modal> </Modal>
<style> <style>
.root {
display: grid;
grid-template-columns: 275px 1fr;
height: 100%;
width: 100%;
background: var(--grey-light);
}
@media only screen and (min-width: 1800px) {
.root { .root {
display: grid; display: grid;
grid-template-columns: 300px 1fr; grid-template-columns: 300px 1fr;
@ -133,7 +124,6 @@
width: 100%; width: 100%;
background: var(--grey-light); background: var(--grey-light);
} }
}
.main { .main {
grid-column: 2; grid-column: 2;
@ -207,7 +197,7 @@
.nav-item-title { .nav-item-title {
font-size: 14px; font-size: 14px;
color: var(--ink); color: var(--ink);
font-weight: 500; font-weight: 400;
margin-left: 12px; margin-left: 12px;
} }

View file

@ -1,14 +1,10 @@
import { import {
generate_css, generate_css,
generate_screen_css, generate_screen_css,
generate_array_styles
} from "../src/builderStore/generate_css.js" } from "../src/builderStore/generate_css.js"
describe("generate_css", () => { describe("generate_css", () => {
test("Check how partially empty arrays are handled", () => {
expect(["", "5", "", ""].map(generate_array_styles)).toEqual(["0px", "5px", "0px", "0px"])
})
test("Check how array styles are output", () => { test("Check how array styles are output", () => {
expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;") expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;")

View file

@ -13,3 +13,6 @@ JWT_SECRET={{cookieKey1}}
# port to run http server on # port to run http server on
PORT=4001 PORT=4001
# error level for koa-pino
LOG_LEVEL=error

View file

@ -44,7 +44,6 @@
"@budibase/client": "^0.0.32", "@budibase/client": "^0.0.32",
"@budibase/core": "^0.0.32", "@budibase/core": "^0.0.32",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"ajv": "^6.12.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"electron-is-dev": "^1.2.0", "electron-is-dev": "^1.2.0",
@ -66,6 +65,7 @@
"squirrelly": "^7.5.0", "squirrelly": "^7.5.0",
"tar-fs": "^2.0.0", "tar-fs": "^2.0.0",
"uuid": "^3.3.2", "uuid": "^3.3.2",
"validate.js": "^0.13.1",
"yargs": "^13.2.4", "yargs": "^13.2.4",
"zlib": "^1.0.5" "zlib": "^1.0.5"
}, },

View file

@ -5,3 +5,4 @@ process.env.JWT_SECRET = "test-jwtsecret"
process.env.CLIENT_ID = "test-client-id" process.env.CLIENT_ID = "test-client-id"
process.env.BUDIBASE_DIR = tmpdir("budibase-unittests") process.env.BUDIBASE_DIR = tmpdir("budibase-unittests")
process.env.ADMIN_SECRET = "test-admin-secret" process.env.ADMIN_SECRET = "test-admin-secret"
process.env.LOG_LEVEL = "silent"

View file

@ -1,9 +1,7 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const Ajv = require("ajv") const validateJs = require("validate.js")
const newid = require("../../db/newid") const newid = require("../../db/newid")
const ajv = new Ajv()
exports.save = async function(ctx) { exports.save = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId) const db = new CouchDB(ctx.params.instanceId)
const record = ctx.request.body const record = ctx.request.body
@ -13,18 +11,18 @@ exports.save = async function(ctx) {
record._id = newid() record._id = newid()
} }
// validation with ajv
const model = await db.get(record.modelId) const model = await db.get(record.modelId)
const validate = ajv.compile({
properties: model.schema,
})
const valid = validate(record)
if (!valid) { const validateResult = await validate({
record,
model,
})
if (!validateResult.valid) {
ctx.status = 400 ctx.status = 400
ctx.body = { ctx.body = {
status: 400, status: 400,
errors: validate.errors, errors: validateResult.errors,
} }
return return
} }
@ -84,3 +82,29 @@ exports.destroy = async function(ctx) {
} }
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId) ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId)
} }
exports.validate = async function(ctx) {
const errors = await validate({
instanceId: ctx.params.instanceId,
modelId: ctx.params.modelId,
record: ctx.request.body,
})
ctx.status = 200
ctx.body = errors
}
async function validate({ instanceId, modelId, record, model }) {
if (!model) {
const db = new CouchDB(instanceId)
model = await db.get(modelId)
}
const errors = {}
for (let fieldName in model.schema) {
const res = validateJs.single(
record[fieldName],
model.schema[fieldName].constraints
)
if (res) errors[fieldName] = res
}
return { valid: Object.keys(errors).length === 0, errors }
}

View file

@ -28,6 +28,11 @@ router
authorized(WRITE_MODEL, ctx => ctx.params.modelId), authorized(WRITE_MODEL, ctx => ctx.params.modelId),
recordController.save recordController.save
) )
.post(
"/api/:instanceId/:modelId/records/validate",
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
recordController.validate
)
.delete( .delete(
"/api/:instanceId/:modelId/records/:recordId/:revId", "/api/:instanceId/:modelId/records/:recordId/:revId",
authorized(WRITE_MODEL, ctx => ctx.params.modelId), authorized(WRITE_MODEL, ctx => ctx.params.modelId),

View file

@ -27,7 +27,12 @@ exports.createModel = async (request, instanceId, model) => {
type: "model", type: "model",
key: "name", key: "name",
schema: { schema: {
name: { type: "string" }, name: {
type: "text",
constraints: {
type: "string",
},
},
}, },
} }

View file

@ -25,8 +25,6 @@ describe("/records", () => {
server.close(); server.close();
}) })
describe("save, load, update, delete", () => {
beforeEach(async () => { beforeEach(async () => {
instance = await createInstance(request, app._id) instance = await createInstance(request, app._id)
model = await createModel(request, instance._id) model = await createModel(request, instance._id)
@ -37,6 +35,8 @@ describe("/records", () => {
} }
}) })
describe("save, load, update, delete", () => {
const createRecord = async r => const createRecord = async r =>
await request await request
.post(`/api/${instance._id}/${model._id}/records`) .post(`/api/${instance._id}/${model._id}/records`)
@ -119,4 +119,31 @@ describe("/records", () => {
.expect(404) .expect(404)
}) })
}) })
describe("validate", () => {
it("should return no errors on valid record", async () => {
const result = await request
.post(`/api/${instance._id}/${model._id}/records/validate`)
.send({ name: "ivan" })
.set(defaultHeaders)
.expect('Content-Type', /json/)
.expect(200)
expect(result.body.valid).toBe(true)
expect(Object.keys(result.body.errors)).toEqual([])
})
it("should errors on invalid record", async () => {
const result = await request
.post(`/api/${instance._id}/${model._id}/records/validate`)
.send({ name: 1 })
.set(defaultHeaders)
.expect('Content-Type', /json/)
.expect(200)
expect(result.body.valid).toBe(false)
expect(Object.keys(result.body.errors)).toEqual(["name"])
})
})
}) })

View file

@ -15,7 +15,7 @@ app.use(
prettyPrint: { prettyPrint: {
levelFirst: true, levelFirst: true,
}, },
level: process.env.NODE_ENV === "jest" ? "silent" : "info", level: env.LOG_LEVEL || "error",
}) })
) )

View file

@ -194,6 +194,20 @@
lodash "^4.17.13" lodash "^4.17.13"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@budibase/client@^0.0.32":
version "0.0.32"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.0.32.tgz#76d9f147563a0bf939eae7f32ce75b2a527ba496"
integrity sha512-jmCCLn0CUoQbL6h623S5IqK6+GYLqX3WzUTZInSb1SCBOM3pI0eLP5HwTR6s7r42SfD0v9jTWRdyTnHiElNj8A==
dependencies:
"@nx-js/compiler-util" "^2.0.0"
bcryptjs "^2.4.3"
deep-equal "^2.0.1"
lodash "^4.17.15"
lunr "^2.3.5"
regexparam "^1.3.0"
shortid "^2.2.8"
svelte "^3.9.2"
"@budibase/core@^0.0.32": "@budibase/core@^0.0.32":
version "0.0.32" version "0.0.32"
resolved "https://registry.yarnpkg.com/@budibase/core/-/core-0.0.32.tgz#c5d9ab869c5e9596a1ac337aaf041e795b1cc7fa" resolved "https://registry.yarnpkg.com/@budibase/core/-/core-0.0.32.tgz#c5d9ab869c5e9596a1ac337aaf041e795b1cc7fa"
@ -655,7 +669,7 @@ ajv-keywords@^3.4.1:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2: ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0:
version "6.12.2" version "6.12.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
@ -825,6 +839,11 @@ array-equal@^1.0.0:
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=
array-filter@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
array-unique@^0.3.2: array-unique@^0.3.2:
version "0.3.2" version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
@ -897,6 +916,13 @@ atomic-sleep@^1.0.0:
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==
dependencies:
array-filter "^1.0.0"
aws-sign2@~0.7.0: aws-sign2@~0.7.0:
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@ -1612,6 +1638,26 @@ decompress-response@^3.3.0:
dependencies: dependencies:
mimic-response "^1.0.0" mimic-response "^1.0.0"
deep-equal@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.3.tgz#cad1c15277ad78a5c01c49c2dee0f54de8a6a7b0"
integrity sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==
dependencies:
es-abstract "^1.17.5"
es-get-iterator "^1.1.0"
is-arguments "^1.0.4"
is-date-object "^1.0.2"
is-regex "^1.0.5"
isarray "^2.0.5"
object-is "^1.1.2"
object-keys "^1.1.1"
object.assign "^4.1.0"
regexp.prototype.flags "^1.3.0"
side-channel "^1.0.2"
which-boxed-primitive "^1.0.1"
which-collection "^1.0.1"
which-typed-array "^1.1.2"
deep-equal@~1.0.1: deep-equal@~1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@ -1965,7 +2011,7 @@ error-inject@^1.0.0:
resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37" resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37"
integrity sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc= integrity sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=
es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5:
version "1.17.5" version "1.17.5"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9"
integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==
@ -1982,6 +2028,19 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
string.prototype.trimleft "^2.1.1" string.prototype.trimleft "^2.1.1"
string.prototype.trimright "^2.1.1" string.prototype.trimright "^2.1.1"
es-get-iterator@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8"
integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==
dependencies:
es-abstract "^1.17.4"
has-symbols "^1.0.1"
is-arguments "^1.0.4"
is-map "^2.0.1"
is-set "^2.0.1"
is-string "^1.0.5"
isarray "^2.0.5"
es-to-primitive@^1.2.1: es-to-primitive@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@ -2967,11 +3026,21 @@ is-accessor-descriptor@^1.0.0:
dependencies: dependencies:
kind-of "^6.0.0" kind-of "^6.0.0"
is-arguments@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
is-arrayish@^0.2.1: is-arrayish@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
is-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4"
integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==
is-binary-path@~2.1.0: is-binary-path@~2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@ -2979,6 +3048,11 @@ is-binary-path@~2.1.0:
dependencies: dependencies:
binary-extensions "^2.0.0" binary-extensions "^2.0.0"
is-boolean-object@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e"
integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==
is-buffer@^1.1.5: is-buffer@^1.1.5:
version "1.1.6" version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@ -3015,7 +3089,7 @@ is-data-descriptor@^1.0.0:
dependencies: dependencies:
kind-of "^6.0.0" kind-of "^6.0.0"
is-date-object@^1.0.1: is-date-object@^1.0.1, is-date-object@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
@ -3090,11 +3164,21 @@ is-installed-globally@^0.3.1:
global-dirs "^2.0.1" global-dirs "^2.0.1"
is-path-inside "^3.0.1" is-path-inside "^3.0.1"
is-map@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1"
integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==
is-npm@^4.0.0: is-npm@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==
is-number-object@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
is-number@^3.0.0: is-number@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@ -3131,11 +3215,21 @@ is-regex@^1.0.5:
dependencies: dependencies:
has "^1.0.3" has "^1.0.3"
is-set@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43"
integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==
is-stream@^1.1.0: is-stream@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
is-string@^1.0.4, is-string@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
is-symbol@^1.0.2: is-symbol@^1.0.2:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
@ -3152,11 +3246,31 @@ is-type-of@^1.0.0:
is-class-hotfix "~0.0.6" is-class-hotfix "~0.0.6"
isstream "~0.1.2" isstream "~0.1.2"
is-typed-array@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d"
integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==
dependencies:
available-typed-arrays "^1.0.0"
es-abstract "^1.17.4"
foreach "^2.0.5"
has-symbols "^1.0.1"
is-typedarray@^1.0.0, is-typedarray@~1.0.0: is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
is-weakmap@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
is-weakset@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83"
integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==
is-windows@^1.0.2: is-windows@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@ -3182,6 +3296,11 @@ isarray@1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isbinaryfile@^4.0.6: isbinaryfile@^4.0.6:
version "4.0.6" version "4.0.6"
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
@ -4589,6 +4708,14 @@ object-inspect@^1.7.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
object-is@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6"
integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@ -5310,6 +5437,19 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2" extend-shallow "^3.0.2"
safe-regex "^1.1.0" safe-regex "^1.1.0"
regexp.prototype.flags@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
regexparam@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f"
integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==
regexpp@^2.0.1: regexpp@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
@ -5638,6 +5778,14 @@ shortid@^2.2.8:
dependencies: dependencies:
nanoid "^2.1.0" nanoid "^2.1.0"
side-channel@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947"
integrity sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==
dependencies:
es-abstract "^1.17.0-next.1"
object-inspect "^1.7.0"
signal-exit@^3.0.0, signal-exit@^3.0.2: signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@ -6046,6 +6194,11 @@ supports-color@^7.1.0:
dependencies: dependencies:
has-flag "^4.0.0" has-flag "^4.0.0"
svelte@^3.9.2:
version "3.23.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.23.0.tgz#bbcd6887cf588c24a975b14467455abfff9acd3f"
integrity sha512-cnyd96bK/Nw5DnYuB1hzm5cl6+I1fpmdKOteA7KLzU9KGLsLmvWsSkSKbcntzODCLmSySN3HjcgTHRo6/rJNTw==
symbol-tree@^3.2.2: symbol-tree@^3.2.2:
version "3.2.4" version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@ -6458,6 +6611,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0" spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0" spdx-expression-parse "^3.0.0"
validate.js@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/validate.js/-/validate.js-0.13.1.tgz#b58bfac04a0f600a340f62e5227e70d95971e92a"
integrity sha512-PnFM3xiZ+kYmLyTiMgTYmU7ZHkjBZz2/+F0DaALc/uUtVzdCt1wAosvYJ5hFQi/hz8O4zb52FQhHZRC+uVkJ+g==
vary@^1.1.2: vary@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@ -6526,11 +6684,44 @@ whatwg-url@^7.0.0:
tr46 "^1.0.1" tr46 "^1.0.1"
webidl-conversions "^4.0.2" webidl-conversions "^4.0.2"
which-boxed-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1"
integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==
dependencies:
is-bigint "^1.0.0"
is-boolean-object "^1.0.0"
is-number-object "^1.0.3"
is-string "^1.0.4"
is-symbol "^1.0.2"
which-collection@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
dependencies:
is-map "^2.0.1"
is-set "^2.0.1"
is-weakmap "^2.0.1"
is-weakset "^2.0.1"
which-module@^2.0.0: which-module@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which-typed-array@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2"
integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==
dependencies:
available-typed-arrays "^1.0.2"
es-abstract "^1.17.5"
foreach "^2.0.5"
function-bind "^1.1.1"
has-symbols "^1.0.1"
is-typed-array "^1.1.3"
which@^1.2.9, which@^1.3.0: which@^1.2.9, which@^1.3.0:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"