mirror of
https://github.com/stashapp/stash.git
synced 2025-12-11 04:43:03 -06:00
Rating system patched components (#5912)
This commit is contained in:
parent
46b0b8cba4
commit
709fdb14de
@ -4,6 +4,7 @@ import { Icon } from "../Icon";
|
||||
import { faPencil, faStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useFocusOnce } from "src/utils/focus";
|
||||
import { useStopWheelScroll } from "src/utils/form";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
export interface IRatingNumberProps {
|
||||
value: number | null;
|
||||
@ -14,151 +15,152 @@ export interface IRatingNumberProps {
|
||||
withoutContext?: boolean;
|
||||
}
|
||||
|
||||
export const RatingNumber: React.FC<IRatingNumberProps> = (
|
||||
props: IRatingNumberProps
|
||||
) => {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [valueStage, setValueStage] = useState<number | null>(props.value);
|
||||
export const RatingNumber = PatchComponent(
|
||||
"RatingNumber",
|
||||
(props: IRatingNumberProps) => {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [valueStage, setValueStage] = useState<number | null>(props.value);
|
||||
|
||||
useEffect(() => {
|
||||
setValueStage(props.value);
|
||||
}, [props.value]);
|
||||
useEffect(() => {
|
||||
setValueStage(props.value);
|
||||
}, [props.value]);
|
||||
|
||||
const showTextField = !props.disabled && (editing || !props.clickToRate);
|
||||
const showTextField = !props.disabled && (editing || !props.clickToRate);
|
||||
|
||||
const [ratingRef] = useFocusOnce(editing, true);
|
||||
useStopWheelScroll(ratingRef);
|
||||
const [ratingRef] = useFocusOnce(editing, true);
|
||||
useStopWheelScroll(ratingRef);
|
||||
|
||||
const effectiveValue = editing ? valueStage : props.value;
|
||||
const effectiveValue = editing ? valueStage : props.value;
|
||||
|
||||
const text = ((effectiveValue ?? 0) / 10).toFixed(1);
|
||||
const useValidation = useRef(true);
|
||||
const text = ((effectiveValue ?? 0) / 10).toFixed(1);
|
||||
const useValidation = useRef(true);
|
||||
|
||||
function stepChange() {
|
||||
useValidation.current = false;
|
||||
}
|
||||
|
||||
function nonStepChange() {
|
||||
useValidation.current = true;
|
||||
}
|
||||
|
||||
function setCursorPosition(
|
||||
target: HTMLInputElement,
|
||||
pos: number,
|
||||
endPos?: number
|
||||
) {
|
||||
// This is a workaround to a missing feature where you can't set cursor position in input numbers.
|
||||
// See https://stackoverflow.com/questions/33406169/failed-to-execute-setselectionrange-on-htmlinputelement-the-input-elements
|
||||
target.type = "text";
|
||||
|
||||
target.setSelectionRange(pos, endPos ?? pos);
|
||||
target.type = "number";
|
||||
}
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (!props.onSetRating) {
|
||||
return;
|
||||
function stepChange() {
|
||||
useValidation.current = false;
|
||||
}
|
||||
|
||||
const setRating = editing ? setValueStage : props.onSetRating;
|
||||
|
||||
let val = e.target.value;
|
||||
if (!useValidation.current) {
|
||||
e.target.value = Number(val).toFixed(1);
|
||||
const tempVal = Number(val) * 10;
|
||||
setRating(tempVal || null);
|
||||
function nonStepChange() {
|
||||
useValidation.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const match = /(\d?)(\d?)(.?)((\d)?)/g.exec(val);
|
||||
const matchOld = /(\d?)(\d?)(.?)((\d{0,2})?)/g.exec(text ?? "");
|
||||
function setCursorPosition(
|
||||
target: HTMLInputElement,
|
||||
pos: number,
|
||||
endPos?: number
|
||||
) {
|
||||
// This is a workaround to a missing feature where you can't set cursor position in input numbers.
|
||||
// See https://stackoverflow.com/questions/33406169/failed-to-execute-setselectionrange-on-htmlinputelement-the-input-elements
|
||||
target.type = "text";
|
||||
|
||||
if (match == null) {
|
||||
return;
|
||||
target.setSelectionRange(pos, endPos ?? pos);
|
||||
target.type = "number";
|
||||
}
|
||||
|
||||
if (match[2] && !(match[2] == "0" && match[1] == "1")) {
|
||||
match[2] = "";
|
||||
}
|
||||
if (match[4] == null || match[4] == "") {
|
||||
match[4] = "0";
|
||||
}
|
||||
|
||||
let value = match[1] + match[2] + "." + match[4];
|
||||
e.target.value = value;
|
||||
|
||||
if (val.length > 0) {
|
||||
if (Number(value) > 10) {
|
||||
value = "10.0";
|
||||
}
|
||||
e.target.value = Number(value).toFixed(1);
|
||||
let tempVal = Number(value) * 10;
|
||||
setRating(tempVal || null);
|
||||
|
||||
let cursorPosition = 0;
|
||||
if (match[2] && !match[4]) {
|
||||
cursorPosition = 3;
|
||||
} else if (matchOld != null && match[1] !== matchOld[1]) {
|
||||
cursorPosition = 2;
|
||||
} else if (
|
||||
matchOld != null &&
|
||||
match[1] === matchOld[1] &&
|
||||
match[2] === matchOld[2] &&
|
||||
match[4] === matchOld[4]
|
||||
) {
|
||||
cursorPosition = 2;
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (!props.onSetRating) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCursorPosition(e.target, cursorPosition);
|
||||
}
|
||||
}
|
||||
const setRating = editing ? setValueStage : props.onSetRating;
|
||||
|
||||
function onBlur() {
|
||||
if (editing) {
|
||||
setEditing(false);
|
||||
if (props.onSetRating && valueStage !== props.value) {
|
||||
props.onSetRating(valueStage);
|
||||
let val = e.target.value;
|
||||
if (!useValidation.current) {
|
||||
e.target.value = Number(val).toFixed(1);
|
||||
const tempVal = Number(val) * 10;
|
||||
setRating(tempVal || null);
|
||||
useValidation.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const match = /(\d?)(\d?)(.?)((\d)?)/g.exec(val);
|
||||
const matchOld = /(\d?)(\d?)(.?)((\d{0,2})?)/g.exec(text ?? "");
|
||||
|
||||
if (match == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (match[2] && !(match[2] == "0" && match[1] == "1")) {
|
||||
match[2] = "";
|
||||
}
|
||||
if (match[4] == null || match[4] == "") {
|
||||
match[4] = "0";
|
||||
}
|
||||
|
||||
let value = match[1] + match[2] + "." + match[4];
|
||||
e.target.value = value;
|
||||
|
||||
if (val.length > 0) {
|
||||
if (Number(value) > 10) {
|
||||
value = "10.0";
|
||||
}
|
||||
e.target.value = Number(value).toFixed(1);
|
||||
let tempVal = Number(value) * 10;
|
||||
setRating(tempVal || null);
|
||||
|
||||
let cursorPosition = 0;
|
||||
if (match[2] && !match[4]) {
|
||||
cursorPosition = 3;
|
||||
} else if (matchOld != null && match[1] !== matchOld[1]) {
|
||||
cursorPosition = 2;
|
||||
} else if (
|
||||
matchOld != null &&
|
||||
match[1] === matchOld[1] &&
|
||||
match[2] === matchOld[2] &&
|
||||
match[4] === matchOld[4]
|
||||
) {
|
||||
cursorPosition = 2;
|
||||
}
|
||||
|
||||
setCursorPosition(e.target, cursorPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!showTextField) {
|
||||
return (
|
||||
<div className="rating-number disabled">
|
||||
{props.withoutContext && <Icon icon={faStar} />}
|
||||
<span>{Number((effectiveValue ?? 0) / 10).toFixed(1)}</span>
|
||||
{!props.disabled && props.clickToRate && (
|
||||
<Button
|
||||
variant="minimal"
|
||||
size="sm"
|
||||
className="edit-rating-button"
|
||||
onClick={() => setEditing(true)}
|
||||
>
|
||||
<Icon className="text-primary" icon={faPencil} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="rating-number">
|
||||
<input
|
||||
ref={ratingRef}
|
||||
className="text-input form-control"
|
||||
name="ratingnumber"
|
||||
type="number"
|
||||
onMouseDown={stepChange}
|
||||
onKeyDown={nonStepChange}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
value={text}
|
||||
min="0.0"
|
||||
step="0.1"
|
||||
max="10"
|
||||
placeholder="0.0"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
function onBlur() {
|
||||
if (editing) {
|
||||
setEditing(false);
|
||||
if (props.onSetRating && valueStage !== props.value) {
|
||||
props.onSetRating(valueStage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!showTextField) {
|
||||
return (
|
||||
<div className="rating-number disabled">
|
||||
{props.withoutContext && <Icon icon={faStar} />}
|
||||
<span>{Number((effectiveValue ?? 0) / 10).toFixed(1)}</span>
|
||||
{!props.disabled && props.clickToRate && (
|
||||
<Button
|
||||
variant="minimal"
|
||||
size="sm"
|
||||
className="edit-rating-button"
|
||||
onClick={() => setEditing(true)}
|
||||
>
|
||||
<Icon className="text-primary" icon={faPencil} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="rating-number">
|
||||
<input
|
||||
ref={ratingRef}
|
||||
className="text-input form-control"
|
||||
name="ratingnumber"
|
||||
type="number"
|
||||
onMouseDown={stepChange}
|
||||
onKeyDown={nonStepChange}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
value={text}
|
||||
min="0.0"
|
||||
step="0.1"
|
||||
max="10"
|
||||
placeholder="0.0"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { Icon } from "../Icon";
|
||||
import { faStar as fasStar } from "@fortawesome/free-solid-svg-icons";
|
||||
@ -11,6 +11,7 @@ import {
|
||||
RatingSystemType,
|
||||
} from "src/utils/rating";
|
||||
import { useIntl } from "react-intl";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
export interface IRatingStarsProps {
|
||||
value: number | null;
|
||||
@ -20,234 +21,235 @@ export interface IRatingStarsProps {
|
||||
valueRequired?: boolean;
|
||||
}
|
||||
|
||||
export const RatingStars: React.FC<IRatingStarsProps> = (
|
||||
props: IRatingStarsProps
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
const [hoverRating, setHoverRating] = useState<number | undefined>();
|
||||
const disabled = props.disabled || !props.onSetRating;
|
||||
export const RatingStars = PatchComponent(
|
||||
"RatingStars",
|
||||
(props: IRatingStarsProps) => {
|
||||
const intl = useIntl();
|
||||
const [hoverRating, setHoverRating] = useState<number | undefined>();
|
||||
const disabled = props.disabled || !props.onSetRating;
|
||||
|
||||
const rating = convertToRatingFormat(props.value, {
|
||||
type: RatingSystemType.Stars,
|
||||
starPrecision: props.precision,
|
||||
});
|
||||
const stars = rating ? Math.floor(rating) : 0;
|
||||
// the upscaling was necesary to fix rounding issue present with tenth place precision
|
||||
const fraction = rating ? ((rating * 10) % 10) / 10 : 0;
|
||||
const rating = convertToRatingFormat(props.value, {
|
||||
type: RatingSystemType.Stars,
|
||||
starPrecision: props.precision,
|
||||
});
|
||||
const stars = rating ? Math.floor(rating) : 0;
|
||||
// the upscaling was necesary to fix rounding issue present with tenth place precision
|
||||
const fraction = rating ? ((rating * 10) % 10) / 10 : 0;
|
||||
|
||||
const max = 5;
|
||||
const precision = getRatingPrecision(props.precision);
|
||||
const max = 5;
|
||||
const precision = getRatingPrecision(props.precision);
|
||||
|
||||
function newToggleFraction() {
|
||||
if (precision !== 1) {
|
||||
if (fraction !== precision) {
|
||||
if (fraction == 0) {
|
||||
return 1 - precision;
|
||||
}
|
||||
|
||||
return fraction - precision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setRating(thisStar: number) {
|
||||
if (!props.onSetRating) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newRating: number | undefined = thisStar;
|
||||
|
||||
// toggle rating fraction if we're clicking on the current rating
|
||||
if (
|
||||
(stars === thisStar && !fraction) ||
|
||||
(stars + 1 === thisStar && fraction)
|
||||
) {
|
||||
const f = newToggleFraction();
|
||||
if (!f) {
|
||||
if (props.valueRequired) {
|
||||
if (fraction) {
|
||||
newRating = stars + 1;
|
||||
} else {
|
||||
newRating = stars;
|
||||
function newToggleFraction() {
|
||||
if (precision !== 1) {
|
||||
if (fraction !== precision) {
|
||||
if (fraction == 0) {
|
||||
return 1 - precision;
|
||||
}
|
||||
|
||||
return fraction - precision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setRating(thisStar: number) {
|
||||
if (!props.onSetRating) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newRating: number | undefined = thisStar;
|
||||
|
||||
// toggle rating fraction if we're clicking on the current rating
|
||||
if (
|
||||
(stars === thisStar && !fraction) ||
|
||||
(stars + 1 === thisStar && fraction)
|
||||
) {
|
||||
const f = newToggleFraction();
|
||||
if (!f) {
|
||||
if (props.valueRequired) {
|
||||
if (fraction) {
|
||||
newRating = stars + 1;
|
||||
} else {
|
||||
newRating = stars;
|
||||
}
|
||||
} else {
|
||||
newRating = undefined;
|
||||
}
|
||||
} else if (fraction) {
|
||||
// we're toggling from an existing fraction so use the stars value
|
||||
newRating = stars + f;
|
||||
} else {
|
||||
newRating = undefined;
|
||||
// we're toggling from a whole value, so decrement from current rating
|
||||
newRating = stars - 1 + f;
|
||||
}
|
||||
} else if (fraction) {
|
||||
// we're toggling from an existing fraction so use the stars value
|
||||
newRating = stars + f;
|
||||
} else {
|
||||
// we're toggling from a whole value, so decrement from current rating
|
||||
newRating = stars - 1 + f;
|
||||
}
|
||||
}
|
||||
|
||||
// set the hover rating to undefined so that it doesn't immediately clear
|
||||
// the stars
|
||||
setHoverRating(undefined);
|
||||
|
||||
if (!newRating) {
|
||||
props.onSetRating(null);
|
||||
return;
|
||||
}
|
||||
|
||||
props.onSetRating(
|
||||
convertFromRatingFormat(newRating, RatingSystemType.Stars)
|
||||
);
|
||||
}
|
||||
|
||||
function onMouseOver(thisStar: number) {
|
||||
if (!disabled) {
|
||||
setHoverRating(thisStar);
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseOut(thisStar: number) {
|
||||
if (!disabled && hoverRating === thisStar) {
|
||||
// set the hover rating to undefined so that it doesn't immediately clear
|
||||
// the stars
|
||||
setHoverRating(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function getClassName(thisStar: number) {
|
||||
if (hoverRating && hoverRating >= thisStar) {
|
||||
if (hoverRating === stars) {
|
||||
return "unsetting";
|
||||
if (!newRating) {
|
||||
props.onSetRating(null);
|
||||
return;
|
||||
}
|
||||
|
||||
return "setting";
|
||||
props.onSetRating(
|
||||
convertFromRatingFormat(newRating, RatingSystemType.Stars)
|
||||
);
|
||||
}
|
||||
|
||||
if (stars && stars >= thisStar) {
|
||||
return "set";
|
||||
}
|
||||
|
||||
return "unset";
|
||||
}
|
||||
|
||||
function getTooltip(thisStar: number, current: RatingFraction | undefined) {
|
||||
if (disabled) {
|
||||
if (rating) {
|
||||
// always return current rating for disabled control
|
||||
return rating.toString();
|
||||
function onMouseOver(thisStar: number) {
|
||||
if (!disabled) {
|
||||
setHoverRating(thisStar);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// adjust tooltip to use fractions
|
||||
if (!current) {
|
||||
return intl.formatMessage({ id: "actions.unset" });
|
||||
function onMouseOut(thisStar: number) {
|
||||
if (!disabled && hoverRating === thisStar) {
|
||||
setHoverRating(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
return (current.rating + current.fraction).toString();
|
||||
}
|
||||
|
||||
type RatingFraction = {
|
||||
rating: number;
|
||||
fraction: number;
|
||||
};
|
||||
|
||||
function getCurrentSelectedRating(): RatingFraction | undefined {
|
||||
let r: number = hoverRating ? hoverRating : stars;
|
||||
let f: number | undefined = fraction;
|
||||
|
||||
if (hoverRating) {
|
||||
if (hoverRating === stars && precision === 1) {
|
||||
if (props.valueRequired) {
|
||||
return { rating: r, fraction: 0 };
|
||||
function getClassName(thisStar: number) {
|
||||
if (hoverRating && hoverRating >= thisStar) {
|
||||
if (hoverRating === stars) {
|
||||
return "unsetting";
|
||||
}
|
||||
|
||||
// unsetting
|
||||
return undefined;
|
||||
return "setting";
|
||||
}
|
||||
if (hoverRating === stars + 1 && fraction && fraction === precision) {
|
||||
if (props.valueRequired) {
|
||||
return { rating: r, fraction: 0 };
|
||||
|
||||
if (stars && stars >= thisStar) {
|
||||
return "set";
|
||||
}
|
||||
|
||||
return "unset";
|
||||
}
|
||||
|
||||
function getTooltip(thisStar: number, current: RatingFraction | undefined) {
|
||||
if (disabled) {
|
||||
if (rating) {
|
||||
// always return current rating for disabled control
|
||||
return rating.toString();
|
||||
}
|
||||
// unsetting
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (f && hoverRating === stars + 1) {
|
||||
f = newToggleFraction();
|
||||
r--;
|
||||
} else if (!f && hoverRating === stars) {
|
||||
f = newToggleFraction();
|
||||
r--;
|
||||
} else {
|
||||
f = 0;
|
||||
// adjust tooltip to use fractions
|
||||
if (!current) {
|
||||
return intl.formatMessage({ id: "actions.unset" });
|
||||
}
|
||||
|
||||
return (current.rating + current.fraction).toString();
|
||||
}
|
||||
|
||||
return { rating: r, fraction: f ?? 0 };
|
||||
}
|
||||
type RatingFraction = {
|
||||
rating: number;
|
||||
fraction: number;
|
||||
};
|
||||
|
||||
function getButtonClassName(
|
||||
thisStar: number,
|
||||
current: RatingFraction | undefined
|
||||
) {
|
||||
if (!current || thisStar > current.rating + 1) {
|
||||
return "star-fill-0";
|
||||
function getCurrentSelectedRating(): RatingFraction | undefined {
|
||||
let r: number = hoverRating ? hoverRating : stars;
|
||||
let f: number | undefined = fraction;
|
||||
|
||||
if (hoverRating) {
|
||||
if (hoverRating === stars && precision === 1) {
|
||||
if (props.valueRequired) {
|
||||
return { rating: r, fraction: 0 };
|
||||
}
|
||||
|
||||
// unsetting
|
||||
return undefined;
|
||||
}
|
||||
if (hoverRating === stars + 1 && fraction && fraction === precision) {
|
||||
if (props.valueRequired) {
|
||||
return { rating: r, fraction: 0 };
|
||||
}
|
||||
// unsetting
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (f && hoverRating === stars + 1) {
|
||||
f = newToggleFraction();
|
||||
r--;
|
||||
} else if (!f && hoverRating === stars) {
|
||||
f = newToggleFraction();
|
||||
r--;
|
||||
} else {
|
||||
f = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return { rating: r, fraction: f ?? 0 };
|
||||
}
|
||||
|
||||
if (thisStar <= current.rating) {
|
||||
return "star-fill-100";
|
||||
}
|
||||
|
||||
let w = current.fraction * 100;
|
||||
return `star-fill-${w}`;
|
||||
}
|
||||
|
||||
const renderRatingButton = (thisStar: number) => {
|
||||
const ratingFraction = getCurrentSelectedRating();
|
||||
|
||||
return (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
className={`minimal ${getButtonClassName(thisStar, ratingFraction)}`}
|
||||
onClick={() => setRating(thisStar)}
|
||||
variant="secondary"
|
||||
onMouseEnter={() => onMouseOver(thisStar)}
|
||||
onMouseLeave={() => onMouseOut(thisStar)}
|
||||
onFocus={() => onMouseOver(thisStar)}
|
||||
onBlur={() => onMouseOut(thisStar)}
|
||||
title={getTooltip(thisStar, ratingFraction)}
|
||||
key={`star-${thisStar}`}
|
||||
>
|
||||
<div className="filled-star">
|
||||
<Icon icon={fasStar} className="set" />
|
||||
</div>
|
||||
<div className="unfilled-star">
|
||||
<Icon icon={farStar} className={getClassName(thisStar)} />
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const maybeRenderStarRatingNumber = () => {
|
||||
const ratingFraction = getCurrentSelectedRating();
|
||||
if (
|
||||
!ratingFraction ||
|
||||
(ratingFraction.rating == 0 && ratingFraction.fraction == 0)
|
||||
function getButtonClassName(
|
||||
thisStar: number,
|
||||
current: RatingFraction | undefined
|
||||
) {
|
||||
return;
|
||||
if (!current || thisStar > current.rating + 1) {
|
||||
return "star-fill-0";
|
||||
}
|
||||
|
||||
if (thisStar <= current.rating) {
|
||||
return "star-fill-100";
|
||||
}
|
||||
|
||||
let w = current.fraction * 100;
|
||||
return `star-fill-${w}`;
|
||||
}
|
||||
|
||||
const renderRatingButton = (thisStar: number) => {
|
||||
const ratingFraction = getCurrentSelectedRating();
|
||||
|
||||
return (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
className={`minimal ${getButtonClassName(thisStar, ratingFraction)}`}
|
||||
onClick={() => setRating(thisStar)}
|
||||
variant="secondary"
|
||||
onMouseEnter={() => onMouseOver(thisStar)}
|
||||
onMouseLeave={() => onMouseOut(thisStar)}
|
||||
onFocus={() => onMouseOver(thisStar)}
|
||||
onBlur={() => onMouseOut(thisStar)}
|
||||
title={getTooltip(thisStar, ratingFraction)}
|
||||
key={`star-${thisStar}`}
|
||||
>
|
||||
<div className="filled-star">
|
||||
<Icon icon={fasStar} className="set" />
|
||||
</div>
|
||||
<div className="unfilled-star">
|
||||
<Icon icon={farStar} className={getClassName(thisStar)} />
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const maybeRenderStarRatingNumber = () => {
|
||||
const ratingFraction = getCurrentSelectedRating();
|
||||
if (
|
||||
!ratingFraction ||
|
||||
(ratingFraction.rating == 0 && ratingFraction.fraction == 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="star-rating-number">
|
||||
{ratingFraction.rating + ratingFraction.fraction}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const precisionClassName = `rating-stars-precision-${props.precision}`;
|
||||
|
||||
return (
|
||||
<span className="star-rating-number">
|
||||
{ratingFraction.rating + ratingFraction.fraction}
|
||||
</span>
|
||||
<div className={`rating-stars ${precisionClassName}`}>
|
||||
{Array.from(Array(max)).map((value, index) =>
|
||||
renderRatingButton(index + 1)
|
||||
)}
|
||||
{maybeRenderStarRatingNumber()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const precisionClassName = `rating-stars-precision-${props.precision}`;
|
||||
|
||||
return (
|
||||
<div className={`rating-stars ${precisionClassName}`}>
|
||||
{Array.from(Array(max)).map((value, index) =>
|
||||
renderRatingButton(index + 1)
|
||||
)}
|
||||
{maybeRenderStarRatingNumber()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
} from "src/utils/rating";
|
||||
import { RatingNumber } from "./RatingNumber";
|
||||
import { RatingStars } from "./RatingStars";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
export interface IRatingSystemProps {
|
||||
value: number | null | undefined;
|
||||
@ -19,34 +20,35 @@ export interface IRatingSystemProps {
|
||||
withoutContext?: boolean;
|
||||
}
|
||||
|
||||
export const RatingSystem: React.FC<IRatingSystemProps> = (
|
||||
props: IRatingSystemProps
|
||||
) => {
|
||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
||||
const ratingSystemOptions =
|
||||
config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
||||
export const RatingSystem = PatchComponent(
|
||||
"RatingSystem",
|
||||
(props: IRatingSystemProps) => {
|
||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
||||
const ratingSystemOptions =
|
||||
config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
||||
|
||||
if (ratingSystemOptions.type === RatingSystemType.Stars) {
|
||||
return (
|
||||
<RatingStars
|
||||
value={props.value ?? null}
|
||||
onSetRating={props.onSetRating}
|
||||
disabled={props.disabled}
|
||||
precision={
|
||||
ratingSystemOptions.starPrecision ?? defaultRatingStarPrecision
|
||||
}
|
||||
valueRequired={props.valueRequired}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<RatingNumber
|
||||
value={props.value ?? null}
|
||||
onSetRating={props.onSetRating}
|
||||
disabled={props.disabled}
|
||||
clickToRate={props.clickToRate}
|
||||
withoutContext={props.withoutContext}
|
||||
/>
|
||||
);
|
||||
if (ratingSystemOptions.type === RatingSystemType.Stars) {
|
||||
return (
|
||||
<RatingStars
|
||||
value={props.value ?? null}
|
||||
onSetRating={props.onSetRating}
|
||||
disabled={props.disabled}
|
||||
precision={
|
||||
ratingSystemOptions.starPrecision ?? defaultRatingStarPrecision
|
||||
}
|
||||
valueRequired={props.valueRequired}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<RatingNumber
|
||||
value={props.value ?? null}
|
||||
onSetRating={props.onSetRating}
|
||||
disabled={props.disabled}
|
||||
clickToRate={props.clickToRate}
|
||||
withoutContext={props.withoutContext}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
@ -196,6 +196,9 @@ Returns `void`.
|
||||
- `PerformerImagesPanel`
|
||||
- `PerformerScenesPanel`
|
||||
- `PluginRoutes`
|
||||
- `RatingNumber`
|
||||
- `RatingStars`
|
||||
- `RatingSystem`
|
||||
- `SceneCard`
|
||||
- `SceneCard.Details`
|
||||
- `SceneCard.Image`
|
||||
|
||||
3
ui/v2.5/src/pluginApi.d.ts
vendored
3
ui/v2.5/src/pluginApi.d.ts
vendored
@ -727,6 +727,9 @@ declare namespace PluginApi {
|
||||
"GalleryCard.Image": React.FC<any>;
|
||||
"GalleryCard.Overlays": React.FC<any>;
|
||||
"GalleryCard.Popovers": React.FC<any>;
|
||||
RatingNumber: React.FC<any>;
|
||||
RatingStars: React.FC<any>;
|
||||
RatingSystem: React.FC<any>;
|
||||
};
|
||||
type PatchableComponentNames = keyof typeof components | string;
|
||||
namespace utils {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user