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 keyboard shortcuts #637

Merged
merged 23 commits into from
Jul 1, 2020
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

See a demo [here](https://vimeo.com/275537038) (password is stashapp).

An in-app manual is available, and the manual pages can be viewed [here](https://github.com/stashapp/stash/tree/develop/ui/v2.5/src/docs/en).

# Docker install

Follow [this README.md in the docker directory.](docker/production/README.md)
Expand Down
2 changes: 2 additions & 0 deletions ui/v2.5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@fortawesome/free-regular-svg-icons": "^5.13.0",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/react-fontawesome": "^0.1.9",
"@types/mousetrap": "^1.6.3",
"apollo-cache": "^1.3.4",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
Expand All @@ -50,6 +51,7 @@
"jimp": "^0.12.1",
"localforage": "1.7.3",
"lodash": "^4.17.15",
"mousetrap": "^1.6.5",
"query-string": "6.12.1",
"react": "16.13.1",
"react-apollo": "^3.1.5",
Expand Down
2 changes: 2 additions & 0 deletions ui/v2.5/src/components/Changelog/versions/v030.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import ReactMarkdown from "react-markdown";

const markup = `
### ✨ New Features
* Add various keyboard shortcuts (see manual).
* Support deleting multiple scenes.
* Add in-app help manual.
* Add support for custom served folders.
* Add support for parent/child studios.

### 🎨 Improvements
* Add dialog when pasting movie images.
* Allow click and click-drag selection after selecting scene.
* Added multi-scene edit dialog.
* Moved images to separate tables, increasing performance.
Expand Down
10 changes: 8 additions & 2 deletions ui/v2.5/src/components/Help/Manual.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Galleries from "src/docs/en/Galleries.md";
import Scraping from "src/docs/en/Scraping.md";
import Contributing from "src/docs/en/Contributing.md";
import SceneFilenameParser from "src/docs/en/SceneFilenameParser.md";
import KeyboardShortcuts from "src/docs/en/KeyboardShortcuts.md";
import Help from "src/docs/en/Help.md";
import { Page } from "./Page";

Expand All @@ -32,7 +33,7 @@ export const Manual: React.FC<IManualProps> = ({ show, onClose }) => {
},
{
key: "Interface.md",
title: "Interface",
title: "Interface Options",
content: Interface,
},
{
Expand Down Expand Up @@ -68,6 +69,11 @@ export const Manual: React.FC<IManualProps> = ({ show, onClose }) => {
title: "Metadata Scraping",
content: Scraping,
},
{
key: "KeyboardShortcuts.md",
title: "Keyboard Shortcuts",
content: KeyboardShortcuts,
},
{
key: "Contributing.md",
title: "Contributing",
Expand Down Expand Up @@ -116,7 +122,7 @@ export const Manual: React.FC<IManualProps> = ({ show, onClose }) => {
id="manual-tabs"
>
<Row>
<Col lg={3} className="mb-3 mb-lg-0">
<Col lg={3} className="mb-3 mb-lg-0 manual-toc">
<Nav variant="pills" className="flex-column">
{content.map((c) => {
return (
Expand Down
14 changes: 8 additions & 6 deletions ui/v2.5/src/components/Help/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@
color: $text-color;
overflow-y: hidden;
}
}

.manual .manual-content {
max-height: calc(100vh - 10rem);
overflow-y: auto;

.indent-1 {
padding-left: 2rem;
}
}

.manual .manual-content,
.manual .manual-toc {
max-height: calc(100vh - 10rem);
overflow-y: auto;
}

@media (max-width: 992px) {
.manual .modal-body {
overflow-y: auto;

.manual-content {
.manual-content,
.manual-toc {
max-height: inherit;
overflow-y: hidden;
}
Expand Down
9 changes: 9 additions & 0 deletions ui/v2.5/src/components/List/AddFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ export const AddFilter: React.FC<IAddFilterProps> = (

const valueStage = useRef<CriterionValue>(criterion.value);

// configure keyboard shortcuts
useEffect(() => {
Mousetrap.bind("f", () => setIsOpen(true));

return () => {
Mousetrap.unbind("f");
};
});

// Configure if we are editing an existing criterion
useEffect(() => {
if (!props.editingCriterion) {
Expand Down
90 changes: 86 additions & 4 deletions ui/v2.5/src/components/List/ListFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _, { debounce } from "lodash";
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { SortDirectionEnum } from "src/core/generated-graphql";
import {
Badge,
Expand All @@ -19,6 +19,7 @@ import { Icon } from "src/components/Shared";
import { Criterion } from "src/models/list-filter/criteria/criterion";
import { ListFilterModel } from "src/models/list-filter/filter";
import { DisplayMode } from "src/models/list-filter/types";
import { useFocus } from "src/utils";
import { AddFilter } from "./AddFilter";

interface IListFilterOperation {
Expand All @@ -27,6 +28,7 @@ interface IListFilterOperation {
}

interface IListFilterProps {
subComponent?: boolean;
onFilterUpdate: (newFilter: ListFilterModel) => void;
zoomIndex?: number;
onChangeZoom?: (zoomIndex: number) => void;
Expand All @@ -40,10 +42,14 @@ interface IListFilterProps {
}

const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"];
const minZoom = 0;
const maxZoom = 3;

export const ListFilter: React.FC<IListFilterProps> = (
props: IListFilterProps
) => {
const [queryRef, setQueryFocus] = useFocus();

const searchCallback = debounce((value: string) => {
const newFilter = _.cloneDeep(props.filter);
newFilter.searchTerm = value;
Expand All @@ -55,6 +61,81 @@ export const ListFilter: React.FC<IListFilterProps> = (
Criterion | undefined
>(undefined);

useEffect(() => {
Mousetrap.bind("/", (e) => {
setQueryFocus();
e.preventDefault();
});

Mousetrap.bind("r", () => onReshuffleRandomSort());
Mousetrap.bind("v g", () => {
if (props.filter.displayModeOptions.includes(DisplayMode.Grid)) {
onChangeDisplayMode(DisplayMode.Grid);
}
});
Mousetrap.bind("v l", () => {
if (props.filter.displayModeOptions.includes(DisplayMode.List)) {
onChangeDisplayMode(DisplayMode.List);
}
});
Mousetrap.bind("v w", () => {
if (props.filter.displayModeOptions.includes(DisplayMode.Wall)) {
onChangeDisplayMode(DisplayMode.Wall);
}
});
Mousetrap.bind("+", () => {
if (
props.onChangeZoom &&
props.zoomIndex !== undefined &&
props.zoomIndex < maxZoom
) {
props.onChangeZoom(props.zoomIndex + 1);
}
});
Mousetrap.bind("-", () => {
if (
props.onChangeZoom &&
props.zoomIndex !== undefined &&
props.zoomIndex > minZoom
) {
props.onChangeZoom(props.zoomIndex - 1);
}
});
Mousetrap.bind("s a", () => onSelectAll());
Mousetrap.bind("s n", () => onSelectNone());

if (!props.subComponent && props.itemsSelected) {
Mousetrap.bind("e", () => {
if (props.onEdit) {
props.onEdit();
}
});

Mousetrap.bind("d d", () => {
if (props.onDelete) {
props.onDelete();
}
});
}

return () => {
Mousetrap.unbind("/");
Mousetrap.unbind("r");
Mousetrap.unbind("v g");
Mousetrap.unbind("v l");
Mousetrap.unbind("v w");
Mousetrap.unbind("+");
Mousetrap.unbind("-");
Mousetrap.unbind("s a");
Mousetrap.unbind("s n");

if (!props.subComponent && props.itemsSelected) {
Mousetrap.unbind("e");
Mousetrap.unbind("d d");
}
};
});

function onChangePageSize(event: React.ChangeEvent<HTMLSelectElement>) {
const val = event.currentTarget.value;

Expand Down Expand Up @@ -322,9 +403,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
<Form.Control
className="zoom-slider d-none d-sm-inline-flex ml-3"
type="range"
min={0}
max={3}
defaultValue={1}
min={minZoom}
max={maxZoom}
value={props.zoomIndex}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChangeZoom(Number.parseInt(e.currentTarget.value, 10))
}
Expand Down Expand Up @@ -369,6 +450,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
<ButtonGroup className="mr-3 my-1">
<InputGroup className="mr-2">
<FormControl
ref={queryRef}
placeholder="Search..."
defaultValue={props.filter.searchTerm}
onInput={onChangeQuery}
Expand Down
49 changes: 45 additions & 4 deletions ui/v2.5/src/components/MainNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { Nav, Navbar, Button } from "react-bootstrap";
import { IconName } from "@fortawesome/fontawesome-svg-core";
import { LinkContainer } from "react-router-bootstrap";
import { Link, NavLink, useLocation } from "react-router-dom";
import { Link, NavLink, useLocation, useHistory } from "react-router-dom";
import { SessionUtils } from "src/utils";

import { Icon } from "src/components/Shared";
Expand Down Expand Up @@ -90,6 +90,7 @@ const menuItems: IMenuItem[] = [
];

export const MainNavbar: React.FC = () => {
const history = useHistory();
const location = useLocation();
const [expanded, setExpanded] = useState(false);
const [showManual, setShowManual] = useState(false);
Expand Down Expand Up @@ -120,7 +121,14 @@ export const MainNavbar: React.FC = () => {
};
}, [expanded]);

const path =
function goto(page: string) {
history.push(page);
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}

const newPath =
location.pathname === "/performers"
? "/performers/new"
: location.pathname === "/studios"
Expand All @@ -129,16 +137,49 @@ export const MainNavbar: React.FC = () => {
? "/movies/new"
: null;
const newButton =
path === null ? (
newPath === null ? (
""
) : (
<Link to={path}>
<Link to={newPath}>
<Button variant="primary">
<FormattedMessage id="new" defaultMessage="New" />
</Button>
</Link>
);

// set up hotkeys
useEffect(() => {
Mousetrap.bind("?", () => setShowManual(!showManual));
Mousetrap.bind("g s", () => goto("/scenes"));
Mousetrap.bind("g v", () => goto("/movies"));
Mousetrap.bind("g k", () => goto("/scenes/markers"));
Mousetrap.bind("g l", () => goto("/galleries"));
Mousetrap.bind("g p", () => goto("/performers"));
Mousetrap.bind("g u", () => goto("/studios"));
Mousetrap.bind("g t", () => goto("/tags"));
Mousetrap.bind("g z", () => goto("/settings"));

if (newPath) {
Mousetrap.bind("n", () => history.push(newPath));
}

return () => {
Mousetrap.unbind("?");
Mousetrap.unbind("g s");
Mousetrap.unbind("g v");
Mousetrap.unbind("g k");
Mousetrap.unbind("g l");
Mousetrap.unbind("g p");
Mousetrap.unbind("g u");
Mousetrap.unbind("g t");
Mousetrap.unbind("g z");

if (newPath) {
Mousetrap.unbind("n");
}
};
});

function maybeRenderLogout() {
if (SessionUtils.isLoggedIn()) {
return (
Expand Down
Loading