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",
"shortid": "^2.2.8",
"string_decoder": "^1.2.0",
"svelte-simple-modal": "^0.3.0",
"uikit": "^3.1.7"
},
"devDependencies": {
@ -58,7 +59,7 @@
"@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5",
"@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",
"browser-sync": "^2.26.7",
"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 nodeglobals from "rollup-plugin-node-globals"
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 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 lodash_fp_exports = [
@ -230,34 +202,9 @@ export default {
// Watch the `dist` directory and refresh the
// browser on changes when not in production
!production && livereload(outputpath),
!production &&
browsersync({
server: outputpath,
middleware: [apiProxy, _builderProxy],
}),
// If we're building for production (npm run build
// instead of npm run dev), minify
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>
import NoPackage from "./NoPackage.svelte"
import PackageRoot from "./PackageRoot.svelte"
import Settings from "./Settings.svelte"
import { store, initialise } from "builderStore"
import Modal from "svelte-simple-modal"
import { onMount } from "svelte"
import IconButton from "components/common/IconButton.svelte"
import Spinner from "components/common/Spinner.svelte"
import { Router, basepath } from "@sveltech/routify"
import { routes } from "@sveltech/routify/tmp/routes"
import { store, initialise } from "builderStore"
import AppNotification, {
showAppNotification,
} from "components/common/AppNotification.svelte"
let init = initialise()
function showErrorBanner() {
showAppNotification({
status: "danger",
@ -24,57 +20,12 @@
window.addEventListener("error", showErrorBanner)
window.addEventListener("unhandledrejection", showErrorBanner)
})
$basepath = "/_builder"
</script>
<main>
<AppNotification />
{#await init}
<div class="spinner-container">
<Spinner />
</div>
{:then result}
<AppNotification />
{#if $store.hasAppPackage}
<PackageRoot />
{:else}
<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>
<Modal>
<Router {routes} />
</Modal>

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") {
LogRocket.init("knlald/budibase")
}
setupRouter(store)
await store.initialise()
} catch (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 = () => {
const INITIAL_BACKEND_UI_STATE = {
leftNavItem: "DATABASE",
selectedView: {
records: [],
name: "",
@ -27,7 +26,6 @@ export const getBackendUiStore = () => {
const store = writable(INITIAL_BACKEND_UI_STATE)
store.actions = {
navigate: name => store.update(state => ({ ...state, leftNavItem: name })),
database: {
select: db =>
store.update(state => {
@ -60,10 +58,6 @@ export const getBackendUiStore = () => {
return state
}),
},
modals: {
show: modal => store.update(state => ({ ...state, visibleModal: modal })),
hide: () => store.update(state => ({ ...state, visibleModal: null })),
},
users: {
create: user =>
store.update(state => {
@ -302,7 +296,7 @@ export const deleteLevel = store => level => {
state.accessLevels.levels = state.accessLevels.levels.filter(
t => t.name !== level.name
)
incrementAccessLevelsVersion(s)
incrementAccessLevelsVersion(state)
saveBackend(state)
return state
})

View File

@ -1,3 +1,4 @@
//
import { filter, cloneDeep, last, concat, isEmpty, values } from "lodash/fp"
import { pipe, getNode, constructHierarchy } from "components/common/core"
import * as backendStoreActions from "./backend"
@ -49,7 +50,7 @@ export const getStore = () => {
const store = writable(initial)
store.initialise = initialise(store, initial)
store.setPackage = setPackage(store, initial)
store.newChildRecord = backendStoreActions.newRecord(store, false)
store.newRootRecord = backendStoreActions.newRecord(store, true)
@ -100,26 +101,12 @@ export const getStore = () => {
export default getStore
const initialise = (store, initial) => async () => {
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 setPackage = (store, initial) => async (pkg) => {
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
.get(`/_builder/api/${appname}/pages/unauthenticated/screens`)
.get(`/_builder/api/${pkg.application.name}/pages/unauthenticated/screens`)
.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 => {
const libs = libUrlsForPreview(pkg, pageName)
return libs
}
initial.appname = appname
initial.appname = pkg.application.name
initial.pages = pkg.pages
initial.hasAppPackage = true
initial.hierarchy = pkg.appDefinition.hierarchy
@ -377,7 +364,7 @@ const savePage = store => async page => {
const addComponentLibrary = store => async lib => {
const response = await api.get(
`/_builder/api/${appname}/componentlibrary?lib=${encodeURI(lib)}`,
`/_builder/api/${s.appname}/componentlibrary?lib=${encodeURI(lib)}`,
undefined,
false
)
@ -436,7 +423,7 @@ const removeStylesheet = store => stylesheet => {
const _savePage = async s => {
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 },
uiFunctions: s.currentPageFunctions,
screens: page._screens,

View File

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

View File

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

View File

@ -22,10 +22,7 @@
let cancel = () => onFinished()
let save = () => {
const newTriggersList = [
...pipe(
allTriggers,
[filter(t => t !== trigger)]
),
...pipe(allTriggers, [filter(t => t !== trigger)]),
clonedTrigger,
]
@ -43,7 +40,7 @@
<ErrorsBox {errors} style="margin-bottom:20px" />
<form class="uk-form-horizontal">
<form on:submit|preventDefault class="uk-form-horizontal">
<Dropdown
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} />
<form class="uk-form-stacked">
<form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={clonedField.name} />
<Dropdown
label="Type"
@ -136,18 +136,19 @@
bind:value={clonedField.typeOptions.maxLength} />
{/if}
</form>
<footer>
</div>
<footer>
<ActionButton primary on:click={save}>Save</ActionButton>
<ActionButton alert on:click={() => onFinished(false)}>Cancel</ActionButton>
</footer>
</div>
</footer>
<style>
.root {
margin: 20px;
}
footer {
position: absolute;
padding: 20px;
width: 100%;
border-radius: 0 0 5px 5px;
bottom: 0;
left: 0;
background: #fafafa;

View File

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

View File

@ -1,5 +1,5 @@
<script>
import { onMount } from "svelte"
import { onMount, getContext } from "svelte"
import { store, backendUiStore } from "builderStore"
import {
tap,
@ -17,10 +17,39 @@
import { getIndexSchema } from "components/common/core"
import ActionButton from "components/common/ActionButton.svelte"
import TablePagination from "./TablePagination.svelte"
import { DeleteRecordModal } from "./modals"
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
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
// Internal headers we want to hide from the user
@ -56,11 +85,7 @@
const getSchema = getIndexSchema($store.hierarchy)
const childViewsForRecord = compose(
flatten,
map("indexes"),
get("children")
)
const childViewsForRecord = compose(flatten, map("indexes"), get("children"))
const hideInternalHeaders = compose(
remove(headerName => INTERNAL_HEADERS.includes(headerName)),
@ -133,16 +158,14 @@
</li>
<li
on:click={() => {
selectRecord(row)
backendUiStore.actions.modals.show('RECORD')
editRecord(row)
}}>
<div>Edit</div>
</li>
<li>
<div
on:click={() => {
selectRecord(row)
backendUiStore.actions.modals.show('DELETE_RECORD')
deleteRecord(row)
}}>
Delete
</div>
@ -166,7 +189,6 @@
</section>
<style>
.title {
font-size: 24px;
font-weight: 600;

View File

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

View File

@ -1,9 +1,5 @@
<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 * as api from "../api"
</script>
<section>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,30 @@
import UsersList from "./UsersList.svelte"
import NavItem from "./NavItem.svelte"
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>
<div class="items-root">
@ -13,9 +37,7 @@
<div class="components-list-container">
<div class="nav-group-header">
<div class="hierarchy-title">Databases</div>
<i
class="ri-add-line hoverable"
on:click={() => backendUiStore.actions.modals.show('DATABASE')} />
<i class="ri-add-line hoverable" on:click={openDatabaseCreator} />
</div>
</div>
@ -29,9 +51,7 @@
<div class="components-list-container">
<div class="nav-group-header">
<div class="hierarchy-title">Users</div>
<i
class="ri-add-line hoverable"
on:click={() => backendUiStore.actions.modals.show('USER')} />
<i class="ri-add-line hoverable" on:click={openUserCreator} />
</div>
</div>
@ -41,7 +61,10 @@
</div>
{/if}
<NavItem name="ACCESS_LEVELS" label="User Access Levels" />
<NavItem
name="ACCESS_LEVELS"
label="User Access Levels"
href="./accesslevels" />
</div>
<style>

View File

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

View File

@ -3,6 +3,12 @@
import { store, backendUiStore } from "builderStore"
import { cloneDeep } from "lodash/fp"
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 node
@ -23,8 +29,15 @@
function selectHierarchyItem(node) {
store.selectExistingNode(node.nodeId)
const modalType = node.type === "index" ? "VIEW" : "MODEL"
backendUiStore.actions.modals.show(modalType)
const modalType =
node.type === "index" ? CreateEditViewModal : CreateEditModelModal
open(
modalType,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
</script>

View File

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

View File

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

View File

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

View File

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

View File

@ -16,7 +16,6 @@
import Dropdown from "components/common/Dropdown.svelte"
import PlusButton from "components/common/PlusButton.svelte"
import IconButton from "components/common/IconButton.svelte"
import Modal from "components/common/Modal.svelte"
import EventEditorModal from "./EventEditorModal.svelte"
import HandlerSelector from "./HandlerSelector.svelte"
@ -64,7 +63,7 @@
</header>
<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}
{#if event.handlers.length > 0}
<div

View File

@ -35,7 +35,7 @@
<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} />
<div class="help-text">
The title of your page, displayed in the bowser tab

View File

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

View File

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

View File

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

View File

@ -1,10 +1,28 @@
<script>
import { store } from "builderStore"
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 IconButton from "../../common/IconButton.svelte"
import { SettingsIcon, PreviewIcon } from "components/common/Icons/"
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)
$: list = component.parent.children.filter(child => child.isIndexable)
</script>
@ -19,12 +37,12 @@
alt="budibase icon" />
</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 }}
<span
class:active={$isActive(path)}
class="topnavitem"
on:click={() => $goto($url(path))}>
on:click={() => $goto(path)}>
{prettyName}
</span>
{/each}
@ -34,9 +52,9 @@
</div>
<div class="toprightnav">
<span
class:active={$isActive(`${component.parent.path}/settings`)}
class:active={$isActive(`/settings`)}
class="topnavitemright"
on:click={() => $goto(`${component.parent.path}/settings`)}>
on:click={() => $goto(`/settings`)}>
<SettingsIcon />
</span>
<span
@ -48,7 +66,12 @@
</div>
</div>
{#await promise}
<!-- This should probably be some kind of loading state? -->
<div />
{:then}
<slot />
{/await}
</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>
import { getContext } from "svelte"
const { open, close } = getContext("simple-modal")
import ButtonGroup from "components/common/ButtonGroup.svelte"
import Button from "components/common/Button.svelte"
import ActionButton from "components/common/ActionButton.svelte"
@ -8,39 +11,34 @@
getNewAccessLevel,
} from "components/common/core"
import getIcon from "components/common/icon"
import AccessLevelView from "./AccessLevelView.svelte"
import Modal from "components/common/Modal.svelte"
import AccessLevelView from "components/accessLevels/AccessLevelView.svelte"
let editingLevel = null
let editingLevelIsNew = false
$: {
if (editingLevel !== null) {
backendUiStore.actions.modals.show("ACCESS_LEVELS")
}
}
$: modalOpen = $backendUiStore.visibleModal === "ACCESS_LEVELS"
let allPermissions = []
store.subscribe(db => {
allPermissions = generateFullPermissions(db.hierarchy, db.actions)
})
let onLevelEdit = level => {
const openModal = (level, newLevel) => {
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
}
let onLevelDelete = level => {
store.deleteLevel(level)
}
let createNewLevel = () => {
editingLevelIsNew = true
editingLevel = getNewAccessLevel()
close()
}
let onEditingFinished = level => {
@ -48,7 +46,7 @@
store.saveLevel(level, editingLevelIsNew, editingLevel)
}
editingLevel = null
backendUiStore.actions.modals.hide()
close()
}
const getPermissionsString = perms => {
@ -58,7 +56,7 @@
<div class="root">
<ButtonGroup>
<ActionButton primary on:click={createNewLevel}>
<ActionButton primary on:click={() => openModal(getNewAccessLevel(), true)}>
Create New Access Level
</ActionButton>
</ButtonGroup>
@ -78,10 +76,10 @@
<td>{level.name}</td>
<td>{getPermissionsString(level.permissions)}</td>
<td class="edit-button">
<span on:click={() => onLevelEdit(level)}>
<span on:click={() => openModal(level, false)}>
{@html getIcon('edit')}
</span>
<span on:click={() => onLevelDelete(level)}>
<span on:click={() => store.deleteLevel(level)}>
{@html getIcon('trash')}
</span>
</td>
@ -91,20 +89,6 @@
</table>
{: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>
<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")
</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")
</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>
import AppList from "../AppList.svelte"
import PackageRoot from "../PackageRoot.svelte"
import Settings from "../Settings.svelte"
import AppList from "components/start/AppList.svelte"
import { onMount } from "svelte"
import IconButton from "../common/IconButton.svelte"
import Spinner from "../common/Spinner.svelte"
import IconButton from "components/common/IconButton.svelte"
import Spinner from "components/common/Spinner.svelte"
let promise = getApps()
@ -21,7 +19,6 @@
</script>
<main>
{#await promise}
<div class="spinner-container">
<Spinner />

View File

@ -3,6 +3,7 @@ const StatusCodes = require("../../utilities/statusCodes")
const {
getPackageForBuilder,
getApps,
saveBackend
} = 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
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.