enhanced modals

This commit is contained in:
Martin McKeaveney 2020-03-21 20:39:37 +00:00
parent a8fec53ff3
commit 9756574e6f
15 changed files with 176 additions and 106 deletions

View File

@ -45,6 +45,11 @@ export const getBackendUiStore = () => {
modals: { modals: {
show: modal => store.update(state => ({ ...state, visibleModal: modal })), show: modal => store.update(state => ({ ...state, visibleModal: modal })),
hide: () => store.update(state => ({ ...state, visibleModal: null })) hide: () => store.update(state => ({ ...state, visibleModal: null }))
},
nodes: {
select: () => {},
update: () => {},
delete: () => {},
} }
} }
@ -52,10 +57,13 @@ export const getBackendUiStore = () => {
}; };
// Store Actions // Store Actions
export const createShadowHierarchy = hierarchy => { export const createShadowHierarchy = hierarchy => constructHierarchy(JSON.parse(JSON.stringify(hierarchy)))
const hi = constructHierarchy(JSON.parse(JSON.stringify(hierarchy)))
console.log(hi) export const createDatabaseForApp = store => appInstance => {
return hi store.update(state => {
state.appInstances.push(appInstance)
return state
})
} }
export const saveBackend = async state => { export const saveBackend = async state => {
@ -98,12 +106,12 @@ export const selectExistingNode = store => nodeId => {
export const newIndex = (store, useRoot) => () => { export const newIndex = (store, useRoot) => () => {
store.update(state => { store.update(state => {
const shadowHierarchy = createShadowHierarchy(state.hierarchy)
state.currentNodeIsNew = true state.currentNodeIsNew = true
state.errors = [] state.errors = []
const shadowHierarchy = createShadowHierarchy(state.hierarchy)
const parent = useRoot const parent = useRoot
? shadowHierarchy ? state.hierarchy
: getNode(shadowHierarchy, state.currentNode.nodeId) : getNode(state.hierarchy, state.currentNode.nodeId)
state.currentNode = templateApi(shadowHierarchy).getNewIndexTemplate(parent) state.currentNode = templateApi(shadowHierarchy).getNewIndexTemplate(parent)
return state return state
@ -114,7 +122,7 @@ export const saveCurrentNode = store => () => {
store.update(state => { store.update(state => {
const errors = validate.node(state.currentNode) const errors = validate.node(state.currentNode)
state.errors = errors state.errors = errors
if (errorstate.length > 0) { if (errors.length > 0) {
return state return state
} }
@ -126,9 +134,10 @@ export const saveCurrentNode = store => () => {
if (existingNode) { if (existingNode) {
// remove existing // remove existing
index = existingNode.parent().children.indexOf(existingNode) index = existingNode.parent().children.indexOf(existingNode)
existingNode.parent().children = pipe(existingNode.parent().children, [ existingNode.parent().children = existingNode.parent().children.filter(c => c.nodeId !== existingNode.nodeId);
filter(c => c.nodeId !== existingNode.nodeId), // existingNode.parent().children = pipe(existingNode.parent().children, [
]) // filter(c => c.nodeId !== existingNode.nodeId),
// ])
} }
// should add node into existing hierarchy // should add node into existing hierarchy
@ -141,7 +150,7 @@ export const saveCurrentNode = store => () => {
return currentIndex >= index ? currentIndex + 1 : currentIndex return currentIndex >= index ? currentIndex + 1 : currentIndex
} }
parentNode.children = pipe(parentNode.children, [sortBy(newIndexOfChild)]) parentNode.children = sortBy(newIndexOfChild, parentNode.children)
if (!existingNode && state.currentNode.type === "record") { if (!existingNode && state.currentNode.type === "record") {
const defaultIndex = templateApi(state.hierarchy).getNewIndexTemplate( const defaultIndex = templateApi(state.hierarchy).getNewIndexTemplate(
@ -162,18 +171,28 @@ export const saveCurrentNode = store => () => {
export const deleteCurrentNode = store => () => { export const deleteCurrentNode = store => () => {
store.update(state => { store.update(state => {
const nodeToDelete = getNode(state.hierarchy, state.currentNode.nodeId) const nodeToDelete = getNode(state.hierarchy, state.currentNode.nodeId)
state.currentNode = hierarchyFunctionstate.isRoot(nodeToDelete.parent()) state.currentNode = hierarchyFunctions.isRoot(nodeToDelete.parent())
? find(n => n != state.currentNode)(state.hierarchy.children) ? find(n => n !== state.currentNode)(state.hierarchy.children)
: nodeToDelete.parent() : nodeToDelete.parent()
if (hierarchyFunctionstate.isRecord(nodeToDelete)) {
nodeToDelete.parent().children = filter( const recordOrIndexKey = hierarchyFunctions.isRecord(nodeToDelete) ? "children" : "indexes";
c => c.nodeId !== nodeToDelete.nodeId
)(nodeToDelete.parent().children) // remove the selected record or index
} else { nodeToDelete.parent()[recordOrIndexKey] = remove(
nodeToDelete.parent().indexes = filter( nodeToDelete.parent()[recordOrIndexKey],
c => c.nodeId !== nodeToDelete.nodeId node => node.nodeId === nodeToDelete.nodeId
)(nodeToDelete.parent().indexes) )
}
// if (hierarchyFunctions.isRecord(nodeToDelete)) {
// nodeToDelete.parent().children = filter(
// c => c.nodeId !== nodeToDelete.nodeId
// )(nodeToDelete.parent().children)
// } else {
// nodeToDelete.parent().indexes = remove(
// nodeToDelete.parent().indexes,
// node => node.nodeId === nodeToDelete.nodeId
// )
// }
state.errors = [] state.errors = []
saveBackend(state) saveBackend(state)
return state return state
@ -202,25 +221,23 @@ export const deleteField = databaseStore => field => {
} }
const incrementAccessLevelsVersion = state => const incrementAccessLevelsVersion = state =>
(state.accessLevelstate.version = (state.accessLevelstate.version || 0) + 1) (state.accessLevels.version = (state.accessLevels.version || 0) + 1)
export const saveLevel = store => (newLevel, isNew, oldLevel = null) => { export const saveLevel = store => (newLevel, isNew, oldLevel = null) => {
store.update(state => { store.update(state => {
const levels = state.accessLevelstate.levels const levels = state.accessLevels.levels
const existingLevel = isNew const existingLevel = isNew
? null ? null
: find(a => a.name === oldLevel.name)(levels) : find(a => a.name === oldLevel.name)(levels)
if (existingLevel) { if (existingLevel) {
state.accessLevelstate.levels = pipe(levels, [ state.accessLevels.levels = levels.map(level => level === existingLevel ? newLevel : level)
map(a => (a === existingLevel ? newLevel : a)),
])
} else { } else {
state.accessLevelstate.levelstate.push(newLevel) state.accessLevels.levels.push(newLevel)
} }
incrementAccessLevelsVersion(s) incrementAccessLevelsVersion(state)
saveBackend(state) saveBackend(state)
return state return state

View File

@ -1,7 +1,6 @@
import { import {
filter, filter,
cloneDeep, cloneDeep,
// sortBy,
map, map,
last, last,
concat, concat,
@ -12,9 +11,7 @@ import {
import { import {
pipe, pipe,
getNode, getNode,
// validate,
constructHierarchy, constructHierarchy,
// templateApi,
} from "../../common/core" } from "../../common/core"
import * as backendStoreActions from "./backend"; import * as backendStoreActions from "./backend";
import { writable } from "svelte/store" import { writable } from "svelte/store"
@ -78,6 +75,7 @@ export const getStore = () => {
store.deleteField = backendStoreActions.deleteField(store) store.deleteField = backendStoreActions.deleteField(store)
store.saveLevel = backendStoreActions.saveLevel(store) store.saveLevel = backendStoreActions.saveLevel(store)
store.deleteLevel = backendStoreActions.deleteLevel(store) store.deleteLevel = backendStoreActions.deleteLevel(store)
store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store)
store.importAppDefinition = importAppDefinition(store) store.importAppDefinition = importAppDefinition(store)
store.saveAction = saveAction(store) store.saveAction = saveAction(store)

View File

@ -24,6 +24,7 @@
} }
} }
} }
</script> </script>
<div bind:this={ukModal} uk-modal {id}> <div bind:this={ukModal} uk-modal {id}>

View File

@ -19,39 +19,32 @@
</script> </script>
<div class="root"> <div class="root">
<ButtonGroup> <div class="button-container">
<ActionButton color="secondary" grouped on:click={store.saveCurrentNode}>
Save
</ActionButton>
{#if !$store.currentNodeIsNew} {#if !$store.currentNodeIsNew}
<ActionButton alert grouped on:click={openConfirmDelete}> <ActionButton alert on:click={deleteCurrentNode}>
Delete Delete
</ActionButton> </ActionButton>
{/if} {/if}
</ButtonGroup>
<ActionButton color="secondary" on:click={store.saveCurrentNode}>
Save
</ActionButton>
</div>
{#if $store.errors && $store.errors.length > 0} {#if $store.errors && $store.errors.length > 0}
<ErrorsBox errors={$store.errors} /> <ErrorsBox errors={$store.errors} />
{/if} {/if}
<Modal onClosed={() => (confirmDelete = false)} bind:isOpen={confirmDelete}>
<span>Are you sure you want to delete {$store.currentNode.name}?</span>
<div class="uk-modal-footer uk-text-right">
<ButtonGroup>
<ActionButton alert on:click={deleteCurrentNode}>Yes</ActionButton>
<ActionButton primary on:click={() => (confirmDelete = false)}>
No
</ActionButton>
</ButtonGroup>
</div>
</Modal>
</div> </div>
<style> <style>
.root { .root {
padding: 1.5rem; display: flex;
background: #fafafa;
width: 100%; width: 100%;
align-items: right; border-top: 1px solid #ccc;
}
.button-container {
padding: 20px;
} }
</style> </style>

View File

@ -12,34 +12,46 @@
CreateEditRecordModal, CreateEditRecordModal,
CreateEditModelModal, CreateEditModelModal,
CreateEditViewModal, CreateEditViewModal,
CreateDatabaseModal CreateDatabaseModal,
DeleteRecordModal
} from "./ModelDataTable/modals" } from "./ModelDataTable/modals"
let selectedRecord let selectedRecord
function selectRecord(record) { function selectRecord(record) {
selectedRecord = record selectedRecord = record
backendUiStore.actions.modals.show("RECORD")
} }
function onClosed() { function onClosed() {
// backendUiStore.actions.modals.hide() backendUiStore.actions.modals.hide()
} }
$: recordOpen = $backendUiStore.visibleModal === "RECORD" $: recordOpen = $backendUiStore.visibleModal === "RECORD"
$: modelOpen = $backendUiStore.visibleModal === "MODEL" $: modelOpen = $backendUiStore.visibleModal === "MODEL"
$: viewOpen = $backendUiStore.visibleModal === "VIEW" $: viewOpen = $backendUiStore.visibleModal === "VIEW"
$: databaseOpen = $backendUiStore.visibleModal === "DATABASE" $: databaseOpen = $backendUiStore.visibleModal === "DATABASE"
$: deleteRecordOpen = $backendUiStore.visibleModal === "DELETE_RECORD"
// $: recordOpen = $store.currentNode && $store.currentNode.type === 'record' // $: recordOpen = $store.currentNode && $store.currentNode.type === 'record'
// $: viewOpen = $store.currentNode && $store.currentNode.type === 'index' // $: viewOpen = $store.currentNode && $store.currentNode.type === 'index'
</script> </script>
({ console.log($backendUiStore.visibleModal) }) <Modal isOpen={!!$backendUiStore.visibleModal} {onClosed}>
{#if recordOpen}
<CreateEditRecordModal modalOpen={recordOpen} record={selectedRecord} {onClosed} /> <CreateEditRecordModal record={selectedRecord} {onClosed} />
<CreateEditModelModal modalOpen={modelOpen} {onClosed} /> {/if}
<CreateEditViewModal modalOpen={viewOpen} {onClosed} /> {#if modelOpen}
<CreateDatabaseModal modalOpen={databaseOpen} {onClosed} /> <CreateEditModelModal {onClosed} />
{/if}
{#if viewOpen}
<CreateEditViewModal {onClosed} />
{/if}
{#if databaseOpen}
<CreateDatabaseModal {onClosed} />
{/if}
{#if deleteRecordOpen}
<DeleteRecordModal record={selectedRecord} />
{/if}
</Modal>
<div class="root"> <div class="root">

View File

@ -38,15 +38,12 @@
} }
onMount(async () => { onMount(async () => {
if (views.length > 0) {
await fetchRecordsForView(views[0].name, currentAppInfo) await fetchRecordsForView(views[0].name, currentAppInfo)
}
}) })
</script> </script>
<DeleteRecordModal
modalOpen={deleteRecordModal}
record={selectedRecord}
/>
<section> <section>
<div class="table-controls"> <div class="table-controls">
<h4 class="budibase__title--3">Shoe database</h4> <h4 class="budibase__title--3">Shoe database</h4>
@ -79,13 +76,16 @@
<div>View</div> <div>View</div>
</li> </li>
<li <li
on:click={() => selectRecord(row)}> on:click={() => {
selectRecord(row)
backendUiStore.actions.modals.show("RECORD")
}}>
<div>Edit</div> <div>Edit</div>
</li> </li>
<li> <li>
<div <div
on:click={() => { on:click={() => {
selectedRecord = row selectRecord(row)
backendUiStore.actions.modals.show("DELETE_RECORD") backendUiStore.actions.modals.show("DELETE_RECORD")
}}> }}>
Delete Delete
@ -112,7 +112,8 @@
table { table {
border: 1px solid #ccc; border: 1px solid #ccc;
background: #fff; background: #fff;
border-radius: 2px; border-radius: 3px;
border-collapse: separate;
} }
thead { thead {
@ -128,6 +129,7 @@
tbody tr { tbody tr {
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
transition: 0.3s background-color; transition: 0.3s background-color;
color: var(--darkslate);
} }
tbody tr:hover { tbody tr:hover {

View File

@ -1,5 +1,12 @@
import api from "../../builderStore/api"; import api from "../../builderStore/api";
import { getNewRecord } from "../../common/core" import { getNewRecord, getNewInstance } from "../../common/core"
export async function createDatabase(appname, instanceName) {
const CREATE_DATABASE_URL = `/_builder/instance/_master/0/api/record`
const database = getNewInstance(appname, instanceName);
const response = await api.post(CREATE_DATABASE_URL, database);
return await response.json()
}
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.key}` const DELETE_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record${record.key}`

View File

@ -1,17 +1,23 @@
<script> <script>
import Modal from "../../../common/Modal.svelte" import Modal from "../../../common/Modal.svelte"
import { store } from "../../../builderStore"
import ActionButton from "../../../common/ActionButton.svelte" import ActionButton from "../../../common/ActionButton.svelte"
import * as api from "../api" import * as api from "../api"
export let modalOpen = false let databaseName
const onClosed = () => (modalOpen = false) async function createDatabase() {
const response = await api.createDatabase($store.appname, databaseName)
store.createDatabaseForApp(response)
onClosed()
}
</script> </script>
<Modal {onClosed} isOpen={modalOpen}> <section>
CREATE A NEW DATABASE FROM HERE CREATE A NEW DATABASE FROM HERE
<input type="text" bind:value={databaseName} />
<div class="actions"> <div class="actions">
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton disabled={false}>Save</ActionButton> <ActionButton disabled={!databaseName} on:click={createDatabase}>Save</ActionButton>
</div> </div>
</Modal> </section>

View File

@ -5,12 +5,9 @@
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 onClosed
</script> </script>
<Modal {onClosed} isOpen={modalOpen}> <section>
<h4 class="budibase__title--4"> <h4 class="budibase__title--4">
<i class="ri-list-settings-line" /> <i class="ri-list-settings-line" />
Create / Edit Model Create / Edit Model
@ -19,4 +16,4 @@
<ModelView /> <ModelView />
<ActionsHeader /> <ActionsHeader />
</div> </div>
</Modal> </section>

View File

@ -7,7 +7,6 @@
import { getNewRecord } from "../../../common/core" import { getNewRecord } from "../../../common/core"
import * as api from "../api" import * as api from "../api"
export let modalOpen = false
export let record export let record
export let onClosed export let onClosed
@ -22,10 +21,9 @@
$: modelFields = selectedModel $: modelFields = selectedModel
? selectedModel.fields.map(({ name }) => name) ? selectedModel.fields.map(({ name }) => name)
: [] : []
</script> </script>
<Modal {onClosed} isOpen={modalOpen}> <div>
<h4 class="budibase__title--4">Create / Edit Record</h4> <h4 class="budibase__title--4">Create / Edit Record</h4>
<div class="actions"> <div class="actions">
<form class="uk-form-stacked"> <form class="uk-form-stacked">
@ -80,10 +78,10 @@
</ActionButton> </ActionButton>
</div> </div>
</div> </div>
</Modal> </div>
<style> <style>
.actions { .actions {
position: absolute; /* position: absolute; */
} }
</style> </style>

View File

@ -12,7 +12,7 @@
</script> </script>
<Modal {onClosed} isOpen={modalOpen}> <section>
<IndexView /> <IndexView />
<ActionsHeader /> <ActionsHeader />
@ -22,4 +22,4 @@
Save Save
</ActionButton> </ActionButton>
</div> --> </div> -->
</Modal> </section>

View File

@ -0,0 +1,27 @@
<script>
import Modal from "../../../common/Modal.svelte"
import { store } from "../../../builderStore"
import ActionButton from "../../../common/ActionButton.svelte"
import * as api from "../api"
export let modalOpen = false
let userName
const onClosed = () => (modalOpen = false)
async function createUser() {
const response = await api.createUser($store.appname, userName)
store.createUserForInstance(response)
onClosed()
}
</script>
<Modal {onClosed} isOpen={modalOpen}>
CREATE A NEW user FROM HERE
<input type="text" bind:value={userName} />
<div class="actions">
<ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton disabled={!userName} on:click={createUser}>Save</ActionButton>
</div>
</Modal>

View File

@ -15,12 +15,12 @@
function onClosed() { function onClosed() {
backendUiStore.actions.modals.hide() backendUiStore.actions.modals.hide()
} }
</script> </script>
<Modal {onClosed} isOpen={modalOpen}> <section>
<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">
<ActionButton on:click={onClosed}>Cancel</ActionButton> <ActionButton on:click={onClosed}>Cancel</ActionButton>
<ActionButton <ActionButton
@ -28,9 +28,9 @@
on:click={async () => { on:click={async () => {
await api.deleteRecord(record, currentAppInfo) await api.deleteRecord(record, currentAppInfo)
backendUiStore.actions.records.delete(record) backendUiStore.actions.records.delete(record)
onClosed(); onClosed()
}}> }}>
Delete Delete
</ActionButton> </ActionButton>
</div> </div>
</Modal> </section>

View File

@ -110,7 +110,7 @@
<div class="uk-form-controls"> <div class="uk-form-controls">
<Select <Select
value={parentRecord} value={parentRecord}
on:change={e => (parentRecord = e.target.value)}> on:change={e => store.newChildRecord()}>
{#each models as model} {#each models as model}
<option value={model}>{model.name}</option> <option value={model}>{model.name}</option>
{/each} {/each}

View File

@ -3,11 +3,15 @@
import { store, backendUiStore } from "../builderStore" import { store, backendUiStore } from "../builderStore"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import getIcon from "../common/icon" import getIcon from "../common/icon"
export let level = 0 export let level = 0
export let node export let node
export let type export let type
let navActive = "" let navActive = ""
let expanded = false
$: hasChildren = (node.children || node.indexes) && (node.children.length || node.indexes.length)
const ICON_MAP = { const ICON_MAP = {
index: "ri-eye-line", index: "ri-eye-line",
@ -34,9 +38,16 @@
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}>
{#if hasChildren}
<i
class={`ri-arrow-${expanded ? "down" : "right"}-s-line`}
on:click={() => expanded = !expanded}
/>
{/if}
<i class={ICON_MAP[type]} /> <i class={ICON_MAP[type]} />
<span style="margin-left: 1rem">{node.name}</span> <span style="margin-left: 1rem">{node.name}</span>
</div> </div>
{#if expanded}
{#if node.children} {#if node.children}
{#each node.children as child} {#each node.children as child}
<svelte:self node={child} level={level + 1} type="record" /> <svelte:self node={child} level={level + 1} type="record" />
@ -47,6 +58,7 @@
<svelte:self node={index} level={level + 1} type="index" /> <svelte:self node={index} level={level + 1} type="index" />
{/each} {/each}
{/if} {/if}
{/if}
</div> </div>
<style> <style>