Add scene duration filter (#313)

This commit is contained in:
Infinite
2020-01-23 14:44:35 +01:00
parent dda36f6b09
commit 71dd939806
7 changed files with 138 additions and 64 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Button, ButtonGroup, InputGroup, Form } from "react-bootstrap"; import { Button, ButtonGroup, InputGroup, Form } from "react-bootstrap";
import { Icon } from "src/components/Shared"; import { Icon } from "src/components/Shared";
import { TextUtils } from "src/utils"; import { DurationUtils } from "src/utils";
interface IProps { interface IProps {
disabled?: boolean; disabled?: boolean;
@@ -11,67 +11,21 @@ interface IProps {
} }
export const DurationInput: React.FC<IProps> = (props: IProps) => { export const DurationInput: React.FC<IProps> = (props: IProps) => {
const [value, setValue] = useState<string>( const [value, setValue] = useState<string>(DurationUtils.secondsToString(props.numericValue));
secondsToString(props.numericValue)
);
useEffect(() => { useEffect(() => {
setValue(secondsToString(props.numericValue)); setValue(DurationUtils.secondsToString(props.numericValue));
}, [props.numericValue]); }, [props.numericValue]);
function secondsToString(seconds: number) {
let ret = TextUtils.secondsToTimestamp(seconds);
if (ret.startsWith("00:")) {
ret = ret.substr(3);
if (ret.startsWith("0")) {
ret = ret.substr(1);
}
}
return ret;
}
function stringToSeconds(v: string) {
if (!v) {
return 0;
}
const splits = v.split(":");
if (splits.length > 3) {
return 0;
}
let seconds = 0;
let factor = 1;
while (splits.length > 0) {
const thisSplit = splits.pop();
if (thisSplit === undefined) {
return 0;
}
const thisInt = parseInt(thisSplit, 10);
if (Number.isNaN(thisInt)) {
return 0;
}
seconds += factor * thisInt;
factor *= 60;
}
return seconds;
}
function increment() { function increment() {
let seconds = stringToSeconds(value); let seconds = DurationUtils.stringToSeconds(value);
seconds += 1; seconds += 1;
props.onValueChange(seconds); props.onValueChange(seconds);
} }
function decrement() { function decrement() {
let seconds = stringToSeconds(value); let seconds = DurationUtils.stringToSeconds(value);
seconds -= 1; seconds -= 1;
props.onValueChange(seconds); props.onValueChange(seconds);
} }
@@ -112,7 +66,7 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
disabled={props.disabled} disabled={props.disabled}
value={value} value={value}
onChange={(e: any) => setValue(e.target.value)} onChange={(e: any) => setValue(e.target.value)}
onBlur={() => props.onValueChange(stringToSeconds(value))} onBlur={() => props.onValueChange(DurationUtils.stringToSeconds(value))}
placeholder="hh:mm:ss" placeholder="hh:mm:ss"
/> />
<InputGroup.Append> <InputGroup.Append>

View File

@@ -5,7 +5,8 @@ import { Icon, FilterSelect } from "src/components/Shared";
import { CriterionModifier } from "src/core/generated-graphql"; import { CriterionModifier } from "src/core/generated-graphql";
import { import {
Criterion, Criterion,
CriterionType CriterionType,
DurationCriterion
} 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 { PerformersCriterion } from "src/models/list-filter/criteria/performers";
@@ -13,6 +14,7 @@ import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
import { TagsCriterion } from "src/models/list-filter/criteria/tags"; 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";
import { DurationInput } from "src/components/Shared";
interface IAddFilterProps { interface IAddFilterProps {
onAddCriterion: (criterion: Criterion, oldId?: string) => void; onAddCriterion: (criterion: Criterion, oldId?: string) => void;
@@ -66,6 +68,11 @@ export const AddFilter: React.FC<IAddFilterProps> = (
valueStage.current = event.target.value; valueStage.current = event.target.value;
} }
function onChangedDuration(valueAsNumber: number) {
valueStage.current = valueAsNumber;
onBlurInput();
}
function onBlurInput() { function onBlurInput() {
const newCriterion = _.cloneDeep(criterion); const newCriterion = _.cloneDeep(criterion);
newCriterion.value = valueStage.current; newCriterion.value = valueStage.current;
@@ -170,6 +177,15 @@ export const AddFilter: React.FC<IAddFilterProps> = (
</Form.Control> </Form.Control>
); );
} }
if (criterion instanceof DurationCriterion) {
// render duration control
return (
<DurationInput
numericValue={criterion.value ? criterion.value : 0}
onValueChange={onChangedDuration}
/>
)
}
return ( return (
<Form.Control <Form.Control
type={criterion.inputType} type={criterion.inputType}

View File

@@ -2,11 +2,13 @@
import { CriterionModifier } from "src/core/generated-graphql"; import { CriterionModifier } from "src/core/generated-graphql";
import { ILabeledId, ILabeledValue } from "../types"; import { ILabeledId, ILabeledValue } from "../types";
import { DurationUtils } from 'src/utils';
export type CriterionType = export type CriterionType =
| "none" | "none"
| "rating" | "rating"
| "resolution" | "resolution"
| "duration"
| "favorite" | "favorite"
| "hasMarkers" | "hasMarkers"
| "isMissing" | "isMissing"
@@ -36,6 +38,7 @@ export abstract class Criterion<Option = any, Value = any> {
return "Rating"; return "Rating";
case "resolution": case "resolution":
return "Resolution"; return "Resolution";
case "duration": return "Duration";
case "favorite": case "favorite":
return "Favorite"; return "Favorite";
case "hasMarkers": case "hasMarkers":
@@ -144,13 +147,18 @@ export abstract class Criterion<Option = any, Value = any> {
modifierString = ""; modifierString = "";
} }
let valueString = "";
if (this.modifier !== CriterionModifier.IsNull && this.modifier !== CriterionModifier.NotNull) {
valueString = this.getLabelValue();
}
return `${Criterion.getLabel(this.type)} ${modifierString} ${valueString}`;
}
public getLabelValue() {
let valueString: string; let valueString: string;
if ( if (Array.isArray(this.value) && this.value.length > 0) {
this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull
) {
valueString = "";
} else if (Array.isArray(this.value) && this.value.length > 0) {
let items = this.value; let items = this.value;
if ((this.value as ILabeledId[])[0].label) { if ((this.value as ILabeledId[])[0].label) {
items = this.value.map(item => item.label) as any; items = this.value.map(item => item.label) as any;
@@ -162,7 +170,7 @@ export abstract class Criterion<Option = any, Value = any> {
valueString = (this.value as any).toString(); valueString = (this.value as any).toString();
} }
return `${Criterion.getLabel(this.type)} ${modifierString} ${valueString}`; return valueString;
} }
public getId(): string { public getId(): string {
@@ -251,3 +259,34 @@ export class NumberCriterion extends Criterion<number, number> {
} }
} }
} }
export class DurationCriterion extends Criterion<number, number> {
public type: CriterionType;
public parameterName: string;
public modifier = CriterionModifier.Equals;
public modifierOptions = [
Criterion.getModifierOption(CriterionModifier.Equals),
Criterion.getModifierOption(CriterionModifier.NotEquals),
Criterion.getModifierOption(CriterionModifier.GreaterThan),
Criterion.getModifierOption(CriterionModifier.LessThan),
];
public options: number[] | undefined;
public value: number = 0;
constructor(type : CriterionType, parameterName?: string, options? : number[]) {
super();
this.type = type;
this.options = options;
if (!!parameterName) {
this.parameterName = parameterName;
} else {
this.parameterName = type;
}
}
public getLabelValue() {
return DurationUtils.secondsToString(this.value);
}
}

View File

@@ -4,7 +4,8 @@ import {
Criterion, Criterion,
CriterionType, CriterionType,
StringCriterion, StringCriterion,
NumberCriterion NumberCriterion,
DurationCriterion
} from "./criterion"; } from "./criterion";
import { FavoriteCriterion } from "./favorite"; import { FavoriteCriterion } from "./favorite";
import { HasMarkersCriterion } from "./has-markers"; import { HasMarkersCriterion } from "./has-markers";
@@ -24,6 +25,7 @@ export function makeCriteria(type: CriterionType = "none") {
return new RatingCriterion(); return new RatingCriterion();
case "resolution": case "resolution":
return new ResolutionCriterion(); return new ResolutionCriterion();
case "duration": return new DurationCriterion(type, type);
case "favorite": case "favorite":
return new FavoriteCriterion(); return new FavoriteCriterion();
case "hasMarkers": case "hasMarkers":
@@ -39,7 +41,7 @@ export function makeCriteria(type: CriterionType = "none") {
case "studios": case "studios":
return new StudiosCriterion(); return new StudiosCriterion();
case "birth_year": case "birth_year": return new NumberCriterion(type, type);
case "age": { case "age": {
const ret = new NumberCriterion(type, type); const ret = new NumberCriterion(type, type);
// null/not null doesn't make sense for these criteria // null/not null doesn't make sense for these criteria

View File

@@ -13,7 +13,8 @@ import {
CriterionType, CriterionType,
CriterionOption, CriterionOption,
NumberCriterion, NumberCriterion,
StringCriterion StringCriterion,
DurationCriterion
} from "./criteria/criterion"; } from "./criteria/criterion";
import { import {
FavoriteCriterion, FavoriteCriterion,
@@ -69,6 +70,10 @@ export class ListFilterModel {
public criterionOptions: ICriterionOption[] = []; public criterionOptions: ICriterionOption[] = [];
public criteria: Array<Criterion<any, any>> = []; public criteria: Array<Criterion<any, any>> = [];
private static createCriterionOption(criterion: CriterionType) {
return new CriterionOption(Criterion.getLabel(criterion), criterion);
}
public constructor(filterMode: FilterMode, rawParms?: any) { public constructor(filterMode: FilterMode, rawParms?: any) {
switch (filterMode) { switch (filterMode) {
case FilterMode.Scenes: case FilterMode.Scenes:
@@ -95,6 +100,7 @@ export class ListFilterModel {
new NoneCriterionOption(), new NoneCriterionOption(),
new RatingCriterionOption(), new RatingCriterionOption(),
new ResolutionCriterionOption(), new ResolutionCriterionOption(),
ListFilterModel.createCriterionOption("duration"),
new HasMarkersCriterionOption(), new HasMarkersCriterionOption(),
new IsMissingCriterionOption(), new IsMissingCriterionOption(),
new TagsCriterionOption(), new TagsCriterionOption(),
@@ -130,7 +136,7 @@ export class ListFilterModel {
this.criterionOptions = this.criterionOptions.concat( this.criterionOptions = this.criterionOptions.concat(
numberCriteria.concat(stringCriteria).map(c => { numberCriteria.concat(stringCriteria).map(c => {
return new CriterionOption(Criterion.getLabel(c), c); return ListFilterModel.createCriterionOption(c);
}) })
); );
break; break;
@@ -291,6 +297,11 @@ export class ListFilterModel {
} }
break; break;
} }
case "duration": {
const durationCrit = criterion as DurationCriterion;
result.duration = { value: durationCrit.value, modifier: durationCrit.modifier }
break;
}
case "hasMarkers": case "hasMarkers":
result.has_markers = (criterion as HasMarkersCriterion).value; result.has_markers = (criterion as HasMarkersCriterion).value;
break; break;

View File

@@ -0,0 +1,51 @@
import TextUtils from "./text";
const secondsToString = (seconds : number) => {
let ret = TextUtils.secondsToTimestamp(seconds);
if (ret.startsWith("00:")) {
ret = ret.substr(3);
if (ret.startsWith("0")) {
ret = ret.substr(1);
}
}
return ret;
}
const stringToSeconds = (v : string) => {
if (!v) {
return 0;
}
const splits = v.split(":");
if (splits.length > 3) {
return 0;
}
let seconds = 0;
let factor = 1;
while(splits.length > 0) {
const thisSplit = splits.pop();
if (thisSplit === undefined) {
return 0;
}
const thisInt = parseInt(thisSplit, 10);
if (Number.isNaN(thisInt)) {
return 0;
}
seconds += factor * thisInt;
factor *= 60;
}
return seconds;
}
export default {
secondsToString,
stringToSeconds
};

View File

@@ -2,3 +2,4 @@ export { default as ImageUtils } from "./image";
export { default as NavUtils } from "./navigation"; export { default as NavUtils } from "./navigation";
export { default as TableUtils } from "./table"; export { default as TableUtils } from "./table";
export { default as TextUtils } from "./text"; export { default as TextUtils } from "./text";
export { default as DurationUtils } from './duration';