@lobb-js/lobb-ext-storage 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +37 -0
- package/studio/README.md +35 -0
- package/studio/extension.types.ts +28 -0
- package/studio/index.html +13 -0
- package/studio/postcss.config.js +6 -0
- package/studio/src/components/childrenFileExplorer.svelte +13 -0
- package/studio/src/components/explorerNotSupported.svelte +13 -0
- package/studio/src/components/fileExplorer.svelte +404 -0
- package/studio/src/components/fileIcon.svelte +46 -0
- package/studio/src/components/fileManagerBreadCrumbs.svelte +50 -0
- package/studio/src/foreignKeyComponent.svelte +44 -0
- package/studio/src/index.ts +28 -0
- package/studio/src/main.ts +12 -0
- package/studio/src/pages/fileExplorerPage.svelte +68 -0
- package/studio/svelte.config.js +8 -0
- package/studio/tailwind.config.ts +92 -0
- package/studio/tsconfig.app.json +22 -0
- package/studio/tsconfig.json +7 -0
- package/studio/tsconfig.node.json +26 -0
- package/studio/vite.config.ts +13 -0
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lobb-js/lobb-ext-storage",
|
|
3
|
+
"version": "0.1.28",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"studio"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./studio/src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "cd studio && vite",
|
|
16
|
+
"build": "cd studio && vite build",
|
|
17
|
+
"preview": "cd studio && vite preview",
|
|
18
|
+
"check": "cd studio && svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@lobb-js/studio": "0.1.31",
|
|
22
|
+
"path-browserify": "^1.0.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@sveltejs/vite-plugin-svelte": "6.2.1",
|
|
26
|
+
"@tsconfig/svelte": "^5.0.6",
|
|
27
|
+
"@types/path-browserify": "^1.0.3",
|
|
28
|
+
"@types/node": "^24.10.1",
|
|
29
|
+
"autoprefixer": "^10.4.23",
|
|
30
|
+
"svelte": "^5.43.8",
|
|
31
|
+
"svelte-check": "^4.3.4",
|
|
32
|
+
"tailwindcss": "^3.4.19",
|
|
33
|
+
"tailwindcss-animate": "^1.0.7",
|
|
34
|
+
"typescript": "~5.9.3",
|
|
35
|
+
"vite": "6.3.3"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/studio/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Storage Extension - Studio
|
|
2
|
+
|
|
3
|
+
This directory contains the frontend/dashboard interface for the storage extension.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
studio/
|
|
9
|
+
├── src/
|
|
10
|
+
│ ├── index.ts # Extension entry point
|
|
11
|
+
│ ├── main.ts # Vite app entry
|
|
12
|
+
│ ├── components/ # UI components
|
|
13
|
+
│ │ ├── fileExplorer.svelte
|
|
14
|
+
│ │ ├── childrenFileExplorer.svelte
|
|
15
|
+
│ │ ├── fileIcon.svelte
|
|
16
|
+
│ │ ├── fileManagerBreadCrumbs.svelte
|
|
17
|
+
│ │ └── explorerNotSupported.svelte
|
|
18
|
+
│ ├── pages/ # UI pages
|
|
19
|
+
│ │ └── fileExplorerPage.svelte
|
|
20
|
+
│ └── foreignKeyComponent.svelte
|
|
21
|
+
├── public/ # Static assets
|
|
22
|
+
├── index.html # HTML entry point
|
|
23
|
+
├── vite.config.ts # Vite configuration
|
|
24
|
+
├── tailwind.config.ts # Tailwind CSS configuration
|
|
25
|
+
└── tsconfig.json # TypeScript configuration
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
The studio interface includes:
|
|
31
|
+
- File explorer with directory navigation
|
|
32
|
+
- File upload and management
|
|
33
|
+
- Visual file icons
|
|
34
|
+
- Breadcrumb navigation
|
|
35
|
+
- Foreign key component integration
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// TODO: Import these types from @lobb-js/studio once available
|
|
2
|
+
export interface Extension {
|
|
3
|
+
name: string;
|
|
4
|
+
onStartup?: (utils: ExtensionUtils) => void;
|
|
5
|
+
components?: Record<string, any>;
|
|
6
|
+
dashboardNavs?: {
|
|
7
|
+
top?: NavItem[];
|
|
8
|
+
middle?: NavItem[];
|
|
9
|
+
bottom?: NavItem[];
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NavItem {
|
|
14
|
+
label: string;
|
|
15
|
+
icon?: any;
|
|
16
|
+
href?: string;
|
|
17
|
+
onclick?: () => void;
|
|
18
|
+
navs?: NavItem[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ExtensionUtils {
|
|
22
|
+
components: {
|
|
23
|
+
Icons: Record<string, any>;
|
|
24
|
+
};
|
|
25
|
+
location: {
|
|
26
|
+
navigate: (path: string) => void;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Lobb Studio</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { FolderX } from "lucide-svelte";
|
|
3
|
+
import type { ExtensionProps } from "../../extension.types";
|
|
4
|
+
import FileExplorer from "./fileExplorer.svelte";
|
|
5
|
+
|
|
6
|
+
interface Props extends ExtensionProps {
|
|
7
|
+
class: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { utils, filter, class: className }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<FileExplorer foreignKeyObject={filter} utils={utils} class={className} />
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { FolderX } from "lucide-svelte";
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="border rounded-md p-4 bg-soft flex items-center flex-col text-muted-foreground gap-2">
|
|
6
|
+
<FolderX size={35} />
|
|
7
|
+
<div class="flex flex-col justify-center items-center">
|
|
8
|
+
<div class="text-sm font-bold">Storage file system is not supported here</div>
|
|
9
|
+
<div class="text-xs text-center max-w-96">
|
|
10
|
+
To access the storage file system for this record, please create the record first, then edit it to enable storage access.
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
|
|
5
|
+
import path from "path-browserify"
|
|
6
|
+
import FileManagerBreadCrumbs from "./fileManagerBreadCrumbs.svelte";
|
|
7
|
+
import FileIcon from "./fileIcon.svelte";
|
|
8
|
+
|
|
9
|
+
export interface Entry {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
path: string;
|
|
13
|
+
type: "directory" | "file";
|
|
14
|
+
icon: string;
|
|
15
|
+
file_mime_type: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Props extends ExtensionProps {
|
|
19
|
+
location?: string;
|
|
20
|
+
onFileSelect?: (entry: Entry) => void;
|
|
21
|
+
foreignKeyObject?: Record<string, any>;
|
|
22
|
+
topLeftHeader?: Snippet<[]>;
|
|
23
|
+
class: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let { location = $bindable('/'), onFileSelect, topLeftHeader, foreignKeyObject = {}, class: className, ...props }: Props = $props();
|
|
27
|
+
|
|
28
|
+
let loading = $state(true);
|
|
29
|
+
let entries: Entry[] = $state([]);
|
|
30
|
+
|
|
31
|
+
let contextType: "directory" | "file" | "background" = $state("background");
|
|
32
|
+
let selectedEntry: Entry | undefined = $state(undefined);
|
|
33
|
+
let contextMenuOpen: boolean = $state(false);
|
|
34
|
+
|
|
35
|
+
const utils = props.utils;
|
|
36
|
+
const icons = utils.components.Icons;
|
|
37
|
+
const { Tooltip, Button, ContextMenu } = utils.components;
|
|
38
|
+
|
|
39
|
+
$effect(() => {
|
|
40
|
+
renderFileExplorer(location);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
async function renderFileExplorer(
|
|
44
|
+
location: string,
|
|
45
|
+
) {
|
|
46
|
+
loading = true;
|
|
47
|
+
|
|
48
|
+
const localFilter = {
|
|
49
|
+
filter: {
|
|
50
|
+
path: location,
|
|
51
|
+
...foreignKeyObject,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const response = await utils.lobb.findAll("storage_fs", localFilter);
|
|
55
|
+
entries = (await response.json()).data;
|
|
56
|
+
|
|
57
|
+
loading = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function refreshFileExplorer() {
|
|
61
|
+
await renderFileExplorer(location);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function handleCreateFolder() {
|
|
65
|
+
contextMenuOpen = false;
|
|
66
|
+
const newFolderName = prompt("Please enter a name for the new folder:");
|
|
67
|
+
if (newFolderName) {
|
|
68
|
+
const response = await utils.lobb.createOne("storage_fs", {
|
|
69
|
+
name: newFolderName,
|
|
70
|
+
path: location,
|
|
71
|
+
type: "directory",
|
|
72
|
+
...foreignKeyObject,
|
|
73
|
+
});
|
|
74
|
+
const result = await response.json();
|
|
75
|
+
if (result.error) {
|
|
76
|
+
if (result.error.status === 409) {
|
|
77
|
+
utils.toast.error(
|
|
78
|
+
"Directories with the same name cannot exist in the same directory.",
|
|
79
|
+
);
|
|
80
|
+
} else {
|
|
81
|
+
utils.toast.error(result.error.message);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
await refreshFileExplorer();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function handleFileUpload() {
|
|
90
|
+
contextMenuOpen = false;
|
|
91
|
+
|
|
92
|
+
async function uploadFile(file: File) {
|
|
93
|
+
if (file) {
|
|
94
|
+
const response = await utils.lobb.createOne(
|
|
95
|
+
"storage_fs",
|
|
96
|
+
{
|
|
97
|
+
path: location,
|
|
98
|
+
...foreignKeyObject,
|
|
99
|
+
},
|
|
100
|
+
file,
|
|
101
|
+
);
|
|
102
|
+
const result = await response.json();
|
|
103
|
+
if (result.error) {
|
|
104
|
+
if (result.error.status === 409) {
|
|
105
|
+
utils.toast.error(
|
|
106
|
+
"Files with the same name cannot exist in the same directory.",
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
utils.toast.error(result.error.message);
|
|
110
|
+
}
|
|
111
|
+
contextMenuOpen = false;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
await refreshFileExplorer();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const file = await utils.getFileFromUser();
|
|
119
|
+
|
|
120
|
+
utils.toast.promise(
|
|
121
|
+
uploadFile(file),
|
|
122
|
+
{
|
|
123
|
+
loading: 'Uploading File...',
|
|
124
|
+
success: 'Done Uploading!',
|
|
125
|
+
error: 'Something went wrong While Uploading!',
|
|
126
|
+
duration: 3000,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function handleDeleteEntry() {
|
|
132
|
+
if (!selectedEntry) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
"The selected Element is (undefined) for some reason",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const entryId = selectedEntry.id;
|
|
138
|
+
if (!entryId) {
|
|
139
|
+
throw new Error("The id of the entry is (null) for some reason");
|
|
140
|
+
}
|
|
141
|
+
const response = await utils.lobb.deleteOne(
|
|
142
|
+
"storage_fs",
|
|
143
|
+
entryId,
|
|
144
|
+
);
|
|
145
|
+
const result = await response.json();
|
|
146
|
+
if (result.status >= 400) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
await refreshFileExplorer();
|
|
150
|
+
utils.toast.success("Deleted entry successfully");
|
|
151
|
+
contextMenuOpen = false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function oncontextmenucapture(e: Event) {
|
|
155
|
+
const element = e.target as HTMLElement;
|
|
156
|
+
const entryId = element.getAttribute("data-id");
|
|
157
|
+
if (entryId) {
|
|
158
|
+
selectedEntry = entries.find((el: any) => el.id == entryId);
|
|
159
|
+
if (selectedEntry?.type === "file") {
|
|
160
|
+
contextType = "file";
|
|
161
|
+
} else if (selectedEntry?.type === "directory") {
|
|
162
|
+
contextType = "directory";
|
|
163
|
+
} else {
|
|
164
|
+
contextType = "background";
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
selectedEntry = undefined;
|
|
168
|
+
contextType = "background";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function handleSelect(e: Event) {
|
|
173
|
+
const element = e.target as HTMLElement;
|
|
174
|
+
const entryId = element.getAttribute("data-id");
|
|
175
|
+
if (entryId) {
|
|
176
|
+
selectedEntry = entries.find((el: any) => el.id == entryId);
|
|
177
|
+
} else {
|
|
178
|
+
selectedEntry = undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function handleEntryDoubleClick(entry: Entry) {
|
|
183
|
+
if (entry.type === "directory") {
|
|
184
|
+
const locationPath = path.join(entry.path, entry.name);
|
|
185
|
+
location = locationPath;
|
|
186
|
+
} else if (entry.type === 'file') {
|
|
187
|
+
if (onFileSelect) {
|
|
188
|
+
onFileSelect(entry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
</script>
|
|
193
|
+
|
|
194
|
+
<div class="flex h-full w-full flex-col bg-background {className}">
|
|
195
|
+
<!-- file explorer header -->
|
|
196
|
+
<div
|
|
197
|
+
class="flex h-10 items-center justify-between border-b px-2"
|
|
198
|
+
>
|
|
199
|
+
<div class="flex items-center justify-between gap-2">
|
|
200
|
+
{@render topLeftHeader?.()}
|
|
201
|
+
<FileManagerBreadCrumbs bind:location={location} { utils } />
|
|
202
|
+
</div>
|
|
203
|
+
<div class="flex gap-2">
|
|
204
|
+
<!-- entries view toggle -->
|
|
205
|
+
<Tooltip.Provider delayDuration={0}>
|
|
206
|
+
<Tooltip.Root>
|
|
207
|
+
<Tooltip.Trigger>
|
|
208
|
+
<Button
|
|
209
|
+
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
210
|
+
variant="ghost"
|
|
211
|
+
size="icon"
|
|
212
|
+
>
|
|
213
|
+
<icons.List />
|
|
214
|
+
</Button>
|
|
215
|
+
</Tooltip.Trigger>
|
|
216
|
+
<Tooltip.Content
|
|
217
|
+
side="bottom"
|
|
218
|
+
sideOffset={7.5}
|
|
219
|
+
>
|
|
220
|
+
List View
|
|
221
|
+
</Tooltip.Content>
|
|
222
|
+
</Tooltip.Root>
|
|
223
|
+
</Tooltip.Provider>
|
|
224
|
+
<Button
|
|
225
|
+
variant="outline"
|
|
226
|
+
class="h-7 px-3 text-xs font-normal"
|
|
227
|
+
Icon={icons.FolderPlus}
|
|
228
|
+
onclick={handleCreateFolder}
|
|
229
|
+
>
|
|
230
|
+
New folder
|
|
231
|
+
</Button>
|
|
232
|
+
<Button
|
|
233
|
+
class="h-7 px-3 text-xs font-normal"
|
|
234
|
+
Icon={icons.FilePlus}
|
|
235
|
+
onclick={() => { handleFileUpload() }}
|
|
236
|
+
>
|
|
237
|
+
New file
|
|
238
|
+
</Button>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
<!-- file explorer body -->
|
|
242
|
+
<div class="flex flex-1 overflow-auto h-[calc(100vh-7.5rem)]">
|
|
243
|
+
<ContextMenu.Root bind:open={contextMenuOpen}>
|
|
244
|
+
<ContextMenu.Trigger
|
|
245
|
+
class="z-10 flex flex-1 flex-wrap content-start items-start justify-start gap-4 p-4"
|
|
246
|
+
onclick={handleSelect}
|
|
247
|
+
{oncontextmenucapture}
|
|
248
|
+
>
|
|
249
|
+
{#if loading}
|
|
250
|
+
<div
|
|
251
|
+
class="flex h-full w-full items-center justify-center"
|
|
252
|
+
>
|
|
253
|
+
<div
|
|
254
|
+
class="flex h-full w-full flex-col items-center justify-center gap-4 text-muted-foreground"
|
|
255
|
+
>
|
|
256
|
+
<icons.LoaderCircle
|
|
257
|
+
class="opacity-50 animate-spin"
|
|
258
|
+
size="50"
|
|
259
|
+
/>
|
|
260
|
+
<div
|
|
261
|
+
class="flex flex-col items-center justify-center"
|
|
262
|
+
>
|
|
263
|
+
<div>Loading files and directories, please wait...</div>
|
|
264
|
+
<div class="text-xs">
|
|
265
|
+
Fetching the contents of the current folder from the lobb server.
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
{:else}
|
|
271
|
+
{#if entries && entries.length}
|
|
272
|
+
{#each entries as entry}
|
|
273
|
+
{@const selectedCss =
|
|
274
|
+
selectedEntry?.id === entry.id
|
|
275
|
+
? "bg-opacity-25 hover:bg-opacity-25"
|
|
276
|
+
: "hover:bg-opacity-10"}
|
|
277
|
+
<button
|
|
278
|
+
class="w-32 cursor-default rounded-md bg-muted-foreground bg-opacity-0 p-2 {selectedCss}"
|
|
279
|
+
data-id={entry.id}
|
|
280
|
+
ondblclick={() =>
|
|
281
|
+
handleEntryDoubleClick(entry)}
|
|
282
|
+
>
|
|
283
|
+
{#if entry.type === "directory"}
|
|
284
|
+
<div
|
|
285
|
+
class="pointer-events-none flex min-w-28 flex-col items-center"
|
|
286
|
+
>
|
|
287
|
+
<div
|
|
288
|
+
class="pointer-events-none relative"
|
|
289
|
+
>
|
|
290
|
+
<icons.Folder
|
|
291
|
+
size="50"
|
|
292
|
+
class="fill-muted-foreground stroke-none"
|
|
293
|
+
/>
|
|
294
|
+
{#if entry.icon}
|
|
295
|
+
{@const key = entry.icon}
|
|
296
|
+
{/* @ts-ignore */ null}
|
|
297
|
+
{@const Icon = icons[key]}
|
|
298
|
+
<div
|
|
299
|
+
class="absolute left-0 top-0 flex h-full w-full items-center justify-center"
|
|
300
|
+
>
|
|
301
|
+
<Icon
|
|
302
|
+
class="text-primary-foreground"
|
|
303
|
+
style="transform: translateY(0.1rem);"
|
|
304
|
+
size="15"
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
{/if}
|
|
308
|
+
</div>
|
|
309
|
+
<div
|
|
310
|
+
class="pointer-events-none w-full break-words text-center text-sm font-medium"
|
|
311
|
+
>
|
|
312
|
+
{entry.name}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
{:else}
|
|
316
|
+
<FileIcon {...props} {entry} />
|
|
317
|
+
{/if}
|
|
318
|
+
</button>
|
|
319
|
+
{/each}
|
|
320
|
+
{:else}
|
|
321
|
+
<div
|
|
322
|
+
class="flex h-full w-full items-center justify-center p-8"
|
|
323
|
+
>
|
|
324
|
+
<div
|
|
325
|
+
class="flex h-full w-full flex-col items-center justify-center gap-4 text-muted-foreground"
|
|
326
|
+
>
|
|
327
|
+
<icons.CircleSlash2
|
|
328
|
+
class="opacity-50"
|
|
329
|
+
size="50"
|
|
330
|
+
/>
|
|
331
|
+
<div
|
|
332
|
+
class="flex flex-col items-center justify-center"
|
|
333
|
+
>
|
|
334
|
+
<div>Folder is empty</div>
|
|
335
|
+
<div class="text-xs">
|
|
336
|
+
Right-click to create a new folder
|
|
337
|
+
or upload a file.
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
{/if}
|
|
343
|
+
{/if}
|
|
344
|
+
</ContextMenu.Trigger>
|
|
345
|
+
<ContextMenu.Content
|
|
346
|
+
class="z-10 flex flex-col rounded-md border border-muted bg-background p-1 shadow-md outline-none w-40"
|
|
347
|
+
>
|
|
348
|
+
{#if contextType === "directory"}
|
|
349
|
+
<Button
|
|
350
|
+
variant="ghost"
|
|
351
|
+
onclick={handleDeleteEntry}
|
|
352
|
+
class="flex items-center gap-2 rounded-md px-4 h-8 hover:bg-muted focus-visible:ring-0 cursor-default font-normal"
|
|
353
|
+
>
|
|
354
|
+
Delete directory
|
|
355
|
+
</Button>
|
|
356
|
+
{:else if contextType === "file"}
|
|
357
|
+
<Button
|
|
358
|
+
variant="ghost"
|
|
359
|
+
onclick={handleDeleteEntry}
|
|
360
|
+
class="rounded-md px-4 h-8 hover:bg-muted focus-visible:ring-0 cursor-default font-normal"
|
|
361
|
+
>
|
|
362
|
+
Delete file
|
|
363
|
+
</Button>
|
|
364
|
+
{:else}
|
|
365
|
+
<Button
|
|
366
|
+
variant="ghost"
|
|
367
|
+
onclick={handleCreateFolder}
|
|
368
|
+
class="flex items-center gap-2 rounded-md px-4 h-8 hover:bg-muted focus-visible:ring-0 cursor-default font-normal"
|
|
369
|
+
>
|
|
370
|
+
New folder
|
|
371
|
+
</Button>
|
|
372
|
+
<Button
|
|
373
|
+
variant="ghost"
|
|
374
|
+
onclick={handleFileUpload}
|
|
375
|
+
class="flex items-center gap-2 rounded-md px-4 h-8 hover:bg-muted focus-visible:ring-0 cursor-default font-normal"
|
|
376
|
+
>
|
|
377
|
+
New file
|
|
378
|
+
</Button>
|
|
379
|
+
{/if}
|
|
380
|
+
</ContextMenu.Content>
|
|
381
|
+
</ContextMenu.Root>
|
|
382
|
+
</div>
|
|
383
|
+
<div
|
|
384
|
+
class="flex h-10 items-center justify-center border-t px-4 text-sm text-muted-foreground"
|
|
385
|
+
>
|
|
386
|
+
{#if loading}
|
|
387
|
+
<div class="flex gap-2 justify-center items-center">
|
|
388
|
+
<icons.LoaderCircle
|
|
389
|
+
class="animate-spin"
|
|
390
|
+
size="15"
|
|
391
|
+
/>
|
|
392
|
+
<div>loading...</div>
|
|
393
|
+
</div>
|
|
394
|
+
{:else}
|
|
395
|
+
{entries ? entries.length : "0"} items available
|
|
396
|
+
{/if}
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<style>
|
|
401
|
+
.selected {
|
|
402
|
+
background-color: theme("colors.muted.DEFAULT");
|
|
403
|
+
}
|
|
404
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import type { Entry } from "./fileExplorer.svelte";
|
|
4
|
+
import { ctx } from "$lib/store.svelte";
|
|
5
|
+
|
|
6
|
+
interface Props extends ExtensionProps {
|
|
7
|
+
entry: Entry;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { entry, utils }: Props = $props();
|
|
11
|
+
|
|
12
|
+
const MainIcon = utils.components.Icons.File;
|
|
13
|
+
const InnerIcon = utils.components.Icons.Text;
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div
|
|
17
|
+
class="pointer-events-none flex flex-col items-center gap-2 min-w-28"
|
|
18
|
+
>
|
|
19
|
+
{#if entry.file_mime_type.startsWith("image/")}
|
|
20
|
+
<div class="rounded-md w-24 h-24 bg-center bg-contain bg-no-repeat" style="background-image: url('{ctx.lobbUrl}/api/collections/storage_fs/{entry.id}?action=view');"></div>
|
|
21
|
+
{:else}
|
|
22
|
+
<div
|
|
23
|
+
class="pointer-events-none relative"
|
|
24
|
+
>
|
|
25
|
+
<MainIcon
|
|
26
|
+
size="50"
|
|
27
|
+
class="fill-muted stroke-muted-foreground"
|
|
28
|
+
style="stroke-width: 0.03rem;"
|
|
29
|
+
/>
|
|
30
|
+
<div
|
|
31
|
+
class="absolute left-0 top-0 flex h-full w-full items-center justify-center"
|
|
32
|
+
>
|
|
33
|
+
<InnerIcon
|
|
34
|
+
class="text-muted-foreground"
|
|
35
|
+
style="transform: translateY(0.1rem);"
|
|
36
|
+
size="15"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
41
|
+
<div
|
|
42
|
+
class="pointer-events-none w-full break-words text-center text-sm font-medium"
|
|
43
|
+
>
|
|
44
|
+
{entry.name}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import path from "path-browserify"
|
|
4
|
+
|
|
5
|
+
interface Props extends ExtensionProps {
|
|
6
|
+
location: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { location = $bindable(), utils }: Props = $props();
|
|
10
|
+
|
|
11
|
+
let pathNames: string[] | undefined = $state(undefined);
|
|
12
|
+
|
|
13
|
+
$effect(() => {
|
|
14
|
+
pathNames = ["/", ...location.split("/").filter(Boolean)];
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<utils.components.Breadcrumb.Root class="pl-2">
|
|
19
|
+
<utils.components.Breadcrumb.List>
|
|
20
|
+
{#if pathNames}
|
|
21
|
+
{#each pathNames as localPath, index}
|
|
22
|
+
{@const isLastElement = pathNames.length - 1 === index}
|
|
23
|
+
{@const currentFullPaths = pathNames
|
|
24
|
+
.slice(1, index + 1)
|
|
25
|
+
.join("/")}
|
|
26
|
+
{@const pathLabel = localPath === "/" ? "Home" : localPath}
|
|
27
|
+
<utils.components.Breadcrumb.Item>
|
|
28
|
+
{#if isLastElement}
|
|
29
|
+
<utils.components.Breadcrumb.Page>
|
|
30
|
+
{pathLabel}
|
|
31
|
+
</utils.components.Breadcrumb.Page>
|
|
32
|
+
{:else}
|
|
33
|
+
<utils.components.Breadcrumb.Link
|
|
34
|
+
class="cursor-pointer"
|
|
35
|
+
onclick={() => {
|
|
36
|
+
const invertedIndex = pathNames && pathNames.length - 1 - index;
|
|
37
|
+
location = path.join(location, Array(invertedIndex).fill("..").join('/'))
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{pathLabel}
|
|
41
|
+
</utils.components.Breadcrumb.Link>
|
|
42
|
+
{/if}
|
|
43
|
+
</utils.components.Breadcrumb.Item>
|
|
44
|
+
{#if !isLastElement}
|
|
45
|
+
<utils.components.Breadcrumb.Separator />
|
|
46
|
+
{/if}
|
|
47
|
+
{/each}
|
|
48
|
+
{/if}
|
|
49
|
+
</utils.components.Breadcrumb.List>
|
|
50
|
+
</utils.components.Breadcrumb.Root>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "../extension.types";
|
|
3
|
+
import FileExplorer, { type Entry } from "./components/fileExplorer.svelte";
|
|
4
|
+
import FileIcon from "./components/fileIcon.svelte";
|
|
5
|
+
|
|
6
|
+
interface Props extends ExtensionProps {
|
|
7
|
+
value: Entry;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { utils, value = $bindable(), ...props }: Props = $props();
|
|
11
|
+
|
|
12
|
+
const { FilePlus2 } = utils.components.Icons;
|
|
13
|
+
const { Drawer } = utils.components;
|
|
14
|
+
|
|
15
|
+
let openDrawer = $state(false);
|
|
16
|
+
|
|
17
|
+
function handleClick() {
|
|
18
|
+
openDrawer = true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function onHideHandle() {
|
|
22
|
+
openDrawer = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function handleOnFileSelect(entry: Entry) {
|
|
26
|
+
value = entry;
|
|
27
|
+
openDrawer = false;
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<button onclick={handleClick} class="flex flex-col items-center gap-2 w-full border p-4 rounded-md bg-soft">
|
|
32
|
+
{#if value}
|
|
33
|
+
<FileIcon entry={value} {utils} />
|
|
34
|
+
{:else}
|
|
35
|
+
<FilePlus2 class="text-muted-foreground" />
|
|
36
|
+
<div class="text-sm text-muted-foreground">Click to add an asset</div>
|
|
37
|
+
{/if}
|
|
38
|
+
</button>
|
|
39
|
+
|
|
40
|
+
{#if openDrawer}
|
|
41
|
+
<Drawer onHide={onHideHandle}>
|
|
42
|
+
<FileExplorer onFileSelect={handleOnFileSelect} utils={utils} {...props} />
|
|
43
|
+
</Drawer>
|
|
44
|
+
{/if}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Extension, ExtensionUtils } from "src/extensions/extension.types";
|
|
2
|
+
|
|
3
|
+
import FileExplorerPage from "./pages/fileExplorerPage.svelte";
|
|
4
|
+
import ForeignKeyComponent from "./foreignKeyComponent.svelte";
|
|
5
|
+
import ChildrenFileExplorer from "./components/childrenFileExplorer.svelte";
|
|
6
|
+
import ExplorerNotSupported from "./components/explorerNotSupported.svelte";
|
|
7
|
+
|
|
8
|
+
export function extension(utils: ExtensionUtils): Extension {
|
|
9
|
+
return {
|
|
10
|
+
name: "storage",
|
|
11
|
+
components: {
|
|
12
|
+
"pages.file_manager": FileExplorerPage,
|
|
13
|
+
"detailView.fields.foreignKey.storage_fs": ForeignKeyComponent,
|
|
14
|
+
"detailView.create.subRecords.storage_fs": ExplorerNotSupported,
|
|
15
|
+
"detailView.update.subRecords.storage_fs": ChildrenFileExplorer,
|
|
16
|
+
"listView.entry.children.storage_fs": ChildrenFileExplorer,
|
|
17
|
+
},
|
|
18
|
+
dashboardNavs: {
|
|
19
|
+
middle: [
|
|
20
|
+
{
|
|
21
|
+
href: "/extensions/storage/file_manager",
|
|
22
|
+
icon: utils.components.Icons.Folders,
|
|
23
|
+
label: "File Manager",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mount } from "svelte";
|
|
2
|
+
import Studio from "@lobb-js/studio";
|
|
3
|
+
import { extension } from "./index.ts";
|
|
4
|
+
|
|
5
|
+
const app = mount(Studio, {
|
|
6
|
+
target: document.getElementById("app")!,
|
|
7
|
+
props: {
|
|
8
|
+
extensions: [extension],
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export default app;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import type { SideBarData, SideBarElement } from "$lib/components/sidebar/sidebarElements.svelte";
|
|
4
|
+
|
|
5
|
+
import FileExplorer, { type Entry } from "../components/fileExplorer.svelte";
|
|
6
|
+
import path from "path-browserify"
|
|
7
|
+
import { onMount } from "svelte";
|
|
8
|
+
|
|
9
|
+
const props: ExtensionProps = $props();
|
|
10
|
+
let { utils } = props;
|
|
11
|
+
let sidebarData: SideBarData | null = $state(null);
|
|
12
|
+
const { location: routerLocation } = utils;
|
|
13
|
+
const icons = utils.components.Icons;
|
|
14
|
+
const { Sidebar, SidebarTrigger } = utils.components;
|
|
15
|
+
let location: string = $state("/");
|
|
16
|
+
|
|
17
|
+
onMount(() => {
|
|
18
|
+
getSidebarData();
|
|
19
|
+
updateExplorerLocationFromRouter(routerLocation);
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
$effect(() => {
|
|
23
|
+
updateRouterLocationFromExplorer(location);
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
async function updateExplorerLocationFromRouter(localLocation: typeof routerLocation) {
|
|
27
|
+
const fileExplorerPath = localLocation.url.pathname.replace('/extensions/storage/file_manager', '');
|
|
28
|
+
location = fileExplorerPath ? fileExplorerPath : "/";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function updateRouterLocationFromExplorer(localLocation: string) {
|
|
32
|
+
const newPath = `/extensions/storage/file_manager${localLocation}`;
|
|
33
|
+
if (routerLocation.url.pathname !== newPath) {
|
|
34
|
+
routerLocation.navigate(newPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function getSidebarData() {
|
|
39
|
+
const response = await utils.lobb.findAll("storage_fs", {
|
|
40
|
+
filter: {
|
|
41
|
+
type: "directory",
|
|
42
|
+
is_pinned_sidebar: {
|
|
43
|
+
$eq: true,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const result = await response.json();
|
|
48
|
+
const entries = result.data;
|
|
49
|
+
|
|
50
|
+
sidebarData = entries.map((el: Entry): SideBarElement => {
|
|
51
|
+
return {
|
|
52
|
+
name: el.name,
|
|
53
|
+
icon: icons.Folder,
|
|
54
|
+
onclick: () => {
|
|
55
|
+
location = path.join(el.path, el.name);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<Sidebar title="Storage" data={sidebarData}>
|
|
63
|
+
<FileExplorer bind:location={location} {...props}>
|
|
64
|
+
{#snippet topLeftHeader()}
|
|
65
|
+
<SidebarTrigger />
|
|
66
|
+
{/snippet}
|
|
67
|
+
</FileExplorer>
|
|
68
|
+
</Sidebar>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
2
|
+
|
|
3
|
+
/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */
|
|
4
|
+
export default {
|
|
5
|
+
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
|
6
|
+
// for more information about preprocessors
|
|
7
|
+
preprocess: vitePreprocess(),
|
|
8
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { fontFamily } from "tailwindcss/defaultTheme";
|
|
2
|
+
import type { Config } from "tailwindcss";
|
|
3
|
+
import tailwindcssAnimate from "tailwindcss-animate";
|
|
4
|
+
|
|
5
|
+
const config: Config = {
|
|
6
|
+
darkMode: ["class"],
|
|
7
|
+
content: [
|
|
8
|
+
"./src/**/*.{html,js,svelte,ts}",
|
|
9
|
+
"../../../packages/studio/src/**/*.{html,js,svelte,ts}",
|
|
10
|
+
],
|
|
11
|
+
safelist: ["dark"],
|
|
12
|
+
theme: {
|
|
13
|
+
container: {
|
|
14
|
+
center: true,
|
|
15
|
+
padding: "2rem",
|
|
16
|
+
screens: {
|
|
17
|
+
"2xl": "1400px",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
extend: {
|
|
21
|
+
colors: {
|
|
22
|
+
border: "hsl(var(--border) / <alpha-value>)",
|
|
23
|
+
input: "hsl(var(--input) / <alpha-value>)",
|
|
24
|
+
ring: "hsl(var(--ring) / <alpha-value>)",
|
|
25
|
+
background: "hsl(var(--background) / <alpha-value>)",
|
|
26
|
+
foreground: "hsl(var(--foreground) / <alpha-value>)",
|
|
27
|
+
primary: {
|
|
28
|
+
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
|
|
29
|
+
foreground: "hsl(var(--primary-foreground) / <alpha-value>)",
|
|
30
|
+
},
|
|
31
|
+
secondary: {
|
|
32
|
+
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
|
|
33
|
+
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)",
|
|
34
|
+
},
|
|
35
|
+
destructive: {
|
|
36
|
+
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
|
|
37
|
+
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)",
|
|
38
|
+
},
|
|
39
|
+
soft: {
|
|
40
|
+
DEFAULT: "hsl(var(--soft) / <alpha-value>)",
|
|
41
|
+
},
|
|
42
|
+
muted: {
|
|
43
|
+
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
|
|
44
|
+
foreground: "hsl(var(--muted-foreground) / <alpha-value>)",
|
|
45
|
+
},
|
|
46
|
+
accent: {
|
|
47
|
+
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
|
|
48
|
+
foreground: "hsl(var(--accent-foreground) / <alpha-value>)",
|
|
49
|
+
},
|
|
50
|
+
popover: {
|
|
51
|
+
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
|
|
52
|
+
foreground: "hsl(var(--popover-foreground) / <alpha-value>)",
|
|
53
|
+
},
|
|
54
|
+
card: {
|
|
55
|
+
DEFAULT: "hsl(var(--card) / <alpha-value>)",
|
|
56
|
+
foreground: "hsl(var(--card-foreground) / <alpha-value>)",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
borderRadius: {
|
|
60
|
+
xl: "calc(var(--radius) + 4px)",
|
|
61
|
+
lg: "var(--radius)",
|
|
62
|
+
md: "calc(var(--radius) - 2px)",
|
|
63
|
+
sm: "calc(var(--radius) - 4px)",
|
|
64
|
+
},
|
|
65
|
+
fontFamily: {
|
|
66
|
+
sans: [...fontFamily.sans],
|
|
67
|
+
},
|
|
68
|
+
keyframes: {
|
|
69
|
+
"accordion-down": {
|
|
70
|
+
from: { height: "0" },
|
|
71
|
+
to: { height: "var(--bits-accordion-content-height)" },
|
|
72
|
+
},
|
|
73
|
+
"accordion-up": {
|
|
74
|
+
from: { height: "var(--bits-accordion-content-height)" },
|
|
75
|
+
to: { height: "0" },
|
|
76
|
+
},
|
|
77
|
+
"caret-blink": {
|
|
78
|
+
"0%,70%,100%": { opacity: "1" },
|
|
79
|
+
"20%,50%": { opacity: "0" },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
animation: {
|
|
83
|
+
"accordion-down": "accordion-down 0.2s ease-out",
|
|
84
|
+
"accordion-up": "accordion-up 0.2s ease-out",
|
|
85
|
+
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
plugins: [tailwindcssAnimate],
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default config;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@tsconfig/svelte/tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"useDefineForClassFields": true,
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"types": ["svelte", "vite/client"],
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"allowArbitraryExtensions": true,
|
|
11
|
+
/**
|
|
12
|
+
* Typecheck JS in `.svelte` and `.js` files by default.
|
|
13
|
+
* Disable checkJs if you'd like to use dynamic types in JS.
|
|
14
|
+
* Note that setting allowJs false does not prevent the use
|
|
15
|
+
* of JS in `.svelte` files.
|
|
16
|
+
*/
|
|
17
|
+
"allowJs": true,
|
|
18
|
+
"checkJs": true,
|
|
19
|
+
"moduleDetection": "force"
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["vite.config.ts"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
// https://vite.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [svelte()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: {
|
|
10
|
+
$lib: path.resolve("../../../packages/studio/src/lib"),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|