mirror of https://github.com/stashapp/stash.git
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:
parent
18dc5e85fa
commit
b3e8d1e8dd
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#recipe-select::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.scene-parser-results {
|
||||
margin-left: 31ch;
|
||||
overflow-x: auto;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)}
|
||||
|
|
Loading…
Reference in New Issue