@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,74 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getFieldRelation } from "$lib/utils";
|
|
3
|
+
import { ExternalLink } from "lucide-svelte";
|
|
4
|
+
import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
|
|
5
|
+
import { getField } from "./utils";
|
|
6
|
+
import _ from "lodash";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
collectionName: string;
|
|
10
|
+
fieldName: string;
|
|
11
|
+
value: any;
|
|
12
|
+
entry: Record<string, any>;
|
|
13
|
+
tableParams?: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
collectionName,
|
|
18
|
+
fieldName,
|
|
19
|
+
value,
|
|
20
|
+
entry,
|
|
21
|
+
tableParams = $bindable(),
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
|
|
24
|
+
const field = getField(fieldName, collectionName);
|
|
25
|
+
const relation = getFieldRelation(collectionName, fieldName);
|
|
26
|
+
const isRefrenceField = Boolean(
|
|
27
|
+
getFieldRelation(collectionName, fieldName),
|
|
28
|
+
);
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
{#if value === ""}
|
|
32
|
+
<div class="text-muted-foreground">EMPTY STRING</div>
|
|
33
|
+
{:else if value === null || value === undefined}
|
|
34
|
+
<div class="text-muted-foreground">NULL</div>
|
|
35
|
+
{:else if isRefrenceField}
|
|
36
|
+
{#if value.id !== 0}
|
|
37
|
+
{@const primaryField = Object.values(value)[1]}
|
|
38
|
+
{#if value.id}
|
|
39
|
+
<div class="flex items-center gap-2">
|
|
40
|
+
<div>{value.id}</div>
|
|
41
|
+
{#if primaryField}
|
|
42
|
+
<div class="border bg-muted px-3 py-1 rounded-full">
|
|
43
|
+
{primaryField}
|
|
44
|
+
</div>
|
|
45
|
+
{/if}
|
|
46
|
+
<UpdateDetailViewButton
|
|
47
|
+
collectionName={relation.to.collection}
|
|
48
|
+
recordId={value.id}
|
|
49
|
+
variant="ghost"
|
|
50
|
+
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
|
|
51
|
+
Icon={ExternalLink}
|
|
52
|
+
onSuccessfullSave={async () => {
|
|
53
|
+
tableParams = { ...tableParams };
|
|
54
|
+
}}
|
|
55
|
+
></UpdateDetailViewButton>
|
|
56
|
+
</div>
|
|
57
|
+
{:else}
|
|
58
|
+
<div class="text-muted-foreground">NULL</div>
|
|
59
|
+
{/if}
|
|
60
|
+
{:else}
|
|
61
|
+
<div class="text-muted-foreground">PARENT ID</div>
|
|
62
|
+
{/if}
|
|
63
|
+
{:else if field.type === "datetime"}
|
|
64
|
+
{@const date = new Date(value).toLocaleDateString()}
|
|
65
|
+
{@const time = new Date(value).toLocaleTimeString()}
|
|
66
|
+
<div>{date}, {time}</div>
|
|
67
|
+
{:else if field.type === "date"}
|
|
68
|
+
{@const date = new Date(value).toLocaleDateString()}
|
|
69
|
+
<div>{date}</div>
|
|
70
|
+
{:else if field.type === "time"}
|
|
71
|
+
<div>{value}</div>
|
|
72
|
+
{:else}
|
|
73
|
+
{value}
|
|
74
|
+
{/if}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Popover from "$lib/components/ui/popover/index.js";
|
|
3
|
+
import Filter from "./filter.svelte";
|
|
4
|
+
import {
|
|
5
|
+
Plus,
|
|
6
|
+
Boxes,
|
|
7
|
+
CircleOff,
|
|
8
|
+
ListFilter,
|
|
9
|
+
Diamond,
|
|
10
|
+
Trash,
|
|
11
|
+
} from "lucide-svelte";
|
|
12
|
+
import { buttonVariants } from "../ui/button";
|
|
13
|
+
import _ from "lodash";
|
|
14
|
+
import { ctx } from "$lib/store.svelte";
|
|
15
|
+
import Button from "../ui/button/button.svelte";
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
filter: any;
|
|
19
|
+
collectionName: string;
|
|
20
|
+
isFirst?: boolean;
|
|
21
|
+
deleteFilter?: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
filter = $bindable({}),
|
|
26
|
+
collectionName,
|
|
27
|
+
isFirst = false,
|
|
28
|
+
deleteFilter,
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
|
|
31
|
+
let firstPopover = $state(false);
|
|
32
|
+
let secondPopover = $state(false);
|
|
33
|
+
|
|
34
|
+
$effect.pre(() => {
|
|
35
|
+
// convert direct values to { $eq: (value) }
|
|
36
|
+
for (let index = 0; index < Object.keys(filter).length; index++) {
|
|
37
|
+
const key = Object.keys(filter)[index];
|
|
38
|
+
const value = filter[key];
|
|
39
|
+
if (key !== "$and" && key !== "$or") {
|
|
40
|
+
if (!_.isPlainObject(value)) {
|
|
41
|
+
filter[key] = {
|
|
42
|
+
$eq: value,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function groupAddingHandler(filter: any, key: string) {
|
|
50
|
+
if (key === "$and" || key === "$or") {
|
|
51
|
+
filter[key] = [];
|
|
52
|
+
} else {
|
|
53
|
+
filter[key] = {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getGroupOptions(filter: any) {
|
|
58
|
+
const collectionFieldNames = Object.keys(
|
|
59
|
+
ctx.meta.collections[collectionName].fields,
|
|
60
|
+
);
|
|
61
|
+
const options = ["$and", "$or", ...collectionFieldNames];
|
|
62
|
+
const existingPropertiesNames = Object.keys(filter);
|
|
63
|
+
const filteredOptions = _.difference(options, existingPropertiesNames);
|
|
64
|
+
return filteredOptions;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getOperatorOptions(filter: any) {
|
|
68
|
+
const operators = ctx.meta.filter.operators;
|
|
69
|
+
const existingPropertiesNames = Object.keys(filter);
|
|
70
|
+
const filteredOptions = _.difference(
|
|
71
|
+
operators,
|
|
72
|
+
existingPropertiesNames,
|
|
73
|
+
);
|
|
74
|
+
return filteredOptions;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function operatorAddingHandler(filter: any, key: string) {
|
|
78
|
+
filter[key] = "";
|
|
79
|
+
}
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
{#snippet filterAddButton(filter: any)}
|
|
83
|
+
<Popover.Root bind:open={firstPopover}>
|
|
84
|
+
<Popover.Trigger
|
|
85
|
+
class={buttonVariants({
|
|
86
|
+
variant: "ghost",
|
|
87
|
+
class: "h-7 px-2 text-xs font-normal text-muted-foreground",
|
|
88
|
+
})}
|
|
89
|
+
>
|
|
90
|
+
<Plus />
|
|
91
|
+
</Popover.Trigger>
|
|
92
|
+
<Popover.Content class="flex w-48 flex-col p-2 max-h-60 overflow-auto">
|
|
93
|
+
{#each getGroupOptions(filter) as fieldName}
|
|
94
|
+
<button
|
|
95
|
+
onclick={() => {
|
|
96
|
+
groupAddingHandler(filter, fieldName);
|
|
97
|
+
firstPopover = false;
|
|
98
|
+
}}
|
|
99
|
+
class="flex cursor-pointer items-center gap-2 rounded-md p-2 px-2 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
100
|
+
>
|
|
101
|
+
<div>{fieldName}</div>
|
|
102
|
+
</button>
|
|
103
|
+
{/each}
|
|
104
|
+
</Popover.Content>
|
|
105
|
+
</Popover.Root>
|
|
106
|
+
{/snippet}
|
|
107
|
+
|
|
108
|
+
<div class="flex flex-col rounded-md {isFirst ? '' : 'border'}">
|
|
109
|
+
<div class="flex justify-between items-center gap-2 border-b p-2 h-10">
|
|
110
|
+
<div class="flex text-xs font-semibold text-muted-foreground gap-2">
|
|
111
|
+
<ListFilter size="17.5" />
|
|
112
|
+
<div>Filter</div>
|
|
113
|
+
</div>
|
|
114
|
+
<div>
|
|
115
|
+
{@render filterAddButton(filter)}
|
|
116
|
+
{#if deleteFilter}
|
|
117
|
+
<Button
|
|
118
|
+
class="text-muted-foreground px-2 h-7"
|
|
119
|
+
variant="ghost"
|
|
120
|
+
size="icon"
|
|
121
|
+
Icon={Trash}
|
|
122
|
+
onclick={deleteFilter}
|
|
123
|
+
></Button>
|
|
124
|
+
{/if}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div
|
|
128
|
+
class="flex flex-col gap-2 p-2 {isFirst
|
|
129
|
+
? 'max-h-100 overflow-auto'
|
|
130
|
+
: ''}"
|
|
131
|
+
>
|
|
132
|
+
{#if Object.entries(filter).length}
|
|
133
|
+
{#each Object.entries(filter) as [key, value]}
|
|
134
|
+
{@const collectionFields =
|
|
135
|
+
ctx.meta.collections[collectionName].fields}
|
|
136
|
+
{#if key === "$and" || key === "$or"}
|
|
137
|
+
<div class="flex flex-col rounded-md border">
|
|
138
|
+
<div
|
|
139
|
+
class="flex justify-between items-center gap-2 text-xs font-semibold text-muted-foreground p-2 border-b h-10"
|
|
140
|
+
>
|
|
141
|
+
<div class="flex gap-2">
|
|
142
|
+
<Boxes size="17.5" />
|
|
143
|
+
{key === "$and" ? "AND" : "OR"}
|
|
144
|
+
</div>
|
|
145
|
+
<div>
|
|
146
|
+
<Button
|
|
147
|
+
class="text-muted-foreground px-2 h-7"
|
|
148
|
+
variant="ghost"
|
|
149
|
+
size="icon"
|
|
150
|
+
Icon={Plus}
|
|
151
|
+
onclick={() =>
|
|
152
|
+
(filter[key] = [...filter[key], {}])}
|
|
153
|
+
></Button>
|
|
154
|
+
<Button
|
|
155
|
+
class="text-muted-foreground px-2 h-7"
|
|
156
|
+
variant="ghost"
|
|
157
|
+
size="icon"
|
|
158
|
+
Icon={Trash}
|
|
159
|
+
onclick={() => {
|
|
160
|
+
delete filter[key];
|
|
161
|
+
}}
|
|
162
|
+
></Button>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
{#if Object.keys(filter[key]).length}
|
|
166
|
+
<div class="flex flex-col gap-2 p-2">
|
|
167
|
+
{#each filter[key] as _, index}
|
|
168
|
+
<Filter
|
|
169
|
+
bind:filter={filter[key][index]}
|
|
170
|
+
{collectionName}
|
|
171
|
+
deleteFilter={() => {
|
|
172
|
+
filter[key].splice(index, 1);
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
{/each}
|
|
176
|
+
</div>
|
|
177
|
+
{:else}
|
|
178
|
+
<div
|
|
179
|
+
class="flex justify-center gap-2 text-xs text-muted-foreground text-center p-4"
|
|
180
|
+
>
|
|
181
|
+
<CircleOff size="17.5" />
|
|
182
|
+
No rules defined
|
|
183
|
+
</div>
|
|
184
|
+
{/if}
|
|
185
|
+
</div>
|
|
186
|
+
{:else}
|
|
187
|
+
<div class="flex flex-col gap-2 rounded-md border">
|
|
188
|
+
<div
|
|
189
|
+
class="flex gap-2 justify-between items-center text-xs font-semibold text-muted-foreground p-2 border-b h-10"
|
|
190
|
+
>
|
|
191
|
+
<div class="flex gap-2">
|
|
192
|
+
<Diamond size="17.5" />
|
|
193
|
+
{key}
|
|
194
|
+
</div>
|
|
195
|
+
<div>
|
|
196
|
+
<Popover.Root bind:open={secondPopover}>
|
|
197
|
+
<Popover.Trigger
|
|
198
|
+
class={buttonVariants({
|
|
199
|
+
variant: "ghost",
|
|
200
|
+
class: "h-7 px-2 text-xs font-normal",
|
|
201
|
+
})}
|
|
202
|
+
>
|
|
203
|
+
<Plus />
|
|
204
|
+
</Popover.Trigger>
|
|
205
|
+
<Popover.Content
|
|
206
|
+
class="flex w-48 flex-col p-2 max-h-60 overflow-auto"
|
|
207
|
+
>
|
|
208
|
+
{#each getOperatorOptions(filter[key]) as fieldName}
|
|
209
|
+
<button
|
|
210
|
+
onclick={() => {
|
|
211
|
+
operatorAddingHandler(
|
|
212
|
+
filter[key],
|
|
213
|
+
fieldName,
|
|
214
|
+
);
|
|
215
|
+
secondPopover = false;
|
|
216
|
+
}}
|
|
217
|
+
class="flex cursor-pointer items-center gap-2 rounded-md p-2 px-2 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
218
|
+
>
|
|
219
|
+
<div>{fieldName}</div>
|
|
220
|
+
</button>
|
|
221
|
+
{/each}
|
|
222
|
+
</Popover.Content>
|
|
223
|
+
</Popover.Root>
|
|
224
|
+
<Button
|
|
225
|
+
class="text-muted-foreground px-2 h-7"
|
|
226
|
+
variant="ghost"
|
|
227
|
+
size="icon"
|
|
228
|
+
Icon={Trash}
|
|
229
|
+
onclick={() => {
|
|
230
|
+
delete filter[key];
|
|
231
|
+
}}
|
|
232
|
+
></Button>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
{#if Object.entries(value as any).length}
|
|
236
|
+
<div
|
|
237
|
+
class="gap-2 p-2"
|
|
238
|
+
style="display: grid; grid-template-columns: auto 1fr auto;"
|
|
239
|
+
>
|
|
240
|
+
{#each Object.entries(value as any) as [ruleKey, ruleValue]}
|
|
241
|
+
<div
|
|
242
|
+
class="rounded-md bg-muted border text-xs text-muted-foreground py-1 px-2"
|
|
243
|
+
>
|
|
244
|
+
{ruleKey}
|
|
245
|
+
</div>
|
|
246
|
+
<input
|
|
247
|
+
class="w-full rounded-md bg-muted border text-xs text-muted-foreground py-1 px-2"
|
|
248
|
+
type="text"
|
|
249
|
+
bind:value={filter[key][ruleKey]}
|
|
250
|
+
/>
|
|
251
|
+
<Button
|
|
252
|
+
class="text-muted-foreground px-2 h-7"
|
|
253
|
+
variant="ghost"
|
|
254
|
+
size="icon"
|
|
255
|
+
Icon={Trash}
|
|
256
|
+
onclick={() => {
|
|
257
|
+
delete filter[key][ruleKey];
|
|
258
|
+
}}
|
|
259
|
+
></Button>
|
|
260
|
+
{/each}
|
|
261
|
+
</div>
|
|
262
|
+
{:else}
|
|
263
|
+
<div
|
|
264
|
+
class="flex justify-center gap-2 text-xs text-muted-foreground text-center rounded-md p-2"
|
|
265
|
+
>
|
|
266
|
+
<CircleOff size="17.5" />
|
|
267
|
+
No rules defined
|
|
268
|
+
</div>
|
|
269
|
+
{/if}
|
|
270
|
+
</div>
|
|
271
|
+
{/if}
|
|
272
|
+
{/each}
|
|
273
|
+
{:else}
|
|
274
|
+
<div
|
|
275
|
+
class="flex justify-center gap-2 text-xs text-muted-foreground text-center rounded-md p-2"
|
|
276
|
+
>
|
|
277
|
+
<CircleOff size="17.5" />
|
|
278
|
+
No rules defined
|
|
279
|
+
</div>
|
|
280
|
+
{/if}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Popover from "$lib/components/ui/popover/index.js";
|
|
3
|
+
import { ListFilter } from "lucide-svelte";
|
|
4
|
+
import { buttonVariants } from "../ui/button";
|
|
5
|
+
import Filter from "./filter.svelte";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
filter: any;
|
|
9
|
+
collectionName: string;
|
|
10
|
+
showText?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
filter = $bindable({}),
|
|
15
|
+
showText = true,
|
|
16
|
+
collectionName,
|
|
17
|
+
}: Props = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Popover.Root>
|
|
21
|
+
<Popover.Trigger
|
|
22
|
+
class={buttonVariants({
|
|
23
|
+
variant: "ghost",
|
|
24
|
+
class: "h-7 px-3 text-xs font-normal",
|
|
25
|
+
})}
|
|
26
|
+
>
|
|
27
|
+
<ListFilter />
|
|
28
|
+
{#if showText}
|
|
29
|
+
{#if Object.keys(filter).length}
|
|
30
|
+
Filtered by {Object.keys(filter).length} rules
|
|
31
|
+
{:else}
|
|
32
|
+
Filter
|
|
33
|
+
{/if}
|
|
34
|
+
{/if}
|
|
35
|
+
</Popover.Trigger>
|
|
36
|
+
<Popover.Content class="w-screen max-w-100 p-0">
|
|
37
|
+
<Filter bind:filter {collectionName} isFirst={true} />
|
|
38
|
+
</Popover.Content>
|
|
39
|
+
</Popover.Root>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ArrowLeft, ArrowRight } from "lucide-svelte";
|
|
3
|
+
import Button from "../ui/button/button.svelte";
|
|
4
|
+
import Input from "../ui/input/input.svelte";
|
|
5
|
+
import * as Select from "../ui/select";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
limit: string;
|
|
9
|
+
totalCount: number;
|
|
10
|
+
currentPage: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
limit = $bindable("100"),
|
|
15
|
+
currentPage = $bindable(1),
|
|
16
|
+
totalCount,
|
|
17
|
+
}: Props = $props();
|
|
18
|
+
|
|
19
|
+
let pageCount: number = $derived(Math.ceil(totalCount / Number(limit)));
|
|
20
|
+
let footerWidth: number = $state(0);
|
|
21
|
+
let footerIsSmall: boolean = $derived(footerWidth < 560);
|
|
22
|
+
|
|
23
|
+
function gotoPage(pageNumber: number) {
|
|
24
|
+
currentPage = pageNumber;
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<div
|
|
29
|
+
bind:clientWidth={footerWidth}
|
|
30
|
+
class="flex justify-between box-content border-t bg-background px-2 h-10"
|
|
31
|
+
>
|
|
32
|
+
<div class="flex items-center gap-2">
|
|
33
|
+
<Button
|
|
34
|
+
disabled={Number(currentPage) <= 1}
|
|
35
|
+
onclick={() => gotoPage(currentPage - 1)}
|
|
36
|
+
variant="outline"
|
|
37
|
+
class="h-7 w-7 p-0"
|
|
38
|
+
Icon={ArrowLeft}
|
|
39
|
+
></Button>
|
|
40
|
+
{#if !footerIsSmall}
|
|
41
|
+
<div class="text-xs text-muted-foreground">Page</div>
|
|
42
|
+
{/if}
|
|
43
|
+
<Input bind:value={currentPage} class="h-7 w-14 bg-muted" />
|
|
44
|
+
<div class="text-xs text-muted-foreground">
|
|
45
|
+
{footerIsSmall ? "/" : "of"}
|
|
46
|
+
{pageCount}
|
|
47
|
+
</div>
|
|
48
|
+
<Button
|
|
49
|
+
disabled={Number(currentPage) >= pageCount}
|
|
50
|
+
onclick={() => gotoPage(currentPage + 1)}
|
|
51
|
+
variant="outline"
|
|
52
|
+
class="h-7 w-7 p-0"
|
|
53
|
+
Icon={ArrowRight}
|
|
54
|
+
></Button>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="flex h-10 items-center gap-2">
|
|
57
|
+
<Select.Root type="single" name="limit" bind:value={limit}>
|
|
58
|
+
<Select.Trigger class="h-7 {footerIsSmall ? 'w-20' : 'w-28'}"
|
|
59
|
+
>{limit}
|
|
60
|
+
{footerIsSmall ? "" : "rows"}</Select.Trigger
|
|
61
|
+
>
|
|
62
|
+
<Select.Content>
|
|
63
|
+
<Select.Group>
|
|
64
|
+
<Select.Item
|
|
65
|
+
value="100"
|
|
66
|
+
label="100 {footerIsSmall ? '' : 'rows'}"
|
|
67
|
+
/>
|
|
68
|
+
<Select.Item
|
|
69
|
+
value="500"
|
|
70
|
+
label="500 {footerIsSmall ? '' : 'rows'}"
|
|
71
|
+
/>
|
|
72
|
+
<Select.Item
|
|
73
|
+
value="1000"
|
|
74
|
+
label="1000 {footerIsSmall ? '' : 'rows'}"
|
|
75
|
+
/>
|
|
76
|
+
</Select.Group>
|
|
77
|
+
</Select.Content>
|
|
78
|
+
</Select.Root>
|
|
79
|
+
<div class="text-xs text-muted-foreground">
|
|
80
|
+
{totalCount}
|
|
81
|
+
{footerIsSmall ? "" : "records"}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ctx } from "$lib/store.svelte";
|
|
3
|
+
import { ListRestart, Plus, SquareStack, Trash } from "lucide-svelte";
|
|
4
|
+
import LlmButton from "../LlmButton.svelte";
|
|
5
|
+
import FilterButton from "./filterButton.svelte";
|
|
6
|
+
import SortButton from "./sortButton.svelte";
|
|
7
|
+
import Button from "../ui/button/button.svelte";
|
|
8
|
+
import CreateManyButton from "../createManyButton.svelte";
|
|
9
|
+
import { showDialog } from "../confirmationDialog/store.svelte";
|
|
10
|
+
import { lobb } from "$lib";
|
|
11
|
+
import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
|
|
12
|
+
import type { Snippet } from "svelte";
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
collectionName: string;
|
|
16
|
+
params: any;
|
|
17
|
+
selectedRecords: string[];
|
|
18
|
+
left?: Snippet<[]>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
collectionName,
|
|
23
|
+
params = $bindable(),
|
|
24
|
+
selectedRecords = $bindable(),
|
|
25
|
+
left
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
let headerWidth: number = $state(0);
|
|
29
|
+
let headerIsSmall: boolean = $derived(headerWidth < 560);
|
|
30
|
+
|
|
31
|
+
async function handleDeleteButton() {
|
|
32
|
+
const result = await showDialog(
|
|
33
|
+
"Are you sure?",
|
|
34
|
+
"This will delete all the records you selected.",
|
|
35
|
+
);
|
|
36
|
+
if (result) {
|
|
37
|
+
const res = await lobb.deleteMany(collectionName, {
|
|
38
|
+
id: {
|
|
39
|
+
$in: selectedRecords,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
if (res.status >= 400) {
|
|
43
|
+
const json = await res.json();
|
|
44
|
+
if (json.message === 'Record has children and cannot be deleted') {
|
|
45
|
+
const result = await showDialog(
|
|
46
|
+
"Delete record and all its children?",
|
|
47
|
+
"This record has related child entries. Deleting it will also remove all its children permanently. This action cannot be undone."
|
|
48
|
+
);
|
|
49
|
+
if (result) {
|
|
50
|
+
await lobb.deleteMany(
|
|
51
|
+
collectionName,
|
|
52
|
+
{
|
|
53
|
+
id: {
|
|
54
|
+
$in: selectedRecords,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
true,
|
|
58
|
+
);
|
|
59
|
+
resetTable();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
resetTable();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resetTable() {
|
|
69
|
+
params = { ...params };
|
|
70
|
+
selectedRecords = [];
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<div
|
|
75
|
+
class="flex justify-between items-center gap-2 p-2 border-b bg-background h-10"
|
|
76
|
+
bind:clientWidth={headerWidth}
|
|
77
|
+
>
|
|
78
|
+
<div class="flex items-center gap-1">
|
|
79
|
+
{@render left?.()}
|
|
80
|
+
{#if selectedRecords.length}
|
|
81
|
+
<Button
|
|
82
|
+
Icon={Trash}
|
|
83
|
+
onclick={handleDeleteButton}
|
|
84
|
+
variant="outline"
|
|
85
|
+
class="h-7 px-3 text-xs font-normal"
|
|
86
|
+
>
|
|
87
|
+
Delete {selectedRecords.length}
|
|
88
|
+
{selectedRecords.length > 1 ? "records" : "record"}
|
|
89
|
+
</Button>
|
|
90
|
+
{:else}
|
|
91
|
+
<FilterButton
|
|
92
|
+
bind:filter={params.filter}
|
|
93
|
+
showText={!headerIsSmall}
|
|
94
|
+
{collectionName}
|
|
95
|
+
/>
|
|
96
|
+
<SortButton
|
|
97
|
+
{collectionName}
|
|
98
|
+
bind:sort={params.sort}
|
|
99
|
+
showText={!headerIsSmall}
|
|
100
|
+
/>
|
|
101
|
+
<LlmButton
|
|
102
|
+
variant="outline"
|
|
103
|
+
title="Filter table with AI"
|
|
104
|
+
description="Tell the AI how do you want to filter the table"
|
|
105
|
+
class="h-7 px-3 text-xs font-normal"
|
|
106
|
+
format={{
|
|
107
|
+
type: "json_object",
|
|
108
|
+
}}
|
|
109
|
+
messages={[
|
|
110
|
+
{
|
|
111
|
+
role: "system",
|
|
112
|
+
content: [
|
|
113
|
+
"You will receive natural language queries from a user describing how they want to filter a table list view.",
|
|
114
|
+
"Your task is to generate a filter object based on the user's prompt.",
|
|
115
|
+
`This is the schema of the filter json: ${JSON.stringify(ctx.meta.filter.filter_schema)}`,
|
|
116
|
+
`This is the schema of the current collection: ${JSON.stringify(ctx.meta.collections[collectionName])}`,
|
|
117
|
+
].join(" "),
|
|
118
|
+
},
|
|
119
|
+
]}
|
|
120
|
+
onApiResponseComplete={async (res) => {
|
|
121
|
+
params.filter = JSON.parse(res);
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
{headerIsSmall ? "" : "Filter with AI"}
|
|
125
|
+
</LlmButton>
|
|
126
|
+
{/if}
|
|
127
|
+
</div>
|
|
128
|
+
<div>
|
|
129
|
+
<Button
|
|
130
|
+
variant="ghost"
|
|
131
|
+
class="h-7 px-2 font-normal text-muted-foreground"
|
|
132
|
+
Icon={ListRestart}
|
|
133
|
+
onclick={() => (params = { ...params })}
|
|
134
|
+
>
|
|
135
|
+
{headerIsSmall ? "" : "Refresh"}
|
|
136
|
+
</Button>
|
|
137
|
+
<CreateManyButton
|
|
138
|
+
{collectionName}
|
|
139
|
+
variant="outline"
|
|
140
|
+
class="h-7 px-2 text-xs font-normal"
|
|
141
|
+
Icon={SquareStack}
|
|
142
|
+
onSuccessfullSave={() => (params = { ...params })}
|
|
143
|
+
></CreateManyButton>
|
|
144
|
+
<CreateDetailViewButton
|
|
145
|
+
{collectionName}
|
|
146
|
+
variant="default"
|
|
147
|
+
class="h-7 px-3 text-xs font-normal"
|
|
148
|
+
Icon={Plus}
|
|
149
|
+
onSuccessfullSave={() => (params = { ...params })}
|
|
150
|
+
>
|
|
151
|
+
{headerIsSmall ? "" : "Create"}
|
|
152
|
+
</CreateDetailViewButton>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|