mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Remove or exempt all uses of 'any
* Refactored LocalForage * Refactored SceneFilenameParser
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": 2,
|
||||||
"lines-between-class-members": "off",
|
"lines-between-class-members": "off",
|
||||||
"@typescript-eslint/interface-name-prefix": [
|
"@typescript-eslint/interface-name-prefix": [
|
||||||
"warn",
|
"warn",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export const App: React.FC = () => {
|
|||||||
const config = StashService.useConfiguration();
|
const config = StashService.useConfiguration();
|
||||||
const language = config.data?.configuration?.interface?.language ?? "en-US";
|
const language = config.data?.configuration?.interface?.language ?? "en-US";
|
||||||
const messageLanguage = language.slice(0, 2);
|
const messageLanguage = language.slice(0, 2);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const messages = flattenMessages((locales as any)[messageLanguage]);
|
const messages = flattenMessages((locales as any)[messageLanguage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export class ErrorBoundary extends React.Component<any, any> {
|
interface IErrorBoundaryProps {
|
||||||
constructor(props: any) {
|
children?: React.ReactNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorInfo = {
|
||||||
|
componentStack: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IErrorBoundaryState {
|
||||||
|
error?: Error;
|
||||||
|
errorInfo?: ErrorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorBoundary extends React.Component<IErrorBoundaryProps, IErrorBoundaryState> {
|
||||||
|
constructor(props: IErrorBoundaryProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { error: null, errorInfo: null };
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidCatch(error: any, errorInfo: any) {
|
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error,
|
error,
|
||||||
errorInfo
|
errorInfo
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const Gallery: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-9 m-auto">
|
<div className="col-9 m-auto">
|
||||||
<GalleryViewer gallery={gallery as any} />
|
<GalleryViewer gallery={gallery} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import { CriterionModifier } from "src/core/generated-graphql";
|
|||||||
import {
|
import {
|
||||||
Criterion,
|
Criterion,
|
||||||
CriterionType,
|
CriterionType,
|
||||||
DurationCriterion
|
DurationCriterion,
|
||||||
|
CriterionValue
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { NoneCriterion } from "src/models/list-filter/criteria/none";
|
import { NoneCriterion } from "src/models/list-filter/criteria/none";
|
||||||
import { PerformersCriterion } from "src/models/list-filter/criteria/performers";
|
|
||||||
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
|
|
||||||
import { TagsCriterion } from "src/models/list-filter/criteria/tags";
|
|
||||||
import { makeCriteria } from "src/models/list-filter/criteria/utils";
|
import { makeCriteria } from "src/models/list-filter/criteria/utils";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
|
||||||
@@ -28,11 +26,11 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
|||||||
const defaultValue = useRef<string | number | undefined>();
|
const defaultValue = useRef<string | number | undefined>();
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [criterion, setCriterion] = useState<Criterion<any, any>>(
|
const [criterion, setCriterion] = useState<Criterion>(
|
||||||
new NoneCriterion()
|
new NoneCriterion()
|
||||||
);
|
);
|
||||||
|
|
||||||
const valueStage = useRef<any>(criterion.value);
|
const valueStage = useRef<CriterionValue>(criterion.value);
|
||||||
|
|
||||||
// Configure if we are editing an existing criterion
|
// Configure if we are editing an existing criterion
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -53,7 +51,7 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
|||||||
event: React.ChangeEvent<HTMLSelectElement>
|
event: React.ChangeEvent<HTMLSelectElement>
|
||||||
) {
|
) {
|
||||||
const newCriterion = _.cloneDeep(criterion);
|
const newCriterion = _.cloneDeep(criterion);
|
||||||
newCriterion.modifier = event.target.value as any;
|
newCriterion.modifier = event.target.value as CriterionModifier;
|
||||||
setCriterion(newCriterion);
|
setCriterion(newCriterion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +81,7 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
|||||||
const value = defaultValue.current;
|
const value = defaultValue.current;
|
||||||
if (
|
if (
|
||||||
criterion.options &&
|
criterion.options &&
|
||||||
|
!Array.isArray(criterion.options) &&
|
||||||
(value === undefined || value === "" || typeof value === "number")
|
(value === undefined || value === "" || typeof value === "number")
|
||||||
) {
|
) {
|
||||||
criterion.value = criterion.options[0];
|
criterion.value = criterion.options[0];
|
||||||
@@ -141,20 +140,15 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(criterion.value)) {
|
if (Array.isArray(criterion.value)) {
|
||||||
let type: "performers" | "studios" | "tags";
|
if(
|
||||||
if (criterion instanceof PerformersCriterion) {
|
criterion.type !== "performers" &&
|
||||||
type = "performers";
|
criterion.type !== "studios" &&
|
||||||
} else if (criterion instanceof StudiosCriterion) {
|
criterion.type !== "tags")
|
||||||
type = "studios";
|
|
||||||
} else if (criterion instanceof TagsCriterion) {
|
|
||||||
type = "tags";
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterSelect
|
<FilterSelect
|
||||||
type={type}
|
type={criterion.type}
|
||||||
isMulti
|
isMulti
|
||||||
onSelect={items => {
|
onSelect={items => {
|
||||||
const newCriterion = _.cloneDeep(criterion);
|
const newCriterion = _.cloneDeep(criterion);
|
||||||
@@ -164,7 +158,7 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
|||||||
}));
|
}));
|
||||||
setCriterion(newCriterion);
|
setCriterion(newCriterion);
|
||||||
}}
|
}}
|
||||||
ids={criterion.value.map((labeled: any) => labeled.id)}
|
ids={criterion.value.map(labeled => labeled.id)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -174,10 +168,10 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
|||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
onChange={onChangedSingleSelect}
|
onChange={onChangedSingleSelect}
|
||||||
value={criterion.value}
|
value={criterion.value.toString()}
|
||||||
>
|
>
|
||||||
{criterion.options.map(c => (
|
{criterion.options.map(c => (
|
||||||
<option key={c} value={c}>
|
<option key={c.toString()} value={c.toString()}>
|
||||||
{c}
|
{c}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
@@ -198,7 +192,7 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
|||||||
type={criterion.inputType}
|
type={criterion.inputType}
|
||||||
onChange={onChangedInput}
|
onChange={onChangedInput}
|
||||||
onBlur={onBlurInput}
|
onBlur={onBlurInput}
|
||||||
value={criterion.value || ""}
|
value={criterion.value.toString()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import React, { SyntheticEvent, useCallback, useState } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import { SortDirectionEnum } from "src/core/generated-graphql";
|
import { SortDirectionEnum } from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
Dropdown,
|
Dropdown,
|
||||||
Form,
|
Form,
|
||||||
OverlayTrigger,
|
OverlayTrigger,
|
||||||
Tooltip
|
Tooltip,
|
||||||
|
SafeAnchor
|
||||||
} from "react-bootstrap";
|
} from "react-bootstrap";
|
||||||
|
|
||||||
import { Icon } from "src/components/Shared";
|
import { Icon } from "src/components/Shared";
|
||||||
@@ -44,8 +45,8 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||||||
props: IListFilterProps
|
props: IListFilterProps
|
||||||
) => {
|
) => {
|
||||||
const searchCallback = useCallback(
|
const searchCallback = useCallback(
|
||||||
debounce((event: any) => {
|
debounce((value: string) => {
|
||||||
props.onChangeQuery(event.target.value);
|
props.onChangeQuery(value);
|
||||||
}, 500),
|
}, 500),
|
||||||
[props.onChangeQuery]
|
[props.onChangeQuery]
|
||||||
);
|
);
|
||||||
@@ -54,14 +55,13 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||||||
Criterion | undefined
|
Criterion | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
|
||||||
function onChangePageSize(event: SyntheticEvent<HTMLSelectElement>) {
|
function onChangePageSize(event: React.FormEvent<HTMLSelectElement>) {
|
||||||
const val = event!.currentTarget!.value;
|
const val = event.currentTarget.value;
|
||||||
props.onChangePageSize(parseInt(val, 10));
|
props.onChangePageSize(parseInt(val, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeQuery(event: SyntheticEvent<HTMLInputElement>) {
|
function onChangeQuery(event: React.FormEvent<HTMLInputElement>) {
|
||||||
event.persist();
|
searchCallback(event.currentTarget.value);
|
||||||
searchCallback(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeSortDirection() {
|
function onChangeSortDirection() {
|
||||||
@@ -72,8 +72,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeSortBy(event: React.MouseEvent<any>) {
|
function onChangeSortBy(event:React.MouseEvent<SafeAnchor>) {
|
||||||
props.onChangeSortBy(event.currentTarget.text);
|
const target = event.currentTarget as unknown as HTMLAnchorElement;
|
||||||
|
props.onChangeSortBy(target.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeDisplayMode(displayMode: DisplayMode) {
|
function onChangeDisplayMode(displayMode: DisplayMode) {
|
||||||
@@ -156,6 +157,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||||||
<Badge
|
<Badge
|
||||||
className="tag-item"
|
className="tag-item"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
key={criterion.getId()}
|
||||||
onClick={() => onClickCriterionTag(criterion)}
|
onClick={() => onClickCriterionTag(criterion)}
|
||||||
>
|
>
|
||||||
{criterion.getLabel()}
|
{criterion.getLabel()}
|
||||||
@@ -241,8 +243,8 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||||||
min={0}
|
min={0}
|
||||||
max={3}
|
max={3}
|
||||||
defaultValue={1}
|
defaultValue={1}
|
||||||
onChange={(event: any) =>
|
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||||
onChangeZoom(Number.parseInt(event.target.value, 10))
|
onChangeZoom(Number.parseInt(e.currentTarget.value, 10))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
// if performers is already present, then we modify it, otherwise add
|
// if performers is already present, then we modify it, otherwise add
|
||||||
let performerCriterion = filter.criteria.find(c => {
|
let performerCriterion = filter.criteria.find(c => {
|
||||||
return c.type === "performers";
|
return c.type === "performers";
|
||||||
});
|
}) as PerformersCriterion;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
performerCriterion &&
|
performerCriterion &&
|
||||||
@@ -25,7 +25,7 @@ export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
) {
|
) {
|
||||||
// add the performer if not present
|
// add the performer if not present
|
||||||
if (
|
if (
|
||||||
!performerCriterion.value.find((p: any) => {
|
!performerCriterion.value.find(p => {
|
||||||
return p.id === performer.id;
|
return p.id === performer.id;
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||||||
<InputGroup className="col-8">
|
<InputGroup className="col-8">
|
||||||
<Form.Control
|
<Form.Control
|
||||||
id="filename-pattern"
|
id="filename-pattern"
|
||||||
onChange={(newValue: any) => setPattern(newValue.target.value)}
|
onChange={(e: React.FormEvent<HTMLInputElement>) => setPattern(e.currentTarget.value)}
|
||||||
value={pattern}
|
value={pattern}
|
||||||
/>
|
/>
|
||||||
<InputGroup.Append>
|
<InputGroup.Append>
|
||||||
@@ -158,7 +158,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
|
||||||
onChange={(newValue: any) => setIgnoreWords(newValue.target.value)}
|
onChange={(e: React.FormEvent<HTMLInputElement>) => setIgnoreWords(e.currentTarget.value)}
|
||||||
value={ignoreWords}
|
value={ignoreWords}
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
@@ -174,8 +174,8 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||||||
</Form.Label>
|
</Form.Label>
|
||||||
<InputGroup className="col-8">
|
<InputGroup className="col-8">
|
||||||
<Form.Control
|
<Form.Control
|
||||||
onChange={(newValue: any) =>
|
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||||
setWhitespaceCharacters(newValue.target.value)
|
setWhitespaceCharacters(e.currentTarget.value)
|
||||||
}
|
}
|
||||||
value={whitespaceCharacters}
|
value={whitespaceCharacters}
|
||||||
/>
|
/>
|
||||||
@@ -229,8 +229,8 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
options={PAGE_SIZE_OPTIONS}
|
options={PAGE_SIZE_OPTIONS}
|
||||||
onChange={(event: any) =>
|
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||||
props.onPageSizeChanged(parseInt(event.target.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 filter-item"
|
||||||
|
|||||||
@@ -1,154 +1,16 @@
|
|||||||
/* 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 } from "react";
|
||||||
import { Badge, 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";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import { LoadingIndicator } from "src/components/Shared";
|
||||||
FilterSelect,
|
|
||||||
StudioSelect,
|
|
||||||
LoadingIndicator
|
|
||||||
} from "src/components/Shared";
|
|
||||||
import { TextUtils } from "src/utils";
|
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import { Pagination } from "src/components/List/Pagination";
|
import { Pagination } from "src/components/List/Pagination";
|
||||||
import { IParserInput, ParserInput } from "./ParserInput";
|
import { IParserInput, ParserInput } from "./ParserInput";
|
||||||
import { ParserField } from "./ParserField";
|
import { ParserField } from "./ParserField";
|
||||||
|
import { SceneParserResult, SceneParserRow } from './SceneParserRow';
|
||||||
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 = {
|
const initialParserInput = {
|
||||||
pattern: "{title}.{ext}",
|
pattern: "{title}.{ext}",
|
||||||
@@ -309,19 +171,19 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newAllTitleSet = !parserResult.some(r => {
|
const newAllTitleSet = !parserResult.some(r => {
|
||||||
return !r.title.set;
|
return !r.title.isSet;
|
||||||
});
|
});
|
||||||
const newAllDateSet = !parserResult.some(r => {
|
const newAllDateSet = !parserResult.some(r => {
|
||||||
return !r.date.set;
|
return !r.date.isSet;
|
||||||
});
|
});
|
||||||
const newAllPerformerSet = !parserResult.some(r => {
|
const newAllPerformerSet = !parserResult.some(r => {
|
||||||
return !r.performerIds.set;
|
return !r.performers.isSet;
|
||||||
});
|
});
|
||||||
const newAllTagSet = !parserResult.some(r => {
|
const newAllTagSet = !parserResult.some(r => {
|
||||||
return !r.tagIds.set;
|
return !r.tags.isSet;
|
||||||
});
|
});
|
||||||
const newAllStudioSet = !parserResult.some(r => {
|
const newAllStudioSet = !parserResult.some(r => {
|
||||||
return !r.studioId.set;
|
return !r.studio.isSet;
|
||||||
});
|
});
|
||||||
|
|
||||||
setAllTitleSet(newAllTitleSet);
|
setAllTitleSet(newAllTitleSet);
|
||||||
@@ -335,7 +197,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
const newResult = [...parserResult];
|
const newResult = [...parserResult];
|
||||||
|
|
||||||
newResult.forEach(r => {
|
newResult.forEach(r => {
|
||||||
r.title.set = selected;
|
r.title.isSet = selected;
|
||||||
});
|
});
|
||||||
|
|
||||||
setParserResult(newResult);
|
setParserResult(newResult);
|
||||||
@@ -346,7 +208,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
const newResult = [...parserResult];
|
const newResult = [...parserResult];
|
||||||
|
|
||||||
newResult.forEach(r => {
|
newResult.forEach(r => {
|
||||||
r.date.set = selected;
|
r.date.isSet = selected;
|
||||||
});
|
});
|
||||||
|
|
||||||
setParserResult(newResult);
|
setParserResult(newResult);
|
||||||
@@ -357,7 +219,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
const newResult = [...parserResult];
|
const newResult = [...parserResult];
|
||||||
|
|
||||||
newResult.forEach(r => {
|
newResult.forEach(r => {
|
||||||
r.performerIds.set = selected;
|
r.performers.isSet = selected;
|
||||||
});
|
});
|
||||||
|
|
||||||
setParserResult(newResult);
|
setParserResult(newResult);
|
||||||
@@ -368,7 +230,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
const newResult = [...parserResult];
|
const newResult = [...parserResult];
|
||||||
|
|
||||||
newResult.forEach(r => {
|
newResult.forEach(r => {
|
||||||
r.tagIds.set = selected;
|
r.tags.isSet = selected;
|
||||||
});
|
});
|
||||||
|
|
||||||
setParserResult(newResult);
|
setParserResult(newResult);
|
||||||
@@ -379,299 +241,13 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
const newResult = [...parserResult];
|
const newResult = [...parserResult];
|
||||||
|
|
||||||
newResult.forEach(r => {
|
newResult.forEach(r => {
|
||||||
r.studioId.set = selected;
|
r.studio.isSet = selected;
|
||||||
});
|
});
|
||||||
|
|
||||||
setParserResult(newResult);
|
setParserResult(newResult);
|
||||||
setAllStudioSet(selected);
|
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.Check
|
|
||||||
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 || ""}
|
|
||||||
onChange={(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 key={name} 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);
|
|
||||||
}}
|
|
||||||
ids={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 className="text-left parser-field-filename">
|
|
||||||
{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) {
|
function onChange(scene: SceneParserResult, changedScene: SceneParserResult) {
|
||||||
const newResult = [...parserResult];
|
const newResult = [...parserResult];
|
||||||
|
|
||||||
@@ -716,7 +292,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
<Table>
|
<Table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="scene-parser-row">
|
<tr className="scene-parser-row">
|
||||||
<th className="w-25">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(
|
{renderHeader(
|
||||||
@@ -734,6 +310,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
scene={scene}
|
scene={scene}
|
||||||
key={scene.id}
|
key={scene.id}
|
||||||
onChange={changedScene => onChange(scene, changedScene)}
|
onChange={changedScene => onChange(scene, changedScene)}
|
||||||
|
showFields={showFields}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
385
ui/v2.5/src/components/SceneFilenameParser/SceneParserRow.tsx
Normal file
385
ui/v2.5/src/components/SceneFilenameParser/SceneParserRow.tsx
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
import React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { Form } from 'react-bootstrap';
|
||||||
|
import {
|
||||||
|
ParseSceneFilenamesQuery,
|
||||||
|
SlimSceneDataFragment,
|
||||||
|
} from "src/core/generated-graphql";
|
||||||
|
import {
|
||||||
|
PerformerSelect,
|
||||||
|
TagSelect,
|
||||||
|
StudioSelect
|
||||||
|
} from "src/components/Shared";
|
||||||
|
import { TextUtils } from "src/utils";
|
||||||
|
|
||||||
|
class ParserResult<T> {
|
||||||
|
public value?: T;
|
||||||
|
public originalValue?: T;
|
||||||
|
public isSet: boolean = false;
|
||||||
|
|
||||||
|
public setOriginalValue(value?: T) {
|
||||||
|
this.originalValue = value;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setValue(value?: T) {
|
||||||
|
if (value) {
|
||||||
|
this.value = value;
|
||||||
|
this.isSet = !_.isEqual(this.value, this.originalValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SceneParserResult {
|
||||||
|
public id: string;
|
||||||
|
public filename: string;
|
||||||
|
public title: ParserResult<string> = new ParserResult<string>();
|
||||||
|
public date: ParserResult<string> = new ParserResult<string>();
|
||||||
|
|
||||||
|
public studio: ParserResult<string> = new ParserResult<string>();
|
||||||
|
public tags: ParserResult<string[]> = new ParserResult<string[]>();
|
||||||
|
public performers: ParserResult<string[]> = new ParserResult<string[]>();
|
||||||
|
|
||||||
|
public scene: SlimSceneDataFragment;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
result: ParseSceneFilenamesQuery["parseSceneFilenames"]["results"][0]
|
||||||
|
) {
|
||||||
|
this.scene = result.scene;
|
||||||
|
|
||||||
|
this.id = this.scene.id;
|
||||||
|
this.filename = TextUtils.fileNameFromPath(this.scene.path);
|
||||||
|
this.title.setOriginalValue(this.scene.title ?? undefined);
|
||||||
|
this.date.setOriginalValue(this.scene.date ?? 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if any of its fields have set == true
|
||||||
|
public isChanged() {
|
||||||
|
return (
|
||||||
|
this.title.isSet ||
|
||||||
|
this.date.isSet ||
|
||||||
|
this.performers.isSet ||
|
||||||
|
this.studio.isSet ||
|
||||||
|
this.tags.isSet
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toSceneUpdateInput() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
details: this.scene.details,
|
||||||
|
url: this.scene.url,
|
||||||
|
rating: 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,
|
||||||
|
studio_id: this.studio.isSet
|
||||||
|
? this.studio.value
|
||||||
|
: this.scene.studio?.id,
|
||||||
|
performer_ids: this.performers.isSet
|
||||||
|
? this.performers.value
|
||||||
|
: this.scene.performers.map(performer => performer.id),
|
||||||
|
tag_ids: this.tags.isSet
|
||||||
|
? this.tags.value
|
||||||
|
: this.scene.tags.map(tag => tag.id)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISceneParserFieldProps<T> {
|
||||||
|
parserResult: ParserResult<T>;
|
||||||
|
className?: string;
|
||||||
|
fieldName: string;
|
||||||
|
onSetChanged: (isSet: boolean) => void;
|
||||||
|
onValueChanged: (value: T) => void;
|
||||||
|
originalParserResult?: ParserResult<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SceneParserStringField(props: ISceneParserFieldProps<string>) {
|
||||||
|
function maybeValueChanged(value: string) {
|
||||||
|
if (value !== props.parserResult.value) {
|
||||||
|
props.onValueChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = props.originalParserResult || props.parserResult;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<td>
|
||||||
|
<Form.Check
|
||||||
|
checked={props.parserResult.isSet}
|
||||||
|
onChange={() => {
|
||||||
|
props.onSetChanged(!props.parserResult.isSet);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Control
|
||||||
|
disabled
|
||||||
|
className={props.className}
|
||||||
|
defaultValue={result.originalValue || ""}
|
||||||
|
/>
|
||||||
|
<Form.Control
|
||||||
|
disabled={!props.parserResult.isSet}
|
||||||
|
className={props.className}
|
||||||
|
value={props.parserResult.value || ""}
|
||||||
|
onChange={(event: React.FormEvent<HTMLInputElement>) => maybeValueChanged(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SceneParserPerformerField(props: ISceneParserFieldProps<string[]>) {
|
||||||
|
function maybeValueChanged(value: string[]) {
|
||||||
|
if (value !== props.parserResult.value) {
|
||||||
|
props.onValueChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalPerformers = (props.originalParserResult?.originalValue ?? []) as string[];
|
||||||
|
const newPerformers = props.parserResult.value ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<td>
|
||||||
|
<Form.Check
|
||||||
|
checked={props.parserResult.isSet}
|
||||||
|
onChange={() => {
|
||||||
|
props.onSetChanged(!props.parserResult.isSet);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Form.Group className={props.className}>
|
||||||
|
<PerformerSelect
|
||||||
|
isDisabled
|
||||||
|
isMulti
|
||||||
|
ids={originalPerformers}
|
||||||
|
/>
|
||||||
|
<PerformerSelect
|
||||||
|
isMulti
|
||||||
|
onSelect={items => {
|
||||||
|
maybeValueChanged(items.map(i => i.id));
|
||||||
|
}}
|
||||||
|
ids={newPerformers}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SceneParserTagField(props: ISceneParserFieldProps<string[]>) {
|
||||||
|
function maybeValueChanged(value: string[]) {
|
||||||
|
if (value !== props.parserResult.value) {
|
||||||
|
props.onValueChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalTags = props.originalParserResult?.originalValue ?? [];
|
||||||
|
const newTags = props.parserResult.value ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<td>
|
||||||
|
<Form.Check
|
||||||
|
checked={props.parserResult.isSet}
|
||||||
|
onChange={() => {
|
||||||
|
props.onSetChanged(!props.parserResult.isSet);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Form.Group className={props.className}>
|
||||||
|
<TagSelect
|
||||||
|
isDisabled
|
||||||
|
isMulti
|
||||||
|
ids={originalTags}
|
||||||
|
/>
|
||||||
|
<TagSelect
|
||||||
|
isMulti
|
||||||
|
onSelect={items => {
|
||||||
|
maybeValueChanged(items.map(i => i.id));
|
||||||
|
}}
|
||||||
|
ids={newTags}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SceneParserStudioField(props: ISceneParserFieldProps<string>) {
|
||||||
|
function maybeValueChanged(value: string) {
|
||||||
|
if (value !== props.parserResult.value) {
|
||||||
|
props.onValueChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalStudio = props.originalParserResult?.originalValue ? [props.originalParserResult?.originalValue] : [];
|
||||||
|
const newStudio = props.parserResult.value ? [props.parserResult.value] : [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<td>
|
||||||
|
<Form.Check
|
||||||
|
checked={props.parserResult.isSet}
|
||||||
|
onChange={() => {
|
||||||
|
props.onSetChanged(!props.parserResult.isSet);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Form.Group className={props.className}>
|
||||||
|
<StudioSelect
|
||||||
|
isDisabled
|
||||||
|
ids={originalStudio}
|
||||||
|
/>
|
||||||
|
<StudioSelect
|
||||||
|
onSelect={items => {
|
||||||
|
maybeValueChanged(items[0].id);
|
||||||
|
}}
|
||||||
|
ids={newStudio}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISceneParserRowProps {
|
||||||
|
scene: SceneParserResult;
|
||||||
|
onChange: (changedScene: SceneParserResult) => void;
|
||||||
|
showFields: Map<string, boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SceneParserRow = (props: ISceneParserRowProps) => {
|
||||||
|
function changeParser<T>(result: ParserResult<T>, isSet: boolean, value: T) {
|
||||||
|
const newParser = _.clone(result);
|
||||||
|
newParser.isSet = isSet;
|
||||||
|
newParser.value = value;
|
||||||
|
return newParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTitleChanged(set: boolean, value: string) {
|
||||||
|
const newResult = _.clone(props.scene);
|
||||||
|
newResult.title = changeParser(newResult.title, set, value);
|
||||||
|
props.onChange(newResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDateChanged(set: boolean, value: string) {
|
||||||
|
const newResult = _.clone(props.scene);
|
||||||
|
newResult.date = changeParser(newResult.date, set, value);
|
||||||
|
props.onChange(newResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPerformerIdsChanged(set: boolean, value: string[]) {
|
||||||
|
const newResult = _.clone(props.scene);
|
||||||
|
newResult.performers = changeParser(newResult.performers, set, value);
|
||||||
|
props.onChange(newResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTagIdsChanged(set: boolean, value: string[]) {
|
||||||
|
const newResult = _.clone(props.scene);
|
||||||
|
newResult.tags= changeParser(newResult.tags, set, value);
|
||||||
|
props.onChange(newResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStudioIdChanged(set: boolean, value: string) {
|
||||||
|
const newResult = _.clone(props.scene);
|
||||||
|
newResult.studio = changeParser(newResult.studio, set, value);
|
||||||
|
props.onChange(newResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className="scene-parser-row">
|
||||||
|
<td className="text-left parser-field-filename">
|
||||||
|
{props.scene.filename}
|
||||||
|
</td>
|
||||||
|
{ props.showFields.get("Title") && (
|
||||||
|
<SceneParserStringField
|
||||||
|
key="title"
|
||||||
|
fieldName="Title"
|
||||||
|
className="parser-field-title"
|
||||||
|
parserResult={props.scene.title}
|
||||||
|
onSetChanged={isSet =>
|
||||||
|
onTitleChanged(isSet, props.scene.title.value ?? '')
|
||||||
|
}
|
||||||
|
onValueChanged={value => onTitleChanged(props.scene.title.isSet, value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{ props.showFields.get("Date") && (
|
||||||
|
<SceneParserStringField
|
||||||
|
key="date"
|
||||||
|
fieldName="Date"
|
||||||
|
className="parser-field-date"
|
||||||
|
parserResult={props.scene.date}
|
||||||
|
onSetChanged={isSet =>
|
||||||
|
onDateChanged(isSet, props.scene.date.value ?? '')
|
||||||
|
}
|
||||||
|
onValueChanged={value => onDateChanged(props.scene.date.isSet, value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{ props.showFields.get("Performers") && (
|
||||||
|
<SceneParserPerformerField
|
||||||
|
key="performers"
|
||||||
|
fieldName="Performers"
|
||||||
|
className="parser-field-performers"
|
||||||
|
parserResult={props.scene.performers}
|
||||||
|
originalParserResult={props.scene.performers}
|
||||||
|
onSetChanged={set =>
|
||||||
|
onPerformerIdsChanged(
|
||||||
|
set,
|
||||||
|
props.scene.performers.value ?? []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onValueChanged={value =>
|
||||||
|
onPerformerIdsChanged(props.scene.performers.isSet, value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{ props.showFields.get("Tags") && (
|
||||||
|
<SceneParserTagField
|
||||||
|
key="tags"
|
||||||
|
fieldName="Tags"
|
||||||
|
className="parser-field-tags"
|
||||||
|
parserResult={props.scene.tags}
|
||||||
|
originalParserResult={props.scene.tags}
|
||||||
|
onSetChanged={isSet =>
|
||||||
|
onTagIdsChanged(isSet, props.scene.tags.value ?? [])
|
||||||
|
}
|
||||||
|
onValueChanged={value =>
|
||||||
|
onTagIdsChanged(props.scene.tags.isSet, value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{ props.showFields.get("Studio") && (
|
||||||
|
<SceneParserStudioField
|
||||||
|
key="studio"
|
||||||
|
fieldName="Studio"
|
||||||
|
className="parser-field-studio"
|
||||||
|
parserResult={props.scene.studio}
|
||||||
|
originalParserResult={props.scene.studio}
|
||||||
|
onSetChanged={set =>
|
||||||
|
onStudioIdChanged(set, props.scene.studio.value ?? '')
|
||||||
|
}
|
||||||
|
onValueChanged={value => onStudioIdChanged(props.scene.studio.isSet, value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
.scene-parser-results {
|
.scene-parser-results {
|
||||||
|
margin-left: 31ch;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scene-parser-row {
|
.scene-parser-row {
|
||||||
.parser-field-filename {
|
.parser-field-filename {
|
||||||
width: 10ch;
|
left: 1ch;
|
||||||
|
position: absolute;
|
||||||
|
width: 30ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.parser-field-title {
|
.parser-field-title {
|
||||||
@@ -16,15 +19,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.parser-field-performers {
|
.parser-field-performers {
|
||||||
width: 20ch;
|
width: 30ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.parser-field-tags {
|
.parser-field-tags {
|
||||||
width: 20ch;
|
width: 30ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.parser-field-studio {
|
.parser-field-studio {
|
||||||
width: 15ch;
|
width: 20ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
@@ -34,4 +37,9 @@
|
|||||||
.form-control + .form-control {
|
.form-control + .form-control {
|
||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-items {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ interface IScenePlayerProps {
|
|||||||
scene: GQL.SceneDataFragment;
|
scene: GQL.SceneDataFragment;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
autoplay?: boolean;
|
autoplay?: boolean;
|
||||||
onReady?: any;
|
onReady?: () => void;
|
||||||
onSeeked?: any;
|
onSeeked?: () => void;
|
||||||
onTime?: any;
|
onTime?: () => void;
|
||||||
config?: GQL.ConfigInterfaceDataFragment;
|
config?: GQL.ConfigInterfaceDataFragment;
|
||||||
}
|
}
|
||||||
interface IScenePlayerState {
|
interface IScenePlayerState {
|
||||||
scrubberPosition: number;
|
scrubberPosition: number;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
config: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeyMap = {
|
const KeyMap = {
|
||||||
@@ -30,6 +32,8 @@ export class ScenePlayerImpl extends React.Component<
|
|||||||
IScenePlayerProps,
|
IScenePlayerProps,
|
||||||
IScenePlayerState
|
IScenePlayerState
|
||||||
> {
|
> {
|
||||||
|
// Typings for jwplayer are, unfortunately, very lacking
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private player: any;
|
private player: any;
|
||||||
private lastTime = 0;
|
private lastTime = 0;
|
||||||
|
|
||||||
@@ -57,7 +61,18 @@ export class ScenePlayerImpl extends React.Component<
|
|||||||
this.onScrubberSeek = this.onScrubberSeek.bind(this);
|
this.onScrubberSeek = this.onScrubberSeek.bind(this);
|
||||||
this.onScrubberScrolled = this.onScrubberScrolled.bind(this);
|
this.onScrubberScrolled = this.onScrubberScrolled.bind(this);
|
||||||
|
|
||||||
this.state = { scrubberPosition: 0 };
|
this.state = {
|
||||||
|
scrubberPosition: 0,
|
||||||
|
config: this.makeJWPlayerConfig(props.scene)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public UNSAFE_componentWillReceiveProps(props: IScenePlayerProps) {
|
||||||
|
if(props.scene !== this.props.scene) {
|
||||||
|
this.setState( state => (
|
||||||
|
{ ...state, config: this.makeJWPlayerConfig(this.props.scene) }
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: IScenePlayerProps) {
|
public componentDidUpdate(prevProps: IScenePlayerProps) {
|
||||||
@@ -114,9 +129,7 @@ export class ScenePlayerImpl extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private shouldRepeat(scene: GQL.SceneDataFragment) {
|
private shouldRepeat(scene: GQL.SceneDataFragment) {
|
||||||
const maxLoopDuration = this.props.config
|
const maxLoopDuration = this.state?.config.maximumLoopDuration ?? 0;
|
||||||
? this.props.config.maximumLoopDuration
|
|
||||||
: 0;
|
|
||||||
return (
|
return (
|
||||||
!!scene.file.duration &&
|
!!scene.file.duration &&
|
||||||
!!maxLoopDuration &&
|
!!maxLoopDuration &&
|
||||||
@@ -132,25 +145,25 @@ export class ScenePlayerImpl extends React.Component<
|
|||||||
const repeat = this.shouldRepeat(scene);
|
const repeat = this.shouldRepeat(scene);
|
||||||
let getDurationHook: (() => GQL.Maybe<number>) | undefined;
|
let getDurationHook: (() => GQL.Maybe<number>) | undefined;
|
||||||
let seekHook:
|
let seekHook:
|
||||||
| ((seekToPosition: number, _videoTag: any) => void)
|
| ((seekToPosition: number, _videoTag: HTMLVideoElement) => void)
|
||||||
| undefined;
|
| undefined;
|
||||||
let getCurrentTimeHook: ((_videoTag: any) => number) | undefined;
|
let getCurrentTimeHook: ((_videoTag: HTMLVideoElement) => number) | undefined;
|
||||||
|
|
||||||
if (!this.props.scene.is_streamable) {
|
if (!this.props.scene.is_streamable) {
|
||||||
getDurationHook = () => {
|
getDurationHook = () => {
|
||||||
return this.props.scene.file.duration ?? null;
|
return this.props.scene.file.duration ?? null;
|
||||||
};
|
};
|
||||||
|
|
||||||
seekHook = (seekToPosition: number, _videoTag: any) => {
|
seekHook = (seekToPosition: number, _videoTag: HTMLVideoElement) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
/* eslint-disable no-param-reassign */
|
||||||
_videoTag.start = seekToPosition;
|
_videoTag.dataset.start = seekToPosition.toString();
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
_videoTag.src = `${this.props.scene.paths.stream}?start=${seekToPosition}`;
|
_videoTag.src = `${this.props.scene.paths.stream}?start=${seekToPosition}`;
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
_videoTag.play();
|
_videoTag.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
getCurrentTimeHook = (_videoTag: any) => {
|
getCurrentTimeHook = (_videoTag: HTMLVideoElement) => {
|
||||||
const start = _videoTag.start || 0;
|
const start = Number.parseInt(_videoTag.dataset?.start ?? '0', 10);
|
||||||
return _videoTag.currentTime + start;
|
return _videoTag.currentTime + start;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -189,20 +202,6 @@ export class ScenePlayerImpl extends React.Component<
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPlayer() {
|
|
||||||
const config = this.makeJWPlayerConfig(this.props.scene);
|
|
||||||
return (
|
|
||||||
<ReactJWPlayer
|
|
||||||
playerId={JWUtils.playerID}
|
|
||||||
playerScript="/jwplayer/jwplayer.js"
|
|
||||||
customProps={config}
|
|
||||||
onReady={this.onReady}
|
|
||||||
onSeeked={this.onSeeked}
|
|
||||||
onTime={this.onTime}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<HotKeys
|
<HotKeys
|
||||||
@@ -214,7 +213,14 @@ export class ScenePlayerImpl extends React.Component<
|
|||||||
id="jwplayer-container"
|
id="jwplayer-container"
|
||||||
className="w-100 col-sm-9 m-sm-auto no-gutter"
|
className="w-100 col-sm-9 m-sm-auto no-gutter"
|
||||||
>
|
>
|
||||||
{this.renderPlayer()}
|
<ReactJWPlayer
|
||||||
|
playerId={JWUtils.playerID}
|
||||||
|
playerScript="/jwplayer/jwplayer.js"
|
||||||
|
customProps={this.state.config}
|
||||||
|
onReady={this.onReady}
|
||||||
|
onSeeked={this.onSeeked}
|
||||||
|
onTime={this.onTime}
|
||||||
|
/>
|
||||||
<ScenePlayerScrubber
|
<ScenePlayerScrubber
|
||||||
scene={this.props.scene}
|
scene={this.props.scene}
|
||||||
position={this.state.scrubberPosition}
|
position={this.state.scrubberPosition}
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (
|
|||||||
const positionIndicatorEl = useRef<HTMLDivElement>(null);
|
const positionIndicatorEl = useRef<HTMLDivElement>(null);
|
||||||
const scrubberSliderEl = useRef<HTMLDivElement>(null);
|
const scrubberSliderEl = useRef<HTMLDivElement>(null);
|
||||||
const mouseDown = useRef(false);
|
const mouseDown = useRef(false);
|
||||||
const lastMouseEvent = useRef<any>(null);
|
const lastMouseEvent = useRef<MouseEvent|null>(null);
|
||||||
const startMouseEvent = useRef<any>(null);
|
const startMouseEvent = useRef<MouseEvent|null>(null);
|
||||||
const velocity = useRef(0);
|
const velocity = useRef(0);
|
||||||
|
|
||||||
const _position = useRef(0);
|
const _position = useRef(0);
|
||||||
@@ -228,7 +228,7 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// negative dragging right (past), positive left (future)
|
// negative dragging right (past), positive left (future)
|
||||||
const delta = event.clientX - lastMouseEvent.current.clientX;
|
const delta = event.clientX - (lastMouseEvent.current?.clientX ?? 0);
|
||||||
|
|
||||||
const movement = event.movementX;
|
const movement = event.movementX;
|
||||||
velocity.current = movement;
|
velocity.current = movement;
|
||||||
@@ -279,10 +279,10 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let tag: any;
|
let tag: Element|null;
|
||||||
for (let index = 0; index < tags.length; index++) {
|
for (let index = 0; index < tags.length; index++) {
|
||||||
tag = tags.item(index) as any;
|
tag = tags.item(index);
|
||||||
const id = tag.getAttribute("data-marker-id");
|
const id = tag?.getAttribute("data-marker-id") ?? null;
|
||||||
if (id === i.toString()) {
|
if (id === i.toString()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (
|
|||||||
const percentage = marker.seconds / duration;
|
const percentage = marker.seconds / duration;
|
||||||
|
|
||||||
const left =
|
const left =
|
||||||
scrubberSliderEl.current.scrollWidth * percentage - tag.clientWidth / 2;
|
scrubberSliderEl.current.scrollWidth * percentage - tag!.clientWidth / 2;
|
||||||
return {
|
return {
|
||||||
left: `${left}px`,
|
left: `${left}px`,
|
||||||
height: 20
|
height: 20
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||||||
<td>URL</td>
|
<td>URL</td>
|
||||||
<td>
|
<td>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
onChange={(newValue: any) => setUrl(newValue.target.value)}
|
onChange={(newValue: React.FormEvent<HTMLInputElement>) => setUrl(newValue.currentTarget.value)}
|
||||||
value={url}
|
value={url}
|
||||||
placeholder="URL"
|
placeholder="URL"
|
||||||
/>
|
/>
|
||||||
@@ -376,7 +376,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||||||
<Form.Control
|
<Form.Control
|
||||||
as="textarea"
|
as="textarea"
|
||||||
className="scene-description"
|
className="scene-description"
|
||||||
onChange={(newValue: any) => setDetails(newValue.target.value)}
|
onChange={(newValue: React.FormEvent<HTMLTextAreaElement>) => setDetails(newValue.currentTarget.value)}
|
||||||
value={details}
|
value={details}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (
|
|||||||
sceneMarkers={props.scene.scene_markers}
|
sceneMarkers={props.scene.scene_markers}
|
||||||
clickHandler={marker => {
|
clickHandler={marker => {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
onClickMarker(marker as any);
|
onClickMarker(marker as GQL.SceneMarkerDataFragment);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
|
|||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
value={rating}
|
value={rating}
|
||||||
onChange={(event: any) => setRating(event.target.value)}
|
onChange={(event: React.FormEvent<HTMLSelectElement>) => setRating(event.currentTarget.value)}
|
||||||
>
|
>
|
||||||
{["", "1", "2", "3", "4", "5"].map(opt => (
|
{["", "1", "2", "3", "4", "5"].map(opt => (
|
||||||
<option key={opt} value={opt}>
|
<option key={opt} value={opt}>
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export const SettingsAboutPanel: React.FC = () => {
|
|||||||
{!dataLatest || loadingLatest || networkStatus === 4 ? (
|
{!dataLatest || loadingLatest || networkStatus === 4 ? (
|
||||||
<LoadingIndicator inline />
|
<LoadingIndicator inline />
|
||||||
) : (
|
) : (
|
||||||
<>{renderLatestVersion()}</>
|
renderLatestVersion()
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||||||
<Form.Control
|
<Form.Control
|
||||||
className="col col-sm-6"
|
className="col col-sm-6"
|
||||||
defaultValue={databasePath}
|
defaultValue={databasePath}
|
||||||
onChange={(e: any) => setDatabasePath(e.target.value)}
|
onChange={(e: React.FormEvent<HTMLInputElement>) => setDatabasePath(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<Form.Text className="text-muted">
|
<Form.Text className="text-muted">
|
||||||
File location for the SQLite database (requires restart)
|
File location for the SQLite database (requires restart)
|
||||||
@@ -187,7 +187,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||||||
<Form.Control
|
<Form.Control
|
||||||
className="col col-sm-6"
|
className="col col-sm-6"
|
||||||
defaultValue={generatedPath}
|
defaultValue={generatedPath}
|
||||||
onChange={(e: any) => setGeneratedPath(e.target.value)}
|
onChange={(e: React.FormEvent<HTMLInputElement>) => setGeneratedPath(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<Form.Text className="text-muted">
|
<Form.Text className="text-muted">
|
||||||
Directory location for the generated files (scene markers, scene
|
Directory location for the generated files (scene markers, scene
|
||||||
@@ -204,8 +204,8 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||||||
<Form.Control
|
<Form.Control
|
||||||
className="col col-sm-6"
|
className="col col-sm-6"
|
||||||
value={regexp}
|
value={regexp}
|
||||||
onChange={(e: any) =>
|
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||||
excludeRegexChanged(i, e.target.value)
|
excludeRegexChanged(i, e.currentTarget.value)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InputGroup.Append>
|
<InputGroup.Append>
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
<Form.Control
|
<Form.Control
|
||||||
as="textarea"
|
as="textarea"
|
||||||
value={css}
|
value={css}
|
||||||
onChange={(e: any) => setCSS(e.target.value)}
|
onChange={(e: React.FormEvent<HTMLTextAreaElement>) => setCSS(e.currentTarget.value)}
|
||||||
rows={16}
|
rows={16}
|
||||||
className="col col-sm-6"
|
className="col col-sm-6"
|
||||||
></Form.Control>
|
></Form.Control>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
|
|||||||
className="duration-control"
|
className="duration-control"
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e: any) => setValue(e.target.value)}
|
onChange={(e: React.FormEvent<HTMLInputElement>) => setValue(e.currentTarget.value)}
|
||||||
onBlur={() =>
|
onBlur={() =>
|
||||||
props.onValueChange(DurationUtils.stringToSeconds(value))
|
props.onValueChange(DurationUtils.stringToSeconds(value))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
|||||||
<InputGroup>
|
<InputGroup>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
placeholder="File path"
|
placeholder="File path"
|
||||||
onChange={(e: any) => setCurrentDirectory(e.target.value)}
|
onChange={(e: React.FormEvent<HTMLInputElement>) => setCurrentDirectory(e.currentTarget.value)}
|
||||||
defaultValue={currentDirectory}
|
defaultValue={currentDirectory}
|
||||||
/>
|
/>
|
||||||
<InputGroup.Append>
|
<InputGroup.Append>
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ interface ITypeProps {
|
|||||||
interface IFilterProps {
|
interface IFilterProps {
|
||||||
ids?: string[];
|
ids?: string[];
|
||||||
initialIds?: string[];
|
initialIds?: string[];
|
||||||
onSelect: (item: ValidTypes[]) => void;
|
onSelect?: (item: ValidTypes[]) => void;
|
||||||
noSelectionString?: string;
|
noSelectionString?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
isMulti?: boolean;
|
isMulti?: boolean;
|
||||||
isClearable?: boolean;
|
isClearable?: boolean;
|
||||||
|
isDisabled?: boolean;
|
||||||
}
|
}
|
||||||
interface ISelectProps {
|
interface ISelectProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -32,6 +33,7 @@ interface ISelectProps {
|
|||||||
creatable?: boolean;
|
creatable?: boolean;
|
||||||
onCreateOption?: (value: string) => void;
|
onCreateOption?: (value: string) => void;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
isDisabled?: boolean;
|
||||||
onChange: (item: ValueType<Option>) => void;
|
onChange: (item: ValueType<Option>) => void;
|
||||||
initialIds?: string[];
|
initialIds?: string[];
|
||||||
isMulti?: boolean;
|
isMulti?: boolean;
|
||||||
@@ -183,7 +185,7 @@ export const PerformerSelect: React.FC<IFilterProps> = props => {
|
|||||||
|
|
||||||
const onChange = (selectedItems: ValueType<Option>) => {
|
const onChange = (selectedItems: ValueType<Option>) => {
|
||||||
const selectedIds = getSelectedValues(selectedItems);
|
const selectedIds = getSelectedValues(selectedItems);
|
||||||
props.onSelect(
|
props.onSelect?.(
|
||||||
normalizedData.filter(item => selectedIds.indexOf(item.id) !== -1)
|
normalizedData.filter(item => selectedIds.indexOf(item.id) !== -1)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -216,7 +218,7 @@ export const StudioSelect: React.FC<IFilterProps> = props => {
|
|||||||
|
|
||||||
const onChange = (selectedItems: ValueType<Option>) => {
|
const onChange = (selectedItems: ValueType<Option>) => {
|
||||||
const selectedIds = getSelectedValues(selectedItems);
|
const selectedIds = getSelectedValues(selectedItems);
|
||||||
props.onSelect(
|
props.onSelect?.(
|
||||||
normalizedData.filter(item => selectedIds.indexOf(item.id) !== -1)
|
normalizedData.filter(item => selectedIds.indexOf(item.id) !== -1)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -262,7 +264,7 @@ export const TagSelect: React.FC<IFilterProps> = props => {
|
|||||||
|
|
||||||
if (result?.data?.tagCreate) {
|
if (result?.data?.tagCreate) {
|
||||||
setSelectedIds([...selectedIds, result.data.tagCreate.id]);
|
setSelectedIds([...selectedIds, result.data.tagCreate.id]);
|
||||||
props.onSelect(
|
props.onSelect?.(
|
||||||
[...tags, result.data.tagCreate].filter(
|
[...tags, result.data.tagCreate].filter(
|
||||||
item => selectedIds.indexOf(item.id) !== -1
|
item => selectedIds.indexOf(item.id) !== -1
|
||||||
)
|
)
|
||||||
@@ -285,7 +287,7 @@ export const TagSelect: React.FC<IFilterProps> = props => {
|
|||||||
const onChange = (selectedItems: ValueType<Option>) => {
|
const onChange = (selectedItems: ValueType<Option>) => {
|
||||||
const selectedValues = getSelectedValues(selectedItems);
|
const selectedValues = getSelectedValues(selectedItems);
|
||||||
setSelectedIds(selectedValues);
|
setSelectedIds(selectedValues);
|
||||||
props.onSelect(tags.filter(item => selectedValues.indexOf(item.id) !== -1));
|
props.onSelect?.(tags.filter(item => selectedValues.indexOf(item.id) !== -1));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -311,6 +313,7 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
|
|||||||
items,
|
items,
|
||||||
selectedOptions,
|
selectedOptions,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isDisabled = false,
|
||||||
onCreateOption,
|
onCreateOption,
|
||||||
isClearable = true,
|
isClearable = true,
|
||||||
creatable = false,
|
creatable = false,
|
||||||
@@ -337,10 +340,12 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
|
|||||||
...base,
|
...base,
|
||||||
color: "#000"
|
color: "#000"
|
||||||
}),
|
}),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
container: (base: CSSProperties, state: any) => ({
|
container: (base: CSSProperties, state: any) => ({
|
||||||
...base,
|
...base,
|
||||||
zIndex: state.isFocused ? 10 : base.zIndex
|
zIndex: state.isFocused ? 10 : base.zIndex
|
||||||
}),
|
}),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
multiValueRemove: (base: CSSProperties, state: any) => ({
|
multiValueRemove: (base: CSSProperties, state: any) => ({
|
||||||
...base,
|
...base,
|
||||||
color: state.isFocused ? base.color : "#333333"
|
color: state.isFocused ? base.color : "#333333"
|
||||||
@@ -356,20 +361,22 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
|
|||||||
isClearable,
|
isClearable,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
noOptionsMessage: () => (type !== "tags" ? "None" : null),
|
noOptionsMessage: () => (type !== "tags" ? "None" : null),
|
||||||
placeholder,
|
placeholder: isDisabled ? '' : placeholder,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
|
isDisabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
styles,
|
styles,
|
||||||
components: {
|
components: {
|
||||||
IndicatorSeparator: () => null,
|
IndicatorSeparator: () => null,
|
||||||
...(!showDropdown && { DropdownIndicator: () => null })
|
...((!showDropdown || isDisabled) && { DropdownIndicator: () => null }),
|
||||||
|
...(isDisabled && { MultiValueRemove: () => null })
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return creatable ? (
|
return creatable ? (
|
||||||
<CreatableSelect
|
<CreatableSelect
|
||||||
{...props}
|
{...props}
|
||||||
isDisabled={isLoading}
|
isDisabled={isLoading || isDisabled}
|
||||||
onCreateOption={onCreateOption}
|
onCreateOption={onCreateOption}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const StudioScenesPanel: React.FC<IStudioScenesPanel> = ({ studio }) => {
|
|||||||
// if studio is already present, then we modify it, otherwise add
|
// if studio is already present, then we modify it, otherwise add
|
||||||
let studioCriterion = filter.criteria.find(c => {
|
let studioCriterion = filter.criteria.find(c => {
|
||||||
return c.type === "studios";
|
return c.type === "studios";
|
||||||
});
|
}) as StudiosCriterion;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
studioCriterion &&
|
studioCriterion &&
|
||||||
@@ -23,7 +23,7 @@ export const StudioScenesPanel: React.FC<IStudioScenesPanel> = ({ studio }) => {
|
|||||||
) {
|
) {
|
||||||
// add the studio if not present
|
// add the studio if not present
|
||||||
if (
|
if (
|
||||||
!studioCriterion.value.find((p: any) => {
|
!studioCriterion.value.find(p => {
|
||||||
return p.id === studio.id;
|
return p.id === studio.id;
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export const TagList: React.FC = () => {
|
|||||||
<Form.Group controlId="tag-name">
|
<Form.Group controlId="tag-name">
|
||||||
<Form.Label>Name</Form.Label>
|
<Form.Label>Name</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
onChange={(newValue: any) => setName(newValue.target.value)}
|
onChange={(newValue:React.FormEvent<HTMLInputElement>) => setName(newValue.currentTarget.value)}
|
||||||
defaultValue={(editingTag && editingTag.name) || ""}
|
defaultValue={(editingTag && editingTag.name) || ""}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import ApolloClient from "apollo-client";
|
import ApolloClient from "apollo-client";
|
||||||
import { WebSocketLink } from "apollo-link-ws";
|
import { WebSocketLink } from "apollo-link-ws";
|
||||||
import { InMemoryCache } from "apollo-cache-inmemory";
|
import { InMemoryCache, NormalizedCacheObject } from "apollo-cache-inmemory";
|
||||||
import { HttpLink } from "apollo-link-http";
|
import { HttpLink } from "apollo-link-http";
|
||||||
import { split } from "apollo-link";
|
import { split } from "apollo-link";
|
||||||
import { getMainDefinition } from "apollo-utilities";
|
import { getMainDefinition } from "apollo-utilities";
|
||||||
@@ -8,7 +8,7 @@ import { ListFilterModel } from "../models/list-filter/filter";
|
|||||||
import * as GQL from "./generated-graphql";
|
import * as GQL from "./generated-graphql";
|
||||||
|
|
||||||
export class StashService {
|
export class StashService {
|
||||||
public static client: ApolloClient<any>;
|
public static client: ApolloClient<NormalizedCacheObject>;
|
||||||
private static cache: InMemoryCache;
|
private static cache: InMemoryCache;
|
||||||
|
|
||||||
public static initialize() {
|
public static initialize() {
|
||||||
@@ -60,12 +60,13 @@ export class StashService {
|
|||||||
cache: StashService.cache
|
cache: StashService.cache
|
||||||
});
|
});
|
||||||
|
|
||||||
(window as any).StashService = StashService;
|
|
||||||
return StashService.client;
|
return StashService.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Invalidation should happen through apollo client, rather than rewriting cache directly
|
||||||
private static invalidateQueries(queries: string[]) {
|
private static invalidateQueries(queries: string[]) {
|
||||||
if (StashService.cache) {
|
if (StashService.cache) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const cache = StashService.cache as any;
|
const cache = StashService.cache as any;
|
||||||
const keyMatchers = queries.map(query => {
|
const keyMatchers = queries.map(query => {
|
||||||
return new RegExp(`^${query}`);
|
return new RegExp(`^${query}`);
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ interface IQuery<T extends IQueryResult, T2 extends IDataItem> {
|
|||||||
const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
||||||
options: IListHookOptions<QueryResult> & IQuery<QueryResult, QueryData>
|
options: IListHookOptions<QueryResult> & IQuery<QueryResult, QueryData>
|
||||||
): IListHookData => {
|
): IListHookData => {
|
||||||
const [interfaceForage, setInterfaceForage] = useInterfaceLocalForage();
|
const [interfaceState, setInterfaceState]= useInterfaceLocalForage();
|
||||||
const forageInitialised = useRef(false);
|
const forageInitialised = useRef(false);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [filter, setFilter] = useState<ListFilterModel>(
|
const [filter, setFilter] = useState<ListFilterModel>(
|
||||||
new ListFilterModel(
|
new ListFilterModel(
|
||||||
options.filterMode,
|
options.filterMode,
|
||||||
options.subComponent ? "" : queryString.parse(location.search)
|
options.subComponent ? undefined : queryString.parse(location.search)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||||
@@ -94,7 +94,7 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
|||||||
const items = options.getData(result);
|
const items = options.getData(result);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!forageInitialised.current && !interfaceForage.loading) {
|
if (!forageInitialised.current && !interfaceState.loading) {
|
||||||
forageInitialised.current = true;
|
forageInitialised.current = true;
|
||||||
|
|
||||||
// Don't use query parameters for sub-components
|
// Don't use query parameters for sub-components
|
||||||
@@ -102,7 +102,7 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
|||||||
// Don't read localForage if page already had query parameters
|
// Don't read localForage if page already had query parameters
|
||||||
if (history.location.search) return;
|
if (history.location.search) return;
|
||||||
|
|
||||||
const queryData = interfaceForage.data?.queries[options.filterMode];
|
const queryData = interfaceState.data?.queries?.[options.filterMode];
|
||||||
if (!queryData) return;
|
if (!queryData) return;
|
||||||
|
|
||||||
const newFilter = new ListFilterModel(
|
const newFilter = new ListFilterModel(
|
||||||
@@ -117,8 +117,8 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
|||||||
history.replace(newLocation);
|
history.replace(newLocation);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
interfaceForage.data,
|
interfaceState.data,
|
||||||
interfaceForage.loading,
|
interfaceState.loading,
|
||||||
history,
|
history,
|
||||||
options.subComponent,
|
options.subComponent,
|
||||||
options.filterMode
|
options.filterMode
|
||||||
@@ -129,15 +129,14 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
|||||||
|
|
||||||
const newFilter = new ListFilterModel(
|
const newFilter = new ListFilterModel(
|
||||||
options.filterMode,
|
options.filterMode,
|
||||||
options.subComponent ? "" : queryString.parse(location.search)
|
options.subComponent ? undefined : queryString.parse(location.search)
|
||||||
);
|
);
|
||||||
setFilter(newFilter);
|
setFilter(newFilter);
|
||||||
|
|
||||||
if (forageInitialised.current) {
|
if (forageInitialised.current) {
|
||||||
setInterfaceForage(config => {
|
setInterfaceState(config => {
|
||||||
const data = { ...config } as IInterfaceConfig;
|
const data = { ...config } as IInterfaceConfig;
|
||||||
data.queries = {
|
data.queries = {
|
||||||
...config?.queries,
|
|
||||||
[options.filterMode]: {
|
[options.filterMode]: {
|
||||||
filter: location.search,
|
filter: location.search,
|
||||||
itemsPerPage: newFilter.itemsPerPage,
|
itemsPerPage: newFilter.itemsPerPage,
|
||||||
@@ -147,7 +146,7 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
|||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [location, options.filterMode, options.subComponent, setInterfaceForage]);
|
}, [location, options.filterMode, options.subComponent, setInterfaceState]);
|
||||||
|
|
||||||
function getFilter() {
|
function getFilter() {
|
||||||
if (!options.filterHook) {
|
if (!options.filterHook) {
|
||||||
@@ -216,7 +215,7 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
|||||||
// Remove duplicate modifiers
|
// Remove duplicate modifiers
|
||||||
newFilter.criteria = newFilter.criteria.filter((obj, pos, arr) => {
|
newFilter.criteria = newFilter.criteria.filter((obj, pos, arr) => {
|
||||||
return (
|
return (
|
||||||
arr.map((mapObj: any) => mapObj.getId()).indexOf(obj.getId()) === pos
|
arr.map(mapObj => mapObj.getId()).indexOf(obj.getId()) === pos
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +1,75 @@
|
|||||||
import localForage from "localforage";
|
import localForage from "localforage";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { Dispatch, SetStateAction } from "react";
|
import React, { Dispatch, SetStateAction, useEffect } from "react";
|
||||||
|
|
||||||
interface IInterfaceWallConfig {}
|
interface IInterfaceWallConfig {}
|
||||||
export interface IInterfaceConfig {
|
interface IInterfaceQueryConfig {
|
||||||
wall: IInterfaceWallConfig;
|
filter: string;
|
||||||
queries: any;
|
itemsPerPage: number;
|
||||||
|
currentPage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidTypes = IInterfaceConfig | undefined;
|
export interface IInterfaceConfig {
|
||||||
|
wall?: IInterfaceWallConfig;
|
||||||
|
queries?: Record<string, IInterfaceQueryConfig>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidTypes = IInterfaceConfig;
|
||||||
|
type Key = "interface";
|
||||||
|
|
||||||
interface ILocalForage<T> {
|
interface ILocalForage<T> {
|
||||||
data: T;
|
data?: T;
|
||||||
setData: Dispatch<SetStateAction<T>>;
|
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useLocalForage(item: string): ILocalForage<ValidTypes> {
|
const Loading:Record<string, boolean> = {};
|
||||||
const [json, setJson] = React.useState<ValidTypes>(undefined);
|
const Cache:Record<string, ValidTypes> = {};
|
||||||
const [err, setErr] = React.useState(null);
|
|
||||||
const [loaded, setLoaded] = React.useState<boolean>(false);
|
|
||||||
|
|
||||||
const prevJson = React.useRef<ValidTypes>(undefined);
|
function useLocalForage(key: Key): [ILocalForage<ValidTypes>, Dispatch<SetStateAction<ValidTypes>>] {
|
||||||
React.useEffect(() => {
|
const [error, setError] = React.useState(null);
|
||||||
async function runAsync() {
|
const [data, setData] = React.useState(Cache[key]);
|
||||||
if (typeof json !== "undefined" && !_.isEqual(json, prevJson.current)) {
|
const [loading, setLoading] = React.useState(Loading[key]);
|
||||||
await localForage.setItem(item, JSON.stringify(json));
|
|
||||||
}
|
|
||||||
prevJson.current = json;
|
|
||||||
}
|
|
||||||
runAsync();
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
async function runAsync() {
|
async function runAsync() {
|
||||||
try {
|
try {
|
||||||
const serialized = await localForage.getItem<any>(item);
|
const serialized = await localForage.getItem<string>(key);
|
||||||
const parsed = JSON.parse(serialized);
|
const parsed = JSON.parse(serialized);
|
||||||
if (typeof json === "undefined" && !Object.is(parsed, null)) {
|
if (!Object.is(parsed, null)) {
|
||||||
setErr(null);
|
setError(null);
|
||||||
setJson(parsed);
|
setData(parsed);
|
||||||
|
Cache[key] = parsed;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
setErr(error);
|
setError(err);
|
||||||
|
} finally {
|
||||||
|
Loading[key] = false;
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoaded(true);
|
|
||||||
}
|
}
|
||||||
runAsync();
|
if(!loading && !Cache[key]) {
|
||||||
|
Loading[key] = true;
|
||||||
|
setLoading(true);
|
||||||
|
runAsync();
|
||||||
|
}
|
||||||
|
}, [loading, data, key]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!_.isEqual(Cache[key], data)) {
|
||||||
|
Cache[key] = _.merge(Cache[key], data);
|
||||||
|
localForage.setItem(key, JSON.stringify(Cache[key]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { data: json, setData: setJson, error: err, loading: !loaded };
|
const isLoading = loading || loading === undefined;
|
||||||
|
|
||||||
|
return [{ data, error, loading: isLoading }, setData];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useInterfaceLocalForage(): [
|
export function useInterfaceLocalForage():
|
||||||
ILocalForage<IInterfaceConfig | undefined>,
|
[ILocalForage<IInterfaceConfig>,
|
||||||
Dispatch<SetStateAction<IInterfaceConfig | undefined>>
|
Dispatch<SetStateAction<IInterfaceConfig>>]
|
||||||
] {
|
{
|
||||||
const result = useLocalForage("interface");
|
return useLocalForage("interface");
|
||||||
|
|
||||||
let returnVal = result;
|
|
||||||
if (!result.data?.queries) {
|
|
||||||
returnVal = {
|
|
||||||
...result,
|
|
||||||
data: {
|
|
||||||
wall: {},
|
|
||||||
queries: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return [returnVal, result.setData];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const ToastProvider: React.FC = ({ children }) => {
|
|||||||
key={toast.id}
|
key={toast.id}
|
||||||
onClose={() => removeToast(toast.id)}
|
onClose={() => removeToast(toast.id)}
|
||||||
className={toast.variant ?? "success"}
|
className={toast.variant ?? "success"}
|
||||||
delay={toast.delay ?? 5000}
|
delay={toast.delay ?? 3000}
|
||||||
>
|
>
|
||||||
<Toast.Header>
|
<Toast.Header>
|
||||||
<span className="mr-auto">{toast.header ?? "Stash"}</span>
|
<span className="mr-auto">{toast.header ?? "Stash"}</span>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import DurationUtils from "src/utils/duration";
|
import DurationUtils from "src/utils/duration";
|
||||||
import { ILabeledId, ILabeledValue } from "../types";
|
import { ILabeledId, ILabeledValue, IOptionType } from "../types";
|
||||||
|
|
||||||
export type CriterionType =
|
export type CriterionType =
|
||||||
| "none"
|
| "none"
|
||||||
@@ -30,7 +30,10 @@ export type CriterionType =
|
|||||||
| "piercings"
|
| "piercings"
|
||||||
| "aliases";
|
| "aliases";
|
||||||
|
|
||||||
export abstract class Criterion<Option = any, Value = any> {
|
type Option = string | number | IOptionType;
|
||||||
|
export type CriterionValue = string | number | ILabeledId[];
|
||||||
|
|
||||||
|
export abstract class Criterion {
|
||||||
public static getLabel(type: CriterionType = "none") {
|
public static getLabel(type: CriterionType = "none") {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "none":
|
case "none":
|
||||||
@@ -114,9 +117,17 @@ export abstract class Criterion<Option = any, Value = any> {
|
|||||||
public abstract modifier: CriterionModifier;
|
public abstract modifier: CriterionModifier;
|
||||||
public abstract modifierOptions: ILabeledValue[];
|
public abstract modifierOptions: ILabeledValue[];
|
||||||
public abstract options: Option[] | undefined;
|
public abstract options: Option[] | undefined;
|
||||||
public abstract value: Value;
|
public abstract value: CriterionValue;
|
||||||
public inputType: "number" | "text" | undefined;
|
public inputType: "number" | "text" | undefined;
|
||||||
|
|
||||||
|
public getLabelValue(): string {
|
||||||
|
if(typeof this.value === "string")
|
||||||
|
return this.value;
|
||||||
|
if(typeof this.value === "number")
|
||||||
|
return this.value.toString();
|
||||||
|
return this.value.map(v => v.label).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
public getLabel(): string {
|
public getLabel(): string {
|
||||||
let modifierString: string;
|
let modifierString: string;
|
||||||
switch (this.modifier) {
|
switch (this.modifier) {
|
||||||
@@ -163,27 +174,11 @@ export abstract class Criterion<Option = any, Value = any> {
|
|||||||
return `${Criterion.getLabel(this.type)} ${modifierString} ${valueString}`;
|
return `${Criterion.getLabel(this.type)} ${modifierString} ${valueString}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue() {
|
|
||||||
let valueString: string;
|
|
||||||
if (Array.isArray(this.value) && this.value.length > 0) {
|
|
||||||
let items = this.value;
|
|
||||||
if ((this.value as ILabeledId[])[0].label) {
|
|
||||||
items = this.value.map(item => item.label) as any;
|
|
||||||
}
|
|
||||||
valueString = items.join(", ");
|
|
||||||
} else if (typeof this.value === "string") {
|
|
||||||
valueString = this.value;
|
|
||||||
} else {
|
|
||||||
valueString = (this.value as any).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return valueString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getId(): string {
|
public getId(): string {
|
||||||
return `${this.parameterName}-${this.modifier.toString()}`; // TODO add values?
|
return `${this.parameterName}-${this.modifier.toString()}`; // TODO add values?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
public set(modifier: CriterionModifier, value: Value) {
|
public set(modifier: CriterionModifier, value: Value) {
|
||||||
this.modifier = modifier;
|
this.modifier = modifier;
|
||||||
if (Array.isArray(this.value)) {
|
if (Array.isArray(this.value)) {
|
||||||
@@ -192,6 +187,7 @@ export abstract class Criterion<Option = any, Value = any> {
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICriterionOption {
|
export interface ICriterionOption {
|
||||||
@@ -209,7 +205,7 @@ export class CriterionOption implements ICriterionOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StringCriterion extends Criterion<string, string> {
|
export class StringCriterion extends Criterion {
|
||||||
public type: CriterionType;
|
public type: CriterionType;
|
||||||
public parameterName: string;
|
public parameterName: string;
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
@@ -222,6 +218,10 @@ export class StringCriterion extends Criterion<string, string> {
|
|||||||
public options: string[] | undefined;
|
public options: string[] | undefined;
|
||||||
public value: string = "";
|
public value: string = "";
|
||||||
|
|
||||||
|
public getLabelValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(type: CriterionType, parameterName?: string, options?: string[]) {
|
constructor(type: CriterionType, parameterName?: string, options?: string[]) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ export class StringCriterion extends Criterion<string, string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberCriterion extends Criterion<number, number> {
|
export class NumberCriterion extends Criterion {
|
||||||
public type: CriterionType;
|
public type: CriterionType;
|
||||||
public parameterName: string;
|
public parameterName: string;
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
@@ -252,6 +252,10 @@ export class NumberCriterion extends Criterion<number, number> {
|
|||||||
public options: number[] | undefined;
|
public options: number[] | undefined;
|
||||||
public value: number = 0;
|
public value: number = 0;
|
||||||
|
|
||||||
|
public getLabelValue() {
|
||||||
|
return this.value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
constructor(type: CriterionType, parameterName?: string, options?: number[]) {
|
constructor(type: CriterionType, parameterName?: string, options?: number[]) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -267,7 +271,7 @@ export class NumberCriterion extends Criterion<number, number> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DurationCriterion extends Criterion<number, number> {
|
export class DurationCriterion extends Criterion {
|
||||||
public type: CriterionType;
|
public type: CriterionType;
|
||||||
public parameterName: string;
|
public parameterName: string;
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
export class FavoriteCriterion extends Criterion<string, string> {
|
export class FavoriteCriterion extends Criterion {
|
||||||
public type: CriterionType = "favorite";
|
public type: CriterionType = "favorite";
|
||||||
public parameterName: string = "filter_favorites";
|
public parameterName: string = "filter_favorites";
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
export class HasMarkersCriterion extends Criterion<string, string> {
|
export class HasMarkersCriterion extends Criterion {
|
||||||
public type: CriterionType = "hasMarkers";
|
public type: CriterionType = "hasMarkers";
|
||||||
public parameterName: string = "has_markers";
|
public parameterName: string = "has_markers";
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
export class IsMissingCriterion extends Criterion<string, string> {
|
export class IsMissingCriterion extends Criterion {
|
||||||
public type: CriterionType = "isMissing";
|
public type: CriterionType = "isMissing";
|
||||||
public parameterName: string = "is_missing";
|
public parameterName: string = "is_missing";
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
export class NoneCriterion extends Criterion<any, any> {
|
export class NoneCriterion extends Criterion {
|
||||||
public type: CriterionType = "none";
|
public type: CriterionType = "none";
|
||||||
public parameterName: string = "";
|
public parameterName: string = "";
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
public modifierOptions = [];
|
public modifierOptions = [];
|
||||||
public options: any;
|
public options: undefined;
|
||||||
public value: any;
|
public value: string = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NoneCriterionOption implements ICriterionOption {
|
export class NoneCriterionOption implements ICriterionOption {
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { ILabeledId } from "../types";
|
import { ILabeledId, IOptionType } from "../types";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
interface IOptionType {
|
export class PerformersCriterion extends Criterion {
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
image_path?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PerformersCriterion extends Criterion<IOptionType, ILabeledId[]> {
|
|
||||||
public type: CriterionType = "performers";
|
public type: CriterionType = "performers";
|
||||||
public parameterName: string = "performers";
|
public parameterName: string = "performers";
|
||||||
public modifier = CriterionModifier.IncludesAll;
|
public modifier = CriterionModifier.IncludesAll;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
export class RatingCriterion extends Criterion<number, number> {
|
export class RatingCriterion extends Criterion {
|
||||||
// TODO <number, number[]>
|
|
||||||
public type: CriterionType = "rating";
|
public type: CriterionType = "rating";
|
||||||
public parameterName: string = "rating";
|
public parameterName: string = "rating";
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
export class ResolutionCriterion extends Criterion<string, string> {
|
export class ResolutionCriterion extends Criterion {
|
||||||
// TODO <string, string[]>
|
|
||||||
public type: CriterionType = "resolution";
|
public type: CriterionType = "resolution";
|
||||||
public parameterName: string = "resolution";
|
public parameterName: string = "resolution";
|
||||||
public modifier = CriterionModifier.Equals;
|
public modifier = CriterionModifier.Equals;
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { ILabeledId } from "../types";
|
import { ILabeledId, IOptionType } from "../types";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
interface IOptionType {
|
export class StudiosCriterion extends Criterion {
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
image_path?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StudiosCriterion extends Criterion<IOptionType, ILabeledId[]> {
|
|
||||||
public type: CriterionType = "studios";
|
public type: CriterionType = "studios";
|
||||||
public parameterName: string = "studios";
|
public parameterName: string = "studios";
|
||||||
public modifier = CriterionModifier.Includes;
|
public modifier = CriterionModifier.Includes;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ILabeledId } from "../types";
|
import { ILabeledId, IOptionType } from "../types";
|
||||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||||
|
|
||||||
export class TagsCriterion extends Criterion<GQL.Tag, ILabeledId[]> {
|
export class TagsCriterion extends Criterion {
|
||||||
public type: CriterionType;
|
public type: CriterionType;
|
||||||
public parameterName: string;
|
public parameterName: string;
|
||||||
public modifier = GQL.CriterionModifier.IncludesAll;
|
public modifier = GQL.CriterionModifier.IncludesAll;
|
||||||
@@ -11,7 +11,7 @@ export class TagsCriterion extends Criterion<GQL.Tag, ILabeledId[]> {
|
|||||||
Criterion.getModifierOption(GQL.CriterionModifier.Includes),
|
Criterion.getModifierOption(GQL.CriterionModifier.Includes),
|
||||||
Criterion.getModifierOption(GQL.CriterionModifier.Excludes)
|
Criterion.getModifierOption(GQL.CriterionModifier.Excludes)
|
||||||
];
|
];
|
||||||
public options: GQL.Tag[] = [];
|
public options: IOptionType[] = [];
|
||||||
public value: ILabeledId[] = [];
|
public value: ILabeledId[] = [];
|
||||||
|
|
||||||
constructor(type: "tags" | "sceneTags") {
|
constructor(type: "tags" | "sceneTags") {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import queryString from "query-string";
|
import queryString, { ParsedQuery } from "query-string";
|
||||||
import {
|
import {
|
||||||
FindFilterType,
|
FindFilterType,
|
||||||
PerformerFilterType,
|
PerformerFilterType,
|
||||||
@@ -76,14 +76,15 @@ export class ListFilterModel {
|
|||||||
public displayMode: DisplayMode = DEFAULT_PARAMS.displayMode;
|
public displayMode: DisplayMode = DEFAULT_PARAMS.displayMode;
|
||||||
public displayModeOptions: DisplayMode[] = [];
|
public displayModeOptions: DisplayMode[] = [];
|
||||||
public criterionOptions: ICriterionOption[] = [];
|
public criterionOptions: ICriterionOption[] = [];
|
||||||
public criteria: Array<Criterion<any, any>> = [];
|
public criteria: Array<Criterion> = [];
|
||||||
public randomSeed = -1;
|
public randomSeed = -1;
|
||||||
|
|
||||||
private static createCriterionOption(criterion: CriterionType) {
|
private static createCriterionOption(criterion: CriterionType) {
|
||||||
return new CriterionOption(Criterion.getLabel(criterion), criterion);
|
return new CriterionOption(Criterion.getLabel(criterion), criterion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public constructor(filterMode: FilterMode, rawParms?: any) {
|
public constructor(filterMode: FilterMode, rawParms?: ParsedQuery<string>) {
|
||||||
|
const params = rawParms as IQueryParameters;
|
||||||
switch (filterMode) {
|
switch (filterMode) {
|
||||||
case FilterMode.Scenes:
|
case FilterMode.Scenes:
|
||||||
this.sortBy = "date";
|
this.sortBy = "date";
|
||||||
@@ -187,11 +188,10 @@ export class ListFilterModel {
|
|||||||
this.displayMode = this.displayModeOptions[0];
|
this.displayMode = this.displayModeOptions[0];
|
||||||
}
|
}
|
||||||
this.sortByOptions = [...this.sortByOptions, "created_at", "updated_at"];
|
this.sortByOptions = [...this.sortByOptions, "created_at", "updated_at"];
|
||||||
if (rawParms) this.configureFromQueryParameters(rawParms);
|
if (params) this.configureFromQueryParameters(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public configureFromQueryParameters(rawParms: any) {
|
public configureFromQueryParameters(params: IQueryParameters) {
|
||||||
const params = rawParms as IQueryParameters;
|
|
||||||
if (params.sortby !== undefined) {
|
if (params.sortby !== undefined) {
|
||||||
this.sortBy = params.sortby;
|
this.sortBy = params.sortby;
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ export class ListFilterModel {
|
|||||||
if (params.c !== undefined) {
|
if (params.c !== undefined) {
|
||||||
this.criteria = [];
|
this.criteria = [];
|
||||||
|
|
||||||
let jsonParameters: any[];
|
let jsonParameters: string[];
|
||||||
if (params.c instanceof Array) {
|
if (params.c instanceof Array) {
|
||||||
jsonParameters = params.c;
|
jsonParameters = params.c;
|
||||||
} else {
|
} else {
|
||||||
@@ -268,10 +268,11 @@ export class ListFilterModel {
|
|||||||
public makeQueryParameters(): string {
|
public makeQueryParameters(): string {
|
||||||
const encodedCriteria: string[] = [];
|
const encodedCriteria: string[] = [];
|
||||||
this.criteria.forEach(criterion => {
|
this.criteria.forEach(criterion => {
|
||||||
const encodedCriterion: any = {};
|
const encodedCriterion:Partial<Criterion> = {
|
||||||
encodedCriterion.type = criterion.type;
|
type: criterion.type,
|
||||||
encodedCriterion.value = criterion.value;
|
value: criterion.value,
|
||||||
encodedCriterion.modifier = criterion.modifier;
|
modifier: criterion.modifier,
|
||||||
|
};
|
||||||
const jsonCriterion = JSON.stringify(encodedCriterion);
|
const jsonCriterion = JSON.stringify(encodedCriterion);
|
||||||
encodedCriteria.push(jsonCriterion);
|
encodedCriteria.push(jsonCriterion);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,3 +21,9 @@ export interface ILabeledValue {
|
|||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IOptionType {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
image_path?: string;
|
||||||
|
}
|
||||||
|
|||||||
2
ui/v2.5/src/models/react-jw-player.d.ts
vendored
2
ui/v2.5/src/models/react-jw-player.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
declare module "react-jw-player" {
|
declare module "react-jw-player" {
|
||||||
// typing module default export as `any` will allow you to access its members without compiler warning
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const ReactJSPlayer: any;
|
const ReactJSPlayer: any;
|
||||||
export default ReactJSPlayer;
|
export default ReactJSPlayer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const flattenMessages = (nestedMessages: any, prefix = "") => {
|
const flattenMessages = (nestedMessages: any, prefix = "") => {
|
||||||
if (nestedMessages === null) {
|
if (nestedMessages === null) {
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const playerID = "main-jwplayer";
|
const playerID = "main-jwplayer";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const getPlayer = () => (window as any).jwplayer(playerID);
|
const getPlayer = () => (window as any).jwplayer(playerID);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
Reference in New Issue
Block a user