@gallop.software/studio 0.1.15 → 0.1.17

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.
@@ -709,14 +709,19 @@ var styles4 = {
709
709
  border-color: #a855f7;
710
710
  }
711
711
  `,
712
- checkbox: css4`
712
+ checkboxWrapper: css4`
713
713
  position: absolute;
714
- top: 8px;
715
- left: 8px;
714
+ top: 0;
715
+ left: 0;
716
716
  z-index: 10;
717
+ padding: 8px;
718
+ cursor: pointer;
719
+ `,
720
+ checkbox: css4`
717
721
  width: 16px;
718
722
  height: 16px;
719
723
  accent-color: #9333ea;
724
+ cursor: pointer;
720
725
  `,
721
726
  cdnBadge: css4`
722
727
  position: absolute;
@@ -741,6 +746,11 @@ var styles4 = {
741
746
  height: 64px;
742
747
  color: #facc15;
743
748
  `,
749
+ fileIcon: css4`
750
+ width: 48px;
751
+ height: 48px;
752
+ color: #9ca3af;
753
+ `,
744
754
  image: css4`
745
755
  max-width: 100%;
746
756
  max-height: 100%;
@@ -825,16 +835,6 @@ function StudioFileGrid() {
825
835
  if (a.type !== "folder" && b.type === "folder") return 1;
826
836
  return a.name.localeCompare(b.name);
827
837
  });
828
- const files = sortedItems.filter((item) => item.type !== "folder");
829
- const allFilesSelected = files.length > 0 && files.every((item) => selectedItems.has(item.path));
830
- const someFilesSelected = files.some((item) => selectedItems.has(item.path));
831
- const handleSelectAll = () => {
832
- if (allFilesSelected) {
833
- clearSelection();
834
- } else {
835
- selectAll(files);
836
- }
837
- };
838
838
  const handleItemClick = (item, e) => {
839
839
  if (item.type === "folder") {
840
840
  setCurrentPath(item.path);
@@ -846,22 +846,38 @@ function StudioFileGrid() {
846
846
  toggleSelection(item.path);
847
847
  }
848
848
  };
849
+ const handleCheckboxClick = (item, e) => {
850
+ if (e.shiftKey && lastSelectedPath) {
851
+ selectRange(lastSelectedPath, item.path, sortedItems);
852
+ } else {
853
+ toggleSelection(item.path);
854
+ }
855
+ };
856
+ const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
857
+ const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
858
+ const handleSelectAll = () => {
859
+ if (allItemsSelected) {
860
+ clearSelection();
861
+ } else {
862
+ selectAll(sortedItems);
863
+ }
864
+ };
849
865
  return /* @__PURE__ */ jsxs4("div", { children: [
850
- files.length > 0 && /* @__PURE__ */ jsx4("div", { css: styles4.selectAllRow, children: /* @__PURE__ */ jsxs4("label", { css: styles4.selectAllLabel, children: [
866
+ sortedItems.length > 0 && /* @__PURE__ */ jsx4("div", { css: styles4.selectAllRow, children: /* @__PURE__ */ jsxs4("label", { css: styles4.selectAllLabel, children: [
851
867
  /* @__PURE__ */ jsx4(
852
868
  "input",
853
869
  {
854
870
  type: "checkbox",
855
871
  css: styles4.selectAllCheckbox,
856
- checked: allFilesSelected,
872
+ checked: allItemsSelected,
857
873
  ref: (el) => {
858
- if (el) el.indeterminate = someFilesSelected && !allFilesSelected;
874
+ if (el) el.indeterminate = someItemsSelected && !allItemsSelected;
859
875
  },
860
876
  onChange: handleSelectAll
861
877
  }
862
878
  ),
863
879
  "Select all (",
864
- files.length,
880
+ sortedItems.length,
865
881
  ")"
866
882
  ] }) }),
867
883
  /* @__PURE__ */ jsx4("div", { css: styles4.grid, children: sortedItems.map((item) => /* @__PURE__ */ jsx4(
@@ -869,42 +885,53 @@ function StudioFileGrid() {
869
885
  {
870
886
  item,
871
887
  isSelected: selectedItems.has(item.path),
872
- onClick: (e) => handleItemClick(item, e)
888
+ onClick: (e) => handleItemClick(item, e),
889
+ onCheckboxClick: (e) => handleCheckboxClick(item, e)
873
890
  },
874
891
  item.path
875
892
  )) })
876
893
  ] });
877
894
  }
878
- function GridItem({ item, isSelected, onClick }) {
895
+ function GridItem({ item, isSelected, onClick, onCheckboxClick }) {
879
896
  const isFolder = item.type === "folder";
880
897
  return /* @__PURE__ */ jsxs4("div", { css: [styles4.item, isSelected && styles4.itemSelected], onClick, children: [
881
- !isFolder && /* @__PURE__ */ jsx4(
882
- "input",
898
+ /* @__PURE__ */ jsx4(
899
+ "div",
883
900
  {
884
- type: "checkbox",
885
- css: styles4.checkbox,
886
- checked: isSelected,
887
- onChange: () => {
888
- },
901
+ css: styles4.checkboxWrapper,
889
902
  onClick: (e) => {
890
903
  e.stopPropagation();
891
- onClick(e);
892
- }
904
+ onCheckboxClick(e);
905
+ },
906
+ children: /* @__PURE__ */ jsx4(
907
+ "input",
908
+ {
909
+ type: "checkbox",
910
+ css: styles4.checkbox,
911
+ checked: isSelected,
912
+ onChange: () => {
913
+ }
914
+ }
915
+ )
893
916
  }
894
917
  ),
895
918
  item.cdnSynced && /* @__PURE__ */ jsx4("span", { css: styles4.cdnBadge, children: "CDN" }),
896
- /* @__PURE__ */ jsx4("div", { css: styles4.content, children: isFolder ? /* @__PURE__ */ jsx4("svg", { css: styles4.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("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" }) }) : /* @__PURE__ */ jsx4(
919
+ /* @__PURE__ */ jsx4("div", { css: styles4.content, children: isFolder ? /* @__PURE__ */ jsx4("svg", { css: styles4.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("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" }) }) : item.thumbnail ? /* @__PURE__ */ jsx4(
897
920
  "img",
898
921
  {
899
922
  css: styles4.image,
900
- src: item.path.replace("public", ""),
923
+ src: item.thumbnail,
901
924
  alt: item.name,
902
925
  loading: "lazy"
903
926
  }
904
- ) }),
927
+ ) : /* @__PURE__ */ jsx4("svg", { css: styles4.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("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" }) }) }),
905
928
  /* @__PURE__ */ jsxs4("div", { css: styles4.label, children: [
906
929
  /* @__PURE__ */ jsx4("p", { css: styles4.name, title: item.name, children: item.name }),
907
- item.size && /* @__PURE__ */ jsx4("p", { css: styles4.size, children: formatFileSize(item.size) })
930
+ isFolder ? /* @__PURE__ */ jsxs4("p", { css: styles4.size, children: [
931
+ item.fileCount !== void 0 ? `${item.fileCount} files` : "",
932
+ item.fileCount !== void 0 && item.totalSize !== void 0 ? " \xB7 " : "",
933
+ item.totalSize !== void 0 ? formatFileSize(item.totalSize) : ""
934
+ ] }) : item.size !== void 0 && /* @__PURE__ */ jsx4("p", { css: styles4.size, children: formatFileSize(item.size) })
908
935
  ] })
909
936
  ] });
910
937
  }
@@ -992,10 +1019,15 @@ var styles5 = {
992
1019
  padding: 8px 0;
993
1020
  border-bottom: 1px solid #f3f4f6;
994
1021
  `,
1022
+ checkboxCell: css5`
1023
+ padding: 8px 12px;
1024
+ cursor: pointer;
1025
+ `,
995
1026
  checkbox: css5`
996
1027
  width: 16px;
997
1028
  height: 16px;
998
1029
  accent-color: #9333ea;
1030
+ cursor: pointer;
999
1031
  `,
1000
1032
  nameCell: css5`
1001
1033
  display: flex;
@@ -1012,6 +1044,13 @@ var styles5 = {
1012
1044
  height: 20px;
1013
1045
  color: #9ca3af;
1014
1046
  `,
1047
+ thumbnail: css5`
1048
+ width: 32px;
1049
+ height: 32px;
1050
+ object-fit: cover;
1051
+ border-radius: 4px;
1052
+ flex-shrink: 0;
1053
+ `,
1015
1054
  name: css5`
1016
1055
  font-size: 14px;
1017
1056
  color: #111827;
@@ -1067,16 +1106,6 @@ function StudioFileList() {
1067
1106
  if (a.type !== "folder" && b.type === "folder") return 1;
1068
1107
  return a.name.localeCompare(b.name);
1069
1108
  });
1070
- const files = sortedItems.filter((item) => item.type !== "folder");
1071
- const allFilesSelected = files.length > 0 && files.every((item) => selectedItems.has(item.path));
1072
- const someFilesSelected = files.some((item) => selectedItems.has(item.path));
1073
- const handleSelectAll = () => {
1074
- if (allFilesSelected) {
1075
- clearSelection();
1076
- } else {
1077
- selectAll(files);
1078
- }
1079
- };
1080
1109
  const handleItemClick = (item, e) => {
1081
1110
  if (item.type === "folder") {
1082
1111
  setCurrentPath(item.path);
@@ -1088,16 +1117,32 @@ function StudioFileList() {
1088
1117
  toggleSelection(item.path);
1089
1118
  }
1090
1119
  };
1120
+ const handleCheckboxClick = (item, e) => {
1121
+ if (e.shiftKey && lastSelectedPath) {
1122
+ selectRange(lastSelectedPath, item.path, sortedItems);
1123
+ } else {
1124
+ toggleSelection(item.path);
1125
+ }
1126
+ };
1127
+ const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
1128
+ const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
1129
+ const handleSelectAll = () => {
1130
+ if (allItemsSelected) {
1131
+ clearSelection();
1132
+ } else {
1133
+ selectAll(sortedItems);
1134
+ }
1135
+ };
1091
1136
  return /* @__PURE__ */ jsxs5("table", { css: styles5.table, children: [
1092
1137
  /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
1093
- /* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thCheckbox], children: files.length > 0 && /* @__PURE__ */ jsx5(
1138
+ /* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ jsx5(
1094
1139
  "input",
1095
1140
  {
1096
1141
  type: "checkbox",
1097
1142
  css: styles5.checkbox,
1098
- checked: allFilesSelected,
1143
+ checked: allItemsSelected,
1099
1144
  ref: (el) => {
1100
- if (el) el.indeterminate = someFilesSelected && !allFilesSelected;
1145
+ if (el) el.indeterminate = someItemsSelected && !allItemsSelected;
1101
1146
  },
1102
1147
  onChange: handleSelectAll
1103
1148
  }
@@ -1112,35 +1157,42 @@ function StudioFileList() {
1112
1157
  {
1113
1158
  item,
1114
1159
  isSelected: selectedItems.has(item.path),
1115
- onClick: (e) => handleItemClick(item, e)
1160
+ onClick: (e) => handleItemClick(item, e),
1161
+ onCheckboxClick: (e) => handleCheckboxClick(item, e)
1116
1162
  },
1117
1163
  item.path
1118
1164
  )) })
1119
1165
  ] });
1120
1166
  }
1121
- function ListRow({ item, isSelected, onClick }) {
1167
+ function ListRow({ item, isSelected, onClick, onCheckboxClick }) {
1122
1168
  const isFolder = item.type === "folder";
1123
1169
  return /* @__PURE__ */ jsxs5("tr", { css: [styles5.row, isSelected && styles5.rowSelected], onClick, children: [
1124
- /* @__PURE__ */ jsx5("td", { css: styles5.td, children: !isFolder && /* @__PURE__ */ jsx5(
1125
- "input",
1170
+ /* @__PURE__ */ jsx5(
1171
+ "td",
1126
1172
  {
1127
- type: "checkbox",
1128
- css: styles5.checkbox,
1129
- checked: isSelected,
1130
- onChange: () => {
1131
- },
1173
+ css: [styles5.td, styles5.checkboxCell],
1132
1174
  onClick: (e) => {
1133
1175
  e.stopPropagation();
1134
- onClick(e);
1135
- }
1176
+ onCheckboxClick(e);
1177
+ },
1178
+ children: /* @__PURE__ */ jsx5(
1179
+ "input",
1180
+ {
1181
+ type: "checkbox",
1182
+ css: styles5.checkbox,
1183
+ checked: isSelected,
1184
+ onChange: () => {
1185
+ }
1186
+ }
1187
+ )
1136
1188
  }
1137
- ) }),
1189
+ ),
1138
1190
  /* @__PURE__ */ jsx5("td", { css: styles5.td, children: /* @__PURE__ */ jsxs5("div", { css: styles5.nameCell, children: [
1139
- isFolder ? /* @__PURE__ */ jsx5("svg", { css: styles5.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("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" }) }) : /* @__PURE__ */ jsx5("svg", { css: styles5.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("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" }) }),
1191
+ isFolder ? /* @__PURE__ */ jsx5("svg", { css: styles5.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("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" }) }) : item.thumbnail ? /* @__PURE__ */ jsx5("img", { css: styles5.thumbnail, src: item.thumbnail, alt: item.name, loading: "lazy" }) : /* @__PURE__ */ jsx5("svg", { css: styles5.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("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" }) }),
1140
1192
  /* @__PURE__ */ jsx5("span", { css: styles5.name, children: item.name })
1141
1193
  ] }) }),
1142
- /* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: item.size ? formatFileSize2(item.size) : "--" }),
1143
- /* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
1194
+ /* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: isFolder ? item.fileCount !== void 0 ? `${item.fileCount} files` : "--" : item.size !== void 0 ? formatFileSize2(item.size) : "--" }),
1195
+ /* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: isFolder ? item.totalSize !== void 0 ? formatFileSize2(item.totalSize) : "--" : item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
1144
1196
  /* @__PURE__ */ jsx5("td", { css: styles5.td, children: item.cdnSynced ? /* @__PURE__ */ jsxs5("span", { css: styles5.cdnBadge, children: [
1145
1197
  /* @__PURE__ */ jsx5("svg", { css: styles5.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx5("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" }) }),
1146
1198
  "Synced"
@@ -1157,6 +1209,16 @@ function formatFileSize2(bytes) {
1157
1209
  import { useState as useState4 } from "react";
1158
1210
  import { css as css6 } from "@emotion/react";
1159
1211
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "@emotion/react/jsx-runtime";
1212
+ var IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"];
1213
+ var VIDEO_EXTENSIONS = [".mp4", ".webm", ".mov", ".avi", ".mkv", ".m4v"];
1214
+ function isImageFile(filename) {
1215
+ const ext = filename.toLowerCase().substring(filename.lastIndexOf("."));
1216
+ return IMAGE_EXTENSIONS.includes(ext);
1217
+ }
1218
+ function isVideoFile(filename) {
1219
+ const ext = filename.toLowerCase().substring(filename.lastIndexOf("."));
1220
+ return VIDEO_EXTENSIONS.includes(ext);
1221
+ }
1160
1222
  var styles6 = {
1161
1223
  panel: css6`
1162
1224
  width: 320px;
@@ -1255,6 +1317,27 @@ var styles6 = {
1255
1317
  color: #9ca3af;
1256
1318
  margin: 0;
1257
1319
  `,
1320
+ filePlaceholder: css6`
1321
+ display: flex;
1322
+ align-items: center;
1323
+ justify-content: center;
1324
+ height: 120px;
1325
+ `,
1326
+ fileIcon: css6`
1327
+ width: 64px;
1328
+ height: 64px;
1329
+ color: #9ca3af;
1330
+ `,
1331
+ folderIcon: css6`
1332
+ width: 64px;
1333
+ height: 64px;
1334
+ color: #facc15;
1335
+ `,
1336
+ video: css6`
1337
+ width: 100%;
1338
+ height: auto;
1339
+ border-radius: 4px;
1340
+ `,
1258
1341
  actions: css6`
1259
1342
  margin-top: 16px;
1260
1343
  padding-top: 16px;
@@ -1367,20 +1450,44 @@ function StudioPreview() {
1367
1450
  ] });
1368
1451
  }
1369
1452
  const selectedPath = Array.from(selectedItems)[0];
1370
- const imageKey = selectedPath.replace(/^public\/images\//, "").replace(/^public\/originals\//, "");
1453
+ const isFolder = !selectedPath.includes(".") || selectedPath.endsWith("/");
1454
+ const filename = selectedPath.split("/").pop() || "";
1455
+ const isImage = isImageFile(filename);
1456
+ const isVideo = isVideoFile(filename);
1457
+ const imageKey = selectedPath.replace(/^public\/images\//, "").replace(/^public\/originals\//, "").replace(/^public\//, "");
1371
1458
  const imageData = meta?.images?.[imageKey];
1372
- return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1373
- modals,
1374
- /* @__PURE__ */ jsxs6("div", { css: styles6.panel, children: [
1375
- /* @__PURE__ */ jsx6("h3", { css: styles6.title, children: "Preview" }),
1376
- /* @__PURE__ */ jsx6("div", { css: styles6.imageContainer, children: /* @__PURE__ */ jsx6(
1459
+ const renderPreview = () => {
1460
+ if (isFolder) {
1461
+ return /* @__PURE__ */ jsx6("div", { css: styles6.filePlaceholder, children: /* @__PURE__ */ jsx6("svg", { css: styles6.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx6("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" }) }) });
1462
+ }
1463
+ if (isImage) {
1464
+ return /* @__PURE__ */ jsx6(
1377
1465
  "img",
1378
1466
  {
1379
1467
  css: styles6.image,
1380
1468
  src: selectedPath.replace("public", ""),
1381
1469
  alt: "Preview"
1382
1470
  }
1383
- ) }),
1471
+ );
1472
+ }
1473
+ if (isVideo) {
1474
+ return /* @__PURE__ */ jsx6(
1475
+ "video",
1476
+ {
1477
+ css: styles6.video,
1478
+ src: selectedPath.replace("public", ""),
1479
+ controls: true,
1480
+ muted: true
1481
+ }
1482
+ );
1483
+ }
1484
+ return /* @__PURE__ */ jsx6("div", { css: styles6.filePlaceholder, children: /* @__PURE__ */ jsx6("svg", { css: styles6.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx6("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" }) }) });
1485
+ };
1486
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1487
+ modals,
1488
+ /* @__PURE__ */ jsxs6("div", { css: styles6.panel, children: [
1489
+ /* @__PURE__ */ jsx6("h3", { css: styles6.title, children: "Preview" }),
1490
+ /* @__PURE__ */ jsx6("div", { css: styles6.imageContainer, children: renderPreview() }),
1384
1491
  /* @__PURE__ */ jsxs6("div", { css: styles6.info, children: [
1385
1492
  /* @__PURE__ */ jsx6(InfoRow, { label: "Filename", value: selectedPath.split("/").pop() || "" }),
1386
1493
  imageData && /* @__PURE__ */ jsxs6(Fragment2, { children: [
@@ -1884,4 +1991,4 @@ export {
1884
1991
  StudioUI,
1885
1992
  StudioUI_default as default
1886
1993
  };
1887
- //# sourceMappingURL=StudioUI-QWSXK4R6.mjs.map
1994
+ //# sourceMappingURL=StudioUI-SKFFRT7U.mjs.map