mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Linting update
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
57
ui/v2.5/src/components/Shared/HoverPopover.tsx
Normal file
57
ui/v2.5/src/components/Shared/HoverPopover.tsx
Normal 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>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -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()} />
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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> : ''}
|
||||
|
||||
@@ -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="" />
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 : []}
|
||||
/>
|
||||
|
||||
@@ -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 '\\' 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);
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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`,
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
2
ui/v2.5/src/models/react-jw-player.d.ts
vendored
2
ui/v2.5/src/models/react-jw-player.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
declare module "react-jw-player" {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 ?? []}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user