More key bindings
Some checks are pending
Tests / tests (push) Waiting to run

This commit is contained in:
Dustin Brett
2025-02-09 20:16:37 -08:00
parent 00ca7e7898
commit 8c031fbb31
6 changed files with 102 additions and 35 deletions

View File

@@ -23,16 +23,19 @@ import {
import useResizeObserver from "hooks/useResizeObserver";
type NavigationProps = {
addressBarRef: React.RefObject<HTMLInputElement | null>;
hideSearch: boolean;
id: string;
searchBarRef: React.RefObject<HTMLInputElement | null>;
};
const CONTEXT_MENU_OFFSET = 3;
const Navigation: FCWithRef<HTMLInputElement, NavigationProps> = ({
const Navigation: FC<NavigationProps> = ({
hideSearch,
id,
ref: inputRef,
addressBarRef,
searchBarRef,
}) => {
const {
url: changeUrl,
@@ -149,8 +152,8 @@ const Navigation: FCWithRef<HTMLInputElement, NavigationProps> = ({
>
<Up />
</Button>
<AddressBar ref={inputRef} id={id} />
{!hideSearch && !removeSearch && <SearchBar id={id} />}
<AddressBar ref={addressBarRef} id={id} />
{!hideSearch && !removeSearch && <SearchBar ref={searchBarRef} id={id} />}
</StyledNavigation>
);
};

View File

@@ -23,7 +23,10 @@ type SearchBarProps = {
const MAX_ENTRIES = 10;
const SearchBar: FC<SearchBarProps> = ({ id }) => {
const SearchBar: FCWithRef<HTMLInputElement, SearchBarProps> = ({
id,
ref: searchBarRef,
}) => {
const [searchTerm, setSearchTerm] = useState("");
const hasUsedSearch = useRef(false);
const {
@@ -32,14 +35,13 @@ const SearchBar: FC<SearchBarProps> = ({ id }) => {
[id]: { url = "" },
},
} = useProcesses();
const searchBarRef = useRef<HTMLInputElement | null>(null);
const results = useSearch(searchTerm);
const { contextMenu } = useMenu();
const { fs } = useFileSystem();
const { updateRecentFiles } = useSession();
useEffect(() => {
if (searchBarRef.current && hasUsedSearch.current) {
if (searchBarRef?.current && hasUsedSearch.current) {
const getItems = (): Promise<MenuItem[]> =>
Promise.all(
[
@@ -59,7 +61,8 @@ const SearchBar: FC<SearchBarProps> = ({ id }) => {
open(pid, { url: infoUrl });
setSearchTerm("");
if (searchBarRef.current) {
if (searchBarRef?.current) {
// eslint-disable-next-line no-param-reassign
searchBarRef.current.value = "";
searchBarRef.current.blur();
}
@@ -84,15 +87,16 @@ const SearchBar: FC<SearchBarProps> = ({ id }) => {
}
});
}
}, [contextMenu, fs, open, results, updateRecentFiles, url]);
}, [contextMenu, fs, open, results, searchBarRef, updateRecentFiles, url]);
useEffect(() => {
if (searchBarRef.current) {
if (searchBarRef?.current) {
// eslint-disable-next-line no-param-reassign
searchBarRef.current.value = "";
setSearchTerm("");
}
// eslint-disable-next-line react-hooks-addons/no-unused-deps
}, [url]);
}, [searchBarRef, url]);
return (
<StyledSearch>

View File

@@ -27,13 +27,22 @@ const FileExplorer: FC<ComponentProcessProps> = ({ id }) => {
const { componentWindow, closing, icon = "", url = "" } = process || {};
const { fs, rootFs } = useFileSystem();
const [currentUrl, setCurrentUrl] = useState(url);
const inputRef = useRef<HTMLInputElement | null>(null);
const addressBarRef = useRef<HTMLInputElement | null>(null);
const searchBarRef = useRef<HTMLInputElement | null>(null);
const directoryName = basename(url);
const mountUrl = getMountUrl(url, rootFs?.mntMap || {});
const onKeyDown = useCallback((event: KeyboardEvent): void => {
if (event.altKey && event.key.toUpperCase() === "D") {
const eventKey = event.key.toUpperCase();
if (event.altKey && eventKey === "D") {
haltEvent(event);
inputRef.current?.focus(PREVENT_SCROLL);
addressBarRef.current?.focus(PREVENT_SCROLL);
} else if (
eventKey === "F3" ||
(event.ctrlKey && (eventKey === "E" || eventKey === "F"))
) {
haltEvent(event);
searchBarRef.current?.focus(PREVENT_SCROLL);
} else {
const fileManagerEntry = (event?.target as HTMLElement)?.querySelector(
"ol li button"
@@ -102,14 +111,24 @@ const FileExplorer: FC<ComponentProcessProps> = ({ id }) => {
}, [closing, id, componentWindow, setProcessIcon, setProcessUrl, url]);
useEffect(() => {
componentWindow?.addEventListener("keydown", onKeyDown);
componentWindow?.addEventListener("keydown", onKeyDown, {
capture: true,
});
return () => componentWindow?.removeEventListener("keydown", onKeyDown);
return () =>
componentWindow?.removeEventListener("keydown", onKeyDown, {
capture: true,
});
}, [componentWindow, onKeyDown]);
return url ? (
<StyledFileExplorer>
<Navigation ref={inputRef} hideSearch={Boolean(mountUrl)} id={id} />
<Navigation
addressBarRef={addressBarRef}
hideSearch={Boolean(mountUrl)}
id={id}
searchBarRef={searchBarRef}
/>
<FileManager id={id} url={url} showStatusBar />
</StyledFileExplorer>
) : // eslint-disable-next-line unicorn/no-null

View File

@@ -242,7 +242,7 @@ const FileManager: FC<FileManagerProps> = ({
ref={fileManagerRef}
$isEmptyFolder={isEmptyFolder}
$scrollable={!hideScrolling}
onKeyDown={onKeyDown}
onKeyDownCapture={onKeyDown}
{...(readOnly
? { onContextMenu: haltEvent }
: {

View File

@@ -31,7 +31,7 @@ const useFileKeyboardShortcuts = (
setView?: (newView: FileManagerViewNames) => void
): KeyboardShortcutEntry => {
const { copyEntries, deletePath, moveEntries } = useFileSystem();
const { url: changeUrl } = useProcesses();
const { open, url: changeUrl } = useProcesses();
const { openTransferDialog } = useTransferDialog();
const { foregroundId } = useSession();
@@ -58,7 +58,7 @@ const useFileKeyboardShortcuts = (
(event) => {
if (isStartMenu) return;
const { ctrlKey, key, target, shiftKey } = event;
const { altKey, ctrlKey, key, target, shiftKey } = event;
if (shiftKey) {
if (ctrlKey && !isDesktop) {
@@ -85,6 +85,18 @@ const useFileKeyboardShortcuts = (
return;
}
const onDelete = (): void => {
if (focusedEntries.length > 0) {
haltEvent(event);
focusedEntries.forEach(async (entry) => {
const path = join(url, entry);
if (await deletePath(path)) updateFiles(undefined, path);
});
blurEntry();
}
};
if (ctrlKey) {
const lKey = key.toLowerCase();
@@ -103,6 +115,13 @@ const useFileKeyboardShortcuts = (
haltEvent(event);
copyEntries(focusedEntries.map((entry) => join(url, entry)));
break;
case "d":
onDelete();
break;
case "r":
haltEvent(event);
updateFiles();
break;
case "x":
haltEvent(event);
moveEntries(focusedEntries.map((entry) => join(url, entry)));
@@ -114,6 +133,16 @@ const useFileKeyboardShortcuts = (
}
break;
}
} else if (altKey) {
const lKey = key.toLowerCase();
if (lKey === "n") {
haltEvent(event);
open("FileExplorer", { url });
} else if (key === "Enter" && focusedEntries.length > 0) {
haltEvent(event);
open("Properties", { url: join(url, focusedEntries[0]) });
}
} else {
switch (key) {
case "F2":
@@ -129,15 +158,7 @@ const useFileKeyboardShortcuts = (
}
break;
case "Delete":
if (focusedEntries.length > 0) {
haltEvent(event);
focusedEntries.forEach(async (entry) => {
const path = join(url, entry);
if (await deletePath(path)) updateFiles(undefined, path);
});
blurEntry();
}
onDelete();
break;
case "Backspace":
if (id) {
@@ -263,6 +284,7 @@ const useFileKeyboardShortcuts = (
isDesktop,
isStartMenu,
moveEntries,
open,
pasteToFolder,
setRenaming,
setView,

View File

@@ -9,7 +9,7 @@ import { useProcesses } from "contexts/process";
import { useSession } from "contexts/session";
import Button from "styles/common/Button";
import Icon from "styles/common/Icon";
import { DIV_BUTTON_PROPS } from "utils/constants";
import { DIV_BUTTON_PROPS, PROCESS_DELIMITER } from "utils/constants";
import { isSafari, label } from "utils/functions";
const PeekWindow = dynamic(
@@ -29,9 +29,10 @@ const TaskbarEntry: FC<TaskbarEntryProps> = ({ icon, id, title }) => {
const {
linkElement,
minimize,
open,
processes: { [id]: process },
} = useProcesses();
const { minimized, progress } = process || {};
const { minimized, progress, singleton } = process || {};
const linkTaskbarEntry = useCallback(
(taskbarEntry: HTMLButtonElement | HTMLDivElement | null) => {
if (taskbarEntry) linkElement(id, "taskbarEntry", taskbarEntry);
@@ -41,11 +42,29 @@ const TaskbarEntry: FC<TaskbarEntryProps> = ({ icon, id, title }) => {
const [isPeekVisible, setIsPeekVisible] = useState(false);
const hidePeek = useCallback((): void => setIsPeekVisible(false), []);
const showPeek = useCallback((): void => setIsPeekVisible(true), []);
const onClick = useCallback((): void => {
if (minimized || isForeground) minimize(id);
const onClick = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
(event): void => {
if (event.shiftKey && !singleton) {
const [pid] = id.split(PROCESS_DELIMITER);
setForegroundId(isForeground ? nextFocusableId : id);
}, [id, isForeground, minimize, minimized, nextFocusableId, setForegroundId]);
open(pid);
} else {
if (minimized || isForeground) minimize(id);
setForegroundId(isForeground ? nextFocusableId : id);
}
},
[
id,
isForeground,
minimize,
minimized,
nextFocusableId,
open,
setForegroundId,
singleton,
]
);
const focusable = useMemo(() => (isSafari() ? DIV_BUTTON_PROPS : {}), []);
return (