Add WIP draft of linked records UI allowing single linked record selection

This commit is contained in:
Andrew Kingston 2020-09-29 18:27:35 +01:00
parent e798fca33b
commit 089ecf5609
8 changed files with 86 additions and 158 deletions

View File

@ -63,7 +63,7 @@
}
},
"dependencies": {
"@budibase/bbui": "^1.37.1",
"@budibase/bbui": "^1.39.0",
"@budibase/client": "^0.1.21",
"@budibase/colorpicker": "^1.0.1",
"@fortawesome/fontawesome-free": "^5.14.0",

View File

@ -91,7 +91,7 @@
{#each headers as header}
<td>
{#if schema[header].type === 'link'}
<LinkedRecord field={schema[header]} ids={row[header]} />
{JSON.stringify(row[header])}
{:else if schema[header].type === 'attachment'}
<AttachmentList files={row[header] || []} />
{:else}{getOr('', header, row)}{/if}

View File

@ -31,6 +31,9 @@
}
let originalName = field.name
$: modelOptions = $backendUiStore.models.filter(
model => model._id !== $backendUiStore.draftModel._id
)
async function saveColumn() {
backendUiStore.update(state => {
@ -67,23 +70,27 @@
</Select>
<Toggle
bind:checked={field.constraints.presence.allowEmpty}
checked={!field.constraints.presence.allowEmpty}
on:change={e => (field.constraints.presence.allowEmpty = !e.target.checked)}
thin
text="Required" />
{#if field.type === 'string' && field.constraints}
{#if field.type === 'string'}
<Input
thin
type="number"
label="Max Length"
bind:value={field.constraints.length.maximum} />
<ValuesList label="Categories" bind:values={field.constraints.inclusion} />
{:else if field.type === 'datetime' && field.constraints}
{:else if field.type === 'options'}
<ValuesList
label="Options (one per line)"
bind:values={field.constraints.inclusion} />
{:else if field.type === 'datetime'}
<DatePicker
label="Earliest"
bind:value={field.constraints.datetime.earliest} />
<DatePicker label="Latest" bind:value={field.constraints.datetime.latest} />
{:else if field.type === 'number' && field.constraints}
{:else if field.type === 'number'}
<Input
thin
type="number"
@ -95,17 +102,16 @@
label="Max Value"
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
{:else if field.type === 'link'}
<div class="field">
<label>Link</label>
<Select bind:value={field.modelId}>
<option value="">Choose an option</option>
{#each $backendUiStore.models as model}
{#if model._id !== $backendUiStore.draftModel._id}
<option value={model._id}>{model.name}</option>
{/if}
{/each}
</Select>
</div>
<Select label="Table" thin secondary bind:value={field.modelId}>
<option value="">Choose an option</option>
{#each modelOptions as model}
<option value={model._id}>{model.name}</option>
{/each}
</Select>
<Input
label={`Column Name in Other Table`}
thin
bind:value={field.fieldName} />
{/if}
<footer>
<Button secondary on:click={onClosed}>Cancel</Button>

View File

@ -47,9 +47,8 @@
<div>
{#if meta.type === 'link'}
<LinkedRecordSelector
bind:linked={record[key]}
linkName={meta.name}
modelId={meta.modelId} />
bind:linkedRecords={record[key]}
schema={meta} />
{:else}
<RecordFieldControl {meta} bind:value={record[key]} />
{/if}

View File

@ -1,64 +1,40 @@
<script>
import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte"
import { capitalise } from "../../../../helpers"
export let meta
export let value = meta.type === "boolean" ? false : ""
export let originalValue
let isSelect =
meta.type === "string" &&
meta.constraints &&
meta.constraints.inclusion &&
meta.constraints.inclusion.length > 0
let type = determineInputType(meta)
const type = determineInputType(meta)
const label = capitalise(meta.name)
function determineInputType(meta) {
if (meta.type === "datetime") return "date"
if (meta.type === "number") return "number"
if (meta.type === "boolean") return "checkbox"
if (meta.type === "attachment") return "file"
if (isSelect) return "select"
if (meta.type === "options") return "select"
return "text"
}
</script>
{#if type === 'select'}
<Select
thin
secondary
label={meta.name}
data-cy="{meta.name}-select"
bind:value>
<Select thin secondary {label} data-cy="{meta.name}-select" bind:value>
<option value="">Choose an option</option>
{#each meta.constraints.inclusion as opt}
<option value={opt}>{opt}</option>
{/each}
</Select>
{:else if type === 'date'}
<DatePicker label={meta.name} bind:value />
<DatePicker {label} bind:value />
{:else if type === 'file'}
<div>
<Label extraSmall grey forAttr={'dropzone-label'}>{meta.name}</Label>
<Label extraSmall grey forAttr={'dropzone-label'}>{label}</Label>
<Dropzone bind:files={value} />
</div>
{:else if type === 'checkbox'}
<Toggle text={meta.name} bind:checked={value} data-cy="{meta.name}-input" />
<Toggle text={label} bind:checked={value} data-cy="{meta.name}-input" />
{:else}
<Input thin label={meta.name} data-cy="{meta.name}-input" {type} bind:value />
<Input thin {label} data-cy="{meta.name}-input" {type} bind:value />
{/if}
<style>
.checkbox {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.checkbox :global(label) {
margin-bottom: 0;
margin-right: var(--spacing-xs);
}
</style>

View File

@ -2,37 +2,28 @@
import { onMount } from "svelte"
import { backendUiStore } from "builderStore"
import api from "builderStore/api"
import { Select, Label } from "@budibase/bbui"
import { capitalise } from "../../helpers"
export let modelId
export let linkName
export let linked = []
export let schema
export let linkedRecords = []
let records = []
let model = {}
let linkedRecords = new Set(linked)
$: linked = [...linkedRecords]
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel.name]
$: schema = $backendUiStore.selectedModel.schema
$: label = capitalise(schema.name)
$: linkedModelId = schema.modelId
$: linkedModel = $backendUiStore.models.find(
model => model._id === linkedModelId
)
async function fetchRecords() {
const FETCH_RECORDS_URL = `/api/${modelId}/records`
const FETCH_RECORDS_URL = `/api/${linkedModelId}/records`
const response = await api.get(FETCH_RECORDS_URL)
const modelResponse = await api.get(`/api/models/${modelId}`)
model = await modelResponse.json()
records = await response.json()
}
function linkRecord(id) {
if (linkedRecords.has(id)) {
linkedRecords.delete(id)
} else {
linkedRecords.add(id)
}
linkedRecords = linkedRecords
function getPrettyName(record) {
return record[linkedModel.primaryDisplay || "_id"]
}
onMount(() => {
@ -40,63 +31,18 @@
})
</script>
<section>
<header>
<h3>{linkName}</h3>
</header>
{#each records as record}
<div class="linked-record" on:click={() => linkRecord(record._id)}>
<div class="fields" class:selected={linkedRecords.has(record._id)}>
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
<div class="field">
<span>{model.schema[key].name}</span>
<p>{record[key]}</p>
</div>
{/each}
</div>
</div>
{/each}
</section>
<style>
.fields.selected {
background: var(--grey-2);
border: var(--purple) 1px solid;
}
h3 {
font-size: 18px;
font-weight: 600;
margin-bottom: 12px;
color: var(--ink);
}
.fields {
padding: 15px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
background: var(--white);
border: 1px solid var(--grey);
border-radius: 5px;
transition: 0.5s all;
margin-bottom: 8px;
}
.fields:hover {
cursor: pointer;
}
.field span {
color: var(--ink-lighter);
font-size: 12px;
}
.field p {
color: var(--ink);
font-size: 14px;
word-break: break-word;
font-weight: 500;
margin-top: 4px;
}
</style>
{#if linkedModel.primaryDisplay == null}
<Label extraSmall grey>{label}</Label>
<Label small black>
Please choose a primary display column for the
<b>{linkedModel.name}</b>
table.
</Label>
{:else}
<Select thin secondary bind:value={linkedRecords[0]} {label}>
<option value="">Choose an option</option>
{#each records as record}
<option value={record._id}>{getPrettyName(record)}</option>
{/each}
</Select>
{/if}

View File

@ -1,6 +1,6 @@
export const FIELDS = {
STRING: {
name: "Plain Text",
name: "Text",
icon: "ri-text",
type: "string",
constraints: {
@ -9,6 +9,16 @@ export const FIELDS = {
presence: { allowEmpty: true },
},
},
OPTIONS: {
name: "Options",
icon: "ri-list-check-2",
type: "options",
constraints: {
type: "string",
presence: { allowEmpty: true },
inclusion: [],
},
},
NUMBER: {
name: "Number",
icon: "ri-number-1",
@ -28,15 +38,6 @@ export const FIELDS = {
presence: { allowEmpty: true },
},
},
// OPTIONS: {
// name: "Options",
// icon: "ri-list-check-2",
// type: "options",
// constraints: {
// type: "string",
// presence: { allowEmpty: true },
// },
// },
DATETIME: {
name: "Date/Time",
icon: "ri-calendar-event-fill",
@ -60,15 +61,15 @@ export const FIELDS = {
presence: { allowEmpty: true },
},
},
// LINKED_FIELDS: {
// name: "Linked Fields",
// icon: "ri-link",
// type: "link",
// modelId: null,
// constraints: {
// type: "array",
// },
// },
LINK: {
name: "Relationship",
icon: "ri-link",
type: "link",
constraints: {
type: "array",
presence: { allowEmpty: true },
},
},
}
export const FILE_TYPES = {

View File

@ -709,10 +709,10 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@budibase/bbui@^1.37.1":
version "1.37.1"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.37.1.tgz#a4d5aa85ee36c8013173e28879f1e8c8421f017c"
integrity sha512-xdwwdqkpEjVYn1L38W7jR1mHkvf+xIoktoOm90LmmocJirzNo+flT4zxM2E7a3C3Bfs1l9NwEd6bqSuuxEF9Zg==
"@budibase/bbui@^1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.39.0.tgz#7d3e259a60a0b4602f3d2da3452679d91a591fdd"
integrity sha512-9IL5Lw488sdYCa9mjHHdrap11VqW6wQHNcNTL8fFHaWNzummtlaUlVMScs9cunYgsR/L4NCgH0zSFdP0RnrUqw==
dependencies:
sirv-cli "^0.4.6"
svelte-flatpickr "^2.4.0"