Linting update

This commit is contained in:
Infinite
2020-01-20 21:25:47 +01:00
parent c83e0898f9
commit 9827647122
58 changed files with 789 additions and 737 deletions

View File

@@ -36,6 +36,14 @@
"spaced-comment": ["error", "always", {
"markers": ["/"]
}],
"max-classes-per-file": "off"
"max-classes-per-file": "off",
"no-plusplus": "off",
"prefer-destructuring": ["error", {"object": true, "array": false}],
"default-case": "off",
"consistent-return": "off",
"@typescript-eslint/no-use-before-define": ["error", { "functions": false, "classes": true }],
"no-underscore-dangle": "off",
"no-nested-ternary": "off",
"jsx-a11y/media-has-caption": "off"
}
}

View File

@@ -1,5 +1,8 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import { ToastProvider } from 'src/hooks/Toast';
import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { ErrorBoundary } from "./components/ErrorBoundary";
import Galleries from "./components/Galleries/Galleries";
import { MainNavbar } from "./components/MainNavbar";
@@ -11,10 +14,7 @@ import { Stats } from "./components/Stats";
import Studios from "./components/Studios/Studios";
import Tags from "./components/Tags/Tags";
import { SceneFilenameParser } from "./components/scenes/SceneFilenameParser";
import { ToastProvider } from 'src/hooks/Toast';
import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
import 'bootstrap/dist/css/bootstrap.min.css';
@@ -27,7 +27,7 @@ export const App: React.FC = () => (
<ToastProvider>
<div className="main">
<Switch>
<Route exact={true} path="/" component={Stats} />
<Route exact path="/" component={Stats} />
<Route path="/scenes" component={Scenes} />
{/* <Route path="/scenes/:id" component={Scene} /> */}
<Route path="/galleries" component={Galleries} />

View File

@@ -5,7 +5,7 @@ import { GalleryList } from "./GalleryList";
const Galleries = () => (
<Switch>
<Route exact={true} path="/galleries" component={GalleryList} />
<Route exact path="/galleries" component={GalleryList} />
<Route path="/galleries/:id" component={Gallery} />
</Switch>
);

View File

@@ -16,7 +16,7 @@ export const GalleryList: React.FC = () => {
if (!result.data || !result.data.findGalleries) { return; }
if (filter.displayMode === DisplayMode.Grid) {
return <h1>TODO</h1>;
} else if (filter.displayMode === DisplayMode.List) {
} if (filter.displayMode === DisplayMode.List) {
return (
<Table style={{margin: "0 auto"}}>
<thead>
@@ -39,7 +39,7 @@ export const GalleryList: React.FC = () => {
</tbody>
</Table>
);
} else if (filter.displayMode === DisplayMode.Wall) {
} if (filter.displayMode === DisplayMode.Wall) {
return <h1>TODO</h1>;
}
}

View File

@@ -69,7 +69,7 @@ export const MainNavbar: React.FC = () => {
{menuItems.map((i) => (
<LinkContainer
activeClassName="active"
exact={true}
exact
to={i.href}
key={i.href}
>
@@ -83,7 +83,7 @@ export const MainNavbar: React.FC = () => {
<Nav>
{newButton}
<LinkContainer
exact={true}
exact
to="/settings">
<Button variant="secondary">
<Icon icon="cog" />

View File

@@ -89,6 +89,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
async function onSave() {
try {
const result = await updateGeneralConfig();
// eslint-disable-next-line no-console
console.log(result);
Toast.success({ content: "Updated config" });
} catch (e) {
@@ -203,7 +204,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="transcode-size">
<Form.Label>Maximum transcode size</Form.Label>
<Form.Control
as="select">
as="select"
onChange={(event:React.FormEvent<HTMLSelectElement>) => setMaxTranscodeSize(translateQuality(event.currentTarget.value))}
value={resolutionToString(maxTranscodeSize)}
>

View File

@@ -41,6 +41,7 @@ export const SettingsInterfacePanel: React.FC = () => {
async function onSave() {
try {
const result = await updateInterfaceConfig();
// eslint-disable-next-line no-console
console.log(result);
Toast.success({ content: "Updated config" });
} catch (e) {
@@ -94,7 +95,7 @@ export const SettingsInterfacePanel: React.FC = () => {
<Form.Control
type="number"
defaultValue={maximumLoopDuration}
onChange={(event:React.FormEvent<HTMLInputElement>) => setMaximumLoopDuration(Number.parseInt(event.currentTarget.value) ?? 0)}
onChange={(event:React.FormEvent<HTMLInputElement>) => setMaximumLoopDuration(Number.parseInt(event.currentTarget.value, 10) ?? 0)}
min={0}
step={1}
/>

View File

@@ -5,19 +5,19 @@ import { StashService } from "src/core/StashService";
function convertTime(logEntry: GQL.LogEntryDataFragment) {
function pad(val : number) {
var ret = val.toString();
let ret = val.toString();
if (val <= 9) {
ret = "0" + ret;
ret = `0${ ret}`;
}
return ret;
}
var date = new Date(logEntry.time);
var month = date.getMonth() + 1;
var day = date.getDate();
var dateStr = date.getFullYear() + "-" + pad(month) + "-" + pad(day);
dateStr += " " + pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds());
const date = new Date(logEntry.time);
const month = date.getMonth() + 1;
const day = date.getDate();
let dateStr = `${date.getFullYear() }-${ pad(month) }-${ pad(day)}`;
dateStr += ` ${ pad(date.getHours()) }:${ pad(date.getMinutes()) }:${ pad(date.getSeconds())}`;
return dateStr;
}
@@ -32,7 +32,7 @@ interface ILogElementProps {
const LogElement: React.FC<ILogElementProps> = ({ logEntry }) => {
// pad to maximum length of level enum
var level = logEntry.level.padEnd(GQL.LogLevel.Progress.length);
const level = logEntry.level.padEnd(GQL.LogLevel.Progress.length);
return (
<>
@@ -58,7 +58,7 @@ class LogEntry {
this.level = logEntry.level;
this.message = logEntry.message;
var id = LogEntry.nextId++;
const id = LogEntry.nextId++;
this.id = id.toString();
}
}
@@ -80,15 +80,15 @@ export const SettingsLogsPanel: React.FC = () => {
.filter(filterByLogLevel).slice(0, MAX_LOG_ENTRIES);
const maybeRenderError = error
? <div className={"error"}>Error connecting to log server: {error.message}</div>
? <div className="error">Error connecting to log server: {error.message}</div>
: '';
function filterByLogLevel(logEntry : LogEntry) {
if (logLevel === "Debug")
return true;
var logLevelIndex = logLevels.indexOf(logLevel);
var levelIndex = logLevels.indexOf(logEntry.level);
const logLevelIndex = logLevels.indexOf(logLevel);
const levelIndex = logLevels.indexOf(logEntry.level);
return levelIndex >= logLevelIndex;
}

View File

@@ -21,8 +21,8 @@ export const SettingsTasksPanel: React.FC = () => {
const jobStatus = StashService.useJobStatus();
const metadataUpdate = StashService.useMetadataUpdate();
function statusToText(status : string) {
switch(status) {
function statusToText(s: string) {
switch(s) {
case "Idle":
return "Idle";
case "Scan":
@@ -37,15 +37,15 @@ export const SettingsTasksPanel: React.FC = () => {
return "Importing from JSON";
case "Auto Tag":
return "Auto tagging scenes";
default:
return "Idle"
}
return "Idle";
}
useEffect(() => {
if (!!jobStatus.data && !!jobStatus.data.jobStatus) {
if (jobStatus?.data?.jobStatus) {
setStatus(statusToText(jobStatus.data.jobStatus.status));
var newProgress = jobStatus.data.jobStatus.progress;
const newProgress = jobStatus.data.jobStatus.progress;
if (newProgress < 0) {
setProgress(0);
} else {
@@ -55,9 +55,9 @@ export const SettingsTasksPanel: React.FC = () => {
}, [jobStatus.data]);
useEffect(() => {
if (!!metadataUpdate.data && !!metadataUpdate.data.metadataUpdate) {
if (metadataUpdate?.data?.metadataUpdate) {
setStatus(statusToText(metadataUpdate.data.metadataUpdate.status));
var newProgress = metadataUpdate.data.metadataUpdate.progress;
const newProgress = metadataUpdate.data.metadataUpdate.progress;
if (newProgress < 0) {
setProgress(0);
} else {
@@ -111,7 +111,7 @@ export const SettingsTasksPanel: React.FC = () => {
async function onScan() {
try {
await StashService.queryMetadataScan({useFileMetadata: useFileMetadata});
await StashService.queryMetadataScan({useFileMetadata});
Toast.success({ content: "Started scan" });
jobStatus.refetch();
} catch (e) {
@@ -120,7 +120,7 @@ export const SettingsTasksPanel: React.FC = () => {
}
function getAutoTagInput() {
var wildcard = ["*"];
const wildcard = ["*"];
return {
performers: autoTagPerformers ? wildcard : [],
studios: autoTagStudios ? wildcard : [],
@@ -210,7 +210,7 @@ export const SettingsTasksPanel: React.FC = () => {
<Form.Group>
<Button>
<Link to={"/sceneFilenameParser"}>Scene Filename Parser</Link>
<Link to="/sceneFilenameParser">Scene Filename Parser</Link>
</Button>
</Form.Group>

View File

@@ -56,17 +56,16 @@ export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
}
function renderScraperMenu() {
if (!props.performer) { return; }
if (!props.isEditing) { return; }
if (!props.performer || !props.isEditing) { return; }
const popover = (
<Popover id="scraper-popover">
<Popover.Content>
<div>
{ props.scrapers ? props.scrapers.map((s) => (
<div onClick={() => props.onDisplayScraperDialog && props.onDisplayScraperDialog(s) }>
<Button variant="link" onClick={() => props.onDisplayScraperDialog && props.onDisplayScraperDialog(s) }>
{s.name}
</div>
</Button>
)) : ''}
</div>
</Popover.Content>
@@ -82,7 +81,7 @@ export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
function renderAutoTagButton() {
if (props.isNew || props.isEditing) { return; }
if (!!props.onAutoTag) {
if (props.onAutoTag) {
return (<Button onClick={() => {
if (props.onAutoTag) { props.onAutoTag() }
}}>Auto Tag</Button>)
@@ -105,14 +104,7 @@ export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
}
function renderDeleteAlert() {
var name;
if (props.performer) {
name = props.performer.name;
}
if (props.studio) {
name = props.studio.name;
}
const name = props?.studio?.name ?? props?.performer?.name;
return (
<Modal

View File

@@ -36,7 +36,7 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
return 0;
}
let splits = v.split(":");
const splits = v.split(":");
if (splits.length > 3) {
return 0;
@@ -45,13 +45,13 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
let seconds = 0;
let factor = 1;
while(splits.length > 0) {
let thisSplit = splits.pop();
const thisSplit = splits.pop();
if (thisSplit === undefined) {
return 0;
}
let thisInt = parseInt(thisSplit, 10);
if (isNaN(thisInt)) {
const thisInt = parseInt(thisSplit, 10);
if (Number.isNaN(thisInt)) {
return 0;
}
@@ -77,7 +77,7 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
function renderButtons() {
return (
<ButtonGroup
vertical={true}
vertical
>
<Button
disabled={props.disabled}

View File

@@ -55,10 +55,8 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
{(!data || !data.directories || loading) ? <Spinner animation="border" variant="light" /> : ''}
</InputGroup.Append>
</InputGroup>
/>
{selectableDirectories.map((path) => {
return <div key={path} onClick={() => setCurrentDirectory(path)}>{path}</div>;
return <Button variant="link" key={path} onClick={() => setCurrentDirectory(path)}>{path}</Button>;
})}
</div>
</Modal.Body>

View File

@@ -0,0 +1,57 @@
import React, { useState, useCallback, useEffect, useRef } from 'react'
import { Overlay, Popover, OverlayProps } from 'react-bootstrap'
interface IHoverPopover {
enterDelay?: number;
leaveDelay?: number;
content: JSX.Element[] | JSX.Element | string;
className?: string;
placement?: OverlayProps["placement"];
}
export const HoverPopover: React.FC<IHoverPopover> = ({ enterDelay = 0, leaveDelay = 400, content, children, className, placement = 'top' }) => {
const [show, setShow] = useState(false);
const triggerRef = useRef<HTMLDivElement>(null);
const enterTimer = useRef<number>();
const leaveTimer = useRef<number>();
const handleMouseEnter = useCallback(() => {
window.clearTimeout(leaveTimer.current);
enterTimer.current = window.setTimeout(() => setShow(true), enterDelay);
}, [enterDelay]);
const handleMouseLeave = useCallback(() => {
window.clearTimeout(enterTimer.current);
leaveTimer.current = window.setTimeout(() => setShow(false), leaveDelay);
}, [leaveDelay]);
useEffect(() => (
() => {
window.clearTimeout(enterTimer.current)
window.clearTimeout(leaveTimer.current)
}
), []);
return (
<>
<div className={className} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} ref={triggerRef}>
{children}
</div>
{ triggerRef.current &&
<Overlay
show={show}
placement={placement}
target={triggerRef.current}
>
<Popover
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
id='popover'
>
{content}
</Popover>
</Overlay>
}
</>
);
}

View File

@@ -35,7 +35,7 @@ const ModalComponent: React.FC<IModal> = ({ children, show, icon, header, cancel
? <Button variant={cancel.variant ?? 'primary'} onClick={cancel.onClick}>{cancel.text ?? 'Cancel'}</Button>
: ''
}
{ <Button variant={accept?.variant ?? 'primary'} onClick={accept?.onClick}>{accept?.text ?? 'Close'}</Button> }
<Button variant={accept?.variant ?? 'primary'} onClick={accept?.onClick}>{accept?.text ?? 'Close'}</Button>
</div>
</Modal.Footer>
</Modal>

View File

@@ -42,6 +42,12 @@ interface ISceneGallerySelect {
sceneId: string;
onSelect: (item: GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined) => void;
}
const getSelectedValues = (selectedItems:ValueType<Option>) => (
(Array.isArray(selectedItems) ? selectedItems : [selectedItems])
.map(item => item.value)
);
export const SceneGallerySelect: React.FC<ISceneGallerySelect> = (props) => {
const { data, loading } = StashService.useValidGalleriesForScene(props.sceneId);
const galleries = data?.validGalleriesForScene ?? [];
@@ -72,7 +78,6 @@ export const ScrapePerformerSuggest: React.FC<IScrapePerformerSuggestProps> = (p
);
const performers = data?.scrapePerformerList ?? [];
console.log(`performers: ${performers}, loading: ${loading}, query: ${query}`);
const items = performers.map(item => ({ label: item.name ?? '', value: item.name ?? '' }));
return <SelectComponent onChange={onChange} onInputChange={onInputChange} isLoading={loading} items={items} initialIds={[]} placeholder={props.placeholder} />
}
@@ -139,6 +144,8 @@ export const TagSelect: React.FC<IFilterProps> = (props) => {
const placeholder = props.noSelectionString ?? "Select tags..."
const tags = data?.allTags ?? [];
const selected = tags.filter(tag => selectedIds.indexOf(tag.id) !== -1).map(tag => ({value: tag.id, label: tag.name}));
const items:Option[] = tags.map(item => ({ value: item.id, label: item.name }));
const onCreate = async (tagName: string) => {
try {
@@ -158,15 +165,24 @@ export const TagSelect: React.FC<IFilterProps> = (props) => {
};
const onChange = (selectedItems:ValueType<Option>) => {
const selected = getSelectedValues(selectedItems);
setSelectedIds(selected);
props.onSelect(tags.filter(item => selected.indexOf(item.id) !== -1));
const selectedValues = getSelectedValues(selectedItems);
setSelectedIds(selectedValues);
props.onSelect(tags.filter(item => selectedValues.indexOf(item.id) !== -1));
};
const selected = tags.filter(tag => selectedIds.indexOf(tag.id) !== -1).map(tag => ({value: tag.id, label: tag.name}));
const items:Option[] = tags.map(item => ({ value: item.id, label: item.name }));
return <SelectComponent {...props} onChange={onChange} creatable={true} type="tags" placeholder={placeholder}
isLoading={loading || dataLoading} items={items} onCreateOption={onCreate} selectedOptions={selected} />
return (
<SelectComponent
{...props}
onChange={onChange}
creatable
type="tags"
placeholder={placeholder}
isLoading={loading || dataLoading}
items={items}
onCreateOption={onCreate}
selectedOptions={selected}
/>
);
}
const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
@@ -186,14 +202,14 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
const defaultValue = items.filter(item => initialIds?.indexOf(item.value) !== -1) ?? null;
const props = {
className: className,
className,
options: items,
value: selectedOptions,
onChange: onChange,
isMulti: isMulti,
defaultValue: defaultValue,
onChange,
isMulti,
defaultValue,
noOptionsMessage: () => (type !== 'tags' ? 'None' : null),
placeholder: placeholder,
placeholder,
onInputChange
}
@@ -203,9 +219,3 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
: <Select {...props} isLoading={isLoading} />
);
};
const getSelectedValues = (selectedItems:ValueType<Option>) => (
(Array.isArray(selectedItems) ? selectedItems : [selectedItems])
.map(item => item.value)
);

View File

@@ -13,3 +13,4 @@ export { default as Modal } from './Modal';
export { DetailsEditNavbar } from './DetailsEditNavbar';
export { DurationInput } from './DurationInput';
export { TagLink } from './TagLink';
export { HoverPopover } from './HoverPopover';

View File

@@ -49,7 +49,7 @@ export const Stats: FunctionComponent = () => {
<Spinner animation="border" role="status" size="sm">
<span className="sr-only">Loading...</span>
</Spinner> : undefined}
{!!error ? <span>error.message</span> : undefined}
{error ? <span>error.message</span> : undefined}
{renderStats()}
<h3>Notes</h3>

View File

@@ -1,3 +1,5 @@
/* eslint-disable react/no-this-in-sfc */
import { Form, Spinner, Table } from 'react-bootstrap';
import React, { useEffect, useState } from "react";
import { useParams, useHistory } from 'react-router-dom';
@@ -36,11 +38,11 @@ export const Studio: React.FC = () => {
setUrl(state.url);
}
function updateStudioData(studio:Partial<GQL.StudioDataFragment>) {
function updateStudioData(studioData: Partial<GQL.StudioDataFragment>) {
setImage(undefined);
updateStudioEditState(studio);
setImagePreview(studio.image_path);
setStudio(studio);
updateStudioEditState(studioData);
setImagePreview(studioData.image_path);
setStudio(studioData);
}
useEffect(() => {

View File

@@ -19,9 +19,9 @@ export const StudioList: React.FC = () => {
{result.data.findStudios.studios.map((studio) => (<StudioCard key={studio.id} studio={studio} />))}
</div>
);
} else if (filter.displayMode === DisplayMode.List) {
} if (filter.displayMode === DisplayMode.List) {
return <h1>TODO</h1>;
} else if (filter.displayMode === DisplayMode.Wall) {
} if (filter.displayMode === DisplayMode.Wall) {
return <h1>TODO</h1>;
}
}

View File

@@ -5,7 +5,7 @@ import { StudioList } from "./StudioList";
const Studios = () => (
<Switch>
<Route exact={true} path="/studios" component={StudioList} />
<Route exact path="/studios" component={StudioList} />
<Route path="/studios/:id" component={Studio} />
</Switch>
);

View File

@@ -92,7 +92,7 @@ export const TagList: React.FC = () => {
<>
{deleteAlert}
<div key={tag.id} className="tag-list-row">
<span onClick={() => setEditingTag(tag)}>{tag.name}</span>
<Button variant="link" onClick={() => setEditingTag(tag)}>{tag.name}</Button>
<div style={{float: "right"}}>
<Button onClick={() => onAutoTag(tag)}>Auto Tag</Button>
<Link to={NavUtils.makeTagScenesUrl(tag)}>Scenes: {tag.scene_count}</Link>

View File

@@ -4,7 +4,7 @@ import { TagList } from "./TagList";
const Tags = () => (
<Switch>
<Route exact={true} path="/tags" component={TagList} />
<Route exact path="/tags" component={TagList} />
</Switch>
);

View File

@@ -27,9 +27,9 @@ export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProp
function onMouseEnter() {
VideoHoverHook.onMouseEnter(videoHoverHook);
if (!videoPath || videoPath === "") {
if (!!props.sceneMarker) {
if (props.sceneMarker) {
setVideoPath(props.sceneMarker.stream || "");
} else if (!!props.scene) {
} else if (props.scene) {
setVideoPath(props.scene.paths.preview || "");
}
}
@@ -95,7 +95,7 @@ export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProp
const className = ["scene-wall-item-container"];
if (videoHoverHook.isHovering.current) { className.push("double-scale"); }
const style: React.CSSProperties = {};
if (!!props.origin) { style.transformOrigin = props.origin; }
if (props.origin) { style.transformOrigin = props.origin; }
return (
<div className="wall grid-item">
<div
@@ -111,8 +111,8 @@ export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProp
src={videoPath}
poster={screenshotPath}
style={videoHoverHook.isHovering.current ? {} : {display: "none"}}
autoPlay={true}
loop={true}
autoPlay
loop
ref={videoHoverHook.videoEl}
/>
<img alt="Preview" src={previewPath || screenshotPath} onError={() => previewNotFound()} />

View File

@@ -1,7 +1,7 @@
import _ from "lodash";
import React, { useEffect, useRef, useState } from "react";
import { Button, Form, Modal, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { Icon } from 'src/components/Shared';
import { Icon , FilterSelect } from 'src/components/Shared';
import { CriterionModifier } from "src/core/generated-graphql";
import { Criterion, CriterionType } from "src/models/list-filter/criteria/criterion";
import { NoneCriterion } from "src/models/list-filter/criteria/none";
@@ -10,7 +10,7 @@ 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 { ListFilterModel } from "src/models/list-filter/filter";
import { FilterSelect } from "src/components/Shared";
interface IAddFilterProps {
onAddCriterion: (criterion: Criterion, oldId?: string) => void;
@@ -73,7 +73,7 @@ export const AddFilter: React.FC<IAddFilterProps> = (props: IAddFilterProps) =>
criterion.value = "";
}
}
const oldId = !!props.editingCriterion ? props.editingCriterion.getId() : undefined;
const oldId = props.editingCriterion ? props.editingCriterion.getId() : undefined;
props.onAddCriterion(criterion, oldId);
onToggle();
}
@@ -127,11 +127,13 @@ export const AddFilter: React.FC<IAddFilterProps> = (props: IAddFilterProps) =>
return (
<FilterSelect
type={type}
onSelect={(items) => criterion.value = items.map((i) => ({id: i.id, label: i.name!}))}
onSelect={(items) => {
criterion.value = items.map(i => ({id: i.id, label: i.name!})) }
}
initialIds={criterion.value.map((labeled: any) => labeled.id)}
/>
);
} else {
}
if (criterion.options) {
defaultValue.current = criterion.value;
return (
@@ -145,7 +147,7 @@ export const AddFilter: React.FC<IAddFilterProps> = (props: IAddFilterProps) =>
))}
</Form.Control>
);
} else {
}
return (
<Form.Control
type={criterion.inputType}
@@ -154,8 +156,8 @@ export const AddFilter: React.FC<IAddFilterProps> = (props: IAddFilterProps) =>
value={criterion.value || ""}
/>
)
}
}
}
return (
<>

View File

@@ -50,7 +50,7 @@ export const ListFilter: React.FC<IListFilterProps> = (props: IListFilterProps)
searchCallback(event);
}
function onChangeSortDirection(_: any) {
function onChangeSortDirection() {
if (props.filter.sortDirection === "asc") {
props.onChangeSortDirection("desc");
} else {
@@ -160,7 +160,7 @@ export const ListFilter: React.FC<IListFilterProps> = (props: IListFilterProps)
}
function renderMore() {
let options = [
const options = [
renderSelectAll(),
renderSelectNone()
];
@@ -201,7 +201,7 @@ export const ListFilter: React.FC<IListFilterProps> = (props: IListFilterProps)
type="range"
min={0}
max={3}
onChange={(event: any) => onChangeZoom(Number.parseInt(event.target.value))}
onChange={(event: any) => onChangeZoom(Number.parseInt(event.target.value, 10))}
/>
</span>
);

View File

@@ -6,85 +6,10 @@ interface IPaginationProps {
currentPage: number;
totalItems: number;
onChangePage: (page: number) => void;
loading?: boolean;
}
interface IPaginationState {
pages: number[];
totalPages: number;
}
export class Pagination extends React.Component<IPaginationProps, IPaginationState> {
constructor(props: IPaginationProps) {
super(props);
this.state = {
pages: [],
totalPages: Number.MAX_SAFE_INTEGER,
};
}
public componentWillMount() {
this.setPage(this.props.currentPage, false);
}
public componentDidUpdate(prevProps: IPaginationProps) {
if (this.props.loading)
return;
if (this.props.totalItems !== prevProps.totalItems || this.props.itemsPerPage !== prevProps.itemsPerPage) {
this.setPage(this.props.currentPage);
}
}
public render() {
if (!this.state || !this.state.pages || this.state.pages.length <= 1) { return null; }
return (
<ButtonGroup className="filter-container">
<Button
disabled={this.props.currentPage === 1}
onClick={() => this.setPage(1)}
>First</Button>
<Button
disabled={this.props.currentPage === 1}
onClick={() => this.setPage(this.props.currentPage - 1)}
>Previous</Button>
{this.renderPageButtons()}
<Button
disabled={this.props.currentPage === this.state.totalPages}
onClick={() => this.setPage(this.props.currentPage + 1)}
>Next</Button>
<Button
disabled={this.props.currentPage === this.state.totalPages}
onClick={() => this.setPage(this.state.totalPages)}
>Last</Button>
</ButtonGroup>
);
}
private renderPageButtons() {
return this.state.pages.map((page: number, index: number) => (
<Button
key={index}
active={this.props.currentPage === page}
onClick={() => this.setPage(page)}
>{page}</Button>
));
}
private setPage(page?: number, propagate: boolean = true) {
if (page === undefined) { return; }
const pagerState = this.getPagerState(this.props.totalItems, page, this.props.itemsPerPage);
if (page < 1) { page = 1; }
if (page > pagerState.totalPages) { page = pagerState.totalPages; }
this.setState(pagerState);
if (propagate) { this.props.onChangePage(page); }
}
private getPagerState(totalItems: number, currentPage: number, pageSize: number) {
const totalPages = Math.max(Math.ceil(totalItems / pageSize), 1);
export const Pagination: React.FC<IPaginationProps> = ({ itemsPerPage, currentPage, totalItems, onChangePage }) => {
const totalPages = Math.ceil(totalItems / itemsPerPage);
let startPage: number;
let endPage: number;
@@ -92,9 +17,7 @@ export class Pagination extends React.Component<IPaginationProps, IPaginationSta
// less than 10 total pages so show all
startPage = 1;
endPage = totalPages;
} else {
// more than 10 total pages so calculate start and end pages
if (currentPage <= 6) {
} else if (currentPage <= 6) {
startPage = 1;
endPage = 10;
} else if (currentPage + 4 >= totalPages) {
@@ -104,14 +27,36 @@ export class Pagination extends React.Component<IPaginationProps, IPaginationSta
startPage = currentPage - 5;
endPage = currentPage + 4;
}
}
// create an array of pages numbers
const pages = [...Array((endPage + 1) - startPage).keys()].map((i) => startPage + i);
return {
pages,
totalPages,
};
}
const pageButtons = pages.map((page: number) => (
<Button
key={page}
active={currentPage === page}
onClick={() => onChangePage(page)}
>{page}</Button>
));
return (
<ButtonGroup className="filter-container">
<Button
disabled={currentPage === 1}
onClick={() => onChangePage(1)}
>First</Button>
<Button
disabled={currentPage === 1}
onClick={() => onChangePage(currentPage - 1)}
>Previous</Button>
{pageButtons}
<Button
disabled={currentPage === totalPages}
onClick={() => onChangePage(currentPage + 1)}
>Next</Button>
<Button
disabled={currentPage === totalPages}
onClick={() => onChangePage(totalPages)}
>Last</Button>
</ButtonGroup>
);
}

View File

@@ -16,7 +16,7 @@ export const PerformerCard: React.FC<IPerformerCardProps> = (props: IPerformerCa
function maybeRenderFavoriteBanner() {
if (props.performer.favorite === false) { return; }
return (
<div className={`rating-banner rating-5`}>
<div className="rating-banner rating-5">
FAVORITE
</div>
);

View File

@@ -1,3 +1,5 @@
/* eslint-disable react/no-this-in-sfc */
import React, { useEffect, useState } from "react";
import { Button, Form, Spinner, Table } from 'react-bootstrap';
import { useParams, useHistory } from 'react-router-dom';
@@ -94,7 +96,7 @@ export const Performer: React.FC = () => {
ImageUtils.usePasteImage(onImageLoad);
useEffect(() => {
var newQueryableScrapers : GQL.ListPerformerScrapersListPerformerScrapers[] = [];
let newQueryableScrapers : GQL.ListPerformerScrapersListPerformerScrapers[] = [];
if (!!Scrapers.data && Scrapers.data.listPerformerScrapers) {
newQueryableScrapers = Scrapers.data.listPerformerScrapers.filter((s) => {
@@ -256,9 +258,9 @@ export const Performer: React.FC = () => {
);
}
function urlScrapable(url: string) {
return !!url && (Scrapers?.data?.listPerformerScrapers ?? []).some(s => (
(s?.performer?.urls ?? []).some(u => url.includes(u))
function urlScrapable(scrapedUrl: string) {
return !!scrapedUrl && (Scrapers?.data?.listPerformerScrapers ?? []).some(s => (
(s?.performer?.urls ?? []).some(u => scrapedUrl.includes(u))
));
}
@@ -316,14 +318,13 @@ export const Performer: React.FC = () => {
onAutoTag={onAutoTag}
/>
<h1>
{ <Form.Control
<Form.Control
readOnly={!isEditing}
plaintext={!isEditing}
defaultValue={name}
placeholder="Name"
onChange={(event: any) => setName(event.target.value)}
/>
}
</h1>
<h6>
<Form.Group className="aliases-field" controlId="aliases">

View File

@@ -20,21 +20,21 @@ export const PerformerList: React.FC = () => {
];
const listData = usePerformersList({
otherOperations: otherOperations,
otherOperations,
renderContent,
});
async function getRandom(result: QueryHookResult<FindPerformersQuery, FindPerformersVariables>, filter: ListFilterModel) {
if (result.data && result.data.findPerformers) {
let count = result.data.findPerformers.count;
let index = Math.floor(Math.random() * count);
let filterCopy = _.cloneDeep(filter);
const {count} = result.data.findPerformers;
const index = Math.floor(Math.random() * count);
const filterCopy = _.cloneDeep(filter);
filterCopy.itemsPerPage = 1;
filterCopy.currentPage = index + 1;
const singleResult = await StashService.queryFindPerformers(filterCopy);
if (singleResult && singleResult.data && singleResult.data.findPerformers && singleResult.data.findPerformers.performers.length === 1) {
let id = singleResult!.data!.findPerformers!.performers[0]!.id;
history.push("/performers/" + id);
const {id} = singleResult!.data!.findPerformers!.performers[0]!;
history.push(`/performers/${ id}`);
}
}
}
@@ -48,10 +48,8 @@ export const PerformerList: React.FC = () => {
{result.data.findPerformers.performers.map((p) => (<PerformerCard key={p.id} performer={p} />))}
</div>
);
} else if (filter.displayMode === DisplayMode.List) {
} if (filter.displayMode === DisplayMode.List) {
return <PerformerListTable performers={result.data.findPerformers.performers}/>;
} else if (filter.displayMode === DisplayMode.Wall) {
return;
}
}

View File

@@ -1,3 +1,5 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from "react";
import { Button, Table } from 'react-bootstrap';
import { Link } from "react-router-dom";
@@ -80,7 +82,7 @@ export const PerformerListTable: React.FC<IPerformerListTableProps> = (props: IP
<Table bordered striped>
<thead>
<tr>
<th></th>
<th />
<th>Name</th>
<th>Aliases</th>
<th>Favourite</th>

View File

@@ -5,7 +5,7 @@ import { PerformerList } from "./PerformerList";
const Performers = () => (
<Switch>
<Route exact={true} path="/performers" component={PerformerList} />
<Route exact path="/performers" component={PerformerList} />
<Route path="/performers/:id" component={Performer} />
</Switch>
);

View File

@@ -1,11 +1,11 @@
import React, { useState } from "react";
import { Button, ButtonGroup, Card, Form, Popover, OverlayTrigger } from 'react-bootstrap';
import { Button, ButtonGroup, Card, Form } from 'react-bootstrap';
import { Link } from "react-router-dom";
import cx from 'classnames';
import * as GQL from "src/core/generated-graphql";
import { StashService } from "src/core/StashService";
import { VideoHoverHook } from "src/hooks";
import { Icon, TagLink } from 'src/components/Shared';
import { Icon, TagLink, HoverPopover } from 'src/components/Shared';
import { TextUtils } from "src/utils";
interface ISceneCardProps {
@@ -33,8 +33,8 @@ export const SceneCard: React.FC<ISceneCardProps> = (props: ISceneCardProps) =>
function maybeRenderSceneSpecsOverlay() {
return (
<div className={`scene-specs-overlay`}>
{props.scene.file.height ? <span className={`overlay-resolution`}> {TextUtils.resolution(props.scene.file.height)}</span> : ''}
<div className="scene-specs-overlay">
{props.scene.file.height ? <span className="overlay-resolution"> {TextUtils.resolution(props.scene.file.height)}</span> : ''}
{props.scene.file.duration !== undefined && props.scene.file.duration >= 1 ? TextUtils.secondsToTimestamp(props.scene.file.duration) : ''}
</div>
);
@@ -55,7 +55,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (props: ISceneCardProps) =>
}
return (
<div className={`scene-studio-overlay`}>
<div className="scene-studio-overlay">
<Link
to={`/studios/${props.scene.studio.id}`}
style={style}
@@ -70,21 +70,20 @@ export const SceneCard: React.FC<ISceneCardProps> = (props: ISceneCardProps) =>
if (props.scene.tags.length <= 0)
return;
const popover = (
<Popover id="tag-popover">
{ props.scene.tags.map((tag) => (
const popoverContent = props.scene.tags.map((tag) => (
<TagLink key={tag.id} tag={tag} />
)) }
</Popover>
);
));
return (
<OverlayTrigger trigger="hover" placement="bottom" overlay={popover}>
<HoverPopover
placement="bottom"
content={popoverContent}
>
<Button>
<Icon icon="tag" />
{props.scene.tags.length}
</Button>
</OverlayTrigger>
</HoverPopover>
);
}
@@ -92,32 +91,27 @@ export const SceneCard: React.FC<ISceneCardProps> = (props: ISceneCardProps) =>
if (props.scene.performers.length <= 0)
return;
const popover = (
<Popover id="performer-popover">
{
props.scene.performers.map((performer) => {
return (
const popoverContent = props.scene.performers.map((performer) => (
<div className="performer-tag-container">
<Link
to={`/performers/${performer.id}`}
className="performer-tag previewable image"
style={{backgroundImage: `url(${performer.image_path})`}}
></Link>
/>
<TagLink key={performer.id} performer={performer} />
</div>
);
})
}
</Popover>
);
));
return (
<OverlayTrigger trigger="hover" placement="bottom" overlay={popover}>
<HoverPopover
placement="bottom"
content={popoverContent}
>
<Button>
<Icon icon="user" />
{props.scene.performers.length}
</Button>
</OverlayTrigger>
</HoverPopover>
);
}
@@ -125,23 +119,21 @@ export const SceneCard: React.FC<ISceneCardProps> = (props: ISceneCardProps) =>
if (props.scene.scene_markers.length <= 0)
return;
const popover = (
<Popover id="marker-popover">
{ props.scene.scene_markers.map((marker) => {
(marker as any).scene = {};
(marker as any).scene.id = props.scene.id;
return <TagLink key={marker.id} marker={marker} />;
}) }
</Popover>
);
const popoverContent = props.scene.scene_markers.map(marker => {
const markerPopover = { ...marker, scene: { id: props.scene.id } };
return <TagLink key={marker.id} marker={markerPopover} />;
});
return (
<OverlayTrigger trigger="hover" placement="bottom" overlay={popover}>
<HoverPopover
placement="bottom"
content={popoverContent}
>
<Button>
<Icon icon="tag" />
{props.scene.scene_markers.length}
</Button>
</OverlayTrigger>
</HoverPopover>
);
}
@@ -174,13 +166,13 @@ export const SceneCard: React.FC<ISceneCardProps> = (props: ISceneCardProps) =>
}
function isPortrait() {
let file = props.scene.file;
let width = file.width ? file.width : 0;
let height = file.height ? file.height : 0;
const {file} = props.scene;
const width = file.width ? file.width : 0;
const height = file.height ? file.height : 0;
return height > width;
}
var shiftKey = false;
let shiftKey = false;
return (
<Card
@@ -193,7 +185,11 @@ export const SceneCard: React.FC<ISceneCardProps> = (props: ISceneCardProps) =>
className="card-select"
checked={props.selected}
onChange={() => props.onSelectedChanged(!props.selected, shiftKey)}
onClick={(event: React.MouseEvent<HTMLInputElement, MouseEvent>) => { shiftKey = event.shiftKey; event.stopPropagation(); } }
onClick={(event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
// eslint-disable-next-line prefer-destructuring
shiftKey = event.shiftKey;
event.stopPropagation();
}}
/>
<Link to={`/scenes/${props.scene.id}`} className={cx('image', 'previewable', {portrait: isPortrait()})}>
<div className="video-container">
@@ -212,7 +208,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (props: ISceneCardProps) =>
</Link>
<div className="card-section">
<h4 className="text-truncate">
{!!props.scene.title ? props.scene.title : TextUtils.fileNameFromPath(props.scene.path)}
{props.scene.title ? props.scene.title : TextUtils.fileNameFromPath(props.scene.path)}
</h4>
<span>{props.scene.date}</span>
<p>{TextUtils.truncate(props.scene.details, 100, "... (continued)")}</p>

View File

@@ -29,8 +29,8 @@ export const Scene: React.FC = () => {
function getInitialTimestamp() {
const params = queryString.parse(location.search);
const timestamp = params?.t;
return Number.parseInt(Array.isArray(timestamp) ? timestamp[0] : timestamp ?? '0', 10);
const initialTimestamp = params?.t ?? '0';
return Number.parseInt(Array.isArray(initialTimestamp) ? initialTimestamp[0] : initialTimestamp, 10);
}
function onClickMarker(marker: GQL.SceneMarkerDataFragment) {
@@ -45,13 +45,13 @@ export const Scene: React.FC = () => {
return <div>{error.message}</div>
const modifiedScene =
Object.assign({scene_marker_tags: data.sceneMarkerTags}, scene) as GQL.SceneDataFragment; // TODO Hack from angular
({scene_marker_tags: data.sceneMarkerTags, ...scene}) as GQL.SceneDataFragment; // TODO Hack from angular
return (
<>
<ScenePlayer scene={modifiedScene} timestamp={timestamp} autoplay={autoplay}/>
<Card id="details-container">
<Tabs id="scene-tabs" mountOnEnter={true}>
<Tabs id="scene-tabs" mountOnEnter>
<Tab eventKey="scene-details-panel" title="Details">
<SceneDetailPanel scene={modifiedScene} />
</Tab>
@@ -67,7 +67,7 @@ export const Scene: React.FC = () => {
<ScenePerformerPanel scene={modifiedScene} />
</Tab> : ''
}
{!!modifiedScene.gallery ?
{modifiedScene.gallery ?
<Tab
eventKey="scene-gallery-panel"
title="Gallery">

View File

@@ -8,7 +8,7 @@ interface ISceneDetailProps {
scene: GQL.SceneDataFragment;
}
export default SceneDetailPanel: React.FC<ISceneDetailProps> = (props: ISceneDetailProps) => {
export const SceneDetailPanel: React.FC<ISceneDetailProps> = (props) => {
function renderDetails() {
if (!props.scene.details || props.scene.details === "") { return; }
return (
@@ -36,7 +36,7 @@ export default SceneDetailPanel: React.FC<ISceneDetailProps> = (props: ISceneDet
<>
{SceneHelpers.maybeRenderStudio(props.scene, 70)}
<h1>
{!!props.scene.title ? props.scene.title : TextUtils.fileNameFromPath(props.scene.path)}
{props.scene.title ? props.scene.title : TextUtils.fileNameFromPath(props.scene.path)}
</h1>
{props.scene.date ? <h4>{props.scene.date}</h4> : ''}
{props.scene.rating ? <h6>Rating: {props.scene.rating}</h6> : ''}

View File

@@ -1,3 +1,5 @@
/* eslint-disable react/no-this-in-sfc */
import React, { useEffect, useState } from "react";
import { Collapse, Dropdown, DropdownButton, Form, Button, Spinner } from 'react-bootstrap';
import * as GQL from "src/core/generated-graphql";
@@ -42,7 +44,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
const deleteScene = StashService.useSceneDestroy(getSceneDeleteInput());
useEffect(() => {
var newQueryableScrapers : GQL.ListSceneScrapersListSceneScrapers[] = [];
let newQueryableScrapers : GQL.ListSceneScrapersListSceneScrapers[] = [];
if (!!Scrapers.data && Scrapers.data.listSceneScrapers) {
newQueryableScrapers = Scrapers.data.listSceneScrapers.filter((s) => {
@@ -55,8 +57,8 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
}, [Scrapers.data])
function updateSceneEditState(state: Partial<GQL.SceneDataFragment>) {
const perfIds = !!state.performers ? state.performers.map((performer) => performer.id) : undefined;
const tIds = !!state.tags ? state.tags.map((tag) => tag.id) : undefined;
const perfIds = state.performers ? state.performers.map((performer) => performer.id) : undefined;
const tIds = state.tags ? state.tags.map((tag) => tag.id) : undefined;
setTitle(state.title);
setDetails(state.details);
@@ -130,7 +132,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
return (
<FilterSelect
type={type}
isMulti={true}
isMulti
onSelect={(items) => {
const ids = items.map((i) => i.id);
switch (type) {
@@ -200,10 +202,10 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
);
}
function urlScrapable(url: string) : boolean {
return !!url && !!Scrapers.data && Scrapers.data.listSceneScrapers && Scrapers.data.listSceneScrapers.some((s) => {
return !!s.scene && !!s.scene.urls && s.scene.urls.some((u) => { return url.includes(u); });
});
function urlScrapable(scrapedUrl: string) : boolean {
return (Scrapers?.data?.listSceneScrapers ?? []).some(s => (
(s?.scene?.urls ?? []).some(u => scrapedUrl.includes(u))
));
}
function updateSceneFromScrapedScene(scene : GQL.ScrapedSceneDataFragment) {
@@ -228,23 +230,23 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
}
if ((!performerIds || performerIds.length === 0) && scene.performers && scene.performers.length > 0) {
let idPerfs = scene.performers.filter((p) => {
const idPerfs = scene.performers.filter((p) => {
return p.id !== undefined && p.id !== null;
});
if (idPerfs.length > 0) {
let newIds = idPerfs.map((p) => p.id);
const newIds = idPerfs.map((p) => p.id);
setPerformerIds(newIds as string[]);
}
}
if ((!tagIds || tagIds.length === 0) && scene.tags && scene.tags.length > 0) {
let idTags = scene.tags.filter((p) => {
const idTags = scene.tags.filter((p) => {
return p.id !== undefined && p.id !== null;
});
if (idTags.length > 0) {
let newIds = idTags.map((p) => p.id);
const newIds = idTags.map((p) => p.id);
setTagIds(newIds as string[]);
}
}
@@ -356,10 +358,10 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
</Form.Group>
<div>
<label onClick={() => setIsCoverImageOpen(!isCoverImageOpen)}>
<Button variant="link" onClick={() => setIsCoverImageOpen(!isCoverImageOpen)}>
<Icon icon={isCoverImageOpen ? "chevron-down" : "chevron-right"} />
<span>Cover Image</span>
</label>
</Button>
<Collapse in={isCoverImageOpen}>
<div>
<img className="scene-cover" src={coverImagePreview} alt="" />

View File

@@ -22,7 +22,7 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (props: IS
return (
<tr>
<td>Path</td>
<td><a href={`file://${path}`}>{"file://"+props.scene.path}</a> </td>
<td><a href={`file://${path}`}>{`file://${props.scene.path}`}</a> </td>
</tr>
);
}

View File

@@ -1,6 +1,6 @@
import React, { CSSProperties, useState } from "react";
import { Badge, Button, Card, Collapse, Form as BootstrapForm } from 'react-bootstrap';
import { Field, FieldProps, Form, Formik, FormikActions } from "formik";
import { Field, FieldProps, Form, Formik } from "formik";
import * as GQL from "src/core/generated-graphql";
import { StashService } from "src/core/StashService";
import { TextUtils } from "src/utils";
@@ -93,7 +93,7 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (props: ISce
}
function renderForm() {
function onSubmit(values: IFormFields, _: FormikActions<IFormFields>) {
function onSubmit(values: IFormFields) {
const isEditing = !!editingMarker;
const variables: GQL.SceneMarkerCreateVariables | GQL.SceneMarkerUpdateVariables = {
title: values.title,
@@ -118,9 +118,9 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (props: ISce
}
function onDelete() {
if (!editingMarker) { return; }
sceneMarkerDestroy({variables: {id: editingMarker.id}}).then((response) => {
console.log(response);
}).catch((err) => console.error(err));
sceneMarkerDestroy({variables: {id: editingMarker.id}})
// eslint-disable-next-line no-console
.catch(err => console.error(err));
setIsEditorOpen(false);
setEditingMarker(null);
}
@@ -152,7 +152,7 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (props: ISce
function renderTagsField(fieldProps: FieldProps<IFormFields>) {
return (
<TagSelect
isMulti={true}
isMulti
onSelect={(tags) => fieldProps.form.setFieldValue("tagIds", tags.map((tag) => tag.id))}
initialIds={editingMarker ? fieldProps.form.values.tagIds : []}
/>

View File

@@ -1,25 +1,26 @@
/* eslint-disable no-param-reassign, jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
import React, { useEffect, useState, useCallback } from "react";
import { Badge, Button, Card, Collapse, Dropdown, DropdownButton, Form, Table, Spinner } from 'react-bootstrap';
import _ from "lodash";
import { StashService } from "src/core/StashService";
import * as GQL from "src/core/generated-graphql";
import { SlimSceneDataFragment, Maybe } from "src/core/generated-graphql";
import { FilterSelect, Icon, StudioSelect } from "src/components/Shared";
import { TextUtils } from "src/utils";
import { useToast } from "src/hooks";
import { Pagination } from "../list/Pagination";
class ParserResult<T> {
public value: Maybe<T>;
public originalValue: Maybe<T>;
public value: GQL.Maybe<T>;
public originalValue: GQL.Maybe<T>;
public set: boolean = false;
public setOriginalValue(v : Maybe<T>) {
public setOriginalValue(v : GQL.Maybe<T>) {
this.originalValue = v;
this.value = v;
}
public setValue(v : Maybe<T>) {
public setValue(v : GQL.Maybe<T>) {
if (v) {
this.value = v;
this.set = !_.isEqual(this.value, this.originalValue);
@@ -37,7 +38,7 @@ class ParserField {
}
public getFieldPattern() {
return "{" + this.field + "}";
return `{${ this.field }}`;
}
static Title = new ParserField("title");
@@ -106,7 +107,7 @@ class SceneParserResult {
public performers: ParserResult<GQL.SlimSceneDataPerformers[]> = new ParserResult();
public performerIds: ParserResult<string[]> = new ParserResult();
public scene : SlimSceneDataFragment;
public scene : GQL.SlimSceneDataFragment;
constructor(result : GQL.ParseSceneFilenamesResults) {
this.scene = result.scene;
@@ -157,9 +158,9 @@ class SceneParserResult {
}
}
private static setInput(object: any, key: string, parserResult : ParserResult<any>) {
private static setInput(obj: any, key: string, parserResult : ParserResult<any>) {
if (parserResult.set) {
object[key] = parserResult.value;
obj[key] = parserResult.value;
}
}
@@ -169,7 +170,7 @@ class SceneParserResult {
}
public toSceneUpdateInput() {
var ret = {
const ret = {
id: this.id,
title: this.scene.title,
details: this.scene.details,
@@ -294,16 +295,16 @@ export const SceneFilenameParser: React.FC = () => {
const updateScenes = StashService.useScenesUpdate(getScenesUpdateData());
const determineFieldsToHide = useCallback(() => {
var pattern = parserInput.pattern;
var titleSet = pattern.includes("{title}");
var dateSet = pattern.includes("{date}") ||
const {pattern} = parserInput;
const titleSet = pattern.includes("{title}");
const dateSet = pattern.includes("{date}") ||
pattern.includes("{dd}") || // don't worry about other partial date fields since this should be implied
ParserField.fullDateFields.some((f) => {
return pattern.includes("{" + f.field + "}");
return pattern.includes(`{${ f.field }}`);
});
var performerSet = pattern.includes("{performer}");
var tagSet = pattern.includes("{tag}");
var studioSet = pattern.includes("{studio}");
const performerSet = pattern.includes("{performer}");
const tagSet = pattern.includes("{tag}");
const studioSet = pattern.includes("{studio}");
const newShowFields = new Map<string, boolean>([
["Title", titleSet],
@@ -318,7 +319,7 @@ export const SceneFilenameParser: React.FC = () => {
const parseResults = useCallback((results : GQL.ParseSceneFilenamesResults[]) => {
if (results) {
var result = results.map((r) => {
const result = results.map((r) => {
return new SceneParserResult(r);
}).filter((r) => !!r) as SceneParserResult[];
@@ -348,7 +349,7 @@ export const SceneFilenameParser: React.FC = () => {
StashService.queryParseSceneFilenames(parserFilter, parserInputData)
.then((response) => {
let result = response.data.parseSceneFilenames;
const result = response.data.parseSceneFilenames;
if (result) {
parseResults(result.results);
setTotalItems(result.count);
@@ -364,7 +365,7 @@ export const SceneFilenameParser: React.FC = () => {
}, [parserInput, parseResults, Toast]);
function onPageSizeChanged(newSize : number) {
var newInput = _.clone(parserInput);
const newInput = _.clone(parserInput);
newInput.page = 1;
newInput.pageSize = newSize;
setParserInput(newInput);
@@ -372,7 +373,7 @@ export const SceneFilenameParser: React.FC = () => {
function onPageChanged(newPage : number) {
if (newPage !== parserInput.page) {
var newInput = _.clone(parserInput);
const newInput = _.clone(parserInput);
newInput.page = newPage;
setParserInput(newInput);
}
@@ -403,19 +404,19 @@ export const SceneFilenameParser: React.FC = () => {
}
useEffect(() => {
var newAllTitleSet = !parserResult.some((r) => {
const newAllTitleSet = !parserResult.some((r) => {
return !r.title.set;
});
var newAllDateSet = !parserResult.some((r) => {
const newAllDateSet = !parserResult.some((r) => {
return !r.date.set;
});
var newAllPerformerSet = !parserResult.some((r) => {
const newAllPerformerSet = !parserResult.some((r) => {
return !r.performerIds.set;
});
var newAllTagSet = !parserResult.some((r) => {
const newAllTagSet = !parserResult.some((r) => {
return !r.tagIds.set;
});
var newAllStudioSet = !parserResult.some((r) => {
const newAllStudioSet = !parserResult.some((r) => {
return !r.studioId.set;
});
@@ -427,7 +428,7 @@ export const SceneFilenameParser: React.FC = () => {
}, [parserResult]);
function onSelectAllTitleSet(selected : boolean) {
var newResult = [...parserResult];
const newResult = [...parserResult];
newResult.forEach((r) => {
r.title.set = selected;
@@ -438,7 +439,7 @@ export const SceneFilenameParser: React.FC = () => {
}
function onSelectAllDateSet(selected : boolean) {
var newResult = [...parserResult];
const newResult = [...parserResult];
newResult.forEach((r) => {
r.date.set = selected;
@@ -449,7 +450,7 @@ export const SceneFilenameParser: React.FC = () => {
}
function onSelectAllPerformerSet(selected : boolean) {
var newResult = [...parserResult];
const newResult = [...parserResult];
newResult.forEach((r) => {
r.performerIds.set = selected;
@@ -460,7 +461,7 @@ export const SceneFilenameParser: React.FC = () => {
}
function onSelectAllTagSet(selected : boolean) {
var newResult = [...parserResult];
const newResult = [...parserResult];
newResult.forEach((r) => {
r.tagIds.set = selected;
@@ -471,7 +472,7 @@ export const SceneFilenameParser: React.FC = () => {
}
function onSelectAllStudioSet(selected : boolean) {
var newResult = [...parserResult];
const newResult = [...parserResult];
newResult.forEach((r) => {
r.studioId.set = selected;
@@ -530,10 +531,10 @@ export const SceneFilenameParser: React.FC = () => {
function onFind() {
props.onFind({
pattern: pattern,
pattern,
ignoreWords: ignoreWords.split(" "),
whitespaceCharacters: whitespaceCharacters,
capitalizeTitle: capitalizeTitle,
whitespaceCharacters,
capitalizeTitle,
page: 1,
pageSize: props.input.pageSize,
findClicked: props.input.findClicked
@@ -569,7 +570,7 @@ export const SceneFilenameParser: React.FC = () => {
</Dropdown.Item>
))}
</DropdownButton>
<div>Use '\\' to escape literal {} characters</div>
<div>Use &apos;\\&apos; to escape literal {} characters</div>
</Form.Group>
<Form.Group>
@@ -624,7 +625,7 @@ export const SceneFilenameParser: React.FC = () => {
as="select"
style={{flexBasis: "min-content"}}
options={PAGE_SIZE_OPTIONS}
onChange={(event: any) => onPageSizeChanged(parseInt(event.target.value))}
onChange={(event: any) => onPageSizeChanged(parseInt(event.target.value, 10))}
defaultValue={props.input.pageSize}
className="filter-item"
>
@@ -678,13 +679,13 @@ export const SceneFilenameParser: React.FC = () => {
}
function renderOriginalInputGroup(props: ISceneParserFieldProps) {
var parserResult = props.originalParserResult || props.parserResult;
const result = props.originalParserResult || props.parserResult;
return (
<Form.Control
disabled
className={props.className}
defaultValue={parserResult.originalValue || ""}
defaultValue={result.originalValue || ""}
/>
);
}
@@ -706,27 +707,27 @@ export const SceneFilenameParser: React.FC = () => {
);
}
function renderNewInputGroup(props : ISceneParserFieldProps, onChange : (value : any) => void) {
function renderNewInputGroup(props : ISceneParserFieldProps, onChangeHandler : (value : any) => void) {
return (
<InputGroupWrapper
className={props.className}
onChange={(value : any) => {onChange(value)}}
onChange={(value : any) => {onChangeHandler(value)}}
parserResult={props.parserResult}
/>
);
}
interface HasName {
interface IHasName {
name: string
}
function renderOriginalSelect(props : ISceneParserFieldProps) {
const parserResult = props.originalParserResult || props.parserResult;
const result = props.originalParserResult || props.parserResult;
const elements = parserResult.originalValue
? Array.isArray(parserResult.originalValue)
? parserResult.originalValue.map((el:HasName) => el.name)
: [parserResult.originalValue.name]
const elements = result.originalValue
? Array.isArray(result.originalValue)
? result.originalValue.map((el:IHasName) => el.name)
: [result.originalValue.name]
: [];
return (
@@ -736,35 +737,35 @@ export const SceneFilenameParser: React.FC = () => {
);
}
function renderNewMultiSelect(type: "performers" | "tags", props : ISceneParserFieldProps, onChange : (value : any) => void) {
function renderNewMultiSelect(type: "performers" | "tags", props : ISceneParserFieldProps, onChangeHandler : (value : any) => void) {
return (
<FilterSelect
className={props.className}
type={type}
isMulti={true}
isMulti
onSelect={(items) => {
const ids = items.map((i) => i.id);
onChange(ids);
onChangeHandler(ids);
}}
initialIds={props.parserResult.value}
/>
);
}
function renderNewPerformerSelect(props : ISceneParserFieldProps, onChange : (value : any) => void) {
return renderNewMultiSelect("performers", props, onChange);
function renderNewPerformerSelect(props : ISceneParserFieldProps, onChangeHandler : (value : any) => void) {
return renderNewMultiSelect("performers", props, onChangeHandler);
}
function renderNewTagSelect(props : ISceneParserFieldProps, onChange : (value : any) => void) {
return renderNewMultiSelect("tags", props, onChange);
function renderNewTagSelect(props : ISceneParserFieldProps, onChangeHandler : (value : any) => void) {
return renderNewMultiSelect("tags", props, onChangeHandler);
}
function renderNewStudioSelect(props : ISceneParserFieldProps, onChange : (value : any) => void) {
function renderNewStudioSelect(props : ISceneParserFieldProps, onChangeHandler : (value : any) => void) {
return (
<StudioSelect
noSelectionString=""
className={props.className}
onSelect={(items) => onChange(items[0]?.id)}
onSelect={(items) => onChangeHandler(items[0]?.id)}
initialIds={props.parserResult.value ? [props.parserResult.value] : []}
/>
);
@@ -778,38 +779,38 @@ export const SceneFilenameParser: React.FC = () => {
function SceneParserRow(props : ISceneParserRowProps) {
function changeParser(result : ParserResult<any>, set : boolean, value : any) {
var newParser = _.clone(result);
const newParser = _.clone(result);
newParser.set = set;
newParser.value = value;
return newParser;
}
function onTitleChanged(set : boolean, value: string | undefined) {
var newResult = _.clone(props.scene);
const newResult = _.clone(props.scene);
newResult.title = changeParser(newResult.title, set, value);
props.onChange(newResult);
}
function onDateChanged(set : boolean, value: string | undefined) {
var newResult = _.clone(props.scene);
const newResult = _.clone(props.scene);
newResult.date = changeParser(newResult.date, set, value);
props.onChange(newResult);
}
function onPerformerIdsChanged(set : boolean, value: string[] | undefined) {
var newResult = _.clone(props.scene);
const newResult = _.clone(props.scene);
newResult.performerIds = changeParser(newResult.performerIds, set, value);
props.onChange(newResult);
}
function onTagIdsChanged(set : boolean, value: string[] | undefined) {
var newResult = _.clone(props.scene);
const newResult = _.clone(props.scene);
newResult.tagIds = changeParser(newResult.tagIds, set, value);
props.onChange(newResult);
}
function onStudioIdChanged(set : boolean, value: string | undefined) {
var newResult = _.clone(props.scene);
const newResult = _.clone(props.scene);
newResult.studioId = changeParser(newResult.studioId, set, value);
props.onChange(newResult);
}
@@ -877,9 +878,9 @@ export const SceneFilenameParser: React.FC = () => {
}
function onChange(scene : SceneParserResult, changedScene : SceneParserResult) {
var newResult = [...parserResult];
const newResult = [...parserResult];
var index = newResult.indexOf(scene);
const index = newResult.indexOf(scene);
newResult[index] = changedScene;
setParserResult(newResult);

View File

@@ -23,7 +23,7 @@ export const SceneList: React.FC = () => {
const listData = useScenesList({
zoomable: true,
otherOperations: otherOperations,
otherOperations,
renderContent,
renderSelectedOptions
});
@@ -31,17 +31,17 @@ export const SceneList: React.FC = () => {
async function playRandom(result: QueryHookResult<FindScenesQuery, FindScenesVariables>, filter: ListFilterModel) {
// query for a random scene
if (result.data && result.data.findScenes) {
let count = result.data.findScenes.count;
const {count} = result.data.findScenes;
let index = Math.floor(Math.random() * count);
let filterCopy = _.cloneDeep(filter);
const index = Math.floor(Math.random() * count);
const filterCopy = _.cloneDeep(filter);
filterCopy.itemsPerPage = 1;
filterCopy.currentPage = index + 1;
const singleResult = await StashService.queryFindScenes(filterCopy);
if (singleResult && singleResult.data && singleResult.data.findScenes && singleResult.data.findScenes.scenes.length === 1) {
let id = singleResult!.data!.findScenes!.scenes[0].id;
const {id} = singleResult!.data!.findScenes!.scenes[0];
// navigate to the scene player page
history.push("/scenes/" + id + "?autoplay=true");
history.push(`/scenes/${ id }?autoplay=true`);
}
}
}
@@ -50,13 +50,11 @@ export const SceneList: React.FC = () => {
// find the selected items from the ids
if (!result.data || !result.data.findScenes) { return undefined; }
var scenes = result.data.findScenes.scenes;
const {scenes} = result.data.findScenes;
var selectedScenes : SlimSceneDataFragment[] = [];
const selectedScenes : SlimSceneDataFragment[] = [];
selectedIds.forEach((id) => {
var scene = scenes.find((scene) => {
return scene.id === id;
});
const scene = scenes.find(s => s.id === id);
if (scene) {
selectedScenes.push(scene);
@@ -65,7 +63,7 @@ export const SceneList: React.FC = () => {
return (
<>
<SceneSelectedOptions selected={selectedScenes} onScenesUpdated={() => { return; }}/>
<SceneSelectedOptions selected={selectedScenes} onScenesUpdated={() => { }}/>
</>
);
}
@@ -90,9 +88,9 @@ export const SceneList: React.FC = () => {
{result.data.findScenes.scenes.map((scene) => renderSceneCard(scene, selectedIds, zoomIndex))}
</div>
);
} else if (filter.displayMode === DisplayMode.List) {
} if (filter.displayMode === DisplayMode.List) {
return <SceneListTable scenes={result.data.findScenes.scenes}/>;
} else if (filter.displayMode === DisplayMode.Wall) {
} if (filter.displayMode === DisplayMode.Wall) {
return <WallPanel scenes={result.data.findScenes.scenes} />;
}
}

View File

@@ -96,8 +96,7 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (props: ISceneList
<Table striped bordered>
<thead>
<tr>
<th></th>
<th>Title</th>
<th colSpan={2}>Title</th>
<th>Rating</th>
<th>Duration</th>
<th>Tags</th>

View File

@@ -18,23 +18,23 @@ export const SceneMarkerList: React.FC = () => {
}];
const listData = useSceneMarkersList({
otherOperations: otherOperations,
otherOperations,
renderContent,
});
async function playRandom(result: QueryHookResult<FindSceneMarkersQuery, FindSceneMarkersVariables>, filter: ListFilterModel) {
// query for a random scene
if (result.data && result.data.findSceneMarkers) {
let count = result.data.findSceneMarkers.count;
const {count} = result.data.findSceneMarkers;
let index = Math.floor(Math.random() * count);
let filterCopy = _.cloneDeep(filter);
const index = Math.floor(Math.random() * count);
const filterCopy = _.cloneDeep(filter);
filterCopy.itemsPerPage = 1;
filterCopy.currentPage = index + 1;
const singleResult = await StashService.queryFindSceneMarkers(filterCopy);
if (singleResult && singleResult.data && singleResult.data.findSceneMarkers && singleResult.data.findSceneMarkers.scene_markers.length === 1) {
// navigate to the scene player page
let url = NavUtils.makeSceneMarkerUrl(singleResult.data.findSceneMarkers.scene_markers[0])
const url = NavUtils.makeSceneMarkerUrl(singleResult.data.findSceneMarkers.scene_markers[0])
history.push(url);
}
}

View File

@@ -30,6 +30,13 @@ export class ScenePlayerImpl extends React.Component<IScenePlayerProps, IScenePl
private player: any;
private lastTime = 0;
private KeyHandlers = {
NUM0: () => {this.onReset()},
NUM1: () => {this.onDecrease()},
NUM2: () => {this.onIncrease()},
SPACE: () => {this.onPause()}
}
constructor(props: IScenePlayerProps) {
super(props);
this.onReady = this.onReady.bind(this);
@@ -48,66 +55,65 @@ export class ScenePlayerImpl extends React.Component<IScenePlayerProps, IScenePl
}
}
renderPlayer() {
const config = this.makeJWPlayerConfig(this.props.scene);
return (
<ReactJWPlayer
playerId={SceneHelpers.getJWPlayerId()}
playerScript="/jwplayer/jwplayer.js"
customProps={config}
onReady={this.onReady}
onSeeked={this.onSeeked}
onTime={this.onTime}
/>
);
}
onIncrease() {
const currentPlaybackRate = !!this.player ? this.player.getPlaybackRate() : 1;
const currentPlaybackRate = this.player ? this.player.getPlaybackRate() : 1;
this.player.setPlaybackRate(currentPlaybackRate + 0.5);
};
onDecrease() {
const currentPlaybackRate = !!this.player ? this.player.getPlaybackRate() : 1;
const currentPlaybackRate = this.player ? this.player.getPlaybackRate() : 1;
this.player.setPlaybackRate(currentPlaybackRate - 0.5);
};
onReset() { this.player.setPlaybackRate(1); };
onPause() { this.player.getState().paused ? this.player.play() : this.player.pause(); };
private KeyHandlers = {
NUM0: () => {this.onReset()},
NUM1: () => {this.onDecrease()},
NUM2: () => {this.onIncrease()},
SPACE: () => {this.onPause()}
onReset() { this.player.setPlaybackRate(1); };
onPause() {
if (this.player.getState().paused)
this.player.play();
else
this.player.pause();
};
private onReady() {
this.player = SceneHelpers.getPlayer();
if (this.props.timestamp > 0) {
this.player.seek(this.props.timestamp);
}
}
public render() {
return (
<HotKeys keyMap={KeyMap} handlers={this.KeyHandlers}>
<div id="jwplayer-container">
{this.renderPlayer()}
<ScenePlayerScrubber
scene={this.props.scene}
position={this.state.scrubberPosition}
onSeek={this.onScrubberSeek}
onScrolled={this.onScrubberScrolled}
/>
</div>
</HotKeys>
);
private onSeeked() {
const position = this.player.getPosition();
this.setState({scrubberPosition: position});
this.player.play();
}
private onTime() {
const position = this.player.getPosition();
const difference = Math.abs(position - this.lastTime);
if (difference > 1) {
this.lastTime = position;
this.setState({scrubberPosition: position});
}
}
private onScrubberSeek(seconds: number) {
this.player.seek(seconds);
}
private onScrubberScrolled() {
this.player.pause();
}
private shouldRepeat(scene: GQL.SceneDataFragment) {
let maxLoopDuration = this.props.config ? this.props.config.maximumLoopDuration : 0;
const maxLoopDuration = this.props.config ? this.props.config.maximumLoopDuration : 0;
return !!scene.file.duration && !!maxLoopDuration && scene.file.duration < maxLoopDuration;
}
private makeJWPlayerConfig(scene: GQL.SceneDataFragment) {
if (!scene.paths.stream) { return {}; }
let repeat = this.shouldRepeat(scene);
let getDurationHook: (() => GQL.Maybe<number>) | undefined = undefined;
let seekHook: ((seekToPosition: number, _videoTag: any) => void) | undefined = undefined;
let getCurrentTimeHook: ((_videoTag: any) => number) | undefined = undefined;
const repeat = this.shouldRepeat(scene);
let getDurationHook: (() => GQL.Maybe<number>) | undefined;
let seekHook: ((seekToPosition: number, _videoTag: any) => void) | undefined;
let getCurrentTimeHook: ((_videoTag: any) => number) | undefined;
if (!this.props.scene.is_streamable) {
getDurationHook = () => {
@@ -115,18 +121,20 @@ export class ScenePlayerImpl extends React.Component<IScenePlayerProps, IScenePl
};
seekHook = (seekToPosition: number, _videoTag: any) => {
// eslint-disable-next-line no-param-reassign
_videoTag.start = seekToPosition;
_videoTag.src = (this.props.scene.paths.stream + "?start=" + seekToPosition);
// eslint-disable-next-line no-param-reassign
_videoTag.src = (`${this.props.scene.paths.stream }?start=${ seekToPosition}`);
_videoTag.play();
};
getCurrentTimeHook = (_videoTag: any) => {
let start = _videoTag.start || 0;
const start = _videoTag.start || 0;
return _videoTag.currentTime + start;
}
}
let ret = {
const ret = {
file: scene.paths.stream,
image: scene.paths.screenshot,
tracks: [
@@ -147,45 +155,45 @@ export class ScenePlayerImpl extends React.Component<IScenePlayerProps, IScenePl
cast: {},
primary: "html5",
autostart: this.props.autoplay || (this.props.config ? this.props.config.autostartVideo : false),
repeat: repeat,
repeat,
playbackRateControls: true,
playbackRates: [0.75, 1, 1.5, 2, 3, 4],
getDurationHook: getDurationHook,
seekHook: seekHook,
getCurrentTimeHook: getCurrentTimeHook
getDurationHook,
seekHook,
getCurrentTimeHook
};
return ret;
}
private onReady() {
this.player = SceneHelpers.getPlayer();
if (this.props.timestamp > 0) {
this.player.seek(this.props.timestamp);
}
renderPlayer() {
const config = this.makeJWPlayerConfig(this.props.scene);
return (
<ReactJWPlayer
playerId={SceneHelpers.getJWPlayerId()}
playerScript="/jwplayer/jwplayer.js"
customProps={config}
onReady={this.onReady}
onSeeked={this.onSeeked}
onTime={this.onTime}
/>
);
}
private onSeeked() {
const position = this.player.getPosition();
this.setState({scrubberPosition: position});
this.player.play();
}
private onTime(data: any) {
const position = this.player.getPosition();
const difference = Math.abs(position - this.lastTime);
if (difference > 1) {
this.lastTime = position;
this.setState({scrubberPosition: position});
}
}
private onScrubberSeek(seconds: number) {
this.player.seek(seconds);
}
private onScrubberScrolled() {
this.player.pause();
public render() {
return (
<HotKeys keyMap={KeyMap} handlers={this.KeyHandlers}>
<div id="jwplayer-container">
{this.renderPlayer()}
<ScenePlayerScrubber
scene={this.props.scene}
position={this.state.scrubberPosition}
onSeek={this.onScrubberSeek}
onScrolled={this.onScrubberScrolled}
/>
</div>
</HotKeys>
);
}
}

View File

@@ -1,3 +1,5 @@
/* eslint-disable react/no-array-index-key */
import React, { CSSProperties, useEffect, useRef, useState, useCallback } from "react";
import { Button } from 'react-bootstrap';
import axios from "axios";
@@ -24,9 +26,6 @@ interface ISceneSpriteItem {
async function fetchSpriteInfo(vttPath: string) {
const response = await axios.get<string>(vttPath, {responseType: "text"});
if (response.status !== 200) {
console.log(response.statusText);
}
// TODO: This is gnarly
const lines = response.data.split("\n");
@@ -36,8 +35,7 @@ async function fetchSpriteInfo(vttPath: string) {
const newSpriteItems: ISceneSpriteItem[] = [];
while (lines.length) {
const line = lines.shift();
if (line === undefined) { continue; }
if (line !== undefined) {
if (line.includes("#") && line.includes("=") && line.includes(",")) {
const size = line.split("#")[1].split("=")[1].split(",");
item.x = Number(size[0]);
@@ -57,6 +55,7 @@ async function fetchSpriteInfo(vttPath: string) {
item.end = (+end[0]) * 60 * 60 + (+end[1]) * 60 + (+end[2]);
}
}
}
return newSpriteItems;
}
@@ -154,7 +153,7 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (props:
mouseDown.current = false;
const delta = Math.abs(event.clientX - startMouseEvent.current.clientX);
if (delta < 1 && event.target instanceof HTMLDivElement) {
const target: HTMLDivElement = event.target;
const {target} = event;
let seekSeconds: number | undefined;
const spriteIdString = target.getAttribute("data-sprite-item-id");
@@ -171,7 +170,7 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (props:
seekSeconds = marker.seconds;
}
if (!!seekSeconds) { props.onSeek(seekSeconds); }
if (seekSeconds) { props.onSeek(seekSeconds); }
} else if (Math.abs(velocity.current) > 25) {
const newPosition = getPosition() + (velocity.current * 10);
setPosition(newPosition);
@@ -274,7 +273,7 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (props:
width: `${sprite.w}px`,
height: `${sprite.h}px`,
margin: "0px auto",
backgroundPosition: -sprite.x + "px " + -sprite.y + "px",
backgroundPosition: `${-sprite.x }px ${ -sprite.y }px`,
backgroundImage: `url(${path})`,
left: `${left}px`,
};

View File

@@ -25,12 +25,12 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
function getSceneInput() : GQL.BulkSceneUpdateInput {
// need to determine what we are actually setting on each scene
var aggregateRating = getRating(props.selected);
var aggregateStudioId = getStudioId(props.selected);
var aggregatePerformerIds = getPerformerIds(props.selected);
var aggregateTagIds = getTagIds(props.selected);
const aggregateRating = getRating(props.selected);
const aggregateStudioId = getStudioId(props.selected);
const aggregatePerformerIds = getPerformerIds(props.selected);
const aggregateTagIds = getTagIds(props.selected);
var sceneInput : GQL.BulkSceneUpdateInput = {
const sceneInput : GQL.BulkSceneUpdateInput = {
ids: props.selected.map((scene) => {
return scene.id;
})
@@ -46,7 +46,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
// otherwise not setting the rating
} else {
// if rating is set, then we are setting the rating for all
sceneInput.rating = Number.parseInt(rating);
sceneInput.rating = Number.parseInt(rating, 10);
}
// if studioId is undefined
@@ -102,34 +102,32 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
}
function getRating(state: GQL.SlimSceneDataFragment[]) {
var ret : number | undefined;
var first = true;
let ret : number | undefined;
let first = true;
state.forEach((scene : GQL.SlimSceneDataFragment) => {
if (first) {
ret = scene.rating;
first = false;
} else {
if (ret !== scene.rating) {
} else if (ret !== scene.rating) {
ret = undefined;
}
}
});
return ret;
}
function getStudioId(state: GQL.SlimSceneDataFragment[]) {
var ret : string | undefined;
var first = true;
let ret : string | undefined;
let first = true;
state.forEach((scene : GQL.SlimSceneDataFragment) => {
if (first) {
ret = scene.studio ? scene.studio.id : undefined;
ret = scene?.studio?.id;
first = false;
} else {
var studioId = scene.studio ? scene.studio.id : undefined;
if (ret !== studioId) {
const studio = scene?.studio?.id;
if (ret !== studio) {
ret = undefined;
}
}
@@ -138,20 +136,16 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
return ret;
}
function toId(object : any) {
return object.id;
}
function getPerformerIds(state: GQL.SlimSceneDataFragment[]) {
var ret : string[] = [];
var first = true;
let ret : string[] = [];
let first = true;
state.forEach((scene : GQL.SlimSceneDataFragment) => {
if (first) {
ret = !!scene.performers ? scene.performers.map(toId).sort() : [];
ret = scene.performers ? scene.performers.map(p => p.id).sort() : [];
first = false;
} else {
const perfIds = !!scene.performers ? scene.performers.map(toId).sort() : [];
const perfIds = scene.performers ? scene.performers.map(p => p.id).sort() : [];
if (!_.isEqual(ret, perfIds)) {
ret = [];
@@ -163,15 +157,15 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
}
function getTagIds(state: GQL.SlimSceneDataFragment[]) {
var ret : string[] = [];
var first = true;
let ret : string[] = [];
let first = true;
state.forEach((scene : GQL.SlimSceneDataFragment) => {
if (first) {
ret = !!scene.tags ? scene.tags.map(toId).sort() : [];
ret = scene.tags ? scene.tags.map(t => t.id).sort() : [];
first = false;
} else {
const tIds = !!scene.tags ? scene.tags.map(toId).sort() : [];
const tIds = scene.tags ? scene.tags.map(t => t.id).sort() : [];
if (!_.isEqual(ret, tIds)) {
ret = [];
@@ -183,50 +177,46 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
}
function updateScenesEditState(state: GQL.SlimSceneDataFragment[]) {
function toId(object : any) {
return object.id;
}
var rating : string = "";
var studioId : string | undefined;
var performerIds : string[] = [];
var tagIds : string[] = [];
var first = true;
let updateRating = "";
let updateStudioId : string | undefined;
let updatePerformerIds : string[] = [];
let updateTagIds : string[] = [];
let first = true;
state.forEach((scene : GQL.SlimSceneDataFragment) => {
var thisRating = scene.rating ? scene.rating.toString() : "";
var thisStudio = scene.studio ? scene.studio.id : undefined;
const thisRating = scene.rating ? scene.rating.toString() : "";
const thisStudio = scene.studio ? scene.studio.id : undefined;
if (first) {
rating = thisRating;
studioId = thisStudio;
performerIds = !!scene.performers ? scene.performers.map(toId).sort() : [];
tagIds = !!scene.tags ? scene.tags.map(toId).sort() : [];
updateRating = thisRating;
updateStudioId = thisStudio;
updatePerformerIds = scene.performers ? scene.performers.map(p => p.id).sort() : [];
updateTagIds = scene.tags ? scene.tags.map(p => p.id).sort() : [];
first = false;
} else {
if (rating !== thisRating) {
rating = "";
updateRating = "";
}
if (studioId !== thisStudio) {
studioId = undefined;
updateStudioId = undefined;
}
const perfIds = !!scene.performers ? scene.performers.map(toId).sort() : [];
const tIds = !!scene.tags ? scene.tags.map(toId).sort() : [];
const perfIds = scene.performers ? scene.performers.map(p => p.id).sort() : [];
const tIds = scene.tags ? scene.tags.map(t => t.id).sort() : [];
if (!_.isEqual(performerIds, perfIds)) {
performerIds = [];
updatePerformerIds = [];
}
if (!_.isEqual(tagIds, tIds)) {
tagIds = [];
updateTagIds = [];
}
}
});
setRating(rating);
setStudioId(studioId);
setPerformerIds(performerIds);
setTagIds(tagIds);
setRating(updateRating);
setStudioId(updateStudioId);
setPerformerIds(updatePerformerIds);
setTagIds(updateTagIds);
}
useEffect(() => {
@@ -237,7 +227,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
return (
<FilterSelect
type={type}
isMulti={true}
isMulti
onSelect={(items) => {
const ids = items.map((i) => i.id);
switch (type) {

View File

@@ -6,8 +6,8 @@ import { SceneMarkerList } from "./SceneMarkerList";
const Scenes = () => (
<Switch>
<Route exact={true} path="/scenes" component={SceneList} />
<Route exact={true} path="/scenes/markers" component={SceneMarkerList} />
<Route exact path="/scenes" component={SceneList} />
<Route exact path="/scenes/markers" component={SceneMarkerList} />
<Route path="/scenes/:id" component={Scene} />
</Switch>
);

View File

@@ -27,8 +27,8 @@ export class StashService {
if (platformUrl.protocol === "https:") {
wsPlatformUrl.protocol = "wss:";
}
const url = platformUrl.toString().slice(0, -1) + "/graphql";
const wsUrl = wsPlatformUrl.toString().slice(0, -1) + "/graphql";
const url = `${platformUrl.toString().slice(0, -1) }/graphql`;
const wsUrl = `${wsPlatformUrl.toString().slice(0, -1) }/graphql`;
const httpLink = new HttpLink({
uri: url,
@@ -52,7 +52,7 @@ export class StashService {
StashService.cache = new InMemoryCache();
StashService.client = new ApolloClient({
link: link,
link,
cache: StashService.cache
});
@@ -65,10 +65,10 @@ export class StashService {
}
private static invalidateQueries(queries : string[]) {
if (!!StashService.cache) {
if (StashService.cache) {
const cache = StashService.cache as any;
const keyMatchers = queries.map(query => {
return new RegExp("^" + query);
return new RegExp(`^${ query}`);
});
const rootQuery = cache.data.data.ROOT_QUERY;
@@ -197,11 +197,11 @@ export class StashService {
public static useFindGallery(id: string) { return GQL.useFindGallery({variables: {id}}); }
public static useFindScene(id: string) { return GQL.useFindScene({variables: {id}}); }
public static useFindPerformer(id: string) {
const skip = id === "new" ? true : false;
const skip = id === "new";
return GQL.useFindPerformer({variables: {id}, skip});
}
public static useFindStudio(id: string) {
const skip = id === "new" ? true : false;
const skip = id === "new";
return GQL.useFindStudio({variables: {id}, skip});
}
@@ -312,7 +312,7 @@ export class StashService {
}
public static useScenesUpdate(input: GQL.SceneUpdateInput[]) {
return GQL.useScenesUpdate({ variables: { input : input }});
return GQL.useScenesUpdate({ variables: { input }});
}
public static useSceneDestroy(input: GQL.SceneDestroyInput) {
@@ -360,7 +360,7 @@ export class StashService {
return GQL.useTagCreate({
variables: input,
refetchQueries: ["AllTags", "AllTagsForFilter"],
//update: () => StashService.invalidateQueries(StashService.tagMutationImpactedQueries)
// update: () => StashService.invalidateQueries(StashService.tagMutationImpactedQueries)
});
}
public static useTagUpdate(input: GQL.TagUpdateInput) {
@@ -435,7 +435,7 @@ export class StashService {
return StashService.client.query<GQL.ScrapePerformerUrlQuery>({
query: GQL.ScrapePerformerUrlDocument,
variables: {
url: url,
url,
},
});
}
@@ -444,7 +444,7 @@ export class StashService {
return StashService.client.query<GQL.ScrapeSceneUrlQuery>({
query: GQL.ScrapeSceneUrlDocument,
variables: {
url: url,
url,
},
});
}
@@ -454,7 +454,7 @@ export class StashService {
query: GQL.ScrapeSceneDocument,
variables: {
scraper_id: scraperId,
scene: scene,
scene,
},
});
}
@@ -507,17 +507,18 @@ export class StashService {
public static querySceneByPathRegex(filter: GQL.FindFilterType) {
return StashService.client.query<GQL.FindScenesByPathRegexQuery>({
query: GQL.FindScenesByPathRegexDocument,
variables: {filter: filter},
variables: {filter},
});
}
public static queryParseSceneFilenames(filter: GQL.FindFilterType, config: GQL.SceneParserInput) {
return StashService.client.query<GQL.ParseSceneFilenamesQuery>({
query: GQL.ParseSceneFilenamesDocument,
variables: {filter: filter, config: config},
variables: {filter, config},
fetchPolicy: "network-only",
});
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
}

View File

@@ -62,61 +62,6 @@ interface IQuery<T extends IQueryResult, T2 extends IDataItem> {
getCount: (data: T) => number;
}
type ScenesQuery = QueryHookResult<FindScenesQuery, FindScenesVariables>;
export const useScenesList = (props:IListHookOptions<ScenesQuery>) => (
useList<ScenesQuery, SlimSceneDataFragment>({
...props,
filterMode: FilterMode.Scenes,
useData: StashService.useFindScenes,
getData: (result:ScenesQuery) => (result?.data?.findScenes?.scenes ?? []),
getCount: (result:ScenesQuery) => (result?.data?.findScenes?.count ?? 0)
})
)
type SceneMarkersQuery = QueryHookResult<FindSceneMarkersQuery, FindSceneMarkersVariables>;
export const useSceneMarkersList = (props:IListHookOptions<SceneMarkersQuery>) => (
useList<SceneMarkersQuery, FindSceneMarkersSceneMarkers>({
...props,
filterMode: FilterMode.SceneMarkers,
useData: StashService.useFindSceneMarkers,
getData: (result:SceneMarkersQuery) => (result?.data?.findSceneMarkers?.scene_markers?? []),
getCount: (result:SceneMarkersQuery) => (result?.data?.findSceneMarkers?.count ?? 0)
})
)
type GalleriesQuery = QueryHookResult<FindGalleriesQuery, FindGalleriesVariables>;
export const useGalleriesList = (props:IListHookOptions<GalleriesQuery>) => (
useList<GalleriesQuery, GalleryDataFragment>({
...props,
filterMode: FilterMode.Galleries,
useData: StashService.useFindGalleries,
getData: (result:GalleriesQuery) => (result?.data?.findGalleries?.galleries ?? []),
getCount: (result:GalleriesQuery) => (result?.data?.findGalleries?.count ?? 0)
})
)
type StudiosQuery = QueryHookResult<FindStudiosQuery, FindStudiosVariables>;
export const useStudiosList = (props:IListHookOptions<StudiosQuery>) => (
useList<StudiosQuery, StudioDataFragment>({
...props,
filterMode: FilterMode.Studios,
useData: StashService.useFindStudios,
getData: (result:StudiosQuery) => (result?.data?.findStudios?.studios ?? []),
getCount: (result:StudiosQuery) => (result?.data?.findStudios?.count ?? 0)
})
)
type PerformersQuery = QueryHookResult<FindPerformersQuery, FindPerformersVariables>;
export const usePerformersList = (props:IListHookOptions<PerformersQuery>) => (
useList<PerformersQuery, PerformerDataFragment>({
...props,
filterMode: FilterMode.Performers,
useData: StashService.useFindPerformers,
getData: (result:PerformersQuery) => (result?.data?.findPerformers?.performers ?? []),
getCount: (result:PerformersQuery) => (result?.data?.findPerformers?.count ?? 0)
})
)
const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
options: (IListHookOptions<QueryResult> & IQuery<QueryResult, QueryData>)
): IListHookData => {
@@ -130,9 +75,9 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
const totalCount = options.getCount(result);
const items = options.getData(result);
function updateQueryParams(filter:ListFilterModel) {
const newLocation = Object.assign({}, history.location);
newLocation.search = filter.makeQueryParameters();
function updateQueryParams(listfilter:ListFilterModel) {
const newLocation = { ...history.location};
newLocation.search = listfilter.makeQueryParameters();
history.replace(newLocation);
}
@@ -180,7 +125,7 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
// Find if we are editing an existing criteria, then modify that. Or create a new one.
const existingIndex = newFilter.criteria.findIndex((c) => {
// If we modified an existing criterion, then look for the old id.
const id = !!oldId ? oldId : criterion.getId();
const id = oldId || criterion.getId();
return c.getId() === id;
});
if (existingIndex === -1) {
@@ -214,14 +159,6 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
updateQueryParams(newFilter);
}
function onSelectChange(id: string, selected : boolean, shiftKey: boolean) {
if (shiftKey) {
multiSelect(id);
} else {
singleSelect(id, selected);
}
}
function singleSelect(id: string, selected: boolean) {
setLastClickedId(id);
@@ -235,6 +172,25 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
setSelectedIds(newSelectedIds);
}
function selectRange(startIndex : number, endIndex : number) {
let start = startIndex;
let end = endIndex;
if (start > end) {
const tmp = start;
start = end;
end = tmp;
}
const subset = items.slice(start, end + 1);
const newSelectedIds : Set<string> = new Set();
subset.forEach((item) => {
newSelectedIds.add(item.id);
});
setSelectedIds(newSelectedIds);
}
function multiSelect(id: string) {
let startIndex = 0;
let thisIndex = -1;
@@ -252,21 +208,12 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
selectRange(startIndex, thisIndex);
}
function selectRange(startIndex : number, endIndex : number) {
if (startIndex > endIndex) {
let tmp = startIndex;
startIndex = endIndex;
endIndex = tmp;
function onSelectChange(id: string, selected : boolean, shiftKey: boolean) {
if (shiftKey) {
multiSelect(id);
} else {
singleSelect(id, selected);
}
const subset = items.slice(startIndex, endIndex + 1);
const newSelectedIds : Set<string> = new Set();
subset.forEach((item) => {
newSelectedIds.add(item.id);
});
setSelectedIds(newSelectedIds);
}
function onSelectAll() {
@@ -324,10 +271,66 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
currentPage={filter.currentPage}
totalItems={totalCount}
onChangePage={onChangePage}
loading={result.loading}
/>
</div>
);
return { filter, template, onSelectChange };
}
type ScenesQuery = QueryHookResult<FindScenesQuery, FindScenesVariables>;
export const useScenesList = (props:IListHookOptions<ScenesQuery>) => (
useList<ScenesQuery, SlimSceneDataFragment>({
...props,
filterMode: FilterMode.Scenes,
useData: StashService.useFindScenes,
getData: (result:ScenesQuery) => (result?.data?.findScenes?.scenes ?? []),
getCount: (result:ScenesQuery) => (result?.data?.findScenes?.count ?? 0)
})
)
type SceneMarkersQuery = QueryHookResult<FindSceneMarkersQuery, FindSceneMarkersVariables>;
export const useSceneMarkersList = (props:IListHookOptions<SceneMarkersQuery>) => (
useList<SceneMarkersQuery, FindSceneMarkersSceneMarkers>({
...props,
filterMode: FilterMode.SceneMarkers,
useData: StashService.useFindSceneMarkers,
getData: (result:SceneMarkersQuery) => (result?.data?.findSceneMarkers?.scene_markers?? []),
getCount: (result:SceneMarkersQuery) => (result?.data?.findSceneMarkers?.count ?? 0)
})
)
type GalleriesQuery = QueryHookResult<FindGalleriesQuery, FindGalleriesVariables>;
export const useGalleriesList = (props:IListHookOptions<GalleriesQuery>) => (
useList<GalleriesQuery, GalleryDataFragment>({
...props,
filterMode: FilterMode.Galleries,
useData: StashService.useFindGalleries,
getData: (result:GalleriesQuery) => (result?.data?.findGalleries?.galleries ?? []),
getCount: (result:GalleriesQuery) => (result?.data?.findGalleries?.count ?? 0)
})
)
type StudiosQuery = QueryHookResult<FindStudiosQuery, FindStudiosVariables>;
export const useStudiosList = (props:IListHookOptions<StudiosQuery>) => (
useList<StudiosQuery, StudioDataFragment>({
...props,
filterMode: FilterMode.Studios,
useData: StashService.useFindStudios,
getData: (result:StudiosQuery) => (result?.data?.findStudios?.studios ?? []),
getCount: (result:StudiosQuery) => (result?.data?.findStudios?.count ?? 0)
})
)
type PerformersQuery = QueryHookResult<FindPerformersQuery, FindPerformersVariables>;
export const usePerformersList = (props:IListHookOptions<PerformersQuery>) => (
useList<PerformersQuery, PerformerDataFragment>({
...props,
filterMode: FilterMode.Performers,
useData: StashService.useFindPerformers,
getData: (result:PerformersQuery) => (result?.data?.findPerformers?.performers ?? []),
getCount: (result:PerformersQuery) => (result?.data?.findPerformers?.count ?? 0)
})
)

View File

@@ -16,21 +16,6 @@ interface ILocalForage<T> {
error: Error | null;
}
export function useInterfaceLocalForage(): ILocalForage<IInterfaceConfig | undefined> {
const result = useLocalForage("interface");
// Set defaults
React.useEffect(() => {
if (result.data === undefined) {
result.setData({
wall: {
// nothing here currently
},
});
}
});
return result;
}
function useLocalForage(item: string): ILocalForage<ValidTypes> {
const [json, setJson] = React.useState<ValidTypes>(undefined);
@@ -64,3 +49,18 @@ function useLocalForage(item: string): ILocalForage<ValidTypes> {
return {data: json, setData: setJson, error: err};
}
export function useInterfaceLocalForage(): ILocalForage<IInterfaceConfig | undefined> {
const result = useLocalForage("interface");
// Set defaults
React.useEffect(() => {
if (result.data === undefined) {
result.setData({
wall: {
// nothing here currently
},
});
}
});
return result;
}

View File

@@ -56,6 +56,7 @@ function createHookObject(toastFunc: (toast:IToast) => void) {
return {
success: toastFunc,
error: (error: Error) => {
// eslint-disable-next-line no-console
console.error(error.message);
toastFunc({
variant: 'danger',

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-param-reassign, no-console */
import { useEffect, useRef } from "react";
import { StashService } from "../core/StashService";
@@ -31,7 +33,7 @@ export class VideoHoverHook {
videoTag.pause();
}
};
videoTag.onpause = () => isPlaying.current = false;
videoTag.onpause = () => { isPlaying.current = false };
}, [videoEl]);
useEffect(() => {

View File

@@ -1,3 +1,5 @@
/* eslint-disable consistent-return */
import { CriterionModifier } from "src/core/generated-graphql";
import { ILabeledId, ILabeledValue } from "../types";
@@ -26,7 +28,7 @@ export type CriterionType =
"aliases";
export abstract class Criterion<Option = any, Value = any> {
public static getLabel(type: CriterionType = "none"): string {
public static getLabel(type: CriterionType = "none") {
switch (type) {
case "none": return "None";
case "rating": return "Rating";
@@ -157,7 +159,7 @@ export class StringCriterion extends Criterion<string, string> {
this.options = options;
this.inputType = "text";
if (!!parameterName) {
if (parameterName) {
this.parameterName = parameterName;
} else {
this.parameterName = type;
@@ -187,7 +189,7 @@ export class NumberCriterion extends Criterion<number, number> {
this.options = options;
this.inputType = "number";
if (!!parameterName) {
if (parameterName) {
this.parameterName = parameterName;
} else {
this.parameterName = type;

View File

@@ -1,5 +1,4 @@
import * as GQL from "src/core/generated-graphql";
import { CriterionModifier } from "src/core/generated-graphql";
import { ILabeledId } from "../types";
import {
Criterion,
@@ -10,11 +9,11 @@ import {
export class TagsCriterion extends Criterion<GQL.AllTagsForFilterAllTags, ILabeledId[]> {
public type: CriterionType;
public parameterName: string;
public modifier = CriterionModifier.IncludesAll;
public modifier = GQL.CriterionModifier.IncludesAll;
public modifierOptions = [
Criterion.getModifierOption(CriterionModifier.IncludesAll),
Criterion.getModifierOption(CriterionModifier.Includes),
Criterion.getModifierOption(CriterionModifier.Excludes),
Criterion.getModifierOption(GQL.CriterionModifier.IncludesAll),
Criterion.getModifierOption(GQL.CriterionModifier.Includes),
Criterion.getModifierOption(GQL.CriterionModifier.Excludes),
];
public options: GQL.AllTagsForFilterAllTags[] = [];
public value: ILabeledId[] = [];

View File

@@ -1,3 +1,4 @@
/* eslint-disable consistent-return, default-case */
import {
CriterionModifier,
} from "src/core/generated-graphql";
@@ -26,8 +27,8 @@ export function makeCriteria(type: CriterionType = "none") {
case "studios": return new StudiosCriterion();
case "birth_year":
case "age":
var ret = new NumberCriterion(type, type);
case "age": {
const ret = new NumberCriterion(type, type);
// null/not null doesn't make sense for these criteria
ret.modifierOptions = [
Criterion.getModifierOption(CriterionModifier.Equals),
@@ -36,6 +37,7 @@ export function makeCriteria(type: CriterionType = "none") {
Criterion.getModifierOption(CriterionModifier.LessThan)
];
return ret;
}
case "ethnicity":
case "country":
case "eye_color":

View File

@@ -67,7 +67,7 @@ export class ListFilterModel {
new StudiosCriterionOption(),
];
break;
case FilterMode.Performers:
case FilterMode.Performers: {
if (!!this.sortBy === false) { this.sortBy = "name"; }
this.sortByOptions = ["name", "height", "birthdate", "scenes_count"];
this.displayModeOptions = [
@@ -75,8 +75,8 @@ export class ListFilterModel {
DisplayMode.List,
];
var numberCriteria : CriterionType[] = ["birth_year", "age"];
var stringCriteria : CriterionType[] = [
const numberCriteria : CriterionType[] = ["birth_year", "age"];
const stringCriteria : CriterionType[] = [
"ethnicity",
"country",
"eye_color",
@@ -98,6 +98,7 @@ export class ListFilterModel {
return new CriterionOption(Criterion.getLabel(c), c);
}));
break;
}
case FilterMode.Studios:
if (!!this.sortBy === false) { this.sortBy = "name"; }
this.sortByOptions = ["name", "scenes_count"];
@@ -173,13 +174,13 @@ export class ListFilterModel {
jsonParameters = [params.c];
}
for (const jsonString of jsonParameters) {
jsonParameters.forEach(jsonString => {
const encodedCriterion = JSON.parse(jsonString);
const criterion = makeCriteria(encodedCriterion.type);
criterion.value = encodedCriterion.value;
criterion.modifier = encodedCriterion.modifier;
this.criteria.push(criterion);
}
});
}
}
@@ -221,10 +222,11 @@ export class ListFilterModel {
const result: SceneFilterType = {};
this.criteria.forEach((criterion) => {
switch (criterion.type) {
case "rating":
case "rating": {
const ratingCrit = criterion as RatingCriterion;
result.rating = { value: ratingCrit.value, modifier: ratingCrit.modifier };
break;
}
case "resolution": {
switch ((criterion as ResolutionCriterion).value) {
case "240p": result.resolution = ResolutionEnum.Low; break;
@@ -232,6 +234,7 @@ export class ListFilterModel {
case "720p": result.resolution = ResolutionEnum.StandardHd; break;
case "1080p": result.resolution = ResolutionEnum.FullHd; break;
case "4k": result.resolution = ResolutionEnum.FourK; break;
// no default
}
break;
}
@@ -241,19 +244,23 @@ export class ListFilterModel {
case "isMissing":
result.is_missing = (criterion as IsMissingCriterion).value;
break;
case "tags":
case "tags": {
const tagsCrit = criterion as TagsCriterion;
result.tags = { value: tagsCrit.value.map((tag) => tag.id), modifier: tagsCrit.modifier };
break;
case "performers":
}
case "performers": {
const perfCrit = criterion as PerformersCriterion;
result.performers = { value: perfCrit.value.map((perf) => perf.id), modifier: perfCrit.modifier };
break;
case "studios":
}
case "studios": {
const studCrit = criterion as StudiosCriterion;
result.studios = { value: studCrit.value.map((studio) => studio.id), modifier: studCrit.modifier };
break;
}
// no default
}
});
return result;
}
@@ -265,55 +272,68 @@ export class ListFilterModel {
case "favorite":
result.filter_favorites = (criterion as FavoriteCriterion).value === "true";
break;
case "birth_year":
case "birth_year": {
const byCrit = criterion as NumberCriterion;
result.birth_year = { value: byCrit.value, modifier: byCrit.modifier };
break;
case "age":
}
case "age": {
const ageCrit = criterion as NumberCriterion;
result.age = { value: ageCrit.value, modifier: ageCrit.modifier };
break;
case "ethnicity":
}
case "ethnicity": {
const ethCrit = criterion as StringCriterion;
result.ethnicity = { value: ethCrit.value, modifier: ethCrit.modifier };
break;
case "country":
}
case "country": {
const cntryCrit = criterion as StringCriterion;
result.country = { value: cntryCrit.value, modifier: cntryCrit.modifier };
break;
case "eye_color":
}
case "eye_color": {
const ecCrit = criterion as StringCriterion;
result.eye_color = { value: ecCrit.value, modifier: ecCrit.modifier };
break;
case "height":
}
case "height": {
const hCrit = criterion as StringCriterion;
result.height = { value: hCrit.value, modifier: hCrit.modifier };
break;
case "measurements":
}
case "measurements": {
const mCrit = criterion as StringCriterion;
result.measurements = { value: mCrit.value, modifier: mCrit.modifier };
break;
case "fake_tits":
}
case "fake_tits": {
const ftCrit = criterion as StringCriterion;
result.fake_tits = { value: ftCrit.value, modifier: ftCrit.modifier };
break;
case "career_length":
}
case "career_length": {
const clCrit = criterion as StringCriterion;
result.career_length = { value: clCrit.value, modifier: clCrit.modifier };
break;
case "tattoos":
}
case "tattoos": {
const tCrit = criterion as StringCriterion;
result.tattoos = { value: tCrit.value, modifier: tCrit.modifier };
break;
case "piercings":
}
case "piercings": {
const pCrit = criterion as StringCriterion;
result.piercings = { value: pCrit.value, modifier: pCrit.modifier };
break;
case "aliases":
}
case "aliases": {
const aCrit = criterion as StringCriterion;
result.aliases = { value: aCrit.value, modifier: aCrit.modifier };
break;
}
// no default
}
});
return result;
}
@@ -322,19 +342,23 @@ export class ListFilterModel {
const result: SceneMarkerFilterType = {};
this.criteria.forEach((criterion) => {
switch (criterion.type) {
case "tags":
case "tags": {
const tagsCrit = criterion as TagsCriterion;
result.tags = { value: tagsCrit.value.map((tag) => tag.id), modifier: tagsCrit.modifier };
break;
case "sceneTags":
}
case "sceneTags": {
const sceneTagsCrit = criterion as TagsCriterion;
result.scene_tags = { value: sceneTagsCrit.value.map((tag) => tag.id), modifier: sceneTagsCrit.modifier };
break;
case "performers":
}
case "performers": {
const performersCrit = criterion as PerformersCriterion;
result.performers = { value: performersCrit.value.map((performer) => performer.id), modifier: performersCrit.modifier };
break;
}
// no default
}
});
return result;
}

View File

@@ -1,5 +1,5 @@
declare module "react-jw-player" {
// typing module default export as `any` will allow you to access its members without compiler warning
var ReactJSPlayer: any;
const ReactJSPlayer: any;
export default ReactJSPlayer;
}

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-console */
// This optional code is used to register a service worker.
// register() is not called by default.
@@ -20,52 +22,16 @@ const isLocalhost = Boolean(
),
);
interface Config {
interface IConfig {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
}
export function register(config?: Config) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href,
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
"This web app is being served cache-first by a service " +
"worker. To learn more, visit http://bit.ly/CRA-PWA",
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
function registerValidSW(swUrl: string, config?: IConfig) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
// eslint-disable-next-line no-param-reassign
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
@@ -106,7 +72,7 @@ function registerValidSW(swUrl: string, config?: Config) {
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
function checkValidServiceWorker(swUrl: string, config?: IConfig) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then((response) => {
@@ -134,6 +100,44 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
});
}
export function register(config?: IConfig) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href,
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
"This web app is being served cache-first by a service " +
"worker. To learn more, visit http://bit.ly/CRA-PWA",
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
export function unregister() {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then((registration) => {

View File

@@ -114,7 +114,7 @@ const renderMultiSelect = (options: {
<td>
<FilterSelect
type={options.type}
isMulti={true}
isMulti
onSelect={(items) => options.onChange(items.map((i) => i.id))}
initialIds={options.initialIds ?? []}
/>

View File

@@ -16,12 +16,13 @@ const truncate = (value?: string, limit: number = 100, tail: string = "...") =>
}
const fileSize = (bytes: number = 0, precision: number = 2) => {
if (Number.isNaN(parseFloat(String(bytes))) || !isFinite(bytes))
if (Number.isNaN(parseFloat(String(bytes))) || !Number.isFinite(bytes))
return "?";
let unit = 0;
while ( bytes >= 1024 ) {
bytes /= 1024;
let count = bytes;
while ( count >= 1024 ) {
count /= 1024;
unit++;
}
@@ -48,7 +49,7 @@ const fileNameFromPath = (path: string) => {
return path.replace(/^.*[\\/]/, "");
}
const age = (dateString?: string, fromDateString?: string) => {
const getAge = (dateString?: string, fromDateString?: string) => {
if (!dateString)
return 0;
@@ -72,16 +73,18 @@ const bitRate = (bitrate: number) => {
const resolution = (height: number) => {
if (height >= 240 && height < 480) {
return "240p";
} else if (height >= 480 && height < 720) {
}
if (height >= 480 && height < 720) {
return "480p";
} else if (height >= 720 && height < 1080) {
}
if (height >= 720 && height < 1080) {
return "720p";
} else if (height >= 1080 && height < 2160) {
}
if (height >= 1080 && height < 2160) {
return "1080p";
} else if (height >= 2160) {
}
if (height >= 2160) {
return "4K";
} else {
return undefined;
}
}
@@ -90,7 +93,7 @@ const TextUtils = {
fileSize,
secondsToTimestamp,
fileNameFromPath,
age,
age: getAge,
bitRate,
resolution
}