This commit is contained in:
Infinite
2020-01-04 22:46:08 +01:00
parent 4d44deff64
commit f50cb45ca5
25 changed files with 188 additions and 241 deletions

View File

@@ -18,7 +18,7 @@
"lodash": "4.17.13", "lodash": "4.17.13",
"node-sass": "4.12.0", "node-sass": "4.12.0",
"query-string": "6.5.0", "query-string": "6.5.0",
"react": "16.12.0", "react": "~16.12.0",
"react-apollo": "2.5.6", "react-apollo": "2.5.6",
"react-apollo-hooks": "0.4.5", "react-apollo-hooks": "0.4.5",
"react-bootstrap": "^1.0.0-beta.16", "react-bootstrap": "^1.0.0-beta.16",
@@ -68,6 +68,6 @@
"graphql-codegen-typescript-client": "0.18.2", "graphql-codegen-typescript-client": "0.18.2",
"graphql-codegen-typescript-common": "0.18.2", "graphql-codegen-typescript-common": "0.18.2",
"graphql-codegen-typescript-react-apollo": "0.18.2", "graphql-codegen-typescript-react-apollo": "0.18.2",
"typescript": "3.4.5" "typescript": "~3.7.4"
} }
} }

View File

@@ -1,4 +1,3 @@
import _ from "lodash";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Spinner } from 'react-bootstrap'; import { Spinner } from 'react-bootstrap';
import * as GQL from "../../core/generated-graphql"; import * as GQL from "../../core/generated-graphql";

View File

@@ -1,4 +1,3 @@
import _ from "lodash";
import React from "react"; import React from "react";
import { Table } from 'react-bootstrap'; import { Table } from 'react-bootstrap';
import { QueryHookResult } from "react-apollo-hooks"; import { QueryHookResult } from "react-apollo-hooks";

View File

@@ -1,4 +1,3 @@
import _ from "lodash";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import Lightbox from "react-images"; import Lightbox from "react-images";
import Gallery from "react-photo-gallery"; import Gallery from "react-photo-gallery";

View File

@@ -1,19 +1,8 @@
import { import React from "react";
H1, import { Table, Spinner } from 'react-bootstrap';
H4,
H6,
HTMLTable,
Spinner,
Tag,
} from "@blueprintjs/core";
import React, { FunctionComponent } from "react";
import * as GQL from "../../core/generated-graphql";
import { TextUtils } from "../../utils/text";
import { StashService } from "../../core/StashService"; import { StashService } from "../../core/StashService";
interface IProps {} export const SettingsAboutPanel: React.FC = () => {
export const SettingsAboutPanel: FunctionComponent<IProps> = (props: IProps) => {
const { data, error, loading } = StashService.useVersion(); const { data, error, loading } = StashService.useVersion();
function maybeRenderTag() { function maybeRenderTag() {
@@ -30,7 +19,7 @@ export const SettingsAboutPanel: FunctionComponent<IProps> = (props: IProps) =>
if (!data || !data.version) { return; } if (!data || !data.version) { return; }
return ( return (
<> <>
<HTMLTable> <Table>
<tbody> <tbody>
{maybeRenderTag()} {maybeRenderTag()}
<tr> <tr>
@@ -42,14 +31,14 @@ export const SettingsAboutPanel: FunctionComponent<IProps> = (props: IProps) =>
<td>{data.version.build_time}</td> <td>{data.version.build_time}</td>
</tr> </tr>
</tbody> </tbody>
</HTMLTable> </Table>
</> </>
); );
} }
return ( return (
<> <>
<H4>About</H4> <h4>About</h4>
{!data || loading ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined} {!data || loading ? <Spinner animation="border" variant="light" /> : undefined}
{!!error ? <span>error.message</span> : undefined} {!!error ? <span>error.message</span> : undefined}
{renderVersion()} {renderVersion()}
</> </>

View File

@@ -3,25 +3,19 @@ import {
Button, Button,
Divider, Divider,
FormGroup, FormGroup,
H1,
H4,
H6,
InputGroup, InputGroup,
Spinner, Spinner,
Tag,
Checkbox, Checkbox,
HTMLSelect, HTMLSelect,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import React, { FunctionComponent, useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import * as GQL from "../../core/generated-graphql"; import * as GQL from "../../core/generated-graphql";
import { StashService } from "../../core/StashService"; import { StashService } from "../../core/StashService";
import { ErrorUtils } from "../../utils/errors"; import { ErrorUtils } from "../../utils/errors";
import { ToastUtils } from "../../utils/toasts"; import { ToastUtils } from "../../utils/toasts";
import { FolderSelect } from "../Shared/FolderSelect/FolderSelect"; import { FolderSelect } from "../Shared/FolderSelect/FolderSelect";
interface IProps {} export const SettingsConfigurationPanel: React.FC = () => {
export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IProps) => {
// Editing config state // Editing config state
const [stashes, setStashes] = useState<string[]>([]); const [stashes, setStashes] = useState<string[]>([]);
const [databasePath, setDatabasePath] = useState<string | undefined>(undefined); const [databasePath, setDatabasePath] = useState<string | undefined>(undefined);
@@ -51,7 +45,6 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
logLevel, logLevel,
logAccess, logAccess,
excludes, excludes,
}); });
useEffect(() => { useEffect(() => {
@@ -86,7 +79,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
} }
function excludeRemoveRegex(idx: number) { function excludeRemoveRegex(idx: number) {
const newExcludes = excludes.filter((regex, i) => i!== idx ); const newExcludes = excludes.filter((_regex, i) => i !== idx );
setExcludes(newExcludes); setExcludes(newExcludes);
} }
@@ -148,7 +141,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
<> <>
{!!error ? <h1>{error.message}</h1> : undefined} {!!error ? <h1>{error.message}</h1> : undefined}
{(!data || !data.configuration || loading) ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined} {(!data || !data.configuration || loading) ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined}
<H4>Library</H4> <h4>Library</h4>
<FormGroup> <FormGroup>
<FormGroup> <FormGroup>
<FormGroup <FormGroup
@@ -208,7 +201,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
<Divider /> <Divider />
<FormGroup> <FormGroup>
<H4>Video</H4> <h4>Video</h4>
<FormGroup <FormGroup
label="Maximum transcode size" label="Maximum transcode size"
helperText="Maximum size for generated transcodes" helperText="Maximum size for generated transcodes"
@@ -233,7 +226,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
<Divider /> <Divider />
<FormGroup> <FormGroup>
<H4>Authentication</H4> <h4>Authentication</h4>
<FormGroup <FormGroup
label="Username" label="Username"
helperText="Username to access Stash. Leave blank to disable user authentication" helperText="Username to access Stash. Leave blank to disable user authentication"
@@ -249,7 +242,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
</FormGroup> </FormGroup>
<Divider /> <Divider />
<H4>Logging</H4> <h4>Logging</h4>
<FormGroup <FormGroup
label="Log file" label="Log file"
helperText="Path to the file to output logging to. Blank to disable file logging. Requires restart." helperText="Path to the file to output logging to. Blank to disable file logging. Requires restart."

View File

@@ -3,12 +3,10 @@ import {
Checkbox, Checkbox,
Divider, Divider,
FormGroup, FormGroup,
H4,
Spinner, Spinner,
TextArea, TextArea,
NumericInput NumericInput
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import _ from "lodash";
import React, { FunctionComponent, useEffect, useState } from "react"; import React, { FunctionComponent, useEffect, useState } from "react";
import { StashService } from "../../core/StashService"; import { StashService } from "../../core/StashService";
import { ErrorUtils } from "../../utils/errors"; import { ErrorUtils } from "../../utils/errors";
@@ -64,7 +62,7 @@ export const SettingsInterfacePanel: FunctionComponent<IProps> = () => {
<> <>
{!!config.error ? <h1>{config.error.message}</h1> : undefined} {!!config.error ? <h1>{config.error.message}</h1> : undefined}
{(!config.data || !config.data.configuration || config.loading) ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined} {(!config.data || !config.data.configuration || config.loading) ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined}
<H4>User Interface</H4> <h4>User Interface</h4>
<FormGroup <FormGroup
label="Scene / Marker Wall" label="Scene / Marker Wall"
helperText="Configuration for wall items" helperText="Configuration for wall items"

View File

@@ -155,7 +155,7 @@ export const SettingsLogsPanel: FunctionComponent<IProps> = (props: IProps) => {
const logLevels = ["Debug", "Info", "Warning", "Error"]; const logLevels = ["Debug", "Info", "Warning", "Error"];
function filterByLogLevel(logEntry : LogEntry) { function filterByLogLevel(logEntry : LogEntry) {
if (logLevel == "Debug") { if (logLevel === "Debug") {
return true; return true;
} }

View File

@@ -4,21 +4,16 @@ import {
Checkbox, Checkbox,
Divider, Divider,
FormGroup, FormGroup,
H4,
AnchorButton,
ProgressBar, ProgressBar,
H5,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import React, { FunctionComponent, useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { StashService } from "../../../core/StashService"; import { StashService } from "../../../core/StashService";
import { ErrorUtils } from "../../../utils/errors"; import { ErrorUtils } from "../../../utils/errors";
import { ToastUtils } from "../../../utils/toasts"; import { ToastUtils } from "../../../utils/toasts";
import { GenerateButton } from "./GenerateButton"; import { GenerateButton } from "./GenerateButton";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
interface IProps {} export const SettingsTasksPanel: React.FC = () => {
export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) => {
const [isImportAlertOpen, setIsImportAlertOpen] = useState<boolean>(false); const [isImportAlertOpen, setIsImportAlertOpen] = useState<boolean>(false);
const [isCleanAlertOpen, setIsCleanAlertOpen] = useState<boolean>(false); const [isCleanAlertOpen, setIsCleanAlertOpen] = useState<boolean>(false);
const [useFileMetadata, setUseFileMetadata] = useState<boolean>(false); const [useFileMetadata, setUseFileMetadata] = useState<boolean>(false);
@@ -173,7 +168,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
return ( return (
<> <>
<FormGroup> <FormGroup>
<H5>Status: {status}</H5> <h5>Status: {status}</h5>
{!!status && status !== "Idle" ? <ProgressBar value={progress}/> : undefined} {!!status && status !== "Idle" ? <ProgressBar value={progress}/> : undefined}
</FormGroup> </FormGroup>
{maybeRenderStop()} {maybeRenderStop()}
@@ -186,13 +181,13 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
{renderImportAlert()} {renderImportAlert()}
{renderCleanAlert()} {renderCleanAlert()}
<H4>Running Jobs</H4> <h4>Running Jobs</h4>
{renderJobStatus()} {renderJobStatus()}
<Divider/> <Divider/>
<H4>Library</H4> <h4>Library</h4>
<FormGroup <FormGroup
helperText="Scan for new content and add it to the database." helperText="Scan for new content and add it to the database."
labelFor="scan" labelFor="scan"
@@ -208,7 +203,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
<Divider /> <Divider />
<H4>Auto Tagging</H4> <h4>Auto Tagging</h4>
<FormGroup <FormGroup
helperText="Auto-tag content based on filenames." helperText="Auto-tag content based on filenames."
@@ -240,7 +235,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
</FormGroup> </FormGroup>
<Divider /> <Divider />
<H4>Generated Content</H4> <h4>Generated Content</h4>
<GenerateButton /> <GenerateButton />
<FormGroup <FormGroup
helperText="Check for missing files and remove them from the database. This is a destructive action." helperText="Check for missing files and remove them from the database. This is a destructive action."
@@ -251,7 +246,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
</FormGroup> </FormGroup>
<Divider /> <Divider />
<H4>Metadata</H4> <h4>Metadata</h4>
<FormGroup <FormGroup
helperText="Export the database content into JSON format" helperText="Export the database content into JSON format"
labelFor="export" labelFor="export"

View File

@@ -1,4 +1,3 @@
import _ from "lodash";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent } from "react";
import { QueryHookResult } from "react-apollo-hooks"; import { QueryHookResult } from "react-apollo-hooks";
import { FindStudiosQuery, FindStudiosVariables } from "../../core/generated-graphql"; import { FindStudiosQuery, FindStudiosVariables } from "../../core/generated-graphql";

View File

@@ -116,7 +116,7 @@ export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProp
loop={true} loop={true}
ref={videoHoverHook.videoEl} ref={videoHoverHook.videoEl}
/> />
<img src={previewPath || screenshotPath} onError={() => previewNotFound()} /> <img alt="Preview" src={previewPath || screenshotPath} onError={() => previewNotFound()} />
{showTextContainer ? {showTextContainer ?
<div className="scene-wall-item-text-container"> <div className="scene-wall-item-text-container">
<div style={{lineHeight: 1}}> <div style={{lineHeight: 1}}>

View File

@@ -1,4 +1,3 @@
import _ from "lodash";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import * as GQL from "../../core/generated-graphql"; import * as GQL from "../../core/generated-graphql";
import "./Wall.scss"; import "./Wall.scss";

View File

@@ -1,15 +1,7 @@
import {
Button,
Classes,
Dialog,
FormGroup,
HTMLSelect,
InputGroup,
Tooltip,
} from "@blueprintjs/core";
import _ from "lodash"; import _ from "lodash";
import React, { FunctionComponent, useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { isArray } from "util"; import { Button, Form, Modal, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CriterionModifier } from "../../core/generated-graphql"; import { CriterionModifier } from "../../core/generated-graphql";
import { Criterion, CriterionType } from "../../models/list-filter/criteria/criterion"; import { Criterion, CriterionType } from "../../models/list-filter/criteria/criterion";
import { NoneCriterion } from "../../models/list-filter/criteria/none"; import { NoneCriterion } from "../../models/list-filter/criteria/none";
@@ -27,8 +19,8 @@ interface IAddFilterProps {
editingCriterion?: Criterion; editingCriterion?: Criterion;
} }
export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterProps) => { export const AddFilter: React.FC<IAddFilterProps> = (props: IAddFilterProps) => {
const singleValueSelect = useRef<HTMLSelect>(null); const defaultValue= useRef<string|number|undefined>();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [criterion, setCriterion] = useState<Criterion<any, any>>(new NoneCriterion()); const [criterion, setCriterion] = useState<Criterion<any, any>>(new NoneCriterion());
@@ -71,8 +63,8 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
} }
function onAddFilter() { function onAddFilter() {
if (!isArray(criterion.value) && !!singleValueSelect.current) { if (!Array.isArray(criterion.value) && defaultValue.current) {
const value = singleValueSelect.current.props.defaultValue; const value = defaultValue.current;
if (criterion.options && (value === undefined || value === "" || typeof value === "number")) { if (criterion.options && (value === undefined || value === "" || typeof value === "number")) {
criterion.value = criterion.options[0]; criterion.value = criterion.options[0];
} else if (typeof value === "number" && value === undefined) { } else if (typeof value === "number" && value === undefined) {
@@ -101,11 +93,15 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
if (criterion.modifierOptions.length === 0) { return; } if (criterion.modifierOptions.length === 0) { return; }
return ( return (
<div> <div>
<HTMLSelect <Form.Control
options={criterion.modifierOptions} as="select"
onChange={onChangedModifierSelect} onChange={onChangedModifierSelect}
defaultValue={criterion.modifier} value={criterion.modifier}
/> >
{ criterion.modifierOptions.map(c => (
<option value={c.value}>{c.label}</option>
))}
</Form.Control>
</div> </div>
); );
} }
@@ -116,7 +112,7 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
return; return;
} }
if (isArray(criterion.value)) { if (Array.isArray(criterion.value)) {
let type: "performers" | "studios" | "tags" | "" = ""; let type: "performers" | "studios" | "tags" | "" = "";
if (criterion instanceof PerformersCriterion) { if (criterion instanceof PerformersCriterion) {
type = "performers"; type = "performers";
@@ -140,21 +136,25 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
} }
} else { } else {
if (criterion.options) { if (criterion.options) {
defaultValue.current = criterion.value;
return ( return (
<HTMLSelect <Form.Control
ref={singleValueSelect} as="select"
options={criterion.options}
onChange={onChangedSingleSelect} onChange={onChangedSingleSelect}
defaultValue={criterion.value} value={criterion.value}
/> >
{ criterion.options.map(c => (
<option value={c}>{c}</option>
))}
</Form.Control>
); );
} else { } else {
return ( return (
<InputGroup <Form.Control
type={criterion.inputType} type={criterion.inputType}
onChange={onChangedInput} onChange={onChangedInput}
onBlur={onBlurInput} onBlur={onBlurInput}
defaultValue={criterion.value ? criterion.value : ""} value={criterion.value || ""}
/> />
) )
} }
@@ -162,57 +162,62 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
} }
return ( return (
<> <>
<FormGroup> <Form.Group>
{renderModifier()} {renderModifier()}
</FormGroup> </Form.Group>
<FormGroup> <Form.Group>
{renderSelect()} {renderSelect()}
</FormGroup> </Form.Group>
</> </>
); );
}; };
function maybeRenderFilterSelect() { function maybeRenderFilterSelect() {
if (!!props.editingCriterion) { return; } if (props.editingCriterion) { return; }
return ( return (
<FormGroup label="Filter"> <Form.Group controlId="filter">
<HTMLSelect <Form.Label>Filter</Form.Label>
style={{flexBasis: "min-content"}} <Form.Control
options={props.filter.criterionOptions} as="select"
onChange={onChangedCriteriaType} onChange={onChangedCriteriaType}
defaultValue={criterion.type} value={criterion.type}>
/> { props.filter.criterionOptions.map(c => (
</FormGroup> <option value={c.value}>{c.label}</option>
))}
</Form.Control>
</Form.Group>
); );
} }
const title = !props.editingCriterion ? "Add Filter" : "Update Filter"; const title = !props.editingCriterion ? "Add Filter" : "Update Filter";
return ( return (
<> <>
<Tooltip <OverlayTrigger
hoverOpenDelay={200} placement="top"
content="Filter" overlay={<Tooltip id="filter-tooltip">Filter</Tooltip>}
> >
<Button <Button
icon="filter"
onClick={() => onToggle()} onClick={() => onToggle()}
active={isOpen} active={isOpen}
large={true}
> >
<FontAwesomeIcon icon="filter" />
</Button> </Button>
</Tooltip> </OverlayTrigger>
<Dialog isOpen={isOpen} onClose={() => onToggle()} title={title}> <Modal
<div className="dialog-content"> show={isOpen}
{maybeRenderFilterSelect()} onHide={() => onToggle()}>
{maybeRenderFilterPopoverContents()} <Modal.Header>{title}</Modal.Header>
</div> <Modal.Body>
<div className={Classes.DIALOG_FOOTER}> <div className="dialog-content">
<div className={Classes.DIALOG_FOOTER_ACTIONS}> {maybeRenderFilterSelect()}
<Button onClick={onAddFilter} disabled={criterion.type === "none"}>{title}</Button> {maybeRenderFilterPopoverContents()}
</div> </div>
</div> </Modal.Body>
</Dialog> <Modal.Footer>
<Button onClick={onAddFilter} disabled={criterion.type === "none"}>{title}</Button>
</Modal.Footer>
</Modal>
</> </>
); );
}; };

View File

@@ -1,17 +1,8 @@
import {
Button,
ButtonGroup,
HTMLSelect,
InputGroup,
Menu,
MenuItem,
Popover,
Tag,
Tooltip,
Slider,
} from "@blueprintjs/core";
import { debounce } from "lodash"; import { debounce } from "lodash";
import React, { FunctionComponent, SyntheticEvent, useEffect, useState } from "react"; import React, { SyntheticEvent, useCallback, useState } from "react";
import { Badge, Button, ButtonGroup, Dropdown, Form, OverlayTrigger, Tooltip } from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Criterion } from "../../models/list-filter/criteria/criterion"; import { Criterion } from "../../models/list-filter/criteria/criterion";
import { ListFilterModel } from "../../models/list-filter/filter"; import { ListFilterModel } from "../../models/list-filter/filter";
import { DisplayMode } from "../../models/list-filter/types"; import { DisplayMode } from "../../models/list-filter/types";
@@ -40,17 +31,15 @@ interface IListFilterProps {
const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"]; const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"];
export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilterProps) => { export const ListFilter: React.FC<IListFilterProps> = (props: IListFilterProps) => {
let searchCallback: any; const searchCallback = useCallback(
debounce((event: any) => {
props.onChangeQuery(event.target.value);
}, 500), [props.onChangeQuery]
);
const [editingCriterion, setEditingCriterion] = useState<Criterion | undefined>(undefined); const [editingCriterion, setEditingCriterion] = useState<Criterion | undefined>(undefined);
useEffect(() => {
searchCallback = debounce((event: any) => {
props.onChangeQuery(event.target.value);
}, 500);
});
function onChangePageSize(event: SyntheticEvent<HTMLSelectElement>) { function onChangePageSize(event: SyntheticEvent<HTMLSelectElement>) {
const val = event!.currentTarget!.value; const val = event!.currentTarget!.value;
props.onChangePageSize(parseInt(val, 10)); props.onChangePageSize(parseInt(val, 10));
@@ -99,16 +88,16 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
function renderSortByOptions() { function renderSortByOptions() {
return props.filter.sortByOptions.map((option) => ( return props.filter.sortByOptions.map((option) => (
<MenuItem onClick={onChangeSortBy} text={option} key={option} /> <Dropdown.Item onClick={onChangeSortBy} key={option}>{option}</Dropdown.Item>
)); ));
} }
function renderDisplayModeOptions() { function renderDisplayModeOptions() {
function getIcon(option: DisplayMode) { function getIcon(option: DisplayMode) {
switch (option) { switch (option) {
case DisplayMode.Grid: return "grid-view"; case DisplayMode.Grid: return "th-large";
case DisplayMode.List: return "list"; case DisplayMode.List: return "list";
case DisplayMode.Wall: return "symbol-square"; case DisplayMode.Wall: return "square";
} }
} }
function getLabel(option: DisplayMode) { function getLabel(option: DisplayMode) {
@@ -119,29 +108,30 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
} }
} }
return props.filter.displayModeOptions.map((option) => ( return props.filter.displayModeOptions.map((option) => (
<Tooltip content={getLabel(option)} hoverOpenDelay={200}> <OverlayTrigger overlay={<Tooltip id="display-mode-tooltip">{getLabel(option)}</Tooltip>}>
<Button <Button
key={option} key={option}
active={props.filter.displayMode === option} active={props.filter.displayMode === option}
onClick={() => onChangeDisplayMode(option)} onClick={() => onChangeDisplayMode(option)}
icon={getIcon(option)} >
/> <FontAwesomeIcon icon={getIcon(option)} />
</Tooltip> </Button>
</OverlayTrigger>
)); ));
} }
function renderFilterTags() { function renderFilterTags() {
return props.filter.criteria.map((criterion) => ( return props.filter.criteria.map((criterion) => (
<Tag <Badge
key={criterion.getId()}
className="tag-item" className="tag-item"
itemID={criterion.getId()} variant="secondary"
interactive={true}
onRemove={() => onRemoveCriterionTag(criterion)}
onClick={() => onClickCriterionTag(criterion)} onClick={() => onClickCriterionTag(criterion)}
> >
{criterion.getLabel()} {criterion.getLabel()}
</Tag> <Button onClick={() => onRemoveCriterionTag(criterion)}>
<FontAwesomeIcon icon="times" />
</Button>
</Badge>
)); ));
} }
@@ -159,45 +149,40 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
function renderSelectAll() { function renderSelectAll() {
if (props.onSelectAll) { if (props.onSelectAll) {
return <MenuItem onClick={() => onSelectAll()} text="Select All" />; return <Dropdown.Item onClick={() => onSelectAll()}>Select All</Dropdown.Item>;
} }
} }
function renderSelectNone() { function renderSelectNone() {
if (props.onSelectNone) { if (props.onSelectNone) {
return <MenuItem onClick={() => onSelectNone()} text="Select None" />; return <Dropdown.Item onClick={() => onSelectNone()}>Select None</Dropdown.Item>;
} }
} }
function renderMore() { function renderMore() {
let options = []; let options = [
options.push(renderSelectAll()); renderSelectAll(),
options.push(renderSelectNone()); renderSelectNone()
];
if (props.otherOperations) { if (props.otherOperations) {
props.otherOperations.forEach((o) => { props.otherOperations.forEach((o) => {
options.push(<MenuItem onClick={o.onClick} text={o.text} />); options.push(<Dropdown.Item onClick={o.onClick}>{o.text}</Dropdown.Item>);
}); });
} }
options = options.filter((o) => !!o); if (options.length > 0) {
let menuItems = options as JSX.Element[];
function renderMoreOptions() {
return ( return (
<> <Dropdown>
{menuItems} <Dropdown.Toggle variant="secondary" id="more-menu">
</> <Button>
) <FontAwesomeIcon icon="ellipsis-h" />
} </Button>
</Dropdown.Toggle>
if (menuItems.length > 0) { <Dropdown.Menu>
return ( {options}
<Popover position="bottom"> </Dropdown.Menu>
<Button icon="more"/> </Dropdown>
<Menu>{renderMoreOptions()}</Menu>
</Popover>
); );
} }
} }
@@ -212,13 +197,11 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
if (props.onChangeZoom) { if (props.onChangeZoom) {
return ( return (
<span className="zoom-slider"> <span className="zoom-slider">
<Slider <Form.Control
type="range"
min={0} min={0}
value={props.zoomIndex}
initialValue={props.zoomIndex}
max={3} max={3}
labelRenderer={false} onChange={(event: any) => onChangeZoom(Number.parseInt(event.target.value))}
onChange={(v) => onChangeZoom(v)}
/> />
</span> </span>
); );
@@ -229,37 +212,37 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
return ( return (
<> <>
<div className="filter-container"> <div className="filter-container">
<InputGroup <Form.Control
large={true}
placeholder="Search..." placeholder="Search..."
defaultValue={props.filter.searchTerm} value={props.filter.searchTerm}
onChange={onChangeQuery} onChange={onChangeQuery}
className="filter-item" className="filter-item"
/> />
<HTMLSelect <Form.Control
large={true} as="select"
style={{flexBasis: "min-content"}}
options={PAGE_SIZE_OPTIONS}
onChange={onChangePageSize} onChange={onChangePageSize}
value={props.filter.itemsPerPage} value={props.filter.itemsPerPage.toString()}
className="filter-item" className="filter-item"
/> >
{ PAGE_SIZE_OPTIONS.map(s => <option value={s}>{s}</option>) }
</Form.Control>
<ButtonGroup className="filter-item"> <ButtonGroup className="filter-item">
<Popover position="bottom"> <Dropdown>
<Button large={true}>{props.filter.sortBy}</Button> <Dropdown.Toggle variant="secondary" id="more-menu">
<Menu>{renderSortByOptions()}</Menu> <Button>{props.filter.sortBy}</Button>
</Popover> </Dropdown.Toggle>
<Dropdown.Menu>
<Tooltip {renderSortByOptions()}
content={props.filter.sortDirection === "asc" ? "Ascending" : "Descending"} </Dropdown.Menu>
hoverOpenDelay={200} </Dropdown>
>
<Button <OverlayTrigger overlay={
rightIcon={props.filter.sortDirection === "asc" ? "caret-up" : "caret-down"} <Tooltip id="sort-direction-tooltip">{props.filter.sortDirection === "asc" ? "Ascending" : "Descending"}</Tooltip>
onClick={onChangeSortDirection} }>
/> <Button onClick={onChangeSortDirection}>
</Tooltip> <FontAwesomeIcon icon={props.filter.sortDirection === "asc" ? "caret-up" : "caret-down"} />
</Button>
</OverlayTrigger>
</ButtonGroup> </ButtonGroup>
<AddFilter <AddFilter

View File

@@ -1,5 +1,5 @@
import { Button, ButtonGroup } from "@blueprintjs/core";
import React from "react"; import React from "react";
import { Button, ButtonGroup } from 'react-bootstrap';
interface IPaginationProps { interface IPaginationProps {
itemsPerPage: number; itemsPerPage: number;
@@ -36,28 +36,24 @@ export class Pagination extends React.Component<IPaginationProps, IPaginationSta
if (!this.state || !this.state.pages || this.state.pages.length <= 1) { return null; } if (!this.state || !this.state.pages || this.state.pages.length <= 1) { return null; }
return ( return (
<ButtonGroup large={true} className="filter-container"> <ButtonGroup className="filter-container">
<Button <Button
text="First"
disabled={this.props.currentPage === 1} disabled={this.props.currentPage === 1}
onClick={() => this.setPage(1)} onClick={() => this.setPage(1)}
/> >First</Button>
<Button <Button
text="Previous"
disabled={this.props.currentPage === 1} disabled={this.props.currentPage === 1}
onClick={() => this.setPage(this.props.currentPage - 1)} onClick={() => this.setPage(this.props.currentPage - 1)}
/> >Previous</Button>
{this.renderPageButtons()} {this.renderPageButtons()}
<Button <Button
text="Next"
disabled={this.props.currentPage === this.state.totalPages} disabled={this.props.currentPage === this.state.totalPages}
onClick={() => this.setPage(this.props.currentPage + 1)} onClick={() => this.setPage(this.props.currentPage + 1)}
/> >Next</Button>
<Button <Button
text="Last"
disabled={this.props.currentPage === this.state.totalPages} disabled={this.props.currentPage === this.state.totalPages}
onClick={() => this.setPage(this.state.totalPages)} onClick={() => this.setPage(this.state.totalPages)}
/> >Last</Button>
</ButtonGroup> </ButtonGroup>
); );
} }
@@ -66,10 +62,9 @@ export class Pagination extends React.Component<IPaginationProps, IPaginationSta
return this.state.pages.map((page: number, index: number) => ( return this.state.pages.map((page: number, index: number) => (
<Button <Button
key={index} key={index}
text={page}
active={this.props.currentPage === page} active={this.props.currentPage === page}
onClick={() => this.setPage(page)} onClick={() => this.setPage(page)}
/> >{page}</Button>
)); ));
} }

View File

@@ -431,19 +431,19 @@ export const SceneFilenameParser: React.FC = () => {
return !r.studioId.set; return !r.studioId.set;
}); });
if (newAllTitleSet != allTitleSet) { if (newAllTitleSet !== allTitleSet) {
setAllTitleSet(newAllTitleSet); setAllTitleSet(newAllTitleSet);
} }
if (newAllDateSet != allDateSet) { if (newAllDateSet !== allDateSet) {
setAllDateSet(newAllDateSet); setAllDateSet(newAllDateSet);
} }
if (newAllPerformerSet != allPerformerSet) { if (newAllPerformerSet !== allPerformerSet) {
setAllTagSet(newAllPerformerSet); setAllTagSet(newAllPerformerSet);
} }
if (newAllTagSet != allTagSet) { if (newAllTagSet !== allTagSet) {
setAllTagSet(newAllTagSet); setAllTagSet(newAllTagSet);
} }
if (newAllStudioSet != allStudioSet) { if (newAllStudioSet !== allStudioSet) {
setAllStudioSet(newAllStudioSet); setAllStudioSet(newAllStudioSet);
} }
}, [parserResult]); }, [parserResult]);
@@ -927,7 +927,7 @@ export const SceneFilenameParser: React.FC = () => {
} }
function renderTable() { function renderTable() {
if (parserResult.length == 0) { return undefined; } if (parserResult.length === 0) { return undefined; }
return ( return (
<> <>

View File

@@ -1,5 +1,5 @@
import axios from "axios"; import axios from "axios";
import React, { CSSProperties, FunctionComponent, RefObject, useEffect, useRef, useState } from "react"; import React, { CSSProperties, useEffect, useRef, useState } from "react";
import * as GQL from "../../../core/generated-graphql"; import * as GQL from "../../../core/generated-graphql";
import { TextUtils } from "../../../utils/text"; import { TextUtils } from "../../../utils/text";
import "./ScenePlayerScrubber.scss"; import "./ScenePlayerScrubber.scss";
@@ -20,7 +20,7 @@ interface ISceneSpriteItem {
h: number; h: number;
} }
export const ScenePlayerScrubber: FunctionComponent<IScenePlayerScrubberProps> = (props: IScenePlayerScrubberProps) => { export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (props: IScenePlayerScrubberProps) => {
const contentEl = useRef<HTMLDivElement>(null); const contentEl = useRef<HTMLDivElement>(null);
const positionIndicatorEl = useRef<HTMLDivElement>(null); const positionIndicatorEl = useRef<HTMLDivElement>(null);
const scrubberSliderEl = useRef<HTMLDivElement>(null); const scrubberSliderEl = useRef<HTMLDivElement>(null);

View File

@@ -93,7 +93,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
async function onSave() { async function onSave() {
setIsLoading(true); setIsLoading(true);
try { try {
const result = await updateScenes(); await updateScenes();
ToastUtils.success("Updated scenes"); ToastUtils.success("Updated scenes");
} catch (e) { } catch (e) {
ErrorUtils.handle(e); ErrorUtils.handle(e);
@@ -130,7 +130,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
first = false; first = false;
} else { } else {
var studioId = scene.studio ? scene.studio.id : undefined; var studioId = scene.studio ? scene.studio.id : undefined;
if (ret != studioId) { if (ret !== studioId) {
ret = undefined; ret = undefined;
} }
} }
@@ -208,7 +208,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
if (rating !== thisRating) { if (rating !== thisRating) {
rating = ""; rating = "";
} }
if (studioId != thisStudio) { if (studioId !== thisStudio) {
studioId = undefined; studioId = undefined;
} }
const perfIds = !!scene.performers ? scene.performers.map(toId).sort() : []; const perfIds = !!scene.performers ? scene.performers.map(toId).sort() : [];
@@ -261,7 +261,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
as="select" as="select"
onChange={(event: any) => setRating(event.target.value)}> onChange={(event: any) => setRating(event.target.value)}>
{ ["", 1, 2, 3, 4, 5].map(opt => ( { ["", 1, 2, 3, 4, 5].map(opt => (
<option selected={opt == rating} value={opt}>{opt}</option> <option selected={opt === rating} value={opt}>{opt}</option>
)) } )) }
</Form.Control> </Form.Control>
</Form.Group> </Form.Group>

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import { Button } from 'react-bootstrap';
import { Button, MenuItem } from "@blueprintjs/core";
import { ISelectProps, ItemPredicate, ItemRenderer, Select } from "@blueprintjs/select"; import { ISelectProps, ItemPredicate, ItemRenderer, Select } from "@blueprintjs/select";
import * as GQL from "../../core/generated-graphql"; import * as GQL from "../../core/generated-graphql";
import { StashService } from "../../core/StashService"; import { StashService } from "../../core/StashService";
@@ -91,7 +91,7 @@ export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) =>
}; };
function onItemSelect(item: ValidTypes | undefined) { function onItemSelect(item: ValidTypes | undefined) {
if (item && item.id == "0") { if (item && item.id === "0") {
item = undefined; item = undefined;
} }
@@ -111,7 +111,7 @@ export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) =>
popoverProps={{position: "bottom"}} popoverProps={{position: "bottom"}}
{...props} {...props}
> >
<Button fill={true} text={buttonText} /> <Button>{buttonText}</Button>
</InternalSelect> </InternalSelect>
); );
}; };

View File

@@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import { MenuItem } from "@blueprintjs/core"; import { MenuItem } from "@blueprintjs/core";
import { ItemPredicate, ItemRenderer, Suggest } from "@blueprintjs/select"; import { ItemRenderer, Suggest } from "@blueprintjs/select";
import * as GQL from "../../core/generated-graphql"; import * as GQL from "../../core/generated-graphql";
import { StashService } from "../../core/StashService"; import { StashService } from "../../core/StashService";
import { HTMLInputProps } from "../../models"; import { HTMLInputProps } from "../../models";

View File

@@ -50,7 +50,7 @@ export const ValidGalleriesSelect: React.FunctionComponent<IProps> = (props: IPr
}; };
function onItemSelect(item: GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined) { function onItemSelect(item: GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined) {
if (item && item.id == "0") { if (item && item.id === "0") {
item = undefined; item = undefined;
} }

View File

@@ -525,7 +525,7 @@ export class StashService {
if (_.isPlainObject(value)) { if (_.isPlainObject(value)) {
return _.mapValues(value, StashService.nullToUndefined); return _.mapValues(value, StashService.nullToUndefined);
} }
if (_.isArray(value)) { if (Array.isArray(value)) {
return value.map(StashService.nullToUndefined); return value.map(StashService.nullToUndefined);
} }
if (value === null) { if (value === null) {

View File

@@ -1,4 +1,3 @@
import { isArray } from "util";
import { CriterionModifier } from "../../../core/generated-graphql"; import { CriterionModifier } from "../../../core/generated-graphql";
import { ILabeledId, ILabeledValue } from "../types"; import { ILabeledId, ILabeledValue } from "../types";
@@ -94,7 +93,7 @@ export abstract class Criterion<Option = any, Value = any> {
let valueString: string; let valueString: string;
if (this.modifier === CriterionModifier.IsNull || this.modifier === CriterionModifier.NotNull) { if (this.modifier === CriterionModifier.IsNull || this.modifier === CriterionModifier.NotNull) {
valueString = ""; valueString = "";
} else if (isArray(this.value) && this.value.length > 0) { } else if (Array.isArray(this.value) && this.value.length > 0) {
let items = this.value; let items = this.value;
if ((this.value as ILabeledId[])[0].label) { if ((this.value as ILabeledId[])[0].label) {
items = this.value.map((item) => item.label) as any; items = this.value.map((item) => item.label) as any;
@@ -103,7 +102,7 @@ export abstract class Criterion<Option = any, Value = any> {
} else if (typeof this.value === "string") { } else if (typeof this.value === "string") {
valueString = this.value; valueString = this.value;
} else { } else {
valueString = this.value.toString(); valueString = (this.value as any).toString();
} }
return `${Criterion.getLabel(this.type)} ${modifierString} ${valueString}`; return `${Criterion.getLabel(this.type)} ${modifierString} ${valueString}`;
@@ -115,7 +114,7 @@ export abstract class Criterion<Option = any, Value = any> {
public set(modifier: CriterionModifier, value: Value) { public set(modifier: CriterionModifier, value: Value) {
this.modifier = modifier; this.modifier = modifier;
if (isArray(this.value)) { if (Array.isArray(this.value)) {
this.value.push(value); this.value.push(value);
} else { } else {
this.value = value; this.value = value;

View File

@@ -15,7 +15,7 @@ export class ImageUtils {
} }
public static pasteImage(e : any, onLoadEnd: (this: FileReader) => any) { public static pasteImage(e : any, onLoadEnd: (this: FileReader) => any) {
if (e.clipboardData.files.length == 0) { if (e.clipboardData.files.length === 0) {
return; return;
} }
@@ -31,4 +31,4 @@ export class ImageUtils {
return () => window.removeEventListener("paste", pasteImage); return () => window.removeEventListener("paste", pasteImage);
}); });
} }
} }

View File

@@ -10128,7 +10128,7 @@ react-transition-group@^4.0.0:
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.6.2" prop-types "^15.6.2"
react@16.12.0: react@~16.12.0:
version "16.12.0" version "16.12.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83"
integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA== integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==
@@ -11781,12 +11781,7 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@3.4.5: typescript@^3.2.2, typescript@~3.7.4:
version "3.4.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99"
integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==
typescript@^3.2.2:
version "3.7.4" version "3.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19"
integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw== integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==