@gallop.software/studio 1.2.8 → 1.3.0
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-2MWJ4M3B.js → StudioUI-52NHWBVJ.js} +548 -229
- package/dist/StudioUI-52NHWBVJ.js.map +1 -0
- package/dist/{StudioUI-2JOOX5WQ.mjs → StudioUI-K6TIH6LF.mjs} +577 -258
- package/dist/StudioUI-K6TIH6LF.mjs.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/StudioUI-2JOOX5WQ.mjs.map +0 -1
- package/dist/StudioUI-2MWJ4M3B.js.map +0 -1
|
@@ -7,11 +7,24 @@ import {
|
|
|
7
7
|
} from "./chunk-RHI3UROE.mjs";
|
|
8
8
|
|
|
9
9
|
// src/components/StudioUI.tsx
|
|
10
|
-
import { useEffect as useEffect5, useCallback as
|
|
10
|
+
import { useEffect as useEffect5, useCallback as useCallback6, useState as useState11 } from "react";
|
|
11
11
|
import { css as css11 } from "@emotion/react";
|
|
12
12
|
|
|
13
13
|
// src/components/StudioContext.tsx
|
|
14
14
|
import { createContext, useContext } from "react";
|
|
15
|
+
var defaultActionState = {
|
|
16
|
+
showProgress: false,
|
|
17
|
+
progressTitle: "",
|
|
18
|
+
progressState: { current: 0, total: 0, percent: 0, status: "processing" },
|
|
19
|
+
showDeleteConfirm: false,
|
|
20
|
+
showMoveModal: false,
|
|
21
|
+
showSyncConfirm: false,
|
|
22
|
+
showProcessConfirm: false,
|
|
23
|
+
actionPaths: [],
|
|
24
|
+
syncImageCount: 0,
|
|
25
|
+
syncHasRemote: false,
|
|
26
|
+
syncHasLocal: false
|
|
27
|
+
};
|
|
15
28
|
var defaultState = {
|
|
16
29
|
isOpen: false,
|
|
17
30
|
openStudio: () => {
|
|
@@ -65,6 +78,34 @@ var defaultState = {
|
|
|
65
78
|
},
|
|
66
79
|
fileItems: [],
|
|
67
80
|
setFileItems: () => {
|
|
81
|
+
},
|
|
82
|
+
// Shared action state
|
|
83
|
+
actionState: defaultActionState,
|
|
84
|
+
// Shared action handlers
|
|
85
|
+
requestDelete: () => {
|
|
86
|
+
},
|
|
87
|
+
requestMove: () => {
|
|
88
|
+
},
|
|
89
|
+
requestSync: () => {
|
|
90
|
+
},
|
|
91
|
+
requestProcess: () => {
|
|
92
|
+
},
|
|
93
|
+
confirmDelete: async () => {
|
|
94
|
+
},
|
|
95
|
+
confirmMove: async () => {
|
|
96
|
+
},
|
|
97
|
+
confirmSync: async () => {
|
|
98
|
+
},
|
|
99
|
+
confirmProcess: async () => {
|
|
100
|
+
},
|
|
101
|
+
cancelAction: () => {
|
|
102
|
+
},
|
|
103
|
+
closeProgress: () => {
|
|
104
|
+
},
|
|
105
|
+
stopProcessing: () => {
|
|
106
|
+
},
|
|
107
|
+
abortController: null,
|
|
108
|
+
deleteOrphans: async () => {
|
|
68
109
|
}
|
|
69
110
|
};
|
|
70
111
|
var StudioContext = createContext(defaultState);
|
|
@@ -4392,17 +4433,23 @@ var styles8 = {
|
|
|
4392
4433
|
`
|
|
4393
4434
|
};
|
|
4394
4435
|
function StudioDetailView() {
|
|
4395
|
-
const {
|
|
4396
|
-
|
|
4436
|
+
const {
|
|
4437
|
+
focusedItem,
|
|
4438
|
+
setFocusedItem,
|
|
4439
|
+
triggerRefresh,
|
|
4440
|
+
fileItems,
|
|
4441
|
+
// Shared action handlers
|
|
4442
|
+
requestDelete,
|
|
4443
|
+
requestMove,
|
|
4444
|
+
requestSync,
|
|
4445
|
+
requestProcess,
|
|
4446
|
+
actionState
|
|
4447
|
+
} = useStudio();
|
|
4397
4448
|
const [showRenameModal, setShowRenameModal] = useState8(false);
|
|
4398
|
-
const [showMoveModal, setShowMoveModal] = useState8(false);
|
|
4399
|
-
const [showProcessConfirm, setShowProcessConfirm] = useState8(false);
|
|
4400
4449
|
const [showR2SetupModal, setShowR2SetupModal] = useState8(false);
|
|
4401
|
-
const [processProgress, setProcessProgress] = useState8(null);
|
|
4402
4450
|
const [alertMessage, setAlertMessage] = useState8(null);
|
|
4403
4451
|
const [showCopied, setShowCopied] = useState8(false);
|
|
4404
|
-
const
|
|
4405
|
-
const [moving, setMoving] = useState8(false);
|
|
4452
|
+
const isActionInProgress = actionState.showProgress;
|
|
4406
4453
|
if (!focusedItem) return null;
|
|
4407
4454
|
const isImage = isImageFile(focusedItem.name);
|
|
4408
4455
|
const isVideo = isVideoFile(focusedItem.name);
|
|
@@ -4449,177 +4496,6 @@ function StudioDetailView() {
|
|
|
4449
4496
|
}
|
|
4450
4497
|
}
|
|
4451
4498
|
};
|
|
4452
|
-
const handleDelete = async () => {
|
|
4453
|
-
setShowDeleteConfirm(false);
|
|
4454
|
-
try {
|
|
4455
|
-
const response = await fetch("/api/studio/delete", {
|
|
4456
|
-
method: "POST",
|
|
4457
|
-
headers: { "Content-Type": "application/json" },
|
|
4458
|
-
body: JSON.stringify({ paths: [focusedItem.path] })
|
|
4459
|
-
});
|
|
4460
|
-
if (response.ok) {
|
|
4461
|
-
clearSelection();
|
|
4462
|
-
triggerRefresh();
|
|
4463
|
-
setFocusedItem(null);
|
|
4464
|
-
} else {
|
|
4465
|
-
const error = await response.json();
|
|
4466
|
-
setAlertMessage({
|
|
4467
|
-
title: "Delete Failed",
|
|
4468
|
-
message: error.error || "Unknown error"
|
|
4469
|
-
});
|
|
4470
|
-
}
|
|
4471
|
-
} catch (error) {
|
|
4472
|
-
console.error("Delete error:", error);
|
|
4473
|
-
setAlertMessage({
|
|
4474
|
-
title: "Delete Failed",
|
|
4475
|
-
message: "Delete failed. Check console for details."
|
|
4476
|
-
});
|
|
4477
|
-
}
|
|
4478
|
-
};
|
|
4479
|
-
const handleMove = async (destination) => {
|
|
4480
|
-
setShowMoveModal(false);
|
|
4481
|
-
setMoving(true);
|
|
4482
|
-
try {
|
|
4483
|
-
const response = await fetch("/api/studio/move", {
|
|
4484
|
-
method: "POST",
|
|
4485
|
-
headers: { "Content-Type": "application/json" },
|
|
4486
|
-
body: JSON.stringify({ paths: [focusedItem.path], destination })
|
|
4487
|
-
});
|
|
4488
|
-
if (!response.body) {
|
|
4489
|
-
throw new Error("No response body");
|
|
4490
|
-
}
|
|
4491
|
-
const reader = response.body.getReader();
|
|
4492
|
-
const decoder = new TextDecoder();
|
|
4493
|
-
let buffer = "";
|
|
4494
|
-
while (true) {
|
|
4495
|
-
const { done, value } = await reader.read();
|
|
4496
|
-
if (done) break;
|
|
4497
|
-
buffer += decoder.decode(value, { stream: true });
|
|
4498
|
-
const lines = buffer.split("\n\n");
|
|
4499
|
-
buffer = lines.pop() || "";
|
|
4500
|
-
for (const line of lines) {
|
|
4501
|
-
if (!line.startsWith("data: ")) continue;
|
|
4502
|
-
try {
|
|
4503
|
-
const data = JSON.parse(line.slice(6));
|
|
4504
|
-
if (data.type === "complete") {
|
|
4505
|
-
if (data.errors > 0 && data.errorMessages?.length > 0) {
|
|
4506
|
-
setAlertMessage({
|
|
4507
|
-
title: "Move Failed",
|
|
4508
|
-
message: data.errorMessages.join("\n")
|
|
4509
|
-
});
|
|
4510
|
-
} else {
|
|
4511
|
-
clearSelection();
|
|
4512
|
-
triggerRefresh();
|
|
4513
|
-
setFocusedItem(null);
|
|
4514
|
-
}
|
|
4515
|
-
} else if (data.type === "error") {
|
|
4516
|
-
setAlertMessage({
|
|
4517
|
-
title: "Move Failed",
|
|
4518
|
-
message: data.message || "Unknown error"
|
|
4519
|
-
});
|
|
4520
|
-
}
|
|
4521
|
-
} catch {
|
|
4522
|
-
}
|
|
4523
|
-
}
|
|
4524
|
-
}
|
|
4525
|
-
} catch (error) {
|
|
4526
|
-
console.error("Move error:", error);
|
|
4527
|
-
setAlertMessage({
|
|
4528
|
-
title: "Move Failed",
|
|
4529
|
-
message: "Failed to move file. Check console for details."
|
|
4530
|
-
});
|
|
4531
|
-
} finally {
|
|
4532
|
-
setMoving(false);
|
|
4533
|
-
}
|
|
4534
|
-
};
|
|
4535
|
-
const handleSync = async () => {
|
|
4536
|
-
const imageKey = "/" + focusedItem.path.replace(/^public\//, "");
|
|
4537
|
-
setPushing(true);
|
|
4538
|
-
try {
|
|
4539
|
-
const response = await fetch("/api/studio/sync", {
|
|
4540
|
-
method: "POST",
|
|
4541
|
-
headers: { "Content-Type": "application/json" },
|
|
4542
|
-
body: JSON.stringify({ imageKeys: [imageKey] })
|
|
4543
|
-
});
|
|
4544
|
-
const data = await response.json();
|
|
4545
|
-
if (response.ok) {
|
|
4546
|
-
setAlertMessage({
|
|
4547
|
-
title: "Push Complete",
|
|
4548
|
-
message: "Successfully pushed to CDN."
|
|
4549
|
-
});
|
|
4550
|
-
triggerRefresh();
|
|
4551
|
-
} else {
|
|
4552
|
-
if (data.error?.includes("R2 not configured") || data.error?.includes("CLOUDFLARE_R2")) {
|
|
4553
|
-
setShowR2SetupModal(true);
|
|
4554
|
-
} else {
|
|
4555
|
-
setAlertMessage({
|
|
4556
|
-
title: "Push Failed",
|
|
4557
|
-
message: data.error || "Failed to push to CDN."
|
|
4558
|
-
});
|
|
4559
|
-
}
|
|
4560
|
-
}
|
|
4561
|
-
} catch (error) {
|
|
4562
|
-
console.error("Push error:", error);
|
|
4563
|
-
setAlertMessage({
|
|
4564
|
-
title: "Push Failed",
|
|
4565
|
-
message: "Failed to push to CDN. Check console for details."
|
|
4566
|
-
});
|
|
4567
|
-
} finally {
|
|
4568
|
-
setPushing(false);
|
|
4569
|
-
}
|
|
4570
|
-
};
|
|
4571
|
-
const handleProcessImage = async () => {
|
|
4572
|
-
setShowProcessConfirm(false);
|
|
4573
|
-
setProcessProgress({
|
|
4574
|
-
current: 0,
|
|
4575
|
-
total: 1,
|
|
4576
|
-
percent: 0,
|
|
4577
|
-
status: "processing",
|
|
4578
|
-
currentFile: focusedItem.name
|
|
4579
|
-
});
|
|
4580
|
-
try {
|
|
4581
|
-
const imageKey = focusedItem.path.replace(/^public\//, "");
|
|
4582
|
-
const formattedKey = imageKey.startsWith("/") ? imageKey : `/${imageKey}`;
|
|
4583
|
-
const response = await fetch("/api/studio/reprocess", {
|
|
4584
|
-
method: "POST",
|
|
4585
|
-
headers: { "Content-Type": "application/json" },
|
|
4586
|
-
body: JSON.stringify({
|
|
4587
|
-
imageKeys: [formattedKey]
|
|
4588
|
-
})
|
|
4589
|
-
});
|
|
4590
|
-
const data = await response.json();
|
|
4591
|
-
if (!response.ok) {
|
|
4592
|
-
throw new Error(data.error || "Processing failed");
|
|
4593
|
-
}
|
|
4594
|
-
if (data.processed?.length > 0) {
|
|
4595
|
-
setProcessProgress({
|
|
4596
|
-
current: 1,
|
|
4597
|
-
total: 1,
|
|
4598
|
-
percent: 100,
|
|
4599
|
-
status: "complete",
|
|
4600
|
-
message: `Processed ${focusedItem.name}`
|
|
4601
|
-
});
|
|
4602
|
-
} else if (data.errors?.length > 0) {
|
|
4603
|
-
setProcessProgress({
|
|
4604
|
-
current: 0,
|
|
4605
|
-
total: 1,
|
|
4606
|
-
percent: 0,
|
|
4607
|
-
status: "error",
|
|
4608
|
-
message: `Failed to process: ${data.errors.join(", ")}`
|
|
4609
|
-
});
|
|
4610
|
-
}
|
|
4611
|
-
triggerRefresh();
|
|
4612
|
-
} catch (error) {
|
|
4613
|
-
console.error("Process error:", error);
|
|
4614
|
-
setProcessProgress({
|
|
4615
|
-
current: 0,
|
|
4616
|
-
total: 1,
|
|
4617
|
-
percent: 0,
|
|
4618
|
-
status: "error",
|
|
4619
|
-
message: error instanceof Error ? error.message : "Failed to process image"
|
|
4620
|
-
});
|
|
4621
|
-
}
|
|
4622
|
-
};
|
|
4623
4499
|
const renderMedia = () => {
|
|
4624
4500
|
if (isImage) {
|
|
4625
4501
|
return /* @__PURE__ */ jsx8("img", { css: styles8.image, src: imageSrc, alt: focusedItem.name });
|
|
@@ -4633,17 +4509,6 @@ function StudioDetailView() {
|
|
|
4633
4509
|
] });
|
|
4634
4510
|
};
|
|
4635
4511
|
return /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
4636
|
-
showDeleteConfirm && /* @__PURE__ */ jsx8(
|
|
4637
|
-
ConfirmModal,
|
|
4638
|
-
{
|
|
4639
|
-
title: "Delete File",
|
|
4640
|
-
message: `Are you sure you want to delete "${focusedItem.name}"? This action cannot be undone.`,
|
|
4641
|
-
confirmLabel: "Delete",
|
|
4642
|
-
variant: "danger",
|
|
4643
|
-
onConfirm: handleDelete,
|
|
4644
|
-
onCancel: () => setShowDeleteConfirm(false)
|
|
4645
|
-
}
|
|
4646
|
-
),
|
|
4647
4512
|
alertMessage && /* @__PURE__ */ jsx8(
|
|
4648
4513
|
AlertModal,
|
|
4649
4514
|
{
|
|
@@ -4671,33 +4536,6 @@ function StudioDetailView() {
|
|
|
4671
4536
|
onCancel: () => setShowRenameModal(false)
|
|
4672
4537
|
}
|
|
4673
4538
|
),
|
|
4674
|
-
showMoveModal && /* @__PURE__ */ jsx8(
|
|
4675
|
-
StudioFolderPicker,
|
|
4676
|
-
{
|
|
4677
|
-
selectedItems: /* @__PURE__ */ new Set([focusedItem.path]),
|
|
4678
|
-
currentPath: focusedItem.path.split("/").slice(0, -1).join("/"),
|
|
4679
|
-
onMove: handleMove,
|
|
4680
|
-
onCancel: () => setShowMoveModal(false)
|
|
4681
|
-
}
|
|
4682
|
-
),
|
|
4683
|
-
showProcessConfirm && /* @__PURE__ */ jsx8(
|
|
4684
|
-
ConfirmModal,
|
|
4685
|
-
{
|
|
4686
|
-
title: "Process Image",
|
|
4687
|
-
message: `Generate thumbnails for "${focusedItem.name}"?`,
|
|
4688
|
-
confirmLabel: "Process",
|
|
4689
|
-
onConfirm: handleProcessImage,
|
|
4690
|
-
onCancel: () => setShowProcessConfirm(false)
|
|
4691
|
-
}
|
|
4692
|
-
),
|
|
4693
|
-
processProgress && /* @__PURE__ */ jsx8(
|
|
4694
|
-
ProgressModal,
|
|
4695
|
-
{
|
|
4696
|
-
title: "Processing Image",
|
|
4697
|
-
progress: processProgress,
|
|
4698
|
-
onClose: () => setProcessProgress(null)
|
|
4699
|
-
}
|
|
4700
|
-
),
|
|
4701
4539
|
/* @__PURE__ */ jsx8("div", { css: styles8.overlay, onClick: handleClose, children: /* @__PURE__ */ jsxs8("div", { css: styles8.container, onClick: (e) => e.stopPropagation(), children: [
|
|
4702
4540
|
/* @__PURE__ */ jsxs8("div", { css: styles8.main, children: [
|
|
4703
4541
|
/* @__PURE__ */ jsxs8("div", { css: styles8.headerButtons, children: [
|
|
@@ -4771,11 +4609,11 @@ function StudioDetailView() {
|
|
|
4771
4609
|
"button",
|
|
4772
4610
|
{
|
|
4773
4611
|
css: styles8.actionBtn,
|
|
4774
|
-
onClick: () =>
|
|
4775
|
-
disabled:
|
|
4612
|
+
onClick: () => requestMove([focusedItem.path]),
|
|
4613
|
+
disabled: isActionInProgress || focusedItem.isProtected,
|
|
4776
4614
|
children: [
|
|
4777
4615
|
/* @__PURE__ */ jsx8("svg", { css: styles8.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx8("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" }) }),
|
|
4778
|
-
|
|
4616
|
+
"Move"
|
|
4779
4617
|
]
|
|
4780
4618
|
}
|
|
4781
4619
|
),
|
|
@@ -4783,12 +4621,12 @@ function StudioDetailView() {
|
|
|
4783
4621
|
"button",
|
|
4784
4622
|
{
|
|
4785
4623
|
css: styles8.actionBtn,
|
|
4786
|
-
onClick:
|
|
4787
|
-
disabled:
|
|
4624
|
+
onClick: () => requestSync([focusedItem.path], fileItems),
|
|
4625
|
+
disabled: isActionInProgress || focusedItem.isProtected || focusedItem.cdnPushed && !focusedItem.isRemote,
|
|
4788
4626
|
title: focusedItem.cdnPushed && !focusedItem.isRemote ? "Already in R2" : void 0,
|
|
4789
4627
|
children: [
|
|
4790
4628
|
/* @__PURE__ */ jsx8("svg", { css: styles8.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx8("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" }) }),
|
|
4791
|
-
|
|
4629
|
+
"Push to CDN"
|
|
4792
4630
|
]
|
|
4793
4631
|
}
|
|
4794
4632
|
),
|
|
@@ -4796,8 +4634,8 @@ function StudioDetailView() {
|
|
|
4796
4634
|
"button",
|
|
4797
4635
|
{
|
|
4798
4636
|
css: styles8.actionBtn,
|
|
4799
|
-
onClick: () =>
|
|
4800
|
-
disabled: focusedItem.isProtected,
|
|
4637
|
+
onClick: () => requestProcess([focusedItem.path]),
|
|
4638
|
+
disabled: isActionInProgress || focusedItem.isProtected,
|
|
4801
4639
|
children: [
|
|
4802
4640
|
/* @__PURE__ */ jsx8("svg", { css: styles8.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx8("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" }) }),
|
|
4803
4641
|
"Process Image"
|
|
@@ -4808,8 +4646,8 @@ function StudioDetailView() {
|
|
|
4808
4646
|
"button",
|
|
4809
4647
|
{
|
|
4810
4648
|
css: [styles8.actionBtn, styles8.actionBtnDanger],
|
|
4811
|
-
onClick: () =>
|
|
4812
|
-
disabled: focusedItem.isProtected,
|
|
4649
|
+
onClick: () => requestDelete([focusedItem.path]),
|
|
4650
|
+
disabled: isActionInProgress || focusedItem.isProtected,
|
|
4813
4651
|
children: [
|
|
4814
4652
|
/* @__PURE__ */ jsx8("svg", { css: styles8.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx8("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" }) }),
|
|
4815
4653
|
"Delete"
|
|
@@ -5393,6 +5231,413 @@ function ErrorModal() {
|
|
|
5393
5231
|
] }) });
|
|
5394
5232
|
}
|
|
5395
5233
|
|
|
5234
|
+
// src/components/useStudioActions.tsx
|
|
5235
|
+
import { useState as useState10, useCallback as useCallback5, useRef as useRef4 } from "react";
|
|
5236
|
+
var defaultActionState2 = {
|
|
5237
|
+
showProgress: false,
|
|
5238
|
+
progressTitle: "",
|
|
5239
|
+
progressState: { current: 0, total: 0, percent: 0, status: "processing" },
|
|
5240
|
+
showDeleteConfirm: false,
|
|
5241
|
+
showMoveModal: false,
|
|
5242
|
+
showSyncConfirm: false,
|
|
5243
|
+
showProcessConfirm: false,
|
|
5244
|
+
actionPaths: [],
|
|
5245
|
+
syncImageCount: 0,
|
|
5246
|
+
syncHasRemote: false,
|
|
5247
|
+
syncHasLocal: false
|
|
5248
|
+
};
|
|
5249
|
+
function useStudioActions({
|
|
5250
|
+
triggerRefresh,
|
|
5251
|
+
clearSelection,
|
|
5252
|
+
setFocusedItem,
|
|
5253
|
+
showError
|
|
5254
|
+
}) {
|
|
5255
|
+
const [actionState, setActionState] = useState10(defaultActionState2);
|
|
5256
|
+
const abortControllerRef = useRef4(null);
|
|
5257
|
+
const setProgressState = useCallback5((update) => {
|
|
5258
|
+
setActionState((prev) => ({
|
|
5259
|
+
...prev,
|
|
5260
|
+
progressState: typeof update === "function" ? update(prev.progressState) : { ...prev.progressState, ...update }
|
|
5261
|
+
}));
|
|
5262
|
+
}, []);
|
|
5263
|
+
const requestDelete = useCallback5((paths) => {
|
|
5264
|
+
setActionState((prev) => ({
|
|
5265
|
+
...prev,
|
|
5266
|
+
actionPaths: paths,
|
|
5267
|
+
showDeleteConfirm: true
|
|
5268
|
+
}));
|
|
5269
|
+
}, []);
|
|
5270
|
+
const requestMove = useCallback5((paths) => {
|
|
5271
|
+
setActionState((prev) => ({
|
|
5272
|
+
...prev,
|
|
5273
|
+
actionPaths: paths,
|
|
5274
|
+
showMoveModal: true
|
|
5275
|
+
}));
|
|
5276
|
+
}, []);
|
|
5277
|
+
const requestSync = useCallback5((paths, fileItems) => {
|
|
5278
|
+
const imageKeys = paths.map((p) => "/" + p.replace(/^public\//, ""));
|
|
5279
|
+
let hasRemote = false;
|
|
5280
|
+
let hasLocal = false;
|
|
5281
|
+
for (const path of paths) {
|
|
5282
|
+
const item = fileItems.find((f) => f.path === path);
|
|
5283
|
+
if (item) {
|
|
5284
|
+
if (item.isRemote) {
|
|
5285
|
+
hasRemote = true;
|
|
5286
|
+
} else if (!item.cdnPushed) {
|
|
5287
|
+
hasLocal = true;
|
|
5288
|
+
}
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5291
|
+
setActionState((prev) => ({
|
|
5292
|
+
...prev,
|
|
5293
|
+
actionPaths: paths,
|
|
5294
|
+
syncImageCount: imageKeys.length,
|
|
5295
|
+
syncHasRemote: hasRemote,
|
|
5296
|
+
syncHasLocal: hasLocal,
|
|
5297
|
+
showSyncConfirm: true
|
|
5298
|
+
}));
|
|
5299
|
+
}, []);
|
|
5300
|
+
const requestProcess = useCallback5((paths) => {
|
|
5301
|
+
setActionState((prev) => ({
|
|
5302
|
+
...prev,
|
|
5303
|
+
actionPaths: paths,
|
|
5304
|
+
showProcessConfirm: true
|
|
5305
|
+
}));
|
|
5306
|
+
}, []);
|
|
5307
|
+
const cancelAction = useCallback5(() => {
|
|
5308
|
+
setActionState((prev) => ({
|
|
5309
|
+
...prev,
|
|
5310
|
+
showDeleteConfirm: false,
|
|
5311
|
+
showMoveModal: false,
|
|
5312
|
+
showSyncConfirm: false,
|
|
5313
|
+
showProcessConfirm: false
|
|
5314
|
+
}));
|
|
5315
|
+
}, []);
|
|
5316
|
+
const closeProgress = useCallback5(() => {
|
|
5317
|
+
setActionState(defaultActionState2);
|
|
5318
|
+
}, []);
|
|
5319
|
+
const stopProcessing = useCallback5(() => {
|
|
5320
|
+
if (abortControllerRef.current) {
|
|
5321
|
+
abortControllerRef.current.abort();
|
|
5322
|
+
}
|
|
5323
|
+
}, []);
|
|
5324
|
+
const confirmDelete = useCallback5(async () => {
|
|
5325
|
+
const paths = actionState.actionPaths;
|
|
5326
|
+
setActionState((prev) => ({ ...prev, showDeleteConfirm: false }));
|
|
5327
|
+
try {
|
|
5328
|
+
const response = await fetch("/api/studio/delete", {
|
|
5329
|
+
method: "POST",
|
|
5330
|
+
headers: { "Content-Type": "application/json" },
|
|
5331
|
+
body: JSON.stringify({ paths })
|
|
5332
|
+
});
|
|
5333
|
+
if (response.ok) {
|
|
5334
|
+
clearSelection();
|
|
5335
|
+
setFocusedItem(null);
|
|
5336
|
+
triggerRefresh();
|
|
5337
|
+
} else {
|
|
5338
|
+
const error = await response.json();
|
|
5339
|
+
showError("Delete Failed", error.error || "Unknown error");
|
|
5340
|
+
}
|
|
5341
|
+
} catch (error) {
|
|
5342
|
+
console.error("Delete error:", error);
|
|
5343
|
+
showError("Delete Failed", "Delete failed. Check console for details.");
|
|
5344
|
+
}
|
|
5345
|
+
}, [actionState.actionPaths, clearSelection, setFocusedItem, triggerRefresh, showError]);
|
|
5346
|
+
const confirmMove = useCallback5(async (destination) => {
|
|
5347
|
+
const paths = actionState.actionPaths;
|
|
5348
|
+
setActionState((prev) => ({
|
|
5349
|
+
...prev,
|
|
5350
|
+
showMoveModal: false,
|
|
5351
|
+
showProgress: true,
|
|
5352
|
+
progressTitle: "Moving Files",
|
|
5353
|
+
progressState: {
|
|
5354
|
+
current: 0,
|
|
5355
|
+
total: paths.length,
|
|
5356
|
+
percent: 0,
|
|
5357
|
+
status: "processing",
|
|
5358
|
+
message: "Moving files..."
|
|
5359
|
+
}
|
|
5360
|
+
}));
|
|
5361
|
+
try {
|
|
5362
|
+
const response = await fetch("/api/studio/move-stream", {
|
|
5363
|
+
method: "POST",
|
|
5364
|
+
headers: { "Content-Type": "application/json" },
|
|
5365
|
+
body: JSON.stringify({ paths, destination })
|
|
5366
|
+
});
|
|
5367
|
+
if (!response.ok) {
|
|
5368
|
+
const error = await response.json();
|
|
5369
|
+
setProgressState({
|
|
5370
|
+
status: "error",
|
|
5371
|
+
message: error.error || "Move failed"
|
|
5372
|
+
});
|
|
5373
|
+
return;
|
|
5374
|
+
}
|
|
5375
|
+
const reader = response.body?.getReader();
|
|
5376
|
+
const decoder = new TextDecoder();
|
|
5377
|
+
if (reader) {
|
|
5378
|
+
let buffer = "";
|
|
5379
|
+
while (true) {
|
|
5380
|
+
const { done, value } = await reader.read();
|
|
5381
|
+
if (done) break;
|
|
5382
|
+
buffer += decoder.decode(value, { stream: true });
|
|
5383
|
+
const lines = buffer.split("\n");
|
|
5384
|
+
buffer = lines.pop() || "";
|
|
5385
|
+
for (const line of lines) {
|
|
5386
|
+
if (line.startsWith("data: ")) {
|
|
5387
|
+
try {
|
|
5388
|
+
const data = JSON.parse(line.slice(6));
|
|
5389
|
+
if (data.type === "start") {
|
|
5390
|
+
setProgressState((prev) => ({ ...prev, total: data.total }));
|
|
5391
|
+
} else if (data.type === "progress") {
|
|
5392
|
+
setProgressState({
|
|
5393
|
+
current: data.current,
|
|
5394
|
+
total: data.total,
|
|
5395
|
+
percent: Math.round(data.current / data.total * 100),
|
|
5396
|
+
status: "processing",
|
|
5397
|
+
message: data.message
|
|
5398
|
+
});
|
|
5399
|
+
} else if (data.type === "complete") {
|
|
5400
|
+
setProgressState((prev) => ({
|
|
5401
|
+
...prev,
|
|
5402
|
+
status: "complete",
|
|
5403
|
+
message: `Moved ${data.moved} file${data.moved !== 1 ? "s" : ""}${data.errors > 0 ? `, ${data.errors} error${data.errors !== 1 ? "s" : ""}` : ""}`
|
|
5404
|
+
}));
|
|
5405
|
+
if (data.errors > 0 && data.errorMessages?.length > 0) {
|
|
5406
|
+
showError("Move Failed", data.errorMessages.join("\n"));
|
|
5407
|
+
}
|
|
5408
|
+
clearSelection();
|
|
5409
|
+
setFocusedItem(null);
|
|
5410
|
+
triggerRefresh();
|
|
5411
|
+
} else if (data.type === "error") {
|
|
5412
|
+
setProgressState((prev) => ({
|
|
5413
|
+
...prev,
|
|
5414
|
+
status: "error",
|
|
5415
|
+
message: data.message || "Unknown error"
|
|
5416
|
+
}));
|
|
5417
|
+
}
|
|
5418
|
+
} catch {
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
} catch (error) {
|
|
5425
|
+
console.error("Move error:", error);
|
|
5426
|
+
setProgressState((prev) => ({
|
|
5427
|
+
...prev,
|
|
5428
|
+
status: "error",
|
|
5429
|
+
message: "Failed to move files. Check console for details."
|
|
5430
|
+
}));
|
|
5431
|
+
}
|
|
5432
|
+
}, [actionState.actionPaths, clearSelection, setFocusedItem, triggerRefresh, showError, setProgressState]);
|
|
5433
|
+
const confirmSync = useCallback5(async () => {
|
|
5434
|
+
const paths = actionState.actionPaths;
|
|
5435
|
+
const imageKeys = paths.map((p) => "/" + p.replace(/^public\//, ""));
|
|
5436
|
+
setActionState((prev) => ({
|
|
5437
|
+
...prev,
|
|
5438
|
+
showSyncConfirm: false,
|
|
5439
|
+
showProgress: true,
|
|
5440
|
+
progressTitle: "Pushing to CDN",
|
|
5441
|
+
progressState: {
|
|
5442
|
+
current: 0,
|
|
5443
|
+
total: imageKeys.length,
|
|
5444
|
+
percent: 0,
|
|
5445
|
+
status: "processing",
|
|
5446
|
+
message: "Pushing to CDN..."
|
|
5447
|
+
}
|
|
5448
|
+
}));
|
|
5449
|
+
let pushed = 0;
|
|
5450
|
+
const errors = [];
|
|
5451
|
+
for (let i = 0; i < imageKeys.length; i++) {
|
|
5452
|
+
const imageKey = imageKeys[i];
|
|
5453
|
+
setProgressState({
|
|
5454
|
+
current: i + 1,
|
|
5455
|
+
total: imageKeys.length,
|
|
5456
|
+
percent: Math.round((i + 1) / imageKeys.length * 100),
|
|
5457
|
+
status: "processing",
|
|
5458
|
+
message: `Pushing ${imageKey}...`
|
|
5459
|
+
});
|
|
5460
|
+
try {
|
|
5461
|
+
const response = await fetch("/api/studio/sync", {
|
|
5462
|
+
method: "POST",
|
|
5463
|
+
headers: { "Content-Type": "application/json" },
|
|
5464
|
+
body: JSON.stringify({ imageKeys: [imageKey] })
|
|
5465
|
+
});
|
|
5466
|
+
if (response.ok) {
|
|
5467
|
+
pushed++;
|
|
5468
|
+
} else {
|
|
5469
|
+
const data = await response.json();
|
|
5470
|
+
errors.push(`${imageKey}: ${data.error || "Unknown error"}`);
|
|
5471
|
+
}
|
|
5472
|
+
} catch (error) {
|
|
5473
|
+
errors.push(`${imageKey}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
setProgressState({
|
|
5477
|
+
current: imageKeys.length,
|
|
5478
|
+
total: imageKeys.length,
|
|
5479
|
+
percent: 100,
|
|
5480
|
+
status: errors.length > 0 ? "error" : "complete",
|
|
5481
|
+
message: `Pushed ${pushed} file${pushed !== 1 ? "s" : ""}${errors.length > 0 ? `, ${errors.length} error${errors.length !== 1 ? "s" : ""}` : ""}`
|
|
5482
|
+
});
|
|
5483
|
+
if (errors.length > 0) {
|
|
5484
|
+
showError("Push Errors", errors.join("\n"));
|
|
5485
|
+
}
|
|
5486
|
+
clearSelection();
|
|
5487
|
+
triggerRefresh();
|
|
5488
|
+
}, [actionState.actionPaths, clearSelection, triggerRefresh, showError, setProgressState]);
|
|
5489
|
+
const confirmProcess = useCallback5(async () => {
|
|
5490
|
+
const paths = actionState.actionPaths;
|
|
5491
|
+
const imageKeys = paths.map((p) => "/" + p.replace(/^public\//, ""));
|
|
5492
|
+
setActionState((prev) => ({
|
|
5493
|
+
...prev,
|
|
5494
|
+
showProcessConfirm: false,
|
|
5495
|
+
showProgress: true,
|
|
5496
|
+
progressTitle: "Processing Images",
|
|
5497
|
+
progressState: {
|
|
5498
|
+
current: 0,
|
|
5499
|
+
total: imageKeys.length,
|
|
5500
|
+
percent: 0,
|
|
5501
|
+
status: "processing",
|
|
5502
|
+
message: "Processing images..."
|
|
5503
|
+
}
|
|
5504
|
+
}));
|
|
5505
|
+
abortControllerRef.current = new AbortController();
|
|
5506
|
+
const signal = abortControllerRef.current.signal;
|
|
5507
|
+
try {
|
|
5508
|
+
const response = await fetch("/api/studio/process-stream", {
|
|
5509
|
+
method: "POST",
|
|
5510
|
+
headers: { "Content-Type": "application/json" },
|
|
5511
|
+
body: JSON.stringify({ imageKeys }),
|
|
5512
|
+
signal
|
|
5513
|
+
});
|
|
5514
|
+
if (!response.ok) {
|
|
5515
|
+
const error = await response.json();
|
|
5516
|
+
setProgressState({
|
|
5517
|
+
status: "error",
|
|
5518
|
+
message: error.error || "Processing failed"
|
|
5519
|
+
});
|
|
5520
|
+
return;
|
|
5521
|
+
}
|
|
5522
|
+
const reader = response.body?.getReader();
|
|
5523
|
+
const decoder = new TextDecoder();
|
|
5524
|
+
if (reader) {
|
|
5525
|
+
let buffer = "";
|
|
5526
|
+
while (true) {
|
|
5527
|
+
const { done, value } = await reader.read();
|
|
5528
|
+
if (done) break;
|
|
5529
|
+
buffer += decoder.decode(value, { stream: true });
|
|
5530
|
+
const lines = buffer.split("\n");
|
|
5531
|
+
buffer = lines.pop() || "";
|
|
5532
|
+
for (const line of lines) {
|
|
5533
|
+
if (line.startsWith("data: ")) {
|
|
5534
|
+
try {
|
|
5535
|
+
const data = JSON.parse(line.slice(6));
|
|
5536
|
+
if (data.type === "start") {
|
|
5537
|
+
setProgressState((prev) => ({
|
|
5538
|
+
...prev,
|
|
5539
|
+
total: data.total
|
|
5540
|
+
}));
|
|
5541
|
+
} else if (data.type === "progress") {
|
|
5542
|
+
setProgressState({
|
|
5543
|
+
current: data.current,
|
|
5544
|
+
total: data.total,
|
|
5545
|
+
percent: Math.round(data.current / data.total * 100),
|
|
5546
|
+
status: "processing",
|
|
5547
|
+
message: data.message
|
|
5548
|
+
});
|
|
5549
|
+
} else if (data.type === "cleanup") {
|
|
5550
|
+
setProgressState((prev) => ({
|
|
5551
|
+
...prev,
|
|
5552
|
+
status: "cleanup",
|
|
5553
|
+
message: data.message
|
|
5554
|
+
}));
|
|
5555
|
+
} else if (data.type === "complete") {
|
|
5556
|
+
setProgressState({
|
|
5557
|
+
current: data.processed,
|
|
5558
|
+
total: data.processed,
|
|
5559
|
+
percent: 100,
|
|
5560
|
+
status: "complete",
|
|
5561
|
+
message: `Processed ${data.processed} image${data.processed !== 1 ? "s" : ""}${data.errors > 0 ? `, ${data.errors} error${data.errors !== 1 ? "s" : ""}` : ""}`
|
|
5562
|
+
});
|
|
5563
|
+
triggerRefresh();
|
|
5564
|
+
} else if (data.type === "error") {
|
|
5565
|
+
setProgressState((prev) => ({
|
|
5566
|
+
...prev,
|
|
5567
|
+
status: "error",
|
|
5568
|
+
message: data.message
|
|
5569
|
+
}));
|
|
5570
|
+
}
|
|
5571
|
+
} catch {
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
} catch (error) {
|
|
5578
|
+
if (signal.aborted) {
|
|
5579
|
+
setProgressState((prev) => ({
|
|
5580
|
+
...prev,
|
|
5581
|
+
status: "stopped",
|
|
5582
|
+
message: "Processing stopped by user"
|
|
5583
|
+
}));
|
|
5584
|
+
} else {
|
|
5585
|
+
console.error("Processing error:", error);
|
|
5586
|
+
setProgressState({
|
|
5587
|
+
current: 0,
|
|
5588
|
+
total: 0,
|
|
5589
|
+
status: "error",
|
|
5590
|
+
message: "Processing failed. Check console for details."
|
|
5591
|
+
});
|
|
5592
|
+
}
|
|
5593
|
+
} finally {
|
|
5594
|
+
abortControllerRef.current = null;
|
|
5595
|
+
}
|
|
5596
|
+
}, [actionState.actionPaths, triggerRefresh, setProgressState]);
|
|
5597
|
+
const deleteOrphans = useCallback5(async () => {
|
|
5598
|
+
const orphanedFiles = actionState.progressState.orphanedFiles;
|
|
5599
|
+
if (!orphanedFiles || orphanedFiles.length === 0) return;
|
|
5600
|
+
try {
|
|
5601
|
+
const response = await fetch("/api/studio/delete-orphans", {
|
|
5602
|
+
method: "POST",
|
|
5603
|
+
headers: { "Content-Type": "application/json" },
|
|
5604
|
+
body: JSON.stringify({ files: orphanedFiles })
|
|
5605
|
+
});
|
|
5606
|
+
if (response.ok) {
|
|
5607
|
+
setProgressState((prev) => ({
|
|
5608
|
+
...prev,
|
|
5609
|
+
orphanedFiles: void 0,
|
|
5610
|
+
message: prev.message?.replace(/Found \d+ orphaned thumbnail\(s\).*/, "Orphaned thumbnails deleted.")
|
|
5611
|
+
}));
|
|
5612
|
+
triggerRefresh();
|
|
5613
|
+
} else {
|
|
5614
|
+
const error = await response.json();
|
|
5615
|
+
showError("Delete Failed", error.error || "Failed to delete orphaned files");
|
|
5616
|
+
}
|
|
5617
|
+
} catch (error) {
|
|
5618
|
+
console.error("Delete orphans error:", error);
|
|
5619
|
+
showError("Delete Failed", "Failed to delete orphaned files. Check console for details.");
|
|
5620
|
+
}
|
|
5621
|
+
}, [actionState.progressState.orphanedFiles, triggerRefresh, showError, setProgressState]);
|
|
5622
|
+
return {
|
|
5623
|
+
actionState,
|
|
5624
|
+
setActionState,
|
|
5625
|
+
abortController: abortControllerRef.current,
|
|
5626
|
+
requestDelete,
|
|
5627
|
+
requestMove,
|
|
5628
|
+
requestSync,
|
|
5629
|
+
requestProcess,
|
|
5630
|
+
cancelAction,
|
|
5631
|
+
closeProgress,
|
|
5632
|
+
stopProcessing,
|
|
5633
|
+
confirmDelete,
|
|
5634
|
+
confirmMove,
|
|
5635
|
+
confirmSync,
|
|
5636
|
+
confirmProcess,
|
|
5637
|
+
deleteOrphans
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5396
5641
|
// src/components/StudioUI.tsx
|
|
5397
5642
|
import { jsx as jsx11, jsxs as jsxs11 } from "@emotion/react/jsx-runtime";
|
|
5398
5643
|
var btnHeight3 = "36px";
|
|
@@ -5536,45 +5781,45 @@ var styles11 = {
|
|
|
5536
5781
|
`
|
|
5537
5782
|
};
|
|
5538
5783
|
function StudioUI({ onClose, isVisible = true }) {
|
|
5539
|
-
const [currentPath, setCurrentPathInternal] =
|
|
5540
|
-
const [selectedItems, setSelectedItems] =
|
|
5541
|
-
const [lastSelectedPath, setLastSelectedPath] =
|
|
5542
|
-
const [viewMode, setViewMode] =
|
|
5543
|
-
const [focusedItem, setFocusedItem] =
|
|
5544
|
-
const [meta, setMeta] =
|
|
5545
|
-
const [isLoading, setIsLoading] =
|
|
5546
|
-
const [refreshKey, setRefreshKey] =
|
|
5547
|
-
const [scanRequested, setScanRequested] =
|
|
5548
|
-
const [searchQuery, setSearchQuery] =
|
|
5549
|
-
const [error, setError] =
|
|
5550
|
-
const [fileItems, setFileItems] =
|
|
5551
|
-
const [isDragging, setIsDragging] =
|
|
5552
|
-
const triggerRefresh =
|
|
5784
|
+
const [currentPath, setCurrentPathInternal] = useState11("public");
|
|
5785
|
+
const [selectedItems, setSelectedItems] = useState11(/* @__PURE__ */ new Set());
|
|
5786
|
+
const [lastSelectedPath, setLastSelectedPath] = useState11(null);
|
|
5787
|
+
const [viewMode, setViewMode] = useState11("grid");
|
|
5788
|
+
const [focusedItem, setFocusedItem] = useState11(null);
|
|
5789
|
+
const [meta, setMeta] = useState11(null);
|
|
5790
|
+
const [isLoading, setIsLoading] = useState11(false);
|
|
5791
|
+
const [refreshKey, setRefreshKey] = useState11(0);
|
|
5792
|
+
const [scanRequested, setScanRequested] = useState11(false);
|
|
5793
|
+
const [searchQuery, setSearchQuery] = useState11("");
|
|
5794
|
+
const [error, setError] = useState11(null);
|
|
5795
|
+
const [fileItems, setFileItems] = useState11([]);
|
|
5796
|
+
const [isDragging, setIsDragging] = useState11(false);
|
|
5797
|
+
const triggerRefresh = useCallback6(() => {
|
|
5553
5798
|
setRefreshKey((k) => k + 1);
|
|
5554
5799
|
}, []);
|
|
5555
|
-
const triggerScan =
|
|
5800
|
+
const triggerScan = useCallback6(() => {
|
|
5556
5801
|
setScanRequested(true);
|
|
5557
5802
|
}, []);
|
|
5558
|
-
const clearScanRequest =
|
|
5803
|
+
const clearScanRequest = useCallback6(() => {
|
|
5559
5804
|
setScanRequested(false);
|
|
5560
5805
|
}, []);
|
|
5561
|
-
const showError =
|
|
5806
|
+
const showError = useCallback6((title, message) => {
|
|
5562
5807
|
setError({ title, message });
|
|
5563
5808
|
}, []);
|
|
5564
|
-
const clearError =
|
|
5809
|
+
const clearError = useCallback6(() => {
|
|
5565
5810
|
setError(null);
|
|
5566
5811
|
}, []);
|
|
5567
|
-
const handleDragOver =
|
|
5812
|
+
const handleDragOver = useCallback6((e) => {
|
|
5568
5813
|
e.preventDefault();
|
|
5569
5814
|
e.stopPropagation();
|
|
5570
5815
|
setIsDragging(true);
|
|
5571
5816
|
}, []);
|
|
5572
|
-
const handleDragLeave =
|
|
5817
|
+
const handleDragLeave = useCallback6((e) => {
|
|
5573
5818
|
e.preventDefault();
|
|
5574
5819
|
e.stopPropagation();
|
|
5575
5820
|
setIsDragging(false);
|
|
5576
5821
|
}, []);
|
|
5577
|
-
const handleDrop =
|
|
5822
|
+
const handleDrop = useCallback6(async (e) => {
|
|
5578
5823
|
e.preventDefault();
|
|
5579
5824
|
e.stopPropagation();
|
|
5580
5825
|
setIsDragging(false);
|
|
@@ -5598,19 +5843,19 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
5598
5843
|
}
|
|
5599
5844
|
triggerRefresh();
|
|
5600
5845
|
}, [currentPath, triggerRefresh]);
|
|
5601
|
-
const navigateUp =
|
|
5846
|
+
const navigateUp = useCallback6(() => {
|
|
5602
5847
|
if (currentPath === "public") return;
|
|
5603
5848
|
const parts = currentPath.split("/");
|
|
5604
5849
|
parts.pop();
|
|
5605
5850
|
setCurrentPathInternal(parts.join("/") || "public");
|
|
5606
5851
|
setSelectedItems(/* @__PURE__ */ new Set());
|
|
5607
5852
|
}, [currentPath]);
|
|
5608
|
-
const setCurrentPath =
|
|
5853
|
+
const setCurrentPath = useCallback6((path) => {
|
|
5609
5854
|
setCurrentPathInternal(path);
|
|
5610
5855
|
setSelectedItems(/* @__PURE__ */ new Set());
|
|
5611
5856
|
setFocusedItem(null);
|
|
5612
5857
|
}, []);
|
|
5613
|
-
const toggleSelection =
|
|
5858
|
+
const toggleSelection = useCallback6((path) => {
|
|
5614
5859
|
setSelectedItems((prev) => {
|
|
5615
5860
|
const next = new Set(prev);
|
|
5616
5861
|
if (next.has(path)) {
|
|
@@ -5622,7 +5867,7 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
5622
5867
|
});
|
|
5623
5868
|
setLastSelectedPath(path);
|
|
5624
5869
|
}, []);
|
|
5625
|
-
const selectRange =
|
|
5870
|
+
const selectRange = useCallback6((fromPath, toPath, allItems) => {
|
|
5626
5871
|
const fromIndex = allItems.findIndex((item) => item.path === fromPath);
|
|
5627
5872
|
const toIndex = allItems.findIndex((item) => item.path === toPath);
|
|
5628
5873
|
if (fromIndex === -1 || toIndex === -1) return;
|
|
@@ -5637,13 +5882,22 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
5637
5882
|
});
|
|
5638
5883
|
setLastSelectedPath(toPath);
|
|
5639
5884
|
}, []);
|
|
5640
|
-
const selectAll =
|
|
5885
|
+
const selectAll = useCallback6((items) => {
|
|
5641
5886
|
setSelectedItems(new Set(items.map((item) => item.path)));
|
|
5642
5887
|
}, []);
|
|
5643
|
-
const clearSelection =
|
|
5888
|
+
const clearSelection = useCallback6(() => {
|
|
5644
5889
|
setSelectedItems(/* @__PURE__ */ new Set());
|
|
5645
5890
|
}, []);
|
|
5646
|
-
const
|
|
5891
|
+
const setFocusedItemCallback = useCallback6((item) => {
|
|
5892
|
+
setFocusedItem(item);
|
|
5893
|
+
}, []);
|
|
5894
|
+
const actions = useStudioActions({
|
|
5895
|
+
triggerRefresh,
|
|
5896
|
+
clearSelection,
|
|
5897
|
+
setFocusedItem: setFocusedItemCallback,
|
|
5898
|
+
showError
|
|
5899
|
+
});
|
|
5900
|
+
const handleKeyDown = useCallback6(
|
|
5647
5901
|
(e) => {
|
|
5648
5902
|
if (e.key === "Escape") {
|
|
5649
5903
|
const target = e.target;
|
|
@@ -5703,7 +5957,22 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
5703
5957
|
showError,
|
|
5704
5958
|
clearError,
|
|
5705
5959
|
fileItems,
|
|
5706
|
-
setFileItems
|
|
5960
|
+
setFileItems,
|
|
5961
|
+
// Shared action state and handlers
|
|
5962
|
+
actionState: actions.actionState,
|
|
5963
|
+
requestDelete: actions.requestDelete,
|
|
5964
|
+
requestMove: actions.requestMove,
|
|
5965
|
+
requestSync: actions.requestSync,
|
|
5966
|
+
requestProcess: actions.requestProcess,
|
|
5967
|
+
confirmDelete: actions.confirmDelete,
|
|
5968
|
+
confirmMove: actions.confirmMove,
|
|
5969
|
+
confirmSync: actions.confirmSync,
|
|
5970
|
+
confirmProcess: actions.confirmProcess,
|
|
5971
|
+
cancelAction: actions.cancelAction,
|
|
5972
|
+
closeProgress: actions.closeProgress,
|
|
5973
|
+
stopProcessing: actions.stopProcessing,
|
|
5974
|
+
abortController: actions.abortController,
|
|
5975
|
+
deleteOrphans: actions.deleteOrphans
|
|
5707
5976
|
};
|
|
5708
5977
|
return /* @__PURE__ */ jsx11(StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs11("div", { css: styles11.container, children: [
|
|
5709
5978
|
/* @__PURE__ */ jsxs11("div", { css: styles11.header, children: [
|
|
@@ -5740,7 +6009,57 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
5740
6009
|
}
|
|
5741
6010
|
),
|
|
5742
6011
|
focusedItem && /* @__PURE__ */ jsx11(StudioDetailView, {}),
|
|
5743
|
-
/* @__PURE__ */ jsx11(ErrorModal, {})
|
|
6012
|
+
/* @__PURE__ */ jsx11(ErrorModal, {}),
|
|
6013
|
+
actions.actionState.showDeleteConfirm && /* @__PURE__ */ jsx11(
|
|
6014
|
+
ConfirmModal,
|
|
6015
|
+
{
|
|
6016
|
+
title: "Delete Files",
|
|
6017
|
+
message: `Are you sure you want to delete ${actions.actionState.actionPaths.length} item${actions.actionState.actionPaths.length !== 1 ? "s" : ""}? This action cannot be undone.`,
|
|
6018
|
+
confirmLabel: "Delete",
|
|
6019
|
+
variant: "danger",
|
|
6020
|
+
onConfirm: actions.confirmDelete,
|
|
6021
|
+
onCancel: actions.cancelAction
|
|
6022
|
+
}
|
|
6023
|
+
),
|
|
6024
|
+
actions.actionState.showSyncConfirm && /* @__PURE__ */ jsx11(
|
|
6025
|
+
ConfirmModal,
|
|
6026
|
+
{
|
|
6027
|
+
title: "Push to CDN",
|
|
6028
|
+
message: `Push ${actions.actionState.syncImageCount} image${actions.actionState.syncImageCount !== 1 ? "s" : ""} to Cloudflare R2?${actions.actionState.syncHasRemote ? " Remote images will be downloaded first." : ""}${actions.actionState.syncHasLocal ? " After pushing, local files will be deleted." : ""}`,
|
|
6029
|
+
confirmLabel: "Push",
|
|
6030
|
+
onConfirm: actions.confirmSync,
|
|
6031
|
+
onCancel: actions.cancelAction
|
|
6032
|
+
}
|
|
6033
|
+
),
|
|
6034
|
+
actions.actionState.showProcessConfirm && /* @__PURE__ */ jsx11(
|
|
6035
|
+
ConfirmModal,
|
|
6036
|
+
{
|
|
6037
|
+
title: "Process Images",
|
|
6038
|
+
message: `Generate thumbnails for ${actions.actionState.actionPaths.length} image${actions.actionState.actionPaths.length !== 1 ? "s" : ""}?`,
|
|
6039
|
+
confirmLabel: "Process",
|
|
6040
|
+
onConfirm: actions.confirmProcess,
|
|
6041
|
+
onCancel: actions.cancelAction
|
|
6042
|
+
}
|
|
6043
|
+
),
|
|
6044
|
+
actions.actionState.showMoveModal && /* @__PURE__ */ jsx11(
|
|
6045
|
+
StudioFolderPicker,
|
|
6046
|
+
{
|
|
6047
|
+
selectedItems: new Set(actions.actionState.actionPaths),
|
|
6048
|
+
currentPath,
|
|
6049
|
+
onMove: (destination) => actions.confirmMove(destination),
|
|
6050
|
+
onCancel: actions.cancelAction
|
|
6051
|
+
}
|
|
6052
|
+
),
|
|
6053
|
+
actions.actionState.showProgress && /* @__PURE__ */ jsx11(
|
|
6054
|
+
ProgressModal,
|
|
6055
|
+
{
|
|
6056
|
+
title: actions.actionState.progressTitle,
|
|
6057
|
+
progress: actions.actionState.progressState,
|
|
6058
|
+
onStop: actions.stopProcessing,
|
|
6059
|
+
onDeleteOrphans: actions.deleteOrphans,
|
|
6060
|
+
onClose: actions.closeProgress
|
|
6061
|
+
}
|
|
6062
|
+
)
|
|
5744
6063
|
] }) });
|
|
5745
6064
|
}
|
|
5746
6065
|
function Breadcrumbs({ currentPath, onNavigate }) {
|
|
@@ -5785,4 +6104,4 @@ export {
|
|
|
5785
6104
|
StudioUI,
|
|
5786
6105
|
StudioUI_default as default
|
|
5787
6106
|
};
|
|
5788
|
-
//# sourceMappingURL=StudioUI-
|
|
6107
|
+
//# sourceMappingURL=StudioUI-K6TIH6LF.mjs.map
|