improvements to linked records

This commit is contained in:
Martin McKeaveney 2020-06-23 13:50:45 +01:00
parent 6adb2a72e0
commit afcc80ab9d
8 changed files with 131 additions and 211 deletions

View File

@ -58,7 +58,7 @@ export const getBackendUiStore = () => {
state.tabs.SETUP_PANEL = "SETUP" state.tabs.SETUP_PANEL = "SETUP"
return state return state
}), }),
save: async ({ instanceId, model }) => { save: async ({ model }) => {
const updatedModel = cloneDeep(model) const updatedModel = cloneDeep(model)
// TODO: refactor // TODO: refactor
@ -70,7 +70,7 @@ export const getBackendUiStore = () => {
} }
} }
const SAVE_MODEL_URL = `/api/${instanceId}/models` const SAVE_MODEL_URL = `/api/models`
const response = await api.post(SAVE_MODEL_URL, updatedModel) const response = await api.post(SAVE_MODEL_URL, updatedModel)
const savedModel = await response.json() const savedModel = await response.json()
@ -86,6 +86,8 @@ export const getBackendUiStore = () => {
state.models = state.models state.models = state.models
} }
// TODO: fetch models
store.actions.models.select(savedModel) store.actions.models.select(savedModel)
return state return state
}) })

View File

@ -9,8 +9,13 @@
let records = [] let records = []
let linkedRecords = new Set(linked)
$: linked = [...linkedRecords]
$: FIELDS_TO_HIDE = ["modelId", "type", "_id", "_rev", $backendUiStore.selectedModel.name]
async function fetchRecords() { async function fetchRecords() {
const FETCH_RECORDS_URL = `/api/${$backendUiStore.selectedDatabase._id}/${modelId}/records` const FETCH_RECORDS_URL = `/api/${modelId}/records`
const response = await api.get(FETCH_RECORDS_URL) const response = await api.get(FETCH_RECORDS_URL)
records = await response.json() records = await response.json()
} }
@ -19,17 +24,25 @@
fetchRecords() fetchRecords()
}) })
function linkRecord(record) { function linkRecord(id) {
linked.push(record._id) if (linkedRecords.has(id)) {
linkedRecords.delete(id);
} else {
linkedRecords.add(id)
}
linkedRecords = linkedRecords
} }
</script> </script>
<section> <section>
<header>
<h3>{linkName}</h3> <h3>{linkName}</h3>
</header>
{#each records as record} {#each records as record}
<div class="linked-record" on:click={() => linkRecord(record)}> <div class="linked-record" on:click={() => linkRecord(record._id)}>
<div class="fields"> <div class="fields" class:selected={linkedRecords.has(record._id)}>
{#each Object.keys(record).slice(0, 2) as key} {#each Object.keys(record).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
<div class="field"> <div class="field">
<span>{key}</span> <span>{key}</span>
<p>{record[key]}</p> <p>{record[key]}</p>
@ -41,8 +54,15 @@
</section> </section>
<style> <style>
.fields.selected {
background: var(--light-grey);
}
h3 { h3 {
font-size: 20px; font-size: 18px;
font-weight: 600;
margin-bottom: 12px;
color: var(--ink);
} }
.fields { .fields {
@ -50,12 +70,14 @@
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px; grid-gap: 20px;
background: var(--grey); background: var(--white);
border: 1px solid var(--grey); border: 1px solid var(--grey);
border-radius: 5px; border-radius: 5px;
transition: 0.5s all;
margin-bottom: 8px;
} }
.field:hover { .fields:hover {
cursor: pointer; cursor: pointer;
} }

View File

@ -4,36 +4,45 @@
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import api from "builderStore/api" import api from "builderStore/api"
export let ids = [] export let ids = []
export let header export let header
let records = [] let records = []
let open = false let open = false
$: FIELDS_TO_HIDE = ["modelId", "type", "_id", "_rev", $backendUiStore.selectedModel.name]
async function fetchRecords() { async function fetchRecords() {
const FETCH_RECORDS_URL = `/api/${$backendUiStore.selectedDatabase._id}/records/search` const response = await api.post("/api/records/search", {
const response = await api.post(FETCH_RECORDS_URL, { keys: ids
keys: ids,
}) })
records = await response.json() records = await response.json()
} }
$: ids && fetchRecords() $: ids && fetchRecords()
function toggleOpen() {
open = !open
}
onMount(() => { onMount(() => {
fetchRecords() fetchRecords()
}) })
</script> </script>
<section> <section>
<a on:click={() => (open = !open)}>{records.length}</a> <a on:click={toggleOpen}>{records.length}</a>
{#if open} {#if open}
<div class="popover" transition:fade> <div class="popover" transition:fade>
<header>
<h3>{header}</h3> <h3>{header}</h3>
<i class="ri-close-circle-fill" on:click={toggleOpen} />
</header>
{#each records as record} {#each records as record}
<div class="linked-record"> <div class="linked-record">
<div class="fields"> <div class="fields">
{#each Object.keys(record).slice(0, 2) as key} {#each Object.keys(record).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
<div class="field"> <div class="field">
<span>{key}</span> <span>{key}</span>
<p>{record[key]}</p> <p>{record[key]}</p>
@ -47,11 +56,29 @@
</section> </section>
<style> <style>
header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
i {
font-size: 24px;
color: var(--ink-lighter);
}
i:hover {
cursor: pointer;
}
a { a {
font-size: 14px; font-size: 14px;
} }
.popover { .popover {
width: 500px;
position: absolute; position: absolute;
right: 15%; right: 15%;
padding: 20px; padding: 20px;
@ -61,6 +88,8 @@
h3 { h3 {
font-size: 20px; font-size: 20px;
font-weight: bold;
margin: 0;
} }
.fields { .fields {
@ -71,10 +100,7 @@
background: var(--white); background: var(--white);
border: 1px solid var(--grey); border: 1px solid var(--grey);
border-radius: 5px; border-radius: 5px;
} margin-bottom: 8px;
.field:hover {
cursor: pointer;
} }
.field span { .field span {

View File

@ -3,7 +3,7 @@
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { notifier } from "@beyonk/svelte-notifications" import { notifier } from "@beyonk/svelte-notifications"
import { compose, map, get, flatten } from "lodash/fp" import { compose, map, get, flatten } from "lodash/fp"
import ActionButton from "components/common/ActionButton.svelte" import { Button } from "@budibase/bbui"
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte" import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
import Select from "components/common/Select.svelte" import Select from "components/common/Select.svelte"
import RecordFieldControl from "./RecordFieldControl.svelte" import RecordFieldControl from "./RecordFieldControl.svelte"
@ -68,13 +68,19 @@
</script> </script>
<div class="actions"> <div class="actions">
<header>
<i class="ri-file-user-fill" />
<h4 class="budibase__title--4">Create / Edit Record</h4> <h4 class="budibase__title--4">Create / Edit Record</h4>
</header>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-stacked"> <form on:submit|preventDefault class="uk-form-stacked">
{#each modelSchema as [key, meta]} {#each modelSchema as [key, meta]}
<div class="uk-margin"> <div class="uk-margin">
{#if meta.type === 'link'} {#if meta.type === 'link'}
<LinkedRecordSelector bind:linked={record[key]} linkName={key} modelId={meta.modelId} /> <LinkedRecordSelector
bind:linked={record[key]}
linkName={key}
modelId={meta.modelId} />
{:else} {:else}
<RecordFieldControl <RecordFieldControl
type={determineInputType(meta)} type={determineInputType(meta)}
@ -87,14 +93,44 @@
</form> </form>
</div> </div>
<footer> <footer>
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <Button secondary on:click={onClosed}>Cancel</Button>
<ActionButton on:click={saveRecord}>Save</ActionButton> <Button attention on:click={saveRecord}>Save</Button>
</footer> </footer>
<style> <style>
header {
margin-bottom: 40px;
display: grid;
grid-gap: 5px;
grid-template-columns: 40px 1fr;
align-items: center;
}
i {
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--secondary);
color: var(--ink);
font-size: 20px;
margin-right: 20px;
border-radius: 3px;
}
h4 {
display: inline-block;
font-size: 24px;
font-weight: bold;
color: var(--ink);
margin: 0;
}
.actions { .actions {
padding: 30px; padding: 30px;
} }
footer { footer {
padding: 20px; padding: 20px;
background: #fafafa; background: #fafafa;

View File

@ -51,3 +51,16 @@
on:input={handleInput} on:input={handleInput}
on:change={handleInput} /> on:change={handleInput} />
{/if} {/if}
<style>
label {
display: block;
font-size: 18px;
font-weight: 500;
margin-bottom: 12px;
}
input {
color: var(--dark-grey);
}
</style>

View File

@ -40,7 +40,7 @@
return return
} }
const DELETE_MODEL_URL = `/api/${instanceId}/models/${model._id}/${model._rev}` const DELETE_MODEL_URL = `/api/models/${model._id}/${model._rev}`
const response = await api.delete(DELETE_MODEL_URL) const response = await api.delete(DELETE_MODEL_URL)
backendUiStore.update(state => { backendUiStore.update(state => {
state.selectedView = null state.selectedView = null

View File

@ -1,143 +0,0 @@
<script>
import { getContext, onMount } from "svelte"
import { store, backendUiStore } from "builderStore"
import HierarchyRow from "./HierarchyRow.svelte"
import NavItem from "./NavItem.svelte"
import getIcon from "components/common/icon"
import api from "builderStore/api"
import {
CreateEditModelModal,
CreateEditViewModal,
} from "components/database/ModelDataTable/modals"
const { open, close } = getContext("simple-modal")
function editModel() {
open(
CreateEditModelModal,
{
model: node,
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
function newModel() {
open(
CreateEditModelModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
function newView() {
open(
CreateEditViewModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
function selectModel(model) {
backendUiStore.update(state => {
state.selectedModel = model
state.selectedView = `all_${model._id}`
return state
})
}
async function deleteModel(modelToDelete) {
const DELETE_MODEL_URL = `/api/models/${node._id}/${node._rev}`
const response = await api.delete(DELETE_MODEL_URL)
backendUiStore.update(state => {
state.models = state.models.filter(
model => model._id !== modelToDelete._id
)
state.selectedView = {}
return state
})
}
function selectView(view) {
backendUiStore.update(state => {
state.selectedView = view.name
return state
})
}
</script>
<div class="items-root">
<div class="hierarchy">
<div class="components-list-container">
<div class="nav-group-header">
<div class="hierarchy-title">Models</div>
<div class="uk-inline">
<i class="ri-add-line hoverable" on:click={newModel} />
</div>
</div>
</div>
<div class="hierarchy-items-container">
{#each $backendUiStore.models as model}
<HierarchyRow onSelect={selectModel} node={model} type="model" />
{/each}
</div>
</div>
<div class="hierarchy">
<div class="components-list-container">
<div class="nav-group-header">
<div class="hierarchy-title">Views</div>
<div class="uk-inline">
<i class="ri-add-line hoverable" on:click={newView} />
</div>
</div>
</div>
<div class="hierarchy-items-container">
{#each $backendUiStore.views as view}
<HierarchyRow onSelect={selectView} node={view} type="view" />
{/each}
</div>
</div>
</div>
<style>
.items-root {
display: flex;
flex-direction: column;
max-height: 100%;
height: 100%;
background-color: var(--white);
}
.nav-group-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 10px 20px;
}
.hierarchy-title {
align-items: center;
font-size: 18px;
font-weight: 700;
text-rendering: optimizeLegibility;
color: var(--ink);
}
.hierarchy {
display: flex;
flex-direction: column;
}
.hierarchy-items-container {
flex: 1 1 auto;
overflow-y: auto;
}
</style>

View File

@ -85,42 +85,10 @@
dimensions = {top, left} dimensions = {top, left}
} }
})
function openColorpicker(event) { function onColorChange(color) {
if (colorPreview) { value = color.detail;
const { dispatch("change", color.detail)
top: spaceAbove,
width,
bottom,
right,
left: spaceLeft,
} = colorPreview.getBoundingClientRect()
const { innerHeight, innerWidth } = window
const { offsetLeft, offsetTop } = colorPreview
//get the scrollTop value for all scrollable parent elements
let scrollTop = parentNodes.reduce(
(scrollAcc, el) => (scrollAcc += el.scrollTop),
0
)
const spaceBelow = innerHeight - spaceAbove - previewHeight
const top =
spaceAbove > spaceBelow
? offsetTop - pickerHeight - scrollTop
: offsetTop + previewHeight - scrollTop
//TOO: Testing and Scroll Awareness for x Scroll
const spaceRight = innerWidth - spaceLeft + previewWidth
const left =
spaceRight > spaceLeft
? offsetLeft + previewWidth + pickerWidth
: offsetLeft - pickerWidth
dimensions = { top, left }
open = true
} }
</script> </script>
@ -142,13 +110,9 @@
<span>&times;</span> <span>&times;</span>
</div> </div>
{/if} {/if}
{:else}
<div class="color-preview preview-error" style={errorPreviewStyle}>
<span>&times;</span>
</div>
{/if}
</div> </div>
<style> <style>
.color-preview-container{ .color-preview-container{
display: flex; display: flex;