Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic keyboard shortcuts #567

Merged
merged 1 commit into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,63 @@
padding-inline-start: var(--pf-v5-global--spacer--sm);
}
}

.shortcuts-dialog {
h2 + .pf-v5-c-description-list {
margin-block-start: var(--pf-v5-global--spacer--md);
}

.pf-v5-l-flex {
// Add standard spacing between the description lists that are in a flex
// (PF Flex does odd stuff by default)
gap: var(--pf-v5-global--spacer--lg) var(--pf-v5-global--spacer--md);

> .pf-v5-c-content {
// Have the content prefer 20em and wrap if too narrow
flex: 1 1 20em;
}
}

.pf-v5-c-description-list {
// PF's gap is weirdly too big; let's use the PF standard size that's used everywhere else
--pf-v5-c-content--dl--ColumnGap: var(--pf-v5-global--spacer--md);
// We're setting this up as a table on the list, so they're consistent
display: grid;
// Fixing the width to the keyboard shortcuts
grid-template-columns: auto 1fr;
// Fix PF's negative margin at the end bug (as it's handled by grid layout anyway)
margin-block-end: 0;

.pf-v5-c-description-list__group {
// Ignore the grid of the group and use the grid from the description list, so everything lines up properly
display: contents;
}
}

kbd {
// Description lists bold the dt; we don't want the keys too look too bold
font-weight: normal;
}

// Style key combos
.keystroke {
display: flex;
align-items: center;
color: var(--pf-v5-global--Color--200);
font-size: var(--pf-v5-global--FontSize--xs);
gap: var(--pf-v5-global--spacer--xs);
}

// Style individual keys
.key {
display: inline-block;
background-color: var(--pf-v5-global--BackgroundColor--200);
border-radius: var(--pf-v5-global--BorderRadius--sm);
border: 1px solid var(--pf-v5-global--BorderColor--100);
color: var(--pf-v5-global--Color--100);
padding-block: var(--pf-v5-global--spacer--xs);
padding-inline: var(--pf-v5-global--spacer--sm);
box-shadow: inset 1px 1px 0 var(--pf-v5-global--BackgroundColor--100);
white-space: nowrap;
}
}
17 changes: 16 additions & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import cockpit from "cockpit";
import { FsInfoClient, FileInfo } from "cockpit/fsinfo.ts";
import { EmptyStatePanel } from "cockpit-components-empty-state";
import { WithDialogs } from "dialogs";
import { usePageLocation } from "hooks";
import { useInit, usePageLocation } from "hooks";
import { superuser } from "superuser";

import { FilesBreadcrumbs } from "./files-breadcrumbs.tsx";
Expand Down Expand Up @@ -145,6 +145,21 @@ export const Application = () => {
[options, path]
);

useInit(() => {
const onKeyboardNav = (e: KeyboardEvent) => {
if (e.key === "L" && e.ctrlKey && !e.altKey) {
e.preventDefault();
document.dispatchEvent(new Event("manual-change-dir"));
}
};

document.addEventListener("keydown", onKeyboardNav);

return () => {
document.removeEventListener("keydown", onKeyboardNav);
jelly marked this conversation as resolved.
Show resolved Hide resolved
Comment on lines +158 to +159
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 added lines are not executed by any test.

};
}, []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dependency array can go


if (loading)
return <EmptyStatePanel loading />;

Expand Down
147 changes: 147 additions & 0 deletions src/dialogs/keyboardShortcutsHelp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React from 'react';

import { Button } from "@patternfly/react-core/dist/esm/components/Button";
import {
DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm
} from "@patternfly/react-core/dist/esm/components/DescriptionList/index";
import { Modal, ModalVariant } from "@patternfly/react-core/dist/esm/components/Modal";
import { Text, TextContent, TextVariants } from "@patternfly/react-core/dist/esm/components/Text";
import { Flex, } from "@patternfly/react-core/dist/esm/layouts/Flex";

import cockpit from 'cockpit';
import { DialogResult, Dialogs } from 'dialogs';

const _ = cockpit.gettext;

const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult<void> }) => {
const footer = (
<Button variant="secondary" onClick={() => dialogResult.resolve()}>{_("Close")}</Button>
);

const toDescriptionListGroups = (item: [React.JSX.Element, string, string]) => {
return (
<DescriptionListGroup key={item[2] + "-listgroup"}>
<DescriptionListTerm>
{item[0]}
</DescriptionListTerm>
<DescriptionListDescription>
{item[1]}
</DescriptionListDescription>
</DescriptionListGroup>
);
};

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>,
_("Go up a directory"),
"go-up",
], [
<kbd className="keystroke" key="go-back">
<kbd className="key">Alt</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>,
_("Go forward"),
"go-forward",
], [
<kbd className="keystroke" key="activate">
<kbd className="key">Alt</kbd> + <kbd className="key">{'\u{2193}'}</kbd>
</kbd>,
_("Activate selected item, enter directory"),
"activate",
], [
<kbd className="keystroke" key="activate-enter">
<kbd className="key">Enter</kbd>
</kbd>,
_("Activate selected item, enter directory"),
"activate-enter",
], [
<kbd className="keystroke" key="edit-path">
<kbd className="key">Ctrl</kbd> +
<kbd className="key">Shift</kbd> +
<kbd className="key">L</kbd>
</kbd>,
_("Edit path"),
"edit-path",
]
];

const editShortcuts: Array<[React.JSX.Element, string, string]> = [
[
<kbd className="key" key="rename">F2</kbd>,
_("Rename selected file or directory"),
"rename",
], [
<kbd className="keystroke" key="create-dir">
<kbd className="key">Shift</kbd> +
<kbd className="key" key="mkdir">N</kbd>
</kbd>,
_("Create new directory"),
"mkdir",
], [
<kbd className="keystroke" key="copy">
<kbd className="key">Ctrl</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>,
_("Paste file or directory"),
"paste",
], [
<kbd className="keystroke" key="select-all">
<kbd className="key">Ctrl</kbd> + <kbd className="key">A</kbd>
</kbd>,
_("Select all"),
"select-all",
]
];

return (
<Modal
position="top"
title={_("Keyboard shortcuts")}
variant={ModalVariant.large}
className="shortcuts-dialog"
onClose={() => dialogResult.resolve()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

footer={footer}
isOpen
>
<Flex>
<TextContent>
<Text component={TextVariants.h2}>{_("Navigation")}</Text>
<DescriptionList
isHorizontal
isFluid
isFillColumns
>
{navShortcuts.map(toDescriptionListGroups)}
</DescriptionList>
</TextContent>
<TextContent>
<Text component={TextVariants.h2}>{_("Editing")}</Text>
<DescriptionList
isHorizontal
isFluid
isFillColumns
>
{editShortcuts.map(toDescriptionListGroups)}
</DescriptionList>
</TextContent>
</Flex>
</Modal>
);
};

export function showKeyboardShortcuts(dialogs: Dialogs) {
dialogs.run(KeyboardShortcutsHelp, {});
}
20 changes: 14 additions & 6 deletions src/files-breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import React, { useCallback, useEffect } from "react";

import { AlertVariant } from "@patternfly/react-core/dist/esm/components/Alert";
import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core/dist/esm/components/Breadcrumb";
Expand Down Expand Up @@ -254,6 +254,19 @@ export function FilesBreadcrumbs({ path }: { path: string }) {
const [editMode, setEditMode] = React.useState(false);
const [newPath, setNewPath] = React.useState<string | null>(null);

const enableEditMode = useCallback(() => {
setEditMode(true);
setNewPath(path);
}, [path]);

useEffect(() => {
document.addEventListener("manual-change-dir", enableEditMode);

return () => {
document.removeEventListener("manual-change-dir", enableEditMode);
};
}, [enableEditMode]);

const handleInputKey = (event: React.KeyboardEvent<HTMLInputElement>) => {
// Don't propogate navigation specific events
if (event.key === "ArrowDown" || event.key === "ArrowUp" ||
Expand All @@ -269,11 +282,6 @@ export function FilesBreadcrumbs({ path }: { path: string }) {
}
};

const enableEditMode = () => {
setEditMode(true);
setNewPath(path);
};

const changePath = () => {
setEditMode(false);
cockpit.assert(newPath !== null, "newPath cannot be null");
Expand Down
Loading