mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
* add rating100 fields to represent rating range 1-100 * deprecate existing (1-5) rating fields * add half- and quarter-star options for rating system * add decimal rating system option Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
467 lines
13 KiB
TypeScript
467 lines
13 KiB
TypeScript
import React from "react";
|
|
import isEqual from "lodash-es/isEqual";
|
|
import clone from "lodash-es/clone";
|
|
import { Form } from "react-bootstrap";
|
|
import {
|
|
ParseSceneFilenamesQuery,
|
|
SlimSceneDataFragment,
|
|
} from "src/core/generated-graphql";
|
|
import {
|
|
PerformerSelect,
|
|
TagSelect,
|
|
StudioSelect,
|
|
} from "src/components/Shared";
|
|
import cx from "classnames";
|
|
import { objectTitle } from "src/core/files";
|
|
|
|
class ParserResult<T> {
|
|
public value?: T;
|
|
public originalValue?: T;
|
|
public isSet: boolean = false;
|
|
|
|
public setOriginalValue(value?: T) {
|
|
this.originalValue = value;
|
|
this.value = value;
|
|
}
|
|
|
|
public setValue(value?: T) {
|
|
if (value) {
|
|
this.value = value;
|
|
this.isSet = !isEqual(this.value, this.originalValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class SceneParserResult {
|
|
public id: string;
|
|
public filename: string;
|
|
public title: ParserResult<string> = new ParserResult<string>();
|
|
public date: ParserResult<string> = new ParserResult<string>();
|
|
public rating: ParserResult<number> = new ParserResult<number>();
|
|
|
|
public studio: ParserResult<string> = new ParserResult<string>();
|
|
public tags: ParserResult<string[]> = new ParserResult<string[]>();
|
|
public performers: ParserResult<string[]> = new ParserResult<string[]>();
|
|
|
|
public scene: SlimSceneDataFragment;
|
|
|
|
constructor(
|
|
result: ParseSceneFilenamesQuery["parseSceneFilenames"]["results"][0]
|
|
) {
|
|
this.scene = result.scene;
|
|
|
|
this.id = this.scene.id;
|
|
this.filename = objectTitle(this.scene);
|
|
this.title.setOriginalValue(this.scene.title ?? undefined);
|
|
this.date.setOriginalValue(this.scene.date ?? undefined);
|
|
this.rating.setOriginalValue(this.scene.rating100 ?? undefined);
|
|
this.performers.setOriginalValue(this.scene.performers.map((p) => p.id));
|
|
this.tags.setOriginalValue(this.scene.tags.map((t) => t.id));
|
|
this.studio.setOriginalValue(this.scene.studio?.id);
|
|
|
|
this.title.setValue(result.title ?? undefined);
|
|
this.date.setValue(result.date ?? undefined);
|
|
this.rating.setValue(result.rating ?? undefined);
|
|
|
|
this.performers.setValue(result.performer_ids ?? undefined);
|
|
this.tags.setValue(result.tag_ids ?? undefined);
|
|
this.studio.setValue(result.studio_id ?? undefined);
|
|
}
|
|
|
|
// returns true if any of its fields have set == true
|
|
public isChanged() {
|
|
return (
|
|
this.title.isSet ||
|
|
this.date.isSet ||
|
|
this.rating.isSet ||
|
|
this.performers.isSet ||
|
|
this.studio.isSet ||
|
|
this.tags.isSet
|
|
);
|
|
}
|
|
|
|
public toSceneUpdateInput() {
|
|
return {
|
|
id: this.id,
|
|
rating: this.rating.isSet ? this.rating.value : undefined,
|
|
title: this.title.isSet ? this.title.value : undefined,
|
|
date: this.date.isSet ? this.date.value : undefined,
|
|
studio_id: this.studio.isSet ? this.studio.value : undefined,
|
|
performer_ids: this.performers.isSet ? this.performers.value : undefined,
|
|
tag_ids: this.tags.isSet ? this.tags.value : undefined,
|
|
};
|
|
}
|
|
}
|
|
|
|
interface ISceneParserFieldProps<T> {
|
|
parserResult: ParserResult<T>;
|
|
className?: string;
|
|
onSetChanged: (isSet: boolean) => void;
|
|
onValueChanged: (value: T) => void;
|
|
originalParserResult?: ParserResult<T>;
|
|
}
|
|
|
|
function SceneParserStringField(props: ISceneParserFieldProps<string>) {
|
|
function maybeValueChanged(value: string) {
|
|
if (value !== props.parserResult.value) {
|
|
props.onValueChanged(value);
|
|
}
|
|
}
|
|
|
|
const result = props.originalParserResult || props.parserResult;
|
|
|
|
return (
|
|
<>
|
|
<td>
|
|
<Form.Check
|
|
checked={props.parserResult.isSet}
|
|
onChange={() => {
|
|
props.onSetChanged(!props.parserResult.isSet);
|
|
}}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<Form.Group>
|
|
<Form.Control
|
|
disabled
|
|
className={props.className}
|
|
defaultValue={result.originalValue || ""}
|
|
/>
|
|
<Form.Control
|
|
readOnly={!props.parserResult.isSet}
|
|
className={props.className}
|
|
value={props.parserResult.value || ""}
|
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
maybeValueChanged(event.currentTarget.value)
|
|
}
|
|
/>
|
|
</Form.Group>
|
|
</td>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function SceneParserRatingField(
|
|
props: ISceneParserFieldProps<number | undefined>
|
|
) {
|
|
function maybeValueChanged(value?: number) {
|
|
if (value !== props.parserResult.value) {
|
|
props.onValueChanged(value);
|
|
}
|
|
}
|
|
|
|
const result = props.originalParserResult || props.parserResult;
|
|
const options = ["", 1, 2, 3, 4, 5];
|
|
|
|
return (
|
|
<>
|
|
<td>
|
|
<Form.Check
|
|
checked={props.parserResult.isSet}
|
|
onChange={() => {
|
|
props.onSetChanged(!props.parserResult.isSet);
|
|
}}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<Form.Group>
|
|
<Form.Control
|
|
disabled
|
|
className={cx("input-control text-input", props.className)}
|
|
defaultValue={result.originalValue || ""}
|
|
/>
|
|
<Form.Control
|
|
as="select"
|
|
className={cx("input-control", props.className)}
|
|
disabled={!props.parserResult.isSet}
|
|
value={props.parserResult.value?.toString()}
|
|
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
|
maybeValueChanged(
|
|
event.currentTarget.value === ""
|
|
? undefined
|
|
: Number.parseInt(event.currentTarget.value, 10)
|
|
)
|
|
}
|
|
>
|
|
{options.map((opt) => (
|
|
<option value={opt} key={opt}>
|
|
{opt}
|
|
</option>
|
|
))}
|
|
</Form.Control>
|
|
</Form.Group>
|
|
</td>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function SceneParserPerformerField(props: ISceneParserFieldProps<string[]>) {
|
|
function maybeValueChanged(value: string[]) {
|
|
if (value !== props.parserResult.value) {
|
|
props.onValueChanged(value);
|
|
}
|
|
}
|
|
|
|
const originalPerformers = (props.originalParserResult?.originalValue ??
|
|
[]) as string[];
|
|
const newPerformers = props.parserResult.value ?? [];
|
|
|
|
return (
|
|
<>
|
|
<td>
|
|
<Form.Check
|
|
checked={props.parserResult.isSet}
|
|
onChange={() => {
|
|
props.onSetChanged(!props.parserResult.isSet);
|
|
}}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<Form.Group className={props.className}>
|
|
<PerformerSelect
|
|
isDisabled
|
|
isMulti
|
|
ids={originalPerformers}
|
|
className="parser-field-performers-select"
|
|
/>
|
|
<PerformerSelect
|
|
className="parser-field-performers-select"
|
|
isMulti
|
|
isDisabled={!props.parserResult.isSet}
|
|
onSelect={(items) => {
|
|
maybeValueChanged(items.map((i) => i.id));
|
|
}}
|
|
ids={newPerformers}
|
|
/>
|
|
</Form.Group>
|
|
</td>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function SceneParserTagField(props: ISceneParserFieldProps<string[]>) {
|
|
function maybeValueChanged(value: string[]) {
|
|
if (value !== props.parserResult.value) {
|
|
props.onValueChanged(value);
|
|
}
|
|
}
|
|
|
|
const originalTags = props.originalParserResult?.originalValue ?? [];
|
|
const newTags = props.parserResult.value ?? [];
|
|
|
|
return (
|
|
<>
|
|
<td>
|
|
<Form.Check
|
|
checked={props.parserResult.isSet}
|
|
onChange={() => {
|
|
props.onSetChanged(!props.parserResult.isSet);
|
|
}}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<Form.Group className={props.className}>
|
|
<TagSelect
|
|
isDisabled
|
|
isMulti
|
|
ids={originalTags}
|
|
className="parser-field-tags-select"
|
|
/>
|
|
<TagSelect
|
|
className="parser-field-tags-select"
|
|
isMulti
|
|
isDisabled={!props.parserResult.isSet}
|
|
onSelect={(items) => {
|
|
maybeValueChanged(items.map((i) => i.id));
|
|
}}
|
|
ids={newTags}
|
|
/>
|
|
</Form.Group>
|
|
</td>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function SceneParserStudioField(props: ISceneParserFieldProps<string>) {
|
|
function maybeValueChanged(value: string) {
|
|
if (value !== props.parserResult.value) {
|
|
props.onValueChanged(value);
|
|
}
|
|
}
|
|
|
|
const originalStudio = props.originalParserResult?.originalValue
|
|
? [props.originalParserResult?.originalValue]
|
|
: [];
|
|
const newStudio = props.parserResult.value ? [props.parserResult.value] : [];
|
|
|
|
return (
|
|
<>
|
|
<td>
|
|
<Form.Check
|
|
checked={props.parserResult.isSet}
|
|
onChange={() => {
|
|
props.onSetChanged(!props.parserResult.isSet);
|
|
}}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<Form.Group className={props.className}>
|
|
<StudioSelect
|
|
isDisabled
|
|
ids={originalStudio}
|
|
className="parser-field-studio-select"
|
|
/>
|
|
<StudioSelect
|
|
className="parser-field-studio-select"
|
|
isDisabled={!props.parserResult.isSet}
|
|
onSelect={(items) => {
|
|
maybeValueChanged(items[0].id);
|
|
}}
|
|
ids={newStudio}
|
|
/>
|
|
</Form.Group>
|
|
</td>
|
|
</>
|
|
);
|
|
}
|
|
|
|
interface ISceneParserRowProps {
|
|
scene: SceneParserResult;
|
|
onChange: (changedScene: SceneParserResult) => void;
|
|
showFields: Map<string, boolean>;
|
|
}
|
|
|
|
export const SceneParserRow = (props: ISceneParserRowProps) => {
|
|
function changeParser<T>(result: ParserResult<T>, isSet: boolean, value?: T) {
|
|
const newParser = clone(result);
|
|
newParser.isSet = isSet;
|
|
newParser.value = value;
|
|
return newParser;
|
|
}
|
|
|
|
function onTitleChanged(set: boolean, value: string) {
|
|
const newResult = clone(props.scene);
|
|
newResult.title = changeParser(newResult.title, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onDateChanged(set: boolean, value: string) {
|
|
const newResult = clone(props.scene);
|
|
newResult.date = changeParser(newResult.date, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onRatingChanged(set: boolean, value?: number) {
|
|
const newResult = clone(props.scene);
|
|
newResult.rating = changeParser(newResult.rating, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onPerformerIdsChanged(set: boolean, value: string[]) {
|
|
const newResult = clone(props.scene);
|
|
newResult.performers = changeParser(newResult.performers, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onTagIdsChanged(set: boolean, value: string[]) {
|
|
const newResult = clone(props.scene);
|
|
newResult.tags = changeParser(newResult.tags, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onStudioIdChanged(set: boolean, value: string) {
|
|
const newResult = clone(props.scene);
|
|
newResult.studio = changeParser(newResult.studio, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
return (
|
|
<tr className="scene-parser-row">
|
|
<td className="text-left parser-field-filename">
|
|
{props.scene.filename}
|
|
</td>
|
|
{props.showFields.get("Title") && (
|
|
<SceneParserStringField
|
|
key="title"
|
|
className="parser-field-title input-control text-input"
|
|
parserResult={props.scene.title}
|
|
onSetChanged={(isSet) =>
|
|
onTitleChanged(isSet, props.scene.title.value ?? "")
|
|
}
|
|
onValueChanged={(value) =>
|
|
onTitleChanged(props.scene.title.isSet, value)
|
|
}
|
|
/>
|
|
)}
|
|
{props.showFields.get("Date") && (
|
|
<SceneParserStringField
|
|
key="date"
|
|
className="parser-field-date input-control text-input"
|
|
parserResult={props.scene.date}
|
|
onSetChanged={(isSet) =>
|
|
onDateChanged(isSet, props.scene.date.value ?? "")
|
|
}
|
|
onValueChanged={(value) =>
|
|
onDateChanged(props.scene.date.isSet, value)
|
|
}
|
|
/>
|
|
)}
|
|
{props.showFields.get("Rating") && (
|
|
<SceneParserRatingField
|
|
key="rating"
|
|
className="parser-field-rating"
|
|
parserResult={props.scene.rating}
|
|
onSetChanged={(isSet) =>
|
|
onRatingChanged(isSet, props.scene.rating.value ?? undefined)
|
|
}
|
|
onValueChanged={(value) =>
|
|
onRatingChanged(props.scene.rating.isSet, value)
|
|
}
|
|
/>
|
|
)}
|
|
{props.showFields.get("Performers") && (
|
|
<SceneParserPerformerField
|
|
key="performers"
|
|
className="parser-field-performers"
|
|
parserResult={props.scene.performers}
|
|
originalParserResult={props.scene.performers}
|
|
onSetChanged={(set) =>
|
|
onPerformerIdsChanged(set, props.scene.performers.value ?? [])
|
|
}
|
|
onValueChanged={(value) =>
|
|
onPerformerIdsChanged(props.scene.performers.isSet, value)
|
|
}
|
|
/>
|
|
)}
|
|
{props.showFields.get("Tags") && (
|
|
<SceneParserTagField
|
|
key="tags"
|
|
className="parser-field-tags"
|
|
parserResult={props.scene.tags}
|
|
originalParserResult={props.scene.tags}
|
|
onSetChanged={(isSet) =>
|
|
onTagIdsChanged(isSet, props.scene.tags.value ?? [])
|
|
}
|
|
onValueChanged={(value) =>
|
|
onTagIdsChanged(props.scene.tags.isSet, value)
|
|
}
|
|
/>
|
|
)}
|
|
{props.showFields.get("Studio") && (
|
|
<SceneParserStudioField
|
|
key="studio"
|
|
className="parser-field-studio"
|
|
parserResult={props.scene.studio}
|
|
originalParserResult={props.scene.studio}
|
|
onSetChanged={(set) =>
|
|
onStudioIdChanged(set, props.scene.studio.value ?? "")
|
|
}
|
|
onValueChanged={(value) =>
|
|
onStudioIdChanged(props.scene.studio.isSet, value)
|
|
}
|
|
/>
|
|
)}
|
|
</tr>
|
|
);
|
|
};
|