@lobb-js/studio 0.19.1 → 0.21.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 +1 -0
- package/dist/components/dataTable/fieldCell.svelte +38 -37
- package/dist/components/dataTable/polymorphicFieldCell.svelte +43 -0
- package/dist/components/dataTable/polymorphicFieldCell.svelte.d.ts +9 -0
- package/dist/components/dataTable/utils.js +27 -17
- package/dist/components/dataTableDrawer/dataTableDrawer.svelte +14 -10
- package/dist/components/dataTableDrawer/dataTableDrawer.svelte.d.ts +1 -0
- package/dist/components/detailView/create/createDetailView.svelte +38 -69
- package/dist/components/detailView/fieldInput.svelte +27 -11
- package/dist/components/detailView/fieldInput.svelte.d.ts +1 -1
- package/dist/components/detailView/update/updateDetailView.svelte +26 -30
- package/dist/components/detailView/utils.d.ts +5 -2
- package/dist/components/detailView/utils.js +53 -71
- package/dist/components/drawer.svelte +24 -8
- package/dist/components/drawer.svelte.d.ts +1 -0
- package/dist/components/polymorphicInput.svelte +141 -0
- package/dist/components/polymorphicInput.svelte.d.ts +10 -0
- package/dist/extensions/extension.types.d.ts +2 -0
- package/dist/extensions/extensionUtils.js +2 -0
- package/dist/relations.d.ts +14 -0
- package/dist/relations.js +47 -0
- package/dist/store.types.d.ts +1 -0
- package/dist/utils.d.ts +0 -3
- package/dist/utils.js +0 -21
- package/package.json +2 -2
- package/src/lib/actions.ts +1 -0
- package/src/lib/components/dataTable/fieldCell.svelte +38 -37
- package/src/lib/components/dataTable/polymorphicFieldCell.svelte +43 -0
- package/src/lib/components/dataTable/utils.ts +21 -18
- package/src/lib/components/dataTableDrawer/dataTableDrawer.svelte +14 -10
- package/src/lib/components/detailView/create/createDetailView.svelte +38 -69
- package/src/lib/components/detailView/fieldInput.svelte +27 -11
- package/src/lib/components/detailView/update/updateDetailView.svelte +26 -30
- package/src/lib/components/detailView/utils.ts +44 -75
- package/src/lib/components/drawer.svelte +24 -8
- package/src/lib/components/polymorphicInput.svelte +141 -0
- package/src/lib/extensions/extension.types.ts +2 -1
- package/src/lib/extensions/extensionUtils.ts +2 -0
- package/src/lib/relations.ts +52 -0
- package/src/lib/store.types.ts +1 -0
- package/src/lib/utils.ts +0 -21
|
@@ -1,30 +1,46 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { calculateDrawerWidth } from "../utils";
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
|
-
import { fade
|
|
4
|
+
import { fade } from "svelte/transition";
|
|
5
|
+
import { cubicOut } from "svelte/easing";
|
|
5
6
|
import Portal from "svelte-portal";
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
children?: Snippet<[]>;
|
|
9
10
|
onHide?: () => Promise<void>;
|
|
11
|
+
position?: "side" | "bottom";
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
let { onHide, children }: Props = $props();
|
|
14
|
+
let { onHide, children, position = "side" }: Props = $props();
|
|
15
|
+
|
|
16
|
+
function slide(_node: Element, { duration = 250, axis }: { duration?: number; axis: "x" | "y" }) {
|
|
17
|
+
return {
|
|
18
|
+
duration,
|
|
19
|
+
easing: cubicOut,
|
|
20
|
+
css: (t: number) => {
|
|
21
|
+
const offset = (1 - t) * 100;
|
|
22
|
+
return axis === "y"
|
|
23
|
+
? `transform: translateY(${offset}%)`
|
|
24
|
+
: `transform: translateX(${offset}%)`;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
13
28
|
</script>
|
|
14
29
|
|
|
15
30
|
<Portal target="body">
|
|
16
31
|
<button
|
|
17
|
-
transition:fade={{ duration:
|
|
32
|
+
transition:fade={{ duration: 200 }}
|
|
18
33
|
onclick={() => onHide?.()}
|
|
19
|
-
class="backgroundDrawerButton fixed left-0 top-0 z-40 h-screen w-screen bg-
|
|
34
|
+
class="backgroundDrawerButton fixed left-0 top-0 z-40 h-screen w-screen bg-black/50 cursor-default"
|
|
20
35
|
aria-label="background used to hide the background"
|
|
21
36
|
></button>
|
|
22
37
|
|
|
23
|
-
<!-- the drawer -->
|
|
24
38
|
<div
|
|
25
|
-
transition:
|
|
26
|
-
class=
|
|
27
|
-
|
|
39
|
+
transition:slide={{ axis: position === "bottom" ? "y" : "x" }}
|
|
40
|
+
class={position === "bottom"
|
|
41
|
+
? "fixed bottom-0 left-0 z-40 flex h-[60vh] w-full flex-col border-t bg-background"
|
|
42
|
+
: "fixed right-0 top-0 z-40 flex h-full w-full flex-col border-l bg-background"}
|
|
43
|
+
style={position === "side" ? `max-width: ${calculateDrawerWidth()}px;` : ""}
|
|
28
44
|
>
|
|
29
45
|
{@render children?.()}
|
|
30
46
|
</div>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Button from "./ui/button/button.svelte";
|
|
3
|
+
import DataTable from "./dataTable/dataTable.svelte";
|
|
4
|
+
import Drawer from "./drawer.svelte";
|
|
5
|
+
import * as Popover from "./ui/popover/index";
|
|
6
|
+
import { getCollectionPrimaryField } from "./dataTable/utils";
|
|
7
|
+
import { getStudioContext } from "../context";
|
|
8
|
+
import { ArrowLeft, Link, ChevronDown } from "lucide-svelte";
|
|
9
|
+
|
|
10
|
+
const { ctx } = getStudioContext();
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
collectionField: string;
|
|
14
|
+
idField: string;
|
|
15
|
+
targetCollections: string[];
|
|
16
|
+
entry: Record<string, any>;
|
|
17
|
+
destructive?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
collectionField,
|
|
22
|
+
idField,
|
|
23
|
+
targetCollections,
|
|
24
|
+
entry = $bindable(),
|
|
25
|
+
destructive,
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const selectedCollection = $derived(entry[collectionField] ?? null);
|
|
29
|
+
const selectedId = $derived(entry[idField]?.id ?? entry[idField] ?? null);
|
|
30
|
+
const primaryField = $derived(entry[idField] ? Object.values(entry[idField])[1] : null);
|
|
31
|
+
|
|
32
|
+
let collectionPopoverOpen = $state(false);
|
|
33
|
+
let recordDrawerOpen = $state(false);
|
|
34
|
+
|
|
35
|
+
function onCollectionChange(col: string) {
|
|
36
|
+
collectionPopoverOpen = false;
|
|
37
|
+
if (entry[collectionField] !== col) {
|
|
38
|
+
entry = { ...entry, [collectionField]: col, [idField]: null };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function onIdChange(e: Event) {
|
|
43
|
+
const raw = (e.target as HTMLInputElement).value;
|
|
44
|
+
const id = raw === "" ? null : Number(raw);
|
|
45
|
+
entry = { ...entry, [idField]: id === null ? null : { id } };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function onRecordSelect(record: any) {
|
|
49
|
+
const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection!);
|
|
50
|
+
const value: any = { id: record.id };
|
|
51
|
+
if (primaryFieldName) value[primaryFieldName] = record[primaryFieldName];
|
|
52
|
+
entry = { ...entry, [idField]: value };
|
|
53
|
+
recordDrawerOpen = false;
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted/30 {destructive ? 'border-destructive bg-destructive/10' : ''}">
|
|
58
|
+
<!-- Collection picker -->
|
|
59
|
+
<Popover.Root bind:open={collectionPopoverOpen}>
|
|
60
|
+
<Popover.Trigger>
|
|
61
|
+
{#snippet child({ props })}
|
|
62
|
+
<button
|
|
63
|
+
{...props}
|
|
64
|
+
class="flex shrink-0 items-center gap-1 h-6 px-2 rounded-sm border bg-muted text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
|
65
|
+
>
|
|
66
|
+
{selectedCollection ?? "NULL"}
|
|
67
|
+
<ChevronDown size="11" />
|
|
68
|
+
</button>
|
|
69
|
+
{/snippet}
|
|
70
|
+
</Popover.Trigger>
|
|
71
|
+
<Popover.Content class="w-48 p-2">
|
|
72
|
+
<div class="flex flex-col gap-1">
|
|
73
|
+
{#each targetCollections as col}
|
|
74
|
+
<Button
|
|
75
|
+
variant={selectedCollection === col ? "default" : "ghost"}
|
|
76
|
+
class="justify-start text-xs font-normal h-7 px-2"
|
|
77
|
+
onclick={() => onCollectionChange(col)}
|
|
78
|
+
>
|
|
79
|
+
{col}
|
|
80
|
+
</Button>
|
|
81
|
+
{/each}
|
|
82
|
+
</div>
|
|
83
|
+
</Popover.Content>
|
|
84
|
+
</Popover.Root>
|
|
85
|
+
|
|
86
|
+
<!-- Transparent id input -->
|
|
87
|
+
<input
|
|
88
|
+
placeholder="NULL"
|
|
89
|
+
type="number"
|
|
90
|
+
class="min-w-0 flex-1 bg-transparent outline-none text-xs placeholder:text-muted-foreground"
|
|
91
|
+
value={selectedId ?? ""}
|
|
92
|
+
oninput={onIdChange}
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
<!-- Primary field badge -->
|
|
96
|
+
{#if primaryField}
|
|
97
|
+
<div class="flex shrink-0 items-center bg-background rounded-full border h-6 px-3 shadow-sm">
|
|
98
|
+
{primaryField}
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
101
|
+
|
|
102
|
+
<!-- Select record button -->
|
|
103
|
+
{#if selectedCollection}
|
|
104
|
+
<Button
|
|
105
|
+
class="h-6 shrink-0 px-2 font-normal text-xs"
|
|
106
|
+
variant="outline"
|
|
107
|
+
onclick={() => (recordDrawerOpen = true)}
|
|
108
|
+
>
|
|
109
|
+
<Link size="13" />
|
|
110
|
+
Select Record
|
|
111
|
+
</Button>
|
|
112
|
+
{/if}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{#if recordDrawerOpen}
|
|
116
|
+
<Drawer onHide={async () => { recordDrawerOpen = false }}>
|
|
117
|
+
<div class="flex h-12 items-center gap-4 border-b px-4">
|
|
118
|
+
<Button
|
|
119
|
+
variant="outline"
|
|
120
|
+
onclick={() => (recordDrawerOpen = false)}
|
|
121
|
+
class="h-8 w-8 rounded-full text-xs font-normal"
|
|
122
|
+
Icon={ArrowLeft}
|
|
123
|
+
/>
|
|
124
|
+
<div class="flex items-center gap-2">
|
|
125
|
+
<div class="text-sm">Select record from</div>
|
|
126
|
+
<span class="rounded-md border bg-muted px-2 py-0.5 text-sm">
|
|
127
|
+
{selectedCollection}
|
|
128
|
+
</span>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="flex-1 overflow-y-auto bg-muted">
|
|
132
|
+
<DataTable
|
|
133
|
+
collectionName={selectedCollection!}
|
|
134
|
+
tableProps={{
|
|
135
|
+
showCheckboxes: false,
|
|
136
|
+
select: { onSelect: onRecordSelect },
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
</Drawer>
|
|
141
|
+
{/if}
|
|
@@ -57,7 +57,8 @@ export interface ExtensionUtils {
|
|
|
57
57
|
location: Location;
|
|
58
58
|
toast: typeof toast;
|
|
59
59
|
showDialog: typeof showDialog;
|
|
60
|
-
openDataTableDrawer: (props: { collectionName: string; filter?: Record<string, any>; title?: string; showHeader?: boolean; showFooter?: boolean }) => void;
|
|
60
|
+
openDataTableDrawer: (props: { collectionName: string; filter?: Record<string, any>; title?: string; showHeader?: boolean; showFooter?: boolean; position?: "side" | "bottom" }) => void;
|
|
61
|
+
emitEvent: (eventName: string, input: Record<string, any>) => Promise<Record<string, any>>;
|
|
61
62
|
components: Components;
|
|
62
63
|
mediaQueries: typeof mediaQueries;
|
|
63
64
|
intlDate: typeof intlDate;
|
|
@@ -8,6 +8,7 @@ import type { LobbClient } from "@lobb-js/sdk";
|
|
|
8
8
|
import type { CTX } from "../store.types";
|
|
9
9
|
import { toast } from "svelte-sonner";
|
|
10
10
|
import { showDialog, openDataTableDrawer } from "../actions";
|
|
11
|
+
import { emitEvent } from "../eventSystem";
|
|
11
12
|
import { Button } from "../components/ui/button";
|
|
12
13
|
import { Input } from "../components/ui/input";
|
|
13
14
|
import { Separator } from "../components/ui/separator";
|
|
@@ -65,6 +66,7 @@ export function getExtensionUtils(lobb: LobbClient, ctx: CTX): ExtensionUtils {
|
|
|
65
66
|
toast: toast,
|
|
66
67
|
showDialog: showDialog,
|
|
67
68
|
openDataTableDrawer: (props) => openDataTableDrawer({ lobb, ctx }, props),
|
|
69
|
+
emitEvent: (eventName, input) => emitEvent({ lobb, ctx }, eventName, input),
|
|
68
70
|
components: getComponents(),
|
|
69
71
|
mediaQueries: mediaQueries,
|
|
70
72
|
intlDate: intlDate,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CTX } from "./store.types";
|
|
2
|
+
|
|
3
|
+
function getFieldRelation(ctx: CTX, collectionName: string, fieldName: string) {
|
|
4
|
+
const relations = ctx.meta.relations;
|
|
5
|
+
for (let index = 0; index < relations.length; index++) {
|
|
6
|
+
const relation = relations[index];
|
|
7
|
+
if (relation.type === "polymorphic") continue;
|
|
8
|
+
if (relation.from.collection === collectionName && relation.from.field === fieldName) {
|
|
9
|
+
return relation;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function isRelationField(ctx: CTX, collectionName: string, fieldName: string): boolean {
|
|
16
|
+
return Boolean(getFieldRelation(ctx, collectionName, fieldName));
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function getFieldRelationTarget(ctx: CTX, collectionName: string, fieldName: string): string | null {
|
|
20
|
+
return getFieldRelation(ctx, collectionName, fieldName)?.to.collection ?? null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function getPolymorphicRelation(ctx: CTX, collectionName: string, fieldName: string) {
|
|
24
|
+
const relations = ctx.meta.relations;
|
|
25
|
+
for (let index = 0; index < relations.length; index++) {
|
|
26
|
+
const relation = relations[index];
|
|
27
|
+
if (
|
|
28
|
+
relation.type === "polymorphic" &&
|
|
29
|
+
relation.from.collection === collectionName &&
|
|
30
|
+
relation.from.virtual_field === fieldName
|
|
31
|
+
) {
|
|
32
|
+
return relation as {
|
|
33
|
+
type: "polymorphic";
|
|
34
|
+
from: { collection: string; virtual_field: string; collection_field: string; id_field: string };
|
|
35
|
+
to: string[];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
export function recordHasChildrean(ctx: CTX, collectionName: string) {
|
|
44
|
+
for (let index = 0; index < ctx.meta.relations.length; index++) {
|
|
45
|
+
const relation = ctx.meta.relations[index];
|
|
46
|
+
if (relation.type === "polymorphic") continue;
|
|
47
|
+
if (relation.to.collection === collectionName) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
package/src/lib/store.types.ts
CHANGED
package/src/lib/utils.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { clsx, type ClassValue } from "clsx";
|
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
3
|
|
|
4
4
|
import { MediaQuery } from 'svelte/reactivity';
|
|
5
|
-
import type { CTX } from "./store.types";
|
|
6
5
|
|
|
7
6
|
export function cn(...inputs: ClassValue[]) {
|
|
8
7
|
return twMerge(clsx(inputs));
|
|
@@ -24,26 +23,6 @@ export const mediaQueries = {
|
|
|
24
23
|
'2xl': new MediaQuery('min-width: 1536px'),
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
export function getFieldRelation(ctx: CTX, collectionName: string, fieldName: string) {
|
|
28
|
-
const relations = ctx.meta.relations;
|
|
29
|
-
for (let index = 0; index < relations.length; index++) {
|
|
30
|
-
const relation = relations[index];
|
|
31
|
-
if (relation.from.collection === collectionName && relation.from.field === fieldName) {
|
|
32
|
-
return relation;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export function recordHasChildrean(ctx: CTX, collectionName: string) {
|
|
39
|
-
for (let index = 0; index < ctx.meta.relations.length; index++) {
|
|
40
|
-
const relation = ctx.meta.relations[index];
|
|
41
|
-
if (relation.to.collection === collectionName) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return false;
|
|
46
|
-
};
|
|
47
26
|
|
|
48
27
|
export function calculateDrawerWidth() {
|
|
49
28
|
const backgroundDrawerButtons = document.querySelectorAll(".backgroundDrawerButton");
|