@lobb-js/studio 0.30.0 → 0.32.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/actions.d.ts +2 -0
- package/dist/components/Studio.svelte +1 -10
- package/dist/components/dataTable/dataTable.svelte +104 -39
- package/dist/components/dataTable/dataTable.svelte.d.ts +4 -1
- package/dist/components/dataTable/fieldCell.svelte +7 -4
- package/dist/components/dataTable/fieldCell.svelte.d.ts +2 -2
- package/dist/components/dataTable/filter.svelte +0 -15
- package/dist/components/dataTable/header.svelte +13 -14
- package/dist/components/dataTable/header.svelte.d.ts +3 -2
- package/dist/components/dataTable/numberCell.svelte +28 -0
- package/dist/components/dataTable/numberCell.svelte.d.ts +7 -0
- package/dist/components/dataTable/polymorphicFieldCell.svelte +3 -3
- package/dist/components/dataTable/polymorphicFieldCell.svelte.d.ts +2 -2
- package/dist/components/dataTablePopup/dataTablePopup.svelte +17 -0
- package/dist/components/dataTablePopup/dataTablePopup.svelte.d.ts +2 -0
- package/dist/components/detailView/create/createDetailView.svelte +28 -54
- package/dist/components/detailView/create/createDetailView.svelte.d.ts +4 -3
- package/dist/components/detailView/create/createDetailViewChildren.svelte +113 -0
- package/dist/components/detailView/create/createDetailViewChildren.svelte.d.ts +9 -0
- package/dist/components/detailView/create/createManyView.svelte +2 -2
- package/dist/components/detailView/detailView.svelte +6 -1
- package/dist/components/detailView/fieldInput.svelte +7 -5
- package/dist/components/detailView/update/updateDetailView.svelte +46 -40
- package/dist/components/detailView/update/updateDetailView.svelte.d.ts +5 -3
- package/dist/components/detailView/update/updateDetailViewButton.svelte +0 -1
- package/dist/components/detailView/update/updateDetailViewChildren.svelte +122 -0
- package/dist/components/detailView/update/updateDetailViewChildren.svelte.d.ts +10 -0
- package/dist/components/detailView/utils.d.ts +1 -2
- package/dist/components/importButton.svelte +1 -1
- package/dist/components/miniSidebar.svelte +6 -3
- package/dist/components/richTextEditor.svelte +2 -0
- package/dist/components/routes/extensions/extension.svelte +1 -1
- package/dist/components/routes/home.svelte +35 -21
- package/dist/components/ui/input/numberInput.svelte +104 -0
- package/dist/components/ui/input/numberInput.svelte.d.ts +9 -0
- package/dist/components/workflowEditor.svelte +6 -4
- package/package.json +4 -3
- package/src/lib/actions.ts +2 -0
- package/src/lib/components/Studio.svelte +1 -10
- package/src/lib/components/dataTable/dataTable.svelte +104 -39
- package/src/lib/components/dataTable/fieldCell.svelte +7 -4
- package/src/lib/components/dataTable/filter.svelte +0 -15
- package/src/lib/components/dataTable/header.svelte +13 -14
- package/src/lib/components/dataTable/numberCell.svelte +28 -0
- package/src/lib/components/dataTable/polymorphicFieldCell.svelte +3 -3
- package/src/lib/components/dataTablePopup/dataTablePopup.svelte +17 -0
- package/src/lib/components/detailView/create/createDetailView.svelte +28 -54
- package/src/lib/components/detailView/create/createDetailViewChildren.svelte +113 -0
- package/src/lib/components/detailView/create/createManyView.svelte +2 -2
- package/src/lib/components/detailView/detailView.svelte +6 -1
- package/src/lib/components/detailView/fieldInput.svelte +7 -5
- package/src/lib/components/detailView/update/updateDetailView.svelte +46 -40
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +0 -1
- package/src/lib/components/detailView/update/updateDetailViewChildren.svelte +122 -0
- package/src/lib/components/detailView/utils.ts +1 -1
- package/src/lib/components/importButton.svelte +1 -1
- package/src/lib/components/miniSidebar.svelte +6 -3
- package/src/lib/components/richTextEditor.svelte +2 -0
- package/src/lib/components/routes/extensions/extension.svelte +1 -1
- package/src/lib/components/routes/home.svelte +35 -21
- package/src/lib/components/ui/input/numberInput.svelte +104 -0
- package/src/lib/components/workflowEditor.svelte +6 -4
- package/dist/components/breadCrumbs.svelte +0 -61
- package/dist/components/breadCrumbs.svelte.d.ts +0 -3
- package/dist/components/detailView/update/detailViewChildren.svelte +0 -61
- package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +0 -9
- package/dist/components/header.svelte +0 -45
- package/dist/components/header.svelte.d.ts +0 -6
- package/src/lib/components/breadCrumbs.svelte +0 -61
- package/src/lib/components/detailView/update/detailViewChildren.svelte +0 -61
- package/src/lib/components/header.svelte +0 -45
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import IMask from "imask";
|
|
3
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
4
|
+
import { cn } from "../../../utils.js";
|
|
5
|
+
|
|
6
|
+
// Locale-neutral grouping: space for thousands, dot for decimals
|
|
7
|
+
// (ISO 31-0). Norwegian, French, scientific contexts — same shape, and
|
|
8
|
+
// space can't be confused with comma-as-decimal vs comma-as-thousands.
|
|
9
|
+
const THOUSANDS = " ";
|
|
10
|
+
const RADIX = ".";
|
|
11
|
+
|
|
12
|
+
type Props = Omit<HTMLInputAttributes, "type" | "value" | "step"> & {
|
|
13
|
+
value?: number | string | null;
|
|
14
|
+
// 0 = integer-only, > 0 = allow that many decimal places.
|
|
15
|
+
scale?: number;
|
|
16
|
+
// When false, render a plain browser number input — no masking, no
|
|
17
|
+
// formatting. Default false so the component is safe to drop in
|
|
18
|
+
// anywhere; opt into grouping only where it makes sense.
|
|
19
|
+
groupDigits?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
value = $bindable<number | string | null | undefined>(),
|
|
24
|
+
scale = 0,
|
|
25
|
+
groupDigits = false,
|
|
26
|
+
class: className,
|
|
27
|
+
...rest
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
let inputEl: HTMLInputElement | undefined = $state();
|
|
31
|
+
let mask: ReturnType<typeof IMask> | null = null;
|
|
32
|
+
// Re-entrance guard: imask's `accept` event fires when we programmatically
|
|
33
|
+
// set unmaskedValue, which would create a sync→reflect loop with the
|
|
34
|
+
// "external value changed" effect below.
|
|
35
|
+
let applyingExternal = false;
|
|
36
|
+
|
|
37
|
+
$effect(() => {
|
|
38
|
+
if (!groupDigits) return;
|
|
39
|
+
if (!inputEl) return;
|
|
40
|
+
mask = IMask(inputEl, {
|
|
41
|
+
mask: Number,
|
|
42
|
+
thousandsSeparator: THOUSANDS,
|
|
43
|
+
radix: RADIX,
|
|
44
|
+
scale,
|
|
45
|
+
signed: true,
|
|
46
|
+
padFractionalZeros: false,
|
|
47
|
+
normalizeZeros: true,
|
|
48
|
+
// Allow the user to type either radix; imask remaps to the chosen
|
|
49
|
+
// one and ignores stray locale separators on input.
|
|
50
|
+
mapToRadix: [",", "."],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
mask.on("accept", () => {
|
|
54
|
+
if (applyingExternal) return;
|
|
55
|
+
const unmasked = mask!.unmaskedValue;
|
|
56
|
+
value = unmasked === "" ? null : Number(unmasked);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
applyingExternal = true;
|
|
60
|
+
mask.unmaskedValue = value == null ? "" : String(value);
|
|
61
|
+
applyingExternal = false;
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
mask?.destroy();
|
|
65
|
+
mask = null;
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Sync external value changes back into the mask (e.g. form reset, parent
|
|
70
|
+
// clearing the value). No-op when grouping is off.
|
|
71
|
+
$effect(() => {
|
|
72
|
+
const v = value;
|
|
73
|
+
if (!mask) return;
|
|
74
|
+
const next = v == null ? "" : String(v);
|
|
75
|
+
if (mask.unmaskedValue === next) return;
|
|
76
|
+
applyingExternal = true;
|
|
77
|
+
mask.unmaskedValue = next;
|
|
78
|
+
applyingExternal = false;
|
|
79
|
+
});
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
{#if groupDigits}
|
|
83
|
+
<input
|
|
84
|
+
bind:this={inputEl}
|
|
85
|
+
type="text"
|
|
86
|
+
inputmode={scale > 0 ? "decimal" : "numeric"}
|
|
87
|
+
class={cn(
|
|
88
|
+
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
89
|
+
className,
|
|
90
|
+
)}
|
|
91
|
+
{...rest}
|
|
92
|
+
/>
|
|
93
|
+
{:else}
|
|
94
|
+
<input
|
|
95
|
+
type="number"
|
|
96
|
+
step={scale > 0 ? "any" : "1"}
|
|
97
|
+
class={cn(
|
|
98
|
+
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
bind:value
|
|
102
|
+
{...rest}
|
|
103
|
+
/>
|
|
104
|
+
{/if}
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
const reponse = await lobb.createOne("core_workflows", workflow);
|
|
96
|
+
const reponse = await lobb.createOne("core_workflows", { data: workflow });
|
|
97
97
|
const result = await reponse.json();
|
|
98
98
|
const workflowEntry = result.data;
|
|
99
99
|
goto(`/studio/workflows/${workflowEntry.name}`);
|
|
@@ -107,9 +107,11 @@
|
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
const reponse = await lobb.updateOne("core_workflows", workflow.id, {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
data: {
|
|
111
|
+
name: workflow.name,
|
|
112
|
+
event_name: workflow.event_name,
|
|
113
|
+
handler: workflow.handler,
|
|
114
|
+
},
|
|
113
115
|
});
|
|
114
116
|
const result = await reponse.json();
|
|
115
117
|
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import * as Breadcrumb from "./ui/breadcrumb";
|
|
3
|
-
import { mediaQueries } from "../utils";
|
|
4
|
-
import { page } from "$app/state";
|
|
5
|
-
import { goto } from "$app/navigation";
|
|
6
|
-
|
|
7
|
-
const isSmall = $derived(!mediaQueries.sm.current);
|
|
8
|
-
const pathNames = $derived(
|
|
9
|
-
page.url.pathname
|
|
10
|
-
.replace("/studio", "")
|
|
11
|
-
.split("/")
|
|
12
|
-
.filter((el: any) => el !== "")
|
|
13
|
-
.slice(0, 3),
|
|
14
|
-
);
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
{#if isSmall}
|
|
18
|
-
<div class="text-muted-foreground text-sm">
|
|
19
|
-
{pathNames[pathNames.length - 1] || "Home"}
|
|
20
|
-
</div>
|
|
21
|
-
{:else}
|
|
22
|
-
<Breadcrumb.Root>
|
|
23
|
-
<Breadcrumb.List class="flex-nowrap">
|
|
24
|
-
<Breadcrumb.Item>
|
|
25
|
-
{#if pathNames.length === 0}
|
|
26
|
-
<Breadcrumb.Page>Home</Breadcrumb.Page>
|
|
27
|
-
{:else}
|
|
28
|
-
<Breadcrumb.Link
|
|
29
|
-
class="cursor-pointer"
|
|
30
|
-
onclick={() => goto("/studio")}
|
|
31
|
-
>
|
|
32
|
-
Home
|
|
33
|
-
</Breadcrumb.Link>
|
|
34
|
-
<Breadcrumb.Separator />
|
|
35
|
-
{/if}
|
|
36
|
-
</Breadcrumb.Item>
|
|
37
|
-
{#each pathNames as path, index}
|
|
38
|
-
{@const isLastElement = pathNames.length - 1 === index}
|
|
39
|
-
{@const currentFullPaths = pathNames
|
|
40
|
-
.slice(0, index + 1)
|
|
41
|
-
.join("/")}
|
|
42
|
-
<Breadcrumb.Item>
|
|
43
|
-
{#if isLastElement}
|
|
44
|
-
<Breadcrumb.Page>{path}</Breadcrumb.Page>
|
|
45
|
-
{:else}
|
|
46
|
-
<Breadcrumb.Link
|
|
47
|
-
class="cursor-pointer"
|
|
48
|
-
onclick={() =>
|
|
49
|
-
goto(`/studio/${currentFullPaths}`)}
|
|
50
|
-
>
|
|
51
|
-
{path}
|
|
52
|
-
</Breadcrumb.Link>
|
|
53
|
-
{/if}
|
|
54
|
-
</Breadcrumb.Item>
|
|
55
|
-
{#if !isLastElement}
|
|
56
|
-
<Breadcrumb.Separator />
|
|
57
|
-
{/if}
|
|
58
|
-
{/each}
|
|
59
|
-
</Breadcrumb.List>
|
|
60
|
-
</Breadcrumb.Root>
|
|
61
|
-
{/if}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import DataTable from "../../dataTable/dataTable.svelte";
|
|
3
|
-
import { getStudioContext } from "../../../context";
|
|
4
|
-
import { Table, Link } from "lucide-svelte";
|
|
5
|
-
|
|
6
|
-
const { ctx } = getStudioContext();
|
|
7
|
-
|
|
8
|
-
import type { Changes, ChildrenChanges } from "../utils";
|
|
9
|
-
|
|
10
|
-
interface LocalProp {
|
|
11
|
-
collectionName: string;
|
|
12
|
-
entry: any;
|
|
13
|
-
activeChanges?: Changes;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let { collectionName, entry, activeChanges }: LocalProp = $props();
|
|
17
|
-
|
|
18
|
-
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
19
|
-
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
20
|
-
|
|
21
|
-
// Ensure all child collection slots exist before the template renders so bind:changes works
|
|
22
|
-
$effect.pre(() => {
|
|
23
|
-
if (!activeChanges) return;
|
|
24
|
-
for (const child of children) {
|
|
25
|
-
if (!activeChanges.children[child.collection]) {
|
|
26
|
-
activeChanges.children[child.collection] = { created: [], updated: [], deleted: [], linked: [], unlinked: [] };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
</script>
|
|
31
|
-
|
|
32
|
-
{#if children.length}
|
|
33
|
-
<div class="flex flex-col gap-3 border-t p-4">
|
|
34
|
-
<div class="flex items-center gap-2">
|
|
35
|
-
<Link size="14" class="text-muted-foreground" />
|
|
36
|
-
<span class="text-sm font-medium">Sub Records</span>
|
|
37
|
-
</div>
|
|
38
|
-
{#each children as child}
|
|
39
|
-
<div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
|
|
40
|
-
<DataTable
|
|
41
|
-
collectionName={child.collection}
|
|
42
|
-
searchParams={{ children_of: collectionName, parent_id: entry.id }}
|
|
43
|
-
parentContext={{ collectionName, recordId: entry.id }}
|
|
44
|
-
bind:changes={activeChanges!.children[child.collection]}
|
|
45
|
-
showImport={false}
|
|
46
|
-
showHeader={true}
|
|
47
|
-
showFooter={true}
|
|
48
|
-
showDelete={child.type === "fk" || child.type === "m2m"}
|
|
49
|
-
tableProps={{ showLastColumnBorder: false, showLastRowBorder: true }}
|
|
50
|
-
>
|
|
51
|
-
{#snippet headerLeft()}
|
|
52
|
-
<div class="flex items-center gap-2 px-1">
|
|
53
|
-
<Table size="14" class="text-muted-foreground" />
|
|
54
|
-
<span class="text-sm font-medium">{child.collection}</span>
|
|
55
|
-
</div>
|
|
56
|
-
{/snippet}
|
|
57
|
-
</DataTable>
|
|
58
|
-
</div>
|
|
59
|
-
{/each}
|
|
60
|
-
</div>
|
|
61
|
-
{/if}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Changes } from "../utils";
|
|
2
|
-
interface LocalProp {
|
|
3
|
-
collectionName: string;
|
|
4
|
-
entry: any;
|
|
5
|
-
activeChanges?: Changes;
|
|
6
|
-
}
|
|
7
|
-
declare const DetailViewChildren: import("svelte").Component<LocalProp, {}, "">;
|
|
8
|
-
type DetailViewChildren = ReturnType<typeof DetailViewChildren>;
|
|
9
|
-
export default DetailViewChildren;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import * as Tooltip from "./ui/tooltip";
|
|
3
|
-
import Button from "./ui/button/button.svelte";
|
|
4
|
-
import { Menu, Moon, Sun } from "lucide-svelte";
|
|
5
|
-
import BreadCrumbs from "./breadCrumbs.svelte";
|
|
6
|
-
import { toggleMode, mode } from "mode-watcher";
|
|
7
|
-
import { mediaQueries } from "../utils";
|
|
8
|
-
import { expandMiniSideBar } from "./miniSidebar.svelte";
|
|
9
|
-
|
|
10
|
-
let isSmallScreen = $derived(!mediaQueries.sm.current);
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<div
|
|
14
|
-
class="flex items-center justify-between border-b border-input bg-background px-3"
|
|
15
|
-
>
|
|
16
|
-
<div class="flex items-center gap-4">
|
|
17
|
-
{#if isSmallScreen}
|
|
18
|
-
<Menu
|
|
19
|
-
class="h-10 text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground"
|
|
20
|
-
style="left: 0.6rem; top: 0rem;"
|
|
21
|
-
size="18"
|
|
22
|
-
onclick={() => expandMiniSideBar()}
|
|
23
|
-
/>
|
|
24
|
-
{/if}
|
|
25
|
-
<BreadCrumbs />
|
|
26
|
-
</div>
|
|
27
|
-
<div class="flex h-full items-center gap-3">
|
|
28
|
-
<Tooltip.Root>
|
|
29
|
-
<Tooltip.Trigger>
|
|
30
|
-
<Button
|
|
31
|
-
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
32
|
-
variant="ghost"
|
|
33
|
-
size="icon"
|
|
34
|
-
onclick={toggleMode}
|
|
35
|
-
Icon={$mode === "light" ? Moon : Sun}
|
|
36
|
-
></Button>
|
|
37
|
-
</Tooltip.Trigger>
|
|
38
|
-
<Tooltip.Content side="bottom" sideOffset={7.5}
|
|
39
|
-
>{$mode === "light"
|
|
40
|
-
? "Night Mode"
|
|
41
|
-
: "Light Mode"}</Tooltip.Content
|
|
42
|
-
>
|
|
43
|
-
</Tooltip.Root>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import * as Breadcrumb from "./ui/breadcrumb";
|
|
3
|
-
import { mediaQueries } from "../utils";
|
|
4
|
-
import { page } from "$app/state";
|
|
5
|
-
import { goto } from "$app/navigation";
|
|
6
|
-
|
|
7
|
-
const isSmall = $derived(!mediaQueries.sm.current);
|
|
8
|
-
const pathNames = $derived(
|
|
9
|
-
page.url.pathname
|
|
10
|
-
.replace("/studio", "")
|
|
11
|
-
.split("/")
|
|
12
|
-
.filter((el: any) => el !== "")
|
|
13
|
-
.slice(0, 3),
|
|
14
|
-
);
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
{#if isSmall}
|
|
18
|
-
<div class="text-muted-foreground text-sm">
|
|
19
|
-
{pathNames[pathNames.length - 1] || "Home"}
|
|
20
|
-
</div>
|
|
21
|
-
{:else}
|
|
22
|
-
<Breadcrumb.Root>
|
|
23
|
-
<Breadcrumb.List class="flex-nowrap">
|
|
24
|
-
<Breadcrumb.Item>
|
|
25
|
-
{#if pathNames.length === 0}
|
|
26
|
-
<Breadcrumb.Page>Home</Breadcrumb.Page>
|
|
27
|
-
{:else}
|
|
28
|
-
<Breadcrumb.Link
|
|
29
|
-
class="cursor-pointer"
|
|
30
|
-
onclick={() => goto("/studio")}
|
|
31
|
-
>
|
|
32
|
-
Home
|
|
33
|
-
</Breadcrumb.Link>
|
|
34
|
-
<Breadcrumb.Separator />
|
|
35
|
-
{/if}
|
|
36
|
-
</Breadcrumb.Item>
|
|
37
|
-
{#each pathNames as path, index}
|
|
38
|
-
{@const isLastElement = pathNames.length - 1 === index}
|
|
39
|
-
{@const currentFullPaths = pathNames
|
|
40
|
-
.slice(0, index + 1)
|
|
41
|
-
.join("/")}
|
|
42
|
-
<Breadcrumb.Item>
|
|
43
|
-
{#if isLastElement}
|
|
44
|
-
<Breadcrumb.Page>{path}</Breadcrumb.Page>
|
|
45
|
-
{:else}
|
|
46
|
-
<Breadcrumb.Link
|
|
47
|
-
class="cursor-pointer"
|
|
48
|
-
onclick={() =>
|
|
49
|
-
goto(`/studio/${currentFullPaths}`)}
|
|
50
|
-
>
|
|
51
|
-
{path}
|
|
52
|
-
</Breadcrumb.Link>
|
|
53
|
-
{/if}
|
|
54
|
-
</Breadcrumb.Item>
|
|
55
|
-
{#if !isLastElement}
|
|
56
|
-
<Breadcrumb.Separator />
|
|
57
|
-
{/if}
|
|
58
|
-
{/each}
|
|
59
|
-
</Breadcrumb.List>
|
|
60
|
-
</Breadcrumb.Root>
|
|
61
|
-
{/if}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import DataTable from "../../dataTable/dataTable.svelte";
|
|
3
|
-
import { getStudioContext } from "../../../context";
|
|
4
|
-
import { Table, Link } from "lucide-svelte";
|
|
5
|
-
|
|
6
|
-
const { ctx } = getStudioContext();
|
|
7
|
-
|
|
8
|
-
import type { Changes, ChildrenChanges } from "../utils";
|
|
9
|
-
|
|
10
|
-
interface LocalProp {
|
|
11
|
-
collectionName: string;
|
|
12
|
-
entry: any;
|
|
13
|
-
activeChanges?: Changes;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let { collectionName, entry, activeChanges }: LocalProp = $props();
|
|
17
|
-
|
|
18
|
-
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
19
|
-
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
20
|
-
|
|
21
|
-
// Ensure all child collection slots exist before the template renders so bind:changes works
|
|
22
|
-
$effect.pre(() => {
|
|
23
|
-
if (!activeChanges) return;
|
|
24
|
-
for (const child of children) {
|
|
25
|
-
if (!activeChanges.children[child.collection]) {
|
|
26
|
-
activeChanges.children[child.collection] = { created: [], updated: [], deleted: [], linked: [], unlinked: [] };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
</script>
|
|
31
|
-
|
|
32
|
-
{#if children.length}
|
|
33
|
-
<div class="flex flex-col gap-3 border-t p-4">
|
|
34
|
-
<div class="flex items-center gap-2">
|
|
35
|
-
<Link size="14" class="text-muted-foreground" />
|
|
36
|
-
<span class="text-sm font-medium">Sub Records</span>
|
|
37
|
-
</div>
|
|
38
|
-
{#each children as child}
|
|
39
|
-
<div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
|
|
40
|
-
<DataTable
|
|
41
|
-
collectionName={child.collection}
|
|
42
|
-
searchParams={{ children_of: collectionName, parent_id: entry.id }}
|
|
43
|
-
parentContext={{ collectionName, recordId: entry.id }}
|
|
44
|
-
bind:changes={activeChanges!.children[child.collection]}
|
|
45
|
-
showImport={false}
|
|
46
|
-
showHeader={true}
|
|
47
|
-
showFooter={true}
|
|
48
|
-
showDelete={child.type === "fk" || child.type === "m2m"}
|
|
49
|
-
tableProps={{ showLastColumnBorder: false, showLastRowBorder: true }}
|
|
50
|
-
>
|
|
51
|
-
{#snippet headerLeft()}
|
|
52
|
-
<div class="flex items-center gap-2 px-1">
|
|
53
|
-
<Table size="14" class="text-muted-foreground" />
|
|
54
|
-
<span class="text-sm font-medium">{child.collection}</span>
|
|
55
|
-
</div>
|
|
56
|
-
{/snippet}
|
|
57
|
-
</DataTable>
|
|
58
|
-
</div>
|
|
59
|
-
{/each}
|
|
60
|
-
</div>
|
|
61
|
-
{/if}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import * as Tooltip from "./ui/tooltip";
|
|
3
|
-
import Button from "./ui/button/button.svelte";
|
|
4
|
-
import { Menu, Moon, Sun } from "lucide-svelte";
|
|
5
|
-
import BreadCrumbs from "./breadCrumbs.svelte";
|
|
6
|
-
import { toggleMode, mode } from "mode-watcher";
|
|
7
|
-
import { mediaQueries } from "../utils";
|
|
8
|
-
import { expandMiniSideBar } from "./miniSidebar.svelte";
|
|
9
|
-
|
|
10
|
-
let isSmallScreen = $derived(!mediaQueries.sm.current);
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<div
|
|
14
|
-
class="flex items-center justify-between border-b border-input bg-background px-3"
|
|
15
|
-
>
|
|
16
|
-
<div class="flex items-center gap-4">
|
|
17
|
-
{#if isSmallScreen}
|
|
18
|
-
<Menu
|
|
19
|
-
class="h-10 text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground"
|
|
20
|
-
style="left: 0.6rem; top: 0rem;"
|
|
21
|
-
size="18"
|
|
22
|
-
onclick={() => expandMiniSideBar()}
|
|
23
|
-
/>
|
|
24
|
-
{/if}
|
|
25
|
-
<BreadCrumbs />
|
|
26
|
-
</div>
|
|
27
|
-
<div class="flex h-full items-center gap-3">
|
|
28
|
-
<Tooltip.Root>
|
|
29
|
-
<Tooltip.Trigger>
|
|
30
|
-
<Button
|
|
31
|
-
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
32
|
-
variant="ghost"
|
|
33
|
-
size="icon"
|
|
34
|
-
onclick={toggleMode}
|
|
35
|
-
Icon={$mode === "light" ? Moon : Sun}
|
|
36
|
-
></Button>
|
|
37
|
-
</Tooltip.Trigger>
|
|
38
|
-
<Tooltip.Content side="bottom" sideOffset={7.5}
|
|
39
|
-
>{$mode === "light"
|
|
40
|
-
? "Night Mode"
|
|
41
|
-
: "Light Mode"}</Tooltip.Content
|
|
42
|
-
>
|
|
43
|
-
</Tooltip.Root>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|