@lobb-js/studio 0.26.0 → 0.27.1
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/dataTable/dataTable.svelte +70 -9
- package/dist/components/dataTable/dataTable.svelte.d.ts +24 -0
- package/dist/components/dataTable/header.svelte +88 -24
- package/dist/components/dataTable/header.svelte.d.ts +4 -0
- package/dist/components/dataTable/listViewChildren.svelte +10 -3
- package/dist/components/dataTable/table.svelte +1 -1
- package/dist/components/detailView/create/createDetailView.svelte +1 -6
- package/dist/components/detailView/update/detailViewChildren.svelte +50 -55
- package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +8 -1
- package/dist/components/detailView/update/updateDetailView.svelte +6 -6
- package/dist/components/detailView/update/updateDetailViewButton.svelte +2 -3
- package/dist/components/detailView/utils.d.ts +0 -2
- package/dist/components/detailView/utils.js +5 -81
- package/dist/components/foreingKeyInput.svelte +42 -17
- package/dist/components/foreingKeyInput.svelte.d.ts +1 -1
- package/dist/components/polymorphicInput.svelte +25 -9
- package/dist/utils.js +2 -1
- package/package.json +2 -2
- package/src/lib/components/dataTable/dataTable.svelte +70 -9
- package/src/lib/components/dataTable/header.svelte +88 -24
- package/src/lib/components/dataTable/listViewChildren.svelte +10 -3
- package/src/lib/components/dataTable/table.svelte +1 -1
- package/src/lib/components/detailView/create/createDetailView.svelte +1 -6
- package/src/lib/components/detailView/update/detailViewChildren.svelte +50 -55
- package/src/lib/components/detailView/update/updateDetailView.svelte +6 -6
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +2 -3
- package/src/lib/components/detailView/utils.ts +2 -64
- package/src/lib/components/foreingKeyInput.svelte +42 -17
- package/src/lib/components/polymorphicInput.svelte +25 -9
- package/src/lib/utils.ts +2 -1
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
var __assign = (this && this.__assign) || function () {
|
|
2
|
-
__assign = Object.assign || function(t) {
|
|
3
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
-
s = arguments[i];
|
|
5
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
-
t[p] = s[p];
|
|
7
|
-
}
|
|
8
|
-
return t;
|
|
9
|
-
};
|
|
10
|
-
return __assign.apply(this, arguments);
|
|
11
|
-
};
|
|
12
1
|
import Mustache from "mustache";
|
|
13
|
-
import { isRelationField } from "../../relations";
|
|
14
2
|
import { getField } from "../dataTable/utils";
|
|
15
3
|
export function getDefaultEntry(ctx, fieldNames, collectionName, values) {
|
|
16
4
|
return Object.fromEntries(fieldNames.map(function (fieldName) {
|
|
@@ -33,42 +21,17 @@ export function getDefaultEntry(ctx, fieldNames, collectionName, values) {
|
|
|
33
21
|
return [fieldName, value];
|
|
34
22
|
}));
|
|
35
23
|
}
|
|
36
|
-
export function serializeEntry(ctx, collectionName, entry) {
|
|
37
|
-
entry = __assign({}, entry);
|
|
38
|
-
// extract FK object fields → ID
|
|
39
|
-
for (var _i = 0, _a = Object.entries(entry); _i < _a.length; _i++) {
|
|
40
|
-
var _b = _a[_i], fieldName = _b[0], fieldValue = _b[1];
|
|
41
|
-
var isRefrenceField = isRelationField(ctx, collectionName, fieldName);
|
|
42
|
-
if (isRefrenceField && fieldValue !== null && fieldValue.id !== undefined) {
|
|
43
|
-
entry[fieldName] = fieldValue.id;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// extract polymorphic id_field objects → ID
|
|
47
|
-
for (var _c = 0, _d = ctx.meta.relations; _c < _d.length; _c++) {
|
|
48
|
-
var relation = _d[_c];
|
|
49
|
-
if (relation.type !== "polymorphic")
|
|
50
|
-
continue;
|
|
51
|
-
if (relation.from.collection !== collectionName)
|
|
52
|
-
continue;
|
|
53
|
-
var idField = relation.from.id_field;
|
|
54
|
-
var fieldValue = entry[idField];
|
|
55
|
-
if (fieldValue !== null && typeof fieldValue === "object" && fieldValue.id !== undefined) {
|
|
56
|
-
entry[idField] = fieldValue.id;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return entry;
|
|
60
|
-
}
|
|
61
24
|
export function buildChildren(ctx, collectionName, entry) {
|
|
62
25
|
var childrenRelations = ctx.meta.relations.filter(function (relation) { return relation.type !== "polymorphic" && relation.to.collection === collectionName; });
|
|
63
26
|
var children = {};
|
|
64
|
-
var
|
|
27
|
+
for (var _i = 0, childrenRelations_1 = childrenRelations; _i < childrenRelations_1.length; _i++) {
|
|
28
|
+
var relation = childrenRelations_1[_i];
|
|
65
29
|
var childCollection = relation.from.collection;
|
|
66
30
|
var childEntries = entry[childCollection];
|
|
67
31
|
if (!(childEntries === null || childEntries === void 0 ? void 0 : childEntries.length))
|
|
68
|
-
|
|
32
|
+
continue;
|
|
69
33
|
var toCreate = childEntries
|
|
70
|
-
.filter(function (e) { return !e.id; })
|
|
71
|
-
.map(function (e) { return serializeEntry(ctx, childCollection, e); });
|
|
34
|
+
.filter(function (e) { return !e.id; });
|
|
72
35
|
var toLink = childEntries
|
|
73
36
|
.filter(function (e) { return e.id; })
|
|
74
37
|
.map(function (e) { return e.id; });
|
|
@@ -79,54 +42,15 @@ export function buildChildren(ctx, collectionName, entry) {
|
|
|
79
42
|
if (toLink.length)
|
|
80
43
|
children[childCollection].link = toLink;
|
|
81
44
|
}
|
|
82
|
-
};
|
|
83
|
-
for (var _i = 0, childrenRelations_1 = childrenRelations; _i < childrenRelations_1.length; _i++) {
|
|
84
|
-
var relation = childrenRelations_1[_i];
|
|
85
|
-
_loop_1(relation);
|
|
86
45
|
}
|
|
87
46
|
return Object.keys(children).length ? children : undefined;
|
|
88
47
|
}
|
|
89
|
-
export function parseDetailViewValues(ctx, collectionName, values) {
|
|
90
|
-
var forignFieldNames = ctx.meta.relations
|
|
91
|
-
.filter(function (relation) { return relation.type !== "polymorphic" && relation.from.collection === collectionName; })
|
|
92
|
-
.map(function (relation) { return relation.from.field; });
|
|
93
|
-
var childCollectionNames = ctx.meta.relations
|
|
94
|
-
.filter(function (relation) { return relation.to.collection === collectionName; })
|
|
95
|
-
.map(function (relation) { return relation.from.collection; });
|
|
96
|
-
for (var _i = 0, _a = Object.entries(values); _i < _a.length; _i++) {
|
|
97
|
-
var _b = _a[_i], key = _b[0], value = _b[1];
|
|
98
|
-
if (forignFieldNames.includes(key)) {
|
|
99
|
-
if (typeof value === 'number') {
|
|
100
|
-
values[key] = {
|
|
101
|
-
id: value,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else if (childCollectionNames.includes(key)) {
|
|
106
|
-
for (var index = 0; index < values[key].length; index++) {
|
|
107
|
-
parseDetailViewValues(ctx, key, values[key][index]);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// wrap polymorphic id_field scalar values into { id: value }
|
|
112
|
-
for (var _c = 0, _d = ctx.meta.relations; _c < _d.length; _c++) {
|
|
113
|
-
var relation = _d[_c];
|
|
114
|
-
if (relation.type !== "polymorphic")
|
|
115
|
-
continue;
|
|
116
|
-
if (relation.from.collection !== collectionName)
|
|
117
|
-
continue;
|
|
118
|
-
var idField = relation.from.id_field;
|
|
119
|
-
if (idField in values && typeof values[idField] === 'number') {
|
|
120
|
-
values[idField] = { id: values[idField] };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
48
|
export function getCollectionFields(ctx, collectionName) {
|
|
125
49
|
var returnedData = [];
|
|
126
50
|
var collectionFields = ctx.meta.collections[collectionName].fields;
|
|
127
51
|
var isSingleton = ctx.meta.collections[collectionName].singleton;
|
|
128
52
|
for (var _i = 0, _a = Object.entries(collectionFields); _i < _a.length; _i++) {
|
|
129
|
-
var
|
|
53
|
+
var fieldName = _a[_i][0];
|
|
130
54
|
if (isSingleton && fieldName === "id") {
|
|
131
55
|
continue;
|
|
132
56
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
2
3
|
import Input from "./ui/input/input.svelte";
|
|
3
4
|
import SelectRecord from "./selectRecord.svelte";
|
|
4
5
|
import UpdateDetailViewButton from "./detailView/update/updateDetailViewButton.svelte";
|
|
6
|
+
import { getCollectionPrimaryField } from "./dataTable/utils";
|
|
7
|
+
import { getStudioContext } from "../context";
|
|
5
8
|
import { ExternalLink } from "lucide-svelte";
|
|
6
9
|
|
|
10
|
+
const { lobb, ctx } = getStudioContext();
|
|
11
|
+
|
|
7
12
|
interface LocalProps {
|
|
8
13
|
parentCollectionName: string;
|
|
9
14
|
collectionName: string;
|
|
10
15
|
fieldName: string;
|
|
11
|
-
value?:
|
|
16
|
+
value?: number | null;
|
|
12
17
|
destructive?: boolean;
|
|
13
18
|
entry: Record<string, any>;
|
|
14
19
|
}
|
|
@@ -22,31 +27,48 @@
|
|
|
22
27
|
entry,
|
|
23
28
|
}: LocalProps = $props();
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
let displayName = $state<string | null>(null);
|
|
31
|
+
|
|
32
|
+
onMount(async () => {
|
|
33
|
+
if (value == null) return;
|
|
34
|
+
try {
|
|
35
|
+
const res = await lobb.findOne(collectionName, value);
|
|
36
|
+
const record = (await res.json()).data;
|
|
37
|
+
const primaryFieldName = getCollectionPrimaryField(ctx, collectionName);
|
|
38
|
+
displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
|
|
39
|
+
} catch {
|
|
40
|
+
displayName = null;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
$effect(() => {
|
|
45
|
+
if (value == null) displayName = null;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function handleSelect(selectedEntry: any) {
|
|
49
|
+
const primaryFieldName = getCollectionPrimaryField(ctx, collectionName);
|
|
50
|
+
value = selectedEntry.id;
|
|
51
|
+
displayName = primaryFieldName ? String(selectedEntry[primaryFieldName]) : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const idIsZero = $derived(value === 0);
|
|
28
55
|
</script>
|
|
29
56
|
|
|
30
|
-
<!-- THE SELECT BUTTON -->
|
|
31
57
|
{#if !idIsZero}
|
|
32
58
|
<div class="relative">
|
|
33
|
-
<div
|
|
34
|
-
|
|
35
|
-
>
|
|
36
|
-
{#if value !== null}
|
|
59
|
+
<div class="flex gap-2 absolute right-0 top-0 mr-9 h-full items-center text-xs">
|
|
60
|
+
{#if value != null}
|
|
37
61
|
<UpdateDetailViewButton
|
|
38
62
|
collectionName={collectionName}
|
|
39
|
-
recordId={value
|
|
63
|
+
recordId={value}
|
|
40
64
|
variant="ghost"
|
|
41
65
|
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
|
|
42
66
|
Icon={ExternalLink}
|
|
43
67
|
></UpdateDetailViewButton>
|
|
44
68
|
{/if}
|
|
45
|
-
{#if
|
|
46
|
-
<div
|
|
47
|
-
|
|
48
|
-
>
|
|
49
|
-
{primaryField}
|
|
69
|
+
{#if displayName}
|
|
70
|
+
<div class="flex items-center bg-background rounded-full border h-6 px-3 shadow-sm">
|
|
71
|
+
{displayName}
|
|
50
72
|
</div>
|
|
51
73
|
{/if}
|
|
52
74
|
<SelectRecord
|
|
@@ -55,7 +77,7 @@
|
|
|
55
77
|
{parentCollectionName}
|
|
56
78
|
{collectionName}
|
|
57
79
|
{fieldName}
|
|
58
|
-
|
|
80
|
+
onSelect={handleSelect}
|
|
59
81
|
{entry}
|
|
60
82
|
/>
|
|
61
83
|
</div>
|
|
@@ -66,7 +88,10 @@
|
|
|
66
88
|
bg-muted/30 text-xs
|
|
67
89
|
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
68
90
|
"
|
|
69
|
-
bind:value={
|
|
91
|
+
bind:value={
|
|
92
|
+
() => value ?? "",
|
|
93
|
+
(v) => (value = (v === "" || v == null) ? null : Number(v))
|
|
94
|
+
}
|
|
70
95
|
/>
|
|
71
96
|
</div>
|
|
72
97
|
{:else}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
2
3
|
import Button from "./ui/button/button.svelte";
|
|
3
4
|
import DataTable from "./dataTable/dataTable.svelte";
|
|
4
5
|
import Drawer from "./drawer.svelte";
|
|
@@ -7,7 +8,7 @@
|
|
|
7
8
|
import { getStudioContext } from "../context";
|
|
8
9
|
import { ArrowLeft, Link, ChevronDown } from "lucide-svelte";
|
|
9
10
|
|
|
10
|
-
const { ctx } = getStudioContext();
|
|
11
|
+
const { ctx, lobb } = getStudioContext();
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
collectionField: string;
|
|
@@ -26,12 +27,28 @@
|
|
|
26
27
|
}: Props = $props();
|
|
27
28
|
|
|
28
29
|
const selectedCollection = $derived(entry[collectionField] ?? null);
|
|
29
|
-
const selectedId = $derived(entry[idField]
|
|
30
|
-
const primaryField = $derived(entry[idField] ? Object.values(entry[idField])[1] : null);
|
|
30
|
+
const selectedId = $derived(entry[idField] ?? null);
|
|
31
31
|
|
|
32
|
+
let displayName = $state<string | null>(null);
|
|
32
33
|
let collectionPopoverOpen = $state(false);
|
|
33
34
|
let recordDrawerOpen = $state(false);
|
|
34
35
|
|
|
36
|
+
onMount(async () => {
|
|
37
|
+
if (selectedCollection == null || selectedId == null) return;
|
|
38
|
+
try {
|
|
39
|
+
const res = await lobb.findOne(selectedCollection, selectedId);
|
|
40
|
+
const record = await res.json();
|
|
41
|
+
const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection);
|
|
42
|
+
displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
|
|
43
|
+
} catch {
|
|
44
|
+
displayName = null;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
$effect(() => {
|
|
49
|
+
if (entry[idField] == null) displayName = null;
|
|
50
|
+
});
|
|
51
|
+
|
|
35
52
|
function onCollectionChange(col: string) {
|
|
36
53
|
collectionPopoverOpen = false;
|
|
37
54
|
if (entry[collectionField] !== col) {
|
|
@@ -42,14 +59,13 @@
|
|
|
42
59
|
function onIdChange(e: Event) {
|
|
43
60
|
const raw = (e.target as HTMLInputElement).value;
|
|
44
61
|
const id = raw === "" ? null : Number(raw);
|
|
45
|
-
entry = { ...entry, [idField]: id
|
|
62
|
+
entry = { ...entry, [idField]: id };
|
|
46
63
|
}
|
|
47
64
|
|
|
48
65
|
function onRecordSelect(record: any) {
|
|
49
66
|
const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection!);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
entry = { ...entry, [idField]: value };
|
|
67
|
+
entry = { ...entry, [idField]: record.id };
|
|
68
|
+
displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
|
|
53
69
|
recordDrawerOpen = false;
|
|
54
70
|
}
|
|
55
71
|
</script>
|
|
@@ -93,9 +109,9 @@
|
|
|
93
109
|
/>
|
|
94
110
|
|
|
95
111
|
<!-- Primary field badge -->
|
|
96
|
-
{#if
|
|
112
|
+
{#if displayName}
|
|
97
113
|
<div class="flex shrink-0 items-center bg-background rounded-full border h-6 px-3 shadow-sm">
|
|
98
|
-
{
|
|
114
|
+
{displayName}
|
|
99
115
|
</div>
|
|
100
116
|
{/if}
|
|
101
117
|
|
package/dist/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { clsx } from "clsx";
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
|
+
import { isEqual } from "lodash";
|
|
3
4
|
import { MediaQuery } from 'svelte/reactivity';
|
|
4
5
|
export function cn() {
|
|
5
6
|
var inputs = [];
|
|
@@ -26,7 +27,7 @@ export function getChangedProperties(oldObj, newObj) {
|
|
|
26
27
|
var changes = {};
|
|
27
28
|
for (var _i = 0, _a = Object.keys(newObj); _i < _a.length; _i++) {
|
|
28
29
|
var key = _a[_i];
|
|
29
|
-
if (oldObj[key]
|
|
30
|
+
if (!isEqual(oldObj[key], newObj[key])) {
|
|
30
31
|
changes[key] = newObj[key];
|
|
31
32
|
}
|
|
32
33
|
}
|
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.27.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"postpublish": "./scripts/postpublish.sh"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@lobb-js/core": "^0.
|
|
45
|
+
"@lobb-js/core": "^0.31.0",
|
|
46
46
|
"@chromatic-com/storybook": "^4.1.2",
|
|
47
47
|
"@storybook/addon-a11y": "^10.0.1",
|
|
48
48
|
"@storybook/addon-docs": "^10.0.1",
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface ParentContext {
|
|
3
|
+
collectionName: string;
|
|
4
|
+
recordId: string | number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type RecordOperation =
|
|
8
|
+
| { type: "link"; record: any }
|
|
9
|
+
| { type: "unlink"; id: string | number }
|
|
10
|
+
| { type: "delete"; id: string | number }
|
|
11
|
+
| { type: "create"; record: any }
|
|
12
|
+
| { type: "update"; id: string | number; data: any };
|
|
13
|
+
</script>
|
|
14
|
+
|
|
1
15
|
<script lang="ts">
|
|
2
16
|
import _ from "lodash";
|
|
3
17
|
import { getStudioContext } from "../../context";
|
|
@@ -5,7 +19,7 @@
|
|
|
5
19
|
import Header from "./header.svelte";
|
|
6
20
|
import Table, { type TableProps } from "./table.svelte";
|
|
7
21
|
import { getCollectionColumns, getCollectionParamsFields } from "./utils";
|
|
8
|
-
import { Pencil, Trash } from "lucide-svelte";
|
|
22
|
+
import { Pencil, Trash, Unlink } from "lucide-svelte";
|
|
9
23
|
import * as icons from "lucide-svelte";
|
|
10
24
|
import ListViewChildren from "./listViewChildren.svelte";
|
|
11
25
|
import FieldCell from "./fieldCell.svelte";
|
|
@@ -26,8 +40,11 @@
|
|
|
26
40
|
collectionName: string;
|
|
27
41
|
filter?: any;
|
|
28
42
|
searchParams?: Record<string, any>;
|
|
43
|
+
parentContext?: ParentContext;
|
|
44
|
+
onOperation?: (op: RecordOperation) => void;
|
|
29
45
|
showHeader?: boolean;
|
|
30
46
|
showFooter?: boolean;
|
|
47
|
+
showImport?: boolean;
|
|
31
48
|
unifiedBgColor?: "bg-muted/30" | "bg-background";
|
|
32
49
|
showDelete?: boolean;
|
|
33
50
|
tableProps?: Partial<TableProps>;
|
|
@@ -38,8 +55,11 @@
|
|
|
38
55
|
collectionName,
|
|
39
56
|
filter,
|
|
40
57
|
searchParams,
|
|
58
|
+
parentContext,
|
|
59
|
+
onOperation,
|
|
41
60
|
showHeader = true,
|
|
42
61
|
showFooter = true,
|
|
62
|
+
showImport = true,
|
|
43
63
|
unifiedBgColor,
|
|
44
64
|
showDelete = false,
|
|
45
65
|
tableProps,
|
|
@@ -78,7 +98,7 @@
|
|
|
78
98
|
let dataTableWidth: number = $state(0);
|
|
79
99
|
const doesCollectionHasChildren = $derived(
|
|
80
100
|
(ctx.meta.collections[collectionName]?.children ?? [])
|
|
81
|
-
.some((c: any) => c.type === "fk" || c.type === "m2m")
|
|
101
|
+
.some((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic")
|
|
82
102
|
);
|
|
83
103
|
|
|
84
104
|
// requests the data from the server when the params is changed
|
|
@@ -109,14 +129,44 @@
|
|
|
109
129
|
loading = false;
|
|
110
130
|
}
|
|
111
131
|
|
|
132
|
+
// Internal handler: updates data optimistically then calls onOperation
|
|
133
|
+
function applyOperation(op: RecordOperation) {
|
|
134
|
+
if (op.type === "link") {
|
|
135
|
+
data = [...data, op.record];
|
|
136
|
+
} else if (op.type === "unlink" || op.type === "delete") {
|
|
137
|
+
data = data.filter((r: any) => String(r.id) !== String(op.id));
|
|
138
|
+
} else if (op.type === "create") {
|
|
139
|
+
data = [...data, { ...op.record, _pending: true }];
|
|
140
|
+
} else if (op.type === "update") {
|
|
141
|
+
data = data.map((r: any) => String(r.id) === String(op.id) ? { ...r, ...op.data } : r);
|
|
142
|
+
}
|
|
143
|
+
onOperation?.(op);
|
|
144
|
+
}
|
|
145
|
+
|
|
112
146
|
async function handleDelete(entryId: string) {
|
|
113
|
-
const result = await showDialog(
|
|
114
|
-
"Are you sure?",
|
|
115
|
-
"This will delete the record you selected.",
|
|
116
|
-
);
|
|
147
|
+
const result = await showDialog("Are you sure?", "This will permanently delete the record.");
|
|
117
148
|
if (result) {
|
|
118
|
-
|
|
119
|
-
|
|
149
|
+
if (onOperation) {
|
|
150
|
+
applyOperation({ type: "delete", id: entryId });
|
|
151
|
+
} else if (parentContext) {
|
|
152
|
+
await lobb.updateOne(parentContext.collectionName, String(parentContext.recordId), {}, { [collectionName]: { delete: [entryId] } });
|
|
153
|
+
params = { ...params };
|
|
154
|
+
} else {
|
|
155
|
+
await lobb.deleteOne(collectionName, entryId);
|
|
156
|
+
params = { ...params };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function handleUnlink(entryId: string) {
|
|
162
|
+
const result = await showDialog("Are you sure?", "This will unlink the record without deleting it.");
|
|
163
|
+
if (result) {
|
|
164
|
+
if (onOperation) {
|
|
165
|
+
applyOperation({ type: "unlink", id: entryId });
|
|
166
|
+
} else {
|
|
167
|
+
await lobb.updateOne(parentContext!.collectionName, String(parentContext!.recordId), {}, { [collectionName]: { unlink: [entryId] } });
|
|
168
|
+
params = { ...params };
|
|
169
|
+
}
|
|
120
170
|
}
|
|
121
171
|
}
|
|
122
172
|
|
|
@@ -159,7 +209,7 @@
|
|
|
159
209
|
{/snippet}
|
|
160
210
|
|
|
161
211
|
{#if showHeader}
|
|
162
|
-
<Header bind:params {collectionName} bind:selectedRecords>
|
|
212
|
+
<Header bind:params {collectionName} bind:selectedRecords {parentContext} {showImport} onOperation={onOperation ? applyOperation : undefined}>
|
|
163
213
|
{#snippet left()}
|
|
164
214
|
{@render headerLeft?.()}
|
|
165
215
|
{/snippet}
|
|
@@ -200,6 +250,16 @@
|
|
|
200
250
|
params = { ...params };
|
|
201
251
|
}}
|
|
202
252
|
></UpdateDetailViewButton>
|
|
253
|
+
{#if parentContext}
|
|
254
|
+
<Button
|
|
255
|
+
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
256
|
+
variant="ghost"
|
|
257
|
+
size="icon"
|
|
258
|
+
onclick={() => handleUnlink(entry.id)}
|
|
259
|
+
Icon={Unlink}
|
|
260
|
+
title="Remove from this entry"
|
|
261
|
+
></Button>
|
|
262
|
+
{/if}
|
|
203
263
|
{#if showDelete}
|
|
204
264
|
<Button
|
|
205
265
|
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
@@ -207,6 +267,7 @@
|
|
|
207
267
|
size="icon"
|
|
208
268
|
onclick={() => handleDelete(entry.id)}
|
|
209
269
|
Icon={Trash}
|
|
270
|
+
title="Delete permanently"
|
|
210
271
|
></Button>
|
|
211
272
|
{/if}
|
|
212
273
|
{#await getWorkflowTools($state.snapshot(entry))}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { getStudioContext } from "../../context";
|
|
3
3
|
|
|
4
4
|
const { lobb, ctx } = getStudioContext();
|
|
5
|
-
import { Download, ListRestart, Plus, Trash } from "lucide-svelte";
|
|
5
|
+
import { Download, ListRestart, Plus, Trash, Link } from "lucide-svelte";
|
|
6
6
|
import * as Tooltip from "../ui/tooltip";
|
|
7
7
|
import LlmButton from "../LlmButton.svelte";
|
|
8
8
|
import FilterButton from "./filterButton.svelte";
|
|
@@ -11,14 +11,19 @@
|
|
|
11
11
|
import ImportButton from "../importButton.svelte";
|
|
12
12
|
import { showDialog } from "../../actions";
|
|
13
13
|
import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
|
|
14
|
+
import SelectRecord from "../selectRecord.svelte";
|
|
14
15
|
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
15
16
|
import { getExtensionUtils } from "../../extensions/extensionUtils";
|
|
16
17
|
import type { Snippet } from "svelte";
|
|
18
|
+
import type { ParentContext, RecordOperation } from "./dataTable.svelte";
|
|
17
19
|
|
|
18
20
|
interface Props {
|
|
19
21
|
collectionName: string;
|
|
20
22
|
params: any;
|
|
21
23
|
selectedRecords: string[];
|
|
24
|
+
parentContext?: ParentContext;
|
|
25
|
+
onOperation?: (op: RecordOperation) => void;
|
|
26
|
+
showImport?: boolean;
|
|
22
27
|
left?: Snippet<[]>;
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -26,9 +31,42 @@
|
|
|
26
31
|
collectionName,
|
|
27
32
|
params = $bindable(),
|
|
28
33
|
selectedRecords = $bindable(),
|
|
34
|
+
parentContext,
|
|
35
|
+
onOperation,
|
|
36
|
+
showImport = true,
|
|
29
37
|
left
|
|
30
38
|
}: Props = $props();
|
|
31
39
|
|
|
40
|
+
async function handleLink(selected: any) {
|
|
41
|
+
if (!parentContext) return;
|
|
42
|
+
if (onOperation) {
|
|
43
|
+
onOperation({ type: "link", record: selected });
|
|
44
|
+
} else {
|
|
45
|
+
await lobb.updateOne(
|
|
46
|
+
parentContext.collectionName,
|
|
47
|
+
String(parentContext.recordId),
|
|
48
|
+
{},
|
|
49
|
+
{ [collectionName]: { link: [selected.id] } },
|
|
50
|
+
);
|
|
51
|
+
resetTable();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function handleChildCreate(formData: any) {
|
|
56
|
+
if (!parentContext) return;
|
|
57
|
+
if (onOperation) {
|
|
58
|
+
onOperation({ type: "create", record: formData });
|
|
59
|
+
} else {
|
|
60
|
+
await lobb.updateOne(
|
|
61
|
+
parentContext.collectionName,
|
|
62
|
+
String(parentContext.recordId),
|
|
63
|
+
{},
|
|
64
|
+
{ [collectionName]: { create: [formData] } },
|
|
65
|
+
);
|
|
66
|
+
resetTable();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
32
70
|
let headerWidth: number = $state(0);
|
|
33
71
|
let headerIsSmall: boolean = $derived(headerWidth < 560);
|
|
34
72
|
|
|
@@ -138,34 +176,60 @@
|
|
|
138
176
|
>
|
|
139
177
|
{headerIsSmall ? "" : "Refresh"}
|
|
140
178
|
</Button>
|
|
141
|
-
|
|
142
|
-
<Tooltip.
|
|
143
|
-
<Tooltip.
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
{#if showImport}
|
|
180
|
+
<Tooltip.Provider delayDuration={0}>
|
|
181
|
+
<Tooltip.Root>
|
|
182
|
+
<Tooltip.Trigger>
|
|
183
|
+
<ImportButton
|
|
184
|
+
{collectionName}
|
|
185
|
+
variant="outline"
|
|
186
|
+
class="h-7 px-2 text-xs font-normal"
|
|
187
|
+
Icon={Download}
|
|
188
|
+
onSuccessfullSave={() => (params = { ...params })}
|
|
189
|
+
/>
|
|
190
|
+
</Tooltip.Trigger>
|
|
191
|
+
<Tooltip.Content>Import</Tooltip.Content>
|
|
192
|
+
</Tooltip.Root>
|
|
193
|
+
</Tooltip.Provider>
|
|
194
|
+
{/if}
|
|
155
195
|
<ExtensionsComponents
|
|
156
196
|
name="listView.header.actions"
|
|
157
197
|
utils={getExtensionUtils(lobb, ctx)}
|
|
158
198
|
{collectionName}
|
|
159
199
|
refresh={() => { params = { ...params }; }}
|
|
160
200
|
/>
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
201
|
+
{#if parentContext}
|
|
202
|
+
{#if parentContext}
|
|
203
|
+
<SelectRecord
|
|
204
|
+
{collectionName}
|
|
205
|
+
variant="outline"
|
|
206
|
+
class="h-7 px-3 text-xs font-normal"
|
|
207
|
+
Icon={Link}
|
|
208
|
+
onSelect={handleLink}
|
|
209
|
+
>
|
|
210
|
+
{headerIsSmall ? "" : "Link"}
|
|
211
|
+
</SelectRecord>
|
|
212
|
+
{/if}
|
|
213
|
+
<CreateDetailViewButton
|
|
214
|
+
{collectionName}
|
|
215
|
+
variant="default"
|
|
216
|
+
class="h-7 px-3 text-xs font-normal"
|
|
217
|
+
Icon={Plus}
|
|
218
|
+
rollback={true}
|
|
219
|
+
onSuccessfullSave={handleChildCreate}
|
|
220
|
+
>
|
|
221
|
+
{headerIsSmall ? "" : "Create"}
|
|
222
|
+
</CreateDetailViewButton>
|
|
223
|
+
{:else}
|
|
224
|
+
<CreateDetailViewButton
|
|
225
|
+
{collectionName}
|
|
226
|
+
variant="default"
|
|
227
|
+
class="h-7 px-3 text-xs font-normal"
|
|
228
|
+
Icon={Plus}
|
|
229
|
+
onSuccessfullSave={() => (params = { ...params })}
|
|
230
|
+
>
|
|
231
|
+
{headerIsSmall ? "" : "Create"}
|
|
232
|
+
</CreateDetailViewButton>
|
|
233
|
+
{/if}
|
|
170
234
|
</div>
|
|
171
235
|
</div>
|