@thetechfossil/upfiles 0.3.0-beta → 0.3.1-beta
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 +306 -0
- package/dist/index.d.mts +89 -1
- package/dist/index.d.ts +89 -1
- package/dist/index.js +494 -35
- package/dist/index.mjs +472 -34
- package/package.json +4 -2
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
|
+
ImagePickerDialog: () => ImagePickerDialog,
|
|
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
|
|
|
@@ -195,19 +216,19 @@ var Uploader = ({
|
|
|
195
216
|
const [isUploading, setIsUploading] = (0, import_react.useState)(false);
|
|
196
217
|
const [isDragOver, setIsDragOver] = (0, import_react.useState)(false);
|
|
197
218
|
const addFiles = (0, import_react.useCallback)((incoming) => {
|
|
198
|
-
const filtered = incoming.filter((
|
|
199
|
-
if (
|
|
219
|
+
const filtered = incoming.filter((f2) => {
|
|
220
|
+
if (f2.size > maxFileSize) {
|
|
200
221
|
return false;
|
|
201
222
|
}
|
|
202
223
|
return true;
|
|
203
224
|
});
|
|
204
225
|
const limited = filtered.slice(0, Math.max(0, maxFiles - files.length));
|
|
205
|
-
const mapped = limited.map((
|
|
226
|
+
const mapped = limited.map((f2) => ({
|
|
206
227
|
id: Math.random().toString(36).slice(2),
|
|
207
|
-
file:
|
|
208
|
-
name:
|
|
209
|
-
size:
|
|
210
|
-
type:
|
|
228
|
+
file: f2,
|
|
229
|
+
name: f2.name,
|
|
230
|
+
size: f2.size,
|
|
231
|
+
type: f2.type,
|
|
211
232
|
progress: 0,
|
|
212
233
|
status: "pending"
|
|
213
234
|
}));
|
|
@@ -226,7 +247,7 @@ var Uploader = ({
|
|
|
226
247
|
}, [addFiles]);
|
|
227
248
|
const uploadOne = (0, import_react.useCallback)(async (item) => {
|
|
228
249
|
const update = (patch) => {
|
|
229
|
-
setFiles((prev) => prev.map((
|
|
250
|
+
setFiles((prev) => prev.map((f2) => f2.id === item.id ? { ...f2, ...patch } : f2));
|
|
230
251
|
};
|
|
231
252
|
try {
|
|
232
253
|
update({ status: "uploading", progress: 1 });
|
|
@@ -283,19 +304,19 @@ var Uploader = ({
|
|
|
283
304
|
}
|
|
284
305
|
}, [client, projectId, folderPath, fetchThumbnails]);
|
|
285
306
|
const uploadAll = (0, import_react.useCallback)(async () => {
|
|
286
|
-
const targets = files.filter((
|
|
307
|
+
const targets = files.filter((f2) => f2.status === "pending");
|
|
287
308
|
if (!targets.length) return;
|
|
288
309
|
setIsUploading(true);
|
|
289
310
|
try {
|
|
290
311
|
const results = [];
|
|
291
|
-
for (const
|
|
312
|
+
for (const f2 of targets) {
|
|
292
313
|
try {
|
|
293
|
-
const done = await uploadOne(
|
|
314
|
+
const done = await uploadOne(f2);
|
|
294
315
|
results.push(done);
|
|
295
316
|
} catch (e) {
|
|
296
317
|
}
|
|
297
318
|
}
|
|
298
|
-
onComplete?.(results.filter((
|
|
319
|
+
onComplete?.(results.filter((f2) => f2.status === "success"));
|
|
299
320
|
} catch (e) {
|
|
300
321
|
onError?.(e);
|
|
301
322
|
} finally {
|
|
@@ -349,7 +370,7 @@ var Uploader = ({
|
|
|
349
370
|
"button",
|
|
350
371
|
{
|
|
351
372
|
className: buttonClassName,
|
|
352
|
-
disabled: isUploading || files.every((
|
|
373
|
+
disabled: isUploading || files.every((f2) => f2.status !== "pending"),
|
|
353
374
|
onClick: uploadAll,
|
|
354
375
|
children: isUploading ? "Uploading..." : "Upload"
|
|
355
376
|
}
|
|
@@ -364,21 +385,21 @@ var Uploader = ({
|
|
|
364
385
|
}
|
|
365
386
|
)
|
|
366
387
|
] }),
|
|
367
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { display: "grid", gap: 8, listStyle: "none", padding: 0 }, children: files.map((
|
|
388
|
+
/* @__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
389
|
/* @__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:
|
|
390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.name }),
|
|
370
391
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12, color: "#666" }, children: [
|
|
371
|
-
(
|
|
392
|
+
(f2.size / (1024 * 1024)).toFixed(2),
|
|
372
393
|
" MB"
|
|
373
394
|
] }),
|
|
374
|
-
|
|
375
|
-
|
|
395
|
+
f2.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "#b91c1c" }, children: f2.error }),
|
|
396
|
+
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
397
|
] }),
|
|
377
398
|
/* @__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:
|
|
399
|
+
/* @__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" } }) }),
|
|
400
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "#666", marginTop: 4 }, children: f2.status })
|
|
380
401
|
] })
|
|
381
|
-
] }) },
|
|
402
|
+
] }) }, f2.id)) })
|
|
382
403
|
] })
|
|
383
404
|
] });
|
|
384
405
|
};
|
|
@@ -423,7 +444,7 @@ var ProjectFilesWidget = ({
|
|
|
423
444
|
};
|
|
424
445
|
}, [client, folderPath]);
|
|
425
446
|
const visible = files.filter(
|
|
426
|
-
(
|
|
447
|
+
(f2) => !query ? true : (f2.originalName || "").toLowerCase().includes(query.toLowerCase())
|
|
427
448
|
);
|
|
428
449
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, children: [
|
|
429
450
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
|
|
@@ -463,31 +484,31 @@ var ProjectFilesWidget = ({
|
|
|
463
484
|
loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
|
|
464
485
|
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
|
|
465
486
|
!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((
|
|
487
|
+
visible.map((f2) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
467
488
|
"li",
|
|
468
489
|
{
|
|
469
490
|
className: itemClassName,
|
|
470
491
|
style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "flex", justifyContent: "space-between", alignItems: "center" },
|
|
471
492
|
children: [
|
|
472
493
|
/* @__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:
|
|
494
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.originalName }),
|
|
474
495
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: 12, color: "#666" }, children: [
|
|
475
|
-
(
|
|
496
|
+
(f2.size / (1024 * 1024)).toFixed(2),
|
|
476
497
|
" MB \u2022 ",
|
|
477
|
-
|
|
498
|
+
f2.contentType
|
|
478
499
|
] })
|
|
479
500
|
] }),
|
|
480
501
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
481
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href:
|
|
502
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: f2.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
|
|
482
503
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
483
504
|
"button",
|
|
484
505
|
{
|
|
485
506
|
onClick: async () => {
|
|
486
|
-
const selected = { name:
|
|
507
|
+
const selected = { name: f2.originalName, key: f2.key, url: f2.url, size: f2.size, contentType: f2.contentType };
|
|
487
508
|
onSelect(selected);
|
|
488
509
|
if (typeof onSave === "function" || !!saveUrl) {
|
|
489
510
|
try {
|
|
490
|
-
setSavingKey(
|
|
511
|
+
setSavingKey(f2.key);
|
|
491
512
|
if (typeof onSave === "function") {
|
|
492
513
|
await onSave(selected);
|
|
493
514
|
onSaved?.();
|
|
@@ -511,23 +532,461 @@ var ProjectFilesWidget = ({
|
|
|
511
532
|
}
|
|
512
533
|
}
|
|
513
534
|
},
|
|
514
|
-
disabled: savingKey ===
|
|
515
|
-
style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey ===
|
|
516
|
-
children: savingKey ===
|
|
535
|
+
disabled: savingKey === f2.key,
|
|
536
|
+
style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey === f2.key ? 0.7 : 1 },
|
|
537
|
+
children: savingKey === f2.key ? "Saving\u2026" : "Use"
|
|
517
538
|
}
|
|
518
539
|
)
|
|
519
540
|
] })
|
|
520
541
|
]
|
|
521
542
|
},
|
|
522
|
-
|
|
543
|
+
f2.key
|
|
523
544
|
)),
|
|
524
545
|
visible.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
|
|
525
546
|
] })
|
|
526
547
|
] });
|
|
527
548
|
};
|
|
549
|
+
|
|
550
|
+
// src/ConnectProjectDialog.tsx
|
|
551
|
+
var React3 = __toESM(require("react"));
|
|
552
|
+
var Dialog = __toESM(require("@radix-ui/react-dialog"));
|
|
553
|
+
|
|
554
|
+
// src/projectConnect.ts
|
|
555
|
+
var f = (globalThis.fetch || fetch).bind(globalThis);
|
|
556
|
+
async function httpJson(input, init, useFetch) {
|
|
557
|
+
const doFetch = useFetch ?? f;
|
|
558
|
+
const res = await doFetch(input, init);
|
|
559
|
+
if (!res.ok) {
|
|
560
|
+
let msg = `Request failed (${res.status})`;
|
|
561
|
+
try {
|
|
562
|
+
const j = await res.json();
|
|
563
|
+
msg = j?.error || msg;
|
|
564
|
+
} catch {
|
|
565
|
+
}
|
|
566
|
+
throw new Error(msg);
|
|
567
|
+
}
|
|
568
|
+
return res.json();
|
|
569
|
+
}
|
|
570
|
+
async function listProjects(opts) {
|
|
571
|
+
const url = `${opts.baseUrl.replace(/\/$/, "")}/api/projects`;
|
|
572
|
+
const data = await httpJson(
|
|
573
|
+
url,
|
|
574
|
+
{ method: "GET", credentials: "include" },
|
|
575
|
+
opts.fetch
|
|
576
|
+
);
|
|
577
|
+
return data?.projects ?? [];
|
|
578
|
+
}
|
|
579
|
+
async function createProject(opts) {
|
|
580
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
581
|
+
const created = await httpJson(
|
|
582
|
+
`${base}/api/projects`,
|
|
583
|
+
{
|
|
584
|
+
method: "POST",
|
|
585
|
+
credentials: "include",
|
|
586
|
+
headers: { "content-type": "application/json" },
|
|
587
|
+
body: JSON.stringify({ name: opts.name })
|
|
588
|
+
},
|
|
589
|
+
opts.fetch
|
|
590
|
+
);
|
|
591
|
+
const projectId = created?.project?.id;
|
|
592
|
+
if (!projectId) throw new Error("Failed to create project");
|
|
593
|
+
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
594
|
+
const key = await httpJson(
|
|
595
|
+
`${base}/api/projects/${projectId}/keys`,
|
|
596
|
+
{
|
|
597
|
+
method: "POST",
|
|
598
|
+
credentials: "include",
|
|
599
|
+
headers: { "content-type": "application/json" },
|
|
600
|
+
body: JSON.stringify({ name: keyLabel })
|
|
601
|
+
},
|
|
602
|
+
opts.fetch
|
|
603
|
+
);
|
|
604
|
+
const plaintext = key?.key?.plaintext;
|
|
605
|
+
if (!plaintext) throw new Error("Failed to create API key");
|
|
606
|
+
return { apiKey: plaintext, projectId };
|
|
607
|
+
}
|
|
608
|
+
async function connectProject(opts) {
|
|
609
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
610
|
+
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
611
|
+
const key = await httpJson(
|
|
612
|
+
`${base}/api/projects/${opts.projectId}/keys`,
|
|
613
|
+
{
|
|
614
|
+
method: "POST",
|
|
615
|
+
credentials: "include",
|
|
616
|
+
headers: { "content-type": "application/json" },
|
|
617
|
+
body: JSON.stringify({ name: keyLabel })
|
|
618
|
+
},
|
|
619
|
+
opts.fetch
|
|
620
|
+
);
|
|
621
|
+
const plaintext = key?.key?.plaintext;
|
|
622
|
+
if (!plaintext) throw new Error("Failed to create API key");
|
|
623
|
+
return { apiKey: plaintext };
|
|
624
|
+
}
|
|
625
|
+
function addApiKeyManually(apiKey) {
|
|
626
|
+
const k = (apiKey || "").trim();
|
|
627
|
+
if (!k) throw new Error("API key is required");
|
|
628
|
+
if (!/^upk_[A-Za-z0-9]+/.test(k)) {
|
|
629
|
+
console.warn("Provided API key does not match expected format upk_*");
|
|
630
|
+
}
|
|
631
|
+
return { apiKey: k };
|
|
632
|
+
}
|
|
633
|
+
function createClientWithKey(baseUrl, apiKey) {
|
|
634
|
+
return new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: "authorization" });
|
|
635
|
+
}
|
|
636
|
+
function connectUsingApiKey(opts) {
|
|
637
|
+
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
638
|
+
const apiKey = (opts.apiKey || "").trim();
|
|
639
|
+
if (!apiKey) throw new Error("apiKey is required");
|
|
640
|
+
const client = new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: opts.apiKeyHeader ?? "authorization" });
|
|
641
|
+
return { apiKey, client };
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/ConnectProjectDialog.tsx
|
|
645
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
646
|
+
function ConnectProjectDialog(props) {
|
|
647
|
+
const { baseUrl, open, onOpenChange, onConnected, fetchImpl, title, description, className } = props;
|
|
648
|
+
const [mode, setMode] = React3.useState("existing");
|
|
649
|
+
const [loading, setLoading] = React3.useState(false);
|
|
650
|
+
const [error, setError] = React3.useState(null);
|
|
651
|
+
const [projects, setProjects] = React3.useState([]);
|
|
652
|
+
const [selectedProjectId, setSelectedProjectId] = React3.useState("");
|
|
653
|
+
const [manualKey, setManualKey] = React3.useState("");
|
|
654
|
+
const [newProjectName, setNewProjectName] = React3.useState("");
|
|
655
|
+
const resetState = React3.useCallback(() => {
|
|
656
|
+
setMode("existing");
|
|
657
|
+
setLoading(false);
|
|
658
|
+
setError(null);
|
|
659
|
+
setProjects([]);
|
|
660
|
+
setSelectedProjectId("");
|
|
661
|
+
setManualKey("");
|
|
662
|
+
setNewProjectName("");
|
|
663
|
+
}, []);
|
|
664
|
+
React3.useEffect(() => {
|
|
665
|
+
const run = async () => {
|
|
666
|
+
if (!open || mode !== "existing") return;
|
|
667
|
+
try {
|
|
668
|
+
setLoading(true);
|
|
669
|
+
setError(null);
|
|
670
|
+
const opts = { baseUrl, fetch: fetchImpl };
|
|
671
|
+
const list = await listProjects(opts);
|
|
672
|
+
setProjects(list);
|
|
673
|
+
if (list.length && !selectedProjectId) setSelectedProjectId(list[0].id);
|
|
674
|
+
} catch (e) {
|
|
675
|
+
setError(e?.message || "Failed to fetch projects");
|
|
676
|
+
} finally {
|
|
677
|
+
setLoading(false);
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
run();
|
|
681
|
+
}, [open, mode, baseUrl]);
|
|
682
|
+
const handleConnectExisting = async () => {
|
|
683
|
+
try {
|
|
684
|
+
setLoading(true);
|
|
685
|
+
setError(null);
|
|
686
|
+
if (!selectedProjectId) throw new Error("Please select a project");
|
|
687
|
+
const { apiKey } = await connectProject({ baseUrl, projectId: selectedProjectId, fetch: fetchImpl });
|
|
688
|
+
onConnected(apiKey, { projectId: selectedProjectId, source: "existing" });
|
|
689
|
+
onOpenChange(false);
|
|
690
|
+
resetState();
|
|
691
|
+
} catch (e) {
|
|
692
|
+
setError(e?.message || "Failed to connect project");
|
|
693
|
+
} finally {
|
|
694
|
+
setLoading(false);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
const handleManual = async () => {
|
|
698
|
+
try {
|
|
699
|
+
setLoading(true);
|
|
700
|
+
setError(null);
|
|
701
|
+
const { apiKey } = addApiKeyManually(manualKey);
|
|
702
|
+
onConnected(apiKey, { source: "manual" });
|
|
703
|
+
onOpenChange(false);
|
|
704
|
+
resetState();
|
|
705
|
+
} catch (e) {
|
|
706
|
+
setError(e?.message || "Invalid API key");
|
|
707
|
+
} finally {
|
|
708
|
+
setLoading(false);
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
const handleCreate = async () => {
|
|
712
|
+
try {
|
|
713
|
+
setLoading(true);
|
|
714
|
+
setError(null);
|
|
715
|
+
if (!newProjectName.trim()) throw new Error("Project name is required");
|
|
716
|
+
const { apiKey, projectId } = await createProject({ baseUrl, name: newProjectName.trim(), fetch: fetchImpl });
|
|
717
|
+
onConnected(apiKey, { projectId, source: "new" });
|
|
718
|
+
onOpenChange(false);
|
|
719
|
+
resetState();
|
|
720
|
+
} catch (e) {
|
|
721
|
+
setError(e?.message || "Failed to create project");
|
|
722
|
+
} finally {
|
|
723
|
+
setLoading(false);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
const ModeSwitcher = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
727
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
728
|
+
"button",
|
|
729
|
+
{
|
|
730
|
+
type: "button",
|
|
731
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "existing" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
732
|
+
onClick: () => setMode("existing"),
|
|
733
|
+
children: "Connect existing"
|
|
734
|
+
}
|
|
735
|
+
),
|
|
736
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
737
|
+
"button",
|
|
738
|
+
{
|
|
739
|
+
type: "button",
|
|
740
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "manual" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
741
|
+
onClick: () => setMode("manual"),
|
|
742
|
+
children: "Add API key"
|
|
743
|
+
}
|
|
744
|
+
),
|
|
745
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
746
|
+
"button",
|
|
747
|
+
{
|
|
748
|
+
type: "button",
|
|
749
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "new" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
750
|
+
onClick: () => setMode("new"),
|
|
751
|
+
children: "Create new"
|
|
752
|
+
}
|
|
753
|
+
)
|
|
754
|
+
] });
|
|
755
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Root, { open, onOpenChange: (o) => {
|
|
756
|
+
if (!o) resetState();
|
|
757
|
+
onOpenChange(o);
|
|
758
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Dialog.Portal, { children: [
|
|
759
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Overlay, { className: "fixed inset-0 bg-black/40" }),
|
|
760
|
+
/* @__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: [
|
|
761
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Title, { className: "text-lg font-semibold", children: title ?? "Connect to Upfiles" }),
|
|
762
|
+
/* @__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." }),
|
|
763
|
+
ModeSwitcher,
|
|
764
|
+
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 }),
|
|
765
|
+
mode === "existing" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
766
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "Project" }),
|
|
767
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
768
|
+
"select",
|
|
769
|
+
{
|
|
770
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
771
|
+
value: selectedProjectId,
|
|
772
|
+
onChange: (e) => setSelectedProjectId(e.target.value),
|
|
773
|
+
disabled: loading,
|
|
774
|
+
children: [
|
|
775
|
+
projects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: loading ? "Loading..." : "No projects found" }),
|
|
776
|
+
projects.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: p.id, children: p.name }, p.id))
|
|
777
|
+
]
|
|
778
|
+
}
|
|
779
|
+
),
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
781
|
+
/* @__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" }) }),
|
|
782
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
783
|
+
"button",
|
|
784
|
+
{
|
|
785
|
+
type: "button",
|
|
786
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
787
|
+
onClick: handleConnectExisting,
|
|
788
|
+
disabled: loading || !selectedProjectId,
|
|
789
|
+
children: loading ? "Connecting\u2026" : "Connect"
|
|
790
|
+
}
|
|
791
|
+
)
|
|
792
|
+
] })
|
|
793
|
+
] }),
|
|
794
|
+
mode === "manual" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "API key" }),
|
|
796
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
797
|
+
"input",
|
|
798
|
+
{
|
|
799
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
800
|
+
placeholder: "upk_...",
|
|
801
|
+
value: manualKey,
|
|
802
|
+
onChange: (e) => setManualKey(e.target.value),
|
|
803
|
+
disabled: loading
|
|
804
|
+
}
|
|
805
|
+
),
|
|
806
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
807
|
+
/* @__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" }) }),
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
809
|
+
"button",
|
|
810
|
+
{
|
|
811
|
+
type: "button",
|
|
812
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
813
|
+
onClick: handleManual,
|
|
814
|
+
disabled: loading || !manualKey.trim(),
|
|
815
|
+
children: loading ? "Adding\u2026" : "Add key"
|
|
816
|
+
}
|
|
817
|
+
)
|
|
818
|
+
] })
|
|
819
|
+
] }),
|
|
820
|
+
mode === "new" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "Project name" }),
|
|
822
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
823
|
+
"input",
|
|
824
|
+
{
|
|
825
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
826
|
+
placeholder: "My project",
|
|
827
|
+
value: newProjectName,
|
|
828
|
+
onChange: (e) => setNewProjectName(e.target.value),
|
|
829
|
+
disabled: loading
|
|
830
|
+
}
|
|
831
|
+
),
|
|
832
|
+
/* @__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." }),
|
|
833
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
834
|
+
/* @__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" }) }),
|
|
835
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
836
|
+
"button",
|
|
837
|
+
{
|
|
838
|
+
type: "button",
|
|
839
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
840
|
+
onClick: handleCreate,
|
|
841
|
+
disabled: loading || !newProjectName.trim(),
|
|
842
|
+
children: loading ? "Creating\u2026" : "Create & connect"
|
|
843
|
+
}
|
|
844
|
+
)
|
|
845
|
+
] })
|
|
846
|
+
] })
|
|
847
|
+
] }) })
|
|
848
|
+
] }) });
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/files.ts
|
|
852
|
+
async function fetchProjectFiles(clientOptions, folderPath) {
|
|
853
|
+
const client = new UpfilesClient(clientOptions);
|
|
854
|
+
return client.getProjectFiles({ folderPath });
|
|
855
|
+
}
|
|
856
|
+
async function fetchFileThumbnails(clientOptions, fileKey) {
|
|
857
|
+
const client = new UpfilesClient(clientOptions);
|
|
858
|
+
return client.getThumbnails(fileKey);
|
|
859
|
+
}
|
|
860
|
+
async function fetchProjectImagesWithThumbs(clientOptions, opts) {
|
|
861
|
+
const files = await fetchProjectFiles(clientOptions, opts?.folderPath);
|
|
862
|
+
const limit = Math.max(1, Math.min(opts?.limitConcurrent ?? 6, 12));
|
|
863
|
+
const queue = [...files];
|
|
864
|
+
const results = [];
|
|
865
|
+
async function worker() {
|
|
866
|
+
while (queue.length) {
|
|
867
|
+
const f2 = queue.shift();
|
|
868
|
+
try {
|
|
869
|
+
const thumbs = await fetchFileThumbnails(clientOptions, f2.key).catch(() => void 0);
|
|
870
|
+
results.push({ ...f2, thumbnails: thumbs });
|
|
871
|
+
} catch {
|
|
872
|
+
results.push({ ...f2 });
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
await Promise.all(Array.from({ length: Math.min(limit, files.length || 1) }, () => worker()));
|
|
877
|
+
return results;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/ImagePickerDialog.tsx
|
|
881
|
+
var React4 = __toESM(require("react"));
|
|
882
|
+
var Dialog2 = __toESM(require("@radix-ui/react-dialog"));
|
|
883
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
884
|
+
function ImagePickerDialog(props) {
|
|
885
|
+
const { open, onOpenChange, clientOptions, folderPath, title, description, className, gridClassName, onSelect } = props;
|
|
886
|
+
const [loading, setLoading] = React4.useState(false);
|
|
887
|
+
const [error, setError] = React4.useState(null);
|
|
888
|
+
const [items, setItems] = React4.useState([]);
|
|
889
|
+
const [query, setQuery] = React4.useState("");
|
|
890
|
+
const load = React4.useCallback(async () => {
|
|
891
|
+
try {
|
|
892
|
+
setLoading(true);
|
|
893
|
+
setError(null);
|
|
894
|
+
const list = await fetchProjectImagesWithThumbs(clientOptions, { folderPath, limitConcurrent: 6 });
|
|
895
|
+
setItems(list);
|
|
896
|
+
} catch (e) {
|
|
897
|
+
setError(e?.message || "Failed to load images");
|
|
898
|
+
} finally {
|
|
899
|
+
setLoading(false);
|
|
900
|
+
}
|
|
901
|
+
}, [clientOptions, folderPath]);
|
|
902
|
+
React4.useEffect(() => {
|
|
903
|
+
if (!open) return;
|
|
904
|
+
load();
|
|
905
|
+
}, [open, load]);
|
|
906
|
+
const visible = React4.useMemo(() => {
|
|
907
|
+
const q = query.trim().toLowerCase();
|
|
908
|
+
return !q ? items : items.filter((i) => (i.originalName || "").toLowerCase().includes(q));
|
|
909
|
+
}, [items, query]);
|
|
910
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Root, { open, onOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Portal, { children: [
|
|
911
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40" }),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Content, { className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-3xl rounded-lg border bg-white p-4 shadow-lg ${className ?? ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "space-y-3", children: [
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Title, { className: "text-lg font-semibold", children: title ?? "Select an image" }),
|
|
914
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Description, { className: "text-sm text-muted-foreground", children: description ?? "Browse your project images. Thumbnails are shown when available." }),
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-2 items-center", children: [
|
|
916
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
917
|
+
"input",
|
|
918
|
+
{
|
|
919
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
920
|
+
placeholder: "Search by filename...",
|
|
921
|
+
value: query,
|
|
922
|
+
onChange: (e) => setQuery(e.target.value)
|
|
923
|
+
}
|
|
924
|
+
),
|
|
925
|
+
/* @__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" })
|
|
926
|
+
] }),
|
|
927
|
+
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 }),
|
|
928
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
929
|
+
"div",
|
|
930
|
+
{
|
|
931
|
+
className: gridClassName ?? "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 max-h-[60vh] overflow-auto",
|
|
932
|
+
children: [
|
|
933
|
+
!loading && visible.map((f2) => {
|
|
934
|
+
const thumbUrl = f2.thumbnails?.[0]?.url || f2.url;
|
|
935
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
936
|
+
"button",
|
|
937
|
+
{
|
|
938
|
+
type: "button",
|
|
939
|
+
onClick: () => {
|
|
940
|
+
onSelect({
|
|
941
|
+
url: f2.url,
|
|
942
|
+
key: f2.key,
|
|
943
|
+
originalName: f2.originalName,
|
|
944
|
+
size: f2.size,
|
|
945
|
+
contentType: f2.contentType,
|
|
946
|
+
thumbnails: f2.thumbnails
|
|
947
|
+
});
|
|
948
|
+
onOpenChange(false);
|
|
949
|
+
},
|
|
950
|
+
className: "group relative aspect-square overflow-hidden rounded border",
|
|
951
|
+
title: f2.originalName,
|
|
952
|
+
children: [
|
|
953
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
954
|
+
"img",
|
|
955
|
+
{
|
|
956
|
+
src: thumbUrl,
|
|
957
|
+
alt: f2.originalName,
|
|
958
|
+
className: "h-full w-full object-cover transition-transform group-hover:scale-105"
|
|
959
|
+
}
|
|
960
|
+
),
|
|
961
|
+
/* @__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", children: (f2.originalName || "").slice(0, 28) })
|
|
962
|
+
]
|
|
963
|
+
},
|
|
964
|
+
f2.key
|
|
965
|
+
);
|
|
966
|
+
}),
|
|
967
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "col-span-full text-sm text-muted-foreground", children: "Loading\u2026" }),
|
|
968
|
+
!loading && visible.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "col-span-full text-sm text-muted-foreground", children: "No images found" })
|
|
969
|
+
]
|
|
970
|
+
}
|
|
971
|
+
),
|
|
972
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Close" }) }) })
|
|
973
|
+
] }) })
|
|
974
|
+
] }) });
|
|
975
|
+
}
|
|
528
976
|
// Annotate the CommonJS export names for ESM import in node:
|
|
529
977
|
0 && (module.exports = {
|
|
978
|
+
ConnectProjectDialog,
|
|
979
|
+
ImagePickerDialog,
|
|
530
980
|
ProjectFilesWidget,
|
|
531
981
|
UpfilesClient,
|
|
532
|
-
Uploader
|
|
982
|
+
Uploader,
|
|
983
|
+
addApiKeyManually,
|
|
984
|
+
connectProject,
|
|
985
|
+
connectUsingApiKey,
|
|
986
|
+
createClientWithKey,
|
|
987
|
+
createProject,
|
|
988
|
+
fetchFileThumbnails,
|
|
989
|
+
fetchProjectFiles,
|
|
990
|
+
fetchProjectImagesWithThumbs,
|
|
991
|
+
listProjects
|
|
533
992
|
});
|