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
9 changed files with 272 additions and 53 deletions

1
.gitattributes vendored
View File

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

View File

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

View File

@@ -131,6 +131,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
</Form.Label> </Form.Label>
<InputGroup className="col-8"> <InputGroup className="col-8">
<Form.Control <Form.Control
className="text-input"
id="filename-pattern" id="filename-pattern"
onChange={(e: React.FormEvent<HTMLInputElement>) => onChange={(e: React.FormEvent<HTMLInputElement>) =>
setPattern(e.currentTarget.value) setPattern(e.currentTarget.value)
@@ -144,8 +145,8 @@ export const ParserInput: React.FC<IParserInputProps> = (
key={item.field} key={item.field}
onSelect={() => addParserField(item)} onSelect={() => addParserField(item)}
> >
<span>{item.field}</span> <span>{item.field || "{}"}</span>
<span className="ml-auto">{item.helperText}</span> <span className="ml-auto text-muted">{item.helperText}</span>
</Dropdown.Item> </Dropdown.Item>
))} ))}
</DropdownButton> </DropdownButton>
@@ -160,6 +161,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
<Form.Label className="col-2">Ignored words</Form.Label> <Form.Label className="col-2">Ignored words</Form.Label>
<InputGroup className="col-8"> <InputGroup className="col-8">
<Form.Control <Form.Control
className="text-input"
onChange={(e: React.FormEvent<HTMLInputElement>) => onChange={(e: React.FormEvent<HTMLInputElement>) =>
setIgnoreWords(e.currentTarget.value) setIgnoreWords(e.currentTarget.value)
} }
@@ -178,6 +180,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
</Form.Label> </Form.Label>
<InputGroup className="col-8"> <InputGroup className="col-8">
<Form.Control <Form.Control
className="text-input"
onChange={(e: React.FormEvent<HTMLInputElement>) => onChange={(e: React.FormEvent<HTMLInputElement>) =>
setWhitespaceCharacters(e.currentTarget.value) setWhitespaceCharacters(e.currentTarget.value)
} }
@@ -206,6 +209,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
variant="secondary" variant="secondary"
id="recipe-select" id="recipe-select"
title="Select Parser Recipe" title="Select Parser Recipe"
drop="up"
> >
{builtInRecipes.map((item) => ( {builtInRecipes.map((item) => (
<Dropdown.Item <Dropdown.Item
@@ -213,7 +217,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
onSelect={() => setParserRecipe(item)} onSelect={() => setParserRecipe(item)}
> >
<span>{item.pattern}</span> <span>{item.pattern}</span>
<span className="mr-auto">{item.description}</span> <span className="ml-auto text-muted">{item.description}</span>
</Dropdown.Item> </Dropdown.Item>
))} ))}
</DropdownButton> </DropdownButton>
@@ -237,7 +241,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
props.onPageSizeChanged(parseInt(e.currentTarget.value, 10)) props.onPageSizeChanged(parseInt(e.currentTarget.value, 10))
} }
defaultValue={props.input.pageSize} defaultValue={props.input.pageSize}
className="col-1 filter-item" className="col-1 input-control filter-item"
> >
{PAGE_SIZE_OPTIONS.map((val) => ( {PAGE_SIZE_OPTIONS.map((val) => (
<option key={val} value={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 */ /* 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 { Button, Card, Form, Table } from "react-bootstrap";
import _ from "lodash"; import _ from "lodash";
import { StashService } from "src/core/StashService"; import { StashService } from "src/core/StashService";
@@ -25,6 +25,7 @@ const initialParserInput = {
const initialShowFieldsState = new Map<string, boolean>([ const initialShowFieldsState = new Map<string, boolean>([
["Title", true], ["Title", true],
["Date", true], ["Date", true],
["Rating", true],
["Performers", true], ["Performers", true],
["Tags", true], ["Tags", true],
["Studio", true], ["Studio", true],
@@ -36,9 +37,12 @@ export const SceneFilenameParser: React.FC = () => {
const [parserInput, setParserInput] = useState<IParserInput>( const [parserInput, setParserInput] = useState<IParserInput>(
initialParserInput initialParserInput
); );
const prevParserInputRef = useRef<IParserInput>();
const prevParserInput = prevParserInputRef.current;
const [allTitleSet, setAllTitleSet] = useState<boolean>(false); const [allTitleSet, setAllTitleSet] = useState<boolean>(false);
const [allDateSet, setAllDateSet] = useState<boolean>(false); const [allDateSet, setAllDateSet] = useState<boolean>(false);
const [allRatingSet, setAllRatingSet] = useState<boolean>(false);
const [allPerformerSet, setAllPerformerSet] = useState<boolean>(false); const [allPerformerSet, setAllPerformerSet] = useState<boolean>(false);
const [allTagSet, setAllTagSet] = useState<boolean>(false); const [allTagSet, setAllTagSet] = useState<boolean>(false);
const [allStudioSet, setAllStudioSet] = useState<boolean>(false); const [allStudioSet, setAllStudioSet] = useState<boolean>(false);
@@ -54,6 +58,10 @@ export const SceneFilenameParser: React.FC = () => {
const [updateScenes] = StashService.useScenesUpdate(getScenesUpdateData()); const [updateScenes] = StashService.useScenesUpdate(getScenesUpdateData());
useEffect(() => {
prevParserInputRef.current = parserInput;
}, [parserInput]);
const determineFieldsToHide = useCallback(() => { const determineFieldsToHide = useCallback(() => {
const { pattern } = parserInput; const { pattern } = parserInput;
const titleSet = pattern.includes("{title}"); const titleSet = pattern.includes("{title}");
@@ -63,6 +71,7 @@ export const SceneFilenameParser: React.FC = () => {
ParserField.fullDateFields.some((f) => { ParserField.fullDateFields.some((f) => {
return pattern.includes(`{${f.field}}`); return pattern.includes(`{${f.field}}`);
}); });
const ratingSet = pattern.includes("{rating}");
const performerSet = pattern.includes("{performer}"); const performerSet = pattern.includes("{performer}");
const tagSet = pattern.includes("{tag}"); const tagSet = pattern.includes("{tag}");
const studioSet = pattern.includes("{studio}"); const studioSet = pattern.includes("{studio}");
@@ -70,6 +79,7 @@ export const SceneFilenameParser: React.FC = () => {
const newShowFields = new Map<string, boolean>([ const newShowFields = new Map<string, boolean>([
["Title", titleSet], ["Title", titleSet],
["Date", dateSet], ["Date", dateSet],
["Rating", ratingSet],
["Performers", performerSet], ["Performers", performerSet],
["Tags", tagSet], ["Tags", tagSet],
["Studio", studioSet], ["Studio", studioSet],
@@ -96,8 +106,7 @@ export const SceneFilenameParser: React.FC = () => {
[determineFieldsToHide] [determineFieldsToHide]
); );
useEffect(() => { const parseSceneFilenames = useCallback(() => {
if (parserInput.findClicked) {
setParserResult([]); setParserResult([]);
setIsLoading(true); setIsLoading(true);
@@ -125,9 +134,19 @@ export const SceneFilenameParser: React.FC = () => {
}) })
.catch((err) => Toast.error(err)) .catch((err) => Toast.error(err))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}
}, [parserInput, parseResults, Toast]); }, [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) { function onPageSizeChanged(newSize: number) {
const newInput = _.clone(parserInput); const newInput = _.clone(parserInput);
newInput.page = 1; newInput.page = 1;
@@ -144,9 +163,10 @@ export const SceneFilenameParser: React.FC = () => {
} }
function onFindClicked(input: IParserInput) { function onFindClicked(input: IParserInput) {
input.page = 1; const newInput = _.clone(input);
input.findClicked = true; newInput.page = 1;
setParserInput(input); newInput.findClicked = true;
setParserInput(newInput);
setTotalItems(0); setTotalItems(0);
} }
@@ -167,6 +187,9 @@ export const SceneFilenameParser: React.FC = () => {
} }
setIsLoading(false); setIsLoading(false);
// trigger a refresh of the results
onFindClicked(parserInput);
} }
useEffect(() => { useEffect(() => {
@@ -176,6 +199,9 @@ export const SceneFilenameParser: React.FC = () => {
const newAllDateSet = !parserResult.some((r) => { const newAllDateSet = !parserResult.some((r) => {
return !r.date.isSet; return !r.date.isSet;
}); });
const newAllRatingSet = !parserResult.some((r) => {
return !r.rating.isSet;
});
const newAllPerformerSet = !parserResult.some((r) => { const newAllPerformerSet = !parserResult.some((r) => {
return !r.performers.isSet; return !r.performers.isSet;
}); });
@@ -188,6 +214,7 @@ export const SceneFilenameParser: React.FC = () => {
setAllTitleSet(newAllTitleSet); setAllTitleSet(newAllTitleSet);
setAllDateSet(newAllDateSet); setAllDateSet(newAllDateSet);
setAllRatingSet(newAllRatingSet);
setAllTagSet(newAllPerformerSet); setAllTagSet(newAllPerformerSet);
setAllTagSet(newAllTagSet); setAllTagSet(newAllTagSet);
setAllStudioSet(newAllStudioSet); setAllStudioSet(newAllStudioSet);
@@ -215,6 +242,17 @@ export const SceneFilenameParser: React.FC = () => {
setAllDateSet(selected); setAllDateSet(selected);
} }
function onSelectAllRatingSet(selected: boolean) {
const newResult = [...parserResult];
newResult.forEach((r) => {
r.rating.isSet = selected;
});
setParserResult(newResult);
setAllRatingSet(selected);
}
function onSelectAllPerformerSet(selected: boolean) { function onSelectAllPerformerSet(selected: boolean) {
const newResult = [...parserResult]; const newResult = [...parserResult];
@@ -295,6 +333,7 @@ export const SceneFilenameParser: React.FC = () => {
<th className="parser-field-filename">Filename</th> <th className="parser-field-filename">Filename</th>
{renderHeader("Title", allTitleSet, onSelectAllTitleSet)} {renderHeader("Title", allTitleSet, onSelectAllTitleSet)}
{renderHeader("Date", allDateSet, onSelectAllDateSet)} {renderHeader("Date", allDateSet, onSelectAllDateSet)}
{renderHeader("Rating", allRatingSet, onSelectAllRatingSet)}
{renderHeader( {renderHeader(
"Performers", "Performers",
allPerformerSet, allPerformerSet,

View File

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

View File

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

View File

@@ -55,7 +55,8 @@ code,
.text-input, .text-input,
.text-input:focus, .text-input:focus,
.text-input[readonly] { .text-input[readonly],
.text-input:disabled {
background-color: $textfield-bg; 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; background-color: $secondary;
color: $text-color; color: $text-color;
.react-select__option { .react-select__option,
.dropdown-item {
color: $text-color; color: $text-color;
} }
.react-select__option--is-focused { .react-select__option--is-focused,
.dropdown-item:focus,
.dropdown-item:hover {
background-color: #8a9ba826; background-color: #8a9ba826;
cursor: pointer; 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 */ /* we don't want to override this for dialogs, which are light colored */
.modal { .modal {
div.react-select__control { div.react-select__control {
@@ -374,8 +396,8 @@ div.react-select__menu {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
input[type="file"], /* FF, IE7+, chrome (except button) */ input[type=file], /* FF, IE7+, chrome (except button) */
input[type="file"]::-webkit-file-upload-button { input[type=file]::-webkit-file-upload-button {
/* chromes and blink button */ /* chromes and blink button */
cursor: pointer; cursor: pointer;
} }

View File

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