mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
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:
@@ -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");
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
85
ui/v2.5/src/hooks/keybinds.ts
Normal file
85
ui/v2.5/src/hooks/keybinds.ts
Normal 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");
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user