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