@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,171 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import _ from "lodash";
|
|
3
|
+
|
|
4
|
+
import * as Popover from "$lib/components/ui/popover/index.js";
|
|
5
|
+
import { ArrowDown, ArrowUp, GripVertical, Plus, X } from "lucide-svelte";
|
|
6
|
+
import Button, { buttonVariants } from "../ui/button/button.svelte";
|
|
7
|
+
import Label from "../ui/label/label.svelte";
|
|
8
|
+
import Switch from "../ui/switch/switch.svelte";
|
|
9
|
+
import { ctx } from "$lib/store.svelte";
|
|
10
|
+
import { getFieldIcon } from "./utils";
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
collectionName: string;
|
|
14
|
+
sort: Record<string, "asc" | "desc">;
|
|
15
|
+
showText: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { collectionName, sort = $bindable({}), showText }: Props = $props();
|
|
19
|
+
|
|
20
|
+
let popoverOpen = $state(false);
|
|
21
|
+
|
|
22
|
+
function getFieldNames() {
|
|
23
|
+
const options = Object.keys(
|
|
24
|
+
ctx.meta.collections[collectionName].fields,
|
|
25
|
+
);
|
|
26
|
+
const existingPropertiesNames = Object.keys(sort);
|
|
27
|
+
const filteredOptions = _.difference(options, existingPropertiesNames);
|
|
28
|
+
return filteredOptions;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function moveProperty<T extends Record<string, any>>(
|
|
32
|
+
obj: T,
|
|
33
|
+
propertyToMove: keyof T,
|
|
34
|
+
direction: "up" | "down",
|
|
35
|
+
steps: number = 1,
|
|
36
|
+
): T {
|
|
37
|
+
const keys = Object.keys(obj) as Array<keyof T>;
|
|
38
|
+
const currentIndex = keys.indexOf(propertyToMove);
|
|
39
|
+
|
|
40
|
+
if (currentIndex === -1) {
|
|
41
|
+
console.warn(
|
|
42
|
+
`Property '${String(propertyToMove)}' not found in the object.`,
|
|
43
|
+
);
|
|
44
|
+
return { ...obj };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [removedKey] = keys.splice(currentIndex, 1) as [keyof T];
|
|
48
|
+
|
|
49
|
+
let newIndex: number;
|
|
50
|
+
if (direction === "up") {
|
|
51
|
+
newIndex = Math.max(0, currentIndex - steps);
|
|
52
|
+
} else if (direction === "down") {
|
|
53
|
+
newIndex = Math.min(keys.length, currentIndex + steps);
|
|
54
|
+
} else {
|
|
55
|
+
console.warn(
|
|
56
|
+
`Invalid direction specified: '${direction}'. Use 'up' or 'down'.`,
|
|
57
|
+
);
|
|
58
|
+
return { ...obj };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
keys.splice(newIndex, 0, removedKey);
|
|
62
|
+
|
|
63
|
+
const newObject: T = {} as T;
|
|
64
|
+
keys.forEach((key) => {
|
|
65
|
+
newObject[key] = obj[key];
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return newObject;
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<div class="flex flex-col gap-2 p-2 text-muted-foreground">
|
|
73
|
+
{#if Object.keys(sort).length}
|
|
74
|
+
<div class="flex flex-col gap-2 text-muted-foreground">
|
|
75
|
+
{#each Object.entries(sort) as [fieldName, order]}
|
|
76
|
+
<div class="flex items-center justify-between text-xs">
|
|
77
|
+
<dir class="m-0 flex items-center gap-3 p-0">
|
|
78
|
+
<div class="flex gap-1">
|
|
79
|
+
<ArrowUp
|
|
80
|
+
onclick={() =>
|
|
81
|
+
(sort = moveProperty(
|
|
82
|
+
sort,
|
|
83
|
+
fieldName,
|
|
84
|
+
"up",
|
|
85
|
+
))}
|
|
86
|
+
class="cursor-pointer hover:text-foreground"
|
|
87
|
+
size="15"
|
|
88
|
+
/>
|
|
89
|
+
<ArrowDown
|
|
90
|
+
onclick={() =>
|
|
91
|
+
(sort = moveProperty(
|
|
92
|
+
sort,
|
|
93
|
+
fieldName,
|
|
94
|
+
"down",
|
|
95
|
+
))}
|
|
96
|
+
class="cursor-pointer hover:text-foreground"
|
|
97
|
+
size="15"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="text-foreground font-medium">
|
|
101
|
+
{fieldName}
|
|
102
|
+
</div>
|
|
103
|
+
</dir>
|
|
104
|
+
<dir class="m-0 flex items-center gap-2 p-0">
|
|
105
|
+
<div class="flex items-center space-x-2">
|
|
106
|
+
<Label class="text-xs">Ascending</Label>
|
|
107
|
+
<!-- <Switch checked={true} /> -->
|
|
108
|
+
<Switch
|
|
109
|
+
bind:checked={
|
|
110
|
+
() => sort[fieldName] === "asc",
|
|
111
|
+
(v) =>
|
|
112
|
+
(sort[fieldName] = v ? "asc" : "desc")
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
<Button
|
|
117
|
+
onclick={() => delete sort[fieldName]}
|
|
118
|
+
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
119
|
+
variant="ghost"
|
|
120
|
+
size="icon"
|
|
121
|
+
Icon={X}
|
|
122
|
+
></Button>
|
|
123
|
+
</dir>
|
|
124
|
+
</div>
|
|
125
|
+
{/each}
|
|
126
|
+
</div>
|
|
127
|
+
{:else}
|
|
128
|
+
<div class="flex flex-col gap-0.5 p-2">
|
|
129
|
+
<div class="text-xs text-foreground">
|
|
130
|
+
No sorts applied to this view
|
|
131
|
+
</div>
|
|
132
|
+
<div class="text-xs text-muted-foreground">
|
|
133
|
+
Add a column below to sort the view
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
{/if}
|
|
137
|
+
</div>
|
|
138
|
+
<div class="flex justify-between border-t p-2">
|
|
139
|
+
{#if getFieldNames().length}
|
|
140
|
+
<Popover.Root bind:open={popoverOpen}>
|
|
141
|
+
<Popover.Trigger
|
|
142
|
+
class={buttonVariants({
|
|
143
|
+
variant: "ghost",
|
|
144
|
+
class: "h-7 px-3 text-xs font-normal",
|
|
145
|
+
})}
|
|
146
|
+
>
|
|
147
|
+
<Plus />
|
|
148
|
+
Add a sort rule
|
|
149
|
+
</Popover.Trigger>
|
|
150
|
+
<Popover.Content class="flex w-48 flex-col p-2">
|
|
151
|
+
{#each getFieldNames() as fieldName}
|
|
152
|
+
{@const FieldIcon = getFieldIcon(fieldName, collectionName)}
|
|
153
|
+
<button
|
|
154
|
+
onclick={() => {
|
|
155
|
+
sort[fieldName] = "asc";
|
|
156
|
+
popoverOpen = false;
|
|
157
|
+
}}
|
|
158
|
+
class="flex cursor-pointer items-center gap-2 rounded-md p-2 px-3 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
159
|
+
>
|
|
160
|
+
<FieldIcon size="15" />
|
|
161
|
+
<div>{fieldName}</div>
|
|
162
|
+
</button>
|
|
163
|
+
{/each}
|
|
164
|
+
</Popover.Content>
|
|
165
|
+
</Popover.Root>
|
|
166
|
+
{:else}
|
|
167
|
+
<div class="flex items-center text-xs text-muted-foreground">
|
|
168
|
+
All columns have been added
|
|
169
|
+
</div>
|
|
170
|
+
{/if}
|
|
171
|
+
</div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Popover from "$lib/components/ui/popover/index.js";
|
|
3
|
+
import { ArrowDownWideNarrow } from "lucide-svelte";
|
|
4
|
+
import { buttonVariants } from "$lib/components/ui/button";
|
|
5
|
+
import Sort from "./sort.svelte";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
collectionName: string;
|
|
9
|
+
sort: Record<string, "asc" | "desc">;
|
|
10
|
+
showText: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { collectionName, sort = $bindable({}), showText }: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<Popover.Root>
|
|
17
|
+
<Popover.Trigger
|
|
18
|
+
class={buttonVariants({
|
|
19
|
+
variant: "ghost",
|
|
20
|
+
class: "h-7 px-3 text-xs font-normal",
|
|
21
|
+
})}
|
|
22
|
+
>
|
|
23
|
+
<ArrowDownWideNarrow />
|
|
24
|
+
{#if showText}
|
|
25
|
+
{@const sortRules = Object.keys(sort).length}
|
|
26
|
+
{#if sortRules}
|
|
27
|
+
Sorted by {sortRules} rules
|
|
28
|
+
{:else}
|
|
29
|
+
Sort
|
|
30
|
+
{/if}
|
|
31
|
+
{/if}
|
|
32
|
+
</Popover.Trigger>
|
|
33
|
+
<Popover.Content class="w-screen max-w-[20rem] p-0">
|
|
34
|
+
<Sort {collectionName} {showText} bind:sort />
|
|
35
|
+
</Popover.Content>
|
|
36
|
+
</Popover.Root>
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
interface Column {
|
|
3
|
+
id: string;
|
|
4
|
+
icon?: any;
|
|
5
|
+
subtext?: any;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type Entry = Record<string, any>;
|
|
9
|
+
|
|
10
|
+
interface Select {
|
|
11
|
+
onSelect: (entry: any) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TableProps {
|
|
15
|
+
data: Entry[];
|
|
16
|
+
columns?: Column[];
|
|
17
|
+
showCollapsible?: boolean;
|
|
18
|
+
|
|
19
|
+
// sorting
|
|
20
|
+
sort?: Record<string, "asc" | "desc">;
|
|
21
|
+
localSorting?: boolean;
|
|
22
|
+
|
|
23
|
+
// checkboxes
|
|
24
|
+
selectedRecords?: Array<any>;
|
|
25
|
+
selectByColumn?: string | null;
|
|
26
|
+
showCheckboxes?: boolean;
|
|
27
|
+
|
|
28
|
+
// styles
|
|
29
|
+
showLastRowBorder?: boolean;
|
|
30
|
+
showLastColumnBorder?: boolean;
|
|
31
|
+
|
|
32
|
+
// snippets
|
|
33
|
+
overrideCell?: Snippet<[any, Column, Entry]>;
|
|
34
|
+
tools?: Snippet<[Entry, number]>;
|
|
35
|
+
collapsible?: Snippet<[Entry, number]>;
|
|
36
|
+
|
|
37
|
+
// other
|
|
38
|
+
parentWidth?: number;
|
|
39
|
+
unifiedBgColor?: "bg-soft" | "bg-background";
|
|
40
|
+
select?: Select;
|
|
41
|
+
tableWidth?: number;
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<script lang="ts">
|
|
46
|
+
import {
|
|
47
|
+
ArrowDownNarrowWide,
|
|
48
|
+
ArrowUpWideNarrow,
|
|
49
|
+
ChevronRight,
|
|
50
|
+
CircleOff,
|
|
51
|
+
} from "lucide-svelte";
|
|
52
|
+
import Checkbox from "../ui/checkbox/checkbox.svelte";
|
|
53
|
+
import { orderBy } from "lodash";
|
|
54
|
+
import type { Snippet } from "svelte";
|
|
55
|
+
import Button from "../ui/button/button.svelte";
|
|
56
|
+
|
|
57
|
+
let {
|
|
58
|
+
data,
|
|
59
|
+
columns = Object.keys(data[0]).map((key) => {
|
|
60
|
+
return {
|
|
61
|
+
id: key,
|
|
62
|
+
};
|
|
63
|
+
}),
|
|
64
|
+
showCollapsible = false,
|
|
65
|
+
sort = $bindable({}),
|
|
66
|
+
localSorting = false,
|
|
67
|
+
selectedRecords = $bindable(),
|
|
68
|
+
showCheckboxes = true,
|
|
69
|
+
selectByColumn,
|
|
70
|
+
showLastRowBorder,
|
|
71
|
+
showLastColumnBorder,
|
|
72
|
+
parentWidth,
|
|
73
|
+
overrideCell,
|
|
74
|
+
tools,
|
|
75
|
+
collapsible,
|
|
76
|
+
unifiedBgColor,
|
|
77
|
+
select,
|
|
78
|
+
tableWidth = $bindable(),
|
|
79
|
+
}: TableProps = $props();
|
|
80
|
+
|
|
81
|
+
let expandedRows: boolean[] = $state(new Array(data.length).fill(false));
|
|
82
|
+
|
|
83
|
+
// calculate columns count
|
|
84
|
+
const toolsExists = selectedRecords || tools ? 1 : 0;
|
|
85
|
+
const columnsLength = columns.length + toolsExists;
|
|
86
|
+
|
|
87
|
+
// set table width
|
|
88
|
+
let columnsWidths: number[] = $state([]);
|
|
89
|
+
$effect(() => {
|
|
90
|
+
tableWidth =
|
|
91
|
+
columnsWidths.reduce((sum, width) => sum + width, 0) +
|
|
92
|
+
(columnsLength - 1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function sortHandler(columnId: string) {
|
|
96
|
+
if (sort[columnId] === "asc") {
|
|
97
|
+
sort[columnId] = "desc";
|
|
98
|
+
} else if (sort[columnId] === "desc") {
|
|
99
|
+
delete sort[columnId];
|
|
100
|
+
} else {
|
|
101
|
+
sort[columnId] = "asc";
|
|
102
|
+
}
|
|
103
|
+
sort = { ...sort };
|
|
104
|
+
|
|
105
|
+
// update the data based on the new sorts
|
|
106
|
+
if (localSorting) {
|
|
107
|
+
if (Object.keys(sort).length) {
|
|
108
|
+
data = orderBy(data, Object.keys(sort), Object.values(sort));
|
|
109
|
+
} else {
|
|
110
|
+
const columnNames = columns.map((column) => column.id);
|
|
111
|
+
const defaultSorts = columns.map((column) => "asc") as Array<
|
|
112
|
+
"asc" | "desc"
|
|
113
|
+
>;
|
|
114
|
+
data = orderBy(data, columnNames, defaultSorts);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function toggleCheckAllHandler(value: boolean) {
|
|
120
|
+
if (value) {
|
|
121
|
+
if (selectByColumn) {
|
|
122
|
+
selectedRecords = data.map(
|
|
123
|
+
(entry, index) => entry[selectByColumn],
|
|
124
|
+
);
|
|
125
|
+
} else {
|
|
126
|
+
selectedRecords = data.map((entry, index) => index);
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
selectedRecords = [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function checkBoxHandler(event: MouseEvent, entry: any, index: number) {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
|
|
136
|
+
const value =
|
|
137
|
+
selectByColumn && entry.hasOwnProperty(selectByColumn)
|
|
138
|
+
? entry[selectByColumn]
|
|
139
|
+
: index;
|
|
140
|
+
|
|
141
|
+
if (selectedRecords) {
|
|
142
|
+
const foundIndex = selectedRecords.findIndex((v) => v === value);
|
|
143
|
+
|
|
144
|
+
if (foundIndex !== -1) {
|
|
145
|
+
selectedRecords.splice(foundIndex, 1);
|
|
146
|
+
} else {
|
|
147
|
+
selectedRecords.push(value);
|
|
148
|
+
}
|
|
149
|
+
selectedRecords = [...selectedRecords];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function isCheckedHandler(entry: any, index: number): boolean {
|
|
154
|
+
const value =
|
|
155
|
+
selectByColumn && entry.hasOwnProperty(selectByColumn)
|
|
156
|
+
? entry[selectByColumn]
|
|
157
|
+
: index;
|
|
158
|
+
|
|
159
|
+
if (selectedRecords) {
|
|
160
|
+
return selectedRecords.includes(value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
<div
|
|
168
|
+
style="
|
|
169
|
+
display: grid;
|
|
170
|
+
grid-template-columns: minmax(auto, 7.5rem) repeat({columnsLength -
|
|
171
|
+
1}, minmax(auto, 15rem));
|
|
172
|
+
grid-template-rows: 2.5rem;
|
|
173
|
+
"
|
|
174
|
+
>
|
|
175
|
+
{#if selectedRecords || tools}
|
|
176
|
+
<div
|
|
177
|
+
bind:clientWidth={columnsWidths[0]}
|
|
178
|
+
class="
|
|
179
|
+
sticky left-0 top-0 z-20
|
|
180
|
+
flex items-center p-2.5 text-xs h-10
|
|
181
|
+
border-r border-b gap-2
|
|
182
|
+
{unifiedBgColor ? unifiedBgColor : 'bg-soft'}
|
|
183
|
+
"
|
|
184
|
+
>
|
|
185
|
+
<!-- collapsable toggle -->
|
|
186
|
+
{#if showCollapsible}
|
|
187
|
+
<div class="w-[20px]"></div>
|
|
188
|
+
{/if}
|
|
189
|
+
{#if selectedRecords && showCheckboxes}
|
|
190
|
+
<Checkbox
|
|
191
|
+
class="border-muted-foreground hover:border-foreground"
|
|
192
|
+
onCheckedChange={toggleCheckAllHandler}
|
|
193
|
+
checked={Boolean(selectedRecords.length)}
|
|
194
|
+
/>
|
|
195
|
+
{/if}
|
|
196
|
+
</div>
|
|
197
|
+
{/if}
|
|
198
|
+
{#each columns as column, index}
|
|
199
|
+
{@const lastColumn = columns.length - 1 === index}
|
|
200
|
+
{@const ColumnIcon = column.icon}
|
|
201
|
+
<button
|
|
202
|
+
bind:clientWidth={columnsWidths[index + toolsExists]}
|
|
203
|
+
onclick={() => sortHandler(column.id)}
|
|
204
|
+
class="
|
|
205
|
+
sticky top-0 z-10
|
|
206
|
+
flex items-center p-2.5 text-xs h-10
|
|
207
|
+
{unifiedBgColor ? unifiedBgColor : 'bg-soft'}
|
|
208
|
+
{lastColumn && !showLastColumnBorder ? '' : 'border-r'}
|
|
209
|
+
border-b gap-2
|
|
210
|
+
"
|
|
211
|
+
>
|
|
212
|
+
{#if sort[column.id] === "asc"}
|
|
213
|
+
<ArrowDownNarrowWide
|
|
214
|
+
size="12.5"
|
|
215
|
+
class="text-muted-foreground"
|
|
216
|
+
/>
|
|
217
|
+
{:else if sort[column.id] === "desc"}
|
|
218
|
+
<ArrowUpWideNarrow size="12.5" class="text-muted-foreground" />
|
|
219
|
+
{:else}
|
|
220
|
+
<ColumnIcon size="12.5" class="text-muted-foreground" />
|
|
221
|
+
{/if}
|
|
222
|
+
<div class="font-bold">{column.id}</div>
|
|
223
|
+
<div class="text-muted-foreground text-[0.7rem]">
|
|
224
|
+
{column.subtext}
|
|
225
|
+
</div>
|
|
226
|
+
</button>
|
|
227
|
+
{/each}
|
|
228
|
+
{#if Object.keys(data).length}
|
|
229
|
+
{#each data as entry, index}
|
|
230
|
+
{@const isDisabled = Boolean(entry.__disabled)}
|
|
231
|
+
{@const lastRow = data.length - 1 === index}
|
|
232
|
+
{#if selectedRecords || tools}
|
|
233
|
+
<div
|
|
234
|
+
class="
|
|
235
|
+
sticky left-0
|
|
236
|
+
flex items-center p-2.5 text-xs h-10
|
|
237
|
+
{unifiedBgColor ? unifiedBgColor : 'bg-background'}
|
|
238
|
+
border-r gap-2
|
|
239
|
+
"
|
|
240
|
+
>
|
|
241
|
+
<!-- collapsable toggle -->
|
|
242
|
+
{#if showCollapsible}
|
|
243
|
+
<Button
|
|
244
|
+
variant="ghost"
|
|
245
|
+
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent transition-transform"
|
|
246
|
+
style={expandedRows[index]
|
|
247
|
+
? "transform: rotate(90deg);"
|
|
248
|
+
: "transform: rotate(0deg);"}
|
|
249
|
+
Icon={ChevronRight}
|
|
250
|
+
onclick={() => {
|
|
251
|
+
expandedRows[index] = !expandedRows[index];
|
|
252
|
+
expandedRows = [...expandedRows];
|
|
253
|
+
}}
|
|
254
|
+
disabled={isDisabled}
|
|
255
|
+
></Button>
|
|
256
|
+
{/if}
|
|
257
|
+
{#if selectedRecords && showCheckboxes}
|
|
258
|
+
<Checkbox
|
|
259
|
+
class="border-muted-foreground
|
|
260
|
+
hover:border-foreground"
|
|
261
|
+
onclick={(event) =>
|
|
262
|
+
checkBoxHandler(event, entry, index)}
|
|
263
|
+
checked={isCheckedHandler(entry, index)}
|
|
264
|
+
disabled={isDisabled}
|
|
265
|
+
/>
|
|
266
|
+
{/if}
|
|
267
|
+
{#if tools && !isDisabled}
|
|
268
|
+
{@render tools(entry, index)}
|
|
269
|
+
{/if}
|
|
270
|
+
</div>
|
|
271
|
+
{/if}
|
|
272
|
+
{#each columns as column, index}
|
|
273
|
+
{@const lastColumn = columns.length - 1 === index}
|
|
274
|
+
{@const fieldValue = entry[column.id]}
|
|
275
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
276
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
277
|
+
<div
|
|
278
|
+
onclick={() => {
|
|
279
|
+
select?.onSelect(entry);
|
|
280
|
+
}}
|
|
281
|
+
class="
|
|
282
|
+
flex items-center p-2.5 text-xs h-10 text-nowrap overflow-clip
|
|
283
|
+
{select ? 'cursor-pointer' : ''}
|
|
284
|
+
{unifiedBgColor ? unifiedBgColor : 'bg-background'}
|
|
285
|
+
{lastColumn && !showLastColumnBorder ? '' : 'border-r'}
|
|
286
|
+
"
|
|
287
|
+
>
|
|
288
|
+
{#if overrideCell}
|
|
289
|
+
{@render overrideCell(fieldValue, column, entry)}
|
|
290
|
+
{:else}
|
|
291
|
+
{fieldValue}
|
|
292
|
+
{/if}
|
|
293
|
+
</div>
|
|
294
|
+
{/each}
|
|
295
|
+
<!-- nested data -->
|
|
296
|
+
<div
|
|
297
|
+
style="grid-column: span {columnsLength};"
|
|
298
|
+
class="
|
|
299
|
+
{!showLastColumnBorder ? '' : 'border-r'}
|
|
300
|
+
{lastRow && !showLastRowBorder ? '' : 'border-b'}
|
|
301
|
+
"
|
|
302
|
+
>
|
|
303
|
+
<div
|
|
304
|
+
style="
|
|
305
|
+
{parentWidth ? `width: ${parentWidth}px` : ''};
|
|
306
|
+
max-width: 100vw;
|
|
307
|
+
{expandedRows[index] ? '' : 'height: 0px;'}
|
|
308
|
+
"
|
|
309
|
+
class="
|
|
310
|
+
sticky left-0 top-0 overflow-auto bg-soft
|
|
311
|
+
{unifiedBgColor ? unifiedBgColor : ''}
|
|
312
|
+
{expandedRows[index] ? 'border-t' : ''}
|
|
313
|
+
"
|
|
314
|
+
>
|
|
315
|
+
{#if collapsible && expandedRows[index]}
|
|
316
|
+
{@render collapsible(entry, index)}
|
|
317
|
+
{/if}
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
{/each}
|
|
321
|
+
{/if}
|
|
322
|
+
</div>
|
|
323
|
+
{#if Object.keys(data).length === 0}
|
|
324
|
+
<div
|
|
325
|
+
class="
|
|
326
|
+
sticky left-0
|
|
327
|
+
flex justify-center items-center gap-2 text-xs
|
|
328
|
+
text-muted-foreground text-center rounded-md p-2 h-14
|
|
329
|
+
"
|
|
330
|
+
style="
|
|
331
|
+
{parentWidth ? `width: ${parentWidth}px` : ''};
|
|
332
|
+
"
|
|
333
|
+
>
|
|
334
|
+
<CircleOff size="17.5" />
|
|
335
|
+
No result
|
|
336
|
+
</div>
|
|
337
|
+
{/if}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { TableProps } from "./table.svelte";
|
|
2
|
+
|
|
3
|
+
import { ctx } from "$lib/store.svelte";
|
|
4
|
+
import {
|
|
5
|
+
Binary,
|
|
6
|
+
Braces,
|
|
7
|
+
Brackets,
|
|
8
|
+
Calendar,
|
|
9
|
+
CalendarClock,
|
|
10
|
+
Clock,
|
|
11
|
+
Hash,
|
|
12
|
+
Key,
|
|
13
|
+
Text,
|
|
14
|
+
Type,
|
|
15
|
+
} from "lucide-svelte/icons";
|
|
16
|
+
|
|
17
|
+
export function getCollectionColumns(collectionName: string): TableProps['columns'] {
|
|
18
|
+
const collectionFields = getFields(collectionName);
|
|
19
|
+
const headers: TableProps['columns'] = [];
|
|
20
|
+
for (const fieldName in collectionFields) {
|
|
21
|
+
const field = collectionFields[fieldName];
|
|
22
|
+
headers.push({
|
|
23
|
+
id: field.key,
|
|
24
|
+
subtext: field.type,
|
|
25
|
+
icon: getFieldIcon(fieldName, collectionName),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return headers;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getFieldIcon(fieldName: string, collectionName: string) {
|
|
32
|
+
const field = getField(fieldName, collectionName);
|
|
33
|
+
if (fieldName === "id") {
|
|
34
|
+
return Key;
|
|
35
|
+
} else if (field.type === "string") {
|
|
36
|
+
return Type;
|
|
37
|
+
} else if (field.type === "text") {
|
|
38
|
+
return Text;
|
|
39
|
+
} else if (field.type === "object") {
|
|
40
|
+
return Braces;
|
|
41
|
+
} else if (field.type === "array") {
|
|
42
|
+
return Brackets;
|
|
43
|
+
} else if (field.type === "bool") {
|
|
44
|
+
return Binary;
|
|
45
|
+
} else if (field.type === "integer") {
|
|
46
|
+
return Hash;
|
|
47
|
+
} else if (field.type === "long") {
|
|
48
|
+
return Hash;
|
|
49
|
+
} else if (field.type === "float") {
|
|
50
|
+
return Hash;
|
|
51
|
+
} else if (field.type === "decimal") {
|
|
52
|
+
return Hash;
|
|
53
|
+
} else if (field.type === "date") {
|
|
54
|
+
return Calendar;
|
|
55
|
+
} else if (field.type === "datetime") {
|
|
56
|
+
return CalendarClock;
|
|
57
|
+
} else if (field.type === "time") {
|
|
58
|
+
return Clock;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new Error(`(${field.type}) doesnt have an icon`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getFields(collectionName: string) {
|
|
65
|
+
return ctx.meta.collections[collectionName].fields;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getField(fieldName: string, collectionName: string) {
|
|
69
|
+
return getFields(collectionName)[fieldName];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getCollectionPrimaryField(collectionName: string): string | undefined {
|
|
73
|
+
const collectionFields =
|
|
74
|
+
ctx.meta.collections[collectionName].fields;
|
|
75
|
+
|
|
76
|
+
const primaryFieldObject: any = Object.values(collectionFields).find(
|
|
77
|
+
(field) => (field as any).type === "string",
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
primaryFieldObject &&
|
|
82
|
+
typeof primaryFieldObject === "object" &&
|
|
83
|
+
"key" in primaryFieldObject
|
|
84
|
+
) {
|
|
85
|
+
const fieldName = primaryFieldObject.key;
|
|
86
|
+
return fieldName;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getCollectionParamsFields(collectionName: string, allFields = false) {
|
|
91
|
+
const relations = ctx.meta.relations;
|
|
92
|
+
const foreignFields = relations
|
|
93
|
+
.filter((relation) => {
|
|
94
|
+
return relation.from.collection === collectionName
|
|
95
|
+
})
|
|
96
|
+
.map((relation) => {
|
|
97
|
+
return {
|
|
98
|
+
field: relation.from.field,
|
|
99
|
+
collection: relation.to.collection,
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const columns = [];
|
|
104
|
+
for (let index = 0; index < foreignFields.length; index++) {
|
|
105
|
+
const foreignField = foreignFields[index];
|
|
106
|
+
if (!foreignField.collection) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
columns.push(`${foreignField.field}.id`);
|
|
110
|
+
|
|
111
|
+
if (!allFields) {
|
|
112
|
+
const primaryField = getCollectionPrimaryField(foreignField.collection)
|
|
113
|
+
if (primaryField) {
|
|
114
|
+
columns.push(`${foreignField.field}.${primaryField}`);
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
const fieldNames = Object.keys(ctx.meta.collections[foreignField.collection].fields);
|
|
118
|
+
for (let index = 0; index < fieldNames.length; index++) {
|
|
119
|
+
const fieldName = fieldNames[index];
|
|
120
|
+
columns.push(`${foreignField.field}.${fieldName}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const foreignColumns = columns.join(",");
|
|
126
|
+
return `*${foreignColumns ? `,${foreignColumns}` : ""}`;
|
|
127
|
+
}
|