mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Rating stars (#567)
* Add ratings stars control. Add to scene details * Replace rating with stars on edit panel * Add changelog entry
This commit is contained in:
@@ -85,7 +85,6 @@
|
||||
"string-no-newline": true,
|
||||
"string-quotes": "double",
|
||||
"time-min-milliseconds": 100,
|
||||
"unit-blacklist": ["em"],
|
||||
"value-list-comma-space-after": "always-single-line",
|
||||
"value-list-comma-space-before": "never",
|
||||
"value-no-vendor-prefix": true
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"@apollo/react-hooks": "^3.1.5",
|
||||
"@formatjs/intl-numberformat": "^4.2.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.13.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.9",
|
||||
"apollo-cache": "^1.3.4",
|
||||
|
||||
@@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown";
|
||||
|
||||
const markup = `
|
||||
### 🎨 Improvements
|
||||
* Show rating as stars in scene page.
|
||||
* Add reload scrapers button.
|
||||
|
||||
`;
|
||||
|
||||
118
ui/v2.5/src/components/Scenes/SceneDetails/RatingStars.tsx
Normal file
118
ui/v2.5/src/components/Scenes/SceneDetails/RatingStars.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
|
||||
export interface IRatingStarsProps {
|
||||
value?: number;
|
||||
onSetRating?: (value?: number) => void;
|
||||
}
|
||||
|
||||
export const RatingStars: React.FC<IRatingStarsProps> = (
|
||||
props: IRatingStarsProps
|
||||
) => {
|
||||
const [hoverRating, setHoverRating] = useState<number | undefined>();
|
||||
const disabled = !props.onSetRating;
|
||||
|
||||
function setRating(rating: number) {
|
||||
if (!props.onSetRating) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newRating: number | undefined = rating;
|
||||
|
||||
// unset if we're clicking on the current rating
|
||||
if (props.value === rating) {
|
||||
newRating = undefined;
|
||||
}
|
||||
|
||||
// set the hover rating to undefined so that it doesn't immediately clear
|
||||
// the stars
|
||||
setHoverRating(undefined);
|
||||
|
||||
props.onSetRating(newRating);
|
||||
}
|
||||
|
||||
function getIconPrefix(rating: number) {
|
||||
if (hoverRating && hoverRating >= rating) {
|
||||
if (hoverRating === props.value) {
|
||||
return "far";
|
||||
}
|
||||
|
||||
return "fas";
|
||||
}
|
||||
|
||||
if (!hoverRating && props.value && props.value >= rating) {
|
||||
return "fas";
|
||||
}
|
||||
|
||||
return "far";
|
||||
}
|
||||
|
||||
function onMouseOver(rating: number) {
|
||||
if (!disabled) {
|
||||
setHoverRating(rating);
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseOut(rating: number) {
|
||||
if (!disabled && hoverRating === rating) {
|
||||
setHoverRating(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function getClassName(rating: number) {
|
||||
if (hoverRating && hoverRating >= rating) {
|
||||
if (hoverRating === props.value) {
|
||||
return "unsetting";
|
||||
}
|
||||
|
||||
return "setting";
|
||||
}
|
||||
|
||||
if (props.value && props.value >= rating) {
|
||||
return "set";
|
||||
}
|
||||
|
||||
return "unset";
|
||||
}
|
||||
|
||||
function getTooltip(rating: number) {
|
||||
if (disabled && props.value) {
|
||||
// always return current rating for disabled control
|
||||
return props.value.toString();
|
||||
}
|
||||
|
||||
if (!disabled) {
|
||||
return rating.toString();
|
||||
}
|
||||
}
|
||||
|
||||
const renderRatingButton = (rating: number) => (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
className="minimal"
|
||||
onClick={() => setRating(rating)}
|
||||
variant="secondary"
|
||||
onMouseOver={() => onMouseOver(rating)}
|
||||
onMouseOut={() => onMouseOut(rating)}
|
||||
onFocus={() => onMouseOver(rating)}
|
||||
onBlur={() => onMouseOut(rating)}
|
||||
title={getTooltip(rating)}
|
||||
>
|
||||
<Icon
|
||||
icon={[getIconPrefix(rating), "star"]}
|
||||
className={getClassName(rating)}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
|
||||
const maxRating = 5;
|
||||
|
||||
return (
|
||||
<div className="rating-stars">
|
||||
{Array.from(Array(maxRating)).map((value, index) =>
|
||||
renderRatingButton(index + 1)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { FormattedDate } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { TextUtils } from "src/utils";
|
||||
import { TagLink } from "src/components/Shared";
|
||||
import { RatingStars } from "./RatingStars";
|
||||
|
||||
interface ISceneDetailProps {
|
||||
scene: GQL.SceneDataFragment;
|
||||
@@ -44,7 +45,13 @@ export const SceneDetailPanel: React.FC<ISceneDetailProps> = (props) => {
|
||||
<FormattedDate value={props.scene.date} format="long" />
|
||||
</h4>
|
||||
) : undefined}
|
||||
{props.scene.rating ? <h6>Rating: {props.scene.rating}</h6> : ""}
|
||||
{props.scene.rating ? (
|
||||
<h6>
|
||||
Rating: <RatingStars value={props.scene.rating} />
|
||||
</h6>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{props.scene.file.height && (
|
||||
<h6>Resolution: {TextUtils.resolution(props.scene.file.height)}</h6>
|
||||
)}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useToast } from "src/hooks";
|
||||
import { ImageUtils, TableUtils } from "src/utils";
|
||||
import { MovieSelect } from "src/components/Shared/Select";
|
||||
import { SceneMovieTable, MovieSceneIndexMap } from "./SceneMovieTable";
|
||||
import { RatingStars } from "./RatingStars";
|
||||
|
||||
interface IProps {
|
||||
scene: GQL.SceneDataFragment;
|
||||
@@ -441,14 +442,15 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
||||
onChange: setDate,
|
||||
placeholder: "YYYY-MM-DD",
|
||||
})}
|
||||
{TableUtils.renderHtmlSelect({
|
||||
title: "Rating",
|
||||
value: rating,
|
||||
isEditing: true,
|
||||
onChange: (value: string) =>
|
||||
setRating(Number.parseInt(value, 10)),
|
||||
selectOptions: ["", 1, 2, 3, 4, 5],
|
||||
})}
|
||||
<tr className="rating">
|
||||
<td>Rating</td>
|
||||
<td>
|
||||
<RatingStars
|
||||
value={rating}
|
||||
onSetRating={(value) => setRating(value)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gallery</td>
|
||||
<td>
|
||||
|
||||
@@ -218,3 +218,44 @@
|
||||
.movie-table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.rating-stars {
|
||||
display: inline-block;
|
||||
|
||||
button {
|
||||
font-size: inherit;
|
||||
margin-right: 1px;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: inherit;
|
||||
opacity: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.unsetting {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
.setting {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
.set {
|
||||
color: gold;
|
||||
}
|
||||
}
|
||||
|
||||
.rating td {
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#scene-edit-details .rating-stars {
|
||||
font-size: 1.3em;
|
||||
height: calc(1.5em + 0.75rem + 2px);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconName } from "@fortawesome/fontawesome-svg-core";
|
||||
import { IconProp, library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faStar as fasStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faStar as farStar } from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
// need these to use far and fas styles of stars
|
||||
library.add(fasStar, farStar);
|
||||
|
||||
interface IIcon {
|
||||
icon: IconName;
|
||||
icon: IconProp;
|
||||
className?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
@@ -1761,6 +1761,13 @@
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@fortawesome/free-regular-svg-icons@^5.13.0":
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.0.tgz#925a13d8bdda0678f71551828cac80ab47b8150c"
|
||||
integrity sha512-70FAyiS5j+ANYD4dh9NGowTorNDnyvQHHpCM7FpnF7GxtDjBUCKdrFqCPzesEIpNDFNd+La3vex+jDk4nnUfpA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^5.13.0":
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz#44d9118668ad96b4fd5c9434a43efc5903525739"
|
||||
|
||||
Reference in New Issue
Block a user