@thetechfossil/upfiles 1.0.9 → 1.0.12

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/dist/index.mjs CHANGED
@@ -1,15 +1,21 @@
1
+ import * as React4 from 'react';
2
+ import React4__default, { useRef, useMemo, useState, useCallback, useEffect } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import * as Dialog2 from '@radix-ui/react-dialog';
5
+
1
6
  // src/client.ts
2
7
  var UpfilesClient = class {
3
8
  constructor(opts) {
4
9
  this.baseUrl = opts.baseUrl?.replace(/\/$/, "");
5
- this.presignPath = opts.presignPath ?? "/api/plugin/upload/presigned-url";
10
+ this.presignPath = opts.presignPath ?? "/api/plugin/upload";
6
11
  this.presignUrl = opts.presignUrl;
7
12
  this.headers = opts.headers;
8
13
  this.withCredentials = opts.withCredentials;
9
14
  this.apiKey = opts.apiKey;
10
15
  this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
11
16
  this.thumbnailsPath = opts.thumbnailsPath ?? "/api/plugin/thumbnails";
12
- this.completionPath = opts.completionPath ?? "/api/plugin/upload/complete";
17
+ this.completionPath = opts.completionPath ?? "/api/plugin/files";
18
+ this.foldersPath = opts.foldersPath ?? "/api/plugin/folders";
13
19
  this.callCompletionEndpoint = opts.callCompletionEndpoint ?? false;
14
20
  }
15
21
  async getPresignedUrl(params) {
@@ -132,6 +138,7 @@ var UpfilesClient = class {
132
138
  }
133
139
  throw new Error(msg);
134
140
  }
141
+ return res.json();
135
142
  }
136
143
  async getThumbnails(fileKey) {
137
144
  const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
@@ -192,6 +199,65 @@ var UpfilesClient = class {
192
199
  const data = await res.json();
193
200
  return { masterWebp: data.masterWebp, thumbnails: data.thumbnails ?? [] };
194
201
  }
202
+ async getFolders(parentId) {
203
+ const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
204
+ const url = parentId ? `${target}?parentId=${encodeURIComponent(parentId)}` : target;
205
+ const headers = { ...this.headers || {} };
206
+ if (this.apiKey) {
207
+ if (this.apiKeyHeader === "authorization") {
208
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
209
+ } else if (this.apiKeyHeader === "x-api-key") {
210
+ headers["x-api-key"] = this.apiKey;
211
+ } else {
212
+ headers["x-up-api-key"] = this.apiKey;
213
+ }
214
+ }
215
+ const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
216
+ if (!res.ok) {
217
+ let msg = "Failed to fetch folders";
218
+ try {
219
+ const data2 = await res.json();
220
+ msg = data2.error || msg;
221
+ } catch {
222
+ }
223
+ throw new Error(msg);
224
+ }
225
+ const data = await res.json();
226
+ return data?.folders ?? [];
227
+ }
228
+ async createFolder(name, parentId) {
229
+ const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
230
+ const headers = {
231
+ "content-type": "application/json",
232
+ ...this.headers || {}
233
+ };
234
+ if (this.apiKey) {
235
+ if (this.apiKeyHeader === "authorization") {
236
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
237
+ } else if (this.apiKeyHeader === "x-api-key") {
238
+ headers["x-api-key"] = this.apiKey;
239
+ } else {
240
+ headers["x-up-api-key"] = this.apiKey;
241
+ }
242
+ }
243
+ const res = await fetch(target, {
244
+ method: "POST",
245
+ headers,
246
+ credentials: this.withCredentials ? "include" : "same-origin",
247
+ body: JSON.stringify({ name, parentId })
248
+ });
249
+ if (!res.ok) {
250
+ let msg = "Failed to create folder";
251
+ try {
252
+ const data2 = await res.json();
253
+ msg = data2.error || msg;
254
+ } catch {
255
+ }
256
+ throw new Error(msg);
257
+ }
258
+ const data = await res.json();
259
+ return data.folder;
260
+ }
195
261
  async getProjectFiles(params) {
196
262
  const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
197
263
  const filesPath = `${basePath}/files`;
@@ -218,13 +284,24 @@ var UpfilesClient = class {
218
284
  throw new Error(msg);
219
285
  }
220
286
  const data = await res.json();
221
- return data?.files ?? [];
287
+ return {
288
+ files: data?.files ?? [],
289
+ updatedAt: res.headers.get("X-Files-Updated-At")
290
+ };
291
+ }
292
+ getEventsUrl(projectId) {
293
+ this.thumbnailsPath.replace(/\/thumbnails$/, "");
294
+ const target = this.baseUrl ? `${this.baseUrl}/api/events` : "/api/events";
295
+ const url = new URL(target, window.location.href);
296
+ if (this.apiKey) {
297
+ url.searchParams.set("apiKey", this.apiKey);
298
+ }
299
+ if (projectId) {
300
+ url.searchParams.set("projectId", projectId);
301
+ }
302
+ return url.toString();
222
303
  }
223
304
  };
224
-
225
- // src/Uploader.tsx
226
- import React, { useCallback, useMemo, useRef, useState } from "react";
227
- import { jsx, jsxs } from "react/jsx-runtime";
228
305
  var Uploader = ({
229
306
  clientOptions,
230
307
  multiple = true,
@@ -243,7 +320,7 @@ var Uploader = ({
243
320
  projectId,
244
321
  folderPath,
245
322
  fetchThumbnails,
246
- autoRecordToDb = false,
323
+ autoRecordToDb = true,
247
324
  recordUrl = "/api/files",
248
325
  autoUpload = false
249
326
  }) => {
@@ -317,9 +394,10 @@ var Uploader = ({
317
394
  xhr.setRequestHeader("Content-Type", item.type || "application/octet-stream");
318
395
  xhr.send(item.file);
319
396
  });
397
+ let completionResult = null;
320
398
  if (autoRecordToDb && projectId) {
321
399
  try {
322
- await client.completeUpload({
400
+ completionResult = await client.completeUpload({
323
401
  fileKey: presign.fileKey,
324
402
  originalName: item.name,
325
403
  size: item.size,
@@ -333,15 +411,21 @@ var Uploader = ({
333
411
  }
334
412
  let thumbs;
335
413
  let masterWebp;
336
- try {
337
- if (fetchThumbnails) {
338
- const result2 = await client.generateThumbnails(presign.fileKey);
414
+ if (completionResult?.thumbnails?.length > 0) {
415
+ thumbs = completionResult.thumbnails;
416
+ masterWebp = completionResult.masterWebp;
417
+ } else if (fetchThumbnails) {
418
+ const fileKeyToUse = completionResult?.file?.key || presign.fileKey;
419
+ try {
420
+ const result2 = await client.generateThumbnails(fileKeyToUse);
339
421
  thumbs = result2.thumbnails;
340
422
  masterWebp = result2.masterWebp;
423
+ } catch (e) {
424
+ console.warn("Failed to generate thumbnails:", e);
341
425
  }
342
- } catch (e) {
343
- console.warn("Failed to generate thumbnails:", e);
344
426
  }
427
+ const finalFileKey = completionResult?.file?.key || presign.fileKey;
428
+ const finalPublicUrl = completionResult?.file?.url || presign.publicUrl;
345
429
  const result = {
346
430
  ...item,
347
431
  status: "success",
@@ -384,7 +468,7 @@ var Uploader = ({
384
468
  setIsUploading(false);
385
469
  }
386
470
  }, [files, onComplete, onError, uploadOne]);
387
- React.useEffect(() => {
471
+ React4__default.useEffect(() => {
388
472
  if (autoUpload && files.some((f2) => f2.status === "pending") && !isUploading) {
389
473
  const timer = setTimeout(() => {
390
474
  uploadAll();
@@ -404,10 +488,10 @@ var Uploader = ({
404
488
  onChange: onInputChange
405
489
  }
406
490
  ),
407
- /* @__PURE__ */ jsx(
491
+ /* @__PURE__ */ jsxs(
408
492
  "div",
409
493
  {
410
- className: dropzoneClassName,
494
+ 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"}`,
411
495
  onDragOver: (e) => {
412
496
  e.preventDefault();
413
497
  e.stopPropagation();
@@ -422,60 +506,126 @@ var Uploader = ({
422
506
  onClick: () => inputRef.current?.click(),
423
507
  role: "button",
424
508
  "aria-label": "Upload files",
425
- children: children ?? /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: 16 }, children: [
426
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 600 }, children: "Drop files here or click to browse" }),
427
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#666" }, children: [
428
- multiple ? `Up to ${maxFiles} files` : "Single file",
429
- " \u2022 Max ",
430
- (maxFileSize / (1024 * 1024)).toFixed(0),
431
- "MB"
509
+ children: [
510
+ /* @__PURE__ */ 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__ */ 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: [
511
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
512
+ /* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
513
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
514
+ ] }) }),
515
+ children ?? /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
516
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Drop files here or click to browse" }),
517
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
518
+ multiple ? `Up to ${maxFiles} files` : "Single file",
519
+ " \u2022 Max ",
520
+ (maxFileSize / (1024 * 1024)).toFixed(0),
521
+ "MB"
522
+ ] })
432
523
  ] })
433
- ] })
524
+ ]
434
525
  }
435
526
  ),
436
- files.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
437
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginBottom: 8 }, children: [
438
- /* @__PURE__ */ jsx(
439
- "button",
440
- {
441
- className: buttonClassName,
442
- disabled: isUploading || files.every((f2) => f2.status !== "pending"),
443
- onClick: uploadAll,
444
- children: isUploading ? "Uploading..." : "Upload"
445
- }
446
- ),
447
- /* @__PURE__ */ jsx(
448
- "button",
449
- {
450
- className: buttonClassName,
451
- disabled: isUploading,
452
- onClick: () => setFiles([]),
453
- children: "Clear"
454
- }
455
- )
527
+ files.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-4", children: [
528
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-4", children: [
529
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-gray-900 dark:text-gray-100", children: "Selected Files" }),
530
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
531
+ /* @__PURE__ */ jsx(
532
+ "button",
533
+ {
534
+ 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",
535
+ disabled: isUploading || files.every((f2) => f2.status !== "pending"),
536
+ onClick: uploadAll,
537
+ children: isUploading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
538
+ /* @__PURE__ */ 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: [
539
+ /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
540
+ /* @__PURE__ */ 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" })
541
+ ] }),
542
+ "Uploading..."
543
+ ] }) : "Upload All"
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsx(
547
+ "button",
548
+ {
549
+ 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",
550
+ disabled: isUploading,
551
+ onClick: () => setFiles([]),
552
+ children: "Clear"
553
+ }
554
+ )
555
+ ] })
456
556
  ] }),
457
- /* @__PURE__ */ jsx("ul", { style: { display: "grid", gap: 8, listStyle: "none", padding: 0 }, children: files.map((f2) => /* @__PURE__ */ jsx("li", { style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 8 }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
458
- /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
459
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.name }),
460
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#666" }, children: [
461
- (f2.size / (1024 * 1024)).toFixed(2),
462
- " MB"
557
+ /* @__PURE__ */ jsx("div", { className: "grid gap-3", children: files.map((f2) => /* @__PURE__ */ 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__ */ jsxs("div", { className: "flex items-center gap-4", children: [
558
+ /* @__PURE__ */ 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__ */ 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: [
559
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
560
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
561
+ /* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
562
+ ] }) : /* @__PURE__ */ 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: [
563
+ /* @__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" }),
564
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" })
565
+ ] }) }),
566
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
567
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
568
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100 truncate pr-4", children: f2.name }),
569
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-500 font-medium whitespace-nowrap", children: [
570
+ (f2.size / (1024 * 1024)).toFixed(2),
571
+ " MB"
572
+ ] })
463
573
  ] }),
464
- f2.error && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#b91c1c" }, children: f2.error }),
465
- f2.publicUrl && f2.status === "success" && /* @__PURE__ */ jsx("a", { href: f2.publicUrl, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" })
574
+ f2.status === "error" ? /* @__PURE__ */ jsx("div", { className: "text-xs text-red-500 font-medium mt-1", children: f2.error }) : /* @__PURE__ */ jsx("div", { className: "h-1.5 w-full bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden mt-2", children: /* @__PURE__ */ jsx(
575
+ "div",
576
+ {
577
+ className: `h-full transition-all duration-300 ${f2.status === "success" ? "bg-green-500" : "bg-blue-500"}`,
578
+ style: { width: `${f2.progress}%` }
579
+ }
580
+ ) })
466
581
  ] }),
467
- /* @__PURE__ */ jsxs("div", { style: { minWidth: 120, textAlign: "right" }, children: [
468
- /* @__PURE__ */ jsx("div", { style: { height: 6, background: "#e5e7eb", borderRadius: 999, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { width: `${f2.progress}%`, height: "100%", background: f2.status === "error" ? "#ef4444" : "#2563eb" } }) }),
469
- /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#666", marginTop: 4 }, children: f2.status })
582
+ /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 ml-4", children: [
583
+ f2.status === "success" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
584
+ /* @__PURE__ */ 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__ */ 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__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) }) }),
585
+ f2.publicUrl && /* @__PURE__ */ jsx(
586
+ "a",
587
+ {
588
+ href: f2.publicUrl,
589
+ target: "_blank",
590
+ rel: "noreferrer",
591
+ className: "p-1.5 text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-colors",
592
+ title: "View online",
593
+ children: /* @__PURE__ */ 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: [
594
+ /* @__PURE__ */ jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
595
+ /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
596
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
597
+ ] })
598
+ }
599
+ )
600
+ ] }),
601
+ f2.status === "error" && /* @__PURE__ */ 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__ */ 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: [
602
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
603
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
604
+ ] }) }),
605
+ f2.status === "uploading" && /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-blue-600 dark:text-blue-400", children: [
606
+ f2.progress,
607
+ "%"
608
+ ] }),
609
+ f2.status === "pending" && /* @__PURE__ */ jsx(
610
+ "button",
611
+ {
612
+ onClick: (e) => {
613
+ e.stopPropagation();
614
+ setFiles((prev) => prev.filter((file) => file.id !== f2.id));
615
+ },
616
+ 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",
617
+ title: "Remove",
618
+ children: /* @__PURE__ */ 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: [
619
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
620
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
621
+ ] })
622
+ }
623
+ )
470
624
  ] })
471
625
  ] }) }, f2.id)) })
472
626
  ] })
473
627
  ] });
474
628
  };
475
-
476
- // src/ProjectFilesWidget.tsx
477
- import { useEffect, useMemo as useMemo2, useState as useState2 } from "react";
478
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
479
629
  var ProjectFilesWidget = ({
480
630
  clientOptions,
481
631
  folderPath,
@@ -488,34 +638,47 @@ var ProjectFilesWidget = ({
488
638
  onSaved,
489
639
  onDelete,
490
640
  deleteUrl = "/api/files",
491
- showDelete = false
641
+ showDelete = false,
642
+ refreshInterval = 5e3
492
643
  }) => {
493
- const client = useMemo2(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
494
- const [files, setFiles] = useState2([]);
495
- const [loading, setLoading] = useState2(false);
496
- const [error, setError] = useState2(null);
497
- const [query, setQuery] = useState2("");
498
- const [savingKey, setSavingKey] = useState2(null);
499
- const [deletingKey, setDeletingKey] = useState2(null);
500
- useEffect(() => {
501
- let cancelled = false;
502
- const run = async () => {
503
- setLoading(true);
504
- setError(null);
505
- try {
506
- const data = await client.getProjectFiles({ folderPath });
507
- if (!cancelled) setFiles(data);
508
- } catch (e) {
509
- if (!cancelled) setError(e?.message || "Failed to load files");
510
- } finally {
511
- if (!cancelled) setLoading(false);
644
+ const client = useMemo(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
645
+ const [files, setFiles] = useState([]);
646
+ const [loading, setLoading] = useState(false);
647
+ const [error, setError] = useState(null);
648
+ const [query, setQuery] = useState("");
649
+ const [savingKey, setSavingKey] = useState(null);
650
+ const [deletingKey, setDeletingKey] = useState(null);
651
+ const lastUpdatedRef = React4__default.useRef(null);
652
+ const isFetchingRef = React4__default.useRef(false);
653
+ const fetchFiles = React4__default.useCallback(async (isPolling = false) => {
654
+ if (isFetchingRef.current) return;
655
+ isFetchingRef.current = true;
656
+ if (!isPolling) setLoading(true);
657
+ setError(null);
658
+ try {
659
+ const { files: newFiles, updatedAt } = await client.getProjectFiles({ folderPath });
660
+ if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
661
+ return;
512
662
  }
513
- };
514
- run();
515
- return () => {
516
- cancelled = true;
517
- };
663
+ setFiles(newFiles);
664
+ lastUpdatedRef.current = updatedAt;
665
+ } catch (e) {
666
+ if (!isPolling) setError(e?.message || "Failed to load files");
667
+ } finally {
668
+ if (!isPolling) setLoading(false);
669
+ isFetchingRef.current = false;
670
+ }
518
671
  }, [client, folderPath]);
672
+ useEffect(() => {
673
+ fetchFiles();
674
+ }, [fetchFiles]);
675
+ useEffect(() => {
676
+ if (!refreshInterval || refreshInterval <= 0) return;
677
+ const timer = setInterval(() => {
678
+ fetchFiles(true);
679
+ }, refreshInterval);
680
+ return () => clearInterval(timer);
681
+ }, [fetchFiles, refreshInterval]);
519
682
  const visible = files.filter(
520
683
  (f2) => !query ? true : (f2.originalName || "").toLowerCase().includes(query.toLowerCase())
521
684
  );
@@ -555,9 +718,9 @@ var ProjectFilesWidget = ({
555
718
  setDeletingKey(null);
556
719
  }
557
720
  };
558
- return /* @__PURE__ */ jsxs2("div", { className, children: [
559
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
560
- /* @__PURE__ */ jsx2(
721
+ return /* @__PURE__ */ jsxs("div", { className, children: [
722
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
723
+ /* @__PURE__ */ jsx(
561
724
  "input",
562
725
  {
563
726
  type: "text",
@@ -567,49 +730,38 @@ var ProjectFilesWidget = ({
567
730
  style: { flex: 1, padding: 8, border: "1px solid #e5e7eb", borderRadius: 6 }
568
731
  }
569
732
  ),
570
- /* @__PURE__ */ jsx2(
733
+ /* @__PURE__ */ jsx(
571
734
  "button",
572
735
  {
573
736
  onClick: () => {
574
737
  setQuery("");
575
- (async () => {
576
- setLoading(true);
577
- setError(null);
578
- try {
579
- const data = await client.getProjectFiles({ folderPath });
580
- setFiles(data);
581
- } catch (e) {
582
- setError(e?.message || "Failed to load files");
583
- } finally {
584
- setLoading(false);
585
- }
586
- })();
738
+ fetchFiles();
587
739
  },
588
740
  style: { padding: "8px 12px", border: "1px solid #e5e7eb", borderRadius: 6, background: "#fff" },
589
741
  children: "Refresh"
590
742
  }
591
743
  )
592
744
  ] }),
593
- loading && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
594
- error && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
595
- !loading && !error && /* @__PURE__ */ jsxs2("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
596
- visible.map((f2) => /* @__PURE__ */ jsxs2(
745
+ loading && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
746
+ error && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
747
+ !loading && !error && /* @__PURE__ */ jsxs("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
748
+ visible.map((f2) => /* @__PURE__ */ jsxs(
597
749
  "li",
598
750
  {
599
751
  className: itemClassName,
600
752
  style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "flex", justifyContent: "space-between", alignItems: "center" },
601
753
  children: [
602
- /* @__PURE__ */ jsxs2("div", { style: { minWidth: 0 }, children: [
603
- /* @__PURE__ */ jsx2("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.originalName }),
604
- /* @__PURE__ */ jsxs2("div", { style: { fontSize: 12, color: "#666" }, children: [
754
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
755
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.originalName }),
756
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#666" }, children: [
605
757
  (f2.size / (1024 * 1024)).toFixed(2),
606
758
  " MB \u2022 ",
607
759
  f2.contentType
608
760
  ] })
609
761
  ] }),
610
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
611
- /* @__PURE__ */ jsx2("a", { href: f2.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
612
- /* @__PURE__ */ jsx2(
762
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
763
+ /* @__PURE__ */ jsx("a", { href: f2.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
764
+ /* @__PURE__ */ jsx(
613
765
  "button",
614
766
  {
615
767
  onClick: async () => {
@@ -646,7 +798,7 @@ var ProjectFilesWidget = ({
646
798
  children: savingKey === f2.key ? "Saving\u2026" : "Use"
647
799
  }
648
800
  ),
649
- showDelete && /* @__PURE__ */ jsx2(
801
+ showDelete && /* @__PURE__ */ jsx(
650
802
  "button",
651
803
  {
652
804
  onClick: () => handleDelete(f2.key),
@@ -660,15 +812,11 @@ var ProjectFilesWidget = ({
660
812
  },
661
813
  f2.key
662
814
  )),
663
- visible.length === 0 && /* @__PURE__ */ jsx2("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
815
+ visible.length === 0 && /* @__PURE__ */ jsx("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
664
816
  ] })
665
817
  ] });
666
818
  };
667
819
 
668
- // src/ConnectProjectDialog.tsx
669
- import * as React3 from "react";
670
- import * as Dialog from "@radix-ui/react-dialog";
671
-
672
820
  // src/projectConnect.ts
673
821
  var f = (globalThis.fetch || fetch).bind(globalThis);
674
822
  async function httpJson(input, init, useFetch) {
@@ -758,19 +906,16 @@ function connectUsingApiKey(opts) {
758
906
  const client = new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: opts.apiKeyHeader ?? "authorization" });
759
907
  return { apiKey, client };
760
908
  }
761
-
762
- // src/ConnectProjectDialog.tsx
763
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
764
909
  function ConnectProjectDialog(props) {
765
910
  const { baseUrl, open, onOpenChange, onConnected, fetchImpl, title, description, className } = props;
766
- const [mode, setMode] = React3.useState("existing");
767
- const [loading, setLoading] = React3.useState(false);
768
- const [error, setError] = React3.useState(null);
769
- const [projects, setProjects] = React3.useState([]);
770
- const [selectedProjectId, setSelectedProjectId] = React3.useState("");
771
- const [manualKey, setManualKey] = React3.useState("");
772
- const [newProjectName, setNewProjectName] = React3.useState("");
773
- const resetState = React3.useCallback(() => {
911
+ const [mode, setMode] = React4.useState("existing");
912
+ const [loading, setLoading] = React4.useState(false);
913
+ const [error, setError] = React4.useState(null);
914
+ const [projects, setProjects] = React4.useState([]);
915
+ const [selectedProjectId, setSelectedProjectId] = React4.useState("");
916
+ const [manualKey, setManualKey] = React4.useState("");
917
+ const [newProjectName, setNewProjectName] = React4.useState("");
918
+ const resetState = React4.useCallback(() => {
774
919
  setMode("existing");
775
920
  setLoading(false);
776
921
  setError(null);
@@ -779,7 +924,7 @@ function ConnectProjectDialog(props) {
779
924
  setManualKey("");
780
925
  setNewProjectName("");
781
926
  }, []);
782
- React3.useEffect(() => {
927
+ React4.useEffect(() => {
783
928
  const run = async () => {
784
929
  if (!open || mode !== "existing") return;
785
930
  try {
@@ -841,8 +986,8 @@ function ConnectProjectDialog(props) {
841
986
  setLoading(false);
842
987
  }
843
988
  };
844
- const ModeSwitcher = /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-3 gap-2", children: [
845
- /* @__PURE__ */ jsx3(
989
+ const ModeSwitcher = /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
990
+ /* @__PURE__ */ jsx(
846
991
  "button",
847
992
  {
848
993
  type: "button",
@@ -851,7 +996,7 @@ function ConnectProjectDialog(props) {
851
996
  children: "Connect existing"
852
997
  }
853
998
  ),
854
- /* @__PURE__ */ jsx3(
999
+ /* @__PURE__ */ jsx(
855
1000
  "button",
856
1001
  {
857
1002
  type: "button",
@@ -860,7 +1005,7 @@ function ConnectProjectDialog(props) {
860
1005
  children: "Add API key"
861
1006
  }
862
1007
  ),
863
- /* @__PURE__ */ jsx3(
1008
+ /* @__PURE__ */ jsx(
864
1009
  "button",
865
1010
  {
866
1011
  type: "button",
@@ -870,19 +1015,19 @@ function ConnectProjectDialog(props) {
870
1015
  }
871
1016
  )
872
1017
  ] });
873
- return /* @__PURE__ */ jsx3(Dialog.Root, { open, onOpenChange: (o) => {
1018
+ return /* @__PURE__ */ jsx(Dialog2.Root, { open, onOpenChange: (o) => {
874
1019
  if (!o) resetState();
875
1020
  onOpenChange(o);
876
- }, children: /* @__PURE__ */ jsxs3(Dialog.Portal, { children: [
877
- /* @__PURE__ */ jsx3(Dialog.Overlay, { className: "fixed inset-0 bg-black/40" }),
878
- /* @__PURE__ */ jsx3(Dialog.Content, { className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-md rounded-lg border bg-white p-4 shadow-lg ${className ?? ""}`, children: /* @__PURE__ */ jsxs3("div", { className: "space-y-3", children: [
879
- /* @__PURE__ */ jsx3(Dialog.Title, { className: "text-lg font-semibold", children: title ?? "Connect to Upfiles" }),
880
- /* @__PURE__ */ jsx3(Dialog.Description, { className: "text-sm text-muted-foreground", children: description ?? "Choose how to connect your project to retrieve an API key." }),
1021
+ }, children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
1022
+ /* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40" }),
1023
+ /* @__PURE__ */ jsx(Dialog2.Content, { className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-md rounded-lg border bg-white p-4 shadow-lg ${className ?? ""}`, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1024
+ /* @__PURE__ */ jsx(Dialog2.Title, { className: "text-lg font-semibold", children: title ?? "Connect to Upfiles" }),
1025
+ /* @__PURE__ */ jsx(Dialog2.Description, { className: "text-sm text-muted-foreground", children: description ?? "Choose how to connect your project to retrieve an API key." }),
881
1026
  ModeSwitcher,
882
- error && /* @__PURE__ */ jsx3("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
883
- mode === "existing" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
884
- /* @__PURE__ */ jsx3("label", { className: "text-sm font-medium", children: "Project" }),
885
- /* @__PURE__ */ jsxs3(
1027
+ error && /* @__PURE__ */ jsx("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
1028
+ mode === "existing" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1029
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Project" }),
1030
+ /* @__PURE__ */ jsxs(
886
1031
  "select",
887
1032
  {
888
1033
  className: "w-full rounded border px-3 py-2 text-sm",
@@ -890,14 +1035,14 @@ function ConnectProjectDialog(props) {
890
1035
  onChange: (e) => setSelectedProjectId(e.target.value),
891
1036
  disabled: loading,
892
1037
  children: [
893
- projects.length === 0 && /* @__PURE__ */ jsx3("option", { value: "", children: loading ? "Loading..." : "No projects found" }),
894
- projects.map((p) => /* @__PURE__ */ jsx3("option", { value: p.id, children: p.name }, p.id))
1038
+ projects.length === 0 && /* @__PURE__ */ jsx("option", { value: "", children: loading ? "Loading..." : "No projects found" }),
1039
+ projects.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: p.name }, p.id))
895
1040
  ]
896
1041
  }
897
1042
  ),
898
- /* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2 pt-2", children: [
899
- /* @__PURE__ */ jsx3(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx3("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
900
- /* @__PURE__ */ jsx3(
1043
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
1044
+ /* @__PURE__ */ jsx(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1045
+ /* @__PURE__ */ jsx(
901
1046
  "button",
902
1047
  {
903
1048
  type: "button",
@@ -909,9 +1054,9 @@ function ConnectProjectDialog(props) {
909
1054
  )
910
1055
  ] })
911
1056
  ] }),
912
- mode === "manual" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
913
- /* @__PURE__ */ jsx3("label", { className: "text-sm font-medium", children: "API key" }),
914
- /* @__PURE__ */ jsx3(
1057
+ mode === "manual" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1058
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "API key" }),
1059
+ /* @__PURE__ */ jsx(
915
1060
  "input",
916
1061
  {
917
1062
  className: "w-full rounded border px-3 py-2 text-sm",
@@ -921,9 +1066,9 @@ function ConnectProjectDialog(props) {
921
1066
  disabled: loading
922
1067
  }
923
1068
  ),
924
- /* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2 pt-2", children: [
925
- /* @__PURE__ */ jsx3(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx3("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
926
- /* @__PURE__ */ jsx3(
1069
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
1070
+ /* @__PURE__ */ jsx(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1071
+ /* @__PURE__ */ jsx(
927
1072
  "button",
928
1073
  {
929
1074
  type: "button",
@@ -935,9 +1080,9 @@ function ConnectProjectDialog(props) {
935
1080
  )
936
1081
  ] })
937
1082
  ] }),
938
- mode === "new" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
939
- /* @__PURE__ */ jsx3("label", { className: "text-sm font-medium", children: "Project name" }),
940
- /* @__PURE__ */ jsx3(
1083
+ mode === "new" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1084
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Project name" }),
1085
+ /* @__PURE__ */ jsx(
941
1086
  "input",
942
1087
  {
943
1088
  className: "w-full rounded border px-3 py-2 text-sm",
@@ -947,10 +1092,10 @@ function ConnectProjectDialog(props) {
947
1092
  disabled: loading
948
1093
  }
949
1094
  ),
950
- /* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: "A project will be created in your Upfiles account and an API key generated." }),
951
- /* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2 pt-2", children: [
952
- /* @__PURE__ */ jsx3(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx3("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
953
- /* @__PURE__ */ jsx3(
1095
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "A project will be created in your Upfiles account and an API key generated." }),
1096
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
1097
+ /* @__PURE__ */ jsx(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1098
+ /* @__PURE__ */ jsx(
954
1099
  "button",
955
1100
  {
956
1101
  type: "button",
@@ -976,7 +1121,7 @@ async function fetchFileThumbnails(clientOptions, fileKey) {
976
1121
  return client.getThumbnails(fileKey);
977
1122
  }
978
1123
  async function fetchProjectImagesWithThumbs(clientOptions, opts) {
979
- const files = await fetchProjectFiles(clientOptions, opts?.folderPath);
1124
+ const { files, updatedAt } = await fetchProjectFiles(clientOptions, opts?.folderPath);
980
1125
  const limit = Math.max(1, Math.min(opts?.limitConcurrent ?? 6, 12));
981
1126
  const queue = [...files];
982
1127
  const results = [];
@@ -998,13 +1143,62 @@ async function fetchProjectImagesWithThumbs(clientOptions, opts) {
998
1143
  await Promise.all(Array.from({ length: workers }, () => worker()));
999
1144
  const keyToIndex = new Map(files.map((f2, i) => [f2.key, i]));
1000
1145
  results.sort((a, b) => (keyToIndex.get(a.key) ?? 0) - (keyToIndex.get(b.key) ?? 0));
1001
- return results;
1146
+ return { items: results, updatedAt };
1002
1147
  }
1003
-
1004
- // src/ImageManager.tsx
1005
- import * as React4 from "react";
1006
- import * as Dialog2 from "@radix-ui/react-dialog";
1007
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1148
+ var IconGrid = ({ className }) => /* @__PURE__ */ 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: [
1149
+ /* @__PURE__ */ jsx("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
1150
+ /* @__PURE__ */ jsx("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
1151
+ /* @__PURE__ */ jsx("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
1152
+ /* @__PURE__ */ jsx("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
1153
+ ] });
1154
+ var IconUploadCloud = ({ className }) => /* @__PURE__ */ 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: [
1155
+ /* @__PURE__ */ jsx("path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }),
1156
+ /* @__PURE__ */ jsx("path", { d: "M12 12v9" }),
1157
+ /* @__PURE__ */ jsx("path", { d: "m16 16-4-4-4 4" })
1158
+ ] });
1159
+ var IconFolder = ({ className }) => /* @__PURE__ */ 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__ */ 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" }) });
1160
+ var IconFolderPlus = ({ className }) => /* @__PURE__ */ 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: [
1161
+ /* @__PURE__ */ 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" }),
1162
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "10", y2: "16" }),
1163
+ /* @__PURE__ */ jsx("line", { x1: "9", x2: "15", y1: "13", y2: "13" })
1164
+ ] });
1165
+ var IconSearch = ({ className }) => /* @__PURE__ */ 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: [
1166
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
1167
+ /* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })
1168
+ ] });
1169
+ var IconRefresh = ({ className }) => /* @__PURE__ */ 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: [
1170
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
1171
+ /* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" }),
1172
+ /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
1173
+ /* @__PURE__ */ jsx("path", { d: "M8 16H3v5" })
1174
+ ] });
1175
+ var IconChevronRight = ({ className }) => /* @__PURE__ */ 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__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
1176
+ var IconTrash = ({ className }) => /* @__PURE__ */ 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: [
1177
+ /* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
1178
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
1179
+ /* @__PURE__ */ jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
1180
+ /* @__PURE__ */ jsx("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
1181
+ /* @__PURE__ */ jsx("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
1182
+ ] });
1183
+ var IconX = ({ className }) => /* @__PURE__ */ 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: [
1184
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
1185
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
1186
+ ] });
1187
+ var IconHome = ({ className }) => /* @__PURE__ */ 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: [
1188
+ /* @__PURE__ */ jsx("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
1189
+ /* @__PURE__ */ jsx("polyline", { points: "9 22 9 12 15 12 15 22" })
1190
+ ] });
1191
+ var IconImage = ({ className }) => /* @__PURE__ */ 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: [
1192
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
1193
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "2" }),
1194
+ /* @__PURE__ */ jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
1195
+ ] });
1196
+ var IconLoader = ({ className }) => /* @__PURE__ */ 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__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
1197
+ var IconLayers = ({ className }) => /* @__PURE__ */ 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: [
1198
+ /* @__PURE__ */ jsx("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
1199
+ /* @__PURE__ */ jsx("polyline", { points: "2 17 12 22 22 17" }),
1200
+ /* @__PURE__ */ jsx("polyline", { points: "2 12 12 17 22 12" })
1201
+ ] });
1008
1202
  function ImageManager(props) {
1009
1203
  const {
1010
1204
  open,
@@ -1024,223 +1218,733 @@ function ImageManager(props) {
1024
1218
  maxFileSize = 100 * 1024 * 1024,
1025
1219
  maxFiles = 10,
1026
1220
  mode = "full",
1027
- showDelete = true
1221
+ showDelete = true,
1222
+ refreshInterval = 5e3
1028
1223
  } = props;
1029
- const [tab, setTab] = React4.useState(mode === "upload" ? "upload" : "browse");
1224
+ const [activeView, setActiveView] = React4.useState(mode === "upload" ? "upload" : "browse");
1030
1225
  const [loading, setLoading] = React4.useState(false);
1031
1226
  const [error, setError] = React4.useState(null);
1032
- const [items, setItems] = React4.useState([]);
1227
+ const [currentFolderId, setCurrentFolderId] = React4.useState(void 0);
1228
+ const [breadcrumbs, setBreadcrumbs] = React4.useState([{ id: void 0, name: "Home" }]);
1229
+ const [files, setFiles] = React4.useState([]);
1230
+ const [folders, setFolders] = React4.useState([]);
1033
1231
  const [query, setQuery] = React4.useState("");
1034
1232
  const [deleting, setDeleting] = React4.useState(null);
1035
- const load = React4.useCallback(async () => {
1233
+ const [showCreateFolder, setShowCreateFolder] = React4.useState(false);
1234
+ const [newFolderName, setNewFolderName] = React4.useState("");
1235
+ const [creatingFolder, setCreatingFolder] = React4.useState(false);
1236
+ const [fileToDelete, setFileToDelete] = React4.useState(null);
1237
+ const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4.useState(null);
1238
+ const [selectedThumbnailForFile, setSelectedThumbnailForFile] = React4.useState(null);
1239
+ const [previewFile, setPreviewFile] = React4.useState(null);
1240
+ const lastUpdatedRef = React4.useRef(null);
1241
+ const isFetchingRef = React4.useRef(false);
1242
+ const deduplicateFiles = React4.useCallback((files2) => {
1243
+ const seen = /* @__PURE__ */ new Set();
1244
+ return files2.filter((file) => {
1245
+ if (seen.has(file.key)) {
1246
+ return false;
1247
+ }
1248
+ seen.add(file.key);
1249
+ return true;
1250
+ });
1251
+ }, []);
1252
+ const load = React4.useCallback(async (isPolling = false) => {
1253
+ if (isFetchingRef.current) return;
1254
+ isFetchingRef.current = true;
1036
1255
  try {
1037
- setLoading(true);
1256
+ if (!isPolling) setLoading(true);
1038
1257
  setError(null);
1039
- const list = await fetchProjectImagesWithThumbs(clientOptions, { folderPath, limitConcurrent: 6 });
1040
- setItems(list);
1258
+ const client = new UpfilesClient(clientOptions);
1259
+ const currentPathPrefix = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
1260
+ const effectiveFolderPath = currentPathPrefix || folderPath;
1261
+ const [filesData, foldersData] = await Promise.all([
1262
+ fetchProjectImagesWithThumbs(clientOptions, { folderPath: effectiveFolderPath, limitConcurrent: 6 }),
1263
+ client.getFolders(currentFolderId)
1264
+ ]);
1265
+ const { items: list, updatedAt } = filesData;
1266
+ if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
1267
+ }
1268
+ setFiles(deduplicateFiles(list));
1269
+ setFolders(foldersData);
1270
+ lastUpdatedRef.current = updatedAt;
1041
1271
  } catch (e) {
1042
- setError(e?.message || "Failed to load images");
1272
+ if (!isPolling) setError(e?.message || "Failed to load images");
1043
1273
  } finally {
1044
- setLoading(false);
1274
+ if (!isPolling) setLoading(false);
1275
+ isFetchingRef.current = false;
1045
1276
  }
1046
- }, [clientOptions, folderPath]);
1277
+ }, [clientOptions, folderPath, currentFolderId, breadcrumbs]);
1047
1278
  React4.useEffect(() => {
1048
1279
  if (!open) {
1049
- setItems([]);
1280
+ setFiles([]);
1281
+ setFolders([]);
1050
1282
  setQuery("");
1051
1283
  setError(null);
1284
+ lastUpdatedRef.current = null;
1052
1285
  return;
1053
1286
  }
1054
- if (mode !== "upload" && tab === "browse") {
1287
+ if (mode !== "upload" && activeView === "browse") {
1055
1288
  load();
1056
1289
  }
1057
- }, [open, tab, load, mode]);
1058
- const visible = React4.useMemo(() => {
1059
- const q = query.trim().toLowerCase();
1060
- return !q ? items : items.filter((i) => (i.originalName || "").toLowerCase().includes(q));
1061
- }, [items, query]);
1062
- const handleDelete = React4.useCallback(
1063
- async (key) => {
1064
- if (!confirm("Delete this file?")) return;
1290
+ }, [open, activeView, load, mode]);
1291
+ React4.useEffect(() => {
1292
+ if (!open) return;
1293
+ const client = new UpfilesClient(clientOptions);
1294
+ const url = client.getEventsUrl(projectId);
1295
+ const eventSource = new EventSource(url);
1296
+ eventSource.onmessage = (event) => {
1065
1297
  try {
1066
- setDeleting(key);
1067
- if (onDelete) {
1068
- await onDelete(key);
1069
- } else {
1070
- const baseUrl = clientOptions?.baseUrl || "";
1071
- const url = baseUrl ? `${baseUrl}${deleteUrl}` : deleteUrl;
1072
- const headers = {
1073
- "content-type": "application/json",
1074
- ...clientOptions?.headers || {}
1075
- };
1076
- if (clientOptions?.apiKey) {
1077
- const keyHeader = clientOptions.apiKeyHeader || "authorization";
1078
- if (keyHeader === "authorization") {
1079
- headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
1080
- } else {
1081
- headers[keyHeader] = clientOptions.apiKey;
1082
- }
1083
- }
1084
- const res = await fetch(url, {
1085
- method: "DELETE",
1086
- headers,
1087
- credentials: clientOptions?.withCredentials ? "include" : "same-origin",
1088
- body: JSON.stringify({ fileKey: key })
1298
+ const payload = JSON.parse(event.data);
1299
+ if (payload.type === "file:uploaded") {
1300
+ const newFile = payload.data.file;
1301
+ setFiles((prev) => {
1302
+ if (prev.find((f2) => f2.key === newFile.key)) return prev;
1303
+ return deduplicateFiles([newFile, ...prev]);
1089
1304
  });
1090
- if (!res.ok) throw new Error("Delete failed");
1305
+ } else if (payload.type === "file:deleted") {
1306
+ const deletedKey = payload.data.fileKey || payload.data.file?.key;
1307
+ setFiles((prev) => prev.filter((f2) => f2.key !== deletedKey));
1091
1308
  }
1092
- setItems((prev) => prev.filter((f2) => f2.key !== key));
1093
1309
  } catch (e) {
1094
- alert(e?.message || "Failed to delete");
1095
- } finally {
1096
- setDeleting(null);
1310
+ console.error("SSE Error parsing message", e);
1097
1311
  }
1098
- },
1099
- [clientOptions, deleteUrl, onDelete]
1100
- );
1101
- return /* @__PURE__ */ jsx4(Dialog2.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
1102
- /* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 dark:bg-black/60" }),
1103
- /* @__PURE__ */ jsx4(
1104
- Dialog2.Content,
1105
- {
1106
- className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-4xl rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4 shadow-lg max-h-[90vh] overflow-auto ${className ?? ""}`,
1107
- children: /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
1108
- /* @__PURE__ */ jsx4(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 dark:text-white", children: title ?? "Image Manager" }),
1109
- /* @__PURE__ */ jsx4(Dialog2.Description, { className: "text-sm text-gray-600 dark:text-gray-400", children: description ?? "Upload new images or select from existing ones." }),
1110
- mode === "full" && /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 border-b border-gray-200 dark:border-gray-700", children: [
1111
- /* @__PURE__ */ jsx4(
1112
- "button",
1312
+ };
1313
+ return () => eventSource.close();
1314
+ }, [open, clientOptions, projectId]);
1315
+ const visibleFiles = React4.useMemo(() => {
1316
+ const q = query.trim().toLowerCase();
1317
+ return !q ? files : files.filter((i) => (i.originalName || "").toLowerCase().includes(q));
1318
+ }, [files, query]);
1319
+ const visibleFolders = React4.useMemo(() => {
1320
+ const q = query.trim().toLowerCase();
1321
+ return !q ? folders : folders.filter((f2) => (f2.name || "").toLowerCase().includes(q));
1322
+ }, [folders, query]);
1323
+ const handleDelete = async () => {
1324
+ if (!fileToDelete) return;
1325
+ const key = fileToDelete.key;
1326
+ try {
1327
+ setDeleting(key);
1328
+ if (onDelete) {
1329
+ await onDelete(key);
1330
+ } else {
1331
+ const client = new UpfilesClient(clientOptions);
1332
+ const baseUrl = clientOptions?.baseUrl || "";
1333
+ const url = baseUrl ? `${baseUrl}${deleteUrl}` : deleteUrl;
1334
+ const headers = {
1335
+ "content-type": "application/json",
1336
+ ...clientOptions?.headers || {}
1337
+ };
1338
+ if (clientOptions?.apiKey) {
1339
+ const keyHeader = clientOptions.apiKeyHeader || "authorization";
1340
+ if (keyHeader === "authorization") {
1341
+ headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
1342
+ } else {
1343
+ headers[keyHeader] = clientOptions.apiKey;
1344
+ }
1345
+ }
1346
+ const res = await fetch(url, {
1347
+ method: "DELETE",
1348
+ headers,
1349
+ credentials: clientOptions?.withCredentials ? "include" : "same-origin",
1350
+ body: JSON.stringify({ fileKey: key })
1351
+ });
1352
+ if (!res.ok) throw new Error("Delete failed");
1353
+ }
1354
+ setFiles((prev) => prev.filter((f2) => f2.key !== key));
1355
+ setFileToDelete(null);
1356
+ } catch (e) {
1357
+ alert(e?.message || "Failed to delete");
1358
+ } finally {
1359
+ setDeleting(null);
1360
+ }
1361
+ };
1362
+ const handleCreateFolder = async () => {
1363
+ if (!newFolderName.trim()) return;
1364
+ setCreatingFolder(true);
1365
+ try {
1366
+ const client = new UpfilesClient(clientOptions);
1367
+ await client.createFolder(newFolderName, currentFolderId);
1368
+ setNewFolderName("");
1369
+ setShowCreateFolder(false);
1370
+ load();
1371
+ } catch (e) {
1372
+ alert(e.message || "Failed to create folder");
1373
+ } finally {
1374
+ setCreatingFolder(false);
1375
+ }
1376
+ };
1377
+ const handleNavigate = (folder) => {
1378
+ setCurrentFolderId(folder.id);
1379
+ setBreadcrumbs((prev) => [...prev, { id: folder.id, name: folder.name }]);
1380
+ setQuery("");
1381
+ };
1382
+ const handleBreadcrumbClick = (index) => {
1383
+ const target = breadcrumbs[index];
1384
+ setBreadcrumbs(breadcrumbs.slice(0, index + 1));
1385
+ setCurrentFolderId(target.id);
1386
+ setQuery("");
1387
+ };
1388
+ const uploadFoldersList = React4.useMemo(() => {
1389
+ const options = [];
1390
+ options.push({ id: "root", name: "Root ( / )", path: void 0 });
1391
+ if (currentFolderId) {
1392
+ const currentPath = breadcrumbs.slice(1).map((b) => b.name).join("/") + "/";
1393
+ options.push({ id: "current", name: `Current ( ${breadcrumbs[breadcrumbs.length - 1].name} )`, path: currentPath });
1394
+ }
1395
+ folders.forEach((f2) => {
1396
+ const parentPath = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : "";
1397
+ options.push({ id: f2.id, name: f2.name, path: parentPath + f2.name + "/" });
1398
+ });
1399
+ return options;
1400
+ }, [breadcrumbs, currentFolderId, folders]);
1401
+ const [selectedUploadFolder, setSelectedUploadFolder] = React4.useState("current");
1402
+ const activeBreadcrumbPath = React4.useMemo(() => {
1403
+ return breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
1404
+ }, [breadcrumbs]);
1405
+ return /* @__PURE__ */ jsxs(Dialog2.Root, { open, onOpenChange, children: [
1406
+ /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
1407
+ /* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/60 backdrop-blur-sm z-50 transition-all duration-300 flex items-center justify-center" }),
1408
+ /* @__PURE__ */ jsxs(
1409
+ Dialog2.Content,
1410
+ {
1411
+ className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] h-[90vh] max-w-7xl min-w-[800px] min-h-[600px] rounded-2xl bg-white dark:bg-gray-950 shadow-2xl z-[51] overflow-hidden flex flex-row ${className ?? ""}`,
1412
+ children: [
1413
+ /* @__PURE__ */ jsx(
1414
+ Dialog2.Title,
1113
1415
  {
1114
- type: "button",
1115
- onClick: () => setTab("browse"),
1116
- className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === "browse" ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`,
1117
- children: "Browse"
1416
+ style: {
1417
+ position: "absolute",
1418
+ width: "1px",
1419
+ height: "1px",
1420
+ padding: "0",
1421
+ margin: "-1px",
1422
+ overflow: "hidden",
1423
+ clip: "rect(0, 0, 0, 0)",
1424
+ whiteSpace: "nowrap",
1425
+ border: "0"
1426
+ },
1427
+ children: title ?? "Media Library"
1118
1428
  }
1119
1429
  ),
1120
- /* @__PURE__ */ jsx4(
1121
- "button",
1122
- {
1123
- type: "button",
1124
- onClick: () => setTab("upload"),
1125
- className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === "upload" ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`,
1126
- children: "Upload"
1127
- }
1128
- )
1129
- ] }),
1130
- (mode === "browse" || mode === "full" && tab === "browse") && /* @__PURE__ */ jsxs4(Fragment, { children: [
1131
- /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 items-center", children: [
1132
- /* @__PURE__ */ jsx4(
1133
- "input",
1134
- {
1135
- className: "w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-400",
1136
- placeholder: "Search by filename...",
1137
- value: query,
1138
- onChange: (e) => setQuery(e.target.value)
1139
- }
1140
- ),
1141
- /* @__PURE__ */ jsx4("button", { type: "button", className: "px-3 py-2 text-sm rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50", onClick: load, disabled: loading, children: loading ? "Loading\u2026" : "Refresh" })
1430
+ /* @__PURE__ */ jsxs("div", { className: "w-64 min-w-[256px] bg-gray-50 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col flex-shrink-0", children: [
1431
+ /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
1432
+ /* @__PURE__ */ jsxs("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
1433
+ /* @__PURE__ */ jsx(IconImage, { className: "text-blue-600" }),
1434
+ title ?? "Media Library"
1435
+ ] }),
1436
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: description ?? "Manage your assets" })
1437
+ ] }),
1438
+ /* @__PURE__ */ jsxs("nav", { className: "flex-1 px-3 space-y-1", children: [
1439
+ /* @__PURE__ */ jsxs(
1440
+ "button",
1441
+ {
1442
+ onClick: () => setActiveView("browse"),
1443
+ 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"}`,
1444
+ children: [
1445
+ /* @__PURE__ */ jsx(IconGrid, { className: "w-4 h-4" }),
1446
+ "All Files"
1447
+ ]
1448
+ }
1449
+ ),
1450
+ mode === "full" && /* @__PURE__ */ jsxs(
1451
+ "button",
1452
+ {
1453
+ onClick: () => setActiveView("upload"),
1454
+ 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"}`,
1455
+ children: [
1456
+ /* @__PURE__ */ jsx(IconUploadCloud, { className: "w-4 h-4" }),
1457
+ "Upload New"
1458
+ ]
1459
+ }
1460
+ )
1461
+ ] }),
1462
+ /* @__PURE__ */ jsx("div", { className: "p-4 border-t border-gray-200 dark:border-gray-800", children: /* @__PURE__ */ jsxs("div", { className: "text-xs text-center text-gray-400", children: [
1463
+ files.length,
1464
+ " files \u2022 ",
1465
+ folders.length,
1466
+ " folders"
1467
+ ] }) })
1142
1468
  ] }),
1143
- error && /* @__PURE__ */ jsx4("div", { className: "rounded border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 px-3 py-2 text-sm text-red-700 dark:text-red-400", children: error }),
1144
- /* @__PURE__ */ jsxs4("div", { className: gridClassName ?? "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 max-h-[50vh] overflow-auto", children: [
1145
- !loading && visible.map((f2) => {
1146
- const thumbUrl = f2.thumbnails?.[0]?.url || f2.url;
1147
- return /* @__PURE__ */ jsxs4("div", { className: "group relative aspect-square overflow-hidden rounded border border-gray-200 dark:border-gray-700", children: [
1148
- /* @__PURE__ */ jsx4("img", { src: thumbUrl, alt: f2.originalName, className: "h-full w-full object-cover" }),
1149
- /* @__PURE__ */ jsxs4("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-center justify-center gap-2", children: [
1150
- /* @__PURE__ */ jsx4(
1151
- "button",
1152
- {
1153
- type: "button",
1154
- onClick: () => {
1155
- onSelect({
1156
- url: f2.url,
1157
- key: f2.key,
1158
- originalName: f2.originalName,
1159
- size: f2.size,
1160
- contentType: f2.contentType,
1161
- thumbnails: f2.thumbnails
1162
- });
1163
- onOpenChange(false);
1164
- },
1165
- className: "opacity-0 group-hover:opacity-100 px-3 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded",
1166
- children: "Select"
1167
- }
1168
- ),
1169
- showDelete && /* @__PURE__ */ jsx4(
1170
- "button",
1469
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col bg-white dark:bg-gray-950 relative", children: [
1470
+ /* @__PURE__ */ jsxs("div", { className: "h-16 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between px-6 gap-4", children: [
1471
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4 flex-1", children: activeView === "browse" && /* @__PURE__ */ jsxs(Fragment, { children: [
1472
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center text-sm text-gray-500 overflow-hidden whitespace-nowrap", children: [
1473
+ /* @__PURE__ */ jsx("button", { onClick: () => handleBreadcrumbClick(0), className: "hover:text-blue-600 transition-colors", children: /* @__PURE__ */ jsx(IconHome, { className: "w-4 h-4" }) }),
1474
+ breadcrumbs.slice(1).map((b, i) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
1475
+ /* @__PURE__ */ jsx(IconChevronRight, { className: "w-4 h-4 text-gray-300 mx-1" }),
1476
+ /* @__PURE__ */ jsx(
1477
+ "button",
1478
+ {
1479
+ onClick: () => handleBreadcrumbClick(i + 1),
1480
+ className: `hover:text-blue-600 transition-colors truncate max-w-[100px] ${i === breadcrumbs.length - 2 ? "font-semibold text-gray-900 dark:text-white" : ""}`,
1481
+ children: b.name
1482
+ }
1483
+ )
1484
+ ] }, `${b.id || "breadcrumb"}-${i}`))
1485
+ ] }),
1486
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
1487
+ /* @__PURE__ */ jsx(
1488
+ "button",
1489
+ {
1490
+ onClick: () => load(),
1491
+ disabled: loading,
1492
+ 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",
1493
+ title: "Refresh",
1494
+ children: /* @__PURE__ */ jsx(IconRefresh, { className: `w-4 h-4 ${loading ? "animate-spin" : ""}` })
1495
+ }
1496
+ ),
1497
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
1498
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1 max-w-sm group", children: [
1499
+ /* @__PURE__ */ 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" }),
1500
+ /* @__PURE__ */ jsx(
1501
+ "input",
1171
1502
  {
1172
- type: "button",
1173
- onClick: () => handleDelete(f2.key),
1174
- disabled: deleting === f2.key,
1175
- className: "opacity-0 group-hover:opacity-100 px-3 py-1 text-xs bg-red-600 hover:bg-red-700 text-white rounded disabled:opacity-50",
1176
- children: deleting === f2.key ? "..." : "Delete"
1503
+ value: query,
1504
+ onChange: (e) => setQuery(e.target.value),
1505
+ placeholder: "Search files...",
1506
+ 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"
1177
1507
  }
1178
1508
  )
1509
+ ] })
1510
+ ] }) }),
1511
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1512
+ activeView === "browse" && /* @__PURE__ */ jsxs(Fragment, { children: [
1513
+ /* @__PURE__ */ 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__ */ jsx(IconFolderPlus, { className: "w-5 h-5" }) }),
1514
+ /* @__PURE__ */ 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__ */ jsx(IconLoader, { className: "w-5 h-5" }) })
1515
+ ] }),
1516
+ /* @__PURE__ */ jsx(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ 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__ */ jsx(IconX, { className: "w-5 h-5" }) }) })
1517
+ ] })
1518
+ ] }),
1519
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50/30 dark:bg-gray-900/30", children: [
1520
+ activeView === "browse" && /* @__PURE__ */ jsxs(Fragment, { children: [
1521
+ error && /* @__PURE__ */ 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__ */ jsx("span", { children: error }) }),
1522
+ /* @__PURE__ */ jsxs("div", { className: gridClassName ?? "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6", children: [
1523
+ visibleFolders.map((folder) => /* @__PURE__ */ jsxs(
1524
+ "div",
1525
+ {
1526
+ onClick: () => handleNavigate(folder),
1527
+ 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",
1528
+ children: [
1529
+ /* @__PURE__ */ 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__ */ jsx(IconFolder, { className: "w-8 h-8 drop-shadow-sm" }) }),
1530
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 truncate w-full text-center", children: folder.name })
1531
+ ]
1532
+ },
1533
+ folder.id
1534
+ )),
1535
+ visibleFiles.map((f2) => {
1536
+ const fWithThumbs = f2;
1537
+ const bestThumb = fWithThumbs.thumbnails?.sort((a, b) => a.size - b.size).find((t) => t.size >= 300) || fWithThumbs.thumbnails?.[fWithThumbs.thumbnails?.length - 1];
1538
+ const thumbUrl = bestThumb?.url || f2.url;
1539
+ return /* @__PURE__ */ jsxs(
1540
+ "div",
1541
+ {
1542
+ onClick: () => setPreviewFile(f2),
1543
+ 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 cursor-pointer",
1544
+ children: [
1545
+ /* @__PURE__ */ jsx("img", { src: thumbUrl, alt: f2.originalName, className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" }),
1546
+ /* @__PURE__ */ 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__ */ jsxs("div", { className: "flex gap-2 justify-center transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [
1547
+ /* @__PURE__ */ jsx(
1548
+ "button",
1549
+ {
1550
+ onClick: (e) => {
1551
+ e.stopPropagation();
1552
+ onSelect({
1553
+ url: f2.url,
1554
+ key: f2.key,
1555
+ originalName: f2.originalName,
1556
+ size: f2.size,
1557
+ contentType: f2.contentType,
1558
+ thumbnails: f2.thumbnails
1559
+ });
1560
+ onOpenChange(false);
1561
+ },
1562
+ className: "px-3 py-1.5 bg-blue-600 text-white text-xs font-semibold rounded-md hover:bg-blue-500 shadow-lg",
1563
+ children: "Select"
1564
+ }
1565
+ ),
1566
+ fWithThumbs.thumbnails && fWithThumbs.thumbnails.length > 0 && /* @__PURE__ */ jsx(
1567
+ "button",
1568
+ {
1569
+ onClick: (e) => {
1570
+ e.stopPropagation();
1571
+ setThumbnailSelectorFile(f2);
1572
+ setSelectedThumbnailForFile(null);
1573
+ },
1574
+ className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-purple-500 backdrop-blur-md transition-colors",
1575
+ title: "Select Thumbnail",
1576
+ children: /* @__PURE__ */ jsx(IconLayers, { className: "w-4 h-4" })
1577
+ }
1578
+ ),
1579
+ showDelete && /* @__PURE__ */ jsx(
1580
+ "button",
1581
+ {
1582
+ onClick: (e) => {
1583
+ e.stopPropagation();
1584
+ setFileToDelete(f2);
1585
+ },
1586
+ className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-red-500 backdrop-blur-md transition-colors",
1587
+ children: /* @__PURE__ */ jsx(IconTrash, { className: "w-4 h-4" })
1588
+ }
1589
+ )
1590
+ ] }) }),
1591
+ /* @__PURE__ */ 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() })
1592
+ ]
1593
+ },
1594
+ f2.key
1595
+ );
1596
+ })
1597
+ ] }),
1598
+ !loading && visibleFolders.length === 0 && visibleFiles.length === 0 && /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col items-center justify-center text-gray-400", children: [
1599
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-4", children: /* @__PURE__ */ jsx(IconSearch, { className: "w-8 h-8 opacity-40" }) }),
1600
+ /* @__PURE__ */ jsx("h3", { className: "text-gray-900 font-medium mb-1", children: "No Assets Found" }),
1601
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
1602
+ ] })
1603
+ ] }),
1604
+ activeView === "upload" && /* @__PURE__ */ jsx("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto", children: /* @__PURE__ */ jsxs("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
1605
+ /* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
1606
+ /* @__PURE__ */ jsx(IconUploadCloud, { className: "text-blue-500" }),
1607
+ "Upload Files"
1179
1608
  ] }),
1180
- /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 bg-black/50 p-1 text-[10px] text-white truncate", children: f2.originalName })
1181
- ] }, f2.key);
1182
- }),
1183
- loading && /* @__PURE__ */ jsx4("div", { className: "col-span-full text-sm text-gray-600 dark:text-gray-400", children: "Loading\u2026" }),
1184
- !loading && visible.length === 0 && /* @__PURE__ */ jsx4("div", { className: "col-span-full text-sm text-gray-600 dark:text-gray-400", children: "No images found" })
1609
+ /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
1610
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Target Folder" }),
1611
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1612
+ /* @__PURE__ */ jsx(
1613
+ "select",
1614
+ {
1615
+ value: selectedUploadFolder,
1616
+ onChange: (e) => setSelectedUploadFolder(e.target.value),
1617
+ 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",
1618
+ children: uploadFoldersList.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.id, children: opt.name }, opt.id))
1619
+ }
1620
+ ),
1621
+ /* @__PURE__ */ jsx("button", { onClick: () => setShowCreateFolder(true), className: "px-3 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-600 transition-colors", children: /* @__PURE__ */ jsx(IconFolderPlus, { className: "w-5 h-5" }) })
1622
+ ] })
1623
+ ] }),
1624
+ /* @__PURE__ */ jsx(
1625
+ Uploader,
1626
+ {
1627
+ clientOptions,
1628
+ projectId,
1629
+ folderPath: uploadFoldersList.find((o) => o.id === selectedUploadFolder)?.path || activeBreadcrumbPath || folderPath,
1630
+ accept: ["image/*"],
1631
+ maxFileSize,
1632
+ maxFiles,
1633
+ autoRecordToDb,
1634
+ fetchThumbnails,
1635
+ autoUpload: true,
1636
+ onClientUploadComplete: (file) => {
1637
+ if (file.status === "success" && file.publicUrl && file.fileKey) {
1638
+ const newItem = {
1639
+ key: file.fileKey,
1640
+ originalName: file.name,
1641
+ size: file.size,
1642
+ contentType: file.type,
1643
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
1644
+ url: file.publicUrl,
1645
+ thumbnails: file.thumbnails
1646
+ };
1647
+ setFiles((prev) => {
1648
+ if (prev.find((f2) => f2.key === newItem.key)) return prev;
1649
+ return deduplicateFiles([newItem, ...prev]);
1650
+ });
1651
+ }
1652
+ },
1653
+ onComplete: () => {
1654
+ if (mode === "full") {
1655
+ setTimeout(() => {
1656
+ setActiveView("browse");
1657
+ load();
1658
+ }, 1e3);
1659
+ }
1660
+ },
1661
+ 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",
1662
+ buttonClassName: "hidden",
1663
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
1664
+ /* @__PURE__ */ jsx("div", { className: "w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx(IconUploadCloud, { className: "w-6 h-6" }) }),
1665
+ /* @__PURE__ */ jsxs("div", { children: [
1666
+ /* @__PURE__ */ jsx("p", { className: "text-gray-900 font-medium", children: "Click to upload or drag and drop" }),
1667
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 mt-1", children: [
1668
+ "Supports PNG, JPG, GIF up to ",
1669
+ Math.round(maxFileSize / 1024 / 1024),
1670
+ "MB"
1671
+ ] })
1672
+ ] })
1673
+ ] })
1674
+ }
1675
+ )
1676
+ ] }) })
1677
+ ] })
1185
1678
  ] })
1679
+ ]
1680
+ }
1681
+ )
1682
+ ] }),
1683
+ /* @__PURE__ */ jsx(Dialog2.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
1684
+ /* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[60]" }),
1685
+ /* @__PURE__ */ 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", "aria-describedby": "new-folder-description", children: [
1686
+ /* @__PURE__ */ jsx(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
1687
+ /* @__PURE__ */ jsx(Dialog2.Description, { id: "new-folder-description", className: "sr-only", children: "Create a new folder to organize your files" }),
1688
+ /* @__PURE__ */ jsx(
1689
+ "input",
1690
+ {
1691
+ autoFocus: true,
1692
+ 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",
1693
+ placeholder: "Folder Name",
1694
+ value: newFolderName,
1695
+ onChange: (e) => setNewFolderName(e.target.value),
1696
+ onKeyDown: (e) => e.key === "Enter" && handleCreateFolder()
1697
+ }
1698
+ ),
1699
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3", children: [
1700
+ /* @__PURE__ */ 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" }),
1701
+ /* @__PURE__ */ jsx(
1702
+ "button",
1703
+ {
1704
+ onClick: handleCreateFolder,
1705
+ disabled: creatingFolder || !newFolderName.trim(),
1706
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50",
1707
+ children: "Create"
1708
+ }
1709
+ )
1710
+ ] })
1711
+ ] })
1712
+ ] }) }),
1713
+ /* @__PURE__ */ jsx(Dialog2.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
1714
+ /* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[80]" }),
1715
+ /* @__PURE__ */ 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]", "aria-describedby": "delete-file-description", children: [
1716
+ /* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
1717
+ /* @__PURE__ */ jsxs(Dialog2.Description, { id: "delete-file-description", className: "text-gray-600 mb-6", children: [
1718
+ "Are you sure you want to delete ",
1719
+ /* @__PURE__ */ jsxs("span", { className: "font-semibold text-gray-900", children: [
1720
+ '"',
1721
+ fileToDelete?.originalName,
1722
+ '"'
1186
1723
  ] }),
1187
- (mode === "upload" || mode === "full" && tab === "upload") && /* @__PURE__ */ jsx4(
1188
- Uploader,
1724
+ "?"
1725
+ ] }),
1726
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3", children: [
1727
+ /* @__PURE__ */ jsx(
1728
+ "button",
1729
+ {
1730
+ onClick: () => setFileToDelete(null),
1731
+ className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg",
1732
+ children: "Cancel"
1733
+ }
1734
+ ),
1735
+ /* @__PURE__ */ jsx(
1736
+ "button",
1737
+ {
1738
+ onClick: handleDelete,
1739
+ disabled: !!deleting,
1740
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-red-600 text-white hover:bg-red-700 disabled:opacity-70",
1741
+ children: deleting ? "Deleting..." : "Delete"
1742
+ }
1743
+ )
1744
+ ] })
1745
+ ] })
1746
+ ] }) }),
1747
+ /* @__PURE__ */ jsx(Dialog2.Root, { open: !!thumbnailSelectorFile, onOpenChange: (open2) => !open2 && setThumbnailSelectorFile(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
1748
+ /* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/50 z-[90]" }),
1749
+ /* @__PURE__ */ jsxs(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-4xl max-h-[80vh] rounded-xl bg-white shadow-2xl z-[91] overflow-hidden", "aria-describedby": "thumbnail-selector-description", children: [
1750
+ /* @__PURE__ */ jsxs("div", { className: "p-6 border-b border-gray-200", children: [
1751
+ /* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Select Thumbnail" }),
1752
+ /* @__PURE__ */ jsxs(Dialog2.Description, { id: "thumbnail-selector-description", className: "text-gray-600", children: [
1753
+ "Choose a thumbnail size for ",
1754
+ /* @__PURE__ */ jsxs("span", { className: "font-semibold text-gray-900", children: [
1755
+ '"',
1756
+ thumbnailSelectorFile?.originalName,
1757
+ '"'
1758
+ ] })
1759
+ ] })
1760
+ ] }),
1761
+ /* @__PURE__ */ jsx("div", { className: "p-6 overflow-y-auto max-h-[60vh]", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4", children: [
1762
+ /* @__PURE__ */ jsxs(
1763
+ "div",
1764
+ {
1765
+ onClick: () => setSelectedThumbnailForFile(null),
1766
+ className: `group cursor-pointer rounded-lg border-2 overflow-hidden transition-all ${selectedThumbnailForFile === null ? "border-blue-500 ring-2 ring-blue-200" : "border-gray-200 hover:border-blue-300"}`,
1767
+ children: [
1768
+ /* @__PURE__ */ jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
1769
+ /* @__PURE__ */ jsx(
1770
+ "img",
1771
+ {
1772
+ src: thumbnailSelectorFile?.url,
1773
+ alt: "Original",
1774
+ className: "w-full h-full object-cover"
1775
+ }
1776
+ ),
1777
+ selectedThumbnailForFile === null && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }) })
1778
+ ] }),
1779
+ /* @__PURE__ */ jsxs("div", { className: "p-3 bg-white", children: [
1780
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-sm text-gray-900", children: "Original" }),
1781
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: "Full size" })
1782
+ ] })
1783
+ ]
1784
+ }
1785
+ ),
1786
+ thumbnailSelectorFile?.thumbnails?.map((thumbnail) => /* @__PURE__ */ jsxs(
1787
+ "div",
1788
+ {
1789
+ onClick: () => setSelectedThumbnailForFile(thumbnail),
1790
+ className: `group cursor-pointer rounded-lg border-2 overflow-hidden transition-all ${selectedThumbnailForFile?.id === thumbnail.id ? "border-blue-500 ring-2 ring-blue-200" : "border-gray-200 hover:border-blue-300"}`,
1791
+ children: [
1792
+ /* @__PURE__ */ jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
1793
+ /* @__PURE__ */ jsx(
1794
+ "img",
1795
+ {
1796
+ src: thumbnail.url,
1797
+ alt: `${thumbnail.size}px thumbnail`,
1798
+ className: "w-full h-full object-cover"
1799
+ }
1800
+ ),
1801
+ selectedThumbnailForFile?.id === thumbnail.id && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }) })
1802
+ ] }),
1803
+ /* @__PURE__ */ jsxs("div", { className: "p-3 bg-white", children: [
1804
+ /* @__PURE__ */ jsxs("div", { className: "font-medium text-sm text-gray-900", children: [
1805
+ thumbnail.size,
1806
+ "px"
1807
+ ] }),
1808
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: thumbnail.sizeType || "Thumbnail" })
1809
+ ] })
1810
+ ]
1811
+ },
1812
+ thumbnail.id
1813
+ ))
1814
+ ] }) }),
1815
+ /* @__PURE__ */ jsxs("div", { className: "p-6 border-t border-gray-200 flex justify-end gap-3", children: [
1816
+ /* @__PURE__ */ jsx(
1817
+ "button",
1818
+ {
1819
+ onClick: () => setThumbnailSelectorFile(null),
1820
+ className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg",
1821
+ children: "Cancel"
1822
+ }
1823
+ ),
1824
+ /* @__PURE__ */ jsxs(
1825
+ "button",
1189
1826
  {
1190
- clientOptions,
1191
- projectId,
1192
- folderPath,
1193
- accept: ["image/*"],
1194
- maxFileSize,
1195
- maxFiles,
1196
- autoRecordToDb,
1197
- fetchThumbnails,
1198
- autoUpload: true,
1199
- onClientUploadComplete: (file) => {
1200
- if (file.status === "success" && file.publicUrl && file.fileKey) {
1201
- const newItem = {
1202
- key: file.fileKey,
1203
- originalName: file.name,
1204
- size: file.size,
1205
- contentType: file.type,
1206
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
1207
- url: file.publicUrl,
1208
- thumbnails: file.thumbnails
1209
- };
1210
- setItems((prev) => [newItem, ...prev]);
1827
+ onClick: () => {
1828
+ if (thumbnailSelectorFile) {
1829
+ onSelect({
1830
+ url: selectedThumbnailForFile?.url || thumbnailSelectorFile.url,
1831
+ key: thumbnailSelectorFile.key,
1832
+ originalName: thumbnailSelectorFile.originalName,
1833
+ size: thumbnailSelectorFile.size,
1834
+ contentType: thumbnailSelectorFile.contentType,
1835
+ thumbnails: thumbnailSelectorFile.thumbnails,
1836
+ selectedThumbnail: selectedThumbnailForFile || void 0
1837
+ });
1838
+ onOpenChange(false);
1839
+ setThumbnailSelectorFile(null);
1211
1840
  }
1212
1841
  },
1213
- onComplete: (files) => {
1214
- if (mode === "full") {
1215
- setTimeout(() => {
1216
- load();
1217
- setTab("browse");
1218
- }, 500);
1842
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700",
1843
+ children: [
1844
+ "Select ",
1845
+ selectedThumbnailForFile ? `${selectedThumbnailForFile.size}px Thumbnail` : "Original"
1846
+ ]
1847
+ }
1848
+ )
1849
+ ] })
1850
+ ] })
1851
+ ] }) }),
1852
+ /* @__PURE__ */ jsx(Dialog2.Root, { open: !!previewFile, onOpenChange: (open2) => !open2 && setPreviewFile(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
1853
+ /* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/90 z-[100]", onClick: () => setPreviewFile(null) }),
1854
+ /* @__PURE__ */ jsx(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] h-[90vh] z-[101] outline-none", "aria-describedby": "image-preview-description", children: /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full flex flex-col", children: [
1855
+ /* @__PURE__ */ jsx("div", { className: "absolute top-0 left-0 right-0 z-10 bg-gradient-to-b from-black/70 to-transparent p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
1856
+ /* @__PURE__ */ jsxs("div", { className: "text-white", children: [
1857
+ /* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold mb-1", children: previewFile?.originalName }),
1858
+ /* @__PURE__ */ jsxs(Dialog2.Description, { id: "image-preview-description", className: "text-sm text-gray-300", children: [
1859
+ previewFile?.size ? `${(previewFile.size / 1024).toFixed(1)} KB` : "",
1860
+ " \u2022 ",
1861
+ previewFile?.contentType
1862
+ ] })
1863
+ ] }),
1864
+ /* @__PURE__ */ jsx(
1865
+ "button",
1866
+ {
1867
+ onClick: () => setPreviewFile(null),
1868
+ className: "p-2 bg-white/10 hover:bg-white/20 rounded-lg backdrop-blur-md transition-colors",
1869
+ children: /* @__PURE__ */ jsx(IconX, { className: "w-6 h-6 text-white" })
1870
+ }
1871
+ )
1872
+ ] }) }),
1873
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center p-6", children: /* @__PURE__ */ jsx(
1874
+ "img",
1875
+ {
1876
+ src: previewFile?.url,
1877
+ alt: previewFile?.originalName,
1878
+ className: "max-w-full max-h-full object-contain",
1879
+ onError: (e) => {
1880
+ const target = e.target;
1881
+ target.style.display = "none";
1882
+ const errorDiv = document.createElement("div");
1883
+ errorDiv.className = "text-white text-center";
1884
+ errorDiv.innerHTML = `
1885
+ <div class="text-red-400 mb-2">Failed to load image</div>
1886
+ <div class="text-sm text-gray-400">URL: ${previewFile?.url}</div>
1887
+ `;
1888
+ target.parentElement?.appendChild(errorDiv);
1889
+ }
1890
+ }
1891
+ ) }),
1892
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 z-10 bg-gradient-to-t from-black/70 to-transparent p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-3 justify-center", children: [
1893
+ /* @__PURE__ */ jsx(
1894
+ "button",
1895
+ {
1896
+ onClick: (e) => {
1897
+ e.stopPropagation();
1898
+ if (previewFile) {
1899
+ onSelect({
1900
+ url: previewFile.url,
1901
+ key: previewFile.key,
1902
+ originalName: previewFile.originalName,
1903
+ size: previewFile.size,
1904
+ contentType: previewFile.contentType,
1905
+ thumbnails: previewFile.thumbnails
1906
+ });
1907
+ onOpenChange(false);
1908
+ setPreviewFile(null);
1219
1909
  }
1220
1910
  },
1221
- 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",
1222
- 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"
1911
+ className: "px-6 py-2.5 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-500 shadow-lg",
1912
+ children: "Select Image"
1223
1913
  }
1224
1914
  ),
1225
- /* @__PURE__ */ jsx4("div", { className: "flex justify-end gap-2 pt-2 border-t border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsx4(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx4("button", { type: "button", className: "px-4 py-2 text-sm rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600", children: "Close" }) }) })
1226
- ] })
1227
- }
1228
- )
1229
- ] }) });
1915
+ /* @__PURE__ */ jsx(
1916
+ "a",
1917
+ {
1918
+ href: previewFile?.url,
1919
+ target: "_blank",
1920
+ rel: "noopener noreferrer",
1921
+ onClick: (e) => e.stopPropagation(),
1922
+ className: "px-6 py-2.5 bg-white/10 text-white font-semibold rounded-lg hover:bg-white/20 backdrop-blur-md transition-colors",
1923
+ children: "Open in New Tab"
1924
+ }
1925
+ ),
1926
+ previewFile?.thumbnails && previewFile.thumbnails.length > 0 && /* @__PURE__ */ jsxs(
1927
+ "button",
1928
+ {
1929
+ onClick: (e) => {
1930
+ e.stopPropagation();
1931
+ setThumbnailSelectorFile(previewFile);
1932
+ setSelectedThumbnailForFile(null);
1933
+ setPreviewFile(null);
1934
+ },
1935
+ className: "px-6 py-2.5 bg-purple-600 text-white font-semibold rounded-lg hover:bg-purple-500 shadow-lg flex items-center gap-2",
1936
+ children: [
1937
+ /* @__PURE__ */ jsx(IconLayers, { className: "w-5 h-5" }),
1938
+ "Select Thumbnail"
1939
+ ]
1940
+ }
1941
+ )
1942
+ ] }) })
1943
+ ] }) })
1944
+ ] }) })
1945
+ ] });
1230
1946
  }
1231
- export {
1232
- ConnectProjectDialog,
1233
- ImageManager,
1234
- ProjectFilesWidget,
1235
- UpfilesClient,
1236
- Uploader,
1237
- addApiKeyManually,
1238
- connectProject,
1239
- connectUsingApiKey,
1240
- createClientWithKey,
1241
- createProject,
1242
- fetchFileThumbnails,
1243
- fetchProjectFiles,
1244
- fetchProjectImagesWithThumbs,
1245
- listProjects
1246
- };
1947
+
1948
+ export { ConnectProjectDialog, ImageManager, ProjectFilesWidget, UpfilesClient, Uploader, addApiKeyManually, connectProject, connectUsingApiKey, createClientWithKey, createProject, fetchFileThumbnails, fetchProjectFiles, fetchProjectImagesWithThumbs, listProjects };
1949
+ //# sourceMappingURL=index.mjs.map
1950
+ //# sourceMappingURL=index.mjs.map