@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/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((f) => {
199
- if (f.size > maxFileSize) {
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((f) => ({
226
+ const mapped = limited.map((f2) => ({
206
227
  id: Math.random().toString(36).slice(2),
207
- file: f,
208
- name: f.name,
209
- size: f.size,
210
- type: f.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((f) => f.id === item.id ? { ...f, ...patch } : f));
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((f) => f.status === "pending");
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 f of targets) {
312
+ for (const f2 of targets) {
292
313
  try {
293
- const done = await uploadOne(f);
314
+ const done = await uploadOne(f2);
294
315
  results.push(done);
295
316
  } catch (e) {
296
317
  }
297
318
  }
298
- onComplete?.(results.filter((f) => f.status === "success"));
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((f) => f.status !== "pending"),
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((f) => /* @__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: [
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: f.name }),
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
- (f.size / (1024 * 1024)).toFixed(2),
392
+ (f2.size / (1024 * 1024)).toFixed(2),
372
393
  " MB"
373
394
  ] }),
374
- f.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "#b91c1c" }, children: f.error }),
375
- f.publicUrl && f.status === "success" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: f.publicUrl, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" })
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: `${f.progress}%`, height: "100%", background: f.status === "error" ? "#ef4444" : "#2563eb" } }) }),
379
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "#666", marginTop: 4 }, children: f.status })
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
- ] }) }, f.id)) })
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
- (f) => !query ? true : (f.originalName || "").toLowerCase().includes(query.toLowerCase())
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((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
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: f.originalName }),
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
- (f.size / (1024 * 1024)).toFixed(2),
496
+ (f2.size / (1024 * 1024)).toFixed(2),
476
497
  " MB \u2022 ",
477
- f.contentType
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: f.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
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: f.originalName, key: f.key, url: f.url, size: f.size, contentType: f.contentType };
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(f.key);
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 === f.key,
515
- style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey === f.key ? 0.7 : 1 },
516
- children: savingKey === f.key ? "Saving\u2026" : "Use"
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
- f.key
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
  });