@thetechfossil/upfiles 1.0.7 → 1.0.11

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