Updates backend routing

This merges in the changes to enable routing for the backend
This commit is contained in:
Kevin Åberg Kultalahti 2020-04-13 16:29:48 +02:00 committed by GitHub
commit 5c16770e3b
55 changed files with 506 additions and 995 deletions

View File

@ -50,6 +50,7 @@
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"shortid": "^2.2.8", "shortid": "^2.2.8",
"string_decoder": "^1.2.0", "string_decoder": "^1.2.0",
"svelte-simple-modal": "^0.3.0",
"uikit": "^3.1.7" "uikit": "^3.1.7"
}, },
"devDependencies": { "devDependencies": {
@ -58,7 +59,7 @@
"@babel/preset-env": "^7.5.5", "@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@rollup/plugin-alias": "^3.0.1", "@rollup/plugin-alias": "^3.0.1",
"@sveltech/routify": "1.5.0-beta.16", "@sveltech/routify": "1.5.0-beta.40",
"babel-jest": "^24.8.0", "babel-jest": "^24.8.0",
"browser-sync": "^2.26.7", "browser-sync": "^2.26.7",
"http-proxy-middleware": "^0.19.1", "http-proxy-middleware": "^0.19.1",

View File

@ -8,38 +8,10 @@ import { terser } from "rollup-plugin-terser"
import builtins from "rollup-plugin-node-builtins" import builtins from "rollup-plugin-node-builtins"
import nodeglobals from "rollup-plugin-node-globals" import nodeglobals from "rollup-plugin-node-globals"
import copy from "rollup-plugin-copy" import copy from "rollup-plugin-copy"
import browsersync from "rollup-plugin-browsersync"
import proxy from "http-proxy-middleware"
import replace from "rollup-plugin-replace" import replace from "rollup-plugin-replace"
import path from "path" import path from "path"
const target = "http://localhost:4001"
const _builderProxy = proxy("/_builder", {
target: "http://localhost:3000",
pathRewrite: { "^/_builder": "" },
})
const apiProxy = proxy(
[
"/_builder/assets/**",
"/_builder/api/**",
"/_builder/**/componentlibrary",
"/_builder/instance/**",
],
{
target,
logLevel: "debug",
changeOrigin: true,
cookieDomainRewrite: true,
onProxyReq(proxyReq) {
if (proxyReq.getHeader("origin")) {
proxyReq.setHeader("origin", target)
}
},
}
)
const production = !process.env.ROLLUP_WATCH const production = !process.env.ROLLUP_WATCH
const lodash_fp_exports = [ const lodash_fp_exports = [
@ -230,34 +202,9 @@ export default {
// Watch the `dist` directory and refresh the // Watch the `dist` directory and refresh the
// browser on changes when not in production // browser on changes when not in production
!production && livereload(outputpath), !production && livereload(outputpath),
!production &&
browsersync({
server: outputpath,
middleware: [apiProxy, _builderProxy],
}),
// If we're building for production (npm run build // If we're building for production (npm run build
// instead of npm run dev), minify // instead of npm run dev), minify
production && terser(), production && terser(),
], ],
watch: {
clearScreen: false,
},
}
function serve() {
let started = false
return {
writeBundle() {
if (!started) {
started = true
require("child_process").spawn("npm", ["run", "start"], {
stdio: ["ignore", "inherit", "inherit"],
shell: true,
})
}
},
}
} }

View File

@ -1,17 +1,13 @@
<script> <script>
import NoPackage from "./NoPackage.svelte" import Modal from "svelte-simple-modal"
import PackageRoot from "./PackageRoot.svelte"
import Settings from "./Settings.svelte"
import { store, initialise } from "builderStore"
import { onMount } from "svelte" import { onMount } from "svelte"
import IconButton from "components/common/IconButton.svelte" import { Router, basepath } from "@sveltech/routify"
import Spinner from "components/common/Spinner.svelte" import { routes } from "@sveltech/routify/tmp/routes"
import { store, initialise } from "builderStore"
import AppNotification, { import AppNotification, {
showAppNotification, showAppNotification,
} from "components/common/AppNotification.svelte" } from "components/common/AppNotification.svelte"
let init = initialise()
function showErrorBanner() { function showErrorBanner() {
showAppNotification({ showAppNotification({
status: "danger", status: "danger",
@ -24,57 +20,12 @@
window.addEventListener("error", showErrorBanner) window.addEventListener("error", showErrorBanner)
window.addEventListener("unhandledrejection", showErrorBanner) window.addEventListener("unhandledrejection", showErrorBanner)
}) })
$basepath = "/_builder"
</script> </script>
<main>
<AppNotification /> <AppNotification />
{#await init}
<div class="spinner-container">
<Spinner />
</div>
{:then result}
{#if $store.hasAppPackage} <Modal>
<PackageRoot /> <Router {routes} />
{:else} </Modal>
<NoPackage />
{/if}
{:catch err}
<h1 style="color:red">{err}</h1>
{/await}
<!--
<div class="settings">
<IconButton icon="settings"
on:click={store.showSettings}/>
</div>
{#if $store.useAnalytics}
<iframe src="https://marblekirby.github.io/bb-analytics.html" width="0" height="0" style="visibility:hidden;display:none"/>
{/if}
-->
</main>
<style>
main {
height: 100%;
width: 100%;
font-family: "Roboto", Helvetica, Arial, sans-serif;
}
.settings {
position: absolute;
bottom: 25px;
right: 25px;
}
.spinner-container {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -1,49 +0,0 @@
<script>
import BackendNav from "components/nav/BackendNav.svelte"
import SchemaManagementDrawer from "components/nav/SchemaManagementDrawer.svelte"
import Database from "components/database/DatabaseRoot.svelte"
import UserInterface from "components/userInterface/UserInterfaceRoot.svelte"
import ActionsAndTriggers from "components/actionsAndTriggers/ActionsAndTriggersRoot.svelte"
import AccessLevels from "components/accessLevels/AccessLevelsRoot.svelte"
import ComingSoon from "components/common/ComingSoon.svelte"
import { store, backendUiStore } from "builderStore"
</script>
<div class="root">
<div class="nav">
<BackendNav />
</div>
<div class="content">
{#if $backendUiStore.leftNavItem === 'DATABASE'}
<Database />
{:else if $backendUiStore.leftNavItem === 'ACTIONS'}
<ActionsAndTriggers />
{:else if $backendUiStore.leftNavItem === 'ACCESS_LEVELS'}
<AccessLevels />
{/if}
</div>
<div class="nav">
<SchemaManagementDrawer />
</div>
</div>
<style>
.root {
height: 100%;
display: flex;
background: #fafafa;
}
.content {
flex: 1 1 auto;
margin: 20px 40px;
}
.nav {
overflow: auto;
flex: 0 1 auto;
width: 275px;
height: 100%;
}
</style>

View File

@ -1,172 +0,0 @@
<script>
import IconButton from "components/common/IconButton.svelte"
import { store } from "builderStore"
import UserInterfaceRoot from "components/userInterface/UserInterfaceRoot.svelte"
import BackendRoot from "./BackendRoot.svelte"
import { fade } from "svelte/transition"
import { SettingsIcon, PreviewIcon } from "components/common/Icons/"
const TABS = {
BACKEND: "backend",
FRONTEND: "frontend",
}
let selectedTab = TABS.BACKEND
</script>
<div class="root">
<div class="top-nav">
<div class="topleftnav">
<button class="home-logo">
<img src="/_builder/assets/budibase-emblem-white.svg" />
</button>
<!-- <IconButton icon="home"
color="var(--slate)"
hoverColor="var(--secondary75)"/> -->
<span
class:active={selectedTab === TABS.BACKEND}
class="topnavitem"
on:click={() => (selectedTab = TABS.BACKEND)}>
Backend
</span>
<span
class:active={selectedTab === TABS.FRONTEND}
class="topnavitem"
on:click={() => (selectedTab = TABS.FRONTEND)}>
Frontend
</span>
</div>
<div class="toprightnav">
<span
class:active={selectedTab === TABS.FRONTEND}
class="topnavitemright"
on:click={() => selectedTab === TABS.FRONTEND}>
<SettingsIcon />
</span>
<span
class:active={selectedTab === TABS.FRONTEND}
class="topnavitemright"
on:click={() => selectedTab === TABS.FRONTEND}>
<PreviewIcon />
</span>
</div>
</div>
<div class="content">
{#if selectedTab === TABS.BACKEND}
<div in:fade out:fade>
<BackendRoot />
</div>
{:else}
<div in:fade out:fade>
<UserInterfaceRoot />
</div>
{/if}
</div>
</div>
<style>
.root {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
.top-nav {
flex: 0 0 auto;
height: 48px;
background: #0d203b;
padding: 0px 20px 0 20px;
display: flex;
box-sizing: border-box;
justify-content: space-between;
align-items: center;
}
.content {
flex: 1 1 auto;
width: 100%;
height: 100px;
overflow: hidden;
}
.content > div {
height: 100%;
width: 100%;
}
.toprightnav {
display: flex;
}
.topleftnav {
display: flex;
align-items: center;
}
.topnavitem {
cursor: pointer;
color: rgb(255, 255, 255, 0.6);
margin: 0px 10px;
padding-top: 4px;
font-weight: 500;
font-size: 1rem;
height: 100%;
align-items: center;
box-sizing: border-box;
}
.topnavitem:hover {
color: rgb(255, 255, 255, 0.8);
font-weight: 500;
}
.active {
color: white;
font-weight: 600;
}
.topnavitemright {
cursor: pointer;
color: rgb(255, 255, 255, 0.6);
margin: 0px 5px;
padding-top: 4px;
font-weight: 500;
font-size: 1rem;
height: 100%;
display: flex;
flex: 1;
align-items: center;
box-sizing: border-box;
}
.topnavitemright:hover {
color: rgb(255, 255, 255, 0.8);
font-weight: 500;
}
.home-logo {
border-style: none;
background-color: rgba(0, 0, 0, 0);
cursor: pointer;
outline: none;
height: 40px;
padding: 8px 10px 8px 0;
}
.home-logo:hover {
color: var(--hovercolor);
}
.home-logo:active {
outline: none;
}
.home-logo img {
height: 100%;
}
</style>

View File

@ -10,20 +10,7 @@ export const initialise = async () => {
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "production") {
LogRocket.init("knlald/budibase") LogRocket.init("knlald/budibase")
} }
setupRouter(store)
await store.initialise()
} catch (err) { } catch (err) {
console.log(err) console.log(err)
} }
} }
const setupRouter = writable => {
const pushState = history.pushState
history.pushState = () => {
pushState.apply(history, [writable])
writable.initialise()
}
window.addEventListener("hashchange", () => {
writable.initialise()
})
}

View File

@ -14,7 +14,6 @@ import {
export const getBackendUiStore = () => { export const getBackendUiStore = () => {
const INITIAL_BACKEND_UI_STATE = { const INITIAL_BACKEND_UI_STATE = {
leftNavItem: "DATABASE",
selectedView: { selectedView: {
records: [], records: [],
name: "", name: "",
@ -27,7 +26,6 @@ export const getBackendUiStore = () => {
const store = writable(INITIAL_BACKEND_UI_STATE) const store = writable(INITIAL_BACKEND_UI_STATE)
store.actions = { store.actions = {
navigate: name => store.update(state => ({ ...state, leftNavItem: name })),
database: { database: {
select: db => select: db =>
store.update(state => { store.update(state => {
@ -60,10 +58,6 @@ export const getBackendUiStore = () => {
return state return state
}), }),
}, },
modals: {
show: modal => store.update(state => ({ ...state, visibleModal: modal })),
hide: () => store.update(state => ({ ...state, visibleModal: null })),
},
users: { users: {
create: user => create: user =>
store.update(state => { store.update(state => {
@ -302,7 +296,7 @@ export const deleteLevel = store => level => {
state.accessLevels.levels = state.accessLevels.levels.filter( state.accessLevels.levels = state.accessLevels.levels.filter(
t => t.name !== level.name t => t.name !== level.name
) )
incrementAccessLevelsVersion(s) incrementAccessLevelsVersion(state)
saveBackend(state) saveBackend(state)
return state return state
}) })

View File

@ -1,3 +1,4 @@
//
import { filter, cloneDeep, last, concat, isEmpty, values } from "lodash/fp" import { filter, cloneDeep, last, concat, isEmpty, values } from "lodash/fp"
import { pipe, getNode, constructHierarchy } from "components/common/core" import { pipe, getNode, constructHierarchy } from "components/common/core"
import * as backendStoreActions from "./backend" import * as backendStoreActions from "./backend"
@ -49,7 +50,7 @@ export const getStore = () => {
const store = writable(initial) const store = writable(initial)
store.initialise = initialise(store, initial) store.setPackage = setPackage(store, initial)
store.newChildRecord = backendStoreActions.newRecord(store, false) store.newChildRecord = backendStoreActions.newRecord(store, false)
store.newRootRecord = backendStoreActions.newRecord(store, true) store.newRootRecord = backendStoreActions.newRecord(store, true)
@ -100,26 +101,12 @@ export const getStore = () => {
export default getStore export default getStore
const initialise = (store, initial) => async () => { const setPackage = (store, initial) => async (pkg) => {
appname = window.location.hash
? last(window.location.hash.substr(1).split("/"))
: ""
if (!appname) {
initial.apps = await api.get(`/_builder/api/apps`).then(r => r.json())
initial.hasAppPackage = false
store.set(initial)
return initial
}
const pkg = await api
.get(`/_builder/api/${appname}/appPackage`)
.then(r => r.json())
const [main_screens, unauth_screens] = await Promise.all([ const [main_screens, unauth_screens] = await Promise.all([
api.get(`/_builder/api/${appname}/pages/main/screens`).then(r => r.json()), api.get(`/_builder/api/${pkg.application.name}/pages/main/screens`).then(r => r.json()),
api api
.get(`/_builder/api/${appname}/pages/unauthenticated/screens`) .get(`/_builder/api/${pkg.application.name}/pages/unauthenticated/screens`)
.then(r => r.json()), .then(r => r.json()),
]) ])
@ -134,12 +121,12 @@ const initialise = (store, initial) => async () => {
}, },
} }
initial.libraries = await loadLibs(appname, pkg) initial.libraries = await loadLibs(pkg.application.name, pkg)
initial.loadLibraryUrls = pageName => { initial.loadLibraryUrls = pageName => {
const libs = libUrlsForPreview(pkg, pageName) const libs = libUrlsForPreview(pkg, pageName)
return libs return libs
} }
initial.appname = appname initial.appname = pkg.application.name
initial.pages = pkg.pages initial.pages = pkg.pages
initial.hasAppPackage = true initial.hasAppPackage = true
initial.hierarchy = pkg.appDefinition.hierarchy initial.hierarchy = pkg.appDefinition.hierarchy
@ -377,7 +364,7 @@ const savePage = store => async page => {
const addComponentLibrary = store => async lib => { const addComponentLibrary = store => async lib => {
const response = await api.get( const response = await api.get(
`/_builder/api/${appname}/componentlibrary?lib=${encodeURI(lib)}`, `/_builder/api/${s.appname}/componentlibrary?lib=${encodeURI(lib)}`,
undefined, undefined,
false false
) )
@ -436,7 +423,7 @@ const removeStylesheet = store => stylesheet => {
const _savePage = async s => { const _savePage = async s => {
const page = s.pages[s.currentPageName] const page = s.pages[s.currentPageName]
await api.post(`/_builder/api/${appname}/pages/${s.currentPageName}`, { await api.post(`/_builder/api/${s.appname}/pages/${s.currentPageName}`, {
page: { componentLibraries: s.pages.componentLibraries, ...page }, page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: s.currentPageFunctions, uiFunctions: s.currentPageFunctions,
screens: page._screens, screens: page._screens,

View File

@ -15,6 +15,8 @@
export let allLevels export let allLevels
export let hierarchy export let hierarchy
export let actions export let actions
export let close
export let title
let errors = [] let errors = []
let clonedLevel = cloneDeep(level) let clonedLevel = cloneDeep(level)
@ -52,6 +54,7 @@
if (errors.length > 0) return if (errors.length > 0) return
onFinished(clonedLevel) onFinished(clonedLevel)
close()
} }
const permissionChanged = perm => ev => { const permissionChanged = perm => ev => {
@ -70,9 +73,13 @@
<div> <div>
<div class="uk-modal-header">
<h4 class="budibase__title--4">{title}</h4>
</div>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form class="uk-form-horizontal"> <form on:submit|preventDefault class="uk-form-horizontal">
<Textbox label="Access Level Name" bind:text={clonedLevel.name} /> <Textbox label="Access Level Name" bind:text={clonedLevel.name} />

View File

@ -68,7 +68,7 @@
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form class="uk-form-horizontal"> <form on:submit|preventDefault class="uk-form-horizontal">
<Textbox label="Name" bind:text={clonedAction.name} /> <Textbox label="Name" bind:text={clonedAction.name} />
<Textbox <Textbox

View File

@ -22,10 +22,7 @@
let cancel = () => onFinished() let cancel = () => onFinished()
let save = () => { let save = () => {
const newTriggersList = [ const newTriggersList = [
...pipe( ...pipe(allTriggers, [filter(t => t !== trigger)]),
allTriggers,
[filter(t => t !== trigger)]
),
clonedTrigger, clonedTrigger,
] ]
@ -43,7 +40,7 @@
<ErrorsBox {errors} style="margin-bottom:20px" /> <ErrorsBox {errors} style="margin-bottom:20px" />
<form class="uk-form-horizontal"> <form on:submit|preventDefault class="uk-form-horizontal">
<Dropdown <Dropdown
label="Event" label="Event"

View File

@ -1,106 +0,0 @@
<script>
import ModelView from "./ModelView.svelte"
import IndexView from "./IndexView.svelte"
import ModelDataTable from "./ModelDataTable"
import { store, backendUiStore } from "builderStore"
import getIcon from "components/common/icon"
import DropdownButton from "components/common/DropdownButton.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import Modal from "components/common/Modal.svelte"
import * as api from "./ModelDataTable/api"
import {
CreateEditRecordModal,
CreateEditModelModal,
CreateEditViewModal,
CreateDatabaseModal,
DeleteRecordModal,
CreateUserModal,
} from "./ModelDataTable/modals"
let selectedRecord
async function selectRecord(record) {
selectedRecord = await api.loadRecord(record.key, {
appname: $store.appname,
instanceId: $backendUiStore.selectedDatabase.id,
})
}
function onClosed() {
backendUiStore.actions.modals.hide()
}
$: recordOpen = $backendUiStore.visibleModal === "RECORD"
$: modelOpen = $backendUiStore.visibleModal === "MODEL"
$: viewOpen = $backendUiStore.visibleModal === "VIEW"
$: databaseOpen = $backendUiStore.visibleModal === "DATABASE"
$: deleteRecordOpen = $backendUiStore.visibleModal === "DELETE_RECORD"
$: userOpen = $backendUiStore.visibleModal === "USER"
$: breadcrumbs = $backendUiStore.breadcrumbs.join(" / ")
</script>
<Modal isOpen={!!$backendUiStore.visibleModal} {onClosed}>
{#if recordOpen}
<CreateEditRecordModal record={selectedRecord} {onClosed} />
{/if}
{#if modelOpen}
<CreateEditModelModal {onClosed} />
{/if}
{#if viewOpen}
<CreateEditViewModal {onClosed} />
{/if}
{#if databaseOpen}
<CreateDatabaseModal {onClosed} />
{/if}
{#if deleteRecordOpen}
<DeleteRecordModal record={selectedRecord} {onClosed} />
{/if}
{#if userOpen}
<CreateUserModal {onClosed} />
{/if}
</Modal>
<div class="root">
<div class="node-view">
<div class="database-actions">
<div class="breadcrumb">{breadcrumbs}</div>
{#if $backendUiStore.selectedDatabase.id}
<ActionButton
primary
on:click={() => {
selectedRecord = null
backendUiStore.actions.modals.show('RECORD')
}}>
Create new record
</ActionButton>
{/if}
</div>
{#if $backendUiStore.selectedDatabase.id}
<ModelDataTable {selectRecord} />
{:else}Please select a database{/if}
</div>
</div>
<style>
.root {
height: 100%;
position: relative;
}
.node-view {
overflow-y: auto;
flex: 1 1 auto;
}
.breadcrumb {
text-transform: uppercase;
font-size: 13px;
font-weight: 500;
color: var(--secondary60);
}
.database-actions {
display: flex;
justify-content: space-between;
}
</style>

View File

@ -67,7 +67,7 @@
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form class="uk-form-stacked"> <form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={clonedField.name} /> <Textbox label="Name" bind:text={clonedField.name} />
<Dropdown <Dropdown
label="Type" label="Type"
@ -136,18 +136,19 @@
bind:value={clonedField.typeOptions.maxLength} /> bind:value={clonedField.typeOptions.maxLength} />
{/if} {/if}
</form> </form>
</div>
<footer> <footer>
<ActionButton primary on:click={save}>Save</ActionButton> <ActionButton primary on:click={save}>Save</ActionButton>
<ActionButton alert on:click={() => onFinished(false)}>Cancel</ActionButton> <ActionButton alert on:click={() => onFinished(false)}>Cancel</ActionButton>
</footer> </footer>
</div>
<style> <style>
.root {
margin: 20px;
}
footer { footer {
position: absolute;
padding: 20px; padding: 20px;
width: 100%; border-radius: 0 0 5px 5px;
bottom: 0; bottom: 0;
left: 0; left: 0;
background: #fafafa; background: #fafafa;

View File

@ -54,7 +54,7 @@
<i class="ri-eye-line button--toggled" /> <i class="ri-eye-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit View</h3> <h3 class="budibase__title--3">Create / Edit View</h3>
</heading> </heading>
<form class="uk-form-stacked root"> <form on:submit|preventDefault class="uk-form-stacked root">
<h4 class="budibase__label--big">Settings</h4> <h4 class="budibase__label--big">Settings</h4>
{#if $store.errors && $store.errors.length > 0} {#if $store.errors && $store.errors.length > 0}
<ErrorsBox errors={$store.errors} /> <ErrorsBox errors={$store.errors} />
@ -144,6 +144,7 @@
} }
heading { heading {
padding: 20px 20px 0 20px;
display: flex; display: flex;
align-items: center; align-items: center;
} }

View File

@ -1,5 +1,5 @@
<script> <script>
import { onMount } from "svelte" import { onMount, getContext } from "svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { import {
tap, tap,
@ -17,10 +17,39 @@
import { getIndexSchema } from "components/common/core" import { getIndexSchema } from "components/common/core"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import TablePagination from "./TablePagination.svelte" import TablePagination from "./TablePagination.svelte"
import { DeleteRecordModal } from "./modals" import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
import * as api from "./api" import * as api from "./api"
export let selectRecord const { open, close } = getContext("simple-modal")
const editRecord = async row => {
open(
CreateEditRecordModal,
{
onClosed: close,
record: await selectRecord(row),
},
{ styleContent: { padding: "0" } }
)
}
const deleteRecord = async row => {
open(
DeleteRecordModal,
{
onClosed: close,
record: await selectRecord(row),
},
{ styleContent: { padding: "0" } }
)
}
async function selectRecord(record) {
return await api.loadRecord(record.key, {
appname: $store.appname,
instanceId: $backendUiStore.selectedDatabase.id,
})
}
const ITEMS_PER_PAGE = 10 const ITEMS_PER_PAGE = 10
// Internal headers we want to hide from the user // Internal headers we want to hide from the user
@ -56,11 +85,7 @@
const getSchema = getIndexSchema($store.hierarchy) const getSchema = getIndexSchema($store.hierarchy)
const childViewsForRecord = compose( const childViewsForRecord = compose(flatten, map("indexes"), get("children"))
flatten,
map("indexes"),
get("children")
)
const hideInternalHeaders = compose( const hideInternalHeaders = compose(
remove(headerName => INTERNAL_HEADERS.includes(headerName)), remove(headerName => INTERNAL_HEADERS.includes(headerName)),
@ -133,16 +158,14 @@
</li> </li>
<li <li
on:click={() => { on:click={() => {
selectRecord(row) editRecord(row)
backendUiStore.actions.modals.show('RECORD')
}}> }}>
<div>Edit</div> <div>Edit</div>
</li> </li>
<li> <li>
<div <div
on:click={() => { on:click={() => {
selectRecord(row) deleteRecord(row)
backendUiStore.actions.modals.show('DELETE_RECORD')
}}> }}>
Delete Delete
</div> </div>
@ -166,7 +189,6 @@
</section> </section>
<style> <style>
.title { .title {
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;

View File

@ -1,6 +1,6 @@
<script> <script>
import Modal from "components/common/Modal.svelte"
import { store } from "builderStore" import { store } from "builderStore"
import Modal from "components/common/Modal.svelte"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import * as api from "../api" import * as api from "../api"
@ -15,24 +15,26 @@
} }
</script> </script>
<div>
<section> <section>
Database Name Database Name
<input class="uk-input" type="text" bind:value={databaseName} /> <input class="uk-input" type="text" bind:value={databaseName} />
</section>
<footer> <footer>
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton disabled={!databaseName} on:click={createDatabase}> <ActionButton disabled={!databaseName} on:click={createDatabase}>
Save Save
</ActionButton> </ActionButton>
</footer> </footer>
</section> </div>
<style> <style>
section {
padding: 30px;
}
footer { footer {
position: absolute;
padding: 20px; padding: 20px;
width: 100%;
bottom: 0;
left: 0;
background: #fafafa; background: #fafafa;
border-radius: 0.5rem;
} }
</style> </style>

View File

@ -1,9 +1,5 @@
<script> <script>
import Modal from "components/common/Modal.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import { backendUiStore } from "builderStore"
import ModelView from "../../ModelView.svelte" import ModelView from "../../ModelView.svelte"
import * as api from "../api"
</script> </script>
<section> <section>

View File

@ -2,7 +2,6 @@
import { onMount } from "svelte" import { onMount } from "svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { compose, map, get, flatten } from "lodash/fp" import { compose, map, get, flatten } from "lodash/fp"
import Modal from "components/common/Modal.svelte"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import Select from "components/common/Select.svelte" import Select from "components/common/Select.svelte"
import { import {
@ -68,11 +67,10 @@
} }
</script> </script>
<div> <div class="actions">
<h4 class="budibase__title--4">Create / Edit Record</h4> <h4 class="budibase__title--4">Create / Edit Record</h4>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<div class="actions"> <form on:submit|preventDefault class="uk-form-stacked">
<form class="uk-form-stacked">
{#if !record} {#if !record}
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Model</label> <label class="uk-form-label" for="form-stacked-text">Model</label>
@ -87,20 +85,19 @@
<RecordFieldControl record={editingRecord} {field} {errors} /> <RecordFieldControl record={editingRecord} {field} {errors} />
{/each} {/each}
</form> </form>
</div>
<footer> <footer>
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton on:click={saveRecord}>Save</ActionButton> <ActionButton on:click={saveRecord}>Save</ActionButton>
</footer> </footer>
</div>
</div>
<style> <style>
.actions {
padding: 30px;
}
footer { footer {
position: absolute;
padding: 20px; padding: 20px;
width: 100%;
bottom: 0;
left: 0;
background: #fafafa; background: #fafafa;
border-radius: 0.5rem;
} }
</style> </style>

View File

@ -1,6 +1,5 @@
<script> <script>
import IndexView from "../../IndexView.svelte" import IndexView from "../../IndexView.svelte"
import * as api from "../api"
</script> </script>
<section> <section>

View File

@ -1,5 +1,4 @@
<script> <script>
import Modal from "components/common/Modal.svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import * as api from "../api" import * as api from "../api"
@ -29,7 +28,8 @@
} }
</script> </script>
<form class="uk-form-stacked"> <form on:submit|preventDefault class="uk-form-stacked">
<div>
<label class="uk-form-label" for="form-stacked-text">Username</label> <label class="uk-form-label" for="form-stacked-text">Username</label>
<input class="uk-input" type="text" bind:value={username} /> <input class="uk-input" type="text" bind:value={username} />
<label class="uk-form-label" for="form-stacked-text">Password</label> <label class="uk-form-label" for="form-stacked-text">Password</label>
@ -40,6 +40,7 @@
<option value={level.name}>{level.name}</option> <option value={level.name}>{level.name}</option>
{/each} {/each}
</select> </select>
</div>
<footer> <footer>
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton disabled={!valid} on:click={createUser}>Save</ActionButton> <ActionButton disabled={!valid} on:click={createUser}>Save</ActionButton>
@ -47,13 +48,13 @@
</form> </form>
<style> <style>
div {
padding: 30px;
}
footer { footer {
position: absolute;
padding: 20px; padding: 20px;
width: 100%;
bottom: 0;
left: 0;
background: #fafafa; background: #fafafa;
border-radius: 0.5rem;
} }
select { select {
width: 100%; width: 100%;

View File

@ -1,22 +1,19 @@
<script> <script>
import Modal from "components/common/Modal.svelte"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import * as api from "../api" import * as api from "../api"
export let record export let record
export let onClosed
$: currentAppInfo = { $: currentAppInfo = {
appname: $store.appname, appname: $store.appname,
instanceId: $backendUiStore.selectedDatabase.id, instanceId: $backendUiStore.selectedDatabase.id,
} }
function onClosed() {
backendUiStore.actions.modals.hide()
}
</script> </script>
<section> <section>
<div class="content">
<heading> <heading>
<i class="ri-information-line alert" /> <i class="ri-information-line alert" />
<h4 class="budibase__title--4">Delete Record</h4> <h4 class="budibase__title--4">Delete Record</h4>
@ -25,6 +22,7 @@
Are you sure you want to delete this record? All of your data will be Are you sure you want to delete this record? All of your data will be
permanently removed. This action cannot be undone. permanently removed. This action cannot be undone.
</p> </p>
</div>
<div class="modal-actions"> <div class="modal-actions">
<ActionButton on:click={onClosed}>Cancel</ActionButton> <ActionButton on:click={onClosed}>Cancel</ActionButton>
<ActionButton <ActionButton
@ -48,18 +46,17 @@
.modal-actions { .modal-actions {
padding: 10px; padding: 10px;
position: absolute;
bottom: 0;
left: 0;
background: #fafafa; background: #fafafa;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
width: 100%;
} }
heading { heading {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.content {
padding: 30px;
}
h4 { h4 {
margin: 0 0 0 10px; margin: 0 0 0 10px;

View File

@ -6,7 +6,6 @@
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import getIcon from "components/common/icon" import getIcon from "components/common/icon"
import FieldView from "./FieldView.svelte" import FieldView from "./FieldView.svelte"
import Modal from "components/common/Modal.svelte"
import { import {
get, get,
compose, compose,
@ -101,7 +100,6 @@
} }
</script> </script>
<div class="root">
<heading> <heading>
{#if !editingField} {#if !editingField}
<i class="ri-list-settings-line button--toggled" /> <i class="ri-list-settings-line button--toggled" />
@ -112,13 +110,14 @@
{/if} {/if}
</heading> </heading>
{#if !editingField} {#if !editingField}
<div class="padding">
<h4 class="budibase__label--big">Settings</h4> <h4 class="budibase__label--big">Settings</h4>
{#if $store.errors && $store.errors.length > 0} {#if $store.errors && $store.errors.length > 0}
<ErrorsBox errors={$store.errors} /> <ErrorsBox errors={$store.errors} />
{/if} {/if}
<form class="uk-form-stacked"> <form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={record.name} on:change={nameChanged} /> <Textbox label="Name" bind:text={record.name} on:change={nameChanged} />
{#if isChildModel} {#if isChildModel}
@ -186,6 +185,7 @@
</ActionButton> </ActionButton>
{/if} {/if}
</div> </div>
</div>
{:else} {:else}
<FieldView <FieldView
field={fieldToEdit} field={fieldToEdit}
@ -193,11 +193,10 @@
allFields={record.fields} allFields={record.fields}
store={$store} /> store={$store} />
{/if} {/if}
</div>
<style> <style>
.root { .padding {
height: 100%; padding: 20px;
} }
.new-field { .new-field {
@ -226,6 +225,7 @@
} }
heading { heading {
padding: 20px 20px 0 20px;
display: flex; display: flex;
align-items: center; align-items: center;
} }

View File

@ -6,6 +6,30 @@
import UsersList from "./UsersList.svelte" import UsersList from "./UsersList.svelte"
import NavItem from "./NavItem.svelte" import NavItem from "./NavItem.svelte"
import getIcon from "components/common/icon" import getIcon from "components/common/icon"
import {
CreateDatabaseModal,
CreateUserModal,
} from "components/database/ModelDataTable/modals"
const { open, close } = getContext("simple-modal")
const openDatabaseCreator = () => {
open(
CreateDatabaseModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
const openUserCreator = () => {
open(
CreateUserModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
</script> </script>
<div class="items-root"> <div class="items-root">
@ -13,9 +37,7 @@
<div class="components-list-container"> <div class="components-list-container">
<div class="nav-group-header"> <div class="nav-group-header">
<div class="hierarchy-title">Databases</div> <div class="hierarchy-title">Databases</div>
<i <i class="ri-add-line hoverable" on:click={openDatabaseCreator} />
class="ri-add-line hoverable"
on:click={() => backendUiStore.actions.modals.show('DATABASE')} />
</div> </div>
</div> </div>
@ -29,9 +51,7 @@
<div class="components-list-container"> <div class="components-list-container">
<div class="nav-group-header"> <div class="nav-group-header">
<div class="hierarchy-title">Users</div> <div class="hierarchy-title">Users</div>
<i <i class="ri-add-line hoverable" on:click={openUserCreator} />
class="ri-add-line hoverable"
on:click={() => backendUiStore.actions.modals.show('USER')} />
</div> </div>
</div> </div>
@ -41,7 +61,10 @@
</div> </div>
{/if} {/if}
<NavItem name="ACCESS_LEVELS" label="User Access Levels" /> <NavItem
name="ACCESS_LEVELS"
label="User Access Levels"
href="./accesslevels" />
</div> </div>
<style> <style>

View File

@ -1,15 +1,14 @@
<script> <script>
import { tick, onMount } from "svelte" import { tick, onMount } from "svelte"
import { goto } from "@sveltech/routify"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import api from "builderStore/api" import api from "builderStore/api"
import getIcon from "../common/icon"
import { CheckIcon } from "../common/Icons" import { CheckIcon } from "../common/Icons"
$: instances = $store.appInstances $: instances = $store.appInstances
$: views = $store.hierarchy.indexes $: views = $store.hierarchy.indexes
async function selectDatabase(database) { async function selectDatabase(database) {
backendUiStore.actions.navigate("DATABASE")
backendUiStore.actions.records.select(null) backendUiStore.actions.records.select(null)
backendUiStore.actions.views.select(views[0]) backendUiStore.actions.views.select(views[0])
backendUiStore.actions.database.select(database) backendUiStore.actions.database.select(database)
@ -25,12 +24,6 @@
return state return state
}) })
} }
onMount(() => {
if ($store.appInstances.length > 0) {
selectDatabase($store.appInstances[0])
}
})
</script> </script>
<div class="root"> <div class="root">
@ -44,7 +37,9 @@
</span> </span>
<button <button
class:active={database.id === $backendUiStore.selectedDatabase.id} class:active={database.id === $backendUiStore.selectedDatabase.id}
on:click={() => selectDatabase(database)}> on:click={() => {
$goto(`./database/${database.id}`), selectDatabase(database)
}}>
{database.name} {database.name}
</button> </button>
<i <i

View File

@ -3,6 +3,12 @@
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import getIcon from "../common/icon" import getIcon from "../common/icon"
import {
CreateEditModelModal,
CreateEditViewModal,
} from "components/database/ModelDataTable/modals"
const { open, close } = getContext("simple-modal")
export let level = 0 export let level = 0
export let node export let node
@ -23,8 +29,15 @@
function selectHierarchyItem(node) { function selectHierarchyItem(node) {
store.selectExistingNode(node.nodeId) store.selectExistingNode(node.nodeId)
const modalType = node.type === "index" ? "VIEW" : "MODEL" const modalType =
backendUiStore.actions.modals.show(modalType) node.type === "index" ? CreateEditViewModal : CreateEditModelModal
open(
modalType,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
} }
</script> </script>

View File

@ -1,24 +1,20 @@
<script> <script>
import getIcon from "../common/icon" import { isActive, url, goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
export let name = ""
export let label = "" export let label = ""
export let href
$: navActive = $backendUiStore.leftNavItem === name
const setActive = () => backendUiStore.actions.navigate(name)
</script> </script>
<div <div
on:click={() => $goto(href)}
class="budibase__nav-item backend-nav-item" class="budibase__nav-item backend-nav-item"
class:selected={navActive} class:selected={$isActive(href)}>
on:click={setActive}>
{label} {label}
</div> </div>
<style> <style>
.backend-nav-item { .backend-nav-item {
padding-left: 25px; padding-left: 25px;
cursor: pointer;
} }
</style> </style>

View File

@ -1,9 +1,16 @@
<script> <script>
import { getContext } from "svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import HierarchyRow from "./HierarchyRow.svelte" import HierarchyRow from "./HierarchyRow.svelte"
import DropdownButton from "components/common/DropdownButton.svelte" import DropdownButton from "components/common/DropdownButton.svelte"
import NavItem from "./NavItem.svelte" import NavItem from "./NavItem.svelte"
import getIcon from "components/common/icon" import getIcon from "components/common/icon"
import {
CreateEditModelModal,
CreateEditViewModal,
} from "components/database/ModelDataTable/modals"
const { open, close } = getContext("simple-modal")
function newModel() { function newModel() {
if ($store.currentNode) { if ($store.currentNode) {
@ -11,12 +18,24 @@
} else { } else {
store.newRootRecord() store.newRootRecord()
} }
backendUiStore.actions.modals.show("MODEL") open(
CreateEditModelModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
} }
function newView() { function newView() {
store.newRootIndex() store.newRootIndex()
backendUiStore.actions.modals.show("VIEW") open(
CreateEditViewModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
} }
</script> </script>

View File

@ -1,8 +1,6 @@
<script> <script>
import Button from "components/common/Button.svelte" import Button from "components/common/Button.svelte"
import { store } from "builderStore" export let apps
let errors = []
</script> </script>
<div class="root"> <div class="root">
@ -12,11 +10,10 @@
class="logo" class="logo"
alt="budibase logo" /> alt="budibase logo" />
<div> <div>
<div> <div>
<h4 style="margin-bottom: 20px">Choose an Application</h4> <h4 style="margin-bottom: 20px">Choose an Application</h4>
{#each $store.apps as app} {#each apps as app}
<a href={`#${app}`} class="app-link">{app}</a> <a href={`/_builder/${app}`} class="app-link">{app}</a>
{/each} {/each}
</div> </div>
</div> </div>

View File

@ -13,7 +13,7 @@
<div class="root"> <div class="root">
<form class="uk-search uk-search-large"> <form on:submit|preventDefault class="uk-search uk-search-large">
<span uk-search-icon /> <span uk-search-icon />
<input <input
class="uk-search-input" class="uk-search-input"

View File

@ -16,7 +16,6 @@
import Dropdown from "components/common/Dropdown.svelte" import Dropdown from "components/common/Dropdown.svelte"
import PlusButton from "components/common/PlusButton.svelte" import PlusButton from "components/common/PlusButton.svelte"
import IconButton from "components/common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
import Modal from "components/common/Modal.svelte"
import EventEditorModal from "./EventEditorModal.svelte" import EventEditorModal from "./EventEditorModal.svelte"
import HandlerSelector from "./HandlerSelector.svelte" import HandlerSelector from "./HandlerSelector.svelte"
@ -64,7 +63,7 @@
</header> </header>
<div class="root"> <div class="root">
<form class="uk-form-stacked form-root"> <form on:submit|preventDefault class="uk-form-stacked form-root">
{#each events as event, index} {#each events as event, index}
{#if event.handlers.length > 0} {#if event.handlers.length > 0}
<div <div

View File

@ -35,7 +35,7 @@
<h3>{$store.currentPageName}</h3> <h3>{$store.currentPageName}</h3>
<form class="uk-form-horizontal"> <form on:submit|preventDefault class="uk-form-horizontal">
<Textbox bind:text={title} label="Title" hasError={!title} /> <Textbox bind:text={title} label="Title" hasError={!title} />
<div class="help-text"> <div class="help-text">
The title of your page, displayed in the bowser tab The title of your page, displayed in the bowser tab

View File

@ -24,7 +24,7 @@
<div class="root"> <div class="root">
<form class="uk-form-stacked form-root"> <form on:submit|preventDefault class="uk-form-stacked form-root">
{#if componentDef} {#if componentDef}
{#each Object.entries(componentDef.props) as [prop_name, prop_def], index} {#each Object.entries(componentDef.props) as [prop_name, prop_def], index}
<div class="prop-container"> <div class="prop-container">

View File

@ -5,7 +5,6 @@
import PagesList from "./PagesList.svelte" import PagesList from "./PagesList.svelte"
import { store } from "builderStore" import { store } from "builderStore"
import IconButton from "components/common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
import Modal from "components/common/Modal.svelte"
import NewScreen from "./NewScreen.svelte" import NewScreen from "./NewScreen.svelte"
import CurrentItemPreview from "./CurrentItemPreview.svelte" import CurrentItemPreview from "./CurrentItemPreview.svelte"
import SettingsView from "./SettingsView.svelte" import SettingsView from "./SettingsView.svelte"

View File

@ -62,6 +62,7 @@
} }
html, body { html, body {
display: grid;
font-family: var(--fontnormal); font-family: var(--fontnormal);
color: var(--secondary80); color: var(--secondary80);
padding: 0; padding: 0;

View File

@ -1,10 +1,28 @@
<script> <script>
import { store } from "builderStore"
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
import { isActive, goto, url, context } from "@sveltech/routify" import { isActive, goto, context } from "@sveltech/routify"
import { SettingsIcon, PreviewIcon } from "../../common/Icons/" import { SettingsIcon, PreviewIcon } from "components/common/Icons/"
import IconButton from "../../common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
// Get Package and set store
export let application
let promise = getPackage()
async function getPackage() {
const res = await fetch(`/_builder/api/${application}/appPackage`)
const pkg = await res.json()
if (res.ok) {
await store.setPackage(pkg)
return pkg
} else {
throw new Error(pkg)
}
}
$: ({ component } = $context) $: ({ component } = $context)
$: list = component.parent.children.filter(child => child.isIndexable) $: list = component.parent.children.filter(child => child.isIndexable)
</script> </script>
@ -19,12 +37,12 @@
alt="budibase icon" /> alt="budibase icon" />
</button> </button>
<!-- List is an array of subfolders in the application folder. --> <!-- This gets all indexable subroutes and sticks them in the top nav. -->
{#each list as { path, prettyName, children, meta }} {#each list as { path, prettyName, children, meta }}
<span <span
class:active={$isActive(path)} class:active={$isActive(path)}
class="topnavitem" class="topnavitem"
on:click={() => $goto($url(path))}> on:click={() => $goto(path)}>
{prettyName} {prettyName}
</span> </span>
{/each} {/each}
@ -34,9 +52,9 @@
</div> </div>
<div class="toprightnav"> <div class="toprightnav">
<span <span
class:active={$isActive(`${component.parent.path}/settings`)} class:active={$isActive(`/settings`)}
class="topnavitemright" class="topnavitemright"
on:click={() => $goto(`${component.parent.path}/settings`)}> on:click={() => $goto(`/settings`)}>
<SettingsIcon /> <SettingsIcon />
</span> </span>
<span <span
@ -48,7 +66,12 @@
</div> </div>
</div> </div>
{#await promise}
<!-- This should probably be some kind of loading state? -->
<div />
{:then}
<slot /> <slot />
{/await}
</div> </div>

View File

@ -0,0 +1,40 @@
<script>
import { getContext } from "svelte"
import { store, backendUiStore } from "builderStore"
import * as api from "components/database/ModelDataTable/api"
import BackendNav from "components/nav/BackendNav.svelte"
import SchemaManagementDrawer from "components/nav/SchemaManagementDrawer.svelte"
</script>
<div class="root">
<div class="nav">
<BackendNav />
</div>
<div class="content">
<slot />
</div>
<div class="nav">
<SchemaManagementDrawer />
</div>
</div>
<style>
.root {
height: 100%;
display: flex;
background: #fafafa;
}
.content {
flex: 1 1 auto;
margin: 20px 40px;
}
.nav {
overflow: auto;
flex: 0 1 auto;
width: 275px;
height: 100%;
}
</style>

View File

@ -1,4 +1,7 @@
<script> <script>
import { getContext } from "svelte"
const { open, close } = getContext("simple-modal")
import ButtonGroup from "components/common/ButtonGroup.svelte" import ButtonGroup from "components/common/ButtonGroup.svelte"
import Button from "components/common/Button.svelte" import Button from "components/common/Button.svelte"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
@ -8,39 +11,34 @@
getNewAccessLevel, getNewAccessLevel,
} from "components/common/core" } from "components/common/core"
import getIcon from "components/common/icon" import getIcon from "components/common/icon"
import AccessLevelView from "./AccessLevelView.svelte" import AccessLevelView from "components/accessLevels/AccessLevelView.svelte"
import Modal from "components/common/Modal.svelte"
let editingLevel = null let editingLevel = null
let editingLevelIsNew = false let editingLevelIsNew = false
$: {
if (editingLevel !== null) {
backendUiStore.actions.modals.show("ACCESS_LEVELS")
}
}
$: modalOpen = $backendUiStore.visibleModal === "ACCESS_LEVELS"
let allPermissions = [] let allPermissions = []
store.subscribe(db => { store.subscribe(db => {
allPermissions = generateFullPermissions(db.hierarchy, db.actions) allPermissions = generateFullPermissions(db.hierarchy, db.actions)
}) })
let onLevelEdit = level => { const openModal = (level, newLevel) => {
editingLevel = level editingLevel = level
editingLevelIsNew = false editingLevelIsNew = newLevel
open(AccessLevelView, {
level: editingLevel,
allPermissions,
onFinished: onEditingFinished,
isNew: editingLevelIsNew,
allLevels: $store.accessLevels.levels,
hierarchy: $store.hierarchy,
actions: $store.actions,
close: close,
title: "Access Level",
})
} }
let onLevelCancel = () => { let cancel = () => {
editingAction = null editingAction = null
} close()
let onLevelDelete = level => {
store.deleteLevel(level)
}
let createNewLevel = () => {
editingLevelIsNew = true
editingLevel = getNewAccessLevel()
} }
let onEditingFinished = level => { let onEditingFinished = level => {
@ -48,7 +46,7 @@
store.saveLevel(level, editingLevelIsNew, editingLevel) store.saveLevel(level, editingLevelIsNew, editingLevel)
} }
editingLevel = null editingLevel = null
backendUiStore.actions.modals.hide() close()
} }
const getPermissionsString = perms => { const getPermissionsString = perms => {
@ -58,7 +56,7 @@
<div class="root"> <div class="root">
<ButtonGroup> <ButtonGroup>
<ActionButton primary on:click={createNewLevel}> <ActionButton primary on:click={() => openModal(getNewAccessLevel(), true)}>
Create New Access Level Create New Access Level
</ActionButton> </ActionButton>
</ButtonGroup> </ButtonGroup>
@ -78,10 +76,10 @@
<td>{level.name}</td> <td>{level.name}</td>
<td>{getPermissionsString(level.permissions)}</td> <td>{getPermissionsString(level.permissions)}</td>
<td class="edit-button"> <td class="edit-button">
<span on:click={() => onLevelEdit(level)}> <span on:click={() => openModal(level, false)}>
{@html getIcon('edit')} {@html getIcon('edit')}
</span> </span>
<span on:click={() => onLevelDelete(level)}> <span on:click={() => store.deleteLevel(level)}>
{@html getIcon('trash')} {@html getIcon('trash')}
</span> </span>
</td> </td>
@ -91,20 +89,6 @@
</table> </table>
{:else}(no actions added){/if} {:else}(no actions added){/if}
<Modal
onClosed={backendUiStore.actions.modals.hide}
bind:isOpen={modalOpen}
title={modalOpen ? 'Edit Access Level' : 'Create Access Level'}>
<AccessLevelView
level={editingLevel}
{allPermissions}
onFinished={onEditingFinished}
isNew={editingLevelIsNew}
allLevels={$store.accessLevels.levels}
hierarchy={$store.hierarchy}
actions={$store.actions} />
</Modal>
</div> </div>
<style> <style>

View File

@ -0,0 +1,54 @@
<script>
import { getContext } from "svelte"
import ModelDataTable from "components/database/ModelDataTable"
import { store, backendUiStore } from "builderStore"
import ActionButton from "components/common/ActionButton.svelte"
import * as api from "components/database/ModelDataTable/api"
import { CreateEditRecordModal } from "components/database/ModelDataTable/modals"
const { open, close } = getContext("simple-modal")
const createNewRecord = () => {
selectedRecord = null
open(
CreateEditRecordModal,
{
onClosed: close,
record: selectedRecord,
},
{ styleContent: { padding: "0" } }
)
}
export let selectedDatabase
let selectedRecord
async function selectRecord(record) {
selectedRecord = await api.loadRecord(record.key, {
appname: $store.appname,
instanceId: selectedDatabase,
})
}
$: breadcrumbs = $backendUiStore.breadcrumbs.join(" / ")
</script>
<div class="database-actions">
<div class="budibase__label--big">{breadcrumbs}</div>
{#if $backendUiStore.selectedDatabase.id}
<ActionButton primary on:click={createNewRecord}>
Create new record
</ActionButton>
{/if}
</div>
{#if $backendUiStore.selectedDatabase.id}
<ModelDataTable {selectRecord} />
{/if}
<style>
.database-actions {
display: flex;
justify-content: space-between;
}
</style>

View File

@ -0,0 +1,39 @@
<script>
import { store, backendUiStore } from "builderStore"
import { goto } from "@sveltech/routify"
import { onMount } from "svelte"
$: instances = $store.appInstances
$: views = $store.hierarchy.indexes
async function selectDatabase(database) {
backendUiStore.actions.records.select(null)
backendUiStore.actions.views.select(views[0])
backendUiStore.actions.database.select(database)
}
onMount(async () => {
if ($store.appInstances.length > 0) {
await selectDatabase($store.appInstances[0])
$goto(`./${$backendUiStore.selectedDatabase.id}`)
}
})
</script>
<div class="root">
<div class="node-view">
<slot />
</div>
</div>
<style>
.root {
height: 100%;
position: relative;
}
.node-view {
overflow-y: auto;
flex: 1 1 auto;
}
</style>

View File

@ -0,0 +1,23 @@
<script>
import { store, backendUiStore } from "builderStore"
import { goto } from "@sveltech/routify"
import { onMount } from "svelte"
$: instances = $store.appInstances
$: views = $store.hierarchy.indexes
async function selectDatabase(database) {
backendUiStore.actions.records.select(null)
backendUiStore.actions.views.select(views[0])
backendUiStore.actions.database.select(database)
}
onMount(async () => {
if ($store.appInstances.length > 0) {
await selectDatabase($store.appInstances[0])
$goto(`../${$backendUiStore.selectedDatabase.id}`)
}
})
</script>
Please select a database

View File

@ -3,4 +3,4 @@
$goto("../database") $goto("../database")
</script> </script>
<!-- routify:options $index=false --> <!-- routify:options index=false -->

View File

@ -0,0 +1,22 @@
<script>
import { store, backendUiStore } from "builderStore"
import { goto } from "@sveltech/routify"
import { onMount } from "svelte"
$: instances = $store.appInstances
$: views = $store.hierarchy.indexes
async function selectDatabase(database) {
backendUiStore.actions.records.select(null)
backendUiStore.actions.views.select(views[0])
backendUiStore.actions.database.select(database)
}
onMount(async () => {
if ($store.appInstances.length > 0 && !$backendUiStore.database) {
await selectDatabase($store.appInstances[0])
}
})
</script>
<slot />

View File

@ -0,0 +1,5 @@
<script>
import UserInterfaceRoot from "components/userInterface/UserInterfaceRoot.svelte"
</script>
<UserInterfaceRoot />

View File

@ -3,4 +3,4 @@
$goto("../backend") $goto("../backend")
</script> </script>
<!-- routify:options $index=false --> <!-- routify:options index=false -->

View File

@ -0,0 +1,2 @@
<!-- routify:options index=false -->
<slot />

View File

@ -1,48 +0,0 @@
<script>
import BackendNav from "../../../nav/BackendNav.svelte"
import SchemaManagementDrawer from "../../../nav/SchemaManagementDrawer.svelte"
import Database from "../../../database/DatabaseRoot.svelte"
import UserInterface from "../../../userInterface/UserInterfaceRoot.svelte"
import ActionsAndTriggers from "../../../actionsAndTriggers/ActionsAndTriggersRoot.svelte"
import AccessLevels from "../../../accessLevels/AccessLevelsRoot.svelte"
import ComingSoon from "../../../common/ComingSoon.svelte"
</script>
<div class="root">
<div class="nav">
<BackendNav />
</div>
<div class="content">
<!-- {#if $backendUiStore.leftNavItem === 'DATABASE'}
<Database />
{:else if $backendUiStore.leftNavItem === 'ACTIONS'}
<ActionsAndTriggers />
{:else if $backendUiStore.leftNavItem === 'ACCESS_LEVELS'}
<AccessLevels />
{/if} -->
<slot />
</div>
<div class="nav">
<!-- <SchemaManagementDrawer /> -->
</div>
</div>
<style>
.root {
height: 100%;
display: flex;
background: #fafafa;
}
.content {
flex: 1 1 auto;
margin: 80px 60px;
}
.nav {
overflow: auto;
flex: 0 1 auto;
width: 300px;
height: 100%;
}
</style>

View File

@ -1,134 +0,0 @@
<script>
import getIcon from "../../../../common/icon"
import Button from "../../../../common/Button.svelte"
import ActionButton from "../../../../common/ActionButton.svelte"
import ButtonGroup from "../../../../common/ButtonGroup.svelte"
import Actions from "../../../../actionsAndTriggers/Actions.svelte"
import Triggers from "../../../../actionsAndTriggers/Triggers.svelte"
import { getNewAction, getNewTrigger } from "../../../../common/core"
let editingAction = null
let editingActionIsNew = true
let editingTrigger = null
let editingTriggerIsNew = true
let getDefaultOptionsHtml = defaultOptions =>
pipe(
defaultOptions,
[
keys,
map(
k =>
`<span style="color:var(--slate)">${k}: </span>${JSON.parse(
typeOptions[k]
)}`
),
join("<br>"),
]
)
let onActionEdit = action => {
editingAction = action
editingActionIsNew = false
}
let newAction = () => {
editingAction = getNewAction()
editingActionIsNew = true
}
let onActionDelete = action => {
store.deleteAction(action)
}
let deleteTrigger = () => {}
let editTrigger = trigger => {
editingTrigger = trigger
editingTriggerIsNew = false
}
let newTrigger = () => {
editingTrigger = getNewTrigger()
editingTriggerIsNew = true
}
let onActionSave = action => {
store.saveAction(action, editingActionIsNew, editingAction)
editingAction = null
}
let onActionCancel = () => {
editingAction = null
}
let onTriggerSave = trigger => {
store.saveTrigger(trigger, editingTriggerIsNew, editingTrigger)
editingTrigger = null
}
let onTriggerCancel = () => {
editingTrigger = null
}
let onTriggerEdit = trigger => {
editingTrigger = trigger
editingTriggerIsNew = false
}
let onTriggerDelete = trigger => {
store.deleteTrigger(trigger)
}
</script>
<div class="root">
<div class="actions-header">
<ButtonGroup>
<ActionButton color="secondary" grouped on:click={newAction}>
Create New Action
</ActionButton>
<ActionButton color="tertiary" grouped on:click={newTrigger}>
Create New Trigger
</ActionButton>
</ButtonGroup>
</div>
<div class="node-view">
<Actions
{editingActionIsNew}
{editingAction}
{onActionEdit}
{onActionDelete}
{onActionSave}
{onActionCancel} />
<Triggers
{editingTriggerIsNew}
{editingTrigger}
{onTriggerEdit}
{onTriggerDelete}
{onTriggerSave}
{onTriggerCancel} />
</div>
</div>
<style>
.root {
height: 100%;
position: relative;
padding: 1.5rem;
}
.actions-header {
flex: 0 1 auto;
margin-bottom: 10px;
}
.node-view {
overflow-y: auto;
flex: 1 1 auto;
}
</style>

View File

@ -1,96 +0,0 @@
<script>
import ModelView from "../../../../database/ModelView.svelte"
import IndexView from "../../../../database/IndexView.svelte"
import ModelDataTable from "../../../../database/ModelDataTable"
import ActionsHeader from "../../../../database/ActionsHeader.svelte"
import { store, backendUiStore } from "../../../../builderStore"
import getIcon from "../../../../common/icon"
import DropdownButton from "../../../../common/DropdownButton.svelte"
import ActionButton from "../../../../common/ActionButton.svelte"
import Modal from "../../../../common/Modal.svelte"
import {
CreateEditRecordModal,
CreateEditModelModal,
CreateEditViewModal,
CreateDatabaseModal,
DeleteRecordModal,
CreateUserModal,
} from "../../../../database/ModelDataTable/modals"
let selectedRecord
function selectRecord(record) {
selectedRecord = record
}
function onClosed() {
backendUiStore.actions.modals.hide()
}
$: recordOpen = $backendUiStore.visibleModal === "RECORD"
$: modelOpen = $backendUiStore.visibleModal === "MODEL"
$: viewOpen = $backendUiStore.visibleModal === "VIEW"
$: databaseOpen = $backendUiStore.visibleModal === "DATABASE"
$: deleteRecordOpen = $backendUiStore.visibleModal === "DELETE_RECORD"
$: userOpen = $backendUiStore.visibleModal === "USER"
$: breadcrumbs = $backendUiStore.breadcrumbs.join(" / ")
</script>
<Modal isOpen={!!$backendUiStore.visibleModal} {onClosed}>
{#if recordOpen}
<CreateEditRecordModal record={selectedRecord} {onClosed} />
{/if}
{#if modelOpen}
<CreateEditModelModal {onClosed} />
{/if}
{#if viewOpen}
<CreateEditViewModal {onClosed} />
{/if}
{#if databaseOpen}
<CreateDatabaseModal {onClosed} />
{/if}
{#if deleteRecordOpen}
<DeleteRecordModal record={selectedRecord} {onClosed} />
{/if}
{#if userOpen}
<CreateUserModal {onClosed} />
{/if}
</Modal>
<div class="root">
<div class="node-view">
<div class="database-actions">
<div class="budibase__label--big">{breadcrumbs}</div>
{#if $backendUiStore.selectedDatabase.id}
<ActionButton
primary
on:click={() => {
selectedRecord = null
backendUiStore.actions.modals.show('RECORD')
}}>
Create new record
</ActionButton>
{/if}
</div>
{#if $backendUiStore.selectedDatabase.id}
<ModelDataTable {selectRecord} />
{:else}Please select a database{/if}
</div>
</div>
<style>
.root {
height: 100%;
position: relative;
}
.node-view {
overflow-y: auto;
flex: 1 1 auto;
}
.database-actions {
display: flex;
justify-content: space-between;
}
</style>

View File

@ -1,2 +0,0 @@
<!-- routify:options $index=false -->
<slot />

View File

@ -1,10 +1,8 @@
<script> <script>
import AppList from "../AppList.svelte" import AppList from "components/start/AppList.svelte"
import PackageRoot from "../PackageRoot.svelte"
import Settings from "../Settings.svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import IconButton from "../common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
import Spinner from "../common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
let promise = getApps() let promise = getApps()
@ -21,7 +19,6 @@
</script> </script>
<main> <main>
{#await promise} {#await promise}
<div class="spinner-container"> <div class="spinner-container">
<Spinner /> <Spinner />

View File

@ -3,6 +3,7 @@ const StatusCodes = require("../../utilities/statusCodes")
const { const {
getPackageForBuilder, getPackageForBuilder,
getApps, getApps,
saveBackend
} = require("../../utilities/builder") } = require("../../utilities/builder")

View File

@ -81,7 +81,7 @@ run the budibase server and builder in dev mode (i.e. with hot reloading):
1. Open a new console 1. Open a new console
2. `yarn dev` (from root) 2. `yarn dev` (from root)
3. Access the builder on http://localhost:3000 3. Access the builder on http://localhost:4001/_builder/
This will enable watch mode for both the client AND the server. This will enable watch mode for both the client AND the server.