merge
This commit is contained in:
commit
398f200661
|
@ -39,6 +39,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@beyonk/svelte-notifications": "^2.0.3",
|
"@beyonk/svelte-notifications": "^2.0.3",
|
||||||
|
"@budibase/bbui": "^0.3.5",
|
||||||
"@budibase/client": "^0.0.32",
|
"@budibase/client": "^0.0.32",
|
||||||
"@nx-js/compiler-util": "^2.0.0",
|
"@nx-js/compiler-util": "^2.0.0",
|
||||||
"codemirror": "^5.51.0",
|
"codemirror": "^5.51.0",
|
||||||
|
@ -83,7 +84,7 @@
|
||||||
"rollup-plugin-svelte": "^5.0.3",
|
"rollup-plugin-svelte": "^5.0.3",
|
||||||
"rollup-plugin-terser": "^4.0.4",
|
"rollup-plugin-terser": "^4.0.4",
|
||||||
"rollup-plugin-url": "^2.2.2",
|
"rollup-plugin-url": "^2.2.2",
|
||||||
"svelte": "^3.0.0"
|
"svelte": "3.23.x"
|
||||||
},
|
},
|
||||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,4 @@
|
||||||
<!-- svelte-notifications -->
|
<!-- svelte-notifications -->
|
||||||
<NotificationDisplay />
|
<NotificationDisplay />
|
||||||
|
|
||||||
<Modal>
|
<Router {routes} />
|
||||||
<Router {routes} />
|
|
||||||
</Modal>
|
|
||||||
|
|
|
@ -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,4 +1,4 @@
|
||||||
import { cloneDeep, values } from "lodash/fp"
|
import { values } from "lodash/fp"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import * as backendStoreActions from "./backend"
|
import * as backendStoreActions from "./backend"
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
|
@ -16,6 +16,14 @@ import { buildCodeForScreens } from "../buildCodeForScreens"
|
||||||
import { generate_screen_css } from "../generate_css"
|
import { generate_screen_css } from "../generate_css"
|
||||||
import { insertCodeMetadata } from "../insertCodeMetadata"
|
import { insertCodeMetadata } from "../insertCodeMetadata"
|
||||||
import { uuid } from "../uuid"
|
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 = {
|
||||||
|
@ -57,10 +65,6 @@ export const getStore = () => {
|
||||||
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.moveUpComponent = moveUpComponent(store)
|
|
||||||
store.moveDownComponent = moveDownComponent(store)
|
|
||||||
store.copyComponent = copyComponent(store)
|
|
||||||
store.getPathToComponent = getPathToComponent(store)
|
store.getPathToComponent = getPathToComponent(store)
|
||||||
store.addTemplatedComponent = addTemplatedComponent(store)
|
store.addTemplatedComponent = addTemplatedComponent(store)
|
||||||
store.setMetadataProp = setMetadataProp(store)
|
store.setMetadataProp = setMetadataProp(store)
|
||||||
|
@ -69,6 +73,9 @@ export const getStore = () => {
|
||||||
|
|
||||||
export default getStore
|
export default getStore
|
||||||
|
|
||||||
|
export const getComponentDefinition = (state, name) =>
|
||||||
|
name.startsWith("##") ? getBuiltin(name) : state.components[name]
|
||||||
|
|
||||||
const setPackage = (store, initial) => async pkg => {
|
const setPackage = (store, initial) => async pkg => {
|
||||||
const [main_screens, unauth_screens] = await Promise.all([
|
const [main_screens, unauth_screens] = await Promise.all([
|
||||||
api
|
api
|
||||||
|
@ -140,12 +147,6 @@ const _saveScreen = async (store, s, screen) => {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
const _saveScreenApi = (screen, s) => {
|
|
||||||
api
|
|
||||||
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
|
|
||||||
.then(() => _savePage(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
const createScreen = store => (screenName, route, layoutComponentName) => {
|
const createScreen = store => (screenName, route, layoutComponentName) => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const rootComponent = state.components[layoutComponentName]
|
const rootComponent = state.components[layoutComponentName]
|
||||||
|
@ -276,14 +277,6 @@ const removeStylesheet = store => stylesheet => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const _savePage = async s => {
|
|
||||||
const page = s.pages[s.currentPageName]
|
|
||||||
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
|
|
||||||
page: { componentLibraries: s.pages.componentLibraries, ...page },
|
|
||||||
screens: page._screens,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const setCurrentPage = store => pageName => {
|
const setCurrentPage = store => pageName => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const current_screens = state.pages[pageName]._screens
|
const current_screens = state.pages[pageName]._screens
|
||||||
|
@ -315,8 +308,6 @@ const setCurrentPage = store => pageName => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// const getComponentDefinition = (components, name) => components.find(c => c.name === name)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} componentToAdd - name of the component to add to the application
|
* @param {string} 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
|
||||||
|
@ -342,9 +333,7 @@ const addChildComponent = store => (componentToAdd, presetName) => {
|
||||||
return state
|
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] : {}
|
||||||
|
|
||||||
|
@ -398,12 +387,7 @@ const addTemplatedComponent = store => props => {
|
||||||
|
|
||||||
const selectComponent = store => component => {
|
const selectComponent = store => component => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const componentDef = component._component.startsWith("##")
|
return _selectComponent(state, component)
|
||||||
? component
|
|
||||||
: state.components[component._component]
|
|
||||||
state.currentComponentInfo = makePropsSafe(componentDef, component)
|
|
||||||
state.currentView = "component"
|
|
||||||
return state
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,75 +454,6 @@ const setScreenType = store => type => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteComponent = store => componentName => {
|
|
||||||
store.update(state => {
|
|
||||||
const parent = getParent(state.currentPreviewItem.props, componentName)
|
|
||||||
|
|
||||||
if (parent) {
|
|
||||||
parent._children = parent._children.filter(
|
|
||||||
component => component !== componentName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_saveCurrentPreviewItem(state)
|
|
||||||
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveUpComponent = store => component => {
|
|
||||||
store.update(s => {
|
|
||||||
const parent = getParent(s.currentPreviewItem.props, component)
|
|
||||||
|
|
||||||
if (parent) {
|
|
||||||
const currentIndex = parent._children.indexOf(component)
|
|
||||||
if (currentIndex === 0) return s
|
|
||||||
|
|
||||||
const newChildren = parent._children.filter(c => c !== component)
|
|
||||||
newChildren.splice(currentIndex - 1, 0, component)
|
|
||||||
parent._children = newChildren
|
|
||||||
}
|
|
||||||
s.currentComponentInfo = component
|
|
||||||
_saveCurrentPreviewItem(s)
|
|
||||||
|
|
||||||
return s
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveDownComponent = store => component => {
|
|
||||||
store.update(s => {
|
|
||||||
const parent = getParent(s.currentPreviewItem.props, component)
|
|
||||||
|
|
||||||
if (parent) {
|
|
||||||
const currentIndex = parent._children.indexOf(component)
|
|
||||||
if (currentIndex === parent._children.length - 1) return s
|
|
||||||
|
|
||||||
const newChildren = parent._children.filter(c => c !== component)
|
|
||||||
newChildren.splice(currentIndex + 1, 0, component)
|
|
||||||
parent._children = newChildren
|
|
||||||
}
|
|
||||||
s.currentComponentInfo = component
|
|
||||||
_saveCurrentPreviewItem(s)
|
|
||||||
|
|
||||||
return s
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyComponent = store => component => {
|
|
||||||
store.update(s => {
|
|
||||||
const parent = getParent(s.currentPreviewItem.props, component)
|
|
||||||
const copiedComponent = cloneDeep(component)
|
|
||||||
walkProps(copiedComponent, p => {
|
|
||||||
p._id = uuid()
|
|
||||||
})
|
|
||||||
parent._children = [...parent._children, copiedComponent]
|
|
||||||
s.curren
|
|
||||||
_saveCurrentPreviewItem(s)
|
|
||||||
s.currentComponentInfo = copiedComponent
|
|
||||||
return s
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPathToComponent = store => component => {
|
const getPathToComponent = store => component => {
|
||||||
// Gets all the components to needed to construct a path.
|
// Gets all the components to needed to construct a path.
|
||||||
const tempStore = get(store)
|
const tempStore = get(store)
|
||||||
|
@ -570,39 +485,9 @@ const getPathToComponent = store => component => {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
const getParent = (rootProps, child) => {
|
|
||||||
let parent
|
|
||||||
walkProps(rootProps, (p, breakWalk) => {
|
|
||||||
if (p._children && p._children.includes(child)) {
|
|
||||||
parent = p
|
|
||||||
breakWalk()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
|
|
||||||
const walkProps = (props, action, cancelToken = null) => {
|
|
||||||
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) => {
|
const setMetadataProp = store => (name, prop) => {
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
s.currentPreviewItem[name] = prop
|
s.currentPreviewItem[name] = prop
|
||||||
return s
|
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,10 @@
|
||||||
|
<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="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414
|
||||||
|
1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 292 B |
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10
|
||||||
|
10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM11 7h2v2h-2V7zm0 4h2v6h-2v-6z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 271 B |
|
@ -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 |
|
@ -29,3 +29,6 @@ export { default as ContributionIcon } from "./Contribution.svelte"
|
||||||
export { default as BugIcon } from "./Bug.svelte"
|
export { default as BugIcon } from "./Bug.svelte"
|
||||||
export { default as EmailIcon } from "./Email.svelte"
|
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 CloseIcon } from "./Close.svelte"
|
||||||
|
export { default as MoreIcon } from "./More.svelte"
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switcher {
|
||||||
|
display: flex;
|
||||||
|
margin: 0px 20px 20px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switcher > .selected {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -15,13 +15,13 @@ export async function createDatabase(appname, instanceName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRecord(record, instanceId) {
|
export async function deleteRecord(record, instanceId) {
|
||||||
const DELETE_RECORDS_URL = `/api/${instanceId}/${record.modelId}/records/${record._id}/${record._rev}`
|
const DELETE_RECORDS_URL = `/api/${instanceId}/${record._modelId}/records/${record._id}/${record._rev}`
|
||||||
const response = await api.delete(DELETE_RECORDS_URL)
|
const response = await api.delete(DELETE_RECORDS_URL)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveRecord(record, instanceId) {
|
export async function saveRecord(record, instanceId, modelId) {
|
||||||
const SAVE_RECORDS_URL = `/api/${instanceId}/${record.modelId}/records`
|
const SAVE_RECORDS_URL = `/api/${instanceId}/${modelId}/records`
|
||||||
const response = await api.post(SAVE_RECORDS_URL, record)
|
const response = await api.post(SAVE_RECORDS_URL, record)
|
||||||
|
|
||||||
return await response.json()
|
return await response.json()
|
||||||
|
|
|
@ -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,24 +24,38 @@
|
||||||
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(
|
||||||
{
|
{
|
||||||
...record,
|
...record,
|
||||||
modelId: $backendUiStore.selectedModel._id,
|
modelId: $backendUiStore.selectedModel._id,
|
||||||
},
|
},
|
||||||
instanceId
|
instanceId,
|
||||||
|
$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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,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">
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
function selectModel(model) {
|
function selectModel(model) {
|
||||||
backendUiStore.update(state => {
|
backendUiStore.update(state => {
|
||||||
state.selectedModel = model
|
state.selectedModel = model
|
||||||
state.selectedView = `all_${model._id}`
|
state.selectedView = `${model._id}`
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<script>
|
||||||
|
import Button from "components/common/Button.svelte"
|
||||||
|
export let name,
|
||||||
|
description = `A minimalist CRM which removes the noise and allows you to focus
|
||||||
|
on your business.`,
|
||||||
|
_id
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="apps-card">
|
||||||
|
<h3 class="app-title">{name}</h3>
|
||||||
|
<p class="app-desc">{description}</p>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="modified-date">Last Edited - 25th May 2020</div>
|
||||||
|
<a href={`/_builder/${_id}`} class="app-button">Open Web App</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.apps-card {
|
||||||
|
background-color: var(--white);
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 150px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid var(--grey-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-button:hover {
|
||||||
|
background-color: var(--grey-light);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--ink);
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-desc {
|
||||||
|
color: var(--ink-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modified-date {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--ink-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-button {
|
||||||
|
background-color: var(--white);
|
||||||
|
color: var(--ink);
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px var(--grey) solid;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import Button from "components/common/Button.svelte"
|
import AppCard from "./AppCard.svelte"
|
||||||
export let apps
|
export let apps
|
||||||
|
|
||||||
function myFunction() {
|
function myFunction() {
|
||||||
|
@ -13,27 +13,23 @@
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<div class="app-section-title">Your Web Apps</div>
|
<div class="app-section-title">Your Web Apps</div>
|
||||||
{#each apps as app}
|
<div class="apps">
|
||||||
<div class="apps-card">
|
{#each apps as app}
|
||||||
<h3 class="app-title">{app.name}</h3>
|
<AppCard {...app} />
|
||||||
<p class="app-desc">
|
{/each}
|
||||||
A minimalist CRM which removes the noise and allows you to focus
|
</div>
|
||||||
on your business.
|
|
||||||
</p>
|
|
||||||
<div class="card-footer">
|
|
||||||
<div class="modified-date">Last Edited - 25th May 2020</div>
|
|
||||||
<a href={`/_builder/${app._id}`} class="app-button">
|
|
||||||
Open Web App
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.apps {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, 400px);
|
||||||
|
grid-gap: 40px 85px;
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
.root {
|
.root {
|
||||||
margin: 40px 80px;
|
margin: 40px 80px;
|
||||||
}
|
}
|
||||||
|
@ -44,59 +40,4 @@
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.apps {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 40px;
|
|
||||||
}
|
|
||||||
.apps-card {
|
|
||||||
background-color: var(--white);
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 400px;
|
|
||||||
max-height: 150px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid var(--grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-button:hover {
|
|
||||||
background-color: var(--grey-light);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--ink);
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-desc {
|
|
||||||
color: var(--ink-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-footer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modified-date {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--ink-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-button {
|
|
||||||
background-color: var(--white);
|
|
||||||
color: var(--ink);
|
|
||||||
padding: 12px 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px var(--grey) solid;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
<script>
|
||||||
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
|
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||||
|
import { goto } from "@sveltech/routify"
|
||||||
|
import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
|
||||||
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
let name = ""
|
||||||
|
let description = ""
|
||||||
|
let loading = false
|
||||||
|
let error = {}
|
||||||
|
|
||||||
|
const createNewApp = async () => {
|
||||||
|
if ((name.length > 100 || name.length < 1) && description.length < 1) {
|
||||||
|
error = {
|
||||||
|
name: true,
|
||||||
|
description: true,
|
||||||
|
}
|
||||||
|
} else if (description.length < 1) {
|
||||||
|
error = {
|
||||||
|
name: false,
|
||||||
|
description: true,
|
||||||
|
}
|
||||||
|
} else if (name.length > 100 || name.length < 1) {
|
||||||
|
error = {
|
||||||
|
name: true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = {}
|
||||||
|
const data = { name, description }
|
||||||
|
loading = true
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/applications", {
|
||||||
|
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
||||||
|
credentials: "same-origin", // include, *same-origin, omit
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
// 'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data), // body data type must match "Content-Type" header
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await response.json()
|
||||||
|
|
||||||
|
$goto(`./${res._id}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let value
|
||||||
|
let onChange = () => {}
|
||||||
|
|
||||||
|
function _onCancel() {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _onOkay() {
|
||||||
|
await createNewApp()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="body">
|
||||||
|
<div class="heading">
|
||||||
|
<span class="icon">
|
||||||
|
<AppsIcon />
|
||||||
|
</span>
|
||||||
|
<h3>Create new web app</h3>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="name"
|
||||||
|
label="Name"
|
||||||
|
placeholder="Enter application name"
|
||||||
|
on:change={e => (name = e.target.value)}
|
||||||
|
on:input={e => (name = e.target.value)} />
|
||||||
|
{#if error.name}
|
||||||
|
<span class="error">You need to enter a name for your application.</span>
|
||||||
|
{/if}
|
||||||
|
<TextArea
|
||||||
|
bind:value={description}
|
||||||
|
name="description"
|
||||||
|
label="Description"
|
||||||
|
placeholder="Describe your application" />
|
||||||
|
{#if error.description}
|
||||||
|
<span class="error">
|
||||||
|
Please enter a short description of your application
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<a href="./#" class="info">
|
||||||
|
<InfoIcon />
|
||||||
|
How to get started
|
||||||
|
</a>
|
||||||
|
<Button outline thin on:click={_onCancel}>Cancel</Button>
|
||||||
|
<Button primary thin on:click={_onOkay}>Save</Button>
|
||||||
|
</div>
|
||||||
|
<div class="close-button" on:click={_onCancel}>
|
||||||
|
<CloseIcon />
|
||||||
|
</div>
|
||||||
|
{#if loading}
|
||||||
|
<div in:fade class="spinner-container">
|
||||||
|
<Spinner />
|
||||||
|
<span class="spinner-text">Creating your app...</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
.close-button :global(svg) {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.heading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
display: grid;
|
||||||
|
border-radius: 3px;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 12px;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--blue-light);
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
color: var(--primary100);
|
||||||
|
text-decoration-color: var(--primary100);
|
||||||
|
}
|
||||||
|
.info :global(svg) {
|
||||||
|
fill: var(--primary100);
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
padding: 40px 40px 80px 40px;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 1fr auto auto;
|
||||||
|
padding: 30px 40px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
border-bottom-right-radius: 50px;
|
||||||
|
background-color: var(--grey-light);
|
||||||
|
}
|
||||||
|
.spinner-container {
|
||||||
|
background: white;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 5px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
align-content: center;
|
||||||
|
grid-gap: 50px;
|
||||||
|
}
|
||||||
|
.spinner-text {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: var(--deletion100);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,232 @@
|
||||||
|
<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 on:click={() => confirmDeleteDialog.show()}>Delete</li>
|
||||||
|
<li on:click={moveUpComponent}>Move up</li>
|
||||||
|
<li on:click={moveDownComponent}>Move down</li>
|
||||||
|
<li on:click={copyComponent}>Duplicate</li>
|
||||||
|
<li on:click={() => storeComponentForCopy(true)}>Cut</li>
|
||||||
|
<li on:click={() => storeComponentForCopy(false)}>Copy</li>
|
||||||
|
<hr />
|
||||||
|
<li class:disabled={noPaste} on:click={() => pasteComponent('above')}>
|
||||||
|
Paste above
|
||||||
|
</li>
|
||||||
|
<li class:disabled={noPaste} on:click={() => pasteComponent('below')}>
|
||||||
|
Paste below
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class:disabled={noPaste || noChildrenAllowed}
|
||||||
|
on:click={() => pasteComponent('inside')}>
|
||||||
|
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(--button-text);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
z-index: 100000;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu li {
|
||||||
|
border-style: none;
|
||||||
|
background-color: transparent;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 4px 5px 4px 15px;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu li:not(.disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu li:not(.disabled):hover {
|
||||||
|
color: var(--button-text);
|
||||||
|
background-color: var(--grey-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
color: var(--grey-dark);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -107,11 +107,12 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title > div:nth-child(1) {
|
.title > div:nth-child(1) {
|
||||||
grid-column-start: name;
|
grid-column-start: name;
|
||||||
color: var(--secondary100);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title > div:nth-child(2) {
|
.title > div:nth-child(2) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
@ -52,32 +60,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
list-style: none;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 30px;
|
|
||||||
border-bottom: 1px solid #d8d8d8;
|
|
||||||
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: 0.14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
color: #808192;
|
|
||||||
margin: 0 5px;
|
|
||||||
padding: 0 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
padding: 20px;
|
padding: 20px 0px;
|
||||||
}
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
.active {
|
|
||||||
border-bottom: solid 3px #0055ff;
|
|
||||||
color: #393c44;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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)
|
||||||
|
@ -51,30 +41,9 @@
|
||||||
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>{get_capitalised_name(component._component)}</div>
|
||||||
<div class="reorder-buttons">
|
<div class="actions">
|
||||||
{#if index > 0}
|
<ComponentDropdownMenu {component} />
|
||||||
<button
|
|
||||||
class:solo={index === components.length - 1}
|
|
||||||
on:click|stopPropagation={() => onMoveUpComponent(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 +51,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}
|
||||||
|
@ -111,7 +76,7 @@
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item button {
|
.actions {
|
||||||
display: none;
|
display: none;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
|
@ -120,37 +85,14 @@
|
||||||
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: #fafafa;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.item:hover button {
|
.item:hover .actions {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item:hover button:hover {
|
|
||||||
color: var(--button-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reorder-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reorder-buttons > button {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
width: 30px;
|
|
||||||
height: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reorder-buttons > button.solo {
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 20px 20px;
|
padding: 20px 20px;
|
||||||
border-left: solid 1px #e8e8ef;
|
border-left: solid 1px var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.switcher {
|
.switcher {
|
||||||
|
|
|
@ -19,20 +19,20 @@
|
||||||
<style>
|
<style>
|
||||||
.flatbutton {
|
.flatbutton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 8px 4px;
|
padding: 8px 2px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
color: var(--ink-light);
|
color: var(--ink-light);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
transition: background 0.5s, color 0.5s ease;
|
transition: all 0.3s;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background: #808192;
|
background: var(--ink-light);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
import FlatButton from "./FlatButton.svelte"
|
import FlatButton from "./FlatButton.svelte"
|
||||||
export let buttonProps = []
|
export let buttonProps = []
|
||||||
export let isMultiSelect = false
|
export let isMultiSelect = false
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<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}>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: 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);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,66 +1,56 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { fly } from "svelte/transition"
|
||||||
export let item
|
export let item
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="item-item" 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>
|
||||||
<div class="item-text">
|
<div class="item-text">
|
||||||
<div class="item-name">{item.name}</div>
|
<div class="item-name">{item.name}</div>
|
||||||
<div class="item-description">
|
|
||||||
<p>{item.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.item-item {
|
.item-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
padding: 10px 0px 8px 10px;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 8px 0px 16px 0px;
|
||||||
|
width: 120px;
|
||||||
|
height: 80px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 8px;
|
||||||
|
background-color: var(--grey-light);
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-item:hover {
|
.item-item:hover {
|
||||||
background: #fbfbfb;
|
background: var(--grey);
|
||||||
border-radius: 5px;
|
border-radius: 3px;
|
||||||
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-icon {
|
.item-icon {
|
||||||
flex: 0 0 40px;
|
border-radius: 3px;
|
||||||
background: #f1f4fc;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 5px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-text {
|
.item-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-left: 16px;
|
|
||||||
padding-top: 8px;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-name {
|
.item-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
}
|
|
||||||
|
|
||||||
.item-description {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #808192;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #808192;
|
color: var(--ink-light);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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,11 +34,6 @@
|
||||||
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")
|
||||||
|
@ -46,7 +41,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="pagelayoutSection">
|
<div class="pagelayoutSection">
|
||||||
<div class="components-nav-page">Page Layout</div>
|
|
||||||
<div
|
<div
|
||||||
class="budibase__nav-item root"
|
class="budibase__nav-item root"
|
||||||
class:selected={$store.currentComponentInfo._id === _layout.component.props._id}
|
class:selected={$store.currentComponentInfo._id === _layout.component.props._id}
|
||||||
|
@ -56,64 +50,41 @@
|
||||||
class:rotate={$store.currentPreviewItem.name !== _layout.title}>
|
class:rotate={$store.currentPreviewItem.name !== _layout.title}>
|
||||||
<ArrowDownIcon />
|
<ArrowDownIcon />
|
||||||
</span>
|
</span>
|
||||||
|
<i class="ri-layout-3-fill icon-big" />
|
||||||
<span class="icon">
|
<span class="title">Master Screen</span>
|
||||||
<GridIcon />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="title">Page Layout</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children}
|
{#if $store.currentPreviewItem.name === _layout.title && _layout.component.props._children}
|
||||||
<ComponentsHierarchyChildren
|
<ComponentsHierarchyChildren
|
||||||
thisComponent={_layout.component.props}
|
thisComponent={_layout.component.props}
|
||||||
components={_layout.component.props._children}
|
components={_layout.component.props._children}
|
||||||
currentComponent={$store.currentComponentInfo}
|
currentComponent={$store.currentComponentInfo} />
|
||||||
onDeleteComponent={confirmDeleteComponent}
|
|
||||||
onMoveUpComponent={store.moveUpComponent}
|
|
||||||
onMoveDownComponent={store.moveDownComponent}
|
|
||||||
onCopyComponent={store.copyComponent} />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmDialog
|
|
||||||
bind:this={confirmDeleteDialog}
|
|
||||||
title="Confirm Delete"
|
|
||||||
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component?`}
|
|
||||||
okText="Delete Component"
|
|
||||||
onOk={() => store.deleteComponent(componentToDelete)} />
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.components-nav-page {
|
.pagelayoutSection {
|
||||||
font-size: 13px;
|
margin: 20px 0px 0px 0px;
|
||||||
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: 500;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
@ -230,10 +202,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,42 +7,84 @@ import InputGroup from "../common/Inputs/InputGroup.svelte"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const layout = [
|
export const layout = [
|
||||||
|
{
|
||||||
|
label: "Display",
|
||||||
|
key: "display",
|
||||||
|
control: OptionSelect,
|
||||||
|
initialValue: "Select Option",
|
||||||
|
options: [
|
||||||
|
{ label: "Select Option", value: "" },
|
||||||
|
{ label: "Flex", value: "flex" },
|
||||||
|
{ label: "Inline Flex", value: "inline-flex" },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Direction",
|
label: "Direction",
|
||||||
key: "flex-direction",
|
key: "flex-direction",
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
initialValue: "columnReverse",
|
initialValue: "Row",
|
||||||
options: [
|
options: [
|
||||||
{ label: "row" },
|
{ label: "Row", value: "row" },
|
||||||
{ label: "row-reverse", value: "rowReverse" },
|
{ label: "Row Reverse", value: "rowReverse" },
|
||||||
{ label: "column" },
|
{ label: "column", value: "column" },
|
||||||
{ label: "column-reverse", value: "columnReverse" },
|
{ label: "Column Reverse", value: "columnReverse" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Justify",
|
||||||
|
key: "justify-content",
|
||||||
|
control: OptionSelect,
|
||||||
|
initialValue: "Flex Start",
|
||||||
|
options: [
|
||||||
|
{ label: "Flex Start", value: "flex-start" },
|
||||||
|
{ label: "Flex End", value: "flex-end" },
|
||||||
|
{ 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",
|
||||||
|
options: [
|
||||||
|
{ label: "Flex Start", value: "flex-start" },
|
||||||
|
{ label: "Flex End", value: "flex-end" },
|
||||||
|
{ label: "Center", value: "center" },
|
||||||
|
{ label: "Baseline", value: "baseline" },
|
||||||
|
{ label: "Stretch", value: "stretch" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ label: "Justify", key: "justify-content", control: Input },
|
|
||||||
{ label: "Align", key: "align-items", control: Input },
|
|
||||||
{
|
{
|
||||||
label: "Wrap",
|
label: "Wrap",
|
||||||
key: "flex-wrap",
|
key: "flex-wrap",
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
options: [{ label: "wrap" }, { label: "no wrap", value: "noWrap" }],
|
initialValue: "NoWrap",
|
||||||
|
options: [
|
||||||
|
{ label: "No Wrap", value: "nowrap" },
|
||||||
|
{ label: "Wrap", value: "wrap" },
|
||||||
|
{ label: "Wrap Reverse", value: "wrap-reverse" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const spacingMeta = [
|
const spacingMeta = [
|
||||||
{ placeholder: "T" },
|
|
||||||
{ placeholder: "R" },
|
|
||||||
{ placeholder: "B" },
|
|
||||||
{ placeholder: "L" },
|
{ placeholder: "L" },
|
||||||
|
{ placeholder: "B" },
|
||||||
|
{ placeholder: "R" },
|
||||||
|
{ placeholder: "T" },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const spacing = [
|
export const spacing = [
|
||||||
|
{ label: "Margin", key: "margin", control: InputGroup, meta: spacingMeta },
|
||||||
{
|
{
|
||||||
label: "Padding",
|
label: "Padding",
|
||||||
key: "padding",
|
key: "padding",
|
||||||
control: InputGroup,
|
control: InputGroup,
|
||||||
meta: spacingMeta,
|
meta: spacingMeta,
|
||||||
},
|
},
|
||||||
{ label: "Margin", key: "margin", control: InputGroup, meta: spacingMeta },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const size = [
|
export const size = [
|
||||||
|
@ -59,14 +101,40 @@ export const position = [
|
||||||
label: "Position",
|
label: "Position",
|
||||||
key: "position",
|
key: "position",
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
|
initialValue: "Wrap",
|
||||||
options: [
|
options: [
|
||||||
{ label: "static" },
|
{ label: "Static", value: "static" },
|
||||||
{ label: "relative" },
|
{ label: "Relative", value: "relative" },
|
||||||
{ label: "fixed" },
|
{ label: "Fixed", value: "fixed" },
|
||||||
{ label: "absolute" },
|
{ label: "Absolute", value: "absolute" },
|
||||||
{ label: "sticky" },
|
{ label: "Sticky", value: "sticky" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Top",
|
||||||
|
key: "top",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Right",
|
||||||
|
key: "right",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Bottom",
|
||||||
|
key: "bottom",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Left",
|
||||||
|
key: "left",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Z-index",
|
||||||
|
key: "z-index",
|
||||||
|
control: Input,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const typography = [
|
export const typography = [
|
||||||
|
@ -77,13 +145,21 @@ export const typography = [
|
||||||
defaultValue: "initial",
|
defaultValue: "initial",
|
||||||
options: [
|
options: [
|
||||||
"initial",
|
"initial",
|
||||||
"Times New Roman",
|
|
||||||
"Georgia",
|
|
||||||
"Arial",
|
"Arial",
|
||||||
"Arial Black",
|
"Arial Black",
|
||||||
|
"Cursive",
|
||||||
|
"Courier",
|
||||||
"Comic Sans MS",
|
"Comic Sans MS",
|
||||||
|
"Helvetica",
|
||||||
"Impact",
|
"Impact",
|
||||||
|
"Inter",
|
||||||
"Lucida Sans Unicode",
|
"Lucida Sans Unicode",
|
||||||
|
"Open Sans",
|
||||||
|
"Playfair",
|
||||||
|
"Roboto",
|
||||||
|
"Roboto Mono",
|
||||||
|
"Times New Roman",
|
||||||
|
"Verdana",
|
||||||
],
|
],
|
||||||
styleBindingProperty: "font-family",
|
styleBindingProperty: "font-family",
|
||||||
},
|
},
|
||||||
|
@ -92,10 +168,15 @@ export const typography = [
|
||||||
key: "font-weight",
|
key: "font-weight",
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
options: [
|
options: [
|
||||||
{ label: "normal" },
|
{ label: "100", value: "100" },
|
||||||
{ label: "bold" },
|
{ label: "200", value: "200" },
|
||||||
{ label: "bolder" },
|
{ label: "300", value: "300" },
|
||||||
{ label: "lighter" },
|
{ label: "400", value: "400" },
|
||||||
|
{ label: "500", value: "500" },
|
||||||
|
{ label: "600", value: "600" },
|
||||||
|
{ label: "700", value: "700" },
|
||||||
|
{ label: "800", value: "800" },
|
||||||
|
{ label: "900", value: "900" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ label: "size", key: "font-size", defaultValue: "", control: Input },
|
{ label: "size", key: "font-size", defaultValue: "", control: Input },
|
||||||
|
@ -103,8 +184,7 @@ export const typography = [
|
||||||
{
|
{
|
||||||
label: "Color",
|
label: "Color",
|
||||||
key: "color",
|
key: "color",
|
||||||
control: OptionSelect,
|
control: Input,
|
||||||
options: ["black", "white", "red", "blue", "green"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "align",
|
label: "align",
|
||||||
|
@ -112,6 +192,20 @@ export const typography = [
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
options: ["initial", "left", "right", "center", "justify"],
|
options: ["initial", "left", "right", "center", "justify"],
|
||||||
}, //custom
|
}, //custom
|
||||||
|
{
|
||||||
|
label: "Decoration",
|
||||||
|
key: "text-decoration-line",
|
||||||
|
control: OptionSelect,
|
||||||
|
defaultValue: "None",
|
||||||
|
options: [
|
||||||
|
{ label: "None", value: "none" },
|
||||||
|
{ label: "Underline", value: "underline" },
|
||||||
|
{ label: "Overline", value: "overline" },
|
||||||
|
{ label: "Line-through", value: "line-through" },
|
||||||
|
{ label: "Under Over", value: "underline overline" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
{ label: "transform", key: "text-transform", control: Input }, //custom
|
{ label: "transform", key: "text-transform", control: Input }, //custom
|
||||||
{ label: "style", key: "font-style", control: Input }, //custom
|
{ label: "style", key: "font-style", control: Input }, //custom
|
||||||
]
|
]
|
||||||
|
@ -120,8 +214,7 @@ export const background = [
|
||||||
{
|
{
|
||||||
label: "Background",
|
label: "Background",
|
||||||
key: "background",
|
key: "background",
|
||||||
control: OptionSelect,
|
control: Input,
|
||||||
options: ["black", "white", "red", "blue", "green"],
|
|
||||||
},
|
},
|
||||||
{ label: "Image", key: "image", control: Input }, //custom
|
{ label: "Image", key: "image", control: Input }, //custom
|
||||||
]
|
]
|
||||||
|
@ -132,15 +225,45 @@ export const border = [
|
||||||
{
|
{
|
||||||
label: "Color",
|
label: "Color",
|
||||||
key: "border-color",
|
key: "border-color",
|
||||||
control: OptionSelect,
|
control: Input,
|
||||||
options: ["black", "white", "red", "blue", "green"],
|
},
|
||||||
|
{
|
||||||
|
label: "Style",
|
||||||
|
key: "border-style",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
"none",
|
||||||
|
"hidden",
|
||||||
|
"dotted",
|
||||||
|
"dashed",
|
||||||
|
"solid",
|
||||||
|
"double",
|
||||||
|
"groove",
|
||||||
|
"ridge",
|
||||||
|
"inset",
|
||||||
|
"outset",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{ label: "Style", key: "border-style", control: Input },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const effects = [
|
export const effects = [
|
||||||
{ label: "Opacity", key: "opacity", control: Input },
|
{ label: "Opacity", key: "opacity", control: Input },
|
||||||
{ label: "Rotate", key: "transform", control: Input }, //needs special control
|
{
|
||||||
|
label: "Rotate",
|
||||||
|
key: "transform",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
{ label: "None", value: "rotate(0deg)" },
|
||||||
|
{ label: "45 degrees", value: "rotate(45deg)" },
|
||||||
|
{ label: "90 degrees", value: "rotate(90deg)" },
|
||||||
|
{ label: "135 degrees", value: "rotate(135deg)" },
|
||||||
|
{ label: "180 degrees", value: "rotate(180deg)" },
|
||||||
|
{ label: "225 degrees", value: "rotate(225deg)" },
|
||||||
|
{ label: "270 degrees", value: "rotate(270deg)" },
|
||||||
|
{ label: "315 degrees", value: "rotate(315deg)" },
|
||||||
|
{ label: "360 degrees", value: "rotate(360deg)" },
|
||||||
|
],
|
||||||
|
}, //needs special control
|
||||||
{ label: "Shadow", key: "box-shadow", control: Input },
|
{ label: "Shadow", key: "box-shadow", control: Input },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,6 @@ export default {
|
||||||
name: "Basic",
|
name: "Basic",
|
||||||
isCategory: true,
|
isCategory: true,
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
_component: "##builtin/screenslot",
|
|
||||||
name: "Screenslot",
|
|
||||||
description:
|
|
||||||
"This component is a placeholder for the rendering of a screen within a page.",
|
|
||||||
icon: "ri-crop-2-line",
|
|
||||||
commonProps: {},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
_component: "@budibase/standard-components/container",
|
_component: "@budibase/standard-components/container",
|
||||||
name: "Container",
|
name: "Container",
|
||||||
|
@ -119,7 +110,7 @@ export default {
|
||||||
{
|
{
|
||||||
name: "Input",
|
name: "Input",
|
||||||
description: "These components handle user input.",
|
description: "These components handle user input.",
|
||||||
icon: "ri-edit-box-line",
|
icon: "ri-edit-box-fill",
|
||||||
commonProps: {},
|
commonProps: {},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -127,7 +118,7 @@ export default {
|
||||||
name: "Textfield",
|
name: "Textfield",
|
||||||
description:
|
description:
|
||||||
"A textfield component that allows the user to input text.",
|
"A textfield component that allows the user to input text.",
|
||||||
icon: "ri-edit-box-line",
|
icon: "ri-edit-box-fill",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [
|
settings: [
|
||||||
|
@ -145,7 +136,7 @@ export default {
|
||||||
_component: "@budibase/standard-components/checkbox",
|
_component: "@budibase/standard-components/checkbox",
|
||||||
name: "Checkbox",
|
name: "Checkbox",
|
||||||
description: "A selectable checkbox component",
|
description: "A selectable checkbox component",
|
||||||
icon: "ri-checkbox-line",
|
icon: "ri-checkbox-fill",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [{ label: "Label", key: "label", control: Input }],
|
settings: [{ label: "Label", key: "label", control: Input }],
|
||||||
|
@ -166,7 +157,7 @@ export default {
|
||||||
name: "Select",
|
name: "Select",
|
||||||
description:
|
description:
|
||||||
"A select component for choosing from different options",
|
"A select component for choosing from different options",
|
||||||
icon: "ri-file-list-line",
|
icon: "ri-file-list-fill",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [],
|
settings: [],
|
||||||
|
@ -236,7 +227,7 @@ export default {
|
||||||
name: "Card",
|
name: "Card",
|
||||||
description:
|
description:
|
||||||
"A basic card component that can contain content and actions.",
|
"A basic card component that can contain content and actions.",
|
||||||
icon: "ri-layout-bottom-line",
|
icon: "ri-layout-bottom-fill",
|
||||||
children: [],
|
children: [],
|
||||||
properties: { design: { ...all } },
|
properties: { design: { ...all } },
|
||||||
},
|
},
|
||||||
|
@ -248,21 +239,6 @@ export default {
|
||||||
children: [],
|
children: [],
|
||||||
properties: { design: { ...all } },
|
properties: { design: { ...all } },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Navigation Bar",
|
|
||||||
_component: "@budibase/standard-components/Navigation",
|
|
||||||
description:
|
|
||||||
"A component for handling the navigation within your app.",
|
|
||||||
icon: "ri-navigation-fill",
|
|
||||||
children: [],
|
|
||||||
properties: { design: { ...all } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Data",
|
|
||||||
isCategory: true,
|
|
||||||
children: [
|
|
||||||
{
|
{
|
||||||
name: "Table",
|
name: "Table",
|
||||||
description: "A component that generates a table from your data.",
|
description: "A component that generates a table from your data.",
|
||||||
|
@ -283,27 +259,11 @@ export default {
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
_component: "@budibase/standard-components/datatable",
|
|
||||||
name: "DataTable",
|
|
||||||
description: "A table for displaying data from the backend.",
|
|
||||||
icon: "ri-archive-drawer-fill",
|
|
||||||
properties: { design: { ...all } },
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_component: "@budibase/standard-components/dataform",
|
|
||||||
name: "DataForm",
|
|
||||||
description: "Form stuff",
|
|
||||||
icon: "ri-file-edit-fill",
|
|
||||||
properties: { design: { ...all } },
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Chart",
|
name: "Chart",
|
||||||
_component: "@budibase/standard-components/datachart",
|
_component: "@budibase/standard-components/datachart",
|
||||||
description: "Shiny chart",
|
description: "Shiny chart",
|
||||||
icon: "ri-bar-chart-line",
|
icon: "ri-bar-chart-fill",
|
||||||
properties: { design: { ...all } },
|
properties: { design: { ...all } },
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
@ -311,7 +271,7 @@ export default {
|
||||||
name: "List",
|
name: "List",
|
||||||
_component: "@budibase/standard-components/datalist",
|
_component: "@budibase/standard-components/datalist",
|
||||||
description: "Shiny list",
|
description: "Shiny list",
|
||||||
icon: "ri-file-list-line",
|
icon: "ri-file-list-fill",
|
||||||
properties: { design: { ...all } },
|
properties: { design: { ...all } },
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
@ -319,11 +279,36 @@ export default {
|
||||||
name: "Map",
|
name: "Map",
|
||||||
_component: "@budibase/standard-components/datamap",
|
_component: "@budibase/standard-components/datamap",
|
||||||
description: "Shiny map",
|
description: "Shiny map",
|
||||||
icon: "ri-map-pin-line",
|
icon: "ri-map-pin-fill",
|
||||||
properties: { design: { ...all } },
|
properties: { design: { ...all } },
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Layouts",
|
||||||
|
isCategory: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
_component: "##builtin/screenslot",
|
||||||
|
name: "Screenslot",
|
||||||
|
description:
|
||||||
|
"This component is a placeholder for the rendering of a screen within a page.",
|
||||||
|
icon: "ri-crop-2-fill",
|
||||||
|
properties: { design: { ...all } },
|
||||||
|
commonProps: {},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nav Bar",
|
||||||
|
_component: "@budibase/standard-components/Navigation",
|
||||||
|
description:
|
||||||
|
"A component for handling the navigation within your app.",
|
||||||
|
icon: "ri-navigation-fill",
|
||||||
|
children: [],
|
||||||
|
properties: { design: { ...all } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
--grey: #F2F2F2;
|
--grey: #F2F2F2;
|
||||||
--grey-light: #FBFBFB;
|
--grey-light: #FBFBFB;
|
||||||
|
--grey-medium: #e8e8ef;
|
||||||
--grey-dark: #E6E6E6;
|
--grey-dark: #E6E6E6;
|
||||||
|
|
||||||
--primary100: #0055ff;
|
--primary100: #0055ff;
|
||||||
|
@ -136,6 +137,10 @@ h5 {
|
||||||
color: var(--darkslate);
|
color: var(--darkslate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-family: var(--fontnormal);
|
||||||
|
}
|
||||||
|
|
||||||
.hoverable:hover {
|
.hoverable:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import Modal from "svelte-simple-modal"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
|
@ -25,55 +26,58 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<Modal>
|
||||||
|
<div class="root">
|
||||||
|
|
||||||
<div class="top-nav">
|
<div class="top-nav">
|
||||||
<div class="topleftnav">
|
<div class="topleftnav">
|
||||||
<button class="home-logo">
|
<button class="home-logo">
|
||||||
<img
|
<img
|
||||||
src="/_builder/assets/bb-logo.svg"
|
src="/_builder/assets/bb-logo.svg"
|
||||||
alt="budibase icon"
|
alt="budibase icon"
|
||||||
on:click={() => $goto(`/`)} />
|
on:click={() => $goto(`/`)} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- This gets all indexable subroutes and sticks them in the top nav. -->
|
<!-- This gets all indexable subroutes and sticks them in the top nav. -->
|
||||||
{#each $layout.children as { path, title }}
|
{#each $layout.children as { path, title }}
|
||||||
<span
|
<span
|
||||||
class:active={$isActive(path)}
|
class:active={$isActive(path)}
|
||||||
class="topnavitem"
|
class="topnavitem"
|
||||||
on:click={() => $goto(path)}>
|
on:click={() => $goto(path)}>
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
<!-- <IconButton icon="home"
|
<!-- <IconButton icon="home"
|
||||||
color="var(--slate)"
|
color="var(--slate)"
|
||||||
hoverColor="var(--secondary75)"/> -->
|
hoverColor="var(--secondary75)"/> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<span
|
<span
|
||||||
class:active={$isActive(`/settings`)}
|
class:active={$isActive(`/settings`)}
|
||||||
class="topnavitemright"
|
class="topnavitemright"
|
||||||
on:click={() => $goto(`/settings`)}>
|
on:click={() => $goto(`/settings`)}>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</span>
|
</span>
|
||||||
<span class:active={false} class="topnavitemright">
|
<span
|
||||||
<a href={`/${application}`} target="_blank">
|
class:active={false}
|
||||||
|
class="topnavitemright"
|
||||||
|
on:click={() => (location = `/${application}`)}>
|
||||||
<PreviewIcon />
|
<PreviewIcon />
|
||||||
</a>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#await promise}
|
||||||
|
<!-- This should probably be some kind of loading state? -->
|
||||||
|
<div />
|
||||||
|
{:then}
|
||||||
|
<slot />
|
||||||
|
{:catch error}
|
||||||
|
<p>Something went wrong: {error.message}</p>
|
||||||
|
{/await}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</Modal>
|
||||||
{#await promise}
|
|
||||||
<!-- This should probably be some kind of loading state? -->
|
|
||||||
<div />
|
|
||||||
{:then}
|
|
||||||
<slot />
|
|
||||||
{:catch error}
|
|
||||||
<p>Something went wrong: {error.message}</p>
|
|
||||||
{/await}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
|
@ -151,7 +155,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnavitemright:hover {
|
.topnavitemright:hover {
|
||||||
color: rgb(255, 255, 255, 0.8);
|
color: var(--ink);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,102 +43,49 @@
|
||||||
|
|
||||||
<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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: 1800px) {
|
|
||||||
.root {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 300px 1fr 300px;
|
|
||||||
width: 100%;
|
|
||||||
background: var(--grey-light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-nav {
|
.ui-nav {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
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;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-pane {
|
.preview-pane {
|
||||||
|
@ -162,44 +100,6 @@
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.components-nav-page {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--ink);
|
|
||||||
padding-left: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
opacity: 0.4;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.components-nav-header {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--ink);
|
|
||||||
margin-top: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
opacity: 0.4;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-items-container {
|
|
||||||
padding: 1rem 0rem 0rem 0rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-group-header {
|
|
||||||
display: flex;
|
|
||||||
padding: 0px 20px 0px 20px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: bold;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-group-header > div:nth-child(1) {
|
.nav-group-header > div:nth-child(1) {
|
||||||
padding: 0rem 0.5rem 0rem 0rem;
|
padding: 0rem 0.5rem 0rem 0rem;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
|
@ -207,13 +107,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;
|
||||||
|
@ -224,19 +117,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>
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
<script>
|
||||||
|
import Modal from "svelte-simple-modal"
|
||||||
|
import {
|
||||||
|
SettingsIcon,
|
||||||
|
AppsIcon,
|
||||||
|
UpdatesIcon,
|
||||||
|
HostingIcon,
|
||||||
|
DocumentationIcon,
|
||||||
|
TutorialsIcon,
|
||||||
|
CommunityIcon,
|
||||||
|
ContributionIcon,
|
||||||
|
BugIcon,
|
||||||
|
EmailIcon,
|
||||||
|
TwitterIcon,
|
||||||
|
} from "components/common/Icons/"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal>
|
||||||
|
<div class="root">
|
||||||
|
<div class="ui-nav">
|
||||||
|
<div class="home-logo">
|
||||||
|
<img src="/_builder/assets/bb-logo.svg" alt="Budibase icon" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-section">
|
||||||
|
<div class="nav-section-title">Build</div>
|
||||||
|
<div class="nav-item-home">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<AppsIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Apps</div>
|
||||||
|
</div>
|
||||||
|
<div class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<SettingsIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Settings</div>
|
||||||
|
</div>
|
||||||
|
<a href="https://budibase.con/login" target="_blank" class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<UpdatesIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Updates</div>
|
||||||
|
</a>
|
||||||
|
<a href="https://budibase.con/login" target="_blank" class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<HostingIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Hosting</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-section">
|
||||||
|
<div class="nav-section-title">Learn</div>
|
||||||
|
<a href="https://docs.budibase.com/" target="_blank" class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<DocumentationIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Documentation</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://docs.budibase.com/tutorial/quick-start"
|
||||||
|
target="_blank"
|
||||||
|
class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<TutorialsIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Tutorials</div>
|
||||||
|
</a>
|
||||||
|
<a href="https://forum.budibase.com/" target="_blank" class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<CommunityIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Community</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-section">
|
||||||
|
<div class="nav-section-title">Contact</div>
|
||||||
|
<a
|
||||||
|
href="https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md"
|
||||||
|
target="_blank"
|
||||||
|
class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<ContributionIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Contribute to our product</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/Budibase/budibase/issues"
|
||||||
|
target="_blank"
|
||||||
|
class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<BugIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Report bug</div>
|
||||||
|
</a>
|
||||||
|
<a href="mailto:support@budibase.com" target="_blank" class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<EmailIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Email</div>
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/budibase" target="_blank" class="nav-item">
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<TwitterIcon />
|
||||||
|
</span>
|
||||||
|
<div class="nav-item-title">Twitter</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px 1fr;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--grey-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-nav {
|
||||||
|
grid-column: 1;
|
||||||
|
background-color: var(--white);
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-right: 1px solid var(--grey-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-logo {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-logo img {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-section {
|
||||||
|
margin: 20px 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-section-title {
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0px 0px 4px 0px;
|
||||||
|
padding: 0px 0px 0px 12px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-home {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0px 0px 4px 0px;
|
||||||
|
padding: 0px 0px 0px 12px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: var(--blue-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background-color: var(--grey-light);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item::selection {
|
||||||
|
background-color: var(--blue-light);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-icon {
|
||||||
|
color: var(--ink-light);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,23 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import AppList from "components/start/AppList.svelte"
|
import AppList from "components/start/AppList.svelte"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
import IconButton from "components/common/IconButton.svelte"
|
import IconButton from "components/common/IconButton.svelte"
|
||||||
import {
|
|
||||||
SettingsIcon,
|
|
||||||
AppsIcon,
|
|
||||||
UpdatesIcon,
|
|
||||||
HostingIcon,
|
|
||||||
DocumentationIcon,
|
|
||||||
TutorialsIcon,
|
|
||||||
CommunityIcon,
|
|
||||||
ContributionIcon,
|
|
||||||
BugIcon,
|
|
||||||
EmailIcon,
|
|
||||||
TwitterIcon,
|
|
||||||
} from "components/common/Icons/"
|
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
|
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||||
|
|
||||||
let promise = getApps()
|
let promise = getApps()
|
||||||
|
|
||||||
|
@ -31,229 +21,54 @@
|
||||||
throw new Error(json)
|
throw new Error(json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle create app modal
|
||||||
|
const { open } = getContext("simple-modal")
|
||||||
|
|
||||||
|
const showCreateAppModal = () => {
|
||||||
|
open(
|
||||||
|
CreateAppModal,
|
||||||
|
{
|
||||||
|
message: "What is your name?",
|
||||||
|
hasForm: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
closeButton: false,
|
||||||
|
closeOnEsc: false,
|
||||||
|
closeOnOuterClick: false,
|
||||||
|
styleContent: { padding: 0 },
|
||||||
|
closeOnOuterClick: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="welcome">Welcome to Budibase</div>
|
||||||
<div class="ui-nav">
|
<div class="banner">
|
||||||
<div class="home-logo">
|
<div class="banner-content">
|
||||||
<img src="/_builder/assets/bb-logo.svg" alt="Budibase icon" />
|
<div class="banner-header">
|
||||||
</div>
|
Every accomplishment starts with a decision to try.
|
||||||
|
|
||||||
<div class="nav-section">
|
|
||||||
<div class="nav-section-title">Build</div>
|
|
||||||
<div class="nav-item-home">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<AppsIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Apps</div>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<SettingsIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Settings</div>
|
|
||||||
</div>
|
|
||||||
<a href="https://budibase.con/login" target="_blank" class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<UpdatesIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Updates</div>
|
|
||||||
</a>
|
|
||||||
<a href="https://budibase.con/login" target="_blank" class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<HostingIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Hosting</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-section">
|
|
||||||
<div class="nav-section-title">Learn</div>
|
|
||||||
<a href="https://docs.budibase.com/" target="_blank" class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<DocumentationIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Documentation</div>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://docs.budibase.com/tutorial/quick-start"
|
|
||||||
target="_blank"
|
|
||||||
class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<TutorialsIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Tutorials</div>
|
|
||||||
</a>
|
|
||||||
<a href="https://forum.budibase.com/" target="_blank" class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<CommunityIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Community</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-section">
|
|
||||||
<div class="nav-section-title">Contact</div>
|
|
||||||
<a
|
|
||||||
href="https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md"
|
|
||||||
target="_blank"
|
|
||||||
class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<ContributionIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Contribute to our product</div>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/Budibase/budibase/issues"
|
|
||||||
target="_blank"
|
|
||||||
class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<BugIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Report bug</div>
|
|
||||||
</a>
|
|
||||||
<a href="mailto:support@budibase.com" target="_blank" class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<EmailIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Email</div>
|
|
||||||
</a>
|
|
||||||
<a href="https://twitter.com/budibase" target="_blank" class="nav-item">
|
|
||||||
<span class="nav-item-icon">
|
|
||||||
<TwitterIcon />
|
|
||||||
</span>
|
|
||||||
<div class="nav-item-title">Twitter</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button class="banner-button" type="button" on:click={showCreateAppModal}>
|
||||||
|
<i class="ri-add-circle-fill" />
|
||||||
|
Create New Web App
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="banner-image">
|
||||||
<div class="main">
|
<img src="/_builder/assets/banner-image.png" alt="Bannerimage" />
|
||||||
<div class="welcome">Welcome to Budibase</div>
|
|
||||||
<div class="banner">
|
|
||||||
<div class="banner-content">
|
|
||||||
<div class="banner-header">
|
|
||||||
Every accomplishment starts with a decision to try.
|
|
||||||
</div>
|
|
||||||
<button class="banner-button" type="button">
|
|
||||||
<i class="ri-add-circle-fill" />
|
|
||||||
Create New Web App
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="banner-image">
|
|
||||||
<img src="/_builder/assets/banner-image.png" alt="Bannerimage" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#await promise}
|
|
||||||
<div class="spinner-container">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
{:then result}
|
|
||||||
<AppList apps={result} />
|
|
||||||
{:catch err}
|
|
||||||
<h1 style="color:red">{err}</h1>
|
|
||||||
{/await}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{#await promise}
|
||||||
|
<div class="spinner-container">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
{:then result}
|
||||||
|
<AppList apps={result} />
|
||||||
|
{:catch err}
|
||||||
|
<h1 style="color:red">{err}</h1>
|
||||||
|
{/await}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 275px 1fr;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background: var(--grey-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 1800px) {
|
|
||||||
.root {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 300px 1fr;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background: var(--grey-light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
grid-column: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-nav {
|
|
||||||
grid-column: 1;
|
|
||||||
background-color: var(--white);
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-right: 1px solid var(--grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-logo {
|
|
||||||
cursor: pointer;
|
|
||||||
height: 40px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-logo img {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-section {
|
|
||||||
margin: 20px 0px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-section-title {
|
|
||||||
font-size: 20px;
|
|
||||||
color: var(--ink);
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 0px 0px 4px 0px;
|
|
||||||
padding: 0px 0px 0px 12px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item-home {
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 0px 0px 4px 0px;
|
|
||||||
padding: 0px 0px 0px 12px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: var(--blue-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item:hover {
|
|
||||||
background-color: var(--grey-light);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item::selection {
|
|
||||||
background-color: var(--blue-light);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item-title {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--ink);
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item-icon {
|
|
||||||
color: var(--ink-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome {
|
.welcome {
|
||||||
margin: 60px 80px 0px 80px;
|
margin: 60px 80px 0px 80px;
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
|
|
|
@ -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
|
|
@ -45,7 +45,6 @@
|
||||||
"@budibase/core": "^0.0.32",
|
"@budibase/core": "^0.0.32",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@sendgrid/mail": "^7.1.1",
|
"@sendgrid/mail": "^7.1.1",
|
||||||
"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",
|
||||||
|
@ -68,6 +67,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"
|
||||||
|
|
|
@ -3,6 +3,10 @@ const ClientDb = require("../../db/clientDb")
|
||||||
const { getPackageForBuilder } = require("../../utilities/builder")
|
const { getPackageForBuilder } = require("../../utilities/builder")
|
||||||
const newid = require("../../db/newid")
|
const newid = require("../../db/newid")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
|
const instanceController = require("./instance")
|
||||||
|
const { resolve, join } = require("path")
|
||||||
|
const { copy, readJSON, writeJSON, exists } = require("fs-extra")
|
||||||
|
const { exec } = require("child_process")
|
||||||
|
|
||||||
exports.fetch = async function(ctx) {
|
exports.fetch = async function(ctx) {
|
||||||
const db = new CouchDB(ClientDb.name(env.CLIENT_ID))
|
const db = new CouchDB(ClientDb.name(env.CLIENT_ID))
|
||||||
|
@ -32,12 +36,77 @@ exports.create = async function(ctx) {
|
||||||
"@budibase/standard-components",
|
"@budibase/standard-components",
|
||||||
"@budibase/materialdesign-components",
|
"@budibase/materialdesign-components",
|
||||||
],
|
],
|
||||||
...ctx.request.body,
|
name: ctx.request.body.name,
|
||||||
|
description: ctx.request.body.description,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rev } = await db.post(newApplication)
|
const { rev } = await db.post(newApplication)
|
||||||
newApplication._rev = rev
|
newApplication._rev = rev
|
||||||
|
|
||||||
|
const createInstCtx = {
|
||||||
|
params: {
|
||||||
|
clientId: env.CLIENT_ID,
|
||||||
|
applicationId: newApplication._id,
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
body: { name: `dev-${env.CLIENT_ID}` },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await instanceController.create(createInstCtx)
|
||||||
|
|
||||||
|
if (ctx.isDev) {
|
||||||
|
const newAppFolder = await createEmptyAppPackage(ctx, newApplication)
|
||||||
|
await runNpmInstall(newAppFolder)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.body = newApplication
|
ctx.body = newApplication
|
||||||
ctx.message = `Application ${ctx.request.body.name} created successfully`
|
ctx.message = `Application ${ctx.request.body.name} created successfully`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createEmptyAppPackage = async (ctx, app) => {
|
||||||
|
const templateFolder = resolve(
|
||||||
|
__dirname,
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"utilities",
|
||||||
|
"appDirectoryTemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const appsFolder = env.BUDIBASE_DIR
|
||||||
|
const newAppFolder = resolve(appsFolder, app._id)
|
||||||
|
|
||||||
|
if (await exists(newAppFolder)) {
|
||||||
|
ctx.throw(400, "App folder already exists for this application")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await copy(templateFolder, newAppFolder)
|
||||||
|
|
||||||
|
const packageJsonPath = join(appsFolder, app._id, "package.json")
|
||||||
|
const packageJson = await readJSON(packageJsonPath)
|
||||||
|
|
||||||
|
packageJson.name = npmFriendlyAppName(app.name)
|
||||||
|
|
||||||
|
await writeJSON(packageJsonPath, packageJson)
|
||||||
|
|
||||||
|
return newAppFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
const runNpmInstall = async newAppFolder => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const cmd = `cd ${newAppFolder} && npm install`
|
||||||
|
exec(cmd, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
resolve(stdout ? stdout : stderr)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const npmFriendlyAppName = name =>
|
||||||
|
name
|
||||||
|
.replace(/_/g, "")
|
||||||
|
.replace(/./g, "")
|
||||||
|
.replace(/ /g, "")
|
||||||
|
.toLowerCase()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -90,3 +88,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)
|
||||||
ctx.eventEmitter.emit(`record:delete`, record)
|
ctx.eventEmitter.emit(`record:delete`, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ exports.create = async function(ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.update = async function(ctx) {}
|
exports.update = async function() {}
|
||||||
|
|
||||||
exports.destroy = async function(ctx) {
|
exports.destroy = async function(ctx) {
|
||||||
const database = new CouchDB(ctx.params.instanceId)
|
const database = new CouchDB(ctx.params.instanceId)
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
const CouchDB = require("../../../db")
|
const CouchDB = require("../../db")
|
||||||
const Ajv = require("ajv")
|
const newid = require("../../db/newid")
|
||||||
const newid = require("../../../db/newid")
|
|
||||||
|
|
||||||
const ajv = new Ajv()
|
|
||||||
|
|
||||||
exports.create = async function(ctx) {
|
exports.create = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.params.instanceId)
|
const db = new CouchDB(ctx.params.instanceId)
|
||||||
|
@ -19,8 +16,7 @@ exports.create = async function(ctx) {
|
||||||
message: "Workflow created successfully",
|
message: "Workflow created successfully",
|
||||||
workflow: {
|
workflow: {
|
||||||
...workflow,
|
...workflow,
|
||||||
_rev: response.rev,
|
...response,
|
||||||
_id: response.id,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -30,7 +30,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",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,19 +46,6 @@ exports.createModel = async (request, instanceId, model) => {
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createRecord = async ({ request, instanceId, modelId, record }) => {
|
|
||||||
record = record || {
|
|
||||||
modelId,
|
|
||||||
name: "test name",
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await request
|
|
||||||
.post(`/api/${instanceId}/${modelId}/records`)
|
|
||||||
.send(record)
|
|
||||||
.set(exports.defaultHeaders)
|
|
||||||
return res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.createView = async (request, instanceId, view) => {
|
exports.createView = async (request, instanceId, view) => {
|
||||||
view = view || {
|
view = view || {
|
||||||
map: "function(doc) { emit(doc[doc.key], doc._id); } ",
|
map: "function(doc) { emit(doc[doc.key], doc._id); } ",
|
||||||
|
|
|
@ -197,4 +197,31 @@ describe("/records", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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"])
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,7 @@ const router = Router()
|
||||||
|
|
||||||
router
|
router
|
||||||
.get(
|
.get(
|
||||||
"/api/:instanceId/views/:viewName",
|
"/api/:instanceId/view/:viewName",
|
||||||
authorized(READ_VIEW, ctx => ctx.params.viewName),
|
authorized(READ_VIEW, ctx => ctx.params.viewName),
|
||||||
recordController.fetchView
|
recordController.fetchView
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,7 +16,7 @@ app.use(
|
||||||
prettyPrint: {
|
prettyPrint: {
|
||||||
levelFirst: true,
|
levelFirst: true,
|
||||||
},
|
},
|
||||||
level: process.env.NODE_ENV === "jest" ? "silent" : "info",
|
level: env.LOG_LEVEL || "error",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
const WORKFLOW_SCHEMA = {
|
|
||||||
properties: {
|
|
||||||
type: "workflow",
|
|
||||||
pageId: {
|
|
||||||
type: "string",
|
|
||||||
},
|
|
||||||
screenId: {
|
|
||||||
type: "string",
|
|
||||||
},
|
|
||||||
live: {
|
|
||||||
type: "boolean",
|
|
||||||
},
|
|
||||||
uiTree: {
|
|
||||||
type: "object",
|
|
||||||
},
|
|
||||||
definition: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
triggers: { type: "array" },
|
|
||||||
steps: { type: "array" },
|
|
||||||
// next: {
|
|
||||||
// type: "object",
|
|
||||||
// properties: {
|
|
||||||
// environment: { environment: "string" },
|
|
||||||
// type: { type: "string" },
|
|
||||||
// actionId: { type: "string" },
|
|
||||||
// args: { type: "object" },
|
|
||||||
// conditions: { type: "array" },
|
|
||||||
// errorHandling: { type: "object" },
|
|
||||||
// next: { type: "object" },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
WORKFLOW_SCHEMA,
|
|
||||||
}
|
|
|
@ -0,0 +1 @@
|
||||||
|
dist/
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@budibase/standard-components": "0.x",
|
||||||
|
"@budibase/materialdesign-components": "0.x"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"title": "Test App",
|
||||||
|
"favicon": "./_shared/favicon.png",
|
||||||
|
"stylesheets": [],
|
||||||
|
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"],
|
||||||
|
"props" : {
|
||||||
|
"_component": "@budibase/standard-components/container",
|
||||||
|
"_children": [],
|
||||||
|
"_id": 0,
|
||||||
|
"type": "div",
|
||||||
|
"_styles": {
|
||||||
|
"layout": {},
|
||||||
|
"position": {}
|
||||||
|
},
|
||||||
|
"_code": ""
|
||||||
|
},
|
||||||
|
"_css": "",
|
||||||
|
"uiFunctions": ""
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"title": "Test App",
|
||||||
|
"favicon": "./_shared/favicon.png",
|
||||||
|
"stylesheets": [],
|
||||||
|
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"],
|
||||||
|
"props" : {
|
||||||
|
"_component": "@budibase/standard-components/container",
|
||||||
|
"_children": [],
|
||||||
|
"_id": 1,
|
||||||
|
"type": "div",
|
||||||
|
"_styles": {
|
||||||
|
"layout": {},
|
||||||
|
"position": {}
|
||||||
|
},
|
||||||
|
"_code": ""
|
||||||
|
},
|
||||||
|
"_css": "",
|
||||||
|
"uiFunctions": ""
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = () => ({})
|
|
@ -693,7 +693,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==
|
||||||
|
@ -6659,6 +6659,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"
|
||||||
|
|
Loading…
Reference in New Issue