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