@lobb-js/studio 0.7.2 → 0.8.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/package.json +2 -1
- package/src/App.svelte +5 -0
- package/src/app.css +124 -0
- package/src/lib/components/LlmButton.svelte +137 -0
- package/src/lib/components/Studio.svelte +129 -0
- package/src/lib/components/alertView.svelte +20 -0
- package/src/lib/components/breadCrumbs.svelte +60 -0
- package/src/lib/components/codeEditor.svelte +152 -0
- package/src/lib/components/combobox.svelte +92 -0
- package/src/lib/components/confirmationDialog/confirmationDialog.svelte +33 -0
- package/src/lib/components/confirmationDialog/store.svelte.ts +28 -0
- package/src/lib/components/createManyButton.svelte +109 -0
- package/src/lib/components/dataTable/childRecords.svelte +142 -0
- package/src/lib/components/dataTable/dataTable.svelte +225 -0
- package/src/lib/components/dataTable/fieldCell.svelte +77 -0
- package/src/lib/components/dataTable/filter.svelte +284 -0
- package/src/lib/components/dataTable/filterButton.svelte +39 -0
- package/src/lib/components/dataTable/footer.svelte +84 -0
- package/src/lib/components/dataTable/header.svelte +155 -0
- package/src/lib/components/dataTable/sort.svelte +173 -0
- package/src/lib/components/dataTable/sortButton.svelte +36 -0
- package/src/lib/components/dataTable/table.svelte +337 -0
- package/src/lib/components/dataTable/utils.ts +127 -0
- package/src/lib/components/detailView/create/children.svelte +70 -0
- package/src/lib/components/detailView/create/createDetailView.svelte +228 -0
- package/src/lib/components/detailView/create/createDetailViewButton.svelte +37 -0
- package/src/lib/components/detailView/create/createManyView.svelte +252 -0
- package/src/lib/components/detailView/create/subRecords.svelte +50 -0
- package/src/lib/components/detailView/detailViewForm.svelte +104 -0
- package/src/lib/components/detailView/fieldCustomInput.svelte +26 -0
- package/src/lib/components/detailView/fieldInput.svelte +258 -0
- package/src/lib/components/detailView/fieldInputReplacement.svelte +199 -0
- package/src/lib/components/detailView/store.svelte.ts +59 -0
- package/src/lib/components/detailView/update/children.svelte +96 -0
- package/src/lib/components/detailView/update/updateDetailView.svelte +176 -0
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +56 -0
- package/src/lib/components/detailView/utils.ts +176 -0
- package/src/lib/components/diffViewer.svelte +105 -0
- package/src/lib/components/drawer.svelte +28 -0
- package/src/lib/components/extensionsComponents.svelte +33 -0
- package/src/lib/components/foreingKeyInput.svelte +80 -0
- package/src/lib/components/header.svelte +45 -0
- package/src/lib/components/loadingTypesForMonacoEditor.ts +36 -0
- package/src/lib/components/miniSidebar.svelte +226 -0
- package/src/lib/components/rangeCalendarButton.svelte +257 -0
- package/src/lib/components/richTextEditor.svelte +284 -0
- package/src/lib/components/routes/collections/collection.svelte +57 -0
- package/src/lib/components/routes/collections/collections.svelte +45 -0
- package/src/lib/components/routes/data_model/dataModel.svelte +40 -0
- package/src/lib/components/routes/data_model/flow.css +22 -0
- package/src/lib/components/routes/data_model/flow.svelte +84 -0
- package/src/lib/components/routes/data_model/syncManager.svelte +94 -0
- package/src/lib/components/routes/data_model/utils.ts +35 -0
- package/src/lib/components/routes/extensions/extension.svelte +19 -0
- package/src/lib/components/routes/home.svelte +40 -0
- package/src/lib/components/routes/workflows/workflows.svelte +136 -0
- package/src/lib/components/selectRecord.svelte +130 -0
- package/src/lib/components/setServerPage.svelte +50 -0
- package/src/lib/components/sidebar/index.ts +4 -0
- package/src/lib/components/sidebar/sidebar.svelte +149 -0
- package/src/lib/components/sidebar/sidebarElements.svelte +144 -0
- package/src/lib/components/sidebar/sidebarTrigger.svelte +33 -0
- package/src/lib/components/singletone.svelte +71 -0
- package/src/lib/components/ui/accordion/accordion-content.svelte +22 -0
- package/src/lib/components/ui/accordion/accordion-item.svelte +12 -0
- package/src/lib/components/ui/accordion/accordion-trigger.svelte +31 -0
- package/src/lib/components/ui/accordion/index.ts +17 -0
- package/src/lib/components/ui/alert/alert-description.svelte +16 -0
- package/src/lib/components/ui/alert/alert-title.svelte +24 -0
- package/src/lib/components/ui/alert/alert.svelte +39 -0
- package/src/lib/components/ui/alert/index.ts +14 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +13 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +17 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +26 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +16 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte +20 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte +19 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte +18 -0
- package/src/lib/components/ui/alert-dialog/index.ts +40 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte +16 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte +31 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte +23 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte +23 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte +27 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb.svelte +15 -0
- package/src/lib/components/ui/breadcrumb/index.ts +25 -0
- package/src/lib/components/ui/button/button.svelte +110 -0
- package/src/lib/components/ui/button/index.ts +17 -0
- package/src/lib/components/ui/checkbox/checkbox.svelte +35 -0
- package/src/lib/components/ui/checkbox/index.ts +6 -0
- package/src/lib/components/ui/command/command-dialog.svelte +35 -0
- package/src/lib/components/ui/command/command-empty.svelte +12 -0
- package/src/lib/components/ui/command/command-group.svelte +31 -0
- package/src/lib/components/ui/command/command-input.svelte +25 -0
- package/src/lib/components/ui/command/command-item.svelte +19 -0
- package/src/lib/components/ui/command/command-link-item.svelte +19 -0
- package/src/lib/components/ui/command/command-list.svelte +16 -0
- package/src/lib/components/ui/command/command-separator.svelte +12 -0
- package/src/lib/components/ui/command/command-shortcut.svelte +20 -0
- package/src/lib/components/ui/command/command.svelte +21 -0
- package/src/lib/components/ui/command/index.ts +40 -0
- package/src/lib/components/ui/dialog/dialog-content.svelte +38 -0
- package/src/lib/components/ui/dialog/dialog-description.svelte +16 -0
- package/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
- package/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
- package/src/lib/components/ui/dialog/dialog-overlay.svelte +19 -0
- package/src/lib/components/ui/dialog/dialog-title.svelte +16 -0
- package/src/lib/components/ui/dialog/index.ts +37 -0
- package/src/lib/components/ui/input/index.ts +7 -0
- package/src/lib/components/ui/input/input.svelte +46 -0
- package/src/lib/components/ui/label/index.ts +7 -0
- package/src/lib/components/ui/label/label.svelte +19 -0
- package/src/lib/components/ui/popover/index.ts +17 -0
- package/src/lib/components/ui/popover/popover-content.svelte +28 -0
- package/src/lib/components/ui/range-calendar/index.ts +30 -0
- package/src/lib/components/ui/range-calendar/range-calendar-cell.svelte +19 -0
- package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +35 -0
- package/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte +12 -0
- package/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte +12 -0
- package/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte +12 -0
- package/src/lib/components/ui/range-calendar/range-calendar-grid.svelte +16 -0
- package/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte +16 -0
- package/src/lib/components/ui/range-calendar/range-calendar-header.svelte +16 -0
- package/src/lib/components/ui/range-calendar/range-calendar-heading.svelte +16 -0
- package/src/lib/components/ui/range-calendar/range-calendar-months.svelte +20 -0
- package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +27 -0
- package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +27 -0
- package/src/lib/components/ui/range-calendar/range-calendar.svelte +57 -0
- package/src/lib/components/ui/select/index.ts +34 -0
- package/src/lib/components/ui/select/select-content.svelte +38 -0
- package/src/lib/components/ui/select/select-group-heading.svelte +16 -0
- package/src/lib/components/ui/select/select-item.svelte +37 -0
- package/src/lib/components/ui/select/select-scroll-down-button.svelte +19 -0
- package/src/lib/components/ui/select/select-scroll-up-button.svelte +19 -0
- package/src/lib/components/ui/select/select-separator.svelte +13 -0
- package/src/lib/components/ui/select/select-trigger.svelte +24 -0
- package/src/lib/components/ui/separator/index.ts +7 -0
- package/src/lib/components/ui/separator/separator.svelte +22 -0
- package/src/lib/components/ui/skeleton/index.ts +7 -0
- package/src/lib/components/ui/skeleton/skeleton.svelte +22 -0
- package/src/lib/components/ui/sonner/index.ts +1 -0
- package/src/lib/components/ui/sonner/sonner.svelte +20 -0
- package/src/lib/components/ui/switch/index.ts +7 -0
- package/src/lib/components/ui/switch/switch.svelte +27 -0
- package/src/lib/components/ui/textarea/index.ts +7 -0
- package/src/lib/components/ui/textarea/textarea.svelte +22 -0
- package/src/lib/components/ui/tooltip/index.ts +18 -0
- package/src/lib/components/ui/tooltip/tooltip-content.svelte +21 -0
- package/src/lib/components/workflowEditor.svelte +188 -0
- package/src/lib/context.ts +22 -0
- package/src/lib/eventSystem.ts +40 -0
- package/src/lib/extensions/extension.types.ts +92 -0
- package/src/lib/extensions/extensionUtils.ts +156 -0
- package/src/lib/index.ts +24 -0
- package/src/lib/store.svelte.ts +13 -0
- package/src/lib/store.types.ts +28 -0
- package/src/lib/utils.ts +68 -0
- package/src/main.ts +18 -0
- package/src/stories/detailView/detailViewForm.stories.svelte +79 -0
- package/src/vite-env.d.ts +2 -0
- package/vite-plugins/index.js +2 -0
- package/vite-plugins/lobb-proxy.js +36 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import DataTable from "../../../components/dataTable/dataTable.svelte";
|
|
3
|
+
import { getStudioContext } from "../../../context";
|
|
4
|
+
import { Link, Plus, TableIcon } from "lucide-svelte";
|
|
5
|
+
import CreateDetailViewButton from "../create/createDetailViewButton.svelte";
|
|
6
|
+
import ExtensionsComponents from "../../../components/extensionsComponents.svelte";
|
|
7
|
+
import { getExtensionUtils } from "../../../extensions/extensionUtils";
|
|
8
|
+
|
|
9
|
+
const { ctx, lobb } = getStudioContext();
|
|
10
|
+
|
|
11
|
+
interface LocalProp {
|
|
12
|
+
collectionName: string;
|
|
13
|
+
entry: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { collectionName, entry }: LocalProp = $props();
|
|
17
|
+
|
|
18
|
+
const childrenRelations = ctx.meta.relations.filter(
|
|
19
|
+
(relation) => relation.to.collection === collectionName,
|
|
20
|
+
);
|
|
21
|
+
const refresh: boolean[] = $state(
|
|
22
|
+
new Array(childrenRelations.length).fill(true),
|
|
23
|
+
);
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
{#if childrenRelations.length}
|
|
27
|
+
<div class="flex flex-col gap-4 border-t p-4">
|
|
28
|
+
<div class="flex items-center gap-2">
|
|
29
|
+
<Link size="17.5" />
|
|
30
|
+
<div>Sub Records</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="flex flex-col gap-4">
|
|
33
|
+
{#each childrenRelations as relation, index}
|
|
34
|
+
{@const childCollection = relation.from.collection}
|
|
35
|
+
{@const childField = relation.from.field}
|
|
36
|
+
<ExtensionsComponents
|
|
37
|
+
name="detailView.update.subRecords.{childCollection}"
|
|
38
|
+
utils={getExtensionUtils(lobb, ctx)}
|
|
39
|
+
collectionName={childCollection}
|
|
40
|
+
filter={{
|
|
41
|
+
[childField]: entry.id,
|
|
42
|
+
}}
|
|
43
|
+
class="bg-muted/30 border rounded-md overflow-hidden"
|
|
44
|
+
>
|
|
45
|
+
<div class="border rounded-md overflow-clip">
|
|
46
|
+
<div
|
|
47
|
+
class="flex items-center justify-between px-2 h-10 bg-muted/30 border-b"
|
|
48
|
+
>
|
|
49
|
+
<div class="flex-1 flex h-full items-center gap-2">
|
|
50
|
+
<TableIcon
|
|
51
|
+
class="text-muted-foreground"
|
|
52
|
+
size="17.5"
|
|
53
|
+
/>
|
|
54
|
+
<div class="text-sm text-muted-foreground">
|
|
55
|
+
{childCollection}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="flex gap-2">
|
|
59
|
+
<CreateDetailViewButton
|
|
60
|
+
variant="ghost"
|
|
61
|
+
class="h-7 px-2 font-normal text-xs"
|
|
62
|
+
Icon={Plus}
|
|
63
|
+
collectionName={childCollection}
|
|
64
|
+
onSuccessfullSave={async () => {
|
|
65
|
+
refresh[index] = !refresh[index];
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
Create
|
|
69
|
+
</CreateDetailViewButton>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="max-h-72 overflow-auto rounded-md">
|
|
73
|
+
{#key refresh[index]}
|
|
74
|
+
<DataTable
|
|
75
|
+
collectionName={childCollection}
|
|
76
|
+
filter={{
|
|
77
|
+
[childField]: entry.id,
|
|
78
|
+
}}
|
|
79
|
+
unifiedBgColor="bg-muted/30"
|
|
80
|
+
showHeader={false}
|
|
81
|
+
showFooter={false}
|
|
82
|
+
showDelete={true}
|
|
83
|
+
tableProps={{
|
|
84
|
+
showLastColumnBorder: false,
|
|
85
|
+
showLastRowBorder: false,
|
|
86
|
+
showCheckboxes: false,
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
{/key}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</ExtensionsComponents>
|
|
93
|
+
{/each}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
{/if}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
interface SubmitButton {
|
|
3
|
+
text: string;
|
|
4
|
+
icon: any;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface UpdateDetailViewProp {
|
|
8
|
+
collectionName: string;
|
|
9
|
+
recordId: string;
|
|
10
|
+
values?: Record<string, any>;
|
|
11
|
+
showRelatedRecords?: boolean;
|
|
12
|
+
rollback?: boolean;
|
|
13
|
+
submitButton?: SubmitButton;
|
|
14
|
+
title?: Snippet<[string]>;
|
|
15
|
+
onSuccessfullSave?: (entry: any) => Promise<void>;
|
|
16
|
+
onCancel?: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import { ArrowLeft, Pencil, X } from "lucide-svelte";
|
|
22
|
+
import Button from "../../../components/ui/button/button.svelte";
|
|
23
|
+
import { fade, fly } from "svelte/transition";
|
|
24
|
+
import { getStudioContext } from "../../../context";
|
|
25
|
+
import { toast } from "svelte-sonner";
|
|
26
|
+
import ExtensionsComponents from "../../extensionsComponents.svelte";
|
|
27
|
+
import { getExtensionUtils } from "../../../extensions/extensionUtils";
|
|
28
|
+
|
|
29
|
+
const { lobb, ctx } = getStudioContext();
|
|
30
|
+
import { calculateDrawerWidth, getChangedProperties } from "../../../utils";
|
|
31
|
+
import { getField, getFieldIcon } from "../../dataTable/utils";
|
|
32
|
+
import Children from "../update/children.svelte";
|
|
33
|
+
import type { Snippet } from "svelte";
|
|
34
|
+
import { getDefaultEntry, parseDetailViewValues, serializeEntry } from "../utils";
|
|
35
|
+
import FieldInput from "../fieldInput.svelte";
|
|
36
|
+
import Drawer from "../../../components/drawer.svelte";
|
|
37
|
+
|
|
38
|
+
let {
|
|
39
|
+
collectionName,
|
|
40
|
+
values = {},
|
|
41
|
+
showRelatedRecords = true,
|
|
42
|
+
onCancel,
|
|
43
|
+
onSuccessfullSave,
|
|
44
|
+
title,
|
|
45
|
+
submitButton,
|
|
46
|
+
recordId,
|
|
47
|
+
}: UpdateDetailViewProp = $props();
|
|
48
|
+
|
|
49
|
+
parseDetailViewValues(ctx, collectionName, values)
|
|
50
|
+
|
|
51
|
+
const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
|
|
52
|
+
let entry: Record<string, any> = $state(
|
|
53
|
+
getDefaultEntry(ctx, fieldNames, collectionName, values),
|
|
54
|
+
);
|
|
55
|
+
const initialEntry = $state.snapshot(entry);
|
|
56
|
+
let localEntry = $derived(
|
|
57
|
+
getChangedProperties(initialEntry, $state.snapshot(entry)),
|
|
58
|
+
);
|
|
59
|
+
let fieldsErrors: Record<string, any> = $state({});
|
|
60
|
+
|
|
61
|
+
async function handleSave() {
|
|
62
|
+
delete localEntry.id;
|
|
63
|
+
localEntry = serializeEntry(ctx, collectionName, localEntry);
|
|
64
|
+
|
|
65
|
+
const response = await lobb.updateOne(
|
|
66
|
+
collectionName,
|
|
67
|
+
recordId,
|
|
68
|
+
localEntry,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!response.bodyUsed) {
|
|
72
|
+
const result = await response.json();
|
|
73
|
+
if (response.status >= 400) {
|
|
74
|
+
if (result.message && result.details) {
|
|
75
|
+
fieldsErrors = result.details;
|
|
76
|
+
return;
|
|
77
|
+
} else if (result.message) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// close detailView side bar
|
|
84
|
+
if (onSuccessfullSave) {
|
|
85
|
+
await onSuccessfullSave(localEntry);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
toast.success(`The record was successfully updated`);
|
|
89
|
+
|
|
90
|
+
onCancel?.();
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<Drawer onHide={onCancel}>
|
|
95
|
+
<div class="flex h-12 items-center gap-4 border-b px-4">
|
|
96
|
+
<Button
|
|
97
|
+
variant="outline"
|
|
98
|
+
onclick={onCancel}
|
|
99
|
+
class=" h-8 w-8 rounded-full text-xs font-normal"
|
|
100
|
+
Icon={ArrowLeft}
|
|
101
|
+
></Button>
|
|
102
|
+
<div class="flex items-center gap-2 text-sm">
|
|
103
|
+
{#if title}
|
|
104
|
+
{@render title(collectionName)}
|
|
105
|
+
{:else}
|
|
106
|
+
<div>Update record of</div>
|
|
107
|
+
<div class="rounded-md border bg-muted px-2 py-0.5">
|
|
108
|
+
{collectionName}
|
|
109
|
+
</div>
|
|
110
|
+
{/if}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="flex-1 overflow-y-auto">
|
|
114
|
+
<div class="flex flex-col gap-4 p-4">
|
|
115
|
+
{#each fieldNames as fieldName}
|
|
116
|
+
{@const field = getField(ctx, fieldName, collectionName)}
|
|
117
|
+
{@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
|
|
118
|
+
<div
|
|
119
|
+
class="flex flex-col gap-2"
|
|
120
|
+
>
|
|
121
|
+
<div
|
|
122
|
+
class="flex flex-1 items-end justify-between gap-2 text-xs"
|
|
123
|
+
>
|
|
124
|
+
<div class="flex gap-2">
|
|
125
|
+
<div class="h-fit">{field.label}</div>
|
|
126
|
+
<div
|
|
127
|
+
class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground"
|
|
128
|
+
>
|
|
129
|
+
<FieldIcon size="12" />
|
|
130
|
+
{field.type}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div>
|
|
134
|
+
<ExtensionsComponents
|
|
135
|
+
name="dvFields.topRight.{collectionName}.{fieldName}"
|
|
136
|
+
utils={getExtensionUtils(lobb, ctx)}
|
|
137
|
+
bind:value={entry[fieldName]}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
<FieldInput
|
|
142
|
+
{collectionName}
|
|
143
|
+
{fieldName}
|
|
144
|
+
bind:value={entry[fieldName]}
|
|
145
|
+
{entry}
|
|
146
|
+
errorMessages={fieldsErrors[fieldName]}
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
{/each}
|
|
150
|
+
</div>
|
|
151
|
+
{#if showRelatedRecords}
|
|
152
|
+
<Children {collectionName} {entry} />
|
|
153
|
+
{/if}
|
|
154
|
+
</div>
|
|
155
|
+
<div class="flex h-12 items-center justify-end gap-2 border-t px-4">
|
|
156
|
+
<div class="flex gap-3">
|
|
157
|
+
<Button
|
|
158
|
+
variant="outline"
|
|
159
|
+
onclick={onCancel}
|
|
160
|
+
class="h-7 px-3 text-xs font-normal"
|
|
161
|
+
Icon={X}
|
|
162
|
+
>
|
|
163
|
+
Cancel
|
|
164
|
+
</Button>
|
|
165
|
+
<Button
|
|
166
|
+
variant="default"
|
|
167
|
+
class="h-7 px-3 text-xs font-normal"
|
|
168
|
+
Icon={submitButton?.icon ? submitButton.icon : Pencil}
|
|
169
|
+
onclick={handleSave}
|
|
170
|
+
disabled={!Object.keys(localEntry).length}
|
|
171
|
+
>
|
|
172
|
+
{submitButton?.text ? submitButton.text : "Update"}
|
|
173
|
+
</Button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</Drawer>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { UpdateDetailViewProp } from "./updateDetailView.svelte";
|
|
3
|
+
import type { ButtonProps } from "../../../components/ui/button/button.svelte";
|
|
4
|
+
import Button from "../../../components/ui/button/button.svelte";
|
|
5
|
+
import UpdateDetailView from "./updateDetailView.svelte";
|
|
6
|
+
import { getStudioContext } from "../../../context";
|
|
7
|
+
import { getCollectionParamsFields } from "../../dataTable/utils";
|
|
8
|
+
|
|
9
|
+
interface LocalProp extends UpdateDetailViewProp {
|
|
10
|
+
variant?: ButtonProps["variant"];
|
|
11
|
+
class?: ButtonProps["class"];
|
|
12
|
+
Icon?: ButtonProps["Icon"];
|
|
13
|
+
children?: ButtonProps["children"];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let props: LocalProp = $props();
|
|
17
|
+
let open = $state(false);
|
|
18
|
+
let values: Record<string, any> | undefined = $state(undefined);
|
|
19
|
+
|
|
20
|
+
const { lobb, ctx } = getStudioContext();
|
|
21
|
+
|
|
22
|
+
async function openView() {
|
|
23
|
+
const params = {
|
|
24
|
+
fields: getCollectionParamsFields(ctx, props.collectionName, true),
|
|
25
|
+
filter: { id: props.recordId },
|
|
26
|
+
limit: 1,
|
|
27
|
+
};
|
|
28
|
+
const response = await lobb.findAll(props.collectionName, params);
|
|
29
|
+
const result = await response.json();
|
|
30
|
+
values = result.data[0];
|
|
31
|
+
open = true;
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<Button
|
|
36
|
+
variant={props.variant}
|
|
37
|
+
class={props.class}
|
|
38
|
+
Icon={props.Icon}
|
|
39
|
+
onclick={openView}
|
|
40
|
+
>
|
|
41
|
+
{#if props.children}
|
|
42
|
+
{@render props.children()}
|
|
43
|
+
{/if}
|
|
44
|
+
</Button>
|
|
45
|
+
|
|
46
|
+
{#if open && values}
|
|
47
|
+
<UpdateDetailView
|
|
48
|
+
{...props}
|
|
49
|
+
{values}
|
|
50
|
+
onCancel={async () => {
|
|
51
|
+
open = false;
|
|
52
|
+
values = undefined;
|
|
53
|
+
await props.onCancel?.();
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
{/if}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import Mustache from "mustache";
|
|
2
|
+
import type { CTX } from "../../store.types";
|
|
3
|
+
import { getFieldRelation } from "../../utils";
|
|
4
|
+
import { getField } from "../dataTable/utils";
|
|
5
|
+
import type { DetailFormField } from "./detailViewForm.svelte";
|
|
6
|
+
|
|
7
|
+
export function getDefaultEntry(ctx: CTX, fieldNames: string[], collectionName: string, values?: Record<string, any>) {
|
|
8
|
+
return Object.fromEntries(
|
|
9
|
+
fieldNames.map((fieldName) => {
|
|
10
|
+
let value = null;
|
|
11
|
+
const field = getField(ctx, fieldName, collectionName);
|
|
12
|
+
if (values && values[fieldName] !== undefined) {
|
|
13
|
+
value = values[fieldName];
|
|
14
|
+
} else if (field.pre_processors?.default) {
|
|
15
|
+
const defualtValue = field.pre_processors.default;
|
|
16
|
+
if (typeof defualtValue === "string") {
|
|
17
|
+
value = Mustache.render(defualtValue, {
|
|
18
|
+
now: new Date().toISOString(),
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
value = defualtValue;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [fieldName, value];
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function serializeEntry(
|
|
30
|
+
ctx: CTX,
|
|
31
|
+
collectionName: string,
|
|
32
|
+
entry: Record<string, any>,
|
|
33
|
+
rollback: boolean = false,
|
|
34
|
+
) {
|
|
35
|
+
// deep clone the object
|
|
36
|
+
entry = { ...entry }
|
|
37
|
+
|
|
38
|
+
// serialize the foreign key field's value
|
|
39
|
+
for (const [fieldName, fieldValue] of Object.entries(entry)) {
|
|
40
|
+
const isRefrenceField = Boolean(getFieldRelation(ctx, collectionName, fieldName));
|
|
41
|
+
if (isRefrenceField && fieldValue !== null && fieldValue.id !== undefined) {
|
|
42
|
+
entry[fieldName] = fieldValue.id;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// check for related collections properties and serialize them too
|
|
47
|
+
if (!rollback) {
|
|
48
|
+
const childrenRelations = ctx.meta.relations.filter((relation) => relation.to.collection === collectionName);
|
|
49
|
+
const childrenCollectionNames = childrenRelations.map((relation) => relation.from.collection);
|
|
50
|
+
for (let index = 0; index < childrenCollectionNames.length; index++) {
|
|
51
|
+
const childrenCollectionName = childrenCollectionNames[index];
|
|
52
|
+
const childrenEntries = entry[childrenCollectionName];
|
|
53
|
+
if (childrenEntries) {
|
|
54
|
+
for (let index = 0; index < childrenEntries.length; index++) {
|
|
55
|
+
childrenEntries[index] = serializeEntry(ctx, childrenCollectionName, childrenEntries[index]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return entry;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function generateTransactionBody(
|
|
65
|
+
ctx: CTX,
|
|
66
|
+
collectionName: string,
|
|
67
|
+
entry: Record<string, any>,
|
|
68
|
+
) {
|
|
69
|
+
entry = { ...entry }
|
|
70
|
+
function handleEntryRecursive(
|
|
71
|
+
transactionBody: any[],
|
|
72
|
+
collectionName: string,
|
|
73
|
+
entry: Record<string, any>,
|
|
74
|
+
parentTransactionIndex?: number,
|
|
75
|
+
) {
|
|
76
|
+
const parentCollectionName = parentTransactionIndex !== undefined ? transactionBody[parentTransactionIndex].props.collectionName : null;
|
|
77
|
+
const foreignKeyFieldName = ctx.meta.relations.find(relation => relation.from.collection === collectionName && relation.to.collection === parentCollectionName)?.from.field;
|
|
78
|
+
const collectionFieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
|
|
79
|
+
const payload: any = {};
|
|
80
|
+
for (let index = 0; index < collectionFieldNames.length; index++) {
|
|
81
|
+
const fieldName = collectionFieldNames[index];
|
|
82
|
+
const isForeignKeyField = fieldName === foreignKeyFieldName;
|
|
83
|
+
if (isForeignKeyField) {
|
|
84
|
+
payload[fieldName] = `{{ responses[${parentTransactionIndex}].data.id }}`
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
payload[fieldName] = entry[fieldName];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const localTransactionIndex = transactionBody.length;
|
|
92
|
+
if (payload.id) {
|
|
93
|
+
const localPayload = {
|
|
94
|
+
[foreignKeyFieldName]: payload[foreignKeyFieldName],
|
|
95
|
+
};
|
|
96
|
+
transactionBody.push({
|
|
97
|
+
method: "updateMany",
|
|
98
|
+
props: {
|
|
99
|
+
collectionName: collectionName,
|
|
100
|
+
data: localPayload,
|
|
101
|
+
filter: { id: payload.id },
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
transactionBody.push({
|
|
106
|
+
method: "createOne",
|
|
107
|
+
props: { collectionName: collectionName, data: payload },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const childrenRelations = ctx.meta.relations.filter((relation) => relation.to.collection === collectionName);
|
|
112
|
+
const childrenCollectionNames = childrenRelations.map((relation) => relation.from.collection);
|
|
113
|
+
for (let index = 0; index < childrenCollectionNames.length; index++) {
|
|
114
|
+
const childrenCollectionName = childrenCollectionNames[index];
|
|
115
|
+
const childrenEntries = entry[childrenCollectionName];
|
|
116
|
+
if (childrenEntries) {
|
|
117
|
+
for (let index = 0; index < childrenEntries.length; index++) {
|
|
118
|
+
const childrenEntry = childrenEntries[index];
|
|
119
|
+
handleEntryRecursive(transactionBody, childrenCollectionName, childrenEntry, localTransactionIndex);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const transactionBody: any[] = [];
|
|
126
|
+
|
|
127
|
+
handleEntryRecursive(transactionBody, collectionName, entry);
|
|
128
|
+
|
|
129
|
+
return transactionBody
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function parseDetailViewValues(ctx: CTX, collectionName: string, values: Record<string, any>) {
|
|
133
|
+
const forignFieldNames = ctx.meta.relations
|
|
134
|
+
.filter((relation) => relation.from.collection === collectionName)
|
|
135
|
+
.map((relation) => relation.from.field);
|
|
136
|
+
const childCollectionNames = ctx.meta.relations
|
|
137
|
+
.filter((relation) => relation.to.collection === collectionName)
|
|
138
|
+
.map((relation) => relation.from.collection);
|
|
139
|
+
|
|
140
|
+
for (const [key, value] of Object.entries(values)) {
|
|
141
|
+
if (forignFieldNames.includes(key)) {
|
|
142
|
+
if (typeof value === 'number') {
|
|
143
|
+
values[key] = {
|
|
144
|
+
id: value,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} else if (childCollectionNames.includes(key)) {
|
|
148
|
+
for (let index = 0; index < values[key].length; index++) {
|
|
149
|
+
parseDetailViewValues(ctx, key, values[key][index]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function getCollectionFields(ctx: CTX, collectionName: string) {
|
|
156
|
+
let returnedData: DetailFormField[] = [];
|
|
157
|
+
|
|
158
|
+
const collectionFields = ctx.meta.collections[collectionName].fields;
|
|
159
|
+
const isSingleton = ctx.meta.collections[collectionName].singleton;
|
|
160
|
+
for (const [fieldName, value] of Object.entries(collectionFields)) {
|
|
161
|
+
|
|
162
|
+
if (isSingleton && fieldName === "id") {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
returnedData.push({
|
|
167
|
+
type: "input",
|
|
168
|
+
key: fieldName,
|
|
169
|
+
label: fieldName,
|
|
170
|
+
disabled: fieldName == "id",
|
|
171
|
+
placeholder: fieldName == "id" ? "AUTO_GENERATED" : "",
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return returnedData;
|
|
176
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from "svelte";
|
|
3
|
+
import { MergeView } from "@codemirror/merge";
|
|
4
|
+
import { EditorView } from "@codemirror/view";
|
|
5
|
+
import { EditorState } from "@codemirror/state";
|
|
6
|
+
import { basicSetup } from "codemirror";
|
|
7
|
+
import { javascript } from "@codemirror/lang-javascript";
|
|
8
|
+
import { sql } from "@codemirror/lang-sql";
|
|
9
|
+
import { cn } from "../utils";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
type: "javascript" | "typescript" | "json" | "sql";
|
|
13
|
+
original: string;
|
|
14
|
+
modified: string;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
type,
|
|
20
|
+
original,
|
|
21
|
+
modified,
|
|
22
|
+
class: className,
|
|
23
|
+
...props
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
let editorContainer: HTMLDivElement;
|
|
27
|
+
let mergeView: MergeView | null = null;
|
|
28
|
+
|
|
29
|
+
const getLanguageExtension = () => {
|
|
30
|
+
switch (type) {
|
|
31
|
+
case 'javascript':
|
|
32
|
+
case 'typescript':
|
|
33
|
+
case 'json':
|
|
34
|
+
return javascript();
|
|
35
|
+
case 'sql':
|
|
36
|
+
return sql();
|
|
37
|
+
default:
|
|
38
|
+
return javascript();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
onMount(() => {
|
|
43
|
+
const langExtension = getLanguageExtension();
|
|
44
|
+
|
|
45
|
+
mergeView = new MergeView({
|
|
46
|
+
a: {
|
|
47
|
+
doc: original,
|
|
48
|
+
extensions: [
|
|
49
|
+
basicSetup,
|
|
50
|
+
langExtension,
|
|
51
|
+
EditorView.editable.of(false),
|
|
52
|
+
EditorState.readOnly.of(true),
|
|
53
|
+
EditorView.theme({
|
|
54
|
+
'&': {
|
|
55
|
+
backgroundColor: 'transparent',
|
|
56
|
+
},
|
|
57
|
+
'.cm-gutters': {
|
|
58
|
+
backgroundColor: 'transparent',
|
|
59
|
+
border: 'none',
|
|
60
|
+
},
|
|
61
|
+
'.cm-content': {
|
|
62
|
+
paddingTop: '10px',
|
|
63
|
+
paddingBottom: '1px',
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
b: {
|
|
69
|
+
doc: modified,
|
|
70
|
+
extensions: [
|
|
71
|
+
basicSetup,
|
|
72
|
+
langExtension,
|
|
73
|
+
EditorView.editable.of(false),
|
|
74
|
+
EditorState.readOnly.of(true),
|
|
75
|
+
EditorView.theme({
|
|
76
|
+
'&': {
|
|
77
|
+
backgroundColor: 'transparent',
|
|
78
|
+
},
|
|
79
|
+
'.cm-gutters': {
|
|
80
|
+
backgroundColor: 'transparent',
|
|
81
|
+
border: 'none',
|
|
82
|
+
},
|
|
83
|
+
'.cm-content': {
|
|
84
|
+
paddingTop: '10px',
|
|
85
|
+
paddingBottom: '1px',
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
parent: editorContainer
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
onDestroy(() => {
|
|
95
|
+
mergeView?.destroy();
|
|
96
|
+
});
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<div class={cn("w-full resize-y rounded-md border bg-muted/30 shadow-sm", className)}>
|
|
100
|
+
<div
|
|
101
|
+
bind:this={editorContainer}
|
|
102
|
+
class="editor pl-2"
|
|
103
|
+
style="height: 100%; width: 100%;"
|
|
104
|
+
></div>
|
|
105
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { calculateDrawerWidth } from "../utils";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
import { fade, fly } from "svelte/transition";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
children?: Snippet<[]>;
|
|
8
|
+
onHide?: () => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { onHide, children }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<button
|
|
15
|
+
transition:fade={{ duration: 250 }}
|
|
16
|
+
onclick={() => onHide?.()}
|
|
17
|
+
class="backgroundDrawerButton fixed left-0 top-0 z-30 h-screen w-screen bg-background opacity-50 cursor-default"
|
|
18
|
+
aria-label="background used to hide the background"
|
|
19
|
+
></button>
|
|
20
|
+
|
|
21
|
+
<!-- the drawer -->
|
|
22
|
+
<div
|
|
23
|
+
transition:fly={{ x: "100%", duration: 250 }}
|
|
24
|
+
class="fixed right-0 top-0 z-30 flex h-full w-full flex-col border-l bg-background"
|
|
25
|
+
style="max-width: {calculateDrawerWidth()}px;"
|
|
26
|
+
>
|
|
27
|
+
{@render children?.()}
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { loadExtensionComponents } from "../extensions/extensionUtils";
|
|
4
|
+
import { getStudioContext } from "../context";
|
|
5
|
+
|
|
6
|
+
const { ctx } = getStudioContext();
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
name: string;
|
|
10
|
+
filterByExtensions?: string[];
|
|
11
|
+
value?: unknown;
|
|
12
|
+
children?: Snippet<[]>;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
name,
|
|
18
|
+
filterByExtensions,
|
|
19
|
+
value = $bindable(),
|
|
20
|
+
children,
|
|
21
|
+
...props
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
|
|
24
|
+
const Components = loadExtensionComponents(ctx, name, filterByExtensions);
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{#if Components.length}
|
|
28
|
+
{#each Components as Component}
|
|
29
|
+
<Component bind:value {...props} />
|
|
30
|
+
{/each}
|
|
31
|
+
{:else}
|
|
32
|
+
{@render children?.()}
|
|
33
|
+
{/if}
|