mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Prettier
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { LoadingIndicator } from 'src/components/Shared';
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { GalleryViewer } from "./GalleryViewer";
|
||||
|
||||
export const Gallery: React.FC = () => {
|
||||
@@ -10,8 +10,7 @@ export const Gallery: React.FC = () => {
|
||||
const { data, error, loading } = StashService.useFindGallery(id);
|
||||
const gallery = data?.findGallery;
|
||||
|
||||
if (loading || !gallery)
|
||||
return <LoadingIndicator />;
|
||||
if (loading || !gallery) return <LoadingIndicator />;
|
||||
if (error) return <div>{error.message}</div>;
|
||||
|
||||
return (
|
||||
|
||||
@@ -91,9 +91,7 @@ export const MainNavbar: React.FC = () => {
|
||||
))}
|
||||
</Nav>
|
||||
<Nav>
|
||||
<div className="d-none d-sm-block">
|
||||
{newButton}
|
||||
</div>
|
||||
<div className="d-none d-sm-block">{newButton}</div>
|
||||
<LinkContainer exact to="/settings">
|
||||
<Button className="minimal">
|
||||
<Icon icon="cog" />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export class ParserField {
|
||||
public field: string;
|
||||
public helperText?: string;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownButton,
|
||||
Form,
|
||||
InputGroup,
|
||||
} from 'react-bootstrap';
|
||||
import { ParserField } from './ParserField';
|
||||
import { ShowFields } from './ShowFields';
|
||||
InputGroup
|
||||
} from "react-bootstrap";
|
||||
import { ParserField } from "./ParserField";
|
||||
import { ShowFields } from "./ShowFields";
|
||||
|
||||
const builtInRecipes = [
|
||||
{
|
||||
@@ -80,7 +80,9 @@ interface IParserInputProps {
|
||||
setShowFields: (fields: Map<string, boolean>) => void;
|
||||
}
|
||||
|
||||
export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProps) => {
|
||||
export const ParserInput: React.FC<IParserInputProps> = (
|
||||
props: IParserInputProps
|
||||
) => {
|
||||
const [pattern, setPattern] = useState<string>(props.input.pattern);
|
||||
const [ignoreWords, setIgnoreWords] = useState<string>(
|
||||
props.input.ignoreWords.join(" ")
|
||||
@@ -124,7 +126,9 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
|
||||
return (
|
||||
<Form.Group>
|
||||
<Form.Group className="row">
|
||||
<Form.Label htmlFor="filename-pattern" className="col-2">Filename Pattern</Form.Label>
|
||||
<Form.Label htmlFor="filename-pattern" className="col-2">
|
||||
Filename Pattern
|
||||
</Form.Label>
|
||||
<InputGroup className="col-8">
|
||||
<Form.Control
|
||||
id="filename-pattern"
|
||||
@@ -134,7 +138,10 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
|
||||
<InputGroup.Append>
|
||||
<DropdownButton id="parser-field-select" title="Add Field">
|
||||
{validFields.map(item => (
|
||||
<Dropdown.Item key={item.field} onSelect={() => addParserField(item)}>
|
||||
<Dropdown.Item
|
||||
key={item.field}
|
||||
onSelect={() => addParserField(item)}
|
||||
>
|
||||
<span>{item.field}</span>
|
||||
<span className="ml-auto">{item.helperText}</span>
|
||||
</Dropdown.Item>
|
||||
@@ -142,7 +149,9 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
|
||||
</DropdownButton>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
<Form.Text className="text-muted row col-10 offset-2">Use '\\' to escape literal {} characters</Form.Text>
|
||||
<Form.Text className="text-muted row col-10 offset-2">
|
||||
Use '\\' to escape literal {} characters
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="row" controlId="ignored-words">
|
||||
@@ -152,12 +161,16 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
|
||||
onChange={(newValue: any) => setIgnoreWords(newValue.target.value)}
|
||||
value={ignoreWords}
|
||||
/>
|
||||
<Form.Text className="text-muted col-10 offset-2">Matches with {"{i}"}</Form.Text>
|
||||
<Form.Text className="text-muted col-10 offset-2">
|
||||
Matches with {"{i}"}
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<h5>Title</h5>
|
||||
<Form.Group className="row">
|
||||
<Form.Label htmlFor="whitespace-characters" className="col-2">Whitespace characters:</Form.Label>
|
||||
<Form.Label htmlFor="whitespace-characters" className="col-2">
|
||||
Whitespace characters:
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="col-8"
|
||||
onChange={(newValue: any) =>
|
||||
@@ -170,7 +183,9 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
<Form.Group className="row">
|
||||
<Form.Label htmlFor="capitalize-title" className="col-2">Capitalize title</Form.Label>
|
||||
<Form.Label htmlFor="capitalize-title" className="col-2">
|
||||
Capitalize title
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="col-8"
|
||||
type="checkbox"
|
||||
@@ -182,9 +197,16 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
|
||||
{/* TODO - mapping stuff will go here */}
|
||||
|
||||
<Form.Group>
|
||||
<DropdownButton variant="secondary" id="recipe-select" title="Select Parser Recipe">
|
||||
<DropdownButton
|
||||
variant="secondary"
|
||||
id="recipe-select"
|
||||
title="Select Parser Recipe"
|
||||
>
|
||||
{builtInRecipes.map(item => (
|
||||
<Dropdown.Item key={item.pattern} onSelect={() => setParserRecipe(item)}>
|
||||
<Dropdown.Item
|
||||
key={item.pattern}
|
||||
onSelect={() => setParserRecipe(item)}
|
||||
>
|
||||
<span>{item.pattern}</span>
|
||||
<span className="mr-auto">{item.description}</span>
|
||||
</Dropdown.Item>
|
||||
@@ -200,7 +222,9 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="row">
|
||||
<Button variant="secondary" className="col-1" onClick={onFind}>Find</Button>
|
||||
<Button variant="secondary" className="col-1" onClick={onFind}>
|
||||
Find
|
||||
</Button>
|
||||
<Form.Control
|
||||
as="select"
|
||||
style={{ flexBasis: "min-content" }}
|
||||
@@ -218,4 +242,4 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
|
||||
</Form.Group>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
/* 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,
|
||||
Form,
|
||||
Table
|
||||
} from "react-bootstrap";
|
||||
import { Badge, Button, Card, Form, Table } from "react-bootstrap";
|
||||
import _ from "lodash";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { FilterSelect, StudioSelect, LoadingIndicator } from "src/components/Shared";
|
||||
import {
|
||||
FilterSelect,
|
||||
StudioSelect,
|
||||
LoadingIndicator
|
||||
} from "src/components/Shared";
|
||||
import { TextUtils } from "src/utils";
|
||||
import { useToast } from "src/hooks";
|
||||
import { Pagination } from "../list/Pagination";
|
||||
import { IParserInput, ParserInput } from './ParserInput';
|
||||
import { ParserField } from './ParserField';
|
||||
import { IParserInput, ParserInput } from "./ParserInput";
|
||||
import { ParserField } from "./ParserField";
|
||||
|
||||
class ParserResult<T> {
|
||||
public value: GQL.Maybe<T> = null;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Collapse
|
||||
} from 'react-bootstrap';
|
||||
import { Icon } from 'src/components/Shared';
|
||||
import React, { useState } from "react";
|
||||
import { Button, Collapse } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
|
||||
interface IShowFieldsProps {
|
||||
fields: Map<string, boolean>;
|
||||
@@ -43,4 +40,4 @@ export const ShowFields = (props: IShowFieldsProps) => {
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Button, Table } from "react-bootstrap";
|
||||
import { LoadingIndicator } from 'src/components/Shared';
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { StashService } from "src/core/StashService";
|
||||
|
||||
export const SettingsAboutPanel: React.FC = () => {
|
||||
|
||||
@@ -153,8 +153,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
}
|
||||
|
||||
if (error) return <h1>{error.message}</h1>;
|
||||
if (!data?.configuration || loading)
|
||||
return <LoadingIndicator />;
|
||||
if (!data?.configuration || loading) return <LoadingIndicator />;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -199,8 +198,8 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
<Form.Group>
|
||||
<h6>Excluded Patterns</h6>
|
||||
<Form.Group>
|
||||
{excludes
|
||||
&& excludes.map((regexp, i) => (
|
||||
{excludes &&
|
||||
excludes.map((regexp, i) => (
|
||||
<InputGroup>
|
||||
<Form.Control
|
||||
className="col col-sm-6"
|
||||
@@ -218,8 +217,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
))
|
||||
}
|
||||
))}
|
||||
</Form.Group>
|
||||
<Button className="minimal" onClick={() => excludeAddRegex()}>
|
||||
<Icon icon="plus" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { DurationInput, LoadingIndicator } from 'src/components/Shared';
|
||||
import { DurationInput, LoadingIndicator } from "src/components/Shared";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
|
||||
@@ -108,7 +108,8 @@ export const SettingsInterfacePanel: React.FC = () => {
|
||||
onValueChange={duration => setMaximumLoopDuration(duration)}
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
Maximum scene duration where scene player will loop the video - 0 to disable
|
||||
Maximum scene duration where scene player will loop the video - 0 to
|
||||
disable
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
</Form.Group>
|
||||
|
||||
@@ -53,7 +53,12 @@ export const GenerateButton: React.FC = () => {
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Button id="generate" variant="secondary" type="submit" onClick={() => onGenerate()}>
|
||||
<Button
|
||||
id="generate"
|
||||
variant="secondary"
|
||||
type="submit"
|
||||
onClick={() => onGenerate()}
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
<Form.Text className="text-muted">
|
||||
|
||||
@@ -242,9 +242,7 @@ export const SettingsTasksPanel: React.FC = () => {
|
||||
|
||||
<Form.Group>
|
||||
<Link to="/sceneFilenameParser">
|
||||
<Button variant="secondary">
|
||||
Scene Filename Parser
|
||||
</Button>
|
||||
<Button variant="secondary">Scene Filename Parser</Button>
|
||||
</Link>
|
||||
</Form.Group>
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
} from "react-bootstrap";
|
||||
import { Button, Modal } from "react-bootstrap";
|
||||
import React, { useState } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { ImageInput } from 'src/components/Shared';
|
||||
import { ImageInput } from "src/components/Shared";
|
||||
|
||||
interface IProps {
|
||||
performer?: Partial<GQL.PerformerDataFragment>;
|
||||
@@ -24,7 +21,11 @@ export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
|
||||
function renderEditButton() {
|
||||
if (props.isNew) return;
|
||||
return (
|
||||
<Button variant="primary" className="edit" onClick={() => props.onToggleEdit()}>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="edit"
|
||||
onClick={() => props.onToggleEdit()}
|
||||
>
|
||||
{props.isEditing ? "Cancel" : "Edit"}
|
||||
</Button>
|
||||
);
|
||||
@@ -43,7 +44,11 @@ export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
|
||||
function renderDeleteButton() {
|
||||
if (props.isNew || props.isEditing) return;
|
||||
return (
|
||||
<Button variant="danger" className="delete d-none d-sm-block" onClick={() => setIsDeleteAlertOpen(true)}>
|
||||
<Button
|
||||
variant="danger"
|
||||
className="delete d-none d-sm-block"
|
||||
onClick={() => setIsDeleteAlertOpen(true)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
);
|
||||
@@ -92,7 +97,10 @@ export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
|
||||
return (
|
||||
<div className="details-edit">
|
||||
{renderEditButton()}
|
||||
<ImageInput isEditing={props.isEditing} onImageChange={props.onImageChange} />
|
||||
<ImageInput
|
||||
isEditing={props.isEditing}
|
||||
onImageChange={props.onImageChange}
|
||||
/>
|
||||
{renderAutoTagButton()}
|
||||
{renderSaveButton()}
|
||||
{renderDeleteButton()}
|
||||
|
||||
@@ -35,10 +35,20 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
|
||||
function renderButtons() {
|
||||
return (
|
||||
<ButtonGroup vertical>
|
||||
<Button variant="secondary" className="duration-button" disabled={props.disabled} onClick={() => increment()}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="duration-button"
|
||||
disabled={props.disabled}
|
||||
onClick={() => increment()}
|
||||
>
|
||||
<Icon icon="chevron-up" />
|
||||
</Button>
|
||||
<Button variant="secondary" className="duration-button" disabled={props.disabled} onClick={() => decrement()}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="duration-button"
|
||||
disabled={props.disabled}
|
||||
onClick={() => decrement()}
|
||||
>
|
||||
<Icon icon="chevron-down" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, InputGroup, Form, Modal } from "react-bootstrap";
|
||||
import { LoadingIndicator } from 'src/components/Shared';
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { StashService } from "src/core/StashService";
|
||||
|
||||
interface IProps {
|
||||
@@ -80,7 +80,9 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="success" onClick={() => onSelectDirectory()}>Add</Button>
|
||||
<Button variant="success" onClick={() => onSelectDirectory()}>
|
||||
Add
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
@@ -103,7 +105,9 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
||||
})}
|
||||
</Form.Group>
|
||||
|
||||
<Button variant="secondary" onClick={() => setIsDisplayingDialog(true)}>Add Directory</Button>
|
||||
<Button variant="secondary" onClick={() => setIsDisplayingDialog(true)}>
|
||||
Add Directory
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from "react";
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
|
||||
interface IImageInput {
|
||||
isEditing: boolean;
|
||||
onImageChange: (event: React.FormEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export const ImageInput: React.FC<IImageInput> = ({ isEditing, onImageChange }) => {
|
||||
export const ImageInput: React.FC<IImageInput> = ({
|
||||
isEditing,
|
||||
onImageChange
|
||||
}) => {
|
||||
if (!isEditing) return <div />;
|
||||
|
||||
return (
|
||||
@@ -19,5 +22,4 @@ export const ImageInput: React.FC<IImageInput> = ({ isEditing, onImageChange })
|
||||
/>
|
||||
</Form.Label>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Spinner } from "react-bootstrap";
|
||||
import cx from 'classnames';
|
||||
import cx from "classnames";
|
||||
|
||||
interface ILoadingProps {
|
||||
message?: string;
|
||||
@@ -10,7 +10,10 @@ interface ILoadingProps {
|
||||
const CLASSNAME = "LoadingIndicator";
|
||||
const CLASSNAME_MESSAGE = `${CLASSNAME}-message`;
|
||||
|
||||
const LoadingIndicator: React.FC<ILoadingProps> = ({ message, inline = false }) => (
|
||||
const LoadingIndicator: React.FC<ILoadingProps> = ({
|
||||
message,
|
||||
inline = false
|
||||
}) => (
|
||||
<div className={cx(CLASSNAME, { inline })}>
|
||||
<Spinner animation="border" role="status">
|
||||
<span className="sr-only">Loading...</span>
|
||||
|
||||
@@ -35,7 +35,7 @@ interface ISelectProps {
|
||||
onChange: (item: ValueType<Option>) => void;
|
||||
initialIds?: string[];
|
||||
isMulti?: boolean;
|
||||
isClearable?: boolean,
|
||||
isClearable?: boolean;
|
||||
onInputChange?: (input: string) => void;
|
||||
placeholder?: string;
|
||||
showDropdown?: boolean;
|
||||
@@ -53,10 +53,11 @@ interface ISceneGallerySelect {
|
||||
}
|
||||
|
||||
const getSelectedValues = (selectedItems: ValueType<Option>) =>
|
||||
selectedItems ?
|
||||
(Array.isArray(selectedItems) ? selectedItems : [selectedItems]).map(
|
||||
selectedItems
|
||||
? (Array.isArray(selectedItems) ? selectedItems : [selectedItems]).map(
|
||||
item => item.value
|
||||
) : [];
|
||||
)
|
||||
: [];
|
||||
|
||||
export const SceneGallerySelect: React.FC<ISceneGallerySelect> = props => {
|
||||
const { data, loading } = StashService.useValidGalleriesForScene(
|
||||
@@ -176,8 +177,9 @@ export const PerformerSelect: React.FC<IFilterProps> = props => {
|
||||
label: item.name ?? ""
|
||||
}));
|
||||
const placeholder = props.noSelectionString ?? "Select performer...";
|
||||
const selectedOptions:Option[] = props.ids ?
|
||||
items.filter(item => props.ids?.indexOf(item.value) !== -1) : [];
|
||||
const selectedOptions: Option[] = props.ids
|
||||
? items.filter(item => props.ids?.indexOf(item.value) !== -1)
|
||||
: [];
|
||||
|
||||
const onChange = (selectedItems: ValueType<Option>) => {
|
||||
const selectedIds = getSelectedValues(selectedItems);
|
||||
@@ -208,8 +210,9 @@ export const StudioSelect: React.FC<IFilterProps> = props => {
|
||||
label: item.name
|
||||
}));
|
||||
const placeholder = props.noSelectionString ?? "Select studio...";
|
||||
const selectedOptions:Option[] = props.ids ?
|
||||
items.filter(item => props.ids?.indexOf(item.value) !== -1) : [];
|
||||
const selectedOptions: Option[] = props.ids
|
||||
? items.filter(item => props.ids?.indexOf(item.value) !== -1)
|
||||
: [];
|
||||
|
||||
const onChange = (selectedItems: ValueType<Option>) => {
|
||||
const selectedIds = getSelectedValues(selectedItems);
|
||||
@@ -320,10 +323,14 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
|
||||
const defaultValue =
|
||||
items.filter(item => initialIds?.indexOf(item.value) !== -1) ?? null;
|
||||
|
||||
const options = groupHeader ? [{
|
||||
const options = groupHeader
|
||||
? [
|
||||
{
|
||||
label: groupHeader,
|
||||
options: items
|
||||
}] : items;
|
||||
}
|
||||
]
|
||||
: items;
|
||||
|
||||
const styles = {
|
||||
option: (base: CSSProperties) => ({
|
||||
|
||||
@@ -15,4 +15,4 @@ export { DurationInput } from "./DurationInput";
|
||||
export { TagLink } from "./TagLink";
|
||||
export { HoverPopover } from "./HoverPopover";
|
||||
export { default as LoadingIndicator } from "./LoadingIndicator";
|
||||
export { ImageInput } from './ImageInput';
|
||||
export { ImageInput } from "./ImageInput";
|
||||
|
||||
@@ -46,7 +46,10 @@ export const Stats: React.FC = () => {
|
||||
</nav>
|
||||
|
||||
<h5>Notes</h5>
|
||||
<em>This is still an early version, some things are still a work in progress.</em>
|
||||
<em>
|
||||
This is still an early version, some things are still a work in
|
||||
progress.
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,11 +10,8 @@ interface IProps {
|
||||
export const StudioCard: React.FC<IProps> = ({ studio }) => {
|
||||
return (
|
||||
<Card className="studio-card">
|
||||
<Link
|
||||
to={`/studios/${studio.id}`}
|
||||
className="studio-image"
|
||||
>
|
||||
<img alt={studio.name} src={studio.image_path ?? ''} />
|
||||
<Link to={`/studios/${studio.id}`} className="studio-image">
|
||||
<img alt={studio.name} src={studio.image_path ?? ""} />
|
||||
</Link>
|
||||
<div className="card-section">
|
||||
<h5 className="text-truncate">{studio.name}</h5>
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
import { Table } from "react-bootstrap";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import cx from 'classnames';
|
||||
import cx from "classnames";
|
||||
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { ImageUtils, TableUtils } from "src/utils";
|
||||
import { DetailsEditNavbar, Modal, LoadingIndicator } from "src/components/Shared";
|
||||
import {
|
||||
DetailsEditNavbar,
|
||||
Modal,
|
||||
LoadingIndicator
|
||||
} from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import { StudioScenesPanel } from './StudioScenesPanel';
|
||||
import { StudioScenesPanel } from "./StudioScenesPanel";
|
||||
|
||||
export const Studio: React.FC = () => {
|
||||
const history = useHistory();
|
||||
@@ -71,8 +75,7 @@ export const Studio: React.FC = () => {
|
||||
ImageUtils.usePasteImage(onImageLoad);
|
||||
|
||||
if (!isNew && !isEditing) {
|
||||
if (!data?.findStudio || loading)
|
||||
return <LoadingIndicator />;
|
||||
if (!data?.findStudio || loading) return <LoadingIndicator />;
|
||||
if (error) return <div>{error.message}</div>;
|
||||
}
|
||||
|
||||
@@ -142,21 +145,26 @@ export const Studio: React.FC = () => {
|
||||
accept={{ text: "Delete", variant: "danger", onClick: onDelete }}
|
||||
cancel={{ onClick: () => setIsDeleteAlertOpen(false) }}
|
||||
>
|
||||
<p>Are you sure you want to delete {studio.name ?? 'studio'}?</p>
|
||||
<p>Are you sure you want to delete {studio.name ?? "studio"}?</p>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className={cx('studio-details', { 'col ml-sm-5': !isNew, 'col-8': isNew})}>
|
||||
<div
|
||||
className={cx("studio-details", {
|
||||
"col ml-sm-5": !isNew,
|
||||
"col-8": isNew
|
||||
})}
|
||||
>
|
||||
{isNew && <h2>Add Studio</h2>}
|
||||
<img className="logo" alt={name} src={imagePreview} />
|
||||
<Table id="performer-details" style={{ width: "100%" }}>
|
||||
<tbody>
|
||||
{TableUtils.renderInputGroup({
|
||||
title: "Name",
|
||||
value: studio.name ?? '',
|
||||
value: studio.name ?? "",
|
||||
isEditing: !!isEditing,
|
||||
onChange: setName
|
||||
})}
|
||||
|
||||
@@ -8,9 +8,7 @@ interface IStudioScenesPanel {
|
||||
studio: Partial<GQL.StudioDataFragment>;
|
||||
}
|
||||
|
||||
export const StudioScenesPanel: React.FC<IStudioScenesPanel> = ({
|
||||
studio
|
||||
}) => {
|
||||
export const StudioScenesPanel: React.FC<IStudioScenesPanel> = ({ studio }) => {
|
||||
function filterHook(filter: ListFilterModel) {
|
||||
const studioValue = { id: studio.id!, label: studio.name! };
|
||||
// if studio is already present, then we modify it, otherwise add
|
||||
|
||||
@@ -14,8 +14,7 @@ export const StudioList: React.FC = () => {
|
||||
result: FindStudiosQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
if (!result.data?.findStudios)
|
||||
return;
|
||||
if (!result.data?.findStudios) return;
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
|
||||
@@ -93,8 +93,7 @@ export const TagList: React.FC = () => {
|
||||
</Modal>
|
||||
);
|
||||
|
||||
if (!data?.allTags)
|
||||
return <LoadingIndicator />;
|
||||
if (!data?.allTags) return <LoadingIndicator />;
|
||||
if (error) return <div>{error.message}</div>;
|
||||
|
||||
const tagElements = data.allTags.map(tag => {
|
||||
@@ -104,7 +103,9 @@ export const TagList: React.FC = () => {
|
||||
{tag.name}
|
||||
</Button>
|
||||
<div style={{ float: "right" }}>
|
||||
<Button variant="secondary" onClick={() => onAutoTag(tag)}>Auto Tag</Button>
|
||||
<Button variant="secondary" onClick={() => onAutoTag(tag)}>
|
||||
Auto Tag
|
||||
</Button>
|
||||
<Button variant="secondary">
|
||||
<Link to={NavUtils.makeTagScenesUrl(tag)}>
|
||||
Scenes: {tag.scene_count}
|
||||
|
||||
@@ -16,9 +16,7 @@ interface IWallItemProps {
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const WallItem: React.FC<IWallItemProps> = (
|
||||
props: IWallItemProps
|
||||
) => {
|
||||
export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
||||
const [videoPath, setVideoPath] = useState<string>();
|
||||
const [previewPath, setPreviewPath] = useState<string>("");
|
||||
const [screenshotPath, setScreenshotPath] = useState<string>("");
|
||||
@@ -28,7 +26,8 @@ export const WallItem: React.FC<IWallItemProps> = (
|
||||
const videoHoverHook = VideoHoverHook.useVideoHover({
|
||||
resetOnMouseLeave: true
|
||||
});
|
||||
const showTextContainer = config.data?.configuration.interface.wallShowTitle ?? true;
|
||||
const showTextContainer =
|
||||
config.data?.configuration.interface.wallShowTitle ?? true;
|
||||
|
||||
function onMouseEnter() {
|
||||
VideoHoverHook.onMouseEnter(videoHoverHook);
|
||||
|
||||
@@ -123,7 +123,9 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
||||
value={criterion.modifier}
|
||||
>
|
||||
{criterion.modifierOptions.map(c => (
|
||||
<option key={c.value} value={c.value}>{c.label}</option>
|
||||
<option key={c.value} value={c.value}>
|
||||
{c.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
);
|
||||
@@ -156,7 +158,10 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
||||
isMulti
|
||||
onSelect={items => {
|
||||
const newCriterion = _.cloneDeep(criterion);
|
||||
newCriterion.value = items.map(i => ({ id: i.id, label: i.name! }));
|
||||
newCriterion.value = items.map(i => ({
|
||||
id: i.id,
|
||||
label: i.name!
|
||||
}));
|
||||
setCriterion(newCriterion);
|
||||
}}
|
||||
ids={criterion.value.map((labeled: any) => labeled.id)}
|
||||
@@ -172,7 +177,9 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
||||
value={criterion.value}
|
||||
>
|
||||
{criterion.options.map(c => (
|
||||
<option key={c} value={c}>{c}</option>
|
||||
<option key={c} value={c}>
|
||||
{c}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
);
|
||||
@@ -216,7 +223,9 @@ export const AddFilter: React.FC<IAddFilterProps> = (
|
||||
value={criterion.type}
|
||||
>
|
||||
{props.filter.criterionOptions.map(c => (
|
||||
<option key={c.value} value={c.value}>{c.label}</option>
|
||||
<option key={c.value} value={c.value}>
|
||||
{c.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
|
||||
@@ -158,7 +158,10 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
onClick={() => onClickCriterionTag(criterion)}
|
||||
>
|
||||
{criterion.getLabel()}
|
||||
<Button variant="secondary" onClick={() => onRemoveCriterionTag(criterion)}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => onRemoveCriterionTag(criterion)}
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</Button>
|
||||
</Badge>
|
||||
@@ -180,7 +183,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
function renderSelectAll() {
|
||||
if (props.onSelectAll) {
|
||||
return (
|
||||
<Dropdown.Item key="select-all" onClick={() => onSelectAll()}>Select All</Dropdown.Item>
|
||||
<Dropdown.Item key="select-all" onClick={() => onSelectAll()}>
|
||||
Select All
|
||||
</Dropdown.Item>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -201,7 +206,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
if (props.otherOperations) {
|
||||
props.otherOperations.forEach(o => {
|
||||
options.push(
|
||||
<Dropdown.Item key={o.text} onClick={o.onClick}>{o.text}</Dropdown.Item>
|
||||
<Dropdown.Item key={o.text} onClick={o.onClick}>
|
||||
{o.text}
|
||||
</Dropdown.Item>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -259,7 +266,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
className="filter-item col-1 d-none d-sm-inline"
|
||||
>
|
||||
{PAGE_SIZE_OPTIONS.map(s => (
|
||||
<option value={s} key={s}>{s}</option>
|
||||
<option value={s} key={s}>
|
||||
{s}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
<ButtonGroup className="filter-item">
|
||||
@@ -288,7 +297,6 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</Dropdown>
|
||||
|
||||
</ButtonGroup>
|
||||
|
||||
<AddFilter
|
||||
@@ -304,7 +312,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
|
||||
{maybeRenderZoom()}
|
||||
|
||||
<ButtonGroup className="filter-item d-none d-sm-inline-flex">{renderMore()}</ButtonGroup>
|
||||
<ButtonGroup className="filter-item d-none d-sm-inline-flex">
|
||||
{renderMore()}
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -38,14 +38,14 @@ export const Pagination: React.FC<IPaginationProps> = ({
|
||||
);
|
||||
|
||||
const calculatePageClass = (buttonPage: number) => {
|
||||
if(pages.length <= 4) return '';
|
||||
if (pages.length <= 4) return "";
|
||||
|
||||
if(currentPage === 1 && buttonPage <= 4) return '';
|
||||
if (currentPage === 1 && buttonPage <= 4) return "";
|
||||
const maxPage = pages[pages.length - 1];
|
||||
if(currentPage === maxPage && buttonPage > (maxPage - 3)) return '';
|
||||
if(Math.abs(buttonPage - currentPage) <= 1) return '';
|
||||
return 'd-none d-sm-block'
|
||||
}
|
||||
if (currentPage === maxPage && buttonPage > maxPage - 3) return "";
|
||||
if (Math.abs(buttonPage - currentPage) <= 1) return "";
|
||||
return "d-none d-sm-block";
|
||||
};
|
||||
|
||||
const pageButtons = pages.map((page: number) => (
|
||||
<Button
|
||||
@@ -59,12 +59,15 @@ export const Pagination: React.FC<IPaginationProps> = ({
|
||||
</Button>
|
||||
));
|
||||
|
||||
if(pages.length <= 1)
|
||||
return <div />;
|
||||
if (pages.length <= 1) return <div />;
|
||||
|
||||
return (
|
||||
<ButtonGroup className="filter-container pagination">
|
||||
<Button variant="secondary" disabled={currentPage === 1} onClick={() => onChangePage(1)}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage === 1}
|
||||
onClick={() => onChangePage(1)}
|
||||
>
|
||||
<span className="d-none d-sm-inline">First</span>
|
||||
<span className="d-inline d-sm-none">《</span>
|
||||
</Button>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Tabs, Tab } from "react-bootstrap";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import cx from 'classnames'
|
||||
import cx from "classnames";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { Icon, LoadingIndicator } from "src/components/Shared";
|
||||
@@ -166,34 +166,52 @@ export const Performer: React.FC = () => {
|
||||
const renderIcons = () => (
|
||||
<span className="name-icons d-block d-sm-inline">
|
||||
<Button
|
||||
className={cx('minimal', performer.favorite ? "favorite" : "not-favorite")}
|
||||
className={cx(
|
||||
"minimal",
|
||||
performer.favorite ? "favorite" : "not-favorite"
|
||||
)}
|
||||
onClick={() => setFavorite(!performer.favorite)}
|
||||
>
|
||||
<Icon icon="heart" />
|
||||
</Button>
|
||||
{performer.url && (
|
||||
<Button className="minimal">
|
||||
<a href={performer.url} className="link" target="_blank" rel="noopener noreferrer">
|
||||
<a
|
||||
href={performer.url}
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon="link" />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{performer.twitter && (
|
||||
<Button className="minimal">
|
||||
<a href={`https://www.twitter.com/${performer.twitter}`} className="twitter" target="_blank" rel="noopener noreferrer">
|
||||
<a
|
||||
href={`https://www.twitter.com/${performer.twitter}`}
|
||||
className="twitter"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon="dove" />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{performer.instagram && (
|
||||
<Button className="minimal">
|
||||
<a href={`https://www.instagram.com/${performer.instagram}`} className="instagram" target="_blank" rel="noopener noreferrer">
|
||||
<a
|
||||
href={`https://www.instagram.com/${performer.instagram}`}
|
||||
className="instagram"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon="camera" />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
function renderNewView() {
|
||||
return (
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
/* eslint-disable react/no-this-in-sfc */
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Popover,
|
||||
OverlayTrigger,
|
||||
Table
|
||||
} from "react-bootstrap";
|
||||
import { Button, Form, Popover, OverlayTrigger, Table } from "react-bootstrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { Icon, Modal, ImageInput, ScrapePerformerSuggest, LoadingIndicator } from "src/components/Shared";
|
||||
import {
|
||||
Icon,
|
||||
Modal,
|
||||
ImageInput,
|
||||
ScrapePerformerSuggest,
|
||||
LoadingIndicator
|
||||
} from "src/components/Shared";
|
||||
import { ImageUtils, TableUtils } from "src/utils";
|
||||
import { useToast } from "src/hooks";
|
||||
|
||||
@@ -276,7 +276,10 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
<Button className="minimal scrape-url-button" onClick={() => onScrapePerformerURL()}>
|
||||
<Button
|
||||
className="minimal scrape-url-button"
|
||||
onClick={() => onScrapePerformerURL()}
|
||||
>
|
||||
<Icon icon="file-upload" />
|
||||
</Button>
|
||||
);
|
||||
@@ -446,7 +449,10 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
||||
isEditing: !!isEditing,
|
||||
onChange: setInstagram
|
||||
})}
|
||||
<ImageInput isEditing={!!isEditing} onImageChange={onImageChangeHandler} />
|
||||
<ImageInput
|
||||
isEditing={!!isEditing}
|
||||
onImageChange={onImageChangeHandler}
|
||||
/>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { Button, Badge, Card } from 'react-bootstrap';
|
||||
import { Button, Badge, Card } from "react-bootstrap";
|
||||
import { TextUtils } from "src/utils";
|
||||
|
||||
interface IPrimaryTags {
|
||||
@@ -9,14 +9,17 @@ interface IPrimaryTags {
|
||||
onEdit: (marker: GQL.SceneMarkerDataFragment) => void;
|
||||
}
|
||||
|
||||
export const PrimaryTags: React.FC<IPrimaryTags> = ({ sceneMarkers, onClickMarker, onEdit }) => {
|
||||
export const PrimaryTags: React.FC<IPrimaryTags> = ({
|
||||
sceneMarkers,
|
||||
onClickMarker,
|
||||
onEdit
|
||||
}) => {
|
||||
if (!sceneMarkers?.length) return <div />;
|
||||
|
||||
const primaries: Record<string, GQL.Tag> = {};
|
||||
const primaryTags: Record<string, GQL.SceneMarkerDataFragment[]> = {};
|
||||
sceneMarkers.forEach(m => {
|
||||
if(primaryTags[m.primary_tag.id])
|
||||
primaryTags[m.primary_tag.id].push(m);
|
||||
if (primaryTags[m.primary_tag.id]) primaryTags[m.primary_tag.id].push(m);
|
||||
else {
|
||||
primaryTags[m.primary_tag.id] = [m];
|
||||
primaries[m.primary_tag.id] = m.primary_tag;
|
||||
@@ -55,16 +58,10 @@ export const PrimaryTags: React.FC<IPrimaryTags> = ({ sceneMarkers, onClickMarke
|
||||
return (
|
||||
<Card className="primary-card col-12 col-sm-3" key={id}>
|
||||
<h3>{primaries[id].name}</h3>
|
||||
<Card.Body className="primary-card-body">
|
||||
{ markers }
|
||||
</Card.Body>
|
||||
<Card.Body className="primary-card-body">{markers}</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="primary-tag row">
|
||||
{ primaryCards }
|
||||
</div>
|
||||
);
|
||||
return <div className="primary-tag row">{primaryCards}</div>;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useParams, useLocation, useHistory } from "react-router-dom";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { GalleryViewer } from "src/components/Galleries/GalleryViewer";
|
||||
import { LoadingIndicator } from 'src/components/Shared';
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { ScenePlayer } from "src/components/scenes/ScenePlayer/ScenePlayer";
|
||||
import { ScenePerformerPanel } from "./ScenePerformerPanel";
|
||||
import { SceneMarkersPanel } from "./SceneMarkersPanel";
|
||||
@@ -25,8 +25,7 @@ export const Scene: React.FC = () => {
|
||||
const autoplay = queryParams?.autoplay === "true";
|
||||
|
||||
useEffect(() => {
|
||||
if(data?.findScene)
|
||||
setScene(data.findScene)
|
||||
if (data?.findScene) setScene(data.findScene);
|
||||
}, [data]);
|
||||
|
||||
function getInitialTimestamp() {
|
||||
@@ -50,21 +49,14 @@ export const Scene: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScenePlayer
|
||||
scene={scene}
|
||||
timestamp={timestamp}
|
||||
autoplay={autoplay}
|
||||
/>
|
||||
<ScenePlayer scene={scene} timestamp={timestamp} autoplay={autoplay} />
|
||||
<div id="details-container" className="col col-sm-9 m-sm-auto">
|
||||
<Tabs id="scene-tabs" mountOnEnter>
|
||||
<Tab eventKey="scene-details-panel" title="Details">
|
||||
<SceneDetailPanel scene={scene} />
|
||||
</Tab>
|
||||
<Tab eventKey="scene-markers-panel" title="Markers">
|
||||
<SceneMarkersPanel
|
||||
scene={scene}
|
||||
onClickMarker={onClickMarker}
|
||||
/>
|
||||
<SceneMarkersPanel scene={scene} onClickMarker={onClickMarker} />
|
||||
</Tab>
|
||||
{scene.performers.length > 0 ? (
|
||||
<Tab eventKey="scene-performer-panel" title="Performers">
|
||||
@@ -80,10 +72,18 @@ export const Scene: React.FC = () => {
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<Tab className="file-info-panel" eventKey="scene-file-info-panel" title="File Info">
|
||||
<Tab
|
||||
className="file-info-panel"
|
||||
eventKey="scene-file-info-panel"
|
||||
title="File Info"
|
||||
>
|
||||
<SceneFileInfoPanel scene={scene} />
|
||||
</Tab>
|
||||
<Tab eventKey="scene-edit-panel" title="Edit" tabClassName="d-none d-sm-block">
|
||||
<Tab
|
||||
eventKey="scene-edit-panel"
|
||||
title="Edit"
|
||||
tabClassName="d-none d-sm-block"
|
||||
>
|
||||
<SceneEditPanel
|
||||
scene={scene}
|
||||
onUpdate={newScene => setScene(newScene)}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const SceneDetailPanel: React.FC<ISceneDetailProps> = props => {
|
||||
{props.scene.title ?? TextUtils.fileNameFromPath(props.scene.path)}
|
||||
</h3>
|
||||
<div className="col-6 scene-details">
|
||||
<h4>{props.scene.date ?? ''}</h4>
|
||||
<h4>{props.scene.date ?? ""}</h4>
|
||||
{props.scene.rating ? <h6>Rating: {props.scene.rating}</h6> : ""}
|
||||
{props.scene.file.height && (
|
||||
<h6>Resolution: {TextUtils.resolution(props.scene.file.height)}</h6>
|
||||
@@ -48,8 +48,14 @@ export const SceneDetailPanel: React.FC<ISceneDetailProps> = props => {
|
||||
</div>
|
||||
<div className="col-4 offset-2">
|
||||
{props.scene.studio && (
|
||||
<Link className="studio-logo" to={`/studios/${props.scene.studio.id}`}>
|
||||
<img src={props.scene.studio.image_path ?? ''} alt={`${props.scene.studio.name} logo`} />
|
||||
<Link
|
||||
className="studio-logo"
|
||||
to={`/studios/${props.scene.studio.id}`}
|
||||
>
|
||||
<img
|
||||
src={props.scene.studio.image_path ?? ""}
|
||||
alt={`${props.scene.studio.name} logo`}
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
/* eslint-disable react/no-this-in-sfc */
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownButton,
|
||||
Form,
|
||||
Table
|
||||
} from "react-bootstrap";
|
||||
import { Button, Dropdown, DropdownButton, Form, Table } from "react-bootstrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
@@ -294,8 +288,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if(isLoading)
|
||||
return <LoadingIndicator />;
|
||||
if (isLoading) return <LoadingIndicator />;
|
||||
|
||||
return (
|
||||
<div className="form-container row">
|
||||
@@ -329,8 +322,9 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
||||
title: "Rating",
|
||||
value: rating,
|
||||
isEditing: true,
|
||||
onChange: (value: string) => setRating(Number.parseInt(value, 10)),
|
||||
selectOptions: ['', 1, 2, 3, 4, 5]
|
||||
onChange: (value: string) =>
|
||||
setRating(Number.parseInt(value, 10)),
|
||||
selectOptions: ["", 1, 2, 3, 4, 5]
|
||||
})}
|
||||
<tr>
|
||||
<td>Gallery</td>
|
||||
@@ -356,7 +350,9 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
||||
<td>
|
||||
<PerformerSelect
|
||||
isMulti
|
||||
onSelect={items => setPerformerIds(items.map(item => item.id))}
|
||||
onSelect={items =>
|
||||
setPerformerIds(items.map(item => item.id))
|
||||
}
|
||||
ids={performerIds}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -94,7 +94,9 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
return (
|
||||
<div className="row">
|
||||
<span className="col-4">Frame Rate</span>
|
||||
<span className="col-8 text-truncate">{props.scene.file.framerate} frames per second</span>
|
||||
<span className="col-8 text-truncate">
|
||||
{props.scene.file.framerate} frames per second
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -106,7 +108,9 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
return (
|
||||
<div className="row">
|
||||
<span className="col-4">Bit Rate</span>
|
||||
<span className="col-8 text-truncate">{TextUtils.bitRate(props.scene.file.bitrate ?? 0)}</span>
|
||||
<span className="col-8 text-truncate">
|
||||
{TextUtils.bitRate(props.scene.file.bitrate ?? 0)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -118,7 +122,9 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
return (
|
||||
<div className="row">
|
||||
<span className="col-4">Video Codec</span>
|
||||
<span className="col-8 text-truncate">{props.scene.file.video_codec}</span>
|
||||
<span className="col-8 text-truncate">
|
||||
{props.scene.file.video_codec}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -130,7 +136,9 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
return (
|
||||
<div className="row">
|
||||
<span className="col-4">Audio Codec</span>
|
||||
<span className="col-8 text-truncate">{props.scene.file.audio_codec}</span>
|
||||
<span className="col-8 text-truncate">
|
||||
{props.scene.file.audio_codec}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
Form
|
||||
} from "react-bootstrap";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { Field, FieldProps, Form as FormikForm, Formik } from "formik";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
@@ -27,16 +24,19 @@ interface ISceneMarkerForm {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({ sceneID, editingMarker, playerPosition, onClose }) => {
|
||||
export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({
|
||||
sceneID,
|
||||
editingMarker,
|
||||
playerPosition,
|
||||
onClose
|
||||
}) => {
|
||||
const [sceneMarkerCreate] = StashService.useSceneMarkerCreate();
|
||||
const [sceneMarkerUpdate] = StashService.useSceneMarkerUpdate();
|
||||
const [sceneMarkerDestroy] = StashService.useSceneMarkerDestroy();
|
||||
const Toast = useToast();
|
||||
|
||||
const onSubmit = (values: IFormFields) => {
|
||||
const variables:
|
||||
| GQL.SceneMarkerUpdateInput
|
||||
| GQL.SceneMarkerCreateInput = {
|
||||
const variables: GQL.SceneMarkerUpdateInput | GQL.SceneMarkerCreateInput = {
|
||||
title: values.title,
|
||||
seconds: parseFloat(values.seconds),
|
||||
scene_id: sceneID,
|
||||
@@ -54,7 +54,7 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({ sceneID, editingMa
|
||||
.then(onClose)
|
||||
.catch(err => Toast.error(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
if (!editingMarker) return;
|
||||
@@ -62,7 +62,7 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({ sceneID, editingMa
|
||||
sceneMarkerDestroy({ variables: { id: editingMarker.id } })
|
||||
.then(onClose)
|
||||
.catch(err => Toast.error(err));
|
||||
}
|
||||
};
|
||||
const renderTitleField = (fieldProps: FieldProps<string>) => (
|
||||
<div className="col-10">
|
||||
<MarkerTitleSuggest
|
||||
@@ -84,7 +84,7 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({ sceneID, editingMa
|
||||
Math.round(playerPosition ?? 0)
|
||||
)
|
||||
}
|
||||
numericValue={Number.parseInt(fieldProps.field.value ?? '0', 10)}
|
||||
numericValue={Number.parseInt(fieldProps.field.value ?? "0", 10)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -114,47 +114,42 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({ sceneID, editingMa
|
||||
);
|
||||
|
||||
const values: IFormFields = {
|
||||
title: editingMarker?.title ?? '',
|
||||
seconds: (editingMarker?.seconds ?? Math.round(playerPosition ?? 0)).toString(),
|
||||
primaryTagId: editingMarker?.primary_tag.id ?? '',
|
||||
title: editingMarker?.title ?? "",
|
||||
seconds: (
|
||||
editingMarker?.seconds ?? Math.round(playerPosition ?? 0)
|
||||
).toString(),
|
||||
primaryTagId: editingMarker?.primary_tag.id ?? "",
|
||||
tagIds: editingMarker?.tags.map(tag => tag.id) ?? []
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={values}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Formik initialValues={values} onSubmit={onSubmit}>
|
||||
<FormikForm>
|
||||
<div>
|
||||
<Form.Group className="row">
|
||||
<Form.Label htmlFor="title" className="col-2">
|
||||
Scene Marker Title
|
||||
</Form.Label>
|
||||
<Field name="title">
|
||||
{renderTitleField}
|
||||
</Field>
|
||||
<Field name="title">{renderTitleField}</Field>
|
||||
</Form.Group>
|
||||
<Form.Group className="row">
|
||||
<Form.Label htmlFor="primaryTagId" className="col-2">
|
||||
Primary Tag
|
||||
</Form.Label>
|
||||
<div className="col-6">
|
||||
<Field name="primaryTagId">
|
||||
{renderPrimaryTagField}
|
||||
</Field>
|
||||
<Field name="primaryTagId">{renderPrimaryTagField}</Field>
|
||||
</div>
|
||||
<Form.Label htmlFor="seconds" className="col-1">Time</Form.Label>
|
||||
<Field name="seconds">
|
||||
{renderSecondsField}
|
||||
</Field>
|
||||
<Form.Label htmlFor="seconds" className="col-1">
|
||||
Time
|
||||
</Form.Label>
|
||||
<Field name="seconds">{renderSecondsField}</Field>
|
||||
</Form.Group>
|
||||
<Form.Group className="row">
|
||||
<Form.Label htmlFor="tagIds" className="col-2">Tags</Form.Label>
|
||||
<Form.Label htmlFor="tagIds" className="col-2">
|
||||
Tags
|
||||
</Form.Label>
|
||||
<div className="col-10">
|
||||
<Field name="tagIds">
|
||||
{renderTagsField}
|
||||
</Field>
|
||||
<Field name="tagIds">{renderTagsField}</Field>
|
||||
</div>
|
||||
</Form.Group>
|
||||
</div>
|
||||
@@ -178,4 +173,4 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({ sceneID, editingMa
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
} from "react-bootstrap";
|
||||
import { Button } from "react-bootstrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { WallPanel } from "src/components/Wall/WallPanel";
|
||||
import { JWUtils } from "src/utils";
|
||||
import { PrimaryTags } from './PrimaryTags';
|
||||
import { SceneMarkerForm } from './SceneMarkerForm';
|
||||
import { PrimaryTags } from "./PrimaryTags";
|
||||
import { SceneMarkerForm } from "./SceneMarkerForm";
|
||||
|
||||
interface ISceneMarkersPanelProps {
|
||||
scene: GQL.SceneDataFragment;
|
||||
@@ -17,10 +15,9 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (
|
||||
props: ISceneMarkersPanelProps
|
||||
) => {
|
||||
const [isEditorOpen, setIsEditorOpen] = useState<boolean>(false);
|
||||
const [
|
||||
editingMarker,
|
||||
setEditingMarker
|
||||
] = useState<GQL.SceneMarkerDataFragment>();
|
||||
const [editingMarker, setEditingMarker] = useState<
|
||||
GQL.SceneMarkerDataFragment
|
||||
>();
|
||||
|
||||
const jwplayer = JWUtils.getPlayer();
|
||||
|
||||
|
||||
@@ -36,9 +36,7 @@ export const SceneMarkerList: React.FC = () => {
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await StashService.queryFindSceneMarkers(filterCopy);
|
||||
if (
|
||||
singleResult?.data?.findSceneMarkers?.scene_markers?.length === 1
|
||||
) {
|
||||
if (singleResult?.data?.findSceneMarkers?.scene_markers?.length === 1) {
|
||||
// navigate to the scene player page
|
||||
const url = NavUtils.makeSceneMarkerUrl(
|
||||
singleResult.data.findSceneMarkers.scene_markers[0]
|
||||
|
||||
@@ -3,7 +3,7 @@ import ReactJWPlayer from "react-jw-player";
|
||||
import { HotKeys } from "react-hotkeys";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { JWUtils } from 'src/utils';
|
||||
import { JWUtils } from "src/utils";
|
||||
import { ScenePlayerScrubber } from "./ScenePlayerScrubber";
|
||||
|
||||
interface IScenePlayerProps {
|
||||
@@ -206,7 +206,10 @@ export class ScenePlayerImpl extends React.Component<
|
||||
public render() {
|
||||
return (
|
||||
<HotKeys keyMap={KeyMap} handlers={this.KeyHandlers} className="row">
|
||||
<div id="jwplayer-container" className="w-100 col-sm-9 m-sm-auto no-gutter" >
|
||||
<div
|
||||
id="jwplayer-container"
|
||||
className="w-100 col-sm-9 m-sm-auto no-gutter"
|
||||
>
|
||||
{this.renderPlayer()}
|
||||
<ScenePlayerScrubber
|
||||
scene={this.props.scene}
|
||||
|
||||
@@ -3,7 +3,11 @@ import { Button, Form } from "react-bootstrap";
|
||||
import _ from "lodash";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { FilterSelect, StudioSelect, LoadingIndicator } from "src/components/Shared";
|
||||
import {
|
||||
FilterSelect,
|
||||
StudioSelect,
|
||||
LoadingIndicator
|
||||
} from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
|
||||
interface IListOperationProps {
|
||||
@@ -223,7 +227,6 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
|
||||
setIsLoading(false);
|
||||
}, [props.selected]);
|
||||
|
||||
|
||||
function renderMultiSelect(
|
||||
type: "performers" | "tags",
|
||||
ids: string[] | undefined
|
||||
@@ -249,19 +252,21 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
|
||||
);
|
||||
}
|
||||
|
||||
if(isLoading)
|
||||
return <LoadingIndicator />;
|
||||
if (isLoading) return <LoadingIndicator />;
|
||||
|
||||
function render() {
|
||||
return (
|
||||
<div className="operation-container">
|
||||
<Form.Group controlId="rating" className="operation-item rating-operation">
|
||||
<Form.Group
|
||||
controlId="rating"
|
||||
className="operation-item rating-operation"
|
||||
>
|
||||
<Form.Label>Rating</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
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>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
FindStudiosQueryResult,
|
||||
FindPerformersQueryResult
|
||||
} from "src/core/generated-graphql";
|
||||
import { LoadingIndicator } from 'src/components/Shared';
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { ListFilter } from "src/components/list/ListFilter";
|
||||
import { Pagination } from "src/components/list/Pagination";
|
||||
import { StashService } from "src/core/StashService";
|
||||
|
||||
@@ -3,4 +3,4 @@ export { default as NavUtils } from "./navigation";
|
||||
export { default as TableUtils } from "./table";
|
||||
export { default as TextUtils } from "./text";
|
||||
export { default as DurationUtils } from "./duration";
|
||||
export { default as JWUtils } from './jwplayer';
|
||||
export { default as JWUtils } from "./jwplayer";
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
const playerID = "main-jwplayer";
|
||||
const getPlayer = () => (
|
||||
(window as any).jwplayer(playerID)
|
||||
)
|
||||
const getPlayer = () => (window as any).jwplayer(playerID);
|
||||
|
||||
export default {
|
||||
playerID,
|
||||
|
||||
@@ -94,7 +94,11 @@ const renderHtmlSelect = (options: {
|
||||
options.onChange(event.currentTarget.value)
|
||||
}
|
||||
>
|
||||
{ options.selectOptions.map(opt => <option value={opt} key={opt}>{opt}</option>)}
|
||||
{options.selectOptions.map(opt => (
|
||||
<option value={opt} key={opt}>
|
||||
{opt}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user