securityos/components/system/Files/FileManager/useFileKeyboardShortcuts.ts

218 lines
7.3 KiB
TypeScript
Raw Normal View History

2024-09-06 15:32:35 +00:00
import useTransferDialog from "components/system/Dialogs/Transfer/useTransferDialog";
import { createFileReaders } from "components/system/Files/FileManager/functions";
import type { FocusEntryFunctions } from "components/system/Files/FileManager/useFocusableEntries";
import type {
Files,
FolderActions,
} from "components/system/Files/FileManager/useFolder";
import type { FileManagerViewNames } from "components/system/Files/Views";
import { useFileSystem } from "contexts/fileSystem";
import { useProcesses } from "contexts/process";
import { useSession } from "contexts/session";
import { dirname, join } from "path";
import { useCallback, useEffect } from "react";
import { DESKTOP_PATH, PREVENT_SCROLL } from "utils/constants";
import { haltEvent, sendMouseClick } from "utils/functions";
type KeyboardShortcutEntry = (file?: string) => React.KeyboardEventHandler;
const useFileKeyboardShortcuts = (
files: Files,
url: string,
focusedEntries: string[],
setRenaming: React.Dispatch<React.SetStateAction<string>>,
{ blurEntry, focusEntry }: FocusEntryFunctions,
{ newPath, pasteToFolder }: FolderActions,
updateFiles: (newFile?: string, oldFile?: string) => void,
fileManagerRef: React.MutableRefObject<HTMLOListElement | null>,
id?: string,
view?: FileManagerViewNames
): KeyboardShortcutEntry => {
const { copyEntries, deletePath, moveEntries } = useFileSystem();
const { url: changeUrl } = useProcesses();
const { openTransferDialog } = useTransferDialog();
const { foregroundId } = useSession();
useEffect(() => {
const pasteHandler = (event: ClipboardEvent): void => {
if (
event.clipboardData?.files?.length &&
((!foregroundId && url === DESKTOP_PATH) || foregroundId === id)
) {
event.stopImmediatePropagation?.();
createFileReaders(event.clipboardData.files, url, newPath).then(
openTransferDialog
);
}
};
document.addEventListener("paste", pasteHandler);
return () => document.removeEventListener("paste", pasteHandler);
}, [foregroundId, id, newPath, openTransferDialog, url]);
return useCallback(
(file?: string): React.KeyboardEventHandler =>
(event) => {
if (view === "list") return;
const { ctrlKey, key, target, shiftKey } = event;
if (shiftKey) return;
if (ctrlKey) {
const lKey = key.toLowerCase();
// eslint-disable-next-line default-case
switch (lKey) {
case "a":
haltEvent(event);
if (target instanceof HTMLOListElement) {
const [firstEntry] = target.querySelectorAll("button");
firstEntry?.focus(PREVENT_SCROLL);
}
Object.keys(files).forEach((fileName) => focusEntry(fileName));
break;
case "c":
haltEvent(event);
copyEntries(focusedEntries.map((entry) => join(url, entry)));
break;
case "x":
haltEvent(event);
moveEntries(focusedEntries.map((entry) => join(url, entry)));
break;
case "v":
event.stopPropagation();
pasteToFolder();
break;
}
} else {
switch (key) {
case "F2":
if (file) {
haltEvent(event);
setRenaming(file);
}
break;
case "F5":
if (id) {
haltEvent(event);
updateFiles();
}
break;
case "Delete":
if (focusedEntries.length > 0) {
haltEvent(event);
focusedEntries.forEach(async (entry) => {
const path = join(url, entry);
await deletePath(path);
updateFiles(undefined, path);
});
blurEntry();
}
break;
case "Backspace":
if (id) {
haltEvent(event);
changeUrl(id, dirname(url));
}
break;
case "Enter":
if (target instanceof HTMLButtonElement) {
haltEvent(event);
sendMouseClick(target, 2);
}
break;
default:
if (key.startsWith("Arrow")) {
haltEvent(event);
if (!(target instanceof HTMLElement)) return;
let targetElement = target;
if (!(target instanceof HTMLButtonElement)) {
targetElement = target.querySelector(
"button"
) as HTMLButtonElement;
if (!targetElement) return;
}
const { x, y, height, width } =
targetElement.getBoundingClientRect();
let movedElement =
key === "ArrowUp" || key === "ArrowDown"
? document.elementFromPoint(
x,
y + (key === "ArrowUp" ? -height : height * 2)
)
: document.elementFromPoint(
x + (key === "ArrowLeft" ? -width : width * 2),
y
);
if (movedElement instanceof HTMLOListElement) {
const nearestLi = targetElement.closest("li");
if (nearestLi instanceof HTMLLIElement) {
const olChildren = [...movedElement.children];
const liPosition = olChildren.indexOf(nearestLi);
if (key === "ArrowUp" || key === "ArrowDown") {
movedElement =
olChildren[
key === "ArrowUp"
? liPosition === 0
? olChildren.length - 1
: liPosition - 1
: liPosition === olChildren.length - 1
? 0
: liPosition + 1
].querySelector("button");
}
}
}
const closestButton = movedElement?.closest("button");
let dispatchElement: HTMLElement = closestButton as HTMLElement;
if (
!(closestButton instanceof HTMLButtonElement) ||
!fileManagerRef?.current?.contains(closestButton)
) {
dispatchElement = targetElement;
}
dispatchElement?.dispatchEvent(
new MouseEvent("mousedown", {
bubbles: true,
})
);
}
}
}
},
[
blurEntry,
changeUrl,
copyEntries,
deletePath,
fileManagerRef,
files,
focusEntry,
focusedEntries,
id,
moveEntries,
pasteToFolder,
setRenaming,
updateFiles,
url,
view,
]
);
};
export default useFileKeyboardShortcuts;