mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Changes
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
"lodash": "4.17.13",
|
||||
"node-sass": "4.12.0",
|
||||
"query-string": "6.5.0",
|
||||
"react": "16.12.0",
|
||||
"react": "~16.12.0",
|
||||
"react-apollo": "2.5.6",
|
||||
"react-apollo-hooks": "0.4.5",
|
||||
"react-bootstrap": "^1.0.0-beta.16",
|
||||
@@ -68,6 +68,6 @@
|
||||
"graphql-codegen-typescript-client": "0.18.2",
|
||||
"graphql-codegen-typescript-common": "0.18.2",
|
||||
"graphql-codegen-typescript-react-apollo": "0.18.2",
|
||||
"typescript": "3.4.5"
|
||||
"typescript": "~3.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "lodash";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Spinner } from 'react-bootstrap';
|
||||
import * as GQL from "../../core/generated-graphql";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { Table } from 'react-bootstrap';
|
||||
import { QueryHookResult } from "react-apollo-hooks";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "lodash";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import Lightbox from "react-images";
|
||||
import Gallery from "react-photo-gallery";
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
import {
|
||||
H1,
|
||||
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 React from "react";
|
||||
import { Table, Spinner } from 'react-bootstrap';
|
||||
import { StashService } from "../../core/StashService";
|
||||
|
||||
interface IProps {}
|
||||
|
||||
export const SettingsAboutPanel: FunctionComponent<IProps> = (props: IProps) => {
|
||||
export const SettingsAboutPanel: React.FC = () => {
|
||||
const { data, error, loading } = StashService.useVersion();
|
||||
|
||||
function maybeRenderTag() {
|
||||
@@ -30,7 +19,7 @@ export const SettingsAboutPanel: FunctionComponent<IProps> = (props: IProps) =>
|
||||
if (!data || !data.version) { return; }
|
||||
return (
|
||||
<>
|
||||
<HTMLTable>
|
||||
<Table>
|
||||
<tbody>
|
||||
{maybeRenderTag()}
|
||||
<tr>
|
||||
@@ -42,14 +31,14 @@ export const SettingsAboutPanel: FunctionComponent<IProps> = (props: IProps) =>
|
||||
<td>{data.version.build_time}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</HTMLTable>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<H4>About</H4>
|
||||
{!data || loading ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined}
|
||||
<h4>About</h4>
|
||||
{!data || loading ? <Spinner animation="border" variant="light" /> : undefined}
|
||||
{!!error ? <span>error.message</span> : undefined}
|
||||
{renderVersion()}
|
||||
</>
|
||||
|
||||
@@ -3,25 +3,19 @@ import {
|
||||
Button,
|
||||
Divider,
|
||||
FormGroup,
|
||||
H1,
|
||||
H4,
|
||||
H6,
|
||||
InputGroup,
|
||||
Spinner,
|
||||
Tag,
|
||||
Checkbox,
|
||||
HTMLSelect,
|
||||
} from "@blueprintjs/core";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import * as GQL from "../../core/generated-graphql";
|
||||
import { StashService } from "../../core/StashService";
|
||||
import { ErrorUtils } from "../../utils/errors";
|
||||
import { ToastUtils } from "../../utils/toasts";
|
||||
import { FolderSelect } from "../Shared/FolderSelect/FolderSelect";
|
||||
|
||||
interface IProps {}
|
||||
|
||||
export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IProps) => {
|
||||
export const SettingsConfigurationPanel: React.FC = () => {
|
||||
// Editing config state
|
||||
const [stashes, setStashes] = useState<string[]>([]);
|
||||
const [databasePath, setDatabasePath] = useState<string | undefined>(undefined);
|
||||
@@ -51,7 +45,6 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
||||
logLevel,
|
||||
logAccess,
|
||||
excludes,
|
||||
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -86,7 +79,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
||||
}
|
||||
|
||||
function excludeRemoveRegex(idx: number) {
|
||||
const newExcludes = excludes.filter((regex, i) => i!== idx );
|
||||
const newExcludes = excludes.filter((_regex, i) => i !== idx );
|
||||
|
||||
setExcludes(newExcludes);
|
||||
}
|
||||
@@ -148,7 +141,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
||||
<>
|
||||
{!!error ? <h1>{error.message}</h1> : undefined}
|
||||
{(!data || !data.configuration || loading) ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined}
|
||||
<H4>Library</H4>
|
||||
<h4>Library</h4>
|
||||
<FormGroup>
|
||||
<FormGroup>
|
||||
<FormGroup
|
||||
@@ -208,7 +201,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
||||
|
||||
<Divider />
|
||||
<FormGroup>
|
||||
<H4>Video</H4>
|
||||
<h4>Video</h4>
|
||||
<FormGroup
|
||||
label="Maximum transcode size"
|
||||
helperText="Maximum size for generated transcodes"
|
||||
@@ -233,7 +226,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
||||
<Divider />
|
||||
|
||||
<FormGroup>
|
||||
<H4>Authentication</H4>
|
||||
<h4>Authentication</h4>
|
||||
<FormGroup
|
||||
label="Username"
|
||||
helperText="Username to access Stash. Leave blank to disable user authentication"
|
||||
@@ -249,7 +242,7 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
||||
</FormGroup>
|
||||
|
||||
<Divider />
|
||||
<H4>Logging</H4>
|
||||
<h4>Logging</h4>
|
||||
<FormGroup
|
||||
label="Log file"
|
||||
helperText="Path to the file to output logging to. Blank to disable file logging. Requires restart."
|
||||
|
||||
@@ -3,12 +3,10 @@ import {
|
||||
Checkbox,
|
||||
Divider,
|
||||
FormGroup,
|
||||
H4,
|
||||
Spinner,
|
||||
TextArea,
|
||||
NumericInput
|
||||
} from "@blueprintjs/core";
|
||||
import _ from "lodash";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import { StashService } from "../../core/StashService";
|
||||
import { ErrorUtils } from "../../utils/errors";
|
||||
@@ -64,7 +62,7 @@ export const SettingsInterfacePanel: FunctionComponent<IProps> = () => {
|
||||
<>
|
||||
{!!config.error ? <h1>{config.error.message}</h1> : undefined}
|
||||
{(!config.data || !config.data.configuration || config.loading) ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined}
|
||||
<H4>User Interface</H4>
|
||||
<h4>User Interface</h4>
|
||||
<FormGroup
|
||||
label="Scene / Marker Wall"
|
||||
helperText="Configuration for wall items"
|
||||
|
||||
@@ -155,7 +155,7 @@ export const SettingsLogsPanel: FunctionComponent<IProps> = (props: IProps) => {
|
||||
const logLevels = ["Debug", "Info", "Warning", "Error"];
|
||||
|
||||
function filterByLogLevel(logEntry : LogEntry) {
|
||||
if (logLevel == "Debug") {
|
||||
if (logLevel === "Debug") {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,21 +4,16 @@ import {
|
||||
Checkbox,
|
||||
Divider,
|
||||
FormGroup,
|
||||
H4,
|
||||
AnchorButton,
|
||||
ProgressBar,
|
||||
H5,
|
||||
} from "@blueprintjs/core";
|
||||
import React, { FunctionComponent, useState, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { StashService } from "../../../core/StashService";
|
||||
import { ErrorUtils } from "../../../utils/errors";
|
||||
import { ToastUtils } from "../../../utils/toasts";
|
||||
import { GenerateButton } from "./GenerateButton";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
interface IProps {}
|
||||
|
||||
export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) => {
|
||||
export const SettingsTasksPanel: React.FC = () => {
|
||||
const [isImportAlertOpen, setIsImportAlertOpen] = useState<boolean>(false);
|
||||
const [isCleanAlertOpen, setIsCleanAlertOpen] = useState<boolean>(false);
|
||||
const [useFileMetadata, setUseFileMetadata] = useState<boolean>(false);
|
||||
@@ -173,7 +168,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
|
||||
return (
|
||||
<>
|
||||
<FormGroup>
|
||||
<H5>Status: {status}</H5>
|
||||
<h5>Status: {status}</h5>
|
||||
{!!status && status !== "Idle" ? <ProgressBar value={progress}/> : undefined}
|
||||
</FormGroup>
|
||||
{maybeRenderStop()}
|
||||
@@ -186,13 +181,13 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
|
||||
{renderImportAlert()}
|
||||
{renderCleanAlert()}
|
||||
|
||||
<H4>Running Jobs</H4>
|
||||
<h4>Running Jobs</h4>
|
||||
|
||||
{renderJobStatus()}
|
||||
|
||||
<Divider/>
|
||||
|
||||
<H4>Library</H4>
|
||||
<h4>Library</h4>
|
||||
<FormGroup
|
||||
helperText="Scan for new content and add it to the database."
|
||||
labelFor="scan"
|
||||
@@ -208,7 +203,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
|
||||
|
||||
<Divider />
|
||||
|
||||
<H4>Auto Tagging</H4>
|
||||
<h4>Auto Tagging</h4>
|
||||
|
||||
<FormGroup
|
||||
helperText="Auto-tag content based on filenames."
|
||||
@@ -240,7 +235,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
|
||||
</FormGroup>
|
||||
<Divider />
|
||||
|
||||
<H4>Generated Content</H4>
|
||||
<h4>Generated Content</h4>
|
||||
<GenerateButton />
|
||||
<FormGroup
|
||||
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>
|
||||
<Divider />
|
||||
|
||||
<H4>Metadata</H4>
|
||||
<h4>Metadata</h4>
|
||||
<FormGroup
|
||||
helperText="Export the database content into JSON format"
|
||||
labelFor="export"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "lodash";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { QueryHookResult } from "react-apollo-hooks";
|
||||
import { FindStudiosQuery, FindStudiosVariables } from "../../core/generated-graphql";
|
||||
|
||||
@@ -116,7 +116,7 @@ export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProp
|
||||
loop={true}
|
||||
ref={videoHoverHook.videoEl}
|
||||
/>
|
||||
<img src={previewPath || screenshotPath} onError={() => previewNotFound()} />
|
||||
<img alt="Preview" src={previewPath || screenshotPath} onError={() => previewNotFound()} />
|
||||
{showTextContainer ?
|
||||
<div className="scene-wall-item-text-container">
|
||||
<div style={{lineHeight: 1}}>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "lodash";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import * as GQL from "../../core/generated-graphql";
|
||||
import "./Wall.scss";
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
Dialog,
|
||||
FormGroup,
|
||||
HTMLSelect,
|
||||
InputGroup,
|
||||
Tooltip,
|
||||
} from "@blueprintjs/core";
|
||||
import _ from "lodash";
|
||||
import React, { FunctionComponent, useEffect, useRef, useState } from "react";
|
||||
import { isArray } from "util";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Button, Form, Modal, OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { CriterionModifier } from "../../core/generated-graphql";
|
||||
import { Criterion, CriterionType } from "../../models/list-filter/criteria/criterion";
|
||||
import { NoneCriterion } from "../../models/list-filter/criteria/none";
|
||||
@@ -27,8 +19,8 @@ interface IAddFilterProps {
|
||||
editingCriterion?: Criterion;
|
||||
}
|
||||
|
||||
export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterProps) => {
|
||||
const singleValueSelect = useRef<HTMLSelect>(null);
|
||||
export const AddFilter: React.FC<IAddFilterProps> = (props: IAddFilterProps) => {
|
||||
const defaultValue= useRef<string|number|undefined>();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [criterion, setCriterion] = useState<Criterion<any, any>>(new NoneCriterion());
|
||||
@@ -71,8 +63,8 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
|
||||
}
|
||||
|
||||
function onAddFilter() {
|
||||
if (!isArray(criterion.value) && !!singleValueSelect.current) {
|
||||
const value = singleValueSelect.current.props.defaultValue;
|
||||
if (!Array.isArray(criterion.value) && defaultValue.current) {
|
||||
const value = defaultValue.current;
|
||||
if (criterion.options && (value === undefined || value === "" || typeof value === "number")) {
|
||||
criterion.value = criterion.options[0];
|
||||
} else if (typeof value === "number" && value === undefined) {
|
||||
@@ -101,11 +93,15 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
|
||||
if (criterion.modifierOptions.length === 0) { return; }
|
||||
return (
|
||||
<div>
|
||||
<HTMLSelect
|
||||
options={criterion.modifierOptions}
|
||||
<Form.Control
|
||||
as="select"
|
||||
onChange={onChangedModifierSelect}
|
||||
defaultValue={criterion.modifier}
|
||||
/>
|
||||
value={criterion.modifier}
|
||||
>
|
||||
{ criterion.modifierOptions.map(c => (
|
||||
<option value={c.value}>{c.label}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -116,7 +112,7 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
|
||||
return;
|
||||
}
|
||||
|
||||
if (isArray(criterion.value)) {
|
||||
if (Array.isArray(criterion.value)) {
|
||||
let type: "performers" | "studios" | "tags" | "" = "";
|
||||
if (criterion instanceof PerformersCriterion) {
|
||||
type = "performers";
|
||||
@@ -140,21 +136,25 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
|
||||
}
|
||||
} else {
|
||||
if (criterion.options) {
|
||||
defaultValue.current = criterion.value;
|
||||
return (
|
||||
<HTMLSelect
|
||||
ref={singleValueSelect}
|
||||
options={criterion.options}
|
||||
<Form.Control
|
||||
as="select"
|
||||
onChange={onChangedSingleSelect}
|
||||
defaultValue={criterion.value}
|
||||
/>
|
||||
value={criterion.value}
|
||||
>
|
||||
{ criterion.options.map(c => (
|
||||
<option value={c}>{c}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<InputGroup
|
||||
<Form.Control
|
||||
type={criterion.inputType}
|
||||
onChange={onChangedInput}
|
||||
onBlur={onBlurInput}
|
||||
defaultValue={criterion.value ? criterion.value : ""}
|
||||
value={criterion.value || ""}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -162,57 +162,62 @@ export const AddFilter: FunctionComponent<IAddFilterProps> = (props: IAddFilterP
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<FormGroup>
|
||||
<Form.Group>
|
||||
{renderModifier()}
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
{renderSelect()}
|
||||
</FormGroup>
|
||||
</Form.Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function maybeRenderFilterSelect() {
|
||||
if (!!props.editingCriterion) { return; }
|
||||
if (props.editingCriterion) { return; }
|
||||
return (
|
||||
<FormGroup label="Filter">
|
||||
<HTMLSelect
|
||||
style={{flexBasis: "min-content"}}
|
||||
options={props.filter.criterionOptions}
|
||||
<Form.Group controlId="filter">
|
||||
<Form.Label>Filter</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
onChange={onChangedCriteriaType}
|
||||
defaultValue={criterion.type}
|
||||
/>
|
||||
</FormGroup>
|
||||
value={criterion.type}>
|
||||
{ props.filter.criterionOptions.map(c => (
|
||||
<option value={c.value}>{c.label}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
const title = !props.editingCriterion ? "Add Filter" : "Update Filter";
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
hoverOpenDelay={200}
|
||||
content="Filter"
|
||||
<OverlayTrigger
|
||||
placement="top"
|
||||
overlay={<Tooltip id="filter-tooltip">Filter</Tooltip>}
|
||||
>
|
||||
<Button
|
||||
icon="filter"
|
||||
onClick={() => onToggle()}
|
||||
active={isOpen}
|
||||
large={true}
|
||||
>
|
||||
<FontAwesomeIcon icon="filter" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</OverlayTrigger>
|
||||
|
||||
<Dialog isOpen={isOpen} onClose={() => onToggle()} title={title}>
|
||||
<div className="dialog-content">
|
||||
{maybeRenderFilterSelect()}
|
||||
{maybeRenderFilterPopoverContents()}
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button onClick={onAddFilter} disabled={criterion.type === "none"}>{title}</Button>
|
||||
<Modal
|
||||
show={isOpen}
|
||||
onHide={() => onToggle()}>
|
||||
<Modal.Header>{title}</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="dialog-content">
|
||||
{maybeRenderFilterSelect()}
|
||||
{maybeRenderFilterPopoverContents()}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={onAddFilter} disabled={criterion.type === "none"}>{title}</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
HTMLSelect,
|
||||
InputGroup,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popover,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Slider,
|
||||
} from "@blueprintjs/core";
|
||||
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 { ListFilterModel } from "../../models/list-filter/filter";
|
||||
import { DisplayMode } from "../../models/list-filter/types";
|
||||
@@ -40,17 +31,15 @@ interface IListFilterProps {
|
||||
|
||||
const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"];
|
||||
|
||||
export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilterProps) => {
|
||||
let searchCallback: any;
|
||||
export const ListFilter: React.FC<IListFilterProps> = (props: IListFilterProps) => {
|
||||
const searchCallback = useCallback(
|
||||
debounce((event: any) => {
|
||||
props.onChangeQuery(event.target.value);
|
||||
}, 500), [props.onChangeQuery]
|
||||
);
|
||||
|
||||
const [editingCriterion, setEditingCriterion] = useState<Criterion | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
searchCallback = debounce((event: any) => {
|
||||
props.onChangeQuery(event.target.value);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
function onChangePageSize(event: SyntheticEvent<HTMLSelectElement>) {
|
||||
const val = event!.currentTarget!.value;
|
||||
props.onChangePageSize(parseInt(val, 10));
|
||||
@@ -99,16 +88,16 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
|
||||
|
||||
function renderSortByOptions() {
|
||||
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 getIcon(option: DisplayMode) {
|
||||
switch (option) {
|
||||
case DisplayMode.Grid: return "grid-view";
|
||||
case DisplayMode.Grid: return "th-large";
|
||||
case DisplayMode.List: return "list";
|
||||
case DisplayMode.Wall: return "symbol-square";
|
||||
case DisplayMode.Wall: return "square";
|
||||
}
|
||||
}
|
||||
function getLabel(option: DisplayMode) {
|
||||
@@ -119,29 +108,30 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
|
||||
}
|
||||
}
|
||||
return props.filter.displayModeOptions.map((option) => (
|
||||
<Tooltip content={getLabel(option)} hoverOpenDelay={200}>
|
||||
<OverlayTrigger overlay={<Tooltip id="display-mode-tooltip">{getLabel(option)}</Tooltip>}>
|
||||
<Button
|
||||
key={option}
|
||||
active={props.filter.displayMode === option}
|
||||
onClick={() => onChangeDisplayMode(option)}
|
||||
icon={getIcon(option)}
|
||||
/>
|
||||
</Tooltip>
|
||||
>
|
||||
<FontAwesomeIcon icon={getIcon(option)} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
));
|
||||
}
|
||||
|
||||
function renderFilterTags() {
|
||||
return props.filter.criteria.map((criterion) => (
|
||||
<Tag
|
||||
key={criterion.getId()}
|
||||
<Badge
|
||||
className="tag-item"
|
||||
itemID={criterion.getId()}
|
||||
interactive={true}
|
||||
onRemove={() => onRemoveCriterionTag(criterion)}
|
||||
variant="secondary"
|
||||
onClick={() => onClickCriterionTag(criterion)}
|
||||
>
|
||||
{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() {
|
||||
if (props.onSelectAll) {
|
||||
return <MenuItem onClick={() => onSelectAll()} text="Select All" />;
|
||||
return <Dropdown.Item onClick={() => onSelectAll()}>Select All</Dropdown.Item>;
|
||||
}
|
||||
}
|
||||
|
||||
function renderSelectNone() {
|
||||
if (props.onSelectNone) {
|
||||
return <MenuItem onClick={() => onSelectNone()} text="Select None" />;
|
||||
return <Dropdown.Item onClick={() => onSelectNone()}>Select None</Dropdown.Item>;
|
||||
}
|
||||
}
|
||||
|
||||
function renderMore() {
|
||||
let options = [];
|
||||
options.push(renderSelectAll());
|
||||
options.push(renderSelectNone());
|
||||
let options = [
|
||||
renderSelectAll(),
|
||||
renderSelectNone()
|
||||
];
|
||||
|
||||
if (props.otherOperations) {
|
||||
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);
|
||||
|
||||
let menuItems = options as JSX.Element[];
|
||||
|
||||
function renderMoreOptions() {
|
||||
if (options.length > 0) {
|
||||
return (
|
||||
<>
|
||||
{menuItems}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (menuItems.length > 0) {
|
||||
return (
|
||||
<Popover position="bottom">
|
||||
<Button icon="more"/>
|
||||
<Menu>{renderMoreOptions()}</Menu>
|
||||
</Popover>
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle variant="secondary" id="more-menu">
|
||||
<Button>
|
||||
<FontAwesomeIcon icon="ellipsis-h" />
|
||||
</Button>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
{options}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -212,13 +197,11 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
|
||||
if (props.onChangeZoom) {
|
||||
return (
|
||||
<span className="zoom-slider">
|
||||
<Slider
|
||||
<Form.Control
|
||||
type="range"
|
||||
min={0}
|
||||
value={props.zoomIndex}
|
||||
initialValue={props.zoomIndex}
|
||||
max={3}
|
||||
labelRenderer={false}
|
||||
onChange={(v) => onChangeZoom(v)}
|
||||
onChange={(event: any) => onChangeZoom(Number.parseInt(event.target.value))}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
@@ -229,37 +212,37 @@ export const ListFilter: FunctionComponent<IListFilterProps> = (props: IListFilt
|
||||
return (
|
||||
<>
|
||||
<div className="filter-container">
|
||||
<InputGroup
|
||||
large={true}
|
||||
<Form.Control
|
||||
placeholder="Search..."
|
||||
defaultValue={props.filter.searchTerm}
|
||||
value={props.filter.searchTerm}
|
||||
onChange={onChangeQuery}
|
||||
className="filter-item"
|
||||
/>
|
||||
<HTMLSelect
|
||||
large={true}
|
||||
style={{flexBasis: "min-content"}}
|
||||
options={PAGE_SIZE_OPTIONS}
|
||||
<Form.Control
|
||||
as="select"
|
||||
onChange={onChangePageSize}
|
||||
value={props.filter.itemsPerPage}
|
||||
value={props.filter.itemsPerPage.toString()}
|
||||
className="filter-item"
|
||||
/>
|
||||
>
|
||||
{ PAGE_SIZE_OPTIONS.map(s => <option value={s}>{s}</option>) }
|
||||
</Form.Control>
|
||||
<ButtonGroup className="filter-item">
|
||||
<Popover position="bottom">
|
||||
<Button large={true}>{props.filter.sortBy}</Button>
|
||||
<Menu>{renderSortByOptions()}</Menu>
|
||||
</Popover>
|
||||
|
||||
<Tooltip
|
||||
content={props.filter.sortDirection === "asc" ? "Ascending" : "Descending"}
|
||||
hoverOpenDelay={200}
|
||||
>
|
||||
<Button
|
||||
rightIcon={props.filter.sortDirection === "asc" ? "caret-up" : "caret-down"}
|
||||
onClick={onChangeSortDirection}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle variant="secondary" id="more-menu">
|
||||
<Button>{props.filter.sortBy}</Button>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
{renderSortByOptions()}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
|
||||
<OverlayTrigger overlay={
|
||||
<Tooltip id="sort-direction-tooltip">{props.filter.sortDirection === "asc" ? "Ascending" : "Descending"}</Tooltip>
|
||||
}>
|
||||
<Button onClick={onChangeSortDirection}>
|
||||
<FontAwesomeIcon icon={props.filter.sortDirection === "asc" ? "caret-up" : "caret-down"} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</ButtonGroup>
|
||||
|
||||
<AddFilter
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, ButtonGroup } from "@blueprintjs/core";
|
||||
import React from "react";
|
||||
import { Button, ButtonGroup } from 'react-bootstrap';
|
||||
|
||||
interface IPaginationProps {
|
||||
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; }
|
||||
|
||||
return (
|
||||
<ButtonGroup large={true} className="filter-container">
|
||||
<ButtonGroup className="filter-container">
|
||||
<Button
|
||||
text="First"
|
||||
disabled={this.props.currentPage === 1}
|
||||
onClick={() => this.setPage(1)}
|
||||
/>
|
||||
>First</Button>
|
||||
<Button
|
||||
text="Previous"
|
||||
disabled={this.props.currentPage === 1}
|
||||
onClick={() => this.setPage(this.props.currentPage - 1)}
|
||||
/>
|
||||
>Previous</Button>
|
||||
{this.renderPageButtons()}
|
||||
<Button
|
||||
text="Next"
|
||||
disabled={this.props.currentPage === this.state.totalPages}
|
||||
onClick={() => this.setPage(this.props.currentPage + 1)}
|
||||
/>
|
||||
>Next</Button>
|
||||
<Button
|
||||
text="Last"
|
||||
disabled={this.props.currentPage === this.state.totalPages}
|
||||
onClick={() => this.setPage(this.state.totalPages)}
|
||||
/>
|
||||
>Last</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
@@ -66,10 +62,9 @@ export class Pagination extends React.Component<IPaginationProps, IPaginationSta
|
||||
return this.state.pages.map((page: number, index: number) => (
|
||||
<Button
|
||||
key={index}
|
||||
text={page}
|
||||
active={this.props.currentPage === page}
|
||||
onClick={() => this.setPage(page)}
|
||||
/>
|
||||
>{page}</Button>
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -431,19 +431,19 @@ export const SceneFilenameParser: React.FC = () => {
|
||||
return !r.studioId.set;
|
||||
});
|
||||
|
||||
if (newAllTitleSet != allTitleSet) {
|
||||
if (newAllTitleSet !== allTitleSet) {
|
||||
setAllTitleSet(newAllTitleSet);
|
||||
}
|
||||
if (newAllDateSet != allDateSet) {
|
||||
if (newAllDateSet !== allDateSet) {
|
||||
setAllDateSet(newAllDateSet);
|
||||
}
|
||||
if (newAllPerformerSet != allPerformerSet) {
|
||||
if (newAllPerformerSet !== allPerformerSet) {
|
||||
setAllTagSet(newAllPerformerSet);
|
||||
}
|
||||
if (newAllTagSet != allTagSet) {
|
||||
if (newAllTagSet !== allTagSet) {
|
||||
setAllTagSet(newAllTagSet);
|
||||
}
|
||||
if (newAllStudioSet != allStudioSet) {
|
||||
if (newAllStudioSet !== allStudioSet) {
|
||||
setAllStudioSet(newAllStudioSet);
|
||||
}
|
||||
}, [parserResult]);
|
||||
@@ -927,7 +927,7 @@ export const SceneFilenameParser: React.FC = () => {
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
if (parserResult.length == 0) { return undefined; }
|
||||
if (parserResult.length === 0) { return undefined; }
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { TextUtils } from "../../../utils/text";
|
||||
import "./ScenePlayerScrubber.scss";
|
||||
@@ -20,7 +20,7 @@ interface ISceneSpriteItem {
|
||||
h: number;
|
||||
}
|
||||
|
||||
export const ScenePlayerScrubber: FunctionComponent<IScenePlayerScrubberProps> = (props: IScenePlayerScrubberProps) => {
|
||||
export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (props: IScenePlayerScrubberProps) => {
|
||||
const contentEl = useRef<HTMLDivElement>(null);
|
||||
const positionIndicatorEl = useRef<HTMLDivElement>(null);
|
||||
const scrubberSliderEl = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -93,7 +93,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
|
||||
async function onSave() {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await updateScenes();
|
||||
await updateScenes();
|
||||
ToastUtils.success("Updated scenes");
|
||||
} catch (e) {
|
||||
ErrorUtils.handle(e);
|
||||
@@ -130,7 +130,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
|
||||
first = false;
|
||||
} else {
|
||||
var studioId = scene.studio ? scene.studio.id : undefined;
|
||||
if (ret != studioId) {
|
||||
if (ret !== studioId) {
|
||||
ret = undefined;
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
|
||||
if (rating !== thisRating) {
|
||||
rating = "";
|
||||
}
|
||||
if (studioId != thisStudio) {
|
||||
if (studioId !== thisStudio) {
|
||||
studioId = undefined;
|
||||
}
|
||||
const perfIds = !!scene.performers ? scene.performers.map(toId).sort() : [];
|
||||
@@ -261,7 +261,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (props: IList
|
||||
as="select"
|
||||
onChange={(event: any) => setRating(event.target.value)}>
|
||||
{ ["", 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.Group>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 * as GQL from "../../core/generated-graphql";
|
||||
import { StashService } from "../../core/StashService";
|
||||
@@ -91,7 +91,7 @@ export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) =>
|
||||
};
|
||||
|
||||
function onItemSelect(item: ValidTypes | undefined) {
|
||||
if (item && item.id == "0") {
|
||||
if (item && item.id === "0") {
|
||||
item = undefined;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) =>
|
||||
popoverProps={{position: "bottom"}}
|
||||
{...props}
|
||||
>
|
||||
<Button fill={true} text={buttonText} />
|
||||
<Button>{buttonText}</Button>
|
||||
</InternalSelect>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
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 { StashService } from "../../core/StashService";
|
||||
import { HTMLInputProps } from "../../models";
|
||||
|
||||
@@ -50,7 +50,7 @@ export const ValidGalleriesSelect: React.FunctionComponent<IProps> = (props: IPr
|
||||
};
|
||||
|
||||
function onItemSelect(item: GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined) {
|
||||
if (item && item.id == "0") {
|
||||
if (item && item.id === "0") {
|
||||
item = undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -525,7 +525,7 @@ export class StashService {
|
||||
if (_.isPlainObject(value)) {
|
||||
return _.mapValues(value, StashService.nullToUndefined);
|
||||
}
|
||||
if (_.isArray(value)) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(StashService.nullToUndefined);
|
||||
}
|
||||
if (value === null) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { isArray } from "util";
|
||||
import { CriterionModifier } from "../../../core/generated-graphql";
|
||||
import { ILabeledId, ILabeledValue } from "../types";
|
||||
|
||||
@@ -94,7 +93,7 @@ export abstract class Criterion<Option = any, Value = any> {
|
||||
let valueString: string;
|
||||
if (this.modifier === CriterionModifier.IsNull || this.modifier === CriterionModifier.NotNull) {
|
||||
valueString = "";
|
||||
} else if (isArray(this.value) && this.value.length > 0) {
|
||||
} else if (Array.isArray(this.value) && this.value.length > 0) {
|
||||
let items = this.value;
|
||||
if ((this.value as ILabeledId[])[0].label) {
|
||||
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") {
|
||||
valueString = this.value;
|
||||
} else {
|
||||
valueString = this.value.toString();
|
||||
valueString = (this.value as any).toString();
|
||||
}
|
||||
|
||||
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) {
|
||||
this.modifier = modifier;
|
||||
if (isArray(this.value)) {
|
||||
if (Array.isArray(this.value)) {
|
||||
this.value.push(value);
|
||||
} else {
|
||||
this.value = value;
|
||||
|
||||
@@ -15,7 +15,7 @@ export class ImageUtils {
|
||||
}
|
||||
|
||||
public static pasteImage(e : any, onLoadEnd: (this: FileReader) => any) {
|
||||
if (e.clipboardData.files.length == 0) {
|
||||
if (e.clipboardData.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,4 +31,4 @@ export class ImageUtils {
|
||||
return () => window.removeEventListener("paste", pasteImage);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10128,7 +10128,7 @@ react-transition-group@^4.0.0:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react@16.12.0:
|
||||
react@~16.12.0:
|
||||
version "16.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83"
|
||||
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"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@3.4.5:
|
||||
version "3.4.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99"
|
||||
integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==
|
||||
|
||||
typescript@^3.2.2:
|
||||
typescript@^3.2.2, typescript@~3.7.4:
|
||||
version "3.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19"
|
||||
integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==
|
||||
|
||||
Reference in New Issue
Block a user