mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
771 lines
21 KiB
TypeScript
771 lines
21 KiB
TypeScript
/* 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 { Badge, Button, Card, Form, Table } from "react-bootstrap";
|
|
import _ from "lodash";
|
|
import { StashService } from "src/core/StashService";
|
|
import * as GQL from "src/core/generated-graphql";
|
|
import {
|
|
FilterSelect,
|
|
StudioSelect,
|
|
LoadingIndicator
|
|
} from "src/components/Shared";
|
|
import { TextUtils } from "src/utils";
|
|
import { useToast } from "src/hooks";
|
|
import { Pagination } from "../list/Pagination";
|
|
import { IParserInput, ParserInput } from "./ParserInput";
|
|
import { ParserField } from "./ParserField";
|
|
|
|
class ParserResult<T> {
|
|
public value: GQL.Maybe<T> = null;
|
|
public originalValue: GQL.Maybe<T> = null;
|
|
public set: boolean = false;
|
|
|
|
public setOriginalValue(v: GQL.Maybe<T>) {
|
|
this.originalValue = v;
|
|
this.value = v;
|
|
}
|
|
|
|
public setValue(v: GQL.Maybe<T>) {
|
|
if (v) {
|
|
this.value = v;
|
|
this.set = !_.isEqual(this.value, this.originalValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
class SceneParserResult {
|
|
public id: string;
|
|
public filename: string;
|
|
public title: ParserResult<string> = new ParserResult();
|
|
public date: ParserResult<string> = new ParserResult();
|
|
|
|
public studio: ParserResult<Partial<GQL.Studio>> = new ParserResult();
|
|
public studioId: ParserResult<string> = new ParserResult();
|
|
public tags: ParserResult<GQL.Tag[]> = new ParserResult();
|
|
public tagIds: ParserResult<string[]> = new ParserResult();
|
|
public performers: ParserResult<
|
|
Partial<GQL.Performer>[]
|
|
> = new ParserResult();
|
|
public performerIds: ParserResult<string[]> = new ParserResult();
|
|
|
|
public scene: GQL.SlimSceneDataFragment;
|
|
|
|
constructor(
|
|
result: GQL.ParseSceneFilenamesQuery["parseSceneFilenames"]["results"][0]
|
|
) {
|
|
this.scene = result.scene;
|
|
|
|
this.id = this.scene.id;
|
|
this.filename = TextUtils.fileNameFromPath(this.scene.path);
|
|
this.title.setOriginalValue(this.scene.title ?? null);
|
|
this.date.setOriginalValue(this.scene.date ?? null);
|
|
this.performerIds.setOriginalValue(this.scene.performers.map(p => p.id));
|
|
this.performers.setOriginalValue(this.scene.performers);
|
|
this.tagIds.setOriginalValue(this.scene.tags.map(t => t.id));
|
|
this.tags.setOriginalValue(this.scene.tags);
|
|
this.studioId.setOriginalValue(this.scene.studio?.id ?? null);
|
|
this.studio.setOriginalValue(this.scene.studio ?? null);
|
|
|
|
this.title.setValue(result.title ?? null);
|
|
this.date.setValue(result.date ?? null);
|
|
this.performerIds.setValue(result.performer_ids ?? []);
|
|
this.tagIds.setValue(result.tag_ids ?? []);
|
|
this.studioId.setValue(result.studio_id ?? null);
|
|
|
|
if (result.performer_ids) {
|
|
this.performers.setValue(
|
|
(result.performer_ids ?? []).map(
|
|
p =>
|
|
({
|
|
id: p,
|
|
name: "",
|
|
favorite: false,
|
|
image_path: ""
|
|
} as GQL.Performer)
|
|
)
|
|
);
|
|
}
|
|
|
|
if (result.tag_ids) {
|
|
this.tags.setValue(
|
|
result.tag_ids.map(t => ({
|
|
id: t,
|
|
name: ""
|
|
}))
|
|
);
|
|
}
|
|
|
|
if (result.studio_id) {
|
|
this.studio.setValue({
|
|
id: result.studio_id,
|
|
name: "",
|
|
image_path: ""
|
|
} as GQL.Studio);
|
|
}
|
|
}
|
|
|
|
private static setInput(
|
|
obj: any,
|
|
key: string,
|
|
parserResult: ParserResult<any>
|
|
) {
|
|
if (parserResult.set) {
|
|
obj[key] = parserResult.value;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
);
|
|
}
|
|
|
|
public toSceneUpdateInput() {
|
|
const ret = {
|
|
id: this.id,
|
|
title: this.scene.title,
|
|
details: this.scene.details,
|
|
url: this.scene.url,
|
|
date: this.scene.date,
|
|
rating: this.scene.rating,
|
|
gallery_id: this.scene.gallery ? this.scene.gallery.id : undefined,
|
|
studio_id: this.scene.studio ? this.scene.studio.id : undefined,
|
|
performer_ids: this.scene.performers.map(performer => performer.id),
|
|
tag_ids: this.scene.tags.map(tag => tag.id)
|
|
};
|
|
|
|
SceneParserResult.setInput(ret, "title", this.title);
|
|
SceneParserResult.setInput(ret, "date", this.date);
|
|
SceneParserResult.setInput(ret, "performer_ids", this.performerIds);
|
|
SceneParserResult.setInput(ret, "studio_id", this.studioId);
|
|
SceneParserResult.setInput(ret, "tag_ids", this.tagIds);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
const initialParserInput = {
|
|
pattern: "{title}.{ext}",
|
|
ignoreWords: [],
|
|
whitespaceCharacters: "._",
|
|
capitalizeTitle: true,
|
|
page: 1,
|
|
pageSize: 20,
|
|
findClicked: false
|
|
};
|
|
|
|
const initialShowFieldsState = new Map<string, boolean>([
|
|
["Title", true],
|
|
["Date", true],
|
|
["Performers", true],
|
|
["Tags", true],
|
|
["Studio", true]
|
|
]);
|
|
|
|
export const SceneFilenameParser: React.FC = () => {
|
|
const Toast = useToast();
|
|
const [parserResult, setParserResult] = useState<SceneParserResult[]>([]);
|
|
const [parserInput, setParserInput] = useState<IParserInput>(
|
|
initialParserInput
|
|
);
|
|
|
|
const [allTitleSet, setAllTitleSet] = useState<boolean>(false);
|
|
const [allDateSet, setAllDateSet] = useState<boolean>(false);
|
|
const [allPerformerSet, setAllPerformerSet] = useState<boolean>(false);
|
|
const [allTagSet, setAllTagSet] = useState<boolean>(false);
|
|
const [allStudioSet, setAllStudioSet] = useState<boolean>(false);
|
|
|
|
const [showFields, setShowFields] = useState<Map<string, boolean>>(
|
|
initialShowFieldsState
|
|
);
|
|
|
|
const [totalItems, setTotalItems] = useState<number>(0);
|
|
|
|
// Network state
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const [updateScenes] = StashService.useScenesUpdate(getScenesUpdateData());
|
|
|
|
const determineFieldsToHide = useCallback(() => {
|
|
const { pattern } = parserInput;
|
|
const titleSet = pattern.includes("{title}");
|
|
const dateSet =
|
|
pattern.includes("{date}") ||
|
|
pattern.includes("{dd}") || // don't worry about other partial date fields since this should be implied
|
|
ParserField.fullDateFields.some(f => {
|
|
return pattern.includes(`{${f.field}}`);
|
|
});
|
|
const performerSet = pattern.includes("{performer}");
|
|
const tagSet = pattern.includes("{tag}");
|
|
const studioSet = pattern.includes("{studio}");
|
|
|
|
const newShowFields = new Map<string, boolean>([
|
|
["Title", titleSet],
|
|
["Date", dateSet],
|
|
["Performers", performerSet],
|
|
["Tags", tagSet],
|
|
["Studio", studioSet]
|
|
]);
|
|
|
|
setShowFields(newShowFields);
|
|
}, [parserInput]);
|
|
|
|
const parseResults = useCallback(
|
|
(
|
|
results: GQL.ParseSceneFilenamesQuery["parseSceneFilenames"]["results"]
|
|
) => {
|
|
if (results) {
|
|
const result = results
|
|
.map(r => {
|
|
return new SceneParserResult(r);
|
|
})
|
|
.filter(r => !!r) as SceneParserResult[];
|
|
|
|
setParserResult(result);
|
|
determineFieldsToHide();
|
|
}
|
|
},
|
|
[determineFieldsToHide]
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (parserInput.findClicked) {
|
|
setParserResult([]);
|
|
setIsLoading(true);
|
|
|
|
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
|
|
};
|
|
|
|
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]);
|
|
|
|
function onPageSizeChanged(newSize: number) {
|
|
const newInput = _.clone(parserInput);
|
|
newInput.page = 1;
|
|
newInput.pageSize = newSize;
|
|
setParserInput(newInput);
|
|
}
|
|
|
|
function onPageChanged(newPage: number) {
|
|
if (newPage !== parserInput.page) {
|
|
const newInput = _.clone(parserInput);
|
|
newInput.page = newPage;
|
|
setParserInput(newInput);
|
|
}
|
|
}
|
|
|
|
function onFindClicked(input: IParserInput) {
|
|
input.page = 1;
|
|
input.findClicked = true;
|
|
setParserInput(input);
|
|
setTotalItems(0);
|
|
}
|
|
|
|
function getScenesUpdateData() {
|
|
return parserResult
|
|
.filter(result => result.isChanged())
|
|
.map(result => result.toSceneUpdateInput());
|
|
}
|
|
|
|
async function onApply() {
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
await updateScenes();
|
|
Toast.success({ content: "Updated scenes" });
|
|
} catch (e) {
|
|
Toast.error(e);
|
|
}
|
|
|
|
setIsLoading(false);
|
|
}
|
|
|
|
useEffect(() => {
|
|
const newAllTitleSet = !parserResult.some(r => {
|
|
return !r.title.set;
|
|
});
|
|
const newAllDateSet = !parserResult.some(r => {
|
|
return !r.date.set;
|
|
});
|
|
const newAllPerformerSet = !parserResult.some(r => {
|
|
return !r.performerIds.set;
|
|
});
|
|
const newAllTagSet = !parserResult.some(r => {
|
|
return !r.tagIds.set;
|
|
});
|
|
const newAllStudioSet = !parserResult.some(r => {
|
|
return !r.studioId.set;
|
|
});
|
|
|
|
setAllTitleSet(newAllTitleSet);
|
|
setAllDateSet(newAllDateSet);
|
|
setAllTagSet(newAllPerformerSet);
|
|
setAllTagSet(newAllTagSet);
|
|
setAllStudioSet(newAllStudioSet);
|
|
}, [parserResult]);
|
|
|
|
function onSelectAllTitleSet(selected: boolean) {
|
|
const newResult = [...parserResult];
|
|
|
|
newResult.forEach(r => {
|
|
r.title.set = selected;
|
|
});
|
|
|
|
setParserResult(newResult);
|
|
setAllTitleSet(selected);
|
|
}
|
|
|
|
function onSelectAllDateSet(selected: boolean) {
|
|
const newResult = [...parserResult];
|
|
|
|
newResult.forEach(r => {
|
|
r.date.set = selected;
|
|
});
|
|
|
|
setParserResult(newResult);
|
|
setAllDateSet(selected);
|
|
}
|
|
|
|
function onSelectAllPerformerSet(selected: boolean) {
|
|
const newResult = [...parserResult];
|
|
|
|
newResult.forEach(r => {
|
|
r.performerIds.set = selected;
|
|
});
|
|
|
|
setParserResult(newResult);
|
|
setAllPerformerSet(selected);
|
|
}
|
|
|
|
function onSelectAllTagSet(selected: boolean) {
|
|
const newResult = [...parserResult];
|
|
|
|
newResult.forEach(r => {
|
|
r.tagIds.set = selected;
|
|
});
|
|
|
|
setParserResult(newResult);
|
|
setAllTagSet(selected);
|
|
}
|
|
|
|
function onSelectAllStudioSet(selected: boolean) {
|
|
const newResult = [...parserResult];
|
|
|
|
newResult.forEach(r => {
|
|
r.studioId.set = selected;
|
|
});
|
|
|
|
setParserResult(newResult);
|
|
setAllStudioSet(selected);
|
|
}
|
|
|
|
interface ISceneParserFieldProps {
|
|
parserResult: ParserResult<any>;
|
|
className?: string;
|
|
fieldName: string;
|
|
onSetChanged: (set: boolean) => void;
|
|
onValueChanged: (value: any) => void;
|
|
originalParserResult?: ParserResult<any>;
|
|
renderOriginalInputField: (props: ISceneParserFieldProps) => JSX.Element;
|
|
renderNewInputField: (
|
|
props: ISceneParserFieldProps,
|
|
onChange: (event: any) => void
|
|
) => JSX.Element;
|
|
}
|
|
|
|
function SceneParserField(props: ISceneParserFieldProps) {
|
|
function maybeValueChanged(value: any) {
|
|
if (value !== props.parserResult.value) {
|
|
props.onValueChanged(value);
|
|
}
|
|
}
|
|
|
|
if (!showFields.get(props.fieldName)) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<td>
|
|
<Form.Control
|
|
type="checkbox"
|
|
checked={props.parserResult.set}
|
|
onChange={() => {
|
|
props.onSetChanged(!props.parserResult.set);
|
|
}}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<Form.Group>
|
|
{props.renderOriginalInputField(props)}
|
|
{props.renderNewInputField(props, value =>
|
|
maybeValueChanged(value)
|
|
)}
|
|
</Form.Group>
|
|
</td>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function renderOriginalInputGroup(props: ISceneParserFieldProps) {
|
|
const result = props.originalParserResult || props.parserResult;
|
|
|
|
return (
|
|
<Form.Control
|
|
disabled
|
|
className={props.className}
|
|
defaultValue={result.originalValue || ""}
|
|
/>
|
|
);
|
|
}
|
|
|
|
interface IInputGroupWrapperProps {
|
|
parserResult: ParserResult<any>;
|
|
onChange: (event: any) => void;
|
|
className?: string;
|
|
}
|
|
|
|
function InputGroupWrapper(props: IInputGroupWrapperProps) {
|
|
return (
|
|
<Form.Control
|
|
disabled={!props.parserResult.set}
|
|
className={props.className}
|
|
value={props.parserResult.value || ""}
|
|
onBlur={(event: any) => props.onChange(event.target.value)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function renderNewInputGroup(
|
|
props: ISceneParserFieldProps,
|
|
onChangeHandler: (value: any) => void
|
|
) {
|
|
return (
|
|
<InputGroupWrapper
|
|
className={props.className}
|
|
onChange={(value: any) => {
|
|
onChangeHandler(value);
|
|
}}
|
|
parserResult={props.parserResult}
|
|
/>
|
|
);
|
|
}
|
|
|
|
interface IHasName {
|
|
name: string;
|
|
}
|
|
|
|
function renderOriginalSelect(props: ISceneParserFieldProps) {
|
|
const result = props.originalParserResult || props.parserResult;
|
|
|
|
const elements = result.originalValue
|
|
? Array.isArray(result.originalValue)
|
|
? result.originalValue.map((el: IHasName) => el.name)
|
|
: [result.originalValue.name]
|
|
: [];
|
|
|
|
return (
|
|
<div>
|
|
{elements.map((name: string) => (
|
|
<Badge variant="secondary">{name}</Badge>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function renderNewMultiSelect(
|
|
type: "performers" | "tags",
|
|
props: ISceneParserFieldProps,
|
|
onChangeHandler: (value: any) => void
|
|
) {
|
|
return (
|
|
<FilterSelect
|
|
className={props.className}
|
|
type={type}
|
|
isMulti
|
|
onSelect={items => {
|
|
const ids = items.map(i => i.id);
|
|
onChangeHandler(ids);
|
|
}}
|
|
initialIds={props.parserResult.value}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function renderNewPerformerSelect(
|
|
props: ISceneParserFieldProps,
|
|
onChangeHandler: (value: any) => void
|
|
) {
|
|
return renderNewMultiSelect("performers", props, onChangeHandler);
|
|
}
|
|
|
|
function renderNewTagSelect(
|
|
props: ISceneParserFieldProps,
|
|
onChangeHandler: (value: any) => void
|
|
) {
|
|
return renderNewMultiSelect("tags", props, onChangeHandler);
|
|
}
|
|
|
|
function renderNewStudioSelect(
|
|
props: ISceneParserFieldProps,
|
|
onChangeHandler: (value: any) => void
|
|
) {
|
|
return (
|
|
<StudioSelect
|
|
noSelectionString=""
|
|
className={props.className}
|
|
onSelect={items => onChangeHandler(items[0]?.id)}
|
|
initialIds={props.parserResult.value ? [props.parserResult.value] : []}
|
|
/>
|
|
);
|
|
}
|
|
|
|
interface ISceneParserRowProps {
|
|
scene: SceneParserResult;
|
|
onChange: (changedScene: SceneParserResult) => void;
|
|
}
|
|
|
|
function SceneParserRow(props: ISceneParserRowProps) {
|
|
function changeParser(result: ParserResult<any>, set: boolean, value: any) {
|
|
const newParser = _.clone(result);
|
|
newParser.set = set;
|
|
newParser.value = value;
|
|
return newParser;
|
|
}
|
|
|
|
function onTitleChanged(set: boolean, value: string | undefined) {
|
|
const newResult = _.clone(props.scene);
|
|
newResult.title = changeParser(newResult.title, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onDateChanged(set: boolean, value: string | undefined) {
|
|
const newResult = _.clone(props.scene);
|
|
newResult.date = changeParser(newResult.date, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onPerformerIdsChanged(set: boolean, value: string[] | undefined) {
|
|
const newResult = _.clone(props.scene);
|
|
newResult.performerIds = changeParser(newResult.performerIds, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onTagIdsChanged(set: boolean, value: string[] | undefined) {
|
|
const newResult = _.clone(props.scene);
|
|
newResult.tagIds = changeParser(newResult.tagIds, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
function onStudioIdChanged(set: boolean, value: string | undefined) {
|
|
const newResult = _.clone(props.scene);
|
|
newResult.studioId = changeParser(newResult.studioId, set, value);
|
|
props.onChange(newResult);
|
|
}
|
|
|
|
return (
|
|
<tr className="scene-parser-row">
|
|
<td style={{ textAlign: "left" }}>{props.scene.filename}</td>
|
|
<SceneParserField
|
|
key="title"
|
|
fieldName="Title"
|
|
className="parser-field-title"
|
|
parserResult={props.scene.title}
|
|
onSetChanged={set =>
|
|
onTitleChanged(set, props.scene.title.value ?? undefined)
|
|
}
|
|
onValueChanged={value => onTitleChanged(props.scene.title.set, value)}
|
|
renderOriginalInputField={renderOriginalInputGroup}
|
|
renderNewInputField={renderNewInputGroup}
|
|
/>
|
|
<SceneParserField
|
|
key="date"
|
|
fieldName="Date"
|
|
className="parser-field-date"
|
|
parserResult={props.scene.date}
|
|
onSetChanged={set =>
|
|
onDateChanged(set, props.scene.date.value ?? undefined)
|
|
}
|
|
onValueChanged={value => onDateChanged(props.scene.date.set, value)}
|
|
renderOriginalInputField={renderOriginalInputGroup}
|
|
renderNewInputField={renderNewInputGroup}
|
|
/>
|
|
<SceneParserField
|
|
key="performers"
|
|
fieldName="Performers"
|
|
className="parser-field-performers"
|
|
parserResult={props.scene.performerIds}
|
|
originalParserResult={props.scene.performers}
|
|
onSetChanged={set =>
|
|
onPerformerIdsChanged(
|
|
set,
|
|
props.scene.performerIds.value ?? undefined
|
|
)
|
|
}
|
|
onValueChanged={value =>
|
|
onPerformerIdsChanged(props.scene.performerIds.set, value)
|
|
}
|
|
renderOriginalInputField={renderOriginalSelect}
|
|
renderNewInputField={renderNewPerformerSelect}
|
|
/>
|
|
<SceneParserField
|
|
key="tags"
|
|
fieldName="Tags"
|
|
className="parser-field-tags"
|
|
parserResult={props.scene.tagIds}
|
|
originalParserResult={props.scene.tags}
|
|
onSetChanged={set =>
|
|
onTagIdsChanged(set, props.scene.tagIds.value ?? undefined)
|
|
}
|
|
onValueChanged={value =>
|
|
onTagIdsChanged(props.scene.tagIds.set, value)
|
|
}
|
|
renderOriginalInputField={renderOriginalSelect}
|
|
renderNewInputField={renderNewTagSelect}
|
|
/>
|
|
<SceneParserField
|
|
key="studio"
|
|
fieldName="Studio"
|
|
className="parser-field-studio"
|
|
parserResult={props.scene.studioId}
|
|
originalParserResult={props.scene.studio}
|
|
onSetChanged={set =>
|
|
onStudioIdChanged(set, props.scene.studioId.value ?? undefined)
|
|
}
|
|
onValueChanged={value =>
|
|
onStudioIdChanged(props.scene.studioId.set, value)
|
|
}
|
|
renderOriginalInputField={renderOriginalSelect}
|
|
renderNewInputField={renderNewStudioSelect}
|
|
/>
|
|
</tr>
|
|
);
|
|
}
|
|
|
|
function onChange(scene: SceneParserResult, changedScene: SceneParserResult) {
|
|
const newResult = [...parserResult];
|
|
|
|
const index = newResult.indexOf(scene);
|
|
newResult[index] = changedScene;
|
|
|
|
setParserResult(newResult);
|
|
}
|
|
|
|
function renderHeader(
|
|
fieldName: string,
|
|
allSet: boolean,
|
|
onAllSet: (set: boolean) => void
|
|
) {
|
|
if (!showFields.get(fieldName)) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<td>
|
|
<Form.Control
|
|
type="checkbox"
|
|
checked={allSet}
|
|
onChange={() => {
|
|
onAllSet(!allSet);
|
|
}}
|
|
/>
|
|
</td>
|
|
<th>{fieldName}</th>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function renderTable() {
|
|
if (parserResult.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div>
|
|
<div className="scene-parser-results">
|
|
<Table>
|
|
<thead>
|
|
<tr className="scene-parser-row">
|
|
<th>Filename</th>
|
|
{renderHeader("Title", allTitleSet, onSelectAllTitleSet)}
|
|
{renderHeader("Date", allDateSet, onSelectAllDateSet)}
|
|
{renderHeader(
|
|
"Performers",
|
|
allPerformerSet,
|
|
onSelectAllPerformerSet
|
|
)}
|
|
{renderHeader("Tags", allTagSet, onSelectAllTagSet)}
|
|
{renderHeader("Studio", allStudioSet, onSelectAllStudioSet)}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{parserResult.map(scene => (
|
|
<SceneParserRow
|
|
scene={scene}
|
|
key={scene.id}
|
|
onChange={changedScene => onChange(scene, changedScene)}
|
|
/>
|
|
))}
|
|
</tbody>
|
|
</Table>
|
|
</div>
|
|
<Pagination
|
|
currentPage={parserInput.page}
|
|
itemsPerPage={parserInput.pageSize}
|
|
totalItems={totalItems}
|
|
onChangePage={page => onPageChanged(page)}
|
|
/>
|
|
<Button variant="primary" onClick={onApply}>
|
|
Apply
|
|
</Button>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card id="parser-container">
|
|
<h4>Scene Filename Parser</h4>
|
|
<ParserInput
|
|
input={parserInput}
|
|
onFind={input => onFindClicked(input)}
|
|
onPageSizeChanged={onPageSizeChanged}
|
|
showFields={showFields}
|
|
setShowFields={setShowFields}
|
|
/>
|
|
|
|
{isLoading && <LoadingIndicator />}
|
|
{renderTable()}
|
|
</Card>
|
|
);
|
|
};
|