refac
This commit is contained in:
@@ -121,10 +121,7 @@
|
||||
const normalizePath = (path: string) => path.replace(/\\/g, '/').replace(/\/{2,}/g, '/');
|
||||
|
||||
const cleanEntryName = (name: string) =>
|
||||
normalizePath(name)
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.at(-1) ?? name.replace(/\/+$/, '');
|
||||
normalizePath(name).split('/').filter(Boolean).at(-1) ?? name.replace(/\/+$/, '');
|
||||
|
||||
const normalizeEntries = (items: FileEntry[]) =>
|
||||
items.map((entry) => ({ ...entry, name: cleanEntryName(entry.name) }));
|
||||
@@ -498,11 +495,7 @@
|
||||
}
|
||||
|
||||
const pathForHome = hintPath && hintPath !== '/' ? hintPath : cwdPath;
|
||||
if (
|
||||
homePath &&
|
||||
pathForHome &&
|
||||
(pathForHome === homePath || pathForHome.startsWith(homePath))
|
||||
) {
|
||||
if (homePath && pathForHome && (pathForHome === homePath || pathForHome.startsWith(homePath))) {
|
||||
return { path: homePath, label: 'Home' };
|
||||
}
|
||||
|
||||
@@ -535,11 +528,11 @@
|
||||
const isDrive = /^[A-Za-z]:$/.test(parts[0] ?? '');
|
||||
const root = isDrive ? { label: parts[0], path: `${parts[0]}/` } : { label: '/', path: '/' };
|
||||
return (isDrive ? parts.slice(1) : parts).reduce(
|
||||
(acc, part) => {
|
||||
const prev = acc[acc.length - 1];
|
||||
acc.push({ label: part, path: asDirectoryPath(joinPath(prev.path, part)) });
|
||||
return acc;
|
||||
},
|
||||
(acc, part) => {
|
||||
const prev = acc[acc.length - 1];
|
||||
acc.push({ label: part, path: asDirectoryPath(joinPath(prev.path, part)) });
|
||||
return acc;
|
||||
},
|
||||
[root]
|
||||
);
|
||||
};
|
||||
@@ -571,7 +564,10 @@
|
||||
};
|
||||
|
||||
// ── Directory operations ─────────────────────────────────────────────
|
||||
const loadDir = async (path: string, options: { preserveTree?: boolean; restoreTree?: boolean } = {}) => {
|
||||
const loadDir = async (
|
||||
path: string,
|
||||
options: { preserveTree?: boolean; restoreTree?: boolean } = {}
|
||||
) => {
|
||||
const terminal = selectedTerminal;
|
||||
if (!terminal) return;
|
||||
const directory = clampToFileRoot(path);
|
||||
@@ -663,7 +659,8 @@
|
||||
};
|
||||
|
||||
const openEntry = async (entry: FileEntry) => {
|
||||
const fullPath = 'fullPath' in entry ? (entry as BrowserRow).fullPath : entryPath(currentPath, entry);
|
||||
const fullPath =
|
||||
'fullPath' in entry ? (entry as BrowserRow).fullPath : entryPath(currentPath, entry);
|
||||
const parentPath = 'parentPath' in entry ? (entry as BrowserRow).parentPath : currentPath;
|
||||
if (entry.type === 'directory') {
|
||||
await loadDir(fullPath);
|
||||
@@ -819,8 +816,8 @@
|
||||
if (rawMove) {
|
||||
try {
|
||||
const data = JSON.parse(rawMove);
|
||||
const paths = data.paths || (data.path ? [data.path] : []);
|
||||
for (const path of paths) await handleMove(path, currentPath);
|
||||
const paths = (data.paths || (data.path ? [data.path] : [])) as string[];
|
||||
await handleMovePaths(paths, currentPath);
|
||||
} catch {}
|
||||
return;
|
||||
}
|
||||
@@ -834,7 +831,7 @@
|
||||
}
|
||||
uploading = false;
|
||||
invalidateTreeCache(currentPath);
|
||||
await loadDir(currentPath, { preserveTree: true });
|
||||
await loadDir(currentPath, { preserveTree: true });
|
||||
};
|
||||
|
||||
const handleUploadFiles = async (files: File[]) => {
|
||||
@@ -926,19 +923,25 @@
|
||||
};
|
||||
|
||||
// ── Move (drag-and-drop) ────────────────────────────────────────────
|
||||
const handleMove = async (source: string, destFolder: string) => {
|
||||
const sourceParentPath = (source: string) => {
|
||||
const cleanSource = normalizePath(source).replace(/\/$/, '');
|
||||
const index = cleanSource.lastIndexOf('/');
|
||||
return asDirectoryPath(index >= 0 ? cleanSource.slice(0, index + 1) : currentPath);
|
||||
};
|
||||
|
||||
const moveOne = async (source: string, destFolder: string) => {
|
||||
const terminal = selectedTerminal;
|
||||
if (!terminal || !currentWritable) return;
|
||||
if (!terminal || !currentWritable) return false;
|
||||
|
||||
const cleanSource = source.replace(/\/$/, '');
|
||||
const cleanSource = normalizePath(source).replace(/\/$/, '');
|
||||
const fileName = cleanSource.split('/').pop() ?? '';
|
||||
const destination = `${asDirectoryPath(destFolder)}${fileName}`;
|
||||
const destination = joinPath(destFolder, fileName);
|
||||
|
||||
if (!fileName || cleanSource === destination) return;
|
||||
if (!fileName || cleanSource === destination) return false;
|
||||
|
||||
// Prevent moving a folder into itself or its own subtree
|
||||
const sourceDir = asDirectoryPath(cleanSource);
|
||||
if (asDirectoryPath(destFolder).startsWith(sourceDir)) return;
|
||||
if (asDirectoryPath(destFolder).startsWith(sourceDir)) return false;
|
||||
|
||||
const result = await moveEntry(
|
||||
terminal.url,
|
||||
@@ -949,11 +952,27 @@
|
||||
);
|
||||
if ('error' in result) {
|
||||
toast.error(result.error);
|
||||
return false;
|
||||
} else {
|
||||
toast.success($i18n.t('Moved {{name}}', { name: fileName }));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAfterMove = async (sources: string[], destFolder: string) => {
|
||||
invalidateTreeCache(currentPath, destFolder, ...sources, ...sources.map(sourceParentPath));
|
||||
clearSelection();
|
||||
await refreshBrowser();
|
||||
};
|
||||
|
||||
const handleMovePaths = async (sources: string[], destFolder: string) => {
|
||||
const movedSources: string[] = [];
|
||||
for (const source of sources) {
|
||||
if (await moveOne(source, destFolder)) movedSources.push(source);
|
||||
}
|
||||
if (movedSources.length > 0) {
|
||||
await refreshAfterMove(movedSources, destFolder);
|
||||
}
|
||||
invalidateTreeCache(currentPath, destFolder, cleanSource.substring(0, cleanSource.lastIndexOf('/') + 1));
|
||||
await loadDir(currentPath, { preserveTree: true });
|
||||
};
|
||||
|
||||
// ── Rename ──────────────────────────────────────────────────────────
|
||||
@@ -1325,7 +1344,7 @@
|
||||
onNewFile={startNewFile}
|
||||
onUploadFiles={handleUploadFiles}
|
||||
onDownloadDir={() => downloadFile(currentPath)}
|
||||
onMove={handleMove}
|
||||
onMove={handleMovePaths}
|
||||
onSort={toggleSort}
|
||||
onToggleHidden={toggleHidden}
|
||||
>
|
||||
@@ -1618,10 +1637,17 @@
|
||||
selected={selectedEntries.has(entry.fullPath)}
|
||||
{selectionMode}
|
||||
selectedPaths={selectedEntries}
|
||||
onOpen={(row) => openEntry({ ...row, fullPath: entry.fullPath, parentPath: entry.parentPath, depth: entry.depth, rowIndex: entry.rowIndex })}
|
||||
onOpen={(row) =>
|
||||
openEntry({
|
||||
...row,
|
||||
fullPath: entry.fullPath,
|
||||
parentPath: entry.parentPath,
|
||||
depth: entry.depth,
|
||||
rowIndex: entry.rowIndex
|
||||
})}
|
||||
onDownload={downloadFile}
|
||||
onDelete={requestDelete}
|
||||
onMove={handleMove}
|
||||
onMove={handleMovePaths}
|
||||
onRename={handleRename}
|
||||
onSelect={handleSelect}
|
||||
onLongPress={enterSelectionMode}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
export let onOpen: (entry: FileEntry) => void = () => {};
|
||||
export let onDownload: (path: string) => void = () => {};
|
||||
export let onDelete: (path: string, name: string) => void = () => {};
|
||||
export let onMove: (source: string, destFolder: string) => void = () => {};
|
||||
export let onMove: (sources: string[], destFolder: string) => void | Promise<void> = () => {};
|
||||
export let onRename: (oldPath: string, newName: string) => void = () => {};
|
||||
|
||||
// ── Selection ─────────────────────────────────────────────────────────
|
||||
@@ -187,7 +187,7 @@
|
||||
dragOverFolder = false;
|
||||
clearExpandTimer();
|
||||
}}
|
||||
on:drop={(e) => {
|
||||
on:drop={async (e) => {
|
||||
if (entry.type !== 'directory') return;
|
||||
if (!writable) return;
|
||||
const raw = e.dataTransfer?.getData('application/x-terminal-file-move');
|
||||
@@ -198,12 +198,12 @@
|
||||
clearExpandTimer();
|
||||
try {
|
||||
const data = JSON.parse(raw);
|
||||
const paths = data.paths || (data.path ? [data.path] : []);
|
||||
const paths = (data.paths || (data.path ? [data.path] : [])) as string[];
|
||||
const destFolder = directoryPath;
|
||||
for (const p of paths) {
|
||||
if (p + '/' === destFolder || p === destFolder) continue;
|
||||
onMove(p, destFolder);
|
||||
}
|
||||
await onMove(
|
||||
paths.filter((p) => p + '/' !== destFolder && p !== destFolder),
|
||||
destFolder
|
||||
);
|
||||
} catch {}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
export let onNewFile: () => void = () => {};
|
||||
export let onUploadFiles: (files: File[]) => void = () => {};
|
||||
export let onDownloadDir: () => void = () => {};
|
||||
export let onMove: (source: string, destFolder: string) => void = () => {};
|
||||
export let onMove: (sources: string[], destFolder: string) => void | Promise<void> = () => {};
|
||||
export let showHidden = false;
|
||||
export let onToggleHidden: () => void = () => {};
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="m-0 flex items-center gap-1 px-1 pt-0 pb-1.5 shrink-0 border-b border-gray-50 dark:border-gray-850/30">
|
||||
<div
|
||||
class="m-0 flex items-center gap-1 px-1 pt-0 pb-1.5 shrink-0 border-b border-gray-50 dark:border-gray-850/30"
|
||||
>
|
||||
<div class="flex shrink-0 items-center gap-0.5 px-1">
|
||||
<!-- Back -->
|
||||
<Tooltip content={$i18n.t('Back')}>
|
||||
@@ -108,7 +110,7 @@
|
||||
on:dragleave={() => {
|
||||
if (dragOverCrumb === i) dragOverCrumb = null;
|
||||
}}
|
||||
on:drop={(e) => {
|
||||
on:drop={async (e) => {
|
||||
if (!writable) return;
|
||||
const raw = e.dataTransfer?.getData('application/x-terminal-file-move');
|
||||
if (!raw) return;
|
||||
@@ -117,8 +119,8 @@
|
||||
dragOverCrumb = null;
|
||||
try {
|
||||
const data = JSON.parse(raw);
|
||||
const paths = data.paths || (data.path ? [data.path] : []);
|
||||
for (const p of paths) onMove(p, crumb.path);
|
||||
const paths = (data.paths || (data.path ? [data.path] : [])) as string[];
|
||||
await onMove(paths, crumb.path);
|
||||
} catch {}
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user