[#976] added optional RelationOptions.DisplayFields and refactored the relation picker UI

This commit is contained in:
Gani Georgiev
2023-01-23 21:57:35 +02:00
parent 4c73e16f54
commit 4c010847e3
106 changed files with 1845 additions and 981 deletions
@@ -56,7 +56,7 @@
<div class="form-field-addon email-visibility-addon">
<button
type="button"
class="btn btn-sm btn-secondary {record.emailVisibility ? 'btn-success' : 'btn-hint'}"
class="btn btn-sm btn-transparent {record.emailVisibility ? 'btn-success' : 'btn-hint'}"
use:tooltip={{
text: "Make email public or private",
position: "top-right",
@@ -70,47 +70,56 @@
}
</script>
<Field class="form-field form-field-file {field.required ? 'required' : ''}" name={field.name} let:uniqueId>
<Field
class="form-field form-field-list form-field-file {field.required ? 'required' : ''}"
name={field.name}
let:uniqueId
>
<label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon(field.type)} />
<span class="txt">{field.name}</span>
</label>
<div bind:this={filesListElem} class="files-list">
<div bind:this={filesListElem} class="list">
{#each valueAsArray as filename, i (filename)}
{@const isDeleted = deletedFileIndexes.includes(i)}
<div class="list-item">
<div class:fade={deletedFileIndexes.includes(i)}>
<RecordFileThumb {record} {filename} />
</div>
<a
href={ApiClient.getFileUrl(record, filename)}
class="filename link-hint"
class:txt-strikethrough={deletedFileIndexes.includes(i)}
title="Download"
target="_blank"
rel="noopener noreferrer"
>
{filename}
</a>
{#if deletedFileIndexes.includes(i)}
<button
type="button"
class="btn btn-sm btn-danger btn-secondary"
on:click={() => restoreExistingFile(i)}
<div class="content">
<a
href={ApiClient.getFileUrl(record, filename)}
class="txt-ellipsis {isDeleted ? 'txt-strikethrough txt-hint' : 'link-primary'}"
title="Download"
target="_blank"
rel="noopener noreferrer"
>
<span class="txt">Restore</span>
</button>
{:else}
<button
type="button"
class="btn btn-secondary btn-sm btn-circle btn-remove txt-hint"
use:tooltip={"Remove file"}
on:click={() => removeExistingFile(i)}
>
<i class="ri-close-line" />
</button>
{/if}
{filename}
</a>
</div>
<div class="actions">
{#if deletedFileIndexes.includes(i)}
<button
type="button"
class="btn btn-sm btn-danger btn-transparent"
on:click={() => restoreExistingFile(i)}
>
<span class="txt">Restore</span>
</button>
{:else}
<button
type="button"
class="btn btn-transparent btn-hint btn-sm btn-circle btn-remove"
use:tooltip={"Remove file"}
on:click={() => removeExistingFile(i)}
>
<i class="ri-close-line" />
</button>
{/if}
</div>
</div>
{/each}
@@ -125,7 +134,7 @@
</div>
<button
type="button"
class="btn btn-secondary btn-sm btn-circle btn-remove"
class="btn btn-transparent btn-sm btn-circle btn-remove"
use:tooltip={"Remove file"}
on:click={() => removeNewFile(i)}
>
@@ -135,7 +144,7 @@
{/each}
{#if !maxReached}
<div class="list-item btn-list-item">
<div class="list-item list-item-btn">
<input
bind:this={fileInput}
type="file"
@@ -151,7 +160,7 @@
/>
<button
type="button"
class="btn btn-secondary btn-sm btn-block"
class="btn btn-transparent btn-sm btn-block"
on:click={() => fileInput?.click()}
>
<i class="ri-upload-cloud-line" />
@@ -1,37 +1,145 @@
<script>
import { SchemaField } from "pocketbase";
import CommonHelper from "@/utils/CommonHelper";
import RecordSelect from "@/components/records/RecordSelect.svelte";
import ApiClient from "@/utils/ApiClient";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import RecordsPicker from "@/components/records/RecordsPicker.svelte";
import RecordInfo from "@/components/records/RecordInfo.svelte";
const batchSize = 100;
export let value;
export let picker;
export let field = new SchemaField();
export let value = undefined;
let list = [];
let isLoading = false;
$: isMultiple = field.options?.maxSelect != 1;
$: if (
isMultiple &&
Array.isArray(value) &&
field.options?.maxSelect &&
value.length > field.options.maxSelect
) {
value = value.slice(field.options.maxSelect - 1);
load();
async function load() {
const ids = CommonHelper.toArray(value);
if (!field?.options?.collectionId || !ids.length) {
list = [];
isLoading = false;
return;
}
isLoading = true;
// batch load all selected records to avoid parser stack overflow errors
const filterIds = ids.slice();
const loadPromises = [];
while (filterIds.length > 0) {
const filters = [];
for (const id of filterIds.splice(0, batchSize)) {
filters.push(`id="${id}"`);
}
loadPromises.push(
ApiClient.collection(field?.options?.collectionId).getFullList(batchSize, {
filter: filters.join("||"),
$autoCancel: false,
})
);
}
try {
let loadedItems = [];
await Promise.all(loadPromises).then((values) => {
loadedItems = loadedItems.concat(...values);
});
// preserve selected order
for (const id of ids) {
const rel = CommonHelper.findByKey(loadedItems, "id", id);
if (rel) {
list.push(rel);
}
}
list = list;
} catch (err) {
ApiClient.errorResponseHandler(err);
}
isLoading = false;
}
function remove(rel) {
CommonHelper.removeByKey(list, "id", rel.id);
list = list;
if (isMultiple) {
value = list.map((r) => r.id);
} else {
value = list[0]?.id || "";
}
}
</script>
<Field class="form-field {field.required ? 'required' : ''}" name={field.name} let:uniqueId>
<Field class="form-field form-field-list {field.required ? 'required' : ''}" name={field.name} let:uniqueId>
<label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon(field.type)} />
<span class="txt">{field.name}</span>
</label>
<RecordSelect
toggle
id={uniqueId}
multiple={isMultiple}
collectionId={field.options?.collectionId}
bind:keyOfSelected={value}
/>
{#if field.options?.maxSelect > 1}
<div class="help-block">Select up to {field.options.maxSelect} items.</div>
{/if}
<div class="list">
<div class="relations-list">
{#each list as record}
<div class="list-item">
<div class="content">
<RecordInfo {record} displayFields={field.options?.displayFields} />
</div>
<div class="actions">
<button
type="button"
class="btn btn-transparent btn-hint btn-sm btn-circle btn-remove"
use:tooltip={"Remove"}
on:click={() => remove(record)}
>
<i class="ri-close-line" />
</button>
</div>
</div>
{:else}
{#if isLoading}
{#each CommonHelper.toArray(value).slice(0, 10) as _}
<div class="list-item">
<div class="skeleton-loader" />
</div>
{/each}
{/if}
{/each}
</div>
<div class="list-item list-item-btn">
<button type="button" class="btn btn-transparent btn-sm btn-block" on:click={() => picker?.show()}>
<i class="ri-layout-line" />
<span class="txt">Open picker</span>
</button>
</div>
</div>
</Field>
<RecordsPicker
bind:this={picker}
{value}
{field}
on:save={(e) => {
list = e.detail || [];
value = isMultiple ? list.map((r) => r.id) : list[0] || "";
}}
/>
<style lang="scss">
.relations-list {
max-height: 300px;
overflow: auto; /* fallback */
overflow: overlay;
}
</style>