@gallop.software/studio 0.1.87 → 0.1.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{StudioUI-ZBSTYTUV.js → StudioUI-JQHRTF45.js} +268 -48
- package/dist/StudioUI-JQHRTF45.js.map +1 -0
- package/dist/{StudioUI-6HTM3QHM.mjs → StudioUI-T7FA7S7Z.mjs} +255 -35
- package/dist/StudioUI-T7FA7S7Z.mjs.map +1 -0
- package/dist/{chunk-CN5NRNWB.js → chunk-HE2DOD2K.js} +1 -1
- package/dist/chunk-HE2DOD2K.js.map +1 -0
- package/dist/{chunk-3RI33B7A.mjs → chunk-QR2FRA4L.mjs} +1 -1
- package/dist/chunk-QR2FRA4L.mjs.map +1 -0
- package/dist/handlers/index.d.mts +1 -1
- package/dist/handlers/index.d.ts +1 -1
- package/dist/handlers/index.js +442 -324
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +452 -334
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{types-C9CMIJLW.d.mts → types-Cxqb0WUK.d.mts} +10 -7
- package/dist/{types-C9CMIJLW.d.ts → types-Cxqb0WUK.d.ts} +10 -7
- package/package.json +1 -1
- package/dist/StudioUI-6HTM3QHM.mjs.map +0 -1
- package/dist/StudioUI-ZBSTYTUV.js.map +0 -1
- package/dist/chunk-3RI33B7A.mjs.map +0 -1
- package/dist/chunk-CN5NRNWB.js.map +0 -1
|
@@ -310,6 +310,23 @@ var progressStyles = {
|
|
|
310
310
|
white-space: nowrap;
|
|
311
311
|
overflow: hidden;
|
|
312
312
|
text-overflow: ellipsis;
|
|
313
|
+
`,
|
|
314
|
+
errorList: css`
|
|
315
|
+
margin-top: 12px;
|
|
316
|
+
padding: 12px;
|
|
317
|
+
background: #fef2f2;
|
|
318
|
+
border: 1px solid #fecaca;
|
|
319
|
+
border-radius: 6px;
|
|
320
|
+
max-height: 200px;
|
|
321
|
+
overflow-y: auto;
|
|
322
|
+
`,
|
|
323
|
+
errorItem: css`
|
|
324
|
+
font-size: ${fontSize.xs};
|
|
325
|
+
color: #991b1b;
|
|
326
|
+
margin: 0 0 4px;
|
|
327
|
+
&:last-child {
|
|
328
|
+
margin-bottom: 0;
|
|
329
|
+
}
|
|
313
330
|
`
|
|
314
331
|
};
|
|
315
332
|
function ProgressModal({
|
|
@@ -331,26 +348,36 @@ function ProgressModal({
|
|
|
331
348
|
" image",
|
|
332
349
|
(progress.processed ?? progress.current) !== 1 ? "s" : "",
|
|
333
350
|
" before stopping."
|
|
334
|
-
] }) : isComplete ? /* @__PURE__ */ jsxs(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
] }) : isComplete ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
352
|
+
/* @__PURE__ */ jsxs("p", { css: styles.message, children: [
|
|
353
|
+
"Processed ",
|
|
354
|
+
progress.processed,
|
|
355
|
+
" image",
|
|
356
|
+
progress.processed !== 1 ? "s" : "",
|
|
357
|
+
".",
|
|
358
|
+
progress.orphansRemoved !== void 0 && progress.orphansRemoved > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
359
|
+
" Removed ",
|
|
360
|
+
progress.orphansRemoved,
|
|
361
|
+
" orphaned thumbnail",
|
|
362
|
+
progress.orphansRemoved !== 1 ? "s" : "",
|
|
363
|
+
"."
|
|
364
|
+
] }) : null,
|
|
365
|
+
progress.errors !== void 0 && progress.errors > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
366
|
+
" ",
|
|
367
|
+
progress.errors,
|
|
368
|
+
" error",
|
|
369
|
+
progress.errors !== 1 ? "s" : "",
|
|
370
|
+
" occurred."
|
|
371
|
+
] }) : null
|
|
372
|
+
] }),
|
|
373
|
+
progress.errorMessages && progress.errorMessages.length > 0 && /* @__PURE__ */ jsxs("div", { css: progressStyles.errorList, children: [
|
|
374
|
+
progress.errorMessages.slice(0, 10).map((msg, i) => /* @__PURE__ */ jsx("p", { css: progressStyles.errorItem, children: msg }, i)),
|
|
375
|
+
progress.errorMessages.length > 10 && /* @__PURE__ */ jsxs("p", { css: progressStyles.errorItem, children: [
|
|
376
|
+
"...and ",
|
|
377
|
+
progress.errorMessages.length - 10,
|
|
378
|
+
" more"
|
|
379
|
+
] })
|
|
380
|
+
] })
|
|
354
381
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
355
382
|
/* @__PURE__ */ jsx("p", { css: styles.message, children: progress.status === "cleanup" ? "Cleaning up orphaned files..." : `Processing images...` }),
|
|
356
383
|
/* @__PURE__ */ jsxs("div", { css: progressStyles.progressContainer, children: [
|
|
@@ -1262,7 +1289,7 @@ function StudioToolbar() {
|
|
|
1262
1289
|
const fileInputRef = useRef(null);
|
|
1263
1290
|
const abortControllerRef = useRef(null);
|
|
1264
1291
|
const [uploading, setUploading] = useState3(false);
|
|
1265
|
-
const [
|
|
1292
|
+
const [scanning, setScanning] = useState3(false);
|
|
1266
1293
|
const [processing, setProcessing] = useState3(false);
|
|
1267
1294
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState3(false);
|
|
1268
1295
|
const [showProcessConfirm, setShowProcessConfirm] = useState3(false);
|
|
@@ -1288,10 +1315,81 @@ function StudioToolbar() {
|
|
|
1288
1315
|
const handleUpload = useCallback(() => {
|
|
1289
1316
|
fileInputRef.current?.click();
|
|
1290
1317
|
}, []);
|
|
1291
|
-
const
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1318
|
+
const handleScan = useCallback(async () => {
|
|
1319
|
+
setScanning(true);
|
|
1320
|
+
setShowProgress(true);
|
|
1321
|
+
setProgressState({
|
|
1322
|
+
current: 0,
|
|
1323
|
+
total: 0,
|
|
1324
|
+
percent: 0,
|
|
1325
|
+
status: "processing",
|
|
1326
|
+
message: "Scanning for files..."
|
|
1327
|
+
});
|
|
1328
|
+
try {
|
|
1329
|
+
const response = await fetch("/api/studio/scan", { method: "POST" });
|
|
1330
|
+
const reader = response.body?.getReader();
|
|
1331
|
+
if (!reader) throw new Error("No reader");
|
|
1332
|
+
const decoder = new TextDecoder();
|
|
1333
|
+
let buffer = "";
|
|
1334
|
+
while (true) {
|
|
1335
|
+
const { done, value } = await reader.read();
|
|
1336
|
+
if (done) break;
|
|
1337
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1338
|
+
const lines = buffer.split("\n\n");
|
|
1339
|
+
buffer = lines.pop() || "";
|
|
1340
|
+
for (const line of lines) {
|
|
1341
|
+
if (!line.startsWith("data: ")) continue;
|
|
1342
|
+
const data = JSON.parse(line.slice(6));
|
|
1343
|
+
if (data.type === "start") {
|
|
1344
|
+
setProgressState({
|
|
1345
|
+
current: 0,
|
|
1346
|
+
total: data.total,
|
|
1347
|
+
percent: 0,
|
|
1348
|
+
status: "processing",
|
|
1349
|
+
message: `Scanning ${data.total} files...`
|
|
1350
|
+
});
|
|
1351
|
+
} else if (data.type === "progress") {
|
|
1352
|
+
setProgressState({
|
|
1353
|
+
current: data.current,
|
|
1354
|
+
total: data.total,
|
|
1355
|
+
percent: data.percent,
|
|
1356
|
+
status: "processing",
|
|
1357
|
+
currentFile: data.currentFile
|
|
1358
|
+
});
|
|
1359
|
+
} else if (data.type === "complete") {
|
|
1360
|
+
setProgressState({
|
|
1361
|
+
current: data.total || 0,
|
|
1362
|
+
total: data.total || 0,
|
|
1363
|
+
percent: 100,
|
|
1364
|
+
status: "complete",
|
|
1365
|
+
processed: data.added,
|
|
1366
|
+
errors: data.errors,
|
|
1367
|
+
message: data.renamed > 0 ? `${data.renamed} file(s) renamed due to conflicts` : void 0
|
|
1368
|
+
});
|
|
1369
|
+
triggerRefresh();
|
|
1370
|
+
} else if (data.type === "error") {
|
|
1371
|
+
setProgressState({
|
|
1372
|
+
current: 0,
|
|
1373
|
+
total: 0,
|
|
1374
|
+
percent: 0,
|
|
1375
|
+
status: "error",
|
|
1376
|
+
message: data.message || "Scan failed"
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
console.error("Scan error:", error);
|
|
1383
|
+
setProgressState({
|
|
1384
|
+
current: 0,
|
|
1385
|
+
total: 0,
|
|
1386
|
+
percent: 0,
|
|
1387
|
+
status: "error",
|
|
1388
|
+
message: "Scan failed"
|
|
1389
|
+
});
|
|
1390
|
+
} finally {
|
|
1391
|
+
setScanning(false);
|
|
1392
|
+
}
|
|
1295
1393
|
}, [triggerRefresh]);
|
|
1296
1394
|
const handleFileChange = useCallback(async (e) => {
|
|
1297
1395
|
const files = e.target.files;
|
|
@@ -1704,6 +1802,7 @@ function StudioToolbar() {
|
|
|
1704
1802
|
setShowProgress(true);
|
|
1705
1803
|
let synced = 0;
|
|
1706
1804
|
let errors = 0;
|
|
1805
|
+
const errorMessages = [];
|
|
1707
1806
|
try {
|
|
1708
1807
|
for (let i = 0; i < imageKeys.length; i++) {
|
|
1709
1808
|
const imageKey = imageKeys[i];
|
|
@@ -1728,13 +1827,18 @@ function StudioToolbar() {
|
|
|
1728
1827
|
return;
|
|
1729
1828
|
}
|
|
1730
1829
|
errors++;
|
|
1830
|
+
errorMessages.push(data.error || `Failed: ${imageKey}`);
|
|
1731
1831
|
} else if (data.synced?.length > 0) {
|
|
1732
1832
|
synced++;
|
|
1733
1833
|
} else if (data.errors?.length > 0) {
|
|
1734
1834
|
errors++;
|
|
1835
|
+
for (const errMsg of data.errors) {
|
|
1836
|
+
errorMessages.push(errMsg);
|
|
1837
|
+
}
|
|
1735
1838
|
}
|
|
1736
|
-
} catch {
|
|
1839
|
+
} catch (err) {
|
|
1737
1840
|
errors++;
|
|
1841
|
+
errorMessages.push(`Network error: ${imageKey}`);
|
|
1738
1842
|
}
|
|
1739
1843
|
}
|
|
1740
1844
|
setProgressState({
|
|
@@ -1743,7 +1847,8 @@ function StudioToolbar() {
|
|
|
1743
1847
|
percent: 100,
|
|
1744
1848
|
status: "complete",
|
|
1745
1849
|
processed: synced,
|
|
1746
|
-
errors
|
|
1850
|
+
errors,
|
|
1851
|
+
errorMessages: errorMessages.length > 0 ? errorMessages : void 0
|
|
1747
1852
|
});
|
|
1748
1853
|
clearSelection();
|
|
1749
1854
|
triggerRefresh();
|
|
@@ -2070,12 +2175,16 @@ function StudioToolbar() {
|
|
|
2070
2175
|
" selected",
|
|
2071
2176
|
/* @__PURE__ */ jsx4("button", { css: styles4.clearBtn, onClick: clearSelection, children: "Clear" })
|
|
2072
2177
|
] }),
|
|
2073
|
-
/* @__PURE__ */
|
|
2178
|
+
/* @__PURE__ */ jsxs4(
|
|
2074
2179
|
"button",
|
|
2075
2180
|
{
|
|
2076
|
-
css:
|
|
2077
|
-
onClick:
|
|
2078
|
-
|
|
2181
|
+
css: styles4.btn,
|
|
2182
|
+
onClick: handleScan,
|
|
2183
|
+
disabled: scanning,
|
|
2184
|
+
children: [
|
|
2185
|
+
/* @__PURE__ */ jsx4(ScanIcon, { spinning: scanning }),
|
|
2186
|
+
"Scan"
|
|
2187
|
+
]
|
|
2079
2188
|
}
|
|
2080
2189
|
),
|
|
2081
2190
|
/* @__PURE__ */ jsxs4("div", { css: styles4.viewToggle, children: [
|
|
@@ -2105,7 +2214,7 @@ function StudioToolbar() {
|
|
|
2105
2214
|
function UploadIcon() {
|
|
2106
2215
|
return /* @__PURE__ */ jsx4("svg", { css: styles4.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("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" }) });
|
|
2107
2216
|
}
|
|
2108
|
-
function
|
|
2217
|
+
function ScanIcon({ spinning }) {
|
|
2109
2218
|
return /* @__PURE__ */ jsx4("svg", { css: [styles4.icon, spinning && styles4.iconSpin], fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("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" }) });
|
|
2110
2219
|
}
|
|
2111
2220
|
function TrashIcon() {
|
|
@@ -2239,6 +2348,7 @@ function useFileList() {
|
|
|
2239
2348
|
} = useStudio();
|
|
2240
2349
|
const [items, setItems] = useState4([]);
|
|
2241
2350
|
const [loading, setLoading] = useState4(true);
|
|
2351
|
+
const [metaEmpty, setMetaEmpty] = useState4(false);
|
|
2242
2352
|
const isInitialLoad = useRef2(true);
|
|
2243
2353
|
const lastPath = useRef2(currentPath);
|
|
2244
2354
|
useEffect2(() => {
|
|
@@ -2251,10 +2361,12 @@ function useFileList() {
|
|
|
2251
2361
|
try {
|
|
2252
2362
|
const data = searchQuery && searchQuery.length >= 2 ? await studioApi.search(searchQuery) : await studioApi.list(currentPath);
|
|
2253
2363
|
setItems(data.items || []);
|
|
2364
|
+
setMetaEmpty(data.isEmpty === true);
|
|
2254
2365
|
} catch (error) {
|
|
2255
2366
|
const message = error instanceof Error ? error.message : "Failed to load items";
|
|
2256
2367
|
showError("Load Error", message);
|
|
2257
2368
|
setItems([]);
|
|
2369
|
+
setMetaEmpty(false);
|
|
2258
2370
|
}
|
|
2259
2371
|
setLoading(false);
|
|
2260
2372
|
isInitialLoad.current = false;
|
|
@@ -2306,6 +2418,7 @@ function useFileList() {
|
|
|
2306
2418
|
items,
|
|
2307
2419
|
loading,
|
|
2308
2420
|
sortedItems,
|
|
2421
|
+
metaEmpty,
|
|
2309
2422
|
// Computed
|
|
2310
2423
|
isAtRoot,
|
|
2311
2424
|
isSearching,
|
|
@@ -2366,6 +2479,27 @@ var styles5 = {
|
|
|
2366
2479
|
font-size: ${fontSize.sm};
|
|
2367
2480
|
}
|
|
2368
2481
|
`,
|
|
2482
|
+
scanButton: css5`
|
|
2483
|
+
margin-top: 16px;
|
|
2484
|
+
padding: 10px 24px;
|
|
2485
|
+
font-size: ${fontSize.base};
|
|
2486
|
+
font-weight: 500;
|
|
2487
|
+
background: ${colors.primary};
|
|
2488
|
+
color: white;
|
|
2489
|
+
border: none;
|
|
2490
|
+
border-radius: 8px;
|
|
2491
|
+
cursor: pointer;
|
|
2492
|
+
transition: background 0.15s ease;
|
|
2493
|
+
|
|
2494
|
+
&:hover:not(:disabled) {
|
|
2495
|
+
background: ${colors.primaryHover};
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
&:disabled {
|
|
2499
|
+
opacity: 0.6;
|
|
2500
|
+
cursor: not-allowed;
|
|
2501
|
+
}
|
|
2502
|
+
`,
|
|
2369
2503
|
grid: css5`
|
|
2370
2504
|
display: grid;
|
|
2371
2505
|
grid-template-columns: 1fr;
|
|
@@ -2649,6 +2783,7 @@ function StudioFileGrid() {
|
|
|
2649
2783
|
const {
|
|
2650
2784
|
loading,
|
|
2651
2785
|
sortedItems,
|
|
2786
|
+
metaEmpty,
|
|
2652
2787
|
isAtRoot,
|
|
2653
2788
|
isSearching,
|
|
2654
2789
|
allItemsSelected,
|
|
@@ -2660,14 +2795,42 @@ function StudioFileGrid() {
|
|
|
2660
2795
|
handleGenerateThumbnail,
|
|
2661
2796
|
handleSelectAll
|
|
2662
2797
|
} = useFileList();
|
|
2798
|
+
const [scanning, setScanning] = useState5(false);
|
|
2799
|
+
const handleScan = async () => {
|
|
2800
|
+
setScanning(true);
|
|
2801
|
+
try {
|
|
2802
|
+
await fetch("/api/studio/scan", { method: "POST" });
|
|
2803
|
+
window.location.reload();
|
|
2804
|
+
} catch (error) {
|
|
2805
|
+
console.error("Scan failed:", error);
|
|
2806
|
+
} finally {
|
|
2807
|
+
setScanning(false);
|
|
2808
|
+
}
|
|
2809
|
+
};
|
|
2663
2810
|
if (loading) {
|
|
2664
2811
|
return /* @__PURE__ */ jsx5("div", { css: styles5.loading, children: /* @__PURE__ */ jsx5("div", { css: styles5.spinner }) });
|
|
2665
2812
|
}
|
|
2813
|
+
if (metaEmpty && isAtRoot) {
|
|
2814
|
+
return /* @__PURE__ */ jsxs5("div", { css: styles5.empty, children: [
|
|
2815
|
+
/* @__PURE__ */ jsx5("svg", { css: styles5.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }),
|
|
2816
|
+
/* @__PURE__ */ jsx5("p", { css: styles5.emptyText, children: "No files tracked yet" }),
|
|
2817
|
+
/* @__PURE__ */ jsx5("p", { css: styles5.emptyText, children: "Click Scan to discover files in your public folder" }),
|
|
2818
|
+
/* @__PURE__ */ jsx5(
|
|
2819
|
+
"button",
|
|
2820
|
+
{
|
|
2821
|
+
css: styles5.scanButton,
|
|
2822
|
+
onClick: handleScan,
|
|
2823
|
+
disabled: scanning,
|
|
2824
|
+
children: scanning ? "Scanning..." : "Scan for Files"
|
|
2825
|
+
}
|
|
2826
|
+
)
|
|
2827
|
+
] });
|
|
2828
|
+
}
|
|
2666
2829
|
if (sortedItems.length === 0 && isAtRoot) {
|
|
2667
2830
|
return /* @__PURE__ */ jsxs5("div", { css: styles5.empty, children: [
|
|
2668
2831
|
/* @__PURE__ */ jsx5("svg", { css: styles5.emptyIcon, 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" }) }),
|
|
2669
2832
|
/* @__PURE__ */ jsx5("p", { css: styles5.emptyText, children: "No files in this folder" }),
|
|
2670
|
-
/* @__PURE__ */ jsx5("p", { css: styles5.emptyText, children: "Upload images
|
|
2833
|
+
/* @__PURE__ */ jsx5("p", { css: styles5.emptyText, children: "Upload images or click Scan in the toolbar" })
|
|
2671
2834
|
] });
|
|
2672
2835
|
}
|
|
2673
2836
|
return /* @__PURE__ */ jsxs5("div", { children: [
|
|
@@ -2864,6 +3027,32 @@ var styles6 = {
|
|
|
2864
3027
|
height: 256px;
|
|
2865
3028
|
color: ${colors.textSecondary};
|
|
2866
3029
|
`,
|
|
3030
|
+
emptyHint: css6`
|
|
3031
|
+
font-size: ${fontSize.sm};
|
|
3032
|
+
color: ${colors.textMuted};
|
|
3033
|
+
margin-top: 4px;
|
|
3034
|
+
`,
|
|
3035
|
+
scanButton: css6`
|
|
3036
|
+
margin-top: 16px;
|
|
3037
|
+
padding: 10px 24px;
|
|
3038
|
+
font-size: ${fontSize.base};
|
|
3039
|
+
font-weight: 500;
|
|
3040
|
+
background: ${colors.primary};
|
|
3041
|
+
color: white;
|
|
3042
|
+
border: none;
|
|
3043
|
+
border-radius: 8px;
|
|
3044
|
+
cursor: pointer;
|
|
3045
|
+
transition: background 0.15s ease;
|
|
3046
|
+
|
|
3047
|
+
&:hover:not(:disabled) {
|
|
3048
|
+
background: ${colors.primaryHover};
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
&:disabled {
|
|
3052
|
+
opacity: 0.6;
|
|
3053
|
+
cursor: not-allowed;
|
|
3054
|
+
}
|
|
3055
|
+
`,
|
|
2867
3056
|
tableWrapper: css6`
|
|
2868
3057
|
background: ${colors.surface};
|
|
2869
3058
|
border-radius: 8px;
|
|
@@ -3150,6 +3339,7 @@ function StudioFileList() {
|
|
|
3150
3339
|
const {
|
|
3151
3340
|
loading,
|
|
3152
3341
|
sortedItems,
|
|
3342
|
+
metaEmpty,
|
|
3153
3343
|
isAtRoot,
|
|
3154
3344
|
isSearching,
|
|
3155
3345
|
allItemsSelected,
|
|
@@ -3161,11 +3351,41 @@ function StudioFileList() {
|
|
|
3161
3351
|
handleGenerateThumbnail,
|
|
3162
3352
|
handleSelectAll
|
|
3163
3353
|
} = useFileList();
|
|
3354
|
+
const [scanning, setScanning] = useState6(false);
|
|
3355
|
+
const handleScan = async () => {
|
|
3356
|
+
setScanning(true);
|
|
3357
|
+
try {
|
|
3358
|
+
await fetch("/api/studio/scan", { method: "POST" });
|
|
3359
|
+
window.location.reload();
|
|
3360
|
+
} catch (error) {
|
|
3361
|
+
console.error("Scan failed:", error);
|
|
3362
|
+
} finally {
|
|
3363
|
+
setScanning(false);
|
|
3364
|
+
}
|
|
3365
|
+
};
|
|
3164
3366
|
if (loading) {
|
|
3165
3367
|
return /* @__PURE__ */ jsx6("div", { css: styles6.loading, children: /* @__PURE__ */ jsx6("div", { css: styles6.spinner }) });
|
|
3166
3368
|
}
|
|
3369
|
+
if (metaEmpty && isAtRoot) {
|
|
3370
|
+
return /* @__PURE__ */ jsxs6("div", { css: styles6.empty, children: [
|
|
3371
|
+
/* @__PURE__ */ jsx6("p", { children: "No files tracked yet" }),
|
|
3372
|
+
/* @__PURE__ */ jsx6("p", { css: styles6.emptyHint, children: "Click Scan to discover files in your public folder" }),
|
|
3373
|
+
/* @__PURE__ */ jsx6(
|
|
3374
|
+
"button",
|
|
3375
|
+
{
|
|
3376
|
+
css: styles6.scanButton,
|
|
3377
|
+
onClick: handleScan,
|
|
3378
|
+
disabled: scanning,
|
|
3379
|
+
children: scanning ? "Scanning..." : "Scan for Files"
|
|
3380
|
+
}
|
|
3381
|
+
)
|
|
3382
|
+
] });
|
|
3383
|
+
}
|
|
3167
3384
|
if (sortedItems.length === 0 && isAtRoot) {
|
|
3168
|
-
return /* @__PURE__ */
|
|
3385
|
+
return /* @__PURE__ */ jsxs6("div", { css: styles6.empty, children: [
|
|
3386
|
+
/* @__PURE__ */ jsx6("p", { children: "No files in this folder" }),
|
|
3387
|
+
/* @__PURE__ */ jsx6("p", { css: styles6.emptyHint, children: "Upload images or click Scan in the toolbar" })
|
|
3388
|
+
] });
|
|
3169
3389
|
}
|
|
3170
3390
|
return /* @__PURE__ */ jsx6("div", { css: styles6.tableWrapper, children: /* @__PURE__ */ jsxs6("table", { css: styles6.table, children: [
|
|
3171
3391
|
/* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsxs6("tr", { children: [
|
|
@@ -4661,4 +4881,4 @@ export {
|
|
|
4661
4881
|
StudioUI,
|
|
4662
4882
|
StudioUI_default as default
|
|
4663
4883
|
};
|
|
4664
|
-
//# sourceMappingURL=StudioUI-
|
|
4884
|
+
//# sourceMappingURL=StudioUI-T7FA7S7Z.mjs.map
|