[#976] added optional RelationOptions.DisplayFields and refactored the relation picker UI
This commit is contained in:
@@ -154,7 +154,7 @@
|
||||
|
||||
<!-- visible only on small screens -->
|
||||
<svelte:fragment slot="footer">
|
||||
<button type="button" class="btn btn-secondary" on:click={() => hide()}>
|
||||
<button type="button" class="btn btn-transparent" on:click={() => hide()}>
|
||||
<span class="txt">Close</span>
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script>
|
||||
import { SchemaField } from "pocketbase";
|
||||
import { Collection, SchemaField } from "pocketbase";
|
||||
import FieldAccordion from "@/components/collections/FieldAccordion.svelte";
|
||||
|
||||
export let collection = {};
|
||||
export let collection = new Collection();
|
||||
|
||||
const baseReservedNames = [
|
||||
"id",
|
||||
@@ -36,8 +36,8 @@
|
||||
reservedNames = baseReservedNames.slice(0);
|
||||
}
|
||||
|
||||
$: if (typeof collection?.schema === "undefined") {
|
||||
collection = collection || {};
|
||||
$: if (typeof collection.schema === "undefined") {
|
||||
collection = collection || new Collection();
|
||||
collection.schema = [];
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-block {collection?.isAuth || collection.schema?.length ? 'btn-secondary' : 'btn-warning'}"
|
||||
class="btn btn-block {collection.schema.length ? 'btn-transparent' : 'btn-secondary'}"
|
||||
on:click={newField}
|
||||
>
|
||||
<i class="ri-add-line" />
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<button autofocus type="button" class="btn btn-secondary" on:click={() => hide()}>
|
||||
<button autofocus type="button" class="btn btn-transparent" on:click={() => hide()}>
|
||||
<span class="txt">Cancel</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-expanded" on:click={() => confirm()}>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import { errors, setErrors, removeError } from "@/stores/errors";
|
||||
import { confirm } from "@/stores/confirmation";
|
||||
import { addSuccessToast } from "@/stores/toasts";
|
||||
import { addCollection, removeCollection } from "@/stores/collections";
|
||||
import { removeAllToasts, addSuccessToast } from "@/stores/toasts";
|
||||
import { loadCollections, removeCollection } from "@/stores/collections";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
import Toggler from "@/components/base/Toggler.svelte";
|
||||
@@ -119,12 +119,16 @@
|
||||
|
||||
request
|
||||
.then((result) => {
|
||||
removeAllToasts();
|
||||
|
||||
loadCollections(result.id);
|
||||
|
||||
confirmClose = false;
|
||||
hide();
|
||||
|
||||
addSuccessToast(
|
||||
collection.isNew ? "Successfully created collection." : "Successfully updated collection."
|
||||
);
|
||||
addCollection(result);
|
||||
|
||||
dispatch("save", {
|
||||
isNew: collection.isNew,
|
||||
@@ -209,7 +213,7 @@
|
||||
|
||||
{#if !collection.isNew && !collection.system}
|
||||
<div class="flex-fill" />
|
||||
<button type="button" class="btn btn-sm btn-circle btn-secondary flex-gap-0">
|
||||
<button type="button" class="btn btn-sm btn-circle btn-transparent flex-gap-0">
|
||||
<i class="ri-more-line" />
|
||||
<Toggler class="dropdown dropdown-right m-t-5">
|
||||
<button
|
||||
@@ -256,7 +260,7 @@
|
||||
<div class="form-field-addon">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm p-r-10 p-l-10 {collection.isNew ? 'btn-hint' : 'btn-secondary'}"
|
||||
class="btn btn-sm p-r-10 p-l-10 {collection.isNew ? 'btn-hint' : 'btn-transparent'}"
|
||||
disabled={!collection.isNew}
|
||||
>
|
||||
<!-- empty span for alignment -->
|
||||
@@ -362,7 +366,7 @@
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<button type="button" class="btn btn-secondary" disabled={isSaving} on:click={() => hide()}>
|
||||
<button type="button" class="btn btn-transparent" disabled={isSaving} on:click={() => hide()}>
|
||||
<span class="txt">Cancel</span>
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { link } from "svelte-spa-router";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import { hideControls } from "@/stores/app";
|
||||
import { collections, activeCollection } from "@/stores/collections";
|
||||
import { collections, activeCollection, isCollectionsLoading } from "@/stores/collections";
|
||||
import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte";
|
||||
|
||||
let collectionPanel;
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="form-field-addon">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs btn-secondary btn-circle btn-clear"
|
||||
class="btn btn-xs btn-transparent btn-circle btn-clear"
|
||||
class:hidden={!hasSearch}
|
||||
on:click={() => (searchTerm = "")}
|
||||
>
|
||||
@@ -56,7 +56,11 @@
|
||||
|
||||
<hr class="m-t-5 m-b-xs" />
|
||||
|
||||
<div class="sidebar-content" class:sidebar-content-compact={filteredCollections.length > 20}>
|
||||
<div
|
||||
class="sidebar-content"
|
||||
class:fade={$isCollectionsLoading}
|
||||
class:sidebar-content-compact={filteredCollections.length > 20}
|
||||
>
|
||||
{#each filteredCollections as collection (collection.id)}
|
||||
<a
|
||||
href="/collections?collectionId={collection.id}"
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
{#if field.toDelete}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-danger btn-secondary"
|
||||
class="btn btn-sm btn-danger btn-transparent"
|
||||
on:click|stopPropagation={() => {
|
||||
field.toDelete = false;
|
||||
}}
|
||||
@@ -311,7 +311,7 @@
|
||||
<div class="col-sm-4 txt-right">
|
||||
<div class="flex-fill" />
|
||||
<div class="inline-flex flex-gap-sm flex-nowrap">
|
||||
<button type="button" class="btn btn-circle btn-sm btn-secondary">
|
||||
<button type="button" class="btn btn-circle btn-sm btn-transparent">
|
||||
<i class="ri-more-line" />
|
||||
<Toggler
|
||||
class="dropdown dropdown-sm dropdown-upside dropdown-right dropdown-nowrap no-min-width"
|
||||
|
||||
@@ -65,14 +65,18 @@
|
||||
{#if isAdminOnly}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-secondary btn-success lock-toggle"
|
||||
class="btn btn-sm btn-transparent btn-success lock-toggle"
|
||||
on:click={unlock}
|
||||
>
|
||||
<i class="ri-lock-unlock-line" />
|
||||
<span class="txt">Set custom rule</span>
|
||||
</button>
|
||||
{:else}
|
||||
<button type="button" class="btn btn-sm btn-secondary btn-hint lock-toggle" on:click={lock}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-transparent btn-hint lock-toggle"
|
||||
on:click={lock}
|
||||
>
|
||||
<i class="ri-lock-line" />
|
||||
<span class="txt">Set Admins only</span>
|
||||
</button>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<button class="btn btn-sm btn-secondary m-t-5" on:click={toggle}>
|
||||
<button class="btn btn-sm btn-transparent m-t-5" on:click={toggle}>
|
||||
{#if expanded}
|
||||
<span class="txt">Hide details</span>
|
||||
<i class="ri-arrow-up-s-line" />
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script>
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
import Select from "@/components/base/Select.svelte";
|
||||
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
|
||||
import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte";
|
||||
import { collections } from "@/stores/collections";
|
||||
|
||||
export let key = "";
|
||||
export let options = {};
|
||||
@@ -14,9 +15,13 @@
|
||||
{ label: "True", value: true },
|
||||
];
|
||||
|
||||
let isLoading = false;
|
||||
let collections = [];
|
||||
const baseFields = ["id", "created", "updated"];
|
||||
|
||||
const authFields = ["username", "email", "emailVisibility", "verified"];
|
||||
|
||||
let upsertPanel = null;
|
||||
let displayFieldsList = [];
|
||||
let oldCollectionId = null;
|
||||
|
||||
// load defaults
|
||||
$: if (CommonHelper.isEmpty(options)) {
|
||||
@@ -24,27 +29,39 @@
|
||||
maxSelect: 1,
|
||||
collectionId: null,
|
||||
cascadeDelete: false,
|
||||
displayFields: [],
|
||||
};
|
||||
}
|
||||
|
||||
$: selectedColection = collections.find((c) => c.id == options.collectionId) || null;
|
||||
$: selectedColection = $collections.find((c) => c.id == options.collectionId) || null;
|
||||
|
||||
loadCollections();
|
||||
$: if (oldCollectionId != options.collectionId) {
|
||||
oldCollectionId = options.collectionId;
|
||||
refreshDisplayFieldsList();
|
||||
}
|
||||
|
||||
async function loadCollections() {
|
||||
isLoading = true;
|
||||
|
||||
try {
|
||||
const result = await ApiClient.collections.getFullList(200, {
|
||||
sort: "created",
|
||||
});
|
||||
|
||||
collections = CommonHelper.sortCollections(result);
|
||||
} catch (err) {
|
||||
ApiClient.errorResponseHandler(err);
|
||||
function refreshDisplayFieldsList() {
|
||||
displayFieldsList = baseFields.slice(0);
|
||||
if (!selectedColection) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
if (selectedColection.isAuth) {
|
||||
displayFieldsList = displayFieldsList.concat(authFields);
|
||||
}
|
||||
|
||||
for (const field of selectedColection.schema) {
|
||||
displayFieldsList.push(field.name);
|
||||
}
|
||||
|
||||
// deselect any missing display field
|
||||
if (options?.displayFields?.length > 0) {
|
||||
for (let i = options.displayFields.length - 1; i >= 0; i--) {
|
||||
if (!displayFieldsList.includes(options.displayFields[i])) {
|
||||
options.displayFields.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -53,19 +70,21 @@
|
||||
<Field class="form-field required" name="schema.{key}.options.collectionId" let:uniqueId>
|
||||
<label for={uniqueId}>Collection</label>
|
||||
<ObjectSelect
|
||||
searchable={collections.length > 5}
|
||||
selectPlaceholder={isLoading ? "Loading..." : "Select collection"}
|
||||
searchable={$collections.length > 5}
|
||||
selectPlaceholder={"Select collection"}
|
||||
noOptionsText="No collections found"
|
||||
selectionKey="id"
|
||||
items={collections}
|
||||
items={$collections}
|
||||
bind:keyOfSelected={options.collectionId}
|
||||
>
|
||||
<svelte:fragment slot="afterOptions">
|
||||
<hr />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning btn-block btn-sm m-t-5"
|
||||
class="btn btn-transparent btn-block btn-sm"
|
||||
on:click={() => upsertPanel?.show()}
|
||||
>
|
||||
<i class="ri-add-line" />
|
||||
<span class="txt">New collection</span>
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
@@ -87,11 +106,30 @@
|
||||
<input type="number" id={uniqueId} step="1" min="1" bind:value={options.maxSelect} />
|
||||
</Field>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<Field class="form-field" name="schema.{key}.options.cascadeDelete" let:uniqueId>
|
||||
<div class="col-sm-9">
|
||||
<Field class="form-field" name="schema.{key}.options.displayFields" let:uniqueId>
|
||||
<label for={uniqueId}>
|
||||
Delete record on {selectedColection ? selectedColection.name : "relation"} delete
|
||||
<span class="txt">Display fields</span>
|
||||
<i
|
||||
class="ri-information-line link-hint"
|
||||
use:tooltip={{
|
||||
text: "Optional select the field(s) that will be used in the listings UI. Leave empty for auto.",
|
||||
position: "top",
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<Select
|
||||
multiple
|
||||
searchable
|
||||
id={uniqueId}
|
||||
items={displayFieldsList}
|
||||
bind:selected={options.displayFields}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<Field class="form-field" name="schema.{key}.options.cascadeDelete" let:uniqueId>
|
||||
<label for={uniqueId}>Cascade delete</label>
|
||||
<ObjectSelect id={uniqueId} items={defaultOptions} bind:keyOfSelected={options.cascadeDelete} />
|
||||
</Field>
|
||||
</div>
|
||||
@@ -103,6 +141,5 @@
|
||||
if (e?.detail?.collection?.id) {
|
||||
options.collectionId = e.detail.collection.id;
|
||||
}
|
||||
loadCollections();
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user