@lobb-js/studio 0.1.31
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/.env.example +1 -0
- package/.storybook/main.ts +31 -0
- package/.storybook/preview.ts +21 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/README.md +47 -0
- package/components.json +16 -0
- package/docker-entrypoint.sh +7 -0
- package/dockerfile +27 -0
- package/index.html +13 -0
- package/package.json +77 -0
- package/public/lobb.svg +15 -0
- package/src/Studio.svelte +150 -0
- package/src/app.css +121 -0
- package/src/components-export.ts +21 -0
- package/src/extensions/extension.types.ts +93 -0
- package/src/extensions/extensionUtils.ts +192 -0
- package/src/lib/Lobb.ts +241 -0
- package/src/lib/components/LlmButton.svelte +136 -0
- package/src/lib/components/alertView.svelte +20 -0
- package/src/lib/components/breadCrumbs.svelte +60 -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 +107 -0
- package/src/lib/components/dataTable/childRecords.svelte +140 -0
- package/src/lib/components/dataTable/dataTable.svelte +223 -0
- package/src/lib/components/dataTable/fieldCell.svelte +74 -0
- package/src/lib/components/dataTable/filter.svelte +282 -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 +154 -0
- package/src/lib/components/dataTable/sort.svelte +171 -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 +68 -0
- package/src/lib/components/detailView/create/createDetailView.svelte +226 -0
- package/src/lib/components/detailView/create/createDetailViewButton.svelte +32 -0
- package/src/lib/components/detailView/create/createManyView.svelte +250 -0
- package/src/lib/components/detailView/create/subRecords.svelte +48 -0
- package/src/lib/components/detailView/detailViewForm.svelte +104 -0
- package/src/lib/components/detailView/fieldCustomInput.svelte +23 -0
- package/src/lib/components/detailView/fieldInput.svelte +287 -0
- package/src/lib/components/detailView/fieldInputReplacement.svelte +199 -0
- package/src/lib/components/detailView/store.svelte.ts +61 -0
- package/src/lib/components/detailView/update/children.svelte +94 -0
- package/src/lib/components/detailView/update/updateDetailView.svelte +175 -0
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +32 -0
- package/src/lib/components/detailView/utils.ts +177 -0
- package/src/lib/components/diffViewer.svelte +102 -0
- package/src/lib/components/drawer.svelte +28 -0
- package/src/lib/components/extensionsComponents.svelte +31 -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 +238 -0
- package/src/lib/components/monacoEditor.svelte +181 -0
- package/src/lib/components/rangeCalendarButton.svelte +257 -0
- package/src/lib/components/selectRecord.svelte +126 -0
- package/src/lib/components/setServerPage.svelte +48 -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 +69 -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 +187 -0
- package/src/lib/eventSystem.ts +38 -0
- package/src/lib/index.ts +40 -0
- package/src/lib/store.svelte.ts +21 -0
- package/src/lib/store.types.ts +28 -0
- package/src/lib/utils.ts +84 -0
- package/src/main.ts +18 -0
- package/src/routes/collections/collection.svelte +46 -0
- package/src/routes/collections/collections.svelte +43 -0
- package/src/routes/data_model/dataModel.svelte +40 -0
- package/src/routes/data_model/flow.css +22 -0
- package/src/routes/data_model/flow.svelte +82 -0
- package/src/routes/data_model/syncManager.svelte +93 -0
- package/src/routes/data_model/utils.ts +35 -0
- package/src/routes/extensions/extension.svelte +16 -0
- package/src/routes/home.svelte +36 -0
- package/src/routes/workflows/workflows.svelte +135 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/stories/detailView/detailViewForm.stories.svelte +79 -0
- package/src/stories/examples/Button.stories.svelte +31 -0
- package/src/stories/examples/Button.svelte +30 -0
- package/src/stories/examples/Header.stories.svelte +26 -0
- package/src/stories/examples/Header.svelte +45 -0
- package/src/stories/examples/Page.stories.svelte +29 -0
- package/src/stories/examples/Page.svelte +70 -0
- package/src/stories/examples/button.css +30 -0
- package/src/stories/examples/header.css +32 -0
- package/src/stories/examples/page.css +68 -0
- package/src/vite-env.d.ts +2 -0
- package/svelte.config.js +7 -0
- package/todo.md +24 -0
- package/tsconfig.app.json +25 -0
- package/tsconfig.json +14 -0
- package/tsconfig.node.json +24 -0
- package/vite-plugin-contextual-lib.js +66 -0
- package/vite.build.svelte.config.ts +18 -0
- package/vite.config.ts +84 -0
- package/vite.extension.config.ts +81 -0
- package/vite_utils.ts +28 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { Icon as IconType } from 'lucide-svelte';
|
|
4
|
+
import FieldInputReplacement, { type CustomFieldSnippet } from "./fieldInputReplacement.svelte";
|
|
5
|
+
import type { HTMLInputTypeAttribute } from "svelte/elements";
|
|
6
|
+
|
|
7
|
+
export interface BaseField {
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
icon?: typeof IconType;
|
|
11
|
+
errorMessages?: string[];
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
customFieldSnippet?: CustomFieldSnippet;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface InputField extends BaseField {
|
|
17
|
+
type: 'input';
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
inputType?: HTMLInputTypeAttribute;
|
|
20
|
+
inputStep?: number | "any" | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TextAreaField extends BaseField {
|
|
24
|
+
type: 'textarea';
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SelectField extends BaseField {
|
|
29
|
+
type: 'select';
|
|
30
|
+
options: string[];
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DateField extends BaseField {
|
|
35
|
+
type: 'date';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface timeField extends BaseField {
|
|
39
|
+
type: 'time';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DateTimeField extends BaseField {
|
|
43
|
+
type: 'datetime';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface BooleanField extends BaseField {
|
|
47
|
+
type: 'switch';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type DetailFormField = InputField | TextAreaField | SelectField | DateField | timeField | DateTimeField | BooleanField;
|
|
51
|
+
|
|
52
|
+
interface DetailViewFormProps {
|
|
53
|
+
fields: DetailFormField[];
|
|
54
|
+
value?: Record<string, unknown>;
|
|
55
|
+
topRight?: Snippet<[]>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let { value = $bindable({}), fields, topRight }: DetailViewFormProps = $props();
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<div class="flex flex-col gap-4 p-4">
|
|
62
|
+
{#each fields as field}
|
|
63
|
+
<div
|
|
64
|
+
class="flex flex-col gap-2"
|
|
65
|
+
>
|
|
66
|
+
<div
|
|
67
|
+
class="flex flex-1 items-end justify-between gap-2 text-xs"
|
|
68
|
+
>
|
|
69
|
+
<div class="flex gap-2">
|
|
70
|
+
<div class="h-fit">{field.label}</div>
|
|
71
|
+
<div
|
|
72
|
+
class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground"
|
|
73
|
+
>
|
|
74
|
+
{#if field.icon}
|
|
75
|
+
<field.icon size="12" />
|
|
76
|
+
{/if}
|
|
77
|
+
{field.type}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
{@render topRight?.()}
|
|
82
|
+
<!-- TODO: WHEN YOU USE THIS COMPONENT IN THE CREATE AND UPDATE DETAIL VIEW ADD THIS PART TO IT -->
|
|
83
|
+
<!-- <ExtensionsComponents
|
|
84
|
+
name="detailView.fields.topRight.{collectionName}.{fieldName}"
|
|
85
|
+
utils={getExtensionUtils()}
|
|
86
|
+
bind:value={value[field.key]}
|
|
87
|
+
/> -->
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<FieldInputReplacement
|
|
91
|
+
field={field}
|
|
92
|
+
bind:value={
|
|
93
|
+
() => value[field.key],
|
|
94
|
+
(v) =>
|
|
95
|
+
(value = {
|
|
96
|
+
...value,
|
|
97
|
+
[field.key]: v,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
customFieldInput={field.customFieldSnippet}
|
|
101
|
+
></FieldInputReplacement>
|
|
102
|
+
</div>
|
|
103
|
+
{/each}
|
|
104
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import MonacoEditor from "../monacoEditor.svelte";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
value: any;
|
|
6
|
+
type: string;
|
|
7
|
+
args: any;
|
|
8
|
+
field: any;
|
|
9
|
+
destructive?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
value = $bindable(),
|
|
14
|
+
type,
|
|
15
|
+
args,
|
|
16
|
+
field,
|
|
17
|
+
destructive,
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
{#if type === "code"}
|
|
22
|
+
<MonacoEditor name={field.key} {...args} bind:value />
|
|
23
|
+
{/if}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ctx } from "$lib/store.svelte";
|
|
3
|
+
import {
|
|
4
|
+
getDiscriminatorFieldRelation,
|
|
5
|
+
getFieldRelation,
|
|
6
|
+
} from "$lib/utils";
|
|
7
|
+
import { Ban, Check, CircleAlert, X } from "lucide-svelte";
|
|
8
|
+
import { getField } from "../dataTable/utils";
|
|
9
|
+
import Button from "../ui/button/button.svelte";
|
|
10
|
+
import FieldCustomInput from "./fieldCustomInput.svelte";
|
|
11
|
+
import Input from "../ui/input/input.svelte";
|
|
12
|
+
import Combobox from "../combobox.svelte";
|
|
13
|
+
import * as Select from "$lib/components/ui/select/index";
|
|
14
|
+
import Textarea from "../ui/textarea/textarea.svelte";
|
|
15
|
+
import ForeingKeyInput from "../foreingKeyInput.svelte";
|
|
16
|
+
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
17
|
+
import { getExtensionUtils } from "../../../extensions/extensionUtils";
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
collectionName: string;
|
|
21
|
+
fieldName: string;
|
|
22
|
+
value: any;
|
|
23
|
+
errorMessages?: string[];
|
|
24
|
+
entry?: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
collectionName,
|
|
29
|
+
fieldName,
|
|
30
|
+
value = $bindable(),
|
|
31
|
+
errorMessages = [],
|
|
32
|
+
entry,
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
const ui_input =
|
|
36
|
+
ctx.meta.collections[collectionName].fields[fieldName].ui_input;
|
|
37
|
+
const ui =
|
|
38
|
+
ctx.meta.collections[collectionName].fields[fieldName].ui;
|
|
39
|
+
const field = getField(fieldName, collectionName);
|
|
40
|
+
const fieldRelation = getFieldRelation(collectionName, fieldName);
|
|
41
|
+
const discriminatorFieldRelation = getDiscriminatorFieldRelation(
|
|
42
|
+
collectionName,
|
|
43
|
+
fieldName,
|
|
44
|
+
);
|
|
45
|
+
const isDisabled = field.key === 'id' || Boolean(ui?.disabled)
|
|
46
|
+
const disabledClasses = "pointer-events-none opacity-50";
|
|
47
|
+
const destructive: boolean = $derived(Boolean(errorMessages.length));
|
|
48
|
+
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div class="{isDisabled ? "cursor-not-allowed" : ''}">
|
|
52
|
+
<div
|
|
53
|
+
class="
|
|
54
|
+
relative flex flex-col gap-2
|
|
55
|
+
{isDisabled ? disabledClasses : ''}
|
|
56
|
+
"
|
|
57
|
+
style="flex: 2;"
|
|
58
|
+
>
|
|
59
|
+
<Button
|
|
60
|
+
onclick={() => (value = null)}
|
|
61
|
+
variant="outline"
|
|
62
|
+
class="absolute right-0 top-0 z-10 mr-1.5 mt-1.5 aspect-square h-6 w-6 p-0"
|
|
63
|
+
Icon={Ban}
|
|
64
|
+
tabindex={-1}
|
|
65
|
+
></Button>
|
|
66
|
+
{#if ui_input}
|
|
67
|
+
<FieldCustomInput
|
|
68
|
+
bind:value
|
|
69
|
+
type={ui_input.type}
|
|
70
|
+
args={ui_input.args}
|
|
71
|
+
{field}
|
|
72
|
+
{destructive}
|
|
73
|
+
/>
|
|
74
|
+
{:else if field.label === "id"}
|
|
75
|
+
<Input
|
|
76
|
+
placeholder="AUTO GENERATED"
|
|
77
|
+
class="bg-soft text-xs"
|
|
78
|
+
bind:value
|
|
79
|
+
/>
|
|
80
|
+
{:else if discriminatorFieldRelation}
|
|
81
|
+
{@const availableCollections =
|
|
82
|
+
discriminatorFieldRelation.to === "*"
|
|
83
|
+
? Object.keys(ctx.meta.collections)
|
|
84
|
+
: discriminatorFieldRelation.to.map(
|
|
85
|
+
(item: any) => item.collection,
|
|
86
|
+
)}
|
|
87
|
+
{@const collections = availableCollections.map((key: any) => {
|
|
88
|
+
return {
|
|
89
|
+
label: key,
|
|
90
|
+
value: key,
|
|
91
|
+
};
|
|
92
|
+
})}
|
|
93
|
+
<Combobox
|
|
94
|
+
placeholder="NULL"
|
|
95
|
+
searchPlaceholder="Search Collections"
|
|
96
|
+
options={collections}
|
|
97
|
+
buttonClass="
|
|
98
|
+
bg-soft text-muted-foreground px-0 pl-4 pr-8 font-normal
|
|
99
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
100
|
+
"
|
|
101
|
+
bind:value
|
|
102
|
+
/>
|
|
103
|
+
{:else if fieldRelation && entry}
|
|
104
|
+
<ExtensionsComponents
|
|
105
|
+
name="detailView.fields.foreignKey.{fieldRelation.to.collection}"
|
|
106
|
+
utils={getExtensionUtils()}
|
|
107
|
+
parentCollectionName={collectionName}
|
|
108
|
+
collectionName={fieldRelation.to.collection}
|
|
109
|
+
bind:value
|
|
110
|
+
{entry}
|
|
111
|
+
fieldName={field.key}
|
|
112
|
+
{destructive}
|
|
113
|
+
>
|
|
114
|
+
<ForeingKeyInput
|
|
115
|
+
parentCollectionName={collectionName}
|
|
116
|
+
collectionName={fieldRelation.to.collection}
|
|
117
|
+
bind:value
|
|
118
|
+
{entry}
|
|
119
|
+
fieldName={field.key}
|
|
120
|
+
{destructive}
|
|
121
|
+
/>
|
|
122
|
+
</ExtensionsComponents>
|
|
123
|
+
{:else if field.type === "string"}
|
|
124
|
+
<!-- if the string has a validator of type enum -->
|
|
125
|
+
{#if field.validators && field.validators.enum}
|
|
126
|
+
<Select.Root
|
|
127
|
+
type="single"
|
|
128
|
+
onValueChange={(newValue) => {
|
|
129
|
+
value = newValue;
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
<Select.Trigger
|
|
133
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
134
|
+
class="
|
|
135
|
+
h-9 w-full bg-soft pr-8
|
|
136
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
137
|
+
"
|
|
138
|
+
>
|
|
139
|
+
{value ? value : "NULL"}
|
|
140
|
+
</Select.Trigger>
|
|
141
|
+
<Select.Content>
|
|
142
|
+
<Select.Group>
|
|
143
|
+
{#each field.validators.enum as option}
|
|
144
|
+
<Select.Item value={option} label={option} />
|
|
145
|
+
{/each}
|
|
146
|
+
</Select.Group>
|
|
147
|
+
</Select.Content>
|
|
148
|
+
</Select.Root>
|
|
149
|
+
{:else}
|
|
150
|
+
<Input
|
|
151
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
152
|
+
type="text"
|
|
153
|
+
class="
|
|
154
|
+
bg-soft text-xs
|
|
155
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
156
|
+
"
|
|
157
|
+
bind:value
|
|
158
|
+
/>
|
|
159
|
+
{/if}
|
|
160
|
+
{:else if field.type === "text"}
|
|
161
|
+
<Textarea
|
|
162
|
+
placeholder={ui?.placeholder ? ui.placeholder : value === "" ? "EMPTY STRING" : "NULL"}
|
|
163
|
+
rows={5}
|
|
164
|
+
class="
|
|
165
|
+
bg-soft text-xs
|
|
166
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
167
|
+
"
|
|
168
|
+
bind:value
|
|
169
|
+
/>
|
|
170
|
+
{:else if field.type === "date"}
|
|
171
|
+
<Input
|
|
172
|
+
type="date"
|
|
173
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
174
|
+
class="
|
|
175
|
+
dateInput block w-full bg-soft pr-9 text-xs
|
|
176
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
177
|
+
"
|
|
178
|
+
bind:value={
|
|
179
|
+
() => {
|
|
180
|
+
if (!value) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const date = new Date(value);
|
|
184
|
+
return date.toISOString().split("T")[0];
|
|
185
|
+
},
|
|
186
|
+
(v) => (value = v)
|
|
187
|
+
}
|
|
188
|
+
/>
|
|
189
|
+
{:else if field.type === "time"}
|
|
190
|
+
<Input
|
|
191
|
+
type="time"
|
|
192
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
193
|
+
class="
|
|
194
|
+
dateInput block w-full bg-soft pr-9 text-xs
|
|
195
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
196
|
+
"
|
|
197
|
+
bind:value={
|
|
198
|
+
() => {
|
|
199
|
+
if (!value) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
},
|
|
204
|
+
(v) => (value = v)
|
|
205
|
+
}
|
|
206
|
+
/>
|
|
207
|
+
{:else if field.type === "datetime"}
|
|
208
|
+
<Input
|
|
209
|
+
type="datetime-local"
|
|
210
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
211
|
+
class="
|
|
212
|
+
dateInput block w-full bg-soft pr-9 text-xs
|
|
213
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
214
|
+
"
|
|
215
|
+
bind:value={
|
|
216
|
+
() => {
|
|
217
|
+
if (!value) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const date = new Date(value);
|
|
221
|
+
return date.toISOString().slice(0, 16);
|
|
222
|
+
},
|
|
223
|
+
(v) => {
|
|
224
|
+
value = v;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/>
|
|
228
|
+
{:else if field.type === "bool"}
|
|
229
|
+
<Select.Root type="single" bind:value>
|
|
230
|
+
<Select.Trigger
|
|
231
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
232
|
+
class="
|
|
233
|
+
bg-soft pr-9
|
|
234
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
235
|
+
"
|
|
236
|
+
>
|
|
237
|
+
{String(value).toUpperCase()}
|
|
238
|
+
</Select.Trigger>
|
|
239
|
+
<Select.Content>
|
|
240
|
+
<Select.Item value={"true"} class="flex gap-1.5">
|
|
241
|
+
<Check size="15" />
|
|
242
|
+
TRUE
|
|
243
|
+
</Select.Item>
|
|
244
|
+
<Select.Item value={"false"} class="flex gap-1.5">
|
|
245
|
+
<X size="15" />
|
|
246
|
+
FALSE
|
|
247
|
+
</Select.Item>
|
|
248
|
+
<Select.Item value={"null"} class="flex gap-1.5">
|
|
249
|
+
<Ban size="15" />
|
|
250
|
+
NULL
|
|
251
|
+
</Select.Item>
|
|
252
|
+
</Select.Content>
|
|
253
|
+
</Select.Root>
|
|
254
|
+
{:else if field.type === "decimal"}
|
|
255
|
+
<Input
|
|
256
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
257
|
+
type="number"
|
|
258
|
+
step="any"
|
|
259
|
+
class="
|
|
260
|
+
bg-soft text-xs
|
|
261
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
262
|
+
"
|
|
263
|
+
bind:value
|
|
264
|
+
/>
|
|
265
|
+
{:else}
|
|
266
|
+
<Input
|
|
267
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
268
|
+
type="number"
|
|
269
|
+
class="
|
|
270
|
+
bg-soft text-xs
|
|
271
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
272
|
+
"
|
|
273
|
+
bind:value
|
|
274
|
+
/>
|
|
275
|
+
{/if}
|
|
276
|
+
{#if errorMessages}
|
|
277
|
+
{#each errorMessages as message}
|
|
278
|
+
<div class="flex gap-1 text-destructive">
|
|
279
|
+
<CircleAlert size="15" class="translate-y-[0.025rem]" />
|
|
280
|
+
<div class="text-[0.7rem]">
|
|
281
|
+
{message}
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
{/each}
|
|
285
|
+
{/if}
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Ban, Check, CircleAlert, X } from "lucide-svelte";
|
|
3
|
+
import Button from "../ui/button/button.svelte";
|
|
4
|
+
import Input from "../ui/input/input.svelte";
|
|
5
|
+
import * as Select from "$lib/components/ui/select/index";
|
|
6
|
+
import Textarea from "../ui/textarea/textarea.svelte";
|
|
7
|
+
import type { EntryField } from "./detailViewForm.svelte";
|
|
8
|
+
import type { Snippet } from "svelte";
|
|
9
|
+
import AlertView from "../alertView.svelte";
|
|
10
|
+
|
|
11
|
+
export type CustomFieldSnippet = Snippet<[EntryField, unknown]>;
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
field: EntryField;
|
|
15
|
+
value: unknown;
|
|
16
|
+
customFieldInput?: CustomFieldSnippet;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
field,
|
|
21
|
+
value = $bindable(),
|
|
22
|
+
customFieldInput,
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
const disabledClasses = "pointer-events-none opacity-50";
|
|
26
|
+
const destructive: boolean = $derived(Boolean(field.errorMessages && field.errorMessages.length));
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div class="{field.disabled ? "cursor-not-allowed" : ''}">
|
|
30
|
+
<div
|
|
31
|
+
class="
|
|
32
|
+
relative flex flex-col gap-2
|
|
33
|
+
{field.disabled ? disabledClasses : ''}
|
|
34
|
+
"
|
|
35
|
+
style="flex: 2;"
|
|
36
|
+
>
|
|
37
|
+
<Button
|
|
38
|
+
onclick={() => (value = null)}
|
|
39
|
+
variant="outline"
|
|
40
|
+
class="absolute right-0 top-0 z-10 mr-1.5 mt-1.5 aspect-square h-6 w-6 p-0"
|
|
41
|
+
Icon={Ban}
|
|
42
|
+
tabindex={-1}
|
|
43
|
+
></Button>
|
|
44
|
+
{#if customFieldInput}
|
|
45
|
+
{@render customFieldInput(field, value)}
|
|
46
|
+
{:else if field.type === "input"}
|
|
47
|
+
<!-- if the string has a validator of type enum -->
|
|
48
|
+
<Input
|
|
49
|
+
placeholder={field.placeholder ? field.placeholder : "NULL"}
|
|
50
|
+
type="text"
|
|
51
|
+
class="
|
|
52
|
+
bg-soft text-xs
|
|
53
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
54
|
+
"
|
|
55
|
+
bind:value
|
|
56
|
+
/>
|
|
57
|
+
{:else if field.type === "select"}
|
|
58
|
+
<Select.Root
|
|
59
|
+
type="single"
|
|
60
|
+
onValueChange={(newValue) => {
|
|
61
|
+
value = newValue;
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
<Select.Trigger
|
|
65
|
+
placeholder={field.placeholder ? field.placeholder : "NULL"}
|
|
66
|
+
class="
|
|
67
|
+
h-9 w-full bg-soft pr-8
|
|
68
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
69
|
+
"
|
|
70
|
+
>
|
|
71
|
+
{value ? value : "NULL"}
|
|
72
|
+
</Select.Trigger>
|
|
73
|
+
<Select.Content>
|
|
74
|
+
<Select.Group>
|
|
75
|
+
{#each field.options as option}
|
|
76
|
+
<Select.Item value={option} label={option} />
|
|
77
|
+
{/each}
|
|
78
|
+
</Select.Group>
|
|
79
|
+
</Select.Content>
|
|
80
|
+
</Select.Root>
|
|
81
|
+
{:else if field.type === "textarea"}
|
|
82
|
+
{#if typeof value === 'string' || typeof value === 'undefined' || value === null}
|
|
83
|
+
<Textarea
|
|
84
|
+
placeholder={field.placeholder ? field.placeholder : value === "" ? "EMPTY STRING" : "NULL"}
|
|
85
|
+
rows={5}
|
|
86
|
+
class="
|
|
87
|
+
bg-soft text-xs
|
|
88
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
89
|
+
"
|
|
90
|
+
bind:value
|
|
91
|
+
/>
|
|
92
|
+
{:else}
|
|
93
|
+
<AlertView title="Couldnt render component">
|
|
94
|
+
The value passed to the Textarea in (fieldInputReplacement) is something different from these (string, undefined, null)
|
|
95
|
+
</AlertView>
|
|
96
|
+
{/if}
|
|
97
|
+
{:else if field.type === "date"}
|
|
98
|
+
<Input
|
|
99
|
+
type="date"
|
|
100
|
+
class="
|
|
101
|
+
dateInput block w-full bg-soft pr-9 text-xs
|
|
102
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
103
|
+
"
|
|
104
|
+
bind:value={
|
|
105
|
+
() => {
|
|
106
|
+
if (typeof value !== "string") {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const date = new Date(value);
|
|
110
|
+
return date.toISOString().split("T")[0];
|
|
111
|
+
},
|
|
112
|
+
(v) => (value = v)
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
{:else if field.type === "time"}
|
|
116
|
+
<Input
|
|
117
|
+
type="time"
|
|
118
|
+
class="
|
|
119
|
+
dateInput block w-full bg-soft pr-9 text-xs
|
|
120
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
121
|
+
"
|
|
122
|
+
bind:value={
|
|
123
|
+
() => {
|
|
124
|
+
if (typeof value !== "string") {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
},
|
|
129
|
+
(v) => (value = v)
|
|
130
|
+
}
|
|
131
|
+
/>
|
|
132
|
+
{:else if field.type === "datetime"}
|
|
133
|
+
<Input
|
|
134
|
+
type="datetime-local"
|
|
135
|
+
class="
|
|
136
|
+
dateInput block w-full bg-soft pr-9 text-xs
|
|
137
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
138
|
+
"
|
|
139
|
+
bind:value={
|
|
140
|
+
() => {
|
|
141
|
+
if (typeof value !== "string") {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const date = new Date(value);
|
|
145
|
+
return date.toISOString().slice(0, 16);
|
|
146
|
+
},
|
|
147
|
+
(v) => {
|
|
148
|
+
value = v;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/>
|
|
152
|
+
{:else if field.type === "switch"}
|
|
153
|
+
{#if typeof value === "string" || typeof value === "undefined"}
|
|
154
|
+
<Select.Root type="single" bind:value>
|
|
155
|
+
<Select.Trigger
|
|
156
|
+
class="
|
|
157
|
+
bg-soft pr-9
|
|
158
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
159
|
+
"
|
|
160
|
+
>
|
|
161
|
+
{String(value).toUpperCase()}
|
|
162
|
+
</Select.Trigger>
|
|
163
|
+
<Select.Content>
|
|
164
|
+
<Select.Item value={"true"} class="flex gap-1.5">
|
|
165
|
+
<Check size="15" />
|
|
166
|
+
TRUE
|
|
167
|
+
</Select.Item>
|
|
168
|
+
<Select.Item value={"false"} class="flex gap-1.5">
|
|
169
|
+
<X size="15" />
|
|
170
|
+
FALSE
|
|
171
|
+
</Select.Item>
|
|
172
|
+
<Select.Item value={"null"} class="flex gap-1.5">
|
|
173
|
+
<Ban size="15" />
|
|
174
|
+
NULL
|
|
175
|
+
</Select.Item>
|
|
176
|
+
</Select.Content>
|
|
177
|
+
</Select.Root>
|
|
178
|
+
{:else}
|
|
179
|
+
<AlertView title="Couldnt render component">
|
|
180
|
+
The value passed to the switch element in (fieldInputReplacement) is not one of these (string, undefined)
|
|
181
|
+
</AlertView>
|
|
182
|
+
{/if}
|
|
183
|
+
{:else}
|
|
184
|
+
<AlertView title="Couldnt render component">
|
|
185
|
+
For some reason couldnt render the (fieldInputReplacement)
|
|
186
|
+
</AlertView>
|
|
187
|
+
{/if}
|
|
188
|
+
{#if field.errorMessages}
|
|
189
|
+
{#each field.errorMessages as message}
|
|
190
|
+
<div class="flex gap-1 text-destructive">
|
|
191
|
+
<CircleAlert size="15" class="translate-y-[0.025rem]" />
|
|
192
|
+
<div class="text-[0.7rem]">
|
|
193
|
+
{message}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
{/each}
|
|
197
|
+
{/if}
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { CreateDetailViewProp } from "./create/createDetailView.svelte";
|
|
2
|
+
import type { UpdateDetailViewProp } from "./update/updateDetailView.svelte";
|
|
3
|
+
import CreateDetailView from "./create/createDetailView.svelte";
|
|
4
|
+
import UpdateDetailView from "./update/updateDetailView.svelte";
|
|
5
|
+
import { lobb } from "$lib";
|
|
6
|
+
import { getCollectionParamsFields } from "../dataTable/utils";
|
|
7
|
+
import { mount, unmount } from "svelte";
|
|
8
|
+
|
|
9
|
+
export function openCreateDetailView(props: CreateDetailViewProp) {
|
|
10
|
+
const targetElement = document.querySelector('main');
|
|
11
|
+
|
|
12
|
+
if (!targetElement) {
|
|
13
|
+
throw new Error("main html element doesn't exist for some reason")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const mountedCreateDetailView = mount(CreateDetailView, {
|
|
17
|
+
target: targetElement,
|
|
18
|
+
props: {
|
|
19
|
+
...props,
|
|
20
|
+
onCancel: async () => {
|
|
21
|
+
props.onCancel?.()
|
|
22
|
+
await unmount(mountedCreateDetailView, {
|
|
23
|
+
outro: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function openUpdateDetailView(props: UpdateDetailViewProp) {
|
|
31
|
+
let params = {
|
|
32
|
+
fields: getCollectionParamsFields(props.collectionName, true),
|
|
33
|
+
filter: {
|
|
34
|
+
id: props.recordId,
|
|
35
|
+
},
|
|
36
|
+
limit: 1,
|
|
37
|
+
};
|
|
38
|
+
const response = await lobb.findAll(props.collectionName, params);
|
|
39
|
+
const result = await response.json();
|
|
40
|
+
const entry = result.data[0];
|
|
41
|
+
|
|
42
|
+
const targetElement = document.querySelector('main');
|
|
43
|
+
|
|
44
|
+
if (!targetElement) {
|
|
45
|
+
throw new Error("main html element doesn't exist for some reason")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const mountedUpdateDetailView = mount(UpdateDetailView, {
|
|
49
|
+
target: targetElement,
|
|
50
|
+
props: {
|
|
51
|
+
...props,
|
|
52
|
+
onCancel: async () => {
|
|
53
|
+
props.onCancel?.()
|
|
54
|
+
await unmount(mountedUpdateDetailView, {
|
|
55
|
+
outro: true
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
values: entry,
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
}
|