@lobb-js/studio 0.7.2 → 0.7.3
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
|
@@ -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,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import CodeEditor from "../codeEditor.svelte";
|
|
3
|
+
import RichTextEditor from "../richTextEditor.svelte";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
value: any;
|
|
7
|
+
type: string;
|
|
8
|
+
args: any;
|
|
9
|
+
field: any;
|
|
10
|
+
destructive?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
value = $bindable(),
|
|
15
|
+
type,
|
|
16
|
+
args,
|
|
17
|
+
field,
|
|
18
|
+
destructive,
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if type === "code"}
|
|
23
|
+
<CodeEditor name={field.key} {...args} bind:value />
|
|
24
|
+
{:else if type === "richtext"}
|
|
25
|
+
<RichTextEditor name={field.key} bind:value />
|
|
26
|
+
{/if}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getStudioContext } from "../../context";
|
|
3
|
+
import { getFieldRelation } from "../../utils";
|
|
4
|
+
import { Ban, Check, CircleAlert, X } from "lucide-svelte";
|
|
5
|
+
import { getField } from "../dataTable/utils";
|
|
6
|
+
import Button from "../ui/button/button.svelte";
|
|
7
|
+
import FieldCustomInput from "./fieldCustomInput.svelte";
|
|
8
|
+
import Input from "../ui/input/input.svelte";
|
|
9
|
+
import * as Select from "../../components/ui/select/index";
|
|
10
|
+
import Textarea from "../ui/textarea/textarea.svelte";
|
|
11
|
+
import ForeingKeyInput from "../foreingKeyInput.svelte";
|
|
12
|
+
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
13
|
+
import { getExtensionUtils } from "../../extensions/extensionUtils";
|
|
14
|
+
|
|
15
|
+
const { ctx, lobb } = getStudioContext();
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
collectionName: string;
|
|
19
|
+
fieldName: string;
|
|
20
|
+
value: any;
|
|
21
|
+
errorMessages?: string[];
|
|
22
|
+
entry?: Record<string, any>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
collectionName,
|
|
27
|
+
fieldName,
|
|
28
|
+
value = $bindable(),
|
|
29
|
+
errorMessages = [],
|
|
30
|
+
entry,
|
|
31
|
+
}: Props = $props();
|
|
32
|
+
|
|
33
|
+
const ui_input =
|
|
34
|
+
ctx.meta.collections[collectionName].fields[fieldName].ui?.input;
|
|
35
|
+
const ui =
|
|
36
|
+
ctx.meta.collections[collectionName].fields[fieldName].ui;
|
|
37
|
+
const field = getField(ctx, fieldName, collectionName);
|
|
38
|
+
const fieldRelation = getFieldRelation(ctx, collectionName, fieldName);
|
|
39
|
+
const isDisabled = field.key === 'id' || Boolean(ui?.disabled)
|
|
40
|
+
const disabledClasses = "pointer-events-none opacity-50";
|
|
41
|
+
const destructive: boolean = $derived(Boolean(errorMessages.length));
|
|
42
|
+
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div class="{isDisabled ? "cursor-not-allowed" : ''}">
|
|
46
|
+
<div
|
|
47
|
+
class="
|
|
48
|
+
relative flex flex-col gap-2
|
|
49
|
+
{isDisabled ? disabledClasses : ''}
|
|
50
|
+
"
|
|
51
|
+
style="flex: 2;"
|
|
52
|
+
>
|
|
53
|
+
<Button
|
|
54
|
+
onclick={() => (value = null)}
|
|
55
|
+
variant="outline"
|
|
56
|
+
class="absolute right-0 top-0 z-10 mr-1.5 mt-1.5 aspect-square h-6 w-6 p-0"
|
|
57
|
+
Icon={Ban}
|
|
58
|
+
tabindex={-1}
|
|
59
|
+
></Button>
|
|
60
|
+
{#if ui_input}
|
|
61
|
+
<FieldCustomInput
|
|
62
|
+
bind:value
|
|
63
|
+
type={ui_input.type}
|
|
64
|
+
args={ui_input.args}
|
|
65
|
+
{field}
|
|
66
|
+
{destructive}
|
|
67
|
+
/>
|
|
68
|
+
{:else if field.label === "id"}
|
|
69
|
+
<Input
|
|
70
|
+
placeholder="AUTO GENERATED"
|
|
71
|
+
class="bg-muted/30 text-xs"
|
|
72
|
+
bind:value
|
|
73
|
+
/>
|
|
74
|
+
{:else if fieldRelation && entry}
|
|
75
|
+
<ExtensionsComponents
|
|
76
|
+
name="detailView.fields.foreignKey.{fieldRelation.to.collection}"
|
|
77
|
+
utils={getExtensionUtils(lobb, ctx)}
|
|
78
|
+
parentCollectionName={collectionName}
|
|
79
|
+
collectionName={fieldRelation.to.collection}
|
|
80
|
+
bind:value
|
|
81
|
+
{entry}
|
|
82
|
+
fieldName={field.key}
|
|
83
|
+
{destructive}
|
|
84
|
+
>
|
|
85
|
+
<ForeingKeyInput
|
|
86
|
+
parentCollectionName={collectionName}
|
|
87
|
+
collectionName={fieldRelation.to.collection}
|
|
88
|
+
bind:value
|
|
89
|
+
{entry}
|
|
90
|
+
fieldName={field.key}
|
|
91
|
+
{destructive}
|
|
92
|
+
/>
|
|
93
|
+
</ExtensionsComponents>
|
|
94
|
+
{:else if field.type === "string"}
|
|
95
|
+
<!-- if the string has a validator of type enum -->
|
|
96
|
+
{#if field.validators && field.validators.enum}
|
|
97
|
+
<Select.Root
|
|
98
|
+
type="single"
|
|
99
|
+
onValueChange={(newValue) => {
|
|
100
|
+
value = newValue;
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<Select.Trigger
|
|
104
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
105
|
+
class="
|
|
106
|
+
h-9 w-full bg-muted/30 pr-8
|
|
107
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
108
|
+
"
|
|
109
|
+
>
|
|
110
|
+
{value ? value : "NULL"}
|
|
111
|
+
</Select.Trigger>
|
|
112
|
+
<Select.Content>
|
|
113
|
+
<Select.Group>
|
|
114
|
+
{#each field.validators.enum as option}
|
|
115
|
+
<Select.Item value={option} label={option} />
|
|
116
|
+
{/each}
|
|
117
|
+
</Select.Group>
|
|
118
|
+
</Select.Content>
|
|
119
|
+
</Select.Root>
|
|
120
|
+
{:else}
|
|
121
|
+
<Input
|
|
122
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
123
|
+
type="text"
|
|
124
|
+
class="
|
|
125
|
+
bg-muted/30 text-xs
|
|
126
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
127
|
+
"
|
|
128
|
+
bind:value
|
|
129
|
+
/>
|
|
130
|
+
{/if}
|
|
131
|
+
{:else if field.type === "text"}
|
|
132
|
+
<Textarea
|
|
133
|
+
placeholder={ui?.placeholder ? ui.placeholder : value === "" ? "EMPTY STRING" : "NULL"}
|
|
134
|
+
rows={5}
|
|
135
|
+
class="
|
|
136
|
+
bg-muted/30 text-xs
|
|
137
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
138
|
+
"
|
|
139
|
+
bind:value
|
|
140
|
+
/>
|
|
141
|
+
{:else if field.type === "date"}
|
|
142
|
+
<Input
|
|
143
|
+
type="date"
|
|
144
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
145
|
+
class="
|
|
146
|
+
dateInput block w-full bg-muted/30 pr-9 text-xs
|
|
147
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
148
|
+
"
|
|
149
|
+
bind:value={
|
|
150
|
+
() => {
|
|
151
|
+
if (!value) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const date = new Date(value);
|
|
155
|
+
return date.toISOString().split("T")[0];
|
|
156
|
+
},
|
|
157
|
+
(v) => (value = v)
|
|
158
|
+
}
|
|
159
|
+
/>
|
|
160
|
+
{:else if field.type === "time"}
|
|
161
|
+
<Input
|
|
162
|
+
type="time"
|
|
163
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
164
|
+
class="
|
|
165
|
+
dateInput block w-full bg-muted/30 pr-9 text-xs
|
|
166
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
167
|
+
"
|
|
168
|
+
bind:value={
|
|
169
|
+
() => {
|
|
170
|
+
if (!value) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
},
|
|
175
|
+
(v) => (value = v)
|
|
176
|
+
}
|
|
177
|
+
/>
|
|
178
|
+
{:else if field.type === "datetime"}
|
|
179
|
+
<Input
|
|
180
|
+
type="datetime-local"
|
|
181
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
182
|
+
class="
|
|
183
|
+
dateInput block w-full bg-muted/30 pr-9 text-xs
|
|
184
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
185
|
+
"
|
|
186
|
+
bind:value={
|
|
187
|
+
() => {
|
|
188
|
+
if (!value) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const date = new Date(value);
|
|
192
|
+
return date.toISOString().slice(0, 16);
|
|
193
|
+
},
|
|
194
|
+
(v) => {
|
|
195
|
+
value = v;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/>
|
|
199
|
+
{:else if field.type === "bool"}
|
|
200
|
+
<Select.Root type="single" bind:value>
|
|
201
|
+
<Select.Trigger
|
|
202
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
203
|
+
class="
|
|
204
|
+
bg-muted/30 pr-9
|
|
205
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
206
|
+
"
|
|
207
|
+
>
|
|
208
|
+
{String(value).toUpperCase()}
|
|
209
|
+
</Select.Trigger>
|
|
210
|
+
<Select.Content>
|
|
211
|
+
<Select.Item value={"true"} class="flex gap-1.5">
|
|
212
|
+
<Check size="15" />
|
|
213
|
+
TRUE
|
|
214
|
+
</Select.Item>
|
|
215
|
+
<Select.Item value={"false"} class="flex gap-1.5">
|
|
216
|
+
<X size="15" />
|
|
217
|
+
FALSE
|
|
218
|
+
</Select.Item>
|
|
219
|
+
<Select.Item value={"null"} class="flex gap-1.5">
|
|
220
|
+
<Ban size="15" />
|
|
221
|
+
NULL
|
|
222
|
+
</Select.Item>
|
|
223
|
+
</Select.Content>
|
|
224
|
+
</Select.Root>
|
|
225
|
+
{:else if field.type === "decimal"}
|
|
226
|
+
<Input
|
|
227
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
228
|
+
type="number"
|
|
229
|
+
step="any"
|
|
230
|
+
class="
|
|
231
|
+
bg-muted/30 text-xs
|
|
232
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
233
|
+
"
|
|
234
|
+
bind:value
|
|
235
|
+
/>
|
|
236
|
+
{:else}
|
|
237
|
+
<Input
|
|
238
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
239
|
+
type="number"
|
|
240
|
+
class="
|
|
241
|
+
bg-muted/30 text-xs
|
|
242
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
243
|
+
"
|
|
244
|
+
bind:value
|
|
245
|
+
/>
|
|
246
|
+
{/if}
|
|
247
|
+
{#if errorMessages}
|
|
248
|
+
{#each errorMessages as message}
|
|
249
|
+
<div class="flex gap-1 text-destructive">
|
|
250
|
+
<CircleAlert size="15" class="translate-y-[0.025rem]" />
|
|
251
|
+
<div class="text-[0.7rem]">
|
|
252
|
+
{message}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
{/each}
|
|
256
|
+
{/if}
|
|
257
|
+
</div>
|
|
258
|
+
</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 "../../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-muted/30 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-muted/30 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-muted/30 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-muted/30 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-muted/30 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-muted/30 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-muted/30 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,59 @@
|
|
|
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 type { StudioContext } from "../../context";
|
|
6
|
+
import { getCollectionParamsFields } from "../dataTable/utils";
|
|
7
|
+
import { createStudioContextMap } from "../../context";
|
|
8
|
+
import { mount, unmount } from "svelte";
|
|
9
|
+
|
|
10
|
+
export function openCreateDetailView(studioContext: StudioContext, props: CreateDetailViewProp) {
|
|
11
|
+
const targetElement = document.querySelector('main');
|
|
12
|
+
|
|
13
|
+
if (!targetElement) {
|
|
14
|
+
throw new Error("main html element doesn't exist for some reason")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const mountedCreateDetailView = mount(CreateDetailView, {
|
|
18
|
+
target: targetElement,
|
|
19
|
+
context: createStudioContextMap(studioContext),
|
|
20
|
+
props: {
|
|
21
|
+
...props,
|
|
22
|
+
onCancel: async () => {
|
|
23
|
+
props.onCancel?.()
|
|
24
|
+
await unmount(mountedCreateDetailView, { outro: true });
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function openUpdateDetailView(studioContext: StudioContext, props: UpdateDetailViewProp) {
|
|
31
|
+
const { lobb, ctx } = studioContext;
|
|
32
|
+
const params = {
|
|
33
|
+
fields: getCollectionParamsFields(ctx, props.collectionName, true),
|
|
34
|
+
filter: { id: props.recordId },
|
|
35
|
+
limit: 1,
|
|
36
|
+
};
|
|
37
|
+
const response = await lobb.findAll(props.collectionName, params);
|
|
38
|
+
const result = await response.json();
|
|
39
|
+
const entry = result.data[0];
|
|
40
|
+
|
|
41
|
+
const targetElement = document.querySelector('main');
|
|
42
|
+
|
|
43
|
+
if (!targetElement) {
|
|
44
|
+
throw new Error("main html element doesn't exist for some reason")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const mountedUpdateDetailView = mount(UpdateDetailView, {
|
|
48
|
+
target: targetElement,
|
|
49
|
+
context: createStudioContextMap(studioContext),
|
|
50
|
+
props: {
|
|
51
|
+
...props,
|
|
52
|
+
onCancel: async () => {
|
|
53
|
+
props.onCancel?.()
|
|
54
|
+
await unmount(mountedUpdateDetailView, { outro: true });
|
|
55
|
+
},
|
|
56
|
+
values: entry,
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
}
|