@thetechfossil/upfiles 1.0.9 → 1.0.11
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/README.md +8 -0
- package/dist/index.d.mts +45 -5
- package/dist/index.d.ts +45 -5
- package/dist/index.js +731 -250
- package/dist/index.mjs +732 -251
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ var UpfilesClient = class {
|
|
|
59
59
|
this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
|
|
60
60
|
this.thumbnailsPath = opts.thumbnailsPath ?? "/api/plugin/thumbnails";
|
|
61
61
|
this.completionPath = opts.completionPath ?? "/api/plugin/upload/complete";
|
|
62
|
+
this.foldersPath = opts.foldersPath ?? "/api/plugin/folders";
|
|
62
63
|
this.callCompletionEndpoint = opts.callCompletionEndpoint ?? false;
|
|
63
64
|
}
|
|
64
65
|
async getPresignedUrl(params) {
|
|
@@ -181,6 +182,7 @@ var UpfilesClient = class {
|
|
|
181
182
|
}
|
|
182
183
|
throw new Error(msg);
|
|
183
184
|
}
|
|
185
|
+
return res.json();
|
|
184
186
|
}
|
|
185
187
|
async getThumbnails(fileKey) {
|
|
186
188
|
const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
|
|
@@ -241,6 +243,65 @@ var UpfilesClient = class {
|
|
|
241
243
|
const data = await res.json();
|
|
242
244
|
return { masterWebp: data.masterWebp, thumbnails: data.thumbnails ?? [] };
|
|
243
245
|
}
|
|
246
|
+
async getFolders(parentId) {
|
|
247
|
+
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
248
|
+
const url = parentId ? `${target}?parentId=${encodeURIComponent(parentId)}` : target;
|
|
249
|
+
const headers = { ...this.headers || {} };
|
|
250
|
+
if (this.apiKey) {
|
|
251
|
+
if (this.apiKeyHeader === "authorization") {
|
|
252
|
+
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
253
|
+
} else if (this.apiKeyHeader === "x-api-key") {
|
|
254
|
+
headers["x-api-key"] = this.apiKey;
|
|
255
|
+
} else {
|
|
256
|
+
headers["x-up-api-key"] = this.apiKey;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
260
|
+
if (!res.ok) {
|
|
261
|
+
let msg = "Failed to fetch folders";
|
|
262
|
+
try {
|
|
263
|
+
const data2 = await res.json();
|
|
264
|
+
msg = data2.error || msg;
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
throw new Error(msg);
|
|
268
|
+
}
|
|
269
|
+
const data = await res.json();
|
|
270
|
+
return data?.folders ?? [];
|
|
271
|
+
}
|
|
272
|
+
async createFolder(name, parentId) {
|
|
273
|
+
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
274
|
+
const headers = {
|
|
275
|
+
"content-type": "application/json",
|
|
276
|
+
...this.headers || {}
|
|
277
|
+
};
|
|
278
|
+
if (this.apiKey) {
|
|
279
|
+
if (this.apiKeyHeader === "authorization") {
|
|
280
|
+
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
281
|
+
} else if (this.apiKeyHeader === "x-api-key") {
|
|
282
|
+
headers["x-api-key"] = this.apiKey;
|
|
283
|
+
} else {
|
|
284
|
+
headers["x-up-api-key"] = this.apiKey;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const res = await fetch(target, {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers,
|
|
290
|
+
credentials: this.withCredentials ? "include" : "same-origin",
|
|
291
|
+
body: JSON.stringify({ name, parentId })
|
|
292
|
+
});
|
|
293
|
+
if (!res.ok) {
|
|
294
|
+
let msg = "Failed to create folder";
|
|
295
|
+
try {
|
|
296
|
+
const data2 = await res.json();
|
|
297
|
+
msg = data2.error || msg;
|
|
298
|
+
} catch {
|
|
299
|
+
}
|
|
300
|
+
throw new Error(msg);
|
|
301
|
+
}
|
|
302
|
+
const data = await res.json();
|
|
303
|
+
return data.folder;
|
|
304
|
+
}
|
|
244
305
|
async getProjectFiles(params) {
|
|
245
306
|
const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
|
|
246
307
|
const filesPath = `${basePath}/files`;
|
|
@@ -267,7 +328,23 @@ var UpfilesClient = class {
|
|
|
267
328
|
throw new Error(msg);
|
|
268
329
|
}
|
|
269
330
|
const data = await res.json();
|
|
270
|
-
return
|
|
331
|
+
return {
|
|
332
|
+
files: data?.files ?? [],
|
|
333
|
+
updatedAt: res.headers.get("X-Files-Updated-At")
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
getEventsUrl(projectId) {
|
|
337
|
+
const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
|
|
338
|
+
const eventsPath = `${basePath}/events`;
|
|
339
|
+
const target = this.baseUrl ? `${this.baseUrl}/api/events` : "/api/events";
|
|
340
|
+
const url = new URL(target, window.location.href);
|
|
341
|
+
if (this.apiKey) {
|
|
342
|
+
url.searchParams.set("apiKey", this.apiKey);
|
|
343
|
+
}
|
|
344
|
+
if (projectId) {
|
|
345
|
+
url.searchParams.set("projectId", projectId);
|
|
346
|
+
}
|
|
347
|
+
return url.toString();
|
|
271
348
|
}
|
|
272
349
|
};
|
|
273
350
|
|
|
@@ -292,7 +369,7 @@ var Uploader = ({
|
|
|
292
369
|
projectId,
|
|
293
370
|
folderPath,
|
|
294
371
|
fetchThumbnails,
|
|
295
|
-
autoRecordToDb =
|
|
372
|
+
autoRecordToDb = true,
|
|
296
373
|
recordUrl = "/api/files",
|
|
297
374
|
autoUpload = false
|
|
298
375
|
}) => {
|
|
@@ -366,9 +443,10 @@ var Uploader = ({
|
|
|
366
443
|
xhr.setRequestHeader("Content-Type", item.type || "application/octet-stream");
|
|
367
444
|
xhr.send(item.file);
|
|
368
445
|
});
|
|
446
|
+
let completionResult = null;
|
|
369
447
|
if (autoRecordToDb && projectId) {
|
|
370
448
|
try {
|
|
371
|
-
await client.completeUpload({
|
|
449
|
+
completionResult = await client.completeUpload({
|
|
372
450
|
fileKey: presign.fileKey,
|
|
373
451
|
originalName: item.name,
|
|
374
452
|
size: item.size,
|
|
@@ -382,15 +460,21 @@ var Uploader = ({
|
|
|
382
460
|
}
|
|
383
461
|
let thumbs;
|
|
384
462
|
let masterWebp;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
463
|
+
if (completionResult?.thumbnails?.length > 0) {
|
|
464
|
+
thumbs = completionResult.thumbnails;
|
|
465
|
+
masterWebp = completionResult.masterWebp;
|
|
466
|
+
} else if (fetchThumbnails) {
|
|
467
|
+
const fileKeyToUse = completionResult?.file?.key || presign.fileKey;
|
|
468
|
+
try {
|
|
469
|
+
const result2 = await client.generateThumbnails(fileKeyToUse);
|
|
388
470
|
thumbs = result2.thumbnails;
|
|
389
471
|
masterWebp = result2.masterWebp;
|
|
472
|
+
} catch (e) {
|
|
473
|
+
console.warn("Failed to generate thumbnails:", e);
|
|
390
474
|
}
|
|
391
|
-
} catch (e) {
|
|
392
|
-
console.warn("Failed to generate thumbnails:", e);
|
|
393
475
|
}
|
|
476
|
+
const finalFileKey = completionResult?.file?.key || presign.fileKey;
|
|
477
|
+
const finalPublicUrl = completionResult?.file?.url || presign.publicUrl;
|
|
394
478
|
const result = {
|
|
395
479
|
...item,
|
|
396
480
|
status: "success",
|
|
@@ -453,10 +537,10 @@ var Uploader = ({
|
|
|
453
537
|
onChange: onInputChange
|
|
454
538
|
}
|
|
455
539
|
),
|
|
456
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
540
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
457
541
|
"div",
|
|
458
542
|
{
|
|
459
|
-
className: dropzoneClassName
|
|
543
|
+
className: `${dropzoneClassName} border-2 border-dashed rounded-xl transition-all duration-200 cursor-pointer p-10 flex flex-col items-center justify-center gap-4 ${isDragOver ? "border-blue-500 bg-blue-50 dark:bg-blue-900/20" : "border-gray-200 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-700 bg-gray-50/50 dark:bg-gray-900/50"}`,
|
|
460
544
|
onDragOver: (e) => {
|
|
461
545
|
e.preventDefault();
|
|
462
546
|
e.stopPropagation();
|
|
@@ -471,51 +555,121 @@ var Uploader = ({
|
|
|
471
555
|
onClick: () => inputRef.current?.click(),
|
|
472
556
|
role: "button",
|
|
473
557
|
"aria-label": "Upload files",
|
|
474
|
-
children:
|
|
475
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
"
|
|
479
|
-
|
|
480
|
-
|
|
558
|
+
children: [
|
|
559
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-16 h-16 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center text-blue-600 dark:text-blue-400", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
560
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
561
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "17 8 12 3 7 8" }),
|
|
562
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
563
|
+
] }) }),
|
|
564
|
+
children ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-center", children: [
|
|
565
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Drop files here or click to browse" }),
|
|
566
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
|
|
567
|
+
multiple ? `Up to ${maxFiles} files` : "Single file",
|
|
568
|
+
" \u2022 Max ",
|
|
569
|
+
(maxFileSize / (1024 * 1024)).toFixed(0),
|
|
570
|
+
"MB"
|
|
571
|
+
] })
|
|
481
572
|
] })
|
|
482
|
-
]
|
|
573
|
+
]
|
|
483
574
|
}
|
|
484
575
|
),
|
|
485
|
-
files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
486
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
487
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
576
|
+
files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-8 space-y-4", children: [
|
|
577
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-4", children: [
|
|
578
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { className: "font-semibold text-gray-900 dark:text-gray-100", children: "Selected Files" }),
|
|
579
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-3", children: [
|
|
580
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
581
|
+
"button",
|
|
582
|
+
{
|
|
583
|
+
className: "px-4 py-2 bg-blue-600 text-white text-sm font-semibold rounded-lg hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors shadow-sm",
|
|
584
|
+
disabled: isUploading || files.every((f2) => f2.status !== "pending"),
|
|
585
|
+
onClick: uploadAll,
|
|
586
|
+
children: isUploading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-2", children: [
|
|
587
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "animate-spin h-4 w-4 text-white", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
|
|
588
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
589
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })
|
|
590
|
+
] }),
|
|
591
|
+
"Uploading..."
|
|
592
|
+
] }) : "Upload All"
|
|
593
|
+
}
|
|
594
|
+
),
|
|
595
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
596
|
+
"button",
|
|
597
|
+
{
|
|
598
|
+
className: "px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm font-semibold rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-50 transition-colors",
|
|
599
|
+
disabled: isUploading,
|
|
600
|
+
onClick: () => setFiles([]),
|
|
601
|
+
children: "Clear"
|
|
602
|
+
}
|
|
603
|
+
)
|
|
604
|
+
] })
|
|
505
605
|
] }),
|
|
506
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("
|
|
507
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
508
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("
|
|
509
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
510
|
-
|
|
511
|
-
|
|
606
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid gap-3", children: files.map((f2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "group relative bg-white dark:bg-gray-900 border border-gray-100 dark:border-gray-800 rounded-xl p-4 transition-all hover:shadow-md", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-4", children: [
|
|
607
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-10 h-10 rounded-lg bg-gray-50 dark:bg-gray-800 flex items-center justify-center flex-shrink-0", children: f2.type.startsWith("image/") ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-indigo-500", children: [
|
|
608
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
609
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
610
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "21 15 16 10 5 21" })
|
|
611
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-blue-500", children: [
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime.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" }),
|
|
613
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "14 2 14 8 20 8" })
|
|
614
|
+
] }) }),
|
|
615
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 min-w-0", children: [
|
|
616
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
|
|
617
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100 truncate pr-4", children: f2.name }),
|
|
618
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-xs text-gray-500 font-medium whitespace-nowrap", children: [
|
|
619
|
+
(f2.size / (1024 * 1024)).toFixed(2),
|
|
620
|
+
" MB"
|
|
621
|
+
] })
|
|
512
622
|
] }),
|
|
513
|
-
f2.error
|
|
514
|
-
|
|
623
|
+
f2.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-xs text-red-500 font-medium mt-1", children: f2.error }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-1.5 w-full bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
624
|
+
"div",
|
|
625
|
+
{
|
|
626
|
+
className: `h-full transition-all duration-300 ${f2.status === "success" ? "bg-green-500" : "bg-blue-500"}`,
|
|
627
|
+
style: { width: `${f2.progress}%` }
|
|
628
|
+
}
|
|
629
|
+
) })
|
|
515
630
|
] }),
|
|
516
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
517
|
-
|
|
518
|
-
|
|
631
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-shrink-0 ml-4", children: [
|
|
632
|
+
f2.status === "success" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
633
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "20 6 9 17 4 12" }) }) }),
|
|
634
|
+
f2.publicUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
635
|
+
"a",
|
|
636
|
+
{
|
|
637
|
+
href: f2.publicUrl,
|
|
638
|
+
target: "_blank",
|
|
639
|
+
rel: "noreferrer",
|
|
640
|
+
className: "p-1.5 text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-colors",
|
|
641
|
+
title: "View online",
|
|
642
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "15 3 21 3 21 9" }),
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
|
|
646
|
+
] })
|
|
647
|
+
}
|
|
648
|
+
)
|
|
649
|
+
] }),
|
|
650
|
+
f2.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
651
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
652
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
653
|
+
] }) }),
|
|
654
|
+
f2.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-xs font-semibold text-blue-600 dark:text-blue-400", children: [
|
|
655
|
+
f2.progress,
|
|
656
|
+
"%"
|
|
657
|
+
] }),
|
|
658
|
+
f2.status === "pending" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
659
|
+
"button",
|
|
660
|
+
{
|
|
661
|
+
onClick: (e) => {
|
|
662
|
+
e.stopPropagation();
|
|
663
|
+
setFiles((prev) => prev.filter((file) => file.id !== f2.id));
|
|
664
|
+
},
|
|
665
|
+
className: "p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-colors",
|
|
666
|
+
title: "Remove",
|
|
667
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
668
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
669
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
670
|
+
] })
|
|
671
|
+
}
|
|
672
|
+
)
|
|
519
673
|
] })
|
|
520
674
|
] }) }, f2.id)) })
|
|
521
675
|
] })
|
|
@@ -523,7 +677,7 @@ var Uploader = ({
|
|
|
523
677
|
};
|
|
524
678
|
|
|
525
679
|
// src/ProjectFilesWidget.tsx
|
|
526
|
-
var import_react2 = require("react");
|
|
680
|
+
var import_react2 = __toESM(require("react"));
|
|
527
681
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
528
682
|
var ProjectFilesWidget = ({
|
|
529
683
|
clientOptions,
|
|
@@ -537,7 +691,8 @@ var ProjectFilesWidget = ({
|
|
|
537
691
|
onSaved,
|
|
538
692
|
onDelete,
|
|
539
693
|
deleteUrl = "/api/files",
|
|
540
|
-
showDelete = false
|
|
694
|
+
showDelete = false,
|
|
695
|
+
refreshInterval = 5e3
|
|
541
696
|
}) => {
|
|
542
697
|
const client = (0, import_react2.useMemo)(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
|
|
543
698
|
const [files, setFiles] = (0, import_react2.useState)([]);
|
|
@@ -546,25 +701,37 @@ var ProjectFilesWidget = ({
|
|
|
546
701
|
const [query, setQuery] = (0, import_react2.useState)("");
|
|
547
702
|
const [savingKey, setSavingKey] = (0, import_react2.useState)(null);
|
|
548
703
|
const [deletingKey, setDeletingKey] = (0, import_react2.useState)(null);
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (!cancelled) setLoading(false);
|
|
704
|
+
const lastUpdatedRef = import_react2.default.useRef(null);
|
|
705
|
+
const isFetchingRef = import_react2.default.useRef(false);
|
|
706
|
+
const fetchFiles = import_react2.default.useCallback(async (isPolling = false) => {
|
|
707
|
+
if (isFetchingRef.current) return;
|
|
708
|
+
isFetchingRef.current = true;
|
|
709
|
+
if (!isPolling) setLoading(true);
|
|
710
|
+
setError(null);
|
|
711
|
+
try {
|
|
712
|
+
const { files: newFiles, updatedAt } = await client.getProjectFiles({ folderPath });
|
|
713
|
+
if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
|
|
714
|
+
return;
|
|
561
715
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
716
|
+
setFiles(newFiles);
|
|
717
|
+
lastUpdatedRef.current = updatedAt;
|
|
718
|
+
} catch (e) {
|
|
719
|
+
if (!isPolling) setError(e?.message || "Failed to load files");
|
|
720
|
+
} finally {
|
|
721
|
+
if (!isPolling) setLoading(false);
|
|
722
|
+
isFetchingRef.current = false;
|
|
723
|
+
}
|
|
567
724
|
}, [client, folderPath]);
|
|
725
|
+
(0, import_react2.useEffect)(() => {
|
|
726
|
+
fetchFiles();
|
|
727
|
+
}, [fetchFiles]);
|
|
728
|
+
(0, import_react2.useEffect)(() => {
|
|
729
|
+
if (!refreshInterval || refreshInterval <= 0) return;
|
|
730
|
+
const timer = setInterval(() => {
|
|
731
|
+
fetchFiles(true);
|
|
732
|
+
}, refreshInterval);
|
|
733
|
+
return () => clearInterval(timer);
|
|
734
|
+
}, [fetchFiles, refreshInterval]);
|
|
568
735
|
const visible = files.filter(
|
|
569
736
|
(f2) => !query ? true : (f2.originalName || "").toLowerCase().includes(query.toLowerCase())
|
|
570
737
|
);
|
|
@@ -621,18 +788,7 @@ var ProjectFilesWidget = ({
|
|
|
621
788
|
{
|
|
622
789
|
onClick: () => {
|
|
623
790
|
setQuery("");
|
|
624
|
-
(
|
|
625
|
-
setLoading(true);
|
|
626
|
-
setError(null);
|
|
627
|
-
try {
|
|
628
|
-
const data = await client.getProjectFiles({ folderPath });
|
|
629
|
-
setFiles(data);
|
|
630
|
-
} catch (e) {
|
|
631
|
-
setError(e?.message || "Failed to load files");
|
|
632
|
-
} finally {
|
|
633
|
-
setLoading(false);
|
|
634
|
-
}
|
|
635
|
-
})();
|
|
791
|
+
fetchFiles();
|
|
636
792
|
},
|
|
637
793
|
style: { padding: "8px 12px", border: "1px solid #e5e7eb", borderRadius: 6, background: "#fff" },
|
|
638
794
|
children: "Refresh"
|
|
@@ -1025,7 +1181,7 @@ async function fetchFileThumbnails(clientOptions, fileKey) {
|
|
|
1025
1181
|
return client.getThumbnails(fileKey);
|
|
1026
1182
|
}
|
|
1027
1183
|
async function fetchProjectImagesWithThumbs(clientOptions, opts) {
|
|
1028
|
-
const files = await fetchProjectFiles(clientOptions, opts?.folderPath);
|
|
1184
|
+
const { files, updatedAt } = await fetchProjectFiles(clientOptions, opts?.folderPath);
|
|
1029
1185
|
const limit = Math.max(1, Math.min(opts?.limitConcurrent ?? 6, 12));
|
|
1030
1186
|
const queue = [...files];
|
|
1031
1187
|
const results = [];
|
|
@@ -1047,13 +1203,62 @@ async function fetchProjectImagesWithThumbs(clientOptions, opts) {
|
|
|
1047
1203
|
await Promise.all(Array.from({ length: workers }, () => worker()));
|
|
1048
1204
|
const keyToIndex = new Map(files.map((f2, i) => [f2.key, i]));
|
|
1049
1205
|
results.sort((a, b) => (keyToIndex.get(a.key) ?? 0) - (keyToIndex.get(b.key) ?? 0));
|
|
1050
|
-
return results;
|
|
1206
|
+
return { items: results, updatedAt };
|
|
1051
1207
|
}
|
|
1052
1208
|
|
|
1053
1209
|
// src/ImageManager.tsx
|
|
1054
1210
|
var React4 = __toESM(require("react"));
|
|
1055
1211
|
var Dialog2 = __toESM(require("@radix-ui/react-dialog"));
|
|
1056
1212
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1213
|
+
var IconGrid = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1214
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
|
|
1215
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
|
|
1216
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
|
|
1217
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
|
|
1218
|
+
] });
|
|
1219
|
+
var IconUploadCloud = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1220
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }),
|
|
1221
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 12v9" }),
|
|
1222
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m16 16-4-4-4 4" })
|
|
1223
|
+
] });
|
|
1224
|
+
var IconFolder = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", stroke: "currentColor", strokeWidth: "0", className, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z" }) });
|
|
1225
|
+
var IconFolderPlus = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1226
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 2H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2Z" }),
|
|
1227
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "12", x2: "12", y1: "10", y2: "16" }),
|
|
1228
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "9", x2: "15", y1: "13", y2: "13" })
|
|
1229
|
+
] });
|
|
1230
|
+
var IconSearch = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1231
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
|
|
1232
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m21 21-4.3-4.3" })
|
|
1233
|
+
] });
|
|
1234
|
+
var IconRefresh = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1235
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
|
|
1236
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M21 3v5h-5" }),
|
|
1237
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
|
|
1238
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M8 16H3v5" })
|
|
1239
|
+
] });
|
|
1240
|
+
var IconChevronRight = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m9 18 6-6-6-6" }) });
|
|
1241
|
+
var IconTrash = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1242
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M3 6h18" }),
|
|
1243
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
1244
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
|
|
1245
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
|
|
1246
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
|
|
1247
|
+
] });
|
|
1248
|
+
var IconX = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1249
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M18 6 6 18" }),
|
|
1250
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 6 12 12" })
|
|
1251
|
+
] });
|
|
1252
|
+
var IconHome = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1253
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
|
|
1254
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("polyline", { points: "9 22 9 12 15 12 15 22" })
|
|
1255
|
+
] });
|
|
1256
|
+
var IconImage = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1257
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
1258
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "9", cy: "9", r: "2" }),
|
|
1259
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
|
|
1260
|
+
] });
|
|
1261
|
+
var IconLoader = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
|
|
1057
1262
|
function ImageManager(props) {
|
|
1058
1263
|
const {
|
|
1059
1264
|
open,
|
|
@@ -1073,209 +1278,485 @@ function ImageManager(props) {
|
|
|
1073
1278
|
maxFileSize = 100 * 1024 * 1024,
|
|
1074
1279
|
maxFiles = 10,
|
|
1075
1280
|
mode = "full",
|
|
1076
|
-
showDelete = true
|
|
1281
|
+
showDelete = true,
|
|
1282
|
+
refreshInterval = 5e3
|
|
1077
1283
|
} = props;
|
|
1078
|
-
const [
|
|
1284
|
+
const [activeView, setActiveView] = React4.useState(mode === "upload" ? "upload" : "browse");
|
|
1079
1285
|
const [loading, setLoading] = React4.useState(false);
|
|
1080
1286
|
const [error, setError] = React4.useState(null);
|
|
1081
|
-
const [
|
|
1287
|
+
const [currentFolderId, setCurrentFolderId] = React4.useState(void 0);
|
|
1288
|
+
const [breadcrumbs, setBreadcrumbs] = React4.useState([{ id: void 0, name: "Home" }]);
|
|
1289
|
+
const [files, setFiles] = React4.useState([]);
|
|
1290
|
+
const [folders, setFolders] = React4.useState([]);
|
|
1082
1291
|
const [query, setQuery] = React4.useState("");
|
|
1083
1292
|
const [deleting, setDeleting] = React4.useState(null);
|
|
1084
|
-
const
|
|
1293
|
+
const [showCreateFolder, setShowCreateFolder] = React4.useState(false);
|
|
1294
|
+
const [newFolderName, setNewFolderName] = React4.useState("");
|
|
1295
|
+
const [creatingFolder, setCreatingFolder] = React4.useState(false);
|
|
1296
|
+
const [fileToDelete, setFileToDelete] = React4.useState(null);
|
|
1297
|
+
const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4.useState(null);
|
|
1298
|
+
const lastUpdatedRef = React4.useRef(null);
|
|
1299
|
+
const isFetchingRef = React4.useRef(false);
|
|
1300
|
+
const load = React4.useCallback(async (isPolling = false) => {
|
|
1301
|
+
if (isFetchingRef.current) return;
|
|
1302
|
+
isFetchingRef.current = true;
|
|
1085
1303
|
try {
|
|
1086
|
-
setLoading(true);
|
|
1304
|
+
if (!isPolling) setLoading(true);
|
|
1087
1305
|
setError(null);
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1306
|
+
const client = new UpfilesClient(clientOptions);
|
|
1307
|
+
const currentPathPrefix = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1308
|
+
const effectiveFolderPath = currentPathPrefix || folderPath;
|
|
1309
|
+
const [filesData, foldersData] = await Promise.all([
|
|
1310
|
+
fetchProjectImagesWithThumbs(clientOptions, { folderPath: effectiveFolderPath, limitConcurrent: 6 }),
|
|
1311
|
+
client.getFolders(currentFolderId)
|
|
1312
|
+
]);
|
|
1313
|
+
const { items: list, updatedAt } = filesData;
|
|
1314
|
+
if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
|
|
1315
|
+
}
|
|
1316
|
+
setFiles(list);
|
|
1317
|
+
setFolders(foldersData);
|
|
1318
|
+
lastUpdatedRef.current = updatedAt;
|
|
1090
1319
|
} catch (e) {
|
|
1091
|
-
setError(e?.message || "Failed to load images");
|
|
1320
|
+
if (!isPolling) setError(e?.message || "Failed to load images");
|
|
1092
1321
|
} finally {
|
|
1093
|
-
setLoading(false);
|
|
1322
|
+
if (!isPolling) setLoading(false);
|
|
1323
|
+
isFetchingRef.current = false;
|
|
1094
1324
|
}
|
|
1095
|
-
}, [clientOptions, folderPath]);
|
|
1325
|
+
}, [clientOptions, folderPath, currentFolderId, breadcrumbs]);
|
|
1096
1326
|
React4.useEffect(() => {
|
|
1097
1327
|
if (!open) {
|
|
1098
|
-
|
|
1328
|
+
setFiles([]);
|
|
1329
|
+
setFolders([]);
|
|
1099
1330
|
setQuery("");
|
|
1100
1331
|
setError(null);
|
|
1332
|
+
lastUpdatedRef.current = null;
|
|
1101
1333
|
return;
|
|
1102
1334
|
}
|
|
1103
|
-
if (mode !== "upload" &&
|
|
1335
|
+
if (mode !== "upload" && activeView === "browse") {
|
|
1104
1336
|
load();
|
|
1105
1337
|
}
|
|
1106
|
-
}, [open,
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
if (!confirm("Delete this file?")) return;
|
|
1338
|
+
}, [open, activeView, load, mode]);
|
|
1339
|
+
React4.useEffect(() => {
|
|
1340
|
+
if (!open) return;
|
|
1341
|
+
const client = new UpfilesClient(clientOptions);
|
|
1342
|
+
const url = client.getEventsUrl(projectId);
|
|
1343
|
+
const eventSource = new EventSource(url);
|
|
1344
|
+
eventSource.onmessage = (event) => {
|
|
1114
1345
|
try {
|
|
1115
|
-
|
|
1116
|
-
if (
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
const headers = {
|
|
1122
|
-
"content-type": "application/json",
|
|
1123
|
-
...clientOptions?.headers || {}
|
|
1124
|
-
};
|
|
1125
|
-
if (clientOptions?.apiKey) {
|
|
1126
|
-
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
1127
|
-
if (keyHeader === "authorization") {
|
|
1128
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
1129
|
-
} else {
|
|
1130
|
-
headers[keyHeader] = clientOptions.apiKey;
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
const res = await fetch(url, {
|
|
1134
|
-
method: "DELETE",
|
|
1135
|
-
headers,
|
|
1136
|
-
credentials: clientOptions?.withCredentials ? "include" : "same-origin",
|
|
1137
|
-
body: JSON.stringify({ fileKey: key })
|
|
1346
|
+
const payload = JSON.parse(event.data);
|
|
1347
|
+
if (payload.type === "file:uploaded") {
|
|
1348
|
+
const newFile = payload.data.file;
|
|
1349
|
+
setFiles((prev) => {
|
|
1350
|
+
if (prev.find((f2) => f2.key === newFile.key)) return prev;
|
|
1351
|
+
return [newFile, ...prev];
|
|
1138
1352
|
});
|
|
1139
|
-
|
|
1353
|
+
} else if (payload.type === "file:deleted") {
|
|
1354
|
+
const deletedKey = payload.data.fileKey || payload.data.file?.key;
|
|
1355
|
+
setFiles((prev) => prev.filter((f2) => f2.key !== deletedKey));
|
|
1140
1356
|
}
|
|
1141
|
-
setItems((prev) => prev.filter((f2) => f2.key !== key));
|
|
1142
1357
|
} catch (e) {
|
|
1143
|
-
|
|
1144
|
-
} finally {
|
|
1145
|
-
setDeleting(null);
|
|
1358
|
+
console.error("SSE Error parsing message", e);
|
|
1146
1359
|
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
);
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1360
|
+
};
|
|
1361
|
+
return () => eventSource.close();
|
|
1362
|
+
}, [open, clientOptions, projectId]);
|
|
1363
|
+
const visibleFiles = React4.useMemo(() => {
|
|
1364
|
+
const q = query.trim().toLowerCase();
|
|
1365
|
+
return !q ? files : files.filter((i) => (i.originalName || "").toLowerCase().includes(q));
|
|
1366
|
+
}, [files, query]);
|
|
1367
|
+
const visibleFolders = React4.useMemo(() => {
|
|
1368
|
+
const q = query.trim().toLowerCase();
|
|
1369
|
+
return !q ? folders : folders.filter((f2) => (f2.name || "").toLowerCase().includes(q));
|
|
1370
|
+
}, [folders, query]);
|
|
1371
|
+
const handleDelete = async () => {
|
|
1372
|
+
if (!fileToDelete) return;
|
|
1373
|
+
const key = fileToDelete.key;
|
|
1374
|
+
try {
|
|
1375
|
+
setDeleting(key);
|
|
1376
|
+
if (onDelete) {
|
|
1377
|
+
await onDelete(key);
|
|
1378
|
+
} else {
|
|
1379
|
+
const client = new UpfilesClient(clientOptions);
|
|
1380
|
+
const baseUrl = clientOptions?.baseUrl || "";
|
|
1381
|
+
const url = baseUrl ? `${baseUrl}${deleteUrl}` : deleteUrl;
|
|
1382
|
+
const headers = {
|
|
1383
|
+
"content-type": "application/json",
|
|
1384
|
+
...clientOptions?.headers || {}
|
|
1385
|
+
};
|
|
1386
|
+
if (clientOptions?.apiKey) {
|
|
1387
|
+
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
1388
|
+
if (keyHeader === "authorization") {
|
|
1389
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
1390
|
+
} else {
|
|
1391
|
+
headers[keyHeader] = clientOptions.apiKey;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const res = await fetch(url, {
|
|
1395
|
+
method: "DELETE",
|
|
1396
|
+
headers,
|
|
1397
|
+
credentials: clientOptions?.withCredentials ? "include" : "same-origin",
|
|
1398
|
+
body: JSON.stringify({ fileKey: key })
|
|
1399
|
+
});
|
|
1400
|
+
if (!res.ok) throw new Error("Delete failed");
|
|
1401
|
+
}
|
|
1402
|
+
setFiles((prev) => prev.filter((f2) => f2.key !== key));
|
|
1403
|
+
setFileToDelete(null);
|
|
1404
|
+
} catch (e) {
|
|
1405
|
+
alert(e?.message || "Failed to delete");
|
|
1406
|
+
} finally {
|
|
1407
|
+
setDeleting(null);
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
const handleCreateFolder = async () => {
|
|
1411
|
+
if (!newFolderName.trim()) return;
|
|
1412
|
+
setCreatingFolder(true);
|
|
1413
|
+
try {
|
|
1414
|
+
const client = new UpfilesClient(clientOptions);
|
|
1415
|
+
await client.createFolder(newFolderName, currentFolderId);
|
|
1416
|
+
setNewFolderName("");
|
|
1417
|
+
setShowCreateFolder(false);
|
|
1418
|
+
load();
|
|
1419
|
+
} catch (e) {
|
|
1420
|
+
alert(e.message || "Failed to create folder");
|
|
1421
|
+
} finally {
|
|
1422
|
+
setCreatingFolder(false);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
const handleNavigate = (folder) => {
|
|
1426
|
+
setCurrentFolderId(folder.id);
|
|
1427
|
+
setBreadcrumbs((prev) => [...prev, { id: folder.id, name: folder.name }]);
|
|
1428
|
+
setQuery("");
|
|
1429
|
+
};
|
|
1430
|
+
const handleBreadcrumbClick = (index) => {
|
|
1431
|
+
const target = breadcrumbs[index];
|
|
1432
|
+
setBreadcrumbs(breadcrumbs.slice(0, index + 1));
|
|
1433
|
+
setCurrentFolderId(target.id);
|
|
1434
|
+
setQuery("");
|
|
1435
|
+
};
|
|
1436
|
+
const uploadFoldersList = React4.useMemo(() => {
|
|
1437
|
+
const options = [];
|
|
1438
|
+
options.push({ id: "root", name: "Root ( / )", path: void 0 });
|
|
1439
|
+
if (currentFolderId) {
|
|
1440
|
+
const currentPath = breadcrumbs.slice(1).map((b) => b.name).join("/") + "/";
|
|
1441
|
+
options.push({ id: "current", name: `Current ( ${breadcrumbs[breadcrumbs.length - 1].name} )`, path: currentPath });
|
|
1442
|
+
}
|
|
1443
|
+
folders.forEach((f2) => {
|
|
1444
|
+
const parentPath = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : "";
|
|
1445
|
+
options.push({ id: f2.id, name: f2.name, path: parentPath + f2.name + "/" });
|
|
1446
|
+
});
|
|
1447
|
+
return options;
|
|
1448
|
+
}, [breadcrumbs, currentFolderId, folders]);
|
|
1449
|
+
const [selectedUploadFolder, setSelectedUploadFolder] = React4.useState("current");
|
|
1450
|
+
const activeBreadcrumbPath = React4.useMemo(() => {
|
|
1451
|
+
return breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1452
|
+
}, [breadcrumbs]);
|
|
1453
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Root, { open, onOpenChange, children: [
|
|
1454
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Portal, { children: [
|
|
1455
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Overlay, { className: "fixed inset-0 bg-gray-900/50 backdrop-blur-sm z-50 transition-all duration-300" }),
|
|
1456
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1457
|
+
Dialog2.Content,
|
|
1458
|
+
{
|
|
1459
|
+
className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] h-[85vh] max-w-6xl rounded-2xl bg-white dark:bg-gray-950 shadow-2xl z-50 overflow-hidden flex flex-row ${className ?? ""}`,
|
|
1460
|
+
children: [
|
|
1461
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "w-64 bg-gray-50 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col flex-shrink-0", children: [
|
|
1462
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "p-6", children: [
|
|
1463
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
|
|
1464
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconImage, { className: "text-blue-600" }),
|
|
1465
|
+
title ?? "Media Library"
|
|
1466
|
+
] }),
|
|
1467
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xs text-gray-500 mt-1", children: description ?? "Manage your assets" })
|
|
1468
|
+
] }),
|
|
1469
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("nav", { className: "flex-1 px-3 space-y-1", children: [
|
|
1470
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1471
|
+
"button",
|
|
1472
|
+
{
|
|
1473
|
+
onClick: () => setActiveView("browse"),
|
|
1474
|
+
className: `w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-colors ${activeView === "browse" ? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"}`,
|
|
1475
|
+
children: [
|
|
1476
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconGrid, { className: "w-4 h-4" }),
|
|
1477
|
+
"All Files"
|
|
1478
|
+
]
|
|
1479
|
+
}
|
|
1480
|
+
),
|
|
1481
|
+
mode === "full" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1482
|
+
"button",
|
|
1483
|
+
{
|
|
1484
|
+
onClick: () => setActiveView("upload"),
|
|
1485
|
+
className: `w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-colors ${activeView === "upload" ? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"}`,
|
|
1486
|
+
children: [
|
|
1487
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconUploadCloud, { className: "w-4 h-4" }),
|
|
1488
|
+
"Upload New"
|
|
1489
|
+
]
|
|
1490
|
+
}
|
|
1491
|
+
)
|
|
1492
|
+
] }),
|
|
1493
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-4 border-t border-gray-200 dark:border-gray-800", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "text-xs text-center text-gray-400", children: [
|
|
1494
|
+
files.length,
|
|
1495
|
+
" files \u2022 ",
|
|
1496
|
+
folders.length,
|
|
1497
|
+
" folders"
|
|
1498
|
+
] }) })
|
|
1191
1499
|
] }),
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1500
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-1 flex flex-col bg-white dark:bg-gray-950 relative", children: [
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "h-16 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between px-6 gap-4", children: [
|
|
1502
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex items-center gap-4 flex-1", children: activeView === "browse" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1503
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center text-sm text-gray-500 overflow-hidden whitespace-nowrap", children: [
|
|
1504
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => handleBreadcrumbClick(0), className: "hover:text-blue-600 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconHome, { className: "w-4 h-4" }) }),
|
|
1505
|
+
breadcrumbs.slice(1).map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(React4.Fragment, { children: [
|
|
1506
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconChevronRight, { className: "w-4 h-4 text-gray-300 mx-1" }),
|
|
1507
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1508
|
+
"button",
|
|
1509
|
+
{
|
|
1510
|
+
onClick: () => handleBreadcrumbClick(i + 1),
|
|
1511
|
+
className: `hover:text-blue-600 transition-colors truncate max-w-[100px] ${i === breadcrumbs.length - 2 ? "font-semibold text-gray-900 dark:text-white" : ""}`,
|
|
1512
|
+
children: b.name
|
|
1513
|
+
}
|
|
1514
|
+
)
|
|
1515
|
+
] }, b.id || i))
|
|
1516
|
+
] }),
|
|
1517
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
|
|
1518
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1519
|
+
"button",
|
|
1520
|
+
{
|
|
1521
|
+
onClick: () => load(),
|
|
1522
|
+
disabled: loading,
|
|
1523
|
+
className: "p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-all disabled:opacity-50",
|
|
1524
|
+
title: "Refresh",
|
|
1525
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconRefresh, { className: `w-4 h-4 ${loading ? "animate-spin" : ""}` })
|
|
1526
|
+
}
|
|
1527
|
+
),
|
|
1528
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
|
|
1529
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "relative flex-1 max-w-sm group", children: [
|
|
1530
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconSearch, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 group-focus-within:text-blue-500 transition-colors" }),
|
|
1199
1531
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1200
|
-
"
|
|
1201
|
-
{
|
|
1202
|
-
type: "button",
|
|
1203
|
-
onClick: () => {
|
|
1204
|
-
onSelect({
|
|
1205
|
-
url: f2.url,
|
|
1206
|
-
key: f2.key,
|
|
1207
|
-
originalName: f2.originalName,
|
|
1208
|
-
size: f2.size,
|
|
1209
|
-
contentType: f2.contentType,
|
|
1210
|
-
thumbnails: f2.thumbnails
|
|
1211
|
-
});
|
|
1212
|
-
onOpenChange(false);
|
|
1213
|
-
},
|
|
1214
|
-
className: "opacity-0 group-hover:opacity-100 px-3 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded",
|
|
1215
|
-
children: "Select"
|
|
1216
|
-
}
|
|
1217
|
-
),
|
|
1218
|
-
showDelete && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1219
|
-
"button",
|
|
1532
|
+
"input",
|
|
1220
1533
|
{
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
className: "
|
|
1225
|
-
children: deleting === f2.key ? "..." : "Delete"
|
|
1534
|
+
value: query,
|
|
1535
|
+
onChange: (e) => setQuery(e.target.value),
|
|
1536
|
+
placeholder: "Search files...",
|
|
1537
|
+
className: "w-full pl-9 pr-4 py-1.5 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
|
|
1226
1538
|
}
|
|
1227
1539
|
)
|
|
1540
|
+
] })
|
|
1541
|
+
] }) }),
|
|
1542
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1543
|
+
activeView === "browse" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1544
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setShowCreateFolder(true), className: "p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 rounded-lg transition-colors", title: "New Folder", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconFolderPlus, { className: "w-5 h-5" }) }),
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => load(), className: `p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 rounded-lg transition-colors ${loading ? "animate-spin text-blue-600" : ""}`, title: "Refresh", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconLoader, { className: "w-5 h-5" }) })
|
|
1546
|
+
] }),
|
|
1547
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "ml-2 p-2 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/10 rounded-lg transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconX, { className: "w-5 h-5" }) }) })
|
|
1548
|
+
] })
|
|
1549
|
+
] }),
|
|
1550
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50/30 dark:bg-gray-900/30", children: [
|
|
1551
|
+
activeView === "browse" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1552
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mb-6 bg-red-50 text-red-600 p-4 rounded-lg text-sm border border-red-100 flex items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: error }) }),
|
|
1553
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: gridClassName ?? "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6", children: [
|
|
1554
|
+
visibleFolders.map((folder) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1555
|
+
"div",
|
|
1556
|
+
{
|
|
1557
|
+
onClick: () => handleNavigate(folder),
|
|
1558
|
+
className: "group flex flex-col items-center gap-3 p-4 rounded-xl border border-gray-200/60 bg-white hover:border-blue-200 hover:shadow-lg hover:shadow-blue-500/5 hover:-translate-y-1 transition-all cursor-pointer",
|
|
1559
|
+
children: [
|
|
1560
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-16 h-12 bg-blue-50 rounded-lg flex items-center justify-center text-blue-500 group-hover:scale-110 transition-transform", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconFolder, { className: "w-8 h-8 drop-shadow-sm" }) }),
|
|
1561
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-sm font-medium text-gray-700 truncate w-full text-center", children: folder.name })
|
|
1562
|
+
]
|
|
1563
|
+
},
|
|
1564
|
+
folder.id
|
|
1565
|
+
)),
|
|
1566
|
+
visibleFiles.map((f2) => {
|
|
1567
|
+
const fWithThumbs = f2;
|
|
1568
|
+
const bestThumb = fWithThumbs.thumbnails?.sort((a, b) => a.size - b.size).find((t) => t.size >= 300) || fWithThumbs.thumbnails?.[fWithThumbs.thumbnails?.length - 1];
|
|
1569
|
+
const thumbUrl = bestThumb?.url || f2.url;
|
|
1570
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1571
|
+
"div",
|
|
1572
|
+
{
|
|
1573
|
+
className: "group relative aspect-[4/3] bg-white rounded-xl border border-gray-200 overflow-hidden shadow-sm hover:shadow-xl hover:shadow-gray-200/50 hover:border-blue-300 transition-all duration-300",
|
|
1574
|
+
children: [
|
|
1575
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: thumbUrl, alt: "", className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" }),
|
|
1576
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex flex-col justify-end p-3", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-2 justify-center transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [
|
|
1577
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1578
|
+
"button",
|
|
1579
|
+
{
|
|
1580
|
+
onClick: () => {
|
|
1581
|
+
onSelect({
|
|
1582
|
+
url: f2.url,
|
|
1583
|
+
key: f2.key,
|
|
1584
|
+
originalName: f2.originalName,
|
|
1585
|
+
size: f2.size,
|
|
1586
|
+
contentType: f2.contentType,
|
|
1587
|
+
thumbnails: f2.thumbnails
|
|
1588
|
+
});
|
|
1589
|
+
onOpenChange(false);
|
|
1590
|
+
},
|
|
1591
|
+
className: "px-3 py-1.5 bg-blue-600 text-white text-xs font-semibold rounded-md hover:bg-blue-500 shadow-lg",
|
|
1592
|
+
children: "Select"
|
|
1593
|
+
}
|
|
1594
|
+
),
|
|
1595
|
+
showDelete && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1596
|
+
"button",
|
|
1597
|
+
{
|
|
1598
|
+
onClick: (e) => {
|
|
1599
|
+
e.stopPropagation();
|
|
1600
|
+
setFileToDelete(f2);
|
|
1601
|
+
},
|
|
1602
|
+
className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-red-500 backdrop-blur-md transition-colors",
|
|
1603
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconTrash, { className: "w-4 h-4" })
|
|
1604
|
+
}
|
|
1605
|
+
)
|
|
1606
|
+
] }) }),
|
|
1607
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute top-2 right-2 bg-black/50 backdrop-blur-md text-white text-[10px] px-1.5 py-0.5 rounded opacity-0 group-hover:opacity-100 transition-opacity", children: f2.originalName.split(".").pop()?.toUpperCase() })
|
|
1608
|
+
]
|
|
1609
|
+
},
|
|
1610
|
+
f2.key
|
|
1611
|
+
);
|
|
1612
|
+
})
|
|
1613
|
+
] }),
|
|
1614
|
+
!loading && visibleFolders.length === 0 && visibleFiles.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "h-full flex flex-col items-center justify-center text-gray-400", children: [
|
|
1615
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconSearch, { className: "w-8 h-8 opacity-40" }) }),
|
|
1616
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "text-gray-900 font-medium mb-1", children: "No Assets Found" }),
|
|
1617
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
|
|
1618
|
+
] })
|
|
1619
|
+
] }),
|
|
1620
|
+
activeView === "upload" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
|
|
1621
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
|
|
1622
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconUploadCloud, { className: "text-blue-500" }),
|
|
1623
|
+
"Upload Files"
|
|
1624
|
+
] }),
|
|
1625
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-6", children: [
|
|
1626
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Target Folder" }),
|
|
1627
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-2", children: [
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1629
|
+
"select",
|
|
1630
|
+
{
|
|
1631
|
+
value: selectedUploadFolder,
|
|
1632
|
+
onChange: (e) => setSelectedUploadFolder(e.target.value),
|
|
1633
|
+
className: "block w-full rounded-lg border-gray-200 bg-gray-50 text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 p-2.5",
|
|
1634
|
+
children: uploadFoldersList.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: opt.id, children: opt.name }, opt.id))
|
|
1635
|
+
}
|
|
1636
|
+
),
|
|
1637
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setShowCreateFolder(true), className: "px-3 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-600 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconFolderPlus, { className: "w-5 h-5" }) })
|
|
1638
|
+
] })
|
|
1228
1639
|
] }),
|
|
1229
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1640
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1641
|
+
Uploader,
|
|
1642
|
+
{
|
|
1643
|
+
clientOptions,
|
|
1644
|
+
projectId,
|
|
1645
|
+
folderPath: uploadFoldersList.find((o) => o.id === selectedUploadFolder)?.path || activeBreadcrumbPath || folderPath,
|
|
1646
|
+
accept: ["image/*"],
|
|
1647
|
+
maxFileSize,
|
|
1648
|
+
maxFiles,
|
|
1649
|
+
autoRecordToDb,
|
|
1650
|
+
fetchThumbnails,
|
|
1651
|
+
autoUpload: true,
|
|
1652
|
+
onClientUploadComplete: (file) => {
|
|
1653
|
+
if (file.status === "success" && file.publicUrl && file.fileKey) {
|
|
1654
|
+
const newItem = {
|
|
1655
|
+
key: file.fileKey,
|
|
1656
|
+
originalName: file.name,
|
|
1657
|
+
size: file.size,
|
|
1658
|
+
contentType: file.type,
|
|
1659
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1660
|
+
url: file.publicUrl,
|
|
1661
|
+
thumbnails: file.thumbnails
|
|
1662
|
+
};
|
|
1663
|
+
setFiles((prev) => [newItem, ...prev]);
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1666
|
+
onComplete: () => {
|
|
1667
|
+
if (mode === "full") {
|
|
1668
|
+
setTimeout(() => {
|
|
1669
|
+
setActiveView("browse");
|
|
1670
|
+
load();
|
|
1671
|
+
}, 1e3);
|
|
1672
|
+
}
|
|
1673
|
+
},
|
|
1674
|
+
dropzoneClassName: "border-2 border-dashed border-gray-300 bg-gray-50 rounded-xl p-12 text-center cursor-pointer hover:border-blue-500 hover:bg-blue-50/50 transition-all",
|
|
1675
|
+
buttonClassName: "hidden",
|
|
1676
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
|
|
1677
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconUploadCloud, { className: "w-6 h-6" }) }),
|
|
1678
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
1679
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-gray-900 font-medium", children: "Click to upload or drag and drop" }),
|
|
1680
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-xs text-gray-500 mt-1", children: [
|
|
1681
|
+
"Supports PNG, JPG, GIF up to ",
|
|
1682
|
+
Math.round(maxFileSize / 1024 / 1024),
|
|
1683
|
+
"MB"
|
|
1684
|
+
] })
|
|
1685
|
+
] })
|
|
1686
|
+
] })
|
|
1687
|
+
}
|
|
1688
|
+
)
|
|
1689
|
+
] }) })
|
|
1690
|
+
] })
|
|
1234
1691
|
] })
|
|
1692
|
+
]
|
|
1693
|
+
}
|
|
1694
|
+
)
|
|
1695
|
+
] }),
|
|
1696
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Portal, { children: [
|
|
1697
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[60]" }),
|
|
1698
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl z-[61] outline-none", children: [
|
|
1699
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1701
|
+
"input",
|
|
1702
|
+
{
|
|
1703
|
+
autoFocus: true,
|
|
1704
|
+
className: "w-full rounded-lg border border-gray-200 bg-gray-50 px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none mb-6",
|
|
1705
|
+
placeholder: "Folder Name",
|
|
1706
|
+
value: newFolderName,
|
|
1707
|
+
onChange: (e) => setNewFolderName(e.target.value),
|
|
1708
|
+
onKeyDown: (e) => e.key === "Enter" && handleCreateFolder()
|
|
1709
|
+
}
|
|
1710
|
+
),
|
|
1711
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex justify-end gap-3", children: [
|
|
1712
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setShowCreateFolder(false), className: "px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg", children: "Cancel" }),
|
|
1713
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1714
|
+
"button",
|
|
1715
|
+
{
|
|
1716
|
+
onClick: handleCreateFolder,
|
|
1717
|
+
disabled: creatingFolder || !newFolderName.trim(),
|
|
1718
|
+
className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50",
|
|
1719
|
+
children: "Create"
|
|
1720
|
+
}
|
|
1721
|
+
)
|
|
1722
|
+
] })
|
|
1723
|
+
] })
|
|
1724
|
+
] }) }),
|
|
1725
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Portal, { children: [
|
|
1726
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[80]" }),
|
|
1727
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md rounded-xl bg-white p-6 shadow-2xl z-[81]", children: [
|
|
1728
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
|
|
1729
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Description, { className: "text-gray-600 mb-6", children: [
|
|
1730
|
+
"Are you sure you want to delete ",
|
|
1731
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "font-semibold text-gray-900", children: [
|
|
1732
|
+
'"',
|
|
1733
|
+
fileToDelete?.originalName,
|
|
1734
|
+
'"'
|
|
1235
1735
|
] }),
|
|
1236
|
-
|
|
1237
|
-
|
|
1736
|
+
"?"
|
|
1737
|
+
] }),
|
|
1738
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex justify-end gap-3", children: [
|
|
1739
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1740
|
+
"button",
|
|
1238
1741
|
{
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
accept: ["image/*"],
|
|
1243
|
-
maxFileSize,
|
|
1244
|
-
maxFiles,
|
|
1245
|
-
autoRecordToDb,
|
|
1246
|
-
fetchThumbnails,
|
|
1247
|
-
autoUpload: true,
|
|
1248
|
-
onClientUploadComplete: (file) => {
|
|
1249
|
-
if (file.status === "success" && file.publicUrl && file.fileKey) {
|
|
1250
|
-
const newItem = {
|
|
1251
|
-
key: file.fileKey,
|
|
1252
|
-
originalName: file.name,
|
|
1253
|
-
size: file.size,
|
|
1254
|
-
contentType: file.type,
|
|
1255
|
-
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1256
|
-
url: file.publicUrl,
|
|
1257
|
-
thumbnails: file.thumbnails
|
|
1258
|
-
};
|
|
1259
|
-
setItems((prev) => [newItem, ...prev]);
|
|
1260
|
-
}
|
|
1261
|
-
},
|
|
1262
|
-
onComplete: (files) => {
|
|
1263
|
-
if (mode === "full") {
|
|
1264
|
-
setTimeout(() => {
|
|
1265
|
-
load();
|
|
1266
|
-
setTab("browse");
|
|
1267
|
-
}, 500);
|
|
1268
|
-
}
|
|
1269
|
-
},
|
|
1270
|
-
dropzoneClassName: "border-2 border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-700/50 rounded-lg p-8 text-center cursor-pointer hover:border-blue-500 dark:hover:border-blue-400 transition-colors text-gray-700 dark:text-gray-300",
|
|
1271
|
-
buttonClassName: "px-4 py-2 text-sm rounded border border-gray-300 dark:border-gray-600 bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50"
|
|
1742
|
+
onClick: () => setFileToDelete(null),
|
|
1743
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg",
|
|
1744
|
+
children: "Cancel"
|
|
1272
1745
|
}
|
|
1273
1746
|
),
|
|
1274
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1747
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1748
|
+
"button",
|
|
1749
|
+
{
|
|
1750
|
+
onClick: handleDelete,
|
|
1751
|
+
disabled: !!deleting,
|
|
1752
|
+
className: "px-4 py-2 text-sm font-medium rounded-lg bg-red-600 text-white hover:bg-red-700 disabled:opacity-70",
|
|
1753
|
+
children: deleting ? "Deleting..." : "Delete"
|
|
1754
|
+
}
|
|
1755
|
+
)
|
|
1275
1756
|
] })
|
|
1276
|
-
}
|
|
1277
|
-
)
|
|
1278
|
-
] })
|
|
1757
|
+
] })
|
|
1758
|
+
] }) })
|
|
1759
|
+
] });
|
|
1279
1760
|
}
|
|
1280
1761
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1281
1762
|
0 && (module.exports = {
|