This commit is contained in:
Infinite
2020-01-24 16:30:07 +01:00
parent e1a1914d16
commit c2544fee98
37 changed files with 5473 additions and 3182 deletions

View File

@@ -1,9 +1,7 @@
import React from "react";
import { Table } from "react-bootstrap";
import { Link } from "react-router-dom";
import {
FindGalleriesQueryResult,
} from "src/core/generated-graphql";
import { FindGalleriesQueryResult } from "src/core/generated-graphql";
import { useGalleriesList } from "src/hooks";
import { ListFilterModel } from "src/models/list-filter/filter";
import { DisplayMode } from "src/models/list-filter/types";
@@ -38,7 +36,10 @@ export const GalleryList: React.FC = () => {
<td>
<Link to={`/galleries/${gallery.id}`}>
{gallery.files.length > 0 ? (
<img alt={gallery.title ?? ''} src={`${gallery.files[0].path}?thumb=true`} />
<img
alt={gallery.title ?? ""}
src={`${gallery.files[0].path}?thumb=true`}
/>
) : (
undefined
)}

View File

@@ -1,5 +1,5 @@
import React, { FunctionComponent, useState } from "react";
import Lightbox from 'react-images';
import Lightbox from "react-images";
import Gallery from "react-photo-gallery";
import * as GQL from "src/core/generated-graphql";
@@ -30,8 +30,8 @@ export const GalleryViewer: FunctionComponent<IProps> = ({ gallery }) => {
}
const photos = gallery.files.map(file => ({
src: file.path ?? '',
caption: file.name ?? ''
src: file.path ?? "",
caption: file.name ?? ""
}));
const thumbs = gallery.files.map(file => ({
src: `${file.path}?thumb=true` || "",
@@ -48,7 +48,9 @@ export const GalleryViewer: FunctionComponent<IProps> = ({ gallery }) => {
onClickPrev={gotoPrevious}
onClickNext={gotoNext}
currentImage={currentImage}
onClickImage={() => window.open(photos[currentImage].src ?? '', "_blank")}
onClickImage={() =>
window.open(photos[currentImage].src ?? "", "_blank")
}
isOpen={lightboxIsOpen}
width={9999}
/>

View File

@@ -4,7 +4,13 @@ import { StashService } from "src/core/StashService";
export const SettingsAboutPanel: React.FC = () => {
const { data, error, loading } = StashService.useVersion();
const { data: dataLatest, error: errorLatest, loading: loadingLatest, refetch, networkStatus } = StashService.useLatestVersion();
const {
data: dataLatest,
error: errorLatest,
loading: loadingLatest,
refetch,
networkStatus
} = StashService.useLatestVersion();
function maybeRenderTag() {
if (!data || !data.version || !data.version.version) {
@@ -19,28 +25,34 @@ export const SettingsAboutPanel: React.FC = () => {
}
function maybeRenderLatestVersion() {
if (!dataLatest || !dataLatest.latestversion || !dataLatest.latestversion.shorthash || !dataLatest.latestversion.url) { return; }
if (
!dataLatest ||
!dataLatest.latestversion ||
!dataLatest.latestversion.shorthash ||
!dataLatest.latestversion.url
) {
return;
}
if (!data || !data.version || !data.version.hash) {
return (
<>{dataLatest.latestversion.shorthash}</>
);
return <>{dataLatest.latestversion.shorthash}</>;
}
if (data.version.hash !== dataLatest.latestversion.shorthash) {
return (
<>
<strong>{dataLatest.latestversion.shorthash} [NEW] </strong><a href={dataLatest.latestversion.url}>Download</a>
<strong>{dataLatest.latestversion.shorthash} [NEW] </strong>
<a href={dataLatest.latestversion.url}>Download</a>
</>
);
}
return (
<>{dataLatest.latestversion.shorthash}</>
);
return <>{dataLatest.latestversion.shorthash}</>;
}
function renderLatestVersion() {
if (!data || !data.version || !data.version.version) { return; } // if there is no "version" latest version check is obviously not supported
if (!data || !data.version || !data.version.version) {
return;
} // if there is no "version" latest version check is obviously not supported
return (
<Table>
<tbody>
@@ -49,7 +61,9 @@ export const SettingsAboutPanel: React.FC = () => {
<td>{maybeRenderLatestVersion()} </td>
</tr>
<tr>
<td><Button onClick={() => refetch()}>Check for new version</Button></td>
<td>
<Button onClick={() => refetch()}>Check for new version</Button>
</td>
</tr>
</tbody>
</Table>
@@ -84,16 +98,54 @@ export const SettingsAboutPanel: React.FC = () => {
<Table>
<tbody>
<tr>
<td>Stash home at <a href="https://github.com/stashapp/stash" rel="noopener noreferrer" target="_blank">Github</a></td>
<td>
Stash home at{" "}
<a
href="https://github.com/stashapp/stash"
rel="noopener noreferrer"
target="_blank"
>
Github
</a>
</td>
</tr>
<tr>
<td>Stash <a href="https://github.com/stashapp/stash/wiki" rel="noopener noreferrer" target="_blank">Wiki</a> page</td>
<td>
Stash{" "}
<a
href="https://github.com/stashapp/stash/wiki"
rel="noopener noreferrer"
target="_blank"
>
Wiki
</a>{" "}
page
</td>
</tr>
<tr>
<td>Join our <a href="https://discord.gg/2TsNFKt" rel="noopener noreferrer" target="_blank">Discord</a> channel</td>
<td>
Join our{" "}
<a
href="https://discord.gg/2TsNFKt"
rel="noopener noreferrer"
target="_blank"
>
Discord
</a>{" "}
channel
</td>
</tr>
<tr>
<td>Support us through <a href="https://opencollective.com/stashapp" rel="noopener noreferrer" target="_blank">Open Collective</a></td>
<td>
Support us through{" "}
<a
href="https://opencollective.com/stashapp"
rel="noopener noreferrer"
target="_blank"
>
Open Collective
</a>
</td>
</tr>
</tbody>
</Table>
@@ -101,7 +153,11 @@ export const SettingsAboutPanel: React.FC = () => {
{error && <span>{error.message}</span>}
{errorLatest && <span>{errorLatest.message}</span>}
{renderVersion()}
{!dataLatest || loadingLatest || networkStatus === 4 ? <Spinner animation="border" variant="light" /> : <>{renderLatestVersion()}</>}
{!dataLatest || loadingLatest || networkStatus === 4 ? (
<Spinner animation="border" variant="light" />
) : (
<>{renderLatestVersion()}</>
)}
</>
);
};

View File

@@ -56,7 +56,9 @@ export const SettingsConfigurationPanel: React.FC = () => {
setDatabasePath(conf.general.databasePath);
setGeneratedPath(conf.general.generatedPath);
setMaxTranscodeSize(conf.general.maxTranscodeSize ?? undefined);
setMaxStreamingTranscodeSize(conf.general.maxStreamingTranscodeSize ?? undefined);
setMaxStreamingTranscodeSize(
conf.general.maxStreamingTranscodeSize ?? undefined
);
setUsername(conf.general.username);
setPassword(conf.general.password);
setLogFile(conf.general.logFile ?? undefined);

View File

@@ -24,10 +24,8 @@ interface IProps {
onImageChange: (event: React.FormEvent<HTMLInputElement>) => void;
// TODO: only for performers. make generic
scrapers?: Pick<GQL.Scraper, 'id' | 'name'>[];
onDisplayScraperDialog?: (
scraper: Pick<GQL.Scraper, 'id' | 'name'>
) => void;
scrapers?: Pick<GQL.Scraper, "id" | "name">[];
onDisplayScraperDialog?: (scraper: Pick<GQL.Scraper, "id" | "name">) => void;
}
export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
@@ -95,9 +93,7 @@ export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
? props.scrapers.map(s => (
<Button
variant="link"
onClick={() =>
props.onDisplayScraperDialog?.(s)
}
onClick={() => props.onDisplayScraperDialog?.(s)}
>
{s.name}
</Button>

View File

@@ -11,13 +11,14 @@ interface IProps {
}
export const DurationInput: React.FC<IProps> = (props: IProps) => {
const [value, setValue] = useState<string>(DurationUtils.secondsToString(props.numericValue));
const [value, setValue] = useState<string>(
DurationUtils.secondsToString(props.numericValue)
);
useEffect(() => {
setValue(DurationUtils.secondsToString(props.numericValue));
}, [props.numericValue]);
function increment() {
let seconds = DurationUtils.stringToSeconds(value);
seconds += 1;
@@ -66,7 +67,9 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
disabled={props.disabled}
value={value}
onChange={(e: any) => setValue(e.target.value)}
onBlur={() => props.onValueChange(DurationUtils.stringToSeconds(value))}
onBlur={() =>
props.onValueChange(DurationUtils.stringToSeconds(value))
}
placeholder="hh:mm:ss"
/>
<InputGroup.Append>

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { Spinner } from 'react-bootstrap';
import React from "react";
import { Spinner } from "react-bootstrap";
interface ILoadingProps {
message: string;
}
const CLASSNAME = 'LoadingIndicator';
const CLASSNAME = "LoadingIndicator";
const CLASSNAME_MESSAGE = `${CLASSNAME}-message`;
const LoadingIndicator: React.FC<ILoadingProps> = ({ message }) => (
@@ -13,9 +13,7 @@ const LoadingIndicator: React.FC<ILoadingProps> = ({ message }) => (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
<h4 className={CLASSNAME_MESSAGE}>
{ message }
</h4>
<h4 className={CLASSNAME_MESSAGE}>{message}</h4>
</div>
);

View File

@@ -41,7 +41,9 @@ interface ISceneGallerySelect {
initialId?: string;
sceneId: string;
onSelect: (
item: GQL.ValidGalleriesForSceneQuery["validGalleriesForScene"][0] | undefined
item:
| GQL.ValidGalleriesForSceneQuery["validGalleriesForScene"][0]
| undefined
) => void;
}
@@ -78,9 +80,7 @@ export const SceneGallerySelect: React.FC<ISceneGallerySelect> = props => {
interface IScrapePerformerSuggestProps {
scraperId: string;
onSelectPerformer: (
performer: GQL.ScrapedPerformerDataFragment
) => void;
onSelectPerformer: (performer: GQL.ScrapedPerformerDataFragment) => void;
placeholder?: string;
}
export const ScrapePerformerSuggest: React.FC<IScrapePerformerSuggestProps> = props => {
@@ -105,8 +105,7 @@ export const ScrapePerformerSuggest: React.FC<IScrapePerformerSuggestProps> = pr
const onChange = (selectedItems: ValueType<Option>) => {
const name = getSelectedValues(selectedItems)[0];
const performer = performers.find(p => p.name === name);
if(performer)
props.onSelectPerformer(performer)
if (performer) props.onSelectPerformer(performer);
};
return (

View File

@@ -14,4 +14,4 @@ export { DetailsEditNavbar } from "./DetailsEditNavbar";
export { DurationInput } from "./DurationInput";
export { TagLink } from "./TagLink";
export { HoverPopover } from "./HoverPopover";
export { default as LoadingIndicator } from './LoadingIndicator';
export { default as LoadingIndicator } from "./LoadingIndicator";

View File

@@ -5,11 +5,9 @@ import { LoadingIndicator } from "src/components/Shared";
export const Stats: React.FC = () => {
const { data, error, loading } = StashService.useStats();
if (loading || !data)
return <LoadingIndicator message="Loading..." />
if (loading || !data) return <LoadingIndicator message="Loading..." />;
if (error)
return <span>error.message</span> ;
if (error) return <span>error.message</span>;
return (
<div className="w-75 m-auto">

View File

@@ -1,7 +1,5 @@
import React from "react";
import {
FindStudiosQueryResult
} from "src/core/generated-graphql";
import { FindStudiosQueryResult } from "src/core/generated-graphql";
import { useStudiosList } from "src/hooks";
import { ListFilterModel } from "src/models/list-filter/filter";
import { DisplayMode } from "src/models/list-filter/types";

View File

@@ -183,7 +183,7 @@ export const AddFilter: React.FC<IAddFilterProps> = (
numericValue={criterion.value ? criterion.value : 0}
onValueChange={onChangedDuration}
/>
)
);
}
return (
<Form.Control

View File

@@ -249,7 +249,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
value={props.filter.searchTerm}
onChange={onChangeQuery}
className="filter-item"
style={{ width: 'inherit' }}
style={{ width: "inherit" }}
/>
<Form.Control
as="select"

View File

@@ -36,7 +36,8 @@ export const PerformerCard: React.FC<IPerformerCardProps> = (
<div className="card-section">
<h5 className="text-truncate">{props.performer.name}</h5>
{age !== 0 ? <div className="text-muted">{ageString}</div> : ""}
<div className="text-muted">Stars in {props.performer.scene_count}{" "}
<div className="text-muted">
Stars in {props.performer.scene_count}{" "}
<Link to={NavUtils.makePerformerScenesUrl(props.performer)}>
scenes
</Link>

View File

@@ -5,16 +5,14 @@ import { Button, Spinner, Tabs, Tab } from "react-bootstrap";
import { useParams, useHistory } from "react-router-dom";
import * as GQL from "src/core/generated-graphql";
import { StashService } from "src/core/StashService";
import {
Icon,
} from "src/components/Shared";
import { useToast } from 'src/hooks';
import { Icon } from "src/components/Shared";
import { useToast } from "src/hooks";
import { TextUtils } from "src/utils";
import Lightbox from 'react-images';
import Lightbox from "react-images";
import { IconName } from "@fortawesome/fontawesome-svg-core";
import { PerformerDetailsPanel } from './PerformerDetailsPanel';
import { PerformerOperationsPanel } from './PerformerOperationsPanel';
import { PerformerScenesPanel } from './PerformerScenesPanel';
import { PerformerDetailsPanel } from "./PerformerDetailsPanel";
import { PerformerOperationsPanel } from "./PerformerOperationsPanel";
import { PerformerScenesPanel } from "./PerformerScenesPanel";
export const Performer: React.FC = () => {
const Toast = useToast();
@@ -23,7 +21,9 @@ export const Performer: React.FC = () => {
const isNew = id === "new";
// Performer state
const [performer, setPerformer] = useState<Partial<GQL.PerformerDataFragment>>({});
const [performer, setPerformer] = useState<
Partial<GQL.PerformerDataFragment>
>({});
const [imagePreview, setImagePreview] = useState<string>();
const [lightboxIsOpen, setLightboxIsOpen] = useState(false);
@@ -35,7 +35,6 @@ export const Performer: React.FC = () => {
const [createPerformer] = StashService.usePerformerCreate();
const [deletePerformer] = StashService.usePerformerDestroy();
useEffect(() => {
setIsLoading(false);
if (data?.findPerformer) setPerformer(data.findPerformer);
@@ -54,15 +53,23 @@ export const Performer: React.FC = () => {
if (error) return <div>{error.message}</div>;
async function onSave(performerInput: Partial<GQL.PerformerCreateInput> | Partial<GQL.PerformerUpdateInput>) {
async function onSave(
performerInput:
| Partial<GQL.PerformerCreateInput>
| Partial<GQL.PerformerUpdateInput>
) {
setIsLoading(true);
try {
if (!isNew) {
const result = await updatePerformer({variables: performerInput as GQL.PerformerUpdateInput});
const result = await updatePerformer({
variables: performerInput as GQL.PerformerUpdateInput
});
if (result.data?.performerUpdate)
setPerformer(result.data?.performerUpdate);
} else {
const result = await createPerformer({variables: performerInput as GQL.PerformerCreateInput});
const result = await createPerformer({
variables: performerInput as GQL.PerformerCreateInput
});
if (result.data?.performerCreate) {
setPerformer(result.data.performerCreate);
history.push(`/performers/${result.data.performerCreate.id}`);
@@ -165,7 +172,7 @@ export const Performer: React.FC = () => {
<Icon icon={icon} />
</a>
</Button>
)
);
}
}
@@ -175,7 +182,8 @@ export const Performer: React.FC = () => {
<Button
className={performer.favorite ? "favorite" : "not-favorite"}
onClick={() => setFavorite(!performer.favorite)}
><Icon icon="heart" />
>
<Icon icon="heart" />
</Button>
{maybeRenderURL(performer.url ?? undefined)}
{/* TODO - render instagram and twitter links with icons */}
@@ -188,7 +196,7 @@ export const Performer: React.FC = () => {
return (
<div className="columns is-multiline no-spacing">
<div className="column is-half details-image-container">
<img className="performer" src={imagePreview} alt='Performer' />
<img className="performer" src={imagePreview} alt="Performer" />
</div>
<div className="column is-half details-detail-container">
{renderTabs()}
@@ -216,7 +224,7 @@ export const Performer: React.FC = () => {
<div id="performer-page">
<div className="details-image-container">
<Button variant="link" onClick={openLightbox}>
<img className="performer" src={imagePreview} alt='Performer' />
<img className="performer" src={imagePreview} alt="Performer" />
</Button>
</div>
<div className="performer-head">
@@ -229,9 +237,7 @@ export const Performer: React.FC = () => {
</div>
<div className="performer-body">
<div className="details-detail-container">
{renderTabs()}
</div>
<div className="details-detail-container">{renderTabs()}</div>
</div>
</div>
<Lightbox

View File

@@ -1,14 +1,17 @@
/* eslint-disable react/no-this-in-sfc */
import React, { useEffect, useState } from "react";
import { Button, Form, Popover, OverlayTrigger, Spinner, Table } from "react-bootstrap";
import {
Button,
Form,
Popover,
OverlayTrigger,
Spinner,
Table
} from "react-bootstrap";
import * as GQL from "src/core/generated-graphql";
import { StashService } from "src/core/StashService";
import {
Icon,
Modal,
ScrapePerformerSuggest
} from "src/components/Shared";
import { Icon, Modal, ScrapePerformerSuggest } from "src/components/Shared";
import { ImageUtils, TableUtils } from "src/utils";
import { useToast } from "src/hooks";
@@ -16,20 +19,34 @@ interface IPerformerDetails {
performer: Partial<GQL.PerformerDataFragment>;
isNew?: boolean;
isEditing?: boolean;
onSave?: (performer: Partial<GQL.PerformerCreateInput> | Partial<GQL.PerformerUpdateInput>) => void;
onSave?: (
performer:
| Partial<GQL.PerformerCreateInput>
| Partial<GQL.PerformerUpdateInput>
) => void;
onDelete?: () => void;
onImageChange?: (image: string) => void;
}
export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({ performer, isNew, isEditing, onSave, onDelete, onImageChange }) => {
export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
performer,
isNew,
isEditing,
onSave,
onDelete,
onImageChange
}) => {
const Toast = useToast();
// Editing state
const [isDisplayingScraperDialog, setIsDisplayingScraperDialog] = useState<GQL.Scraper>();
const [scrapePerformerDetails, setScrapePerformerDetails] = useState<GQL.ScrapedPerformerDataFragment>();
const [isDisplayingScraperDialog, setIsDisplayingScraperDialog] = useState<
GQL.Scraper
>();
const [scrapePerformerDetails, setScrapePerformerDetails] = useState<
GQL.ScrapedPerformerDataFragment
>();
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
// Editing performer state
const [image, setImage] = useState<string>();
const [name, setName] = useState<string>();
@@ -55,7 +72,6 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({ performer,
const Scrapers = StashService.useListPerformerScrapers();
const [queryableScrapers, setQueryableScrapers] = useState<GQL.Scraper[]>([]);
function updatePerformerEditState(
state: Partial<GQL.PerformerDataFragment | GQL.ScrapedPerformer>
) {
@@ -91,19 +107,17 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({ performer,
}
}
if (isEditing)
ImageUtils.usePasteImage(onImageLoad);
if (isEditing) ImageUtils.usePasteImage(onImageLoad);
useEffect(() => {
const newQueryableScrapers = (Scrapers?.data?.listPerformerScrapers ?? []).filter(s => (
s.performer?.supported_scrapes.includes(GQL.ScrapeType.Name)
));
const newQueryableScrapers = (
Scrapers?.data?.listPerformerScrapers ?? []
).filter(s => s.performer?.supported_scrapes.includes(GQL.ScrapeType.Name));
setQueryableScrapers(newQueryableScrapers);
}, [Scrapers]);
if (isLoading)
return <Spinner animation="border" variant="light" />;
if (isLoading) return <Spinner animation="border" variant="light" />;
function getPerformerInput() {
const performerInput: Partial<
@@ -138,9 +152,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({ performer,
ImageUtils.onImageChange(event, onImageLoad);
}
function onDisplayFreeOnesDialog(
scraper: GQL.Scraper
) {
function onDisplayFreeOnesDialog(scraper: GQL.Scraper) {
setIsDisplayingScraperDialog(scraper);
}
@@ -212,9 +224,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({ performer,
? queryableScrapers.map(s => (
<Button
variant="link"
onClick={() =>
onDisplayFreeOnesDialog(s)
}
onClick={() => onDisplayFreeOnesDialog(s)}
>
{s.name}
</Button>
@@ -299,15 +309,30 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({ performer,
if (isEditing) {
return (
<>
<Button className="edit-button" variant="primary" onClick={() => onSave?.(getPerformerInput())}>Save</Button>
{!isNew ? <Button className="edit-button" variant="danger" onClick={() => setIsDeleteAlertOpen(true)}>Delete</Button> : ''}
<Button
className="edit-button"
variant="primary"
onClick={() => onSave?.(getPerformerInput())}
>
Save
</Button>
{!isNew ? (
<Button
className="edit-button"
variant="danger"
onClick={() => setIsDeleteAlertOpen(true)}
>
Delete
</Button>
) : (
""
)}
{renderScraperMenu()}
</>
);
}
}
function renderDeleteAlert() {
return (
<Modal
@@ -316,34 +341,50 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({ performer,
accept={{ text: "Delete", variant: "danger", onClick: onDelete }}
cancel={{ onClick: () => setIsDeleteAlertOpen(false) }}
>
<p>
Are you sure you want to delete {name}?
</p>
<p>Are you sure you want to delete {name}?</p>
</Modal>
);
}
function renderImageInput() {
if (!isEditing) { return; }
if (!isEditing) {
return;
}
return (
<tr>
<td>Image</td>
<td><Form.Control type="file" onChange={onImageChangeHandler} accept=".jpg,.jpeg" /></td>
<td>
<Form.Control
type="file"
onChange={onImageChangeHandler}
accept=".jpg,.jpeg"
/>
</td>
</tr>
)
);
}
function maybeRenderName() {
if (isEditing) {
return TableUtils.renderInputGroup(
{title: "Name", value: name, isEditing: !!isEditing, placeholder: "Name", onChange: setName});
return TableUtils.renderInputGroup({
title: "Name",
value: name,
isEditing: !!isEditing,
placeholder: "Name",
onChange: setName
});
}
}
function maybeRenderAliases() {
if (isEditing) {
return TableUtils.renderInputGroup(
{title: "Aliases", value: aliases, isEditing: !!isEditing, placeholder: "Aliases", onChange: setAliases});
return TableUtils.renderInputGroup({
title: "Aliases",
value: aliases,
isEditing: !!isEditing,
placeholder: "Aliases",
onChange: setAliases
});
}
}

View File

@@ -1,16 +1,16 @@
import {
Button,
} from "react-bootstrap";
import { Button } from "react-bootstrap";
import React from "react";
import * as GQL from "src/core/generated-graphql";
import { StashService } from "src/core/StashService";
import { useToast } from "src/hooks";
interface IPerformerOperationsProps {
performer: Partial<GQL.PerformerDataFragment>
performer: Partial<GQL.PerformerDataFragment>;
}
export const PerformerOperationsPanel: React.FC<IPerformerOperationsProps> = ({ performer }) => {
export const PerformerOperationsPanel: React.FC<IPerformerOperationsProps> = ({
performer
}) => {
const Toast = useToast();
async function onAutoTag() {

View File

@@ -5,25 +5,30 @@ import { ListFilterModel } from "src/models/list-filter/filter";
import { SceneList } from "../../scenes/SceneList";
interface IPerformerDetailsProps {
performer: Partial<GQL.PerformerDataFragment>
performer: Partial<GQL.PerformerDataFragment>;
}
export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({ performer }) => {
export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({
performer
}) => {
function filterHook(filter: ListFilterModel) {
const performerValue = { id: performer.id!, label: performer.name! };
// if performers is already present, then we modify it, otherwise add
let performerCriterion = filter.criteria.find((c) => {
let performerCriterion = filter.criteria.find(c => {
return c.type === "performers";
});
if (performerCriterion &&
if (
performerCriterion &&
(performerCriterion.modifier === GQL.CriterionModifier.IncludesAll ||
performerCriterion.modifier === GQL.CriterionModifier.Includes)) {
performerCriterion.modifier === GQL.CriterionModifier.Includes)
) {
// add the performer if not present
if (!performerCriterion.value.find((p : any) => {
if (
!performerCriterion.value.find((p: any) => {
return p.id === performer.id;
})) {
})
) {
performerCriterion.value.push(performerValue);
}
@@ -38,10 +43,5 @@ export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({ perform
return filter;
}
return (
<SceneList
subComponent
filterHook={filterHook}
/>
);
}
return <SceneList subComponent filterHook={filterHook} />;
};

View File

@@ -1,9 +1,7 @@
import _ from "lodash";
import React from "react";
import { useHistory } from "react-router-dom";
import {
FindPerformersQueryResult,
} from "src/core/generated-graphql";
import { FindPerformersQueryResult } from "src/core/generated-graphql";
import { StashService } from "src/core/StashService";
import { usePerformersList } from "src/hooks";
import { ListFilterModel } from "src/models/list-filter/filter";

View File

@@ -24,7 +24,8 @@ export const SceneCard: React.FC<ISceneCardProps> = (
});
const config = StashService.useConfiguration();
const showStudioAsText = config?.data?.configuration.interface.showStudioAsText ?? false;
const showStudioAsText =
config?.data?.configuration.interface.showStudioAsText ?? false;
function maybeRenderRatingBanner() {
if (!props.scene.rating) {
@@ -221,7 +222,13 @@ export const SceneCard: React.FC<ISceneCardProps> = (
: TextUtils.fileNameFromPath(props.scene.path)}
</h4>
<span>{props.scene.date}</span>
<p>{TextUtils.truncate(props.scene.details ?? "", 100, "... (continued)")}</p>
<p>
{TextUtils.truncate(
props.scene.details ?? "",
100,
"... (continued)"
)}
</p>
</div>
{maybeRenderPopoverButtonGroup()}

View File

@@ -57,9 +57,9 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
const [deleteScene] = StashService.useSceneDestroy(getSceneDeleteInput());
useEffect(() => {
const newQueryableScrapers = (Scrapers?.data?.listSceneScrapers ?? []).filter(s => (
s.scene?.supported_scrapes.includes(GQL.ScrapeType.Fragment)
));
const newQueryableScrapers = (
Scrapers?.data?.listSceneScrapers ?? []
).filter(s => s.scene?.supported_scrapes.includes(GQL.ScrapeType.Fragment));
setQueryableScrapers(newQueryableScrapers);
}, [Scrapers]);
@@ -202,9 +202,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
ImageUtils.onImageChange(event, onImageLoad);
}
async function onScrapeClicked(
scraper: GQL.Scraper
) {
async function onScrapeClicked(scraper: GQL.Scraper) {
setIsLoading(true);
try {
const result = await StashService.queryScrapeScene(
@@ -418,7 +416,11 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
</Button>
<Collapse in={isCoverImageOpen}>
<div>
<img className="scene-cover" src={coverImagePreview} alt="Scene cover" />
<img
className="scene-cover"
src={coverImagePreview}
alt="Scene cover"
/>
<Form.Group className="test" controlId="cover">
<Form.Control
type="file"

View File

@@ -38,7 +38,9 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
<tr>
<td>Stream</td>
<td>
<a href={props.scene.paths.stream ?? ''}>{props.scene.paths.stream}</a>{" "}
<a href={props.scene.paths.stream ?? ""}>
{props.scene.paths.stream}
</a>{" "}
</td>
</tr>
);
@@ -51,7 +53,9 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
return (
<tr>
<td>File Size</td>
<td>{TextUtils.fileSize(parseInt(props.scene.file.size ?? '0', 10))}</td>
<td>
{TextUtils.fileSize(parseInt(props.scene.file.size ?? "0", 10))}
</td>
</tr>
);
}

View File

@@ -114,12 +114,16 @@ class SceneParserResult {
public studioId: ParserResult<string> = new ParserResult();
public tags: ParserResult<GQL.Tag[]> = new ParserResult();
public tagIds: ParserResult<string[]> = new ParserResult();
public performers: ParserResult<Partial<GQL.Performer>[]> = new ParserResult();
public performers: ParserResult<
Partial<GQL.Performer>[]
> = new ParserResult();
public performerIds: ParserResult<string[]> = new ParserResult();
public scene: GQL.SlimSceneDataFragment;
constructor(result: GQL.ParseSceneFilenamesQuery["parseSceneFilenames"]["results"][0]) {
constructor(
result: GQL.ParseSceneFilenamesQuery["parseSceneFilenames"]["results"][0]
) {
this.scene = result.scene;
this.id = this.scene.id;
@@ -141,12 +145,15 @@ class SceneParserResult {
if (result.performer_ids) {
this.performers.setValue(
(result.performer_ids ?? []).map(p => ({
(result.performer_ids ?? []).map(
p =>
({
id: p,
name: "",
favorite: false,
image_path: ""
} as GQL.Performer))
} as GQL.Performer)
)
);
}
@@ -343,7 +350,9 @@ export const SceneFilenameParser: React.FC = () => {
}, [parserInput]);
const parseResults = useCallback(
(results: GQL.ParseSceneFilenamesQuery["parseSceneFilenames"]["results"]) => {
(
results: GQL.ParseSceneFilenamesQuery["parseSceneFilenames"]["results"]
) => {
if (results) {
const result = results
.map(r => {
@@ -897,7 +906,9 @@ export const SceneFilenameParser: React.FC = () => {
fieldName="Title"
className="parser-field-title"
parserResult={props.scene.title}
onSetChanged={set => onTitleChanged(set, props.scene.title.value ?? undefined)}
onSetChanged={set =>
onTitleChanged(set, props.scene.title.value ?? undefined)
}
onValueChanged={value => onTitleChanged(props.scene.title.set, value)}
renderOriginalInputField={renderOriginalInputGroup}
renderNewInputField={renderNewInputGroup}
@@ -907,7 +918,9 @@ export const SceneFilenameParser: React.FC = () => {
fieldName="Date"
className="parser-field-date"
parserResult={props.scene.date}
onSetChanged={set => onDateChanged(set, props.scene.date.value ?? undefined)}
onSetChanged={set =>
onDateChanged(set, props.scene.date.value ?? undefined)
}
onValueChanged={value => onDateChanged(props.scene.date.set, value)}
renderOriginalInputField={renderOriginalInputGroup}
renderNewInputField={renderNewInputGroup}
@@ -919,7 +932,10 @@ export const SceneFilenameParser: React.FC = () => {
parserResult={props.scene.performerIds}
originalParserResult={props.scene.performers}
onSetChanged={set =>
onPerformerIdsChanged(set, props.scene.performerIds.value ?? undefined)
onPerformerIdsChanged(
set,
props.scene.performerIds.value ?? undefined
)
}
onValueChanged={value =>
onPerformerIdsChanged(props.scene.performerIds.set, value)
@@ -933,7 +949,9 @@ export const SceneFilenameParser: React.FC = () => {
className="parser-field-tags"
parserResult={props.scene.tagIds}
originalParserResult={props.scene.tags}
onSetChanged={set => onTagIdsChanged(set, props.scene.tagIds.value ?? undefined)}
onSetChanged={set =>
onTagIdsChanged(set, props.scene.tagIds.value ?? undefined)
}
onValueChanged={value =>
onTagIdsChanged(props.scene.tagIds.set, value)
}

View File

@@ -19,7 +19,10 @@ interface ISceneList {
filterHook?: (filter: ListFilterModel) => ListFilterModel;
}
export const SceneList: React.FC<ISceneList> = ({ subComponent, filterHook }) => {
export const SceneList: React.FC<ISceneList> = ({
subComponent,
filterHook
}) => {
const history = useHistory();
const otherOperations = [
{

View File

@@ -1,9 +1,7 @@
import _ from "lodash";
import React from "react";
import { useHistory } from "react-router-dom";
import {
FindSceneMarkersQueryResult
} from "src/core/generated-graphql";
import { FindSceneMarkersQueryResult } from "src/core/generated-graphql";
import { StashService } from "src/core/StashService";
import { NavUtils } from "src/utils";
import { useSceneMarkersList } from "src/hooks";

View File

@@ -1,8 +1,8 @@
import ApolloClient from "apollo-client";
import { WebSocketLink } from "apollo-link-ws";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from 'apollo-link-http';
import { split } from 'apollo-link';
import { HttpLink } from "apollo-link-http";
import { split } from "apollo-link";
import { getMainDefinition } from "apollo-utilities";
import { ListFilterModel } from "../models/list-filter/filter";
import * as GQL from "./generated-graphql";
@@ -45,7 +45,10 @@ export class StashService {
const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === "OperationDefinition" && definition.operation === "subscription";
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
@@ -267,7 +270,9 @@ export class StashService {
return GQL.useAllStudiosForFilterQuery();
}
public static useValidGalleriesForScene(sceneId: string) {
return GQL.useValidGalleriesForSceneQuery({ variables: { scene_id: sceneId } });
return GQL.useValidGalleriesForSceneQuery({
variables: { scene_id: sceneId }
});
}
public static useStats() {
return GQL.useStatsQuery();
@@ -275,8 +280,12 @@ export class StashService {
public static useVersion() {
return GQL.useVersionQuery();
}
public static useLatestVersion() { return GQL.useLatestVersionQuery({ notifyOnNetworkStatusChange: true, errorPolicy: 'ignore' }); }
public static useLatestVersion() {
return GQL.useLatestVersionQuery({
notifyOnNetworkStatusChange: true,
errorPolicy: "ignore"
});
}
public static useConfiguration() {
return GQL.useConfigurationQuery();

File diff suppressed because it is too large Load Diff

View File

@@ -77,7 +77,7 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
const [filter, setFilter] = useState<ListFilterModel>(
new ListFilterModel(
options.filterMode,
options.subComponent ? '' : queryString.parse(history.location.search)
options.subComponent ? "" : queryString.parse(history.location.search)
)
);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
@@ -318,8 +318,10 @@ export const useScenesList = (props: IListHookOptions<FindScenesQueryResult>) =>
...props,
filterMode: FilterMode.Scenes,
useData: StashService.useFindScenes,
getData: (result: FindScenesQueryResult) => result?.data?.findScenes?.scenes ?? [],
getCount: (result: FindScenesQueryResult) => result?.data?.findScenes?.count ?? 0
getData: (result: FindScenesQueryResult) =>
result?.data?.findScenes?.scenes ?? [],
getCount: (result: FindScenesQueryResult) =>
result?.data?.findScenes?.count ?? 0
});
export const useSceneMarkersList = (
@@ -335,7 +337,9 @@ export const useSceneMarkersList = (
result?.data?.findSceneMarkers?.count ?? 0
});
export const useGalleriesList = (props: IListHookOptions<FindGalleriesQueryResult>) =>
export const useGalleriesList = (
props: IListHookOptions<FindGalleriesQueryResult>
) =>
useList<FindGalleriesQueryResult, GalleryDataFragment>({
...props,
filterMode: FilterMode.Galleries,
@@ -346,16 +350,22 @@ export const useGalleriesList = (props: IListHookOptions<FindGalleriesQueryResul
result?.data?.findGalleries?.count ?? 0
});
export const useStudiosList = (props: IListHookOptions<FindStudiosQueryResult>) =>
export const useStudiosList = (
props: IListHookOptions<FindStudiosQueryResult>
) =>
useList<FindStudiosQueryResult, StudioDataFragment>({
...props,
filterMode: FilterMode.Studios,
useData: StashService.useFindStudios,
getData: (result: FindStudiosQueryResult) => result?.data?.findStudios?.studios ?? [],
getCount: (result: FindStudiosQueryResult) => result?.data?.findStudios?.count ?? 0
getData: (result: FindStudiosQueryResult) =>
result?.data?.findStudios?.studios ?? [],
getCount: (result: FindStudiosQueryResult) =>
result?.data?.findStudios?.count ?? 0
});
export const usePerformersList = (props: IListHookOptions<FindPerformersQueryResult>) =>
export const usePerformersList = (
props: IListHookOptions<FindPerformersQueryResult>
) =>
useList<FindPerformersQueryResult, PerformerDataFragment>({
...props,
filterMode: FilterMode.Performers,

View File

@@ -1,7 +1,7 @@
/* eslint-disable consistent-return */
import { CriterionModifier } from "src/core/generated-graphql";
import { DurationUtils } from 'src/utils';
import { DurationUtils } from "src/utils";
import { ILabeledId, ILabeledValue } from "../types";
export type CriterionType =
@@ -38,7 +38,8 @@ export abstract class Criterion<Option = any, Value = any> {
return "Rating";
case "resolution":
return "Resolution";
case "duration": return "Duration";
case "duration":
return "Duration";
case "favorite":
return "Favorite";
case "hasMarkers":
@@ -149,7 +150,10 @@ export abstract class Criterion<Option = any, Value = any> {
let valueString = "";
if (this.modifier !== CriterionModifier.IsNull && this.modifier !== CriterionModifier.NotNull) {
if (
this.modifier !== CriterionModifier.IsNull &&
this.modifier !== CriterionModifier.NotNull
) {
valueString = this.getLabelValue();
}
@@ -268,7 +272,7 @@ export class DurationCriterion extends Criterion<number, number> {
Criterion.getModifierOption(CriterionModifier.Equals),
Criterion.getModifierOption(CriterionModifier.NotEquals),
Criterion.getModifierOption(CriterionModifier.GreaterThan),
Criterion.getModifierOption(CriterionModifier.LessThan),
Criterion.getModifierOption(CriterionModifier.LessThan)
];
public options: number[] | undefined;
public value: number = 0;

View File

@@ -2,10 +2,7 @@ import * as GQL from "src/core/generated-graphql";
import { ILabeledId } from "../types";
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
export class TagsCriterion extends Criterion<
GQL.Tag,
ILabeledId[]
> {
export class TagsCriterion extends Criterion<GQL.Tag, ILabeledId[]> {
public type: CriterionType;
public parameterName: string;
public modifier = GQL.CriterionModifier.IncludesAll;

View File

@@ -25,7 +25,8 @@ export function makeCriteria(type: CriterionType = "none") {
return new RatingCriterion();
case "resolution":
return new ResolutionCriterion();
case "duration": return new DurationCriterion(type, type);
case "duration":
return new DurationCriterion(type, type);
case "favorite":
return new FavoriteCriterion();
case "hasMarkers":
@@ -41,7 +42,8 @@ export function makeCriteria(type: CriterionType = "none") {
case "studios":
return new StudiosCriterion();
case "birth_year": return new NumberCriterion(type, type);
case "birth_year":
return new NumberCriterion(type, type);
case "age": {
const ret = new NumberCriterion(type, type);
// null/not null doesn't make sense for these criteria

View File

@@ -299,7 +299,10 @@ export class ListFilterModel {
}
case "duration": {
const durationCrit = criterion as DurationCriterion;
result.duration = { value: durationCrit.value, modifier: durationCrit.modifier }
result.duration = {
value: durationCrit.value,
modifier: durationCrit.modifier
};
break;
}
case "hasMarkers":

View File

@@ -12,7 +12,7 @@ const secondsToString = (seconds : number) => {
}
return ret;
}
};
const stringToSeconds = (v: string) => {
if (!v) {
@@ -43,7 +43,7 @@ const stringToSeconds = (v : string) => {
}
return seconds;
}
};
export default {
secondsToString,

View File

@@ -2,4 +2,4 @@ export { default as ImageUtils } from "./image";
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 DurationUtils } from "./duration";

View File

@@ -55,8 +55,8 @@ const renderInputGroup = (options: {
placeholder?: string;
value: string | undefined;
isEditing: boolean;
asURL?: boolean,
urlPrefix?: string,
asURL?: boolean;
urlPrefix?: string;
onChange: (value: string) => void;
}) => (
<tr>