s3kit 0.1.0 → 0.1.1
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/README.md +15 -1
- package/dist/adapters/express.cjs +99 -3
- package/dist/adapters/express.cjs.map +1 -1
- package/dist/adapters/express.d.cts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +99 -3
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fetch.cjs +99 -3
- package/dist/adapters/fetch.cjs.map +1 -1
- package/dist/adapters/fetch.d.cts +2 -2
- package/dist/adapters/fetch.d.ts +2 -2
- package/dist/adapters/fetch.js +99 -3
- package/dist/adapters/fetch.js.map +1 -1
- package/dist/adapters/next.cjs +386 -20
- package/dist/adapters/next.cjs.map +1 -1
- package/dist/adapters/next.d.cts +2 -2
- package/dist/adapters/next.d.ts +2 -2
- package/dist/adapters/next.js +387 -20
- package/dist/adapters/next.js.map +1 -1
- package/dist/client/index.cjs +15 -1
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +12 -2
- package/dist/client/index.d.ts +12 -2
- package/dist/client/index.js +15 -1
- package/dist/client/index.js.map +1 -1
- package/dist/core/index.cjs +300 -19
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +8 -3
- package/dist/core/index.d.ts +8 -3
- package/dist/core/index.js +299 -18
- package/dist/core/index.js.map +1 -1
- package/dist/http/index.cjs +99 -3
- package/dist/http/index.cjs.map +1 -1
- package/dist/http/index.d.cts +5 -2
- package/dist/http/index.d.ts +5 -2
- package/dist/http/index.js +99 -3
- package/dist/http/index.js.map +1 -1
- package/dist/index.cjs +403 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +403 -21
- package/dist/index.js.map +1 -1
- package/dist/{manager-BbmXpgXN.d.ts → manager-BtW1-sC0.d.ts} +11 -1
- package/dist/{manager-gIjo-t8h.d.cts → manager-DSsCYKEz.d.cts} +11 -1
- package/dist/react/index.cjs +334 -31
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +334 -31
- package/dist/react/index.js.map +1 -1
- package/dist/{types-g2IYvH3O.d.cts → types-B0yU5sod.d.cts} +51 -3
- package/dist/{types-g2IYvH3O.d.ts → types-B0yU5sod.d.ts} +51 -3
- package/package.json +1 -1
package/dist/react/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { CSSProperties } from 'react';
|
|
3
|
-
import { i as S3Entry } from '../types-
|
|
3
|
+
import { i as S3Entry } from '../types-B0yU5sod.cjs';
|
|
4
4
|
|
|
5
5
|
interface FileManagerProps {
|
|
6
6
|
/** API endpoint URL, e.g., "/api/s3" */
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { CSSProperties } from 'react';
|
|
3
|
-
import { i as S3Entry } from '../types-
|
|
3
|
+
import { i as S3Entry } from '../types-B0yU5sod.js';
|
|
4
4
|
|
|
5
5
|
interface FileManagerProps {
|
|
6
6
|
/** API endpoint URL, e.g., "/api/s3" */
|
package/dist/react/index.js
CHANGED
|
@@ -96,11 +96,25 @@ var S3FileManagerClient = class {
|
|
|
96
96
|
getPreviewUrl(options) {
|
|
97
97
|
return fetchJson(this.f, this.endpoint("/preview"), options);
|
|
98
98
|
}
|
|
99
|
+
getFolderLock(options) {
|
|
100
|
+
return fetchJson(this.f, this.endpoint("/folder/lock/get"), options);
|
|
101
|
+
}
|
|
102
|
+
getFileAttributes(options) {
|
|
103
|
+
return fetchJson(this.f, this.endpoint("/file/attributes/get"), options);
|
|
104
|
+
}
|
|
105
|
+
setFileAttributes(options) {
|
|
106
|
+
return fetchJson(this.f, this.endpoint("/file/attributes/set"), options);
|
|
107
|
+
}
|
|
99
108
|
async uploadFiles(args) {
|
|
100
109
|
const prepare = {
|
|
101
110
|
items: args.files.map((f) => ({
|
|
102
111
|
path: f.path,
|
|
103
|
-
contentType: f.contentType ?? f.file.type
|
|
112
|
+
contentType: f.contentType ?? f.file.type,
|
|
113
|
+
...f.cacheControl !== void 0 ? { cacheControl: f.cacheControl } : {},
|
|
114
|
+
...f.contentDisposition !== void 0 ? { contentDisposition: f.contentDisposition } : {},
|
|
115
|
+
...f.metadata !== void 0 ? { metadata: f.metadata } : {},
|
|
116
|
+
...f.expiresAt !== void 0 ? { expiresAt: f.expiresAt } : {},
|
|
117
|
+
...f.ifNoneMatch !== void 0 ? { ifNoneMatch: f.ifNoneMatch } : {}
|
|
104
118
|
})),
|
|
105
119
|
...args.expiresInSeconds !== void 0 ? { expiresInSeconds: args.expiresInSeconds } : {}
|
|
106
120
|
};
|
|
@@ -601,10 +615,15 @@ function FileManager({
|
|
|
601
615
|
const [hoverRow, setHoverRow] = useState2(null);
|
|
602
616
|
const [previewData, setPreviewData] = useState2(null);
|
|
603
617
|
const [previewDisplay, setPreviewDisplay] = useState2(null);
|
|
618
|
+
const [fileAttributes, setFileAttributes] = useState2(null);
|
|
619
|
+
const [attributesLoading, setAttributesLoading] = useState2(false);
|
|
620
|
+
const [attributesError, setAttributesError] = useState2(null);
|
|
604
621
|
const [isPreviewClosing, setIsPreviewClosing] = useState2(false);
|
|
605
622
|
const [sidebarWidth, setSidebarWidth] = useState2(320);
|
|
606
623
|
const [isResizing, setIsResizing] = useState2(false);
|
|
607
624
|
const [inlinePreviews, setInlinePreviews] = useState2({});
|
|
625
|
+
const [folderLocks, setFolderLocks] = useState2({});
|
|
626
|
+
const [pendingFolderMoves, setPendingFolderMoves] = useState2(/* @__PURE__ */ new Set());
|
|
608
627
|
const [isSelectionMode, setIsSelectionMode] = useState2(false);
|
|
609
628
|
const [searchQuery, setSearchQuery] = useState2("");
|
|
610
629
|
const [sortBy, setSortBy] = useState2("name");
|
|
@@ -655,7 +674,111 @@ function FileManager({
|
|
|
655
674
|
const longPressTimerRef = useRef(null);
|
|
656
675
|
const suppressClickRef = useRef(false);
|
|
657
676
|
const dragSelectionBaseRef = useRef(/* @__PURE__ */ new Set());
|
|
677
|
+
const requestFolderLock = useCallback(
|
|
678
|
+
(path2) => {
|
|
679
|
+
if (folderLocks[path2]) return;
|
|
680
|
+
setFolderLocks((prev) => ({ ...prev, [path2]: { status: "loading" } }));
|
|
681
|
+
const run = async () => {
|
|
682
|
+
try {
|
|
683
|
+
const out = await client.getFolderLock({ path: path2 });
|
|
684
|
+
const isActive = out?.expiresAt && new Date(out.expiresAt).getTime() > Date.now() ? true : false;
|
|
685
|
+
setFolderLocks((prev) => ({
|
|
686
|
+
...prev,
|
|
687
|
+
[path2]: isActive && out ? { status: "locked", lock: out } : { status: "unlocked" }
|
|
688
|
+
}));
|
|
689
|
+
} catch {
|
|
690
|
+
setFolderLocks((prev) => ({ ...prev, [path2]: { status: "unlocked" } }));
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
void run();
|
|
694
|
+
},
|
|
695
|
+
[client, folderLocks]
|
|
696
|
+
);
|
|
697
|
+
const getFolderLockLabel = useCallback(
|
|
698
|
+
(entry) => {
|
|
699
|
+
if (entry.type !== "folder") return null;
|
|
700
|
+
if (entry.path === `${TRASH_PATH}/`) return null;
|
|
701
|
+
if (pendingFolderMoves.has(entry.path)) return "Renaming";
|
|
702
|
+
const lock = folderLocks[entry.path];
|
|
703
|
+
if (lock?.status === "locked") return "Locked";
|
|
704
|
+
return null;
|
|
705
|
+
},
|
|
706
|
+
[folderLocks, pendingFolderMoves]
|
|
707
|
+
);
|
|
708
|
+
const handleEntryHover = useCallback(
|
|
709
|
+
(entry) => {
|
|
710
|
+
setHoverRow(entry.path);
|
|
711
|
+
if (entry.type === "folder" && entry.path !== `${TRASH_PATH}/`) {
|
|
712
|
+
requestFolderLock(entry.path);
|
|
713
|
+
}
|
|
714
|
+
},
|
|
715
|
+
[requestFolderLock]
|
|
716
|
+
);
|
|
717
|
+
const resolveEntryEtag = useCallback(
|
|
718
|
+
(entry) => {
|
|
719
|
+
if (entry.type !== "file") return void 0;
|
|
720
|
+
if (fileAttributes?.path === entry.path && fileAttributes.etag) return fileAttributes.etag;
|
|
721
|
+
return entry.etag;
|
|
722
|
+
},
|
|
723
|
+
[fileAttributes]
|
|
724
|
+
);
|
|
725
|
+
const parseApiError = useCallback((err) => {
|
|
726
|
+
const fallback = err instanceof Error ? { code: void 0, message: err.message } : { code: void 0, message: "Request failed" };
|
|
727
|
+
if (!(err instanceof Error)) return fallback;
|
|
728
|
+
const raw = err.message;
|
|
729
|
+
if (!raw.trim().startsWith("{")) return fallback;
|
|
730
|
+
try {
|
|
731
|
+
const parsed = JSON.parse(raw);
|
|
732
|
+
if (parsed?.error) {
|
|
733
|
+
return { code: parsed.error.code, message: parsed.error.message ?? raw };
|
|
734
|
+
}
|
|
735
|
+
} catch {
|
|
736
|
+
return fallback;
|
|
737
|
+
}
|
|
738
|
+
return fallback;
|
|
739
|
+
}, []);
|
|
740
|
+
const isConflictError = useCallback(
|
|
741
|
+
(err) => {
|
|
742
|
+
const info = parseApiError(err);
|
|
743
|
+
if (info.code === "conflict") return true;
|
|
744
|
+
if (info.message && /conflict|already in progress/i.test(info.message)) return true;
|
|
745
|
+
return false;
|
|
746
|
+
},
|
|
747
|
+
[parseApiError]
|
|
748
|
+
);
|
|
749
|
+
const showConflictAlert = useCallback(() => {
|
|
750
|
+
window.alert("This item changed elsewhere. Refresh and try again.");
|
|
751
|
+
}, []);
|
|
752
|
+
const renderFolderLockBadge = useCallback(
|
|
753
|
+
(entry, variant) => {
|
|
754
|
+
const label = getFolderLockLabel(entry);
|
|
755
|
+
if (!label) return null;
|
|
756
|
+
const isPending = label === "Renaming";
|
|
757
|
+
const style2 = {
|
|
758
|
+
display: "inline-flex",
|
|
759
|
+
alignItems: "center",
|
|
760
|
+
fontSize: 10,
|
|
761
|
+
fontWeight: 600,
|
|
762
|
+
textTransform: "uppercase",
|
|
763
|
+
letterSpacing: "0.04em",
|
|
764
|
+
padding: "4px 6px",
|
|
765
|
+
borderRadius: 6,
|
|
766
|
+
border: `1px solid ${isPending ? theme.accent : theme.border}`,
|
|
767
|
+
backgroundColor: isPending ? theme.accent : theme.bgSecondary,
|
|
768
|
+
color: isPending ? theme.bg : theme.textSecondary
|
|
769
|
+
};
|
|
770
|
+
return /* @__PURE__ */ jsx5(
|
|
771
|
+
"div",
|
|
772
|
+
{
|
|
773
|
+
style: variant === "grid" ? { ...style2, position: "absolute", top: 10, right: 10 } : { ...style2, marginLeft: 8 },
|
|
774
|
+
children: label
|
|
775
|
+
}
|
|
776
|
+
);
|
|
777
|
+
},
|
|
778
|
+
[getFolderLockLabel, theme]
|
|
779
|
+
);
|
|
658
780
|
const lastSelectionSigRef = useRef("");
|
|
781
|
+
const lastSelectedPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
659
782
|
const [dragSelect, setDragSelect] = useState2(null);
|
|
660
783
|
const handleKeyDown = useCallback(
|
|
661
784
|
(e) => {
|
|
@@ -921,6 +1044,9 @@ function FileManager({
|
|
|
921
1044
|
setPath("");
|
|
922
1045
|
}
|
|
923
1046
|
}, [hideTrash, view]);
|
|
1047
|
+
useEffect2(() => {
|
|
1048
|
+
lastSelectedPathsRef.current = /* @__PURE__ */ new Set();
|
|
1049
|
+
}, [mode]);
|
|
924
1050
|
useEffect2(() => {
|
|
925
1051
|
const timeoutId = setTimeout(() => {
|
|
926
1052
|
performSearch(searchQuery);
|
|
@@ -937,7 +1063,16 @@ function FileManager({
|
|
|
937
1063
|
if (sig === lastSelectionSigRef.current) return;
|
|
938
1064
|
lastSelectionSigRef.current = sig;
|
|
939
1065
|
onSelectionChange?.(selectedEntries);
|
|
940
|
-
|
|
1066
|
+
const nextSelectedPaths = new Set(selectedEntries.map((entry) => entry.path));
|
|
1067
|
+
if (mode === "picker" && onFileSelect) {
|
|
1068
|
+
selectedEntries.forEach((entry) => {
|
|
1069
|
+
if (entry.type === "file" && !lastSelectedPathsRef.current.has(entry.path)) {
|
|
1070
|
+
onFileSelect(entry);
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
lastSelectedPathsRef.current = nextSelectedPaths;
|
|
1075
|
+
}, [entries, searchResults, selected, searchQuery, onSelectionChange, onFileSelect, mode]);
|
|
941
1076
|
useEffect2(() => {
|
|
942
1077
|
const source = searchQuery.trim() ? searchResults : entries;
|
|
943
1078
|
const selectedFiles = source.filter((entry) => selected.has(entry.path)).filter((entry) => entry.type === "file");
|
|
@@ -979,6 +1114,45 @@ function FileManager({
|
|
|
979
1114
|
setPreviewData(null);
|
|
980
1115
|
}
|
|
981
1116
|
}, [selected, lastSelected, client]);
|
|
1117
|
+
useEffect2(() => {
|
|
1118
|
+
if (selected.size !== 1) return;
|
|
1119
|
+
const selectedPath = Array.from(selected)[0];
|
|
1120
|
+
if (!selectedPath) return;
|
|
1121
|
+
const source = searchQuery.trim() ? searchResults : entries;
|
|
1122
|
+
const entry = source.find((item) => item.path === selectedPath);
|
|
1123
|
+
if (entry?.type === "folder") {
|
|
1124
|
+
requestFolderLock(entry.path);
|
|
1125
|
+
}
|
|
1126
|
+
}, [entries, requestFolderLock, searchQuery, searchResults, selected]);
|
|
1127
|
+
useEffect2(() => {
|
|
1128
|
+
const entry = previewData?.entry;
|
|
1129
|
+
if (!entry || entry.type !== "file") {
|
|
1130
|
+
setFileAttributes(null);
|
|
1131
|
+
setAttributesError(null);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
let cancelled = false;
|
|
1135
|
+
setAttributesLoading(true);
|
|
1136
|
+
setAttributesError(null);
|
|
1137
|
+
const run = async () => {
|
|
1138
|
+
try {
|
|
1139
|
+
const out = await client.getFileAttributes({ path: entry.path });
|
|
1140
|
+
if (cancelled) return;
|
|
1141
|
+
setFileAttributes(out);
|
|
1142
|
+
} catch (e) {
|
|
1143
|
+
if (cancelled) return;
|
|
1144
|
+
const message = e instanceof Error ? e.message : "Failed to load attributes";
|
|
1145
|
+
setAttributesError(message);
|
|
1146
|
+
setFileAttributes(null);
|
|
1147
|
+
} finally {
|
|
1148
|
+
if (!cancelled) setAttributesLoading(false);
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
void run();
|
|
1152
|
+
return () => {
|
|
1153
|
+
cancelled = true;
|
|
1154
|
+
};
|
|
1155
|
+
}, [client, previewData?.entry]);
|
|
982
1156
|
useEffect2(() => {
|
|
983
1157
|
if (previewData) {
|
|
984
1158
|
setPreviewDisplay(previewData);
|
|
@@ -1119,6 +1293,7 @@ function FileManager({
|
|
|
1119
1293
|
if (isRenaming) return;
|
|
1120
1294
|
const target = renameTarget ?? lastSelected;
|
|
1121
1295
|
if (!target) return;
|
|
1296
|
+
const isFolder = target.type === "folder";
|
|
1122
1297
|
const oldPath = target.path;
|
|
1123
1298
|
const parent = getParentPath(oldPath);
|
|
1124
1299
|
const existingNames = new Set(
|
|
@@ -1133,16 +1308,40 @@ function FileManager({
|
|
|
1133
1308
|
return;
|
|
1134
1309
|
}
|
|
1135
1310
|
try {
|
|
1311
|
+
if (isFolder) {
|
|
1312
|
+
setPendingFolderMoves((prev) => {
|
|
1313
|
+
const next = new Set(prev);
|
|
1314
|
+
next.add(oldPath);
|
|
1315
|
+
return next;
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1136
1318
|
setIsRenaming(true);
|
|
1137
|
-
|
|
1319
|
+
const renameEtag = target.type === "file" ? resolveEntryEtag(target) : void 0;
|
|
1320
|
+
await client.move({
|
|
1321
|
+
fromPath: oldPath,
|
|
1322
|
+
toPath: newPath,
|
|
1323
|
+
...renameEtag ? { ifMatch: renameEtag } : {}
|
|
1324
|
+
});
|
|
1138
1325
|
setRenameOpen(false);
|
|
1139
1326
|
setRenameTarget(null);
|
|
1140
1327
|
refresh();
|
|
1141
1328
|
} catch (e) {
|
|
1142
1329
|
console.error("Rename failed", e);
|
|
1143
|
-
|
|
1330
|
+
if (isConflictError(e)) {
|
|
1331
|
+
showConflictAlert();
|
|
1332
|
+
} else {
|
|
1333
|
+
alert("Rename failed");
|
|
1334
|
+
}
|
|
1144
1335
|
} finally {
|
|
1145
1336
|
setIsRenaming(false);
|
|
1337
|
+
if (isFolder) {
|
|
1338
|
+
setPendingFolderMoves((prev) => {
|
|
1339
|
+
const next = new Set(prev);
|
|
1340
|
+
next.delete(oldPath);
|
|
1341
|
+
return next;
|
|
1342
|
+
});
|
|
1343
|
+
setFolderLocks((prev) => ({ ...prev, [oldPath]: { status: "unlocked" } }));
|
|
1344
|
+
}
|
|
1146
1345
|
}
|
|
1147
1346
|
}
|
|
1148
1347
|
async function deleteEntries(targets) {
|
|
@@ -1151,12 +1350,26 @@ function FileManager({
|
|
|
1151
1350
|
for (const target of targets) {
|
|
1152
1351
|
if (target.path.startsWith(TRASH_PATH)) continue;
|
|
1153
1352
|
const dest = joinPath(TRASH_PATH, target.path);
|
|
1154
|
-
|
|
1353
|
+
const moveEtag = target.type === "file" ? resolveEntryEtag(target) : void 0;
|
|
1354
|
+
await client.move({
|
|
1355
|
+
fromPath: target.path,
|
|
1356
|
+
toPath: dest,
|
|
1357
|
+
...moveEtag ? { ifMatch: moveEtag } : {}
|
|
1358
|
+
});
|
|
1155
1359
|
}
|
|
1156
1360
|
} else {
|
|
1157
|
-
const files = targets.filter((e) => e.type === "file")
|
|
1361
|
+
const files = targets.filter((e) => e.type === "file");
|
|
1158
1362
|
const folders = targets.filter((e) => e.type === "folder");
|
|
1159
|
-
if (files.length > 0)
|
|
1363
|
+
if (files.length > 0)
|
|
1364
|
+
await client.deleteFiles({
|
|
1365
|
+
items: files.map((file) => {
|
|
1366
|
+
const deleteEtag = resolveEntryEtag(file);
|
|
1367
|
+
return {
|
|
1368
|
+
path: file.path,
|
|
1369
|
+
...deleteEtag ? { ifMatch: deleteEtag } : {}
|
|
1370
|
+
};
|
|
1371
|
+
})
|
|
1372
|
+
});
|
|
1160
1373
|
for (const folder of folders) {
|
|
1161
1374
|
await client.deleteFolder({ path: folder.path, recursive: true });
|
|
1162
1375
|
}
|
|
@@ -1174,7 +1387,11 @@ function FileManager({
|
|
|
1174
1387
|
refresh();
|
|
1175
1388
|
} catch (e) {
|
|
1176
1389
|
console.error("Delete failed", e);
|
|
1177
|
-
|
|
1390
|
+
if (isConflictError(e)) {
|
|
1391
|
+
showConflictAlert();
|
|
1392
|
+
} else {
|
|
1393
|
+
alert("Delete failed");
|
|
1394
|
+
}
|
|
1178
1395
|
} finally {
|
|
1179
1396
|
setIsDeleting(false);
|
|
1180
1397
|
}
|
|
@@ -1185,15 +1402,29 @@ function FileManager({
|
|
|
1185
1402
|
if (!target.path.startsWith(TRASH_PATH)) continue;
|
|
1186
1403
|
const originalPath = target.path.slice(TRASH_PATH.length + 1);
|
|
1187
1404
|
if (!originalPath) continue;
|
|
1188
|
-
|
|
1405
|
+
const restoreEtag = target.type === "file" ? resolveEntryEtag(target) : void 0;
|
|
1406
|
+
await client.move({
|
|
1407
|
+
fromPath: target.path,
|
|
1408
|
+
toPath: originalPath,
|
|
1409
|
+
...restoreEtag ? { ifMatch: restoreEtag } : {}
|
|
1410
|
+
});
|
|
1189
1411
|
}
|
|
1190
1412
|
}
|
|
1191
1413
|
async function onRestore() {
|
|
1192
1414
|
if (!can.restore) return;
|
|
1193
1415
|
const source = searchQuery.trim() ? searchResults : entries;
|
|
1194
1416
|
const targets = source.filter((e) => selected.has(e.path));
|
|
1195
|
-
|
|
1196
|
-
|
|
1417
|
+
try {
|
|
1418
|
+
await restoreEntries(targets);
|
|
1419
|
+
refresh();
|
|
1420
|
+
} catch (e) {
|
|
1421
|
+
console.error("Restore failed", e);
|
|
1422
|
+
if (isConflictError(e)) {
|
|
1423
|
+
showConflictAlert();
|
|
1424
|
+
} else {
|
|
1425
|
+
alert("Restore failed");
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1197
1428
|
}
|
|
1198
1429
|
async function onEmptyTrash() {
|
|
1199
1430
|
if (!can.restore) return;
|
|
@@ -1502,11 +1733,26 @@ function FileManager({
|
|
|
1502
1733
|
const dest = window.prompt("Copy to folder path", path || "");
|
|
1503
1734
|
if (!dest) return;
|
|
1504
1735
|
const baseDest = dest.replace(/\/+$/, "");
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1736
|
+
try {
|
|
1737
|
+
for (const entry of entriesToCopy) {
|
|
1738
|
+
if (entry.path.startsWith(TRASH_PATH)) continue;
|
|
1739
|
+
const targetName = entry.name || entry.path.split("/").pop() || entry.path;
|
|
1740
|
+
const toPath = entry.type === "folder" ? `${joinPath(baseDest, targetName)}/` : joinPath(baseDest, targetName);
|
|
1741
|
+
const copyEtag = entry.type === "file" ? resolveEntryEtag(entry) : void 0;
|
|
1742
|
+
await client.copy({
|
|
1743
|
+
fromPath: entry.path,
|
|
1744
|
+
toPath,
|
|
1745
|
+
...copyEtag ? { ifMatch: copyEtag } : {}
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
} catch (e) {
|
|
1749
|
+
console.error("Copy failed", e);
|
|
1750
|
+
if (isConflictError(e)) {
|
|
1751
|
+
showConflictAlert();
|
|
1752
|
+
} else {
|
|
1753
|
+
alert("Copy failed");
|
|
1754
|
+
}
|
|
1755
|
+
return;
|
|
1510
1756
|
}
|
|
1511
1757
|
refresh();
|
|
1512
1758
|
}
|
|
@@ -1515,11 +1761,26 @@ function FileManager({
|
|
|
1515
1761
|
const dest = window.prompt("Move to folder path", path || "");
|
|
1516
1762
|
if (!dest) return;
|
|
1517
1763
|
const baseDest = dest.replace(/\/+$/, "");
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1764
|
+
try {
|
|
1765
|
+
for (const entry of entriesToMove) {
|
|
1766
|
+
if (entry.path.startsWith(TRASH_PATH)) continue;
|
|
1767
|
+
const targetName = entry.name || entry.path.split("/").pop() || entry.path;
|
|
1768
|
+
const toPath = entry.type === "folder" ? `${joinPath(baseDest, targetName)}/` : joinPath(baseDest, targetName);
|
|
1769
|
+
const moveEtag = entry.type === "file" ? resolveEntryEtag(entry) : void 0;
|
|
1770
|
+
await client.move({
|
|
1771
|
+
fromPath: entry.path,
|
|
1772
|
+
toPath,
|
|
1773
|
+
...moveEtag ? { ifMatch: moveEtag } : {}
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
} catch (e) {
|
|
1777
|
+
console.error("Move failed", e);
|
|
1778
|
+
if (isConflictError(e)) {
|
|
1779
|
+
showConflictAlert();
|
|
1780
|
+
} else {
|
|
1781
|
+
alert("Move failed");
|
|
1782
|
+
}
|
|
1783
|
+
return;
|
|
1523
1784
|
}
|
|
1524
1785
|
refresh();
|
|
1525
1786
|
}
|
|
@@ -2310,7 +2571,7 @@ function FileManager({
|
|
|
2310
2571
|
},
|
|
2311
2572
|
onClick: (e) => handleEntryClickWithSelection(entry, index, filteredEntries, e),
|
|
2312
2573
|
onDoubleClick: () => openEntry(entry),
|
|
2313
|
-
onMouseEnter: () =>
|
|
2574
|
+
onMouseEnter: () => handleEntryHover(entry),
|
|
2314
2575
|
onMouseLeave: () => setHoverRow(null),
|
|
2315
2576
|
onTouchStart: () => {
|
|
2316
2577
|
if (!isMobile) return;
|
|
@@ -2346,7 +2607,8 @@ function FileManager({
|
|
|
2346
2607
|
},
|
|
2347
2608
|
children: entryLabel
|
|
2348
2609
|
}
|
|
2349
|
-
)
|
|
2610
|
+
),
|
|
2611
|
+
renderFolderLockBadge(entry, "grid")
|
|
2350
2612
|
]
|
|
2351
2613
|
},
|
|
2352
2614
|
entry.path
|
|
@@ -2376,7 +2638,7 @@ function FileManager({
|
|
|
2376
2638
|
},
|
|
2377
2639
|
onClick: (e) => handleEntryClickWithSelection(entry, index, filteredEntries, e),
|
|
2378
2640
|
onDoubleClick: () => openEntry(entry),
|
|
2379
|
-
onMouseEnter: () =>
|
|
2641
|
+
onMouseEnter: () => handleEntryHover(entry),
|
|
2380
2642
|
onMouseLeave: () => setHoverRow(null),
|
|
2381
2643
|
onTouchStart: () => {
|
|
2382
2644
|
if (!isMobile) return;
|
|
@@ -2462,7 +2724,7 @@ function FileManager({
|
|
|
2462
2724
|
e.stopPropagation();
|
|
2463
2725
|
}
|
|
2464
2726
|
},
|
|
2465
|
-
onMouseEnter: () =>
|
|
2727
|
+
onMouseEnter: () => handleEntryHover(entry),
|
|
2466
2728
|
onMouseLeave: () => setHoverRow(null),
|
|
2467
2729
|
onTouchStart: () => {
|
|
2468
2730
|
if (!isMobile) return;
|
|
@@ -2498,7 +2760,8 @@ function FileManager({
|
|
|
2498
2760
|
},
|
|
2499
2761
|
children: entryLabel
|
|
2500
2762
|
}
|
|
2501
|
-
)
|
|
2763
|
+
),
|
|
2764
|
+
renderFolderLockBadge(entry, "grid")
|
|
2502
2765
|
]
|
|
2503
2766
|
}
|
|
2504
2767
|
),
|
|
@@ -2739,7 +3002,7 @@ function FileManager({
|
|
|
2739
3002
|
handleEntryClickWithSelection(entry, index, filteredEntries, e);
|
|
2740
3003
|
},
|
|
2741
3004
|
onDoubleClick: () => openEntry(entry),
|
|
2742
|
-
onMouseEnter: () =>
|
|
3005
|
+
onMouseEnter: () => handleEntryHover(entry),
|
|
2743
3006
|
onMouseLeave: () => setHoverRow(null),
|
|
2744
3007
|
onTouchStart: () => {
|
|
2745
3008
|
if (!isMobile) return;
|
|
@@ -2836,7 +3099,7 @@ function FileManager({
|
|
|
2836
3099
|
handleEntryClickWithSelection(entry, index, filteredEntries, e);
|
|
2837
3100
|
},
|
|
2838
3101
|
onDoubleClick: () => openEntry(entry),
|
|
2839
|
-
onMouseEnter: () =>
|
|
3102
|
+
onMouseEnter: () => handleEntryHover(entry),
|
|
2840
3103
|
onMouseLeave: () => setHoverRow(null),
|
|
2841
3104
|
onTouchStart: () => {
|
|
2842
3105
|
if (!isMobile) return;
|
|
@@ -2919,7 +3182,8 @@ function FileManager({
|
|
|
2919
3182
|
}
|
|
2920
3183
|
return getFileIcon(entry.name);
|
|
2921
3184
|
})(),
|
|
2922
|
-
entryLabel
|
|
3185
|
+
entryLabel,
|
|
3186
|
+
renderFolderLockBadge(entry, "list")
|
|
2923
3187
|
]
|
|
2924
3188
|
}
|
|
2925
3189
|
),
|
|
@@ -2994,7 +3258,7 @@ function FileManager({
|
|
|
2994
3258
|
}
|
|
2995
3259
|
e.stopPropagation();
|
|
2996
3260
|
},
|
|
2997
|
-
onMouseEnter: () =>
|
|
3261
|
+
onMouseEnter: () => handleEntryHover(entry),
|
|
2998
3262
|
onMouseLeave: () => setHoverRow(null),
|
|
2999
3263
|
onTouchStart: () => {
|
|
3000
3264
|
if (!isMobile) return;
|
|
@@ -3077,7 +3341,8 @@ function FileManager({
|
|
|
3077
3341
|
}
|
|
3078
3342
|
return getFileIcon(entry.name);
|
|
3079
3343
|
})(),
|
|
3080
|
-
entryLabel
|
|
3344
|
+
entryLabel,
|
|
3345
|
+
renderFolderLockBadge(entry, "list")
|
|
3081
3346
|
]
|
|
3082
3347
|
}
|
|
3083
3348
|
),
|
|
@@ -3731,6 +3996,44 @@ function FileManager({
|
|
|
3731
3996
|
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "Modified" }),
|
|
3732
3997
|
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: previewDisplay.entry.type === "file" && previewDisplay.entry.lastModified ? new Date(previewDisplay.entry.lastModified).toLocaleString() : "--" })
|
|
3733
3998
|
] }),
|
|
3999
|
+
attributesLoading && /* @__PURE__ */ jsxs2("div", { className: FileManager_default.metaItem, children: [
|
|
4000
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "Attributes" }),
|
|
4001
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: "Loading..." })
|
|
4002
|
+
] }),
|
|
4003
|
+
attributesError && /* @__PURE__ */ jsxs2("div", { className: FileManager_default.metaItem, children: [
|
|
4004
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "Attributes" }),
|
|
4005
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: attributesError })
|
|
4006
|
+
] }),
|
|
4007
|
+
!attributesLoading && !attributesError && previewDisplay.entry.type === "file" && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
4008
|
+
/* @__PURE__ */ jsxs2("div", { className: FileManager_default.metaItem, children: [
|
|
4009
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "Content Type" }),
|
|
4010
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: fileAttributes?.contentType ?? "--" })
|
|
4011
|
+
] }),
|
|
4012
|
+
/* @__PURE__ */ jsxs2("div", { className: FileManager_default.metaItem, children: [
|
|
4013
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "Cache Control" }),
|
|
4014
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: fileAttributes?.cacheControl ?? "--" })
|
|
4015
|
+
] }),
|
|
4016
|
+
/* @__PURE__ */ jsxs2("div", { className: FileManager_default.metaItem, children: [
|
|
4017
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "Disposition" }),
|
|
4018
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: fileAttributes?.contentDisposition ?? "--" })
|
|
4019
|
+
] }),
|
|
4020
|
+
/* @__PURE__ */ jsxs2("div", { className: FileManager_default.metaItem, children: [
|
|
4021
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "ETag" }),
|
|
4022
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: fileAttributes?.etag ?? previewDisplay.entry.etag ?? "--" })
|
|
4023
|
+
] }),
|
|
4024
|
+
/* @__PURE__ */ jsxs2("div", { className: FileManager_default.metaItem, children: [
|
|
4025
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "Expires" }),
|
|
4026
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: fileAttributes?.expiresAt ? new Date(fileAttributes.expiresAt).toLocaleString() : "--" })
|
|
4027
|
+
] }),
|
|
4028
|
+
/* @__PURE__ */ jsxs2("div", { className: FileManager_default.metaItem, children: [
|
|
4029
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaLabel, children: "Metadata" }),
|
|
4030
|
+
/* @__PURE__ */ jsx5("div", { className: FileManager_default.metaValue, children: fileAttributes?.metadata && Object.keys(fileAttributes.metadata).length > 0 ? /* @__PURE__ */ jsx5("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: Object.entries(fileAttributes.metadata).map(([k, v]) => /* @__PURE__ */ jsxs2("div", { children: [
|
|
4031
|
+
k,
|
|
4032
|
+
": ",
|
|
4033
|
+
v
|
|
4034
|
+
] }, k)) }) : "--" })
|
|
4035
|
+
] })
|
|
4036
|
+
] }),
|
|
3734
4037
|
/* @__PURE__ */ jsx5(
|
|
3735
4038
|
"div",
|
|
3736
4039
|
{
|