improvements to linked records
This commit is contained in:
parent
6adb2a72e0
commit
afcc80ab9d
|
@ -58,7 +58,7 @@ export const getBackendUiStore = () => {
|
|||
state.tabs.SETUP_PANEL = "SETUP"
|
||||
return state
|
||||
}),
|
||||
save: async ({ instanceId, model }) => {
|
||||
save: async ({ model }) => {
|
||||
const updatedModel = cloneDeep(model)
|
||||
|
||||
// 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 savedModel = await response.json()
|
||||
|
||||
|
@ -86,6 +86,8 @@ export const getBackendUiStore = () => {
|
|||
state.models = state.models
|
||||
}
|
||||
|
||||
// TODO: fetch models
|
||||
|
||||
store.actions.models.select(savedModel)
|
||||
return state
|
||||
})
|
||||
|
|
|
@ -9,8 +9,13 @@
|
|||
|
||||
let records = []
|
||||
|
||||
let linkedRecords = new Set(linked)
|
||||
|
||||
$: linked = [...linkedRecords]
|
||||
$: FIELDS_TO_HIDE = ["modelId", "type", "_id", "_rev", $backendUiStore.selectedModel.name]
|
||||
|
||||
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)
|
||||
records = await response.json()
|
||||
}
|
||||
|
@ -19,17 +24,25 @@
|
|||
fetchRecords()
|
||||
})
|
||||
|
||||
function linkRecord(record) {
|
||||
linked.push(record._id)
|
||||
function linkRecord(id) {
|
||||
if (linkedRecords.has(id)) {
|
||||
linkedRecords.delete(id);
|
||||
} else {
|
||||
linkedRecords.add(id)
|
||||
}
|
||||
|
||||
linkedRecords = linkedRecords
|
||||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<h3>{linkName}</h3>
|
||||
<header>
|
||||
<h3>{linkName}</h3>
|
||||
</header>
|
||||
{#each records as record}
|
||||
<div class="linked-record" on:click={() => linkRecord(record)}>
|
||||
<div class="fields">
|
||||
{#each Object.keys(record).slice(0, 2) as key}
|
||||
<div class="linked-record" on:click={() => linkRecord(record._id)}>
|
||||
<div class="fields" class:selected={linkedRecords.has(record._id)}>
|
||||
{#each Object.keys(record).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
||||
<div class="field">
|
||||
<span>{key}</span>
|
||||
<p>{record[key]}</p>
|
||||
|
@ -41,8 +54,15 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
.fields.selected {
|
||||
background: var(--light-grey);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.fields {
|
||||
|
@ -50,12 +70,14 @@
|
|||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: 20px;
|
||||
background: var(--grey);
|
||||
background: var(--white);
|
||||
border: 1px solid var(--grey);
|
||||
border-radius: 5px;
|
||||
transition: 0.5s all;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.field:hover {
|
||||
.fields:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,21 +4,27 @@
|
|||
import { backendUiStore } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
|
||||
|
||||
export let ids = []
|
||||
export let header
|
||||
|
||||
let records = []
|
||||
let open = false
|
||||
|
||||
$: FIELDS_TO_HIDE = ["modelId", "type", "_id", "_rev", $backendUiStore.selectedModel.name]
|
||||
|
||||
async function fetchRecords() {
|
||||
const FETCH_RECORDS_URL = `/api/${$backendUiStore.selectedDatabase._id}/records/search`
|
||||
const response = await api.post(FETCH_RECORDS_URL, {
|
||||
keys: ids,
|
||||
const response = await api.post("/api/records/search", {
|
||||
keys: ids
|
||||
})
|
||||
records = await response.json()
|
||||
}
|
||||
|
||||
$: ids && fetchRecords()
|
||||
$: ids && fetchRecords()
|
||||
|
||||
function toggleOpen() {
|
||||
open = !open
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
fetchRecords()
|
||||
|
@ -26,14 +32,17 @@
|
|||
</script>
|
||||
|
||||
<section>
|
||||
<a on:click={() => (open = !open)}>{records.length}</a>
|
||||
<a on:click={toggleOpen}>{records.length}</a>
|
||||
{#if open}
|
||||
<div class="popover" transition:fade>
|
||||
<header>
|
||||
<h3>{header}</h3>
|
||||
<i class="ri-close-circle-fill" on:click={toggleOpen} />
|
||||
</header>
|
||||
{#each records as record}
|
||||
<div class="linked-record">
|
||||
<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">
|
||||
<span>{key}</span>
|
||||
<p>{record[key]}</p>
|
||||
|
@ -47,11 +56,29 @@
|
|||
</section>
|
||||
|
||||
<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 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.popover {
|
||||
width: 500px;
|
||||
position: absolute;
|
||||
right: 15%;
|
||||
padding: 20px;
|
||||
|
@ -61,6 +88,8 @@
|
|||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fields {
|
||||
|
@ -71,10 +100,7 @@
|
|||
background: var(--white);
|
||||
border: 1px solid var(--grey);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.field:hover {
|
||||
cursor: pointer;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.field span {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { store, backendUiStore } from "builderStore"
|
||||
import { notifier } from "@beyonk/svelte-notifications"
|
||||
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 Select from "components/common/Select.svelte"
|
||||
import RecordFieldControl from "./RecordFieldControl.svelte"
|
||||
|
@ -68,13 +68,19 @@
|
|||
</script>
|
||||
|
||||
<div class="actions">
|
||||
<h4 class="budibase__title--4">Create / Edit Record</h4>
|
||||
<header>
|
||||
<i class="ri-file-user-fill" />
|
||||
<h4 class="budibase__title--4">Create / Edit Record</h4>
|
||||
</header>
|
||||
<ErrorsBox {errors} />
|
||||
<form on:submit|preventDefault class="uk-form-stacked">
|
||||
{#each modelSchema as [key, meta]}
|
||||
<div class="uk-margin">
|
||||
{#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}
|
||||
<RecordFieldControl
|
||||
type={determineInputType(meta)}
|
||||
|
@ -87,14 +93,44 @@
|
|||
</form>
|
||||
</div>
|
||||
<footer>
|
||||
<ActionButton alert on:click={onClosed}>Cancel</ActionButton>
|
||||
<ActionButton on:click={saveRecord}>Save</ActionButton>
|
||||
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||
<Button attention on:click={saveRecord}>Save</Button>
|
||||
</footer>
|
||||
|
||||
<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 {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
|
|
|
@ -51,3 +51,16 @@
|
|||
on:input={handleInput}
|
||||
on:change={handleInput} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
input {
|
||||
color: var(--dark-grey);
|
||||
}
|
||||
</style>
|
|
@ -40,7 +40,7 @@
|
|||
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)
|
||||
backendUiStore.update(state => {
|
||||
state.selectedView = null
|
||||
|
|
|
@ -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>
|
|
@ -85,42 +85,10 @@
|
|||
|
||||
dimensions = {top, left}
|
||||
}
|
||||
})
|
||||
|
||||
function openColorpicker(event) {
|
||||
if (colorPreview) {
|
||||
const {
|
||||
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
|
||||
function onColorChange(color) {
|
||||
value = color.detail;
|
||||
dispatch("change", color.detail)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -142,13 +110,9 @@
|
|||
<span>×</span>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="color-preview preview-error" style={errorPreviewStyle}>
|
||||
<span>×</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.color-preview-container{
|
||||
display: flex;
|
||||
|
|
Loading…
Reference in New Issue