@gallop.software/studio 0.1.81 → 0.1.83

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.
@@ -52,6 +52,11 @@ var defaultState = {
52
52
  },
53
53
  searchQuery: "",
54
54
  setSearchQuery: () => {
55
+ },
56
+ error: null,
57
+ showError: () => {
58
+ },
59
+ clearError: () => {
55
60
  }
56
61
  };
57
62
  var StudioContext = _react.createContext.call(void 0, defaultState);
@@ -669,13 +674,312 @@ function StudioFolderPicker({ selectedItems, currentPath, onMove, onCancel }) {
669
674
  ] }) });
670
675
  }
671
676
 
677
+ // src/components/R2SetupModal.tsx
678
+
679
+
680
+ var styles3 = {
681
+ overlay: _react3.css`
682
+ position: fixed;
683
+ inset: 0;
684
+ background: rgba(0, 0, 0, 0.6);
685
+ display: flex;
686
+ align-items: center;
687
+ justify-content: center;
688
+ z-index: 1100;
689
+ padding: 20px;
690
+ `,
691
+ modal: _react3.css`
692
+ background: ${_chunkUFCWGUAGjs.colors.surface};
693
+ border-radius: 12px;
694
+ max-width: 560px;
695
+ width: 100%;
696
+ max-height: 90vh;
697
+ overflow-y: auto;
698
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
699
+ `,
700
+ header: _react3.css`
701
+ display: flex;
702
+ align-items: center;
703
+ gap: 12px;
704
+ padding: 20px 24px;
705
+ border-bottom: 1px solid ${_chunkUFCWGUAGjs.colors.border};
706
+ `,
707
+ icon: _react3.css`
708
+ width: 32px;
709
+ height: 32px;
710
+ color: ${_chunkUFCWGUAGjs.colors.primary};
711
+ flex-shrink: 0;
712
+ `,
713
+ title: _react3.css`
714
+ font-size: ${_chunkUFCWGUAGjs.fontSize.xl};
715
+ font-weight: 600;
716
+ color: ${_chunkUFCWGUAGjs.colors.text};
717
+ margin: 0;
718
+ `,
719
+ closeBtn: _react3.css`
720
+ margin-left: auto;
721
+ background: none;
722
+ border: none;
723
+ padding: 4px;
724
+ cursor: pointer;
725
+ color: ${_chunkUFCWGUAGjs.colors.textMuted};
726
+ border-radius: 4px;
727
+
728
+ &:hover {
729
+ color: ${_chunkUFCWGUAGjs.colors.text};
730
+ background: ${_chunkUFCWGUAGjs.colors.surfaceHover};
731
+ }
732
+ `,
733
+ closeIcon: _react3.css`
734
+ width: 20px;
735
+ height: 20px;
736
+ `,
737
+ content: _react3.css`
738
+ padding: 24px;
739
+ `,
740
+ intro: _react3.css`
741
+ font-size: ${_chunkUFCWGUAGjs.fontSize.base};
742
+ color: ${_chunkUFCWGUAGjs.colors.textSecondary};
743
+ margin: 0 0 20px 0;
744
+ line-height: 1.6;
745
+ `,
746
+ steps: _react3.css`
747
+ list-style: none;
748
+ padding: 0;
749
+ margin: 0;
750
+ display: flex;
751
+ flex-direction: column;
752
+ gap: 16px;
753
+ `,
754
+ step: _react3.css`
755
+ display: flex;
756
+ gap: 12px;
757
+ `,
758
+ stepNumber: _react3.css`
759
+ width: 28px;
760
+ height: 28px;
761
+ border-radius: 50%;
762
+ background: ${_chunkUFCWGUAGjs.colors.primaryLight};
763
+ color: ${_chunkUFCWGUAGjs.colors.primary};
764
+ font-size: ${_chunkUFCWGUAGjs.fontSize.sm};
765
+ font-weight: 600;
766
+ display: flex;
767
+ align-items: center;
768
+ justify-content: center;
769
+ flex-shrink: 0;
770
+ `,
771
+ stepContent: _react3.css`
772
+ flex: 1;
773
+ padding-top: 3px;
774
+ `,
775
+ stepTitle: _react3.css`
776
+ font-size: ${_chunkUFCWGUAGjs.fontSize.base};
777
+ font-weight: 500;
778
+ color: ${_chunkUFCWGUAGjs.colors.text};
779
+ margin: 0 0 4px 0;
780
+ `,
781
+ stepDesc: _react3.css`
782
+ font-size: ${_chunkUFCWGUAGjs.fontSize.sm};
783
+ color: ${_chunkUFCWGUAGjs.colors.textSecondary};
784
+ margin: 0;
785
+ line-height: 1.5;
786
+ `,
787
+ link: _react3.css`
788
+ color: ${_chunkUFCWGUAGjs.colors.primary};
789
+ text-decoration: none;
790
+ font-weight: 500;
791
+
792
+ &:hover {
793
+ text-decoration: underline;
794
+ }
795
+ `,
796
+ envVars: _react3.css`
797
+ background: ${_chunkUFCWGUAGjs.colors.background};
798
+ border: 1px solid ${_chunkUFCWGUAGjs.colors.border};
799
+ border-radius: 8px;
800
+ padding: 16px;
801
+ margin-top: 20px;
802
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
803
+ font-size: 13px;
804
+ line-height: 1.8;
805
+ color: ${_chunkUFCWGUAGjs.colors.text};
806
+ overflow-x: auto;
807
+ `,
808
+ envVar: _react3.css`
809
+ display: block;
810
+ `,
811
+ envKey: _react3.css`
812
+ color: ${_chunkUFCWGUAGjs.colors.primary};
813
+ `,
814
+ envValue: _react3.css`
815
+ color: ${_chunkUFCWGUAGjs.colors.textSecondary};
816
+ `,
817
+ footer: _react3.css`
818
+ padding: 16px 24px;
819
+ border-top: 1px solid ${_chunkUFCWGUAGjs.colors.border};
820
+ display: flex;
821
+ justify-content: flex-end;
822
+ gap: 12px;
823
+ `,
824
+ docsBtn: _react3.css`
825
+ padding: 10px 16px;
826
+ border-radius: 6px;
827
+ font-size: ${_chunkUFCWGUAGjs.fontSize.base};
828
+ font-weight: 500;
829
+ border: 1px solid ${_chunkUFCWGUAGjs.colors.border};
830
+ background: ${_chunkUFCWGUAGjs.colors.surface};
831
+ color: ${_chunkUFCWGUAGjs.colors.text};
832
+ cursor: pointer;
833
+ text-decoration: none;
834
+ display: inline-flex;
835
+ align-items: center;
836
+ gap: 6px;
837
+ transition: all 0.15s ease;
838
+
839
+ &:hover {
840
+ background: ${_chunkUFCWGUAGjs.colors.surfaceHover};
841
+ border-color: #d0d5dd;
842
+ }
843
+ `,
844
+ doneBtn: _react3.css`
845
+ padding: 10px 20px;
846
+ border-radius: 6px;
847
+ font-size: ${_chunkUFCWGUAGjs.fontSize.base};
848
+ font-weight: 500;
849
+ border: none;
850
+ background: ${_chunkUFCWGUAGjs.colors.primary};
851
+ color: white;
852
+ cursor: pointer;
853
+ transition: background 0.15s ease;
854
+
855
+ &:hover {
856
+ background: ${_chunkUFCWGUAGjs.colors.primaryHover};
857
+ }
858
+ `,
859
+ externalIcon: _react3.css`
860
+ width: 14px;
861
+ height: 14px;
862
+ `
863
+ };
864
+ function R2SetupModal({ isOpen, onClose }) {
865
+ if (!isOpen) return null;
866
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.overlay, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.modal, onClick: (e) => e.stopPropagation(), children: [
867
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.header, children: [
868
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" }) }),
869
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { css: styles3.title, children: "Set Up CDN Storage" }),
870
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles3.closeBtn, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.closeIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
871
+ ] }),
872
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.content, children: [
873
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles3.intro, children: "Sync your images to Cloudflare R2 for faster global delivery. R2 offers generous free tier with no egress fees." }),
874
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "ol", { css: styles3.steps, children: [
875
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { css: styles3.step, children: [
876
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.stepNumber, children: "1" }),
877
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.stepContent, children: [
878
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h4", { css: styles3.stepTitle, children: "Create a Cloudflare account" }),
879
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles3.stepDesc, children: [
880
+ "Sign up at",
881
+ " ",
882
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "a", { css: styles3.link, href: "https://dash.cloudflare.com/sign-up", target: "_blank", rel: "noopener noreferrer", children: "dash.cloudflare.com" }),
883
+ " ",
884
+ "if you don't have one already."
885
+ ] })
886
+ ] })
887
+ ] }),
888
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { css: styles3.step, children: [
889
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.stepNumber, children: "2" }),
890
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.stepContent, children: [
891
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h4", { css: styles3.stepTitle, children: "Create an R2 bucket" }),
892
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles3.stepDesc, children: [
893
+ "Go to R2 in your Cloudflare dashboard and create a new bucket. Choose a name like ",
894
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "code", { children: "my-images" }),
895
+ "."
896
+ ] })
897
+ ] })
898
+ ] }),
899
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { css: styles3.step, children: [
900
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.stepNumber, children: "3" }),
901
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.stepContent, children: [
902
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h4", { css: styles3.stepTitle, children: "Enable public access" }),
903
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles3.stepDesc, children: [
904
+ 'In bucket settings, enable "Public Access" and copy the public URL (e.g., ',
905
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "code", { children: "https://pub-xxx.r2.dev" }),
906
+ ")."
907
+ ] })
908
+ ] })
909
+ ] }),
910
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { css: styles3.step, children: [
911
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.stepNumber, children: "4" }),
912
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.stepContent, children: [
913
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h4", { css: styles3.stepTitle, children: "Create API token" }),
914
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles3.stepDesc, children: 'Go to R2 \u2192 Manage R2 API Tokens \u2192 Create API Token. Select "Object Read & Write" permissions for your bucket.' })
915
+ ] })
916
+ ] }),
917
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { css: styles3.step, children: [
918
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.stepNumber, children: "5" }),
919
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.stepContent, children: [
920
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h4", { css: styles3.stepTitle, children: "Add environment variables" }),
921
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles3.stepDesc, children: [
922
+ "Add these to your ",
923
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "code", { children: ".env.local" }),
924
+ " file:"
925
+ ] })
926
+ ] })
927
+ ] })
928
+ ] }),
929
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.envVars, children: [
930
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles3.envVar, children: [
931
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envKey, children: "CLOUDFLARE_R2_ACCOUNT_ID" }),
932
+ "=",
933
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envValue, children: "your_account_id" })
934
+ ] }),
935
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles3.envVar, children: [
936
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envKey, children: "CLOUDFLARE_R2_ACCESS_KEY_ID" }),
937
+ "=",
938
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envValue, children: "your_access_key" })
939
+ ] }),
940
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles3.envVar, children: [
941
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envKey, children: "CLOUDFLARE_R2_SECRET_ACCESS_KEY" }),
942
+ "=",
943
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envValue, children: "your_secret_key" })
944
+ ] }),
945
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles3.envVar, children: [
946
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envKey, children: "CLOUDFLARE_R2_BUCKET_NAME" }),
947
+ "=",
948
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envValue, children: "your_bucket_name" })
949
+ ] }),
950
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles3.envVar, children: [
951
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envKey, children: "CLOUDFLARE_R2_PUBLIC_URL" }),
952
+ "=",
953
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envValue, children: "https://pub-xxx.r2.dev" })
954
+ ] })
955
+ ] })
956
+ ] }),
957
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.footer, children: [
958
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
959
+ "a",
960
+ {
961
+ css: styles3.docsBtn,
962
+ href: "https://developers.cloudflare.com/r2/get-started/",
963
+ target: "_blank",
964
+ rel: "noopener noreferrer",
965
+ children: [
966
+ "R2 Documentation",
967
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.externalIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" }) })
968
+ ]
969
+ }
970
+ ),
971
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles3.doneBtn, onClick: onClose, children: "Got it" })
972
+ ] })
973
+ ] }) });
974
+ }
975
+
672
976
  // src/components/StudioToolbar.tsx
673
977
 
674
978
  var btnHeight = "36px";
675
979
  var spin = _react3.keyframes`
676
980
  to { transform: rotate(360deg); }
677
981
  `;
678
- var styles3 = {
982
+ var styles4 = {
679
983
  toolbar: _react3.css`
680
984
  display: flex;
681
985
  flex-wrap: nowrap;
@@ -893,6 +1197,8 @@ function StudioToolbar() {
893
1197
  const [showNewFolderModal, setShowNewFolderModal] = _react.useState.call(void 0, false);
894
1198
  const [showRenameFolderModal, setShowRenameFolderModal] = _react.useState.call(void 0, false);
895
1199
  const [showMoveModal, setShowMoveModal] = _react.useState.call(void 0, false);
1200
+ const [showR2SetupModal, setShowR2SetupModal] = _react.useState.call(void 0, false);
1201
+ const [syncing, setSyncing] = _react.useState.call(void 0, false);
896
1202
  const isInImagesFolder = currentPath === "public/images" || currentPath.startsWith("public/images/");
897
1203
  const handleUpload = _react.useCallback.call(void 0, () => {
898
1204
  _optionalChain([fileInputRef, 'access', _2 => _2.current, 'optionalAccess', _3 => _3.click, 'call', _4 => _4()]);
@@ -1189,9 +1495,60 @@ function StudioToolbar() {
1189
1495
  });
1190
1496
  }
1191
1497
  }, [selectedItems, clearSelection, triggerRefresh]);
1192
- const handleSyncCdn = _react.useCallback.call(void 0, () => {
1193
- console.log("Sync CDN clicked", selectedItems);
1194
- }, [selectedItems]);
1498
+ const handleSyncCdn = _react.useCallback.call(void 0, async () => {
1499
+ if (selectedItems.size === 0) return;
1500
+ const imageKeys = Array.from(selectedItems).filter((p) => !p.endsWith("/")).map((p) => "/" + p.replace(/^public\//, ""));
1501
+ if (imageKeys.length === 0) {
1502
+ setAlertMessage({
1503
+ title: "No Images Selected",
1504
+ message: "Please select image files to sync to CDN."
1505
+ });
1506
+ return;
1507
+ }
1508
+ setSyncing(true);
1509
+ try {
1510
+ const response = await fetch("/api/studio/sync", {
1511
+ method: "POST",
1512
+ headers: { "Content-Type": "application/json" },
1513
+ body: JSON.stringify({ imageKeys })
1514
+ });
1515
+ const data = await response.json();
1516
+ if (response.ok) {
1517
+ const syncedCount = _optionalChain([data, 'access', _19 => _19.synced, 'optionalAccess', _20 => _20.length]) || 0;
1518
+ const errorCount = _optionalChain([data, 'access', _21 => _21.errors, 'optionalAccess', _22 => _22.length]) || 0;
1519
+ if (errorCount > 0) {
1520
+ setAlertMessage({
1521
+ title: "Sync Partially Complete",
1522
+ message: `Synced ${syncedCount} images. ${errorCount} failed.`
1523
+ });
1524
+ } else {
1525
+ setAlertMessage({
1526
+ title: "Sync Complete",
1527
+ message: `Successfully synced ${syncedCount} images to CDN.`
1528
+ });
1529
+ }
1530
+ clearSelection();
1531
+ triggerRefresh();
1532
+ } else {
1533
+ if (_optionalChain([data, 'access', _23 => _23.error, 'optionalAccess', _24 => _24.includes, 'call', _25 => _25("R2 not configured")]) || _optionalChain([data, 'access', _26 => _26.error, 'optionalAccess', _27 => _27.includes, 'call', _28 => _28("CLOUDFLARE_R2")])) {
1534
+ setShowR2SetupModal(true);
1535
+ } else {
1536
+ setAlertMessage({
1537
+ title: "Sync Failed",
1538
+ message: data.error || "Failed to sync to CDN."
1539
+ });
1540
+ }
1541
+ }
1542
+ } catch (error) {
1543
+ console.error("Sync error:", error);
1544
+ setAlertMessage({
1545
+ title: "Sync Failed",
1546
+ message: "Failed to sync to CDN. Check console for details."
1547
+ });
1548
+ } finally {
1549
+ setSyncing(false);
1550
+ }
1551
+ }, [selectedItems, clearSelection, triggerRefresh]);
1195
1552
  const handleCreateFolder = _react.useCallback.call(void 0, async (folderName) => {
1196
1553
  setShowNewFolderModal(false);
1197
1554
  try {
@@ -1370,7 +1727,14 @@ function StudioToolbar() {
1370
1727
  onClose: () => setAlertMessage(null)
1371
1728
  }
1372
1729
  ),
1373
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.toolbar, children: [
1730
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1731
+ R2SetupModal,
1732
+ {
1733
+ isOpen: showR2SetupModal,
1734
+ onClose: () => setShowR2SetupModal(false)
1735
+ }
1736
+ ),
1737
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.toolbar, children: [
1374
1738
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1375
1739
  "input",
1376
1740
  {
@@ -1382,11 +1746,11 @@ function StudioToolbar() {
1382
1746
  style: { display: "none" }
1383
1747
  }
1384
1748
  ),
1385
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.left, children: [
1749
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.left, children: [
1386
1750
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1387
1751
  "button",
1388
1752
  {
1389
- css: [styles3.btn, styles3.btnPrimary],
1753
+ css: [styles4.btn, styles4.btnPrimary],
1390
1754
  onClick: handleUpload,
1391
1755
  disabled: uploading || isInImagesFolder,
1392
1756
  children: [
@@ -1398,7 +1762,7 @@ function StudioToolbar() {
1398
1762
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1399
1763
  "button",
1400
1764
  {
1401
- css: styles3.btn,
1765
+ css: styles4.btn,
1402
1766
  onClick: () => singleFolderSelected ? setShowRenameFolderModal(true) : setShowNewFolderModal(true),
1403
1767
  disabled: isInImagesFolder && !singleFolderSelected,
1404
1768
  title: isInImagesFolder && !singleFolderSelected ? "Cannot create folders in protected images folder" : void 0,
@@ -1408,11 +1772,11 @@ function StudioToolbar() {
1408
1772
  ]
1409
1773
  }
1410
1774
  ),
1411
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.divider }),
1775
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.divider }),
1412
1776
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1413
1777
  "button",
1414
1778
  {
1415
- css: styles3.btn,
1779
+ css: styles4.btn,
1416
1780
  onClick: handleProcessImages,
1417
1781
  disabled: processing || isInImagesFolder,
1418
1782
  title: isInImagesFolder ? "Cannot process images folder" : void 0,
@@ -1425,7 +1789,7 @@ function StudioToolbar() {
1425
1789
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1426
1790
  "button",
1427
1791
  {
1428
- css: [styles3.btn, styles3.btnDanger],
1792
+ css: [styles4.btn, styles4.btnDanger],
1429
1793
  onClick: handleDeleteClick,
1430
1794
  disabled: !hasSelection,
1431
1795
  children: [
@@ -1437,7 +1801,7 @@ function StudioToolbar() {
1437
1801
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1438
1802
  "button",
1439
1803
  {
1440
- css: styles3.btn,
1804
+ css: styles4.btn,
1441
1805
  onClick: handleMoveClick,
1442
1806
  disabled: !hasSelection,
1443
1807
  children: [
@@ -1449,20 +1813,20 @@ function StudioToolbar() {
1449
1813
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1450
1814
  "button",
1451
1815
  {
1452
- css: styles3.btn,
1816
+ css: styles4.btn,
1453
1817
  onClick: handleSyncCdn,
1454
- disabled: !hasSelection,
1818
+ disabled: !hasSelection || syncing,
1455
1819
  children: [
1456
1820
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloudIcon, {}),
1457
- "Sync CDN"
1821
+ syncing ? "Syncing..." : "Sync CDN"
1458
1822
  ]
1459
1823
  }
1460
1824
  ),
1461
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.searchWrapper, children: [
1825
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.searchWrapper, children: [
1462
1826
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1463
1827
  "input",
1464
1828
  {
1465
- css: styles3.searchInput,
1829
+ css: styles4.searchInput,
1466
1830
  type: "text",
1467
1831
  placeholder: "Search images...",
1468
1832
  value: searchQuery,
@@ -1473,7 +1837,7 @@ function StudioToolbar() {
1473
1837
  searchQuery && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1474
1838
  "button",
1475
1839
  {
1476
- css: styles3.searchClearBtn,
1840
+ css: styles4.searchClearBtn,
1477
1841
  onClick: () => setSearchQuery(""),
1478
1842
  title: "Clear search",
1479
1843
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { width: "14", height: "14", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
@@ -1481,25 +1845,25 @@ function StudioToolbar() {
1481
1845
  )
1482
1846
  ] })
1483
1847
  ] }),
1484
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.right, children: [
1485
- hasSelection && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles3.selectionCount, children: [
1848
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.right, children: [
1849
+ hasSelection && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles4.selectionCount, children: [
1486
1850
  selectedItems.size,
1487
1851
  " selected",
1488
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles3.clearBtn, onClick: clearSelection, children: "Clear" })
1852
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles4.clearBtn, onClick: clearSelection, children: "Clear" })
1489
1853
  ] }),
1490
1854
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1491
1855
  "button",
1492
1856
  {
1493
- css: [styles3.btn, styles3.btnIconOnly],
1857
+ css: [styles4.btn, styles4.btnIconOnly],
1494
1858
  onClick: handleRefresh,
1495
1859
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RefreshIcon, { spinning: refreshing })
1496
1860
  }
1497
1861
  ),
1498
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.viewToggle, children: [
1862
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.viewToggle, children: [
1499
1863
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1500
1864
  "button",
1501
1865
  {
1502
- css: [styles3.viewBtn, viewMode === "grid" && styles3.viewBtnActive],
1866
+ css: [styles4.viewBtn, viewMode === "grid" && styles4.viewBtnActive],
1503
1867
  onClick: () => setViewMode("grid"),
1504
1868
  "aria-label": "Grid view",
1505
1869
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, GridIcon, {})
@@ -1508,7 +1872,7 @@ function StudioToolbar() {
1508
1872
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1509
1873
  "button",
1510
1874
  {
1511
- css: [styles3.viewBtn, viewMode === "list" && styles3.viewBtnActive],
1875
+ css: [styles4.viewBtn, viewMode === "list" && styles4.viewBtnActive],
1512
1876
  onClick: () => setViewMode("list"),
1513
1877
  "aria-label": "List view",
1514
1878
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ListIcon, {})
@@ -1520,44 +1884,232 @@ function StudioToolbar() {
1520
1884
  ] });
1521
1885
  }
1522
1886
  function UploadIcon() {
1523
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) });
1887
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) });
1524
1888
  }
1525
1889
  function RefreshIcon({ spinning }) {
1526
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: [styles3.icon, spinning && styles3.iconSpin], fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) });
1890
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: [styles4.icon, spinning && styles4.iconSpin], fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) });
1527
1891
  }
1528
1892
  function TrashIcon() {
1529
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) });
1893
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) });
1530
1894
  }
1531
1895
  function FolderPlusIcon() {
1532
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" }) });
1896
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" }) });
1533
1897
  }
1534
1898
  function RenameIcon() {
1535
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) });
1899
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) });
1536
1900
  }
1537
1901
  function MoveIcon() {
1538
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" }) });
1902
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" }) });
1539
1903
  }
1540
1904
  function CloudIcon() {
1541
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) });
1905
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) });
1542
1906
  }
1543
1907
  function GridIcon() {
1544
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" }) });
1908
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" }) });
1545
1909
  }
1546
1910
  function ListIcon() {
1547
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 10h16M4 14h16M4 18h16" }) });
1911
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 10h16M4 14h16M4 18h16" }) });
1548
1912
  }
1549
1913
  function ImageStackIcon() {
1550
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) });
1914
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) });
1915
+ }
1916
+
1917
+ // src/components/StudioFileGrid.tsx
1918
+
1919
+
1920
+
1921
+ // src/hooks/useFileList.ts
1922
+
1923
+
1924
+ // src/lib/api.ts
1925
+ var StudioApiClient = class {
1926
+ async get(url) {
1927
+ const response = await fetch(url);
1928
+ if (!response.ok) {
1929
+ const data = await response.json().catch(() => ({}));
1930
+ throw new Error(data.error || `Request failed: ${response.status}`);
1931
+ }
1932
+ return response.json();
1933
+ }
1934
+ async post(url, body) {
1935
+ const response = await fetch(url, {
1936
+ method: "POST",
1937
+ headers: body ? { "Content-Type": "application/json" } : void 0,
1938
+ body: body ? JSON.stringify(body) : void 0
1939
+ });
1940
+ if (!response.ok) {
1941
+ const data = await response.json().catch(() => ({}));
1942
+ throw new Error(data.error || `Request failed: ${response.status}`);
1943
+ }
1944
+ return response.json();
1945
+ }
1946
+ // List handlers
1947
+ async list(path = "public") {
1948
+ return this.get(`/api/studio/list?path=${encodeURIComponent(path)}`);
1949
+ }
1950
+ async search(query) {
1951
+ return this.get(`/api/studio/search?q=${encodeURIComponent(query)}`);
1952
+ }
1953
+ async listFolders() {
1954
+ return this.get("/api/studio/list-folders");
1955
+ }
1956
+ async countImages() {
1957
+ return this.get("/api/studio/count-images");
1958
+ }
1959
+ async folderImages(folders) {
1960
+ return this.get(`/api/studio/folder-images?folders=${encodeURIComponent(folders.join(","))}`);
1961
+ }
1962
+ // File handlers
1963
+ async upload(file, targetPath = "public") {
1964
+ const formData = new FormData();
1965
+ formData.append("file", file);
1966
+ formData.append("path", targetPath);
1967
+ const response = await fetch("/api/studio/upload", {
1968
+ method: "POST",
1969
+ body: formData
1970
+ });
1971
+ if (!response.ok) {
1972
+ const data = await response.json().catch(() => ({}));
1973
+ throw new Error(data.error || `Upload failed: ${response.status}`);
1974
+ }
1975
+ return response.json();
1976
+ }
1977
+ async delete(paths) {
1978
+ return this.post("/api/studio/delete", { paths });
1979
+ }
1980
+ async createFolder(parentPath, name) {
1981
+ return this.post("/api/studio/create-folder", { parentPath, name });
1982
+ }
1983
+ async rename(oldPath, newName) {
1984
+ return this.post("/api/studio/rename", { oldPath, newName });
1985
+ }
1986
+ async move(paths, destination) {
1987
+ return this.post("/api/studio/move", { paths, destination });
1988
+ }
1989
+ // Image handlers
1990
+ async sync(imageKeys) {
1991
+ return this.post("/api/studio/sync", { imageKeys });
1992
+ }
1993
+ async reprocess(imageKeys) {
1994
+ return this.post("/api/studio/reprocess", { imageKeys });
1995
+ }
1996
+ // Process all returns a stream, handle separately
1997
+ processAllStream() {
1998
+ return new EventSource("/api/studio/process-all");
1999
+ }
2000
+ };
2001
+ var studioApi = new StudioApiClient();
2002
+
2003
+ // src/hooks/useFileList.ts
2004
+ function useFileList() {
2005
+ const {
2006
+ currentPath,
2007
+ setCurrentPath,
2008
+ navigateUp,
2009
+ selectedItems,
2010
+ toggleSelection,
2011
+ selectRange,
2012
+ lastSelectedPath,
2013
+ selectAll,
2014
+ clearSelection,
2015
+ refreshKey,
2016
+ setFocusedItem,
2017
+ triggerRefresh,
2018
+ searchQuery,
2019
+ showError
2020
+ } = useStudio();
2021
+ const [items, setItems] = _react.useState.call(void 0, []);
2022
+ const [loading, setLoading] = _react.useState.call(void 0, true);
2023
+ const isInitialLoad = _react.useRef.call(void 0, true);
2024
+ const lastPath = _react.useRef.call(void 0, currentPath);
2025
+ _react.useEffect.call(void 0, () => {
2026
+ async function loadItems() {
2027
+ const isPathChange = lastPath.current !== currentPath;
2028
+ if (isInitialLoad.current || isPathChange) {
2029
+ setLoading(true);
2030
+ }
2031
+ lastPath.current = currentPath;
2032
+ try {
2033
+ const data = searchQuery && searchQuery.length >= 2 ? await studioApi.search(searchQuery) : await studioApi.list(currentPath);
2034
+ setItems(data.items || []);
2035
+ } catch (error) {
2036
+ const message = error instanceof Error ? error.message : "Failed to load items";
2037
+ showError("Load Error", message);
2038
+ setItems([]);
2039
+ }
2040
+ setLoading(false);
2041
+ isInitialLoad.current = false;
2042
+ }
2043
+ loadItems();
2044
+ }, [currentPath, refreshKey, searchQuery, showError]);
2045
+ const isAtRoot = currentPath === "public";
2046
+ const isSearching = searchQuery && searchQuery.length >= 2;
2047
+ const sortedItems = [...items].sort((a, b) => {
2048
+ if (a.type === "folder" && b.type !== "folder") return -1;
2049
+ if (a.type !== "folder" && b.type === "folder") return 1;
2050
+ return a.name.localeCompare(b.name);
2051
+ });
2052
+ const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
2053
+ const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
2054
+ const handleItemClick = _react.useCallback.call(void 0, (item, e) => {
2055
+ if (e.shiftKey && lastSelectedPath) {
2056
+ selectRange(lastSelectedPath, item.path, sortedItems);
2057
+ } else {
2058
+ toggleSelection(item.path);
2059
+ }
2060
+ }, [lastSelectedPath, selectRange, sortedItems, toggleSelection]);
2061
+ const handleOpen = _react.useCallback.call(void 0, (item) => {
2062
+ if (item.type === "folder") {
2063
+ setCurrentPath(item.path);
2064
+ } else {
2065
+ setFocusedItem(item);
2066
+ }
2067
+ }, [setCurrentPath, setFocusedItem]);
2068
+ const handleGenerateThumbnail = _react.useCallback.call(void 0, async (item) => {
2069
+ try {
2070
+ const imageKey = "/" + item.path.replace(/^public\//, "");
2071
+ await studioApi.reprocess([imageKey]);
2072
+ triggerRefresh();
2073
+ } catch (error) {
2074
+ const message = error instanceof Error ? error.message : "Failed to generate thumbnail";
2075
+ showError("Processing Error", message);
2076
+ }
2077
+ }, [triggerRefresh, showError]);
2078
+ const handleSelectAll = _react.useCallback.call(void 0, () => {
2079
+ if (allItemsSelected) {
2080
+ clearSelection();
2081
+ } else {
2082
+ selectAll(sortedItems);
2083
+ }
2084
+ }, [allItemsSelected, clearSelection, selectAll, sortedItems]);
2085
+ return {
2086
+ // State
2087
+ items,
2088
+ loading,
2089
+ sortedItems,
2090
+ // Computed
2091
+ isAtRoot,
2092
+ isSearching,
2093
+ allItemsSelected,
2094
+ someItemsSelected,
2095
+ // Context values
2096
+ currentPath,
2097
+ selectedItems,
2098
+ navigateUp,
2099
+ // Handlers
2100
+ handleItemClick,
2101
+ handleOpen,
2102
+ handleGenerateThumbnail,
2103
+ handleSelectAll
2104
+ };
1551
2105
  }
1552
2106
 
1553
2107
  // src/components/StudioFileGrid.tsx
1554
2108
 
1555
-
1556
-
1557
2109
  var spin2 = _react3.keyframes`
1558
2110
  to { transform: rotate(360deg); }
1559
2111
  `;
1560
- var styles4 = {
2112
+ var styles5 = {
1561
2113
  loading: _react3.css`
1562
2114
  display: flex;
1563
2115
  align-items: center;
@@ -1875,93 +2427,37 @@ var styles4 = {
1875
2427
  `
1876
2428
  };
1877
2429
  function StudioFileGrid() {
1878
- const { currentPath, setCurrentPath, navigateUp, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey, setFocusedItem, triggerRefresh, searchQuery } = useStudio();
1879
- const [items, setItems] = _react.useState.call(void 0, []);
1880
- const [loading, setLoading] = _react.useState.call(void 0, true);
1881
- const isInitialLoad = _react.useRef.call(void 0, true);
1882
- const lastPath = _react.useRef.call(void 0, currentPath);
1883
- _react.useEffect.call(void 0, () => {
1884
- async function loadItems() {
1885
- const isPathChange = lastPath.current !== currentPath;
1886
- if (isInitialLoad.current || isPathChange) {
1887
- setLoading(true);
1888
- }
1889
- lastPath.current = currentPath;
1890
- try {
1891
- const url = searchQuery && searchQuery.length >= 2 ? `/api/studio/search?q=${encodeURIComponent(searchQuery)}` : `/api/studio/list?path=${encodeURIComponent(currentPath)}`;
1892
- const response = await fetch(url);
1893
- if (response.ok) {
1894
- const data = await response.json();
1895
- setItems(data.items || []);
1896
- }
1897
- } catch (error) {
1898
- console.error("Failed to load items:", error);
1899
- }
1900
- setLoading(false);
1901
- isInitialLoad.current = false;
1902
- }
1903
- loadItems();
1904
- }, [currentPath, refreshKey, searchQuery]);
2430
+ const {
2431
+ loading,
2432
+ sortedItems,
2433
+ isAtRoot,
2434
+ isSearching,
2435
+ allItemsSelected,
2436
+ someItemsSelected,
2437
+ selectedItems,
2438
+ navigateUp,
2439
+ handleItemClick,
2440
+ handleOpen,
2441
+ handleGenerateThumbnail,
2442
+ handleSelectAll
2443
+ } = useFileList();
1905
2444
  if (loading) {
1906
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.spinner }) });
2445
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.spinner }) });
1907
2446
  }
1908
- const isAtRoot = currentPath === "public";
1909
- if (items.length === 0 && isAtRoot) {
1910
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.empty, children: [
1911
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
1912
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.emptyText, children: "No files in this folder" }),
1913
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.emptyText, children: "Upload images to get started" })
2447
+ if (sortedItems.length === 0 && isAtRoot) {
2448
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.empty, children: [
2449
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
2450
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.emptyText, children: "No files in this folder" }),
2451
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.emptyText, children: "Upload images to get started" })
1914
2452
  ] });
1915
2453
  }
1916
- const isSearching = searchQuery && searchQuery.length >= 2;
1917
- const sortedItems = [...items].sort((a, b) => {
1918
- if (a.type === "folder" && b.type !== "folder") return -1;
1919
- if (a.type !== "folder" && b.type === "folder") return 1;
1920
- return a.name.localeCompare(b.name);
1921
- });
1922
- const handleItemClick = (item, e) => {
1923
- if (e.shiftKey && lastSelectedPath) {
1924
- selectRange(lastSelectedPath, item.path, sortedItems);
1925
- } else {
1926
- toggleSelection(item.path);
1927
- }
1928
- };
1929
- const handleOpen = (item) => {
1930
- if (item.type === "folder") {
1931
- setCurrentPath(item.path);
1932
- } else {
1933
- setFocusedItem(item);
1934
- }
1935
- };
1936
- const handleGenerateThumbnail = async (item) => {
1937
- try {
1938
- const imageKey = item.path.replace(/^public\//, "");
1939
- await fetch("/api/studio/reprocess", {
1940
- method: "POST",
1941
- headers: { "Content-Type": "application/json" },
1942
- body: JSON.stringify({ imageKeys: [imageKey] })
1943
- });
1944
- triggerRefresh();
1945
- } catch (error) {
1946
- console.error("Failed to generate thumbnail:", error);
1947
- }
1948
- };
1949
- const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
1950
- const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
1951
- const handleSelectAll = () => {
1952
- if (allItemsSelected) {
1953
- clearSelection();
1954
- } else {
1955
- selectAll(sortedItems);
1956
- }
1957
- };
1958
2454
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
1959
- sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.selectAllRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "label", { css: styles4.selectAllLabel, children: [
2455
+ sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.selectAllRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "label", { css: styles5.selectAllLabel, children: [
1960
2456
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1961
2457
  "input",
1962
2458
  {
1963
2459
  type: "checkbox",
1964
- css: styles4.selectAllCheckbox,
2460
+ css: styles5.selectAllCheckbox,
1965
2461
  checked: allItemsSelected,
1966
2462
  ref: (el) => {
1967
2463
  if (el) el.indeterminate = someItemsSelected && !allItemsSelected;
@@ -1973,17 +2469,17 @@ function StudioFileGrid() {
1973
2469
  sortedItems.length,
1974
2470
  ")"
1975
2471
  ] }) }),
1976
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.grid, children: [
2472
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.grid, children: [
1977
2473
  !isAtRoot && !isSearching && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1978
2474
  "div",
1979
2475
  {
1980
- css: [styles4.item, styles4.parentItem],
2476
+ css: [styles5.item, styles5.parentItem],
1981
2477
  onClick: navigateUp,
1982
2478
  children: [
1983
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.content, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.parentIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" }) }) }),
1984
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.label, children: [
1985
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.name, children: ".." }),
1986
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.size, children: "Parent folder" })
2479
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.content, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.parentIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" }) }) }),
2480
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.label, children: [
2481
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.name, children: ".." }),
2482
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.size, children: "Parent folder" })
1987
2483
  ] })
1988
2484
  ]
1989
2485
  }
@@ -2017,43 +2513,43 @@ function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
2017
2513
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2018
2514
  "div",
2019
2515
  {
2020
- css: [styles4.item, isSelected && styles4.itemSelected],
2516
+ css: [styles5.item, isSelected && styles5.itemSelected],
2021
2517
  onClick,
2022
2518
  children: [
2023
2519
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2024
2520
  "div",
2025
2521
  {
2026
- css: styles4.checkboxWrapper,
2522
+ css: styles5.checkboxWrapper,
2027
2523
  onClick: (e) => e.stopPropagation(),
2028
2524
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2029
2525
  "input",
2030
2526
  {
2031
2527
  type: "checkbox",
2032
- css: styles4.checkbox,
2528
+ css: styles5.checkbox,
2033
2529
  checked: isSelected,
2034
2530
  onChange: () => onClick({})
2035
2531
  }
2036
2532
  )
2037
2533
  }
2038
2534
  ),
2039
- item.cdnSynced && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles4.cdnBadge, children: "CDN" }),
2040
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.content, children: [
2535
+ item.cdnSynced && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.cdnBadge, children: "CDN" }),
2536
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.content, children: [
2041
2537
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2042
2538
  "button",
2043
2539
  {
2044
- css: styles4.copyBtn,
2540
+ css: styles5.copyBtn,
2045
2541
  onClick: handleCopyPath,
2046
2542
  title: "Copy file path",
2047
2543
  children: [
2048
- showCopied && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles4.tooltip, children: "Copied!" }),
2049
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" }) })
2544
+ showCopied && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.tooltip, children: "Copied!" }),
2545
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" }) })
2050
2546
  ]
2051
2547
  }
2052
2548
  ),
2053
2549
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2054
2550
  "button",
2055
2551
  {
2056
- css: styles4.openBtn,
2552
+ css: styles5.openBtn,
2057
2553
  onClick: (e) => {
2058
2554
  e.stopPropagation();
2059
2555
  onOpen();
@@ -2061,13 +2557,13 @@ function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
2061
2557
  children: "Open"
2062
2558
  }
2063
2559
  ),
2064
- isFolder ? isImagesFolder ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.imagesFolderWrapper, children: [
2065
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.imagesFolderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }),
2066
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.lockIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { fillRule: "evenodd", d: "M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z", clipRule: "evenodd" }) })
2067
- ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }) : isImage && item.hasThumbnail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2560
+ isFolder ? isImagesFolder ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.imagesFolderWrapper, children: [
2561
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.imagesFolderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }),
2562
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.lockIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { fillRule: "evenodd", d: "M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z", clipRule: "evenodd" }) })
2563
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }) : isImage && item.hasThumbnail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2068
2564
  "img",
2069
2565
  {
2070
- css: styles4.image,
2566
+ css: styles5.image,
2071
2567
  src: item.thumbnail,
2072
2568
  alt: item.name,
2073
2569
  loading: "lazy"
@@ -2075,26 +2571,26 @@ function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
2075
2571
  ) : isImage && !item.hasThumbnail ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2076
2572
  "button",
2077
2573
  {
2078
- css: styles4.noThumbnail,
2574
+ css: styles5.noThumbnail,
2079
2575
  onClick: (e) => {
2080
2576
  e.stopPropagation();
2081
2577
  onGenerateThumbnail();
2082
2578
  },
2083
2579
  title: "Generate thumbnail",
2084
2580
  children: [
2085
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.noThumbnailIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
2086
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles4.noThumbnailText, children: "Generate" })
2581
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.noThumbnailIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
2582
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.noThumbnailText, children: "Generate" })
2087
2583
  ]
2088
2584
  }
2089
- ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) })
2585
+ ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) })
2090
2586
  ] }),
2091
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.label, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.labelRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.labelText, children: [
2092
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.name, title: item.name, children: truncateMiddle(item.name) }),
2093
- isFolder ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles4.size, children: [
2587
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.label, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.labelRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.labelText, children: [
2588
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.name, title: item.name, children: truncateMiddle(item.name) }),
2589
+ isFolder ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles5.size, children: [
2094
2590
  item.fileCount !== void 0 ? `${item.fileCount} files` : "",
2095
2591
  item.fileCount !== void 0 && item.totalSize !== void 0 ? " \xB7 " : "",
2096
2592
  item.totalSize !== void 0 ? formatFileSize(item.totalSize) : ""
2097
- ] }) : item.size !== void 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.size, children: formatFileSize(item.size) })
2593
+ ] }) : item.size !== void 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.size, children: formatFileSize(item.size) })
2098
2594
  ] }) }) })
2099
2595
  ]
2100
2596
  }
@@ -2126,7 +2622,7 @@ function truncateMiddle(str, maxLength = 24) {
2126
2622
  var spin3 = _react3.keyframes`
2127
2623
  to { transform: rotate(360deg); }
2128
2624
  `;
2129
- var styles5 = {
2625
+ var styles6 = {
2130
2626
  loading: _react3.css`
2131
2627
  display: flex;
2132
2628
  align-items: center;
@@ -2432,89 +2928,33 @@ var styles5 = {
2432
2928
  `
2433
2929
  };
2434
2930
  function StudioFileList() {
2435
- const { currentPath, setCurrentPath, navigateUp, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey, setFocusedItem, triggerRefresh, searchQuery } = useStudio();
2436
- const [items, setItems] = _react.useState.call(void 0, []);
2437
- const [loading, setLoading] = _react.useState.call(void 0, true);
2438
- const isInitialLoad = _react.useRef.call(void 0, true);
2439
- const lastPath = _react.useRef.call(void 0, currentPath);
2440
- _react.useEffect.call(void 0, () => {
2441
- async function loadItems() {
2442
- const isPathChange = lastPath.current !== currentPath;
2443
- if (isInitialLoad.current || isPathChange) {
2444
- setLoading(true);
2445
- }
2446
- lastPath.current = currentPath;
2447
- try {
2448
- const url = searchQuery && searchQuery.length >= 2 ? `/api/studio/search?q=${encodeURIComponent(searchQuery)}` : `/api/studio/list?path=${encodeURIComponent(currentPath)}`;
2449
- const response = await fetch(url);
2450
- if (response.ok) {
2451
- const data = await response.json();
2452
- setItems(data.items || []);
2453
- }
2454
- } catch (error) {
2455
- console.error("Failed to load items:", error);
2456
- }
2457
- setLoading(false);
2458
- isInitialLoad.current = false;
2459
- }
2460
- loadItems();
2461
- }, [currentPath, refreshKey, searchQuery]);
2931
+ const {
2932
+ loading,
2933
+ sortedItems,
2934
+ isAtRoot,
2935
+ isSearching,
2936
+ allItemsSelected,
2937
+ someItemsSelected,
2938
+ selectedItems,
2939
+ navigateUp,
2940
+ handleItemClick,
2941
+ handleOpen,
2942
+ handleGenerateThumbnail,
2943
+ handleSelectAll
2944
+ } = useFileList();
2462
2945
  if (loading) {
2463
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.spinner }) });
2946
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.spinner }) });
2464
2947
  }
2465
- const isAtRoot = currentPath === "public";
2466
- if (items.length === 0 && isAtRoot) {
2467
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.empty, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: "No files in this folder" }) });
2948
+ if (sortedItems.length === 0 && isAtRoot) {
2949
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.empty, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: "No files in this folder" }) });
2468
2950
  }
2469
- const isSearching = searchQuery && searchQuery.length >= 2;
2470
- const sortedItems = [...items].sort((a, b) => {
2471
- if (a.type === "folder" && b.type !== "folder") return -1;
2472
- if (a.type !== "folder" && b.type === "folder") return 1;
2473
- return a.name.localeCompare(b.name);
2474
- });
2475
- const handleItemClick = (item, e) => {
2476
- if (e.shiftKey && lastSelectedPath) {
2477
- selectRange(lastSelectedPath, item.path, sortedItems);
2478
- } else {
2479
- toggleSelection(item.path);
2480
- }
2481
- };
2482
- const handleOpen = (item) => {
2483
- if (item.type === "folder") {
2484
- setCurrentPath(item.path);
2485
- } else {
2486
- setFocusedItem(item);
2487
- }
2488
- };
2489
- const handleGenerateThumbnail = async (item) => {
2490
- try {
2491
- const imageKey = item.path.replace(/^public\//, "");
2492
- await fetch("/api/studio/reprocess", {
2493
- method: "POST",
2494
- headers: { "Content-Type": "application/json" },
2495
- body: JSON.stringify({ imageKeys: [imageKey] })
2496
- });
2497
- triggerRefresh();
2498
- } catch (error) {
2499
- console.error("Failed to generate thumbnail:", error);
2500
- }
2501
- };
2502
- const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
2503
- const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
2504
- const handleSelectAll = () => {
2505
- if (allItemsSelected) {
2506
- clearSelection();
2507
- } else {
2508
- selectAll(sortedItems);
2509
- }
2510
- };
2511
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.tableWrapper, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles5.table, children: [
2951
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.tableWrapper, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles6.table, children: [
2512
2952
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "thead", { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { children: [
2513
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2953
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles6.th, styles6.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2514
2954
  "input",
2515
2955
  {
2516
2956
  type: "checkbox",
2517
- css: styles5.checkbox,
2957
+ css: styles6.checkbox,
2518
2958
  checked: allItemsSelected,
2519
2959
  ref: (el) => {
2520
2960
  if (el) el.indeterminate = someItemsSelected && !allItemsSelected;
@@ -2522,21 +2962,21 @@ function StudioFileList() {
2522
2962
  onChange: handleSelectAll
2523
2963
  }
2524
2964
  ) }),
2525
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: styles5.th, children: "Name" }),
2526
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thSize], children: "Size" }),
2527
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thDimensions], children: "Dimensions" }),
2528
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCdn], children: "CDN" })
2965
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: styles6.th, children: "Name" }),
2966
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles6.th, styles6.thSize], children: "Size" }),
2967
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles6.th, styles6.thDimensions], children: "Dimensions" }),
2968
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles6.th, styles6.thCdn], children: "CDN" })
2529
2969
  ] }) }),
2530
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tbody", { css: styles5.tbody, children: [
2531
- !isAtRoot && !isSearching && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: styles5.parentRow, onClick: navigateUp, children: [
2532
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td }),
2533
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.nameCell, children: [
2534
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.parentIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" }) }),
2535
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.name, children: ".." })
2970
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tbody", { css: styles6.tbody, children: [
2971
+ !isAtRoot && !isSearching && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: styles6.parentRow, onClick: navigateUp, children: [
2972
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles6.td }),
2973
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles6.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.nameCell, children: [
2974
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.parentIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" }) }),
2975
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.name, children: ".." })
2536
2976
  ] }) }),
2537
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: "--" }),
2538
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: "Parent folder" }),
2539
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: "--" })
2977
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles6.td, styles6.meta], children: "--" }),
2978
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles6.td, styles6.meta], children: "Parent folder" }),
2979
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles6.td, children: "--" })
2540
2980
  ] }),
2541
2981
  sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2542
2982
  ListRow,
@@ -2567,59 +3007,59 @@ function ListRow({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
2567
3007
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2568
3008
  "tr",
2569
3009
  {
2570
- css: [styles5.row, isSelected && styles5.rowSelected],
3010
+ css: [styles6.row, isSelected && styles6.rowSelected],
2571
3011
  onClick,
2572
3012
  children: [
2573
3013
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2574
3014
  "td",
2575
3015
  {
2576
- css: [styles5.td, styles5.checkboxCell],
3016
+ css: [styles6.td, styles6.checkboxCell],
2577
3017
  onClick: (e) => e.stopPropagation(),
2578
3018
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2579
3019
  "input",
2580
3020
  {
2581
3021
  type: "checkbox",
2582
- css: styles5.checkbox,
3022
+ css: styles6.checkbox,
2583
3023
  checked: isSelected,
2584
3024
  onChange: () => onClick({})
2585
3025
  }
2586
3026
  )
2587
3027
  }
2588
3028
  ),
2589
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.nameCell, children: [
2590
- isFolder ? isImagesFolder ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.imagesFolderWrapper, children: [
2591
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.imagesFolderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }),
2592
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.lockIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { fillRule: "evenodd", d: "M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z", clipRule: "evenodd" }) })
2593
- ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.folderIconWrapper, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }) }) : isImage && item.hasThumbnail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.thumbnailWrapper, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { css: styles5.thumbnail, src: item.thumbnail, alt: item.name, loading: "lazy" }) }) : isImage && !item.hasThumbnail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.thumbnailWrapper, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3029
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles6.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.nameCell, children: [
3030
+ isFolder ? isImagesFolder ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.imagesFolderWrapper, children: [
3031
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.imagesFolderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }),
3032
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.lockIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { fillRule: "evenodd", d: "M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z", clipRule: "evenodd" }) })
3033
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.folderIconWrapper, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }) }) : isImage && item.hasThumbnail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.thumbnailWrapper, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { css: styles6.thumbnail, src: item.thumbnail, alt: item.name, loading: "lazy" }) }) : isImage && !item.hasThumbnail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.thumbnailWrapper, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2594
3034
  "button",
2595
3035
  {
2596
- css: styles5.noThumbnail,
3036
+ css: styles6.noThumbnail,
2597
3037
  onClick: (e) => {
2598
3038
  e.stopPropagation();
2599
3039
  onGenerateThumbnail();
2600
3040
  },
2601
3041
  title: "Generate thumbnail",
2602
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.noThumbnailIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) })
3042
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.noThumbnailIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) })
2603
3043
  }
2604
- ) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.thumbnailWrapper, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) }) }),
2605
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.name, title: item.name, children: truncateMiddle2(item.name) }),
2606
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.actionsCell, children: [
3044
+ ) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.thumbnailWrapper, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) }) }),
3045
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.name, title: item.name, children: truncateMiddle2(item.name) }),
3046
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.actionsCell, children: [
2607
3047
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2608
3048
  "button",
2609
3049
  {
2610
- css: styles5.copyBtn,
3050
+ css: styles6.copyBtn,
2611
3051
  onClick: handleCopyPath,
2612
3052
  title: "Copy file path",
2613
3053
  children: [
2614
- showCopied && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.tooltip, children: "Copied!" }),
2615
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" }) })
3054
+ showCopied && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.tooltip, children: "Copied!" }),
3055
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" }) })
2616
3056
  ]
2617
3057
  }
2618
3058
  ),
2619
3059
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2620
3060
  "button",
2621
3061
  {
2622
- css: styles5.openBtn,
3062
+ css: styles6.openBtn,
2623
3063
  onClick: (e) => {
2624
3064
  e.stopPropagation();
2625
3065
  onOpen();
@@ -2629,12 +3069,12 @@ function ListRow({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
2629
3069
  )
2630
3070
  ] })
2631
3071
  ] }) }),
2632
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: isFolder ? item.fileCount !== void 0 ? `${item.fileCount} files` : "--" : item.size !== void 0 ? formatFileSize2(item.size) : "--" }),
2633
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: isFolder ? item.totalSize !== void 0 ? formatFileSize2(item.totalSize) : "--" : item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
2634
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: item.cdnSynced ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles5.cdnBadge, children: [
2635
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }),
3072
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles6.td, styles6.meta], children: isFolder ? item.fileCount !== void 0 ? `${item.fileCount} files` : "--" : item.size !== void 0 ? formatFileSize2(item.size) : "--" }),
3073
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles6.td, styles6.meta], children: isFolder ? item.totalSize !== void 0 ? formatFileSize2(item.totalSize) : "--" : item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
3074
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles6.td, children: item.cdnSynced ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles6.cdnBadge, children: [
3075
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }),
2636
3076
  "Synced"
2637
- ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.cdnEmpty, children: "--" }) })
3077
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.cdnEmpty, children: "--" }) })
2638
3078
  ]
2639
3079
  }
2640
3080
  );
@@ -2672,7 +3112,7 @@ function isVideoFile(filename) {
2672
3112
  const ext = filename.toLowerCase().substring(filename.lastIndexOf("."));
2673
3113
  return VIDEO_EXTENSIONS.includes(ext);
2674
3114
  }
2675
- var styles6 = {
3115
+ var styles7 = {
2676
3116
  overlay: _react3.css`
2677
3117
  position: absolute;
2678
3118
  top: 0;
@@ -2923,9 +3363,11 @@ function StudioDetailView() {
2923
3363
  const [showDeleteConfirm, setShowDeleteConfirm] = _react.useState.call(void 0, false);
2924
3364
  const [showRenameModal, setShowRenameModal] = _react.useState.call(void 0, false);
2925
3365
  const [showProcessConfirm, setShowProcessConfirm] = _react.useState.call(void 0, false);
3366
+ const [showR2SetupModal, setShowR2SetupModal] = _react.useState.call(void 0, false);
2926
3367
  const [processProgress, setProcessProgress] = _react.useState.call(void 0, null);
2927
3368
  const [alertMessage, setAlertMessage] = _react.useState.call(void 0, null);
2928
3369
  const [showCopied, setShowCopied] = _react.useState.call(void 0, false);
3370
+ const [syncing, setSyncing] = _react.useState.call(void 0, false);
2929
3371
  if (!focusedItem) return null;
2930
3372
  const isImage = isImageFile(focusedItem.name);
2931
3373
  const isVideo = isVideoFile(focusedItem.name);
@@ -2997,8 +3439,41 @@ function StudioDetailView() {
2997
3439
  });
2998
3440
  }
2999
3441
  };
3000
- const handleSync = () => {
3001
- console.log("Sync to CDN:", focusedItem.path);
3442
+ const handleSync = async () => {
3443
+ const imageKey = "/" + focusedItem.path.replace(/^public\//, "");
3444
+ setSyncing(true);
3445
+ try {
3446
+ const response = await fetch("/api/studio/sync", {
3447
+ method: "POST",
3448
+ headers: { "Content-Type": "application/json" },
3449
+ body: JSON.stringify({ imageKeys: [imageKey] })
3450
+ });
3451
+ const data = await response.json();
3452
+ if (response.ok) {
3453
+ setAlertMessage({
3454
+ title: "Sync Complete",
3455
+ message: "Successfully synced to CDN."
3456
+ });
3457
+ triggerRefresh();
3458
+ } else {
3459
+ if (_optionalChain([data, 'access', _29 => _29.error, 'optionalAccess', _30 => _30.includes, 'call', _31 => _31("R2 not configured")]) || _optionalChain([data, 'access', _32 => _32.error, 'optionalAccess', _33 => _33.includes, 'call', _34 => _34("CLOUDFLARE_R2")])) {
3460
+ setShowR2SetupModal(true);
3461
+ } else {
3462
+ setAlertMessage({
3463
+ title: "Sync Failed",
3464
+ message: data.error || "Failed to sync to CDN."
3465
+ });
3466
+ }
3467
+ }
3468
+ } catch (error) {
3469
+ console.error("Sync error:", error);
3470
+ setAlertMessage({
3471
+ title: "Sync Failed",
3472
+ message: "Failed to sync to CDN. Check console for details."
3473
+ });
3474
+ } finally {
3475
+ setSyncing(false);
3476
+ }
3002
3477
  };
3003
3478
  const handleProcessImage = async () => {
3004
3479
  setShowProcessConfirm(false);
@@ -3020,7 +3495,7 @@ function StudioDetailView() {
3020
3495
  if (!response.ok) {
3021
3496
  throw new Error("Processing failed");
3022
3497
  }
3023
- const reader = _optionalChain([response, 'access', _19 => _19.body, 'optionalAccess', _20 => _20.getReader, 'call', _21 => _21()]);
3498
+ const reader = _optionalChain([response, 'access', _35 => _35.body, 'optionalAccess', _36 => _36.getReader, 'call', _37 => _37()]);
3024
3499
  if (!reader) {
3025
3500
  throw new Error("No response body");
3026
3501
  }
@@ -3056,14 +3531,14 @@ function StudioDetailView() {
3056
3531
  };
3057
3532
  const renderMedia = () => {
3058
3533
  if (isImage) {
3059
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { css: styles6.image, src: imageSrc, alt: focusedItem.name });
3534
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { css: styles7.image, src: imageSrc, alt: focusedItem.name });
3060
3535
  }
3061
3536
  if (isVideo) {
3062
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "video", { css: styles6.video, src: imageSrc, controls: true });
3537
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "video", { css: styles7.video, src: imageSrc, controls: true });
3063
3538
  }
3064
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.filePlaceholder, children: [
3065
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) }),
3066
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.fileName, children: focusedItem.name })
3539
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.filePlaceholder, children: [
3540
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) }),
3541
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.fileName, children: focusedItem.name })
3067
3542
  ] });
3068
3543
  };
3069
3544
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
@@ -3086,6 +3561,13 @@ function StudioDetailView() {
3086
3561
  onClose: () => setAlertMessage(null)
3087
3562
  }
3088
3563
  ),
3564
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3565
+ R2SetupModal,
3566
+ {
3567
+ isOpen: showR2SetupModal,
3568
+ onClose: () => setShowR2SetupModal(false)
3569
+ }
3570
+ ),
3089
3571
  showRenameModal && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3090
3572
  InputModal,
3091
3573
  {
@@ -3116,61 +3598,61 @@ function StudioDetailView() {
3116
3598
  onClose: () => setProcessProgress(null)
3117
3599
  }
3118
3600
  ),
3119
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.overlay, onClick: handleClose, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.container, onClick: (e) => e.stopPropagation(), children: [
3120
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.main, children: [
3121
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.headerButtons, children: [
3122
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles6.copyBtn, onClick: handleCopyPath, title: "Copy file path", children: [
3123
- showCopied && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.tooltip, children: "Copied!" }),
3124
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" }) })
3601
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles7.overlay, onClick: handleClose, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.container, onClick: (e) => e.stopPropagation(), children: [
3602
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.main, children: [
3603
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.headerButtons, children: [
3604
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles7.copyBtn, onClick: handleCopyPath, title: "Copy file path", children: [
3605
+ showCopied && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.tooltip, children: "Copied!" }),
3606
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" }) })
3125
3607
  ] }),
3126
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles6.mainCloseBtn, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.mainCloseIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
3608
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.mainCloseBtn, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.mainCloseIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
3127
3609
  ] }),
3128
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.mediaWrapper, children: renderMedia() })
3610
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles7.mediaWrapper, children: renderMedia() })
3129
3611
  ] }),
3130
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.sidebar, children: [
3131
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.sidebarHeader, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles6.sidebarTitle, children: "Details" }) }),
3132
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.sidebarContent, children: [
3133
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.info, children: [
3134
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.infoRow, children: [
3135
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoLabel, children: "Name" }),
3136
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoValueWrap, children: focusedItem.name })
3612
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.sidebar, children: [
3613
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles7.sidebarHeader, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles7.sidebarTitle, children: "Details" }) }),
3614
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.sidebarContent, children: [
3615
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.info, children: [
3616
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.infoRow, children: [
3617
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoLabel, children: "Name" }),
3618
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoValueWrap, children: focusedItem.name })
3137
3619
  ] }),
3138
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.infoRow, children: [
3139
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoLabel, children: "Path" }),
3140
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoValueWrap, children: focusedItem.path.replace(/^public\//, "") })
3620
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.infoRow, children: [
3621
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoLabel, children: "Path" }),
3622
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoValueWrap, children: focusedItem.path.replace(/^public\//, "") })
3141
3623
  ] }),
3142
- focusedItem.size !== void 0 && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.infoRow, children: [
3143
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoLabel, children: "Size" }),
3144
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoValue, children: formatFileSize3(focusedItem.size) })
3624
+ focusedItem.size !== void 0 && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.infoRow, children: [
3625
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoLabel, children: "Size" }),
3626
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoValue, children: formatFileSize3(focusedItem.size) })
3145
3627
  ] }),
3146
- focusedItem.dimensions && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.infoRow, children: [
3147
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoLabel, children: "Dimensions" }),
3148
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles6.infoValue, children: [
3628
+ focusedItem.dimensions && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.infoRow, children: [
3629
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoLabel, children: "Dimensions" }),
3630
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles7.infoValue, children: [
3149
3631
  focusedItem.dimensions.width,
3150
3632
  " \xD7 ",
3151
3633
  focusedItem.dimensions.height
3152
3634
  ] })
3153
3635
  ] }),
3154
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.infoRow, children: [
3155
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoLabel, children: "CDN Status" }),
3156
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.infoValue, children: focusedItem.cdnSynced ? "Synced" : "Not synced" })
3636
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.infoRow, children: [
3637
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoLabel, children: "CDN Status" }),
3638
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.infoValue, children: focusedItem.cdnSynced ? "Synced" : "Not synced" })
3157
3639
  ] })
3158
3640
  ] }),
3159
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.actions, children: [
3160
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles6.actionBtn, onClick: () => setShowRenameModal(true), children: [
3161
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) }),
3641
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.actions, children: [
3642
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles7.actionBtn, onClick: () => setShowRenameModal(true), children: [
3643
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) }),
3162
3644
  "Rename"
3163
3645
  ] }),
3164
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles6.actionBtn, onClick: handleSync, children: [
3165
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) }),
3166
- "Sync to CDN"
3646
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles7.actionBtn, onClick: handleSync, disabled: syncing, children: [
3647
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) }),
3648
+ syncing ? "Syncing..." : "Sync to CDN"
3167
3649
  ] }),
3168
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles6.actionBtn, onClick: () => setShowProcessConfirm(true), children: [
3169
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
3650
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles7.actionBtn, onClick: () => setShowProcessConfirm(true), children: [
3651
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
3170
3652
  "Process Image"
3171
3653
  ] }),
3172
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: [styles6.actionBtn, styles6.actionBtnDanger], onClick: () => setShowDeleteConfirm(true), children: [
3173
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) }),
3654
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: [styles7.actionBtn, styles7.actionBtnDanger], onClick: () => setShowDeleteConfirm(true), children: [
3655
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) }),
3174
3656
  "Delete"
3175
3657
  ] })
3176
3658
  ] })
@@ -3190,7 +3672,7 @@ function formatFileSize3(bytes) {
3190
3672
 
3191
3673
 
3192
3674
  var btnHeight2 = "36px";
3193
- var styles7 = {
3675
+ var styles8 = {
3194
3676
  btn: _react3.css`
3195
3677
  height: ${btnHeight2};
3196
3678
  padding: 0 12px;
@@ -3428,10 +3910,10 @@ var styles7 = {
3428
3910
  function StudioSettings() {
3429
3911
  const [isOpen, setIsOpen] = _react.useState.call(void 0, false);
3430
3912
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
3431
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.btn, onClick: () => setIsOpen(true), "aria-label": "Settings", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3913
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles8.btn, onClick: () => setIsOpen(true), "aria-label": "Settings", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3432
3914
  "svg",
3433
3915
  {
3434
- css: styles7.icon,
3916
+ css: styles8.icon,
3435
3917
  xmlns: "http://www.w3.org/2000/svg",
3436
3918
  viewBox: "0 0 24 24",
3437
3919
  fill: "none",
@@ -3460,58 +3942,133 @@ function SettingsPanel({ onClose }) {
3460
3942
  setCopied(true);
3461
3943
  setTimeout(() => setCopied(false), 2e3);
3462
3944
  };
3463
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles7.overlay, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.panel, onClick: (e) => e.stopPropagation(), children: [
3464
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.header, children: [
3465
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { css: styles7.title, children: "Settings" }),
3466
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.closeBtn, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
3945
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.overlay, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.panel, onClick: (e) => e.stopPropagation(), children: [
3946
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.header, children: [
3947
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { css: styles8.title, children: "Settings" }),
3948
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles8.closeBtn, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles8.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
3467
3949
  ] }),
3468
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.sections, children: [
3950
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.sections, children: [
3469
3951
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "section", { children: [
3470
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles7.sectionTitle, children: "Cloudflare R2" }),
3471
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.description, children: "Configure in .env.local file:" }),
3472
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.codeWrapper, children: [
3473
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles7.copyBtn, onClick: handleCopy, title: "Copy to clipboard", children: [
3474
- copied && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.tooltip, children: "Copied!" }),
3475
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" }) })
3952
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles8.sectionTitle, children: "Cloudflare R2" }),
3953
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles8.description, children: "Configure in .env.local file:" }),
3954
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.codeWrapper, children: [
3955
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles8.copyBtn, onClick: handleCopy, title: "Copy to clipboard", children: [
3956
+ copied && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles8.tooltip, children: "Copied!" }),
3957
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles8.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" }) })
3476
3958
  ] }),
3477
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.code, children: [
3478
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_ACCOUNT_ID=abc123def456ghi789" }),
3479
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_ACCESS_KEY_ID=your_access_key_id_here" }),
3480
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_SECRET_ACCESS_KEY=your_secret_access_key_here" }),
3481
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_BUCKET_NAME=my-images-bucket" }),
3482
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_PUBLIC_URL=https://cdn.yourdomain.com" })
3959
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.code, children: [
3960
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles8.codeLine, children: "CLOUDFLARE_R2_ACCOUNT_ID=abc123def456ghi789" }),
3961
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles8.codeLine, children: "CLOUDFLARE_R2_ACCESS_KEY_ID=your_access_key_id_here" }),
3962
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles8.codeLine, children: "CLOUDFLARE_R2_SECRET_ACCESS_KEY=your_secret_access_key_here" }),
3963
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles8.codeLine, children: "CLOUDFLARE_R2_BUCKET_NAME=my-images-bucket" }),
3964
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles8.codeLine, children: "CLOUDFLARE_R2_PUBLIC_URL=https://cdn.yourdomain.com" })
3483
3965
  ] })
3484
3966
  ] })
3485
3967
  ] }),
3486
3968
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "section", { children: [
3487
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles7.sectionTitle, children: "Thumbnail Sizes" }),
3488
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.grid, children: [
3969
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles8.sectionTitle, children: "Thumbnail Sizes" }),
3970
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.grid, children: [
3489
3971
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
3490
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles7.label, children: "Small" }),
3491
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles7.input, type: "number", defaultValue: 300 })
3972
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles8.label, children: "Small" }),
3973
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles8.input, type: "number", defaultValue: 300 })
3492
3974
  ] }),
3493
3975
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
3494
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles7.label, children: "Medium" }),
3495
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles7.input, type: "number", defaultValue: 700 })
3976
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles8.label, children: "Medium" }),
3977
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles8.input, type: "number", defaultValue: 700 })
3496
3978
  ] }),
3497
3979
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
3498
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles7.label, children: "Large" }),
3499
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles7.input, type: "number", defaultValue: 1400 })
3980
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles8.label, children: "Large" }),
3981
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles8.input, type: "number", defaultValue: 1400 })
3500
3982
  ] })
3501
3983
  ] })
3502
3984
  ] })
3503
3985
  ] }),
3504
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.footer, children: [
3505
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.cancelBtn, onClick: onClose, children: "Cancel" }),
3506
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.saveBtn, children: "Save Changes" })
3986
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.footer, children: [
3987
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles8.cancelBtn, onClick: onClose, children: "Cancel" }),
3988
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles8.saveBtn, children: "Save Changes" })
3507
3989
  ] })
3508
3990
  ] }) });
3509
3991
  }
3510
3992
 
3993
+ // src/components/ErrorModal.tsx
3994
+
3995
+
3996
+ var styles9 = {
3997
+ overlay: _react3.css`
3998
+ position: fixed;
3999
+ inset: 0;
4000
+ background: rgba(0, 0, 0, 0.5);
4001
+ display: flex;
4002
+ align-items: center;
4003
+ justify-content: center;
4004
+ z-index: 1100;
4005
+ `,
4006
+ modal: _react3.css`
4007
+ background: ${_chunkUFCWGUAGjs.colors.surface};
4008
+ border-radius: 12px;
4009
+ padding: 24px;
4010
+ max-width: 400px;
4011
+ width: 90%;
4012
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
4013
+ `,
4014
+ header: _react3.css`
4015
+ display: flex;
4016
+ align-items: center;
4017
+ gap: 12px;
4018
+ margin-bottom: 12px;
4019
+ `,
4020
+ icon: _react3.css`
4021
+ width: 24px;
4022
+ height: 24px;
4023
+ color: ${_chunkUFCWGUAGjs.colors.danger};
4024
+ flex-shrink: 0;
4025
+ `,
4026
+ title: _react3.css`
4027
+ font-size: ${_chunkUFCWGUAGjs.fontSize.lg};
4028
+ font-weight: 600;
4029
+ color: ${_chunkUFCWGUAGjs.colors.text};
4030
+ margin: 0;
4031
+ `,
4032
+ message: _react3.css`
4033
+ font-size: ${_chunkUFCWGUAGjs.fontSize.base};
4034
+ color: ${_chunkUFCWGUAGjs.colors.textSecondary};
4035
+ margin: 0 0 20px 0;
4036
+ line-height: 1.5;
4037
+ `,
4038
+ button: _react3.css`
4039
+ width: 100%;
4040
+ padding: 10px 16px;
4041
+ border-radius: 6px;
4042
+ font-size: ${_chunkUFCWGUAGjs.fontSize.base};
4043
+ font-weight: 500;
4044
+ border: none;
4045
+ background: ${_chunkUFCWGUAGjs.colors.primary};
4046
+ color: white;
4047
+ cursor: pointer;
4048
+ transition: background 0.15s ease;
4049
+
4050
+ &:hover {
4051
+ background: ${_chunkUFCWGUAGjs.colors.primaryHover};
4052
+ }
4053
+ `
4054
+ };
4055
+ function ErrorModal() {
4056
+ const { error, clearError } = useStudio();
4057
+ if (!error) return null;
4058
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles9.overlay, onClick: clearError, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles9.modal, onClick: (e) => e.stopPropagation(), children: [
4059
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles9.header, children: [
4060
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles9.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
4061
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles9.title, children: error.title })
4062
+ ] }),
4063
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles9.message, children: error.message }),
4064
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles9.button, onClick: clearError, children: "OK" })
4065
+ ] }) });
4066
+ }
4067
+
3511
4068
  // src/components/StudioUI.tsx
3512
4069
 
3513
4070
  var btnHeight3 = "36px";
3514
- var styles8 = {
4071
+ var styles10 = {
3515
4072
  container: _react3.css`
3516
4073
  ${_chunkUFCWGUAGjs.baseReset}
3517
4074
  display: flex;
@@ -3658,10 +4215,17 @@ function StudioUI({ onClose, isVisible = true }) {
3658
4215
  const [isLoading, setIsLoading] = _react.useState.call(void 0, false);
3659
4216
  const [refreshKey, setRefreshKey] = _react.useState.call(void 0, 0);
3660
4217
  const [searchQuery, setSearchQuery] = _react.useState.call(void 0, "");
4218
+ const [error, setError] = _react.useState.call(void 0, null);
3661
4219
  const [isDragging, setIsDragging] = _react.useState.call(void 0, false);
3662
4220
  const triggerRefresh = _react.useCallback.call(void 0, () => {
3663
4221
  setRefreshKey((k) => k + 1);
3664
4222
  }, []);
4223
+ const showError = _react.useCallback.call(void 0, (title, message) => {
4224
+ setError({ title, message });
4225
+ }, []);
4226
+ const clearError = _react.useCallback.call(void 0, () => {
4227
+ setError(null);
4228
+ }, []);
3665
4229
  const handleDragOver = _react.useCallback.call(void 0, (e) => {
3666
4230
  e.preventDefault();
3667
4231
  e.stopPropagation();
@@ -3690,8 +4254,8 @@ function StudioUI({ onClose, isVisible = true }) {
3690
4254
  method: "POST",
3691
4255
  body: formData
3692
4256
  });
3693
- } catch (error) {
3694
- console.error("Upload error:", error);
4257
+ } catch (error2) {
4258
+ console.error("Upload error:", error2);
3695
4259
  }
3696
4260
  }
3697
4261
  triggerRefresh();
@@ -3793,18 +4357,21 @@ function StudioUI({ onClose, isVisible = true }) {
3793
4357
  refreshKey,
3794
4358
  triggerRefresh,
3795
4359
  searchQuery,
3796
- setSearchQuery
4360
+ setSearchQuery,
4361
+ error,
4362
+ showError,
4363
+ clearError
3797
4364
  };
3798
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.container, children: [
3799
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.header, children: [
3800
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.headerLeft, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { css: styles8.title, children: "Studio" }) }),
3801
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.headerCenter, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Breadcrumbs, { currentPath, onNavigate: setCurrentPath }) }),
3802
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.headerActions, children: [
4365
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles10.container, children: [
4366
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles10.header, children: [
4367
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles10.headerLeft, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { css: styles10.title, children: "Studio" }) }),
4368
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles10.headerCenter, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Breadcrumbs, { currentPath, onNavigate: setCurrentPath }) }),
4369
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles10.headerActions, children: [
3803
4370
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioSettings, {}),
3804
4371
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3805
4372
  "button",
3806
4373
  {
3807
- css: styles8.headerBtn,
4374
+ css: styles10.headerBtn,
3808
4375
  onClick: onClose,
3809
4376
  "aria-label": "Close Studio",
3810
4377
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloseIcon, {})
@@ -3816,20 +4383,21 @@ function StudioUI({ onClose, isVisible = true }) {
3816
4383
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3817
4384
  "div",
3818
4385
  {
3819
- css: styles8.content,
4386
+ css: styles10.content,
3820
4387
  onDragOver: handleDragOver,
3821
4388
  onDragLeave: handleDragLeave,
3822
4389
  onDrop: handleDrop,
3823
4390
  children: [
3824
- isDragging && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.dropOverlay, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.dropMessage, children: [
3825
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles8.dropIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) }),
4391
+ isDragging && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles10.dropOverlay, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles10.dropMessage, children: [
4392
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles10.dropIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) }),
3826
4393
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Drop files to upload" })
3827
4394
  ] }) }),
3828
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileGrid, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileList, {}) })
4395
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles10.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileGrid, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileList, {}) })
3829
4396
  ]
3830
4397
  }
3831
4398
  ),
3832
- focusedItem && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioDetailView, {})
4399
+ focusedItem && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioDetailView, {}),
4400
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ErrorModal, {})
3833
4401
  ] }) });
3834
4402
  }
3835
4403
  function Breadcrumbs({ currentPath, onNavigate }) {
@@ -3838,12 +4406,12 @@ function Breadcrumbs({ currentPath, onNavigate }) {
3838
4406
  name: part,
3839
4407
  path: parts.slice(0, index + 1).join("/")
3840
4408
  }));
3841
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.breadcrumbs, children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
3842
- index > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles8.breadcrumbSeparator, children: "/" }),
3843
- index === breadcrumbs.length - 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles8.breadcrumbCurrent, children: crumb.name }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
4409
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles10.breadcrumbs, children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
4410
+ index > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles10.breadcrumbSeparator, children: "/" }),
4411
+ index === breadcrumbs.length - 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles10.breadcrumbCurrent, children: crumb.name }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3844
4412
  "span",
3845
4413
  {
3846
- css: styles8.breadcrumbItem,
4414
+ css: styles10.breadcrumbItem,
3847
4415
  onClick: () => onNavigate(crumb.path),
3848
4416
  children: crumb.name
3849
4417
  }
@@ -3854,7 +4422,7 @@ function CloseIcon() {
3854
4422
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3855
4423
  "svg",
3856
4424
  {
3857
- css: styles8.headerIcon,
4425
+ css: styles10.headerIcon,
3858
4426
  xmlns: "http://www.w3.org/2000/svg",
3859
4427
  viewBox: "0 0 24 24",
3860
4428
  fill: "none",
@@ -3874,4 +4442,4 @@ var StudioUI_default = StudioUI;
3874
4442
 
3875
4443
 
3876
4444
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
3877
- //# sourceMappingURL=StudioUI-RH4ZXWKP.js.map
4445
+ //# sourceMappingURL=StudioUI-MVIOZTZH.js.map