Merge branch 'master' of https://github.com/Budibase/budibase into property-panel/components-from-design
This commit is contained in:
commit
2c407d4da7
|
@ -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": {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}`
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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 |
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: '×',
|
text: "×",
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,8 @@ html, body {
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;")
|
||||||
|
|
|
@ -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
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue