mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Use RatingSystem control in RatingFilter (#3133)
* Use RatingSystem control in RatingFilter * Improve styling for rating on performer page
This commit is contained in:
@@ -216,11 +216,7 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
|
|||||||
}
|
}
|
||||||
if (criterion instanceof RatingCriterion) {
|
if (criterion instanceof RatingCriterion) {
|
||||||
return (
|
return (
|
||||||
<RatingFilter
|
<RatingFilter criterion={criterion} onValueChanged={onValueChanged} />
|
||||||
criterion={criterion}
|
|
||||||
onValueChanged={onValueChanged}
|
|
||||||
configuration={config}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -307,12 +303,32 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValid() {
|
||||||
|
if (criterion.criterionOption.type === "none") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (criterion instanceof RatingCriterion) {
|
||||||
|
switch (criterion.modifier) {
|
||||||
|
case CriterionModifier.Equals:
|
||||||
|
case CriterionModifier.NotEquals:
|
||||||
|
case CriterionModifier.LessThan:
|
||||||
|
return !!criterion.value.value;
|
||||||
|
case CriterionModifier.Between:
|
||||||
|
case CriterionModifier.NotBetween:
|
||||||
|
return criterion.value.value < (criterion.value.value2 ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const title = !editingCriterion
|
const title = !editingCriterion
|
||||||
? intl.formatMessage({ id: "search_filter.add_filter" })
|
? intl.formatMessage({ id: "search_filter.add_filter" })
|
||||||
: intl.formatMessage({ id: "search_filter.update_filter" });
|
: intl.formatMessage({ id: "search_filter.update_filter" });
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal show onHide={() => onCancel()}>
|
<Modal show onHide={() => onCancel()} className="add-filter-dialog">
|
||||||
<Modal.Header>{title}</Modal.Header>
|
<Modal.Header>{title}</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<div className="dialog-content">
|
<div className="dialog-content">
|
||||||
@@ -322,10 +338,7 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<Button
|
<Button onClick={onAddFilter} disabled={!isValid()}>
|
||||||
onClick={onAddFilter}
|
|
||||||
disabled={criterion.criterionOption.type === "none"}
|
|
||||||
>
|
|
||||||
{title}
|
{title}
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,135 +1,61 @@
|
|||||||
import React, { useRef } from "react";
|
import React from "react";
|
||||||
import { Form } from "react-bootstrap";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useIntl } from "react-intl";
|
|
||||||
import { CriterionModifier } from "../../../core/generated-graphql";
|
import { CriterionModifier } from "../../../core/generated-graphql";
|
||||||
import { INumberValue } from "../../../models/list-filter/types";
|
import { INumberValue } from "../../../models/list-filter/types";
|
||||||
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
||||||
import {
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
convertFromRatingFormat,
|
|
||||||
convertToRatingFormat,
|
|
||||||
defaultRatingSystemOptions,
|
|
||||||
} from "src/utils/rating";
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
|
||||||
import { IUIConfig } from "src/core/config";
|
|
||||||
|
|
||||||
interface IDurationFilterProps {
|
interface IRatingFilterProps {
|
||||||
criterion: Criterion<INumberValue>;
|
criterion: Criterion<INumberValue>;
|
||||||
onValueChanged: (value: INumberValue) => void;
|
onValueChanged: (value: INumberValue) => void;
|
||||||
configuration: GQL.ConfigDataFragment | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RatingFilter: React.FC<IDurationFilterProps> = ({
|
export const RatingFilter: React.FC<IRatingFilterProps> = ({
|
||||||
criterion,
|
criterion,
|
||||||
onValueChanged,
|
onValueChanged,
|
||||||
configuration,
|
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
function getRatingSystem(field: "value" | "value2") {
|
||||||
const ratingSystem =
|
const defaultValue = field === "value" ? 0 : undefined;
|
||||||
(configuration?.ui as IUIConfig)?.ratingSystemOptions ??
|
|
||||||
defaultRatingSystemOptions;
|
|
||||||
|
|
||||||
const valueStage = useRef<INumberValue>(criterion.value);
|
return (
|
||||||
|
<div>
|
||||||
function onChanged(
|
<RatingSystem
|
||||||
event: React.ChangeEvent<HTMLInputElement>,
|
value={criterion.value[field]}
|
||||||
property: "value" | "value2"
|
onSetRating={(value) => {
|
||||||
) {
|
onValueChanged({
|
||||||
const value = parseInt(event.target.value, 10);
|
...criterion.value,
|
||||||
valueStage.current[property] = !Number.isNaN(value)
|
[field]: value ?? defaultValue,
|
||||||
? convertFromRatingFormat(value, ratingSystem.type)
|
});
|
||||||
: 0;
|
}}
|
||||||
|
valueRequired
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBlurInput() {
|
|
||||||
onValueChanged(valueStage.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
let equalsControl: JSX.Element | null = null;
|
|
||||||
if (
|
if (
|
||||||
criterion.modifier === CriterionModifier.Equals ||
|
criterion.modifier === CriterionModifier.Equals ||
|
||||||
criterion.modifier === CriterionModifier.NotEquals
|
criterion.modifier === CriterionModifier.NotEquals ||
|
||||||
) {
|
|
||||||
equalsControl = (
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Control
|
|
||||||
className="btn-secondary"
|
|
||||||
type="number"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
onChanged(e, "value")
|
|
||||||
}
|
|
||||||
onBlur={onBlurInput}
|
|
||||||
defaultValue={
|
|
||||||
convertToRatingFormat(criterion.value?.value, ratingSystem) ?? ""
|
|
||||||
}
|
|
||||||
placeholder={intl.formatMessage({ id: "criterion.value" })}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let lowerControl: JSX.Element | null = null;
|
|
||||||
if (
|
|
||||||
criterion.modifier === CriterionModifier.GreaterThan ||
|
criterion.modifier === CriterionModifier.GreaterThan ||
|
||||||
criterion.modifier === CriterionModifier.Between ||
|
criterion.modifier === CriterionModifier.LessThan
|
||||||
criterion.modifier === CriterionModifier.NotBetween
|
|
||||||
) {
|
) {
|
||||||
lowerControl = (
|
return getRatingSystem("value");
|
||||||
<Form.Group>
|
|
||||||
<Form.Control
|
|
||||||
className="btn-secondary"
|
|
||||||
type="number"
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
onChanged(e, "value")
|
|
||||||
}
|
|
||||||
onBlur={onBlurInput}
|
|
||||||
defaultValue={
|
|
||||||
convertToRatingFormat(criterion.value?.value, ratingSystem) ?? ""
|
|
||||||
}
|
|
||||||
placeholder={intl.formatMessage({ id: "criterion.greater_than" })}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let upperControl: JSX.Element | null = null;
|
|
||||||
if (
|
if (
|
||||||
criterion.modifier === CriterionModifier.LessThan ||
|
|
||||||
criterion.modifier === CriterionModifier.Between ||
|
criterion.modifier === CriterionModifier.Between ||
|
||||||
criterion.modifier === CriterionModifier.NotBetween
|
criterion.modifier === CriterionModifier.NotBetween
|
||||||
) {
|
) {
|
||||||
upperControl = (
|
return (
|
||||||
<Form.Group>
|
<div className="rating-filter">
|
||||||
<Form.Control
|
{getRatingSystem("value")}
|
||||||
className="btn-secondary"
|
<span className="and-divider">
|
||||||
type="number"
|
<FormattedMessage id="between_and" />
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
</span>
|
||||||
onChanged(
|
{getRatingSystem("value2")}
|
||||||
e,
|
</div>
|
||||||
criterion.modifier === CriterionModifier.LessThan
|
|
||||||
? "value"
|
|
||||||
: "value2"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onBlur={onBlurInput}
|
|
||||||
defaultValue={
|
|
||||||
convertToRatingFormat(
|
|
||||||
criterion.modifier === CriterionModifier.LessThan
|
|
||||||
? criterion.value?.value
|
|
||||||
: criterion.value?.value2,
|
|
||||||
ratingSystem
|
|
||||||
) ?? ""
|
|
||||||
}
|
|
||||||
placeholder={intl.formatMessage({ id: "criterion.less_than" })}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <></>;
|
||||||
<>
|
|
||||||
{equalsControl}
|
|
||||||
{lowerControl}
|
|
||||||
{upperControl}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,3 +107,12 @@ input[type="range"].zoom-slider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-filter-dialog .rating-stars {
|
||||||
|
font-size: 1.3em;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating-filter .and-divider {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rating-number .form-control {
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alias {
|
.alias {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export interface IRatingStarsProps {
|
|||||||
onSetRating?: (value?: number) => void;
|
onSetRating?: (value?: number) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
precision: RatingStarPrecision;
|
precision: RatingStarPrecision;
|
||||||
|
valueRequired?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RatingStars: React.FC<IRatingStarsProps> = (
|
export const RatingStars: React.FC<IRatingStarsProps> = (
|
||||||
@@ -62,7 +63,15 @@ export const RatingStars: React.FC<IRatingStarsProps> = (
|
|||||||
) {
|
) {
|
||||||
const f = newToggleFraction();
|
const f = newToggleFraction();
|
||||||
if (!f) {
|
if (!f) {
|
||||||
newRating = undefined;
|
if (props.valueRequired) {
|
||||||
|
if (fraction) {
|
||||||
|
newRating = stars + 1;
|
||||||
|
} else {
|
||||||
|
newRating = stars;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newRating = undefined;
|
||||||
|
}
|
||||||
} else if (fraction) {
|
} else if (fraction) {
|
||||||
// we're toggling from an existing fraction so use the stars value
|
// we're toggling from an existing fraction so use the stars value
|
||||||
newRating = stars + f;
|
newRating = stars + f;
|
||||||
@@ -143,10 +152,17 @@ export const RatingStars: React.FC<IRatingStarsProps> = (
|
|||||||
|
|
||||||
if (hoverRating) {
|
if (hoverRating) {
|
||||||
if (hoverRating === stars && precision === 1) {
|
if (hoverRating === stars && precision === 1) {
|
||||||
|
if (props.valueRequired) {
|
||||||
|
return { rating: r, fraction: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
// unsetting
|
// unsetting
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (hoverRating === stars + 1 && fraction && fraction === precision) {
|
if (hoverRating === stars + 1 && fraction && fraction === precision) {
|
||||||
|
if (props.valueRequired) {
|
||||||
|
return { rating: r, fraction: 0 };
|
||||||
|
}
|
||||||
// unsetting
|
// unsetting
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface IRatingSystemProps {
|
|||||||
value?: number;
|
value?: number;
|
||||||
onSetRating?: (value?: number) => void;
|
onSetRating?: (value?: number) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
valueRequired?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RatingSystem: React.FC<IRatingSystemProps> = (
|
export const RatingSystem: React.FC<IRatingSystemProps> = (
|
||||||
@@ -32,6 +33,7 @@ export const RatingSystem: React.FC<IRatingSystemProps> = (
|
|||||||
precision={
|
precision={
|
||||||
ratingSystemOptions.starPrecision ?? defaultRatingStarPrecision
|
ratingSystemOptions.starPrecision ?? defaultRatingStarPrecision
|
||||||
}
|
}
|
||||||
|
valueRequired={props.valueRequired}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,7 @@
|
|||||||
"birth_year": "Birth Year",
|
"birth_year": "Birth Year",
|
||||||
"birthdate": "Birthdate",
|
"birthdate": "Birthdate",
|
||||||
"bitrate": "Bit Rate",
|
"bitrate": "Bit Rate",
|
||||||
|
"between_and": "and",
|
||||||
"captions": "Captions",
|
"captions": "Captions",
|
||||||
"career_length": "Career Length",
|
"career_length": "Career Length",
|
||||||
"component_tagger": {
|
"component_tagger": {
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ export class NullNumberCriterionOption extends CriterionOption {
|
|||||||
CriterionModifier.IsNull,
|
CriterionModifier.IsNull,
|
||||||
CriterionModifier.NotNull,
|
CriterionModifier.NotNull,
|
||||||
],
|
],
|
||||||
defaultModifier: CriterionModifier.GreaterThan,
|
defaultModifier: CriterionModifier.Equals,
|
||||||
inputType: "number",
|
inputType: "number",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ export class RatingCriterion extends Criterion<INumberValue> {
|
|||||||
this.modifier === CriterionModifier.Between ||
|
this.modifier === CriterionModifier.Between ||
|
||||||
this.modifier === CriterionModifier.NotBetween
|
this.modifier === CriterionModifier.NotBetween
|
||||||
) {
|
) {
|
||||||
return `${convertToRatingFormat(value, this.ratingSystem)}, ${
|
return `${convertToRatingFormat(value, this.ratingSystem) ?? 0}, ${
|
||||||
convertToRatingFormat(value2, this.ratingSystem) ?? 0
|
convertToRatingFormat(value2, this.ratingSystem) ?? 0
|
||||||
}`;
|
}`;
|
||||||
} else {
|
} else {
|
||||||
return `${convertToRatingFormat(value, this.ratingSystem)}`;
|
return `${convertToRatingFormat(value, this.ratingSystem) ?? 0}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user