adding, deleting and editing records working

This commit is contained in:
Martin McKeaveney 2020-03-20 18:47:01 +00:00
parent 38d7623862
commit a8fec53ff3
26 changed files with 375 additions and 423 deletions

View File

@ -103,6 +103,7 @@ const lodash_fp_exports = [
"toNumber", "toNumber",
"takeRight", "takeRight",
"toPairs", "toPairs",
"remove"
] ]
const lodash_exports = [ const lodash_exports = [

View File

@ -7,10 +7,7 @@
import AccessLevels from "./accessLevels/AccessLevelsRoot.svelte" import AccessLevels from "./accessLevels/AccessLevelsRoot.svelte"
import ComingSoon from "./common/ComingSoon.svelte" import ComingSoon from "./common/ComingSoon.svelte"
import { store } from "./builderStore" import { store, backendUiStore } from "./builderStore"
import { setContext } from "svelte"
let activeNav = "database"
</script> </script>
<div class="root"> <div class="root">
@ -18,13 +15,14 @@
<BackendNav /> <BackendNav />
</div> </div>
<div class="content"> <div class="content">
<Database /> {#if $backendUiStore.leftNavItem === 'DATABASE'}
</div> <Database />
<!-- {:else if activeNav === 'actions'} {:else if $backendUiStore.leftNavItem === 'ACTIONS'}
<ActionsAndTriggers /> <ActionsAndTriggers />
{:else if activeNav === 'access levels'} {:else if $backendUiStore.leftNavItem === 'ACCESS_LEVELS'}
<AccessLevels /> <AccessLevels />
{/if} --> {/if}
</div>
<div class="nav"> <div class="nav">
<SchemaManagementDrawer /> <SchemaManagementDrawer />
</div> </div>

View File

@ -1,7 +1,9 @@
import getStore from "./store" import { getStore } from "./store"
import { getBackendUiStore } from "./store/backend"
import LogRocket from "logrocket"; import LogRocket from "logrocket";
export const store = getStore() export const store = getStore()
export const backendUiStore = getBackendUiStore()
export const initialise = async () => { export const initialise = async () => {
try { try {

View File

@ -1,9 +1,12 @@
import { writable } from "svelte/store";
import api from "../api" import api from "../api"
import { import {
filter, filter,
cloneDeep, cloneDeep,
sortBy, sortBy,
find map,
find,
remove
} from "lodash/fp" } from "lodash/fp"
import { hierarchy as hierarchyFunctions } from "../../../../core/src" import { hierarchy as hierarchyFunctions } from "../../../../core/src"
import { import {
@ -13,10 +16,47 @@ import {
constructHierarchy, constructHierarchy,
templateApi, templateApi,
} from "../../common/core" } from "../../common/core"
import { store } from "../index";
export const createShadowHierarchy = hierarchy => export const getBackendUiStore = () => {
constructHierarchy(JSON.parse(JSON.stringify(hierarchy))) const INITIAL_BACKEND_UI_STATE = {
leftNavItem: "DATABASE",
selectedView: {
records: [],
name: ""
},
selectedRecord: {},
selectedDatabase: {},
selectedModel: {},
}
const store = writable(INITIAL_BACKEND_UI_STATE)
store.actions = {
navigate: name => store.update(state => ({ ...state, leftNavItem: name })),
database: {
select: db => store.update(state => ({ ...state, selectedDatabase: db })),
},
records: {
delete: record => store.update(state => {
state.selectedView.records = remove(state.selectedView.records, { key: record.key });
return state
}),
},
modals: {
show: modal => store.update(state => ({ ...state, visibleModal: modal })),
hide: () => store.update(state => ({ ...state, visibleModal: null }))
}
}
return store
};
// Store Actions
export const createShadowHierarchy = hierarchy => {
const hi = constructHierarchy(JSON.parse(JSON.stringify(hierarchy)))
console.log(hi)
return hi
}
export const saveBackend = async state => { export const saveBackend = async state => {
await api.post(`/_builder/api/${state.appname}/backend`, { await api.post(`/_builder/api/${state.appname}/backend`, {
@ -30,58 +70,57 @@ export const saveBackend = async state => {
} }
export const newRecord = (store, useRoot) => () => { export const newRecord = (store, useRoot) => () => {
store.update(s => { store.update(state => {
s.currentNodeIsNew = true state.currentNodeIsNew = true
const shadowHierarchy = createShadowHierarchy(s.hierarchy) const shadowHierarchy = createShadowHierarchy(state.hierarchy)
const parent = useRoot const parent = useRoot
? shadowHierarchy ? shadowHierarchy
: getNode(shadowHierarchy, s.currentNode.nodeId) : getNode(shadowHierarchy, state.currentNode.nodeId)
s.errors = [] state.errors = []
s.currentNode = templateApi(shadowHierarchy).getNewRecordTemplate( state.currentNode = templateApi(shadowHierarchy).getNewRecordTemplate(
parent, parent,
"", "",
true true
) )
return s return state
}) })
} }
export const selectExistingNode = store => nodeId => { export const selectExistingNode = store => nodeId => {
store.update(s => { store.update(state => {
const shadowHierarchy = createShadowHierarchy(s.hierarchy) const shadowHierarchy = createShadowHierarchy(state.hierarchy)
s.currentNode = getNode(shadowHierarchy, nodeId) state.currentNode = getNode(shadowHierarchy, nodeId)
s.currentNodeIsNew = false state.currentNodeIsNew = false
s.errors = [] state.errors = []
return s return state
}) })
} }
export const newIndex = (store, useRoot) => () => { export const newIndex = (store, useRoot) => () => {
store.update(s => { store.update(state => {
s.currentNodeIsNew = true state.currentNodeIsNew = true
s.errors = [] state.errors = []
const shadowHierarchy = createShadowHierarchy(s.hierarchy) const shadowHierarchy = createShadowHierarchy(state.hierarchy)
const parent = useRoot const parent = useRoot
? shadowHierarchy ? shadowHierarchy
: getNode(shadowHierarchy, s.currentNode.nodeId) : getNode(shadowHierarchy, state.currentNode.nodeId)
s.currentNode = templateApi(shadowHierarchy).getNewIndexTemplate(parent) state.currentNode = templateApi(shadowHierarchy).getNewIndexTemplate(parent)
return s return state
}) })
} }
// TODO: ONLY SEEMS TO BE CALLED BY THE BACKEND
export const saveCurrentNode = store => () => { export const saveCurrentNode = store => () => {
store.update(s => { store.update(state => {
const errors = validate.node(s.currentNode) const errors = validate.node(state.currentNode)
s.errors = errors state.errors = errors
if (errors.length > 0) { if (errorstate.length > 0) {
return s return state
} }
const parentNode = getNode(s.hierarchy, s.currentNode.parent().nodeId) const parentNode = getNode(state.hierarchy, state.currentNode.parent().nodeId)
const existingNode = getNode(s.hierarchy, s.currentNode.nodeId) const existingNode = getNode(state.hierarchy, state.currentNode.nodeId)
let index = parentNode.children.length let index = parentNode.children.length
if (existingNode) { if (existingNode) {
@ -93,8 +132,8 @@ export const saveCurrentNode = store => () => {
} }
// should add node into existing hierarchy // should add node into existing hierarchy
const cloned = cloneDeep(s.currentNode) const cloned = cloneDeep(state.currentNode)
templateApi(s.hierarchy).constructNode(parentNode, cloned) templateApi(state.hierarchy).constructNode(parentNode, cloned)
const newIndexOfChild = child => { const newIndexOfChild = child => {
if (child === cloned) return index if (child === cloned) return index
@ -104,29 +143,29 @@ export const saveCurrentNode = store => () => {
parentNode.children = pipe(parentNode.children, [sortBy(newIndexOfChild)]) parentNode.children = pipe(parentNode.children, [sortBy(newIndexOfChild)])
if (!existingNode && s.currentNode.type === "record") { if (!existingNode && state.currentNode.type === "record") {
const defaultIndex = templateApi(s.hierarchy).getNewIndexTemplate( const defaultIndex = templateApi(state.hierarchy).getNewIndexTemplate(
cloned.parent() cloned.parent()
) )
defaultIndex.name = `all_${cloned.collectionName}` defaultIndex.name = `all_${cloned.collectionName}`
defaultIndex.allowedRecordNodeIds = [cloned.nodeId] defaultIndex.allowedRecordNodeIds = [cloned.nodeId]
} }
s.currentNodeIsNew = false state.currentNodeIsNew = false
saveBackend(s) saveBackend(state)
return s return state
}) })
} }
export const deleteCurrentNode = store => () => { export const deleteCurrentNode = store => () => {
store.update(s => { store.update(state => {
const nodeToDelete = getNode(s.hierarchy, s.currentNode.nodeId) const nodeToDelete = getNode(state.hierarchy, state.currentNode.nodeId)
s.currentNode = hierarchyFunctions.isRoot(nodeToDelete.parent()) state.currentNode = hierarchyFunctionstate.isRoot(nodeToDelete.parent())
? find(n => n != s.currentNode)(s.hierarchy.children) ? find(n => n != state.currentNode)(state.hierarchy.children)
: nodeToDelete.parent() : nodeToDelete.parent()
if (hierarchyFunctions.isRecord(nodeToDelete)) { if (hierarchyFunctionstate.isRecord(nodeToDelete)) {
nodeToDelete.parent().children = filter( nodeToDelete.parent().children = filter(
c => c.nodeId !== nodeToDelete.nodeId c => c.nodeId !== nodeToDelete.nodeId
)(nodeToDelete.parent().children) )(nodeToDelete.parent().children)
@ -135,9 +174,9 @@ export const deleteCurrentNode = store => () => {
c => c.nodeId !== nodeToDelete.nodeId c => c.nodeId !== nodeToDelete.nodeId
)(nodeToDelete.parent().indexes) )(nodeToDelete.parent().indexes)
} }
s.errors = [] state.errors = []
saveBackend(s) saveBackend(state)
return s return state
}) })
} }
@ -160,4 +199,41 @@ export const deleteField = databaseStore => field => {
return db return db
}) })
}
const incrementAccessLevelsVersion = state =>
(state.accessLevelstate.version = (state.accessLevelstate.version || 0) + 1)
export const saveLevel = store => (newLevel, isNew, oldLevel = null) => {
store.update(state => {
const levels = state.accessLevelstate.levels
const existingLevel = isNew
? null
: find(a => a.name === oldLevel.name)(levels)
if (existingLevel) {
state.accessLevelstate.levels = pipe(levels, [
map(a => (a === existingLevel ? newLevel : a)),
])
} else {
state.accessLevelstate.levelstate.push(newLevel)
}
incrementAccessLevelsVersion(s)
saveBackend(state)
return state
})
}
export const deleteLevel = store => level => {
store.update(state => {
state.accessLevelstate.levels = filter(t => t.name !== level.name)(
state.accessLevelstate.levels
)
incrementAccessLevelsVersion(s)
saveBackend(state)
return state
})
} }

View File

@ -65,61 +65,6 @@ export const getStore = () => {
const store = writable(initial) const store = writable(initial)
// store.api = {
// appDefinition: {
// create: () => {},
// },
// records: {
// create: () => {},
// update: () => {},
// delete: () => {},
// },
// indexes: {
// create: () => {},
// update: () => {},
// delete: () => {},
// },
// pages: {
// create: () => {},
// update: () => {},
// delete: () => {},
// },
// screens: {
// create: () => {},
// select: () => {},
// rename: () => {}
// },
// accessLevels: {
// create: () => {},
// update: () => {},
// delete: () => {},
// },
// users: {
// create: () => {},
// update: () => {},
// delete: () => {},
// },
// actions: {
// update: () => {},
// delete: () => {}
// },
// triggers: {
// create: () => {},
// update: () => {},
// delete: () => {},
// },
// stylesheets: {
// create: () => {},
// update: () => {},
// delete: () => {},
// },
// components: {
// create: () => {},
// update: () => {},
// delete: () => {},
// }
// }
store.initialise = initialise(store, initial) store.initialise = initialise(store, initial)
store.newChildRecord = backendStoreActions.newRecord(store, false) store.newChildRecord = backendStoreActions.newRecord(store, false)
@ -131,14 +76,14 @@ export const getStore = () => {
store.deleteCurrentNode = backendStoreActions.deleteCurrentNode(store) store.deleteCurrentNode = backendStoreActions.deleteCurrentNode(store)
store.saveField = backendStoreActions.saveField(store) store.saveField = backendStoreActions.saveField(store)
store.deleteField = backendStoreActions.deleteField(store) store.deleteField = backendStoreActions.deleteField(store)
store.saveLevel = backendStoreActions.saveLevel(store)
store.deleteLevel = backendStoreActions.deleteLevel(store)
store.importAppDefinition = importAppDefinition(store) store.importAppDefinition = importAppDefinition(store)
store.saveAction = saveAction(store) store.saveAction = saveAction(store)
store.deleteAction = deleteAction(store) store.deleteAction = deleteAction(store)
store.saveTrigger = saveTrigger(store) store.saveTrigger = saveTrigger(store)
store.deleteTrigger = deleteTrigger(store) store.deleteTrigger = deleteTrigger(store)
store.saveLevel = saveLevel(store)
store.deleteLevel = deleteLevel(store)
store.saveScreen = saveScreen(store) store.saveScreen = saveScreen(store)
store.addComponentLibrary = addComponentLibrary(store) store.addComponentLibrary = addComponentLibrary(store)
store.renameScreen = renameScreen(store) store.renameScreen = renameScreen(store)
@ -319,43 +264,6 @@ const deleteTrigger = store => trigger => {
}) })
} }
const incrementAccessLevelsVersion = s =>
(s.accessLevels.version = (s.accessLevels.version || 0) + 1)
const saveLevel = store => (newLevel, isNew, oldLevel = null) => {
store.update(s => {
const levels = s.accessLevels.levels
const existingLevel = isNew
? null
: find(a => a.name === oldLevel.name)(levels)
if (existingLevel) {
s.accessLevels.levels = pipe(levels, [
map(a => (a === existingLevel ? newLevel : a)),
])
} else {
s.accessLevels.levels.push(newLevel)
}
incrementAccessLevelsVersion(s)
saveBackend(s)
return s
})
}
const deleteLevel = store => level => {
store.update(s => {
s.accessLevels.levels = filter(t => t.name !== level.name)(
s.accessLevels.levels
)
incrementAccessLevelsVersion(s)
saveBackend(s)
return s
})
}
const createShadowHierarchy = hierarchy => const createShadowHierarchy = hierarchy =>
constructHierarchy(JSON.parse(JSON.stringify(hierarchy))) constructHierarchy(JSON.parse(JSON.stringify(hierarchy)))

View File

@ -3,7 +3,7 @@
import ActionButton from "../common/ActionButton.svelte" import ActionButton from "../common/ActionButton.svelte"
export let isOpen = false export let isOpen = false
export let onClosed = () => {} export let onClosed
export let id = "" export let id = ""
export let title export let title
@ -27,19 +27,21 @@
</script> </script>
<div bind:this={ukModal} uk-modal {id}> <div bind:this={ukModal} uk-modal {id}>
<div class="uk-modal-dialog" uk-overflow-auto> {#if isOpen}
{#if title} <div class="uk-modal-dialog" uk-overflow-auto>
<div class="uk-modal-header"> {#if title}
<h4 class="budibase__title--4">{title}</h4> <div class="uk-modal-header">
</div> <h4 class="budibase__title--4">{title}</h4>
{/if} </div>
<div class="uk-modal-body">
{#if onClosed}
<button class="uk-modal-close-default" type="button" uk-close />
{/if} {/if}
<slot /> <div class="uk-modal-body">
{#if onClosed}
<button class="uk-modal-close-default" type="button" uk-close />
{/if}
<slot />
</div>
</div> </div>
</div> {/if}
</div> </div>
<style> <style>

View File

@ -6,20 +6,12 @@
import Modal from "../common/Modal.svelte" import Modal from "../common/Modal.svelte"
import ErrorsBox from "../common/ErrorsBox.svelte" import ErrorsBox from "../common/ErrorsBox.svelte"
export let left
let confirmDelete = false let confirmDelete = false
const openConfirmDelete = () => { const openConfirmDelete = () => {
confirmDelete = true confirmDelete = true
} }
// const deleteCurrentNode = () => {
// confirmDelete = false
// store.deleteCurrentNode()
// }
// TODO: COMPLETELY REFACTOR THIS SHIT
const deleteCurrentNode = () => { const deleteCurrentNode = () => {
confirmDelete = false confirmDelete = false
store.deleteCurrentNode() store.deleteCurrentNode()
@ -39,10 +31,8 @@
{/if} {/if}
</ButtonGroup> </ButtonGroup>
{#if !!$store.errors && $store.errors.length > 0} {#if $store.errors && $store.errors.length > 0}
<div style="width: 500px"> <ErrorsBox errors={$store.errors} />
<ErrorsBox errors={$store.errors} />
</div>
{/if} {/if}
<Modal onClosed={() => (confirmDelete = false)} bind:isOpen={confirmDelete}> <Modal onClosed={() => (confirmDelete = false)} bind:isOpen={confirmDelete}>

View File

@ -1,10 +1,9 @@
<script> <script>
import HierarchyRow from "./HierarchyRow.svelte"
import ModelView from "./ModelView.svelte" import ModelView from "./ModelView.svelte"
import IndexView from "./IndexView.svelte" import IndexView from "./IndexView.svelte"
import ModelDataTable from "./ModelDataTable" import ModelDataTable from "./ModelDataTable"
import ActionsHeader from "./ActionsHeader.svelte" import ActionsHeader from "./ActionsHeader.svelte"
import { store } from "../builderStore" import { store, backendUiStore } from "../builderStore"
import getIcon from "../common/icon" import getIcon from "../common/icon"
import DropdownButton from "../common/DropdownButton.svelte" import DropdownButton from "../common/DropdownButton.svelte"
import ActionButton from "../common/ActionButton.svelte" import ActionButton from "../common/ActionButton.svelte"
@ -13,35 +12,53 @@
CreateEditRecordModal, CreateEditRecordModal,
CreateEditModelModal, CreateEditModelModal,
CreateEditViewModal, CreateEditViewModal,
CreateDatabaseModal
} from "./ModelDataTable/modals" } from "./ModelDataTable/modals"
let modalOpen
let selectedRecord let selectedRecord
function selectRecord(record) { function selectRecord(record) {
selectedRecord = record selectedRecord = record
modalOpen = true backendUiStore.actions.modals.show("RECORD")
} }
$: recordOpen = $store.currentNode && $store.currentNode.type === 'record' function onClosed() {
$: viewOpen = $store.currentNode && $store.currentNode.type === 'index' // backendUiStore.actions.modals.hide()
}
$: recordOpen = $backendUiStore.visibleModal === "RECORD"
$: modelOpen = $backendUiStore.visibleModal === "MODEL"
$: viewOpen = $backendUiStore.visibleModal === "VIEW"
$: databaseOpen = $backendUiStore.visibleModal === "DATABASE"
// $: recordOpen = $store.currentNode && $store.currentNode.type === 'record'
// $: viewOpen = $store.currentNode && $store.currentNode.type === 'index'
</script> </script>
<CreateEditRecordModal bind:modalOpen record={selectedRecord} /> ({ console.log($backendUiStore.visibleModal) })
<CreateEditModelModal modalOpen={recordOpen} />
<CreateEditViewModal modalOpen={viewOpen} /> <CreateEditRecordModal modalOpen={recordOpen} record={selectedRecord} {onClosed} />
<CreateEditModelModal modalOpen={modelOpen} {onClosed} />
<CreateEditViewModal modalOpen={viewOpen} {onClosed} />
<CreateDatabaseModal modalOpen={databaseOpen} {onClosed} />
<div class="root"> <div class="root">
<div class="node-view"> <div class="node-view">
<div class="breadcrumbs">{$store.currentlySelectedDatabase}</div> <div class="database-actions">
<ActionButton <div class="budibase__label--big">
primary {#if $backendUiStore.selectedDatabase.name}
on:click={() => { {$backendUiStore.selectedDatabase.name} / {$store.currentNode}
selectedRecord = null {/if}
modalOpen = true </div>
}}> <ActionButton
Create new record primary
</ActionButton> on:click={() => {
selectedRecord = null
backendUiStore.actions.modals.show("RECORD")
}}>
Create new record
</ActionButton>
</div>
<ModelDataTable {selectRecord} /> <ModelDataTable {selectRecord} />
</div> </div>
</div> </div>
@ -56,4 +73,9 @@
overflow-y: auto; overflow-y: auto;
flex: 1 1 auto; flex: 1 1 auto;
} }
.database-actions {
display: flex;
justify-content: space-between;
}
</style> </style>

View File

@ -1,42 +0,0 @@
<script>
import { store } from "../builderStore"
import { cloneDeep } from "lodash/fp"
export let level = 0
export let node
</script>
<div class="root">
<div
class="title"
on:click={() => store.selectExistingNode(node.nodeId)}
style="padding-left: {20 + level * 20}px">
{node.name}
</div>
{#if node.children}
{#each node.children as child}
<svelte:self node={child} level={level + 1} />
{/each}
{/if}
</div>
<style>
.root {
display: block;
font-size: 0.9rem;
width: 100%;
cursor: pointer;
font-weight: bold;
}
.title {
font: var(--fontblack);
padding-top: 10px;
padding-right: 5px;
padding-bottom: 10px;
color: var(--secondary100);
}
.title:hover {
background-color: var(--secondary10);
}
</style>

View File

@ -1,6 +1,6 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { store } from "../../builderStore" import { store, backendUiStore } from "../../builderStore"
import Select from "../../common/Select.svelte" import Select from "../../common/Select.svelte"
import { getIndexSchema } from "../../common/core" import { getIndexSchema } from "../../common/core"
import ActionButton from "../../common/ActionButton.svelte" import ActionButton from "../../common/ActionButton.svelte"
@ -13,8 +13,6 @@
let pages = [1, 2, 3] let pages = [1, 2, 3]
let selectedView = "" let selectedView = ""
let modalOpen = false let modalOpen = false
let deleteRecordModal = false
let data = []
let headers = [] let headers = []
let selectedRecord let selectedRecord
@ -23,13 +21,20 @@
appname: $store.appname, appname: $store.appname,
instanceId: $store.currentInstanceId instanceId: $store.currentInstanceId
} }
$: data = $backendUiStore.selectedView.records
$: deleteRecordModal = $backendUiStore.visibleModal === "DELETE_RECORD"
const getSchema = getIndexSchema($store.hierarchy) const getSchema = getIndexSchema($store.hierarchy)
async function fetchRecordsForView(viewName) { async function fetchRecordsForView(viewName) {
const recordsForIndex = await api.fetchDataForView(viewName, currentAppInfo) const recordsForIndex = await api.fetchDataForView(viewName, currentAppInfo)
data = recordsForIndex backendUiStore.update(state => {
headers = Object.keys(data[0]) state.selectedView.records = recordsForIndex
if (state.selectedView.records.length > 0) {
headers = Object.keys(recordsForIndex[0])
}
return state
})
} }
onMount(async () => { onMount(async () => {
@ -37,7 +42,10 @@
}) })
</script> </script>
<DeleteRecordModal modalOpen={deleteRecordModal} record={selectedRecord} /> <DeleteRecordModal
modalOpen={deleteRecordModal}
record={selectedRecord}
/>
<section> <section>
<div class="table-controls"> <div class="table-controls">
@ -46,7 +54,6 @@
icon="ri-eye-line" icon="ri-eye-line"
on:change={e => fetchRecordsForView(e.target.value)}> on:change={e => fetchRecordsForView(e.target.value)}>
{#each views as view} {#each views as view}
<!-- ({console.log(getSchema(view))}) ({console.log(view)}) -->
<option value={view.name}>{view.name}</option> <option value={view.name}>{view.name}</option>
{/each} {/each}
</Select> </Select>
@ -62,7 +69,7 @@
</thead> </thead>
<tbody> <tbody>
{#each data as row} {#each data as row}
<tr> <tr class="hoverable">
<td> <td>
<div class="uk-inline"> <div class="uk-inline">
<i class="ri-more-line" /> <i class="ri-more-line" />
@ -78,8 +85,8 @@
<li> <li>
<div <div
on:click={() => { on:click={() => {
deleteRecordModal = true
selectedRecord = row selectedRecord = row
backendUiStore.actions.modals.show("DELETE_RECORD")
}}> }}>
Delete Delete
</div> </div>
@ -98,7 +105,7 @@
{/each} {/each}
</tbody> </tbody>
</table> </table>
<TablePagination {data} /> <TablePagination />
</section> </section>
<style> <style>
@ -118,8 +125,13 @@
font-weight: 500; font-weight: 500;
} }
tr { tbody tr {
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
transition: 0.3s background-color;
}
tbody tr:hover {
background: #fafafa;
} }
.table-controls { .table-controls {

View File

@ -1,5 +1,7 @@
<script> <script>
export let data = [] import { backendUiStore } from "../../builderStore"
$: data = $backendUiStore.selectedView.records
</script> </script>
<div class="pagination"> <div class="pagination">
@ -41,4 +43,4 @@
.ri-more-line:hover { .ri-more-line:hover {
cursor: pointer; cursor: pointer;
} }
</style> </style>

View File

@ -2,18 +2,27 @@
import { getNewRecord } from "../../common/core" import { getNewRecord } from "../../common/core"
export async function deleteRecord(record, { appname, instanceId }) { export async function deleteRecord(record, { appname, instanceId }) {
const DELETE_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record/${record.name}/${record.id}` const DELETE_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record${record.key}`
const response = await api.delete({ const response = await api.delete(DELETE_RECORDS_URL);
url: DELETE_RECORDS_URL
});
return response; return response;
} }
export async function saveRecord(record, { appname, instanceId }) { export async function saveRecord(record, { appname, instanceId }) {
const SAVE_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record` let recordBase = { ...record }
const updatedRecord = getNewRecord(record, "")
const response = await api.post(SAVE_RECORDS_URL, updatedRecord) // brand new record
return response if (record.collectionName) {
const collectionKey = `/${record.collectionName}`
recordBase = getNewRecord(recordBase, collectionKey)
// overwrite the new record template values
for (let key in recordBase) {
if (record[key]) recordBase[key] = record[key]
}
}
const SAVE_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record/`
const response = await api.post(SAVE_RECORDS_URL, recordBase)
return await response.json()
} }
export async function fetchDataForView(viewName, { appname, instanceId }) { export async function fetchDataForView(viewName, { appname, instanceId }) {

View File

@ -0,0 +1,17 @@
<script>
import Modal from "../../../common/Modal.svelte"
import ActionButton from "../../../common/ActionButton.svelte"
import * as api from "../api"
export let modalOpen = false
const onClosed = () => (modalOpen = false)
</script>
<Modal {onClosed} isOpen={modalOpen}>
CREATE A NEW DATABASE FROM HERE
<div class="actions">
<ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton disabled={false}>Save</ActionButton>
</div>
</Modal>

View File

@ -10,7 +10,7 @@
const onClosed = () => (modalOpen = false) const onClosed = () => (modalOpen = false)
</script> </script>
<Modal {onClosed} bind:isOpen={modalOpen} title={"Create / Edit Field"}> <Modal {onClosed} isOpen={modalOpen}>
<div class="actions"> <div class="actions">
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <ActionButton alert on:click={onClosed}>Cancel</ActionButton>

View File

@ -1,27 +1,22 @@
<script> <script>
import Modal from "../../../common/Modal.svelte" import Modal from "../../../common/Modal.svelte"
import ActionButton from "../../../common/ActionButton.svelte" import ActionButton from "../../../common/ActionButton.svelte"
import { backendUiStore } from "../../../builderStore"
import ModelView from "../../ModelView.svelte" import ModelView from "../../ModelView.svelte"
import ActionsHeader from "../../ActionsHeader.svelte" import ActionsHeader from "../../ActionsHeader.svelte"
import * as api from "./api" import * as api from "../api"
export let modalOpen = false export let modalOpen = false
export let onClosed
let recordInfo = {}
const onClosed = () => (modalOpen = false)
</script> </script>
<Modal {onClosed} bind:isOpen={modalOpen} title={'Record'}> <Modal {onClosed} isOpen={modalOpen}>
<h4 class="budibase__title--4">Create / Edit Model</h4> <h4 class="budibase__title--4">
<i class="ri-list-settings-line" />
Create / Edit Model
</h4>
<div class="actions"> <div class="actions">
<ModelView /> <ModelView />
<ActionsHeader /> <ActionsHeader />
<!-- <ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton
disabled={false}
on:click={() => api.createNewRecord(recordInfo)}>
Save
</ActionButton> -->
</div> </div>
</Modal> </Modal>

View File

@ -1,13 +1,15 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { store } from "../../../builderStore" import { store, backendUiStore } from "../../../builderStore"
import Modal from "../../../common/Modal.svelte" import Modal from "../../../common/Modal.svelte"
import ActionButton from "../../../common/ActionButton.svelte" import ActionButton from "../../../common/ActionButton.svelte"
import Select from "../../../common/Select.svelte" import Select from "../../../common/Select.svelte"
import { getNewRecord } from "../../../common/core"
import * as api from "../api" import * as api from "../api"
export let modalOpen = false export let modalOpen = false
export let record export let record
export let onClosed
let selectedModel let selectedModel
@ -20,16 +22,12 @@
$: modelFields = selectedModel $: modelFields = selectedModel
? selectedModel.fields.map(({ name }) => name) ? selectedModel.fields.map(({ name }) => name)
: [] : []
const onClosed = () => (modalOpen = false)
</script> </script>
<Modal {onClosed} isOpen={modalOpen}> <Modal {onClosed} isOpen={modalOpen}>
<h4 class="budibase__title--4">Create / Edit Record</h4> <h4 class="budibase__title--4">Create / Edit Record</h4>
<div class="actions"> <div class="actions">
{console.log('record', record)}
{console.log('selectedModel', selectedModel)}
{console.log('recordFields', recordFields)}
<form class="uk-form-stacked"> <form class="uk-form-stacked">
{#if !record} {#if !record}
<div class="uk-margin"> <div class="uk-margin">
@ -70,7 +68,14 @@
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton <ActionButton
disabled={false} disabled={false}
on:click={() => api.saveRecord(record || selectedModel, currentAppInfo)}> on:click={async () => {
const recordResponse = await api.saveRecord(record || selectedModel, currentAppInfo)
backendUiStore.update(state => {
state.selectedView.records.push(recordResponse)
return state
})
onClosed()
}}>
Save Save
</ActionButton> </ActionButton>
</div> </div>

View File

@ -1,23 +1,25 @@
<script> <script>
import Modal from "../../common/Modal.svelte" import Modal from "../../../common/Modal.svelte"
import ActionButton from "../../common/ActionButton.svelte" import ActionButton from "../../../common/ActionButton.svelte"
import * as api from "./api" import IndexView from "../../IndexView.svelte"
import ActionsHeader from "../../ActionsHeader.svelte"
import * as api from "../api"
export let modalOpen = false export let modalOpen = false
export let onClosed
let recordInfo = {} let recordInfo = {}
const onClosed = () => (modalOpen = false)
</script> </script>
<Modal {onClosed} bind:isOpen={modalOpen} title={'Record'}> <Modal {onClosed} isOpen={modalOpen}>
<IndexView />
<ActionsHeader />
<div class="actions"> <!-- <div class="actions">
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton <ActionButton disabled={false} on:click={() => api.saveRecord(recordInfo)}>
disabled={false}
on:click={() => api.createNewRecord(recordInfo)}>
Save Save
</ActionButton> </ActionButton>
</div> </div> -->
</Modal> </Modal>

View File

@ -1,15 +1,24 @@
<script> <script>
import Modal from "../../../common/Modal.svelte" import Modal from "../../../common/Modal.svelte"
import ActionButton from "../../../common/ActionButton.svelte" import ActionButton from "../../../common/ActionButton.svelte"
import { store, backendUiStore } from "../../../builderStore"
import * as api from "../api" import * as api from "../api"
export let modalOpen = false export let modalOpen = false
export let record export let record
const onClosed = () => (modalOpen = false) $: currentAppInfo = {
instanceId: $store.currentInstanceId,
appname: $store.appname,
}
function onClosed() {
backendUiStore.actions.modals.hide()
}
</script> </script>
<Modal {onClosed} bind:isOpen={modalOpen}> <Modal {onClosed} isOpen={modalOpen}>
<h4 class="budibase__title--4">Delete Record</h4> <h4 class="budibase__title--4">Delete Record</h4>
Are you sure you want to delete this record? All of your data will be permanently removed. This action cannot be undone. Are you sure you want to delete this record? All of your data will be permanently removed. This action cannot be undone.
<div class="modal-actions"> <div class="modal-actions">
@ -17,7 +26,8 @@
<ActionButton <ActionButton
alert alert
on:click={async () => { on:click={async () => {
await api.deleteRecord(record) await api.deleteRecord(record, currentAppInfo)
backendUiStore.actions.records.delete(record)
onClosed(); onClosed();
}}> }}>
Delete Delete

View File

@ -1,3 +1,5 @@
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"; export { default as DeleteRecordModal } from "./DeleteRecord.svelte";
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"; export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte";
export { default as CreateEditModelModal } from "./CreateEditModel.svelte"; export { default as CreateEditModelModal } from "./CreateEditModel.svelte";
export { default as CreateEditViewModal } from "./CreateEditView.svelte";
export { default as CreateDatabaseModal } from "./CreateDatabase.svelte";

View File

@ -1,6 +1,7 @@
<script> <script>
import Textbox from "../common/Textbox.svelte" import Textbox from "../common/Textbox.svelte"
import Button from "../common/Button.svelte" import Button from "../common/Button.svelte"
import Select from "../common/Select.svelte"
import getIcon from "../common/icon" import getIcon from "../common/icon"
import FieldView from "./FieldView.svelte" import FieldView from "./FieldView.svelte"
import Modal from "../common/Modal.svelte" import Modal from "../common/Modal.svelte"
@ -19,6 +20,9 @@
let deleteField let deleteField
let onFinishedFieldEdit let onFinishedFieldEdit
let editIndex let editIndex
let parentRecord
$: models = $store.hierarchy.children
store.subscribe($store => { store.subscribe($store => {
record = $store.currentNode record = $store.currentNode
@ -98,16 +102,20 @@
</script> </script>
<div class="root"> <div class="root">
<form class="uk-form-stacked"> <form class="uk-form-stacked">
<h3 class="budibase__title--3">
<i class="ri-list-settings-line" />
Create / Edit Model
</h3>
<h3 class="budibase__label--big">Settings</h3> <h3 class="budibase__label--big">Settings</h3>
<Textbox label="Name" bind:text={record.name} on:change={nameChanged} /> <Textbox label="Name" bind:text={record.name} on:change={nameChanged} />
<label class="uk-form-label">Parent</label>
<div class="uk-form-controls">
<Select
value={parentRecord}
on:change={e => (parentRecord = e.target.value)}>
{#each models as model}
<option value={model}>{model.name}</option>
{/each}
</Select>
</div>
{#if !record.isSingle} {#if !record.isSingle}
<Textbox label="Collection Name" bind:text={record.collectionName} /> <Textbox label="Collection Name" bind:text={record.collectionName} />
<Textbox <Textbox
@ -121,12 +129,6 @@
<span class="budibase__label--big">Fields</span> <span class="budibase__label--big">Fields</span>
<h4 class="hoverable" on:click={newField}>Add new field</h4> <h4 class="hoverable" on:click={newField}>Add new field</h4>
</div> </div>
<!-- <h3 class="budibase__label--big">
Fields
<span class="add-field-button" on:click={newField}>
{@html getIcon('plus')}
</span>
</h3> -->
<table class="fields-table uk-table budibase__table"> <table class="fields-table uk-table budibase__table">
<thead> <thead>
@ -139,7 +141,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each record ? record.fields : [] as field} {#each record ? record.fields : [] as field}
<tr> <tr>
<td> <td>
<i class="ri-more-line" on:click={() => editField(field)} /> <i class="ri-more-line" on:click={() => editField(field)} />
@ -148,7 +150,7 @@
<div>{field.name}</div> <div>{field.name}</div>
</td> </td>
<td>{field.type}</td> <td>{field.type}</td>
<td>({console.log(field.typeOptions)}) {field.typeOptions.values}</td> <td>{field.typeOptions.values}</td>
<td> <td>
<span class="edit-button" on:click={() => deleteField(field)}> <span class="edit-button" on:click={() => deleteField(field)}>
{@html getIcon('trash')} {@html getIcon('trash')}

View File

@ -1,6 +1,6 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { store } from "../builderStore" import { store, backendUiStore } from "../builderStore"
import HierarchyRow from "./HierarchyRow.svelte" import HierarchyRow from "./HierarchyRow.svelte"
import DatabasesList from "./DatabasesList.svelte" import DatabasesList from "./DatabasesList.svelte"
import UsersList from "./UsersList.svelte" import UsersList from "./UsersList.svelte"
@ -8,59 +8,16 @@
import NavItem from "./NavItem.svelte" import NavItem from "./NavItem.svelte"
import getIcon from "../common/icon" import getIcon from "../common/icon"
// top level store modifiers
const newRootRecord = () => store.newRootRecord()
const newChildIndex = () => store.newChildIndex()
const newRootIndex = () => store.newRootIndex()
const newUser = () => {
store.update(state => {})
}
const newDatabase = () => { const newDatabase = () => {
store.update(state => {}) store.update(state => {})
} }
const userManagementActions = [
{
label: "New User",
onclick: newUser,
},
]
const databaseManagementActions = [ const databaseManagementActions = [
{ {
label: "New Database", label: "New Database",
onclick: newDatabase, onclick: newDatabase,
}, },
] ]
// let newChildActions = defaultNewChildActions
const setActiveNav = name => () => getContext("navigation").setActiveNav(name);
// store.subscribe(db => {
// if (!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) {
// newChildActions = defaultNewChildActions
// } else {
// newChildActions = [
// {
// label: "New Root Record",
// onclick: newRootRecord,
// },
// {
// label: "New Root Index",
// onclick: newRootIndex,
// },
// {
// label: `New Child Record of ${db.currentNode.name}`,
// onclick: newChildRecord,
// },
// {
// label: `New Index on ${db.currentNode.name}`,
// onclick: newChildIndex,
// },
// ]
// }
// })
</script> </script>
<div class="items-root"> <div class="items-root">
@ -68,20 +25,12 @@
<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 class="ri-add-line" /> <i class="ri-add-line hoverable" on:click={() => backendUiStore.actions.modals.show("DATABASE")} />
</div> </div>
</div> </div>
<div class="hierarchy-items-container"> <div class="hierarchy-items-container">
<DatabasesList /> <DatabasesList />
<!-- {#each $store.hierarchy.children as record}
<HierarchyRow node={record} type="record" />
{/each}
{#each $store.hierarchy.indexes as index}
<HierarchyRow node={index} type="index" />
{/each} -->
</div> </div>
</div> </div>
<hr /> <hr />
@ -95,10 +44,9 @@
<div class="hierarchy-items-container"> <div class="hierarchy-items-container">
<UsersList /> <UsersList />
<!-- {#each $store.hierarchy.children as record}
<HierarchyRow node={record} type="record" />
{/each} -->
</div> </div>
<NavItem name="ACCESS_LEVELS" label="User Levels" />
</div> </div>
</div> </div>

View File

@ -1,32 +1,30 @@
<script> <script>
import { store } from "../builderStore" import { store, backendUiStore } from "../builderStore"
import getIcon from "../common/icon" import getIcon from "../common/icon"
import { CheckIcon } from "../common/Icons" import { CheckIcon } from "../common/Icons"
$: instances = $store.appInstances $: instances = $store.appInstances
function selectDatabase(databaseId) { function selectDatabase(database) {
store.update(state => { backendUiStore.actions.database.select(database)
state.currentlySelectedDatabase = databaseId backendUiStore.actions.navigate("DATABASE")
return state
})
} }
</script> </script>
<div class="root"> <div class="root">
<ul> <ul>
{#each $store.appInstances as { id, name }} {#each $store.appInstances as database}
<li> <li>
<span class="icon"> <span class="icon">
{#if id === $store.currentlySelectedDatabase} {#if database.id === $backendUiStore.selectedDatabase.id}
<CheckIcon /> <CheckIcon />
{/if} {/if}
</span> </span>
<button <button
class:active={id === $store.currentlySelectedDatabase} class:active={database.id === $backendUiStore.selectedDatabase.id}
on:click={() => selectDatabase(id)}> on:click={() => selectDatabase(database)}>
{name} {database.name}
</button> </button>
</li> </li>
{/each} {/each}

View File

@ -1,6 +1,6 @@
<script> <script>
import { getContext } from "svelte"; import { getContext } from "svelte"
import { store } 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"
export let level = 0 export let level = 0
@ -10,8 +10,8 @@
let navActive = "" let navActive = ""
const ICON_MAP = { const ICON_MAP = {
index: "ri-equalizer-line", index: "ri-eye-line",
record: "ri-list-settings-line" record: "ri-list-settings-line",
} }
store.subscribe(state => { store.subscribe(state => {
@ -19,13 +19,19 @@
navActive = node.nodeId === state.currentNode.nodeId navActive = node.nodeId === state.currentNode.nodeId
} }
}) })
function selectHierarchyItem(node) {
store.selectExistingNode(node.nodeId)
const modalType = node.type === "index" ? "VIEW" : "MODEL"
backendUiStore.actions.modals.show(modalType)
}
</script> </script>
<div> <div>
<div <div
on:click={() => store.selectExistingNode(node.nodeId)} on:click={() => selectHierarchyItem(node)}
class="budibase__nav-item hierarchy-item" class="budibase__nav-item hierarchy-item"
class:capitalized={type === "record"} class:capitalized={type === 'record'}
style="padding-left: {20 + level * 20}px" style="padding-left: {20 + level * 20}px"
class:selected={navActive}> class:selected={navActive}>
<i class={ICON_MAP[type]} /> <i class={ICON_MAP[type]} />
@ -44,12 +50,12 @@
</div> </div>
<style> <style>
.hierarchy-item { .hierarchy-item {
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
} }
.capitalized { .capitalized {
text-transform: capitalize; text-transform: capitalize;
} }
</style> </style>

View File

@ -1,17 +1,13 @@
<script> <script>
import { getContext, setContext } from "svelte";
import getIcon from "../common/icon" import getIcon from "../common/icon"
import { backendUiStore } from "../builderStore";
export let name = "" export let name = ""
export let label = "" export let label = ""
$: navActive = getContext("activeNav") === name $: navActive = $backendUiStore.leftNavItem === name
// store.subscribe(db => { const setActive = () => backendUiStore.actions.navigate(name)
// navActive = db.activeNav === name
// })
const setActive = () => setContext("activeNav", name);
</script> </script>
<div <div

View File

@ -1,35 +1,19 @@
<script> <script>
import { store } from "../builderStore" import { store, backendUiStore } from "../builderStore"
import HierarchyRow from "./HierarchyRow.svelte" import HierarchyRow from "./HierarchyRow.svelte"
import DropdownButton from "../common/DropdownButton.svelte" import DropdownButton from "../common/DropdownButton.svelte"
import { hierarchy as hierarchyFunctions } from "../../../core/src" import { hierarchy as hierarchyFunctions } from "../../../core/src"
import NavItem from "./NavItem.svelte" import NavItem from "./NavItem.svelte"
import getIcon from "../common/icon" import getIcon from "../common/icon"
const newRootRecord = () => {
store.newRootRecord()
}
const newRootIndex = () => {
store.newRootIndex()
}
const newChildRecord = () => {
store.newChildRecord()
}
const newChildIndex = () => {
store.newChildIndex()
}
const defaultNewChildActions = [ const defaultNewChildActions = [
{ {
label: "New Root Record", label: "New Root Record",
onclick: newRootRecord, onclick: store.newRootRecord,
}, },
{ {
label: "New Root Index", label: "New Root Index",
onclick: newRootIndex, onclick: store.newRootIndex,
}, },
] ]
@ -46,19 +30,19 @@
newChildActions = [ newChildActions = [
{ {
label: "New Root Record", label: "New Root Record",
onclick: newRootRecord, onclick: store.newRootRecord,
}, },
{ {
label: "New Root Index", label: "New Root Index",
onclick: newRootIndex, onclick: store.newRootIndex,
}, },
{ {
label: `New Child Record of ${db.currentNode.name}`, label: `New Child Record of ${db.currentNode.name}`,
onclick: newChildRecord, onclick: store.newChildRecord,
}, },
{ {
label: `New Index on ${db.currentNode.name}`, label: `New Index on ${db.currentNode.name}`,
onclick: newChildIndex, onclick: store.newChildIndex,
}, },
] ]
} }
@ -72,12 +56,22 @@
<div class="hierarchy-title">Schema</div> <div class="hierarchy-title">Schema</div>
<div class="uk-inline"> <div class="uk-inline">
<i class="ri-add-line hoverable" /> <i class="ri-add-line hoverable" />
<div uk-dropdown="mode: click"> <div uk-dropdown="mode: click;">
<ul class="uk-nav uk-dropdown-nav"> <ul class="uk-nav uk-dropdown-nav">
<li class="hoverable" on:click={newRootRecord}> <li
class="hoverable"
on:click={() => {
store.newRootRecord()
backendUiStore.actions.modals.show('MODEL')
}}>
Model Model
</li> </li>
<li class="hoverable" on:click={newRootIndex}> <li
class="hoverable"
on:click={() => {
store.newRootIndex()
backendUiStore.actions.modals.show('VIEW')
}}>
View View
</li> </li>
</ul> </ul>

View File

@ -237,6 +237,7 @@ module.exports = (config, app) => {
} else { } else {
ctx.response.status = StatusCodes.UNAUTHORIZED ctx.response.status = StatusCodes.UNAUTHORIZED
} }
next()
}) })
.post("/:appname/api/changeMyPassword", routeHandlers.changeMyPassword) .post("/:appname/api/changeMyPassword", routeHandlers.changeMyPassword)
.post( .post(
@ -319,8 +320,4 @@ module.exports = (config, app) => {
.post("/:appname/api/apphierarchy", routeHandlers.saveAppHierarchy) .post("/:appname/api/apphierarchy", routeHandlers.saveAppHierarchy)
return router return router
} }
/*
front end get authenticateTemporaryAccess {}
*/