@lobb-js/studio 0.42.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/confirmationDialog/confirmationDialog.svelte +1 -1
- package/dist/components/dataTable/dataTable.svelte +49 -17
- package/dist/components/dataTable/filter.svelte +26 -23
- package/dist/components/dataTable/header.svelte +17 -13
- package/dist/components/dataTable/header.svelte.d.ts +1 -0
- package/dist/components/dataTable/listViewChildren.svelte +60 -77
- package/dist/components/dataTable/listViewChildren.svelte.d.ts +1 -1
- package/dist/components/dataTable/table.svelte +20 -61
- package/dist/components/dataTable/table.svelte.d.ts +2 -2
- package/dist/components/detailView/changeTreeUtils.d.ts +7 -0
- package/dist/components/detailView/changeTreeUtils.js +47 -0
- package/dist/components/detailView/create/createDetailView.svelte +17 -13
- package/dist/components/detailView/detailView.svelte +7 -2
- package/dist/components/detailView/detailView.svelte.d.ts +1 -0
- package/dist/components/detailView/fieldInput.svelte +10 -9
- package/dist/components/detailView/fieldInput.svelte.d.ts +1 -0
- package/dist/components/detailView/update/updateDetailView.svelte +22 -18
- package/dist/components/drawer.svelte +15 -2
- package/dist/components/foreingKeyInput.svelte +32 -60
- package/dist/components/foreingKeyInput.svelte.d.ts +1 -1
- package/dist/components/polymorphicInput.svelte +42 -66
- package/dist/components/polymorphicInput.svelte.d.ts +1 -0
- package/package.json +2 -2
- package/src/app.css +2 -2
- package/src/lib/components/confirmationDialog/confirmationDialog.svelte +1 -1
- package/src/lib/components/dataTable/dataTable.svelte +49 -17
- package/src/lib/components/dataTable/filter.svelte +26 -23
- package/src/lib/components/dataTable/header.svelte +17 -13
- package/src/lib/components/dataTable/listViewChildren.svelte +60 -77
- package/src/lib/components/dataTable/table.svelte +20 -61
- package/src/lib/components/detailView/changeTreeUtils.ts +39 -0
- package/src/lib/components/detailView/create/createDetailView.svelte +17 -13
- package/src/lib/components/detailView/detailView.svelte +7 -2
- package/src/lib/components/detailView/fieldInput.svelte +10 -9
- package/src/lib/components/detailView/update/updateDetailView.svelte +22 -18
- package/src/lib/components/drawer.svelte +15 -2
- package/src/lib/components/foreingKeyInput.svelte +32 -60
- package/src/lib/components/polymorphicInput.svelte +42 -66
|
@@ -4,16 +4,13 @@
|
|
|
4
4
|
import DataTable from "./dataTable/dataTable.svelte";
|
|
5
5
|
import Drawer from "./drawer.svelte";
|
|
6
6
|
import * as Popover from "./ui/popover/index";
|
|
7
|
-
import { getCollectionPrimaryField } from "./dataTable/utils";
|
|
8
|
-
import { getStudioContext } from "../context";
|
|
9
7
|
import { ArrowLeft, Link, ChevronDown, Plus } from "lucide-svelte";
|
|
10
8
|
import CreateDetailView from "./detailView/create/createDetailView.svelte";
|
|
11
9
|
|
|
12
|
-
const { ctx, lobb } = getStudioContext();
|
|
13
|
-
|
|
14
10
|
interface Props {
|
|
15
11
|
collectionField: string;
|
|
16
12
|
idField: string;
|
|
13
|
+
virtualField: string;
|
|
17
14
|
targetCollections: string[];
|
|
18
15
|
entry: Record<string, any>;
|
|
19
16
|
destructive?: boolean;
|
|
@@ -22,64 +19,68 @@
|
|
|
22
19
|
let {
|
|
23
20
|
collectionField,
|
|
24
21
|
idField,
|
|
22
|
+
virtualField,
|
|
25
23
|
targetCollections,
|
|
26
24
|
entry = $bindable(),
|
|
27
25
|
destructive,
|
|
28
26
|
}: Props = $props();
|
|
29
27
|
|
|
30
28
|
const selectedCollection = $derived(entry[collectionField] ?? null);
|
|
31
|
-
const selectedId
|
|
29
|
+
const selectedId = $derived(entry[idField] ?? null);
|
|
30
|
+
const virtualVal = $derived(entry[virtualField] ?? null);
|
|
31
|
+
|
|
32
|
+
let initialId = $state<number | null | undefined>(undefined);
|
|
33
|
+
let initialCollection = $state<string | null | undefined>(undefined);
|
|
34
|
+
onMount(() => {
|
|
35
|
+
initialId = entry[idField] ?? null;
|
|
36
|
+
initialCollection = entry[collectionField] ?? null;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const isPendingCreate = $derived(virtualVal && typeof virtualVal === 'object' && virtualVal.create);
|
|
40
|
+
const isChanged = $derived(
|
|
41
|
+
initialId !== undefined &&
|
|
42
|
+
!isPendingCreate &&
|
|
43
|
+
(selectedId !== initialId || selectedCollection !== initialCollection)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const bgClass = $derived(
|
|
47
|
+
isPendingCreate ? '!bg-green-500/5 border-green-500/40' :
|
|
48
|
+
isChanged ? '!bg-orange-500/5' :
|
|
49
|
+
''
|
|
50
|
+
);
|
|
32
51
|
|
|
33
|
-
let displayName = $state<string | null>(null);
|
|
34
52
|
let collectionPopoverOpen = $state(false);
|
|
35
53
|
let recordDrawerOpen = $state(false);
|
|
36
54
|
let createDrawerOpen = $state(false);
|
|
37
55
|
|
|
38
|
-
onMount(async () => {
|
|
39
|
-
if (selectedCollection == null || selectedId == null) return;
|
|
40
|
-
try {
|
|
41
|
-
const res = await lobb.findOne(selectedCollection, selectedId);
|
|
42
|
-
const record = await res.json();
|
|
43
|
-
const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection);
|
|
44
|
-
displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
|
|
45
|
-
} catch {
|
|
46
|
-
displayName = null;
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
$effect(() => {
|
|
51
|
-
if (entry[idField] == null) displayName = null;
|
|
52
|
-
});
|
|
53
|
-
|
|
54
56
|
function onCollectionChange(col: string) {
|
|
55
57
|
collectionPopoverOpen = false;
|
|
56
58
|
if (entry[collectionField] !== col) {
|
|
57
|
-
entry = { ...entry, [collectionField]: col, [idField]: null };
|
|
59
|
+
entry = { ...entry, [collectionField]: col, [idField]: null, [virtualField]: undefined };
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
function onIdChange(e: Event) {
|
|
62
64
|
const raw = (e.target as HTMLInputElement).value;
|
|
63
65
|
const id = raw === "" ? null : Number(raw);
|
|
64
|
-
entry = { ...entry, [idField]: id };
|
|
66
|
+
entry = { ...entry, [idField]: id, [virtualField]: undefined };
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
function onRecordSelect(record: any) {
|
|
68
|
-
|
|
69
|
-
entry = { ...entry, [idField]: record.id };
|
|
70
|
-
displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
|
|
70
|
+
entry = { ...entry, [idField]: record.id, [virtualField]: undefined };
|
|
71
71
|
recordDrawerOpen = false;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
async function onPolyCreated(record: any) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
if (!record.id) {
|
|
76
|
+
entry = { ...entry, [virtualField]: { collection: selectedCollection, create: record }, [collectionField]: selectedCollection, [idField]: null };
|
|
77
|
+
} else {
|
|
78
|
+
entry = { ...entry, [idField]: record.id, [virtualField]: undefined };
|
|
79
|
+
}
|
|
78
80
|
}
|
|
79
81
|
</script>
|
|
80
82
|
|
|
81
|
-
<div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted {destructive ? '
|
|
82
|
-
<!-- Collection picker -->
|
|
83
|
+
<div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted {bgClass} {destructive ? '!bg-destructive/10 border-destructive' : ''}">
|
|
83
84
|
<Popover.Root bind:open={collectionPopoverOpen}>
|
|
84
85
|
<Popover.Trigger>
|
|
85
86
|
{#snippet child({ props })}
|
|
@@ -107,39 +108,22 @@
|
|
|
107
108
|
</Popover.Content>
|
|
108
109
|
</Popover.Root>
|
|
109
110
|
|
|
110
|
-
<!-- Transparent id input -->
|
|
111
111
|
<input
|
|
112
|
-
placeholder="NULL"
|
|
112
|
+
placeholder={isPendingCreate ? "AUTO GENERATED" : "NULL"}
|
|
113
113
|
type="number"
|
|
114
114
|
class="min-w-0 flex-1 bg-transparent outline-none text-xs placeholder:text-muted-foreground"
|
|
115
115
|
value={selectedId ?? ""}
|
|
116
116
|
oninput={onIdChange}
|
|
117
117
|
/>
|
|
118
118
|
|
|
119
|
-
<!-- Primary field badge -->
|
|
120
|
-
{#if displayName}
|
|
121
|
-
<div class="flex shrink-0 items-center bg-background rounded-full border h-6 px-3 shadow-sm">
|
|
122
|
-
{displayName}
|
|
123
|
-
</div>
|
|
124
|
-
{/if}
|
|
125
|
-
|
|
126
|
-
<!-- Create / Select record buttons -->
|
|
127
119
|
{#if selectedCollection}
|
|
128
|
-
<Button
|
|
129
|
-
class="h-6 shrink-0 px-2 font-normal text-xs"
|
|
130
|
-
variant="outline"
|
|
131
|
-
onclick={() => (createDrawerOpen = true)}
|
|
132
|
-
>
|
|
120
|
+
<Button class="h-6 shrink-0 px-2 font-normal text-xs" variant="outline" onclick={() => (createDrawerOpen = true)}>
|
|
133
121
|
<Plus size="13" />
|
|
134
122
|
Create
|
|
135
123
|
</Button>
|
|
136
|
-
<Button
|
|
137
|
-
class="h-6 shrink-0 px-2 font-normal text-xs"
|
|
138
|
-
variant="outline"
|
|
139
|
-
onclick={() => (recordDrawerOpen = true)}
|
|
140
|
-
>
|
|
124
|
+
<Button class="h-6 shrink-0 px-2 font-normal text-xs" variant="outline" onclick={() => (recordDrawerOpen = true)}>
|
|
141
125
|
<Link size="13" />
|
|
142
|
-
|
|
126
|
+
Link
|
|
143
127
|
</Button>
|
|
144
128
|
{/if}
|
|
145
129
|
</div>
|
|
@@ -147,26 +131,17 @@
|
|
|
147
131
|
{#if recordDrawerOpen}
|
|
148
132
|
<Drawer onHide={async () => { recordDrawerOpen = false }}>
|
|
149
133
|
<div class="flex h-12 items-center gap-4 border-b px-4">
|
|
150
|
-
<Button
|
|
151
|
-
variant="outline"
|
|
152
|
-
onclick={() => (recordDrawerOpen = false)}
|
|
153
|
-
class="h-8 w-8 rounded-full text-xs font-normal"
|
|
154
|
-
Icon={ArrowLeft}
|
|
155
|
-
/>
|
|
134
|
+
<Button variant="outline" onclick={() => (recordDrawerOpen = false)} class="h-8 w-8 rounded-full text-xs font-normal" Icon={ArrowLeft} />
|
|
156
135
|
<div class="flex items-center gap-2">
|
|
157
136
|
<div class="text-sm">Select record from</div>
|
|
158
|
-
<span class="rounded-md border bg-muted px-2 py-0.5 text-sm">
|
|
159
|
-
{selectedCollection}
|
|
160
|
-
</span>
|
|
137
|
+
<span class="rounded-md border bg-muted px-2 py-0.5 text-sm">{selectedCollection}</span>
|
|
161
138
|
</div>
|
|
162
139
|
</div>
|
|
163
140
|
<div class="flex-1 overflow-y-auto bg-muted">
|
|
164
141
|
<DataTable
|
|
165
142
|
collectionName={selectedCollection!}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
select: { onSelect: onRecordSelect },
|
|
169
|
-
}}
|
|
143
|
+
filter={selectedId != null ? { id: { $ne: selectedId } } : undefined}
|
|
144
|
+
tableProps={{ showCheckboxes: false, select: { onSelect: onRecordSelect } }}
|
|
170
145
|
/>
|
|
171
146
|
</div>
|
|
172
147
|
</Drawer>
|
|
@@ -176,6 +151,7 @@
|
|
|
176
151
|
<CreateDetailView
|
|
177
152
|
collectionName={selectedCollection}
|
|
178
153
|
onCreated={onPolyCreated}
|
|
154
|
+
onChanges={() => {}}
|
|
179
155
|
onCancel={async () => { createDrawerOpen = false; }}
|
|
180
156
|
/>
|
|
181
157
|
{/if}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/studio",
|
|
3
3
|
"license": "UNLICENSED",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.44.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"postpublish": "./scripts/postpublish.sh"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@lobb-js/core": "^0.
|
|
48
|
+
"@lobb-js/core": "^0.38.0",
|
|
49
49
|
"@chromatic-com/storybook": "^4.1.2",
|
|
50
50
|
"@playwright/test": "^1.60.0",
|
|
51
51
|
"@storybook/addon-a11y": "^10.0.1",
|
package/src/app.css
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
--secondary: oklch(0.97 0.001 138);
|
|
25
25
|
--secondary-foreground: oklch(0.20 0.012 138);
|
|
26
26
|
|
|
27
|
-
--muted: oklch(0.
|
|
27
|
+
--muted: oklch(0.98 0.001 138);
|
|
28
28
|
--muted-foreground: oklch(0.55 0.012 138);
|
|
29
29
|
|
|
30
30
|
--accent: oklch(0.95 0.001 138);
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
--secondary: oklch(0.24 0.012 138);
|
|
53
53
|
--secondary-foreground: oklch(0.94 0.012 138);
|
|
54
54
|
|
|
55
|
-
--muted: oklch(0.
|
|
55
|
+
--muted: oklch(0.28 0.012 138);
|
|
56
56
|
--muted-foreground: oklch(0.65 0.012 138);
|
|
57
57
|
|
|
58
58
|
--accent: oklch(0.27 0.012 138);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<AlertDialog.Content>
|
|
18
18
|
<AlertDialog.Header>
|
|
19
19
|
<AlertDialog.Title>{title}</AlertDialog.Title>
|
|
20
|
-
<AlertDialog.Description class="whitespace-pre-
|
|
20
|
+
<AlertDialog.Description class="whitespace-pre-wrap font-mono text-xs">
|
|
21
21
|
{description}
|
|
22
22
|
</AlertDialog.Description>
|
|
23
23
|
</AlertDialog.Header>
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
import Table, { type TableProps } from "./table.svelte";
|
|
15
15
|
import { getCollectionColumns, getCollectionParamsFields } from "./utils";
|
|
16
16
|
import CanAccess from "../canAccess.svelte";
|
|
17
|
-
import { Pencil, Trash, Unlink, RotateCcw } from "lucide-svelte";
|
|
17
|
+
import { Pencil, Trash, Unlink, RotateCcw, Network } from "lucide-svelte";
|
|
18
18
|
import ListViewChildren from "./listViewChildren.svelte";
|
|
19
|
+
import Drawer from "../drawer.svelte";
|
|
20
|
+
import { ArrowLeft } from "lucide-svelte";
|
|
19
21
|
import FieldCell from "./fieldCell.svelte";
|
|
20
22
|
import Skeleton from "../ui/skeleton/skeleton.svelte";
|
|
21
23
|
import Button from "../ui/button/button.svelte";
|
|
@@ -73,6 +75,7 @@
|
|
|
73
75
|
}: Props = $props();
|
|
74
76
|
|
|
75
77
|
const isRecordingMode = onChanges !== undefined;
|
|
78
|
+
const isSelectMode = $derived(tableProps?.select != null);
|
|
76
79
|
let localChanges = $state<ChildrenChanges>(
|
|
77
80
|
untrack(() => changes) ?? { created: [], updated: [], deleted: [], linked: [], unlinked: [] }
|
|
78
81
|
);
|
|
@@ -135,17 +138,17 @@
|
|
|
135
138
|
const state = entry._recordingState;
|
|
136
139
|
const border = cellIndex === 0 ? {
|
|
137
140
|
deleted: 'border-l-2 border-l-red-500',
|
|
138
|
-
unlinked: 'border-l-2 border-l-
|
|
141
|
+
unlinked: 'border-l-2 border-l-slate-500',
|
|
139
142
|
created: 'border-l-2 border-l-green-500',
|
|
140
143
|
linked: 'border-l-2 border-l-blue-500',
|
|
141
|
-
updated: 'border-l-2 border-l-
|
|
144
|
+
updated: 'border-l-2 border-l-orange-500',
|
|
142
145
|
}[state as string] ?? '' : '';
|
|
143
146
|
const bg: Record<string, string> = {
|
|
144
|
-
deleted: '!bg-red-500/5
|
|
145
|
-
unlinked: '!bg-
|
|
147
|
+
deleted: '!bg-red-500/5',
|
|
148
|
+
unlinked: '!bg-slate-500/5',
|
|
146
149
|
created: '!bg-green-500/5',
|
|
147
150
|
linked: '!bg-blue-500/5',
|
|
148
|
-
updated: '!bg-
|
|
151
|
+
updated: '!bg-orange-500/5',
|
|
149
152
|
};
|
|
150
153
|
return `${bg[state as string] ?? ''} ${border}`.trim();
|
|
151
154
|
}
|
|
@@ -220,6 +223,10 @@
|
|
|
220
223
|
(ctx.meta.collections[collectionName]?.children ?? [])
|
|
221
224
|
.some((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic")
|
|
222
225
|
);
|
|
226
|
+
// Select-mode drawer has no checkbox, edit, delete, or unlink — the
|
|
227
|
+
// left-sticky tools column would render empty, so drop it entirely.
|
|
228
|
+
const showLeftTools = $derived(!isSelectMode);
|
|
229
|
+
let childrenDrawerEntry = $state<Record<string, any> | null>(null);
|
|
223
230
|
|
|
224
231
|
// requests the data from the server when the params is changed
|
|
225
232
|
$effect(() => {
|
|
@@ -404,6 +411,7 @@
|
|
|
404
411
|
bind:selectedRecords
|
|
405
412
|
{showImport}
|
|
406
413
|
{showFilter}
|
|
414
|
+
showCreate={!isSelectMode}
|
|
407
415
|
{loading}
|
|
408
416
|
{parentContext}
|
|
409
417
|
onLink={isRecordingMode ? handleLink : undefined}
|
|
@@ -453,7 +461,6 @@
|
|
|
453
461
|
<Table
|
|
454
462
|
{data}
|
|
455
463
|
{columns}
|
|
456
|
-
showCollapsible={doesCollectionHasChildren}
|
|
457
464
|
selectByColumn="id"
|
|
458
465
|
showLastRowBorder={true}
|
|
459
466
|
showLastColumnBorder={true}
|
|
@@ -461,11 +468,25 @@
|
|
|
461
468
|
bind:selectedRecords
|
|
462
469
|
bind:tableWidth={dataTableWidth}
|
|
463
470
|
{...tableProps}
|
|
471
|
+
{showLeftTools}
|
|
464
472
|
rowActions={hasRowActions ? rowActionsSnippet : undefined}
|
|
465
473
|
onCellClass={isRecordingMode ? onCellClass : undefined}>
|
|
474
|
+
{#snippet preTools(entry)}
|
|
475
|
+
{#if doesCollectionHasChildren}
|
|
476
|
+
<Button
|
|
477
|
+
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
478
|
+
variant="ghost"
|
|
479
|
+
size="icon"
|
|
480
|
+
onclick={() => { childrenDrawerEntry = entry; }}
|
|
481
|
+
Icon={Network}
|
|
482
|
+
title="Show children"
|
|
483
|
+
aria-label={`Show children of record ${entry.id}`}
|
|
484
|
+
></Button>
|
|
485
|
+
{/if}
|
|
486
|
+
{/snippet}
|
|
466
487
|
{#snippet tools(entry)}
|
|
467
488
|
{#if entry._recordingState !== 'deleted' && entry._recordingState !== 'unlinked'}
|
|
468
|
-
{#if showEdit}
|
|
489
|
+
{#if showEdit && !isSelectMode}
|
|
469
490
|
{@const isPending = entry._recordingState === 'created'}
|
|
470
491
|
<CanAccess collection={collectionName} action="update">
|
|
471
492
|
<UpdateDetailViewButton
|
|
@@ -524,15 +545,6 @@
|
|
|
524
545
|
refresh={() => { params = { ...params }; }}
|
|
525
546
|
/>
|
|
526
547
|
{/snippet}
|
|
527
|
-
{#snippet collapsible(entry)}
|
|
528
|
-
<ListViewChildren
|
|
529
|
-
{collectionName}
|
|
530
|
-
recordId={entry.id}
|
|
531
|
-
width={dataTableWidth > dataTableContainerWidth
|
|
532
|
-
? dataTableContainerWidth
|
|
533
|
-
: dataTableWidth}
|
|
534
|
-
/>
|
|
535
|
-
{/snippet}
|
|
536
548
|
</Table>
|
|
537
549
|
{/if}
|
|
538
550
|
</div>
|
|
@@ -546,3 +558,23 @@
|
|
|
546
558
|
/>
|
|
547
559
|
{/if}
|
|
548
560
|
</div>
|
|
561
|
+
|
|
562
|
+
{#if childrenDrawerEntry}
|
|
563
|
+
<Drawer position="bottom" onHide={async () => { childrenDrawerEntry = null; }}>
|
|
564
|
+
<div class="flex h-12 items-center gap-4 border-b px-4 shrink-0">
|
|
565
|
+
<Button
|
|
566
|
+
variant="outline"
|
|
567
|
+
onclick={() => { childrenDrawerEntry = null; }}
|
|
568
|
+
class="h-8 w-8 rounded-full text-xs font-normal"
|
|
569
|
+
Icon={ArrowLeft}
|
|
570
|
+
></Button>
|
|
571
|
+
<div class="flex items-center gap-2 text-sm">
|
|
572
|
+
<span>Children of</span>
|
|
573
|
+
<span class="rounded-md border bg-muted px-2 py-0.5">{collectionName} #{childrenDrawerEntry.id}</span>
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
<div class="flex-1 overflow-y-auto">
|
|
577
|
+
<ListViewChildren {collectionName} recordId={String(childrenDrawerEntry.id)} />
|
|
578
|
+
</div>
|
|
579
|
+
</Drawer>
|
|
580
|
+
{/if}
|
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
// • anything else → explicit $and/$or arrays.
|
|
21
21
|
|
|
22
22
|
import * as Select from "../ui/select/index.js";
|
|
23
|
+
import * as Popover from "../ui/popover/index.js";
|
|
23
24
|
import Button from "../ui/button/button.svelte";
|
|
24
|
-
import { Plus, X, Boxes, Settings2 } from "lucide-svelte";
|
|
25
|
+
import { ChevronDown, Plus, X, Boxes, Settings2 } from "lucide-svelte";
|
|
25
26
|
import { getStudioContext } from "../../context";
|
|
26
27
|
import { getFieldIcon } from "./utils";
|
|
27
28
|
import { getFieldRelationTarget } from "../../relations";
|
|
29
|
+
import FieldPicker from "./fieldPicker.svelte";
|
|
28
30
|
|
|
29
31
|
const { ctx } = getStudioContext();
|
|
30
32
|
|
|
@@ -43,6 +45,9 @@
|
|
|
43
45
|
isEmpty = $bindable(true),
|
|
44
46
|
}: Props = $props();
|
|
45
47
|
|
|
48
|
+
// Per-rule open state for the field-picker popovers.
|
|
49
|
+
let fieldPickerOpen = $state<Record<string, boolean>>({});
|
|
50
|
+
|
|
46
51
|
type OperatorDef = {
|
|
47
52
|
value: string;
|
|
48
53
|
label: string;
|
|
@@ -471,30 +476,28 @@
|
|
|
471
476
|
{@const currentOp = ops.find((o) => o.value === rule.operator) ?? ops[0]}
|
|
472
477
|
{@const kind = getFieldKind(rule.field)}
|
|
473
478
|
{@const FieldIcon = getFieldIcon(ctx, rule.field, collectionName)}
|
|
474
|
-
<!-- Field picker -->
|
|
475
|
-
<
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
<Select.Trigger class="bg-muted h-7 w-36 text-xs">
|
|
481
|
-
<div class="inline-flex items-center gap-1.5">
|
|
479
|
+
<!-- Field picker — typeahead popover, same widget Sort uses -->
|
|
480
|
+
<Popover.Root bind:open={fieldPickerOpen[rule.id]}>
|
|
481
|
+
<Popover.Trigger
|
|
482
|
+
class="inline-flex h-7 w-36 items-center justify-between gap-1.5 rounded-md border bg-muted px-2 text-xs"
|
|
483
|
+
>
|
|
484
|
+
<div class="inline-flex items-center gap-1.5 truncate">
|
|
482
485
|
<FieldIcon size="13" />
|
|
483
|
-
{rule.field}
|
|
486
|
+
<span class="truncate">{rule.field}</span>
|
|
484
487
|
</div>
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
</
|
|
497
|
-
</
|
|
488
|
+
<ChevronDown size="13" class="text-muted-foreground shrink-0" />
|
|
489
|
+
</Popover.Trigger>
|
|
490
|
+
<Popover.Content class="w-64 p-2">
|
|
491
|
+
<FieldPicker
|
|
492
|
+
{collectionName}
|
|
493
|
+
placeholder="Pick a field…"
|
|
494
|
+
onPick={(fname: string) => {
|
|
495
|
+
patchRuleInPlace(rule.id, { field: fname });
|
|
496
|
+
fieldPickerOpen[rule.id] = false;
|
|
497
|
+
}}
|
|
498
|
+
/>
|
|
499
|
+
</Popover.Content>
|
|
500
|
+
</Popover.Root>
|
|
498
501
|
|
|
499
502
|
<!-- Operator picker -->
|
|
500
503
|
<Select.Root
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
onCreate?: (changes: Changes) => void;
|
|
27
27
|
showImport?: boolean;
|
|
28
28
|
showFilter?: boolean;
|
|
29
|
+
showCreate?: boolean;
|
|
29
30
|
loading?: boolean;
|
|
30
31
|
left?: Snippet<[]>;
|
|
31
32
|
excludeIds?: (string | number)[];
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
onCreate,
|
|
41
42
|
showImport = true,
|
|
42
43
|
showFilter = true,
|
|
44
|
+
showCreate = true,
|
|
43
45
|
loading = false,
|
|
44
46
|
left,
|
|
45
47
|
excludeIds = [],
|
|
@@ -154,7 +156,7 @@
|
|
|
154
156
|
{headerIsSmall ? "" : "Refresh"}
|
|
155
157
|
{/if}
|
|
156
158
|
</Button>
|
|
157
|
-
{#if showImport}
|
|
159
|
+
{#if showImport && showCreate}
|
|
158
160
|
<CanAccess collection={collectionName} action="create">
|
|
159
161
|
<ImportButton
|
|
160
162
|
{collectionName}
|
|
@@ -183,17 +185,19 @@
|
|
|
183
185
|
{headerIsSmall ? "" : "Link"}
|
|
184
186
|
</SelectRecord>
|
|
185
187
|
{/if}
|
|
186
|
-
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
{#if showCreate}
|
|
189
|
+
<CanAccess collection={collectionName} action="create">
|
|
190
|
+
<CreateDetailViewButton
|
|
191
|
+
{collectionName}
|
|
192
|
+
variant="default"
|
|
193
|
+
size="sm"
|
|
194
|
+
Icon={Plus}
|
|
195
|
+
onChanges={onCreate ? handleCreate : undefined}
|
|
196
|
+
onSuccessfullSave={onCreate ? undefined : handleCreate}
|
|
197
|
+
>
|
|
198
|
+
{headerIsSmall ? "" : "Create"}
|
|
199
|
+
</CreateDetailViewButton>
|
|
200
|
+
</CanAccess>
|
|
201
|
+
{/if}
|
|
198
202
|
</div>
|
|
199
203
|
</div>
|