@thetechfossil/upfiles 0.3.0 → 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 +5 -4
package/dist/index.mjs
CHANGED
|
@@ -167,19 +167,19 @@ var Uploader = ({
|
|
|
167
167
|
const [isUploading, setIsUploading] = useState(false);
|
|
168
168
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
169
169
|
const addFiles = useCallback((incoming) => {
|
|
170
|
-
const filtered = incoming.filter((
|
|
171
|
-
if (
|
|
170
|
+
const filtered = incoming.filter((f2) => {
|
|
171
|
+
if (f2.size > maxFileSize) {
|
|
172
172
|
return false;
|
|
173
173
|
}
|
|
174
174
|
return true;
|
|
175
175
|
});
|
|
176
176
|
const limited = filtered.slice(0, Math.max(0, maxFiles - files.length));
|
|
177
|
-
const mapped = limited.map((
|
|
177
|
+
const mapped = limited.map((f2) => ({
|
|
178
178
|
id: Math.random().toString(36).slice(2),
|
|
179
|
-
file:
|
|
180
|
-
name:
|
|
181
|
-
size:
|
|
182
|
-
type:
|
|
179
|
+
file: f2,
|
|
180
|
+
name: f2.name,
|
|
181
|
+
size: f2.size,
|
|
182
|
+
type: f2.type,
|
|
183
183
|
progress: 0,
|
|
184
184
|
status: "pending"
|
|
185
185
|
}));
|
|
@@ -198,7 +198,7 @@ var Uploader = ({
|
|
|
198
198
|
}, [addFiles]);
|
|
199
199
|
const uploadOne = useCallback(async (item) => {
|
|
200
200
|
const update = (patch) => {
|
|
201
|
-
setFiles((prev) => prev.map((
|
|
201
|
+
setFiles((prev) => prev.map((f2) => f2.id === item.id ? { ...f2, ...patch } : f2));
|
|
202
202
|
};
|
|
203
203
|
try {
|
|
204
204
|
update({ status: "uploading", progress: 1 });
|
|
@@ -255,19 +255,19 @@ var Uploader = ({
|
|
|
255
255
|
}
|
|
256
256
|
}, [client, projectId, folderPath, fetchThumbnails]);
|
|
257
257
|
const uploadAll = useCallback(async () => {
|
|
258
|
-
const targets = files.filter((
|
|
258
|
+
const targets = files.filter((f2) => f2.status === "pending");
|
|
259
259
|
if (!targets.length) return;
|
|
260
260
|
setIsUploading(true);
|
|
261
261
|
try {
|
|
262
262
|
const results = [];
|
|
263
|
-
for (const
|
|
263
|
+
for (const f2 of targets) {
|
|
264
264
|
try {
|
|
265
|
-
const done = await uploadOne(
|
|
265
|
+
const done = await uploadOne(f2);
|
|
266
266
|
results.push(done);
|
|
267
267
|
} catch (e) {
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
|
-
onComplete?.(results.filter((
|
|
270
|
+
onComplete?.(results.filter((f2) => f2.status === "success"));
|
|
271
271
|
} catch (e) {
|
|
272
272
|
onError?.(e);
|
|
273
273
|
} finally {
|
|
@@ -321,7 +321,7 @@ var Uploader = ({
|
|
|
321
321
|
"button",
|
|
322
322
|
{
|
|
323
323
|
className: buttonClassName,
|
|
324
|
-
disabled: isUploading || files.every((
|
|
324
|
+
disabled: isUploading || files.every((f2) => f2.status !== "pending"),
|
|
325
325
|
onClick: uploadAll,
|
|
326
326
|
children: isUploading ? "Uploading..." : "Upload"
|
|
327
327
|
}
|
|
@@ -336,21 +336,21 @@ var Uploader = ({
|
|
|
336
336
|
}
|
|
337
337
|
)
|
|
338
338
|
] }),
|
|
339
|
-
/* @__PURE__ */ jsx("ul", { style: { display: "grid", gap: 8, listStyle: "none", padding: 0 }, children: files.map((
|
|
339
|
+
/* @__PURE__ */ jsx("ul", { style: { display: "grid", gap: 8, listStyle: "none", padding: 0 }, children: files.map((f2) => /* @__PURE__ */ jsx("li", { style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 8 }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
340
340
|
/* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
|
|
341
|
-
/* @__PURE__ */ jsx("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children:
|
|
341
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.name }),
|
|
342
342
|
/* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#666" }, children: [
|
|
343
|
-
(
|
|
343
|
+
(f2.size / (1024 * 1024)).toFixed(2),
|
|
344
344
|
" MB"
|
|
345
345
|
] }),
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
f2.error && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#b91c1c" }, children: f2.error }),
|
|
347
|
+
f2.publicUrl && f2.status === "success" && /* @__PURE__ */ jsx("a", { href: f2.publicUrl, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" })
|
|
348
348
|
] }),
|
|
349
349
|
/* @__PURE__ */ jsxs("div", { style: { minWidth: 120, textAlign: "right" }, children: [
|
|
350
|
-
/* @__PURE__ */ jsx("div", { style: { height: 6, background: "#e5e7eb", borderRadius: 999, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { width: `${
|
|
351
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#666", marginTop: 4 }, children:
|
|
350
|
+
/* @__PURE__ */ jsx("div", { style: { height: 6, background: "#e5e7eb", borderRadius: 999, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { width: `${f2.progress}%`, height: "100%", background: f2.status === "error" ? "#ef4444" : "#2563eb" } }) }),
|
|
351
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#666", marginTop: 4 }, children: f2.status })
|
|
352
352
|
] })
|
|
353
|
-
] }) },
|
|
353
|
+
] }) }, f2.id)) })
|
|
354
354
|
] })
|
|
355
355
|
] });
|
|
356
356
|
};
|
|
@@ -395,7 +395,7 @@ var ProjectFilesWidget = ({
|
|
|
395
395
|
};
|
|
396
396
|
}, [client, folderPath]);
|
|
397
397
|
const visible = files.filter(
|
|
398
|
-
(
|
|
398
|
+
(f2) => !query ? true : (f2.originalName || "").toLowerCase().includes(query.toLowerCase())
|
|
399
399
|
);
|
|
400
400
|
return /* @__PURE__ */ jsxs2("div", { className, children: [
|
|
401
401
|
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
|
|
@@ -435,31 +435,31 @@ var ProjectFilesWidget = ({
|
|
|
435
435
|
loading && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
|
|
436
436
|
error && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
|
|
437
437
|
!loading && !error && /* @__PURE__ */ jsxs2("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
|
|
438
|
-
visible.map((
|
|
438
|
+
visible.map((f2) => /* @__PURE__ */ jsxs2(
|
|
439
439
|
"li",
|
|
440
440
|
{
|
|
441
441
|
className: itemClassName,
|
|
442
442
|
style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "flex", justifyContent: "space-between", alignItems: "center" },
|
|
443
443
|
children: [
|
|
444
444
|
/* @__PURE__ */ jsxs2("div", { style: { minWidth: 0 }, children: [
|
|
445
|
-
/* @__PURE__ */ jsx2("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children:
|
|
445
|
+
/* @__PURE__ */ jsx2("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.originalName }),
|
|
446
446
|
/* @__PURE__ */ jsxs2("div", { style: { fontSize: 12, color: "#666" }, children: [
|
|
447
|
-
(
|
|
447
|
+
(f2.size / (1024 * 1024)).toFixed(2),
|
|
448
448
|
" MB \u2022 ",
|
|
449
|
-
|
|
449
|
+
f2.contentType
|
|
450
450
|
] })
|
|
451
451
|
] }),
|
|
452
452
|
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
453
|
-
/* @__PURE__ */ jsx2("a", { href:
|
|
453
|
+
/* @__PURE__ */ jsx2("a", { href: f2.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
|
|
454
454
|
/* @__PURE__ */ jsx2(
|
|
455
455
|
"button",
|
|
456
456
|
{
|
|
457
457
|
onClick: async () => {
|
|
458
|
-
const selected = { name:
|
|
458
|
+
const selected = { name: f2.originalName, key: f2.key, url: f2.url, size: f2.size, contentType: f2.contentType };
|
|
459
459
|
onSelect(selected);
|
|
460
460
|
if (typeof onSave === "function" || !!saveUrl) {
|
|
461
461
|
try {
|
|
462
|
-
setSavingKey(
|
|
462
|
+
setSavingKey(f2.key);
|
|
463
463
|
if (typeof onSave === "function") {
|
|
464
464
|
await onSave(selected);
|
|
465
465
|
onSaved?.();
|
|
@@ -483,22 +483,460 @@ var ProjectFilesWidget = ({
|
|
|
483
483
|
}
|
|
484
484
|
}
|
|
485
485
|
},
|
|
486
|
-
disabled: savingKey ===
|
|
487
|
-
style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey ===
|
|
488
|
-
children: savingKey ===
|
|
486
|
+
disabled: savingKey === f2.key,
|
|
487
|
+
style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey === f2.key ? 0.7 : 1 },
|
|
488
|
+
children: savingKey === f2.key ? "Saving\u2026" : "Use"
|
|
489
489
|
}
|
|
490
490
|
)
|
|
491
491
|
] })
|
|
492
492
|
]
|
|
493
493
|
},
|
|
494
|
-
|
|
494
|
+
f2.key
|
|
495
495
|
)),
|
|
496
496
|
visible.length === 0 && /* @__PURE__ */ jsx2("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
|
|
497
497
|
] })
|
|
498
498
|
] });
|
|
499
499
|
};
|
|
500
|
+
|
|
501
|
+
// src/ConnectProjectDialog.tsx
|
|
502
|
+
import * as React3 from "react";
|
|
503
|
+
import * as Dialog from "@radix-ui/react-dialog";
|
|
504
|
+
|
|
505
|
+
// src/projectConnect.ts
|
|
506
|
+
var f = (globalThis.fetch || fetch).bind(globalThis);
|
|
507
|
+
async function httpJson(input, init, useFetch) {
|
|
508
|
+
const doFetch = useFetch ?? f;
|
|
509
|
+
const res = await doFetch(input, init);
|
|
510
|
+
if (!res.ok) {
|
|
511
|
+
let msg = `Request failed (${res.status})`;
|
|
512
|
+
try {
|
|
513
|
+
const j = await res.json();
|
|
514
|
+
msg = j?.error || msg;
|
|
515
|
+
} catch {
|
|
516
|
+
}
|
|
517
|
+
throw new Error(msg);
|
|
518
|
+
}
|
|
519
|
+
return res.json();
|
|
520
|
+
}
|
|
521
|
+
async function listProjects(opts) {
|
|
522
|
+
const url = `${opts.baseUrl.replace(/\/$/, "")}/api/projects`;
|
|
523
|
+
const data = await httpJson(
|
|
524
|
+
url,
|
|
525
|
+
{ method: "GET", credentials: "include" },
|
|
526
|
+
opts.fetch
|
|
527
|
+
);
|
|
528
|
+
return data?.projects ?? [];
|
|
529
|
+
}
|
|
530
|
+
async function createProject(opts) {
|
|
531
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
532
|
+
const created = await httpJson(
|
|
533
|
+
`${base}/api/projects`,
|
|
534
|
+
{
|
|
535
|
+
method: "POST",
|
|
536
|
+
credentials: "include",
|
|
537
|
+
headers: { "content-type": "application/json" },
|
|
538
|
+
body: JSON.stringify({ name: opts.name })
|
|
539
|
+
},
|
|
540
|
+
opts.fetch
|
|
541
|
+
);
|
|
542
|
+
const projectId = created?.project?.id;
|
|
543
|
+
if (!projectId) throw new Error("Failed to create project");
|
|
544
|
+
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
545
|
+
const key = await httpJson(
|
|
546
|
+
`${base}/api/projects/${projectId}/keys`,
|
|
547
|
+
{
|
|
548
|
+
method: "POST",
|
|
549
|
+
credentials: "include",
|
|
550
|
+
headers: { "content-type": "application/json" },
|
|
551
|
+
body: JSON.stringify({ name: keyLabel })
|
|
552
|
+
},
|
|
553
|
+
opts.fetch
|
|
554
|
+
);
|
|
555
|
+
const plaintext = key?.key?.plaintext;
|
|
556
|
+
if (!plaintext) throw new Error("Failed to create API key");
|
|
557
|
+
return { apiKey: plaintext, projectId };
|
|
558
|
+
}
|
|
559
|
+
async function connectProject(opts) {
|
|
560
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
561
|
+
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
562
|
+
const key = await httpJson(
|
|
563
|
+
`${base}/api/projects/${opts.projectId}/keys`,
|
|
564
|
+
{
|
|
565
|
+
method: "POST",
|
|
566
|
+
credentials: "include",
|
|
567
|
+
headers: { "content-type": "application/json" },
|
|
568
|
+
body: JSON.stringify({ name: keyLabel })
|
|
569
|
+
},
|
|
570
|
+
opts.fetch
|
|
571
|
+
);
|
|
572
|
+
const plaintext = key?.key?.plaintext;
|
|
573
|
+
if (!plaintext) throw new Error("Failed to create API key");
|
|
574
|
+
return { apiKey: plaintext };
|
|
575
|
+
}
|
|
576
|
+
function addApiKeyManually(apiKey) {
|
|
577
|
+
const k = (apiKey || "").trim();
|
|
578
|
+
if (!k) throw new Error("API key is required");
|
|
579
|
+
if (!/^upk_[A-Za-z0-9]+/.test(k)) {
|
|
580
|
+
console.warn("Provided API key does not match expected format upk_*");
|
|
581
|
+
}
|
|
582
|
+
return { apiKey: k };
|
|
583
|
+
}
|
|
584
|
+
function createClientWithKey(baseUrl, apiKey) {
|
|
585
|
+
return new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: "authorization" });
|
|
586
|
+
}
|
|
587
|
+
function connectUsingApiKey(opts) {
|
|
588
|
+
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
589
|
+
const apiKey = (opts.apiKey || "").trim();
|
|
590
|
+
if (!apiKey) throw new Error("apiKey is required");
|
|
591
|
+
const client = new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: opts.apiKeyHeader ?? "authorization" });
|
|
592
|
+
return { apiKey, client };
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/ConnectProjectDialog.tsx
|
|
596
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
597
|
+
function ConnectProjectDialog(props) {
|
|
598
|
+
const { baseUrl, open, onOpenChange, onConnected, fetchImpl, title, description, className } = props;
|
|
599
|
+
const [mode, setMode] = React3.useState("existing");
|
|
600
|
+
const [loading, setLoading] = React3.useState(false);
|
|
601
|
+
const [error, setError] = React3.useState(null);
|
|
602
|
+
const [projects, setProjects] = React3.useState([]);
|
|
603
|
+
const [selectedProjectId, setSelectedProjectId] = React3.useState("");
|
|
604
|
+
const [manualKey, setManualKey] = React3.useState("");
|
|
605
|
+
const [newProjectName, setNewProjectName] = React3.useState("");
|
|
606
|
+
const resetState = React3.useCallback(() => {
|
|
607
|
+
setMode("existing");
|
|
608
|
+
setLoading(false);
|
|
609
|
+
setError(null);
|
|
610
|
+
setProjects([]);
|
|
611
|
+
setSelectedProjectId("");
|
|
612
|
+
setManualKey("");
|
|
613
|
+
setNewProjectName("");
|
|
614
|
+
}, []);
|
|
615
|
+
React3.useEffect(() => {
|
|
616
|
+
const run = async () => {
|
|
617
|
+
if (!open || mode !== "existing") return;
|
|
618
|
+
try {
|
|
619
|
+
setLoading(true);
|
|
620
|
+
setError(null);
|
|
621
|
+
const opts = { baseUrl, fetch: fetchImpl };
|
|
622
|
+
const list = await listProjects(opts);
|
|
623
|
+
setProjects(list);
|
|
624
|
+
if (list.length && !selectedProjectId) setSelectedProjectId(list[0].id);
|
|
625
|
+
} catch (e) {
|
|
626
|
+
setError(e?.message || "Failed to fetch projects");
|
|
627
|
+
} finally {
|
|
628
|
+
setLoading(false);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
run();
|
|
632
|
+
}, [open, mode, baseUrl]);
|
|
633
|
+
const handleConnectExisting = async () => {
|
|
634
|
+
try {
|
|
635
|
+
setLoading(true);
|
|
636
|
+
setError(null);
|
|
637
|
+
if (!selectedProjectId) throw new Error("Please select a project");
|
|
638
|
+
const { apiKey } = await connectProject({ baseUrl, projectId: selectedProjectId, fetch: fetchImpl });
|
|
639
|
+
onConnected(apiKey, { projectId: selectedProjectId, source: "existing" });
|
|
640
|
+
onOpenChange(false);
|
|
641
|
+
resetState();
|
|
642
|
+
} catch (e) {
|
|
643
|
+
setError(e?.message || "Failed to connect project");
|
|
644
|
+
} finally {
|
|
645
|
+
setLoading(false);
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
const handleManual = async () => {
|
|
649
|
+
try {
|
|
650
|
+
setLoading(true);
|
|
651
|
+
setError(null);
|
|
652
|
+
const { apiKey } = addApiKeyManually(manualKey);
|
|
653
|
+
onConnected(apiKey, { source: "manual" });
|
|
654
|
+
onOpenChange(false);
|
|
655
|
+
resetState();
|
|
656
|
+
} catch (e) {
|
|
657
|
+
setError(e?.message || "Invalid API key");
|
|
658
|
+
} finally {
|
|
659
|
+
setLoading(false);
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
const handleCreate = async () => {
|
|
663
|
+
try {
|
|
664
|
+
setLoading(true);
|
|
665
|
+
setError(null);
|
|
666
|
+
if (!newProjectName.trim()) throw new Error("Project name is required");
|
|
667
|
+
const { apiKey, projectId } = await createProject({ baseUrl, name: newProjectName.trim(), fetch: fetchImpl });
|
|
668
|
+
onConnected(apiKey, { projectId, source: "new" });
|
|
669
|
+
onOpenChange(false);
|
|
670
|
+
resetState();
|
|
671
|
+
} catch (e) {
|
|
672
|
+
setError(e?.message || "Failed to create project");
|
|
673
|
+
} finally {
|
|
674
|
+
setLoading(false);
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
const ModeSwitcher = /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
678
|
+
/* @__PURE__ */ jsx3(
|
|
679
|
+
"button",
|
|
680
|
+
{
|
|
681
|
+
type: "button",
|
|
682
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "existing" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
683
|
+
onClick: () => setMode("existing"),
|
|
684
|
+
children: "Connect existing"
|
|
685
|
+
}
|
|
686
|
+
),
|
|
687
|
+
/* @__PURE__ */ jsx3(
|
|
688
|
+
"button",
|
|
689
|
+
{
|
|
690
|
+
type: "button",
|
|
691
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "manual" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
692
|
+
onClick: () => setMode("manual"),
|
|
693
|
+
children: "Add API key"
|
|
694
|
+
}
|
|
695
|
+
),
|
|
696
|
+
/* @__PURE__ */ jsx3(
|
|
697
|
+
"button",
|
|
698
|
+
{
|
|
699
|
+
type: "button",
|
|
700
|
+
className: `px-3 py-2 rounded border text-sm ${mode === "new" ? "bg-primary text-primary-foreground" : "bg-background"} `,
|
|
701
|
+
onClick: () => setMode("new"),
|
|
702
|
+
children: "Create new"
|
|
703
|
+
}
|
|
704
|
+
)
|
|
705
|
+
] });
|
|
706
|
+
return /* @__PURE__ */ jsx3(Dialog.Root, { open, onOpenChange: (o) => {
|
|
707
|
+
if (!o) resetState();
|
|
708
|
+
onOpenChange(o);
|
|
709
|
+
}, children: /* @__PURE__ */ jsxs3(Dialog.Portal, { children: [
|
|
710
|
+
/* @__PURE__ */ jsx3(Dialog.Overlay, { className: "fixed inset-0 bg-black/40" }),
|
|
711
|
+
/* @__PURE__ */ jsx3(Dialog.Content, { className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-md rounded-lg border bg-white p-4 shadow-lg ${className ?? ""}`, children: /* @__PURE__ */ jsxs3("div", { className: "space-y-3", children: [
|
|
712
|
+
/* @__PURE__ */ jsx3(Dialog.Title, { className: "text-lg font-semibold", children: title ?? "Connect to Upfiles" }),
|
|
713
|
+
/* @__PURE__ */ jsx3(Dialog.Description, { className: "text-sm text-muted-foreground", children: description ?? "Choose how to connect your project to retrieve an API key." }),
|
|
714
|
+
ModeSwitcher,
|
|
715
|
+
error && /* @__PURE__ */ jsx3("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
|
|
716
|
+
mode === "existing" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
717
|
+
/* @__PURE__ */ jsx3("label", { className: "text-sm font-medium", children: "Project" }),
|
|
718
|
+
/* @__PURE__ */ jsxs3(
|
|
719
|
+
"select",
|
|
720
|
+
{
|
|
721
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
722
|
+
value: selectedProjectId,
|
|
723
|
+
onChange: (e) => setSelectedProjectId(e.target.value),
|
|
724
|
+
disabled: loading,
|
|
725
|
+
children: [
|
|
726
|
+
projects.length === 0 && /* @__PURE__ */ jsx3("option", { value: "", children: loading ? "Loading..." : "No projects found" }),
|
|
727
|
+
projects.map((p) => /* @__PURE__ */ jsx3("option", { value: p.id, children: p.name }, p.id))
|
|
728
|
+
]
|
|
729
|
+
}
|
|
730
|
+
),
|
|
731
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
732
|
+
/* @__PURE__ */ jsx3(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx3("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
733
|
+
/* @__PURE__ */ jsx3(
|
|
734
|
+
"button",
|
|
735
|
+
{
|
|
736
|
+
type: "button",
|
|
737
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
738
|
+
onClick: handleConnectExisting,
|
|
739
|
+
disabled: loading || !selectedProjectId,
|
|
740
|
+
children: loading ? "Connecting\u2026" : "Connect"
|
|
741
|
+
}
|
|
742
|
+
)
|
|
743
|
+
] })
|
|
744
|
+
] }),
|
|
745
|
+
mode === "manual" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
746
|
+
/* @__PURE__ */ jsx3("label", { className: "text-sm font-medium", children: "API key" }),
|
|
747
|
+
/* @__PURE__ */ jsx3(
|
|
748
|
+
"input",
|
|
749
|
+
{
|
|
750
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
751
|
+
placeholder: "upk_...",
|
|
752
|
+
value: manualKey,
|
|
753
|
+
onChange: (e) => setManualKey(e.target.value),
|
|
754
|
+
disabled: loading
|
|
755
|
+
}
|
|
756
|
+
),
|
|
757
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
758
|
+
/* @__PURE__ */ jsx3(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx3("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
759
|
+
/* @__PURE__ */ jsx3(
|
|
760
|
+
"button",
|
|
761
|
+
{
|
|
762
|
+
type: "button",
|
|
763
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
764
|
+
onClick: handleManual,
|
|
765
|
+
disabled: loading || !manualKey.trim(),
|
|
766
|
+
children: loading ? "Adding\u2026" : "Add key"
|
|
767
|
+
}
|
|
768
|
+
)
|
|
769
|
+
] })
|
|
770
|
+
] }),
|
|
771
|
+
mode === "new" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
772
|
+
/* @__PURE__ */ jsx3("label", { className: "text-sm font-medium", children: "Project name" }),
|
|
773
|
+
/* @__PURE__ */ jsx3(
|
|
774
|
+
"input",
|
|
775
|
+
{
|
|
776
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
777
|
+
placeholder: "My project",
|
|
778
|
+
value: newProjectName,
|
|
779
|
+
onChange: (e) => setNewProjectName(e.target.value),
|
|
780
|
+
disabled: loading
|
|
781
|
+
}
|
|
782
|
+
),
|
|
783
|
+
/* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: "A project will be created in your Upfiles account and an API key generated." }),
|
|
784
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
785
|
+
/* @__PURE__ */ jsx3(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx3("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
786
|
+
/* @__PURE__ */ jsx3(
|
|
787
|
+
"button",
|
|
788
|
+
{
|
|
789
|
+
type: "button",
|
|
790
|
+
className: "px-3 py-2 text-sm rounded bg-blue-600 text-white disabled:opacity-60",
|
|
791
|
+
onClick: handleCreate,
|
|
792
|
+
disabled: loading || !newProjectName.trim(),
|
|
793
|
+
children: loading ? "Creating\u2026" : "Create & connect"
|
|
794
|
+
}
|
|
795
|
+
)
|
|
796
|
+
] })
|
|
797
|
+
] })
|
|
798
|
+
] }) })
|
|
799
|
+
] }) });
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/files.ts
|
|
803
|
+
async function fetchProjectFiles(clientOptions, folderPath) {
|
|
804
|
+
const client = new UpfilesClient(clientOptions);
|
|
805
|
+
return client.getProjectFiles({ folderPath });
|
|
806
|
+
}
|
|
807
|
+
async function fetchFileThumbnails(clientOptions, fileKey) {
|
|
808
|
+
const client = new UpfilesClient(clientOptions);
|
|
809
|
+
return client.getThumbnails(fileKey);
|
|
810
|
+
}
|
|
811
|
+
async function fetchProjectImagesWithThumbs(clientOptions, opts) {
|
|
812
|
+
const files = await fetchProjectFiles(clientOptions, opts?.folderPath);
|
|
813
|
+
const limit = Math.max(1, Math.min(opts?.limitConcurrent ?? 6, 12));
|
|
814
|
+
const queue = [...files];
|
|
815
|
+
const results = [];
|
|
816
|
+
async function worker() {
|
|
817
|
+
while (queue.length) {
|
|
818
|
+
const f2 = queue.shift();
|
|
819
|
+
try {
|
|
820
|
+
const thumbs = await fetchFileThumbnails(clientOptions, f2.key).catch(() => void 0);
|
|
821
|
+
results.push({ ...f2, thumbnails: thumbs });
|
|
822
|
+
} catch {
|
|
823
|
+
results.push({ ...f2 });
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
await Promise.all(Array.from({ length: Math.min(limit, files.length || 1) }, () => worker()));
|
|
828
|
+
return results;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/ImagePickerDialog.tsx
|
|
832
|
+
import * as React4 from "react";
|
|
833
|
+
import * as Dialog2 from "@radix-ui/react-dialog";
|
|
834
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
835
|
+
function ImagePickerDialog(props) {
|
|
836
|
+
const { open, onOpenChange, clientOptions, folderPath, title, description, className, gridClassName, onSelect } = props;
|
|
837
|
+
const [loading, setLoading] = React4.useState(false);
|
|
838
|
+
const [error, setError] = React4.useState(null);
|
|
839
|
+
const [items, setItems] = React4.useState([]);
|
|
840
|
+
const [query, setQuery] = React4.useState("");
|
|
841
|
+
const load = React4.useCallback(async () => {
|
|
842
|
+
try {
|
|
843
|
+
setLoading(true);
|
|
844
|
+
setError(null);
|
|
845
|
+
const list = await fetchProjectImagesWithThumbs(clientOptions, { folderPath, limitConcurrent: 6 });
|
|
846
|
+
setItems(list);
|
|
847
|
+
} catch (e) {
|
|
848
|
+
setError(e?.message || "Failed to load images");
|
|
849
|
+
} finally {
|
|
850
|
+
setLoading(false);
|
|
851
|
+
}
|
|
852
|
+
}, [clientOptions, folderPath]);
|
|
853
|
+
React4.useEffect(() => {
|
|
854
|
+
if (!open) return;
|
|
855
|
+
load();
|
|
856
|
+
}, [open, load]);
|
|
857
|
+
const visible = React4.useMemo(() => {
|
|
858
|
+
const q = query.trim().toLowerCase();
|
|
859
|
+
return !q ? items : items.filter((i) => (i.originalName || "").toLowerCase().includes(q));
|
|
860
|
+
}, [items, query]);
|
|
861
|
+
return /* @__PURE__ */ jsx4(Dialog2.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
|
|
862
|
+
/* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40" }),
|
|
863
|
+
/* @__PURE__ */ jsx4(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__ */ jsxs4("div", { className: "space-y-3", children: [
|
|
864
|
+
/* @__PURE__ */ jsx4(Dialog2.Title, { className: "text-lg font-semibold", children: title ?? "Select an image" }),
|
|
865
|
+
/* @__PURE__ */ jsx4(Dialog2.Description, { className: "text-sm text-muted-foreground", children: description ?? "Browse your project images. Thumbnails are shown when available." }),
|
|
866
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex gap-2 items-center", children: [
|
|
867
|
+
/* @__PURE__ */ jsx4(
|
|
868
|
+
"input",
|
|
869
|
+
{
|
|
870
|
+
className: "w-full rounded border px-3 py-2 text-sm",
|
|
871
|
+
placeholder: "Search by filename...",
|
|
872
|
+
value: query,
|
|
873
|
+
onChange: (e) => setQuery(e.target.value)
|
|
874
|
+
}
|
|
875
|
+
),
|
|
876
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: "px-3 py-2 text-sm rounded border", onClick: load, disabled: loading, children: loading ? "Loading\u2026" : "Refresh" })
|
|
877
|
+
] }),
|
|
878
|
+
error && /* @__PURE__ */ jsx4("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
|
|
879
|
+
/* @__PURE__ */ jsxs4(
|
|
880
|
+
"div",
|
|
881
|
+
{
|
|
882
|
+
className: gridClassName ?? "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 max-h-[60vh] overflow-auto",
|
|
883
|
+
children: [
|
|
884
|
+
!loading && visible.map((f2) => {
|
|
885
|
+
const thumbUrl = f2.thumbnails?.[0]?.url || f2.url;
|
|
886
|
+
return /* @__PURE__ */ jsxs4(
|
|
887
|
+
"button",
|
|
888
|
+
{
|
|
889
|
+
type: "button",
|
|
890
|
+
onClick: () => {
|
|
891
|
+
onSelect({
|
|
892
|
+
url: f2.url,
|
|
893
|
+
key: f2.key,
|
|
894
|
+
originalName: f2.originalName,
|
|
895
|
+
size: f2.size,
|
|
896
|
+
contentType: f2.contentType,
|
|
897
|
+
thumbnails: f2.thumbnails
|
|
898
|
+
});
|
|
899
|
+
onOpenChange(false);
|
|
900
|
+
},
|
|
901
|
+
className: "group relative aspect-square overflow-hidden rounded border",
|
|
902
|
+
title: f2.originalName,
|
|
903
|
+
children: [
|
|
904
|
+
/* @__PURE__ */ jsx4(
|
|
905
|
+
"img",
|
|
906
|
+
{
|
|
907
|
+
src: thumbUrl,
|
|
908
|
+
alt: f2.originalName,
|
|
909
|
+
className: "h-full w-full object-cover transition-transform group-hover:scale-105"
|
|
910
|
+
}
|
|
911
|
+
),
|
|
912
|
+
/* @__PURE__ */ jsx4("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) })
|
|
913
|
+
]
|
|
914
|
+
},
|
|
915
|
+
f2.key
|
|
916
|
+
);
|
|
917
|
+
}),
|
|
918
|
+
loading && /* @__PURE__ */ jsx4("div", { className: "col-span-full text-sm text-muted-foreground", children: "Loading\u2026" }),
|
|
919
|
+
!loading && visible.length === 0 && /* @__PURE__ */ jsx4("div", { className: "col-span-full text-sm text-muted-foreground", children: "No images found" })
|
|
920
|
+
]
|
|
921
|
+
}
|
|
922
|
+
),
|
|
923
|
+
/* @__PURE__ */ jsx4("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx4(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx4("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Close" }) }) })
|
|
924
|
+
] }) })
|
|
925
|
+
] }) });
|
|
926
|
+
}
|
|
500
927
|
export {
|
|
928
|
+
ConnectProjectDialog,
|
|
929
|
+
ImagePickerDialog,
|
|
501
930
|
ProjectFilesWidget,
|
|
502
931
|
UpfilesClient,
|
|
503
|
-
Uploader
|
|
932
|
+
Uploader,
|
|
933
|
+
addApiKeyManually,
|
|
934
|
+
connectProject,
|
|
935
|
+
connectUsingApiKey,
|
|
936
|
+
createClientWithKey,
|
|
937
|
+
createProject,
|
|
938
|
+
fetchFileThumbnails,
|
|
939
|
+
fetchProjectFiles,
|
|
940
|
+
fetchProjectImagesWithThumbs,
|
|
941
|
+
listProjects
|
|
504
942
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thetechfossil/upfiles",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1-beta",
|
|
4
4
|
"description": "Lightweight client and React components for Upfiles Plugin API (presigned S3)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "UpFiles",
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
16
|
"import": "./dist/index.mjs",
|
|
17
|
-
"require": "./dist/index.cjs"
|
|
18
|
-
"types": "./dist/index.d.ts"
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
19
18
|
}
|
|
20
19
|
},
|
|
21
20
|
"files": [
|
|
@@ -32,9 +31,11 @@
|
|
|
32
31
|
"prepublishOnly": "bun run build"
|
|
33
32
|
},
|
|
34
33
|
"peerDependencies": {
|
|
35
|
-
"react": ">=18"
|
|
34
|
+
"react": ">=18",
|
|
35
|
+
"@radix-ui/react-dialog": ">=1.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
38
39
|
"@types/react": "^19",
|
|
39
40
|
"tsup": "^8.3.0",
|
|
40
41
|
"typescript": "^5.6.2"
|