mirror of
https://github.com/stashapp/stash.git
synced 2025-12-10 02:15:30 +01:00
Upgrade javascript libraries (#516)
* Bump react-bootstrap * Bump library versions and clean up hooks * Bump intl libraries * Fix image pasting
This commit is contained in:
parent
99f88b8d73
commit
df2d2c2d09
50 changed files with 3192 additions and 2797 deletions
|
|
@ -25,79 +25,77 @@
|
|||
"not op_mini all"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apollo/react-hooks": "^3.1.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.26",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.12.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.8",
|
||||
"@apollo/react-hooks": "^3.1.5",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.9",
|
||||
"apollo-cache": "^1.3.4",
|
||||
"apollo-cache-inmemory": "^1.6.5",
|
||||
"apollo-client": "^2.6.8",
|
||||
"apollo-link": "^1.2.13",
|
||||
"apollo-link-http": "^1.5.16",
|
||||
"apollo-link-ws": "^1.0.19",
|
||||
"apollo-link": "^1.2.14",
|
||||
"apollo-link-error": "^1.1.13",
|
||||
"apollo-link-http": "^1.5.17",
|
||||
"apollo-link-ws": "^1.0.20",
|
||||
"apollo-utilities": "^1.3.3",
|
||||
"axios": "0.18.1",
|
||||
"axios": "0.19.2",
|
||||
"bootstrap": "^4.4.1",
|
||||
"classnames": "^2.2.6",
|
||||
"flag-icon-css": "^3.4.6",
|
||||
"formik": "^2.1.2",
|
||||
"formik": "^2.1.4",
|
||||
"graphql": "^14.5.8",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"graphql-tag": "^2.10.3",
|
||||
"i18n-iso-countries": "^5.2.0",
|
||||
"localforage": "1.7.3",
|
||||
"lodash": "^4.17.15",
|
||||
"query-string": "6.10.1",
|
||||
"react": "~16.12.0",
|
||||
"react-apollo": "^3.1.3",
|
||||
"react-bootstrap": "^1.0.0-beta.16",
|
||||
"react-dom": "16.12.0",
|
||||
"query-string": "6.12.1",
|
||||
"react": "16.13.1",
|
||||
"react-apollo": "^3.1.5",
|
||||
"react-bootstrap": "1.0.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-images": "0.5.19",
|
||||
"react-intl": "^3.12.0",
|
||||
"react-jw-player": "1.19.0",
|
||||
"react-intl": "^4.5.1",
|
||||
"react-jw-player": "1.19.1",
|
||||
"react-photo-gallery": "^8.0.0",
|
||||
"react-router-bootstrap": "^0.25.0",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-select": "^3.0.8",
|
||||
"react-select": "^3.1.0",
|
||||
"subscriptions-transport-ws": "^0.9.16",
|
||||
"universal-cookie": "^4.0.3",
|
||||
"video.js": "^7.6.0"
|
||||
"universal-cookie": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.11.2",
|
||||
"@graphql-codegen/cli": "^1.11.2",
|
||||
"@graphql-codegen/time": "^1.11.2",
|
||||
"@graphql-codegen/typescript": "^1.11.2",
|
||||
"@graphql-codegen/typescript-compatibility": "^1.11.2",
|
||||
"@graphql-codegen/typescript-operations": "^1.11.2",
|
||||
"@graphql-codegen/typescript-react-apollo": "^1.11.2",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/jest": "24.0.13",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/node": "13.1.8",
|
||||
"@types/react": "16.9.19",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@graphql-codegen/add": "^1.13.5",
|
||||
"@graphql-codegen/cli": "^1.13.5",
|
||||
"@graphql-codegen/time": "^1.13.5",
|
||||
"@graphql-codegen/typescript": "^1.13.5",
|
||||
"@graphql-codegen/typescript-compatibility": "^1.13.5",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.5",
|
||||
"@graphql-codegen/typescript-react-apollo": "^1.13.5",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/lodash": "^4.14.150",
|
||||
"@types/node": "13.13.4",
|
||||
"@types/react": "16.9.34",
|
||||
"@types/react-dom": "^16.9.7",
|
||||
"@types/react-images": "^0.5.1",
|
||||
"@types/react-router-bootstrap": "^0.24.5",
|
||||
"@types/react-router-dom": "5.1.3",
|
||||
"@types/react-select": "^3.0.8",
|
||||
"@types/video.js": "^7.2.11",
|
||||
"@typescript-eslint/eslint-plugin": "^2.16.0",
|
||||
"@typescript-eslint/parser": "^2.16.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-config-airbnb-typescript": "^6.3.1",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-plugin-import": "^2.20.0",
|
||||
"@types/react-router-dom": "5.1.5",
|
||||
"@types/react-select": "^3.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^2.30.0",
|
||||
"@typescript-eslint/parser": "^2.30.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-typescript": "^7.2.1",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-react": "^7.18.0",
|
||||
"eslint-plugin-react-hooks": "^1.7.0",
|
||||
"extract-react-intl-messages": "^2.3.5",
|
||||
"node-sass": "4.13.1",
|
||||
"postcss-safe-parser": "^4.0.1",
|
||||
"prettier": "2.0.2",
|
||||
"react-scripts": "^3.3.1",
|
||||
"stylelint": "^13.0.0",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"extract-react-intl-messages": "^4.1.1",
|
||||
"node-sass": "4.14.0",
|
||||
"postcss-safe-parser": "^4.0.2",
|
||||
"prettier": "2.0.5",
|
||||
"react-scripts": "^3.4.1",
|
||||
"stylelint": "^13.3.3",
|
||||
"stylelint-config-prettier": "^8.0.1",
|
||||
"stylelint-order": "^4.0.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { library } from "@fortawesome/fontawesome-svg-core";
|
|||
import { fas } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import locales from "src/locale";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useConfiguration } from "src/core/StashService";
|
||||
import { flattenMessages } from "src/utils";
|
||||
import { ErrorBoundary } from "./components/ErrorBoundary";
|
||||
import Galleries from "./components/Galleries/Galleries";
|
||||
|
|
@ -25,7 +25,7 @@ import Movies from "./components/Movies/Movies";
|
|||
library.add(fas);
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const config = StashService.useConfiguration();
|
||||
const config = useConfiguration();
|
||||
const language = config.data?.configuration?.interface?.language ?? "en-US";
|
||||
const messageLanguage = language.slice(0, 2);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useFindGallery } from "src/core/StashService";
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { GalleryViewer } from "./GalleryViewer";
|
||||
|
||||
export const Gallery: React.FC = () => {
|
||||
const { id = "" } = useParams();
|
||||
|
||||
const { data, error, loading } = StashService.useFindGallery(id);
|
||||
const { data, error, loading } = useFindGallery(id);
|
||||
const gallery = data?.findGallery;
|
||||
|
||||
if (loading || !gallery) return <LoadingIndicator />;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { debounce } from "lodash";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { SortDirectionEnum } from "src/core/generated-graphql";
|
||||
import {
|
||||
Badge,
|
||||
|
|
@ -9,7 +9,7 @@ import {
|
|||
Form,
|
||||
OverlayTrigger,
|
||||
Tooltip,
|
||||
SafeAnchor,
|
||||
SafeAnchorProps,
|
||||
} from "react-bootstrap";
|
||||
|
||||
import { Icon } from "src/components/Shared";
|
||||
|
|
@ -45,18 +45,15 @@ const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"];
|
|||
export const ListFilter: React.FC<IListFilterProps> = (
|
||||
props: IListFilterProps
|
||||
) => {
|
||||
const searchCallback = useCallback(
|
||||
debounce((value: string) => {
|
||||
props.onChangeQuery(value);
|
||||
}, 500),
|
||||
[props.onChangeQuery]
|
||||
);
|
||||
const searchCallback = debounce((value: string) => {
|
||||
props.onChangeQuery(value);
|
||||
}, 500);
|
||||
|
||||
const [editingCriterion, setEditingCriterion] = useState<
|
||||
Criterion | undefined
|
||||
>(undefined);
|
||||
|
||||
function onChangePageSize(event: React.FormEvent<HTMLSelectElement>) {
|
||||
function onChangePageSize(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||
const val = event.currentTarget.value;
|
||||
props.onChangePageSize(parseInt(val, 10));
|
||||
}
|
||||
|
|
@ -73,8 +70,8 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||
}
|
||||
}
|
||||
|
||||
function onChangeSortBy(event: React.MouseEvent<SafeAnchor>) {
|
||||
const target = (event.currentTarget as unknown) as HTMLAnchorElement;
|
||||
function onChangeSortBy(event: React.MouseEvent<SafeAnchorProps>) {
|
||||
const target = event.currentTarget as HTMLAnchorElement;
|
||||
props.onChangeSortBy(target.text);
|
||||
}
|
||||
|
||||
|
|
@ -266,7 +263,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||
min={0}
|
||||
max={3}
|
||||
defaultValue={1}
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChangeZoom(Number.parseInt(e.currentTarget.value, 10))
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,54 +1,90 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import {
|
||||
defineMessages,
|
||||
FormattedMessage,
|
||||
MessageDescriptor,
|
||||
useIntl,
|
||||
} from "react-intl";
|
||||
import { Nav, Navbar, Button } from "react-bootstrap";
|
||||
import { IconName } from "@fortawesome/fontawesome-svg-core";
|
||||
import { LinkContainer } from "react-router-bootstrap";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { Link, NavLink, useLocation } from "react-router-dom";
|
||||
import { SessionUtils } from "src/utils";
|
||||
|
||||
import { Icon } from "src/components/Shared";
|
||||
|
||||
interface IMenuItem {
|
||||
messageID: string;
|
||||
message: MessageDescriptor;
|
||||
href: string;
|
||||
icon: IconName;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
scenes: {
|
||||
id: "scenes",
|
||||
defaultMessage: "Scenes",
|
||||
},
|
||||
movies: {
|
||||
id: "movies",
|
||||
defaultMessage: "Movies",
|
||||
},
|
||||
markers: {
|
||||
id: "markers",
|
||||
defaultMessage: "Markers",
|
||||
},
|
||||
performers: {
|
||||
id: "performers",
|
||||
defaultMessage: "Performers",
|
||||
},
|
||||
studios: {
|
||||
id: "studios",
|
||||
defaultMessage: "Studios",
|
||||
},
|
||||
tags: {
|
||||
id: "tags",
|
||||
defaultMessage: "Tags",
|
||||
},
|
||||
galleries: {
|
||||
id: "galleries",
|
||||
defaultMessage: "Galleries",
|
||||
},
|
||||
});
|
||||
|
||||
const menuItems: IMenuItem[] = [
|
||||
{
|
||||
icon: "play-circle",
|
||||
messageID: "scenes",
|
||||
message: messages.scenes,
|
||||
href: "/scenes",
|
||||
},
|
||||
{
|
||||
href: "/movies",
|
||||
icon: "film",
|
||||
messageID: "movies",
|
||||
message: messages.movies,
|
||||
},
|
||||
{
|
||||
href: "/scenes/markers",
|
||||
icon: "map-marker-alt",
|
||||
messageID: "markers",
|
||||
message: messages.markers,
|
||||
},
|
||||
{
|
||||
href: "/galleries",
|
||||
icon: "image",
|
||||
messageID: "galleries",
|
||||
message: messages.galleries,
|
||||
},
|
||||
{
|
||||
href: "/performers",
|
||||
icon: "user",
|
||||
messageID: "performers",
|
||||
message: messages.performers,
|
||||
},
|
||||
{
|
||||
href: "/studios",
|
||||
icon: "video",
|
||||
messageID: "studios",
|
||||
message: messages.studios,
|
||||
},
|
||||
{
|
||||
href: "/tags",
|
||||
icon: "tag",
|
||||
messageID: "tags",
|
||||
message: messages.tags,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -58,6 +94,7 @@ export const MainNavbar: React.FC = () => {
|
|||
// react-bootstrap typing bug
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const navbarRef = useRef<any>();
|
||||
const intl = useIntl();
|
||||
|
||||
const maybeCollapse = (event: Event) => {
|
||||
if (
|
||||
|
|
@ -92,11 +129,11 @@ export const MainNavbar: React.FC = () => {
|
|||
path === null ? (
|
||||
""
|
||||
) : (
|
||||
<LinkContainer to={path}>
|
||||
<Link to={path}>
|
||||
<Button variant="primary">
|
||||
<FormattedMessage id="new" defaultMessage="New" />
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
</Link>
|
||||
);
|
||||
|
||||
function maybeRenderLogout() {
|
||||
|
|
@ -140,17 +177,10 @@ export const MainNavbar: React.FC = () => {
|
|||
<Nav className="mr-md-auto">
|
||||
{menuItems.map((i) => (
|
||||
<Nav.Link eventKey={i.href} as="div" key={i.href}>
|
||||
<LinkContainer
|
||||
activeClassName="active"
|
||||
exact
|
||||
to={i.href}
|
||||
key={i.href}
|
||||
>
|
||||
<LinkContainer activeClassName="active" exact to={i.href}>
|
||||
<Button className="minimal w-100">
|
||||
<Icon icon={i.icon} />
|
||||
<span>
|
||||
<FormattedMessage id={i.messageID} />
|
||||
</span>
|
||||
<span>{intl.formatMessage(i.message)}</span>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
</Nav.Link>
|
||||
|
|
@ -159,11 +189,11 @@ export const MainNavbar: React.FC = () => {
|
|||
</Navbar.Collapse>
|
||||
<Nav className="order-2 flex-row">
|
||||
<div className="d-none d-sm-block">{newButton}</div>
|
||||
<LinkContainer exact to="/settings" onClick={() => setExpanded(false)}>
|
||||
<NavLink exact to="/settings" onClick={() => setExpanded(false)}>
|
||||
<Button className="minimal settings-button">
|
||||
<Icon icon="cog" />
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
</NavLink>
|
||||
{maybeRenderLogout()}
|
||||
</Nav>
|
||||
</Navbar>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
/* eslint-disable react/no-this-in-sfc */
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
useFindMovie,
|
||||
useMovieUpdate,
|
||||
useMovieCreate,
|
||||
useMovieDestroy,
|
||||
} from "src/core/StashService";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import cx from "classnames";
|
||||
import {
|
||||
|
|
@ -53,14 +58,10 @@ export const Movie: React.FC = () => {
|
|||
);
|
||||
|
||||
// Network state
|
||||
const { data, error, loading } = StashService.useFindMovie(id);
|
||||
const [updateMovie] = StashService.useMovieUpdate(
|
||||
getMovieInput() as GQL.MovieUpdateInput
|
||||
);
|
||||
const [createMovie] = StashService.useMovieCreate(
|
||||
getMovieInput() as GQL.MovieCreateInput
|
||||
);
|
||||
const [deleteMovie] = StashService.useMovieDestroy(
|
||||
const { data, error, loading } = useFindMovie(id);
|
||||
const [updateMovie] = useMovieUpdate(getMovieInput() as GQL.MovieUpdateInput);
|
||||
const [createMovie] = useMovieCreate(getMovieInput() as GQL.MovieCreateInput);
|
||||
const [deleteMovie] = useMovieDestroy(
|
||||
getMovieInput() as GQL.MovieDestroyInput
|
||||
);
|
||||
|
||||
|
|
@ -104,8 +105,8 @@ export const Movie: React.FC = () => {
|
|||
setBackImage(this.result as string);
|
||||
}
|
||||
|
||||
ImageUtils.usePasteImage(onImageLoad);
|
||||
ImageUtils.usePasteImage(onBackImageLoad);
|
||||
ImageUtils.usePasteImage(onImageLoad, isEditing);
|
||||
ImageUtils.usePasteImage(onBackImageLoad, isEditing);
|
||||
|
||||
if (!isNew && !isEditing) {
|
||||
if (!data || !data.findMovie || loading) return <LoadingIndicator />;
|
||||
|
|
@ -280,7 +281,7 @@ export const Movie: React.FC = () => {
|
|||
as="textarea"
|
||||
readOnly={!isEditing}
|
||||
className="movie-synopsis text-input"
|
||||
onChange={(newValue: React.FormEvent<HTMLTextAreaElement>) =>
|
||||
onChange={(newValue: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
setSynopsis(newValue.currentTarget.value)
|
||||
}
|
||||
value={synopsis}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ import { Button, Tabs, Tab } from "react-bootstrap";
|
|||
import { useParams, useHistory } from "react-router-dom";
|
||||
import cx from "classnames";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
useFindPerformer,
|
||||
usePerformerUpdate,
|
||||
usePerformerCreate,
|
||||
usePerformerDestroy,
|
||||
} from "src/core/StashService";
|
||||
import { Icon, LoadingIndicator } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import { getCountryISO, TextUtils } from "src/utils";
|
||||
|
|
@ -26,25 +31,22 @@ export const Performer: React.FC = () => {
|
|||
>({});
|
||||
const [imagePreview, setImagePreview] = useState<string>();
|
||||
const [lightboxIsOpen, setLightboxIsOpen] = useState(false);
|
||||
const activeImage = imagePreview ?? performer.image_path ?? "";
|
||||
|
||||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { data, error } = StashService.useFindPerformer(id);
|
||||
const [updatePerformer] = StashService.usePerformerUpdate();
|
||||
const [createPerformer] = StashService.usePerformerCreate();
|
||||
const [deletePerformer] = StashService.usePerformerDestroy();
|
||||
const { data, error } = useFindPerformer(id);
|
||||
const [updatePerformer] = usePerformerUpdate();
|
||||
const [createPerformer] = usePerformerCreate();
|
||||
const [deletePerformer] = usePerformerDestroy();
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(false);
|
||||
if (data?.findPerformer) setPerformer(data.findPerformer);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
setImagePreview(performer.image_path ?? undefined);
|
||||
}, [performer]);
|
||||
|
||||
function onImageChange(image: string) {
|
||||
function onImageChange(image?: string) {
|
||||
setImagePreview(image);
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +66,10 @@ export const Performer: React.FC = () => {
|
|||
const result = await updatePerformer({
|
||||
variables: performerInput as GQL.PerformerUpdateInput,
|
||||
});
|
||||
if (performerInput.image) {
|
||||
// Refetch image to bust browser cache
|
||||
await fetch(`/performer/${performer.id}/image`, { cache: "reload" });
|
||||
}
|
||||
if (result.data?.performerUpdate)
|
||||
setPerformer(result.data?.performerUpdate);
|
||||
} else {
|
||||
|
|
@ -105,9 +111,15 @@ export const Performer: React.FC = () => {
|
|||
return undefined;
|
||||
};
|
||||
|
||||
function renderTabs() {
|
||||
function renderEditPanel() {
|
||||
return (
|
||||
const renderTabs = () => (
|
||||
<Tabs defaultActiveKey="details" id="performer-details" unmountOnExit>
|
||||
<Tab eventKey="details" title="Details">
|
||||
<PerformerDetailsPanel performer={performer} isEditing={false} />
|
||||
</Tab>
|
||||
<Tab eventKey="scenes" title="Scenes">
|
||||
<PerformerScenesPanel performer={performer} />
|
||||
</Tab>
|
||||
<Tab eventKey="edit" title="Edit">
|
||||
<PerformerDetailsPanel
|
||||
performer={performer}
|
||||
isEditing
|
||||
|
|
@ -116,55 +128,33 @@ export const Performer: React.FC = () => {
|
|||
onSave={onSave}
|
||||
onImageChange={onImageChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// render tabs if not new
|
||||
if (!isNew) {
|
||||
return (
|
||||
<Tabs defaultActiveKey="details" id="performer-details">
|
||||
<Tab eventKey="details" title="Details">
|
||||
<PerformerDetailsPanel performer={performer} isEditing={false} />
|
||||
</Tab>
|
||||
<Tab eventKey="scenes" title="Scenes">
|
||||
<PerformerScenesPanel performer={performer} />
|
||||
</Tab>
|
||||
<Tab eventKey="edit" title="Edit">
|
||||
{renderEditPanel()}
|
||||
</Tab>
|
||||
<Tab eventKey="operations" title="Operations">
|
||||
<PerformerOperationsPanel performer={performer} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
return renderEditPanel();
|
||||
}
|
||||
</Tab>
|
||||
<Tab eventKey="operations" title="Operations">
|
||||
<PerformerOperationsPanel performer={performer} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
);
|
||||
|
||||
function maybeRenderAge() {
|
||||
if (performer && performer.birthdate) {
|
||||
if (performer?.birthdate) {
|
||||
// calculate the age from birthdate. In future, this should probably be
|
||||
// provided by the server
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<span className="age">{TextUtils.age(performer.birthdate)}</span>
|
||||
<span className="age-tail"> years old</span>
|
||||
</div>
|
||||
</>
|
||||
<div>
|
||||
<span className="age">{TextUtils.age(performer.birthdate)}</span>
|
||||
<span className="age-tail"> years old</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderAliases() {
|
||||
if (performer && performer.aliases) {
|
||||
if (performer?.aliases) {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<span className="alias-head">Also known as </span>
|
||||
<span className="alias">{performer.aliases}</span>
|
||||
</div>
|
||||
</>
|
||||
<div>
|
||||
<span className="alias-head">Also known as </span>
|
||||
<span className="alias">{performer.aliases}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -231,34 +221,36 @@ export const Performer: React.FC = () => {
|
|||
);
|
||||
|
||||
function renderPerformerImage() {
|
||||
if (imagePreview) {
|
||||
return <img className="photo" src={imagePreview} alt="Performer" />;
|
||||
if (activeImage) {
|
||||
return <img className="photo" src={activeImage} alt="Performer" />;
|
||||
}
|
||||
}
|
||||
|
||||
function renderNewView() {
|
||||
if (isNew)
|
||||
return (
|
||||
<div className="row new-view">
|
||||
<div className="col-4">{renderPerformerImage()}</div>
|
||||
<div className="col-6">
|
||||
<h2>Create Performer</h2>
|
||||
{renderTabs()}
|
||||
<PerformerDetailsPanel
|
||||
performer={performer}
|
||||
isEditing
|
||||
isNew={isNew}
|
||||
onDelete={onDelete}
|
||||
onSave={onSave}
|
||||
onImageChange={onImageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const photos = [{ src: imagePreview || "", caption: "Image" }];
|
||||
|
||||
if (isNew) {
|
||||
return renderNewView();
|
||||
}
|
||||
const photos = [{ src: activeImage, caption: "Image" }];
|
||||
|
||||
return (
|
||||
<div id="performer-page" className="row">
|
||||
<div className="image-container col-sm-4 offset-sm-1 d-none d-sm-block">
|
||||
<Button variant="link" onClick={() => setLightboxIsOpen(true)}>
|
||||
<img className="performer" src={imagePreview} alt="Performer" />
|
||||
<img className="performer" src={activeImage} alt="Performer" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="col col-sm-6">
|
||||
|
|
@ -282,7 +274,7 @@ export const Performer: React.FC = () => {
|
|||
onClose={() => setLightboxIsOpen(false)}
|
||||
currentImage={0}
|
||||
isOpen={lightboxIsOpen}
|
||||
onClickImage={() => window.open(imagePreview, "_blank")}
|
||||
onClickImage={() => window.open(activeImage, "_blank")}
|
||||
width={9999}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,14 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Popover, OverlayTrigger, Table } from "react-bootstrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
getGenderStrings,
|
||||
useListPerformerScrapers,
|
||||
genderToString,
|
||||
stringToGender,
|
||||
queryScrapePerformer,
|
||||
queryScrapePerformerURL,
|
||||
} from "src/core/StashService";
|
||||
import {
|
||||
Icon,
|
||||
Modal,
|
||||
|
|
@ -29,7 +36,7 @@ interface IPerformerDetails {
|
|||
| Partial<GQL.PerformerUpdateInput>
|
||||
) => void;
|
||||
onDelete?: () => void;
|
||||
onImageChange?: (image: string) => void;
|
||||
onImageChange?: (image?: string) => void;
|
||||
}
|
||||
|
||||
export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
||||
|
|
@ -74,9 +81,11 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const Scrapers = StashService.useListPerformerScrapers();
|
||||
const Scrapers = useListPerformerScrapers();
|
||||
const [queryableScrapers, setQueryableScrapers] = useState<GQL.Scraper[]>([]);
|
||||
|
||||
ImageUtils.usePasteImage(onImageLoad, isEditing);
|
||||
|
||||
function updatePerformerEditState(
|
||||
state: Partial<GQL.PerformerDataFragment | GQL.ScrapedPerformerDataFragment>
|
||||
) {
|
||||
|
|
@ -99,9 +108,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
setTwitter(state.twitter ?? undefined);
|
||||
setInstagram(state.instagram ?? undefined);
|
||||
setGender(
|
||||
StashService.genderToString(
|
||||
(state as GQL.PerformerDataFragment).gender ?? undefined
|
||||
)
|
||||
genderToString((state as GQL.PerformerDataFragment).gender ?? undefined)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -114,16 +121,16 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
|
||||
// try to translate from enum values first
|
||||
const upperGender = scrapedGender?.toUpperCase();
|
||||
const asEnum = StashService.genderToString(upperGender as GQL.GenderEnum);
|
||||
const asEnum = genderToString(upperGender as GQL.GenderEnum);
|
||||
if (asEnum) {
|
||||
retEnum = StashService.stringToGender(asEnum);
|
||||
retEnum = stringToGender(asEnum);
|
||||
} else {
|
||||
// try to match against gender strings
|
||||
const caseInsensitive = true;
|
||||
retEnum = StashService.stringToGender(scrapedGender, caseInsensitive);
|
||||
retEnum = stringToGender(scrapedGender, caseInsensitive);
|
||||
}
|
||||
|
||||
return StashService.genderToString(retEnum);
|
||||
return genderToString(retEnum);
|
||||
}
|
||||
|
||||
function updatePerformerEditStateFromScraper(
|
||||
|
|
@ -142,25 +149,24 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
) {
|
||||
const imageStr = (state as GQL.ScrapedPerformerDataFragment).image;
|
||||
setImage(imageStr ?? undefined);
|
||||
if (onImageChange) {
|
||||
onImageChange(imageStr!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onImageLoad(this: FileReader) {
|
||||
setImage(this.result as string);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setImage(undefined);
|
||||
updatePerformerEditState(performer);
|
||||
}, [performer]);
|
||||
|
||||
function onImageLoad(this: FileReader) {
|
||||
setImage(this.result as string);
|
||||
useEffect(() => {
|
||||
if (onImageChange) {
|
||||
onImageChange(this.result as string);
|
||||
onImageChange(image);
|
||||
}
|
||||
}
|
||||
|
||||
if (isEditing) ImageUtils.usePasteImage(onImageLoad);
|
||||
return () => onImageChange?.();
|
||||
}, [image, onImageChange]);
|
||||
|
||||
useEffect(() => {
|
||||
const newQueryableScrapers = (
|
||||
|
|
@ -195,7 +201,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
twitter,
|
||||
instagram,
|
||||
image,
|
||||
gender: StashService.stringToGender(gender),
|
||||
gender: stringToGender(gender),
|
||||
};
|
||||
|
||||
if (!isNew) {
|
||||
|
|
@ -225,7 +231,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
try {
|
||||
if (!scrapePerformerDetails || !isDisplayingScraperDialog) return;
|
||||
setIsLoading(true);
|
||||
const result = await StashService.queryScrapePerformer(
|
||||
const result = await queryScrapePerformer(
|
||||
isDisplayingScraperDialog.id,
|
||||
getQueryScraperPerformerInput()
|
||||
);
|
||||
|
|
@ -242,7 +248,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
if (!url) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await StashService.queryScrapePerformerURL(url);
|
||||
const result = await queryScrapePerformerURL(url);
|
||||
if (!result.data || !result.data.scrapePerformerURL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -442,7 +448,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
value: gender,
|
||||
isEditing: !!isEditing,
|
||||
onChange: (value: string) => setGender(value),
|
||||
selectOptions: [""].concat(StashService.getGenderStrings()),
|
||||
selectOptions: [""].concat(getGenderStrings()),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from "react-bootstrap";
|
||||
import React from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { mutateMetadataAutoTag } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
|
||||
interface IPerformerOperationsProps {
|
||||
|
|
@ -18,7 +18,7 @@ export const PerformerOperationsPanel: React.FC<IPerformerOperationsProps> = ({
|
|||
return;
|
||||
}
|
||||
try {
|
||||
await StashService.mutateMetadataAutoTag({ performers: [performer.id] });
|
||||
await mutateMetadataAutoTag({ performers: [performer.id] });
|
||||
Toast.success({ content: "Started auto tagging" });
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import _ from "lodash";
|
|||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { FindPerformersQueryResult } from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { queryFindPerformers } from "src/core/StashService";
|
||||
import { usePerformersList } from "src/hooks";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { DisplayMode } from "src/models/list-filter/types";
|
||||
|
|
@ -33,7 +33,7 @@ export const PerformerList: React.FC = () => {
|
|||
const filterCopy = _.cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await StashService.queryFindPerformers(filterCopy);
|
||||
const singleResult = await queryFindPerformers(filterCopy);
|
||||
if (
|
||||
singleResult &&
|
||||
singleResult.data &&
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||
<Form.Control
|
||||
className="text-input"
|
||||
id="filename-pattern"
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setPattern(e.currentTarget.value)
|
||||
}
|
||||
value={pattern}
|
||||
|
|
@ -162,7 +162,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||
<InputGroup className="col-8">
|
||||
<Form.Control
|
||||
className="text-input"
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setIgnoreWords(e.currentTarget.value)
|
||||
}
|
||||
value={ignoreWords}
|
||||
|
|
@ -181,7 +181,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||
<InputGroup className="col-8">
|
||||
<Form.Control
|
||||
className="text-input"
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setWhitespaceCharacters(e.currentTarget.value)
|
||||
}
|
||||
value={whitespaceCharacters}
|
||||
|
|
@ -236,8 +236,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||
</Button>
|
||||
<Form.Control
|
||||
as="select"
|
||||
options={PAGE_SIZE_OPTIONS}
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onPageSizeChanged(parseInt(e.currentTarget.value, 10))
|
||||
}
|
||||
defaultValue={props.input.pageSize}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
import React, { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { Button, Card, Form, Table } from "react-bootstrap";
|
||||
import _ from "lodash";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
queryParseSceneFilenames,
|
||||
useScenesUpdate,
|
||||
} from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
|
|
@ -56,7 +59,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [updateScenes] = StashService.useScenesUpdate(getScenesUpdateData());
|
||||
const [updateScenes] = useScenesUpdate(getScenesUpdateData());
|
||||
|
||||
useEffect(() => {
|
||||
prevParserInputRef.current = parserInput;
|
||||
|
|
@ -124,7 +127,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||
capitalizeTitle: parserInput.capitalizeTitle,
|
||||
};
|
||||
|
||||
StashService.queryParseSceneFilenames(parserFilter, parserInputData)
|
||||
queryParseSceneFilenames(parserFilter, parserInputData)
|
||||
.then((response) => {
|
||||
const result = response.data.parseSceneFilenames;
|
||||
if (result) {
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ function SceneParserStringField(props: ISceneParserFieldProps<string>) {
|
|||
readOnly={!props.parserResult.isSet}
|
||||
className={props.className}
|
||||
value={props.parserResult.value || ""}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
maybeValueChanged(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
|
|
@ -177,7 +177,7 @@ function SceneParserRatingField(
|
|||
className={props.className}
|
||||
disabled={!props.parserResult.isSet}
|
||||
value={props.parserResult.value?.toString()}
|
||||
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
maybeValueChanged(
|
||||
event.currentTarget.value === ""
|
||||
? undefined
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import ReactJWPlayer from "react-jw-player";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useConfiguration } from "src/core/StashService";
|
||||
import { JWUtils } from "src/utils";
|
||||
import { ScenePlayerScrubber } from "./ScenePlayerScrubber";
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ export class ScenePlayerImpl extends React.Component<
|
|||
export const ScenePlayer: React.FC<IScenePlayerProps> = (
|
||||
props: IScenePlayerProps
|
||||
) => {
|
||||
const config = StashService.useConfiguration();
|
||||
const config = useConfiguration();
|
||||
|
||||
return (
|
||||
<ScenePlayerImpl
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { Button, ButtonGroup, Card, Form } from "react-bootstrap";
|
|||
import { Link } from "react-router-dom";
|
||||
import cx from "classnames";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { VideoHoverHook } from "src/hooks";
|
||||
import { useConfiguration } from "src/core/StashService";
|
||||
import { useVideoHover } from "src/hooks";
|
||||
import { Icon, TagLink, HoverPopover, SweatDrops } from "src/components/Shared";
|
||||
import { TextUtils } from "src/utils";
|
||||
|
||||
|
|
@ -19,11 +19,11 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
props: ISceneCardProps
|
||||
) => {
|
||||
const [previewPath, setPreviewPath] = useState<string>();
|
||||
const videoHoverHook = VideoHoverHook.useVideoHover({
|
||||
const hoverHandler = useVideoHover({
|
||||
resetOnMouseLeave: false,
|
||||
});
|
||||
|
||||
const config = StashService.useConfiguration();
|
||||
const config = useConfiguration();
|
||||
const showStudioAsText =
|
||||
config?.data?.configuration.interface.showStudioAsText ?? false;
|
||||
|
||||
|
|
@ -219,10 +219,10 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
if (!previewPath || previewPath === "") {
|
||||
setPreviewPath(props.scene.paths.preview || "");
|
||||
}
|
||||
VideoHoverHook.onMouseEnter(videoHoverHook);
|
||||
hoverHandler.onMouseEnter();
|
||||
}
|
||||
function onMouseLeave() {
|
||||
VideoHoverHook.onMouseLeave(videoHoverHook);
|
||||
hoverHandler.onMouseLeave();
|
||||
setPreviewPath("");
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +260,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
loop
|
||||
className={cx("scene-card-video", { portrait: isPortrait() })}
|
||||
poster={props.scene.paths.screenshot || ""}
|
||||
ref={videoHoverHook.videoEl}
|
||||
ref={hoverHandler.videoEl}
|
||||
>
|
||||
{previewPath ? <source src={previewPath} /> : ""}
|
||||
</video>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@ import queryString from "query-string";
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useParams, useLocation, useHistory } from "react-router-dom";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
useFindScene,
|
||||
useSceneIncrementO,
|
||||
useSceneDecrementO,
|
||||
useSceneResetO,
|
||||
} from "src/core/StashService";
|
||||
import { GalleryViewer } from "src/components/Galleries/GalleryViewer";
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
|
|
@ -24,11 +29,11 @@ export const Scene: React.FC = () => {
|
|||
const Toast = useToast();
|
||||
const [timestamp, setTimestamp] = useState<number>(getInitialTimestamp());
|
||||
const [scene, setScene] = useState<GQL.SceneDataFragment | undefined>();
|
||||
const { data, error, loading } = StashService.useFindScene(id);
|
||||
const { data, error, loading } = useFindScene(id);
|
||||
const [oLoading, setOLoading] = useState(false);
|
||||
const [incrementO] = StashService.useSceneIncrementO(scene?.id ?? "0");
|
||||
const [decrementO] = StashService.useSceneDecrementO(scene?.id ?? "0");
|
||||
const [resetO] = StashService.useSceneResetO(scene?.id ?? "0");
|
||||
const [incrementO] = useSceneIncrementO(scene?.id ?? "0");
|
||||
const [decrementO] = useSceneDecrementO(scene?.id ?? "0");
|
||||
const [resetO] = useSceneResetO(scene?.id ?? "0");
|
||||
|
||||
const queryParams = queryString.parse(location.search);
|
||||
const autoplay = queryParams?.autoplay === "true";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Dropdown, DropdownButton, Form, Table } from "react-bootstrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
queryScrapeScene,
|
||||
queryScrapeSceneURL,
|
||||
useListSceneScrapers,
|
||||
useSceneUpdate,
|
||||
useSceneDestroy,
|
||||
} from "src/core/StashService";
|
||||
import {
|
||||
PerformerSelect,
|
||||
TagSelect,
|
||||
|
|
@ -42,7 +48,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||
const [tagIds, setTagIds] = useState<string[]>();
|
||||
const [coverImage, setCoverImage] = useState<string>();
|
||||
|
||||
const Scrapers = StashService.useListSceneScrapers();
|
||||
const Scrapers = useListSceneScrapers();
|
||||
const [queryableScrapers, setQueryableScrapers] = useState<GQL.Scraper[]>([]);
|
||||
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
||||
|
|
@ -54,8 +60,8 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [updateScene] = StashService.useSceneUpdate(getSceneInput());
|
||||
const [deleteScene] = StashService.useSceneDestroy(getSceneDeleteInput());
|
||||
const [updateScene] = useSceneUpdate(getSceneInput());
|
||||
const [deleteScene] = useSceneDestroy(getSceneDeleteInput());
|
||||
|
||||
useEffect(() => {
|
||||
const newQueryableScrapers = (
|
||||
|
|
@ -127,7 +133,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||
setIsLoading(false);
|
||||
}, [props.scene]);
|
||||
|
||||
ImageUtils.usePasteImage(onImageLoad);
|
||||
ImageUtils.usePasteImage(onImageLoad, true);
|
||||
|
||||
function getSceneInput(): GQL.SceneUpdateInput {
|
||||
return {
|
||||
|
|
@ -252,10 +258,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||
async function onScrapeClicked(scraper: GQL.Scraper) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await StashService.queryScrapeScene(
|
||||
scraper.id,
|
||||
getSceneInput()
|
||||
);
|
||||
const result = await queryScrapeScene(scraper.id, getSceneInput());
|
||||
if (!result.data || !result.data.scrapeScene) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -364,7 +367,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||
}
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await StashService.queryScrapeSceneURL(url);
|
||||
const result = await queryScrapeSceneURL(url);
|
||||
if (!result.data || !result.data.scrapeSceneURL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -404,7 +407,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||
<td>URL</td>
|
||||
<td>
|
||||
<Form.Control
|
||||
onChange={(newValue: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(newValue: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setUrl(newValue.currentTarget.value)
|
||||
}
|
||||
value={url}
|
||||
|
|
@ -494,7 +497,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||
<Form.Control
|
||||
as="textarea"
|
||||
className="scene-description text-input"
|
||||
onChange={(newValue: React.FormEvent<HTMLTextAreaElement>) =>
|
||||
onChange={(newValue: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
setDetails(newValue.currentTarget.value)
|
||||
}
|
||||
value={details}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import React from "react";
|
|||
import { Button, Form } from "react-bootstrap";
|
||||
import { Field, FieldProps, Form as FormikForm, Formik } from "formik";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
useSceneMarkerCreate,
|
||||
useSceneMarkerUpdate,
|
||||
useSceneMarkerDestroy,
|
||||
} from "src/core/StashService";
|
||||
import {
|
||||
DurationInput,
|
||||
TagSelect,
|
||||
|
|
@ -29,9 +33,9 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({
|
|||
editingMarker,
|
||||
onClose,
|
||||
}) => {
|
||||
const [sceneMarkerCreate] = StashService.useSceneMarkerCreate();
|
||||
const [sceneMarkerUpdate] = StashService.useSceneMarkerUpdate();
|
||||
const [sceneMarkerDestroy] = StashService.useSceneMarkerDestroy();
|
||||
const [sceneMarkerCreate] = useSceneMarkerCreate();
|
||||
const [sceneMarkerUpdate] = useSceneMarkerUpdate();
|
||||
const [sceneMarkerDestroy] = useSceneMarkerDestroy();
|
||||
const Toast = useToast();
|
||||
|
||||
const onSubmit = (values: IFormFields) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useAllMoviesForFilter } from "src/core/StashService";
|
||||
import { Form } from "react-bootstrap";
|
||||
|
||||
type ValidTypes = GQL.SlimMovieDataFragment;
|
||||
|
|
@ -15,7 +15,7 @@ export interface IProps {
|
|||
export const SceneMovieTable: React.FunctionComponent<IProps> = (
|
||||
props: IProps
|
||||
) => {
|
||||
const { data } = StashService.useAllMoviesForFilter();
|
||||
const { data } = useAllMoviesForFilter();
|
||||
|
||||
const items = !!data && !!data.allMoviesSlim ? data.allMoviesSlim : [];
|
||||
let itemsFilter: ValidTypes[] = [];
|
||||
|
|
@ -49,7 +49,7 @@ export const SceneMovieTable: React.FunctionComponent<IProps> = (
|
|||
as="select"
|
||||
className="input-control"
|
||||
value={storeIdx[index] ? storeIdx[index]?.toString() : ""}
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
updateFieldChanged(
|
||||
item.id,
|
||||
Number.parseInt(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from "react-bootstrap";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useSceneGenerateScreenshot } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
import { JWUtils } from "src/utils";
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ export const SceneOperationsPanel: FunctionComponent<IOperationsPanelProps> = (
|
|||
props: IOperationsPanelProps
|
||||
) => {
|
||||
const Toast = useToast();
|
||||
const [generateScreenshot] = StashService.useSceneGenerateScreenshot();
|
||||
const [generateScreenshot] = useSceneGenerateScreenshot();
|
||||
|
||||
async function onGenerateScreenshot(at?: number) {
|
||||
await generateScreenshot({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
FindScenesQueryResult,
|
||||
SlimSceneDataFragment,
|
||||
} from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { queryFindScenes } from "src/core/StashService";
|
||||
import { useScenesList } from "src/hooks";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { DisplayMode } from "src/models/list-filter/types";
|
||||
|
|
@ -52,7 +52,7 @@ export const SceneList: React.FC<ISceneList> = ({
|
|||
const filterCopy = _.cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await StashService.queryFindScenes(filterCopy);
|
||||
const singleResult = await queryFindScenes(filterCopy);
|
||||
if (
|
||||
singleResult &&
|
||||
singleResult.data &&
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import _ from "lodash";
|
|||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { FindSceneMarkersQueryResult } from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { queryFindSceneMarkers } from "src/core/StashService";
|
||||
import { NavUtils } from "src/utils";
|
||||
import { useSceneMarkersList } from "src/hooks";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
|
|
@ -35,7 +35,7 @@ export const SceneMarkerList: React.FC = () => {
|
|||
const filterCopy = _.cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await StashService.queryFindSceneMarkers(filterCopy);
|
||||
const singleResult = await queryFindSceneMarkers(filterCopy);
|
||||
if (singleResult?.data?.findSceneMarkers?.scene_markers?.length === 1) {
|
||||
// navigate to the scene player page
|
||||
const url = NavUtils.makeSceneMarkerUrl(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import _ from "lodash";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useBulkSceneUpdate } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StudioSelect, LoadingIndicator } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
|
|
@ -27,7 +27,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
|
|||
);
|
||||
const [tagIds, setTagIds] = useState<string[]>();
|
||||
|
||||
const [updateScenes] = StashService.useBulkSceneUpdate(getSceneInput());
|
||||
const [updateScenes] = useBulkSceneUpdate(getSceneInput());
|
||||
|
||||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
|
@ -318,7 +318,7 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
|
|||
as="select"
|
||||
className="btn-secondary"
|
||||
value={rating}
|
||||
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setRating(event.currentTarget.value)
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import React from "react";
|
||||
import { Button, Table } from "react-bootstrap";
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useVersion, useLatestVersion } from "src/core/StashService";
|
||||
|
||||
export const SettingsAboutPanel: React.FC = () => {
|
||||
const { data, error, loading } = StashService.useVersion();
|
||||
const { data, error, loading } = useVersion();
|
||||
const {
|
||||
data: dataLatest,
|
||||
error: errorLatest,
|
||||
loading: loadingLatest,
|
||||
refetch,
|
||||
networkStatus,
|
||||
} = StashService.useLatestVersion();
|
||||
} = useLatestVersion();
|
||||
|
||||
function maybeRenderTag() {
|
||||
if (!data || !data.version || !data.version.version) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Form, InputGroup } from "react-bootstrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useConfiguration, useConfigureGeneral } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
import { Icon, LoadingIndicator } from "src/components/Shared";
|
||||
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
||||
|
|
@ -36,9 +36,9 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||
undefined
|
||||
);
|
||||
|
||||
const { data, error, loading } = StashService.useConfiguration();
|
||||
const { data, error, loading } = useConfiguration();
|
||||
|
||||
const [updateGeneralConfig] = StashService.useConfigureGeneral({
|
||||
const [updateGeneralConfig] = useConfigureGeneral({
|
||||
stashes,
|
||||
databasePath,
|
||||
generatedPath,
|
||||
|
|
@ -189,7 +189,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||
<Form.Control
|
||||
className="col col-sm-6 text-input"
|
||||
defaultValue={databasePath}
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setDatabasePath(e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
|
|
@ -203,7 +203,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||
<Form.Control
|
||||
className="col col-sm-6 text-input"
|
||||
defaultValue={generatedPath}
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setGeneratedPath(e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
|
|
@ -222,7 +222,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||
<Form.Control
|
||||
className="col col-sm-6 text-input"
|
||||
value={regexp}
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
excludeRegexChanged(i, e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
|
|
@ -262,7 +262,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||
<Form.Control
|
||||
className="col col-sm-6 input-control"
|
||||
as="select"
|
||||
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setMaxTranscodeSize(translateQuality(event.currentTarget.value))
|
||||
}
|
||||
value={resolutionToString(maxTranscodeSize)}
|
||||
|
|
@ -282,7 +282,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||
<Form.Control
|
||||
className="col col-sm-6 input-control"
|
||||
as="select"
|
||||
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setMaxStreamingTranscodeSize(
|
||||
translateQuality(event.currentTarget.value)
|
||||
)
|
||||
|
|
@ -332,7 +332,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||
<Form.Control
|
||||
className="col col-sm-6 text-input"
|
||||
defaultValue={scraperUserAgent}
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setScraperUserAgent(e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
|
|
@ -425,7 +425,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
|||
<Form.Control
|
||||
className="col col-sm-6 input-control"
|
||||
as="select"
|
||||
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setLogLevel(event.currentTarget.value)
|
||||
}
|
||||
value={logLevel}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { DurationInput, LoadingIndicator } from "src/components/Shared";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useConfiguration, useConfigureInterface } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
|
||||
export const SettingsInterfacePanel: React.FC = () => {
|
||||
const Toast = useToast();
|
||||
const { data: config, error, loading } = StashService.useConfiguration();
|
||||
const { data: config, error, loading } = useConfiguration();
|
||||
const [soundOnPreview, setSoundOnPreview] = useState<boolean>(true);
|
||||
const [wallShowTitle, setWallShowTitle] = useState<boolean>(true);
|
||||
const [maximumLoopDuration, setMaximumLoopDuration] = useState<number>(0);
|
||||
|
|
@ -16,7 +16,7 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
const [cssEnabled, setCSSEnabled] = useState<boolean>(false);
|
||||
const [language, setLanguage] = useState<string>("en");
|
||||
|
||||
const [updateInterfaceConfig] = StashService.useConfigureInterface({
|
||||
const [updateInterfaceConfig] = useConfigureInterface({
|
||||
soundOnPreview,
|
||||
wallShowTitle,
|
||||
maximumLoopDuration,
|
||||
|
|
@ -62,7 +62,7 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
as="select"
|
||||
className="col-4 input-control"
|
||||
value={language}
|
||||
onChange={(e: React.FormEvent<HTMLSelectElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setLanguage(e.currentTarget.value)
|
||||
}
|
||||
>
|
||||
|
|
@ -142,12 +142,12 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
<Form.Control
|
||||
as="textarea"
|
||||
value={css}
|
||||
onChange={(e: React.FormEvent<HTMLTextAreaElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
setCSS(e.currentTarget.value)
|
||||
}
|
||||
rows={16}
|
||||
className="col col-sm-6 text-input code"
|
||||
></Form.Control>
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
Page must be reloaded for changes to take effect.
|
||||
</Form.Text>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useReducer, useState } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useLogs, useLoggingSubscribe } from "src/core/StashService";
|
||||
|
||||
function convertTime(logEntry: GQL.LogEntryDataFragment) {
|
||||
function pad(val: number) {
|
||||
|
|
@ -74,8 +74,8 @@ const logReducer = (existingEntries: LogEntry[], newEntries: LogEntry[]) => [
|
|||
];
|
||||
|
||||
export const SettingsLogsPanel: React.FC = () => {
|
||||
const { data, error } = StashService.useLoggingSubscribe();
|
||||
const { data: existingData } = StashService.useLogs();
|
||||
const { data, error } = useLoggingSubscribe();
|
||||
const { data: existingData } = useLogs();
|
||||
const [currentData, dispatchLogUpdate] = useReducer(logReducer, []);
|
||||
const [logLevel, setLogLevel] = useState<string>("Info");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { mutateMetadataGenerate } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
|
||||
export const GenerateButton: React.FC = () => {
|
||||
|
|
@ -12,7 +12,7 @@ export const GenerateButton: React.FC = () => {
|
|||
|
||||
async function onGenerate() {
|
||||
try {
|
||||
await StashService.mutateMetadataGenerate({
|
||||
await mutateMetadataGenerate({
|
||||
sprites,
|
||||
previews,
|
||||
markers,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Button, Form, ProgressBar } from "react-bootstrap";
|
||||
import { Link } from "react-router-dom";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
useJobStatus,
|
||||
useMetadataUpdate,
|
||||
mutateMetadataImport,
|
||||
mutateMetadataClean,
|
||||
mutateMetadataScan,
|
||||
mutateMetadataAutoTag,
|
||||
mutateMetadataExport,
|
||||
mutateStopJob,
|
||||
} from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
import { Modal } from "src/components/Shared";
|
||||
import { GenerateButton } from "./GenerateButton";
|
||||
|
|
@ -18,8 +27,8 @@ export const SettingsTasksPanel: React.FC = () => {
|
|||
const [autoTagStudios, setAutoTagStudios] = useState<boolean>(true);
|
||||
const [autoTagTags, setAutoTagTags] = useState<boolean>(true);
|
||||
|
||||
const jobStatus = StashService.useJobStatus();
|
||||
const metadataUpdate = StashService.useMetadataUpdate();
|
||||
const jobStatus = useJobStatus();
|
||||
const metadataUpdate = useMetadataUpdate();
|
||||
|
||||
function statusToText(s: string) {
|
||||
switch (s) {
|
||||
|
|
@ -68,7 +77,7 @@ export const SettingsTasksPanel: React.FC = () => {
|
|||
|
||||
function onImport() {
|
||||
setIsImportAlertOpen(false);
|
||||
StashService.mutateMetadataImport().then(() => {
|
||||
mutateMetadataImport().then(() => {
|
||||
jobStatus.refetch();
|
||||
});
|
||||
}
|
||||
|
|
@ -91,7 +100,7 @@ export const SettingsTasksPanel: React.FC = () => {
|
|||
|
||||
function onClean() {
|
||||
setIsCleanAlertOpen(false);
|
||||
StashService.mutateMetadataClean().then(() => {
|
||||
mutateMetadataClean().then(() => {
|
||||
jobStatus.refetch();
|
||||
});
|
||||
}
|
||||
|
|
@ -115,7 +124,7 @@ export const SettingsTasksPanel: React.FC = () => {
|
|||
|
||||
async function onScan() {
|
||||
try {
|
||||
await StashService.mutateMetadataScan({ useFileMetadata });
|
||||
await mutateMetadataScan({ useFileMetadata });
|
||||
Toast.success({ content: "Started scan" });
|
||||
jobStatus.refetch();
|
||||
} catch (e) {
|
||||
|
|
@ -134,7 +143,7 @@ export const SettingsTasksPanel: React.FC = () => {
|
|||
|
||||
async function onAutoTag() {
|
||||
try {
|
||||
await StashService.mutateMetadataAutoTag(getAutoTagInput());
|
||||
await mutateMetadataAutoTag(getAutoTagInput());
|
||||
Toast.success({ content: "Started auto tagging" });
|
||||
jobStatus.refetch();
|
||||
} catch (e) {
|
||||
|
|
@ -152,9 +161,7 @@ export const SettingsTasksPanel: React.FC = () => {
|
|||
<Button
|
||||
id="stop"
|
||||
variant="danger"
|
||||
onClick={() =>
|
||||
StashService.mutateStopJob().then(() => jobStatus.refetch())
|
||||
}
|
||||
onClick={() => mutateStopJob().then(() => jobStatus.refetch())}
|
||||
>
|
||||
Stop
|
||||
</Button>
|
||||
|
|
@ -277,7 +284,7 @@ export const SettingsTasksPanel: React.FC = () => {
|
|||
variant="secondary"
|
||||
type="submit"
|
||||
onClick={() =>
|
||||
StashService.mutateMetadataExport().then(() => {
|
||||
mutateMetadataExport().then(() => {
|
||||
jobStatus.refetch();
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
|
|||
className="duration-control text-input"
|
||||
disabled={props.disabled}
|
||||
value={value}
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue(e.currentTarget.value)
|
||||
}
|
||||
onBlur={() => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, InputGroup, Form, Modal } from "react-bootstrap";
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useDirectories } from "src/core/StashService";
|
||||
|
||||
interface IProps {
|
||||
directories: string[];
|
||||
|
|
@ -12,9 +12,7 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
|||
const [currentDirectory, setCurrentDirectory] = useState<string>("");
|
||||
const [isDisplayingDialog, setIsDisplayingDialog] = useState<boolean>(false);
|
||||
const [selectedDirectories, setSelectedDirectories] = useState<string[]>([]);
|
||||
const { data, error, loading } = StashService.useDirectories(
|
||||
currentDirectory
|
||||
);
|
||||
const { data, error, loading } = useDirectories(currentDirectory);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedDirectories(props.directories);
|
||||
|
|
@ -51,7 +49,7 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
|||
<InputGroup>
|
||||
<Form.Control
|
||||
placeholder="File path"
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setCurrentDirectory(e.currentTarget.value)
|
||||
}
|
||||
defaultValue={currentDirectory}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Button, Form } from "react-bootstrap";
|
|||
interface IImageInput {
|
||||
isEditing: boolean;
|
||||
text?: string;
|
||||
onImageChange: (event: React.FormEvent<HTMLInputElement>) => void;
|
||||
onImageChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
acceptSVG?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
import React, { useState, useCallback, CSSProperties } from "react";
|
||||
import React, { useState, CSSProperties } from "react";
|
||||
import Select, { ValueType } from "react-select";
|
||||
import CreatableSelect from "react-select/creatable";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
useAllTagsForFilter,
|
||||
useAllMoviesForFilter,
|
||||
useAllStudiosForFilter,
|
||||
useAllPerformersForFilter,
|
||||
useMarkerStrings,
|
||||
useScrapePerformerList,
|
||||
useValidGalleriesForScene,
|
||||
useTagCreate,
|
||||
} from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
|
||||
type ValidTypes =
|
||||
|
|
@ -63,9 +72,7 @@ const getSelectedValues = (selectedItems: ValueType<Option>) =>
|
|||
: [];
|
||||
|
||||
export const SceneGallerySelect: React.FC<ISceneGallerySelect> = (props) => {
|
||||
const { data, loading } = StashService.useValidGalleriesForScene(
|
||||
props.sceneId
|
||||
);
|
||||
const { data, loading } = useValidGalleriesForScene(props.sceneId);
|
||||
const galleries = data?.validGalleriesForScene ?? [];
|
||||
const items = (galleries.length > 0
|
||||
? [{ path: "None", id: "0" }, ...galleries]
|
||||
|
|
@ -103,10 +110,7 @@ export const ScrapePerformerSuggest: React.FC<IScrapePerformerSuggestProps> = (
|
|||
props
|
||||
) => {
|
||||
const [query, setQuery] = React.useState<string>("");
|
||||
const { data, loading } = StashService.useScrapePerformerList(
|
||||
props.scraperId,
|
||||
query
|
||||
);
|
||||
const { data, loading } = useScrapePerformerList(props.scraperId, query);
|
||||
|
||||
const performers = data?.scrapePerformerList ?? [];
|
||||
const items = performers.map((item) => ({
|
||||
|
|
@ -114,12 +118,10 @@ export const ScrapePerformerSuggest: React.FC<IScrapePerformerSuggestProps> = (
|
|||
value: item.name ?? "",
|
||||
}));
|
||||
|
||||
const onInputChange = useCallback(
|
||||
debounce((input: string) => {
|
||||
setQuery(input);
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
const onInputChange = debounce((input: string) => {
|
||||
setQuery(input);
|
||||
}, 500);
|
||||
|
||||
const onChange = (selectedItems: ValueType<Option>) => {
|
||||
const name = getSelectedValues(selectedItems)[0];
|
||||
const performer = performers.find((p) => p.name === name);
|
||||
|
|
@ -145,7 +147,7 @@ interface IMarkerSuggestProps {
|
|||
onChange: (title: string) => void;
|
||||
}
|
||||
export const MarkerTitleSuggest: React.FC<IMarkerSuggestProps> = (props) => {
|
||||
const { data, loading } = StashService.useMarkerStrings();
|
||||
const { data, loading } = useMarkerStrings();
|
||||
const suggestions = data?.markerStrings ?? [];
|
||||
|
||||
const onChange = (selectedItems: ValueType<Option>) =>
|
||||
|
|
@ -182,7 +184,7 @@ export const FilterSelect: React.FC<IFilterProps & ITypeProps> = (props) =>
|
|||
);
|
||||
|
||||
export const PerformerSelect: React.FC<IFilterProps> = (props) => {
|
||||
const { data, loading } = StashService.useAllPerformersForFilter();
|
||||
const { data, loading } = useAllPerformersForFilter();
|
||||
|
||||
const normalizedData = data?.allPerformersSlim ?? [];
|
||||
const items: Option[] = normalizedData.map((item) => ({
|
||||
|
|
@ -215,7 +217,7 @@ export const PerformerSelect: React.FC<IFilterProps> = (props) => {
|
|||
};
|
||||
|
||||
export const StudioSelect: React.FC<IFilterProps> = (props) => {
|
||||
const { data, loading } = StashService.useAllStudiosForFilter();
|
||||
const { data, loading } = useAllStudiosForFilter();
|
||||
|
||||
const normalizedData = data?.allStudiosSlim ?? [];
|
||||
|
||||
|
|
@ -253,7 +255,7 @@ export const StudioSelect: React.FC<IFilterProps> = (props) => {
|
|||
};
|
||||
|
||||
export const MovieSelect: React.FC<IFilterProps> = (props) => {
|
||||
const { data, loading } = StashService.useAllMoviesForFilter();
|
||||
const { data, loading } = useAllMoviesForFilter();
|
||||
|
||||
const normalizedData = data?.allMoviesSlim ?? [];
|
||||
|
||||
|
|
@ -293,8 +295,8 @@ export const MovieSelect: React.FC<IFilterProps> = (props) => {
|
|||
export const TagSelect: React.FC<IFilterProps> = (props) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>(props.ids ?? []);
|
||||
const { data, loading: dataLoading } = StashService.useAllTagsForFilter();
|
||||
const [createTag] = StashService.useTagCreate({ name: "" });
|
||||
const { data, loading: dataLoading } = useAllTagsForFilter();
|
||||
const [createTag] = useTagCreate({ name: "" });
|
||||
const Toast = useToast();
|
||||
const placeholder = props.noSelectionString ?? "Select tags...";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React from "react";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { useStats } from "src/core/StashService";
|
||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
|
||||
export const Stats: React.FC = () => {
|
||||
const { data, error, loading } = StashService.useStats();
|
||||
const { data, error, loading } = useStats();
|
||||
|
||||
if (loading || !data) return <LoadingIndicator />;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ import { useParams, useHistory } from "react-router-dom";
|
|||
import cx from "classnames";
|
||||
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
useFindStudio,
|
||||
useStudioUpdate,
|
||||
useStudioCreate,
|
||||
useStudioDestroy,
|
||||
mutateMetadataAutoTag,
|
||||
} from "src/core/StashService";
|
||||
import { ImageUtils, TableUtils } from "src/utils";
|
||||
import {
|
||||
DetailsEditNavbar,
|
||||
|
|
@ -35,14 +41,14 @@ export const Studio: React.FC = () => {
|
|||
const [studio, setStudio] = useState<Partial<GQL.StudioDataFragment>>({});
|
||||
const [imagePreview, setImagePreview] = useState<string>();
|
||||
|
||||
const { data, error, loading } = StashService.useFindStudio(id);
|
||||
const [updateStudio] = StashService.useStudioUpdate(
|
||||
const { data, error, loading } = useFindStudio(id);
|
||||
const [updateStudio] = useStudioUpdate(
|
||||
getStudioInput() as GQL.StudioUpdateInput
|
||||
);
|
||||
const [createStudio] = StashService.useStudioCreate(
|
||||
const [createStudio] = useStudioCreate(
|
||||
getStudioInput() as GQL.StudioCreateInput
|
||||
);
|
||||
const [deleteStudio] = StashService.useStudioDestroy(
|
||||
const [deleteStudio] = useStudioDestroy(
|
||||
getStudioInput() as GQL.StudioDestroyInput
|
||||
);
|
||||
|
||||
|
|
@ -72,7 +78,7 @@ export const Studio: React.FC = () => {
|
|||
setImage(this.result as string);
|
||||
}
|
||||
|
||||
ImageUtils.usePasteImage(onImageLoad);
|
||||
ImageUtils.usePasteImage(onImageLoad, isEditing);
|
||||
|
||||
if (!isNew && !isEditing) {
|
||||
if (!data?.findStudio || loading) return <LoadingIndicator />;
|
||||
|
|
@ -115,7 +121,7 @@ export const Studio: React.FC = () => {
|
|||
async function onAutoTag() {
|
||||
if (!studio.id) return;
|
||||
try {
|
||||
await StashService.mutateMetadataAutoTag({ studios: [studio.id] });
|
||||
await mutateMetadataAutoTag({ studios: [studio.id] });
|
||||
Toast.success({ content: "Started auto tagging" });
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import React, { useState } from "react";
|
|||
import { Button, Form } from "react-bootstrap";
|
||||
import { Link } from "react-router-dom";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
mutateMetadataAutoTag,
|
||||
useAllTags,
|
||||
useTagUpdate,
|
||||
useTagCreate,
|
||||
useTagDestroy,
|
||||
} from "src/core/StashService";
|
||||
import { NavUtils } from "src/utils";
|
||||
import { Icon, Modal, LoadingIndicator } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
|
|
@ -18,16 +24,10 @@ export const TagList: React.FC = () => {
|
|||
GQL.TagDataFragment
|
||||
> | null>(null);
|
||||
|
||||
const { data, error } = StashService.useAllTags();
|
||||
const [updateTag] = StashService.useTagUpdate(
|
||||
getTagInput() as GQL.TagUpdateInput
|
||||
);
|
||||
const [createTag] = StashService.useTagCreate(
|
||||
getTagInput() as GQL.TagCreateInput
|
||||
);
|
||||
const [deleteTag] = StashService.useTagDestroy(
|
||||
getDeleteTagInput() as GQL.TagDestroyInput
|
||||
);
|
||||
const { data, error } = useAllTags();
|
||||
const [updateTag] = useTagUpdate(getTagInput() as GQL.TagUpdateInput);
|
||||
const [createTag] = useTagCreate(getTagInput() as GQL.TagCreateInput);
|
||||
const [deleteTag] = useTagDestroy(getDeleteTagInput() as GQL.TagDestroyInput);
|
||||
|
||||
function getTagInput() {
|
||||
const tagInput: Partial<GQL.TagCreateInput | GQL.TagUpdateInput> = { name };
|
||||
|
|
@ -62,7 +62,7 @@ export const TagList: React.FC = () => {
|
|||
async function onAutoTag(tag: GQL.TagDataFragment) {
|
||||
if (!tag) return;
|
||||
try {
|
||||
await StashService.mutateMetadataAutoTag({ tags: [tag.id] });
|
||||
await mutateMetadataAutoTag({ tags: [tag.id] });
|
||||
Toast.success({ content: "Started auto tagging" });
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
|
|
@ -160,7 +160,7 @@ export const TagList: React.FC = () => {
|
|||
<Form.Group controlId="tag-name">
|
||||
<Form.Label>Name</Form.Label>
|
||||
<Form.Control
|
||||
onChange={(newValue: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(newValue: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setName(newValue.currentTarget.value)
|
||||
}
|
||||
defaultValue={(editingTag && editingTag.name) || ""}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import _ from "lodash";
|
|||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { VideoHoverHook } from "src/hooks";
|
||||
import { useConfiguration } from "src/core/StashService";
|
||||
import { useVideoHover } from "src/hooks";
|
||||
import { TextUtils, NavUtils } from "src/utils";
|
||||
|
||||
interface IWallItemProps {
|
||||
|
|
@ -22,15 +22,15 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
|||
const [screenshotPath, setScreenshotPath] = useState<string>("");
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [tags, setTags] = useState<JSX.Element[]>([]);
|
||||
const config = StashService.useConfiguration();
|
||||
const videoHoverHook = VideoHoverHook.useVideoHover({
|
||||
const config = useConfiguration();
|
||||
const hoverHandler = useVideoHover({
|
||||
resetOnMouseLeave: true,
|
||||
});
|
||||
const showTextContainer =
|
||||
config.data?.configuration.interface.wallShowTitle ?? true;
|
||||
|
||||
function onMouseEnter() {
|
||||
VideoHoverHook.onMouseEnter(videoHoverHook);
|
||||
hoverHandler.onMouseEnter();
|
||||
if (!videoPath || videoPath === "") {
|
||||
if (props.sceneMarker) {
|
||||
setVideoPath(props.sceneMarker.stream || "");
|
||||
|
|
@ -43,7 +43,7 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
|||
const debouncedOnMouseEnter = useRef(_.debounce(onMouseEnter, 500));
|
||||
|
||||
function onMouseLeave() {
|
||||
VideoHoverHook.onMouseLeave(videoHoverHook);
|
||||
hoverHandler.onMouseLeave();
|
||||
setVideoPath("");
|
||||
debouncedOnMouseEnter.current.cancel();
|
||||
props.onOverlay(false);
|
||||
|
|
@ -111,7 +111,7 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
|||
}
|
||||
|
||||
const className = ["scene-wall-item-container"];
|
||||
if (videoHoverHook.isHovering.current) {
|
||||
if (hoverHandler.isHovering.current) {
|
||||
className.push("double-scale");
|
||||
}
|
||||
const style: React.CSSProperties = {};
|
||||
|
|
@ -133,10 +133,10 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
|||
src={videoPath}
|
||||
poster={screenshotPath}
|
||||
className="scene-wall-video"
|
||||
style={videoHoverHook.isHovering.current ? {} : { display: "none" }}
|
||||
style={hoverHandler.isHovering.current ? {} : { display: "none" }}
|
||||
autoPlay
|
||||
loop
|
||||
ref={videoHoverHook.videoEl}
|
||||
ref={hoverHandler.videoEl}
|
||||
/>
|
||||
<img
|
||||
alt={title}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
82
ui/v2.5/src/core/createClient.ts
Normal file
82
ui/v2.5/src/core/createClient.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import ApolloClient from "apollo-client";
|
||||
import { InMemoryCache } from "apollo-cache-inmemory";
|
||||
import { WebSocketLink } from "apollo-link-ws";
|
||||
import { HttpLink } from "apollo-link-http";
|
||||
import { onError } from "apollo-link-error";
|
||||
import { ServerError } from "apollo-link-http-common";
|
||||
import { split, from } from "apollo-link";
|
||||
import { getMainDefinition } from "apollo-utilities";
|
||||
|
||||
export const getPlatformURL = (ws?: boolean) => {
|
||||
const platformUrl = new URL(window.location.origin);
|
||||
|
||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
||||
platformUrl.port = "9999"; // TODO: Hack. Development expects port 9999
|
||||
|
||||
if (process.env.REACT_APP_HTTPS === "true") {
|
||||
platformUrl.protocol = "https:";
|
||||
}
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
platformUrl.protocol = "ws:";
|
||||
}
|
||||
|
||||
return platformUrl;
|
||||
};
|
||||
|
||||
export const createClient = () => {
|
||||
const platformUrl = getPlatformURL();
|
||||
const wsPlatformUrl = getPlatformURL(true);
|
||||
|
||||
if (platformUrl.protocol === "https:") {
|
||||
wsPlatformUrl.protocol = "wss:";
|
||||
}
|
||||
|
||||
const url = `${platformUrl.toString().slice(0, -1)}/graphql`;
|
||||
const wsUrl = `${wsPlatformUrl.toString().slice(0, -1)}/graphql`;
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
uri: url,
|
||||
});
|
||||
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: wsUrl,
|
||||
options: {
|
||||
reconnect: true,
|
||||
},
|
||||
});
|
||||
|
||||
const errorLink = onError(({ networkError }) => {
|
||||
// handle unauthorized error by redirecting to the login page
|
||||
if (networkError && (networkError as ServerError).statusCode === 401) {
|
||||
// redirect to login page
|
||||
window.location.href = "/login";
|
||||
}
|
||||
});
|
||||
|
||||
const splitLink = split(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === "OperationDefinition" &&
|
||||
definition.operation === "subscription"
|
||||
);
|
||||
},
|
||||
wsLink,
|
||||
httpLink
|
||||
);
|
||||
|
||||
const link = from([errorLink, splitLink]);
|
||||
|
||||
const cache = new InMemoryCache();
|
||||
const client = new ApolloClient({
|
||||
link,
|
||||
cache,
|
||||
});
|
||||
|
||||
return {
|
||||
cache,
|
||||
client,
|
||||
};
|
||||
};
|
||||
|
|
@ -25,7 +25,14 @@ import {
|
|||
import { LoadingIndicator } from "src/components/Shared";
|
||||
import { ListFilter } from "src/components/List/ListFilter";
|
||||
import { Pagination, PaginationIndex } from "src/components/List/Pagination";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import {
|
||||
useFindScenes,
|
||||
useFindSceneMarkers,
|
||||
useFindMovies,
|
||||
useFindStudios,
|
||||
useFindGalleries,
|
||||
useFindPerformers,
|
||||
} from "src/core/StashService";
|
||||
import { Criterion } from "src/models/list-filter/criteria/criterion";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { DisplayMode, FilterMode } from "src/models/list-filter/types";
|
||||
|
|
@ -418,7 +425,7 @@ export const useScenesList = (props: IListHookOptions<FindScenesQueryResult>) =>
|
|||
useList<FindScenesQueryResult, SlimSceneDataFragment>({
|
||||
...props,
|
||||
filterMode: FilterMode.Scenes,
|
||||
useData: StashService.useFindScenes,
|
||||
useData: useFindScenes,
|
||||
getData: (result: FindScenesQueryResult) =>
|
||||
result?.data?.findScenes?.scenes ?? [],
|
||||
getCount: (result: FindScenesQueryResult) =>
|
||||
|
|
@ -431,7 +438,7 @@ export const useSceneMarkersList = (
|
|||
useList<FindSceneMarkersQueryResult, SceneMarkerDataFragment>({
|
||||
...props,
|
||||
filterMode: FilterMode.SceneMarkers,
|
||||
useData: StashService.useFindSceneMarkers,
|
||||
useData: useFindSceneMarkers,
|
||||
getData: (result: FindSceneMarkersQueryResult) =>
|
||||
result?.data?.findSceneMarkers?.scene_markers ?? [],
|
||||
getCount: (result: FindSceneMarkersQueryResult) =>
|
||||
|
|
@ -444,7 +451,7 @@ export const useGalleriesList = (
|
|||
useList<FindGalleriesQueryResult, GalleryDataFragment>({
|
||||
...props,
|
||||
filterMode: FilterMode.Galleries,
|
||||
useData: StashService.useFindGalleries,
|
||||
useData: useFindGalleries,
|
||||
getData: (result: FindGalleriesQueryResult) =>
|
||||
result?.data?.findGalleries?.galleries ?? [],
|
||||
getCount: (result: FindGalleriesQueryResult) =>
|
||||
|
|
@ -457,7 +464,7 @@ export const useStudiosList = (
|
|||
useList<FindStudiosQueryResult, StudioDataFragment>({
|
||||
...props,
|
||||
filterMode: FilterMode.Studios,
|
||||
useData: StashService.useFindStudios,
|
||||
useData: useFindStudios,
|
||||
getData: (result: FindStudiosQueryResult) =>
|
||||
result?.data?.findStudios?.studios ?? [],
|
||||
getCount: (result: FindStudiosQueryResult) =>
|
||||
|
|
@ -470,7 +477,7 @@ export const usePerformersList = (
|
|||
useList<FindPerformersQueryResult, PerformerDataFragment>({
|
||||
...props,
|
||||
filterMode: FilterMode.Performers,
|
||||
useData: StashService.useFindPerformers,
|
||||
useData: useFindPerformers,
|
||||
getData: (result: FindPerformersQueryResult) =>
|
||||
result?.data?.findPerformers?.performers ?? [],
|
||||
getCount: (result: FindPerformersQueryResult) =>
|
||||
|
|
@ -481,7 +488,7 @@ export const useMoviesList = (props: IListHookOptions<FindMoviesQueryResult>) =>
|
|||
useList<FindMoviesQueryResult, MovieDataFragment>({
|
||||
...props,
|
||||
filterMode: FilterMode.Movies,
|
||||
useData: StashService.useFindMovies,
|
||||
useData: useFindMovies,
|
||||
getData: (result: FindMoviesQueryResult) =>
|
||||
result?.data?.findMovies?.movies ?? [],
|
||||
getCount: (result: FindMoviesQueryResult) =>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
/* eslint-disable no-param-reassign, no-console */
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { StashService } from "../core/StashService";
|
||||
import { useConfiguration } from "../core/StashService";
|
||||
|
||||
export interface IVideoHoverHookData {
|
||||
videoEl: React.RefObject<HTMLVideoElement>;
|
||||
|
|
@ -14,76 +12,78 @@ export interface IVideoHoverHookOptions {
|
|||
resetOnMouseLeave: boolean;
|
||||
}
|
||||
|
||||
export class VideoHoverHook {
|
||||
public static useVideoHover(
|
||||
options: IVideoHoverHookOptions
|
||||
): IVideoHoverHookData {
|
||||
const videoEl = useRef<HTMLVideoElement>(null);
|
||||
const isPlaying = useRef<boolean>(false);
|
||||
const isHovering = useRef<boolean>(false);
|
||||
export const useVideoHover = (options: IVideoHoverHookOptions) => {
|
||||
const videoEl = useRef<HTMLVideoElement>(null);
|
||||
const isPlaying = useRef<boolean>(false);
|
||||
const isHovering = useRef<boolean>(false);
|
||||
const config = useConfiguration();
|
||||
|
||||
const config = StashService.useConfiguration();
|
||||
const soundEnabled =
|
||||
!!config.data && !!config.data.configuration
|
||||
? config.data.configuration.interface.soundOnPreview
|
||||
: true;
|
||||
const onMouseEnter = () => {
|
||||
isHovering.current = true;
|
||||
|
||||
useEffect(() => {
|
||||
const videoTag = videoEl.current;
|
||||
if (!videoTag) {
|
||||
return;
|
||||
}
|
||||
videoTag.onplaying = () => {
|
||||
if (isHovering.current === true) {
|
||||
isPlaying.current = true;
|
||||
} else {
|
||||
videoTag.pause();
|
||||
}
|
||||
};
|
||||
videoTag.onpause = () => {
|
||||
isPlaying.current = false;
|
||||
};
|
||||
}, [videoEl]);
|
||||
|
||||
useEffect(() => {
|
||||
const videoTag = videoEl.current;
|
||||
if (!videoTag) {
|
||||
return;
|
||||
}
|
||||
videoTag.volume = soundEnabled ? 0.05 : 0;
|
||||
}, [soundEnabled]);
|
||||
|
||||
return { videoEl, isPlaying, isHovering, options };
|
||||
}
|
||||
|
||||
public static onMouseEnter(data: IVideoHoverHookData) {
|
||||
data.isHovering.current = true;
|
||||
|
||||
const videoTag = data.videoEl.current;
|
||||
const videoTag = videoEl.current;
|
||||
if (!videoTag) {
|
||||
return;
|
||||
}
|
||||
if (videoTag.paused && !data.isPlaying.current) {
|
||||
if (videoTag.paused && !isPlaying.current) {
|
||||
videoTag.play().catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static onMouseLeave(data: IVideoHoverHookData) {
|
||||
data.isHovering.current = false;
|
||||
const onMouseLeave = () => {
|
||||
isHovering.current = false;
|
||||
|
||||
const videoTag = data.videoEl.current;
|
||||
const videoTag = videoEl.current;
|
||||
if (!videoTag) {
|
||||
return;
|
||||
}
|
||||
if (!videoTag.paused && data.isPlaying) {
|
||||
if (!videoTag.paused && isPlaying) {
|
||||
videoTag.pause();
|
||||
if (data.options.resetOnMouseLeave) {
|
||||
if (options.resetOnMouseLeave) {
|
||||
videoTag.removeAttribute("src");
|
||||
videoTag.load();
|
||||
data.isPlaying.current = false;
|
||||
isPlaying.current = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const soundEnabled =
|
||||
config?.data?.configuration?.interface?.soundOnPreview ?? true;
|
||||
|
||||
useEffect(() => {
|
||||
const videoTag = videoEl.current;
|
||||
if (!videoTag) {
|
||||
return;
|
||||
}
|
||||
videoTag.onplaying = () => {
|
||||
if (isHovering.current === true) {
|
||||
isPlaying.current = true;
|
||||
} else {
|
||||
videoTag.pause();
|
||||
}
|
||||
};
|
||||
videoTag.onpause = () => {
|
||||
isPlaying.current = false;
|
||||
};
|
||||
}, [videoEl]);
|
||||
|
||||
useEffect(() => {
|
||||
const videoTag = videoEl.current;
|
||||
if (!videoTag) {
|
||||
return;
|
||||
}
|
||||
videoTag.volume = soundEnabled ? 0.05 : 0;
|
||||
}, [soundEnabled]);
|
||||
|
||||
return {
|
||||
videoEl,
|
||||
isPlaying,
|
||||
isHovering,
|
||||
options,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export { default as useToast } from "./Toast";
|
||||
export { useInterfaceLocalForage } from "./LocalForage";
|
||||
export { VideoHoverHook } from "./VideoHover";
|
||||
export { useVideoHover } from "./VideoHover";
|
||||
export {
|
||||
useScenesList,
|
||||
useSceneMarkersList,
|
||||
|
|
|
|||
|
|
@ -3,19 +3,16 @@ import { ApolloProvider } from "react-apollo";
|
|||
import ReactDOM from "react-dom";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { App } from "./App";
|
||||
import { StashService } from "./core/StashService";
|
||||
import { getClient } from "./core/StashService";
|
||||
import { getPlatformURL } from "./core/createClient";
|
||||
import "./index.scss";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
|
||||
ReactDOM.render(
|
||||
<>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href={`${StashService.getPlatformURL()}css`}
|
||||
/>
|
||||
<link rel="stylesheet" type="text/css" href={`${getPlatformURL()}css`} />
|
||||
<BrowserRouter>
|
||||
<ApolloProvider client={StashService.initialize()!}>
|
||||
<ApolloProvider client={getClient()}>
|
||||
<App />
|
||||
</ApolloProvider>
|
||||
</BrowserRouter>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
{
|
||||
"new": "Neu",
|
||||
"tags": "Etiketten",
|
||||
"scenes": "Szenen",
|
||||
"movies": "Filme",
|
||||
"studios": "Studios",
|
||||
"galleries": "Galerien",
|
||||
"performers": "Künstler",
|
||||
"markers": "Marken",
|
||||
"stats": {
|
||||
"notes": "Anmerkungen",
|
||||
"warning": "Dies ist noch eine frühe Version, einige Dinge sind noch in Arbeit."
|
||||
}
|
||||
}
|
||||
{
|
||||
"galleries": "Galerien",
|
||||
"library-size": "",
|
||||
"markers": "",
|
||||
"movies": "Filme",
|
||||
"new": "Neu",
|
||||
"performers": "Künstler",
|
||||
"scenes": "Szenen",
|
||||
"studios": "Studios",
|
||||
"tags": "Etiketten"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
{
|
||||
"new": "New",
|
||||
"tags": "Tags",
|
||||
"scenes": "Scenes",
|
||||
"movies": "Movies",
|
||||
"studios": "Studios",
|
||||
"galleries": "Galleries",
|
||||
"performers": "Performers",
|
||||
"markers": "Markers",
|
||||
"stats": {
|
||||
"notes": "Notes",
|
||||
"warning": "This is still an early version, some things are still a work in progress."
|
||||
}
|
||||
}
|
||||
{
|
||||
"galleries": "Galleries",
|
||||
"library-size": "Library size",
|
||||
"markers": "Markers",
|
||||
"movies": "Movies",
|
||||
"new": "New",
|
||||
"performers": "Performers",
|
||||
"scenes": "Scenes",
|
||||
"studios": "Studios",
|
||||
"tags": "Tags"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CriterionModifier } from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { getGenderStrings } from "src/core/StashService";
|
||||
import { Criterion, CriterionType, ICriterionOption } from "./criterion";
|
||||
|
||||
export class GenderCriterion extends Criterion {
|
||||
|
|
@ -7,7 +7,7 @@ export class GenderCriterion extends Criterion {
|
|||
public parameterName: string = "gender";
|
||||
public modifier = CriterionModifier.Equals;
|
||||
public modifierOptions = [];
|
||||
public options: string[] = StashService.getGenderStrings();
|
||||
public options: string[] = getGenderStrings();
|
||||
public value: string = "";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
SortDirectionEnum,
|
||||
MovieFilterType,
|
||||
} from "src/core/generated-graphql";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { stringToGender } from "src/core/StashService";
|
||||
import {
|
||||
Criterion,
|
||||
ICriterionOption,
|
||||
|
|
@ -512,7 +512,7 @@ export class ListFilterModel {
|
|||
case "gender": {
|
||||
const gCrit = criterion as GenderCriterion;
|
||||
result.gender = {
|
||||
value: StashService.stringToGender(gCrit.value),
|
||||
value: stringToGender(gCrit.value),
|
||||
modifier: gCrit.modifier,
|
||||
};
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const renderTextArea = (options: {
|
|||
as="textarea"
|
||||
readOnly={!options.isEditing}
|
||||
plaintext={!options.isEditing}
|
||||
onChange={(event: React.FormEvent<HTMLTextAreaElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
options.onChange(event.currentTarget.value)
|
||||
}
|
||||
value={options.value}
|
||||
|
|
@ -32,7 +32,7 @@ const renderEditableText = (options: {
|
|||
<Form.Control
|
||||
readOnly={!options.isEditing}
|
||||
plaintext={!options.isEditing}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
options.onChange(event.currentTarget.value)
|
||||
}
|
||||
value={
|
||||
|
|
@ -68,7 +68,7 @@ const renderInputGroup = (options: {
|
|||
plaintext={!options.isEditing}
|
||||
value={options.value ?? ""}
|
||||
placeholder={options.placeholder ?? options.title}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
options.onChange(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
|
|
@ -150,7 +150,7 @@ const renderHtmlSelect = (options: {
|
|||
disabled={!options.isEditing}
|
||||
plaintext={!options.isEditing}
|
||||
value={options.value?.toString()}
|
||||
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
options.onChange(event.currentTarget.value)
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -25,13 +25,18 @@ const onImageChange = (
|
|||
if (file) readImage(file, onLoadEnd);
|
||||
};
|
||||
|
||||
const usePasteImage = (onLoadEnd: (this: FileReader) => void) => {
|
||||
const usePasteImage = (
|
||||
onLoadEnd: (this: FileReader) => void,
|
||||
isActive: boolean = true
|
||||
) => {
|
||||
useEffect(() => {
|
||||
const paste = (event: ClipboardEvent) => pasteImage(event, onLoadEnd);
|
||||
document.addEventListener("paste", paste);
|
||||
if (isActive) {
|
||||
document.addEventListener("paste", paste);
|
||||
}
|
||||
|
||||
return () => document.removeEventListener("paste", paste);
|
||||
});
|
||||
}, [isActive, onLoadEnd]);
|
||||
};
|
||||
|
||||
const Image = {
|
||||
|
|
|
|||
3722
ui/v2.5/yarn.lock
3722
ui/v2.5/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue