@sparkstudio/storage-ui 1.0.28 → 1.0.30

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,558 @@ 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
+ onDeleted
782
+ } = props;
783
+ const sdkDb = (0, import_react5.useMemo)(
784
+ () => new SparkStudioStorageSDK(containerApiBaseUrl),
785
+ [containerApiBaseUrl]
786
+ );
787
+ const sdkS3 = (0, import_react5.useMemo)(
788
+ () => new SparkStudioStorageSDK(storageApiBaseUrl),
789
+ [storageApiBaseUrl]
790
+ );
791
+ const [loading, setLoading] = (0, import_react5.useState)(false);
792
+ const [files, setFiles] = (0, import_react5.useState)([]);
793
+ const [dragOver, setDragOver] = (0, import_react5.useState)(false);
794
+ const [errMsg, setErrMsg] = (0, import_react5.useState)(null);
795
+ const idsRef = (0, import_react5.useRef)(containerIds);
796
+ (0, import_react5.useEffect)(() => {
797
+ idsRef.current = containerIds;
798
+ }, [containerIds]);
799
+ const createdByFileRef = (0, import_react5.useRef)(/* @__PURE__ */ new WeakMap());
800
+ (0, import_react5.useEffect)(() => {
801
+ let cancelled = false;
802
+ async function loadByIds(ids) {
803
+ setErrMsg(null);
804
+ if (!ids || ids.length === 0) {
805
+ setFiles([]);
806
+ return;
807
+ }
808
+ setLoading(true);
809
+ try {
810
+ const results = await Promise.all(
811
+ ids.map(async (id) => {
812
+ try {
813
+ const dto = await sdkDb.container.Read?.(id);
814
+ return dto ?? null;
815
+ } catch {
816
+ return null;
817
+ }
818
+ })
819
+ );
820
+ if (cancelled) return;
821
+ const map = /* @__PURE__ */ new Map();
822
+ for (const r of results) if (r?.Id) map.set(r.Id, r);
823
+ setFiles(
824
+ ids.map((id) => map.get(id)).filter(Boolean)
825
+ );
826
+ } catch (e) {
827
+ if (!cancelled)
828
+ setErrMsg(e instanceof Error ? e.message : "Failed to load files");
829
+ } finally {
830
+ if (!cancelled) setLoading(false);
831
+ }
832
+ }
833
+ void loadByIds(containerIds);
834
+ return () => {
835
+ cancelled = true;
836
+ };
837
+ }, [containerIds, sdkDb]);
838
+ const getPresignedUrl = async (file) => {
839
+ const contentType = file.type || "application/octet-stream";
840
+ const containerDTO = await sdkDb.container.CreateFileContainer(
841
+ file.name,
842
+ file.size,
843
+ encodeURIComponent(contentType)
844
+ );
845
+ createdByFileRef.current.set(file, containerDTO);
846
+ let lastError;
847
+ for (let i = 1; i <= 3; i++) {
848
+ try {
849
+ return await sdkS3.s3.GetPreSignedUrl(containerDTO);
850
+ } catch (e) {
851
+ lastError = e;
852
+ if (i < 3) await new Promise((r) => setTimeout(r, 500 * i));
853
+ }
854
+ }
855
+ throw lastError instanceof Error ? lastError : new Error("Failed to fetch presigned URL");
856
+ };
857
+ const { uploads, startUploadsIfNeeded } = UseUploadManager({
858
+ autoUpload: true,
859
+ getPresignedUrl,
860
+ onUploadComplete: async (file) => {
861
+ setErrMsg(null);
862
+ const created = createdByFileRef.current.get(file);
863
+ if (created?.Id) {
864
+ const prev = idsRef.current ?? [];
865
+ const next = prev.includes(created.Id) ? prev : [...prev, created.Id];
866
+ onContainerIdsChange(next);
867
+ }
868
+ if (created?.Id) {
869
+ try {
870
+ const refreshed = await sdkDb.container.Read?.(created.Id);
871
+ if (refreshed) {
872
+ setFiles((prevFiles) => {
873
+ const nextFiles = prevFiles.slice();
874
+ const idx = nextFiles.findIndex((x) => x.Id === created.Id);
875
+ if (idx >= 0) nextFiles[idx] = refreshed;
876
+ else nextFiles.push(refreshed);
877
+ return nextFiles;
878
+ });
879
+ }
880
+ } catch {
881
+ }
882
+ }
883
+ },
884
+ onUploadError: (_file, error) => {
885
+ setErrMsg(error?.message ?? "Upload failed");
886
+ }
887
+ });
888
+ const handleDelete = async (file) => {
889
+ if (deleteDisabled) return;
890
+ await sdkDb.container.DeleteContainer(file.Id);
891
+ await sdkS3.s3.DeleteS3(file);
892
+ const prev = idsRef.current ?? [];
893
+ onContainerIdsChange(prev.filter((id) => id !== file.Id));
894
+ setFiles((prevFiles) => prevFiles.filter((x) => x.Id !== file.Id));
895
+ onDeleted?.(file);
896
+ };
897
+ const openPicker = () => {
898
+ const input = document.createElement("input");
899
+ input.type = "file";
900
+ input.multiple = multiple;
901
+ input.accept = accept;
902
+ input.onchange = () => {
903
+ if (input.files) startUploadsIfNeeded(input.files);
904
+ };
905
+ input.click();
906
+ };
907
+ const onDrop = (ev) => {
908
+ ev.preventDefault();
909
+ ev.stopPropagation();
910
+ setDragOver(false);
911
+ const list = ev.dataTransfer.files;
912
+ if (!list || list.length === 0) return;
913
+ startUploadsIfNeeded(list);
914
+ };
915
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className, style: { display: "grid", gap: 12 }, children: [
916
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
917
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UploadProgressList, { uploads }),
918
+ errMsg ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 12, color: "crimson", marginTop: 6 }, children: errMsg }) : null
919
+ ] }),
920
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
921
+ "div",
922
+ {
923
+ onDragEnter: (e) => {
924
+ e.preventDefault();
925
+ e.stopPropagation();
926
+ setDragOver(true);
927
+ },
928
+ onDragOver: (e) => {
929
+ e.preventDefault();
930
+ e.stopPropagation();
931
+ setDragOver(true);
932
+ },
933
+ onDragLeave: (e) => {
934
+ e.preventDefault();
935
+ e.stopPropagation();
936
+ setDragOver(false);
937
+ },
938
+ onDrop,
939
+ style: {
940
+ position: "relative",
941
+ borderRadius: 14,
942
+ border: `2px dashed ${dragOver ? "rgba(13,110,253,0.9)" : "rgba(0,0,0,0.15)"}`,
943
+ background: dragOver ? "rgba(13,110,253,0.06)" : "transparent",
944
+ padding: 12
945
+ },
946
+ children: [
947
+ dragOver ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
948
+ "div",
949
+ {
950
+ style: {
951
+ position: "absolute",
952
+ inset: 0,
953
+ borderRadius: 14,
954
+ display: "grid",
955
+ placeItems: "center",
956
+ pointerEvents: "none",
957
+ background: "rgba(13,110,253,0.10)",
958
+ fontWeight: 800
959
+ },
960
+ children: "Drop files to upload"
961
+ }
962
+ ) : null,
963
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
964
+ "div",
965
+ {
966
+ style: {
967
+ display: "flex",
968
+ justifyContent: "space-between",
969
+ marginBottom: 10
970
+ },
971
+ children: [
972
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontWeight: 700 }, children: "Files" }),
973
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
974
+ "button",
975
+ {
976
+ type: "button",
977
+ onClick: openPicker,
978
+ style: {
979
+ border: "1px solid rgba(0,0,0,0.18)",
980
+ background: "white",
981
+ borderRadius: 10,
982
+ padding: "8px 10px",
983
+ cursor: "pointer",
984
+ fontWeight: 600
985
+ },
986
+ children: "Browse\u2026"
987
+ }
988
+ )
989
+ ]
990
+ }
991
+ ),
992
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
993
+ "div",
994
+ {
995
+ className: "d-flex justify-content-center align-items-center",
996
+ style: { minHeight: 120 },
997
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "spinner-border text-secondary", role: "status" })
998
+ }
999
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1000
+ FileIconGrid,
1001
+ {
1002
+ files,
1003
+ deleteDisabled,
1004
+ onDeleted: handleDelete,
1005
+ selectedId,
1006
+ onSelect,
1007
+ icon,
1008
+ iconHtml
1009
+ }
1010
+ )
1011
+ ]
1012
+ }
1013
+ )
1014
+ ] });
1015
+ }
1016
+
1017
+ // src/components/ContainerUploadPanel.tsx
1018
+ var import_react10 = require("react");
1019
+
1020
+ // src/components/UploadContainer.tsx
1021
+ var import_react8 = require("react");
1022
+
1023
+ // src/components/UploadDropzone.tsx
1024
+ var import_react6 = require("react");
1025
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1026
+ var UploadDropzone = ({
1027
+ isDragging,
1028
+ onDragOver,
1029
+ onDragLeave,
1030
+ onDrop,
1031
+ className = "",
1032
+ style,
1033
+ children
1034
+ }) => {
1035
+ const baseClass = "rounded-3 d-flex flex-column align-items-center justify-content-center";
1036
+ const stateClass = isDragging ? "bg-body-secondary border-dashed border-2 border-secondary" : "bg-body-trasparent border-solid border-transparent border-2";
1037
+ const combinedClassName = `${baseClass} ${stateClass} ${className}`.trim();
1038
+ const handleDragOver = (e) => {
1039
+ e.preventDefault();
1040
+ onDragOver?.(e);
1041
+ };
1042
+ const handleDragLeave = (e) => {
1043
+ e.preventDefault();
1044
+ onDragLeave?.(e);
1045
+ };
1046
+ const handleDrop = (e) => {
1047
+ e.preventDefault();
1048
+ onDrop?.(e);
1049
+ };
1050
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1051
+ "div",
1052
+ {
1053
+ className: combinedClassName,
1054
+ style: { minHeight: "140px", ...style },
1055
+ onDragOver: handleDragOver,
1056
+ onDragLeave: handleDragLeave,
1057
+ onDrop: handleDrop,
1058
+ children
1059
+ }
1060
+ );
1061
+ };
1062
+
555
1063
  // src/components/DesktopFileIcon.tsx
556
1064
  var import_free_solid_svg_icons = require("@fortawesome/free-solid-svg-icons");
557
1065
  var import_react_fontawesome = require("@fortawesome/react-fontawesome");
558
- var import_react4 = require("react");
559
- var import_jsx_runtime3 = require("react/jsx-runtime");
1066
+ var import_react7 = require("react");
1067
+ var import_jsx_runtime6 = require("react/jsx-runtime");
560
1068
  function getFileExtension(name) {
561
1069
  if (!name) return null;
562
1070
  const lastDot = name.lastIndexOf(".");
@@ -635,13 +1143,13 @@ var DesktopFileIcon = ({
635
1143
  onOpen,
636
1144
  onDelete
637
1145
  }) => {
638
- const [contextMenuPos, setContextMenuPos] = (0, import_react4.useState)(
1146
+ const [contextMenuPos, setContextMenuPos] = (0, import_react7.useState)(
639
1147
  null
640
1148
  );
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);
1149
+ const [isHovered, setIsHovered] = (0, import_react7.useState)(false);
1150
+ const [isDeleting, setIsDeleting] = (0, import_react7.useState)(false);
1151
+ const iconRef = (0, import_react7.useRef)(null);
1152
+ const menuRef = (0, import_react7.useRef)(null);
645
1153
  const handleDoubleClick = () => {
646
1154
  if (isDeleting) return;
647
1155
  if (onOpen) {
@@ -699,7 +1207,7 @@ var DesktopFileIcon = ({
699
1207
  const formattedSize = typeof sizeBytes === "number" ? `${(sizeBytes / 1024).toFixed(1)} KB` : void 0;
700
1208
  const ext = getFileExtension(name);
701
1209
  const iconToRender = getIconForExtension(ext);
702
- (0, import_react4.useEffect)(() => {
1210
+ (0, import_react7.useEffect)(() => {
703
1211
  if (!contextMenuPos) return;
704
1212
  const handleGlobalClick = (e) => {
705
1213
  const target = e.target;
@@ -716,8 +1224,8 @@ var DesktopFileIcon = ({
716
1224
  document.removeEventListener("mousedown", handleGlobalClick);
717
1225
  };
718
1226
  }, [contextMenuPos]);
719
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
720
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1227
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1228
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
721
1229
  "div",
722
1230
  {
723
1231
  ref: iconRef,
@@ -738,7 +1246,7 @@ var DesktopFileIcon = ({
738
1246
  onMouseEnter: () => setIsHovered(true),
739
1247
  onMouseLeave: () => setIsHovered(false),
740
1248
  children: [
741
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1249
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
742
1250
  "div",
743
1251
  {
744
1252
  className: "border rounded-3 d-flex align-items-center justify-content-center mb-1 shadow-sm position-relative",
@@ -747,17 +1255,17 @@ var DesktopFileIcon = ({
747
1255
  height: 64
748
1256
  },
749
1257
  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" }) })
1258
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_fontawesome.FontAwesomeIcon, { icon: iconToRender, className: "fs-2" }),
1259
+ 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
1260
  ]
753
1261
  }
754
1262
  ),
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 })
1263
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "small text-center text-truncate", style: { width: "100%" }, children: name }),
1264
+ formattedSize && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("small", { className: "text-muted mt-1", children: formattedSize })
757
1265
  ]
758
1266
  }
759
1267
  ),
760
- contextMenuPos && !isDeleting && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1268
+ contextMenuPos && !isDeleting && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
761
1269
  "div",
762
1270
  {
763
1271
  ref: menuRef,
@@ -770,7 +1278,7 @@ var DesktopFileIcon = ({
770
1278
  },
771
1279
  onClick: (e) => e.stopPropagation(),
772
1280
  children: [
773
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1281
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
774
1282
  "button",
775
1283
  {
776
1284
  type: "button",
@@ -780,7 +1288,7 @@ var DesktopFileIcon = ({
780
1288
  children: "Download file"
781
1289
  }
782
1290
  ),
783
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1291
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
784
1292
  "button",
785
1293
  {
786
1294
  type: "button",
@@ -790,8 +1298,8 @@ var DesktopFileIcon = ({
790
1298
  children: "Copy download URL"
791
1299
  }
792
1300
  ),
793
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dropdown-divider" }),
794
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1301
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dropdown-divider" }),
1302
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
795
1303
  "button",
796
1304
  {
797
1305
  type: "button",
@@ -807,160 +1315,165 @@ var DesktopFileIcon = ({
807
1315
  };
808
1316
 
809
1317
  // 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,
1318
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1319
+ function filesToFileList(files) {
1320
+ const dt = new DataTransfer();
1321
+ for (const f of files) dt.items.add(f);
1322
+ return dt.files;
1323
+ }
1324
+ var UploadContainer = (0, import_react8.forwardRef)(
1325
+ ({
1326
+ multiple = true,
1327
+ accept = "*/*",
1328
+ onFilesSelected,
1329
+ existingFiles = [],
1330
+ existingFilesLoading = false,
1331
+ onExistingFileClick,
1332
+ onDeleteFile,
1333
+ autoUpload = false,
825
1334
  getPresignedUrl,
826
1335
  onUploadComplete,
827
1336
  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);
1337
+ }, ref) => {
1338
+ const [isDragging, setIsDragging] = (0, import_react8.useState)(false);
1339
+ const inputId = (0, import_react8.useMemo)(() => `filePicker_${crypto.randomUUID()}`, []);
1340
+ const inputRef = (0, import_react8.useRef)(null);
1341
+ const { uploads, startUploadsIfNeeded } = UseUploadManager({
1342
+ autoUpload,
1343
+ getPresignedUrl,
1344
+ onUploadComplete,
1345
+ onUploadError
1346
+ });
1347
+ const selectAndUpload = (files) => {
1348
+ if (!files || files.length === 0) return;
1349
+ onFilesSelected?.(files);
1350
+ startUploadsIfNeeded(files);
1351
+ };
1352
+ (0, import_react8.useImperativeHandle)(ref, () => ({
1353
+ enqueueFiles(files) {
1354
+ const list = Array.isArray(files) ? filesToFileList(files) : files;
1355
+ selectAndUpload(list);
1356
+ },
1357
+ openFilePicker() {
1358
+ inputRef.current?.click();
1359
+ }
1360
+ }));
1361
+ const handleDragOver = (e) => {
1362
+ e.preventDefault();
1363
+ setIsDragging(true);
1364
+ };
1365
+ const handleDragLeave = (e) => {
1366
+ e.preventDefault();
1367
+ setIsDragging(false);
1368
+ };
1369
+ const handleDrop = (e) => {
1370
+ e.preventDefault();
1371
+ setIsDragging(false);
1372
+ const files = e.dataTransfer.files;
1373
+ if (!files || files.length === 0) return;
1374
+ selectAndUpload(files);
1375
+ };
1376
+ const handleExistingFileOpen = (file) => {
1377
+ if (onExistingFileClick) {
1378
+ onExistingFileClick(file);
1379
+ return;
1380
+ }
1381
+ const a = document.createElement("a");
1382
+ a.href = file.PublicUrl ?? "";
1383
+ a.download = file.Name ?? "";
1384
+ a.target = "_blank";
1385
+ a.rel = "noopener noreferrer";
1386
+ document.body.appendChild(a);
1387
+ a.click();
1388
+ document.body.removeChild(a);
1389
+ };
1390
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1391
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "w-100", children: [
1392
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1393
+ "input",
1394
+ {
1395
+ ref: inputRef,
1396
+ id: inputId,
1397
+ type: "file",
1398
+ multiple,
1399
+ accept,
1400
+ className: "d-none",
1401
+ onChange: (e) => {
1402
+ if (!e.target.files) return;
1403
+ selectAndUpload(e.target.files);
1404
+ e.currentTarget.value = "";
1405
+ }
872
1406
  }
873
- }
874
- ),
875
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-start", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
876
- "button",
1407
+ ),
1408
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-start", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1409
+ "button",
1410
+ {
1411
+ type: "button",
1412
+ className: "btn btn-primary float-start",
1413
+ onClick: () => inputRef.current?.click(),
1414
+ children: "Browse files\u2026"
1415
+ }
1416
+ ) })
1417
+ ] }),
1418
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1419
+ UploadDropzone,
877
1420
  {
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"
1421
+ isDragging,
1422
+ onDragOver: handleDragOver,
1423
+ onDragLeave: handleDragLeave,
1424
+ onDrop: handleDrop,
1425
+ className: "w-100",
1426
+ style: { minHeight: "100px", alignItems: "stretch" },
1427
+ children: [
1428
+ /* @__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 }) }) }) }),
1429
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1430
+ "div",
1431
+ {
1432
+ className: "w-100 d-flex flex-wrap gap-4 align-content-start",
1433
+ style: { minHeight: "140px" },
1434
+ 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)(
1435
+ "div",
1436
+ {
1437
+ className: "w-100 d-flex flex-column align-items-center justify-content-center text-muted",
1438
+ style: {
1439
+ minHeight: "160px",
1440
+ padding: "20px",
1441
+ cursor: "pointer",
1442
+ transition: "background 0.12s, border-color 0.12s"
1443
+ },
1444
+ onClick: () => inputRef.current?.click(),
1445
+ children: [
1446
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: "Drag & drop files here" }),
1447
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("small", { className: "mt-1", children: "\u2026or click to browse" })
1448
+ ]
1449
+ }
1450
+ ) : existingFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1451
+ DesktopFileIcon,
1452
+ {
1453
+ name: file.Name,
1454
+ sizeBytes: file.FileSize,
1455
+ downloadUrl: file.PublicUrl,
1456
+ onOpen: () => handleExistingFileOpen(file),
1457
+ onDelete: () => onDeleteFile?.(file)
916
1458
  },
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
- };
1459
+ file.Id
1460
+ ))
1461
+ }
1462
+ )
1463
+ ]
1464
+ }
1465
+ )
1466
+ ] });
1467
+ }
1468
+ );
1469
+ UploadContainer.displayName = "UploadContainer";
957
1470
 
958
1471
  // src/hooks/UseContainers.ts
959
- var import_react6 = require("react");
1472
+ var import_react9 = require("react");
960
1473
  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);
1474
+ const [containers, setContainers] = (0, import_react9.useState)([]);
1475
+ const [loading, setLoading] = (0, import_react9.useState)(false);
1476
+ const [error, setError] = (0, import_react9.useState)(null);
964
1477
  const load = async () => {
965
1478
  setLoading(true);
966
1479
  setError(null);
@@ -976,7 +1489,7 @@ function UseContainers({ apiBaseUrl, parentId }) {
976
1489
  setLoading(false);
977
1490
  }
978
1491
  };
979
- (0, import_react6.useEffect)(() => {
1492
+ (0, import_react9.useEffect)(() => {
980
1493
  void load();
981
1494
  }, [apiBaseUrl, parentId]);
982
1495
  return {
@@ -989,11 +1502,12 @@ function UseContainers({ apiBaseUrl, parentId }) {
989
1502
  }
990
1503
 
991
1504
  // src/components/ContainerUploadPanel.tsx
992
- var import_jsx_runtime5 = require("react/jsx-runtime");
1505
+ var import_jsx_runtime8 = require("react/jsx-runtime");
993
1506
  var ContainerUploadPanel = ({
994
1507
  containerApiBaseUrl,
995
1508
  storageApiBaseUrl,
996
- parentContainerId
1509
+ parentContainerId,
1510
+ uploadRef
997
1511
  }) => {
998
1512
  const { containers, setContainers, reload, loading } = UseContainers({
999
1513
  apiBaseUrl: containerApiBaseUrl,
@@ -1048,9 +1562,10 @@ var ContainerUploadPanel = ({
1048
1562
  await sdkS3.s3.DeleteS3(file);
1049
1563
  setContainers((prev) => prev.filter((c) => c.Id !== file.Id));
1050
1564
  };
1051
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1565
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1052
1566
  UploadContainer,
1053
1567
  {
1568
+ ref: uploadRef,
1054
1569
  existingFiles: containers,
1055
1570
  existingFilesLoading: loading,
1056
1571
  onExistingFileClick: handleExistingFileClick,
@@ -1063,9 +1578,172 @@ var ContainerUploadPanel = ({
1063
1578
  );
1064
1579
  };
1065
1580
 
1581
+ // src/components/FileGridUploadPanel.tsx
1582
+ var import_react11 = require("react");
1583
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1584
+ function FileGridUploadPanel(props) {
1585
+ const {
1586
+ containerApiBaseUrl,
1587
+ storageApiBaseUrl,
1588
+ parentContainerId,
1589
+ accept = "*/*",
1590
+ multiple = true,
1591
+ selectedId,
1592
+ onSelect,
1593
+ icon,
1594
+ iconHtml,
1595
+ deleteDisabled
1596
+ } = props;
1597
+ const { containers, setContainers, reload, loading } = UseContainers({
1598
+ apiBaseUrl: containerApiBaseUrl,
1599
+ parentId: parentContainerId
1600
+ });
1601
+ const [isDragging, setIsDragging] = (0, import_react11.useState)(false);
1602
+ const [error, setError] = (0, import_react11.useState)(null);
1603
+ const sdkDb = (0, import_react11.useMemo)(() => new SparkStudioStorageSDK(containerApiBaseUrl), [containerApiBaseUrl]);
1604
+ const sdkS3 = (0, import_react11.useMemo)(() => new SparkStudioStorageSDK(storageApiBaseUrl), [storageApiBaseUrl]);
1605
+ const getPresignedUrl = async (file) => {
1606
+ const contentType = file.type || "application/octet-stream";
1607
+ const containerDTO = await sdkDb.container.CreateFileContainer(
1608
+ file.name,
1609
+ file.size,
1610
+ encodeURIComponent(contentType)
1611
+ );
1612
+ let lastError;
1613
+ for (let i = 1; i <= 3; i++) {
1614
+ try {
1615
+ return await sdkS3.s3.GetPreSignedUrl(containerDTO);
1616
+ } catch (e) {
1617
+ lastError = e;
1618
+ if (i < 3) await new Promise((r) => setTimeout(r, 500 * i));
1619
+ }
1620
+ }
1621
+ throw lastError instanceof Error ? lastError : new Error("Failed to fetch presigned URL");
1622
+ };
1623
+ const { uploads, startUploadsIfNeeded } = UseUploadManager({
1624
+ autoUpload: true,
1625
+ getPresignedUrl,
1626
+ onUploadComplete: async () => {
1627
+ setError(null);
1628
+ await reload();
1629
+ },
1630
+ onUploadError: (_file, err) => {
1631
+ setError(err.message ?? "Upload failed");
1632
+ }
1633
+ });
1634
+ const handleDeleteFile = async (file) => {
1635
+ const sdkDb2 = new SparkStudioStorageSDK(containerApiBaseUrl);
1636
+ const sdkS32 = new SparkStudioStorageSDK(storageApiBaseUrl);
1637
+ await sdkDb2.container.DeleteContainer(file.Id);
1638
+ await sdkS32.s3.DeleteS3(file);
1639
+ setContainers((prev) => prev.filter((c) => c.Id !== file.Id));
1640
+ };
1641
+ const openPicker = () => {
1642
+ const input = document.createElement("input");
1643
+ input.type = "file";
1644
+ input.multiple = multiple;
1645
+ input.accept = accept;
1646
+ input.onchange = () => {
1647
+ if (!input.files || input.files.length === 0) return;
1648
+ startUploadsIfNeeded(input.files);
1649
+ };
1650
+ input.click();
1651
+ };
1652
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "grid", gap: 12 }, children: [
1653
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
1654
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(UploadProgressList, { uploads }),
1655
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: 12, color: "crimson", marginTop: 6 }, children: error }) : null
1656
+ ] }),
1657
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1658
+ "div",
1659
+ {
1660
+ onDragEnter: (e) => {
1661
+ e.preventDefault();
1662
+ e.stopPropagation();
1663
+ setIsDragging(true);
1664
+ },
1665
+ onDragOver: (e) => {
1666
+ e.preventDefault();
1667
+ e.stopPropagation();
1668
+ setIsDragging(true);
1669
+ },
1670
+ onDragLeave: (e) => {
1671
+ e.preventDefault();
1672
+ e.stopPropagation();
1673
+ setIsDragging(false);
1674
+ },
1675
+ onDrop: (e) => {
1676
+ e.preventDefault();
1677
+ e.stopPropagation();
1678
+ setIsDragging(false);
1679
+ const files = e.dataTransfer.files;
1680
+ if (!files || files.length === 0) return;
1681
+ startUploadsIfNeeded(files);
1682
+ },
1683
+ style: {
1684
+ position: "relative",
1685
+ borderRadius: 14,
1686
+ border: isDragging ? "2px dashed rgba(13,110,253,0.9)" : "2px dashed rgba(0,0,0,0.15)",
1687
+ background: isDragging ? "rgba(13,110,253,0.06)" : "transparent",
1688
+ padding: 12
1689
+ },
1690
+ children: [
1691
+ isDragging ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1692
+ "div",
1693
+ {
1694
+ style: {
1695
+ position: "absolute",
1696
+ inset: 0,
1697
+ borderRadius: 14,
1698
+ display: "grid",
1699
+ placeItems: "center",
1700
+ pointerEvents: "none",
1701
+ background: "rgba(13,110,253,0.08)",
1702
+ fontWeight: 800
1703
+ },
1704
+ children: "Drop files to upload"
1705
+ }
1706
+ ) : null,
1707
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 10 }, children: [
1708
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontWeight: 700 }, children: "Files" }),
1709
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1710
+ "button",
1711
+ {
1712
+ type: "button",
1713
+ onClick: openPicker,
1714
+ style: {
1715
+ border: "1px solid rgba(0,0,0,0.18)",
1716
+ background: "white",
1717
+ borderRadius: 10,
1718
+ padding: "8px 10px",
1719
+ cursor: "pointer",
1720
+ fontWeight: 600
1721
+ },
1722
+ children: "Browse\u2026"
1723
+ }
1724
+ )
1725
+ ] }),
1726
+ 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)(
1727
+ FileIconGrid,
1728
+ {
1729
+ files: containers,
1730
+ deleteDisabled,
1731
+ onDeleted: handleDeleteFile,
1732
+ selectedId,
1733
+ onSelect,
1734
+ icon,
1735
+ iconHtml
1736
+ }
1737
+ )
1738
+ ]
1739
+ }
1740
+ )
1741
+ ] });
1742
+ }
1743
+
1066
1744
  // src/components/SingleFileProcessUploader.tsx
1067
- var import_react8 = require("react");
1068
- var import_jsx_runtime6 = require("react/jsx-runtime");
1745
+ var import_react12 = require("react");
1746
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1069
1747
  var SingleFileProcessUploader = ({
1070
1748
  getPresignedUrl,
1071
1749
  onUploadComplete,
@@ -1074,28 +1752,28 @@ var SingleFileProcessUploader = ({
1074
1752
  disabled,
1075
1753
  uploadOnDrop = false
1076
1754
  }) => {
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);
1755
+ const [selectedFile, setSelectedFile] = (0, import_react12.useState)(null);
1756
+ const [isDragging, setIsDragging] = (0, import_react12.useState)(false);
1757
+ const [progress, setProgress] = (0, import_react12.useState)(null);
1758
+ const [status, setStatus] = (0, import_react12.useState)("idle");
1759
+ const fileInputRef = (0, import_react12.useRef)(null);
1082
1760
  function getErrorMessage(err) {
1083
1761
  if (err instanceof Error) return err.message;
1084
1762
  if (typeof err === "string") return err;
1085
1763
  return "Unknown error during upload.";
1086
1764
  }
1087
1765
  const isUploading = status === "uploading";
1088
- const resetUiForNewFile = (0, import_react8.useCallback)((file) => {
1766
+ const resetUiForNewFile = (0, import_react12.useCallback)((file) => {
1089
1767
  setSelectedFile(file);
1090
1768
  setStatus("idle");
1091
1769
  setProgress(null);
1092
1770
  }, []);
1093
- const handleBrowseClick = (0, import_react8.useCallback)(() => {
1771
+ const handleBrowseClick = (0, import_react12.useCallback)(() => {
1094
1772
  if (disabled) return;
1095
1773
  if (isUploading) return;
1096
1774
  fileInputRef.current?.click();
1097
1775
  }, [disabled, isUploading]);
1098
- const startUpload = (0, import_react8.useCallback)(
1776
+ const startUpload = (0, import_react12.useCallback)(
1099
1777
  async (file) => {
1100
1778
  if (disabled) return;
1101
1779
  if (isUploading) return;
@@ -1119,7 +1797,7 @@ var SingleFileProcessUploader = ({
1119
1797
  },
1120
1798
  [disabled, getPresignedUrl, isUploading, onUploadComplete, onUploadFailed]
1121
1799
  );
1122
- const handleFilesSelected = (0, import_react8.useCallback)(
1800
+ const handleFilesSelected = (0, import_react12.useCallback)(
1123
1801
  (files) => {
1124
1802
  if (!files || files.length === 0) return;
1125
1803
  if (disabled) return;
@@ -1164,8 +1842,8 @@ var SingleFileProcessUploader = ({
1164
1842
  disabled ? "opacity-50" : "cursor-pointer",
1165
1843
  isDragging ? "bg-body-secondary border-dashed border-2 border-secondary" : "bg-body-trasparent border-dashed border-2"
1166
1844
  ].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.jsx)("div", { className: "small", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "progress", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1845
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "d-flex flex-column gap-2", children: [
1846
+ 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)(
1169
1847
  "div",
1170
1848
  {
1171
1849
  className: "progress-bar progress-bar-striped progress-bar-animated",
@@ -1175,7 +1853,7 @@ var SingleFileProcessUploader = ({
1175
1853
  "aria-valuenow": progress ?? 0,
1176
1854
  style: { width: `${progress ?? 0}%` }
1177
1855
  }
1178
- ) }) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1856
+ ) }) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1179
1857
  "div",
1180
1858
  {
1181
1859
  className: dropzoneClasses,
@@ -1186,7 +1864,7 @@ var SingleFileProcessUploader = ({
1186
1864
  role: "button",
1187
1865
  "aria-disabled": disabled,
1188
1866
  children: [
1189
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1867
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1190
1868
  "input",
1191
1869
  {
1192
1870
  ref: fileInputRef,
@@ -1197,8 +1875,8 @@ var SingleFileProcessUploader = ({
1197
1875
  disabled: disabled || isUploading
1198
1876
  }
1199
1877
  ),
1200
- selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "small", children: [
1201
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1878
+ selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "small", children: [
1879
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1202
1880
  "button",
1203
1881
  {
1204
1882
  type: "button",
@@ -1207,15 +1885,15 @@ var SingleFileProcessUploader = ({
1207
1885
  children: "Browse file\u2026"
1208
1886
  }
1209
1887
  ),
1210
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1211
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Selected file:" }),
1888
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
1889
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: "Selected file:" }),
1212
1890
  " ",
1213
1891
  selectedFile.name
1214
1892
  ] }),
1215
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-muted", children: "Click here to change file or drag a new one." }),
1216
- uploadOnDrop && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-muted", children: "Upload starts automatically." })
1217
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "small", children: [
1218
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1893
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-muted", children: "Click here to change file or drag a new one." }),
1894
+ uploadOnDrop && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-muted", children: "Upload starts automatically." })
1895
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "small", children: [
1896
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1219
1897
  "button",
1220
1898
  {
1221
1899
  type: "button",
@@ -1224,13 +1902,13 @@ var SingleFileProcessUploader = ({
1224
1902
  children: "Browse file\u2026"
1225
1903
  }
1226
1904
  ),
1227
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: "Drag & drop a file here" }),
1228
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-muted", children: "or click to browse" })
1905
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: "Drag & drop a file here" }),
1906
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-muted", children: "or click to browse" })
1229
1907
  ] })
1230
1908
  ]
1231
1909
  }
1232
1910
  ),
1233
- !uploadOnDrop && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1911
+ !uploadOnDrop && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1234
1912
  "button",
1235
1913
  {
1236
1914
  type: "button",
@@ -1244,34 +1922,44 @@ var SingleFileProcessUploader = ({
1244
1922
  };
1245
1923
 
1246
1924
  // src/views/HomeView.tsx
1925
+ var import_react13 = require("react");
1247
1926
  var import_authentication_ui = require("@sparkstudio/authentication-ui");
1248
- var import_jsx_runtime7 = require("react/jsx-runtime");
1927
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1928
+ var CONTAINER_API = "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod";
1929
+ var STORAGE_API = "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod";
1249
1930
  function HomeView() {
1250
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1931
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1251
1932
  import_authentication_ui.AuthenticatorProvider,
1252
1933
  {
1253
1934
  googleClientId: import_authentication_ui.AppSettings.GoogleClientId,
1254
1935
  authenticationUrl: import_authentication_ui.AppSettings.AuthenticationUrl,
1255
1936
  accountsUrl: import_authentication_ui.AppSettings.AccountsUrl,
1256
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HomeContent, {})
1937
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(HomeContent, {})
1257
1938
  }
1258
1939
  );
1259
1940
  }
1260
1941
  function HomeContent() {
1261
1942
  const { user } = (0, import_authentication_ui.useUser)();
1943
+ const [ids, setIds] = (0, import_react13.useState)([]);
1944
+ const [selectedId, setSelectedId] = (0, import_react13.useState)(void 0);
1945
+ const [selectedFile, setSelectedFile] = (0, import_react13.useState)(null);
1946
+ (0, import_react13.useEffect)(() => {
1947
+ if (selectedId && !ids.includes(selectedId)) {
1948
+ setSelectedId(void 0);
1949
+ setSelectedFile(null);
1950
+ }
1951
+ }, [ids, selectedId]);
1262
1952
  async function getPresignedUrlFromApi(file) {
1263
- const res = new SparkStudioStorageSDK(
1264
- "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod"
1265
- //"https://localhost:5001"
1266
- );
1953
+ const sdk = new SparkStudioStorageSDK(STORAGE_API);
1267
1954
  const contentType = file.type || "application/octet-stream";
1268
- const result = await res.s3.GetTemporaryPreSignedUrl(new TemporaryFileDTO({ Name: file.name, ContentType: contentType }));
1269
- return result;
1955
+ return sdk.s3.GetTemporaryPreSignedUrl(
1956
+ new TemporaryFileDTO({ Name: file.name, ContentType: contentType })
1957
+ );
1270
1958
  }
1271
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1272
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_authentication_ui.UserInfoCard, {}),
1273
- user && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1274
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1959
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1960
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_authentication_ui.UserInfoCard, {}),
1961
+ user ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1962
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1275
1963
  SingleFileProcessUploader,
1276
1964
  {
1277
1965
  uploadOnDrop: true,
@@ -1282,14 +1970,46 @@ function HomeContent() {
1282
1970
  }
1283
1971
  }
1284
1972
  ),
1285
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1286
- ContainerUploadPanel,
1973
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1974
+ ContainerIdGridPanel,
1287
1975
  {
1288
- containerApiBaseUrl: "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod",
1289
- storageApiBaseUrl: "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod"
1976
+ containerApiBaseUrl: CONTAINER_API,
1977
+ storageApiBaseUrl: STORAGE_API,
1978
+ containerIds: ids,
1979
+ onContainerIdsChange: setIds,
1980
+ multiple: true,
1981
+ accept: "*/*",
1982
+ selectedId,
1983
+ onSelect: (file) => {
1984
+ setSelectedId(file.Id);
1985
+ setSelectedFile(file);
1986
+ },
1987
+ onDeleted: (file) => {
1988
+ console.log("Deleted:", file);
1989
+ if (selectedId === file.Id) {
1990
+ setSelectedId(void 0);
1991
+ setSelectedFile(null);
1992
+ }
1993
+ }
1290
1994
  }
1291
- )
1292
- ] })
1995
+ ),
1996
+ selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1997
+ "div",
1998
+ {
1999
+ style: {
2000
+ marginTop: 12,
2001
+ padding: 12,
2002
+ border: "1px solid rgba(0,0,0,0.12)",
2003
+ borderRadius: 12
2004
+ },
2005
+ children: [
2006
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontWeight: 700 }, children: "Selected file" }),
2007
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { children: selectedFile.Name ?? "(no name)" }),
2008
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 12, opacity: 0.7 }, children: selectedFile.PublicUrl ?? "(no public url)" })
2009
+ ]
2010
+ }
2011
+ ) : null
2012
+ ] }) : null
1293
2013
  ] });
1294
2014
  }
1295
2015
  // Annotate the CommonJS export names for ESM import in node:
@@ -1297,9 +2017,13 @@ function HomeContent() {
1297
2017
  AWSPresignedUrlDTO,
1298
2018
  Container,
1299
2019
  ContainerDTO,
2020
+ ContainerIdGridPanel,
1300
2021
  ContainerType,
1301
2022
  ContainerUploadPanel,
1302
2023
  DesktopFileIcon,
2024
+ FileGridUploadPanel,
2025
+ FileIconCard,
2026
+ FileIconGrid,
1303
2027
  Home,
1304
2028
  HomeView,
1305
2029
  S3,