dr-widget 0.1.3__py3-none-any.whl
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.
- dr_widget/__init__.py +5 -0
- dr_widget/py.typed +0 -0
- dr_widget/widgets/__init__.py +5 -0
- dr_widget/widgets/config_file_manager/.gitignore +24 -0
- dr_widget/widgets/config_file_manager/.vscode/extensions.json +3 -0
- dr_widget/widgets/config_file_manager/README.md +89 -0
- dr_widget/widgets/config_file_manager/__init__.py +283 -0
- dr_widget/widgets/config_file_manager/components.json +16 -0
- dr_widget/widgets/config_file_manager/index.html +12 -0
- dr_widget/widgets/config_file_manager/jsrepo.json +18 -0
- dr_widget/widgets/config_file_manager/package.json +49 -0
- dr_widget/widgets/config_file_manager/postcss.config.js +6 -0
- dr_widget/widgets/config_file_manager/public/fonts/Inter-roman.var.woff2 +0 -0
- dr_widget/widgets/config_file_manager/public/vite.svg +1 -0
- dr_widget/widgets/config_file_manager/src/App.svelte +62 -0
- dr_widget/widgets/config_file_manager/src/ConfigFileManager.svelte +605 -0
- dr_widget/widgets/config_file_manager/src/app.css +134 -0
- dr_widget/widgets/config_file_manager/src/index.js +5 -0
- dr_widget/widgets/config_file_manager/src/lib/@test_state.json +20 -0
- dr_widget/widgets/config_file_manager/src/lib/Counter.svelte +10 -0
- dr_widget/widgets/config_file_manager/src/lib/components/file-drop/BrowseConfigsPanel.svelte +137 -0
- dr_widget/widgets/config_file_manager/src/lib/components/file-drop/ComplexJsonViewer.svelte +94 -0
- dr_widget/widgets/config_file_manager/src/lib/components/file-drop/ConfigViewerPanel.svelte +282 -0
- dr_widget/widgets/config_file_manager/src/lib/components/file-drop/LoadedConfigPreview.svelte +74 -0
- dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SaveConfigPanel.svelte +449 -0
- dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SelectedFileRow.svelte +38 -0
- dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SelectedFilesList.svelte +30 -0
- dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SimpleJsonViewer.svelte +405 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/badge/badge.svelte +50 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/badge/index.ts +2 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/button/button.svelte +128 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/button/index.ts +27 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-action.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-content.svelte +15 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-description.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-footer.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-header.svelte +23 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-title.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card.svelte +23 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/card/index.ts +25 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-content.svelte +47 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-description.svelte +21 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-footer.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-header.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-overlay.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-title.svelte +21 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/index.ts +41 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-close.svelte +11 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-content.svelte +41 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-description.svelte +21 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-footer.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-header.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-nested.svelte +16 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-overlay.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-title.svelte +21 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-trigger.svelte +11 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer.svelte +16 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/index.ts +45 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-content.svelte +23 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-description.svelte +23 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-header.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-media.svelte +41 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-title.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty.svelte +23 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/index.ts +22 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-content.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-description.svelte +25 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-error.svelte +58 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-group.svelte +23 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-label.svelte +26 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-legend.svelte +29 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-separator.svelte +38 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-set.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-title.svelte +23 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field.svelte +53 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/field/index.ts +33 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/file-drop-zone.svelte +178 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/index.ts +29 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/types.ts +51 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/index.ts +34 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-actions.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-content.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-description.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-footer.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-group.svelte +21 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-header.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-media.svelte +42 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-separator.svelte +19 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-title.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item.svelte +60 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/label/index.ts +7 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/label/label.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/index.ts +13 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-content.svelte +29 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-description.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-footer.svelte +29 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-header.svelte +29 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-title.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-trigger.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal.svelte +24 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal.svelte.ts +32 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/separator/index.ts +7 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/separator/separator.svelte +21 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/index.ts +16 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-list.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-trigger.svelte +20 -0
- dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs.svelte +19 -0
- dr_widget/widgets/config_file_manager/src/lib/hooks/use-file-bindings.ts +189 -0
- dr_widget/widgets/config_file_manager/src/lib/react/JsonTreeCanvas.tsx +207 -0
- dr_widget/widgets/config_file_manager/src/lib/utils/config-format.ts +113 -0
- dr_widget/widgets/config_file_manager/src/lib/utils/utils.ts +21 -0
- dr_widget/widgets/config_file_manager/src/lib/utils.ts +17 -0
- dr_widget/widgets/config_file_manager/src/main.js +7 -0
- dr_widget/widgets/config_file_manager/static/fonts/Inter-roman.var.woff2 +0 -0
- dr_widget/widgets/config_file_manager/static/index.js +9719 -0
- dr_widget/widgets/config_file_manager/static/style.css +1 -0
- dr_widget/widgets/config_file_manager/static/vite.svg +1 -0
- dr_widget/widgets/config_file_manager/svelte.config.js +8 -0
- dr_widget/widgets/config_file_manager/tailwind.config.js +12 -0
- dr_widget/widgets/config_file_manager/tsconfig.json +28 -0
- dr_widget/widgets/config_file_manager/vite.config.js +36 -0
- dr_widget-0.1.3.dist-info/METADATA +62 -0
- dr_widget-0.1.3.dist-info/RECORD +127 -0
- dr_widget-0.1.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: TabsPrimitive.ContentProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<TabsPrimitive.Content
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="tabs-content"
|
|
15
|
+
class={cn("flex-1 outline-none", className)}
|
|
16
|
+
{...restProps}
|
|
17
|
+
/>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: TabsPrimitive.ListProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<TabsPrimitive.List
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="tabs-list"
|
|
15
|
+
class={cn(
|
|
16
|
+
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...restProps}
|
|
20
|
+
/>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: TabsPrimitive.TriggerProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<TabsPrimitive.Trigger
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="tabs-trigger"
|
|
15
|
+
class={cn(
|
|
16
|
+
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent px-2 py-1 text-sm font-medium transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...restProps}
|
|
20
|
+
/>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
value = $bindable(""),
|
|
8
|
+
class: className,
|
|
9
|
+
...restProps
|
|
10
|
+
}: TabsPrimitive.RootProps = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<TabsPrimitive.Root
|
|
14
|
+
bind:ref
|
|
15
|
+
bind:value
|
|
16
|
+
data-slot="tabs"
|
|
17
|
+
class={cn("flex flex-col gap-2", className)}
|
|
18
|
+
{...restProps}
|
|
19
|
+
/>
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { FileDropZoneProps } from "$lib/components/ui/file-drop-zone";
|
|
2
|
+
|
|
3
|
+
export type BoundFile = {
|
|
4
|
+
name: string;
|
|
5
|
+
size: number;
|
|
6
|
+
type: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type FileBinding = {
|
|
10
|
+
file_count: number;
|
|
11
|
+
files: string;
|
|
12
|
+
error: string;
|
|
13
|
+
current_state?: string | null;
|
|
14
|
+
baseline_state?: string | null;
|
|
15
|
+
config_file?: string | null;
|
|
16
|
+
config_file_display?: string | null;
|
|
17
|
+
version?: string | null;
|
|
18
|
+
saved_at?: string | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const coerceString = (value?: string | null) =>
|
|
22
|
+
typeof value === "string" ? value : (value ?? "");
|
|
23
|
+
|
|
24
|
+
export const normalizeBoundFiles = (
|
|
25
|
+
files: unknown,
|
|
26
|
+
limit?: number,
|
|
27
|
+
): BoundFile[] => {
|
|
28
|
+
if (!Array.isArray(files)) return [];
|
|
29
|
+
|
|
30
|
+
const validItems = files
|
|
31
|
+
.filter(
|
|
32
|
+
(item) =>
|
|
33
|
+
item &&
|
|
34
|
+
typeof item.name === "string" &&
|
|
35
|
+
typeof item.size === "number" &&
|
|
36
|
+
typeof item.type === "string",
|
|
37
|
+
)
|
|
38
|
+
.map((item) => ({
|
|
39
|
+
name: item.name,
|
|
40
|
+
size: item.size,
|
|
41
|
+
type: item.type,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const limitSize = typeof limit === "number" ? limit : validItems.length;
|
|
45
|
+
return validItems.slice(0, limitSize);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const readBindingFiles = (bindings: FileBinding): BoundFile[] => {
|
|
49
|
+
if (!bindings?.files) return [];
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(bindings.files) as unknown;
|
|
52
|
+
return normalizeBoundFiles(parsed);
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const writeBindingFiles = (
|
|
59
|
+
bindings: FileBinding,
|
|
60
|
+
files: BoundFile[],
|
|
61
|
+
): void => {
|
|
62
|
+
const normalized = normalizeBoundFiles(files);
|
|
63
|
+
bindings.files = JSON.stringify(normalized);
|
|
64
|
+
bindings.file_count = normalized.length;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const writeBindingCurrentState = (
|
|
68
|
+
bindings: FileBinding,
|
|
69
|
+
contents?: string | null,
|
|
70
|
+
): void => {
|
|
71
|
+
bindings.current_state = coerceString(contents);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const writeBindingBaselineState = (
|
|
75
|
+
bindings: FileBinding,
|
|
76
|
+
contents?: string | null,
|
|
77
|
+
): void => {
|
|
78
|
+
bindings.baseline_state = coerceString(contents);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const writeBindingVersion = (
|
|
82
|
+
bindings: FileBinding,
|
|
83
|
+
version?: string | null,
|
|
84
|
+
): void => {
|
|
85
|
+
bindings.version = coerceString(version);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const writeBindingConfigFile = (
|
|
89
|
+
bindings: FileBinding,
|
|
90
|
+
path?: string | null,
|
|
91
|
+
): void => {
|
|
92
|
+
bindings.config_file = coerceString(path);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const writeBindingConfigFileDisplay = (
|
|
96
|
+
bindings: FileBinding,
|
|
97
|
+
path?: string | null,
|
|
98
|
+
): void => {
|
|
99
|
+
bindings.config_file_display = coerceString(path);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const writeBindingSavedAt = (
|
|
103
|
+
bindings: FileBinding,
|
|
104
|
+
timestamp?: string | null,
|
|
105
|
+
): void => {
|
|
106
|
+
bindings.saved_at = coerceString(timestamp);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const writeBindingError = (
|
|
110
|
+
bindings: FileBinding,
|
|
111
|
+
error?: string | null,
|
|
112
|
+
): void => {
|
|
113
|
+
bindings.error = coerceString(error);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
type UploadHandler = FileDropZoneProps["onUpload"];
|
|
117
|
+
type RejectHandler = NonNullable<FileDropZoneProps["onFileRejected"]>;
|
|
118
|
+
|
|
119
|
+
export function createFileBindingHandlers({
|
|
120
|
+
bindings,
|
|
121
|
+
maxFiles,
|
|
122
|
+
}: {
|
|
123
|
+
bindings: FileBinding;
|
|
124
|
+
maxFiles?: number;
|
|
125
|
+
}) {
|
|
126
|
+
const maxFileCount = Number.isFinite(maxFiles) ? maxFiles : undefined;
|
|
127
|
+
|
|
128
|
+
const enforceLimit = (files: BoundFile[]): BoundFile[] => {
|
|
129
|
+
if (!maxFileCount) return files;
|
|
130
|
+
return files.slice(0, maxFileCount);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const readBoundFiles = (): BoundFile[] => readBindingFiles(bindings);
|
|
134
|
+
|
|
135
|
+
const writeBoundFiles = (files: BoundFile[]): void => {
|
|
136
|
+
writeBindingFiles(bindings, enforceLimit(files));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const handleUpload: UploadHandler = async (files) => {
|
|
140
|
+
const nextFiles = files
|
|
141
|
+
.slice(0, maxFileCount ?? files.length)
|
|
142
|
+
.map((file) => ({
|
|
143
|
+
name: file.name,
|
|
144
|
+
size: file.size,
|
|
145
|
+
type: file.type,
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
if (nextFiles.length === 0) return;
|
|
149
|
+
|
|
150
|
+
writeBoundFiles(nextFiles);
|
|
151
|
+
writeBindingError(bindings, "");
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const handleFileRejected: RejectHandler = ({ reason, file }) => {
|
|
155
|
+
writeBindingError(bindings, `${file.name}: ${reason}`);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const removeFile = (index: number): void => {
|
|
159
|
+
const current = readBoundFiles();
|
|
160
|
+
current.splice(index, 1);
|
|
161
|
+
writeBoundFiles(current);
|
|
162
|
+
|
|
163
|
+
if (current.length === 0) {
|
|
164
|
+
writeBindingError(bindings, "");
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
bindings,
|
|
170
|
+
readBoundFiles,
|
|
171
|
+
writeBoundFiles,
|
|
172
|
+
handleUpload,
|
|
173
|
+
handleFileRejected,
|
|
174
|
+
removeFile,
|
|
175
|
+
writeCurrentState: (contents?: string | null) =>
|
|
176
|
+
writeBindingCurrentState(bindings, contents),
|
|
177
|
+
writeBaselineState: (contents?: string | null) =>
|
|
178
|
+
writeBindingBaselineState(bindings, contents),
|
|
179
|
+
writeVersion: (version?: string | null) =>
|
|
180
|
+
writeBindingVersion(bindings, version),
|
|
181
|
+
writeConfigFile: (path?: string | null) =>
|
|
182
|
+
writeBindingConfigFile(bindings, path),
|
|
183
|
+
writeConfigFileDisplay: (path?: string | null) =>
|
|
184
|
+
writeBindingConfigFileDisplay(bindings, path),
|
|
185
|
+
writeSavedAt: (timestamp?: string | null) =>
|
|
186
|
+
writeBindingSavedAt(bindings, timestamp),
|
|
187
|
+
writeError: (error?: string | null) => writeBindingError(bindings, error),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Canvas,
|
|
4
|
+
Edge,
|
|
5
|
+
EdgeData,
|
|
6
|
+
EdgeProps,
|
|
7
|
+
Node,
|
|
8
|
+
NodeData,
|
|
9
|
+
NodeProps,
|
|
10
|
+
} from "reaflow";
|
|
11
|
+
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
|
12
|
+
|
|
13
|
+
type JsonTreeCanvasProps = {
|
|
14
|
+
data?: unknown;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type BuildResult = {
|
|
18
|
+
nodes: NodeData[];
|
|
19
|
+
edges: EdgeData[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const formatPrimitive = (value: unknown): string => {
|
|
23
|
+
if (typeof value === "string") {
|
|
24
|
+
return `"${value}"`;
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
27
|
+
return `${value}`;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === "boolean") {
|
|
30
|
+
return value ? "true" : "false";
|
|
31
|
+
}
|
|
32
|
+
if (value === null) {
|
|
33
|
+
return "null";
|
|
34
|
+
}
|
|
35
|
+
if (value === undefined) {
|
|
36
|
+
return "undefined";
|
|
37
|
+
}
|
|
38
|
+
return String(value);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const createGraph = (input: unknown): BuildResult => {
|
|
42
|
+
const nodes: NodeData[] = [];
|
|
43
|
+
const edges: EdgeData[] = [];
|
|
44
|
+
let idCounter = 0;
|
|
45
|
+
let edgeCounter = 0;
|
|
46
|
+
|
|
47
|
+
const createNode = (text: string, depth: number) => {
|
|
48
|
+
const id = `node-${idCounter++}`;
|
|
49
|
+
const lines = text.split("\n").length;
|
|
50
|
+
const width = Math.min(Math.max(text.length * 8 + 32, 160), 320);
|
|
51
|
+
const height = Math.max(lines * 22 + 20, 48);
|
|
52
|
+
|
|
53
|
+
nodes.push({
|
|
54
|
+
id,
|
|
55
|
+
text,
|
|
56
|
+
width,
|
|
57
|
+
height,
|
|
58
|
+
className:
|
|
59
|
+
depth === 0 ? "json-tree-node json-tree-node--root" : "json-tree-node",
|
|
60
|
+
data: { depth, text },
|
|
61
|
+
selectionDisabled: true,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return id;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const createEdge = (from: string, to: string) => {
|
|
68
|
+
const id = `edge-${edgeCounter++}`;
|
|
69
|
+
edges.push({
|
|
70
|
+
id,
|
|
71
|
+
from,
|
|
72
|
+
to,
|
|
73
|
+
className: "json-tree-edge",
|
|
74
|
+
containerClassName: "json-tree-edge-container",
|
|
75
|
+
selectionDisabled: true,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const visit = (
|
|
80
|
+
value: unknown,
|
|
81
|
+
key: string,
|
|
82
|
+
depth: number,
|
|
83
|
+
parentId?: string,
|
|
84
|
+
) => {
|
|
85
|
+
const isObject = value !== null && typeof value === "object";
|
|
86
|
+
const isArray = Array.isArray(value);
|
|
87
|
+
|
|
88
|
+
const baseLabel = depth === 0 ? "(root)" : key === "" ? "(item)" : key;
|
|
89
|
+
|
|
90
|
+
let label = baseLabel;
|
|
91
|
+
|
|
92
|
+
if (!isObject) {
|
|
93
|
+
label = `${baseLabel}: ${formatPrimitive(value)}`;
|
|
94
|
+
} else if (isArray) {
|
|
95
|
+
label = `${baseLabel} [${(value as unknown[]).length}]`;
|
|
96
|
+
} else {
|
|
97
|
+
label = `${baseLabel} {${Object.keys(value as object).length}}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const currentId = createNode(label, depth);
|
|
101
|
+
|
|
102
|
+
if (parentId) {
|
|
103
|
+
createEdge(parentId, currentId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!isObject) {
|
|
107
|
+
return currentId;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const entries = isArray
|
|
111
|
+
? (value as unknown[]).map<[string, unknown]>((item, index) => [
|
|
112
|
+
index.toString(),
|
|
113
|
+
item,
|
|
114
|
+
])
|
|
115
|
+
: Object.entries(value as Record<string, unknown>);
|
|
116
|
+
|
|
117
|
+
if (entries.length === 0) {
|
|
118
|
+
const emptyId = createNode("(empty)", depth + 1);
|
|
119
|
+
createEdge(currentId, emptyId);
|
|
120
|
+
return currentId;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const [childKey, childValue] of entries) {
|
|
124
|
+
visit(childValue, childKey, depth + 1, currentId);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return currentId;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
visit(input, "root", 0);
|
|
131
|
+
|
|
132
|
+
return { nodes, edges };
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export function JsonTreeCanvas({ data }: JsonTreeCanvasProps) {
|
|
136
|
+
const { nodes, edges, error } = useMemo(() => {
|
|
137
|
+
if (data === undefined) {
|
|
138
|
+
return { nodes: [], edges: [], error: null as null | string };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const build = createGraph(data);
|
|
143
|
+
return { ...build, error: null };
|
|
144
|
+
} catch (err) {
|
|
145
|
+
return {
|
|
146
|
+
nodes: [],
|
|
147
|
+
edges: [],
|
|
148
|
+
error:
|
|
149
|
+
err instanceof Error ? err.message : "Failed to render JSON graph.",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}, [data]);
|
|
153
|
+
|
|
154
|
+
const renderNode = (props: NodeProps<NodeData>) => <Node {...props} />;
|
|
155
|
+
|
|
156
|
+
const renderEdge = (props: Partial<EdgeProps>) => <Edge {...props} />;
|
|
157
|
+
|
|
158
|
+
if (data === undefined) {
|
|
159
|
+
return (
|
|
160
|
+
<div className="json-tree-placeholder">
|
|
161
|
+
<p>Select a file to visualise the graph.</p>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (error) {
|
|
167
|
+
return (
|
|
168
|
+
<div className="json-tree-error">
|
|
169
|
+
<p>{error}</p>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<TransformWrapper
|
|
176
|
+
minScale={0.2}
|
|
177
|
+
maxScale={1.6}
|
|
178
|
+
initialScale={0.8}
|
|
179
|
+
wheel={{ step: 0.04 }}
|
|
180
|
+
doubleClick={{ disabled: true }}
|
|
181
|
+
>
|
|
182
|
+
<TransformComponent
|
|
183
|
+
wrapperStyle={{
|
|
184
|
+
height: "100%",
|
|
185
|
+
width: "100%",
|
|
186
|
+
overflow: "hidden",
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
<Canvas
|
|
190
|
+
nodes={nodes}
|
|
191
|
+
edges={edges}
|
|
192
|
+
height={Math.max(nodes.length * 140, 400)}
|
|
193
|
+
width={Math.max(nodes.length * 120, 600)}
|
|
194
|
+
maxHeight={1600}
|
|
195
|
+
maxWidth={1600}
|
|
196
|
+
animated={false}
|
|
197
|
+
pannable={false}
|
|
198
|
+
zoomable={false}
|
|
199
|
+
fit={true}
|
|
200
|
+
direction="RIGHT"
|
|
201
|
+
node={renderNode}
|
|
202
|
+
edge={renderEdge}
|
|
203
|
+
/>
|
|
204
|
+
</TransformComponent>
|
|
205
|
+
</TransformWrapper>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export type NormalizedConfigPayload = {
|
|
2
|
+
data: Record<string, unknown>;
|
|
3
|
+
metadata: Record<string, unknown>;
|
|
4
|
+
version?: string;
|
|
5
|
+
savedAt?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function normalizeConfigPayload(
|
|
9
|
+
payload: unknown,
|
|
10
|
+
): NormalizedConfigPayload {
|
|
11
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
12
|
+
return { data: {}, metadata: {} };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const record = payload as Record<string, unknown>;
|
|
16
|
+
const dataCandidate = record["data"];
|
|
17
|
+
let data: Record<string, unknown>;
|
|
18
|
+
const metadataCandidate = record["metadata"];
|
|
19
|
+
const metadata: Record<string, unknown> =
|
|
20
|
+
metadataCandidate &&
|
|
21
|
+
typeof metadataCandidate === "object" &&
|
|
22
|
+
!Array.isArray(metadataCandidate)
|
|
23
|
+
? { ...(metadataCandidate as Record<string, unknown>) }
|
|
24
|
+
: {};
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
dataCandidate &&
|
|
28
|
+
typeof dataCandidate === "object" &&
|
|
29
|
+
!Array.isArray(dataCandidate)
|
|
30
|
+
) {
|
|
31
|
+
data = dataCandidate as Record<string, unknown>;
|
|
32
|
+
} else if (
|
|
33
|
+
record["selections"] &&
|
|
34
|
+
typeof record["selections"] === "object" &&
|
|
35
|
+
!Array.isArray(record["selections"])
|
|
36
|
+
) {
|
|
37
|
+
data = { selections: record["selections"] as Record<string, unknown> };
|
|
38
|
+
} else {
|
|
39
|
+
data = Object.fromEntries(
|
|
40
|
+
Object.entries(record).filter(
|
|
41
|
+
([key]) =>
|
|
42
|
+
key !== "version" && key !== "saved_at" && key !== "metadata",
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const versionValue = metadata["version"] ?? record["version"];
|
|
48
|
+
const savedAtValue = metadata["saved_at"] ?? record["saved_at"];
|
|
49
|
+
const version =
|
|
50
|
+
typeof versionValue === "string"
|
|
51
|
+
? versionValue
|
|
52
|
+
: typeof versionValue === "number"
|
|
53
|
+
? String(versionValue)
|
|
54
|
+
: undefined;
|
|
55
|
+
const savedAt = typeof savedAtValue === "string" ? savedAtValue : undefined;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
data,
|
|
59
|
+
metadata,
|
|
60
|
+
version,
|
|
61
|
+
savedAt,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function buildWrappedPayload({
|
|
66
|
+
data,
|
|
67
|
+
version,
|
|
68
|
+
savedAt,
|
|
69
|
+
metadata,
|
|
70
|
+
}: {
|
|
71
|
+
data: Record<string, unknown>;
|
|
72
|
+
version?: string | null;
|
|
73
|
+
savedAt?: string | null;
|
|
74
|
+
metadata?: Record<string, unknown> | null;
|
|
75
|
+
}): Record<string, unknown> {
|
|
76
|
+
const payloadMetadata: Record<string, unknown> = metadata
|
|
77
|
+
? { ...metadata }
|
|
78
|
+
: {};
|
|
79
|
+
|
|
80
|
+
if (version && version.trim().length > 0) {
|
|
81
|
+
payloadMetadata.version = version;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (savedAt && savedAt.trim().length > 0) {
|
|
85
|
+
payloadMetadata.saved_at = savedAt;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const payload: Record<string, unknown> = {
|
|
89
|
+
metadata: payloadMetadata,
|
|
90
|
+
data,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return payload;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function formatSavedAt(value: unknown): string | undefined {
|
|
97
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const parsed = new Date(value);
|
|
102
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
107
|
+
year: "numeric",
|
|
108
|
+
month: "short",
|
|
109
|
+
day: "numeric",
|
|
110
|
+
hour: "2-digit",
|
|
111
|
+
minute: "2-digit",
|
|
112
|
+
}).format(parsed);
|
|
113
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Installed from @ieedan/shadcn-svelte-extras
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type ClassValue, clsx } from "clsx";
|
|
6
|
+
import { twMerge } from "tailwind-merge";
|
|
7
|
+
|
|
8
|
+
export function cn(...inputs: ClassValue[]) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
export type WithoutChildren<T> = T extends { children?: any }
|
|
16
|
+
? Omit<T, "children">
|
|
17
|
+
: T;
|
|
18
|
+
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
|
19
|
+
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
|
|
20
|
+
ref?: U | null;
|
|
21
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export type WithoutChildren<T> = T extends { children?: any }
|
|
12
|
+
? Omit<T, "children">
|
|
13
|
+
: T;
|
|
14
|
+
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
|
15
|
+
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
|
|
16
|
+
ref?: U | null;
|
|
17
|
+
};
|
|
Binary file
|