Add Handy / Funscript support (#1377)

* Add funscript route to scenes

Adds a /scene/:id/funscript route which serves a funscript file, if present.

Current convention is that these are files stored with the same path, but with the extension ".funscript".

* Look for funscript during scan

This is stored in the Scene record and used to drive UI changes for funscript support.

Currently, that's limited to a funscript link in the Scene's file info.

* Add filtering and sorting for interactive
* Add Handy connection key to interface config
* Add Handy client and placeholder component.

Uses defucilis/thehandy, but not thehandy-react as I had difficulty integrating the context with the existing components.

Instead, the expensive calculation for the server time offset is put in localStorage for reuse.

A debounce was added when scrubbing the video, as otherwise it spammed the Handy API with updates to the current offset.
This commit is contained in:
UnluckyChemical765
2021-05-23 20:34:28 -07:00
committed by GitHub
parent 33999d3e93
commit 547f6d79ad
32 changed files with 301 additions and 24 deletions

View File

@@ -53,7 +53,8 @@ export type CriterionType =
| "performer_count"
| "death_year"
| "url"
| "stash_id";
| "stash_id"
| "interactive";
type Option = string | number | IOptionType;
export type CriterionValue = string | number | ILabeledId[];
@@ -153,6 +154,8 @@ export abstract class Criterion {
return "URL";
case "stash_id":
return "StashID";
case "interactive":
return "Interactive";
}
}

View File

@@ -0,0 +1,16 @@
import { CriterionModifier } from "src/core/generated-graphql";
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
export class InteractiveCriterion extends Criterion {
public type: CriterionType = "interactive";
public parameterName: string = "interactive";
public modifier = CriterionModifier.Equals;
public modifierOptions = [];
public options: string[] = [true.toString(), false.toString()];
public value: string = "";
}
export class InteractiveCriterionOption implements ICriterionOption {
public label: string = Criterion.getLabel("interactive");
public value: CriterionType = "interactive";
}

View File

@@ -28,6 +28,7 @@ import { TagsCriterion } from "./tags";
import { GenderCriterion } from "./gender";
import { MoviesCriterion } from "./movies";
import { GalleriesCriterion } from "./galleries";
import { InteractiveCriterion } from "./interactive";
export function makeCriteria(type: CriterionType = "none") {
switch (type) {
@@ -109,5 +110,7 @@ export function makeCriteria(type: CriterionType = "none") {
case "url":
case "stash_id":
return new StringCriterion(type, type);
case "interactive":
return new InteractiveCriterion();
}
}

View File

@@ -74,6 +74,10 @@ import { DisplayMode, FilterMode } from "./types";
import { GenderCriterionOption, GenderCriterion } from "./criteria/gender";
import { MoviesCriterionOption, MoviesCriterion } from "./criteria/movies";
import { GalleriesCriterion } from "./criteria/galleries";
import {
InteractiveCriterion,
InteractiveCriterionOption,
} from "./criteria/interactive";
interface IQueryParameters {
perPage?: string;
@@ -136,6 +140,7 @@ export class ListFilterModel {
"performer_count",
"random",
"movie_scene_number",
"interactive",
];
this.displayModeOptions = [
DisplayMode.Grid,
@@ -162,6 +167,7 @@ export class ListFilterModel {
new MoviesCriterionOption(),
ListFilterModel.createCriterionOption("url"),
ListFilterModel.createCriterionOption("stash_id"),
new InteractiveCriterionOption(),
];
break;
case FilterMode.Images:
@@ -671,6 +677,11 @@ export class ListFilterModel {
};
break;
}
case "interactive": {
result.interactive =
(criterion as InteractiveCriterion).value === "true";
break;
}
// no default
}
});