1
0
Fork 0
mirror of synced 2024-07-05 22:40:39 +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) =>
const [ main_screens, unauth_screens ] = await Promise.all([ name.startsWith("##") ? getBuiltin(name) : state.components[name]
api.get(`/_builder/api/${pkg.application._id}/pages/main/screens`).then((r) => r.json()),
api.get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`).then((r) => r.json())
]);
pkg.pages = { const setPackage = (store, initial) => async pkg => {
main: { const [main_screens, unauth_screens] = await Promise.all([
...pkg.pages.main, api
_screens: Object.values(main_screens) .get(`/_builder/api/${pkg.application._id}/pages/main/screens`)
}, .then(r => r.json()),
unauthenticated: { api
...pkg.pages.unauthenticated, .get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`)
_screens: Object.values(unauth_screens) .then(r => r.json()),
} ])
};
initial.libraries = pkg.application.componentLibraries; pkg.pages = {
initial.components = await fetchComponentLibDefinitions(pkg.application._id); main: {
initial.appname = pkg.application.name; ...pkg.pages.main,
initial.appId = pkg.application._id; _screens: Object.values(main_screens),
initial.pages = pkg.pages; },
initial.hasAppPackage = true; unauthenticated: {
initial.screens = values(pkg.screens); ...pkg.pages.unauthenticated,
initial.builtins = [ getBuiltin('##builtin/screenslot') ]; _screens: Object.values(unauth_screens),
initial.appInstances = pkg.application.instances; },
initial.appId = pkg.application._id; }
store.set(initial); initial.libraries = pkg.application.componentLibraries
return initial; initial.components = await fetchComponentLibDefinitions(pkg.application._id)
}; initial.appname = pkg.application.name
initial.appId = pkg.application._id
initial.pages = pkg.pages
initial.hasAppPackage = true
initial.screens = values(pkg.screens)
initial.builtins = [getBuiltin("##builtin/screenslot")]
initial.appInstances = pkg.application.instances
initial.appId = pkg.application._id
const saveScreen = (store) => (screen) => { store.set(initial)
store.update((state) => { return initial
return _saveScreen(store, state, screen); }
});
}; const saveScreen = store => screen => {
store.update(state => {
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) => { const newScreen = {
store.update((state) => { name: screenName || "",
const rootComponent = state.components[layoutComponentName]; description: "",
url: "",
_css: "",
uiFunctions: "",
props: createProps(rootComponent).props,
}
const newScreen = { newScreen.route = route
name: screenName || '', state.currentPreviewItem = newScreen
description: '', state.currentComponentInfo = newScreen.props
url: '', state.currentFrontEndType = "screen"
_css: '',
uiFunctions: '',
props: createProps(rootComponent).props
};
newScreen.route = route; _saveScreen(store, state, newScreen)
state.currentPreviewItem = newScreen;
state.currentComponentInfo = newScreen.props;
state.currentFrontEndType = 'screen';
_saveScreen(store, state, newScreen); return state
})
}
return state; const setCurrentScreen = store => screenName => {
}); store.update(s => {
}; const screen = getExactComponent(s.screens, screenName)
screen._css = generate_screen_css([screen.props])
s.currentPreviewItem = screen
s.currentFrontEndType = "screen"
s.currentView = "detail"
const setCurrentScreen = (store) => (screenName) => { const safeProps = makePropsSafe(
store.update((s) => { s.components[screen.props._component],
const screen = getExactComponent(s.screens, screenName); screen.props
screen._css = generate_screen_css([ screen.props ]); )
s.currentPreviewItem = screen; screen.props = safeProps
s.currentFrontEndType = 'screen'; s.currentComponentInfo = safeProps
s.currentView = 'detail'; setCurrentPageFunctions(s)
return s
})
}
const safeProps = makePropsSafe(s.components[screen.props._component], screen.props); const deleteScreen = store => name => {
screen.props = safeProps; store.update(s => {
s.currentComponentInfo = safeProps; const components = s.components.filter(c => c.name !== name)
setCurrentPageFunctions(s); const screens = s.screens.filter(c => c.name !== name)
return s;
});
};
const deleteScreen = (store) => (name) => { s.components = components
store.update((s) => { s.screens = screens
const components = s.components.filter((c) => c.name !== name); if (s.currentPreviewItem.name === name) {
const screens = s.screens.filter((c) => c.name !== name); s.currentPreviewItem = null
s.currentFrontEndType = ""
}
s.components = components; api.delete(`/_builder/api/${s.appId}/screen/${name}`)
s.screens = screens;
if (s.currentPreviewItem.name === name) {
s.currentPreviewItem = null;
s.currentFrontEndType = '';
}
api.delete(`/_builder/api/${s.appId}/screen/${name}`); return s
})
}
return s; const renameScreen = store => (oldname, newname) => {
}); store.update(s => {
}; const { screens, pages, error, changedScreens } = rename(
s.pages,
s.screens,
oldname,
newname
)
const renameScreen = (store) => (oldname, newname) => { if (error) {
store.update((s) => { // should really do something with this
const { screens, pages, error, changedScreens } = rename(s.pages, s.screens, oldname, newname); return s
}
if (error) { s.screens = screens
// should really do something with this s.pages = pages
return s; if (s.currentPreviewItem.name === oldname)
} s.currentPreviewItem.name = newname
s.screens = screens; const saveAllChanged = async () => {
s.pages = pages; for (let screenName of changedScreens) {
if (s.currentPreviewItem.name === oldname) s.currentPreviewItem.name = newname; const changedScreen = getExactComponent(screens, screenName)
await api.post(`/_builder/api/${s.appId}/screen`, changedScreen)
}
}
const saveAllChanged = async () => { api
for (let screenName of changedScreens) { .patch(`/_builder/api/${s.appId}/screen`, {
const changedScreen = getExactComponent(screens, screenName); oldname,
await api.post(`/_builder/api/${s.appId}/screen`, changedScreen); newname,
} })
}; .then(() => saveAllChanged())
.then(() => {
_savePage(s)
})
api return s
.patch(`/_builder/api/${s.appId}/screen`, { })
oldname, }
newname
})
.then(() => saveAllChanged())
.then(() => {
_savePage(s);
});
return s; const savePage = store => async page => {
}); store.update(state => {
}; if (state.currentFrontEndType !== "page" || !state.currentPageName) {
return state
}
const savePage = (store) => async (page) => { state.pages[state.currentPageName] = page
store.update((state) => { _savePage(state)
if (state.currentFrontEndType !== 'page' || !state.currentPageName) { return state
return state; })
} }
state.pages[state.currentPageName] = page; const addStylesheet = store => stylesheet => {
_savePage(state); store.update(s => {
return state; s.pages.stylesheets.push(stylesheet)
}); _savePage(s)
}; return s
})
}
const addStylesheet = (store) => (stylesheet) => { const removeStylesheet = store => stylesheet => {
store.update((s) => { store.update(state => {
s.pages.stylesheets.push(stylesheet); state.pages.stylesheets = state.pages.stylesheets.filter(
_savePage(s); s => s !== stylesheet
return s; )
}); _savePage(state)
}; return state
})
}
const removeStylesheet = (store) => (stylesheet) => { const setCurrentPage = store => pageName => {
store.update((state) => { store.update(state => {
state.pages.stylesheets = state.pages.stylesheets.filter((s) => s !== stylesheet); const current_screens = state.pages[pageName]._screens
_savePage(state);
return state;
});
};
const _savePage = async (s) => { const currentPage = state.pages[pageName]
const page = s.pages[s.currentPageName];
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: s.currentPageFunctions,
screens: page._screens
});
};
const setCurrentPage = (store) => (pageName) => { state.currentFrontEndType = "page"
store.update((state) => { state.currentPageName = pageName
const current_screens = state.pages[pageName]._screens; state.screens = Array.isArray(current_screens)
? current_screens
: Object.values(current_screens)
const safeProps = makePropsSafe(
state.components[currentPage.props._component],
currentPage.props
)
state.currentComponentInfo = safeProps
currentPage.props = safeProps
state.currentPreviewItem = state.pages[pageName]
state.currentPreviewItem._css = generate_screen_css([
state.currentPreviewItem.props,
])
const currentPage = state.pages[pageName]; for (let screen of state.screens) {
screen._css = generate_screen_css([screen.props])
}
state.currentFrontEndType = 'page'; setCurrentPageFunctions(state)
state.currentPageName = pageName; return state
state.screens = Array.isArray(current_screens) ? current_screens : Object.values(current_screens); })
const safeProps = makePropsSafe(state.components[currentPage.props._component], currentPage.props); }
state.currentComponentInfo = safeProps;
currentPage.props = safeProps;
state.currentPreviewItem = state.pages[pageName];
state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem.props ]);
for (let screen of state.screens) {
screen._css = generate_screen_css([ screen.props ]);
}
setCurrentPageFunctions(state);
return state;
});
};
// const getComponentDefinition = (components, name) => components.find(c => c.name === name)
/** /**
* @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) => { const selectComponent = store => component => {
store.update((state) => { store.update(state => {
const current_component = state.currentComponentInfo; return _selectComponent(state, component)
state.currentComponentInfo[name] = value; })
}
_saveCurrentPreviewItem(state); const setComponentProp = store => (name, value) => {
store.update(state => {
const current_component = state.currentComponentInfo
state.currentComponentInfo[name] = value
state.currentComponentInfo = current_component; _saveCurrentPreviewItem(state)
return state;
});
};
const setComponentStyle = (store) => (type, name, value) => { state.currentComponentInfo = current_component
store.update((state) => { return state
if (!state.currentComponentInfo._styles) { })
state.currentComponentInfo._styles = {}; }
}
state.currentComponentInfo._styles[type][name] = value;
state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem.props ]); const setComponentStyle = store => (type, name, value) => {
store.update(state => {
if (!state.currentComponentInfo._styles) {
state.currentComponentInfo._styles = {}
}
state.currentComponentInfo._styles[type][name] = value
// save without messing with the store state.currentPreviewItem._css = generate_screen_css([
_saveCurrentPreviewItem(state); state.currentPreviewItem.props,
return state; ])
});
};
const setComponentCode = (store) => (code) => { // save without messing with the store
store.update((state) => { _saveCurrentPreviewItem(state)
state.currentComponentInfo._code = code; return state
})
}
setCurrentPageFunctions(state); const setComponentCode = store => code => {
// save without messing with the store store.update(state => {
_saveScreenApi(state.currentPreviewItem, state); state.currentComponentInfo._code = code
return state; setCurrentPageFunctions(state)
}); // save without messing with the store
}; _saveScreenApi(state.currentPreviewItem, state)
const setCurrentPageFunctions = (s) => { return state
s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName]); })
insertCodeMetadata(s.currentPreviewItem.props); }
};
const buildPageCode = (screens, page) => buildCodeForScreens([ page, ...screens ]); const setCurrentPageFunctions = s => {
s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName])
insertCodeMetadata(s.currentPreviewItem.props)
}
const setScreenType = (store) => (type) => { const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens])
store.update((state) => {
state.currentFrontEndType = type;
const pageOrScreen = const setScreenType = store => type => {
type === 'page' ? state.pages[state.currentPageName] : state.pages[state.currentPageName]._screens[0]; store.update(state => {
state.currentFrontEndType = type
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null; const pageOrScreen =
state.currentPreviewItem = pageOrScreen; type === "page"
return state; ? state.pages[state.currentPageName]
}); : state.pages[state.currentPageName]._screens[0]
};
const deleteComponent = (store) => (componentName) => { state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
store.update((state) => { state.currentPreviewItem = pageOrScreen
const parent = getParent(state.currentPreviewItem.props, componentName); return state
})
}
if (parent) { const getPathToComponent = store => component => {
parent._children = parent._children.filter((component) => component !== componentName); // Gets all the components to needed to construct a path.
} const tempStore = get(store)
let pathComponents = []
let parent = component
let root = false
while (!root) {
parent = getParent(tempStore.currentPreviewItem.props, parent)
if (!parent) {
root = true
} else {
pathComponents.push(parent)
}
}
_saveCurrentPreviewItem(state); // Remove root entry since it's the screen or page layout.
// Reverse array since we need the correct order of the IDs
const reversedComponents = pathComponents.reverse().slice(1)
return state; // Add component
}); const allComponents = [...reversedComponents, component]
};
const moveUpComponent = (store) => (component) => { // Map IDs
store.update((s) => { const IdList = allComponents.map(c => c._id)
const parent = getParent(s.currentPreviewItem.props, component);
if (parent) { // Construct ID Path:
const currentIndex = parent._children.indexOf(component); const path = IdList.join("/")
if (currentIndex === 0) return s;
const newChildren = parent._children.filter((c) => c !== component); return path
newChildren.splice(currentIndex - 1, 0, component); }
parent._children = newChildren;
}
s.currentComponentInfo = component;
_saveCurrentPreviewItem(s);
return s; const setMetadataProp = store => (name, prop) => {
}); store.update(s => {
}; s.currentPreviewItem[name] = prop
return s
const moveDownComponent = (store) => (component) => { })
store.update((s) => { }
const parent = getParent(s.currentPreviewItem.props, component);
if (parent) {
const currentIndex = parent._children.indexOf(component);
if (currentIndex === parent._children.length - 1) return s;
const newChildren = parent._children.filter((c) => c !== component);
newChildren.splice(currentIndex + 1, 0, component);
parent._children = newChildren;
}
s.currentComponentInfo = component;
_saveCurrentPreviewItem(s);
return s;
});
};
const copyComponent = (store) => (component) => {
store.update((s) => {
const parent = getParent(s.currentPreviewItem.props, component);
const copiedComponent = cloneDeep(component);
walkProps(copiedComponent, (p) => {
p._id = uuid();
});
parent._children = [ ...parent._children, copiedComponent ];
s.curren;
_saveCurrentPreviewItem(s);
s.currentComponentInfo = copiedComponent;
return s;
});
};
const getPathToComponent = (store) => (component) => {
// Gets all the components to needed to construct a path.
const tempStore = get(store);
let pathComponents = [];
let parent = component;
let root = false;
while (!root) {
parent = getParent(tempStore.currentPreviewItem.props, parent);
if (!parent) {
root = true;
} else {
pathComponents.push(parent);
}
}
// Remove root entry since it's the screen or page layout.
// Reverse array since we need the correct order of the IDs
const reversedComponents = pathComponents.reverse().slice(1);
// Add component
const allComponents = [ ...reversedComponents, component ];
// Map IDs
const IdList = allComponents.map((c) => c._id);
// Construct ID Path:
const path = IdList.join('/');
return path;
};
const getParent = (rootProps, child) => {
let parent;
walkProps(rootProps, (p, breakWalk) => {
if (p._children && p._children.includes(child)) {
parent = p;
breakWalk();
}
});
return parent;
};
const walkProps = (props, action, cancelToken = null) => {
cancelToken = cancelToken || { cancelled: false };
action(props, () => {
cancelToken.cancelled = true;
});
if (props._children) {
for (let child of props._children) {
if (cancelToken.cancelled) return;
walkProps(child, action, cancelToken);
}
}
};
const setMetadataProp = (store) => (name, prop) => {
store.update((s) => {
s.currentPreviewItem[name] = prop;
return s;
});
};
const _saveCurrentPreviewItem = (s) =>
s.currentFrontEndType === 'page' ? _savePage(s) : _saveScreenApi(s.currentPreviewItem, s);

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>
<input
class={className} {#if type === 'select'}
class:uk-form-danger={errors.length > 0} <select
{checked} class={determineClassName(type)}
{type} bind:value
{value} class:uk-form-danger={errors.length > 0}>
on:input={handleInput} {#each options as opt}
on:change={handleInput} /> <option value={opt}>{opt}</option>
{/each}
</select>
{:else}
<input
class={determineClassName(type)}
class:uk-form-danger={errors.length > 0}
{checked}
{type}
{value}
on:input={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,86 +34,51 @@
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
<div class="components-nav-page">Page Layout</div> class="budibase__nav-item root"
<div class:selected={$store.currentComponentInfo._id === _layout.component.props._id}
class="budibase__nav-item root" on:click|stopPropagation={setCurrentScreenToLayout}>
class:selected={$store.currentComponentInfo._id === _layout.component.props._id} <span
on:click|stopPropagation={setCurrentScreenToLayout}> class="icon"
<span class:rotate={$store.currentPreviewItem.name !== _layout.title}>
class="icon" <ArrowDownIcon />
class:rotate={$store.currentPreviewItem.name !== _layout.title}> </span>
<ArrowDownIcon /> <i class="ri-layout-3-fill icon-big" />
</span> <span class="title">Master Screen</span>
<span class="icon">
<GridIcon />
</span>
<span class="title">Page Layout</span>
</div>
{#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children}
<ComponentsHierarchyChildren
thisComponent={_layout.component.props}
components={_layout.component.props._children}
currentComponent={$store.currentComponentInfo}
onDeleteComponent={confirmDeleteComponent}
onMoveUpComponent={store.moveUpComponent}
onMoveDownComponent={store.moveDownComponent}
onCopyComponent={store.copyComponent} />
{/if}
</div> </div>
<ConfirmDialog {#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children}
bind:this={confirmDeleteDialog} <ComponentsHierarchyChildren
title="Confirm Delete" thisComponent={_layout.component.props}
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component?`} components={_layout.component.props._children}
okText="Delete Component" currentComponent={$store.currentComponentInfo} />
onOk={() => store.deleteComponent(componentToDelete)} /> {/if}
<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,7 +18,8 @@
}, },
] ]
store.setCurrentPage($params.page ? $params.page : "main") if (!$store.currentPageName)
store.setCurrentPage($params.page ? $params.page : "main")
const changePage = id => { const changePage = id => {
store.setCurrentPage(id) store.setCurrentPage(id)
@ -29,63 +28,37 @@
</script> </script>
<div class="root"> <div class="root">
<ul> {#each pages as { title, id }}
{#each pages as { title, id }} <button class:active={id === $params.page} on:click={() => changePage(id)}>
<li> {title}
<span class="icon"> </button>
{#if id === $params.page} {/each}
<CheckIcon />
{/if}
</span>
<button
class:active={id === $params.page}
on:click={() => changePage(id)}>
{title}
</button>
</li>
{/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;
@ -114,20 +97,10 @@
.root { .root {
display: grid; display: grid;
grid-template-columns: 275px 1fr 300px; grid-template-columns: 300px 1fr 300px;
height: 100%; height: 100%;
width: 100%; width: 100%;
background: #fafafa; background: #fbfbfb;
}
@media only screen and (min-width: 1800px) {
.root {
display: grid;
grid-template-columns: 300px 1fr 300px;
height: 100%;
width: 100%;
background: #fafafa;
}
} }
.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,407 +1,429 @@
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
*/ */
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', ],
key: 'flex-direction', },
control: FlatButtonGroup, {
buttonProps: [ label: "Direction",
{ icon: 'ri-arrow-right-line', padding: '0px 5px', value: 'row' }, key: "flex-direction",
{ icon: 'ri-arrow-left-line', padding: '0px 5px', value: 'rowReverse' }, control: FlatButtonGroup,
{ icon: 'ri-arrow-down-line', padding: '0px 5px', value: 'column' }, buttonProps: [
{ { icon: "ri-arrow-right-line", padding: "0px 5px", value: "row" },
icon: 'ri-arrow-up-line', { icon: "ri-arrow-left-line", padding: "0px 5px", value: "rowReverse" },
padding: '0px 5px', { icon: "ri-arrow-down-line", padding: "0px 5px", value: "column" },
value: 'columnReverse' {
} icon: "ri-arrow-up-line",
] padding: "0px 5px",
}, value: "columnReverse",
{ },
label: 'Justify', ],
key: 'justify-content', },
control: OptionSelect, {
initialValue: 'Flex Start', label: "Justify",
options: [ key: "justify-content",
{ label: 'Flex Start', value: 'flex-start' }, control: OptionSelect,
{ label: 'Flex End', value: 'flex-end' }, initialValue: "Flex Start",
{ label: 'Center', value: 'center' }, options: [
{ label: 'Space Between', value: 'space-between' }, { label: "Flex Start", value: "flex-start" },
{ label: 'Space Around', value: 'space-around' }, { label: "Flex End", value: "flex-end" },
{ label: 'Space Evenly', value: 'space-evenly' } { label: "Center", value: "center" },
] { label: "Space Between", value: "space-between" },
}, { label: "Space Around", value: "space-around" },
{ { label: "Space Evenly", value: "space-evenly" },
label: 'Align', ],
key: 'align-items', },
control: OptionSelect, {
initialValue: 'Flex Start', label: "Align",
options: [ key: "align-items",
{ label: 'Flex Start', value: 'flex-start' }, control: OptionSelect,
{ label: 'Flex End', value: 'flex-end' }, initialValue: "Flex Start",
{ label: 'Center', value: 'center' }, options: [
{ label: 'Baseline', value: 'baseline' }, { label: "Flex Start", value: "flex-start" },
{ label: 'Stretch', value: 'stretch' } { label: "Flex End", value: "flex-end" },
] { label: "Center", value: "center" },
}, { label: "Baseline", value: "baseline" },
{ { label: "Stretch", value: "stretch" },
label: 'Wrap', ],
key: 'flex-wrap', },
control: OptionSelect, {
options: [ { label: 'wrap', value: 'wrap' }, { label: 'no wrap', value: 'noWrap' } ] label: "Wrap",
} key: "flex-wrap",
]; control: OptionSelect,
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', label: "Top",
key: 'top', key: "top",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Right', label: "Right",
key: 'right', key: "right",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Bottom', label: "Bottom",
key: 'bottom', key: "bottom",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Left', label: "Left",
key: 'left', key: "left",
control: Input, control: Input,
placeholder: 'px', placeholder: "px",
width: '48px', width: "48px",
textAlign: 'center' textAlign: "center",
}, },
{ {
label: 'Z-index', label: "Z-index",
key: 'z-index', key: "z-index",
control: Input, control: Input,
placeholder: 'num', placeholder: "num",
width: '48px', width: "48px",
textAlign: 'center' 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,
}, },
{ {
label: "Image", label: "Image",
key: "background-image", key: "background-image",
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",
key: "box-shadow", key: "box-shadow",
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,
spacing, spacing,
size, size,
position, position,
typography, typography,
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

@ -34,16 +34,7 @@
.nav { .nav {
overflow: auto; overflow: auto;
flex: 0 1 auto; flex: 0 1 auto;
width: 275px; width: 300px;
height: 100%; height: 100%;
} }
@media only screen and (min-width: 1800px) {
.nav {
overflow: auto;
flex: 0 1 auto;
width: 300px;
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>
<div slot="1">
<div class="nav-items-container"> <ComponentSelectionList toggleTab={leftNavSwitcher.selectTab} />
<PagesList />
</div> </div>
</div> </Switcher>
<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> </div>
<div class="preview-pane"> <div class="preview-pane">
<CurrentItemPreview /> {#if $store.currentPageName && $store.currentPageName.length > 0}
<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 { .root {
display: grid; display: grid;
grid-template-columns: 275px 1fr 275px; grid-template-columns: 300px 1fr 300px;
width: 100%; width: 100%;
background: var(--grey-light); background: var(--grey-light);
} flex: 1;
min-height: 0;
@media only screen and (min-width: 1800px) { align-items: stretch;
.root {
display: grid;
grid-template-columns: 300px 1fr 300px;
width: 100%;
background: var(--grey-light);
}
} }
.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

@ -119,22 +119,12 @@
<style> <style>
.root { .root {
display: grid; display: grid;
grid-template-columns: 275px 1fr; grid-template-columns: 300px 1fr;
height: 100%; height: 100%;
width: 100%; width: 100%;
background: var(--grey-light); background: var(--grey-light);
} }
@media only screen and (min-width: 1800px) {
.root {
display: grid;
grid-template-columns: 300px 1fr;
height: 100%;
width: 100%;
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

@ -12,4 +12,7 @@ ADMIN_SECRET={{adminSecret}}
JWT_SECRET={{cookieKey1}} 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,17 +25,17 @@ describe("/records", () => {
server.close(); server.close();
}) })
describe("save, load, update, delete", () => { beforeEach(async () => {
instance = await createInstance(request, app._id)
model = await createModel(request, instance._id)
record = {
name: "Test Contact",
status: "new",
modelId: model._id
}
})
beforeEach(async () => { describe("save, load, update, delete", () => {
instance = await createInstance(request, app._id)
model = await createModel(request, instance._id)
record = {
name: "Test Contact",
status: "new",
modelId: model._id
}
})
const createRecord = async r => const createRecord = async r =>
await request await request
@ -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"