Skip to content

Commit

Permalink
Support Mac specific keybinds
Browse files Browse the repository at this point in the history
fixes: #757
  • Loading branch information
tomasmatus committed Oct 3, 2024
1 parent b04f942 commit 2518198
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 25 deletions.
5 changes: 4 additions & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { WithDialogs } from "dialogs";
import { useInit, usePageLocation } from "hooks";
import { superuser } from "superuser";

import { testIsAppleDevice } from "./common.ts";
import { FilesBreadcrumbs } from "./files-breadcrumbs.tsx";
import { FilesFolderView } from "./files-folder-view.tsx";
import filetype_data from './filetype-data'; // eslint-disable-line import/extensions
Expand Down Expand Up @@ -146,8 +147,10 @@ export const Application = () => {
);

useInit(() => {
const isApple = testIsAppleDevice();

const onKeyboardNav = (e: KeyboardEvent) => {
if (e.key === "L" && e.ctrlKey && !e.altKey) {
if (e.key === "L" && (isApple ? e.metaKey : e.ctrlKey) && !e.altKey) {
e.preventDefault();
document.dispatchEvent(new Event("manual-change-dir"));
}
Expand Down
4 changes: 4 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ export function * map_permissions<T>(func: (value: number, label: string) => T)
yield func(value, label);
}
}

export function testIsAppleDevice() {
return /Mac|iPhone|iPad|iPod/.test(navigator.platform);
}
22 changes: 14 additions & 8 deletions src/dialogs/keyboardShortcutsHelp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ import { Flex, } from "@patternfly/react-core/dist/esm/layouts/Flex";
import cockpit from 'cockpit';
import { DialogResult, Dialogs } from 'dialogs';

import { testIsAppleDevice } from '../common.ts';

const _ = cockpit.gettext;

const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult<void> }) => {
const isApple = testIsAppleDevice();
const ctrlString = isApple ? "Command" : "Ctrl";
const altString = isApple ? "Option" : "Alt";

const footer = (
<Button variant="secondary" onClick={() => dialogResult.resolve()}>{_("Close")}</Button>
);
Expand All @@ -34,25 +40,25 @@ const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult<v
const navShortcuts: Array<[React.JSX.Element, string, string]> = [
[
<kbd className="keystroke" key="go-up">
<kbd className="key">Alt</kbd> + <kbd className="key">{'\u{2191}'}</kbd>
<kbd className="key">{altString}</kbd> + <kbd className="key">{'\u{2191}'}</kbd>
</kbd>,
_("Go up a directory"),
"go-up",
], [
<kbd className="keystroke" key="go-back">
<kbd className="key">Alt</kbd> + <kbd className="key">{'\u{2190}'}</kbd>
<kbd className="key">{altString}</kbd> + <kbd className="key">{'\u{2190}'}</kbd>
</kbd>,
_("Go back"),
"go-back",
], [
<kbd className="keystroke" key="go-forward">
<kbd className="key">Alt</kbd> + <kbd className="key">{'\u{2192}'}</kbd>
<kbd className="key">{altString}</kbd> + <kbd className="key">{'\u{2192}'}</kbd>
</kbd>,
_("Go forward"),
"go-forward",
], [
<kbd className="keystroke" key="activate">
<kbd className="key">Alt</kbd> + <kbd className="key">{'\u{2193}'}</kbd>
<kbd className="key">{altString}</kbd> + <kbd className="key">{'\u{2193}'}</kbd>
</kbd>,
_("Activate selected item, enter directory"),
"activate",
Expand All @@ -64,7 +70,7 @@ const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult<v
"activate-enter",
], [
<kbd className="keystroke" key="edit-path">
<kbd className="key">Ctrl</kbd> +
<kbd className="key">{ctrlString}</kbd> +
<kbd className="key">Shift</kbd> +
<kbd className="key">L</kbd>
</kbd>,
Expand All @@ -87,19 +93,19 @@ const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult<v
"mkdir",
], [
<kbd className="keystroke" key="copy">
<kbd className="key">Ctrl</kbd> + <kbd className="key">C</kbd>
<kbd className="key">{ctrlString}</kbd> + <kbd className="key">C</kbd>
</kbd>,
_("Copy selected file or directory"),
"copy",
], [
<kbd className="keystroke" key="paste">
<kbd className="key">Ctrl</kbd> + <kbd className="key">V</kbd>
<kbd className="key">{ctrlString}</kbd> + <kbd className="key">V</kbd>
</kbd>,
_("Paste file or directory"),
"paste",
], [
<kbd className="keystroke" key="select-all">
<kbd className="key">Ctrl</kbd> + <kbd className="key">A</kbd>
<kbd className="key">{ctrlString}</kbd> + <kbd className="key">A</kbd>
</kbd>,
_("Select all"),
"select-all",
Expand Down
35 changes: 19 additions & 16 deletions src/files-card-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { useDialogs } from "dialogs";
import * as timeformat from "timeformat";

import { FolderFileInfo, useFilesContext } from "./app.tsx";
import { get_permissions } from "./common.ts";
import { get_permissions, testIsAppleDevice } from "./common.ts";
import { confirm_delete } from "./dialogs/delete.tsx";
import { show_create_directory_dialog } from "./dialogs/mkdir.tsx";
import { show_rename_dialog } from "./dialogs/rename.tsx";
Expand Down Expand Up @@ -207,6 +207,7 @@ export const FilesCardBody = ({

useEffect(() => {
let folderViewElem = null;
const isApple = testIsAppleDevice();

const resetSelected = (e: MouseEvent) => {
if ((e.target instanceof HTMLElement)) {
Expand Down Expand Up @@ -273,14 +274,16 @@ export const FilesCardBody = ({
}
};

const hasNoKeydownModifiers = (event: KeyboardEvent) => {
return !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey;
const hasNoKeydownModifiers = (event: KeyboardEvent, ctrlKey: boolean) => {
return !event.shiftKey && !ctrlKey && !event.altKey && !event.metaKey;
};

const onKeyboardNav = (e: KeyboardEvent) => {
const ctrlKey = isApple ? e.metaKey : e.ctrlKey;

switch (e.key) {
case "ArrowRight":
if (hasNoKeydownModifiers(e)) {
if (hasNoKeydownModifiers(e, ctrlKey)) {
setSelected(_selected => {
const firstSelectedName = _selected?.[0]?.name;
const selectedIdx = sortedFiles?.findIndex(file => file.name === firstSelectedName);
Expand All @@ -294,7 +297,7 @@ export const FilesCardBody = ({
break;

case "ArrowLeft":
if (hasNoKeydownModifiers(e)) {
if (hasNoKeydownModifiers(e, ctrlKey)) {
setSelected(_selected => {
const firstSelectedName = _selected?.[0]?.name;
const selectedIdx = sortedFiles?.findIndex(file => file.name === firstSelectedName);
Expand All @@ -308,9 +311,9 @@ export const FilesCardBody = ({
break;

case "ArrowUp":
if (e.altKey && !e.shiftKey && !e.ctrlKey) {
if (e.altKey && !e.shiftKey && !ctrlKey) {
goUpOneDir();
} else if (hasNoKeydownModifiers(e)) {
} else if (hasNoKeydownModifiers(e, ctrlKey)) {
setSelected(_selected => {
const firstSelectedName = _selected?.[0]?.name;
const selectedIdx = sortedFiles?.findIndex(file => file.name === firstSelectedName);
Expand All @@ -322,9 +325,9 @@ export const FilesCardBody = ({
break;

case "ArrowDown":
if (e.altKey && !e.shiftKey && !e.ctrlKey && selected.length === 1) {
if (e.altKey && !e.shiftKey && !ctrlKey && selected.length === 1) {
onDoubleClickNavigate(selected[0]);
} else if (hasNoKeydownModifiers(e)) {
} else if (hasNoKeydownModifiers(e, ctrlKey)) {
setSelected(_selected => {
const firstSelectedName = _selected?.[0]?.name;
const selectedIdx = sortedFiles?.findIndex(file => file.name === firstSelectedName);
Expand All @@ -336,49 +339,49 @@ export const FilesCardBody = ({
break;

case "Enter":
if (hasNoKeydownModifiers(e) && selected.length === 1) {
if (hasNoKeydownModifiers(e, ctrlKey) && selected.length === 1) {
onDoubleClickNavigate(selected[0]);
}
break;

case "Delete":
if (hasNoKeydownModifiers(e) && selected.length !== 0) {
if (hasNoKeydownModifiers(e, ctrlKey) && selected.length !== 0) {
confirm_delete(dialogs, path, selected, setSelected);
}
break;

case "F2":
if (hasNoKeydownModifiers(e) && selected.length === 1) {
if (hasNoKeydownModifiers(e, ctrlKey) && selected.length === 1) {
show_rename_dialog(dialogs, path, selected[0]);
}
break;

case "a":
// Keep standard text editing behavior by excluding input fields
if (e.ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) {
if (ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) {
e.preventDefault();
setSelected(sortedFiles);
}
break;

case "c":
// Keep standard text editing behavior by excluding input fields
if (e.ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) {
if (ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) {
e.preventDefault();
setClipboard(selected.map(s => path + s.name));
}
break;

case "v":
// Keep standard text editing behavior by excluding input fields
if (e.ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) {
if (ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) {
e.preventDefault();
pasteFromClipboard(clipboard, cwdInfo, path, addAlert);
}
break;

case "N":
if (!e.ctrlKey && !e.altKey) {
if (!ctrlKey && !e.altKey) {
e.preventDefault();
show_create_directory_dialog(dialogs, path);
}
Expand Down

0 comments on commit 2518198

Please sign in to comment.