@thetechfossil/upfiles 0.5.0 → 1.0.0

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