hazo_files 1.0.0
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/.cursor/rules/db_schema.mdc +0 -0
- package/.cursor/rules/design.mdc +0 -0
- package/.cursor/rules/general.mdc +0 -0
- package/CHANGE_LOG.md +341 -0
- package/CLAUDE.md +926 -0
- package/README.md +929 -0
- package/SETUP_CHECKLIST.md +931 -0
- package/TECHDOC.md +325 -0
- package/dist/index.d.mts +1031 -0
- package/dist/index.d.ts +1031 -0
- package/dist/index.js +2457 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2333 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ui/index.js +4054 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +3982 -0
- package/dist/ui/index.mjs.map +1 -0
- package/docs/ADDING_MODULES.md +964 -0
- package/hazo_files_config.ini +31 -0
- package/package.json +83 -0
|
@@ -0,0 +1,3982 @@
|
|
|
1
|
+
// src/ui/components/FileBrowser.tsx
|
|
2
|
+
import { useState as useState6, useCallback as useCallback7, useEffect as useEffect4 } from "react";
|
|
3
|
+
|
|
4
|
+
// src/ui/components/PathBreadcrumb.tsx
|
|
5
|
+
import React from "react";
|
|
6
|
+
|
|
7
|
+
// src/common/path-utils.ts
|
|
8
|
+
function normalizePath(inputPath) {
|
|
9
|
+
if (!inputPath) return "/";
|
|
10
|
+
let normalized = inputPath.replace(/\\/g, "/");
|
|
11
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
12
|
+
const parts = normalized.split("/");
|
|
13
|
+
const result = [];
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
if (part === "." || part === "") continue;
|
|
16
|
+
if (part === "..") {
|
|
17
|
+
result.pop();
|
|
18
|
+
} else {
|
|
19
|
+
result.push(part);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
normalized = "/" + result.join("/");
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
function getPathSegments(inputPath) {
|
|
26
|
+
const normalized = normalizePath(inputPath);
|
|
27
|
+
if (normalized === "/") return [];
|
|
28
|
+
return normalized.slice(1).split("/");
|
|
29
|
+
}
|
|
30
|
+
function getExtension(filename) {
|
|
31
|
+
const lastDot = filename.lastIndexOf(".");
|
|
32
|
+
if (lastDot === -1 || lastDot === 0) return "";
|
|
33
|
+
return filename.slice(lastDot);
|
|
34
|
+
}
|
|
35
|
+
function getNameWithoutExtension(filename) {
|
|
36
|
+
const lastDot = filename.lastIndexOf(".");
|
|
37
|
+
if (lastDot === -1 || lastDot === 0) return filename;
|
|
38
|
+
return filename.slice(0, lastDot);
|
|
39
|
+
}
|
|
40
|
+
function getBreadcrumbs(inputPath) {
|
|
41
|
+
const segments = getPathSegments(inputPath);
|
|
42
|
+
const breadcrumbs = [
|
|
43
|
+
{ name: "Root", path: "/" }
|
|
44
|
+
];
|
|
45
|
+
let currentPath = "";
|
|
46
|
+
for (const segment of segments) {
|
|
47
|
+
currentPath += "/" + segment;
|
|
48
|
+
breadcrumbs.push({
|
|
49
|
+
name: segment,
|
|
50
|
+
path: currentPath
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return breadcrumbs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/ui/icons/FileIcons.tsx
|
|
57
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
58
|
+
var defaultSize = 24;
|
|
59
|
+
function FolderIcon({ className, size = defaultSize }) {
|
|
60
|
+
return /* @__PURE__ */ jsx(
|
|
61
|
+
"svg",
|
|
62
|
+
{
|
|
63
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
64
|
+
width: size,
|
|
65
|
+
height: size,
|
|
66
|
+
viewBox: "0 0 24 24",
|
|
67
|
+
fill: "none",
|
|
68
|
+
stroke: "currentColor",
|
|
69
|
+
strokeWidth: "2",
|
|
70
|
+
strokeLinecap: "round",
|
|
71
|
+
strokeLinejoin: "round",
|
|
72
|
+
className,
|
|
73
|
+
children: /* @__PURE__ */ jsx("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" })
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function FolderOpenIcon({ className, size = defaultSize }) {
|
|
78
|
+
return /* @__PURE__ */ jsx(
|
|
79
|
+
"svg",
|
|
80
|
+
{
|
|
81
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
82
|
+
width: size,
|
|
83
|
+
height: size,
|
|
84
|
+
viewBox: "0 0 24 24",
|
|
85
|
+
fill: "none",
|
|
86
|
+
stroke: "currentColor",
|
|
87
|
+
strokeWidth: "2",
|
|
88
|
+
strokeLinecap: "round",
|
|
89
|
+
strokeLinejoin: "round",
|
|
90
|
+
className,
|
|
91
|
+
children: /* @__PURE__ */ jsx("path", { d: "m6 14 1.45-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6a2 2 0 0 1-1.94 1.5H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H18a2 2 0 0 1 2 2v2" })
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function FileIcon({ className, size = defaultSize }) {
|
|
96
|
+
return /* @__PURE__ */ jsxs(
|
|
97
|
+
"svg",
|
|
98
|
+
{
|
|
99
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
100
|
+
width: size,
|
|
101
|
+
height: size,
|
|
102
|
+
viewBox: "0 0 24 24",
|
|
103
|
+
fill: "none",
|
|
104
|
+
stroke: "currentColor",
|
|
105
|
+
strokeWidth: "2",
|
|
106
|
+
strokeLinecap: "round",
|
|
107
|
+
strokeLinejoin: "round",
|
|
108
|
+
className,
|
|
109
|
+
children: [
|
|
110
|
+
/* @__PURE__ */ jsx("path", { d: "M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" }),
|
|
111
|
+
/* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" })
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
function FileTextIcon({ className, size = defaultSize }) {
|
|
117
|
+
return /* @__PURE__ */ jsxs(
|
|
118
|
+
"svg",
|
|
119
|
+
{
|
|
120
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
121
|
+
width: size,
|
|
122
|
+
height: size,
|
|
123
|
+
viewBox: "0 0 24 24",
|
|
124
|
+
fill: "none",
|
|
125
|
+
stroke: "currentColor",
|
|
126
|
+
strokeWidth: "2",
|
|
127
|
+
strokeLinecap: "round",
|
|
128
|
+
strokeLinejoin: "round",
|
|
129
|
+
className,
|
|
130
|
+
children: [
|
|
131
|
+
/* @__PURE__ */ jsx("path", { d: "M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" }),
|
|
132
|
+
/* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" }),
|
|
133
|
+
/* @__PURE__ */ jsx("line", { x1: "16", x2: "8", y1: "13", y2: "13" }),
|
|
134
|
+
/* @__PURE__ */ jsx("line", { x1: "16", x2: "8", y1: "17", y2: "17" }),
|
|
135
|
+
/* @__PURE__ */ jsx("line", { x1: "10", x2: "8", y1: "9", y2: "9" })
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
function ImageIcon({ className, size = defaultSize }) {
|
|
141
|
+
return /* @__PURE__ */ jsxs(
|
|
142
|
+
"svg",
|
|
143
|
+
{
|
|
144
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
145
|
+
width: size,
|
|
146
|
+
height: size,
|
|
147
|
+
viewBox: "0 0 24 24",
|
|
148
|
+
fill: "none",
|
|
149
|
+
stroke: "currentColor",
|
|
150
|
+
strokeWidth: "2",
|
|
151
|
+
strokeLinecap: "round",
|
|
152
|
+
strokeLinejoin: "round",
|
|
153
|
+
className,
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
156
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "2" }),
|
|
157
|
+
/* @__PURE__ */ jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
function VideoIcon({ className, size = defaultSize }) {
|
|
163
|
+
return /* @__PURE__ */ jsxs(
|
|
164
|
+
"svg",
|
|
165
|
+
{
|
|
166
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
167
|
+
width: size,
|
|
168
|
+
height: size,
|
|
169
|
+
viewBox: "0 0 24 24",
|
|
170
|
+
fill: "none",
|
|
171
|
+
stroke: "currentColor",
|
|
172
|
+
strokeWidth: "2",
|
|
173
|
+
strokeLinecap: "round",
|
|
174
|
+
strokeLinejoin: "round",
|
|
175
|
+
className,
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ jsx("path", { d: "m22 8-6 4 6 4V8Z" }),
|
|
178
|
+
/* @__PURE__ */ jsx("rect", { width: "14", height: "12", x: "2", y: "6", rx: "2", ry: "2" })
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
function AudioIcon({ className, size = defaultSize }) {
|
|
184
|
+
return /* @__PURE__ */ jsxs(
|
|
185
|
+
"svg",
|
|
186
|
+
{
|
|
187
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
188
|
+
width: size,
|
|
189
|
+
height: size,
|
|
190
|
+
viewBox: "0 0 24 24",
|
|
191
|
+
fill: "none",
|
|
192
|
+
stroke: "currentColor",
|
|
193
|
+
strokeWidth: "2",
|
|
194
|
+
strokeLinecap: "round",
|
|
195
|
+
strokeLinejoin: "round",
|
|
196
|
+
className,
|
|
197
|
+
children: [
|
|
198
|
+
/* @__PURE__ */ jsx("path", { d: "M9 18V5l12-2v13" }),
|
|
199
|
+
/* @__PURE__ */ jsx("circle", { cx: "6", cy: "18", r: "3" }),
|
|
200
|
+
/* @__PURE__ */ jsx("circle", { cx: "18", cy: "16", r: "3" })
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
function ArchiveIcon({ className, size = defaultSize }) {
|
|
206
|
+
return /* @__PURE__ */ jsxs(
|
|
207
|
+
"svg",
|
|
208
|
+
{
|
|
209
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
210
|
+
width: size,
|
|
211
|
+
height: size,
|
|
212
|
+
viewBox: "0 0 24 24",
|
|
213
|
+
fill: "none",
|
|
214
|
+
stroke: "currentColor",
|
|
215
|
+
strokeWidth: "2",
|
|
216
|
+
strokeLinecap: "round",
|
|
217
|
+
strokeLinejoin: "round",
|
|
218
|
+
className,
|
|
219
|
+
children: [
|
|
220
|
+
/* @__PURE__ */ jsx("rect", { width: "20", height: "5", x: "2", y: "3", rx: "1" }),
|
|
221
|
+
/* @__PURE__ */ jsx("path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" }),
|
|
222
|
+
/* @__PURE__ */ jsx("path", { d: "M10 12h4" })
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
function PdfIcon({ className, size = defaultSize }) {
|
|
228
|
+
return /* @__PURE__ */ jsxs(
|
|
229
|
+
"svg",
|
|
230
|
+
{
|
|
231
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
232
|
+
width: size,
|
|
233
|
+
height: size,
|
|
234
|
+
viewBox: "0 0 24 24",
|
|
235
|
+
fill: "none",
|
|
236
|
+
stroke: "currentColor",
|
|
237
|
+
strokeWidth: "2",
|
|
238
|
+
strokeLinecap: "round",
|
|
239
|
+
strokeLinejoin: "round",
|
|
240
|
+
className,
|
|
241
|
+
children: [
|
|
242
|
+
/* @__PURE__ */ jsx("path", { d: "M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" }),
|
|
243
|
+
/* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" }),
|
|
244
|
+
/* @__PURE__ */ jsx("path", { d: "M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1" }),
|
|
245
|
+
/* @__PURE__ */ jsx("path", { d: "M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1" })
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
function CodeIcon({ className, size = defaultSize }) {
|
|
251
|
+
return /* @__PURE__ */ jsxs(
|
|
252
|
+
"svg",
|
|
253
|
+
{
|
|
254
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
255
|
+
width: size,
|
|
256
|
+
height: size,
|
|
257
|
+
viewBox: "0 0 24 24",
|
|
258
|
+
fill: "none",
|
|
259
|
+
stroke: "currentColor",
|
|
260
|
+
strokeWidth: "2",
|
|
261
|
+
strokeLinecap: "round",
|
|
262
|
+
strokeLinejoin: "round",
|
|
263
|
+
className,
|
|
264
|
+
children: [
|
|
265
|
+
/* @__PURE__ */ jsx("polyline", { points: "16 18 22 12 16 6" }),
|
|
266
|
+
/* @__PURE__ */ jsx("polyline", { points: "8 6 2 12 8 18" })
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
function UploadIcon({ className, size = defaultSize }) {
|
|
272
|
+
return /* @__PURE__ */ jsxs(
|
|
273
|
+
"svg",
|
|
274
|
+
{
|
|
275
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
276
|
+
width: size,
|
|
277
|
+
height: size,
|
|
278
|
+
viewBox: "0 0 24 24",
|
|
279
|
+
fill: "none",
|
|
280
|
+
stroke: "currentColor",
|
|
281
|
+
strokeWidth: "2",
|
|
282
|
+
strokeLinecap: "round",
|
|
283
|
+
strokeLinejoin: "round",
|
|
284
|
+
className,
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
287
|
+
/* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
|
|
288
|
+
/* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "3", y2: "15" })
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
function DownloadIcon({ className, size = defaultSize }) {
|
|
294
|
+
return /* @__PURE__ */ jsxs(
|
|
295
|
+
"svg",
|
|
296
|
+
{
|
|
297
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
298
|
+
width: size,
|
|
299
|
+
height: size,
|
|
300
|
+
viewBox: "0 0 24 24",
|
|
301
|
+
fill: "none",
|
|
302
|
+
stroke: "currentColor",
|
|
303
|
+
strokeWidth: "2",
|
|
304
|
+
strokeLinecap: "round",
|
|
305
|
+
strokeLinejoin: "round",
|
|
306
|
+
className,
|
|
307
|
+
children: [
|
|
308
|
+
/* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
309
|
+
/* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
|
|
310
|
+
/* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
function TrashIcon({ className, size = defaultSize }) {
|
|
316
|
+
return /* @__PURE__ */ jsxs(
|
|
317
|
+
"svg",
|
|
318
|
+
{
|
|
319
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
320
|
+
width: size,
|
|
321
|
+
height: size,
|
|
322
|
+
viewBox: "0 0 24 24",
|
|
323
|
+
fill: "none",
|
|
324
|
+
stroke: "currentColor",
|
|
325
|
+
strokeWidth: "2",
|
|
326
|
+
strokeLinecap: "round",
|
|
327
|
+
strokeLinejoin: "round",
|
|
328
|
+
className,
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
|
|
331
|
+
/* @__PURE__ */ jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
332
|
+
/* @__PURE__ */ jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" })
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
function PencilIcon({ className, size = defaultSize }) {
|
|
338
|
+
return /* @__PURE__ */ jsxs(
|
|
339
|
+
"svg",
|
|
340
|
+
{
|
|
341
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
342
|
+
width: size,
|
|
343
|
+
height: size,
|
|
344
|
+
viewBox: "0 0 24 24",
|
|
345
|
+
fill: "none",
|
|
346
|
+
stroke: "currentColor",
|
|
347
|
+
strokeWidth: "2",
|
|
348
|
+
strokeLinecap: "round",
|
|
349
|
+
strokeLinejoin: "round",
|
|
350
|
+
className,
|
|
351
|
+
children: [
|
|
352
|
+
/* @__PURE__ */ jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }),
|
|
353
|
+
/* @__PURE__ */ jsx("path", { d: "m15 5 4 4" })
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
function PlusIcon({ className, size = defaultSize }) {
|
|
359
|
+
return /* @__PURE__ */ jsxs(
|
|
360
|
+
"svg",
|
|
361
|
+
{
|
|
362
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
363
|
+
width: size,
|
|
364
|
+
height: size,
|
|
365
|
+
viewBox: "0 0 24 24",
|
|
366
|
+
fill: "none",
|
|
367
|
+
stroke: "currentColor",
|
|
368
|
+
strokeWidth: "2",
|
|
369
|
+
strokeLinecap: "round",
|
|
370
|
+
strokeLinejoin: "round",
|
|
371
|
+
className,
|
|
372
|
+
children: [
|
|
373
|
+
/* @__PURE__ */ jsx("path", { d: "M5 12h14" }),
|
|
374
|
+
/* @__PURE__ */ jsx("path", { d: "M12 5v14" })
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
function RefreshIcon({ className, size = defaultSize }) {
|
|
380
|
+
return /* @__PURE__ */ jsxs(
|
|
381
|
+
"svg",
|
|
382
|
+
{
|
|
383
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
384
|
+
width: size,
|
|
385
|
+
height: size,
|
|
386
|
+
viewBox: "0 0 24 24",
|
|
387
|
+
fill: "none",
|
|
388
|
+
stroke: "currentColor",
|
|
389
|
+
strokeWidth: "2",
|
|
390
|
+
strokeLinecap: "round",
|
|
391
|
+
strokeLinejoin: "round",
|
|
392
|
+
className,
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
|
|
395
|
+
/* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" }),
|
|
396
|
+
/* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
|
|
397
|
+
/* @__PURE__ */ jsx("path", { d: "M8 16H3v5" })
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
function ChevronRightIcon({ className, size = defaultSize }) {
|
|
403
|
+
return /* @__PURE__ */ jsx(
|
|
404
|
+
"svg",
|
|
405
|
+
{
|
|
406
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
407
|
+
width: size,
|
|
408
|
+
height: size,
|
|
409
|
+
viewBox: "0 0 24 24",
|
|
410
|
+
fill: "none",
|
|
411
|
+
stroke: "currentColor",
|
|
412
|
+
strokeWidth: "2",
|
|
413
|
+
strokeLinecap: "round",
|
|
414
|
+
strokeLinejoin: "round",
|
|
415
|
+
className,
|
|
416
|
+
children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
function ChevronDownIcon({ className, size = defaultSize }) {
|
|
421
|
+
return /* @__PURE__ */ jsx(
|
|
422
|
+
"svg",
|
|
423
|
+
{
|
|
424
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
425
|
+
width: size,
|
|
426
|
+
height: size,
|
|
427
|
+
viewBox: "0 0 24 24",
|
|
428
|
+
fill: "none",
|
|
429
|
+
stroke: "currentColor",
|
|
430
|
+
strokeWidth: "2",
|
|
431
|
+
strokeLinecap: "round",
|
|
432
|
+
strokeLinejoin: "round",
|
|
433
|
+
className,
|
|
434
|
+
children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
435
|
+
}
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
function HomeIcon({ className, size = defaultSize }) {
|
|
439
|
+
return /* @__PURE__ */ jsxs(
|
|
440
|
+
"svg",
|
|
441
|
+
{
|
|
442
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
443
|
+
width: size,
|
|
444
|
+
height: size,
|
|
445
|
+
viewBox: "0 0 24 24",
|
|
446
|
+
fill: "none",
|
|
447
|
+
stroke: "currentColor",
|
|
448
|
+
strokeWidth: "2",
|
|
449
|
+
strokeLinecap: "round",
|
|
450
|
+
strokeLinejoin: "round",
|
|
451
|
+
className,
|
|
452
|
+
children: [
|
|
453
|
+
/* @__PURE__ */ jsx("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
|
|
454
|
+
/* @__PURE__ */ jsx("polyline", { points: "9 22 9 12 15 12 15 22" })
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
function MoreVerticalIcon({ className, size = defaultSize }) {
|
|
460
|
+
return /* @__PURE__ */ jsxs(
|
|
461
|
+
"svg",
|
|
462
|
+
{
|
|
463
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
464
|
+
width: size,
|
|
465
|
+
height: size,
|
|
466
|
+
viewBox: "0 0 24 24",
|
|
467
|
+
fill: "none",
|
|
468
|
+
stroke: "currentColor",
|
|
469
|
+
strokeWidth: "2",
|
|
470
|
+
strokeLinecap: "round",
|
|
471
|
+
strokeLinejoin: "round",
|
|
472
|
+
className,
|
|
473
|
+
children: [
|
|
474
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1" }),
|
|
475
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "5", r: "1" }),
|
|
476
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "19", r: "1" })
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
function XIcon({ className, size = defaultSize }) {
|
|
482
|
+
return /* @__PURE__ */ jsxs(
|
|
483
|
+
"svg",
|
|
484
|
+
{
|
|
485
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
486
|
+
width: size,
|
|
487
|
+
height: size,
|
|
488
|
+
viewBox: "0 0 24 24",
|
|
489
|
+
fill: "none",
|
|
490
|
+
stroke: "currentColor",
|
|
491
|
+
strokeWidth: "2",
|
|
492
|
+
strokeLinecap: "round",
|
|
493
|
+
strokeLinejoin: "round",
|
|
494
|
+
className,
|
|
495
|
+
children: [
|
|
496
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
497
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
function LoaderIcon({ className, size = defaultSize }) {
|
|
503
|
+
return /* @__PURE__ */ jsx(
|
|
504
|
+
"svg",
|
|
505
|
+
{
|
|
506
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
507
|
+
width: size,
|
|
508
|
+
height: size,
|
|
509
|
+
viewBox: "0 0 24 24",
|
|
510
|
+
fill: "none",
|
|
511
|
+
stroke: "currentColor",
|
|
512
|
+
strokeWidth: "2",
|
|
513
|
+
strokeLinecap: "round",
|
|
514
|
+
strokeLinejoin: "round",
|
|
515
|
+
className: `animate-spin ${className || ""}`,
|
|
516
|
+
children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
|
|
517
|
+
}
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
function getFileIcon(mimeType, isDirectory) {
|
|
521
|
+
if (isDirectory) {
|
|
522
|
+
return FolderIcon;
|
|
523
|
+
}
|
|
524
|
+
if (mimeType.startsWith("image/")) {
|
|
525
|
+
return ImageIcon;
|
|
526
|
+
}
|
|
527
|
+
if (mimeType.startsWith("video/")) {
|
|
528
|
+
return VideoIcon;
|
|
529
|
+
}
|
|
530
|
+
if (mimeType.startsWith("audio/")) {
|
|
531
|
+
return AudioIcon;
|
|
532
|
+
}
|
|
533
|
+
if (mimeType === "application/pdf") {
|
|
534
|
+
return PdfIcon;
|
|
535
|
+
}
|
|
536
|
+
if (mimeType.startsWith("text/") || mimeType === "application/json" || mimeType === "application/javascript" || mimeType === "application/typescript") {
|
|
537
|
+
return FileTextIcon;
|
|
538
|
+
}
|
|
539
|
+
if (mimeType === "application/zip" || mimeType === "application/x-rar-compressed" || mimeType === "application/x-7z-compressed" || mimeType === "application/gzip") {
|
|
540
|
+
return ArchiveIcon;
|
|
541
|
+
}
|
|
542
|
+
return FileIcon;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/ui/components/PathBreadcrumb.tsx
|
|
546
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
547
|
+
function PathBreadcrumb({ currentPath, onNavigate, className = "" }) {
|
|
548
|
+
const breadcrumbs = getBreadcrumbs(currentPath);
|
|
549
|
+
return /* @__PURE__ */ jsx2("nav", { className: `flex items-center space-x-1 text-sm ${className}`, children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsxs2(React.Fragment, { children: [
|
|
550
|
+
index > 0 && /* @__PURE__ */ jsx2(ChevronRightIcon, { size: 16, className: "text-gray-400 flex-shrink-0" }),
|
|
551
|
+
/* @__PURE__ */ jsxs2(
|
|
552
|
+
"button",
|
|
553
|
+
{
|
|
554
|
+
onClick: () => onNavigate(crumb.path),
|
|
555
|
+
className: `
|
|
556
|
+
flex items-center px-2 py-1 rounded hover:bg-gray-100 transition-colors
|
|
557
|
+
${index === breadcrumbs.length - 1 ? "font-medium text-gray-900" : "text-gray-600"}
|
|
558
|
+
`,
|
|
559
|
+
children: [
|
|
560
|
+
index === 0 ? /* @__PURE__ */ jsx2(HomeIcon, { size: 16, className: "mr-1" }) : null,
|
|
561
|
+
/* @__PURE__ */ jsx2("span", { className: "truncate max-w-[150px]", children: crumb.name })
|
|
562
|
+
]
|
|
563
|
+
}
|
|
564
|
+
)
|
|
565
|
+
] }, crumb.path)) });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/ui/components/FolderTree.tsx
|
|
569
|
+
import { useCallback } from "react";
|
|
570
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
571
|
+
function TreeNodeItem({
|
|
572
|
+
node,
|
|
573
|
+
level,
|
|
574
|
+
currentPath,
|
|
575
|
+
onSelect,
|
|
576
|
+
onExpand,
|
|
577
|
+
onToggle
|
|
578
|
+
}) {
|
|
579
|
+
const isSelected = currentPath === node.path;
|
|
580
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
581
|
+
const isExpanded = node.isExpanded;
|
|
582
|
+
const isLoading = node.isLoading;
|
|
583
|
+
const handleToggle = useCallback((e) => {
|
|
584
|
+
e.stopPropagation();
|
|
585
|
+
if (isExpanded) {
|
|
586
|
+
onToggle(node.path);
|
|
587
|
+
} else {
|
|
588
|
+
onExpand(node.path);
|
|
589
|
+
}
|
|
590
|
+
}, [isExpanded, node.path, onExpand, onToggle]);
|
|
591
|
+
const handleSelect = useCallback(() => {
|
|
592
|
+
onSelect(node.path);
|
|
593
|
+
}, [node.path, onSelect]);
|
|
594
|
+
return /* @__PURE__ */ jsxs3("div", { children: [
|
|
595
|
+
/* @__PURE__ */ jsxs3(
|
|
596
|
+
"div",
|
|
597
|
+
{
|
|
598
|
+
className: `
|
|
599
|
+
flex items-center py-1 px-2 cursor-pointer rounded
|
|
600
|
+
hover:bg-gray-100 transition-colors
|
|
601
|
+
${isSelected ? "bg-blue-100 text-blue-700" : "text-gray-700"}
|
|
602
|
+
`,
|
|
603
|
+
style: { paddingLeft: `${level * 16 + 8}px` },
|
|
604
|
+
onClick: handleSelect,
|
|
605
|
+
onDoubleClick: handleToggle,
|
|
606
|
+
children: [
|
|
607
|
+
/* @__PURE__ */ jsx3(
|
|
608
|
+
"button",
|
|
609
|
+
{
|
|
610
|
+
onClick: handleToggle,
|
|
611
|
+
className: "p-0.5 mr-1 hover:bg-gray-200 rounded flex-shrink-0",
|
|
612
|
+
children: isLoading ? /* @__PURE__ */ jsx3(LoaderIcon, { size: 14 }) : hasChildren || !isExpanded ? isExpanded ? /* @__PURE__ */ jsx3(ChevronDownIcon, { size: 14 }) : /* @__PURE__ */ jsx3(ChevronRightIcon, { size: 14 }) : /* @__PURE__ */ jsx3("span", { className: "w-3.5" })
|
|
613
|
+
}
|
|
614
|
+
),
|
|
615
|
+
isExpanded ? /* @__PURE__ */ jsx3(FolderOpenIcon, { size: 16, className: "mr-2 text-yellow-500 flex-shrink-0" }) : /* @__PURE__ */ jsx3(FolderIcon, { size: 16, className: "mr-2 text-yellow-500 flex-shrink-0" }),
|
|
616
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate text-sm", children: node.name })
|
|
617
|
+
]
|
|
618
|
+
}
|
|
619
|
+
),
|
|
620
|
+
isExpanded && hasChildren && /* @__PURE__ */ jsx3("div", { children: node.children.map((child) => /* @__PURE__ */ jsx3(
|
|
621
|
+
TreeNodeItem,
|
|
622
|
+
{
|
|
623
|
+
node: child,
|
|
624
|
+
level: level + 1,
|
|
625
|
+
currentPath,
|
|
626
|
+
onSelect,
|
|
627
|
+
onExpand,
|
|
628
|
+
onToggle
|
|
629
|
+
},
|
|
630
|
+
child.id
|
|
631
|
+
)) })
|
|
632
|
+
] });
|
|
633
|
+
}
|
|
634
|
+
function FolderTree({
|
|
635
|
+
tree,
|
|
636
|
+
currentPath,
|
|
637
|
+
onSelect,
|
|
638
|
+
onExpand,
|
|
639
|
+
onToggle,
|
|
640
|
+
className = ""
|
|
641
|
+
}) {
|
|
642
|
+
const rootNode = {
|
|
643
|
+
id: "root",
|
|
644
|
+
name: "Root",
|
|
645
|
+
path: "/",
|
|
646
|
+
children: tree,
|
|
647
|
+
isExpanded: true
|
|
648
|
+
};
|
|
649
|
+
return /* @__PURE__ */ jsx3("div", { className: `overflow-auto ${className}`, children: /* @__PURE__ */ jsx3(
|
|
650
|
+
TreeNodeItem,
|
|
651
|
+
{
|
|
652
|
+
node: rootNode,
|
|
653
|
+
level: 0,
|
|
654
|
+
currentPath,
|
|
655
|
+
onSelect,
|
|
656
|
+
onExpand,
|
|
657
|
+
onToggle
|
|
658
|
+
}
|
|
659
|
+
) });
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/ui/components/FileList.tsx
|
|
663
|
+
import { useCallback as useCallback2 } from "react";
|
|
664
|
+
|
|
665
|
+
// src/common/utils.ts
|
|
666
|
+
function formatBytes(bytes, decimals = 2) {
|
|
667
|
+
if (bytes === 0) return "0 Bytes";
|
|
668
|
+
const k = 1024;
|
|
669
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
670
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
|
|
671
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
672
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/ui/components/FileList.tsx
|
|
676
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
677
|
+
function FileItemCard({
|
|
678
|
+
item,
|
|
679
|
+
isSelected,
|
|
680
|
+
onSelect,
|
|
681
|
+
onOpen,
|
|
682
|
+
onContextMenu,
|
|
683
|
+
viewMode
|
|
684
|
+
}) {
|
|
685
|
+
const Icon = getFileIcon(
|
|
686
|
+
item.isDirectory ? "folder" : item.mimeType,
|
|
687
|
+
item.isDirectory
|
|
688
|
+
);
|
|
689
|
+
const handleClick = useCallback2((e) => {
|
|
690
|
+
e.stopPropagation();
|
|
691
|
+
onSelect();
|
|
692
|
+
}, [onSelect]);
|
|
693
|
+
const handleDoubleClick = useCallback2((e) => {
|
|
694
|
+
e.stopPropagation();
|
|
695
|
+
onOpen();
|
|
696
|
+
}, [onOpen]);
|
|
697
|
+
const handleContextMenu = useCallback2((e) => {
|
|
698
|
+
e.preventDefault();
|
|
699
|
+
onSelect();
|
|
700
|
+
onContextMenu?.(e);
|
|
701
|
+
}, [onSelect, onContextMenu]);
|
|
702
|
+
if (viewMode === "grid") {
|
|
703
|
+
return /* @__PURE__ */ jsx4(
|
|
704
|
+
"div",
|
|
705
|
+
{
|
|
706
|
+
className: `
|
|
707
|
+
p-3 rounded-lg cursor-pointer transition-all
|
|
708
|
+
hover:bg-gray-100
|
|
709
|
+
${isSelected ? "bg-blue-100 ring-2 ring-blue-500" : "bg-white"}
|
|
710
|
+
`,
|
|
711
|
+
onClick: handleClick,
|
|
712
|
+
onDoubleClick: handleDoubleClick,
|
|
713
|
+
onContextMenu: handleContextMenu,
|
|
714
|
+
children: /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center text-center", children: [
|
|
715
|
+
/* @__PURE__ */ jsx4(
|
|
716
|
+
Icon,
|
|
717
|
+
{
|
|
718
|
+
size: 40,
|
|
719
|
+
className: item.isDirectory ? "text-yellow-500" : "text-gray-500"
|
|
720
|
+
}
|
|
721
|
+
),
|
|
722
|
+
/* @__PURE__ */ jsx4("span", { className: "mt-2 text-sm truncate w-full", title: item.name, children: item.name }),
|
|
723
|
+
!item.isDirectory && /* @__PURE__ */ jsx4("span", { className: "text-xs text-gray-400 mt-1", children: formatBytes(item.size) })
|
|
724
|
+
] })
|
|
725
|
+
}
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
return /* @__PURE__ */ jsxs4(
|
|
729
|
+
"div",
|
|
730
|
+
{
|
|
731
|
+
className: `
|
|
732
|
+
flex items-center p-2 rounded cursor-pointer transition-all
|
|
733
|
+
hover:bg-gray-100
|
|
734
|
+
${isSelected ? "bg-blue-100" : ""}
|
|
735
|
+
`,
|
|
736
|
+
onClick: handleClick,
|
|
737
|
+
onDoubleClick: handleDoubleClick,
|
|
738
|
+
onContextMenu: handleContextMenu,
|
|
739
|
+
children: [
|
|
740
|
+
/* @__PURE__ */ jsx4(
|
|
741
|
+
Icon,
|
|
742
|
+
{
|
|
743
|
+
size: 20,
|
|
744
|
+
className: `flex-shrink-0 ${item.isDirectory ? "text-yellow-500" : "text-gray-500"}`
|
|
745
|
+
}
|
|
746
|
+
),
|
|
747
|
+
/* @__PURE__ */ jsx4("span", { className: "ml-3 flex-1 truncate text-sm", title: item.name, children: item.name }),
|
|
748
|
+
!item.isDirectory && /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
749
|
+
/* @__PURE__ */ jsx4("span", { className: "text-xs text-gray-400 ml-4", children: formatBytes(item.size) }),
|
|
750
|
+
/* @__PURE__ */ jsx4("span", { className: "text-xs text-gray-400 ml-4 w-32 truncate", children: new Date(item.modifiedAt).toLocaleDateString() })
|
|
751
|
+
] })
|
|
752
|
+
]
|
|
753
|
+
}
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
function FileList({
|
|
757
|
+
files,
|
|
758
|
+
selectedItem,
|
|
759
|
+
isLoading,
|
|
760
|
+
onSelect,
|
|
761
|
+
onOpen,
|
|
762
|
+
onContextMenu,
|
|
763
|
+
viewMode = "grid",
|
|
764
|
+
className = ""
|
|
765
|
+
}) {
|
|
766
|
+
const handleBackgroundClick = useCallback2(() => {
|
|
767
|
+
onSelect(null);
|
|
768
|
+
}, [onSelect]);
|
|
769
|
+
if (isLoading) {
|
|
770
|
+
return /* @__PURE__ */ jsx4("div", { className: `flex items-center justify-center h-full ${className}`, children: /* @__PURE__ */ jsx4(LoaderIcon, { size: 32, className: "text-gray-400" }) });
|
|
771
|
+
}
|
|
772
|
+
if (files.length === 0) {
|
|
773
|
+
return /* @__PURE__ */ jsxs4(
|
|
774
|
+
"div",
|
|
775
|
+
{
|
|
776
|
+
className: `flex flex-col items-center justify-center h-full text-gray-400 ${className}`,
|
|
777
|
+
onClick: handleBackgroundClick,
|
|
778
|
+
children: [
|
|
779
|
+
/* @__PURE__ */ jsx4(
|
|
780
|
+
"svg",
|
|
781
|
+
{
|
|
782
|
+
className: "w-16 h-16 mb-4",
|
|
783
|
+
fill: "none",
|
|
784
|
+
stroke: "currentColor",
|
|
785
|
+
viewBox: "0 0 24 24",
|
|
786
|
+
children: /* @__PURE__ */ jsx4(
|
|
787
|
+
"path",
|
|
788
|
+
{
|
|
789
|
+
strokeLinecap: "round",
|
|
790
|
+
strokeLinejoin: "round",
|
|
791
|
+
strokeWidth: 1.5,
|
|
792
|
+
d: "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
|
|
793
|
+
}
|
|
794
|
+
)
|
|
795
|
+
}
|
|
796
|
+
),
|
|
797
|
+
/* @__PURE__ */ jsx4("p", { children: "This folder is empty" })
|
|
798
|
+
]
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
if (viewMode === "grid") {
|
|
803
|
+
return /* @__PURE__ */ jsx4(
|
|
804
|
+
"div",
|
|
805
|
+
{
|
|
806
|
+
className: `grid grid-cols-[repeat(auto-fill,minmax(120px,1fr))] gap-2 p-2 ${className}`,
|
|
807
|
+
onClick: handleBackgroundClick,
|
|
808
|
+
children: files.map((file) => /* @__PURE__ */ jsx4(
|
|
809
|
+
FileItemCard,
|
|
810
|
+
{
|
|
811
|
+
item: file,
|
|
812
|
+
isSelected: selectedItem?.id === file.id,
|
|
813
|
+
onSelect: () => onSelect(file),
|
|
814
|
+
onOpen: () => onOpen(file),
|
|
815
|
+
onContextMenu: onContextMenu ? (e) => onContextMenu(file, e) : void 0,
|
|
816
|
+
viewMode: "grid"
|
|
817
|
+
},
|
|
818
|
+
file.id
|
|
819
|
+
))
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
return /* @__PURE__ */ jsxs4(
|
|
824
|
+
"div",
|
|
825
|
+
{
|
|
826
|
+
className: `flex flex-col ${className}`,
|
|
827
|
+
onClick: handleBackgroundClick,
|
|
828
|
+
children: [
|
|
829
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center p-2 border-b text-xs text-gray-500 font-medium", children: [
|
|
830
|
+
/* @__PURE__ */ jsx4("span", { className: "flex-1 ml-8", children: "Name" }),
|
|
831
|
+
/* @__PURE__ */ jsx4("span", { className: "w-20 ml-4", children: "Size" }),
|
|
832
|
+
/* @__PURE__ */ jsx4("span", { className: "w-32 ml-4", children: "Modified" })
|
|
833
|
+
] }),
|
|
834
|
+
/* @__PURE__ */ jsx4("div", { className: "flex-1 overflow-auto", children: files.map((file) => /* @__PURE__ */ jsx4(
|
|
835
|
+
FileItemCard,
|
|
836
|
+
{
|
|
837
|
+
item: file,
|
|
838
|
+
isSelected: selectedItem?.id === file.id,
|
|
839
|
+
onSelect: () => onSelect(file),
|
|
840
|
+
onOpen: () => onOpen(file),
|
|
841
|
+
onContextMenu: onContextMenu ? (e) => onContextMenu(file, e) : void 0,
|
|
842
|
+
viewMode: "list"
|
|
843
|
+
},
|
|
844
|
+
file.id
|
|
845
|
+
)) })
|
|
846
|
+
]
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/ui/components/FilePreview.tsx
|
|
852
|
+
import { useEffect, useState } from "react";
|
|
853
|
+
|
|
854
|
+
// src/common/mime-types.ts
|
|
855
|
+
var MIME_TYPES = {
|
|
856
|
+
// Text
|
|
857
|
+
".txt": "text/plain",
|
|
858
|
+
".html": "text/html",
|
|
859
|
+
".htm": "text/html",
|
|
860
|
+
".css": "text/css",
|
|
861
|
+
".csv": "text/csv",
|
|
862
|
+
".xml": "text/xml",
|
|
863
|
+
".json": "application/json",
|
|
864
|
+
".js": "application/javascript",
|
|
865
|
+
".ts": "application/typescript",
|
|
866
|
+
".jsx": "text/jsx",
|
|
867
|
+
".tsx": "text/tsx",
|
|
868
|
+
".md": "text/markdown",
|
|
869
|
+
".yaml": "text/yaml",
|
|
870
|
+
".yml": "text/yaml",
|
|
871
|
+
// Images
|
|
872
|
+
".png": "image/png",
|
|
873
|
+
".jpg": "image/jpeg",
|
|
874
|
+
".jpeg": "image/jpeg",
|
|
875
|
+
".gif": "image/gif",
|
|
876
|
+
".bmp": "image/bmp",
|
|
877
|
+
".webp": "image/webp",
|
|
878
|
+
".svg": "image/svg+xml",
|
|
879
|
+
".ico": "image/x-icon",
|
|
880
|
+
// Documents
|
|
881
|
+
".pdf": "application/pdf",
|
|
882
|
+
".doc": "application/msword",
|
|
883
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
884
|
+
".xls": "application/vnd.ms-excel",
|
|
885
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
886
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
887
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
888
|
+
// Archives
|
|
889
|
+
".zip": "application/zip",
|
|
890
|
+
".rar": "application/x-rar-compressed",
|
|
891
|
+
".7z": "application/x-7z-compressed",
|
|
892
|
+
".tar": "application/x-tar",
|
|
893
|
+
".gz": "application/gzip",
|
|
894
|
+
// Audio
|
|
895
|
+
".mp3": "audio/mpeg",
|
|
896
|
+
".wav": "audio/wav",
|
|
897
|
+
".ogg": "audio/ogg",
|
|
898
|
+
".m4a": "audio/mp4",
|
|
899
|
+
".flac": "audio/flac",
|
|
900
|
+
// Video
|
|
901
|
+
".mp4": "video/mp4",
|
|
902
|
+
".webm": "video/webm",
|
|
903
|
+
".avi": "video/x-msvideo",
|
|
904
|
+
".mov": "video/quicktime",
|
|
905
|
+
".wmv": "video/x-ms-wmv",
|
|
906
|
+
".mkv": "video/x-matroska",
|
|
907
|
+
// Fonts
|
|
908
|
+
".ttf": "font/ttf",
|
|
909
|
+
".otf": "font/otf",
|
|
910
|
+
".woff": "font/woff",
|
|
911
|
+
".woff2": "font/woff2",
|
|
912
|
+
// Other
|
|
913
|
+
".exe": "application/x-msdownload",
|
|
914
|
+
".dmg": "application/x-apple-diskimage",
|
|
915
|
+
".bin": "application/octet-stream"
|
|
916
|
+
};
|
|
917
|
+
var EXTENSION_BY_MIME = Object.entries(MIME_TYPES).reduce(
|
|
918
|
+
(acc, [ext, mime]) => {
|
|
919
|
+
if (!acc[mime]) {
|
|
920
|
+
acc[mime] = ext;
|
|
921
|
+
}
|
|
922
|
+
return acc;
|
|
923
|
+
},
|
|
924
|
+
{}
|
|
925
|
+
);
|
|
926
|
+
function getMimeType(filename) {
|
|
927
|
+
const ext = getExtension(filename).toLowerCase();
|
|
928
|
+
return MIME_TYPES[ext] || "application/octet-stream";
|
|
929
|
+
}
|
|
930
|
+
function isImage(filenameOrMime) {
|
|
931
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
932
|
+
return mime.startsWith("image/");
|
|
933
|
+
}
|
|
934
|
+
function isVideo(filenameOrMime) {
|
|
935
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
936
|
+
return mime.startsWith("video/");
|
|
937
|
+
}
|
|
938
|
+
function isAudio(filenameOrMime) {
|
|
939
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
940
|
+
return mime.startsWith("audio/");
|
|
941
|
+
}
|
|
942
|
+
function isText(filenameOrMime) {
|
|
943
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
944
|
+
return mime.startsWith("text/") || mime === "application/json" || mime === "application/javascript";
|
|
945
|
+
}
|
|
946
|
+
function isPreviewable(filenameOrMime) {
|
|
947
|
+
return isImage(filenameOrMime) || isText(filenameOrMime) || isVideo(filenameOrMime) || isAudio(filenameOrMime);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/ui/components/FilePreview.tsx
|
|
951
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
952
|
+
function FilePreview({
|
|
953
|
+
item,
|
|
954
|
+
getPreviewUrl,
|
|
955
|
+
getFileContent,
|
|
956
|
+
className = ""
|
|
957
|
+
}) {
|
|
958
|
+
const [previewUrl, setPreviewUrl] = useState(null);
|
|
959
|
+
const [textContent, setTextContent] = useState(null);
|
|
960
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
961
|
+
const [error, setError] = useState(null);
|
|
962
|
+
useEffect(() => {
|
|
963
|
+
setPreviewUrl(null);
|
|
964
|
+
setTextContent(null);
|
|
965
|
+
setError(null);
|
|
966
|
+
if (!item || item.isDirectory) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const fileItem2 = item;
|
|
970
|
+
const mimeType2 = fileItem2.mimeType;
|
|
971
|
+
const loadPreview = async () => {
|
|
972
|
+
setIsLoading(true);
|
|
973
|
+
try {
|
|
974
|
+
if ((isImage(mimeType2) || isVideo(mimeType2) || isAudio(mimeType2)) && getPreviewUrl) {
|
|
975
|
+
const url = await getPreviewUrl(item.path);
|
|
976
|
+
setPreviewUrl(url);
|
|
977
|
+
} else if (isText(mimeType2) && getFileContent) {
|
|
978
|
+
const content = await getFileContent(item.path);
|
|
979
|
+
setTextContent(content);
|
|
980
|
+
}
|
|
981
|
+
} catch (err) {
|
|
982
|
+
setError(err.message);
|
|
983
|
+
} finally {
|
|
984
|
+
setIsLoading(false);
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
if (isPreviewable(mimeType2) && (getPreviewUrl || getFileContent)) {
|
|
988
|
+
loadPreview();
|
|
989
|
+
}
|
|
990
|
+
}, [item, getPreviewUrl, getFileContent]);
|
|
991
|
+
if (!item) {
|
|
992
|
+
return /* @__PURE__ */ jsx5("div", { className: `flex items-center justify-center h-full text-gray-400 ${className}`, children: /* @__PURE__ */ jsx5("p", { children: "Select a file to preview" }) });
|
|
993
|
+
}
|
|
994
|
+
if (item.isDirectory) {
|
|
995
|
+
const Icon2 = getFileIcon("folder", true);
|
|
996
|
+
return /* @__PURE__ */ jsxs5("div", { className: `flex flex-col items-center justify-center h-full p-4 ${className}`, children: [
|
|
997
|
+
/* @__PURE__ */ jsx5(Icon2, { size: 64, className: "text-yellow-500 mb-4" }),
|
|
998
|
+
/* @__PURE__ */ jsx5("h3", { className: "text-lg font-medium", children: item.name }),
|
|
999
|
+
/* @__PURE__ */ jsx5("p", { className: "text-sm text-gray-500 mt-2", children: "Folder" }),
|
|
1000
|
+
/* @__PURE__ */ jsxs5("div", { className: "mt-4 text-xs text-gray-400 space-y-1", children: [
|
|
1001
|
+
/* @__PURE__ */ jsxs5("p", { children: [
|
|
1002
|
+
"Created: ",
|
|
1003
|
+
new Date(item.createdAt).toLocaleString()
|
|
1004
|
+
] }),
|
|
1005
|
+
/* @__PURE__ */ jsxs5("p", { children: [
|
|
1006
|
+
"Modified: ",
|
|
1007
|
+
new Date(item.modifiedAt).toLocaleString()
|
|
1008
|
+
] })
|
|
1009
|
+
] })
|
|
1010
|
+
] });
|
|
1011
|
+
}
|
|
1012
|
+
const fileItem = item;
|
|
1013
|
+
const mimeType = fileItem.mimeType;
|
|
1014
|
+
const Icon = getFileIcon(mimeType, false);
|
|
1015
|
+
if (isLoading) {
|
|
1016
|
+
return /* @__PURE__ */ jsx5("div", { className: `flex items-center justify-center h-full ${className}`, children: /* @__PURE__ */ jsx5(LoaderIcon, { size: 32, className: "text-gray-400" }) });
|
|
1017
|
+
}
|
|
1018
|
+
if (error) {
|
|
1019
|
+
return /* @__PURE__ */ jsxs5("div", { className: `flex flex-col items-center justify-center h-full p-4 text-red-500 ${className}`, children: [
|
|
1020
|
+
/* @__PURE__ */ jsx5("p", { children: "Error loading preview" }),
|
|
1021
|
+
/* @__PURE__ */ jsx5("p", { className: "text-sm mt-2", children: error })
|
|
1022
|
+
] });
|
|
1023
|
+
}
|
|
1024
|
+
return /* @__PURE__ */ jsxs5("div", { className: `flex flex-col h-full ${className}`, children: [
|
|
1025
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex-1 overflow-auto p-4 flex items-center justify-center bg-gray-50", children: [
|
|
1026
|
+
isImage(mimeType) && previewUrl && /* @__PURE__ */ jsx5(
|
|
1027
|
+
"img",
|
|
1028
|
+
{
|
|
1029
|
+
src: previewUrl,
|
|
1030
|
+
alt: item.name,
|
|
1031
|
+
className: "max-w-full max-h-full object-contain"
|
|
1032
|
+
}
|
|
1033
|
+
),
|
|
1034
|
+
isVideo(mimeType) && previewUrl && /* @__PURE__ */ jsx5(
|
|
1035
|
+
"video",
|
|
1036
|
+
{
|
|
1037
|
+
src: previewUrl,
|
|
1038
|
+
controls: true,
|
|
1039
|
+
className: "max-w-full max-h-full",
|
|
1040
|
+
children: "Your browser does not support video playback."
|
|
1041
|
+
}
|
|
1042
|
+
),
|
|
1043
|
+
isAudio(mimeType) && previewUrl && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-center", children: [
|
|
1044
|
+
/* @__PURE__ */ jsx5(Icon, { size: 64, className: "text-gray-400 mb-4" }),
|
|
1045
|
+
/* @__PURE__ */ jsx5("audio", { src: previewUrl, controls: true, className: "w-full max-w-md", children: "Your browser does not support audio playback." })
|
|
1046
|
+
] }),
|
|
1047
|
+
isText(mimeType) && textContent !== null && /* @__PURE__ */ jsx5("pre", { className: "w-full h-full overflow-auto text-sm bg-white p-4 rounded border font-mono whitespace-pre-wrap", children: textContent }),
|
|
1048
|
+
!previewUrl && textContent === null && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-center text-gray-400", children: [
|
|
1049
|
+
/* @__PURE__ */ jsx5(Icon, { size: 64, className: "mb-4" }),
|
|
1050
|
+
/* @__PURE__ */ jsx5("p", { children: "No preview available" })
|
|
1051
|
+
] })
|
|
1052
|
+
] }),
|
|
1053
|
+
/* @__PURE__ */ jsxs5("div", { className: "border-t bg-white p-4", children: [
|
|
1054
|
+
/* @__PURE__ */ jsx5("h3", { className: "font-medium truncate", title: item.name, children: item.name }),
|
|
1055
|
+
/* @__PURE__ */ jsxs5("div", { className: "mt-2 text-sm text-gray-500 grid grid-cols-2 gap-2", children: [
|
|
1056
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1057
|
+
/* @__PURE__ */ jsx5("span", { className: "text-gray-400", children: "Size:" }),
|
|
1058
|
+
" ",
|
|
1059
|
+
formatBytes(fileItem.size)
|
|
1060
|
+
] }),
|
|
1061
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1062
|
+
/* @__PURE__ */ jsx5("span", { className: "text-gray-400", children: "Type:" }),
|
|
1063
|
+
" ",
|
|
1064
|
+
mimeType
|
|
1065
|
+
] }),
|
|
1066
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1067
|
+
/* @__PURE__ */ jsx5("span", { className: "text-gray-400", children: "Created:" }),
|
|
1068
|
+
" ",
|
|
1069
|
+
new Date(item.createdAt).toLocaleDateString()
|
|
1070
|
+
] }),
|
|
1071
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1072
|
+
/* @__PURE__ */ jsx5("span", { className: "text-gray-400", children: "Modified:" }),
|
|
1073
|
+
" ",
|
|
1074
|
+
new Date(item.modifiedAt).toLocaleDateString()
|
|
1075
|
+
] })
|
|
1076
|
+
] })
|
|
1077
|
+
] })
|
|
1078
|
+
] });
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/ui/components/FileActions.tsx
|
|
1082
|
+
import { useRef } from "react";
|
|
1083
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1084
|
+
function ActionButton({ icon, label, onClick, disabled, variant = "default" }) {
|
|
1085
|
+
return /* @__PURE__ */ jsxs6(
|
|
1086
|
+
"button",
|
|
1087
|
+
{
|
|
1088
|
+
onClick,
|
|
1089
|
+
disabled,
|
|
1090
|
+
title: label,
|
|
1091
|
+
className: `
|
|
1092
|
+
flex items-center gap-2 px-3 py-2 rounded text-sm transition-colors
|
|
1093
|
+
${disabled ? "text-gray-300 cursor-not-allowed" : variant === "danger" ? "text-red-600 hover:bg-red-50" : "text-gray-700 hover:bg-gray-100"}
|
|
1094
|
+
`,
|
|
1095
|
+
children: [
|
|
1096
|
+
icon,
|
|
1097
|
+
/* @__PURE__ */ jsx6("span", { className: "hidden sm:inline", children: label })
|
|
1098
|
+
]
|
|
1099
|
+
}
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
function FileActions({
|
|
1103
|
+
selectedItem,
|
|
1104
|
+
onCreateFolder,
|
|
1105
|
+
onUpload,
|
|
1106
|
+
onDownload,
|
|
1107
|
+
onDelete,
|
|
1108
|
+
onRename,
|
|
1109
|
+
onRefresh,
|
|
1110
|
+
isLoading,
|
|
1111
|
+
className = ""
|
|
1112
|
+
}) {
|
|
1113
|
+
const fileInputRef = useRef(null);
|
|
1114
|
+
const handleUploadClick = () => {
|
|
1115
|
+
fileInputRef.current?.click();
|
|
1116
|
+
};
|
|
1117
|
+
const handleFileChange = (e) => {
|
|
1118
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
1119
|
+
onUpload(e.target.files);
|
|
1120
|
+
e.target.value = "";
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
const hasSelection = selectedItem !== null;
|
|
1124
|
+
const isFile = hasSelection && !selectedItem.isDirectory;
|
|
1125
|
+
return /* @__PURE__ */ jsxs6("div", { className: `flex items-center gap-1 ${className}`, children: [
|
|
1126
|
+
/* @__PURE__ */ jsx6(
|
|
1127
|
+
"input",
|
|
1128
|
+
{
|
|
1129
|
+
ref: fileInputRef,
|
|
1130
|
+
type: "file",
|
|
1131
|
+
multiple: true,
|
|
1132
|
+
onChange: handleFileChange,
|
|
1133
|
+
className: "hidden"
|
|
1134
|
+
}
|
|
1135
|
+
),
|
|
1136
|
+
/* @__PURE__ */ jsx6(
|
|
1137
|
+
ActionButton,
|
|
1138
|
+
{
|
|
1139
|
+
icon: /* @__PURE__ */ jsx6(PlusIcon, { size: 18 }),
|
|
1140
|
+
label: "New Folder",
|
|
1141
|
+
onClick: onCreateFolder,
|
|
1142
|
+
disabled: isLoading
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
/* @__PURE__ */ jsx6(
|
|
1146
|
+
ActionButton,
|
|
1147
|
+
{
|
|
1148
|
+
icon: /* @__PURE__ */ jsx6(UploadIcon, { size: 18 }),
|
|
1149
|
+
label: "Upload",
|
|
1150
|
+
onClick: handleUploadClick,
|
|
1151
|
+
disabled: isLoading
|
|
1152
|
+
}
|
|
1153
|
+
),
|
|
1154
|
+
/* @__PURE__ */ jsx6("div", { className: "w-px h-6 bg-gray-200 mx-1" }),
|
|
1155
|
+
/* @__PURE__ */ jsx6(
|
|
1156
|
+
ActionButton,
|
|
1157
|
+
{
|
|
1158
|
+
icon: /* @__PURE__ */ jsx6(DownloadIcon, { size: 18 }),
|
|
1159
|
+
label: "Download",
|
|
1160
|
+
onClick: onDownload,
|
|
1161
|
+
disabled: !isFile || isLoading
|
|
1162
|
+
}
|
|
1163
|
+
),
|
|
1164
|
+
/* @__PURE__ */ jsx6(
|
|
1165
|
+
ActionButton,
|
|
1166
|
+
{
|
|
1167
|
+
icon: /* @__PURE__ */ jsx6(PencilIcon, { size: 18 }),
|
|
1168
|
+
label: "Rename",
|
|
1169
|
+
onClick: onRename,
|
|
1170
|
+
disabled: !hasSelection || isLoading
|
|
1171
|
+
}
|
|
1172
|
+
),
|
|
1173
|
+
/* @__PURE__ */ jsx6(
|
|
1174
|
+
ActionButton,
|
|
1175
|
+
{
|
|
1176
|
+
icon: /* @__PURE__ */ jsx6(TrashIcon, { size: 18 }),
|
|
1177
|
+
label: "Delete",
|
|
1178
|
+
onClick: onDelete,
|
|
1179
|
+
disabled: !hasSelection || isLoading,
|
|
1180
|
+
variant: "danger"
|
|
1181
|
+
}
|
|
1182
|
+
),
|
|
1183
|
+
/* @__PURE__ */ jsx6("div", { className: "w-px h-6 bg-gray-200 mx-1" }),
|
|
1184
|
+
/* @__PURE__ */ jsx6(
|
|
1185
|
+
ActionButton,
|
|
1186
|
+
{
|
|
1187
|
+
icon: /* @__PURE__ */ jsx6(RefreshIcon, { size: 18, className: isLoading ? "animate-spin" : "" }),
|
|
1188
|
+
label: "Refresh",
|
|
1189
|
+
onClick: onRefresh,
|
|
1190
|
+
disabled: isLoading
|
|
1191
|
+
}
|
|
1192
|
+
)
|
|
1193
|
+
] });
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// src/ui/components/dialogs/CreateFolderDialog.tsx
|
|
1197
|
+
import { useState as useState2, useCallback as useCallback3, useEffect as useEffect2 } from "react";
|
|
1198
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1199
|
+
function CreateFolderDialog({
|
|
1200
|
+
isOpen,
|
|
1201
|
+
onClose,
|
|
1202
|
+
onSubmit,
|
|
1203
|
+
currentPath
|
|
1204
|
+
}) {
|
|
1205
|
+
const [name, setName] = useState2("");
|
|
1206
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
1207
|
+
const [error, setError] = useState2(null);
|
|
1208
|
+
useEffect2(() => {
|
|
1209
|
+
if (isOpen) {
|
|
1210
|
+
setName("");
|
|
1211
|
+
setError(null);
|
|
1212
|
+
}
|
|
1213
|
+
}, [isOpen]);
|
|
1214
|
+
const handleSubmit = useCallback3(async (e) => {
|
|
1215
|
+
e.preventDefault();
|
|
1216
|
+
if (!name.trim()) {
|
|
1217
|
+
setError("Please enter a folder name");
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (/[<>:"/\\|?*]/.test(name)) {
|
|
1221
|
+
setError("Folder name contains invalid characters");
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
setIsLoading(true);
|
|
1225
|
+
setError(null);
|
|
1226
|
+
try {
|
|
1227
|
+
await onSubmit(name.trim());
|
|
1228
|
+
onClose();
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
setError(err.message);
|
|
1231
|
+
} finally {
|
|
1232
|
+
setIsLoading(false);
|
|
1233
|
+
}
|
|
1234
|
+
}, [name, onSubmit, onClose]);
|
|
1235
|
+
if (!isOpen) return null;
|
|
1236
|
+
return /* @__PURE__ */ jsxs7("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1237
|
+
/* @__PURE__ */ jsx7(
|
|
1238
|
+
"div",
|
|
1239
|
+
{
|
|
1240
|
+
className: "absolute inset-0 bg-black/50",
|
|
1241
|
+
onClick: onClose
|
|
1242
|
+
}
|
|
1243
|
+
),
|
|
1244
|
+
/* @__PURE__ */ jsxs7("div", { className: "relative bg-white rounded-lg shadow-xl w-full max-w-md mx-4", children: [
|
|
1245
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between p-4 border-b", children: [
|
|
1246
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
1247
|
+
/* @__PURE__ */ jsx7(FolderIcon, { size: 20, className: "text-yellow-500" }),
|
|
1248
|
+
/* @__PURE__ */ jsx7("h2", { className: "text-lg font-medium", children: "Create New Folder" })
|
|
1249
|
+
] }),
|
|
1250
|
+
/* @__PURE__ */ jsx7(
|
|
1251
|
+
"button",
|
|
1252
|
+
{
|
|
1253
|
+
onClick: onClose,
|
|
1254
|
+
className: "p-1 hover:bg-gray-100 rounded transition-colors",
|
|
1255
|
+
children: /* @__PURE__ */ jsx7(XIcon, { size: 20 })
|
|
1256
|
+
}
|
|
1257
|
+
)
|
|
1258
|
+
] }),
|
|
1259
|
+
/* @__PURE__ */ jsxs7("form", { onSubmit: handleSubmit, children: [
|
|
1260
|
+
/* @__PURE__ */ jsxs7("div", { className: "p-4", children: [
|
|
1261
|
+
/* @__PURE__ */ jsxs7("div", { className: "text-sm text-gray-500 mb-4", children: [
|
|
1262
|
+
"Creating folder in: ",
|
|
1263
|
+
/* @__PURE__ */ jsx7("span", { className: "font-medium", children: currentPath })
|
|
1264
|
+
] }),
|
|
1265
|
+
/* @__PURE__ */ jsxs7("label", { className: "block", children: [
|
|
1266
|
+
/* @__PURE__ */ jsx7("span", { className: "text-sm font-medium text-gray-700", children: "Folder Name" }),
|
|
1267
|
+
/* @__PURE__ */ jsx7(
|
|
1268
|
+
"input",
|
|
1269
|
+
{
|
|
1270
|
+
type: "text",
|
|
1271
|
+
value: name,
|
|
1272
|
+
onChange: (e) => setName(e.target.value),
|
|
1273
|
+
placeholder: "Enter folder name",
|
|
1274
|
+
className: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
|
|
1275
|
+
autoFocus: true,
|
|
1276
|
+
disabled: isLoading
|
|
1277
|
+
}
|
|
1278
|
+
)
|
|
1279
|
+
] }),
|
|
1280
|
+
error && /* @__PURE__ */ jsx7("p", { className: "mt-2 text-sm text-red-600", children: error })
|
|
1281
|
+
] }),
|
|
1282
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex justify-end gap-2 p-4 border-t bg-gray-50 rounded-b-lg", children: [
|
|
1283
|
+
/* @__PURE__ */ jsx7(
|
|
1284
|
+
"button",
|
|
1285
|
+
{
|
|
1286
|
+
type: "button",
|
|
1287
|
+
onClick: onClose,
|
|
1288
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-md transition-colors",
|
|
1289
|
+
disabled: isLoading,
|
|
1290
|
+
children: "Cancel"
|
|
1291
|
+
}
|
|
1292
|
+
),
|
|
1293
|
+
/* @__PURE__ */ jsxs7(
|
|
1294
|
+
"button",
|
|
1295
|
+
{
|
|
1296
|
+
type: "submit",
|
|
1297
|
+
className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2",
|
|
1298
|
+
disabled: isLoading || !name.trim(),
|
|
1299
|
+
children: [
|
|
1300
|
+
isLoading && /* @__PURE__ */ jsx7(LoaderIcon, { size: 16 }),
|
|
1301
|
+
"Create Folder"
|
|
1302
|
+
]
|
|
1303
|
+
}
|
|
1304
|
+
)
|
|
1305
|
+
] })
|
|
1306
|
+
] })
|
|
1307
|
+
] })
|
|
1308
|
+
] });
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/ui/components/dialogs/RenameDialog.tsx
|
|
1312
|
+
import { useState as useState3, useCallback as useCallback4, useEffect as useEffect3 } from "react";
|
|
1313
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1314
|
+
function RenameDialog({
|
|
1315
|
+
isOpen,
|
|
1316
|
+
item,
|
|
1317
|
+
onClose,
|
|
1318
|
+
onSubmit
|
|
1319
|
+
}) {
|
|
1320
|
+
const [name, setName] = useState3("");
|
|
1321
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
1322
|
+
const [error, setError] = useState3(null);
|
|
1323
|
+
useEffect3(() => {
|
|
1324
|
+
if (isOpen && item) {
|
|
1325
|
+
setName(item.name);
|
|
1326
|
+
setError(null);
|
|
1327
|
+
}
|
|
1328
|
+
}, [isOpen, item]);
|
|
1329
|
+
const handleSubmit = useCallback4(async (e) => {
|
|
1330
|
+
e.preventDefault();
|
|
1331
|
+
if (!name.trim()) {
|
|
1332
|
+
setError("Please enter a name");
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (/[<>:"/\\|?*]/.test(name)) {
|
|
1336
|
+
setError("Name contains invalid characters");
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
if (name.trim() === item?.name) {
|
|
1340
|
+
onClose();
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
setIsLoading(true);
|
|
1344
|
+
setError(null);
|
|
1345
|
+
try {
|
|
1346
|
+
await onSubmit(name.trim());
|
|
1347
|
+
onClose();
|
|
1348
|
+
} catch (err) {
|
|
1349
|
+
setError(err.message);
|
|
1350
|
+
} finally {
|
|
1351
|
+
setIsLoading(false);
|
|
1352
|
+
}
|
|
1353
|
+
}, [name, item, onSubmit, onClose]);
|
|
1354
|
+
if (!isOpen || !item) return null;
|
|
1355
|
+
return /* @__PURE__ */ jsxs8("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1356
|
+
/* @__PURE__ */ jsx8(
|
|
1357
|
+
"div",
|
|
1358
|
+
{
|
|
1359
|
+
className: "absolute inset-0 bg-black/50",
|
|
1360
|
+
onClick: onClose
|
|
1361
|
+
}
|
|
1362
|
+
),
|
|
1363
|
+
/* @__PURE__ */ jsxs8("div", { className: "relative bg-white rounded-lg shadow-xl w-full max-w-md mx-4", children: [
|
|
1364
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between p-4 border-b", children: [
|
|
1365
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
|
|
1366
|
+
/* @__PURE__ */ jsx8(PencilIcon, { size: 20, className: "text-gray-500" }),
|
|
1367
|
+
/* @__PURE__ */ jsxs8("h2", { className: "text-lg font-medium", children: [
|
|
1368
|
+
"Rename ",
|
|
1369
|
+
item.isDirectory ? "Folder" : "File"
|
|
1370
|
+
] })
|
|
1371
|
+
] }),
|
|
1372
|
+
/* @__PURE__ */ jsx8(
|
|
1373
|
+
"button",
|
|
1374
|
+
{
|
|
1375
|
+
onClick: onClose,
|
|
1376
|
+
className: "p-1 hover:bg-gray-100 rounded transition-colors",
|
|
1377
|
+
children: /* @__PURE__ */ jsx8(XIcon, { size: 20 })
|
|
1378
|
+
}
|
|
1379
|
+
)
|
|
1380
|
+
] }),
|
|
1381
|
+
/* @__PURE__ */ jsxs8("form", { onSubmit: handleSubmit, children: [
|
|
1382
|
+
/* @__PURE__ */ jsxs8("div", { className: "p-4", children: [
|
|
1383
|
+
/* @__PURE__ */ jsxs8("label", { className: "block", children: [
|
|
1384
|
+
/* @__PURE__ */ jsx8("span", { className: "text-sm font-medium text-gray-700", children: "New Name" }),
|
|
1385
|
+
/* @__PURE__ */ jsx8(
|
|
1386
|
+
"input",
|
|
1387
|
+
{
|
|
1388
|
+
type: "text",
|
|
1389
|
+
value: name,
|
|
1390
|
+
onChange: (e) => setName(e.target.value),
|
|
1391
|
+
placeholder: "Enter new name",
|
|
1392
|
+
className: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
|
|
1393
|
+
autoFocus: true,
|
|
1394
|
+
disabled: isLoading
|
|
1395
|
+
}
|
|
1396
|
+
)
|
|
1397
|
+
] }),
|
|
1398
|
+
error && /* @__PURE__ */ jsx8("p", { className: "mt-2 text-sm text-red-600", children: error })
|
|
1399
|
+
] }),
|
|
1400
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex justify-end gap-2 p-4 border-t bg-gray-50 rounded-b-lg", children: [
|
|
1401
|
+
/* @__PURE__ */ jsx8(
|
|
1402
|
+
"button",
|
|
1403
|
+
{
|
|
1404
|
+
type: "button",
|
|
1405
|
+
onClick: onClose,
|
|
1406
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-md transition-colors",
|
|
1407
|
+
disabled: isLoading,
|
|
1408
|
+
children: "Cancel"
|
|
1409
|
+
}
|
|
1410
|
+
),
|
|
1411
|
+
/* @__PURE__ */ jsxs8(
|
|
1412
|
+
"button",
|
|
1413
|
+
{
|
|
1414
|
+
type: "submit",
|
|
1415
|
+
className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2",
|
|
1416
|
+
disabled: isLoading || !name.trim(),
|
|
1417
|
+
children: [
|
|
1418
|
+
isLoading && /* @__PURE__ */ jsx8(LoaderIcon, { size: 16 }),
|
|
1419
|
+
"Rename"
|
|
1420
|
+
]
|
|
1421
|
+
}
|
|
1422
|
+
)
|
|
1423
|
+
] })
|
|
1424
|
+
] })
|
|
1425
|
+
] })
|
|
1426
|
+
] });
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// src/ui/components/dialogs/DeleteConfirmDialog.tsx
|
|
1430
|
+
import { useState as useState4, useCallback as useCallback5 } from "react";
|
|
1431
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1432
|
+
function DeleteConfirmDialog({
|
|
1433
|
+
isOpen,
|
|
1434
|
+
item,
|
|
1435
|
+
onClose,
|
|
1436
|
+
onConfirm
|
|
1437
|
+
}) {
|
|
1438
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
1439
|
+
const [error, setError] = useState4(null);
|
|
1440
|
+
const handleConfirm = useCallback5(async () => {
|
|
1441
|
+
setIsLoading(true);
|
|
1442
|
+
setError(null);
|
|
1443
|
+
try {
|
|
1444
|
+
await onConfirm();
|
|
1445
|
+
onClose();
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
setError(err.message);
|
|
1448
|
+
} finally {
|
|
1449
|
+
setIsLoading(false);
|
|
1450
|
+
}
|
|
1451
|
+
}, [onConfirm, onClose]);
|
|
1452
|
+
if (!isOpen || !item) return null;
|
|
1453
|
+
return /* @__PURE__ */ jsxs9("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1454
|
+
/* @__PURE__ */ jsx9(
|
|
1455
|
+
"div",
|
|
1456
|
+
{
|
|
1457
|
+
className: "absolute inset-0 bg-black/50",
|
|
1458
|
+
onClick: onClose
|
|
1459
|
+
}
|
|
1460
|
+
),
|
|
1461
|
+
/* @__PURE__ */ jsxs9("div", { className: "relative bg-white rounded-lg shadow-xl w-full max-w-md mx-4", children: [
|
|
1462
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between p-4 border-b", children: [
|
|
1463
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
|
|
1464
|
+
/* @__PURE__ */ jsx9(TrashIcon, { size: 20, className: "text-red-500" }),
|
|
1465
|
+
/* @__PURE__ */ jsxs9("h2", { className: "text-lg font-medium", children: [
|
|
1466
|
+
"Delete ",
|
|
1467
|
+
item.isDirectory ? "Folder" : "File"
|
|
1468
|
+
] })
|
|
1469
|
+
] }),
|
|
1470
|
+
/* @__PURE__ */ jsx9(
|
|
1471
|
+
"button",
|
|
1472
|
+
{
|
|
1473
|
+
onClick: onClose,
|
|
1474
|
+
className: "p-1 hover:bg-gray-100 rounded transition-colors",
|
|
1475
|
+
children: /* @__PURE__ */ jsx9(XIcon, { size: 20 })
|
|
1476
|
+
}
|
|
1477
|
+
)
|
|
1478
|
+
] }),
|
|
1479
|
+
/* @__PURE__ */ jsxs9("div", { className: "p-4", children: [
|
|
1480
|
+
/* @__PURE__ */ jsxs9("p", { className: "text-gray-700", children: [
|
|
1481
|
+
"Are you sure you want to delete",
|
|
1482
|
+
" ",
|
|
1483
|
+
/* @__PURE__ */ jsx9("span", { className: "font-medium", children: item.name }),
|
|
1484
|
+
"?"
|
|
1485
|
+
] }),
|
|
1486
|
+
item.isDirectory && /* @__PURE__ */ jsx9("p", { className: "mt-2 text-sm text-amber-600 bg-amber-50 p-3 rounded", children: "Warning: This folder and all its contents will be permanently deleted." }),
|
|
1487
|
+
error && /* @__PURE__ */ jsx9("p", { className: "mt-2 text-sm text-red-600", children: error })
|
|
1488
|
+
] }),
|
|
1489
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex justify-end gap-2 p-4 border-t bg-gray-50 rounded-b-lg", children: [
|
|
1490
|
+
/* @__PURE__ */ jsx9(
|
|
1491
|
+
"button",
|
|
1492
|
+
{
|
|
1493
|
+
type: "button",
|
|
1494
|
+
onClick: onClose,
|
|
1495
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-md transition-colors",
|
|
1496
|
+
disabled: isLoading,
|
|
1497
|
+
children: "Cancel"
|
|
1498
|
+
}
|
|
1499
|
+
),
|
|
1500
|
+
/* @__PURE__ */ jsxs9(
|
|
1501
|
+
"button",
|
|
1502
|
+
{
|
|
1503
|
+
type: "button",
|
|
1504
|
+
onClick: handleConfirm,
|
|
1505
|
+
className: "px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2",
|
|
1506
|
+
disabled: isLoading,
|
|
1507
|
+
children: [
|
|
1508
|
+
isLoading && /* @__PURE__ */ jsx9(LoaderIcon, { size: 16 }),
|
|
1509
|
+
"Delete"
|
|
1510
|
+
]
|
|
1511
|
+
}
|
|
1512
|
+
)
|
|
1513
|
+
] })
|
|
1514
|
+
] })
|
|
1515
|
+
] });
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// src/ui/components/dialogs/UploadDialog.tsx
|
|
1519
|
+
import { useState as useState5, useCallback as useCallback6, useRef as useRef2 } from "react";
|
|
1520
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1521
|
+
function UploadDialog({
|
|
1522
|
+
isOpen,
|
|
1523
|
+
currentPath,
|
|
1524
|
+
onClose,
|
|
1525
|
+
onUpload
|
|
1526
|
+
}) {
|
|
1527
|
+
const [selectedFiles, setSelectedFiles] = useState5([]);
|
|
1528
|
+
const [isDragOver, setIsDragOver] = useState5(false);
|
|
1529
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
1530
|
+
const [error, setError] = useState5(null);
|
|
1531
|
+
const fileInputRef = useRef2(null);
|
|
1532
|
+
const handleFileSelect = useCallback6((files) => {
|
|
1533
|
+
if (files) {
|
|
1534
|
+
setSelectedFiles((prev) => [...prev, ...Array.from(files)]);
|
|
1535
|
+
setError(null);
|
|
1536
|
+
}
|
|
1537
|
+
}, []);
|
|
1538
|
+
const handleDragOver = useCallback6((e) => {
|
|
1539
|
+
e.preventDefault();
|
|
1540
|
+
setIsDragOver(true);
|
|
1541
|
+
}, []);
|
|
1542
|
+
const handleDragLeave = useCallback6((e) => {
|
|
1543
|
+
e.preventDefault();
|
|
1544
|
+
setIsDragOver(false);
|
|
1545
|
+
}, []);
|
|
1546
|
+
const handleDrop = useCallback6((e) => {
|
|
1547
|
+
e.preventDefault();
|
|
1548
|
+
setIsDragOver(false);
|
|
1549
|
+
handleFileSelect(e.dataTransfer.files);
|
|
1550
|
+
}, [handleFileSelect]);
|
|
1551
|
+
const handleRemoveFile = useCallback6((index) => {
|
|
1552
|
+
setSelectedFiles((prev) => prev.filter((_, i) => i !== index));
|
|
1553
|
+
}, []);
|
|
1554
|
+
const handleUpload = useCallback6(async () => {
|
|
1555
|
+
if (selectedFiles.length === 0) {
|
|
1556
|
+
setError("Please select files to upload");
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
setIsLoading(true);
|
|
1560
|
+
setError(null);
|
|
1561
|
+
try {
|
|
1562
|
+
const dt = new DataTransfer();
|
|
1563
|
+
selectedFiles.forEach((file) => dt.items.add(file));
|
|
1564
|
+
await onUpload(dt.files);
|
|
1565
|
+
setSelectedFiles([]);
|
|
1566
|
+
onClose();
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
setError(err.message);
|
|
1569
|
+
} finally {
|
|
1570
|
+
setIsLoading(false);
|
|
1571
|
+
}
|
|
1572
|
+
}, [selectedFiles, onUpload, onClose]);
|
|
1573
|
+
const handleClose = useCallback6(() => {
|
|
1574
|
+
setSelectedFiles([]);
|
|
1575
|
+
setError(null);
|
|
1576
|
+
onClose();
|
|
1577
|
+
}, [onClose]);
|
|
1578
|
+
if (!isOpen) return null;
|
|
1579
|
+
const totalSize = selectedFiles.reduce((sum, file) => sum + file.size, 0);
|
|
1580
|
+
return /* @__PURE__ */ jsxs10("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1581
|
+
/* @__PURE__ */ jsx10(
|
|
1582
|
+
"div",
|
|
1583
|
+
{
|
|
1584
|
+
className: "absolute inset-0 bg-black/50",
|
|
1585
|
+
onClick: handleClose
|
|
1586
|
+
}
|
|
1587
|
+
),
|
|
1588
|
+
/* @__PURE__ */ jsxs10("div", { className: "relative bg-white rounded-lg shadow-xl w-full max-w-lg mx-4", children: [
|
|
1589
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between p-4 border-b", children: [
|
|
1590
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
1591
|
+
/* @__PURE__ */ jsx10(UploadIcon, { size: 20, className: "text-blue-500" }),
|
|
1592
|
+
/* @__PURE__ */ jsx10("h2", { className: "text-lg font-medium", children: "Upload Files" })
|
|
1593
|
+
] }),
|
|
1594
|
+
/* @__PURE__ */ jsx10(
|
|
1595
|
+
"button",
|
|
1596
|
+
{
|
|
1597
|
+
onClick: handleClose,
|
|
1598
|
+
className: "p-1 hover:bg-gray-100 rounded transition-colors",
|
|
1599
|
+
children: /* @__PURE__ */ jsx10(XIcon, { size: 20 })
|
|
1600
|
+
}
|
|
1601
|
+
)
|
|
1602
|
+
] }),
|
|
1603
|
+
/* @__PURE__ */ jsxs10("div", { className: "p-4", children: [
|
|
1604
|
+
/* @__PURE__ */ jsxs10("div", { className: "text-sm text-gray-500 mb-4", children: [
|
|
1605
|
+
"Uploading to: ",
|
|
1606
|
+
/* @__PURE__ */ jsx10("span", { className: "font-medium", children: currentPath })
|
|
1607
|
+
] }),
|
|
1608
|
+
/* @__PURE__ */ jsxs10(
|
|
1609
|
+
"div",
|
|
1610
|
+
{
|
|
1611
|
+
className: `
|
|
1612
|
+
border-2 border-dashed rounded-lg p-8 text-center transition-colors
|
|
1613
|
+
${isDragOver ? "border-blue-500 bg-blue-50" : "border-gray-300"}
|
|
1614
|
+
`,
|
|
1615
|
+
onDragOver: handleDragOver,
|
|
1616
|
+
onDragLeave: handleDragLeave,
|
|
1617
|
+
onDrop: handleDrop,
|
|
1618
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1619
|
+
children: [
|
|
1620
|
+
/* @__PURE__ */ jsx10(
|
|
1621
|
+
"input",
|
|
1622
|
+
{
|
|
1623
|
+
ref: fileInputRef,
|
|
1624
|
+
type: "file",
|
|
1625
|
+
multiple: true,
|
|
1626
|
+
onChange: (e) => handleFileSelect(e.target.files),
|
|
1627
|
+
className: "hidden"
|
|
1628
|
+
}
|
|
1629
|
+
),
|
|
1630
|
+
/* @__PURE__ */ jsx10(UploadIcon, { size: 40, className: "mx-auto text-gray-400 mb-4" }),
|
|
1631
|
+
/* @__PURE__ */ jsxs10("p", { className: "text-gray-600", children: [
|
|
1632
|
+
"Drag and drop files here, or",
|
|
1633
|
+
" ",
|
|
1634
|
+
/* @__PURE__ */ jsx10("span", { className: "text-blue-500 cursor-pointer", children: "browse" })
|
|
1635
|
+
] })
|
|
1636
|
+
]
|
|
1637
|
+
}
|
|
1638
|
+
),
|
|
1639
|
+
selectedFiles.length > 0 && /* @__PURE__ */ jsxs10("div", { className: "mt-4", children: [
|
|
1640
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between mb-2", children: [
|
|
1641
|
+
/* @__PURE__ */ jsxs10("span", { className: "text-sm font-medium text-gray-700", children: [
|
|
1642
|
+
selectedFiles.length,
|
|
1643
|
+
" file(s) selected"
|
|
1644
|
+
] }),
|
|
1645
|
+
/* @__PURE__ */ jsxs10("span", { className: "text-sm text-gray-500", children: [
|
|
1646
|
+
"Total: ",
|
|
1647
|
+
formatBytes(totalSize)
|
|
1648
|
+
] })
|
|
1649
|
+
] }),
|
|
1650
|
+
/* @__PURE__ */ jsx10("div", { className: "max-h-40 overflow-auto space-y-2", children: selectedFiles.map((file, index) => /* @__PURE__ */ jsxs10(
|
|
1651
|
+
"div",
|
|
1652
|
+
{
|
|
1653
|
+
className: "flex items-center justify-between p-2 bg-gray-50 rounded",
|
|
1654
|
+
children: [
|
|
1655
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
1656
|
+
/* @__PURE__ */ jsx10(FileIcon, { size: 16, className: "text-gray-400 flex-shrink-0" }),
|
|
1657
|
+
/* @__PURE__ */ jsx10("span", { className: "text-sm truncate", children: file.name }),
|
|
1658
|
+
/* @__PURE__ */ jsx10("span", { className: "text-xs text-gray-400 flex-shrink-0", children: formatBytes(file.size) })
|
|
1659
|
+
] }),
|
|
1660
|
+
/* @__PURE__ */ jsx10(
|
|
1661
|
+
"button",
|
|
1662
|
+
{
|
|
1663
|
+
onClick: () => handleRemoveFile(index),
|
|
1664
|
+
className: "p-1 hover:bg-gray-200 rounded",
|
|
1665
|
+
children: /* @__PURE__ */ jsx10(XIcon, { size: 14 })
|
|
1666
|
+
}
|
|
1667
|
+
)
|
|
1668
|
+
]
|
|
1669
|
+
},
|
|
1670
|
+
index
|
|
1671
|
+
)) })
|
|
1672
|
+
] }),
|
|
1673
|
+
error && /* @__PURE__ */ jsx10("p", { className: "mt-2 text-sm text-red-600", children: error })
|
|
1674
|
+
] }),
|
|
1675
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex justify-end gap-2 p-4 border-t bg-gray-50 rounded-b-lg", children: [
|
|
1676
|
+
/* @__PURE__ */ jsx10(
|
|
1677
|
+
"button",
|
|
1678
|
+
{
|
|
1679
|
+
type: "button",
|
|
1680
|
+
onClick: handleClose,
|
|
1681
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-md transition-colors",
|
|
1682
|
+
disabled: isLoading,
|
|
1683
|
+
children: "Cancel"
|
|
1684
|
+
}
|
|
1685
|
+
),
|
|
1686
|
+
/* @__PURE__ */ jsxs10(
|
|
1687
|
+
"button",
|
|
1688
|
+
{
|
|
1689
|
+
type: "button",
|
|
1690
|
+
onClick: handleUpload,
|
|
1691
|
+
className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2",
|
|
1692
|
+
disabled: isLoading || selectedFiles.length === 0,
|
|
1693
|
+
children: [
|
|
1694
|
+
isLoading && /* @__PURE__ */ jsx10(LoaderIcon, { size: 16 }),
|
|
1695
|
+
"Upload ",
|
|
1696
|
+
selectedFiles.length > 0 && `(${selectedFiles.length})`
|
|
1697
|
+
]
|
|
1698
|
+
}
|
|
1699
|
+
)
|
|
1700
|
+
] })
|
|
1701
|
+
] })
|
|
1702
|
+
] });
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// src/ui/components/FileBrowser.tsx
|
|
1706
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1707
|
+
function FileBrowser({
|
|
1708
|
+
api,
|
|
1709
|
+
initialPath = "/",
|
|
1710
|
+
showPreview = true,
|
|
1711
|
+
showTree = true,
|
|
1712
|
+
viewMode = "grid",
|
|
1713
|
+
className = "",
|
|
1714
|
+
treeWidth = 250,
|
|
1715
|
+
previewHeight = 300,
|
|
1716
|
+
onError,
|
|
1717
|
+
onNavigate,
|
|
1718
|
+
onSelect
|
|
1719
|
+
}) {
|
|
1720
|
+
const [currentPath, setCurrentPath] = useState6(initialPath);
|
|
1721
|
+
const [files, setFiles] = useState6([]);
|
|
1722
|
+
const [tree, setTree] = useState6([]);
|
|
1723
|
+
const [selectedItem, setSelectedItem] = useState6(null);
|
|
1724
|
+
const [isLoading, setIsLoading] = useState6(false);
|
|
1725
|
+
const [createFolderOpen, setCreateFolderOpen] = useState6(false);
|
|
1726
|
+
const [renameOpen, setRenameOpen] = useState6(false);
|
|
1727
|
+
const [deleteOpen, setDeleteOpen] = useState6(false);
|
|
1728
|
+
const [uploadOpen, setUploadOpen] = useState6(false);
|
|
1729
|
+
const loadDirectory = useCallback7(async (path) => {
|
|
1730
|
+
setIsLoading(true);
|
|
1731
|
+
try {
|
|
1732
|
+
const result = await api.listDirectory(path);
|
|
1733
|
+
if (result.success && result.data) {
|
|
1734
|
+
setFiles(result.data);
|
|
1735
|
+
setCurrentPath(path);
|
|
1736
|
+
setSelectedItem(null);
|
|
1737
|
+
onNavigate?.(path);
|
|
1738
|
+
} else {
|
|
1739
|
+
onError?.(result.error || "Failed to load directory");
|
|
1740
|
+
}
|
|
1741
|
+
} finally {
|
|
1742
|
+
setIsLoading(false);
|
|
1743
|
+
}
|
|
1744
|
+
}, [api, onError, onNavigate]);
|
|
1745
|
+
const loadTree = useCallback7(async () => {
|
|
1746
|
+
const result = await api.getFolderTree("/", 3);
|
|
1747
|
+
if (result.success && result.data) {
|
|
1748
|
+
setTree(result.data);
|
|
1749
|
+
}
|
|
1750
|
+
}, [api]);
|
|
1751
|
+
useEffect4(() => {
|
|
1752
|
+
loadDirectory(initialPath);
|
|
1753
|
+
if (showTree) {
|
|
1754
|
+
loadTree();
|
|
1755
|
+
}
|
|
1756
|
+
}, [initialPath, loadDirectory, loadTree, showTree]);
|
|
1757
|
+
const handleNavigate = useCallback7((path) => {
|
|
1758
|
+
loadDirectory(path);
|
|
1759
|
+
}, [loadDirectory]);
|
|
1760
|
+
const handleRefresh = useCallback7(async () => {
|
|
1761
|
+
await loadDirectory(currentPath);
|
|
1762
|
+
if (showTree) {
|
|
1763
|
+
await loadTree();
|
|
1764
|
+
}
|
|
1765
|
+
}, [currentPath, loadDirectory, loadTree, showTree]);
|
|
1766
|
+
const handleSelect = useCallback7((item) => {
|
|
1767
|
+
setSelectedItem(item);
|
|
1768
|
+
onSelect?.(item);
|
|
1769
|
+
}, [onSelect]);
|
|
1770
|
+
const handleOpen = useCallback7((item) => {
|
|
1771
|
+
if (item.isDirectory) {
|
|
1772
|
+
loadDirectory(item.path);
|
|
1773
|
+
} else {
|
|
1774
|
+
handleDownload();
|
|
1775
|
+
}
|
|
1776
|
+
}, [loadDirectory]);
|
|
1777
|
+
const handleTreeToggle = useCallback7((path) => {
|
|
1778
|
+
setTree((prev) => toggleTreeNode(prev, path));
|
|
1779
|
+
}, []);
|
|
1780
|
+
const handleTreeExpand = useCallback7(async (path) => {
|
|
1781
|
+
const result = await api.listDirectory(path);
|
|
1782
|
+
if (result.success && result.data) {
|
|
1783
|
+
const folders = result.data.filter((item) => item.isDirectory);
|
|
1784
|
+
const children = folders.map((folder) => ({
|
|
1785
|
+
id: folder.id,
|
|
1786
|
+
name: folder.name,
|
|
1787
|
+
path: folder.path,
|
|
1788
|
+
children: []
|
|
1789
|
+
}));
|
|
1790
|
+
setTree((prev) => updateTreeNode(prev, path, children, true));
|
|
1791
|
+
}
|
|
1792
|
+
}, [api]);
|
|
1793
|
+
const handleCreateFolder = useCallback7(async (name) => {
|
|
1794
|
+
const newPath = currentPath === "/" ? `/${name}` : `${currentPath}/${name}`;
|
|
1795
|
+
const result = await api.createDirectory(newPath);
|
|
1796
|
+
if (result.success && result.data) {
|
|
1797
|
+
setFiles((prev) => sortItems([...prev, result.data]));
|
|
1798
|
+
await loadTree();
|
|
1799
|
+
} else {
|
|
1800
|
+
throw new Error(result.error || "Failed to create folder");
|
|
1801
|
+
}
|
|
1802
|
+
}, [api, currentPath, loadTree]);
|
|
1803
|
+
const handleUpload = useCallback7(async (fileList) => {
|
|
1804
|
+
for (let i = 0; i < fileList.length; i++) {
|
|
1805
|
+
const file = fileList[i];
|
|
1806
|
+
const remotePath = currentPath === "/" ? `/${file.name}` : `${currentPath}/${file.name}`;
|
|
1807
|
+
const result = await api.uploadFile(file, remotePath);
|
|
1808
|
+
if (result.success && result.data) {
|
|
1809
|
+
setFiles((prev) => sortItems([...prev, result.data]));
|
|
1810
|
+
} else {
|
|
1811
|
+
onError?.(result.error || `Failed to upload ${file.name}`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}, [api, currentPath, onError]);
|
|
1815
|
+
const handleDownload = useCallback7(async () => {
|
|
1816
|
+
if (!selectedItem || selectedItem.isDirectory) return;
|
|
1817
|
+
const result = await api.downloadFile(selectedItem.path);
|
|
1818
|
+
if (result.success && result.data) {
|
|
1819
|
+
const blob = result.data;
|
|
1820
|
+
const url = URL.createObjectURL(blob);
|
|
1821
|
+
const a = document.createElement("a");
|
|
1822
|
+
a.href = url;
|
|
1823
|
+
a.download = selectedItem.name;
|
|
1824
|
+
document.body.appendChild(a);
|
|
1825
|
+
a.click();
|
|
1826
|
+
document.body.removeChild(a);
|
|
1827
|
+
URL.revokeObjectURL(url);
|
|
1828
|
+
} else {
|
|
1829
|
+
onError?.(result.error || "Failed to download file");
|
|
1830
|
+
}
|
|
1831
|
+
}, [api, selectedItem, onError]);
|
|
1832
|
+
const handleDelete = useCallback7(async () => {
|
|
1833
|
+
if (!selectedItem) return;
|
|
1834
|
+
let result;
|
|
1835
|
+
if (selectedItem.isDirectory) {
|
|
1836
|
+
result = await api.removeDirectory(selectedItem.path, true);
|
|
1837
|
+
} else {
|
|
1838
|
+
result = await api.deleteFile(selectedItem.path);
|
|
1839
|
+
}
|
|
1840
|
+
if (result.success) {
|
|
1841
|
+
setFiles((prev) => prev.filter((f) => f.path !== selectedItem.path));
|
|
1842
|
+
setSelectedItem(null);
|
|
1843
|
+
if (selectedItem.isDirectory) {
|
|
1844
|
+
await loadTree();
|
|
1845
|
+
}
|
|
1846
|
+
} else {
|
|
1847
|
+
throw new Error(result.error || "Failed to delete");
|
|
1848
|
+
}
|
|
1849
|
+
}, [api, selectedItem, loadTree]);
|
|
1850
|
+
const handleRename = useCallback7(async (newName) => {
|
|
1851
|
+
if (!selectedItem) return;
|
|
1852
|
+
let result;
|
|
1853
|
+
if (selectedItem.isDirectory) {
|
|
1854
|
+
result = await api.renameFolder(selectedItem.path, newName);
|
|
1855
|
+
} else {
|
|
1856
|
+
result = await api.renameFile(selectedItem.path, newName);
|
|
1857
|
+
}
|
|
1858
|
+
if (result.success && result.data) {
|
|
1859
|
+
setFiles((prev) => sortItems(prev.filter((f) => f.path !== selectedItem.path).concat(result.data)));
|
|
1860
|
+
setSelectedItem(result.data);
|
|
1861
|
+
if (selectedItem.isDirectory) {
|
|
1862
|
+
await loadTree();
|
|
1863
|
+
}
|
|
1864
|
+
} else {
|
|
1865
|
+
throw new Error(result.error || "Failed to rename");
|
|
1866
|
+
}
|
|
1867
|
+
}, [api, selectedItem, loadTree]);
|
|
1868
|
+
return /* @__PURE__ */ jsxs11("div", { className: `flex flex-col h-full bg-white border rounded-lg overflow-hidden ${className}`, children: [
|
|
1869
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between p-3 border-b bg-gray-50", children: [
|
|
1870
|
+
/* @__PURE__ */ jsx11(
|
|
1871
|
+
PathBreadcrumb,
|
|
1872
|
+
{
|
|
1873
|
+
currentPath,
|
|
1874
|
+
onNavigate: handleNavigate
|
|
1875
|
+
}
|
|
1876
|
+
),
|
|
1877
|
+
/* @__PURE__ */ jsx11(
|
|
1878
|
+
FileActions,
|
|
1879
|
+
{
|
|
1880
|
+
selectedItem,
|
|
1881
|
+
onCreateFolder: () => setCreateFolderOpen(true),
|
|
1882
|
+
onUpload: (files2) => handleUpload(files2),
|
|
1883
|
+
onDownload: handleDownload,
|
|
1884
|
+
onDelete: () => setDeleteOpen(true),
|
|
1885
|
+
onRename: () => setRenameOpen(true),
|
|
1886
|
+
onRefresh: handleRefresh,
|
|
1887
|
+
isLoading
|
|
1888
|
+
}
|
|
1889
|
+
)
|
|
1890
|
+
] }),
|
|
1891
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-1 min-h-0", children: [
|
|
1892
|
+
showTree && /* @__PURE__ */ jsx11(
|
|
1893
|
+
"div",
|
|
1894
|
+
{
|
|
1895
|
+
className: "border-r overflow-auto",
|
|
1896
|
+
style: { width: treeWidth, minWidth: treeWidth },
|
|
1897
|
+
children: /* @__PURE__ */ jsx11(
|
|
1898
|
+
FolderTree,
|
|
1899
|
+
{
|
|
1900
|
+
tree,
|
|
1901
|
+
currentPath,
|
|
1902
|
+
onSelect: handleNavigate,
|
|
1903
|
+
onExpand: handleTreeExpand,
|
|
1904
|
+
onToggle: handleTreeToggle,
|
|
1905
|
+
className: "p-2"
|
|
1906
|
+
}
|
|
1907
|
+
)
|
|
1908
|
+
}
|
|
1909
|
+
),
|
|
1910
|
+
/* @__PURE__ */ jsx11("div", { className: "flex-1 min-w-0 overflow-auto", children: /* @__PURE__ */ jsx11(
|
|
1911
|
+
FileList,
|
|
1912
|
+
{
|
|
1913
|
+
files,
|
|
1914
|
+
selectedItem,
|
|
1915
|
+
isLoading,
|
|
1916
|
+
onSelect: handleSelect,
|
|
1917
|
+
onOpen: handleOpen,
|
|
1918
|
+
viewMode,
|
|
1919
|
+
className: "h-full"
|
|
1920
|
+
}
|
|
1921
|
+
) })
|
|
1922
|
+
] }),
|
|
1923
|
+
showPreview && /* @__PURE__ */ jsx11(
|
|
1924
|
+
"div",
|
|
1925
|
+
{
|
|
1926
|
+
className: "border-t",
|
|
1927
|
+
style: { height: previewHeight, minHeight: previewHeight },
|
|
1928
|
+
children: /* @__PURE__ */ jsx11(
|
|
1929
|
+
FilePreview,
|
|
1930
|
+
{
|
|
1931
|
+
item: selectedItem,
|
|
1932
|
+
getPreviewUrl: api.getPreviewUrl,
|
|
1933
|
+
getFileContent: api.getFileContent,
|
|
1934
|
+
className: "h-full"
|
|
1935
|
+
}
|
|
1936
|
+
)
|
|
1937
|
+
}
|
|
1938
|
+
),
|
|
1939
|
+
/* @__PURE__ */ jsx11(
|
|
1940
|
+
CreateFolderDialog,
|
|
1941
|
+
{
|
|
1942
|
+
isOpen: createFolderOpen,
|
|
1943
|
+
currentPath,
|
|
1944
|
+
onClose: () => setCreateFolderOpen(false),
|
|
1945
|
+
onSubmit: handleCreateFolder
|
|
1946
|
+
}
|
|
1947
|
+
),
|
|
1948
|
+
/* @__PURE__ */ jsx11(
|
|
1949
|
+
RenameDialog,
|
|
1950
|
+
{
|
|
1951
|
+
isOpen: renameOpen,
|
|
1952
|
+
item: selectedItem,
|
|
1953
|
+
onClose: () => setRenameOpen(false),
|
|
1954
|
+
onSubmit: handleRename
|
|
1955
|
+
}
|
|
1956
|
+
),
|
|
1957
|
+
/* @__PURE__ */ jsx11(
|
|
1958
|
+
DeleteConfirmDialog,
|
|
1959
|
+
{
|
|
1960
|
+
isOpen: deleteOpen,
|
|
1961
|
+
item: selectedItem,
|
|
1962
|
+
onClose: () => setDeleteOpen(false),
|
|
1963
|
+
onConfirm: handleDelete
|
|
1964
|
+
}
|
|
1965
|
+
),
|
|
1966
|
+
/* @__PURE__ */ jsx11(
|
|
1967
|
+
UploadDialog,
|
|
1968
|
+
{
|
|
1969
|
+
isOpen: uploadOpen,
|
|
1970
|
+
currentPath,
|
|
1971
|
+
onClose: () => setUploadOpen(false),
|
|
1972
|
+
onUpload: handleUpload
|
|
1973
|
+
}
|
|
1974
|
+
)
|
|
1975
|
+
] });
|
|
1976
|
+
}
|
|
1977
|
+
function toggleTreeNode(nodes, path) {
|
|
1978
|
+
return nodes.map((node) => {
|
|
1979
|
+
if (node.path === path) {
|
|
1980
|
+
return { ...node, isExpanded: !node.isExpanded };
|
|
1981
|
+
}
|
|
1982
|
+
if (node.children.length > 0) {
|
|
1983
|
+
return { ...node, children: toggleTreeNode(node.children, path) };
|
|
1984
|
+
}
|
|
1985
|
+
return node;
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
function updateTreeNode(nodes, path, children, isExpanded) {
|
|
1989
|
+
return nodes.map((node) => {
|
|
1990
|
+
if (node.path === path) {
|
|
1991
|
+
return { ...node, children, isExpanded };
|
|
1992
|
+
}
|
|
1993
|
+
if (node.children.length > 0) {
|
|
1994
|
+
return { ...node, children: updateTreeNode(node.children, path, children, isExpanded) };
|
|
1995
|
+
}
|
|
1996
|
+
return node;
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
function sortItems(items) {
|
|
2000
|
+
return [...items].sort((a, b) => {
|
|
2001
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
2002
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
2003
|
+
return a.name.localeCompare(b.name);
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// src/ui/hooks/useFileBrowser.ts
|
|
2008
|
+
import { useCallback as useCallback8, useEffect as useEffect5, useState as useState7 } from "react";
|
|
2009
|
+
function useFileBrowser(options) {
|
|
2010
|
+
const { api, initialPath = "/", autoLoad = true } = options;
|
|
2011
|
+
const [state, setState] = useState7({
|
|
2012
|
+
currentPath: initialPath,
|
|
2013
|
+
files: [],
|
|
2014
|
+
tree: [],
|
|
2015
|
+
selectedItem: null,
|
|
2016
|
+
isLoading: false,
|
|
2017
|
+
error: null
|
|
2018
|
+
});
|
|
2019
|
+
const loadDirectory = useCallback8(async (path) => {
|
|
2020
|
+
setState((prev) => ({ ...prev, isLoading: true, error: null }));
|
|
2021
|
+
const result = await api.listDirectory(path);
|
|
2022
|
+
if (result.success && result.data) {
|
|
2023
|
+
setState((prev) => ({
|
|
2024
|
+
...prev,
|
|
2025
|
+
files: result.data,
|
|
2026
|
+
currentPath: path,
|
|
2027
|
+
isLoading: false
|
|
2028
|
+
}));
|
|
2029
|
+
} else {
|
|
2030
|
+
setState((prev) => ({
|
|
2031
|
+
...prev,
|
|
2032
|
+
error: result.error || "Failed to load directory",
|
|
2033
|
+
isLoading: false
|
|
2034
|
+
}));
|
|
2035
|
+
}
|
|
2036
|
+
}, [api]);
|
|
2037
|
+
const loadTree = useCallback8(async (depth = 2) => {
|
|
2038
|
+
const result = await api.getFolderTree("/", depth);
|
|
2039
|
+
if (result.success && result.data) {
|
|
2040
|
+
setState((prev) => ({ ...prev, tree: result.data }));
|
|
2041
|
+
}
|
|
2042
|
+
}, [api]);
|
|
2043
|
+
const navigateTo = useCallback8(async (path) => {
|
|
2044
|
+
setState((prev) => ({ ...prev, selectedItem: null }));
|
|
2045
|
+
await loadDirectory(path);
|
|
2046
|
+
}, [loadDirectory]);
|
|
2047
|
+
const navigateUp = useCallback8(async () => {
|
|
2048
|
+
if (state.currentPath === "/") return;
|
|
2049
|
+
const parentPath = state.currentPath.split("/").slice(0, -1).join("/") || "/";
|
|
2050
|
+
await navigateTo(parentPath);
|
|
2051
|
+
}, [state.currentPath, navigateTo]);
|
|
2052
|
+
const refresh = useCallback8(async () => {
|
|
2053
|
+
await loadDirectory(state.currentPath);
|
|
2054
|
+
await loadTree();
|
|
2055
|
+
}, [loadDirectory, loadTree, state.currentPath]);
|
|
2056
|
+
const selectItem = useCallback8((item) => {
|
|
2057
|
+
setState((prev) => ({ ...prev, selectedItem: item }));
|
|
2058
|
+
}, []);
|
|
2059
|
+
const toggleTreeNode2 = useCallback8((path) => {
|
|
2060
|
+
setState((prev) => ({
|
|
2061
|
+
...prev,
|
|
2062
|
+
tree: toggleNode(prev.tree, path)
|
|
2063
|
+
}));
|
|
2064
|
+
}, []);
|
|
2065
|
+
const expandTreeNode = useCallback8(async (path) => {
|
|
2066
|
+
const result = await api.listDirectory(path);
|
|
2067
|
+
if (result.success && result.data) {
|
|
2068
|
+
const folders = result.data.filter((item) => item.isDirectory);
|
|
2069
|
+
const children = folders.map((folder) => ({
|
|
2070
|
+
id: folder.id,
|
|
2071
|
+
name: folder.name,
|
|
2072
|
+
path: folder.path,
|
|
2073
|
+
children: []
|
|
2074
|
+
}));
|
|
2075
|
+
setState((prev) => ({
|
|
2076
|
+
...prev,
|
|
2077
|
+
tree: updateTreeNode2(prev.tree, path, children, true)
|
|
2078
|
+
}));
|
|
2079
|
+
}
|
|
2080
|
+
}, [api]);
|
|
2081
|
+
const createFolder = useCallback8(async (name) => {
|
|
2082
|
+
const newPath = state.currentPath === "/" ? `/${name}` : `${state.currentPath}/${name}`;
|
|
2083
|
+
const result = await api.createDirectory(newPath);
|
|
2084
|
+
if (result.success && result.data) {
|
|
2085
|
+
setState((prev) => ({
|
|
2086
|
+
...prev,
|
|
2087
|
+
files: sortItems2([...prev.files, result.data])
|
|
2088
|
+
}));
|
|
2089
|
+
}
|
|
2090
|
+
return result;
|
|
2091
|
+
}, [api, state.currentPath]);
|
|
2092
|
+
const deleteItem = useCallback8(async (path) => {
|
|
2093
|
+
const item = state.files.find((f) => f.path === path);
|
|
2094
|
+
if (!item) {
|
|
2095
|
+
return { success: false, error: "Item not found" };
|
|
2096
|
+
}
|
|
2097
|
+
let result;
|
|
2098
|
+
if (item.isDirectory) {
|
|
2099
|
+
result = await api.removeDirectory(path, true);
|
|
2100
|
+
} else {
|
|
2101
|
+
result = await api.deleteFile(path);
|
|
2102
|
+
}
|
|
2103
|
+
if (result.success) {
|
|
2104
|
+
setState((prev) => ({
|
|
2105
|
+
...prev,
|
|
2106
|
+
files: prev.files.filter((f) => f.path !== path),
|
|
2107
|
+
selectedItem: prev.selectedItem?.path === path ? null : prev.selectedItem
|
|
2108
|
+
}));
|
|
2109
|
+
}
|
|
2110
|
+
return result;
|
|
2111
|
+
}, [api, state.files]);
|
|
2112
|
+
const deleteSelected = useCallback8(async () => {
|
|
2113
|
+
if (!state.selectedItem) {
|
|
2114
|
+
return { success: false, error: "No item selected" };
|
|
2115
|
+
}
|
|
2116
|
+
return deleteItem(state.selectedItem.path);
|
|
2117
|
+
}, [deleteItem, state.selectedItem]);
|
|
2118
|
+
const renameItem = useCallback8(async (path, newName, isDirectory) => {
|
|
2119
|
+
let result;
|
|
2120
|
+
if (isDirectory) {
|
|
2121
|
+
result = await api.renameFolder(path, newName);
|
|
2122
|
+
} else {
|
|
2123
|
+
result = await api.renameFile(path, newName);
|
|
2124
|
+
}
|
|
2125
|
+
if (result.success && result.data) {
|
|
2126
|
+
setState((prev) => ({
|
|
2127
|
+
...prev,
|
|
2128
|
+
files: sortItems2(prev.files.filter((f) => f.path !== path).concat(result.data)),
|
|
2129
|
+
selectedItem: prev.selectedItem?.path === path ? result.data : prev.selectedItem
|
|
2130
|
+
}));
|
|
2131
|
+
}
|
|
2132
|
+
return result;
|
|
2133
|
+
}, [api]);
|
|
2134
|
+
const renameSelected = useCallback8(async (newName) => {
|
|
2135
|
+
if (!state.selectedItem) {
|
|
2136
|
+
return { success: false, error: "No item selected" };
|
|
2137
|
+
}
|
|
2138
|
+
return renameItem(state.selectedItem.path, newName, state.selectedItem.isDirectory);
|
|
2139
|
+
}, [renameItem, state.selectedItem]);
|
|
2140
|
+
const uploadFiles = useCallback8(async (files) => {
|
|
2141
|
+
const results = [];
|
|
2142
|
+
for (let i = 0; i < files.length; i++) {
|
|
2143
|
+
const file = files[i];
|
|
2144
|
+
const remotePath = state.currentPath === "/" ? `/${file.name}` : `${state.currentPath}/${file.name}`;
|
|
2145
|
+
const result = await api.uploadFile(file, remotePath);
|
|
2146
|
+
results.push(result);
|
|
2147
|
+
if (result.success && result.data) {
|
|
2148
|
+
setState((prev) => ({
|
|
2149
|
+
...prev,
|
|
2150
|
+
files: sortItems2([...prev.files, result.data])
|
|
2151
|
+
}));
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
return results;
|
|
2155
|
+
}, [api, state.currentPath]);
|
|
2156
|
+
const downloadFile = useCallback8(async (path) => {
|
|
2157
|
+
const item = state.files.find((f) => f.path === path);
|
|
2158
|
+
if (!item || item.isDirectory) return;
|
|
2159
|
+
const result = await api.downloadFile(path);
|
|
2160
|
+
if (result.success && result.data) {
|
|
2161
|
+
const blob = result.data;
|
|
2162
|
+
const url = URL.createObjectURL(blob);
|
|
2163
|
+
const a = document.createElement("a");
|
|
2164
|
+
a.href = url;
|
|
2165
|
+
a.download = item.name;
|
|
2166
|
+
document.body.appendChild(a);
|
|
2167
|
+
a.click();
|
|
2168
|
+
document.body.removeChild(a);
|
|
2169
|
+
URL.revokeObjectURL(url);
|
|
2170
|
+
}
|
|
2171
|
+
}, [api, state.files]);
|
|
2172
|
+
const downloadSelected = useCallback8(async () => {
|
|
2173
|
+
if (!state.selectedItem || state.selectedItem.isDirectory) return;
|
|
2174
|
+
await downloadFile(state.selectedItem.path);
|
|
2175
|
+
}, [downloadFile, state.selectedItem]);
|
|
2176
|
+
const moveItem = useCallback8(async (sourcePath, destinationPath) => {
|
|
2177
|
+
const result = await api.moveItem(sourcePath, destinationPath);
|
|
2178
|
+
if (result.success) {
|
|
2179
|
+
setState((prev) => ({
|
|
2180
|
+
...prev,
|
|
2181
|
+
files: prev.files.filter((f) => f.path !== sourcePath),
|
|
2182
|
+
selectedItem: prev.selectedItem?.path === sourcePath ? null : prev.selectedItem
|
|
2183
|
+
}));
|
|
2184
|
+
}
|
|
2185
|
+
return result;
|
|
2186
|
+
}, [api]);
|
|
2187
|
+
useEffect5(() => {
|
|
2188
|
+
if (autoLoad) {
|
|
2189
|
+
loadDirectory(initialPath);
|
|
2190
|
+
loadTree();
|
|
2191
|
+
}
|
|
2192
|
+
}, [autoLoad, initialPath, loadDirectory, loadTree]);
|
|
2193
|
+
return {
|
|
2194
|
+
// State
|
|
2195
|
+
currentPath: state.currentPath,
|
|
2196
|
+
files: state.files,
|
|
2197
|
+
tree: state.tree,
|
|
2198
|
+
selectedItem: state.selectedItem,
|
|
2199
|
+
isLoading: state.isLoading,
|
|
2200
|
+
error: state.error,
|
|
2201
|
+
// Navigation
|
|
2202
|
+
navigateTo,
|
|
2203
|
+
navigateUp,
|
|
2204
|
+
refresh,
|
|
2205
|
+
// Selection
|
|
2206
|
+
selectItem,
|
|
2207
|
+
// Tree
|
|
2208
|
+
toggleTreeNode: toggleTreeNode2,
|
|
2209
|
+
expandTreeNode,
|
|
2210
|
+
loadTree,
|
|
2211
|
+
// Operations
|
|
2212
|
+
createFolder,
|
|
2213
|
+
deleteItem,
|
|
2214
|
+
deleteSelected,
|
|
2215
|
+
renameItem,
|
|
2216
|
+
renameSelected,
|
|
2217
|
+
uploadFiles,
|
|
2218
|
+
downloadFile,
|
|
2219
|
+
downloadSelected,
|
|
2220
|
+
moveItem
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
function toggleNode(nodes, path) {
|
|
2224
|
+
return nodes.map((node) => {
|
|
2225
|
+
if (node.path === path) {
|
|
2226
|
+
return { ...node, isExpanded: !node.isExpanded };
|
|
2227
|
+
}
|
|
2228
|
+
if (node.children.length > 0) {
|
|
2229
|
+
return { ...node, children: toggleNode(node.children, path) };
|
|
2230
|
+
}
|
|
2231
|
+
return node;
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
function updateTreeNode2(nodes, path, children, isExpanded) {
|
|
2235
|
+
return nodes.map((node) => {
|
|
2236
|
+
if (node.path === path) {
|
|
2237
|
+
return { ...node, children, isExpanded };
|
|
2238
|
+
}
|
|
2239
|
+
if (node.children.length > 0) {
|
|
2240
|
+
return { ...node, children: updateTreeNode2(node.children, path, children, isExpanded) };
|
|
2241
|
+
}
|
|
2242
|
+
return node;
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
function sortItems2(items) {
|
|
2246
|
+
return [...items].sort((a, b) => {
|
|
2247
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
2248
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
2249
|
+
return a.name.localeCompare(b.name);
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// src/ui/hooks/useFileOperations.ts
|
|
2254
|
+
import { useState as useState8, useCallback as useCallback9 } from "react";
|
|
2255
|
+
function useFileOperations() {
|
|
2256
|
+
const [state, setState] = useState8({
|
|
2257
|
+
isLoading: false,
|
|
2258
|
+
error: null,
|
|
2259
|
+
lastResult: null
|
|
2260
|
+
});
|
|
2261
|
+
const execute = useCallback9(async (operation) => {
|
|
2262
|
+
setState({ isLoading: true, error: null, lastResult: null });
|
|
2263
|
+
try {
|
|
2264
|
+
const result = await operation();
|
|
2265
|
+
setState({
|
|
2266
|
+
isLoading: false,
|
|
2267
|
+
error: result.success ? null : result.error || "Operation failed",
|
|
2268
|
+
lastResult: result
|
|
2269
|
+
});
|
|
2270
|
+
return result;
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
const errorMessage = error.message || "Unknown error";
|
|
2273
|
+
const result = { success: false, error: errorMessage };
|
|
2274
|
+
setState({
|
|
2275
|
+
isLoading: false,
|
|
2276
|
+
error: errorMessage,
|
|
2277
|
+
lastResult: result
|
|
2278
|
+
});
|
|
2279
|
+
return result;
|
|
2280
|
+
}
|
|
2281
|
+
}, []);
|
|
2282
|
+
const reset = useCallback9(() => {
|
|
2283
|
+
setState({ isLoading: false, error: null, lastResult: null });
|
|
2284
|
+
}, []);
|
|
2285
|
+
return { state, execute, reset };
|
|
2286
|
+
}
|
|
2287
|
+
function useMultiFileOperations() {
|
|
2288
|
+
const [state, setState] = useState8({
|
|
2289
|
+
activeOperations: /* @__PURE__ */ new Set(),
|
|
2290
|
+
errors: /* @__PURE__ */ new Map()
|
|
2291
|
+
});
|
|
2292
|
+
const isOperating = useCallback9((id) => {
|
|
2293
|
+
return state.activeOperations.has(id);
|
|
2294
|
+
}, [state.activeOperations]);
|
|
2295
|
+
const getError = useCallback9((id) => {
|
|
2296
|
+
return state.errors.get(id);
|
|
2297
|
+
}, [state.errors]);
|
|
2298
|
+
const execute = useCallback9(async (id, operation) => {
|
|
2299
|
+
setState((prev) => {
|
|
2300
|
+
const newActive = new Set(prev.activeOperations);
|
|
2301
|
+
newActive.add(id);
|
|
2302
|
+
const newErrors = new Map(prev.errors);
|
|
2303
|
+
newErrors.delete(id);
|
|
2304
|
+
return { activeOperations: newActive, errors: newErrors };
|
|
2305
|
+
});
|
|
2306
|
+
try {
|
|
2307
|
+
const result = await operation();
|
|
2308
|
+
setState((prev) => {
|
|
2309
|
+
const newActive = new Set(prev.activeOperations);
|
|
2310
|
+
newActive.delete(id);
|
|
2311
|
+
const newErrors = new Map(prev.errors);
|
|
2312
|
+
if (!result.success && result.error) {
|
|
2313
|
+
newErrors.set(id, result.error);
|
|
2314
|
+
}
|
|
2315
|
+
return { activeOperations: newActive, errors: newErrors };
|
|
2316
|
+
});
|
|
2317
|
+
return result;
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
const errorMessage = error.message || "Unknown error";
|
|
2320
|
+
setState((prev) => {
|
|
2321
|
+
const newActive = new Set(prev.activeOperations);
|
|
2322
|
+
newActive.delete(id);
|
|
2323
|
+
const newErrors = new Map(prev.errors);
|
|
2324
|
+
newErrors.set(id, errorMessage);
|
|
2325
|
+
return { activeOperations: newActive, errors: newErrors };
|
|
2326
|
+
});
|
|
2327
|
+
return { success: false, error: errorMessage };
|
|
2328
|
+
}
|
|
2329
|
+
}, []);
|
|
2330
|
+
const clear = useCallback9((id) => {
|
|
2331
|
+
setState((prev) => {
|
|
2332
|
+
const newErrors = new Map(prev.errors);
|
|
2333
|
+
newErrors.delete(id);
|
|
2334
|
+
return { ...prev, errors: newErrors };
|
|
2335
|
+
});
|
|
2336
|
+
}, []);
|
|
2337
|
+
const clearAll = useCallback9(() => {
|
|
2338
|
+
setState({ activeOperations: /* @__PURE__ */ new Set(), errors: /* @__PURE__ */ new Map() });
|
|
2339
|
+
}, []);
|
|
2340
|
+
return { state, isOperating, getError, execute, clear, clearAll };
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// src/ui/context/FileBrowserContext.tsx
|
|
2344
|
+
import { createContext, useContext, useReducer, useCallback as useCallback10 } from "react";
|
|
2345
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
2346
|
+
var initialState = {
|
|
2347
|
+
currentPath: "/",
|
|
2348
|
+
selectedItem: null,
|
|
2349
|
+
tree: [],
|
|
2350
|
+
files: [],
|
|
2351
|
+
isLoading: false,
|
|
2352
|
+
error: null
|
|
2353
|
+
};
|
|
2354
|
+
function fileBrowserReducer(state, action) {
|
|
2355
|
+
switch (action.type) {
|
|
2356
|
+
case "SET_LOADING":
|
|
2357
|
+
return { ...state, isLoading: action.payload };
|
|
2358
|
+
case "SET_ERROR":
|
|
2359
|
+
return { ...state, error: action.payload, isLoading: false };
|
|
2360
|
+
case "SET_CURRENT_PATH":
|
|
2361
|
+
return { ...state, currentPath: action.payload };
|
|
2362
|
+
case "SET_FILES":
|
|
2363
|
+
return { ...state, files: action.payload, isLoading: false };
|
|
2364
|
+
case "SET_TREE":
|
|
2365
|
+
return { ...state, tree: action.payload };
|
|
2366
|
+
case "SET_SELECTED_ITEM":
|
|
2367
|
+
return { ...state, selectedItem: action.payload };
|
|
2368
|
+
case "UPDATE_TREE_NODE": {
|
|
2369
|
+
const updateNode = (nodes, path, children, isExpanded) => {
|
|
2370
|
+
return nodes.map((node) => {
|
|
2371
|
+
if (node.path === path) {
|
|
2372
|
+
return { ...node, children, isExpanded };
|
|
2373
|
+
}
|
|
2374
|
+
if (node.children.length > 0) {
|
|
2375
|
+
return { ...node, children: updateNode(node.children, path, children, isExpanded) };
|
|
2376
|
+
}
|
|
2377
|
+
return node;
|
|
2378
|
+
});
|
|
2379
|
+
};
|
|
2380
|
+
return {
|
|
2381
|
+
...state,
|
|
2382
|
+
tree: updateNode(state.tree, action.payload.path, action.payload.children, action.payload.isExpanded)
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
case "TOGGLE_TREE_NODE": {
|
|
2386
|
+
const toggleNode2 = (nodes, path) => {
|
|
2387
|
+
return nodes.map((node) => {
|
|
2388
|
+
if (node.path === path) {
|
|
2389
|
+
return { ...node, isExpanded: !node.isExpanded };
|
|
2390
|
+
}
|
|
2391
|
+
if (node.children.length > 0) {
|
|
2392
|
+
return { ...node, children: toggleNode2(node.children, path) };
|
|
2393
|
+
}
|
|
2394
|
+
return node;
|
|
2395
|
+
});
|
|
2396
|
+
};
|
|
2397
|
+
return { ...state, tree: toggleNode2(state.tree, action.payload) };
|
|
2398
|
+
}
|
|
2399
|
+
case "ADD_ITEM":
|
|
2400
|
+
return {
|
|
2401
|
+
...state,
|
|
2402
|
+
files: [...state.files, action.payload].sort((a, b) => {
|
|
2403
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
2404
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
2405
|
+
return a.name.localeCompare(b.name);
|
|
2406
|
+
})
|
|
2407
|
+
};
|
|
2408
|
+
case "REMOVE_ITEM":
|
|
2409
|
+
return {
|
|
2410
|
+
...state,
|
|
2411
|
+
files: state.files.filter((f) => f.path !== action.payload),
|
|
2412
|
+
selectedItem: state.selectedItem?.path === action.payload ? null : state.selectedItem
|
|
2413
|
+
};
|
|
2414
|
+
case "UPDATE_ITEM":
|
|
2415
|
+
return {
|
|
2416
|
+
...state,
|
|
2417
|
+
files: state.files.map((f) => f.path === action.payload.path ? action.payload : f),
|
|
2418
|
+
selectedItem: state.selectedItem?.path === action.payload.path ? action.payload : state.selectedItem
|
|
2419
|
+
};
|
|
2420
|
+
case "REFRESH":
|
|
2421
|
+
return { ...state, isLoading: true };
|
|
2422
|
+
default:
|
|
2423
|
+
return state;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
var FileBrowserContext = createContext(null);
|
|
2427
|
+
function FileBrowserProvider({ children, api, initialPath = "/" }) {
|
|
2428
|
+
const [state, dispatch] = useReducer(fileBrowserReducer, {
|
|
2429
|
+
...initialState,
|
|
2430
|
+
currentPath: initialPath
|
|
2431
|
+
});
|
|
2432
|
+
const navigateTo = useCallback10(async (path) => {
|
|
2433
|
+
dispatch({ type: "SET_LOADING", payload: true });
|
|
2434
|
+
dispatch({ type: "SET_CURRENT_PATH", payload: path });
|
|
2435
|
+
dispatch({ type: "SET_SELECTED_ITEM", payload: null });
|
|
2436
|
+
const result = await api.listDirectory(path);
|
|
2437
|
+
if (result.success && result.data) {
|
|
2438
|
+
dispatch({ type: "SET_FILES", payload: result.data });
|
|
2439
|
+
} else {
|
|
2440
|
+
dispatch({ type: "SET_ERROR", payload: result.error || "Failed to load directory" });
|
|
2441
|
+
}
|
|
2442
|
+
}, [api]);
|
|
2443
|
+
const refresh = useCallback10(async () => {
|
|
2444
|
+
await navigateTo(state.currentPath);
|
|
2445
|
+
const treeResult = await api.getFolderTree("/", 2);
|
|
2446
|
+
if (treeResult.success && treeResult.data) {
|
|
2447
|
+
dispatch({ type: "SET_TREE", payload: treeResult.data });
|
|
2448
|
+
}
|
|
2449
|
+
}, [api, navigateTo, state.currentPath]);
|
|
2450
|
+
const selectItem = useCallback10((item) => {
|
|
2451
|
+
dispatch({ type: "SET_SELECTED_ITEM", payload: item });
|
|
2452
|
+
}, []);
|
|
2453
|
+
const toggleTreeNode2 = useCallback10((path) => {
|
|
2454
|
+
dispatch({ type: "TOGGLE_TREE_NODE", payload: path });
|
|
2455
|
+
}, []);
|
|
2456
|
+
const expandTreeNode = useCallback10(async (path) => {
|
|
2457
|
+
const result = await api.listDirectory(path);
|
|
2458
|
+
if (result.success && result.data) {
|
|
2459
|
+
const folders = result.data.filter((item) => item.isDirectory);
|
|
2460
|
+
const children2 = folders.map((folder) => ({
|
|
2461
|
+
id: folder.id,
|
|
2462
|
+
name: folder.name,
|
|
2463
|
+
path: folder.path,
|
|
2464
|
+
children: []
|
|
2465
|
+
}));
|
|
2466
|
+
dispatch({ type: "UPDATE_TREE_NODE", payload: { path, children: children2, isExpanded: true } });
|
|
2467
|
+
}
|
|
2468
|
+
}, [api]);
|
|
2469
|
+
const createFolder = useCallback10(async (name) => {
|
|
2470
|
+
const newPath = state.currentPath === "/" ? `/${name}` : `${state.currentPath}/${name}`;
|
|
2471
|
+
const result = await api.createDirectory(newPath);
|
|
2472
|
+
if (result.success && result.data) {
|
|
2473
|
+
dispatch({ type: "ADD_ITEM", payload: result.data });
|
|
2474
|
+
}
|
|
2475
|
+
return result;
|
|
2476
|
+
}, [api, state.currentPath]);
|
|
2477
|
+
const deleteSelected = useCallback10(async () => {
|
|
2478
|
+
if (!state.selectedItem) {
|
|
2479
|
+
return { success: false, error: "No item selected" };
|
|
2480
|
+
}
|
|
2481
|
+
let result;
|
|
2482
|
+
if (state.selectedItem.isDirectory) {
|
|
2483
|
+
result = await api.removeDirectory(state.selectedItem.path, true);
|
|
2484
|
+
} else {
|
|
2485
|
+
result = await api.deleteFile(state.selectedItem.path);
|
|
2486
|
+
}
|
|
2487
|
+
if (result.success) {
|
|
2488
|
+
dispatch({ type: "REMOVE_ITEM", payload: state.selectedItem.path });
|
|
2489
|
+
}
|
|
2490
|
+
return result;
|
|
2491
|
+
}, [api, state.selectedItem]);
|
|
2492
|
+
const renameSelected = useCallback10(async (newName) => {
|
|
2493
|
+
if (!state.selectedItem) {
|
|
2494
|
+
return { success: false, error: "No item selected" };
|
|
2495
|
+
}
|
|
2496
|
+
let result;
|
|
2497
|
+
if (state.selectedItem.isDirectory) {
|
|
2498
|
+
result = await api.renameFolder(state.selectedItem.path, newName);
|
|
2499
|
+
} else {
|
|
2500
|
+
result = await api.renameFile(state.selectedItem.path, newName);
|
|
2501
|
+
}
|
|
2502
|
+
if (result.success && result.data) {
|
|
2503
|
+
dispatch({ type: "REMOVE_ITEM", payload: state.selectedItem.path });
|
|
2504
|
+
dispatch({ type: "ADD_ITEM", payload: result.data });
|
|
2505
|
+
dispatch({ type: "SET_SELECTED_ITEM", payload: result.data });
|
|
2506
|
+
}
|
|
2507
|
+
return result;
|
|
2508
|
+
}, [api, state.selectedItem]);
|
|
2509
|
+
const uploadFiles = useCallback10(async (files) => {
|
|
2510
|
+
const results = [];
|
|
2511
|
+
for (let i = 0; i < files.length; i++) {
|
|
2512
|
+
const file = files[i];
|
|
2513
|
+
const remotePath = state.currentPath === "/" ? `/${file.name}` : `${state.currentPath}/${file.name}`;
|
|
2514
|
+
const result = await api.uploadFile(file, remotePath);
|
|
2515
|
+
results.push(result);
|
|
2516
|
+
if (result.success && result.data) {
|
|
2517
|
+
dispatch({ type: "ADD_ITEM", payload: result.data });
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
return results;
|
|
2521
|
+
}, [api, state.currentPath]);
|
|
2522
|
+
const downloadSelected = useCallback10(async () => {
|
|
2523
|
+
if (!state.selectedItem || state.selectedItem.isDirectory) {
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
const result = await api.downloadFile(state.selectedItem.path);
|
|
2527
|
+
if (result.success && result.data) {
|
|
2528
|
+
const blob = result.data;
|
|
2529
|
+
const url = URL.createObjectURL(blob);
|
|
2530
|
+
const a = document.createElement("a");
|
|
2531
|
+
a.href = url;
|
|
2532
|
+
a.download = state.selectedItem.name;
|
|
2533
|
+
document.body.appendChild(a);
|
|
2534
|
+
a.click();
|
|
2535
|
+
document.body.removeChild(a);
|
|
2536
|
+
URL.revokeObjectURL(url);
|
|
2537
|
+
}
|
|
2538
|
+
}, [api, state.selectedItem]);
|
|
2539
|
+
const moveSelected = useCallback10(async (destinationPath) => {
|
|
2540
|
+
if (!state.selectedItem) {
|
|
2541
|
+
return { success: false, error: "No item selected" };
|
|
2542
|
+
}
|
|
2543
|
+
const result = await api.moveItem(state.selectedItem.path, destinationPath);
|
|
2544
|
+
if (result.success) {
|
|
2545
|
+
dispatch({ type: "REMOVE_ITEM", payload: state.selectedItem.path });
|
|
2546
|
+
}
|
|
2547
|
+
return result;
|
|
2548
|
+
}, [api, state.selectedItem]);
|
|
2549
|
+
const value = {
|
|
2550
|
+
state,
|
|
2551
|
+
dispatch,
|
|
2552
|
+
api,
|
|
2553
|
+
navigateTo,
|
|
2554
|
+
refresh,
|
|
2555
|
+
selectItem,
|
|
2556
|
+
toggleTreeNode: toggleTreeNode2,
|
|
2557
|
+
expandTreeNode,
|
|
2558
|
+
createFolder,
|
|
2559
|
+
deleteSelected,
|
|
2560
|
+
renameSelected,
|
|
2561
|
+
uploadFiles,
|
|
2562
|
+
downloadSelected,
|
|
2563
|
+
moveSelected
|
|
2564
|
+
};
|
|
2565
|
+
return /* @__PURE__ */ jsx12(FileBrowserContext.Provider, { value, children });
|
|
2566
|
+
}
|
|
2567
|
+
function useFileBrowserContext() {
|
|
2568
|
+
const context = useContext(FileBrowserContext);
|
|
2569
|
+
if (!context) {
|
|
2570
|
+
throw new Error("useFileBrowserContext must be used within a FileBrowserProvider");
|
|
2571
|
+
}
|
|
2572
|
+
return context;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
// src/ui/components/naming/NamingRuleConfigurator.tsx
|
|
2576
|
+
import { useCallback as useCallback13, useRef as useRef5, useState as useState12 } from "react";
|
|
2577
|
+
import {
|
|
2578
|
+
DndContext,
|
|
2579
|
+
DragOverlay,
|
|
2580
|
+
PointerSensor,
|
|
2581
|
+
useSensor,
|
|
2582
|
+
useSensors,
|
|
2583
|
+
closestCenter
|
|
2584
|
+
} from "@dnd-kit/core";
|
|
2585
|
+
|
|
2586
|
+
// src/ui/hooks/useNamingRule.ts
|
|
2587
|
+
import { useState as useState9, useCallback as useCallback11, useEffect as useEffect6, useRef as useRef3 } from "react";
|
|
2588
|
+
|
|
2589
|
+
// src/common/naming-utils.ts
|
|
2590
|
+
var DEFAULT_DATE_FORMATS = [
|
|
2591
|
+
"YYYY",
|
|
2592
|
+
"YY",
|
|
2593
|
+
"MM",
|
|
2594
|
+
"M",
|
|
2595
|
+
"DD",
|
|
2596
|
+
"D",
|
|
2597
|
+
"MMM",
|
|
2598
|
+
"MMMM",
|
|
2599
|
+
"YYYY-MM-DD",
|
|
2600
|
+
"YYYY-MMM-DD",
|
|
2601
|
+
"DD-MM-YYYY",
|
|
2602
|
+
"MM-DD-YYYY"
|
|
2603
|
+
];
|
|
2604
|
+
var MONTH_NAMES_SHORT = [
|
|
2605
|
+
"Jan",
|
|
2606
|
+
"Feb",
|
|
2607
|
+
"Mar",
|
|
2608
|
+
"Apr",
|
|
2609
|
+
"May",
|
|
2610
|
+
"Jun",
|
|
2611
|
+
"Jul",
|
|
2612
|
+
"Aug",
|
|
2613
|
+
"Sep",
|
|
2614
|
+
"Oct",
|
|
2615
|
+
"Nov",
|
|
2616
|
+
"Dec"
|
|
2617
|
+
];
|
|
2618
|
+
var MONTH_NAMES_FULL = [
|
|
2619
|
+
"January",
|
|
2620
|
+
"February",
|
|
2621
|
+
"March",
|
|
2622
|
+
"April",
|
|
2623
|
+
"May",
|
|
2624
|
+
"June",
|
|
2625
|
+
"July",
|
|
2626
|
+
"August",
|
|
2627
|
+
"September",
|
|
2628
|
+
"October",
|
|
2629
|
+
"November",
|
|
2630
|
+
"December"
|
|
2631
|
+
];
|
|
2632
|
+
var SYSTEM_DATE_VARIABLES = [
|
|
2633
|
+
{ variable_name: "YYYY", description: "Full year (4 digits)", example_value: "2024", category: "date" },
|
|
2634
|
+
{ variable_name: "YY", description: "Year (2 digits)", example_value: "24", category: "date" },
|
|
2635
|
+
{ variable_name: "MM", description: "Month (2 digits, 01-12)", example_value: "01", category: "date" },
|
|
2636
|
+
{ variable_name: "M", description: "Month (1-12)", example_value: "1", category: "date" },
|
|
2637
|
+
{ variable_name: "DD", description: "Day (2 digits, 01-31)", example_value: "15", category: "date" },
|
|
2638
|
+
{ variable_name: "D", description: "Day (1-31)", example_value: "15", category: "date" },
|
|
2639
|
+
{ variable_name: "MMM", description: "Short month name", example_value: "Jan", category: "date" },
|
|
2640
|
+
{ variable_name: "MMMM", description: "Full month name", example_value: "January", category: "date" },
|
|
2641
|
+
{ variable_name: "YYYY-MM-DD", description: "ISO date format", example_value: "2024-01-15", category: "date" },
|
|
2642
|
+
{ variable_name: "YYYY-MMM-DD", description: "Date with month name", example_value: "2024-Jan-15", category: "date" },
|
|
2643
|
+
{ variable_name: "DD-MM-YYYY", description: "Day-Month-Year", example_value: "15-01-2024", category: "date" },
|
|
2644
|
+
{ variable_name: "MM-DD-YYYY", description: "Month-Day-Year", example_value: "01-15-2024", category: "date" }
|
|
2645
|
+
];
|
|
2646
|
+
var SYSTEM_FILE_VARIABLES = [
|
|
2647
|
+
{ variable_name: "original_name", description: "Original filename without extension", example_value: "document", category: "file" },
|
|
2648
|
+
{ variable_name: "extension", description: "Original file extension with dot", example_value: ".pdf", category: "file" },
|
|
2649
|
+
{ variable_name: "ext", description: "Extension without dot", example_value: "pdf", category: "file" }
|
|
2650
|
+
];
|
|
2651
|
+
var SYSTEM_COUNTER_VARIABLES = [
|
|
2652
|
+
{ variable_name: "counter", description: "Auto-incrementing number (3 digits)", example_value: "001", category: "counter" }
|
|
2653
|
+
];
|
|
2654
|
+
var ALL_SYSTEM_VARIABLES = [
|
|
2655
|
+
...SYSTEM_DATE_VARIABLES,
|
|
2656
|
+
...SYSTEM_FILE_VARIABLES,
|
|
2657
|
+
...SYSTEM_COUNTER_VARIABLES
|
|
2658
|
+
];
|
|
2659
|
+
function formatDateToken(date, format) {
|
|
2660
|
+
const year = date.getFullYear();
|
|
2661
|
+
const month = date.getMonth();
|
|
2662
|
+
const day = date.getDate();
|
|
2663
|
+
switch (format) {
|
|
2664
|
+
case "YYYY":
|
|
2665
|
+
return String(year);
|
|
2666
|
+
case "YY":
|
|
2667
|
+
return String(year).slice(-2);
|
|
2668
|
+
case "MM":
|
|
2669
|
+
return String(month + 1).padStart(2, "0");
|
|
2670
|
+
case "M":
|
|
2671
|
+
return String(month + 1);
|
|
2672
|
+
case "DD":
|
|
2673
|
+
return String(day).padStart(2, "0");
|
|
2674
|
+
case "D":
|
|
2675
|
+
return String(day);
|
|
2676
|
+
case "MMM":
|
|
2677
|
+
return MONTH_NAMES_SHORT[month];
|
|
2678
|
+
case "MMMM":
|
|
2679
|
+
return MONTH_NAMES_FULL[month];
|
|
2680
|
+
case "YYYY-MM-DD":
|
|
2681
|
+
return `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
2682
|
+
case "YYYY-MMM-DD":
|
|
2683
|
+
return `${year}-${MONTH_NAMES_SHORT[month]}-${String(day).padStart(2, "0")}`;
|
|
2684
|
+
case "DD-MM-YYYY":
|
|
2685
|
+
return `${String(day).padStart(2, "0")}-${String(month + 1).padStart(2, "0")}-${year}`;
|
|
2686
|
+
case "MM-DD-YYYY":
|
|
2687
|
+
return `${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}-${year}`;
|
|
2688
|
+
default:
|
|
2689
|
+
return format;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
function formatCounter(value, digits = 3) {
|
|
2693
|
+
return String(value).padStart(digits, "0");
|
|
2694
|
+
}
|
|
2695
|
+
function getFileMetadataValues(filename) {
|
|
2696
|
+
if (!filename) {
|
|
2697
|
+
return {
|
|
2698
|
+
original_name: "",
|
|
2699
|
+
extension: "",
|
|
2700
|
+
ext: ""
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
const extension = getExtension(filename);
|
|
2704
|
+
const originalName = getNameWithoutExtension(filename);
|
|
2705
|
+
return {
|
|
2706
|
+
original_name: originalName,
|
|
2707
|
+
extension,
|
|
2708
|
+
ext: extension.startsWith(".") ? extension.slice(1) : extension
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
function validateNamingRuleSchema(schema) {
|
|
2712
|
+
if (!schema || typeof schema !== "object") return false;
|
|
2713
|
+
const s = schema;
|
|
2714
|
+
if (typeof s.version !== "number") return false;
|
|
2715
|
+
if (!Array.isArray(s.filePattern) || !Array.isArray(s.folderPattern)) return false;
|
|
2716
|
+
const isValidSegment = (seg) => {
|
|
2717
|
+
if (!seg || typeof seg !== "object") return false;
|
|
2718
|
+
const segment = seg;
|
|
2719
|
+
return typeof segment.id === "string" && (segment.type === "variable" || segment.type === "literal") && typeof segment.value === "string";
|
|
2720
|
+
};
|
|
2721
|
+
return s.filePattern.every(isValidSegment) && s.folderPattern.every(isValidSegment);
|
|
2722
|
+
}
|
|
2723
|
+
function generateSegmentId() {
|
|
2724
|
+
return `seg_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
2725
|
+
}
|
|
2726
|
+
function createVariableSegment(variableName) {
|
|
2727
|
+
return {
|
|
2728
|
+
id: generateSegmentId(),
|
|
2729
|
+
type: "variable",
|
|
2730
|
+
value: variableName
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
function createLiteralSegment(text) {
|
|
2734
|
+
return {
|
|
2735
|
+
id: generateSegmentId(),
|
|
2736
|
+
type: "literal",
|
|
2737
|
+
value: text
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
function clonePattern(pattern) {
|
|
2741
|
+
return pattern.map((seg) => ({
|
|
2742
|
+
...seg,
|
|
2743
|
+
id: generateSegmentId()
|
|
2744
|
+
}));
|
|
2745
|
+
}
|
|
2746
|
+
function getSystemVariablePreviewValues(date = /* @__PURE__ */ new Date(), options = {}) {
|
|
2747
|
+
const { counterValue = 1, counterDigits = 3, originalFileName } = options;
|
|
2748
|
+
const fileMetadata = getFileMetadataValues(originalFileName);
|
|
2749
|
+
const values = {};
|
|
2750
|
+
for (const format of DEFAULT_DATE_FORMATS) {
|
|
2751
|
+
values[format] = formatDateToken(date, format);
|
|
2752
|
+
}
|
|
2753
|
+
values.original_name = fileMetadata.original_name || "document";
|
|
2754
|
+
values.extension = fileMetadata.extension || ".pdf";
|
|
2755
|
+
values.ext = fileMetadata.ext || "pdf";
|
|
2756
|
+
values.counter = formatCounter(counterValue, counterDigits);
|
|
2757
|
+
return values;
|
|
2758
|
+
}
|
|
2759
|
+
function generatePreviewName(pattern, userVariables, options = {}) {
|
|
2760
|
+
if (pattern.length === 0) {
|
|
2761
|
+
return "(empty pattern)";
|
|
2762
|
+
}
|
|
2763
|
+
const systemValues = getSystemVariablePreviewValues(
|
|
2764
|
+
options.date,
|
|
2765
|
+
options
|
|
2766
|
+
);
|
|
2767
|
+
const userValues = {};
|
|
2768
|
+
for (const variable of userVariables) {
|
|
2769
|
+
userValues[variable.variable_name] = variable.example_value;
|
|
2770
|
+
}
|
|
2771
|
+
const allValues = { ...userValues, ...systemValues };
|
|
2772
|
+
const parts = [];
|
|
2773
|
+
for (const segment of pattern) {
|
|
2774
|
+
if (segment.type === "literal") {
|
|
2775
|
+
parts.push(segment.value);
|
|
2776
|
+
} else if (segment.type === "variable") {
|
|
2777
|
+
const value = allValues[segment.value];
|
|
2778
|
+
if (value !== void 0) {
|
|
2779
|
+
parts.push(value);
|
|
2780
|
+
} else {
|
|
2781
|
+
parts.push(`{${segment.value}}`);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
return parts.join("") || "(empty)";
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
// src/ui/hooks/useNamingRule.ts
|
|
2789
|
+
var MAX_HISTORY_SIZE = 50;
|
|
2790
|
+
function useNamingRule(options = {}) {
|
|
2791
|
+
const { initialSchema, onChange } = options;
|
|
2792
|
+
const [filePattern, setFilePattern] = useState9(
|
|
2793
|
+
initialSchema?.filePattern ? clonePattern(initialSchema.filePattern) : []
|
|
2794
|
+
);
|
|
2795
|
+
const [folderPattern, setFolderPattern] = useState9(
|
|
2796
|
+
initialSchema?.folderPattern ? clonePattern(initialSchema.folderPattern) : []
|
|
2797
|
+
);
|
|
2798
|
+
const [history, setHistory] = useState9([]);
|
|
2799
|
+
const [historyIndex, setHistoryIndex] = useState9(-1);
|
|
2800
|
+
const initialStateRef = useRef3({
|
|
2801
|
+
filePattern: initialSchema?.filePattern ? clonePattern(initialSchema.filePattern) : [],
|
|
2802
|
+
folderPattern: initialSchema?.folderPattern ? clonePattern(initialSchema.folderPattern) : []
|
|
2803
|
+
});
|
|
2804
|
+
const isUndoRedoRef = useRef3(false);
|
|
2805
|
+
const recordHistory = useCallback11(() => {
|
|
2806
|
+
if (isUndoRedoRef.current) return;
|
|
2807
|
+
const entry = {
|
|
2808
|
+
filePattern: clonePattern(filePattern),
|
|
2809
|
+
folderPattern: clonePattern(folderPattern),
|
|
2810
|
+
timestamp: Date.now()
|
|
2811
|
+
};
|
|
2812
|
+
setHistory((prev) => {
|
|
2813
|
+
const newHistory = prev.slice(0, historyIndex + 1);
|
|
2814
|
+
newHistory.push(entry);
|
|
2815
|
+
if (newHistory.length > MAX_HISTORY_SIZE) {
|
|
2816
|
+
return newHistory.slice(-MAX_HISTORY_SIZE);
|
|
2817
|
+
}
|
|
2818
|
+
return newHistory;
|
|
2819
|
+
});
|
|
2820
|
+
setHistoryIndex((prev) => Math.min(prev + 1, MAX_HISTORY_SIZE - 1));
|
|
2821
|
+
}, [filePattern, folderPattern, historyIndex]);
|
|
2822
|
+
useEffect6(() => {
|
|
2823
|
+
if (onChange) {
|
|
2824
|
+
onChange(getSchema());
|
|
2825
|
+
}
|
|
2826
|
+
}, [filePattern, folderPattern]);
|
|
2827
|
+
const getSchema = useCallback11(() => {
|
|
2828
|
+
return {
|
|
2829
|
+
version: 1,
|
|
2830
|
+
filePattern: clonePattern(filePattern),
|
|
2831
|
+
folderPattern: clonePattern(folderPattern),
|
|
2832
|
+
metadata: {
|
|
2833
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2834
|
+
}
|
|
2835
|
+
};
|
|
2836
|
+
}, [filePattern, folderPattern]);
|
|
2837
|
+
const loadSchema = useCallback11((schema) => {
|
|
2838
|
+
const newFilePattern = schema.filePattern ? clonePattern(schema.filePattern) : [];
|
|
2839
|
+
const newFolderPattern = schema.folderPattern ? clonePattern(schema.folderPattern) : [];
|
|
2840
|
+
setFilePattern(newFilePattern);
|
|
2841
|
+
setFolderPattern(newFolderPattern);
|
|
2842
|
+
setHistory([]);
|
|
2843
|
+
setHistoryIndex(-1);
|
|
2844
|
+
initialStateRef.current = {
|
|
2845
|
+
filePattern: clonePattern(newFilePattern),
|
|
2846
|
+
folderPattern: clonePattern(newFolderPattern)
|
|
2847
|
+
};
|
|
2848
|
+
}, []);
|
|
2849
|
+
const reset = useCallback11(() => {
|
|
2850
|
+
setFilePattern(clonePattern(initialStateRef.current.filePattern));
|
|
2851
|
+
setFolderPattern(clonePattern(initialStateRef.current.folderPattern));
|
|
2852
|
+
setHistory([]);
|
|
2853
|
+
setHistoryIndex(-1);
|
|
2854
|
+
}, []);
|
|
2855
|
+
const undo = useCallback11(() => {
|
|
2856
|
+
if (historyIndex < 0) return;
|
|
2857
|
+
isUndoRedoRef.current = true;
|
|
2858
|
+
const entry = history[historyIndex];
|
|
2859
|
+
if (entry) {
|
|
2860
|
+
setFilePattern(clonePattern(entry.filePattern));
|
|
2861
|
+
setFolderPattern(clonePattern(entry.folderPattern));
|
|
2862
|
+
}
|
|
2863
|
+
setHistoryIndex((prev) => prev - 1);
|
|
2864
|
+
setTimeout(() => {
|
|
2865
|
+
isUndoRedoRef.current = false;
|
|
2866
|
+
}, 0);
|
|
2867
|
+
}, [history, historyIndex]);
|
|
2868
|
+
const redo = useCallback11(() => {
|
|
2869
|
+
if (historyIndex >= history.length - 1) return;
|
|
2870
|
+
isUndoRedoRef.current = true;
|
|
2871
|
+
const entry = history[historyIndex + 1];
|
|
2872
|
+
if (entry) {
|
|
2873
|
+
setFilePattern(clonePattern(entry.filePattern));
|
|
2874
|
+
setFolderPattern(clonePattern(entry.folderPattern));
|
|
2875
|
+
}
|
|
2876
|
+
setHistoryIndex((prev) => prev + 1);
|
|
2877
|
+
setTimeout(() => {
|
|
2878
|
+
isUndoRedoRef.current = false;
|
|
2879
|
+
}, 0);
|
|
2880
|
+
}, [history, historyIndex]);
|
|
2881
|
+
const addToFilePattern = useCallback11(
|
|
2882
|
+
(segment, index) => {
|
|
2883
|
+
recordHistory();
|
|
2884
|
+
setFilePattern((prev) => {
|
|
2885
|
+
const newSegment = { ...segment, id: generateSegmentId() };
|
|
2886
|
+
if (index !== void 0 && index >= 0 && index <= prev.length) {
|
|
2887
|
+
const newPattern = [...prev];
|
|
2888
|
+
newPattern.splice(index, 0, newSegment);
|
|
2889
|
+
return newPattern;
|
|
2890
|
+
}
|
|
2891
|
+
return [...prev, newSegment];
|
|
2892
|
+
});
|
|
2893
|
+
},
|
|
2894
|
+
[recordHistory]
|
|
2895
|
+
);
|
|
2896
|
+
const removeFromFilePattern = useCallback11(
|
|
2897
|
+
(id) => {
|
|
2898
|
+
recordHistory();
|
|
2899
|
+
setFilePattern((prev) => prev.filter((seg) => seg.id !== id));
|
|
2900
|
+
},
|
|
2901
|
+
[recordHistory]
|
|
2902
|
+
);
|
|
2903
|
+
const updateFilePatternSegment = useCallback11(
|
|
2904
|
+
(id, value) => {
|
|
2905
|
+
recordHistory();
|
|
2906
|
+
setFilePattern(
|
|
2907
|
+
(prev) => prev.map((seg) => seg.id === id ? { ...seg, value } : seg)
|
|
2908
|
+
);
|
|
2909
|
+
},
|
|
2910
|
+
[recordHistory]
|
|
2911
|
+
);
|
|
2912
|
+
const reorderFilePattern = useCallback11(
|
|
2913
|
+
(fromIndex, toIndex) => {
|
|
2914
|
+
if (fromIndex === toIndex) return;
|
|
2915
|
+
recordHistory();
|
|
2916
|
+
setFilePattern((prev) => {
|
|
2917
|
+
const newPattern = [...prev];
|
|
2918
|
+
const [removed] = newPattern.splice(fromIndex, 1);
|
|
2919
|
+
newPattern.splice(toIndex, 0, removed);
|
|
2920
|
+
return newPattern;
|
|
2921
|
+
});
|
|
2922
|
+
},
|
|
2923
|
+
[recordHistory]
|
|
2924
|
+
);
|
|
2925
|
+
const clearFilePattern = useCallback11(() => {
|
|
2926
|
+
recordHistory();
|
|
2927
|
+
setFilePattern([]);
|
|
2928
|
+
}, [recordHistory]);
|
|
2929
|
+
const addToFolderPattern = useCallback11(
|
|
2930
|
+
(segment, index) => {
|
|
2931
|
+
recordHistory();
|
|
2932
|
+
setFolderPattern((prev) => {
|
|
2933
|
+
const newSegment = { ...segment, id: generateSegmentId() };
|
|
2934
|
+
if (index !== void 0 && index >= 0 && index <= prev.length) {
|
|
2935
|
+
const newPattern = [...prev];
|
|
2936
|
+
newPattern.splice(index, 0, newSegment);
|
|
2937
|
+
return newPattern;
|
|
2938
|
+
}
|
|
2939
|
+
return [...prev, newSegment];
|
|
2940
|
+
});
|
|
2941
|
+
},
|
|
2942
|
+
[recordHistory]
|
|
2943
|
+
);
|
|
2944
|
+
const removeFromFolderPattern = useCallback11(
|
|
2945
|
+
(id) => {
|
|
2946
|
+
recordHistory();
|
|
2947
|
+
setFolderPattern((prev) => prev.filter((seg) => seg.id !== id));
|
|
2948
|
+
},
|
|
2949
|
+
[recordHistory]
|
|
2950
|
+
);
|
|
2951
|
+
const updateFolderPatternSegment = useCallback11(
|
|
2952
|
+
(id, value) => {
|
|
2953
|
+
recordHistory();
|
|
2954
|
+
setFolderPattern(
|
|
2955
|
+
(prev) => prev.map((seg) => seg.id === id ? { ...seg, value } : seg)
|
|
2956
|
+
);
|
|
2957
|
+
},
|
|
2958
|
+
[recordHistory]
|
|
2959
|
+
);
|
|
2960
|
+
const reorderFolderPattern = useCallback11(
|
|
2961
|
+
(fromIndex, toIndex) => {
|
|
2962
|
+
if (fromIndex === toIndex) return;
|
|
2963
|
+
recordHistory();
|
|
2964
|
+
setFolderPattern((prev) => {
|
|
2965
|
+
const newPattern = [...prev];
|
|
2966
|
+
const [removed] = newPattern.splice(fromIndex, 1);
|
|
2967
|
+
newPattern.splice(toIndex, 0, removed);
|
|
2968
|
+
return newPattern;
|
|
2969
|
+
});
|
|
2970
|
+
},
|
|
2971
|
+
[recordHistory]
|
|
2972
|
+
);
|
|
2973
|
+
const clearFolderPattern = useCallback11(() => {
|
|
2974
|
+
recordHistory();
|
|
2975
|
+
setFolderPattern([]);
|
|
2976
|
+
}, [recordHistory]);
|
|
2977
|
+
useEffect6(() => {
|
|
2978
|
+
const handleKeyDown = (e) => {
|
|
2979
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "z") {
|
|
2980
|
+
if (e.shiftKey) {
|
|
2981
|
+
e.preventDefault();
|
|
2982
|
+
redo();
|
|
2983
|
+
} else {
|
|
2984
|
+
e.preventDefault();
|
|
2985
|
+
undo();
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "y") {
|
|
2989
|
+
e.preventDefault();
|
|
2990
|
+
redo();
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
2994
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
2995
|
+
}, [undo, redo]);
|
|
2996
|
+
const isDirty = JSON.stringify(filePattern) !== JSON.stringify(initialStateRef.current.filePattern) || JSON.stringify(folderPattern) !== JSON.stringify(initialStateRef.current.folderPattern);
|
|
2997
|
+
return {
|
|
2998
|
+
// State
|
|
2999
|
+
filePattern,
|
|
3000
|
+
folderPattern,
|
|
3001
|
+
canUndo: historyIndex >= 0,
|
|
3002
|
+
canRedo: historyIndex < history.length - 1,
|
|
3003
|
+
isDirty,
|
|
3004
|
+
// File pattern operations
|
|
3005
|
+
addToFilePattern,
|
|
3006
|
+
removeFromFilePattern,
|
|
3007
|
+
updateFilePatternSegment,
|
|
3008
|
+
reorderFilePattern,
|
|
3009
|
+
clearFilePattern,
|
|
3010
|
+
// Folder pattern operations
|
|
3011
|
+
addToFolderPattern,
|
|
3012
|
+
removeFromFolderPattern,
|
|
3013
|
+
updateFolderPatternSegment,
|
|
3014
|
+
reorderFolderPattern,
|
|
3015
|
+
clearFolderPattern,
|
|
3016
|
+
// History
|
|
3017
|
+
undo,
|
|
3018
|
+
redo,
|
|
3019
|
+
// Schema
|
|
3020
|
+
getSchema,
|
|
3021
|
+
loadSchema,
|
|
3022
|
+
reset
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
// src/ui/components/naming/VariableList.tsx
|
|
3027
|
+
import { useState as useState10 } from "react";
|
|
3028
|
+
|
|
3029
|
+
// src/ui/components/naming/DraggableVariable.tsx
|
|
3030
|
+
import { useDraggable } from "@dnd-kit/core";
|
|
3031
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3032
|
+
function getCategoryColor(category) {
|
|
3033
|
+
switch (category) {
|
|
3034
|
+
case "date":
|
|
3035
|
+
return "bg-emerald-100 hover:bg-emerald-200 border-emerald-300 text-emerald-800";
|
|
3036
|
+
case "file":
|
|
3037
|
+
return "bg-purple-100 hover:bg-purple-200 border-purple-300 text-purple-800";
|
|
3038
|
+
case "counter":
|
|
3039
|
+
return "bg-amber-100 hover:bg-amber-200 border-amber-300 text-amber-800";
|
|
3040
|
+
case "user":
|
|
3041
|
+
default:
|
|
3042
|
+
return "bg-blue-100 hover:bg-blue-200 border-blue-300 text-blue-800";
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
function DraggableVariable({
|
|
3046
|
+
variable,
|
|
3047
|
+
onClick,
|
|
3048
|
+
className = "",
|
|
3049
|
+
disabled = false
|
|
3050
|
+
}) {
|
|
3051
|
+
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
3052
|
+
id: `draggable-${variable.variable_name}`,
|
|
3053
|
+
data: {
|
|
3054
|
+
type: "variable",
|
|
3055
|
+
variable
|
|
3056
|
+
},
|
|
3057
|
+
disabled
|
|
3058
|
+
});
|
|
3059
|
+
const colorClass = getCategoryColor(variable.category);
|
|
3060
|
+
return /* @__PURE__ */ jsxs12(
|
|
3061
|
+
"div",
|
|
3062
|
+
{
|
|
3063
|
+
ref: setNodeRef,
|
|
3064
|
+
...listeners,
|
|
3065
|
+
...attributes,
|
|
3066
|
+
onClick: () => !disabled && onClick?.(variable),
|
|
3067
|
+
className: `
|
|
3068
|
+
inline-flex items-center gap-1 px-2.5 py-1 rounded-full border text-sm font-medium
|
|
3069
|
+
cursor-grab active:cursor-grabbing select-none transition-all
|
|
3070
|
+
${colorClass}
|
|
3071
|
+
${isDragging ? "opacity-50 scale-95 shadow-lg" : "shadow-sm"}
|
|
3072
|
+
${disabled ? "opacity-50 cursor-not-allowed" : ""}
|
|
3073
|
+
${className}
|
|
3074
|
+
`,
|
|
3075
|
+
title: `${variable.description}
|
|
3076
|
+
Example: ${variable.example_value}`,
|
|
3077
|
+
children: [
|
|
3078
|
+
/* @__PURE__ */ jsx13("span", { className: "text-xs opacity-60", children: "{" }),
|
|
3079
|
+
/* @__PURE__ */ jsx13("span", { children: variable.variable_name }),
|
|
3080
|
+
/* @__PURE__ */ jsx13("span", { className: "text-xs opacity-60", children: "}" })
|
|
3081
|
+
]
|
|
3082
|
+
}
|
|
3083
|
+
);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// src/ui/components/naming/VariableList.tsx
|
|
3087
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3088
|
+
function VariableList({
|
|
3089
|
+
userVariables,
|
|
3090
|
+
customDateFormats,
|
|
3091
|
+
onVariableClick,
|
|
3092
|
+
disabled = false,
|
|
3093
|
+
className = ""
|
|
3094
|
+
}) {
|
|
3095
|
+
const [activeCategory, setActiveCategory] = useState10("user");
|
|
3096
|
+
const userVars = userVariables.map((v) => ({
|
|
3097
|
+
...v,
|
|
3098
|
+
category: "user"
|
|
3099
|
+
}));
|
|
3100
|
+
const dateVars = customDateFormats ? SYSTEM_DATE_VARIABLES.filter(
|
|
3101
|
+
(v) => customDateFormats.includes(v.variable_name)
|
|
3102
|
+
) : SYSTEM_DATE_VARIABLES;
|
|
3103
|
+
const fileVars = SYSTEM_FILE_VARIABLES;
|
|
3104
|
+
const counterVars = SYSTEM_COUNTER_VARIABLES;
|
|
3105
|
+
const allTabs = [
|
|
3106
|
+
{ id: "user", label: "User", count: userVars.length },
|
|
3107
|
+
{ id: "date", label: "Dates", count: dateVars.length },
|
|
3108
|
+
{ id: "file", label: "File", count: fileVars.length },
|
|
3109
|
+
{ id: "counter", label: "Counter", count: counterVars.length }
|
|
3110
|
+
];
|
|
3111
|
+
const tabs = allTabs.filter((tab) => tab.count > 0);
|
|
3112
|
+
const getVariablesForCategory = (category) => {
|
|
3113
|
+
switch (category) {
|
|
3114
|
+
case "user":
|
|
3115
|
+
return userVars;
|
|
3116
|
+
case "date":
|
|
3117
|
+
return dateVars;
|
|
3118
|
+
case "file":
|
|
3119
|
+
return fileVars;
|
|
3120
|
+
case "counter":
|
|
3121
|
+
return counterVars;
|
|
3122
|
+
default:
|
|
3123
|
+
return [];
|
|
3124
|
+
}
|
|
3125
|
+
};
|
|
3126
|
+
const activeVariables = getVariablesForCategory(activeCategory);
|
|
3127
|
+
if (activeVariables.length === 0 && tabs.length > 0) {
|
|
3128
|
+
const firstTab = tabs[0];
|
|
3129
|
+
if (firstTab.id !== activeCategory) {
|
|
3130
|
+
setActiveCategory(firstTab.id);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
return /* @__PURE__ */ jsxs13("div", { className: `bg-gray-50 rounded-lg border border-gray-200 ${className}`, children: [
|
|
3134
|
+
/* @__PURE__ */ jsx14("div", { className: "flex border-b border-gray-200", children: tabs.map((tab) => /* @__PURE__ */ jsxs13(
|
|
3135
|
+
"button",
|
|
3136
|
+
{
|
|
3137
|
+
type: "button",
|
|
3138
|
+
onClick: () => setActiveCategory(tab.id),
|
|
3139
|
+
className: `
|
|
3140
|
+
flex-1 px-3 py-2 text-sm font-medium transition-colors
|
|
3141
|
+
${activeCategory === tab.id ? "bg-white border-b-2 border-blue-500 text-blue-600" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100"}
|
|
3142
|
+
`,
|
|
3143
|
+
children: [
|
|
3144
|
+
tab.label,
|
|
3145
|
+
/* @__PURE__ */ jsxs13("span", { className: "ml-1 text-xs text-gray-400", children: [
|
|
3146
|
+
"(",
|
|
3147
|
+
tab.count,
|
|
3148
|
+
")"
|
|
3149
|
+
] })
|
|
3150
|
+
]
|
|
3151
|
+
},
|
|
3152
|
+
tab.id
|
|
3153
|
+
)) }),
|
|
3154
|
+
/* @__PURE__ */ jsxs13("div", { className: "p-3", children: [
|
|
3155
|
+
activeVariables.length > 0 ? /* @__PURE__ */ jsx14("div", { className: "flex flex-wrap gap-2", children: activeVariables.map((variable) => /* @__PURE__ */ jsx14(
|
|
3156
|
+
DraggableVariable,
|
|
3157
|
+
{
|
|
3158
|
+
variable,
|
|
3159
|
+
onClick: onVariableClick,
|
|
3160
|
+
disabled
|
|
3161
|
+
},
|
|
3162
|
+
variable.variable_name
|
|
3163
|
+
)) }) : /* @__PURE__ */ jsx14("p", { className: "text-sm text-gray-500 text-center py-4", children: "No variables available in this category" }),
|
|
3164
|
+
/* @__PURE__ */ jsx14("p", { className: "text-xs text-gray-400 mt-3 text-center", children: "Drag variables to the pattern below, or click to add" })
|
|
3165
|
+
] })
|
|
3166
|
+
] });
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// src/ui/components/naming/PatternBuilder.tsx
|
|
3170
|
+
import { useCallback as useCallback12 } from "react";
|
|
3171
|
+
import { useDroppable } from "@dnd-kit/core";
|
|
3172
|
+
import {
|
|
3173
|
+
SortableContext,
|
|
3174
|
+
horizontalListSortingStrategy
|
|
3175
|
+
} from "@dnd-kit/sortable";
|
|
3176
|
+
|
|
3177
|
+
// src/ui/components/naming/PatternSegmentItem.tsx
|
|
3178
|
+
import { useState as useState11, useRef as useRef4, useEffect as useEffect7 } from "react";
|
|
3179
|
+
import { useSortable } from "@dnd-kit/sortable";
|
|
3180
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
3181
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3182
|
+
function getSegmentStyle(type) {
|
|
3183
|
+
if (type === "variable") {
|
|
3184
|
+
return "bg-blue-100 border-blue-300 text-blue-800";
|
|
3185
|
+
}
|
|
3186
|
+
return "bg-gray-100 border-gray-300 text-gray-700";
|
|
3187
|
+
}
|
|
3188
|
+
function PatternSegmentItem({
|
|
3189
|
+
segment,
|
|
3190
|
+
onUpdate,
|
|
3191
|
+
onDelete,
|
|
3192
|
+
readOnly = false,
|
|
3193
|
+
className = ""
|
|
3194
|
+
}) {
|
|
3195
|
+
const [isEditing, setIsEditing] = useState11(false);
|
|
3196
|
+
const [editValue, setEditValue] = useState11(segment.value);
|
|
3197
|
+
const inputRef = useRef4(null);
|
|
3198
|
+
const {
|
|
3199
|
+
attributes,
|
|
3200
|
+
listeners,
|
|
3201
|
+
setNodeRef,
|
|
3202
|
+
transform,
|
|
3203
|
+
transition,
|
|
3204
|
+
isDragging
|
|
3205
|
+
} = useSortable({
|
|
3206
|
+
id: segment.id,
|
|
3207
|
+
disabled: readOnly
|
|
3208
|
+
});
|
|
3209
|
+
const style = {
|
|
3210
|
+
transform: CSS.Transform.toString(transform),
|
|
3211
|
+
transition
|
|
3212
|
+
};
|
|
3213
|
+
useEffect7(() => {
|
|
3214
|
+
if (isEditing && inputRef.current) {
|
|
3215
|
+
inputRef.current.focus();
|
|
3216
|
+
inputRef.current.select();
|
|
3217
|
+
}
|
|
3218
|
+
}, [isEditing]);
|
|
3219
|
+
const handleSubmit = () => {
|
|
3220
|
+
if (segment.type === "literal" && editValue !== segment.value) {
|
|
3221
|
+
onUpdate?.(segment.id, editValue);
|
|
3222
|
+
}
|
|
3223
|
+
setIsEditing(false);
|
|
3224
|
+
};
|
|
3225
|
+
const handleKeyDown = (e) => {
|
|
3226
|
+
if (e.key === "Enter") {
|
|
3227
|
+
handleSubmit();
|
|
3228
|
+
} else if (e.key === "Escape") {
|
|
3229
|
+
setEditValue(segment.value);
|
|
3230
|
+
setIsEditing(false);
|
|
3231
|
+
} else if (e.key === "Delete" || e.key === "Backspace") {
|
|
3232
|
+
if (!isEditing) {
|
|
3233
|
+
e.preventDefault();
|
|
3234
|
+
onDelete?.(segment.id);
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
};
|
|
3238
|
+
const segmentStyle = getSegmentStyle(segment.type);
|
|
3239
|
+
return /* @__PURE__ */ jsxs14(
|
|
3240
|
+
"div",
|
|
3241
|
+
{
|
|
3242
|
+
ref: setNodeRef,
|
|
3243
|
+
style,
|
|
3244
|
+
className: `
|
|
3245
|
+
inline-flex items-center gap-1 rounded border text-sm
|
|
3246
|
+
${segmentStyle}
|
|
3247
|
+
${isDragging ? "opacity-50 shadow-lg z-50" : "shadow-sm"}
|
|
3248
|
+
${readOnly ? "" : "cursor-grab active:cursor-grabbing"}
|
|
3249
|
+
${className}
|
|
3250
|
+
`,
|
|
3251
|
+
...attributes,
|
|
3252
|
+
...listeners,
|
|
3253
|
+
onKeyDown: handleKeyDown,
|
|
3254
|
+
tabIndex: 0,
|
|
3255
|
+
children: [
|
|
3256
|
+
segment.type === "variable" ? (
|
|
3257
|
+
// Variable segment - not editable, just display
|
|
3258
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center px-2 py-1", children: [
|
|
3259
|
+
/* @__PURE__ */ jsx15("span", { className: "text-xs opacity-60", children: "{" }),
|
|
3260
|
+
/* @__PURE__ */ jsx15("span", { className: "font-medium", children: segment.value }),
|
|
3261
|
+
/* @__PURE__ */ jsx15("span", { className: "text-xs opacity-60", children: "}" })
|
|
3262
|
+
] })
|
|
3263
|
+
) : isEditing && !readOnly ? (
|
|
3264
|
+
// Literal segment in edit mode
|
|
3265
|
+
/* @__PURE__ */ jsx15(
|
|
3266
|
+
"input",
|
|
3267
|
+
{
|
|
3268
|
+
ref: inputRef,
|
|
3269
|
+
type: "text",
|
|
3270
|
+
value: editValue,
|
|
3271
|
+
onChange: (e) => setEditValue(e.target.value),
|
|
3272
|
+
onBlur: handleSubmit,
|
|
3273
|
+
onKeyDown: handleKeyDown,
|
|
3274
|
+
className: "px-2 py-1 bg-transparent border-none outline-none min-w-[40px] text-sm",
|
|
3275
|
+
style: { width: `${Math.max(40, editValue.length * 8)}px` }
|
|
3276
|
+
}
|
|
3277
|
+
)
|
|
3278
|
+
) : (
|
|
3279
|
+
// Literal segment in display mode
|
|
3280
|
+
/* @__PURE__ */ jsx15(
|
|
3281
|
+
"div",
|
|
3282
|
+
{
|
|
3283
|
+
className: "px-2 py-1 cursor-text",
|
|
3284
|
+
onDoubleClick: () => !readOnly && setIsEditing(true),
|
|
3285
|
+
children: segment.value || /* @__PURE__ */ jsx15("span", { className: "opacity-50 italic", children: "empty" })
|
|
3286
|
+
}
|
|
3287
|
+
)
|
|
3288
|
+
),
|
|
3289
|
+
!readOnly && /* @__PURE__ */ jsx15(
|
|
3290
|
+
"button",
|
|
3291
|
+
{
|
|
3292
|
+
type: "button",
|
|
3293
|
+
onClick: (e) => {
|
|
3294
|
+
e.stopPropagation();
|
|
3295
|
+
onDelete?.(segment.id);
|
|
3296
|
+
},
|
|
3297
|
+
className: "p-1 hover:bg-black/10 rounded transition-colors mr-1",
|
|
3298
|
+
title: "Remove segment",
|
|
3299
|
+
children: /* @__PURE__ */ jsxs14(
|
|
3300
|
+
"svg",
|
|
3301
|
+
{
|
|
3302
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3303
|
+
width: "12",
|
|
3304
|
+
height: "12",
|
|
3305
|
+
viewBox: "0 0 24 24",
|
|
3306
|
+
fill: "none",
|
|
3307
|
+
stroke: "currentColor",
|
|
3308
|
+
strokeWidth: "2",
|
|
3309
|
+
strokeLinecap: "round",
|
|
3310
|
+
strokeLinejoin: "round",
|
|
3311
|
+
children: [
|
|
3312
|
+
/* @__PURE__ */ jsx15("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
3313
|
+
/* @__PURE__ */ jsx15("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
3314
|
+
]
|
|
3315
|
+
}
|
|
3316
|
+
)
|
|
3317
|
+
}
|
|
3318
|
+
)
|
|
3319
|
+
]
|
|
3320
|
+
}
|
|
3321
|
+
);
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
// src/ui/components/naming/SeparatorPicker.tsx
|
|
3325
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3326
|
+
var COMMON_SEPARATORS = [
|
|
3327
|
+
{ value: "-", label: "-", title: "Hyphen" },
|
|
3328
|
+
{ value: "_", label: "_", title: "Underscore" },
|
|
3329
|
+
{ value: "/", label: "/", title: "Slash (for folder paths)" },
|
|
3330
|
+
{ value: " ", label: "\u2423", title: "Space" },
|
|
3331
|
+
{ value: ".", label: ".", title: "Period" }
|
|
3332
|
+
];
|
|
3333
|
+
function SeparatorPicker({
|
|
3334
|
+
onSelect,
|
|
3335
|
+
disabled = false,
|
|
3336
|
+
className = ""
|
|
3337
|
+
}) {
|
|
3338
|
+
return /* @__PURE__ */ jsxs15("div", { className: `flex items-center gap-1 ${className}`, children: [
|
|
3339
|
+
/* @__PURE__ */ jsx16("span", { className: "text-xs text-gray-500 mr-1", children: "Add:" }),
|
|
3340
|
+
COMMON_SEPARATORS.map(({ value, label, title }) => /* @__PURE__ */ jsx16(
|
|
3341
|
+
"button",
|
|
3342
|
+
{
|
|
3343
|
+
type: "button",
|
|
3344
|
+
onClick: () => onSelect(value),
|
|
3345
|
+
disabled,
|
|
3346
|
+
title,
|
|
3347
|
+
className: `
|
|
3348
|
+
w-7 h-7 flex items-center justify-center rounded border text-sm font-mono
|
|
3349
|
+
bg-gray-50 border-gray-200 text-gray-600
|
|
3350
|
+
hover:bg-gray-100 hover:border-gray-300
|
|
3351
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
3352
|
+
transition-colors
|
|
3353
|
+
`,
|
|
3354
|
+
children: label
|
|
3355
|
+
},
|
|
3356
|
+
value
|
|
3357
|
+
))
|
|
3358
|
+
] });
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
// src/ui/components/naming/PatternBuilder.tsx
|
|
3362
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3363
|
+
function DroppableZone({
|
|
3364
|
+
id,
|
|
3365
|
+
children,
|
|
3366
|
+
isEmpty,
|
|
3367
|
+
placeholder
|
|
3368
|
+
}) {
|
|
3369
|
+
const { setNodeRef, isOver } = useDroppable({ id });
|
|
3370
|
+
return /* @__PURE__ */ jsx17(
|
|
3371
|
+
"div",
|
|
3372
|
+
{
|
|
3373
|
+
ref: setNodeRef,
|
|
3374
|
+
className: `
|
|
3375
|
+
min-h-[48px] p-2 rounded-lg border-2 border-dashed transition-colors
|
|
3376
|
+
${isOver ? "border-blue-400 bg-blue-50" : isEmpty ? "border-gray-300 bg-gray-50" : "border-gray-200 bg-white"}
|
|
3377
|
+
`,
|
|
3378
|
+
children: isEmpty ? /* @__PURE__ */ jsx17("div", { className: "flex items-center justify-center h-8 text-gray-400 text-sm", children: placeholder }) : /* @__PURE__ */ jsx17("div", { className: "flex flex-wrap gap-1.5 items-center", children })
|
|
3379
|
+
}
|
|
3380
|
+
);
|
|
3381
|
+
}
|
|
3382
|
+
function PatternBuilder({
|
|
3383
|
+
pattern,
|
|
3384
|
+
label,
|
|
3385
|
+
droppableId,
|
|
3386
|
+
onAddSegment,
|
|
3387
|
+
onRemoveSegment,
|
|
3388
|
+
onUpdateSegment,
|
|
3389
|
+
onClear,
|
|
3390
|
+
readOnly = false,
|
|
3391
|
+
placeholder = "Drop variables here or type text...",
|
|
3392
|
+
className = ""
|
|
3393
|
+
}) {
|
|
3394
|
+
const handleAddSeparator = useCallback12(
|
|
3395
|
+
(separator) => {
|
|
3396
|
+
onAddSegment(createLiteralSegment(separator));
|
|
3397
|
+
},
|
|
3398
|
+
[onAddSegment]
|
|
3399
|
+
);
|
|
3400
|
+
const handleAddText = useCallback12(() => {
|
|
3401
|
+
onAddSegment(createLiteralSegment("text"));
|
|
3402
|
+
}, [onAddSegment]);
|
|
3403
|
+
return /* @__PURE__ */ jsxs16("div", { className: `space-y-2 ${className}`, children: [
|
|
3404
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center justify-between", children: [
|
|
3405
|
+
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium text-gray-700", children: label }),
|
|
3406
|
+
!readOnly && pattern.length > 0 && /* @__PURE__ */ jsx17(
|
|
3407
|
+
"button",
|
|
3408
|
+
{
|
|
3409
|
+
type: "button",
|
|
3410
|
+
onClick: onClear,
|
|
3411
|
+
className: "text-xs text-red-500 hover:text-red-700 transition-colors",
|
|
3412
|
+
children: "Clear"
|
|
3413
|
+
}
|
|
3414
|
+
)
|
|
3415
|
+
] }),
|
|
3416
|
+
/* @__PURE__ */ jsx17(
|
|
3417
|
+
SortableContext,
|
|
3418
|
+
{
|
|
3419
|
+
items: pattern.map((seg) => seg.id),
|
|
3420
|
+
strategy: horizontalListSortingStrategy,
|
|
3421
|
+
children: /* @__PURE__ */ jsx17(
|
|
3422
|
+
DroppableZone,
|
|
3423
|
+
{
|
|
3424
|
+
id: droppableId,
|
|
3425
|
+
isEmpty: pattern.length === 0,
|
|
3426
|
+
placeholder,
|
|
3427
|
+
children: pattern.map((segment) => /* @__PURE__ */ jsx17(
|
|
3428
|
+
PatternSegmentItem,
|
|
3429
|
+
{
|
|
3430
|
+
segment,
|
|
3431
|
+
onUpdate: onUpdateSegment,
|
|
3432
|
+
onDelete: onRemoveSegment,
|
|
3433
|
+
readOnly
|
|
3434
|
+
},
|
|
3435
|
+
segment.id
|
|
3436
|
+
))
|
|
3437
|
+
}
|
|
3438
|
+
)
|
|
3439
|
+
}
|
|
3440
|
+
),
|
|
3441
|
+
!readOnly && /* @__PURE__ */ jsxs16("div", { className: "flex items-center justify-between", children: [
|
|
3442
|
+
/* @__PURE__ */ jsx17(SeparatorPicker, { onSelect: handleAddSeparator, disabled: readOnly }),
|
|
3443
|
+
/* @__PURE__ */ jsx17(
|
|
3444
|
+
"button",
|
|
3445
|
+
{
|
|
3446
|
+
type: "button",
|
|
3447
|
+
onClick: handleAddText,
|
|
3448
|
+
className: "text-xs text-blue-600 hover:text-blue-800 transition-colors",
|
|
3449
|
+
children: "+ Add text"
|
|
3450
|
+
}
|
|
3451
|
+
)
|
|
3452
|
+
] })
|
|
3453
|
+
] });
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
// src/ui/components/naming/PatternPreview.tsx
|
|
3457
|
+
import { useMemo } from "react";
|
|
3458
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3459
|
+
function PatternPreview({
|
|
3460
|
+
filePattern,
|
|
3461
|
+
folderPattern,
|
|
3462
|
+
userVariables,
|
|
3463
|
+
sampleFileName = "document.pdf",
|
|
3464
|
+
previewDate,
|
|
3465
|
+
counterValue = 1,
|
|
3466
|
+
className = ""
|
|
3467
|
+
}) {
|
|
3468
|
+
const date = previewDate || /* @__PURE__ */ new Date();
|
|
3469
|
+
const filePreview = useMemo(() => {
|
|
3470
|
+
if (filePattern.length === 0) {
|
|
3471
|
+
return { name: "(no pattern defined)", isEmpty: true };
|
|
3472
|
+
}
|
|
3473
|
+
const baseName = generatePreviewName(filePattern, userVariables, {
|
|
3474
|
+
date,
|
|
3475
|
+
counterValue,
|
|
3476
|
+
originalFileName: sampleFileName
|
|
3477
|
+
});
|
|
3478
|
+
const ext = sampleFileName.includes(".") ? sampleFileName.substring(sampleFileName.lastIndexOf(".")) : "";
|
|
3479
|
+
const hasExtension = baseName.includes(".");
|
|
3480
|
+
return {
|
|
3481
|
+
name: hasExtension ? baseName : baseName + ext,
|
|
3482
|
+
isEmpty: false
|
|
3483
|
+
};
|
|
3484
|
+
}, [filePattern, userVariables, date, counterValue, sampleFileName]);
|
|
3485
|
+
const folderPreview = useMemo(() => {
|
|
3486
|
+
if (folderPattern.length === 0) {
|
|
3487
|
+
return { name: "(no pattern defined)", isEmpty: true };
|
|
3488
|
+
}
|
|
3489
|
+
return {
|
|
3490
|
+
name: generatePreviewName(folderPattern, userVariables, {
|
|
3491
|
+
date,
|
|
3492
|
+
counterValue,
|
|
3493
|
+
originalFileName: sampleFileName
|
|
3494
|
+
}),
|
|
3495
|
+
isEmpty: false
|
|
3496
|
+
};
|
|
3497
|
+
}, [folderPattern, userVariables, date, counterValue, sampleFileName]);
|
|
3498
|
+
const dateDisplay = date.toLocaleDateString("en-US", {
|
|
3499
|
+
weekday: "short",
|
|
3500
|
+
year: "numeric",
|
|
3501
|
+
month: "short",
|
|
3502
|
+
day: "numeric"
|
|
3503
|
+
});
|
|
3504
|
+
return /* @__PURE__ */ jsxs17(
|
|
3505
|
+
"div",
|
|
3506
|
+
{
|
|
3507
|
+
className: `bg-gradient-to-br from-gray-50 to-gray-100 rounded-lg border border-gray-200 p-4 ${className}`,
|
|
3508
|
+
children: [
|
|
3509
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-center justify-between mb-3", children: [
|
|
3510
|
+
/* @__PURE__ */ jsx18("h3", { className: "text-sm font-medium text-gray-700", children: "Preview" }),
|
|
3511
|
+
/* @__PURE__ */ jsx18("span", { className: "text-xs text-gray-400", children: dateDisplay })
|
|
3512
|
+
] }),
|
|
3513
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-3", children: [
|
|
3514
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-start gap-3", children: [
|
|
3515
|
+
/* @__PURE__ */ jsx18("div", { className: "flex-shrink-0 w-16 text-xs text-gray-500 pt-0.5", children: "File:" }),
|
|
3516
|
+
/* @__PURE__ */ jsx18(
|
|
3517
|
+
"div",
|
|
3518
|
+
{
|
|
3519
|
+
className: `
|
|
3520
|
+
flex-1 font-mono text-sm px-3 py-2 rounded bg-white border
|
|
3521
|
+
${filePreview.isEmpty ? "text-gray-400 border-gray-200" : "text-gray-800 border-gray-300"}
|
|
3522
|
+
`,
|
|
3523
|
+
children: /* @__PURE__ */ jsxs17("span", { className: "flex items-center gap-2", children: [
|
|
3524
|
+
/* @__PURE__ */ jsxs17(
|
|
3525
|
+
"svg",
|
|
3526
|
+
{
|
|
3527
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3528
|
+
width: "14",
|
|
3529
|
+
height: "14",
|
|
3530
|
+
viewBox: "0 0 24 24",
|
|
3531
|
+
fill: "none",
|
|
3532
|
+
stroke: "currentColor",
|
|
3533
|
+
strokeWidth: "2",
|
|
3534
|
+
strokeLinecap: "round",
|
|
3535
|
+
strokeLinejoin: "round",
|
|
3536
|
+
className: "text-gray-400",
|
|
3537
|
+
children: [
|
|
3538
|
+
/* @__PURE__ */ jsx18("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
3539
|
+
/* @__PURE__ */ jsx18("polyline", { points: "14,2 14,8 20,8" })
|
|
3540
|
+
]
|
|
3541
|
+
}
|
|
3542
|
+
),
|
|
3543
|
+
filePreview.name
|
|
3544
|
+
] })
|
|
3545
|
+
}
|
|
3546
|
+
)
|
|
3547
|
+
] }),
|
|
3548
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-start gap-3", children: [
|
|
3549
|
+
/* @__PURE__ */ jsx18("div", { className: "flex-shrink-0 w-16 text-xs text-gray-500 pt-0.5", children: "Folder:" }),
|
|
3550
|
+
/* @__PURE__ */ jsx18(
|
|
3551
|
+
"div",
|
|
3552
|
+
{
|
|
3553
|
+
className: `
|
|
3554
|
+
flex-1 font-mono text-sm px-3 py-2 rounded bg-white border
|
|
3555
|
+
${folderPreview.isEmpty ? "text-gray-400 border-gray-200" : "text-gray-800 border-gray-300"}
|
|
3556
|
+
`,
|
|
3557
|
+
children: /* @__PURE__ */ jsxs17("span", { className: "flex items-center gap-2", children: [
|
|
3558
|
+
/* @__PURE__ */ jsx18(
|
|
3559
|
+
"svg",
|
|
3560
|
+
{
|
|
3561
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3562
|
+
width: "14",
|
|
3563
|
+
height: "14",
|
|
3564
|
+
viewBox: "0 0 24 24",
|
|
3565
|
+
fill: "none",
|
|
3566
|
+
stroke: "currentColor",
|
|
3567
|
+
strokeWidth: "2",
|
|
3568
|
+
strokeLinecap: "round",
|
|
3569
|
+
strokeLinejoin: "round",
|
|
3570
|
+
className: "text-yellow-500",
|
|
3571
|
+
children: /* @__PURE__ */ jsx18("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" })
|
|
3572
|
+
}
|
|
3573
|
+
),
|
|
3574
|
+
folderPreview.name
|
|
3575
|
+
] })
|
|
3576
|
+
}
|
|
3577
|
+
)
|
|
3578
|
+
] }),
|
|
3579
|
+
!filePreview.isEmpty && !folderPreview.isEmpty && /* @__PURE__ */ jsxs17("div", { className: "flex items-start gap-3 pt-2 border-t border-gray-200", children: [
|
|
3580
|
+
/* @__PURE__ */ jsx18("div", { className: "flex-shrink-0 w-16 text-xs text-gray-500 pt-0.5", children: "Full path:" }),
|
|
3581
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex-1 font-mono text-xs text-gray-600 px-3 py-2 rounded bg-gray-100 border border-gray-200 overflow-x-auto", children: [
|
|
3582
|
+
"/",
|
|
3583
|
+
folderPreview.name,
|
|
3584
|
+
"/",
|
|
3585
|
+
filePreview.name
|
|
3586
|
+
] })
|
|
3587
|
+
] })
|
|
3588
|
+
] }),
|
|
3589
|
+
/* @__PURE__ */ jsx18("p", { className: "text-xs text-gray-400 mt-3 text-center", children: "Preview uses example values from your variables" })
|
|
3590
|
+
]
|
|
3591
|
+
}
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
// src/ui/components/naming/NamingRuleConfigurator.tsx
|
|
3596
|
+
import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3597
|
+
function NamingRuleConfigurator({
|
|
3598
|
+
variables,
|
|
3599
|
+
initialSchema,
|
|
3600
|
+
onChange,
|
|
3601
|
+
onExport,
|
|
3602
|
+
onImport,
|
|
3603
|
+
className = "",
|
|
3604
|
+
customDateFormats,
|
|
3605
|
+
readOnly = false,
|
|
3606
|
+
sampleFileName = "document.pdf"
|
|
3607
|
+
}) {
|
|
3608
|
+
const fileInputRef = useRef5(null);
|
|
3609
|
+
const [activeVariable, setActiveVariable] = useState12(null);
|
|
3610
|
+
const sensors = useSensors(
|
|
3611
|
+
useSensor(PointerSensor, {
|
|
3612
|
+
activationConstraint: {
|
|
3613
|
+
distance: 8
|
|
3614
|
+
}
|
|
3615
|
+
})
|
|
3616
|
+
);
|
|
3617
|
+
const {
|
|
3618
|
+
filePattern,
|
|
3619
|
+
folderPattern,
|
|
3620
|
+
canUndo,
|
|
3621
|
+
canRedo,
|
|
3622
|
+
isDirty,
|
|
3623
|
+
addToFilePattern,
|
|
3624
|
+
removeFromFilePattern,
|
|
3625
|
+
updateFilePatternSegment,
|
|
3626
|
+
reorderFilePattern,
|
|
3627
|
+
clearFilePattern,
|
|
3628
|
+
addToFolderPattern,
|
|
3629
|
+
removeFromFolderPattern,
|
|
3630
|
+
updateFolderPatternSegment,
|
|
3631
|
+
reorderFolderPattern,
|
|
3632
|
+
clearFolderPattern,
|
|
3633
|
+
undo,
|
|
3634
|
+
redo,
|
|
3635
|
+
getSchema,
|
|
3636
|
+
loadSchema
|
|
3637
|
+
} = useNamingRule({
|
|
3638
|
+
initialSchema,
|
|
3639
|
+
onChange
|
|
3640
|
+
});
|
|
3641
|
+
const handleVariableClick = useCallback13(
|
|
3642
|
+
(variable) => {
|
|
3643
|
+
addToFilePattern(createVariableSegment(variable.variable_name));
|
|
3644
|
+
},
|
|
3645
|
+
[addToFilePattern]
|
|
3646
|
+
);
|
|
3647
|
+
const handleDragStart = useCallback13((event) => {
|
|
3648
|
+
const { active } = event;
|
|
3649
|
+
if (String(active.id).startsWith("draggable-")) {
|
|
3650
|
+
const data = active.data.current;
|
|
3651
|
+
if (data?.type === "variable" && data.variable) {
|
|
3652
|
+
setActiveVariable(data.variable);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
}, []);
|
|
3656
|
+
const handleDragEnd = useCallback13(
|
|
3657
|
+
(event) => {
|
|
3658
|
+
const { active, over } = event;
|
|
3659
|
+
setActiveVariable(null);
|
|
3660
|
+
if (!over) return;
|
|
3661
|
+
const activeId = String(active.id);
|
|
3662
|
+
const overId = String(over.id);
|
|
3663
|
+
if (activeId.startsWith("draggable-")) {
|
|
3664
|
+
const data = active.data.current;
|
|
3665
|
+
if (data?.type === "variable" && data.variable) {
|
|
3666
|
+
const segment = createVariableSegment(data.variable.variable_name);
|
|
3667
|
+
if (overId === "file-pattern-drop") {
|
|
3668
|
+
addToFilePattern(segment);
|
|
3669
|
+
} else if (overId === "folder-pattern-drop") {
|
|
3670
|
+
addToFolderPattern(segment);
|
|
3671
|
+
} else {
|
|
3672
|
+
const fileIndex = filePattern.findIndex((seg) => seg.id === overId);
|
|
3673
|
+
const folderIndex = folderPattern.findIndex((seg) => seg.id === overId);
|
|
3674
|
+
if (fileIndex >= 0) {
|
|
3675
|
+
addToFilePattern(segment, fileIndex + 1);
|
|
3676
|
+
} else if (folderIndex >= 0) {
|
|
3677
|
+
addToFolderPattern(segment, folderIndex + 1);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
if (activeId !== overId) {
|
|
3684
|
+
const activeFileIndex = filePattern.findIndex((seg) => seg.id === activeId);
|
|
3685
|
+
const overFileIndex = filePattern.findIndex((seg) => seg.id === overId);
|
|
3686
|
+
if (activeFileIndex >= 0 && overFileIndex >= 0) {
|
|
3687
|
+
reorderFilePattern(activeFileIndex, overFileIndex);
|
|
3688
|
+
return;
|
|
3689
|
+
}
|
|
3690
|
+
const activeFolderIndex = folderPattern.findIndex((seg) => seg.id === activeId);
|
|
3691
|
+
const overFolderIndex = folderPattern.findIndex((seg) => seg.id === overId);
|
|
3692
|
+
if (activeFolderIndex >= 0 && overFolderIndex >= 0) {
|
|
3693
|
+
reorderFolderPattern(activeFolderIndex, overFolderIndex);
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
},
|
|
3698
|
+
[filePattern, folderPattern, addToFilePattern, addToFolderPattern, reorderFilePattern, reorderFolderPattern]
|
|
3699
|
+
);
|
|
3700
|
+
const handleExport = useCallback13(() => {
|
|
3701
|
+
const schema = getSchema();
|
|
3702
|
+
schema.metadata = {
|
|
3703
|
+
...schema.metadata,
|
|
3704
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3705
|
+
};
|
|
3706
|
+
if (onExport) {
|
|
3707
|
+
onExport(schema);
|
|
3708
|
+
} else {
|
|
3709
|
+
const blob = new Blob([JSON.stringify(schema, null, 2)], {
|
|
3710
|
+
type: "application/json"
|
|
3711
|
+
});
|
|
3712
|
+
const url = URL.createObjectURL(blob);
|
|
3713
|
+
const a = document.createElement("a");
|
|
3714
|
+
a.href = url;
|
|
3715
|
+
a.download = "naming-rule.json";
|
|
3716
|
+
document.body.appendChild(a);
|
|
3717
|
+
a.click();
|
|
3718
|
+
document.body.removeChild(a);
|
|
3719
|
+
URL.revokeObjectURL(url);
|
|
3720
|
+
}
|
|
3721
|
+
}, [getSchema, onExport]);
|
|
3722
|
+
const handleImportClick = useCallback13(() => {
|
|
3723
|
+
fileInputRef.current?.click();
|
|
3724
|
+
}, []);
|
|
3725
|
+
const handleFileChange = useCallback13(
|
|
3726
|
+
async (e) => {
|
|
3727
|
+
const file = e.target.files?.[0];
|
|
3728
|
+
if (!file) return;
|
|
3729
|
+
try {
|
|
3730
|
+
const text = await file.text();
|
|
3731
|
+
const schema = JSON.parse(text);
|
|
3732
|
+
if (validateNamingRuleSchema(schema)) {
|
|
3733
|
+
loadSchema(schema);
|
|
3734
|
+
onImport?.(schema);
|
|
3735
|
+
} else {
|
|
3736
|
+
console.error("Invalid naming rule schema");
|
|
3737
|
+
alert("Invalid naming rule schema. Please check the file format.");
|
|
3738
|
+
}
|
|
3739
|
+
} catch (err) {
|
|
3740
|
+
console.error("Failed to parse JSON:", err);
|
|
3741
|
+
alert("Failed to parse JSON file. Please ensure it is valid JSON.");
|
|
3742
|
+
}
|
|
3743
|
+
e.target.value = "";
|
|
3744
|
+
},
|
|
3745
|
+
[loadSchema, onImport]
|
|
3746
|
+
);
|
|
3747
|
+
return /* @__PURE__ */ jsxs18(
|
|
3748
|
+
DndContext,
|
|
3749
|
+
{
|
|
3750
|
+
sensors,
|
|
3751
|
+
collisionDetection: closestCenter,
|
|
3752
|
+
onDragStart: handleDragStart,
|
|
3753
|
+
onDragEnd: handleDragEnd,
|
|
3754
|
+
children: [
|
|
3755
|
+
/* @__PURE__ */ jsxs18(
|
|
3756
|
+
"div",
|
|
3757
|
+
{
|
|
3758
|
+
className: `flex flex-col bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden h-full ${className}`,
|
|
3759
|
+
children: [
|
|
3760
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex-1 overflow-y-auto min-h-0", children: [
|
|
3761
|
+
/* @__PURE__ */ jsxs18("div", { className: "p-4 bg-gray-50 border-b border-gray-200", children: [
|
|
3762
|
+
/* @__PURE__ */ jsx19("h3", { className: "text-sm font-semibold text-gray-800 mb-3", children: "Available Variables" }),
|
|
3763
|
+
/* @__PURE__ */ jsx19(
|
|
3764
|
+
VariableList,
|
|
3765
|
+
{
|
|
3766
|
+
userVariables: variables,
|
|
3767
|
+
customDateFormats,
|
|
3768
|
+
onVariableClick: handleVariableClick,
|
|
3769
|
+
disabled: readOnly
|
|
3770
|
+
}
|
|
3771
|
+
)
|
|
3772
|
+
] }),
|
|
3773
|
+
/* @__PURE__ */ jsxs18("div", { className: "p-4 space-y-6", children: [
|
|
3774
|
+
/* @__PURE__ */ jsx19(
|
|
3775
|
+
PatternBuilder,
|
|
3776
|
+
{
|
|
3777
|
+
pattern: filePattern,
|
|
3778
|
+
label: "File Name Pattern",
|
|
3779
|
+
droppableId: "file-pattern-drop",
|
|
3780
|
+
onPatternChange: () => {
|
|
3781
|
+
},
|
|
3782
|
+
onAddSegment: addToFilePattern,
|
|
3783
|
+
onRemoveSegment: removeFromFilePattern,
|
|
3784
|
+
onUpdateSegment: updateFilePatternSegment,
|
|
3785
|
+
onReorderSegments: reorderFilePattern,
|
|
3786
|
+
onClear: clearFilePattern,
|
|
3787
|
+
readOnly,
|
|
3788
|
+
placeholder: "Drop variables here or click to add..."
|
|
3789
|
+
}
|
|
3790
|
+
),
|
|
3791
|
+
/* @__PURE__ */ jsx19(
|
|
3792
|
+
PatternBuilder,
|
|
3793
|
+
{
|
|
3794
|
+
pattern: folderPattern,
|
|
3795
|
+
label: "Folder Name Pattern",
|
|
3796
|
+
droppableId: "folder-pattern-drop",
|
|
3797
|
+
onPatternChange: () => {
|
|
3798
|
+
},
|
|
3799
|
+
onAddSegment: addToFolderPattern,
|
|
3800
|
+
onRemoveSegment: removeFromFolderPattern,
|
|
3801
|
+
onUpdateSegment: updateFolderPatternSegment,
|
|
3802
|
+
onReorderSegments: reorderFolderPattern,
|
|
3803
|
+
onClear: clearFolderPattern,
|
|
3804
|
+
readOnly,
|
|
3805
|
+
placeholder: "Drop variables here for folder structure..."
|
|
3806
|
+
}
|
|
3807
|
+
)
|
|
3808
|
+
] }),
|
|
3809
|
+
/* @__PURE__ */ jsx19("div", { className: "p-4 border-t border-gray-200", children: /* @__PURE__ */ jsx19(
|
|
3810
|
+
PatternPreview,
|
|
3811
|
+
{
|
|
3812
|
+
filePattern,
|
|
3813
|
+
folderPattern,
|
|
3814
|
+
userVariables: variables,
|
|
3815
|
+
sampleFileName
|
|
3816
|
+
}
|
|
3817
|
+
) })
|
|
3818
|
+
] }),
|
|
3819
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-center justify-between px-4 py-3 bg-gray-50 border-t border-gray-200", children: [
|
|
3820
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", children: [
|
|
3821
|
+
/* @__PURE__ */ jsx19(
|
|
3822
|
+
"button",
|
|
3823
|
+
{
|
|
3824
|
+
type: "button",
|
|
3825
|
+
onClick: undo,
|
|
3826
|
+
disabled: !canUndo || readOnly,
|
|
3827
|
+
className: `
|
|
3828
|
+
p-2 rounded hover:bg-gray-200 transition-colors
|
|
3829
|
+
${canUndo && !readOnly ? "text-gray-600" : "text-gray-300 cursor-not-allowed"}
|
|
3830
|
+
`,
|
|
3831
|
+
title: "Undo (Ctrl+Z)",
|
|
3832
|
+
children: /* @__PURE__ */ jsxs18(
|
|
3833
|
+
"svg",
|
|
3834
|
+
{
|
|
3835
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3836
|
+
width: "16",
|
|
3837
|
+
height: "16",
|
|
3838
|
+
viewBox: "0 0 24 24",
|
|
3839
|
+
fill: "none",
|
|
3840
|
+
stroke: "currentColor",
|
|
3841
|
+
strokeWidth: "2",
|
|
3842
|
+
strokeLinecap: "round",
|
|
3843
|
+
strokeLinejoin: "round",
|
|
3844
|
+
children: [
|
|
3845
|
+
/* @__PURE__ */ jsx19("path", { d: "M3 7v6h6" }),
|
|
3846
|
+
/* @__PURE__ */ jsx19("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" })
|
|
3847
|
+
]
|
|
3848
|
+
}
|
|
3849
|
+
)
|
|
3850
|
+
}
|
|
3851
|
+
),
|
|
3852
|
+
/* @__PURE__ */ jsx19(
|
|
3853
|
+
"button",
|
|
3854
|
+
{
|
|
3855
|
+
type: "button",
|
|
3856
|
+
onClick: redo,
|
|
3857
|
+
disabled: !canRedo || readOnly,
|
|
3858
|
+
className: `
|
|
3859
|
+
p-2 rounded hover:bg-gray-200 transition-colors
|
|
3860
|
+
${canRedo && !readOnly ? "text-gray-600" : "text-gray-300 cursor-not-allowed"}
|
|
3861
|
+
`,
|
|
3862
|
+
title: "Redo (Ctrl+Y)",
|
|
3863
|
+
children: /* @__PURE__ */ jsxs18(
|
|
3864
|
+
"svg",
|
|
3865
|
+
{
|
|
3866
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3867
|
+
width: "16",
|
|
3868
|
+
height: "16",
|
|
3869
|
+
viewBox: "0 0 24 24",
|
|
3870
|
+
fill: "none",
|
|
3871
|
+
stroke: "currentColor",
|
|
3872
|
+
strokeWidth: "2",
|
|
3873
|
+
strokeLinecap: "round",
|
|
3874
|
+
strokeLinejoin: "round",
|
|
3875
|
+
children: [
|
|
3876
|
+
/* @__PURE__ */ jsx19("path", { d: "M21 7v6h-6" }),
|
|
3877
|
+
/* @__PURE__ */ jsx19("path", { d: "M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7" })
|
|
3878
|
+
]
|
|
3879
|
+
}
|
|
3880
|
+
)
|
|
3881
|
+
}
|
|
3882
|
+
),
|
|
3883
|
+
isDirty && /* @__PURE__ */ jsx19("span", { className: "text-xs text-amber-600 ml-2", children: "* Unsaved changes" })
|
|
3884
|
+
] }),
|
|
3885
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", children: [
|
|
3886
|
+
/* @__PURE__ */ jsx19(
|
|
3887
|
+
"input",
|
|
3888
|
+
{
|
|
3889
|
+
ref: fileInputRef,
|
|
3890
|
+
type: "file",
|
|
3891
|
+
accept: ".json",
|
|
3892
|
+
onChange: handleFileChange,
|
|
3893
|
+
className: "hidden"
|
|
3894
|
+
}
|
|
3895
|
+
),
|
|
3896
|
+
/* @__PURE__ */ jsx19(
|
|
3897
|
+
"button",
|
|
3898
|
+
{
|
|
3899
|
+
type: "button",
|
|
3900
|
+
onClick: handleImportClick,
|
|
3901
|
+
disabled: readOnly,
|
|
3902
|
+
className: `
|
|
3903
|
+
px-3 py-1.5 text-sm rounded border transition-colors
|
|
3904
|
+
${readOnly ? "bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed" : "bg-white text-gray-600 border-gray-300 hover:bg-gray-50"}
|
|
3905
|
+
`,
|
|
3906
|
+
children: "Import"
|
|
3907
|
+
}
|
|
3908
|
+
),
|
|
3909
|
+
/* @__PURE__ */ jsx19(
|
|
3910
|
+
"button",
|
|
3911
|
+
{
|
|
3912
|
+
type: "button",
|
|
3913
|
+
onClick: handleExport,
|
|
3914
|
+
className: "px-3 py-1.5 text-sm rounded bg-blue-600 text-white hover:bg-blue-700 transition-colors",
|
|
3915
|
+
children: "Export"
|
|
3916
|
+
}
|
|
3917
|
+
)
|
|
3918
|
+
] })
|
|
3919
|
+
] })
|
|
3920
|
+
]
|
|
3921
|
+
}
|
|
3922
|
+
),
|
|
3923
|
+
/* @__PURE__ */ jsx19(DragOverlay, { children: activeVariable ? /* @__PURE__ */ jsx19(
|
|
3924
|
+
DraggableVariable,
|
|
3925
|
+
{
|
|
3926
|
+
variable: activeVariable,
|
|
3927
|
+
className: "shadow-xl opacity-90"
|
|
3928
|
+
}
|
|
3929
|
+
) : null })
|
|
3930
|
+
]
|
|
3931
|
+
}
|
|
3932
|
+
);
|
|
3933
|
+
}
|
|
3934
|
+
export {
|
|
3935
|
+
ArchiveIcon,
|
|
3936
|
+
AudioIcon,
|
|
3937
|
+
ChevronDownIcon,
|
|
3938
|
+
ChevronRightIcon,
|
|
3939
|
+
CodeIcon,
|
|
3940
|
+
CreateFolderDialog,
|
|
3941
|
+
DeleteConfirmDialog,
|
|
3942
|
+
DownloadIcon,
|
|
3943
|
+
DraggableVariable,
|
|
3944
|
+
FileActions,
|
|
3945
|
+
FileBrowser,
|
|
3946
|
+
FileBrowserProvider,
|
|
3947
|
+
FileIcon,
|
|
3948
|
+
FileList,
|
|
3949
|
+
FilePreview,
|
|
3950
|
+
FileTextIcon,
|
|
3951
|
+
FolderIcon,
|
|
3952
|
+
FolderOpenIcon,
|
|
3953
|
+
FolderTree,
|
|
3954
|
+
HomeIcon,
|
|
3955
|
+
ImageIcon,
|
|
3956
|
+
LoaderIcon,
|
|
3957
|
+
MoreVerticalIcon,
|
|
3958
|
+
NamingRuleConfigurator,
|
|
3959
|
+
PathBreadcrumb,
|
|
3960
|
+
PatternBuilder,
|
|
3961
|
+
PatternPreview,
|
|
3962
|
+
PatternSegmentItem,
|
|
3963
|
+
PdfIcon,
|
|
3964
|
+
PencilIcon,
|
|
3965
|
+
PlusIcon,
|
|
3966
|
+
RefreshIcon,
|
|
3967
|
+
RenameDialog,
|
|
3968
|
+
SeparatorPicker,
|
|
3969
|
+
TrashIcon,
|
|
3970
|
+
UploadDialog,
|
|
3971
|
+
UploadIcon,
|
|
3972
|
+
VariableList,
|
|
3973
|
+
VideoIcon,
|
|
3974
|
+
XIcon,
|
|
3975
|
+
getFileIcon,
|
|
3976
|
+
useFileBrowser,
|
|
3977
|
+
useFileBrowserContext,
|
|
3978
|
+
useFileOperations,
|
|
3979
|
+
useMultiFileOperations,
|
|
3980
|
+
useNamingRule
|
|
3981
|
+
};
|
|
3982
|
+
//# sourceMappingURL=index.mjs.map
|