@sparkstudio/storage-ui 1.0.27 → 1.0.29

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.cjs CHANGED
@@ -23,9 +23,13 @@ __export(index_exports, {
23
23
  AWSPresignedUrlDTO: () => AWSPresignedUrlDTO,
24
24
  Container: () => Container,
25
25
  ContainerDTO: () => ContainerDTO,
26
+ ContainerIdGridPanel: () => ContainerIdGridPanel,
26
27
  ContainerType: () => ContainerType,
27
28
  ContainerUploadPanel: () => ContainerUploadPanel,
28
29
  DesktopFileIcon: () => DesktopFileIcon,
30
+ FileGridUploadPanel: () => FileGridUploadPanel,
31
+ FileIconCard: () => FileIconCard,
32
+ FileIconGrid: () => FileIconGrid,
29
33
  Home: () => Home,
30
34
  HomeView: () => HomeView,
31
35
  S3: () => S3,
@@ -276,54 +280,11 @@ var ContainerType = /* @__PURE__ */ ((ContainerType2) => {
276
280
  return ContainerType2;
277
281
  })(ContainerType || {});
278
282
 
279
- // src/components/ContainerUploadPanel.tsx
280
- var import_react7 = require("react");
281
-
282
- // src/components/UploadContainer.tsx
283
+ // src/components/ContainerIdGridPanel.tsx
283
284
  var import_react5 = require("react");
284
285
 
285
- // src/components/UploadDropzone.tsx
286
- var import_react = require("react");
287
- var import_jsx_runtime = require("react/jsx-runtime");
288
- var UploadDropzone = ({
289
- isDragging,
290
- onDragOver,
291
- onDragLeave,
292
- onDrop,
293
- className = "",
294
- style,
295
- children
296
- }) => {
297
- const baseClass = "rounded-3 d-flex flex-column align-items-center justify-content-center";
298
- const stateClass = isDragging ? "bg-body-secondary border-dashed border-2 border-secondary" : "bg-body-trasparent border-solid border-transparent border-2";
299
- const combinedClassName = `${baseClass} ${stateClass} ${className}`.trim();
300
- const handleDragOver = (e) => {
301
- e.preventDefault();
302
- onDragOver?.(e);
303
- };
304
- const handleDragLeave = (e) => {
305
- e.preventDefault();
306
- onDragLeave?.(e);
307
- };
308
- const handleDrop = (e) => {
309
- e.preventDefault();
310
- onDrop?.(e);
311
- };
312
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
313
- "div",
314
- {
315
- className: combinedClassName,
316
- style: { minHeight: "140px", ...style },
317
- onDragOver: handleDragOver,
318
- onDragLeave: handleDragLeave,
319
- onDrop: handleDrop,
320
- children
321
- }
322
- );
323
- };
324
-
325
286
  // src/hooks/UseUploadManager.ts
326
- var import_react2 = require("react");
287
+ var import_react = require("react");
327
288
 
328
289
  // src/engines/UploadFileToS3.ts
329
290
  async function UploadFileToS3(file, presignedUrl, onProgress, maxRetries = 3) {
@@ -380,14 +341,14 @@ function UseUploadManager({
380
341
  onUploadComplete,
381
342
  onUploadError
382
343
  }) {
383
- const [uploads, setUploads] = (0, import_react2.useState)([]);
344
+ const [uploads, setUploads] = (0, import_react.useState)([]);
384
345
  const createUploadStates = (files) => Array.from(files).map((file) => ({
385
346
  id: `${file.name}-${file.size}-${file.lastModified}-${Math.random()}`,
386
347
  file,
387
348
  progress: 0,
388
349
  status: "pending"
389
350
  }));
390
- const uploadFile = (0, import_react2.useCallback)(
351
+ const uploadFile = (0, import_react.useCallback)(
391
352
  async (upload) => {
392
353
  setUploads(
393
354
  (prev) => prev.map(
@@ -453,7 +414,7 @@ function UseUploadManager({
453
414
  }
454
415
  throw lastError instanceof Error ? lastError : new Error("Failed to fetch presigned URL");
455
416
  }
456
- const startUploadsIfNeeded = (0, import_react2.useCallback)(
417
+ const startUploadsIfNeeded = (0, import_react.useCallback)(
457
418
  (files) => {
458
419
  if (!autoUpload || !getPresignedUrl) return;
459
420
  const newUploads = createUploadStates(files);
@@ -464,7 +425,7 @@ function UseUploadManager({
464
425
  },
465
426
  [autoUpload, getPresignedUrl, uploadFile]
466
427
  );
467
- const resetUploads = (0, import_react2.useCallback)(() => setUploads([]), []);
428
+ const resetUploads = (0, import_react.useCallback)(() => setUploads([]), []);
468
429
  return {
469
430
  uploads,
470
431
  startUploadsIfNeeded,
@@ -473,19 +434,19 @@ function UseUploadManager({
473
434
  }
474
435
 
475
436
  // src/components/UploadProgressList.tsx
476
- var import_react3 = require("react");
477
- var import_jsx_runtime2 = require("react/jsx-runtime");
437
+ var import_react2 = require("react");
438
+ var import_jsx_runtime = require("react/jsx-runtime");
478
439
  var UploadProgressList = ({
479
440
  uploads
480
441
  }) => {
481
442
  const visibleUploads = uploads.filter((u) => u.status !== "success");
482
443
  if (visibleUploads.length === 0) return null;
483
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
444
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
484
445
  "div",
485
446
  {
486
447
  className: "w-100 d-flex flex-wrap gap-4 align-content-start mt-3",
487
448
  style: { minHeight: "80px" },
488
- children: visibleUploads.map((u) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
449
+ children: visibleUploads.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
489
450
  "div",
490
451
  {
491
452
  className: "d-flex flex-column align-items-center",
@@ -495,7 +456,7 @@ var UploadProgressList = ({
495
456
  },
496
457
  title: u.file.name,
497
458
  children: [
498
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
499
460
  "div",
500
461
  {
501
462
  className: "border rounded-3 d-flex align-items-center justify-content-center mb-1 shadow-sm position-relative",
@@ -504,20 +465,20 @@ var UploadProgressList = ({
504
465
  height: 64
505
466
  },
506
467
  children: [
507
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("i", { className: "bi bi-file-earmark fs-2" }),
508
- u.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
468
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { className: "bi bi-file-earmark fs-2" }),
469
+ u.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
509
470
  "div",
510
471
  {
511
472
  className: "position-absolute top-50 start-50 translate-middle",
512
473
  style: { pointerEvents: "none" },
513
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "spinner-border spinner-border-sm text-primary" })
474
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "spinner-border spinner-border-sm text-primary" })
514
475
  }
515
476
  ),
516
- u.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger", children: "!" })
477
+ u.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger", children: "!" })
517
478
  ]
518
479
  }
519
480
  ),
520
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
521
482
  "div",
522
483
  {
523
484
  className: "small text-center text-truncate",
@@ -525,7 +486,7 @@ var UploadProgressList = ({
525
486
  children: u.file.name
526
487
  }
527
488
  ),
528
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-100 mt-1", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "progress", style: { height: "4px" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
489
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-100 mt-1", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "progress", style: { height: "4px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
529
490
  "div",
530
491
  {
531
492
  className: "progress-bar " + (u.status === "error" ? "bg-danger" : ""),
@@ -536,7 +497,7 @@ var UploadProgressList = ({
536
497
  "aria-valuemax": 100
537
498
  }
538
499
  ) }) }),
539
- u.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
500
+ u.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
540
501
  "div",
541
502
  {
542
503
  className: "text-danger small mt-1 text-center",
@@ -552,11 +513,556 @@ var UploadProgressList = ({
552
513
  );
553
514
  };
554
515
 
516
+ // src/components/FileIconGrid.tsx
517
+ var import_react4 = require("react");
518
+
519
+ // src/components/FileIconCard.tsx
520
+ var import_react3 = require("react");
521
+ var import_jsx_runtime2 = require("react/jsx-runtime");
522
+ function sanitizeIconHtml(input) {
523
+ return input.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "").replace(/\son\w+="[^"]*"/gi, "").replace(/\son\w+='[^']*'/gi, "").replace(/javascript:/gi, "");
524
+ }
525
+ var FileIconCard = (props) => {
526
+ const {
527
+ file,
528
+ deleteDisabled,
529
+ selected,
530
+ onSelect,
531
+ onDeleteFile,
532
+ onClickFile,
533
+ icon,
534
+ iconHtml,
535
+ title,
536
+ className
537
+ } = props;
538
+ const [deleting, setDeleting] = (0, import_react3.useState)(false);
539
+ const [error, setError] = (0, import_react3.useState)(null);
540
+ const safeIconHtml = (0, import_react3.useMemo)(() => {
541
+ if (!iconHtml) return null;
542
+ return sanitizeIconHtml(iconHtml);
543
+ }, [iconHtml]);
544
+ const canDelete = !!onDeleteFile && !deleteDisabled && !deleting;
545
+ const handleClick = () => {
546
+ onSelect?.(file);
547
+ onClickFile?.(file);
548
+ };
549
+ const handleDelete = async (ev) => {
550
+ ev.stopPropagation();
551
+ if (!canDelete) return;
552
+ setError(null);
553
+ setDeleting(true);
554
+ try {
555
+ await onDeleteFile?.(file);
556
+ } catch (e) {
557
+ setError(e instanceof Error ? e.message : "Delete failed");
558
+ } finally {
559
+ setDeleting(false);
560
+ }
561
+ };
562
+ const borderColor = selected ? "rgba(13,110,253,0.8)" : "rgba(0,0,0,0.12)";
563
+ const boxShadow = selected ? "0 0 0 3px rgba(13,110,253,0.2)" : "none";
564
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
565
+ "div",
566
+ {
567
+ className: [
568
+ "file-icon-card",
569
+ deleteDisabled ? "is-delete-disabled" : "",
570
+ deleting ? "is-deleting" : "",
571
+ className ?? ""
572
+ ].join(" "),
573
+ role: "button",
574
+ tabIndex: 0,
575
+ onClick: handleClick,
576
+ onKeyDown: (e) => {
577
+ if (e.key === "Enter" || e.key === " ") {
578
+ e.preventDefault();
579
+ handleClick();
580
+ }
581
+ },
582
+ title: title ?? file.Name ?? "File",
583
+ style: {
584
+ display: "flex",
585
+ alignItems: "center",
586
+ gap: 12,
587
+ padding: 12,
588
+ border: `1px solid ${borderColor}`,
589
+ boxShadow,
590
+ borderRadius: 12,
591
+ userSelect: "none",
592
+ cursor: "pointer",
593
+ background: "white"
594
+ },
595
+ children: [
596
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
597
+ "div",
598
+ {
599
+ style: {
600
+ width: 44,
601
+ height: 44,
602
+ borderRadius: 10,
603
+ display: "grid",
604
+ placeItems: "center",
605
+ background: "rgba(0,0,0,0.04)",
606
+ flex: "0 0 auto"
607
+ },
608
+ children: icon ? icon : safeIconHtml ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": true, dangerouslySetInnerHTML: { __html: safeIconHtml } }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultFileIcon, {})
609
+ }
610
+ ),
611
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0, flex: "1 1 auto" }, children: [
612
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
613
+ "div",
614
+ {
615
+ style: {
616
+ fontWeight: 600,
617
+ overflow: "hidden",
618
+ textOverflow: "ellipsis",
619
+ whiteSpace: "nowrap"
620
+ },
621
+ children: title ?? file.Name ?? "Untitled"
622
+ }
623
+ ),
624
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, opacity: 0.7 }, children: typeof file.FileSize === "number" ? formatBytes(file.FileSize) : "" }),
625
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "crimson", marginTop: 4 }, children: error }) : null
626
+ ] }),
627
+ !deleteDisabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
628
+ "button",
629
+ {
630
+ type: "button",
631
+ onClick: handleDelete,
632
+ disabled: !canDelete,
633
+ "aria-label": "Delete file",
634
+ style: {
635
+ border: "1px solid rgba(0,0,0,0.18)",
636
+ background: canDelete ? "white" : "rgba(0,0,0,0.04)",
637
+ borderRadius: 10,
638
+ padding: "8px 10px",
639
+ cursor: canDelete ? "pointer" : "not-allowed",
640
+ display: "flex",
641
+ alignItems: "center",
642
+ gap: 8
643
+ },
644
+ children: [
645
+ deleting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SmallSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrashIcon, {}),
646
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 13 }, children: deleting ? "Deleting\u2026" : "Delete" })
647
+ ]
648
+ }
649
+ ) : null
650
+ ]
651
+ }
652
+ );
653
+ };
654
+ function formatBytes(bytes) {
655
+ if (!Number.isFinite(bytes) || bytes < 0) return "";
656
+ const units = ["B", "KB", "MB", "GB", "TB"];
657
+ let v = bytes;
658
+ let i = 0;
659
+ while (v >= 1024 && i < units.length - 1) {
660
+ v /= 1024;
661
+ i++;
662
+ }
663
+ return `${v.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
664
+ }
665
+ function SmallSpinner() {
666
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
667
+ "span",
668
+ {
669
+ "aria-hidden": true,
670
+ style: {
671
+ width: 14,
672
+ height: 14,
673
+ borderRadius: "50%",
674
+ border: "2px solid rgba(0,0,0,0.2)",
675
+ borderTopColor: "rgba(0,0,0,0.7)",
676
+ display: "inline-block",
677
+ animation: "fileIconSpin 0.8s linear infinite"
678
+ }
679
+ }
680
+ );
681
+ }
682
+ function DefaultFileIcon() {
683
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, children: [
684
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
685
+ "path",
686
+ {
687
+ d: "M7 3h7l3 3v15a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z",
688
+ stroke: "currentColor",
689
+ strokeWidth: "2"
690
+ }
691
+ ),
692
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M14 3v4a1 1 0 0 0 1 1h4", stroke: "currentColor", strokeWidth: "2" })
693
+ ] });
694
+ }
695
+ function TrashIcon() {
696
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, children: [
697
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 6h18", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
698
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
699
+ "path",
700
+ {
701
+ d: "M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2",
702
+ stroke: "currentColor",
703
+ strokeWidth: "2"
704
+ }
705
+ ),
706
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
707
+ "path",
708
+ {
709
+ d: "M6 6l1 16a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-16",
710
+ stroke: "currentColor",
711
+ strokeWidth: "2"
712
+ }
713
+ ),
714
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
715
+ "path",
716
+ {
717
+ d: "M10 11v7M14 11v7",
718
+ stroke: "currentColor",
719
+ strokeWidth: "2",
720
+ strokeLinecap: "round"
721
+ }
722
+ )
723
+ ] });
724
+ }
725
+
726
+ // src/components/FileIconGrid.tsx
727
+ var import_jsx_runtime3 = require("react/jsx-runtime");
728
+ function FileIconGrid(props) {
729
+ const {
730
+ files,
731
+ deleteDisabled,
732
+ onDeleted,
733
+ selectedId,
734
+ onSelect,
735
+ icon,
736
+ iconHtml,
737
+ className
738
+ } = props;
739
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
740
+ "div",
741
+ {
742
+ className,
743
+ style: {
744
+ display: "grid",
745
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
746
+ gap: 12
747
+ },
748
+ children: files.map((f) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
749
+ FileIconCard,
750
+ {
751
+ file: f,
752
+ deleteDisabled,
753
+ onDeleteFile: onDeleted,
754
+ onSelect,
755
+ selected: selectedId === f.Id,
756
+ icon,
757
+ iconHtml
758
+ },
759
+ f.Id
760
+ ))
761
+ }
762
+ );
763
+ }
764
+
765
+ // src/components/ContainerIdGridPanel.tsx
766
+ var import_jsx_runtime4 = require("react/jsx-runtime");
767
+ function ContainerIdGridPanel(props) {
768
+ const {
769
+ containerApiBaseUrl,
770
+ storageApiBaseUrl,
771
+ containerIds,
772
+ onContainerIdsChange,
773
+ accept = "*/*",
774
+ multiple = true,
775
+ selectedId,
776
+ onSelect,
777
+ icon,
778
+ iconHtml,
779
+ deleteDisabled,
780
+ className
781
+ } = props;
782
+ const sdkDb = (0, import_react5.useMemo)(
783
+ () => new SparkStudioStorageSDK(containerApiBaseUrl),
784
+ [containerApiBaseUrl]
785
+ );
786
+ const sdkS3 = (0, import_react5.useMemo)(
787
+ () => new SparkStudioStorageSDK(storageApiBaseUrl),
788
+ [storageApiBaseUrl]
789
+ );
790
+ const [loading, setLoading] = (0, import_react5.useState)(false);
791
+ const [files, setFiles] = (0, import_react5.useState)([]);
792
+ const [dragOver, setDragOver] = (0, import_react5.useState)(false);
793
+ const [errMsg, setErrMsg] = (0, import_react5.useState)(null);
794
+ const idsRef = (0, import_react5.useRef)(containerIds);
795
+ (0, import_react5.useEffect)(() => {
796
+ idsRef.current = containerIds;
797
+ }, [containerIds]);
798
+ const createdByFileRef = (0, import_react5.useRef)(/* @__PURE__ */ new WeakMap());
799
+ (0, import_react5.useEffect)(() => {
800
+ let cancelled = false;
801
+ async function loadByIds(ids) {
802
+ setErrMsg(null);
803
+ if (!ids || ids.length === 0) {
804
+ setFiles([]);
805
+ return;
806
+ }
807
+ setLoading(true);
808
+ try {
809
+ const results = await Promise.all(
810
+ ids.map(async (id) => {
811
+ try {
812
+ const dto = await sdkDb.container.Read?.(id);
813
+ return dto ?? null;
814
+ } catch {
815
+ return null;
816
+ }
817
+ })
818
+ );
819
+ if (cancelled) return;
820
+ const map = /* @__PURE__ */ new Map();
821
+ for (const r of results) if (r?.Id) map.set(r.Id, r);
822
+ setFiles(
823
+ ids.map((id) => map.get(id)).filter(Boolean)
824
+ );
825
+ } catch (e) {
826
+ if (!cancelled)
827
+ setErrMsg(e instanceof Error ? e.message : "Failed to load files");
828
+ } finally {
829
+ if (!cancelled) setLoading(false);
830
+ }
831
+ }
832
+ void loadByIds(containerIds);
833
+ return () => {
834
+ cancelled = true;
835
+ };
836
+ }, [containerIds, sdkDb]);
837
+ const getPresignedUrl = async (file) => {
838
+ const contentType = file.type || "application/octet-stream";
839
+ const containerDTO = await sdkDb.container.CreateFileContainer(
840
+ file.name,
841
+ file.size,
842
+ encodeURIComponent(contentType)
843
+ );
844
+ createdByFileRef.current.set(file, containerDTO);
845
+ let lastError;
846
+ for (let i = 1; i <= 3; i++) {
847
+ try {
848
+ return await sdkS3.s3.GetPreSignedUrl(containerDTO);
849
+ } catch (e) {
850
+ lastError = e;
851
+ if (i < 3) await new Promise((r) => setTimeout(r, 500 * i));
852
+ }
853
+ }
854
+ throw lastError instanceof Error ? lastError : new Error("Failed to fetch presigned URL");
855
+ };
856
+ const { uploads, startUploadsIfNeeded } = UseUploadManager({
857
+ autoUpload: true,
858
+ getPresignedUrl,
859
+ onUploadComplete: async (file) => {
860
+ setErrMsg(null);
861
+ const created = createdByFileRef.current.get(file);
862
+ if (created?.Id) {
863
+ const prev = idsRef.current ?? [];
864
+ const next = prev.includes(created.Id) ? prev : [...prev, created.Id];
865
+ onContainerIdsChange(next);
866
+ }
867
+ if (created?.Id) {
868
+ try {
869
+ const refreshed = await sdkDb.container.Read?.(created.Id);
870
+ if (refreshed) {
871
+ setFiles((prevFiles) => {
872
+ const nextFiles = prevFiles.slice();
873
+ const idx = nextFiles.findIndex((x) => x.Id === created.Id);
874
+ if (idx >= 0) nextFiles[idx] = refreshed;
875
+ else nextFiles.push(refreshed);
876
+ return nextFiles;
877
+ });
878
+ }
879
+ } catch {
880
+ }
881
+ }
882
+ },
883
+ onUploadError: (_file, error) => {
884
+ setErrMsg(error?.message ?? "Upload failed");
885
+ }
886
+ });
887
+ const handleDelete = async (file) => {
888
+ if (deleteDisabled) return;
889
+ await sdkDb.container.DeleteContainer(file.Id);
890
+ await sdkS3.s3.DeleteS3(file);
891
+ const prev = idsRef.current ?? [];
892
+ onContainerIdsChange(prev.filter((id) => id !== file.Id));
893
+ setFiles((prevFiles) => prevFiles.filter((x) => x.Id !== file.Id));
894
+ };
895
+ const openPicker = () => {
896
+ const input = document.createElement("input");
897
+ input.type = "file";
898
+ input.multiple = multiple;
899
+ input.accept = accept;
900
+ input.onchange = () => {
901
+ if (input.files) startUploadsIfNeeded(input.files);
902
+ };
903
+ input.click();
904
+ };
905
+ const onDrop = (ev) => {
906
+ ev.preventDefault();
907
+ ev.stopPropagation();
908
+ setDragOver(false);
909
+ const list = ev.dataTransfer.files;
910
+ if (!list || list.length === 0) return;
911
+ startUploadsIfNeeded(list);
912
+ };
913
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className, style: { display: "grid", gap: 12 }, children: [
914
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
915
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UploadProgressList, { uploads }),
916
+ errMsg ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 12, color: "crimson", marginTop: 6 }, children: errMsg }) : null
917
+ ] }),
918
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
919
+ "div",
920
+ {
921
+ onDragEnter: (e) => {
922
+ e.preventDefault();
923
+ e.stopPropagation();
924
+ setDragOver(true);
925
+ },
926
+ onDragOver: (e) => {
927
+ e.preventDefault();
928
+ e.stopPropagation();
929
+ setDragOver(true);
930
+ },
931
+ onDragLeave: (e) => {
932
+ e.preventDefault();
933
+ e.stopPropagation();
934
+ setDragOver(false);
935
+ },
936
+ onDrop,
937
+ style: {
938
+ position: "relative",
939
+ borderRadius: 14,
940
+ border: `2px dashed ${dragOver ? "rgba(13,110,253,0.9)" : "rgba(0,0,0,0.15)"}`,
941
+ background: dragOver ? "rgba(13,110,253,0.06)" : "transparent",
942
+ padding: 12
943
+ },
944
+ children: [
945
+ dragOver ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
946
+ "div",
947
+ {
948
+ style: {
949
+ position: "absolute",
950
+ inset: 0,
951
+ borderRadius: 14,
952
+ display: "grid",
953
+ placeItems: "center",
954
+ pointerEvents: "none",
955
+ background: "rgba(13,110,253,0.10)",
956
+ fontWeight: 800
957
+ },
958
+ children: "Drop files to upload"
959
+ }
960
+ ) : null,
961
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
962
+ "div",
963
+ {
964
+ style: {
965
+ display: "flex",
966
+ justifyContent: "space-between",
967
+ marginBottom: 10
968
+ },
969
+ children: [
970
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontWeight: 700 }, children: "Files" }),
971
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
972
+ "button",
973
+ {
974
+ type: "button",
975
+ onClick: openPicker,
976
+ style: {
977
+ border: "1px solid rgba(0,0,0,0.18)",
978
+ background: "white",
979
+ borderRadius: 10,
980
+ padding: "8px 10px",
981
+ cursor: "pointer",
982
+ fontWeight: 600
983
+ },
984
+ children: "Browse\u2026"
985
+ }
986
+ )
987
+ ]
988
+ }
989
+ ),
990
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
991
+ "div",
992
+ {
993
+ className: "d-flex justify-content-center align-items-center",
994
+ style: { minHeight: 120 },
995
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "spinner-border text-secondary", role: "status" })
996
+ }
997
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
998
+ FileIconGrid,
999
+ {
1000
+ files,
1001
+ deleteDisabled,
1002
+ onDeleted: handleDelete,
1003
+ selectedId,
1004
+ onSelect,
1005
+ icon,
1006
+ iconHtml
1007
+ }
1008
+ )
1009
+ ]
1010
+ }
1011
+ )
1012
+ ] });
1013
+ }
1014
+
1015
+ // src/components/ContainerUploadPanel.tsx
1016
+ var import_react10 = require("react");
1017
+
1018
+ // src/components/UploadContainer.tsx
1019
+ var import_react8 = require("react");
1020
+
1021
+ // src/components/UploadDropzone.tsx
1022
+ var import_react6 = require("react");
1023
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1024
+ var UploadDropzone = ({
1025
+ isDragging,
1026
+ onDragOver,
1027
+ onDragLeave,
1028
+ onDrop,
1029
+ className = "",
1030
+ style,
1031
+ children
1032
+ }) => {
1033
+ const baseClass = "rounded-3 d-flex flex-column align-items-center justify-content-center";
1034
+ const stateClass = isDragging ? "bg-body-secondary border-dashed border-2 border-secondary" : "bg-body-trasparent border-solid border-transparent border-2";
1035
+ const combinedClassName = `${baseClass} ${stateClass} ${className}`.trim();
1036
+ const handleDragOver = (e) => {
1037
+ e.preventDefault();
1038
+ onDragOver?.(e);
1039
+ };
1040
+ const handleDragLeave = (e) => {
1041
+ e.preventDefault();
1042
+ onDragLeave?.(e);
1043
+ };
1044
+ const handleDrop = (e) => {
1045
+ e.preventDefault();
1046
+ onDrop?.(e);
1047
+ };
1048
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1049
+ "div",
1050
+ {
1051
+ className: combinedClassName,
1052
+ style: { minHeight: "140px", ...style },
1053
+ onDragOver: handleDragOver,
1054
+ onDragLeave: handleDragLeave,
1055
+ onDrop: handleDrop,
1056
+ children
1057
+ }
1058
+ );
1059
+ };
1060
+
555
1061
  // src/components/DesktopFileIcon.tsx
556
1062
  var import_free_solid_svg_icons = require("@fortawesome/free-solid-svg-icons");
557
1063
  var import_react_fontawesome = require("@fortawesome/react-fontawesome");
558
- var import_react4 = require("react");
559
- var import_jsx_runtime3 = require("react/jsx-runtime");
1064
+ var import_react7 = require("react");
1065
+ var import_jsx_runtime6 = require("react/jsx-runtime");
560
1066
  function getFileExtension(name) {
561
1067
  if (!name) return null;
562
1068
  const lastDot = name.lastIndexOf(".");
@@ -635,13 +1141,13 @@ var DesktopFileIcon = ({
635
1141
  onOpen,
636
1142
  onDelete
637
1143
  }) => {
638
- const [contextMenuPos, setContextMenuPos] = (0, import_react4.useState)(
1144
+ const [contextMenuPos, setContextMenuPos] = (0, import_react7.useState)(
639
1145
  null
640
1146
  );
641
- const [isHovered, setIsHovered] = (0, import_react4.useState)(false);
642
- const [isDeleting, setIsDeleting] = (0, import_react4.useState)(false);
643
- const iconRef = (0, import_react4.useRef)(null);
644
- const menuRef = (0, import_react4.useRef)(null);
1147
+ const [isHovered, setIsHovered] = (0, import_react7.useState)(false);
1148
+ const [isDeleting, setIsDeleting] = (0, import_react7.useState)(false);
1149
+ const iconRef = (0, import_react7.useRef)(null);
1150
+ const menuRef = (0, import_react7.useRef)(null);
645
1151
  const handleDoubleClick = () => {
646
1152
  if (isDeleting) return;
647
1153
  if (onOpen) {
@@ -699,7 +1205,7 @@ var DesktopFileIcon = ({
699
1205
  const formattedSize = typeof sizeBytes === "number" ? `${(sizeBytes / 1024).toFixed(1)} KB` : void 0;
700
1206
  const ext = getFileExtension(name);
701
1207
  const iconToRender = getIconForExtension(ext);
702
- (0, import_react4.useEffect)(() => {
1208
+ (0, import_react7.useEffect)(() => {
703
1209
  if (!contextMenuPos) return;
704
1210
  const handleGlobalClick = (e) => {
705
1211
  const target = e.target;
@@ -716,8 +1222,8 @@ var DesktopFileIcon = ({
716
1222
  document.removeEventListener("mousedown", handleGlobalClick);
717
1223
  };
718
1224
  }, [contextMenuPos]);
719
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
720
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1225
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1226
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
721
1227
  "div",
722
1228
  {
723
1229
  ref: iconRef,
@@ -738,7 +1244,7 @@ var DesktopFileIcon = ({
738
1244
  onMouseEnter: () => setIsHovered(true),
739
1245
  onMouseLeave: () => setIsHovered(false),
740
1246
  children: [
741
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1247
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
742
1248
  "div",
743
1249
  {
744
1250
  className: "border rounded-3 d-flex align-items-center justify-content-center mb-1 shadow-sm position-relative",
@@ -747,17 +1253,17 @@ var DesktopFileIcon = ({
747
1253
  height: 64
748
1254
  },
749
1255
  children: [
750
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_fontawesome.FontAwesomeIcon, { icon: iconToRender, className: "fs-2" }),
751
- isDeleting && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "position-absolute top-50 start-50 translate-middle", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "spinner-border spinner-border-sm text-danger" }) })
1256
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_fontawesome.FontAwesomeIcon, { icon: iconToRender, className: "fs-2" }),
1257
+ isDeleting && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "position-absolute top-50 start-50 translate-middle", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "spinner-border spinner-border-sm text-danger" }) })
752
1258
  ]
753
1259
  }
754
1260
  ),
755
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "small text-center text-truncate", style: { width: "100%" }, children: name }),
756
- formattedSize && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { className: "text-muted mt-1", children: formattedSize })
1261
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "small text-center text-truncate", style: { width: "100%" }, children: name }),
1262
+ formattedSize && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("small", { className: "text-muted mt-1", children: formattedSize })
757
1263
  ]
758
1264
  }
759
1265
  ),
760
- contextMenuPos && !isDeleting && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1266
+ contextMenuPos && !isDeleting && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
761
1267
  "div",
762
1268
  {
763
1269
  ref: menuRef,
@@ -770,7 +1276,7 @@ var DesktopFileIcon = ({
770
1276
  },
771
1277
  onClick: (e) => e.stopPropagation(),
772
1278
  children: [
773
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1279
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
774
1280
  "button",
775
1281
  {
776
1282
  type: "button",
@@ -780,7 +1286,7 @@ var DesktopFileIcon = ({
780
1286
  children: "Download file"
781
1287
  }
782
1288
  ),
783
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1289
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
784
1290
  "button",
785
1291
  {
786
1292
  type: "button",
@@ -790,8 +1296,8 @@ var DesktopFileIcon = ({
790
1296
  children: "Copy download URL"
791
1297
  }
792
1298
  ),
793
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dropdown-divider" }),
794
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1299
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dropdown-divider" }),
1300
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
795
1301
  "button",
796
1302
  {
797
1303
  type: "button",
@@ -807,160 +1313,165 @@ var DesktopFileIcon = ({
807
1313
  };
808
1314
 
809
1315
  // src/components/UploadContainer.tsx
810
- var import_jsx_runtime4 = require("react/jsx-runtime");
811
- var UploadContainer = ({
812
- onFilesSelected,
813
- existingFiles = [],
814
- existingFilesLoading = false,
815
- onExistingFileClick,
816
- onDeleteFile,
817
- autoUpload = false,
818
- getPresignedUrl,
819
- onUploadComplete,
820
- onUploadError
821
- }) => {
822
- const [isDragging, setIsDragging] = (0, import_react5.useState)(false);
823
- const { uploads, startUploadsIfNeeded } = UseUploadManager({
824
- autoUpload,
1316
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1317
+ function filesToFileList(files) {
1318
+ const dt = new DataTransfer();
1319
+ for (const f of files) dt.items.add(f);
1320
+ return dt.files;
1321
+ }
1322
+ var UploadContainer = (0, import_react8.forwardRef)(
1323
+ ({
1324
+ multiple = true,
1325
+ accept = "*/*",
1326
+ onFilesSelected,
1327
+ existingFiles = [],
1328
+ existingFilesLoading = false,
1329
+ onExistingFileClick,
1330
+ onDeleteFile,
1331
+ autoUpload = false,
825
1332
  getPresignedUrl,
826
1333
  onUploadComplete,
827
1334
  onUploadError
828
- });
829
- const handleDragOver = (e) => {
830
- e.preventDefault();
831
- setIsDragging(true);
832
- };
833
- const handleDragLeave = (e) => {
834
- e.preventDefault();
835
- setIsDragging(false);
836
- };
837
- const handleDrop = (e) => {
838
- e.preventDefault();
839
- setIsDragging(false);
840
- const files = e.dataTransfer.files;
841
- if (!files || files.length === 0) return;
842
- onFilesSelected?.(files);
843
- startUploadsIfNeeded(files);
844
- };
845
- const handleExistingFileOpen = (file) => {
846
- if (onExistingFileClick) {
847
- onExistingFileClick(file);
848
- return;
849
- }
850
- const a = document.createElement("a");
851
- a.href = file.PublicUrl ?? "";
852
- a.download = file.Name ?? "";
853
- a.target = "_blank";
854
- a.rel = "noopener noreferrer";
855
- document.body.appendChild(a);
856
- a.click();
857
- document.body.removeChild(a);
858
- };
859
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
860
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "w-100", children: [
861
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
862
- "input",
863
- {
864
- id: "filePicker",
865
- type: "file",
866
- multiple: true,
867
- className: "d-none",
868
- onChange: (e) => {
869
- if (!e.target.files) return;
870
- onFilesSelected?.(e.target.files);
871
- startUploadsIfNeeded(e.target.files);
1335
+ }, ref) => {
1336
+ const [isDragging, setIsDragging] = (0, import_react8.useState)(false);
1337
+ const inputId = (0, import_react8.useMemo)(() => `filePicker_${crypto.randomUUID()}`, []);
1338
+ const inputRef = (0, import_react8.useRef)(null);
1339
+ const { uploads, startUploadsIfNeeded } = UseUploadManager({
1340
+ autoUpload,
1341
+ getPresignedUrl,
1342
+ onUploadComplete,
1343
+ onUploadError
1344
+ });
1345
+ const selectAndUpload = (files) => {
1346
+ if (!files || files.length === 0) return;
1347
+ onFilesSelected?.(files);
1348
+ startUploadsIfNeeded(files);
1349
+ };
1350
+ (0, import_react8.useImperativeHandle)(ref, () => ({
1351
+ enqueueFiles(files) {
1352
+ const list = Array.isArray(files) ? filesToFileList(files) : files;
1353
+ selectAndUpload(list);
1354
+ },
1355
+ openFilePicker() {
1356
+ inputRef.current?.click();
1357
+ }
1358
+ }));
1359
+ const handleDragOver = (e) => {
1360
+ e.preventDefault();
1361
+ setIsDragging(true);
1362
+ };
1363
+ const handleDragLeave = (e) => {
1364
+ e.preventDefault();
1365
+ setIsDragging(false);
1366
+ };
1367
+ const handleDrop = (e) => {
1368
+ e.preventDefault();
1369
+ setIsDragging(false);
1370
+ const files = e.dataTransfer.files;
1371
+ if (!files || files.length === 0) return;
1372
+ selectAndUpload(files);
1373
+ };
1374
+ const handleExistingFileOpen = (file) => {
1375
+ if (onExistingFileClick) {
1376
+ onExistingFileClick(file);
1377
+ return;
1378
+ }
1379
+ const a = document.createElement("a");
1380
+ a.href = file.PublicUrl ?? "";
1381
+ a.download = file.Name ?? "";
1382
+ a.target = "_blank";
1383
+ a.rel = "noopener noreferrer";
1384
+ document.body.appendChild(a);
1385
+ a.click();
1386
+ document.body.removeChild(a);
1387
+ };
1388
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1389
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "w-100", children: [
1390
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1391
+ "input",
1392
+ {
1393
+ ref: inputRef,
1394
+ id: inputId,
1395
+ type: "file",
1396
+ multiple,
1397
+ accept,
1398
+ className: "d-none",
1399
+ onChange: (e) => {
1400
+ if (!e.target.files) return;
1401
+ selectAndUpload(e.target.files);
1402
+ e.currentTarget.value = "";
1403
+ }
872
1404
  }
873
- }
874
- ),
875
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-start", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
876
- "button",
1405
+ ),
1406
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-start", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1407
+ "button",
1408
+ {
1409
+ type: "button",
1410
+ className: "btn btn-primary float-start",
1411
+ onClick: () => inputRef.current?.click(),
1412
+ children: "Browse files\u2026"
1413
+ }
1414
+ ) })
1415
+ ] }),
1416
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1417
+ UploadDropzone,
877
1418
  {
878
- type: "button",
879
- className: "btn btn-primary float-start",
880
- onClick: () => document.getElementById("filePicker")?.click(),
881
- children: "Browse files\u2026"
882
- }
883
- ) })
884
- ] }),
885
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
886
- UploadDropzone,
887
- {
888
- isDragging,
889
- onDragOver: handleDragOver,
890
- onDragLeave: handleDragLeave,
891
- onDrop: handleDrop,
892
- className: "w-100",
893
- style: { minHeight: "100px", alignItems: "stretch" },
894
- children: [
895
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-100 mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-grow-1 w-100", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UploadProgressList, { uploads }) }) }) }),
896
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
897
- "div",
898
- {
899
- className: "w-100 d-flex flex-wrap gap-4 align-content-start",
900
- style: { minHeight: "140px" },
901
- children: existingFilesLoading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-100 d-flex justify-content-center align-items-center", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
902
- "div",
903
- {
904
- className: "spinner-border text-secondary",
905
- role: "status"
906
- }
907
- ) }) : existingFiles.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
908
- "div",
909
- {
910
- className: "w-100 d-flex flex-column align-items-center justify-content-center text-muted",
911
- style: {
912
- minHeight: "160px",
913
- padding: "20px",
914
- cursor: "pointer",
915
- transition: "background 0.12s, border-color 0.12s"
1419
+ isDragging,
1420
+ onDragOver: handleDragOver,
1421
+ onDragLeave: handleDragLeave,
1422
+ onDrop: handleDrop,
1423
+ className: "w-100",
1424
+ style: { minHeight: "100px", alignItems: "stretch" },
1425
+ children: [
1426
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "w-100 mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex-grow-1 w-100", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(UploadProgressList, { uploads }) }) }) }),
1427
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1428
+ "div",
1429
+ {
1430
+ className: "w-100 d-flex flex-wrap gap-4 align-content-start",
1431
+ style: { minHeight: "140px" },
1432
+ children: existingFilesLoading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "w-100 d-flex justify-content-center align-items-center", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "spinner-border text-secondary", role: "status" }) }) : existingFiles.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1433
+ "div",
1434
+ {
1435
+ className: "w-100 d-flex flex-column align-items-center justify-content-center text-muted",
1436
+ style: {
1437
+ minHeight: "160px",
1438
+ padding: "20px",
1439
+ cursor: "pointer",
1440
+ transition: "background 0.12s, border-color 0.12s"
1441
+ },
1442
+ onClick: () => inputRef.current?.click(),
1443
+ children: [
1444
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: "Drag & drop files here" }),
1445
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("small", { className: "mt-1", children: "\u2026or click to browse" })
1446
+ ]
1447
+ }
1448
+ ) : existingFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1449
+ DesktopFileIcon,
1450
+ {
1451
+ name: file.Name,
1452
+ sizeBytes: file.FileSize,
1453
+ downloadUrl: file.PublicUrl,
1454
+ onOpen: () => handleExistingFileOpen(file),
1455
+ onDelete: () => onDeleteFile?.(file)
916
1456
  },
917
- onClick: () => document.getElementById("filePicker")?.click(),
918
- onMouseEnter: (e) => e.currentTarget.style.borderColor = "#888",
919
- onMouseLeave: (e) => e.currentTarget.style.borderColor = "#ccc",
920
- children: [
921
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
922
- "input",
923
- {
924
- id: "filePicker",
925
- type: "file",
926
- multiple: true,
927
- hidden: true,
928
- onChange: (e) => {
929
- if (!e.target.files) return;
930
- onFilesSelected?.(e.target.files);
931
- startUploadsIfNeeded(e.target.files);
932
- }
933
- }
934
- ),
935
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "Drag & drop files here" }),
936
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { className: "mt-1", children: "\u2026or click to browse" })
937
- ]
938
- }
939
- ) : existingFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
940
- DesktopFileIcon,
941
- {
942
- name: file.Name,
943
- sizeBytes: file.FileSize,
944
- downloadUrl: file.PublicUrl,
945
- onOpen: () => handleExistingFileOpen(file),
946
- onDelete: () => onDeleteFile?.(file)
947
- },
948
- file.Id
949
- ))
950
- }
951
- )
952
- ]
953
- }
954
- )
955
- ] });
956
- };
1457
+ file.Id
1458
+ ))
1459
+ }
1460
+ )
1461
+ ]
1462
+ }
1463
+ )
1464
+ ] });
1465
+ }
1466
+ );
1467
+ UploadContainer.displayName = "UploadContainer";
957
1468
 
958
1469
  // src/hooks/UseContainers.ts
959
- var import_react6 = require("react");
1470
+ var import_react9 = require("react");
960
1471
  function UseContainers({ apiBaseUrl, parentId }) {
961
- const [containers, setContainers] = (0, import_react6.useState)([]);
962
- const [loading, setLoading] = (0, import_react6.useState)(false);
963
- const [error, setError] = (0, import_react6.useState)(null);
1472
+ const [containers, setContainers] = (0, import_react9.useState)([]);
1473
+ const [loading, setLoading] = (0, import_react9.useState)(false);
1474
+ const [error, setError] = (0, import_react9.useState)(null);
964
1475
  const load = async () => {
965
1476
  setLoading(true);
966
1477
  setError(null);
@@ -976,7 +1487,7 @@ function UseContainers({ apiBaseUrl, parentId }) {
976
1487
  setLoading(false);
977
1488
  }
978
1489
  };
979
- (0, import_react6.useEffect)(() => {
1490
+ (0, import_react9.useEffect)(() => {
980
1491
  void load();
981
1492
  }, [apiBaseUrl, parentId]);
982
1493
  return {
@@ -989,11 +1500,12 @@ function UseContainers({ apiBaseUrl, parentId }) {
989
1500
  }
990
1501
 
991
1502
  // src/components/ContainerUploadPanel.tsx
992
- var import_jsx_runtime5 = require("react/jsx-runtime");
1503
+ var import_jsx_runtime8 = require("react/jsx-runtime");
993
1504
  var ContainerUploadPanel = ({
994
1505
  containerApiBaseUrl,
995
1506
  storageApiBaseUrl,
996
- parentContainerId
1507
+ parentContainerId,
1508
+ uploadRef
997
1509
  }) => {
998
1510
  const { containers, setContainers, reload, loading } = UseContainers({
999
1511
  apiBaseUrl: containerApiBaseUrl,
@@ -1048,9 +1560,10 @@ var ContainerUploadPanel = ({
1048
1560
  await sdkS3.s3.DeleteS3(file);
1049
1561
  setContainers((prev) => prev.filter((c) => c.Id !== file.Id));
1050
1562
  };
1051
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1563
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1052
1564
  UploadContainer,
1053
1565
  {
1566
+ ref: uploadRef,
1054
1567
  existingFiles: containers,
1055
1568
  existingFilesLoading: loading,
1056
1569
  onExistingFileClick: handleExistingFileClick,
@@ -1063,9 +1576,172 @@ var ContainerUploadPanel = ({
1063
1576
  );
1064
1577
  };
1065
1578
 
1579
+ // src/components/FileGridUploadPanel.tsx
1580
+ var import_react11 = require("react");
1581
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1582
+ function FileGridUploadPanel(props) {
1583
+ const {
1584
+ containerApiBaseUrl,
1585
+ storageApiBaseUrl,
1586
+ parentContainerId,
1587
+ accept = "*/*",
1588
+ multiple = true,
1589
+ selectedId,
1590
+ onSelect,
1591
+ icon,
1592
+ iconHtml,
1593
+ deleteDisabled
1594
+ } = props;
1595
+ const { containers, setContainers, reload, loading } = UseContainers({
1596
+ apiBaseUrl: containerApiBaseUrl,
1597
+ parentId: parentContainerId
1598
+ });
1599
+ const [isDragging, setIsDragging] = (0, import_react11.useState)(false);
1600
+ const [error, setError] = (0, import_react11.useState)(null);
1601
+ const sdkDb = (0, import_react11.useMemo)(() => new SparkStudioStorageSDK(containerApiBaseUrl), [containerApiBaseUrl]);
1602
+ const sdkS3 = (0, import_react11.useMemo)(() => new SparkStudioStorageSDK(storageApiBaseUrl), [storageApiBaseUrl]);
1603
+ const getPresignedUrl = async (file) => {
1604
+ const contentType = file.type || "application/octet-stream";
1605
+ const containerDTO = await sdkDb.container.CreateFileContainer(
1606
+ file.name,
1607
+ file.size,
1608
+ encodeURIComponent(contentType)
1609
+ );
1610
+ let lastError;
1611
+ for (let i = 1; i <= 3; i++) {
1612
+ try {
1613
+ return await sdkS3.s3.GetPreSignedUrl(containerDTO);
1614
+ } catch (e) {
1615
+ lastError = e;
1616
+ if (i < 3) await new Promise((r) => setTimeout(r, 500 * i));
1617
+ }
1618
+ }
1619
+ throw lastError instanceof Error ? lastError : new Error("Failed to fetch presigned URL");
1620
+ };
1621
+ const { uploads, startUploadsIfNeeded } = UseUploadManager({
1622
+ autoUpload: true,
1623
+ getPresignedUrl,
1624
+ onUploadComplete: async () => {
1625
+ setError(null);
1626
+ await reload();
1627
+ },
1628
+ onUploadError: (_file, err) => {
1629
+ setError(err.message ?? "Upload failed");
1630
+ }
1631
+ });
1632
+ const handleDeleteFile = async (file) => {
1633
+ const sdkDb2 = new SparkStudioStorageSDK(containerApiBaseUrl);
1634
+ const sdkS32 = new SparkStudioStorageSDK(storageApiBaseUrl);
1635
+ await sdkDb2.container.DeleteContainer(file.Id);
1636
+ await sdkS32.s3.DeleteS3(file);
1637
+ setContainers((prev) => prev.filter((c) => c.Id !== file.Id));
1638
+ };
1639
+ const openPicker = () => {
1640
+ const input = document.createElement("input");
1641
+ input.type = "file";
1642
+ input.multiple = multiple;
1643
+ input.accept = accept;
1644
+ input.onchange = () => {
1645
+ if (!input.files || input.files.length === 0) return;
1646
+ startUploadsIfNeeded(input.files);
1647
+ };
1648
+ input.click();
1649
+ };
1650
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "grid", gap: 12 }, children: [
1651
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
1652
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(UploadProgressList, { uploads }),
1653
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: 12, color: "crimson", marginTop: 6 }, children: error }) : null
1654
+ ] }),
1655
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1656
+ "div",
1657
+ {
1658
+ onDragEnter: (e) => {
1659
+ e.preventDefault();
1660
+ e.stopPropagation();
1661
+ setIsDragging(true);
1662
+ },
1663
+ onDragOver: (e) => {
1664
+ e.preventDefault();
1665
+ e.stopPropagation();
1666
+ setIsDragging(true);
1667
+ },
1668
+ onDragLeave: (e) => {
1669
+ e.preventDefault();
1670
+ e.stopPropagation();
1671
+ setIsDragging(false);
1672
+ },
1673
+ onDrop: (e) => {
1674
+ e.preventDefault();
1675
+ e.stopPropagation();
1676
+ setIsDragging(false);
1677
+ const files = e.dataTransfer.files;
1678
+ if (!files || files.length === 0) return;
1679
+ startUploadsIfNeeded(files);
1680
+ },
1681
+ style: {
1682
+ position: "relative",
1683
+ borderRadius: 14,
1684
+ border: isDragging ? "2px dashed rgba(13,110,253,0.9)" : "2px dashed rgba(0,0,0,0.15)",
1685
+ background: isDragging ? "rgba(13,110,253,0.06)" : "transparent",
1686
+ padding: 12
1687
+ },
1688
+ children: [
1689
+ isDragging ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1690
+ "div",
1691
+ {
1692
+ style: {
1693
+ position: "absolute",
1694
+ inset: 0,
1695
+ borderRadius: 14,
1696
+ display: "grid",
1697
+ placeItems: "center",
1698
+ pointerEvents: "none",
1699
+ background: "rgba(13,110,253,0.08)",
1700
+ fontWeight: 800
1701
+ },
1702
+ children: "Drop files to upload"
1703
+ }
1704
+ ) : null,
1705
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 10 }, children: [
1706
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontWeight: 700 }, children: "Files" }),
1707
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1708
+ "button",
1709
+ {
1710
+ type: "button",
1711
+ onClick: openPicker,
1712
+ style: {
1713
+ border: "1px solid rgba(0,0,0,0.18)",
1714
+ background: "white",
1715
+ borderRadius: 10,
1716
+ padding: "8px 10px",
1717
+ cursor: "pointer",
1718
+ fontWeight: 600
1719
+ },
1720
+ children: "Browse\u2026"
1721
+ }
1722
+ )
1723
+ ] }),
1724
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "d-flex justify-content-center align-items-center", style: { minHeight: 120 }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "spinner-border text-secondary", role: "status" }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1725
+ FileIconGrid,
1726
+ {
1727
+ files: containers,
1728
+ deleteDisabled,
1729
+ onDeleted: handleDeleteFile,
1730
+ selectedId,
1731
+ onSelect,
1732
+ icon,
1733
+ iconHtml
1734
+ }
1735
+ )
1736
+ ]
1737
+ }
1738
+ )
1739
+ ] });
1740
+ }
1741
+
1066
1742
  // src/components/SingleFileProcessUploader.tsx
1067
- var import_react8 = require("react");
1068
- var import_jsx_runtime6 = require("react/jsx-runtime");
1743
+ var import_react12 = require("react");
1744
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1069
1745
  var SingleFileProcessUploader = ({
1070
1746
  getPresignedUrl,
1071
1747
  onUploadComplete,
@@ -1074,28 +1750,28 @@ var SingleFileProcessUploader = ({
1074
1750
  disabled,
1075
1751
  uploadOnDrop = false
1076
1752
  }) => {
1077
- const [selectedFile, setSelectedFile] = (0, import_react8.useState)(null);
1078
- const [isDragging, setIsDragging] = (0, import_react8.useState)(false);
1079
- const [progress, setProgress] = (0, import_react8.useState)(null);
1080
- const [status, setStatus] = (0, import_react8.useState)("idle");
1081
- const fileInputRef = (0, import_react8.useRef)(null);
1753
+ const [selectedFile, setSelectedFile] = (0, import_react12.useState)(null);
1754
+ const [isDragging, setIsDragging] = (0, import_react12.useState)(false);
1755
+ const [progress, setProgress] = (0, import_react12.useState)(null);
1756
+ const [status, setStatus] = (0, import_react12.useState)("idle");
1757
+ const fileInputRef = (0, import_react12.useRef)(null);
1082
1758
  function getErrorMessage(err) {
1083
1759
  if (err instanceof Error) return err.message;
1084
1760
  if (typeof err === "string") return err;
1085
1761
  return "Unknown error during upload.";
1086
1762
  }
1087
1763
  const isUploading = status === "uploading";
1088
- const resetUiForNewFile = (0, import_react8.useCallback)((file) => {
1764
+ const resetUiForNewFile = (0, import_react12.useCallback)((file) => {
1089
1765
  setSelectedFile(file);
1090
1766
  setStatus("idle");
1091
1767
  setProgress(null);
1092
1768
  }, []);
1093
- const handleBrowseClick = (0, import_react8.useCallback)(() => {
1769
+ const handleBrowseClick = (0, import_react12.useCallback)(() => {
1094
1770
  if (disabled) return;
1095
1771
  if (isUploading) return;
1096
1772
  fileInputRef.current?.click();
1097
1773
  }, [disabled, isUploading]);
1098
- const startUpload = (0, import_react8.useCallback)(
1774
+ const startUpload = (0, import_react12.useCallback)(
1099
1775
  async (file) => {
1100
1776
  if (disabled) return;
1101
1777
  if (isUploading) return;
@@ -1119,7 +1795,7 @@ var SingleFileProcessUploader = ({
1119
1795
  },
1120
1796
  [disabled, getPresignedUrl, isUploading, onUploadComplete, onUploadFailed]
1121
1797
  );
1122
- const handleFilesSelected = (0, import_react8.useCallback)(
1798
+ const handleFilesSelected = (0, import_react12.useCallback)(
1123
1799
  (files) => {
1124
1800
  if (!files || files.length === 0) return;
1125
1801
  if (disabled) return;
@@ -1164,31 +1840,18 @@ var SingleFileProcessUploader = ({
1164
1840
  disabled ? "opacity-50" : "cursor-pointer",
1165
1841
  isDragging ? "bg-body-secondary border-dashed border-2 border-secondary" : "bg-body-trasparent border-dashed border-2"
1166
1842
  ].join(" ");
1167
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "d-flex flex-column gap-2", children: [
1168
- isUploading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "small", children: [
1169
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "d-flex justify-content-between mb-1", children: [
1170
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
1171
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "spinner-border spinner-border-sm text-primary" }),
1172
- " ",
1173
- "Uploading..."
1174
- ] }),
1175
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
1176
- progress ?? 0,
1177
- "%"
1178
- ] })
1179
- ] }),
1180
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "progress", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1181
- "div",
1182
- {
1183
- className: "progress-bar progress-bar-striped progress-bar-animated",
1184
- role: "progressbar",
1185
- "aria-valuemin": 0,
1186
- "aria-valuemax": 100,
1187
- "aria-valuenow": progress ?? 0,
1188
- style: { width: `${progress ?? 0}%` }
1189
- }
1190
- ) })
1191
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1843
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "d-flex flex-column gap-2", children: [
1844
+ isUploading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "small", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "progress", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1845
+ "div",
1846
+ {
1847
+ className: "progress-bar progress-bar-striped progress-bar-animated",
1848
+ role: "progressbar",
1849
+ "aria-valuemin": 0,
1850
+ "aria-valuemax": 100,
1851
+ "aria-valuenow": progress ?? 0,
1852
+ style: { width: `${progress ?? 0}%` }
1853
+ }
1854
+ ) }) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1192
1855
  "div",
1193
1856
  {
1194
1857
  className: dropzoneClasses,
@@ -1199,7 +1862,7 @@ var SingleFileProcessUploader = ({
1199
1862
  role: "button",
1200
1863
  "aria-disabled": disabled,
1201
1864
  children: [
1202
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1865
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1203
1866
  "input",
1204
1867
  {
1205
1868
  ref: fileInputRef,
@@ -1210,8 +1873,8 @@ var SingleFileProcessUploader = ({
1210
1873
  disabled: disabled || isUploading
1211
1874
  }
1212
1875
  ),
1213
- selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "small", children: [
1214
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1876
+ selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "small", children: [
1877
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1215
1878
  "button",
1216
1879
  {
1217
1880
  type: "button",
@@ -1220,15 +1883,15 @@ var SingleFileProcessUploader = ({
1220
1883
  children: "Browse file\u2026"
1221
1884
  }
1222
1885
  ),
1223
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1224
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Selected file:" }),
1886
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
1887
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: "Selected file:" }),
1225
1888
  " ",
1226
1889
  selectedFile.name
1227
1890
  ] }),
1228
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-muted", children: "Click here to change file or drag a new one." }),
1229
- uploadOnDrop && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-muted", children: "Upload starts automatically." })
1230
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "small", children: [
1231
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1891
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-muted", children: "Click here to change file or drag a new one." }),
1892
+ uploadOnDrop && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-muted", children: "Upload starts automatically." })
1893
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "small", children: [
1894
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1232
1895
  "button",
1233
1896
  {
1234
1897
  type: "button",
@@ -1237,13 +1900,13 @@ var SingleFileProcessUploader = ({
1237
1900
  children: "Browse file\u2026"
1238
1901
  }
1239
1902
  ),
1240
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: "Drag & drop a file here" }),
1241
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-muted", children: "or click to browse" })
1903
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: "Drag & drop a file here" }),
1904
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-muted", children: "or click to browse" })
1242
1905
  ] })
1243
1906
  ]
1244
1907
  }
1245
1908
  ),
1246
- !uploadOnDrop && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1909
+ !uploadOnDrop && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1247
1910
  "button",
1248
1911
  {
1249
1912
  type: "button",
@@ -1257,34 +1920,44 @@ var SingleFileProcessUploader = ({
1257
1920
  };
1258
1921
 
1259
1922
  // src/views/HomeView.tsx
1923
+ var import_react13 = require("react");
1260
1924
  var import_authentication_ui = require("@sparkstudio/authentication-ui");
1261
- var import_jsx_runtime7 = require("react/jsx-runtime");
1925
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1926
+ var CONTAINER_API = "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod";
1927
+ var STORAGE_API = "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod";
1262
1928
  function HomeView() {
1263
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1929
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1264
1930
  import_authentication_ui.AuthenticatorProvider,
1265
1931
  {
1266
1932
  googleClientId: import_authentication_ui.AppSettings.GoogleClientId,
1267
1933
  authenticationUrl: import_authentication_ui.AppSettings.AuthenticationUrl,
1268
1934
  accountsUrl: import_authentication_ui.AppSettings.AccountsUrl,
1269
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HomeContent, {})
1935
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(HomeContent, {})
1270
1936
  }
1271
1937
  );
1272
1938
  }
1273
1939
  function HomeContent() {
1274
1940
  const { user } = (0, import_authentication_ui.useUser)();
1941
+ const [ids, setIds] = (0, import_react13.useState)([]);
1942
+ const [selectedId, setSelectedId] = (0, import_react13.useState)(void 0);
1943
+ const [selectedFile, setSelectedFile] = (0, import_react13.useState)(null);
1944
+ (0, import_react13.useEffect)(() => {
1945
+ if (selectedId && !ids.includes(selectedId)) {
1946
+ setSelectedId(void 0);
1947
+ setSelectedFile(null);
1948
+ }
1949
+ }, [ids, selectedId]);
1275
1950
  async function getPresignedUrlFromApi(file) {
1276
- const res = new SparkStudioStorageSDK(
1277
- "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod"
1278
- //"https://localhost:5001"
1279
- );
1951
+ const sdk = new SparkStudioStorageSDK(STORAGE_API);
1280
1952
  const contentType = file.type || "application/octet-stream";
1281
- const result = await res.s3.GetTemporaryPreSignedUrl(new TemporaryFileDTO({ Name: file.name, ContentType: contentType }));
1282
- return result;
1953
+ return sdk.s3.GetTemporaryPreSignedUrl(
1954
+ new TemporaryFileDTO({ Name: file.name, ContentType: contentType })
1955
+ );
1283
1956
  }
1284
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1285
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_authentication_ui.UserInfoCard, {}),
1286
- user && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1287
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1957
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1958
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_authentication_ui.UserInfoCard, {}),
1959
+ user ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1960
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1288
1961
  SingleFileProcessUploader,
1289
1962
  {
1290
1963
  uploadOnDrop: true,
@@ -1295,14 +1968,39 @@ function HomeContent() {
1295
1968
  }
1296
1969
  }
1297
1970
  ),
1298
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1299
- ContainerUploadPanel,
1971
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1972
+ ContainerIdGridPanel,
1300
1973
  {
1301
- containerApiBaseUrl: "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod",
1302
- storageApiBaseUrl: "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod"
1974
+ containerApiBaseUrl: CONTAINER_API,
1975
+ storageApiBaseUrl: STORAGE_API,
1976
+ containerIds: ids,
1977
+ onContainerIdsChange: setIds,
1978
+ multiple: true,
1979
+ accept: "*/*",
1980
+ selectedId,
1981
+ onSelect: (file) => {
1982
+ setSelectedId(file.Id);
1983
+ setSelectedFile(file);
1984
+ }
1303
1985
  }
1304
- )
1305
- ] })
1986
+ ),
1987
+ selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1988
+ "div",
1989
+ {
1990
+ style: {
1991
+ marginTop: 12,
1992
+ padding: 12,
1993
+ border: "1px solid rgba(0,0,0,0.12)",
1994
+ borderRadius: 12
1995
+ },
1996
+ children: [
1997
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontWeight: 700 }, children: "Selected file" }),
1998
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { children: selectedFile.Name ?? "(no name)" }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 12, opacity: 0.7 }, children: selectedFile.PublicUrl ?? "(no public url)" })
2000
+ ]
2001
+ }
2002
+ ) : null
2003
+ ] }) : null
1306
2004
  ] });
1307
2005
  }
1308
2006
  // Annotate the CommonJS export names for ESM import in node:
@@ -1310,9 +2008,13 @@ function HomeContent() {
1310
2008
  AWSPresignedUrlDTO,
1311
2009
  Container,
1312
2010
  ContainerDTO,
2011
+ ContainerIdGridPanel,
1313
2012
  ContainerType,
1314
2013
  ContainerUploadPanel,
1315
2014
  DesktopFileIcon,
2015
+ FileGridUploadPanel,
2016
+ FileIconCard,
2017
+ FileIconGrid,
1316
2018
  Home,
1317
2019
  HomeView,
1318
2020
  S3,