Add scene rating to scene filename parser (#432)

* Fix scene parser display issues in 2.5
* Dropdown menu presentation improvements
* Fix refresh on parser apply
* Ignore line endings on scss files
This commit is contained in:
WithoutPants 2020-04-05 07:59:57 +10:00 committed by GitHub
parent 18dc5e85fa
commit b3e8d1e8dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 272 additions and 53 deletions

1
.gitattributes vendored
View File

@ -1,3 +1,4 @@
go.mod text eol=lf
go.sum text eol=lf
ui/v2.5/**/*.ts* text eol=lf
ui/v2.5/**/*.scss text eol=lf

View File

@ -87,6 +87,7 @@ func initParserFields() {
//I = new ParserField("i", undefined, "Matches any ignored word", false);
ret["d"] = newParserField("d", `(?:\.|-|_)`, false)
ret["rating"] = newParserField("rating", `\d`, true)
ret["performer"] = newParserField("performer", ".*", true)
ret["studio"] = newParserField("studio", ".*", true)
ret["movie"] = newParserField("movie", ".*", true)
@ -224,6 +225,10 @@ func newSceneHolder(scene *models.Scene) *sceneHolder {
return &ret
}
func validateRating(rating int) bool {
return rating >= 1 && rating <= 5
}
func validateDate(dateStr string) bool {
splits := strings.Split(dateStr, "-")
if len(splits) != 3 {
@ -304,6 +309,14 @@ func (h *sceneHolder) setField(field parserField, value interface{}) {
Valid: true,
}
}
case "rating":
rating, _ := strconv.Atoi(value.(string))
if validateRating(rating) {
h.result.Rating = sql.NullInt64{
Int64: int64(rating),
Valid: true,
}
}
case "performer":
// add performer to list
h.performers = append(h.performers, value.(string))
@ -661,6 +674,11 @@ func (p *SceneFilenameParser) setParserResult(h sceneHolder, result *models.Scen
result.Date = &h.result.Date.String
}
if h.result.Rating.Valid {
rating := int(h.result.Rating.Int64)
result.Rating = &rating
}
if len(h.performers) > 0 {
p.setPerformers(h, result)
}

View File

@ -13,6 +13,7 @@ export class ParserField {
static Title = new ParserField("title");
static Ext = new ParserField("ext", "File extension");
static Rating = new ParserField("rating");
static I = new ParserField("i", "Matches any ignored word");
static D = new ParserField("d", "Matches any delimiter (.-_)");
@ -39,6 +40,7 @@ export class ParserField {
ParserField.Ext,
ParserField.D,
ParserField.I,
ParserField.Rating,
ParserField.Performer,
ParserField.Studio,
ParserField.Tag,

View File

@ -131,6 +131,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
</Form.Label>
<InputGroup className="col-8">
<Form.Control
className="text-input"
id="filename-pattern"
onChange={(e: React.FormEvent<HTMLInputElement>) =>
setPattern(e.currentTarget.value)
@ -144,8 +145,8 @@ export const ParserInput: React.FC<IParserInputProps> = (
key={item.field}
onSelect={() => addParserField(item)}
>
<span>{item.field}</span>
<span className="ml-auto">{item.helperText}</span>
<span>{item.field || "{}"}</span>
<span className="ml-auto text-muted">{item.helperText}</span>
</Dropdown.Item>
))}
</DropdownButton>
@ -160,6 +161,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
<Form.Label className="col-2">Ignored words</Form.Label>
<InputGroup className="col-8">
<Form.Control
className="text-input"
onChange={(e: React.FormEvent<HTMLInputElement>) =>
setIgnoreWords(e.currentTarget.value)
}
@ -178,6 +180,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
</Form.Label>
<InputGroup className="col-8">
<Form.Control
className="text-input"
onChange={(e: React.FormEvent<HTMLInputElement>) =>
setWhitespaceCharacters(e.currentTarget.value)
}
@ -206,6 +209,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
variant="secondary"
id="recipe-select"
title="Select Parser Recipe"
drop="up"
>
{builtInRecipes.map((item) => (
<Dropdown.Item
@ -213,7 +217,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
onSelect={() => setParserRecipe(item)}
>
<span>{item.pattern}</span>
<span className="mr-auto">{item.description}</span>
<span className="ml-auto text-muted">{item.description}</span>
</Dropdown.Item>
))}
</DropdownButton>
@ -237,7 +241,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
props.onPageSizeChanged(parseInt(e.currentTarget.value, 10))
}
defaultValue={props.input.pageSize}
className="col-1 filter-item"
className="col-1 input-control filter-item"
>
{PAGE_SIZE_OPTIONS.map((val) => (
<option key={val} value={val}>

View File

@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign, jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
import React, { useEffect, useState, useCallback } from "react";
import React, { useEffect, useState, useCallback, useRef } from "react";
import { Button, Card, Form, Table } from "react-bootstrap";
import _ from "lodash";
import { StashService } from "src/core/StashService";
@ -25,6 +25,7 @@ const initialParserInput = {
const initialShowFieldsState = new Map<string, boolean>([
["Title", true],
["Date", true],
["Rating", true],
["Performers", true],
["Tags", true],
["Studio", true],
@ -36,9 +37,12 @@ export const SceneFilenameParser: React.FC = () => {
const [parserInput, setParserInput] = useState<IParserInput>(
initialParserInput
);
const prevParserInputRef = useRef<IParserInput>();
const prevParserInput = prevParserInputRef.current;
const [allTitleSet, setAllTitleSet] = useState<boolean>(false);
const [allDateSet, setAllDateSet] = useState<boolean>(false);
const [allRatingSet, setAllRatingSet] = useState<boolean>(false);
const [allPerformerSet, setAllPerformerSet] = useState<boolean>(false);
const [allTagSet, setAllTagSet] = useState<boolean>(false);
const [allStudioSet, setAllStudioSet] = useState<boolean>(false);
@ -54,6 +58,10 @@ export const SceneFilenameParser: React.FC = () => {
const [updateScenes] = StashService.useScenesUpdate(getScenesUpdateData());
useEffect(() => {
prevParserInputRef.current = parserInput;
}, [parserInput]);
const determineFieldsToHide = useCallback(() => {
const { pattern } = parserInput;
const titleSet = pattern.includes("{title}");
@ -63,6 +71,7 @@ export const SceneFilenameParser: React.FC = () => {
ParserField.fullDateFields.some((f) => {
return pattern.includes(`{${f.field}}`);
});
const ratingSet = pattern.includes("{rating}");
const performerSet = pattern.includes("{performer}");
const tagSet = pattern.includes("{tag}");
const studioSet = pattern.includes("{studio}");
@ -70,6 +79,7 @@ export const SceneFilenameParser: React.FC = () => {
const newShowFields = new Map<string, boolean>([
["Title", titleSet],
["Date", dateSet],
["Rating", ratingSet],
["Performers", performerSet],
["Tags", tagSet],
["Studio", studioSet],
@ -96,38 +106,47 @@ export const SceneFilenameParser: React.FC = () => {
[determineFieldsToHide]
);
useEffect(() => {
if (parserInput.findClicked) {
setParserResult([]);
setIsLoading(true);
const parseSceneFilenames = useCallback(() => {
setParserResult([]);
setIsLoading(true);
const parserFilter = {
q: parserInput.pattern,
page: parserInput.page,
per_page: parserInput.pageSize,
sort: "path",
direction: GQL.SortDirectionEnum.Asc,
};
const parserFilter = {
q: parserInput.pattern,
page: parserInput.page,
per_page: parserInput.pageSize,
sort: "path",
direction: GQL.SortDirectionEnum.Asc,
};
const parserInputData = {
ignoreWords: parserInput.ignoreWords,
whitespaceCharacters: parserInput.whitespaceCharacters,
capitalizeTitle: parserInput.capitalizeTitle,
};
const parserInputData = {
ignoreWords: parserInput.ignoreWords,
whitespaceCharacters: parserInput.whitespaceCharacters,
capitalizeTitle: parserInput.capitalizeTitle,
};
StashService.queryParseSceneFilenames(parserFilter, parserInputData)
.then((response) => {
const result = response.data.parseSceneFilenames;
if (result) {
parseResults(result.results);
setTotalItems(result.count);
}
})
.catch((err) => Toast.error(err))
.finally(() => setIsLoading(false));
}
StashService.queryParseSceneFilenames(parserFilter, parserInputData)
.then((response) => {
const result = response.data.parseSceneFilenames;
if (result) {
parseResults(result.results);
setTotalItems(result.count);
}
})
.catch((err) => Toast.error(err))
.finally(() => setIsLoading(false));
}, [parserInput, parseResults, Toast]);
useEffect(() => {
// only refresh if parserInput actually changed
if (prevParserInput === parserInput) {
return;
}
if (parserInput.findClicked) {
parseSceneFilenames();
}
}, [parserInput, parseSceneFilenames, prevParserInput]);
function onPageSizeChanged(newSize: number) {
const newInput = _.clone(parserInput);
newInput.page = 1;
@ -144,9 +163,10 @@ export const SceneFilenameParser: React.FC = () => {
}
function onFindClicked(input: IParserInput) {
input.page = 1;
input.findClicked = true;
setParserInput(input);
const newInput = _.clone(input);
newInput.page = 1;
newInput.findClicked = true;
setParserInput(newInput);
setTotalItems(0);
}
@ -167,6 +187,9 @@ export const SceneFilenameParser: React.FC = () => {
}
setIsLoading(false);
// trigger a refresh of the results
onFindClicked(parserInput);
}
useEffect(() => {
@ -176,6 +199,9 @@ export const SceneFilenameParser: React.FC = () => {
const newAllDateSet = !parserResult.some((r) => {
return !r.date.isSet;
});
const newAllRatingSet = !parserResult.some((r) => {
return !r.rating.isSet;
});
const newAllPerformerSet = !parserResult.some((r) => {
return !r.performers.isSet;
});
@ -188,6 +214,7 @@ export const SceneFilenameParser: React.FC = () => {
setAllTitleSet(newAllTitleSet);
setAllDateSet(newAllDateSet);
setAllRatingSet(newAllRatingSet);
setAllTagSet(newAllPerformerSet);
setAllTagSet(newAllTagSet);
setAllStudioSet(newAllStudioSet);
@ -215,6 +242,17 @@ export const SceneFilenameParser: React.FC = () => {
setAllDateSet(selected);
}
function onSelectAllRatingSet(selected: boolean) {
const newResult = [...parserResult];
newResult.forEach((r) => {
r.rating.isSet = selected;
});
setParserResult(newResult);
setAllRatingSet(selected);
}
function onSelectAllPerformerSet(selected: boolean) {
const newResult = [...parserResult];
@ -295,6 +333,7 @@ export const SceneFilenameParser: React.FC = () => {
<th className="parser-field-filename">Filename</th>
{renderHeader("Title", allTitleSet, onSelectAllTitleSet)}
{renderHeader("Date", allDateSet, onSelectAllDateSet)}
{renderHeader("Rating", allRatingSet, onSelectAllRatingSet)}
{renderHeader(
"Performers",
allPerformerSet,

View File

@ -35,6 +35,7 @@ export class SceneParserResult {
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[]>();
@ -51,12 +52,14 @@ export class SceneParserResult {
this.filename = TextUtils.fileNameFromPath(this.scene.path);
this.title.setOriginalValue(this.scene.title ?? undefined);
this.date.setOriginalValue(this.scene.date ?? undefined);
this.rating.setOriginalValue(this.scene.rating ?? 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);
}
// returns true if any of its fields have set == true
@ -64,6 +67,7 @@ export class SceneParserResult {
return (
this.title.isSet ||
this.date.isSet ||
this.rating.isSet ||
this.performers.isSet ||
this.studio.isSet ||
this.tags.isSet
@ -75,7 +79,7 @@ export class SceneParserResult {
id: this.id,
details: this.scene.details,
url: this.scene.url,
rating: this.scene.rating,
rating: this.rating.isSet ? this.rating.value : this.scene.rating,
gallery_id: this.scene.gallery?.id,
title: this.title.isSet ? this.title.value : this.scene.title,
date: this.date.isSet ? this.date.value : this.scene.date,
@ -121,12 +125,12 @@ function SceneParserStringField(props: ISceneParserFieldProps<string>) {
<td>
<Form.Group>
<Form.Control
disabled
readOnly
className={props.className}
defaultValue={result.originalValue || ""}
/>
<Form.Control
disabled={!props.parserResult.isSet}
readOnly={!props.parserResult.isSet}
className={props.className}
value={props.parserResult.value || ""}
onChange={(event: React.FormEvent<HTMLInputElement>) =>
@ -139,6 +143,60 @@ function SceneParserStringField(props: ISceneParserFieldProps<string>) {
);
}
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
readOnly
className={props.className}
defaultValue={result.originalValue || ""}
/>
<Form.Control
as="select"
className={props.className}
disabled={!props.parserResult.isSet}
value={props.parserResult.value?.toString()}
onChange={(event: React.FormEvent<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) {
@ -165,6 +223,7 @@ function SceneParserPerformerField(props: ISceneParserFieldProps<string[]>) {
<PerformerSelect isDisabled isMulti ids={originalPerformers} />
<PerformerSelect
isMulti
isDisabled={!props.parserResult.isSet}
onSelect={(items) => {
maybeValueChanged(items.map((i) => i.id));
}}
@ -201,6 +260,7 @@ function SceneParserTagField(props: ISceneParserFieldProps<string[]>) {
<TagSelect isDisabled isMulti ids={originalTags} />
<TagSelect
isMulti
isDisabled={!props.parserResult.isSet}
onSelect={(items) => {
maybeValueChanged(items.map((i) => i.id));
}}
@ -238,6 +298,7 @@ function SceneParserStudioField(props: ISceneParserFieldProps<string>) {
<Form.Group className={props.className}>
<StudioSelect isDisabled ids={originalStudio} />
<StudioSelect
isDisabled={!props.parserResult.isSet}
onSelect={(items) => {
maybeValueChanged(items[0].id);
}}
@ -256,7 +317,7 @@ interface ISceneParserRowProps {
}
export const SceneParserRow = (props: ISceneParserRowProps) => {
function changeParser<T>(result: ParserResult<T>, isSet: boolean, value: T) {
function changeParser<T>(result: ParserResult<T>, isSet: boolean, value?: T) {
const newParser = _.clone(result);
newParser.isSet = isSet;
newParser.value = value;
@ -275,6 +336,12 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
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);
@ -302,7 +369,7 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
<SceneParserStringField
key="title"
fieldName="Title"
className="parser-field-title"
className="parser-field-title input-control text-input"
parserResult={props.scene.title}
onSetChanged={(isSet) =>
onTitleChanged(isSet, props.scene.title.value ?? "")
@ -316,7 +383,7 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
<SceneParserStringField
key="date"
fieldName="Date"
className="parser-field-date"
className="parser-field-date input-control text-input"
parserResult={props.scene.date}
onSetChanged={(isSet) =>
onDateChanged(isSet, props.scene.date.value ?? "")
@ -326,11 +393,25 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
}
/>
)}
{props.showFields.get("Rating") && (
<SceneParserRatingField
key="rating"
fieldName="Rating"
className="parser-field-rating input-control text-input"
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"
fieldName="Performers"
className="parser-field-performers"
className="parser-field-performers input-control text-input"
parserResult={props.scene.performers}
originalParserResult={props.scene.performers}
onSetChanged={(set) =>
@ -345,7 +426,7 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
<SceneParserTagField
key="tags"
fieldName="Tags"
className="parser-field-tags"
className="parser-field-tags input-control text-input"
parserResult={props.scene.tags}
originalParserResult={props.scene.tags}
onSetChanged={(isSet) =>
@ -360,7 +441,7 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
<SceneParserStudioField
key="studio"
fieldName="Studio"
className="parser-field-studio"
className="parser-field-studio input-control text-input"
parserResult={props.scene.studio}
originalParserResult={props.scene.studio}
onSetChanged={(set) =>

View File

@ -1,3 +1,7 @@
#recipe-select::after {
content: none;
}
.scene-parser-results {
margin-left: 31ch;
overflow-x: auto;

View File

@ -55,7 +55,8 @@ code,
.text-input,
.text-input:focus,
.text-input[readonly] {
.text-input[readonly],
.text-input:disabled {
background-color: $textfield-bg;
}
@ -171,20 +172,41 @@ div.react-select__control {
}
}
div.react-select__menu {
div.react-select__menu,
div.dropdown-menu {
background-color: $secondary;
color: $text-color;
.react-select__option {
.react-select__option,
.dropdown-item {
color: $text-color;
}
.react-select__option--is-focused {
.react-select__option--is-focused,
.dropdown-item:focus,
.dropdown-item:hover {
background-color: #8a9ba826;
cursor: pointer;
}
}
div.dropdown-menu {
max-height: 300px;
overflow-y: auto;
.dropdown-item {
display: flex;
& > * {
margin-right: 7px;
}
& > :last-child {
margin-right: 0;
}
}
}
/* we don't want to override this for dialogs, which are light colored */
.modal {
div.react-select__control {
@ -374,8 +396,8 @@ div.react-select__menu {
overflow: hidden;
position: relative;
input[type="file"], /* FF, IE7+, chrome (except button) */
input[type="file"]::-webkit-file-upload-button {
input[type=file], /* FF, IE7+, chrome (except button) */
input[type=file]::-webkit-file-upload-button {
/* chromes and blink button */
cursor: pointer;
}

View File

@ -70,6 +70,7 @@ class ParserField {
static Performer = new ParserField("performer");
static Studio = new ParserField("studio");
static Tag = new ParserField("tag");
static Rating = new ParserField("rating");
// date fields
static Date = new ParserField("date", "YYYY-MM-DD");
@ -89,6 +90,7 @@ class ParserField {
ParserField.Ext,
ParserField.D,
ParserField.I,
ParserField.Rating,
ParserField.Performer,
ParserField.Studio,
ParserField.Tag,
@ -119,6 +121,7 @@ class SceneParserResult {
public filename: string;
public title: ParserResult<string> = new ParserResult();
public date: ParserResult<string> = new ParserResult();
public rating: ParserResult<number> = new ParserResult();
public studio: ParserResult<GQL.SlimSceneDataStudio> = new ParserResult();
public studioId: ParserResult<string> = new ParserResult();
@ -133,6 +136,7 @@ class SceneParserResult {
this.scene = result.scene;
this.id = this.scene.id;
this.rating.setOriginalValue(this.scene.rating);
this.filename = TextUtils.fileNameFromPath(this.scene.path);
this.title.setOriginalValue(this.scene.title);
this.date.setOriginalValue(this.scene.date);
@ -144,6 +148,7 @@ class SceneParserResult {
this.studio.setOriginalValue(this.scene.studio);
this.title.setValue(result.title);
this.rating.setValue(result.rating);
this.date.setValue(result.date);
this.performerIds.setValue(result.performer_ids);
this.tagIds.setValue(result.tag_ids);
@ -186,7 +191,7 @@ class SceneParserResult {
// returns true if any of its fields have set == true
public isChanged() {
return this.title.set || this.date.set || this.performerIds.set || this.studioId.set || this.tagIds.set;
return this.title.set || this.date.set || this.rating.set || this.performerIds.set || this.studioId.set || this.tagIds.set;
}
public toSceneUpdateInput() {
@ -205,6 +210,7 @@ class SceneParserResult {
SceneParserResult.setInput(ret, "title", this.title);
SceneParserResult.setInput(ret, "date", this.date);
SceneParserResult.setInput(ret, "rating", this.rating);
SceneParserResult.setInput(ret, "performer_ids", this.performerIds);
SceneParserResult.setInput(ret, "studio_id", this.studioId);
SceneParserResult.setInput(ret, "tag_ids", this.tagIds);
@ -282,6 +288,7 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
const [allTitleSet, setAllTitleSet] = useState<boolean>(false);
const [allDateSet, setAllDateSet] = useState<boolean>(false);
const [allRatingSet, setAllRatingSet] = useState<boolean>(false);
const [allPerformerSet, setAllPerformerSet] = useState<boolean>(false);
const [allTagSet, setAllTagSet] = useState<boolean>(false);
const [allStudioSet, setAllStudioSet] = useState<boolean>(false);
@ -311,6 +318,7 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
return new Map<string, boolean>([
["Title", true],
["Date", true],
["Rating", true],
["Performers", true],
["Tags", true],
["Studio", true]
@ -419,6 +427,7 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
ParserField.fullDateFields.some((f) => {
return pattern.includes("{" + f.field + "}");
});
var ratingSet = pattern.includes("{rating}");
var performerSet = pattern.includes("{performer}");
var tagSet = pattern.includes("{tag}");
var studioSet = pattern.includes("{studio}");
@ -426,6 +435,7 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
var showFieldsCopy = _.clone(showFields);
showFieldsCopy.set("Title", titleSet);
showFieldsCopy.set("Date", dateSet);
showFieldsCopy.set("Rating", ratingSet);
showFieldsCopy.set("Performers", performerSet);
showFieldsCopy.set("Tags", tagSet);
showFieldsCopy.set("Studio", studioSet);
@ -439,6 +449,9 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
var newAllDateSet = !parserResult.some((r) => {
return !r.date.set;
});
var newAllRatingSet = !parserResult.some((r) => {
return !r.rating.set;
});
var newAllPerformerSet = !parserResult.some((r) => {
return !r.performerIds.set;
});
@ -455,6 +468,9 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
if (newAllDateSet !== allDateSet) {
setAllDateSet(newAllDateSet);
}
if (newAllRatingSet !== allRatingSet) {
setAllRatingSet(newAllRatingSet);
}
if (newAllPerformerSet !== allPerformerSet) {
setAllTagSet(newAllPerformerSet);
}
@ -488,6 +504,17 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
setAllDateSet(selected);
}
function onSelectAllRatingSet(selected : boolean) {
var newResult = [...parserResult];
newResult.forEach((r) => {
r.rating.set = selected;
});
setParserResult(newResult);
setAllRatingSet(selected);
}
function onSelectAllPerformerSet(selected : boolean) {
var newResult = [...parserResult];
@ -545,14 +572,18 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
},
{
id: 3,
label: "Performers",
label: "Rating",
},
{
id: 4,
label: "Tags",
label: "Performers",
},
{
id: 5,
label: "Tags",
},
{
id: 6,
label: "Studio",
}
]
@ -960,6 +991,12 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
props.onChange(newResult);
}
function onRatingChanged(set : boolean, value: number | undefined) {
var newResult = _.clone(props.scene);
newResult.rating = changeParser(newResult.rating, set, value);
props.onChange(newResult);
}
function onPerformerIdsChanged(set : boolean, value: string[] | undefined) {
var newResult = _.clone(props.scene);
newResult.performerIds = changeParser(newResult.performerIds, set, value);
@ -1004,6 +1041,16 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
renderOriginalInputField={renderOriginalInputGroup}
renderNewInputField={renderNewInputGroup}
/>
<SceneParserField
key="rating"
fieldName="Rating"
className="parser-field-rating"
parserResult={props.scene.rating}
onSetChanged={(set) => onRatingChanged(set, props.scene.rating.value)}
onValueChanged={(value) => onRatingChanged(props.scene.rating.set, value)}
renderOriginalInputField={renderOriginalInputGroup}
renderNewInputField={renderNewInputGroup}
/>
<SceneParserField
key="performers"
fieldName="Performers"
@ -1083,6 +1130,7 @@ export const SceneFilenameParser: FunctionComponent<IProps> = (props: IProps) =>
<th>Filename</th>
{renderHeader("Title", allTitleSet, onSelectAllTitleSet)}
{renderHeader("Date", allDateSet, onSelectAllDateSet)}
{renderHeader("Rating", allRatingSet, onSelectAllRatingSet)}
{renderHeader("Performers", allPerformerSet, onSelectAllPerformerSet)}
{renderHeader("Tags", allTagSet, onSelectAllTagSet)}
{renderHeader("Studio", allStudioSet, onSelectAllStudioSet)}