@thetechfossil/upfiles 0.6.0 → 1.0.2
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 +397 -70
- package/dist/index.d.mts +134 -1
- package/dist/index.d.ts +134 -1
- package/dist/index.js +699 -40
- package/dist/index.mjs +677 -39
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,14 +17,33 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
33
|
+
ConnectProjectDialog: () => ConnectProjectDialog,
|
|
34
|
+
ImageManager: () => ImageManager,
|
|
23
35
|
ProjectFilesWidget: () => ProjectFilesWidget,
|
|
24
36
|
UpfilesClient: () => UpfilesClient,
|
|
25
|
-
Uploader: () => Uploader
|
|
37
|
+
Uploader: () => Uploader,
|
|
38
|
+
addApiKeyManually: () => addApiKeyManually,
|
|
39
|
+
connectProject: () => connectProject,
|
|
40
|
+
connectUsingApiKey: () => connectUsingApiKey,
|
|
41
|
+
createClientWithKey: () => createClientWithKey,
|
|
42
|
+
createProject: () => createProject,
|
|
43
|
+
fetchFileThumbnails: () => fetchFileThumbnails,
|
|
44
|
+
fetchProjectFiles: () => fetchProjectFiles,
|
|
45
|
+
fetchProjectImagesWithThumbs: () => fetchProjectImagesWithThumbs,
|
|
46
|
+
listProjects: () => listProjects
|
|
26
47
|
});
|
|
27
48
|
module.exports = __toCommonJS(index_exports);
|
|
28
49
|
|
|
@@ -181,13 +202,18 @@ var Uploader = ({
|
|
|
181
202
|
maxFiles = 10,
|
|
182
203
|
onComplete,
|
|
183
204
|
onError,
|
|
205
|
+
onUploadBegin,
|
|
206
|
+
onUploadProgress,
|
|
207
|
+
onClientUploadComplete,
|
|
184
208
|
className,
|
|
185
209
|
buttonClassName,
|
|
186
210
|
dropzoneClassName,
|
|
187
211
|
children,
|
|
188
212
|
projectId,
|
|
189
213
|
folderPath,
|
|
190
|
-
fetchThumbnails
|
|
214
|
+
fetchThumbnails,
|
|
215
|
+
autoRecordToDb = false,
|
|
216
|
+
recordUrl = "/api/files"
|
|
191
217
|
}) => {
|
|
192
218
|
const inputRef = (0, import_react.useRef)(null);
|
|
193
219
|
const client = (0, import_react.useMemo)(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
|
|
@@ -195,19 +221,19 @@ var Uploader = ({
|
|
|
195
221
|
const [isUploading, setIsUploading] = (0, import_react.useState)(false);
|
|
196
222
|
const [isDragOver, setIsDragOver] = (0, import_react.useState)(false);
|
|
197
223
|
const addFiles = (0, import_react.useCallback)((incoming) => {
|
|
198
|
-
const filtered = incoming.filter((
|
|
199
|
-
if (
|
|
224
|
+
const filtered = incoming.filter((f2) => {
|
|
225
|
+
if (f2.size > maxFileSize) {
|
|
200
226
|
return false;
|
|
201
227
|
}
|
|
202
228
|
return true;
|
|
203
229
|
});
|
|
204
230
|
const limited = filtered.slice(0, Math.max(0, maxFiles - files.length));
|
|
205
|
-
const mapped = limited.map((
|
|
231
|
+
const mapped = limited.map((f2) => ({
|
|
206
232
|
id: Math.random().toString(36).slice(2),
|
|
207
|
-
file:
|
|
208
|
-
name:
|
|
209
|
-
size:
|
|
210
|
-
type:
|
|
233
|
+
file: f2,
|
|
234
|
+
name: f2.name,
|
|
235
|
+
size: f2.size,
|
|
236
|
+
type: f2.type,
|
|
211
237
|
progress: 0,
|
|
212
238
|
status: "pending"
|
|
213
239
|
}));
|
|
@@ -226,9 +252,10 @@ var Uploader = ({
|
|
|
226
252
|
}, [addFiles]);
|
|
227
253
|
const uploadOne = (0, import_react.useCallback)(async (item) => {
|
|
228
254
|
const update = (patch) => {
|
|
229
|
-
setFiles((prev) => prev.map((
|
|
255
|
+
setFiles((prev) => prev.map((f2) => f2.id === item.id ? { ...f2, ...patch } : f2));
|
|
230
256
|
};
|
|
231
257
|
try {
|
|
258
|
+
onUploadBegin?.(item.name);
|
|
232
259
|
update({ status: "uploading", progress: 1 });
|
|
233
260
|
const presign = await client.getPresignedUrl({
|
|
234
261
|
fileName: item.name,
|
|
@@ -244,6 +271,7 @@ var Uploader = ({
|
|
|
244
271
|
if (ev.lengthComputable) {
|
|
245
272
|
const pct = Math.min(99, Math.floor(ev.loaded / ev.total * 100));
|
|
246
273
|
update({ progress: pct });
|
|
274
|
+
onUploadProgress?.(item.name, pct);
|
|
247
275
|
}
|
|
248
276
|
};
|
|
249
277
|
xhr.onload = () => {
|
|
@@ -264,8 +292,39 @@ var Uploader = ({
|
|
|
264
292
|
}
|
|
265
293
|
} catch {
|
|
266
294
|
}
|
|
267
|
-
|
|
268
|
-
|
|
295
|
+
if (autoRecordToDb && projectId) {
|
|
296
|
+
try {
|
|
297
|
+
const baseUrl = clientOptions?.baseUrl || "";
|
|
298
|
+
const url = baseUrl ? `${baseUrl}${recordUrl}` : recordUrl;
|
|
299
|
+
const headers = {
|
|
300
|
+
"content-type": "application/json",
|
|
301
|
+
...clientOptions?.headers || {}
|
|
302
|
+
};
|
|
303
|
+
if (clientOptions?.apiKey) {
|
|
304
|
+
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
305
|
+
if (keyHeader === "authorization") {
|
|
306
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
307
|
+
} else {
|
|
308
|
+
headers[keyHeader] = clientOptions.apiKey;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
await fetch(url, {
|
|
312
|
+
method: "POST",
|
|
313
|
+
headers,
|
|
314
|
+
credentials: clientOptions?.withCredentials ? "include" : "same-origin",
|
|
315
|
+
body: JSON.stringify({
|
|
316
|
+
key: presign.fileKey,
|
|
317
|
+
originalName: item.name,
|
|
318
|
+
size: item.size,
|
|
319
|
+
contentType: item.type,
|
|
320
|
+
projectId
|
|
321
|
+
})
|
|
322
|
+
});
|
|
323
|
+
} catch (e) {
|
|
324
|
+
console.warn("Failed to record file to database:", e);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const result = {
|
|
269
328
|
...item,
|
|
270
329
|
status: "success",
|
|
271
330
|
progress: 100,
|
|
@@ -276,26 +335,29 @@ var Uploader = ({
|
|
|
276
335
|
// @ts-ignore - allow downstream to access thumbs if present
|
|
277
336
|
thumbnails: thumbs
|
|
278
337
|
};
|
|
338
|
+
update({ status: "success", progress: 100, fileKey: presign.fileKey, publicUrl: presign.publicUrl });
|
|
339
|
+
onClientUploadComplete?.(result);
|
|
340
|
+
return result;
|
|
279
341
|
} catch (err) {
|
|
280
342
|
const message = err instanceof Error ? err.message : "Upload failed";
|
|
281
343
|
update({ status: "error", error: message });
|
|
282
344
|
throw err;
|
|
283
345
|
}
|
|
284
|
-
}, [client, projectId, folderPath, fetchThumbnails]);
|
|
346
|
+
}, [client, projectId, folderPath, fetchThumbnails, autoRecordToDb, recordUrl, clientOptions, onUploadBegin, onUploadProgress, onClientUploadComplete]);
|
|
285
347
|
const uploadAll = (0, import_react.useCallback)(async () => {
|
|
286
|
-
const targets = files.filter((
|
|
348
|
+
const targets = files.filter((f2) => f2.status === "pending");
|
|
287
349
|
if (!targets.length) return;
|
|
288
350
|
setIsUploading(true);
|
|
289
351
|
try {
|
|
290
352
|
const results = [];
|
|
291
|
-
for (const
|
|
353
|
+
for (const f2 of targets) {
|
|
292
354
|
try {
|
|
293
|
-
const done = await uploadOne(
|
|
355
|
+
const done = await uploadOne(f2);
|
|
294
356
|
results.push(done);
|
|
295
357
|
} catch (e) {
|
|
296
358
|
}
|
|
297
359
|
}
|
|
298
|
-
onComplete?.(results.filter((
|
|
360
|
+
onComplete?.(results.filter((f2) => f2.status === "success"));
|
|
299
361
|
} catch (e) {
|
|
300
362
|
onError?.(e);
|
|
301
363
|
} finally {
|
|
@@ -349,7 +411,7 @@ var Uploader = ({
|
|
|
349
411
|
"button",
|
|
350
412
|
{
|
|
351
413
|
className: buttonClassName,
|
|
352
|
-
disabled: isUploading || files.every((
|
|
414
|
+
disabled: isUploading || files.every((f2) => f2.status !== "pending"),
|
|
353
415
|
onClick: uploadAll,
|
|
354
416
|
children: isUploading ? "Uploading..." : "Upload"
|
|
355
417
|
}
|
|
@@ -364,21 +426,21 @@ var Uploader = ({
|
|
|
364
426
|
}
|
|
365
427
|
)
|
|
366
428
|
] }),
|
|
367
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { display: "grid", gap: 8, listStyle: "none", padding: 0 }, children: files.map((
|
|
429
|
+
/* @__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: [
|
|
368
430
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { minWidth: 0 }, children: [
|
|
369
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children:
|
|
431
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.name }),
|
|
370
432
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12, color: "#666" }, children: [
|
|
371
|
-
(
|
|
433
|
+
(f2.size / (1024 * 1024)).toFixed(2),
|
|
372
434
|
" MB"
|
|
373
435
|
] }),
|
|
374
|
-
|
|
375
|
-
|
|
436
|
+
f2.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "#b91c1c" }, children: f2.error }),
|
|
437
|
+
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" })
|
|
376
438
|
] }),
|
|
377
439
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { minWidth: 120, textAlign: "right" }, children: [
|
|
378
|
-
/* @__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: `${
|
|
379
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "#666", marginTop: 4 }, children:
|
|
440
|
+
/* @__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" } }) }),
|
|
441
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "#666", marginTop: 4 }, children: f2.status })
|
|
380
442
|
] })
|
|
381
|
-
] }) },
|
|
443
|
+
] }) }, f2.id)) })
|
|
382
444
|
] })
|
|
383
445
|
] });
|
|
384
446
|
};
|
|
@@ -395,7 +457,10 @@ var ProjectFilesWidget = ({
|
|
|
395
457
|
onSelect,
|
|
396
458
|
saveUrl,
|
|
397
459
|
onSave,
|
|
398
|
-
onSaved
|
|
460
|
+
onSaved,
|
|
461
|
+
onDelete,
|
|
462
|
+
deleteUrl = "/api/files",
|
|
463
|
+
showDelete = false
|
|
399
464
|
}) => {
|
|
400
465
|
const client = (0, import_react2.useMemo)(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
|
|
401
466
|
const [files, setFiles] = (0, import_react2.useState)([]);
|
|
@@ -403,6 +468,7 @@ var ProjectFilesWidget = ({
|
|
|
403
468
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
404
469
|
const [query, setQuery] = (0, import_react2.useState)("");
|
|
405
470
|
const [savingKey, setSavingKey] = (0, import_react2.useState)(null);
|
|
471
|
+
const [deletingKey, setDeletingKey] = (0, import_react2.useState)(null);
|
|
406
472
|
(0, import_react2.useEffect)(() => {
|
|
407
473
|
let cancelled = false;
|
|
408
474
|
const run = async () => {
|
|
@@ -423,8 +489,44 @@ var ProjectFilesWidget = ({
|
|
|
423
489
|
};
|
|
424
490
|
}, [client, folderPath]);
|
|
425
491
|
const visible = files.filter(
|
|
426
|
-
(
|
|
492
|
+
(f2) => !query ? true : (f2.originalName || "").toLowerCase().includes(query.toLowerCase())
|
|
427
493
|
);
|
|
494
|
+
const handleDelete = async (key) => {
|
|
495
|
+
if (!confirm("Delete this file?")) return;
|
|
496
|
+
try {
|
|
497
|
+
setDeletingKey(key);
|
|
498
|
+
if (onDelete) {
|
|
499
|
+
await onDelete(key);
|
|
500
|
+
} else {
|
|
501
|
+
const baseUrl = clientOptions?.baseUrl || "";
|
|
502
|
+
const url = baseUrl ? `${baseUrl}${deleteUrl}` : deleteUrl;
|
|
503
|
+
const headers = {
|
|
504
|
+
"content-type": "application/json",
|
|
505
|
+
...clientOptions?.headers || {}
|
|
506
|
+
};
|
|
507
|
+
if (clientOptions?.apiKey) {
|
|
508
|
+
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
509
|
+
if (keyHeader === "authorization") {
|
|
510
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
511
|
+
} else {
|
|
512
|
+
headers[keyHeader] = clientOptions.apiKey;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const res = await fetch(url, {
|
|
516
|
+
method: "DELETE",
|
|
517
|
+
headers,
|
|
518
|
+
credentials: clientOptions?.withCredentials ? "include" : "same-origin",
|
|
519
|
+
body: JSON.stringify({ fileKey: key })
|
|
520
|
+
});
|
|
521
|
+
if (!res.ok) throw new Error("Delete failed");
|
|
522
|
+
}
|
|
523
|
+
setFiles((prev) => prev.filter((f2) => f2.key !== key));
|
|
524
|
+
} catch (e) {
|
|
525
|
+
alert(e?.message || "Failed to delete");
|
|
526
|
+
} finally {
|
|
527
|
+
setDeletingKey(null);
|
|
528
|
+
}
|
|
529
|
+
};
|
|
428
530
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, children: [
|
|
429
531
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
|
|
430
532
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -463,31 +565,31 @@ var ProjectFilesWidget = ({
|
|
|
463
565
|
loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
|
|
464
566
|
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
|
|
465
567
|
!loading && !error && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
|
|
466
|
-
visible.map((
|
|
568
|
+
visible.map((f2) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
467
569
|
"li",
|
|
468
570
|
{
|
|
469
571
|
className: itemClassName,
|
|
470
572
|
style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "flex", justifyContent: "space-between", alignItems: "center" },
|
|
471
573
|
children: [
|
|
472
574
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0 }, children: [
|
|
473
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children:
|
|
575
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.originalName }),
|
|
474
576
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: 12, color: "#666" }, children: [
|
|
475
|
-
(
|
|
577
|
+
(f2.size / (1024 * 1024)).toFixed(2),
|
|
476
578
|
" MB \u2022 ",
|
|
477
|
-
|
|
579
|
+
f2.contentType
|
|
478
580
|
] })
|
|
479
581
|
] }),
|
|
480
582
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
481
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href:
|
|
583
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: f2.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
|
|
482
584
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
483
585
|
"button",
|
|
484
586
|
{
|
|
485
587
|
onClick: async () => {
|
|
486
|
-
const selected = { name:
|
|
588
|
+
const selected = { name: f2.originalName, key: f2.key, url: f2.url, size: f2.size, contentType: f2.contentType };
|
|
487
589
|
onSelect(selected);
|
|
488
590
|
if (typeof onSave === "function" || !!saveUrl) {
|
|
489
591
|
try {
|
|
490
|
-
setSavingKey(
|
|
592
|
+
setSavingKey(f2.key);
|
|
491
593
|
if (typeof onSave === "function") {
|
|
492
594
|
await onSave(selected);
|
|
493
595
|
onSaved?.();
|
|
@@ -511,23 +613,580 @@ var ProjectFilesWidget = ({
|
|
|
511
613
|
}
|
|
512
614
|
}
|
|
513
615
|
},
|
|
514
|
-
disabled: savingKey ===
|
|
515
|
-
style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey ===
|
|
516
|
-
children: savingKey ===
|
|
616
|
+
disabled: savingKey === f2.key,
|
|
617
|
+
style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey === f2.key ? 0.7 : 1 },
|
|
618
|
+
children: savingKey === f2.key ? "Saving\u2026" : "Use"
|
|
619
|
+
}
|
|
620
|
+
),
|
|
621
|
+
showDelete && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
622
|
+
"button",
|
|
623
|
+
{
|
|
624
|
+
onClick: () => handleDelete(f2.key),
|
|
625
|
+
disabled: deletingKey === f2.key,
|
|
626
|
+
style: { padding: "6px 10px", background: "#dc2626", color: "#fff", border: "1px solid #b91c1c", borderRadius: 6, opacity: deletingKey === f2.key ? 0.7 : 1 },
|
|
627
|
+
children: deletingKey === f2.key ? "Deleting\u2026" : "Delete"
|
|
517
628
|
}
|
|
518
629
|
)
|
|
519
630
|
] })
|
|
520
631
|
]
|
|
521
632
|
},
|
|
522
|
-
|
|
633
|
+
f2.key
|
|
523
634
|
)),
|
|
524
635
|
visible.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
|
|
525
636
|
] })
|
|
526
637
|
] });
|
|
527
638
|
};
|
|
639
|
+
|
|
640
|
+
// src/ConnectProjectDialog.tsx
|
|
641
|
+
var React3 = __toESM(require("react"));
|
|
642
|
+
var Dialog = __toESM(require("@radix-ui/react-dialog"));
|
|
643
|
+
|
|
644
|
+
// src/projectConnect.ts
|
|
645
|
+
var f = (globalThis.fetch || fetch).bind(globalThis);
|
|
646
|
+
async function httpJson(input, init, useFetch) {
|
|
647
|
+
const doFetch = useFetch ?? f;
|
|
648
|
+
const res = await doFetch(input, init);
|
|
649
|
+
if (!res.ok) {
|
|
650
|
+
let msg = `Request failed (${res.status})`;
|
|
651
|
+
try {
|
|
652
|
+
const j = await res.json();
|
|
653
|
+
msg = j?.error || msg;
|
|
654
|
+
} catch {
|
|
655
|
+
}
|
|
656
|
+
throw new Error(msg);
|
|
657
|
+
}
|
|
658
|
+
return res.json();
|
|
659
|
+
}
|
|
660
|
+
async function listProjects(opts) {
|
|
661
|
+
const url = `${opts.baseUrl.replace(/\/$/, "")}/api/projects`;
|
|
662
|
+
const data = await httpJson(
|
|
663
|
+
url,
|
|
664
|
+
{ method: "GET", credentials: "include" },
|
|
665
|
+
opts.fetch
|
|
666
|
+
);
|
|
667
|
+
return data?.projects ?? [];
|
|
668
|
+
}
|
|
669
|
+
async function createProject(opts) {
|
|
670
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
671
|
+
const created = await httpJson(
|
|
672
|
+
`${base}/api/projects`,
|
|
673
|
+
{
|
|
674
|
+
method: "POST",
|
|
675
|
+
credentials: "include",
|
|
676
|
+
headers: { "content-type": "application/json" },
|
|
677
|
+
body: JSON.stringify({ name: opts.name })
|
|
678
|
+
},
|
|
679
|
+
opts.fetch
|
|
680
|
+
);
|
|
681
|
+
const projectId = created?.project?.id;
|
|
682
|
+
if (!projectId) throw new Error("Failed to create project");
|
|
683
|
+
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
684
|
+
const key = await httpJson(
|
|
685
|
+
`${base}/api/projects/${projectId}/keys`,
|
|
686
|
+
{
|
|
687
|
+
method: "POST",
|
|
688
|
+
credentials: "include",
|
|
689
|
+
headers: { "content-type": "application/json" },
|
|
690
|
+
body: JSON.stringify({ name: keyLabel })
|
|
691
|
+
},
|
|
692
|
+
opts.fetch
|
|
693
|
+
);
|
|
694
|
+
const plaintext = key?.key?.plaintext;
|
|
695
|
+
if (!plaintext) throw new Error("Failed to create API key");
|
|
696
|
+
return { apiKey: plaintext, projectId };
|
|
697
|
+
}
|
|
698
|
+
async function connectProject(opts) {
|
|
699
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
700
|
+
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
701
|
+
const key = await httpJson(
|
|
702
|
+
`${base}/api/projects/${opts.projectId}/keys`,
|
|
703
|
+
{
|
|
704
|
+
method: "POST",
|
|
705
|
+
credentials: "include",
|
|
706
|
+
headers: { "content-type": "application/json" },
|
|
707
|
+
body: JSON.stringify({ name: keyLabel })
|
|
708
|
+
},
|
|
709
|
+
opts.fetch
|
|
710
|
+
);
|
|
711
|
+
const plaintext = key?.key?.plaintext;
|
|
712
|
+
if (!plaintext) throw new Error("Failed to create API key");
|
|
713
|
+
return { apiKey: plaintext };
|
|
714
|
+
}
|
|
715
|
+
function addApiKeyManually(apiKey) {
|
|
716
|
+
const k = (apiKey || "").trim();
|
|
717
|
+
if (!k) throw new Error("API key is required");
|
|
718
|
+
if (!/^upk_[A-Za-z0-9]+/.test(k)) {
|
|
719
|
+
console.warn("Provided API key does not match expected format upk_*");
|
|
720
|
+
}
|
|
721
|
+
return { apiKey: k };
|
|
722
|
+
}
|
|
723
|
+
function createClientWithKey(baseUrl, apiKey) {
|
|
724
|
+
return new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: "authorization" });
|
|
725
|
+
}
|
|
726
|
+
function connectUsingApiKey(opts) {
|
|
727
|
+
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
728
|
+
const apiKey = (opts.apiKey || "").trim();
|
|
729
|
+
if (!apiKey) throw new Error("apiKey is required");
|
|
730
|
+
const client = new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: opts.apiKeyHeader ?? "authorization" });
|
|
731
|
+
return { apiKey, client };
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/ConnectProjectDialog.tsx
|
|
735
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
736
|
+
function ConnectProjectDialog(props) {
|
|
737
|
+
const { baseUrl, open, onOpenChange, onConnected, fetchImpl, title, description, className } = props;
|
|
738
|
+
const [mode, setMode] = React3.useState("existing");
|
|
739
|
+
const [loading, setLoading] = React3.useState(false);
|
|
740
|
+
const [error, setError] = React3.useState(null);
|
|
741
|
+
const [projects, setProjects] = React3.useState([]);
|
|
742
|
+
const [selectedProjectId, setSelectedProjectId] = React3.useState("");
|
|
743
|
+
const [manualKey, setManualKey] = React3.useState("");
|
|
744
|
+
const [newProjectName, setNewProjectName] = React3.useState("");
|
|
745
|
+
const resetState = React3.useCallback(() => {
|
|
746
|
+
setMode("existing");
|
|
747
|
+
setLoading(false);
|
|
748
|
+
setError(null);
|
|
749
|
+
setProjects([]);
|
|
750
|
+
setSelectedProjectId("");
|
|
751
|
+
setManualKey("");
|
|
752
|
+
setNewProjectName("");
|
|
753
|
+
}, []);
|
|
754
|
+
React3.useEffect(() => {
|
|
755
|
+
const run = async () => {
|
|
756
|
+
if (!open || mode !== "existing") return;
|
|
757
|
+
try {
|
|
758
|
+
setLoading(true);
|
|
759
|
+
setError(null);
|
|
760
|
+
const opts = { baseUrl, fetch: fetchImpl };
|
|
761
|
+
const list = await listProjects(opts);
|
|
762
|
+
setProjects(list);
|
|
763
|
+
if (list.length && !selectedProjectId) setSelectedProjectId(list[0].id);
|
|
764
|
+
} catch (e) {
|
|
765
|
+
setError(e?.message || "Failed to fetch projects");
|
|
766
|
+
} finally {
|
|
767
|
+
setLoading(false);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
run();
|
|
771
|
+
}, [open, mode, baseUrl]);
|
|
772
|
+
const handleConnectExisting = async () => {
|
|
773
|
+
try {
|
|
774
|
+
setLoading(true);
|
|
775
|
+
setError(null);
|
|
776
|
+
if (!selectedProjectId) throw new Error("Please select a project");
|
|
777
|
+
const { apiKey } = await connectProject({ baseUrl, projectId: selectedProjectId, fetch: fetchImpl });
|
|
778
|
+
onConnected(apiKey, { projectId: selectedProjectId, source: "existing" });
|
|
779
|
+
onOpenChange(false);
|
|
780
|
+
resetState();
|
|
781
|
+
} catch (e) {
|
|
782
|
+
setError(e?.message || "Failed to connect project");
|
|
783
|
+
} finally {
|
|
784
|
+
setLoading(false);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
const handleManual = async () => {
|
|
788
|
+
try {
|
|
789
|
+
setLoading(true);
|
|
790
|
+
setError(null);
|
|
791
|
+
const { apiKey } = addApiKeyManually(manualKey);
|
|
792
|
+
onConnected(apiKey, { source: "manual" });
|
|
793
|
+
onOpenChange(false);
|
|
794
|
+
resetState();
|
|
795
|
+
} catch (e) {
|
|
796
|
+
setError(e?.message || "Invalid API key");
|
|
797
|
+
} finally {
|
|
798
|
+
setLoading(false);
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
const handleCreate = async () => {
|
|
802
|
+
try {
|
|
803
|
+
setLoading(true);
|
|
804
|
+
setError(null);
|
|
805
|
+
if (!newProjectName.trim()) throw new Error("Project name is required");
|
|
806
|
+
const { apiKey, projectId } = await createProject({ baseUrl, name: newProjectName.trim(), fetch: fetchImpl });
|
|
807
|
+
onConnected(apiKey, { projectId, source: "new" });
|
|
808
|
+
onOpenChange(false);
|
|
809
|
+
resetState();
|
|
810
|
+
} catch (e) {
|
|
811
|
+
setError(e?.message || "Failed to create project");
|
|
812
|
+
} finally {
|
|
813
|
+
setLoading(false);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
const ModeSwitcher = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
817
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
818
|
+
"button",
|
|
819
|
+
{
|
|
820
|
+
type: "button",
|
|
821
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "existing" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
822
|
+
onClick: () => setMode("existing"),
|
|
823
|
+
children: "Connect existing"
|
|
824
|
+
}
|
|
825
|
+
),
|
|
826
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
827
|
+
"button",
|
|
828
|
+
{
|
|
829
|
+
type: "button",
|
|
830
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "manual" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
831
|
+
onClick: () => setMode("manual"),
|
|
832
|
+
children: "Add API key"
|
|
833
|
+
}
|
|
834
|
+
),
|
|
835
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
836
|
+
"button",
|
|
837
|
+
{
|
|
838
|
+
type: "button",
|
|
839
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "new" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
840
|
+
onClick: () => setMode("new"),
|
|
841
|
+
children: "Create new"
|
|
842
|
+
}
|
|
843
|
+
)
|
|
844
|
+
] });
|
|
845
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Root, { open, onOpenChange: (o) => {
|
|
846
|
+
if (!o) resetState();
|
|
847
|
+
onOpenChange(o);
|
|
848
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Dialog.Portal, { children: [
|
|
849
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Overlay, { className: "fixed inset-0 bg-black/40" }),
|
|
850
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Content, { className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-md rounded-lg border bg-white p-4 shadow-lg ${className ?? ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3", children: [
|
|
851
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Title, { className: "text-lg font-semibold", children: title ?? "Connect to Upfiles" }),
|
|
852
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Description, { className: "text-sm text-muted-foreground", children: description ?? "Choose how to connect your project to retrieve an API key." }),
|
|
853
|
+
ModeSwitcher,
|
|
854
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
|
|
855
|
+
mode === "existing" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "Project" }),
|
|
857
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
858
|
+
"select",
|
|
859
|
+
{
|
|
860
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
861
|
+
value: selectedProjectId,
|
|
862
|
+
onChange: (e) => setSelectedProjectId(e.target.value),
|
|
863
|
+
disabled: loading,
|
|
864
|
+
children: [
|
|
865
|
+
projects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: loading ? "Loading..." : "No projects found" }),
|
|
866
|
+
projects.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: p.id, children: p.name }, p.id))
|
|
867
|
+
]
|
|
868
|
+
}
|
|
869
|
+
),
|
|
870
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
871
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
872
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
873
|
+
"button",
|
|
874
|
+
{
|
|
875
|
+
type: "button",
|
|
876
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
877
|
+
onClick: handleConnectExisting,
|
|
878
|
+
disabled: loading || !selectedProjectId,
|
|
879
|
+
children: loading ? "Connecting\u2026" : "Connect"
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
] })
|
|
883
|
+
] }),
|
|
884
|
+
mode === "manual" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
885
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "API key" }),
|
|
886
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
887
|
+
"input",
|
|
888
|
+
{
|
|
889
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
890
|
+
placeholder: "upk_...",
|
|
891
|
+
value: manualKey,
|
|
892
|
+
onChange: (e) => setManualKey(e.target.value),
|
|
893
|
+
disabled: loading
|
|
894
|
+
}
|
|
895
|
+
),
|
|
896
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
897
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
898
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
899
|
+
"button",
|
|
900
|
+
{
|
|
901
|
+
type: "button",
|
|
902
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
903
|
+
onClick: handleManual,
|
|
904
|
+
disabled: loading || !manualKey.trim(),
|
|
905
|
+
children: loading ? "Adding\u2026" : "Add key"
|
|
906
|
+
}
|
|
907
|
+
)
|
|
908
|
+
] })
|
|
909
|
+
] }),
|
|
910
|
+
mode === "new" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
911
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "Project name" }),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
913
|
+
"input",
|
|
914
|
+
{
|
|
915
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
916
|
+
placeholder: "My project",
|
|
917
|
+
value: newProjectName,
|
|
918
|
+
onChange: (e) => setNewProjectName(e.target.value),
|
|
919
|
+
disabled: loading
|
|
920
|
+
}
|
|
921
|
+
),
|
|
922
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-muted-foreground", children: "A project will be created in your Upfiles account and an API key generated." }),
|
|
923
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
924
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
925
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
926
|
+
"button",
|
|
927
|
+
{
|
|
928
|
+
type: "button",
|
|
929
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
930
|
+
onClick: handleCreate,
|
|
931
|
+
disabled: loading || !newProjectName.trim(),
|
|
932
|
+
children: loading ? "Creating\u2026" : "Create & connect"
|
|
933
|
+
}
|
|
934
|
+
)
|
|
935
|
+
] })
|
|
936
|
+
] })
|
|
937
|
+
] }) })
|
|
938
|
+
] }) });
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/files.ts
|
|
942
|
+
async function fetchProjectFiles(clientOptions, folderPath) {
|
|
943
|
+
const client = new UpfilesClient(clientOptions);
|
|
944
|
+
return client.getProjectFiles({ folderPath });
|
|
945
|
+
}
|
|
946
|
+
async function fetchFileThumbnails(clientOptions, fileKey) {
|
|
947
|
+
const client = new UpfilesClient(clientOptions);
|
|
948
|
+
return client.getThumbnails(fileKey);
|
|
949
|
+
}
|
|
950
|
+
async function fetchProjectImagesWithThumbs(clientOptions, opts) {
|
|
951
|
+
const files = await fetchProjectFiles(clientOptions, opts?.folderPath);
|
|
952
|
+
const limit = Math.max(1, Math.min(opts?.limitConcurrent ?? 6, 12));
|
|
953
|
+
const queue = [...files];
|
|
954
|
+
const results = [];
|
|
955
|
+
async function worker() {
|
|
956
|
+
while (queue.length) {
|
|
957
|
+
const f2 = queue.shift();
|
|
958
|
+
try {
|
|
959
|
+
const thumbs = await fetchFileThumbnails(clientOptions, f2.key).catch(() => void 0);
|
|
960
|
+
results.push({ ...f2, thumbnails: thumbs });
|
|
961
|
+
} catch {
|
|
962
|
+
results.push({ ...f2 });
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
await Promise.all(Array.from({ length: Math.min(limit, files.length || 1) }, () => worker()));
|
|
967
|
+
return results;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/ImageManager.tsx
|
|
971
|
+
var React4 = __toESM(require("react"));
|
|
972
|
+
var Dialog2 = __toESM(require("@radix-ui/react-dialog"));
|
|
973
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
974
|
+
function ImageManager(props) {
|
|
975
|
+
const {
|
|
976
|
+
open,
|
|
977
|
+
onOpenChange,
|
|
978
|
+
clientOptions,
|
|
979
|
+
projectId,
|
|
980
|
+
folderPath,
|
|
981
|
+
title,
|
|
982
|
+
description,
|
|
983
|
+
className,
|
|
984
|
+
gridClassName,
|
|
985
|
+
onSelect,
|
|
986
|
+
onDelete,
|
|
987
|
+
deleteUrl = "/api/files",
|
|
988
|
+
autoRecordToDb = true,
|
|
989
|
+
fetchThumbnails = true,
|
|
990
|
+
maxFileSize = 100 * 1024 * 1024,
|
|
991
|
+
maxFiles = 10,
|
|
992
|
+
mode = "full",
|
|
993
|
+
showDelete = true
|
|
994
|
+
} = props;
|
|
995
|
+
const [tab, setTab] = React4.useState(mode === "upload" ? "upload" : "browse");
|
|
996
|
+
const [loading, setLoading] = React4.useState(false);
|
|
997
|
+
const [error, setError] = React4.useState(null);
|
|
998
|
+
const [items, setItems] = React4.useState([]);
|
|
999
|
+
const [query, setQuery] = React4.useState("");
|
|
1000
|
+
const [deleting, setDeleting] = React4.useState(null);
|
|
1001
|
+
const load = React4.useCallback(async () => {
|
|
1002
|
+
try {
|
|
1003
|
+
setLoading(true);
|
|
1004
|
+
setError(null);
|
|
1005
|
+
const list = await fetchProjectImagesWithThumbs(clientOptions, { folderPath, limitConcurrent: 6 });
|
|
1006
|
+
setItems(list);
|
|
1007
|
+
} catch (e) {
|
|
1008
|
+
setError(e?.message || "Failed to load images");
|
|
1009
|
+
} finally {
|
|
1010
|
+
setLoading(false);
|
|
1011
|
+
}
|
|
1012
|
+
}, [clientOptions, folderPath]);
|
|
1013
|
+
React4.useEffect(() => {
|
|
1014
|
+
if (!open) return;
|
|
1015
|
+
if (mode !== "upload" && tab === "browse") {
|
|
1016
|
+
load();
|
|
1017
|
+
}
|
|
1018
|
+
}, [open, tab, load, mode]);
|
|
1019
|
+
const visible = React4.useMemo(() => {
|
|
1020
|
+
const q = query.trim().toLowerCase();
|
|
1021
|
+
return !q ? items : items.filter((i) => (i.originalName || "").toLowerCase().includes(q));
|
|
1022
|
+
}, [items, query]);
|
|
1023
|
+
const handleDelete = React4.useCallback(
|
|
1024
|
+
async (key) => {
|
|
1025
|
+
if (!confirm("Delete this file?")) return;
|
|
1026
|
+
try {
|
|
1027
|
+
setDeleting(key);
|
|
1028
|
+
if (onDelete) {
|
|
1029
|
+
await onDelete(key);
|
|
1030
|
+
} else {
|
|
1031
|
+
const baseUrl = clientOptions?.baseUrl || "";
|
|
1032
|
+
const url = baseUrl ? `${baseUrl}${deleteUrl}` : deleteUrl;
|
|
1033
|
+
const headers = {
|
|
1034
|
+
"content-type": "application/json",
|
|
1035
|
+
...clientOptions?.headers || {}
|
|
1036
|
+
};
|
|
1037
|
+
if (clientOptions?.apiKey) {
|
|
1038
|
+
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
1039
|
+
if (keyHeader === "authorization") {
|
|
1040
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
1041
|
+
} else {
|
|
1042
|
+
headers[keyHeader] = clientOptions.apiKey;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
const res = await fetch(url, {
|
|
1046
|
+
method: "DELETE",
|
|
1047
|
+
headers,
|
|
1048
|
+
credentials: clientOptions?.withCredentials ? "include" : "same-origin",
|
|
1049
|
+
body: JSON.stringify({ fileKey: key })
|
|
1050
|
+
});
|
|
1051
|
+
if (!res.ok) throw new Error("Delete failed");
|
|
1052
|
+
}
|
|
1053
|
+
setItems((prev) => prev.filter((f2) => f2.key !== key));
|
|
1054
|
+
} catch (e) {
|
|
1055
|
+
alert(e?.message || "Failed to delete");
|
|
1056
|
+
} finally {
|
|
1057
|
+
setDeleting(null);
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
[clientOptions, deleteUrl, onDelete]
|
|
1061
|
+
);
|
|
1062
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Root, { open, onOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Portal, { children: [
|
|
1063
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40" }),
|
|
1064
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1065
|
+
Dialog2.Content,
|
|
1066
|
+
{
|
|
1067
|
+
className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-4xl rounded-lg border bg-white p-4 shadow-lg max-h-[90vh] overflow-auto ${className ?? ""}`,
|
|
1068
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "space-y-3", children: [
|
|
1069
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Title, { className: "text-lg font-semibold", children: title ?? "Image Manager" }),
|
|
1070
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Description, { className: "text-sm text-muted-foreground", children: description ?? "Upload new images or select from existing ones." }),
|
|
1071
|
+
mode === "full" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-2 border-b", children: [
|
|
1072
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1073
|
+
"button",
|
|
1074
|
+
{
|
|
1075
|
+
type: "button",
|
|
1076
|
+
onClick: () => setTab("browse"),
|
|
1077
|
+
className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === "browse" ? "border-blue-600 text-blue-600" : "border-transparent text-gray-600 hover:text-gray-900"}`,
|
|
1078
|
+
children: "Browse"
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1082
|
+
"button",
|
|
1083
|
+
{
|
|
1084
|
+
type: "button",
|
|
1085
|
+
onClick: () => setTab("upload"),
|
|
1086
|
+
className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === "upload" ? "border-blue-600 text-blue-600" : "border-transparent text-gray-600 hover:text-gray-900"}`,
|
|
1087
|
+
children: "Upload"
|
|
1088
|
+
}
|
|
1089
|
+
)
|
|
1090
|
+
] }),
|
|
1091
|
+
(mode === "browse" || mode === "full" && tab === "browse") && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1092
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-2 items-center", children: [
|
|
1093
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1094
|
+
"input",
|
|
1095
|
+
{
|
|
1096
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
1097
|
+
placeholder: "Search by filename...",
|
|
1098
|
+
value: query,
|
|
1099
|
+
onChange: (e) => setQuery(e.target.value)
|
|
1100
|
+
}
|
|
1101
|
+
),
|
|
1102
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "px-3 py-2 text-sm rounded border", onClick: load, disabled: loading, children: loading ? "Loading\u2026" : "Refresh" })
|
|
1103
|
+
] }),
|
|
1104
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
|
|
1105
|
+
/* @__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: [
|
|
1106
|
+
!loading && visible.map((f2) => {
|
|
1107
|
+
const thumbUrl = f2.thumbnails?.[0]?.url || f2.url;
|
|
1108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "group relative aspect-square overflow-hidden rounded border", children: [
|
|
1109
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: thumbUrl, alt: f2.originalName, className: "h-full w-full object-cover" }),
|
|
1110
|
+
/* @__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: [
|
|
1111
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1112
|
+
"button",
|
|
1113
|
+
{
|
|
1114
|
+
type: "button",
|
|
1115
|
+
onClick: () => {
|
|
1116
|
+
onSelect({
|
|
1117
|
+
url: f2.url,
|
|
1118
|
+
key: f2.key,
|
|
1119
|
+
originalName: f2.originalName,
|
|
1120
|
+
size: f2.size,
|
|
1121
|
+
contentType: f2.contentType,
|
|
1122
|
+
thumbnails: f2.thumbnails
|
|
1123
|
+
});
|
|
1124
|
+
onOpenChange(false);
|
|
1125
|
+
},
|
|
1126
|
+
className: "opacity-0 group-hover:opacity-100 px-3 py-1 text-xs bg-blue-600 text-white rounded",
|
|
1127
|
+
children: "Select"
|
|
1128
|
+
}
|
|
1129
|
+
),
|
|
1130
|
+
showDelete && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1131
|
+
"button",
|
|
1132
|
+
{
|
|
1133
|
+
type: "button",
|
|
1134
|
+
onClick: () => handleDelete(f2.key),
|
|
1135
|
+
disabled: deleting === f2.key,
|
|
1136
|
+
className: "opacity-0 group-hover:opacity-100 px-3 py-1 text-xs bg-red-600 text-white rounded disabled:opacity-50",
|
|
1137
|
+
children: deleting === f2.key ? "..." : "Delete"
|
|
1138
|
+
}
|
|
1139
|
+
)
|
|
1140
|
+
] }),
|
|
1141
|
+
/* @__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 })
|
|
1142
|
+
] }, f2.key);
|
|
1143
|
+
}),
|
|
1144
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "col-span-full text-sm text-muted-foreground", children: "Loading\u2026" }),
|
|
1145
|
+
!loading && visible.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "col-span-full text-sm text-muted-foreground", children: "No images found" })
|
|
1146
|
+
] })
|
|
1147
|
+
] }),
|
|
1148
|
+
(mode === "upload" || mode === "full" && tab === "upload") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1149
|
+
Uploader,
|
|
1150
|
+
{
|
|
1151
|
+
clientOptions,
|
|
1152
|
+
projectId,
|
|
1153
|
+
folderPath,
|
|
1154
|
+
accept: ["image/*"],
|
|
1155
|
+
maxFileSize,
|
|
1156
|
+
maxFiles,
|
|
1157
|
+
autoRecordToDb,
|
|
1158
|
+
fetchThumbnails,
|
|
1159
|
+
onComplete: (files) => {
|
|
1160
|
+
if (mode === "full") {
|
|
1161
|
+
load();
|
|
1162
|
+
setTab("browse");
|
|
1163
|
+
} else {
|
|
1164
|
+
}
|
|
1165
|
+
},
|
|
1166
|
+
dropzoneClassName: "border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer hover:border-blue-500 transition-colors",
|
|
1167
|
+
buttonClassName: "px-4 py-2 text-sm rounded border bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50"
|
|
1168
|
+
}
|
|
1169
|
+
),
|
|
1170
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex justify-end gap-2 pt-2 border-t", 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", children: "Close" }) }) })
|
|
1171
|
+
] })
|
|
1172
|
+
}
|
|
1173
|
+
)
|
|
1174
|
+
] }) });
|
|
1175
|
+
}
|
|
528
1176
|
// Annotate the CommonJS export names for ESM import in node:
|
|
529
1177
|
0 && (module.exports = {
|
|
1178
|
+
ConnectProjectDialog,
|
|
1179
|
+
ImageManager,
|
|
530
1180
|
ProjectFilesWidget,
|
|
531
1181
|
UpfilesClient,
|
|
532
|
-
Uploader
|
|
1182
|
+
Uploader,
|
|
1183
|
+
addApiKeyManually,
|
|
1184
|
+
connectProject,
|
|
1185
|
+
connectUsingApiKey,
|
|
1186
|
+
createClientWithKey,
|
|
1187
|
+
createProject,
|
|
1188
|
+
fetchFileThumbnails,
|
|
1189
|
+
fetchProjectFiles,
|
|
1190
|
+
fetchProjectImagesWithThumbs,
|
|
1191
|
+
listProjects
|
|
533
1192
|
});
|