Add shortcuts for decimal rating (#3226)

* Add shortcuts for decimal rating
* Add shortcut to reset decimal rating
* Generalise rating keybind code

Use r x x for decimal ratings.
* Update manual page
---------

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
apache202119
2023-02-07 02:23:18 +01:00
committed by GitHub
parent 65d1353f2c
commit 53f9530524
9 changed files with 143 additions and 144 deletions

View File

@@ -36,6 +36,8 @@ import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
import { GalleryScrapeDialog } from "./GalleryScrapeDialog";
import { faSyncAlt } from "@fortawesome/free-solid-svg-icons";
import { galleryTitle } from "src/core/galleries";
import { useRatingKeybinds } from "src/hooks/keybinds";
import { ConfigurationContext } from "src/hooks/Config";
interface IProps {
isVisible: boolean;
@@ -65,6 +67,8 @@ export const GalleryEditPanel: React.FC<
}))
);
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
const Scrapers = useListGalleryScrapers();
const [queryableScrapers, setQueryableScrapers] = useState<GQL.Scraper[]>([]);
@@ -133,6 +137,12 @@ export const GalleryEditPanel: React.FC<
);
}
useRatingKeybinds(
isVisible,
stashConfig?.ui.ratingSystemOptions.type,
setRating
);
useEffect(() => {
if (isVisible) {
Mousetrap.bind("s s", () => {
@@ -142,35 +152,9 @@ export const GalleryEditPanel: React.FC<
onDelete();
});
// numeric keypresses get caught by jwplayer, so blur the element
// if the rating sequence is started
Mousetrap.bind("r", () => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
Mousetrap.bind("0", () => setRating(NaN));
Mousetrap.bind("1", () => setRating(20));
Mousetrap.bind("2", () => setRating(40));
Mousetrap.bind("3", () => setRating(60));
Mousetrap.bind("4", () => setRating(80));
Mousetrap.bind("5", () => setRating(100));
setTimeout(() => {
Mousetrap.unbind("0");
Mousetrap.unbind("1");
Mousetrap.unbind("2");
Mousetrap.unbind("3");
Mousetrap.unbind("4");
Mousetrap.unbind("5");
}, 1000);
});
return () => {
Mousetrap.unbind("s s");
Mousetrap.unbind("d d");
Mousetrap.unbind("r");
};
}
});

View File

@@ -17,6 +17,8 @@ import { FormUtils } from "src/utils";
import { useFormik } from "formik";
import { Prompt } from "react-router-dom";
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
import { useRatingKeybinds } from "src/hooks/keybinds";
import { ConfigurationContext } from "src/hooks/Config";
interface IProps {
image: GQL.ImageDataFragment;
@@ -35,6 +37,8 @@ export const ImageEditPanel: React.FC<IProps> = ({
// Network state
const [isLoading, setIsLoading] = useState(false);
const { configuration } = React.useContext(ConfigurationContext);
const [updateImage] = useImageUpdate();
const schema = yup.object({
@@ -69,6 +73,12 @@ export const ImageEditPanel: React.FC<IProps> = ({
formik.setFieldValue("rating100", v);
}
useRatingKeybinds(
true,
configuration?.ui.ratingSystemOptions.type,
setRating
);
useEffect(() => {
if (isVisible) {
Mousetrap.bind("s s", () => {
@@ -78,35 +88,9 @@ export const ImageEditPanel: React.FC<IProps> = ({
onDelete();
});
// numeric keypresses get caught by jwplayer, so blur the element
// if the rating sequence is started
Mousetrap.bind("r", () => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
Mousetrap.bind("0", () => setRating(NaN));
Mousetrap.bind("1", () => setRating(20));
Mousetrap.bind("2", () => setRating(40));
Mousetrap.bind("3", () => setRating(60));
Mousetrap.bind("4", () => setRating(80));
Mousetrap.bind("5", () => setRating(100));
setTimeout(() => {
Mousetrap.unbind("0");
Mousetrap.unbind("1");
Mousetrap.unbind("2");
Mousetrap.unbind("3");
Mousetrap.unbind("4");
Mousetrap.unbind("5");
}, 1000);
});
return () => {
Mousetrap.unbind("s s");
Mousetrap.unbind("d d");
Mousetrap.unbind("r");
};
}
});

View File

@@ -21,6 +21,8 @@ import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
import { useFormik } from "formik";
import { Prompt } from "react-router-dom";
import { MovieScrapeDialog } from "./MovieScrapeDialog";
import { useRatingKeybinds } from "src/hooks/keybinds";
import { ConfigurationContext } from "src/hooks/Config";
interface IMovieEditPanel {
movie?: Partial<GQL.MovieDataFragment>;
@@ -45,6 +47,7 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
}) => {
const intl = useIntl();
const Toast = useToast();
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
const isNew = movie === undefined;
@@ -119,14 +122,10 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
formik.setFieldValue("rating100", v);
}
useRatingKeybinds(true, stashConfig?.ui.ratingSystemOptions.type, setRating);
// set up hotkeys
useEffect(() => {
Mousetrap.bind("r 0", () => setRating(NaN));
Mousetrap.bind("r 1", () => setRating(20));
Mousetrap.bind("r 2", () => setRating(40));
Mousetrap.bind("r 3", () => setRating(60));
Mousetrap.bind("r 4", () => setRating(80));
Mousetrap.bind("r 5", () => setRating(100));
// Mousetrap.bind("u", (e) => {
// setStudioFocus()
// e.preventDefault();
@@ -134,12 +133,6 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
Mousetrap.bind("s s", () => formik.handleSubmit());
return () => {
Mousetrap.unbind("r 0");
Mousetrap.unbind("r 1");
Mousetrap.unbind("r 2");
Mousetrap.unbind("r 3");
Mousetrap.unbind("r 4");
Mousetrap.unbind("r 5");
// Mousetrap.unbind("u");
Mousetrap.unbind("s s");
};

View File

@@ -39,6 +39,7 @@ import {
faLink,
} from "@fortawesome/free-solid-svg-icons";
import { IUIConfig } from "src/core/config";
import { useRatingKeybinds } from "src/hooks/keybinds";
interface IProps {
performer: GQL.PerformerDataFragment;
@@ -110,6 +111,12 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
}
}
useRatingKeybinds(
true,
configuration?.ui.ratingSystemOptions.type,
setRating
);
// set up hotkeys
useEffect(() => {
Mousetrap.bind("a", () => setActiveTabKey("details"));
@@ -119,30 +126,6 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
Mousetrap.bind("m", () => setActiveTabKey("movies"));
Mousetrap.bind("f", () => setFavorite(!performer.favorite));
// numeric keypresses get caught by jwplayer, so blur the element
// if the rating sequence is started
Mousetrap.bind("r", () => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
Mousetrap.bind("0", () => setRating(NaN));
Mousetrap.bind("1", () => setRating(20));
Mousetrap.bind("2", () => setRating(40));
Mousetrap.bind("3", () => setRating(60));
Mousetrap.bind("4", () => setRating(80));
Mousetrap.bind("5", () => setRating(100));
setTimeout(() => {
Mousetrap.unbind("0");
Mousetrap.unbind("1");
Mousetrap.unbind("2");
Mousetrap.unbind("3");
Mousetrap.unbind("4");
Mousetrap.unbind("5");
}, 1000);
});
return () => {
Mousetrap.unbind("a");
Mousetrap.unbind("e");

View File

@@ -47,6 +47,7 @@ import {
faTrashAlt,
} from "@fortawesome/free-solid-svg-icons";
import { objectTitle } from "src/core/files";
import { useRatingKeybinds } from "src/hooks/keybinds";
const SceneScrapeDialog = lazy(() => import("./SceneScrapeDialog"));
const SceneQueryModal = lazy(() => import("./SceneQueryModal"));
@@ -187,6 +188,12 @@ export const SceneEditPanel: React.FC<IProps> = ({
);
}
useRatingKeybinds(
isVisible,
stashConfig?.ui.ratingSystemOptions.type,
setRating
);
useEffect(() => {
if (isVisible) {
Mousetrap.bind("s s", () => {
@@ -198,35 +205,9 @@ export const SceneEditPanel: React.FC<IProps> = ({
}
});
// numeric keypresses get caught by jwplayer, so blur the element
// if the rating sequence is started
Mousetrap.bind("r", () => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
Mousetrap.bind("0", () => setRating(NaN));
Mousetrap.bind("1", () => setRating(20));
Mousetrap.bind("2", () => setRating(40));
Mousetrap.bind("3", () => setRating(60));
Mousetrap.bind("4", () => setRating(80));
Mousetrap.bind("5", () => setRating(100));
setTimeout(() => {
Mousetrap.unbind("0");
Mousetrap.unbind("1");
Mousetrap.unbind("2");
Mousetrap.unbind("3");
Mousetrap.unbind("4");
Mousetrap.unbind("5");
}, 1000);
});
return () => {
Mousetrap.unbind("s s");
Mousetrap.unbind("d d");
Mousetrap.unbind("r");
};
}
});

View File

@@ -11,6 +11,8 @@ import { useFormik } from "formik";
import { Prompt } from "react-router-dom";
import { StringListInput } from "../../Shared/StringListInput";
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
import { useRatingKeybinds } from "src/hooks/keybinds";
import { ConfigurationContext } from "src/hooks/Config";
interface IStudioEditPanel {
studio: Partial<GQL.StudioDataFragment>;
@@ -33,6 +35,8 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
}) => {
const intl = useIntl();
const { configuration } = React.useContext(ConfigurationContext);
const isNew = !studio || !studio.id;
const imageEncoding = ImageUtils.usePasteImage(onImageLoad, true);
@@ -99,38 +103,18 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
return input;
}
useRatingKeybinds(
true,
configuration?.ui.ratingSystemOptions.type,
setRating
);
// set up hotkeys
useEffect(() => {
Mousetrap.bind("s s", () => formik.handleSubmit());
// numeric keypresses get caught by jwplayer, so blur the element
// if the rating sequence is started
Mousetrap.bind("r", () => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
Mousetrap.bind("0", () => setRating(NaN));
Mousetrap.bind("1", () => setRating(20));
Mousetrap.bind("2", () => setRating(40));
Mousetrap.bind("3", () => setRating(60));
Mousetrap.bind("4", () => setRating(80));
Mousetrap.bind("5", () => setRating(100));
setTimeout(() => {
Mousetrap.unbind("0");
Mousetrap.unbind("1");
Mousetrap.unbind("2");
Mousetrap.unbind("3");
Mousetrap.unbind("4");
Mousetrap.unbind("5");
}, 1000);
});
return () => {
Mousetrap.unbind("s s");
Mousetrap.unbind("e");
};
});

View File

@@ -10,6 +10,7 @@
* Added Anonymise task to generate an anonymised version of the database. ([#3186](https://github.com/stashapp/stash/pull/3186))
### 🎨 Improvements
* Added `r x x` keyboard shortcuts to set decimal ratings. ([#3226](https://github.com/stashapp/stash/pull/3226))
* Changed performer aliases to be a list, rather than a string field. ([#3113](https://github.com/stashapp/stash/pull/3113))
### 🐛 Bug fixes

View File

@@ -84,8 +84,10 @@
| Keyboard sequence | Action |
|-------------------|--------|
| `r {1-5}` | Set rating |
| `r 0` | Unset rating |
| `r {1-5}` | Set rating (stars) |
| `r 0` | Unset rating (stars) |
| `r {0-9} {0-9}` | Set rating (decimal - `00` for `10.0`) |
| ``r ` `` | Unset rating (decimal) |
| `s s` | Save Scene |
| `d d` | Delete Scene |
| `Ctrl + v` | Paste Scene cover |
@@ -110,8 +112,10 @@
| `e` | Edit Movie |
| `s s` | Save Movie |
| `d d` | Delete Movie |
| `r {1-5}` | Set rating (in edit mode) |
| `r 0` | Unset rating (in edit mode) |
| `r {1-5}` | [Edit mode] Set rating (stars) |
| `r 0` | [Edit mode] Unset rating (stars) |
| `r {0-9} {0-9}` | [Edit mode] Set rating (decimal - `r 0 0` for `10.0`) |
| ``r ` `` | [Edit mode] Unset rating (decimal) |
| `Ctrl + v` | Paste Movie image |
[//]: # "Commented until implementation is dealt with"

View File

@@ -0,0 +1,85 @@
import Mousetrap from "mousetrap";
import { useEffect, useRef } from "react";
import { RatingSystemType } from "src/utils/rating";
export function useRatingKeybinds(
isVisible: boolean,
ratingSystem: RatingSystemType,
setRating: (v: number) => void
) {
const firstChar = useRef<string | undefined>(undefined);
const starRatingShortcuts: { [char: string]: number } = {
"0": NaN,
"1": 20,
"2": 40,
"3": 60,
"4": 80,
"5": 100,
};
function handleStarRatingKeybinds() {
for (const key in starRatingShortcuts) {
Mousetrap.bind(key, () => setRating(starRatingShortcuts[key]));
}
setTimeout(() => {
for (const key in starRatingShortcuts) {
Mousetrap.unbind(key);
}
}, 1000);
}
function handleDecimalKeybinds() {
Mousetrap.bind("`", () => {
setRating(NaN);
});
for (let i = 0; i <= 9; ++i) {
Mousetrap.bind(i.toString(), () => {
if (firstChar.current !== undefined) {
let combined = parseInt(firstChar.current + i.toString());
if (combined === 0) {
combined = 100;
}
setRating(combined);
firstChar.current = undefined;
} else {
firstChar.current = i.toString();
}
});
}
setTimeout(() => {
firstChar.current = undefined;
Mousetrap.unbind("`");
for (let i = 0; i <= 9; ++i) {
Mousetrap.unbind(i.toString());
}
}, 1000);
}
useEffect(() => {
if (!isVisible) return;
Mousetrap.bind("r", () => {
// numeric keypresses get caught by jwplayer, so blur the element
// if the rating sequence is started
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
if (ratingSystem === RatingSystemType.Stars) {
return handleStarRatingKeybinds();
} else {
return handleDecimalKeybinds();
}
});
return () => {
Mousetrap.unbind("r");
};
});
}