@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/README.md +8 -0
- package/dist/index.d.mts +56 -4
- package/dist/index.d.ts +56 -4
- package/dist/index.js +790 -280
- package/dist/index.mjs +791 -281
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,9 @@ var UpfilesClient = class {
|
|
|
9
9
|
this.apiKey = opts.apiKey;
|
|
10
10
|
this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
|
|
11
11
|
this.thumbnailsPath = opts.thumbnailsPath ?? "/api/plugin/thumbnails";
|
|
12
|
+
this.completionPath = opts.completionPath ?? "/api/plugin/upload/complete";
|
|
13
|
+
this.foldersPath = opts.foldersPath ?? "/api/plugin/folders";
|
|
14
|
+
this.callCompletionEndpoint = opts.callCompletionEndpoint ?? false;
|
|
12
15
|
}
|
|
13
16
|
async getPresignedUrl(params) {
|
|
14
17
|
if (!params.fileName) throw new Error("fileName is required");
|
|
@@ -67,6 +70,20 @@ var UpfilesClient = class {
|
|
|
67
70
|
if (!res.ok) {
|
|
68
71
|
throw new Error(`Upload failed with status ${res.status}`);
|
|
69
72
|
}
|
|
73
|
+
const shouldCallCompletion = extras?.callCompletionEndpoint ?? this.callCompletionEndpoint;
|
|
74
|
+
if (shouldCallCompletion) {
|
|
75
|
+
try {
|
|
76
|
+
await this.completeUpload({
|
|
77
|
+
fileKey: presign.fileKey,
|
|
78
|
+
originalName: file.name,
|
|
79
|
+
size: file.size,
|
|
80
|
+
contentType: file.type || "application/octet-stream",
|
|
81
|
+
projectId: extras?.projectId
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error(`Upload verification failed: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
70
87
|
let thumbnails;
|
|
71
88
|
if (extras?.fetchThumbnails) {
|
|
72
89
|
try {
|
|
@@ -86,6 +103,38 @@ var UpfilesClient = class {
|
|
|
86
103
|
thumbnails
|
|
87
104
|
};
|
|
88
105
|
}
|
|
106
|
+
async completeUpload(params) {
|
|
107
|
+
const target = this.baseUrl ? `${this.baseUrl}${this.completionPath}` : this.completionPath;
|
|
108
|
+
const headers = {
|
|
109
|
+
"content-type": "application/json",
|
|
110
|
+
...this.headers || {}
|
|
111
|
+
};
|
|
112
|
+
if (this.apiKey) {
|
|
113
|
+
if (this.apiKeyHeader === "authorization") {
|
|
114
|
+
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
115
|
+
} else if (this.apiKeyHeader === "x-api-key") {
|
|
116
|
+
headers["x-api-key"] = this.apiKey;
|
|
117
|
+
} else {
|
|
118
|
+
headers["x-up-api-key"] = this.apiKey;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const res = await fetch(target, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers,
|
|
124
|
+
credentials: this.withCredentials ? "include" : "same-origin",
|
|
125
|
+
body: JSON.stringify(params)
|
|
126
|
+
});
|
|
127
|
+
if (!res.ok) {
|
|
128
|
+
let msg = "Failed to complete upload";
|
|
129
|
+
try {
|
|
130
|
+
const data = await res.json();
|
|
131
|
+
msg = data.error || msg;
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
throw new Error(msg);
|
|
135
|
+
}
|
|
136
|
+
return res.json();
|
|
137
|
+
}
|
|
89
138
|
async getThumbnails(fileKey) {
|
|
90
139
|
const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
|
|
91
140
|
const url = `${target}?fileKey=${encodeURIComponent(fileKey)}`;
|
|
@@ -145,6 +194,65 @@ var UpfilesClient = class {
|
|
|
145
194
|
const data = await res.json();
|
|
146
195
|
return { masterWebp: data.masterWebp, thumbnails: data.thumbnails ?? [] };
|
|
147
196
|
}
|
|
197
|
+
async getFolders(parentId) {
|
|
198
|
+
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
199
|
+
const url = parentId ? `${target}?parentId=${encodeURIComponent(parentId)}` : target;
|
|
200
|
+
const headers = { ...this.headers || {} };
|
|
201
|
+
if (this.apiKey) {
|
|
202
|
+
if (this.apiKeyHeader === "authorization") {
|
|
203
|
+
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
204
|
+
} else if (this.apiKeyHeader === "x-api-key") {
|
|
205
|
+
headers["x-api-key"] = this.apiKey;
|
|
206
|
+
} else {
|
|
207
|
+
headers["x-up-api-key"] = this.apiKey;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
211
|
+
if (!res.ok) {
|
|
212
|
+
let msg = "Failed to fetch folders";
|
|
213
|
+
try {
|
|
214
|
+
const data2 = await res.json();
|
|
215
|
+
msg = data2.error || msg;
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
throw new Error(msg);
|
|
219
|
+
}
|
|
220
|
+
const data = await res.json();
|
|
221
|
+
return data?.folders ?? [];
|
|
222
|
+
}
|
|
223
|
+
async createFolder(name, parentId) {
|
|
224
|
+
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
225
|
+
const headers = {
|
|
226
|
+
"content-type": "application/json",
|
|
227
|
+
...this.headers || {}
|
|
228
|
+
};
|
|
229
|
+
if (this.apiKey) {
|
|
230
|
+
if (this.apiKeyHeader === "authorization") {
|
|
231
|
+
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
232
|
+
} else if (this.apiKeyHeader === "x-api-key") {
|
|
233
|
+
headers["x-api-key"] = this.apiKey;
|
|
234
|
+
} else {
|
|
235
|
+
headers["x-up-api-key"] = this.apiKey;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const res = await fetch(target, {
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers,
|
|
241
|
+
credentials: this.withCredentials ? "include" : "same-origin",
|
|
242
|
+
body: JSON.stringify({ name, parentId })
|
|
243
|
+
});
|
|
244
|
+
if (!res.ok) {
|
|
245
|
+
let msg = "Failed to create folder";
|
|
246
|
+
try {
|
|
247
|
+
const data2 = await res.json();
|
|
248
|
+
msg = data2.error || msg;
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
throw new Error(msg);
|
|
252
|
+
}
|
|
253
|
+
const data = await res.json();
|
|
254
|
+
return data.folder;
|
|
255
|
+
}
|
|
148
256
|
async getProjectFiles(params) {
|
|
149
257
|
const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
|
|
150
258
|
const filesPath = `${basePath}/files`;
|
|
@@ -171,7 +279,23 @@ var UpfilesClient = class {
|
|
|
171
279
|
throw new Error(msg);
|
|
172
280
|
}
|
|
173
281
|
const data = await res.json();
|
|
174
|
-
return
|
|
282
|
+
return {
|
|
283
|
+
files: data?.files ?? [],
|
|
284
|
+
updatedAt: res.headers.get("X-Files-Updated-At")
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
getEventsUrl(projectId) {
|
|
288
|
+
const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
|
|
289
|
+
const eventsPath = `${basePath}/events`;
|
|
290
|
+
const target = this.baseUrl ? `${this.baseUrl}/api/events` : "/api/events";
|
|
291
|
+
const url = new URL(target, window.location.href);
|
|
292
|
+
if (this.apiKey) {
|
|
293
|
+
url.searchParams.set("apiKey", this.apiKey);
|
|
294
|
+
}
|
|
295
|
+
if (projectId) {
|
|
296
|
+
url.searchParams.set("projectId", projectId);
|
|
297
|
+
}
|
|
298
|
+
return url.toString();
|
|
175
299
|
}
|
|
176
300
|
};
|
|
177
301
|
|
|
@@ -196,7 +320,7 @@ var Uploader = ({
|
|
|
196
320
|
projectId,
|
|
197
321
|
folderPath,
|
|
198
322
|
fetchThumbnails,
|
|
199
|
-
autoRecordToDb =
|
|
323
|
+
autoRecordToDb = true,
|
|
200
324
|
recordUrl = "/api/files",
|
|
201
325
|
autoUpload = false
|
|
202
326
|
}) => {
|
|
@@ -270,49 +394,38 @@ var Uploader = ({
|
|
|
270
394
|
xhr.setRequestHeader("Content-Type", item.type || "application/octet-stream");
|
|
271
395
|
xhr.send(item.file);
|
|
272
396
|
});
|
|
397
|
+
let completionResult = null;
|
|
398
|
+
if (autoRecordToDb && projectId) {
|
|
399
|
+
try {
|
|
400
|
+
completionResult = await client.completeUpload({
|
|
401
|
+
fileKey: presign.fileKey,
|
|
402
|
+
originalName: item.name,
|
|
403
|
+
size: item.size,
|
|
404
|
+
contentType: item.type,
|
|
405
|
+
projectId
|
|
406
|
+
});
|
|
407
|
+
} catch (e) {
|
|
408
|
+
console.error("Failed to complete upload:", e);
|
|
409
|
+
throw new Error(`Upload verification failed: ${e.message}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
273
412
|
let thumbs;
|
|
274
413
|
let masterWebp;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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);
|
|
278
421
|
thumbs = result2.thumbnails;
|
|
279
422
|
masterWebp = result2.masterWebp;
|
|
280
|
-
}
|
|
281
|
-
} catch (e) {
|
|
282
|
-
console.warn("Failed to generate thumbnails:", e);
|
|
283
|
-
}
|
|
284
|
-
if (autoRecordToDb && projectId) {
|
|
285
|
-
try {
|
|
286
|
-
const baseUrl = clientOptions?.baseUrl || "";
|
|
287
|
-
const url = baseUrl ? `${baseUrl}${recordUrl}` : recordUrl;
|
|
288
|
-
const headers = {
|
|
289
|
-
"content-type": "application/json",
|
|
290
|
-
...clientOptions?.headers || {}
|
|
291
|
-
};
|
|
292
|
-
if (clientOptions?.apiKey) {
|
|
293
|
-
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
294
|
-
if (keyHeader === "authorization") {
|
|
295
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
296
|
-
} else {
|
|
297
|
-
headers[keyHeader] = clientOptions.apiKey;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
await fetch(url, {
|
|
301
|
-
method: "POST",
|
|
302
|
-
headers,
|
|
303
|
-
credentials: clientOptions?.withCredentials ? "include" : "same-origin",
|
|
304
|
-
body: JSON.stringify({
|
|
305
|
-
key: presign.fileKey,
|
|
306
|
-
originalName: item.name,
|
|
307
|
-
size: item.size,
|
|
308
|
-
contentType: item.type,
|
|
309
|
-
projectId
|
|
310
|
-
})
|
|
311
|
-
});
|
|
312
423
|
} catch (e) {
|
|
313
|
-
console.warn("Failed to
|
|
424
|
+
console.warn("Failed to generate thumbnails:", e);
|
|
314
425
|
}
|
|
315
426
|
}
|
|
427
|
+
const finalFileKey = completionResult?.file?.key || presign.fileKey;
|
|
428
|
+
const finalPublicUrl = completionResult?.file?.url || presign.publicUrl;
|
|
316
429
|
const result = {
|
|
317
430
|
...item,
|
|
318
431
|
status: "success",
|
|
@@ -375,10 +488,10 @@ var Uploader = ({
|
|
|
375
488
|
onChange: onInputChange
|
|
376
489
|
}
|
|
377
490
|
),
|
|
378
|
-
/* @__PURE__ */
|
|
491
|
+
/* @__PURE__ */ jsxs(
|
|
379
492
|
"div",
|
|
380
493
|
{
|
|
381
|
-
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"}`,
|
|
382
495
|
onDragOver: (e) => {
|
|
383
496
|
e.preventDefault();
|
|
384
497
|
e.stopPropagation();
|
|
@@ -393,51 +506,121 @@ var Uploader = ({
|
|
|
393
506
|
onClick: () => inputRef.current?.click(),
|
|
394
507
|
role: "button",
|
|
395
508
|
"aria-label": "Upload files",
|
|
396
|
-
children:
|
|
397
|
-
/* @__PURE__ */ jsx("div", {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
"
|
|
401
|
-
|
|
402
|
-
|
|
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
|
+
] })
|
|
403
523
|
] })
|
|
404
|
-
]
|
|
524
|
+
]
|
|
405
525
|
}
|
|
406
526
|
),
|
|
407
|
-
files.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
408
|
-
/* @__PURE__ */ jsxs("div", {
|
|
409
|
-
/* @__PURE__ */ jsx(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
+
] })
|
|
427
556
|
] }),
|
|
428
|
-
/* @__PURE__ */ jsx("
|
|
429
|
-
/* @__PURE__ */
|
|
430
|
-
/* @__PURE__ */ jsx("
|
|
431
|
-
/* @__PURE__ */
|
|
432
|
-
|
|
433
|
-
|
|
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
|
+
] })
|
|
434
573
|
] }),
|
|
435
|
-
f2.error
|
|
436
|
-
|
|
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
|
+
) })
|
|
437
581
|
] }),
|
|
438
|
-
/* @__PURE__ */ jsxs("div", {
|
|
439
|
-
|
|
440
|
-
|
|
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
|
+
)
|
|
441
624
|
] })
|
|
442
625
|
] }) }, f2.id)) })
|
|
443
626
|
] })
|
|
@@ -445,7 +628,7 @@ var Uploader = ({
|
|
|
445
628
|
};
|
|
446
629
|
|
|
447
630
|
// src/ProjectFilesWidget.tsx
|
|
448
|
-
import { useEffect, useMemo as useMemo2, useState as useState2 } from "react";
|
|
631
|
+
import React2, { useEffect, useMemo as useMemo2, useState as useState2 } from "react";
|
|
449
632
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
450
633
|
var ProjectFilesWidget = ({
|
|
451
634
|
clientOptions,
|
|
@@ -459,7 +642,8 @@ var ProjectFilesWidget = ({
|
|
|
459
642
|
onSaved,
|
|
460
643
|
onDelete,
|
|
461
644
|
deleteUrl = "/api/files",
|
|
462
|
-
showDelete = false
|
|
645
|
+
showDelete = false,
|
|
646
|
+
refreshInterval = 5e3
|
|
463
647
|
}) => {
|
|
464
648
|
const client = useMemo2(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
|
|
465
649
|
const [files, setFiles] = useState2([]);
|
|
@@ -468,25 +652,37 @@ var ProjectFilesWidget = ({
|
|
|
468
652
|
const [query, setQuery] = useState2("");
|
|
469
653
|
const [savingKey, setSavingKey] = useState2(null);
|
|
470
654
|
const [deletingKey, setDeletingKey] = useState2(null);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (!cancelled) setLoading(false);
|
|
655
|
+
const lastUpdatedRef = React2.useRef(null);
|
|
656
|
+
const isFetchingRef = React2.useRef(false);
|
|
657
|
+
const fetchFiles = React2.useCallback(async (isPolling = false) => {
|
|
658
|
+
if (isFetchingRef.current) return;
|
|
659
|
+
isFetchingRef.current = true;
|
|
660
|
+
if (!isPolling) setLoading(true);
|
|
661
|
+
setError(null);
|
|
662
|
+
try {
|
|
663
|
+
const { files: newFiles, updatedAt } = await client.getProjectFiles({ folderPath });
|
|
664
|
+
if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
|
|
665
|
+
return;
|
|
483
666
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
667
|
+
setFiles(newFiles);
|
|
668
|
+
lastUpdatedRef.current = updatedAt;
|
|
669
|
+
} catch (e) {
|
|
670
|
+
if (!isPolling) setError(e?.message || "Failed to load files");
|
|
671
|
+
} finally {
|
|
672
|
+
if (!isPolling) setLoading(false);
|
|
673
|
+
isFetchingRef.current = false;
|
|
674
|
+
}
|
|
489
675
|
}, [client, folderPath]);
|
|
676
|
+
useEffect(() => {
|
|
677
|
+
fetchFiles();
|
|
678
|
+
}, [fetchFiles]);
|
|
679
|
+
useEffect(() => {
|
|
680
|
+
if (!refreshInterval || refreshInterval <= 0) return;
|
|
681
|
+
const timer = setInterval(() => {
|
|
682
|
+
fetchFiles(true);
|
|
683
|
+
}, refreshInterval);
|
|
684
|
+
return () => clearInterval(timer);
|
|
685
|
+
}, [fetchFiles, refreshInterval]);
|
|
490
686
|
const visible = files.filter(
|
|
491
687
|
(f2) => !query ? true : (f2.originalName || "").toLowerCase().includes(query.toLowerCase())
|
|
492
688
|
);
|
|
@@ -543,18 +739,7 @@ var ProjectFilesWidget = ({
|
|
|
543
739
|
{
|
|
544
740
|
onClick: () => {
|
|
545
741
|
setQuery("");
|
|
546
|
-
(
|
|
547
|
-
setLoading(true);
|
|
548
|
-
setError(null);
|
|
549
|
-
try {
|
|
550
|
-
const data = await client.getProjectFiles({ folderPath });
|
|
551
|
-
setFiles(data);
|
|
552
|
-
} catch (e) {
|
|
553
|
-
setError(e?.message || "Failed to load files");
|
|
554
|
-
} finally {
|
|
555
|
-
setLoading(false);
|
|
556
|
-
}
|
|
557
|
-
})();
|
|
742
|
+
fetchFiles();
|
|
558
743
|
},
|
|
559
744
|
style: { padding: "8px 12px", border: "1px solid #e5e7eb", borderRadius: 6, background: "#fff" },
|
|
560
745
|
children: "Refresh"
|
|
@@ -947,7 +1132,7 @@ async function fetchFileThumbnails(clientOptions, fileKey) {
|
|
|
947
1132
|
return client.getThumbnails(fileKey);
|
|
948
1133
|
}
|
|
949
1134
|
async function fetchProjectImagesWithThumbs(clientOptions, opts) {
|
|
950
|
-
const files = await fetchProjectFiles(clientOptions, opts?.folderPath);
|
|
1135
|
+
const { files, updatedAt } = await fetchProjectFiles(clientOptions, opts?.folderPath);
|
|
951
1136
|
const limit = Math.max(1, Math.min(opts?.limitConcurrent ?? 6, 12));
|
|
952
1137
|
const queue = [...files];
|
|
953
1138
|
const results = [];
|
|
@@ -969,13 +1154,62 @@ async function fetchProjectImagesWithThumbs(clientOptions, opts) {
|
|
|
969
1154
|
await Promise.all(Array.from({ length: workers }, () => worker()));
|
|
970
1155
|
const keyToIndex = new Map(files.map((f2, i) => [f2.key, i]));
|
|
971
1156
|
results.sort((a, b) => (keyToIndex.get(a.key) ?? 0) - (keyToIndex.get(b.key) ?? 0));
|
|
972
|
-
return results;
|
|
1157
|
+
return { items: results, updatedAt };
|
|
973
1158
|
}
|
|
974
1159
|
|
|
975
1160
|
// src/ImageManager.tsx
|
|
976
1161
|
import * as React4 from "react";
|
|
977
1162
|
import * as Dialog2 from "@radix-ui/react-dialog";
|
|
978
|
-
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1163
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1164
|
+
var IconGrid = ({ className }) => /* @__PURE__ */ jsxs4("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: [
|
|
1165
|
+
/* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
|
|
1166
|
+
/* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
|
|
1167
|
+
/* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
|
|
1168
|
+
/* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
|
|
1169
|
+
] });
|
|
1170
|
+
var IconUploadCloud = ({ className }) => /* @__PURE__ */ jsxs4("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: [
|
|
1171
|
+
/* @__PURE__ */ jsx4("path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }),
|
|
1172
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 12v9" }),
|
|
1173
|
+
/* @__PURE__ */ jsx4("path", { d: "m16 16-4-4-4 4" })
|
|
1174
|
+
] });
|
|
1175
|
+
var IconFolder = ({ className }) => /* @__PURE__ */ jsx4("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__ */ jsx4("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" }) });
|
|
1176
|
+
var IconFolderPlus = ({ className }) => /* @__PURE__ */ jsxs4("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__ */ jsx4("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" }),
|
|
1178
|
+
/* @__PURE__ */ jsx4("line", { x1: "12", x2: "12", y1: "10", y2: "16" }),
|
|
1179
|
+
/* @__PURE__ */ jsx4("line", { x1: "9", x2: "15", y1: "13", y2: "13" })
|
|
1180
|
+
] });
|
|
1181
|
+
var IconSearch = ({ className }) => /* @__PURE__ */ jsxs4("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: [
|
|
1182
|
+
/* @__PURE__ */ jsx4("circle", { cx: "11", cy: "11", r: "8" }),
|
|
1183
|
+
/* @__PURE__ */ jsx4("path", { d: "m21 21-4.3-4.3" })
|
|
1184
|
+
] });
|
|
1185
|
+
var IconRefresh = ({ className }) => /* @__PURE__ */ jsxs4("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: [
|
|
1186
|
+
/* @__PURE__ */ jsx4("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
|
|
1187
|
+
/* @__PURE__ */ jsx4("path", { d: "M21 3v5h-5" }),
|
|
1188
|
+
/* @__PURE__ */ jsx4("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
|
|
1189
|
+
/* @__PURE__ */ jsx4("path", { d: "M8 16H3v5" })
|
|
1190
|
+
] });
|
|
1191
|
+
var IconChevronRight = ({ className }) => /* @__PURE__ */ jsx4("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__ */ jsx4("path", { d: "m9 18 6-6-6-6" }) });
|
|
1192
|
+
var IconTrash = ({ className }) => /* @__PURE__ */ jsxs4("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: [
|
|
1193
|
+
/* @__PURE__ */ jsx4("path", { d: "M3 6h18" }),
|
|
1194
|
+
/* @__PURE__ */ jsx4("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
1195
|
+
/* @__PURE__ */ jsx4("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
|
|
1196
|
+
/* @__PURE__ */ jsx4("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
|
|
1197
|
+
/* @__PURE__ */ jsx4("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
|
|
1198
|
+
] });
|
|
1199
|
+
var IconX = ({ className }) => /* @__PURE__ */ jsxs4("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: [
|
|
1200
|
+
/* @__PURE__ */ jsx4("path", { d: "M18 6 6 18" }),
|
|
1201
|
+
/* @__PURE__ */ jsx4("path", { d: "m6 6 12 12" })
|
|
1202
|
+
] });
|
|
1203
|
+
var IconHome = ({ className }) => /* @__PURE__ */ jsxs4("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: [
|
|
1204
|
+
/* @__PURE__ */ jsx4("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
|
|
1205
|
+
/* @__PURE__ */ jsx4("polyline", { points: "9 22 9 12 15 12 15 22" })
|
|
1206
|
+
] });
|
|
1207
|
+
var IconImage = ({ className }) => /* @__PURE__ */ jsxs4("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: [
|
|
1208
|
+
/* @__PURE__ */ jsx4("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
1209
|
+
/* @__PURE__ */ jsx4("circle", { cx: "9", cy: "9", r: "2" }),
|
|
1210
|
+
/* @__PURE__ */ jsx4("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
|
|
1211
|
+
] });
|
|
1212
|
+
var IconLoader = ({ className }) => /* @__PURE__ */ jsx4("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__ */ jsx4("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
|
|
979
1213
|
function ImageManager(props) {
|
|
980
1214
|
const {
|
|
981
1215
|
open,
|
|
@@ -995,209 +1229,485 @@ function ImageManager(props) {
|
|
|
995
1229
|
maxFileSize = 100 * 1024 * 1024,
|
|
996
1230
|
maxFiles = 10,
|
|
997
1231
|
mode = "full",
|
|
998
|
-
showDelete = true
|
|
1232
|
+
showDelete = true,
|
|
1233
|
+
refreshInterval = 5e3
|
|
999
1234
|
} = props;
|
|
1000
|
-
const [
|
|
1235
|
+
const [activeView, setActiveView] = React4.useState(mode === "upload" ? "upload" : "browse");
|
|
1001
1236
|
const [loading, setLoading] = React4.useState(false);
|
|
1002
1237
|
const [error, setError] = React4.useState(null);
|
|
1003
|
-
const [
|
|
1238
|
+
const [currentFolderId, setCurrentFolderId] = React4.useState(void 0);
|
|
1239
|
+
const [breadcrumbs, setBreadcrumbs] = React4.useState([{ id: void 0, name: "Home" }]);
|
|
1240
|
+
const [files, setFiles] = React4.useState([]);
|
|
1241
|
+
const [folders, setFolders] = React4.useState([]);
|
|
1004
1242
|
const [query, setQuery] = React4.useState("");
|
|
1005
1243
|
const [deleting, setDeleting] = React4.useState(null);
|
|
1006
|
-
const
|
|
1244
|
+
const [showCreateFolder, setShowCreateFolder] = React4.useState(false);
|
|
1245
|
+
const [newFolderName, setNewFolderName] = React4.useState("");
|
|
1246
|
+
const [creatingFolder, setCreatingFolder] = React4.useState(false);
|
|
1247
|
+
const [fileToDelete, setFileToDelete] = React4.useState(null);
|
|
1248
|
+
const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4.useState(null);
|
|
1249
|
+
const lastUpdatedRef = React4.useRef(null);
|
|
1250
|
+
const isFetchingRef = React4.useRef(false);
|
|
1251
|
+
const load = React4.useCallback(async (isPolling = false) => {
|
|
1252
|
+
if (isFetchingRef.current) return;
|
|
1253
|
+
isFetchingRef.current = true;
|
|
1007
1254
|
try {
|
|
1008
|
-
setLoading(true);
|
|
1255
|
+
if (!isPolling) setLoading(true);
|
|
1009
1256
|
setError(null);
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1257
|
+
const client = new UpfilesClient(clientOptions);
|
|
1258
|
+
const currentPathPrefix = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1259
|
+
const effectiveFolderPath = currentPathPrefix || folderPath;
|
|
1260
|
+
const [filesData, foldersData] = await Promise.all([
|
|
1261
|
+
fetchProjectImagesWithThumbs(clientOptions, { folderPath: effectiveFolderPath, limitConcurrent: 6 }),
|
|
1262
|
+
client.getFolders(currentFolderId)
|
|
1263
|
+
]);
|
|
1264
|
+
const { items: list, updatedAt } = filesData;
|
|
1265
|
+
if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
|
|
1266
|
+
}
|
|
1267
|
+
setFiles(list);
|
|
1268
|
+
setFolders(foldersData);
|
|
1269
|
+
lastUpdatedRef.current = updatedAt;
|
|
1012
1270
|
} catch (e) {
|
|
1013
|
-
setError(e?.message || "Failed to load images");
|
|
1271
|
+
if (!isPolling) setError(e?.message || "Failed to load images");
|
|
1014
1272
|
} finally {
|
|
1015
|
-
setLoading(false);
|
|
1273
|
+
if (!isPolling) setLoading(false);
|
|
1274
|
+
isFetchingRef.current = false;
|
|
1016
1275
|
}
|
|
1017
|
-
}, [clientOptions, folderPath]);
|
|
1276
|
+
}, [clientOptions, folderPath, currentFolderId, breadcrumbs]);
|
|
1018
1277
|
React4.useEffect(() => {
|
|
1019
1278
|
if (!open) {
|
|
1020
|
-
|
|
1279
|
+
setFiles([]);
|
|
1280
|
+
setFolders([]);
|
|
1021
1281
|
setQuery("");
|
|
1022
1282
|
setError(null);
|
|
1283
|
+
lastUpdatedRef.current = null;
|
|
1023
1284
|
return;
|
|
1024
1285
|
}
|
|
1025
|
-
if (mode !== "upload" &&
|
|
1286
|
+
if (mode !== "upload" && activeView === "browse") {
|
|
1026
1287
|
load();
|
|
1027
1288
|
}
|
|
1028
|
-
}, [open,
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
if (!confirm("Delete this file?")) return;
|
|
1289
|
+
}, [open, activeView, load, mode]);
|
|
1290
|
+
React4.useEffect(() => {
|
|
1291
|
+
if (!open) return;
|
|
1292
|
+
const client = new UpfilesClient(clientOptions);
|
|
1293
|
+
const url = client.getEventsUrl(projectId);
|
|
1294
|
+
const eventSource = new EventSource(url);
|
|
1295
|
+
eventSource.onmessage = (event) => {
|
|
1036
1296
|
try {
|
|
1037
|
-
|
|
1038
|
-
if (
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const headers = {
|
|
1044
|
-
"content-type": "application/json",
|
|
1045
|
-
...clientOptions?.headers || {}
|
|
1046
|
-
};
|
|
1047
|
-
if (clientOptions?.apiKey) {
|
|
1048
|
-
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
1049
|
-
if (keyHeader === "authorization") {
|
|
1050
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
1051
|
-
} else {
|
|
1052
|
-
headers[keyHeader] = clientOptions.apiKey;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
const res = await fetch(url, {
|
|
1056
|
-
method: "DELETE",
|
|
1057
|
-
headers,
|
|
1058
|
-
credentials: clientOptions?.withCredentials ? "include" : "same-origin",
|
|
1059
|
-
body: JSON.stringify({ fileKey: key })
|
|
1297
|
+
const payload = JSON.parse(event.data);
|
|
1298
|
+
if (payload.type === "file:uploaded") {
|
|
1299
|
+
const newFile = payload.data.file;
|
|
1300
|
+
setFiles((prev) => {
|
|
1301
|
+
if (prev.find((f2) => f2.key === newFile.key)) return prev;
|
|
1302
|
+
return [newFile, ...prev];
|
|
1060
1303
|
});
|
|
1061
|
-
|
|
1304
|
+
} else if (payload.type === "file:deleted") {
|
|
1305
|
+
const deletedKey = payload.data.fileKey || payload.data.file?.key;
|
|
1306
|
+
setFiles((prev) => prev.filter((f2) => f2.key !== deletedKey));
|
|
1062
1307
|
}
|
|
1063
|
-
setItems((prev) => prev.filter((f2) => f2.key !== key));
|
|
1064
1308
|
} catch (e) {
|
|
1065
|
-
|
|
1066
|
-
} finally {
|
|
1067
|
-
setDeleting(null);
|
|
1309
|
+
console.error("SSE Error parsing message", e);
|
|
1068
1310
|
}
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
);
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1311
|
+
};
|
|
1312
|
+
return () => eventSource.close();
|
|
1313
|
+
}, [open, clientOptions, projectId]);
|
|
1314
|
+
const visibleFiles = React4.useMemo(() => {
|
|
1315
|
+
const q = query.trim().toLowerCase();
|
|
1316
|
+
return !q ? files : files.filter((i) => (i.originalName || "").toLowerCase().includes(q));
|
|
1317
|
+
}, [files, query]);
|
|
1318
|
+
const visibleFolders = React4.useMemo(() => {
|
|
1319
|
+
const q = query.trim().toLowerCase();
|
|
1320
|
+
return !q ? folders : folders.filter((f2) => (f2.name || "").toLowerCase().includes(q));
|
|
1321
|
+
}, [folders, query]);
|
|
1322
|
+
const handleDelete = async () => {
|
|
1323
|
+
if (!fileToDelete) return;
|
|
1324
|
+
const key = fileToDelete.key;
|
|
1325
|
+
try {
|
|
1326
|
+
setDeleting(key);
|
|
1327
|
+
if (onDelete) {
|
|
1328
|
+
await onDelete(key);
|
|
1329
|
+
} else {
|
|
1330
|
+
const client = new UpfilesClient(clientOptions);
|
|
1331
|
+
const baseUrl = clientOptions?.baseUrl || "";
|
|
1332
|
+
const url = baseUrl ? `${baseUrl}${deleteUrl}` : deleteUrl;
|
|
1333
|
+
const headers = {
|
|
1334
|
+
"content-type": "application/json",
|
|
1335
|
+
...clientOptions?.headers || {}
|
|
1336
|
+
};
|
|
1337
|
+
if (clientOptions?.apiKey) {
|
|
1338
|
+
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
1339
|
+
if (keyHeader === "authorization") {
|
|
1340
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
1341
|
+
} else {
|
|
1342
|
+
headers[keyHeader] = clientOptions.apiKey;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
const res = await fetch(url, {
|
|
1346
|
+
method: "DELETE",
|
|
1347
|
+
headers,
|
|
1348
|
+
credentials: clientOptions?.withCredentials ? "include" : "same-origin",
|
|
1349
|
+
body: JSON.stringify({ fileKey: key })
|
|
1350
|
+
});
|
|
1351
|
+
if (!res.ok) throw new Error("Delete failed");
|
|
1352
|
+
}
|
|
1353
|
+
setFiles((prev) => prev.filter((f2) => f2.key !== key));
|
|
1354
|
+
setFileToDelete(null);
|
|
1355
|
+
} catch (e) {
|
|
1356
|
+
alert(e?.message || "Failed to delete");
|
|
1357
|
+
} finally {
|
|
1358
|
+
setDeleting(null);
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
const handleCreateFolder = async () => {
|
|
1362
|
+
if (!newFolderName.trim()) return;
|
|
1363
|
+
setCreatingFolder(true);
|
|
1364
|
+
try {
|
|
1365
|
+
const client = new UpfilesClient(clientOptions);
|
|
1366
|
+
await client.createFolder(newFolderName, currentFolderId);
|
|
1367
|
+
setNewFolderName("");
|
|
1368
|
+
setShowCreateFolder(false);
|
|
1369
|
+
load();
|
|
1370
|
+
} catch (e) {
|
|
1371
|
+
alert(e.message || "Failed to create folder");
|
|
1372
|
+
} finally {
|
|
1373
|
+
setCreatingFolder(false);
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
const handleNavigate = (folder) => {
|
|
1377
|
+
setCurrentFolderId(folder.id);
|
|
1378
|
+
setBreadcrumbs((prev) => [...prev, { id: folder.id, name: folder.name }]);
|
|
1379
|
+
setQuery("");
|
|
1380
|
+
};
|
|
1381
|
+
const handleBreadcrumbClick = (index) => {
|
|
1382
|
+
const target = breadcrumbs[index];
|
|
1383
|
+
setBreadcrumbs(breadcrumbs.slice(0, index + 1));
|
|
1384
|
+
setCurrentFolderId(target.id);
|
|
1385
|
+
setQuery("");
|
|
1386
|
+
};
|
|
1387
|
+
const uploadFoldersList = React4.useMemo(() => {
|
|
1388
|
+
const options = [];
|
|
1389
|
+
options.push({ id: "root", name: "Root ( / )", path: void 0 });
|
|
1390
|
+
if (currentFolderId) {
|
|
1391
|
+
const currentPath = breadcrumbs.slice(1).map((b) => b.name).join("/") + "/";
|
|
1392
|
+
options.push({ id: "current", name: `Current ( ${breadcrumbs[breadcrumbs.length - 1].name} )`, path: currentPath });
|
|
1393
|
+
}
|
|
1394
|
+
folders.forEach((f2) => {
|
|
1395
|
+
const parentPath = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : "";
|
|
1396
|
+
options.push({ id: f2.id, name: f2.name, path: parentPath + f2.name + "/" });
|
|
1397
|
+
});
|
|
1398
|
+
return options;
|
|
1399
|
+
}, [breadcrumbs, currentFolderId, folders]);
|
|
1400
|
+
const [selectedUploadFolder, setSelectedUploadFolder] = React4.useState("current");
|
|
1401
|
+
const activeBreadcrumbPath = React4.useMemo(() => {
|
|
1402
|
+
return breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1403
|
+
}, [breadcrumbs]);
|
|
1404
|
+
return /* @__PURE__ */ jsxs4(Dialog2.Root, { open, onOpenChange, children: [
|
|
1405
|
+
/* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
|
|
1406
|
+
/* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-gray-900/50 backdrop-blur-sm z-50 transition-all duration-300" }),
|
|
1407
|
+
/* @__PURE__ */ jsxs4(
|
|
1408
|
+
Dialog2.Content,
|
|
1409
|
+
{
|
|
1410
|
+
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 ?? ""}`,
|
|
1411
|
+
children: [
|
|
1412
|
+
/* @__PURE__ */ jsxs4("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: [
|
|
1413
|
+
/* @__PURE__ */ jsxs4("div", { className: "p-6", children: [
|
|
1414
|
+
/* @__PURE__ */ jsxs4("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
|
|
1415
|
+
/* @__PURE__ */ jsx4(IconImage, { className: "text-blue-600" }),
|
|
1416
|
+
title ?? "Media Library"
|
|
1417
|
+
] }),
|
|
1418
|
+
/* @__PURE__ */ jsx4("p", { className: "text-xs text-gray-500 mt-1", children: description ?? "Manage your assets" })
|
|
1419
|
+
] }),
|
|
1420
|
+
/* @__PURE__ */ jsxs4("nav", { className: "flex-1 px-3 space-y-1", children: [
|
|
1421
|
+
/* @__PURE__ */ jsxs4(
|
|
1422
|
+
"button",
|
|
1423
|
+
{
|
|
1424
|
+
onClick: () => setActiveView("browse"),
|
|
1425
|
+
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"}`,
|
|
1426
|
+
children: [
|
|
1427
|
+
/* @__PURE__ */ jsx4(IconGrid, { className: "w-4 h-4" }),
|
|
1428
|
+
"All Files"
|
|
1429
|
+
]
|
|
1430
|
+
}
|
|
1431
|
+
),
|
|
1432
|
+
mode === "full" && /* @__PURE__ */ jsxs4(
|
|
1433
|
+
"button",
|
|
1434
|
+
{
|
|
1435
|
+
onClick: () => setActiveView("upload"),
|
|
1436
|
+
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"}`,
|
|
1437
|
+
children: [
|
|
1438
|
+
/* @__PURE__ */ jsx4(IconUploadCloud, { className: "w-4 h-4" }),
|
|
1439
|
+
"Upload New"
|
|
1440
|
+
]
|
|
1441
|
+
}
|
|
1442
|
+
)
|
|
1443
|
+
] }),
|
|
1444
|
+
/* @__PURE__ */ jsx4("div", { className: "p-4 border-t border-gray-200 dark:border-gray-800", children: /* @__PURE__ */ jsxs4("div", { className: "text-xs text-center text-gray-400", children: [
|
|
1445
|
+
files.length,
|
|
1446
|
+
" files \u2022 ",
|
|
1447
|
+
folders.length,
|
|
1448
|
+
" folders"
|
|
1449
|
+
] }) })
|
|
1113
1450
|
] }),
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1451
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex-1 flex flex-col bg-white dark:bg-gray-950 relative", children: [
|
|
1452
|
+
/* @__PURE__ */ jsxs4("div", { className: "h-16 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between px-6 gap-4", children: [
|
|
1453
|
+
/* @__PURE__ */ jsx4("div", { className: "flex items-center gap-4 flex-1", children: activeView === "browse" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1454
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center text-sm text-gray-500 overflow-hidden whitespace-nowrap", children: [
|
|
1455
|
+
/* @__PURE__ */ jsx4("button", { onClick: () => handleBreadcrumbClick(0), className: "hover:text-blue-600 transition-colors", children: /* @__PURE__ */ jsx4(IconHome, { className: "w-4 h-4" }) }),
|
|
1456
|
+
breadcrumbs.slice(1).map((b, i) => /* @__PURE__ */ jsxs4(React4.Fragment, { children: [
|
|
1457
|
+
/* @__PURE__ */ jsx4(IconChevronRight, { className: "w-4 h-4 text-gray-300 mx-1" }),
|
|
1458
|
+
/* @__PURE__ */ jsx4(
|
|
1459
|
+
"button",
|
|
1460
|
+
{
|
|
1461
|
+
onClick: () => handleBreadcrumbClick(i + 1),
|
|
1462
|
+
className: `hover:text-blue-600 transition-colors truncate max-w-[100px] ${i === breadcrumbs.length - 2 ? "font-semibold text-gray-900 dark:text-white" : ""}`,
|
|
1463
|
+
children: b.name
|
|
1464
|
+
}
|
|
1465
|
+
)
|
|
1466
|
+
] }, b.id || i))
|
|
1467
|
+
] }),
|
|
1468
|
+
/* @__PURE__ */ jsx4("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
|
|
1469
|
+
/* @__PURE__ */ jsx4(
|
|
1470
|
+
"button",
|
|
1471
|
+
{
|
|
1472
|
+
onClick: () => load(),
|
|
1473
|
+
disabled: loading,
|
|
1474
|
+
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",
|
|
1475
|
+
title: "Refresh",
|
|
1476
|
+
children: /* @__PURE__ */ jsx4(IconRefresh, { className: `w-4 h-4 ${loading ? "animate-spin" : ""}` })
|
|
1477
|
+
}
|
|
1478
|
+
),
|
|
1479
|
+
/* @__PURE__ */ jsx4("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
|
|
1480
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative flex-1 max-w-sm group", children: [
|
|
1481
|
+
/* @__PURE__ */ jsx4(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" }),
|
|
1121
1482
|
/* @__PURE__ */ jsx4(
|
|
1122
|
-
"
|
|
1483
|
+
"input",
|
|
1123
1484
|
{
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
key: f2.key,
|
|
1129
|
-
originalName: f2.originalName,
|
|
1130
|
-
size: f2.size,
|
|
1131
|
-
contentType: f2.contentType,
|
|
1132
|
-
thumbnails: f2.thumbnails
|
|
1133
|
-
});
|
|
1134
|
-
onOpenChange(false);
|
|
1135
|
-
},
|
|
1136
|
-
className: "opacity-0 group-hover:opacity-100 px-3 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded",
|
|
1137
|
-
children: "Select"
|
|
1138
|
-
}
|
|
1139
|
-
),
|
|
1140
|
-
showDelete && /* @__PURE__ */ jsx4(
|
|
1141
|
-
"button",
|
|
1142
|
-
{
|
|
1143
|
-
type: "button",
|
|
1144
|
-
onClick: () => handleDelete(f2.key),
|
|
1145
|
-
disabled: deleting === f2.key,
|
|
1146
|
-
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",
|
|
1147
|
-
children: deleting === f2.key ? "..." : "Delete"
|
|
1485
|
+
value: query,
|
|
1486
|
+
onChange: (e) => setQuery(e.target.value),
|
|
1487
|
+
placeholder: "Search files...",
|
|
1488
|
+
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"
|
|
1148
1489
|
}
|
|
1149
1490
|
)
|
|
1491
|
+
] })
|
|
1492
|
+
] }) }),
|
|
1493
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
|
|
1494
|
+
activeView === "browse" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1495
|
+
/* @__PURE__ */ jsx4("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__ */ jsx4(IconFolderPlus, { className: "w-5 h-5" }) }),
|
|
1496
|
+
/* @__PURE__ */ jsx4("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__ */ jsx4(IconLoader, { className: "w-5 h-5" }) })
|
|
1497
|
+
] }),
|
|
1498
|
+
/* @__PURE__ */ jsx4(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx4("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__ */ jsx4(IconX, { className: "w-5 h-5" }) }) })
|
|
1499
|
+
] })
|
|
1500
|
+
] }),
|
|
1501
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50/30 dark:bg-gray-900/30", children: [
|
|
1502
|
+
activeView === "browse" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1503
|
+
error && /* @__PURE__ */ jsx4("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__ */ jsx4("span", { children: error }) }),
|
|
1504
|
+
/* @__PURE__ */ jsxs4("div", { className: gridClassName ?? "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6", children: [
|
|
1505
|
+
visibleFolders.map((folder) => /* @__PURE__ */ jsxs4(
|
|
1506
|
+
"div",
|
|
1507
|
+
{
|
|
1508
|
+
onClick: () => handleNavigate(folder),
|
|
1509
|
+
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",
|
|
1510
|
+
children: [
|
|
1511
|
+
/* @__PURE__ */ jsx4("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__ */ jsx4(IconFolder, { className: "w-8 h-8 drop-shadow-sm" }) }),
|
|
1512
|
+
/* @__PURE__ */ jsx4("span", { className: "text-sm font-medium text-gray-700 truncate w-full text-center", children: folder.name })
|
|
1513
|
+
]
|
|
1514
|
+
},
|
|
1515
|
+
folder.id
|
|
1516
|
+
)),
|
|
1517
|
+
visibleFiles.map((f2) => {
|
|
1518
|
+
const fWithThumbs = f2;
|
|
1519
|
+
const bestThumb = fWithThumbs.thumbnails?.sort((a, b) => a.size - b.size).find((t) => t.size >= 300) || fWithThumbs.thumbnails?.[fWithThumbs.thumbnails?.length - 1];
|
|
1520
|
+
const thumbUrl = bestThumb?.url || f2.url;
|
|
1521
|
+
return /* @__PURE__ */ jsxs4(
|
|
1522
|
+
"div",
|
|
1523
|
+
{
|
|
1524
|
+
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",
|
|
1525
|
+
children: [
|
|
1526
|
+
/* @__PURE__ */ jsx4("img", { src: thumbUrl, alt: "", className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" }),
|
|
1527
|
+
/* @__PURE__ */ jsx4("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__ */ jsxs4("div", { className: "flex gap-2 justify-center transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [
|
|
1528
|
+
/* @__PURE__ */ jsx4(
|
|
1529
|
+
"button",
|
|
1530
|
+
{
|
|
1531
|
+
onClick: () => {
|
|
1532
|
+
onSelect({
|
|
1533
|
+
url: f2.url,
|
|
1534
|
+
key: f2.key,
|
|
1535
|
+
originalName: f2.originalName,
|
|
1536
|
+
size: f2.size,
|
|
1537
|
+
contentType: f2.contentType,
|
|
1538
|
+
thumbnails: f2.thumbnails
|
|
1539
|
+
});
|
|
1540
|
+
onOpenChange(false);
|
|
1541
|
+
},
|
|
1542
|
+
className: "px-3 py-1.5 bg-blue-600 text-white text-xs font-semibold rounded-md hover:bg-blue-500 shadow-lg",
|
|
1543
|
+
children: "Select"
|
|
1544
|
+
}
|
|
1545
|
+
),
|
|
1546
|
+
showDelete && /* @__PURE__ */ jsx4(
|
|
1547
|
+
"button",
|
|
1548
|
+
{
|
|
1549
|
+
onClick: (e) => {
|
|
1550
|
+
e.stopPropagation();
|
|
1551
|
+
setFileToDelete(f2);
|
|
1552
|
+
},
|
|
1553
|
+
className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-red-500 backdrop-blur-md transition-colors",
|
|
1554
|
+
children: /* @__PURE__ */ jsx4(IconTrash, { className: "w-4 h-4" })
|
|
1555
|
+
}
|
|
1556
|
+
)
|
|
1557
|
+
] }) }),
|
|
1558
|
+
/* @__PURE__ */ jsx4("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() })
|
|
1559
|
+
]
|
|
1560
|
+
},
|
|
1561
|
+
f2.key
|
|
1562
|
+
);
|
|
1563
|
+
})
|
|
1150
1564
|
] }),
|
|
1151
|
-
/* @__PURE__ */
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1565
|
+
!loading && visibleFolders.length === 0 && visibleFiles.length === 0 && /* @__PURE__ */ jsxs4("div", { className: "h-full flex flex-col items-center justify-center text-gray-400", children: [
|
|
1566
|
+
/* @__PURE__ */ jsx4("div", { className: "w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-4", children: /* @__PURE__ */ jsx4(IconSearch, { className: "w-8 h-8 opacity-40" }) }),
|
|
1567
|
+
/* @__PURE__ */ jsx4("h3", { className: "text-gray-900 font-medium mb-1", children: "No Assets Found" }),
|
|
1568
|
+
/* @__PURE__ */ jsx4("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
|
|
1569
|
+
] })
|
|
1570
|
+
] }),
|
|
1571
|
+
activeView === "upload" && /* @__PURE__ */ jsx4("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto", children: /* @__PURE__ */ jsxs4("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
|
|
1572
|
+
/* @__PURE__ */ jsxs4("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
|
|
1573
|
+
/* @__PURE__ */ jsx4(IconUploadCloud, { className: "text-blue-500" }),
|
|
1574
|
+
"Upload Files"
|
|
1575
|
+
] }),
|
|
1576
|
+
/* @__PURE__ */ jsxs4("div", { className: "mb-6", children: [
|
|
1577
|
+
/* @__PURE__ */ jsx4("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Target Folder" }),
|
|
1578
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex gap-2", children: [
|
|
1579
|
+
/* @__PURE__ */ jsx4(
|
|
1580
|
+
"select",
|
|
1581
|
+
{
|
|
1582
|
+
value: selectedUploadFolder,
|
|
1583
|
+
onChange: (e) => setSelectedUploadFolder(e.target.value),
|
|
1584
|
+
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",
|
|
1585
|
+
children: uploadFoldersList.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.id, children: opt.name }, opt.id))
|
|
1586
|
+
}
|
|
1587
|
+
),
|
|
1588
|
+
/* @__PURE__ */ jsx4("button", { onClick: () => setShowCreateFolder(true), className: "px-3 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-600 transition-colors", children: /* @__PURE__ */ jsx4(IconFolderPlus, { className: "w-5 h-5" }) })
|
|
1589
|
+
] })
|
|
1590
|
+
] }),
|
|
1591
|
+
/* @__PURE__ */ jsx4(
|
|
1592
|
+
Uploader,
|
|
1593
|
+
{
|
|
1594
|
+
clientOptions,
|
|
1595
|
+
projectId,
|
|
1596
|
+
folderPath: uploadFoldersList.find((o) => o.id === selectedUploadFolder)?.path || activeBreadcrumbPath || folderPath,
|
|
1597
|
+
accept: ["image/*"],
|
|
1598
|
+
maxFileSize,
|
|
1599
|
+
maxFiles,
|
|
1600
|
+
autoRecordToDb,
|
|
1601
|
+
fetchThumbnails,
|
|
1602
|
+
autoUpload: true,
|
|
1603
|
+
onClientUploadComplete: (file) => {
|
|
1604
|
+
if (file.status === "success" && file.publicUrl && file.fileKey) {
|
|
1605
|
+
const newItem = {
|
|
1606
|
+
key: file.fileKey,
|
|
1607
|
+
originalName: file.name,
|
|
1608
|
+
size: file.size,
|
|
1609
|
+
contentType: file.type,
|
|
1610
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1611
|
+
url: file.publicUrl,
|
|
1612
|
+
thumbnails: file.thumbnails
|
|
1613
|
+
};
|
|
1614
|
+
setFiles((prev) => [newItem, ...prev]);
|
|
1615
|
+
}
|
|
1616
|
+
},
|
|
1617
|
+
onComplete: () => {
|
|
1618
|
+
if (mode === "full") {
|
|
1619
|
+
setTimeout(() => {
|
|
1620
|
+
setActiveView("browse");
|
|
1621
|
+
load();
|
|
1622
|
+
}, 1e3);
|
|
1623
|
+
}
|
|
1624
|
+
},
|
|
1625
|
+
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",
|
|
1626
|
+
buttonClassName: "hidden",
|
|
1627
|
+
children: /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center gap-4", children: [
|
|
1628
|
+
/* @__PURE__ */ jsx4("div", { className: "w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx4(IconUploadCloud, { className: "w-6 h-6" }) }),
|
|
1629
|
+
/* @__PURE__ */ jsxs4("div", { children: [
|
|
1630
|
+
/* @__PURE__ */ jsx4("p", { className: "text-gray-900 font-medium", children: "Click to upload or drag and drop" }),
|
|
1631
|
+
/* @__PURE__ */ jsxs4("p", { className: "text-xs text-gray-500 mt-1", children: [
|
|
1632
|
+
"Supports PNG, JPG, GIF up to ",
|
|
1633
|
+
Math.round(maxFileSize / 1024 / 1024),
|
|
1634
|
+
"MB"
|
|
1635
|
+
] })
|
|
1636
|
+
] })
|
|
1637
|
+
] })
|
|
1638
|
+
}
|
|
1639
|
+
)
|
|
1640
|
+
] }) })
|
|
1641
|
+
] })
|
|
1156
1642
|
] })
|
|
1643
|
+
]
|
|
1644
|
+
}
|
|
1645
|
+
)
|
|
1646
|
+
] }),
|
|
1647
|
+
/* @__PURE__ */ jsx4(Dialog2.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
|
|
1648
|
+
/* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[60]" }),
|
|
1649
|
+
/* @__PURE__ */ jsxs4(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: [
|
|
1650
|
+
/* @__PURE__ */ jsx4(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
|
|
1651
|
+
/* @__PURE__ */ jsx4(
|
|
1652
|
+
"input",
|
|
1653
|
+
{
|
|
1654
|
+
autoFocus: true,
|
|
1655
|
+
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",
|
|
1656
|
+
placeholder: "Folder Name",
|
|
1657
|
+
value: newFolderName,
|
|
1658
|
+
onChange: (e) => setNewFolderName(e.target.value),
|
|
1659
|
+
onKeyDown: (e) => e.key === "Enter" && handleCreateFolder()
|
|
1660
|
+
}
|
|
1661
|
+
),
|
|
1662
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex justify-end gap-3", children: [
|
|
1663
|
+
/* @__PURE__ */ jsx4("button", { onClick: () => setShowCreateFolder(false), className: "px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg", children: "Cancel" }),
|
|
1664
|
+
/* @__PURE__ */ jsx4(
|
|
1665
|
+
"button",
|
|
1666
|
+
{
|
|
1667
|
+
onClick: handleCreateFolder,
|
|
1668
|
+
disabled: creatingFolder || !newFolderName.trim(),
|
|
1669
|
+
className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50",
|
|
1670
|
+
children: "Create"
|
|
1671
|
+
}
|
|
1672
|
+
)
|
|
1673
|
+
] })
|
|
1674
|
+
] })
|
|
1675
|
+
] }) }),
|
|
1676
|
+
/* @__PURE__ */ jsx4(Dialog2.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
|
|
1677
|
+
/* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[80]" }),
|
|
1678
|
+
/* @__PURE__ */ jsxs4(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: [
|
|
1679
|
+
/* @__PURE__ */ jsx4(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
|
|
1680
|
+
/* @__PURE__ */ jsxs4(Dialog2.Description, { className: "text-gray-600 mb-6", children: [
|
|
1681
|
+
"Are you sure you want to delete ",
|
|
1682
|
+
/* @__PURE__ */ jsxs4("span", { className: "font-semibold text-gray-900", children: [
|
|
1683
|
+
'"',
|
|
1684
|
+
fileToDelete?.originalName,
|
|
1685
|
+
'"'
|
|
1157
1686
|
] }),
|
|
1158
|
-
|
|
1159
|
-
|
|
1687
|
+
"?"
|
|
1688
|
+
] }),
|
|
1689
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex justify-end gap-3", children: [
|
|
1690
|
+
/* @__PURE__ */ jsx4(
|
|
1691
|
+
"button",
|
|
1160
1692
|
{
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
accept: ["image/*"],
|
|
1165
|
-
maxFileSize,
|
|
1166
|
-
maxFiles,
|
|
1167
|
-
autoRecordToDb,
|
|
1168
|
-
fetchThumbnails,
|
|
1169
|
-
autoUpload: true,
|
|
1170
|
-
onClientUploadComplete: (file) => {
|
|
1171
|
-
if (file.status === "success" && file.publicUrl && file.fileKey) {
|
|
1172
|
-
const newItem = {
|
|
1173
|
-
key: file.fileKey,
|
|
1174
|
-
originalName: file.name,
|
|
1175
|
-
size: file.size,
|
|
1176
|
-
contentType: file.type,
|
|
1177
|
-
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1178
|
-
url: file.publicUrl,
|
|
1179
|
-
thumbnails: file.thumbnails
|
|
1180
|
-
};
|
|
1181
|
-
setItems((prev) => [newItem, ...prev]);
|
|
1182
|
-
}
|
|
1183
|
-
},
|
|
1184
|
-
onComplete: (files) => {
|
|
1185
|
-
if (mode === "full") {
|
|
1186
|
-
setTimeout(() => {
|
|
1187
|
-
load();
|
|
1188
|
-
setTab("browse");
|
|
1189
|
-
}, 500);
|
|
1190
|
-
}
|
|
1191
|
-
},
|
|
1192
|
-
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",
|
|
1193
|
-
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"
|
|
1693
|
+
onClick: () => setFileToDelete(null),
|
|
1694
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg",
|
|
1695
|
+
children: "Cancel"
|
|
1194
1696
|
}
|
|
1195
1697
|
),
|
|
1196
|
-
/* @__PURE__ */ jsx4(
|
|
1698
|
+
/* @__PURE__ */ jsx4(
|
|
1699
|
+
"button",
|
|
1700
|
+
{
|
|
1701
|
+
onClick: handleDelete,
|
|
1702
|
+
disabled: !!deleting,
|
|
1703
|
+
className: "px-4 py-2 text-sm font-medium rounded-lg bg-red-600 text-white hover:bg-red-700 disabled:opacity-70",
|
|
1704
|
+
children: deleting ? "Deleting..." : "Delete"
|
|
1705
|
+
}
|
|
1706
|
+
)
|
|
1197
1707
|
] })
|
|
1198
|
-
}
|
|
1199
|
-
)
|
|
1200
|
-
] })
|
|
1708
|
+
] })
|
|
1709
|
+
] }) })
|
|
1710
|
+
] });
|
|
1201
1711
|
}
|
|
1202
1712
|
export {
|
|
1203
1713
|
ConnectProjectDialog,
|