Library updates (#792)

* Upgrade Typescript to 4.0
* Update i18n-iso-countries to 6.0
* Update react-intl to 5.8.0
* Update jimp to 0.16.1
* Update apollo and graphql libraries
* Update various libraries and fix linting/type errors
* Refactor cache invalidation
* Codegen refetch queries
This commit is contained in:
InfiniteTF
2020-09-11 05:01:00 +02:00
committed by GitHub
parent 5ba11e0e93
commit 5cd7dcaeb2
27 changed files with 2076 additions and 1881 deletions

View File

@@ -1,4 +1,10 @@
{
"env": {
"browser": true
},
"globals": {
"Mousetrap": "readonly"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
@@ -16,11 +22,18 @@
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": 2,
"lines-between-class-members": "off",
"@typescript-eslint/interface-name-prefix": [
"warn",
{ "prefixWithI": "always" }
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": ["PascalCase"],
"custom": {
"regex": "^I[A-Z]",
"match": true
}
}
],
"lines-between-class-members": "off",
"import/extensions": [
"error",
"ignorePackages",
@@ -46,6 +59,7 @@
"@typescript-eslint/indent": "off",
"react/prop-types": "off",
"react/destructuring-assignment": "off",
"react/require-default-props": "off",
"react/jsx-props-no-spreading": "off",
"react/style-prop-object": ["error", {
"allow": ["FormattedNumber"]

View File

@@ -3,13 +3,12 @@ schema: "../../graphql/schema/**/*.graphql"
documents: "../../graphql/documents/**/*.graphql"
generates:
src/core/generated-graphql.tsx:
config:
withHooks: true
withHOC: false
withComponents: false
plugins:
- add: "/* eslint-disable */"
- add:
content: "/* eslint-disable */"
- time
- typescript
- typescript-operations
- typescript-react-apollo
config:
withRefetchFn: true

View File

@@ -25,66 +25,56 @@
"not op_mini all"
],
"dependencies": {
"@apollo/react-hooks": "^3.1.5",
"@formatjs/intl-numberformat": "^4.2.1",
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-regular-svg-icons": "^5.13.0",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/react-fontawesome": "^0.1.9",
"@apollo/client": "^3.1.4",
"@formatjs/intl-numberformat": "^5.6.0",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-regular-svg-icons": "^5.14.0",
"@fortawesome/free-solid-svg-icons": "^5.14.0",
"@fortawesome/react-fontawesome": "^0.1.11",
"@types/mousetrap": "^1.6.3",
"apollo-cache": "^1.3.4",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
"apollo-link": "^1.2.14",
"apollo-link-error": "^1.1.13",
"apollo-link-http": "^1.5.17",
"apollo-link-ws": "^1.0.20",
"apollo-utilities": "^1.3.3",
"axios": "0.19.2",
"bootstrap": "^4.4.1",
"axios": "0.20.0",
"bootstrap": "^4.5.2",
"classnames": "^2.2.6",
"flag-icon-css": "^3.4.6",
"formik": "^2.1.4",
"graphql": "^14.5.8",
"graphql-tag": "^2.10.3",
"i18n-iso-countries": "^5.2.0",
"jimp": "^0.12.1",
"localforage": "1.7.3",
"lodash": "^4.17.15",
"flag-icon-css": "^3.5.0",
"formik": "^2.1.5",
"graphql": "^15.3.0",
"graphql-tag": "^2.11.0",
"i18n-iso-countries": "^6.0.0",
"jimp": "^0.16.1",
"localforage": "1.9.0",
"lodash": "^4.17.20",
"mousetrap": "^1.6.5",
"query-string": "6.12.1",
"query-string": "6.13.1",
"react": "16.13.1",
"react-apollo": "^3.1.5",
"react-bootstrap": "1.0.1",
"react-bootstrap": "1.3.0",
"react-dom": "16.13.1",
"react-images": "0.5.19",
"react-intl": "^4.5.1",
"react-intl": "^5.8.0",
"react-jw-player": "1.19.1",
"react-markdown": "^4.3.1",
"react-photo-gallery": "^8.0.0",
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "^5.1.2",
"react-router-dom": "^5.2.0",
"react-select": "^3.1.0",
"subscriptions-transport-ws": "^0.9.16",
"subscriptions-transport-ws": "^0.9.18",
"universal-cookie": "^4.0.3"
},
"devDependencies": {
"@graphql-codegen/add": "^1.13.5",
"@graphql-codegen/cli": "^1.13.5",
"@graphql-codegen/time": "^1.13.5",
"@graphql-codegen/typescript": "^1.13.5",
"@graphql-codegen/typescript-compatibility": "^1.13.5",
"@graphql-codegen/typescript-operations": "^1.13.5",
"@graphql-codegen/typescript-react-apollo": "^1.13.5",
"@graphql-codegen/add": "^2.0.1",
"@graphql-codegen/cli": "^1.17.8",
"@graphql-codegen/time": "^2.0.1",
"@graphql-codegen/typescript": "^1.17.9",
"@graphql-codegen/typescript-operations": "^1.17.8",
"@graphql-codegen/typescript-react-apollo": "^2.0.6",
"@types/classnames": "^2.2.10",
"@types/lodash": "^4.14.150",
"@types/node": "13.13.4",
"@types/react": "16.9.34",
"@types/react-dom": "^16.9.7",
"@types/react-images": "^0.5.1",
"@types/lodash": "^4.14.161",
"@types/node": "14.6.4",
"@types/react": "16.9.43",
"@types/react-dom": "^16.9.8",
"@types/react-images": "^0.5.3",
"@types/react-router-bootstrap": "^0.24.5",
"@types/react-router-dom": "5.1.5",
"@types/react-select": "^3.0.12",
"@types/react-select": "3.0.19",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"eslint": "^6.8.0",
@@ -95,13 +85,13 @@
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^4.0.0",
"extract-react-intl-messages": "^4.1.1",
"node-sass": "4.14.0",
"node-sass": "4.14.1",
"postcss-safe-parser": "^4.0.2",
"prettier": "2.0.5",
"react-scripts": "^3.4.1",
"prettier": "2.1.1",
"react-scripts": "^3.4.3",
"stylelint": "^13.3.3",
"stylelint-config-prettier": "^8.0.1",
"stylelint-order": "^4.0.0",
"typescript": "^3.8.3"
"typescript": "^3.9.7"
}
}

View File

@@ -5,8 +5,8 @@ import { ToastProvider } from "src/hooks/Toast";
import { library } from "@fortawesome/fontawesome-svg-core";
import { fas } from "@fortawesome/free-solid-svg-icons";
import "@formatjs/intl-numberformat/polyfill";
import "@formatjs/intl-numberformat/dist/locale-data/en";
import "@formatjs/intl-numberformat/dist/locale-data/en-GB";
import "@formatjs/intl-numberformat/locale-data/en";
import "@formatjs/intl-numberformat/locale-data/en-GB";
import locales from "src/locale";
import { useConfiguration } from "src/core/StashService";

View File

@@ -4,8 +4,12 @@ import { useFindGallery } from "src/core/StashService";
import { LoadingIndicator } from "src/components/Shared";
import { GalleryViewer } from "./GalleryViewer";
interface IGalleryParams {
id: string;
}
export const Gallery: React.FC = () => {
const { id = "" } = useParams();
const { id } = useParams<IGalleryParams>();
const { data, error, loading } = useFindGallery(id);
const gallery = data?.findGallery;

View File

@@ -124,7 +124,7 @@ export const Manual: React.FC<IManualProps> = ({ show, onClose }) => {
<Container className="manual-container">
<Tab.Container
activeKey={activeTab}
onSelect={(k) => setActiveTab(k)}
onSelect={(k) => k && setActiveTab(k)}
id="manual-tabs"
>
<Row>

View File

@@ -9,7 +9,6 @@ import {
Form,
OverlayTrigger,
Tooltip,
SafeAnchorProps,
InputGroup,
FormControl,
ButtonToolbar,
@@ -160,11 +159,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
props.onFilterUpdate(newFilter);
}
function onChangeSortBy(event: React.MouseEvent<SafeAnchorProps>) {
const target = event.currentTarget as HTMLAnchorElement;
function onChangeSortBy(event: React.MouseEvent<HTMLAnchorElement>) {
const newFilter = _.cloneDeep(props.filter);
newFilter.sortBy = target.text;
newFilter.sortBy = event.currentTarget.text;
newFilter.currentPage = 1;
props.onFilterUpdate(newFilter);
}

View File

@@ -30,10 +30,14 @@ import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
import { MovieScenesPanel } from "./MovieScenesPanel";
import { MovieScrapeDialog } from "./MovieScrapeDialog";
interface IMovieParams {
id?: string;
}
export const Movie: React.FC = () => {
const history = useHistory();
const Toast = useToast();
const { id = "new" } = useParams();
const { id = "new" } = useParams<IMovieParams>();
const isNew = id === "new";
// Editing state

View File

@@ -17,10 +17,15 @@ import { PerformerDetailsPanel } from "./PerformerDetailsPanel";
import { PerformerOperationsPanel } from "./PerformerOperationsPanel";
import { PerformerScenesPanel } from "./PerformerScenesPanel";
interface IPerformerParams {
id?: string;
tab?: string;
}
export const Performer: React.FC = () => {
const Toast = useToast();
const history = useHistory();
const { tab = "details", id = "new" } = useParams();
const { tab = "details", id = "new" } = useParams<IPerformerParams>();
const isNew = id === "new";
// Performer state
@@ -51,7 +56,7 @@ export const Performer: React.FC = () => {
tab === "scenes" || tab === "edit" || tab === "operations"
? tab
: "details";
const setActiveTabKey = (newTab: string) => {
const setActiveTabKey = (newTab: string | null) => {
if (tab !== newTab) {
const tabParam = newTab === "details" ? "" : `/${newTab}`;
history.replace(`/performers/${id}${tabParam}`);

View File

@@ -129,7 +129,7 @@ export const SceneFilenameParser: React.FC = () => {
queryParseSceneFilenames(parserFilter, parserInputData)
.then((response) => {
const result = response.data.parseSceneFilenames;
const result = response?.data?.parseSceneFilenames;
if (result) {
parseResults(result.results);
setTotalItems(result.count);

View File

@@ -101,7 +101,6 @@ export class SceneParserResult {
interface ISceneParserFieldProps<T> {
parserResult: ParserResult<T>;
className?: string;
fieldName: string;
onSetChanged: (isSet: boolean) => void;
onValueChanged: (value: T) => void;
originalParserResult?: ParserResult<T>;
@@ -372,7 +371,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
{props.showFields.get("Title") && (
<SceneParserStringField
key="title"
fieldName="Title"
className="parser-field-title input-control text-input"
parserResult={props.scene.title}
onSetChanged={(isSet) =>
@@ -386,7 +384,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
{props.showFields.get("Date") && (
<SceneParserStringField
key="date"
fieldName="Date"
className="parser-field-date input-control text-input"
parserResult={props.scene.date}
onSetChanged={(isSet) =>
@@ -400,7 +397,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
{props.showFields.get("Rating") && (
<SceneParserRatingField
key="rating"
fieldName="Rating"
className="parser-field-rating input-control text-input"
parserResult={props.scene.rating}
onSetChanged={(isSet) =>
@@ -414,7 +410,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
{props.showFields.get("Performers") && (
<SceneParserPerformerField
key="performers"
fieldName="Performers"
className="parser-field-performers input-control text-input"
parserResult={props.scene.performers}
originalParserResult={props.scene.performers}
@@ -429,7 +424,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
{props.showFields.get("Tags") && (
<SceneParserTagField
key="tags"
fieldName="Tags"
className="parser-field-tags input-control text-input"
parserResult={props.scene.tags}
originalParserResult={props.scene.tags}
@@ -444,7 +438,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => {
{props.showFields.get("Studio") && (
<SceneParserStudioField
key="studio"
fieldName="Studio"
className="parser-field-studio input-control text-input"
parserResult={props.scene.studio}
originalParserResult={props.scene.studio}

View File

@@ -26,8 +26,12 @@ import { SceneMoviePanel } from "./SceneMoviePanel";
import { DeleteScenesDialog } from "../DeleteScenesDialog";
import { SceneGenerateDialog } from "../SceneGenerateDialog";
interface ISceneParams {
id?: string;
}
export const Scene: React.FC = () => {
const { id = "new" } = useParams();
const { id = "new" } = useParams<ISceneParams>();
const location = useLocation();
const history = useHistory();
const Toast = useToast();
@@ -210,7 +214,7 @@ export const Scene: React.FC = () => {
return (
<Tab.Container
activeKey={activeTabKey}
onSelect={(k) => setActiveTabKey(k)}
onSelect={(k) => k && setActiveTabKey(k)}
>
<div>
<Nav variant="tabs" className="mr-auto">

View File

@@ -26,15 +26,14 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
</Link>
));
const renderMovies = (movies: Partial<GQL.SceneMovie>[]) => {
return movies.map((sceneMovie) =>
const renderMovies = (scene: GQL.SlimSceneDataFragment) =>
scene.movies.map((sceneMovie) =>
!sceneMovie.movie ? undefined : (
<Link to={NavUtils.makeMovieScenesUrl(sceneMovie.movie)}>
<h6>{sceneMovie.movie.name}</h6>
</Link>
)
);
};
const renderSceneRow = (scene: GQL.SlimSceneDataFragment) => (
<tr key={scene.id}>
@@ -68,7 +67,7 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
</Link>
)}
</td>
<td>{renderMovies(scene.movies)}</td>
<td>{renderMovies(scene)}</td>
</tr>
);

View File

@@ -21,7 +21,7 @@ export const Settings: React.FC = () => {
<Tab.Container
defaultActiveKey={defaultTab}
id="configuration-tabs"
onSelect={onSelect}
onSelect={(tab) => tab && onSelect(tab)}
>
<Row>
<Col sm={3} md={2}>

View File

@@ -1,45 +1,24 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { Button } from "react-bootstrap";
import { mutateReloadPlugins, usePlugins } from "src/core/StashService";
import { useToast } from "src/hooks";
import * as GQL from "src/core/generated-graphql";
import { TextUtils } from "src/utils";
import { Icon, LoadingIndicator } from "../Shared";
import { Icon, LoadingIndicator } from "src/components/Shared";
export const SettingsPluginsPanel: React.FC = () => {
const Toast = useToast();
const plugins = usePlugins();
// Network state
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (plugins) {
setIsLoading(false);
}
}, [plugins]);
const { data, loading } = usePlugins();
async function onReloadPlugins() {
setIsLoading(true);
try {
await mutateReloadPlugins();
// reload the performer scrapers
await plugins.refetch();
} catch (e) {
Toast.error(e);
} finally {
setIsLoading(false);
}
await mutateReloadPlugins().catch((e) => Toast.error(e));
}
function renderLink(plugin: GQL.Plugin) {
if (plugin.url) {
function renderLink(url?: string) {
if (url) {
return (
<Button className="minimal">
<a
href={TextUtils.sanitiseURL(plugin.url)}
href={TextUtils.sanitiseURL(url)}
className="link"
target="_blank"
rel="noopener noreferrer"
@@ -51,30 +30,24 @@ export const SettingsPluginsPanel: React.FC = () => {
}
}
function renderPlugin(plugin: GQL.Plugin) {
return (
function renderPlugins() {
const elements = (data?.plugins ?? []).map((plugin) => (
<div key={plugin.id}>
<h5>
{plugin.name} {plugin.version ? `(${plugin.version})` : undefined}{" "}
{renderLink(plugin)}
{renderLink(plugin.url ?? undefined)}
</h5>
{plugin.description ? (
<small className="text-muted">{plugin.description}</small>
) : undefined}
<hr />
</div>
);
));
return <div>{elements}</div>;
}
function renderPlugins() {
if (!plugins.data || !plugins.data.plugins) {
return;
}
return <div>{plugins.data?.plugins.map(renderPlugin)}</div>;
}
if (isLoading) return <LoadingIndicator />;
if (loading) return <LoadingIndicator />;
return (
<>

View File

@@ -15,10 +15,13 @@ import {
mutateRunPluginTask,
} from "src/core/StashService";
import { useToast } from "src/hooks";
import * as GQL from "src/core/generated-graphql";
import { Modal } from "src/components/Shared";
import { Plugin, PluginTask } from "src/core/generated-graphql";
import { GenerateButton } from "./GenerateButton";
type Plugin = Pick<GQL.Plugin, "id">;
type PluginTask = Pick<GQL.PluginTask, "name" | "description">;
export const SettingsTasksPanel: React.FC = () => {
const Toast = useToast();
const [isImportAlertOpen, setIsImportAlertOpen] = useState<boolean>(false);
@@ -199,17 +202,11 @@ export const SettingsTasksPanel: React.FC = () => {
);
}
async function onPluginTaskClicked(
plugin: Partial<Plugin>,
operation: Partial<PluginTask>
) {
await mutateRunPluginTask(plugin.id!, operation.name!);
async function onPluginTaskClicked(plugin: Plugin, operation: PluginTask) {
await mutateRunPluginTask(plugin.id, operation.name);
}
function renderPluginTasks(
plugin: Partial<Plugin>,
pluginTasks: Partial<PluginTask>[] | undefined
) {
function renderPluginTasks(plugin: Plugin, pluginTasks: PluginTask[]) {
if (!pluginTasks) {
return;
}

View File

@@ -1,5 +1,5 @@
import React, { useState, CSSProperties } from "react";
import Select, { ValueType } from "react-select";
import Select, { ValueType, Props } from "react-select";
import CreatableSelect from "react-select/creatable";
import { debounce } from "lodash";
@@ -388,15 +388,13 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
...base,
color: "#000",
}),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
container: (base: CSSProperties, state: any) => ({
container: (base: CSSProperties, props: Props) => ({
...base,
zIndex: state.isFocused ? 10 : base.zIndex,
zIndex: props.isFocused ? 10 : base.zIndex,
}),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
multiValueRemove: (base: CSSProperties, state: any) => ({
multiValueRemove: (base: CSSProperties, props: Props) => ({
...base,
color: state.isFocused ? base.color : "#333333",
color: props.isFocused ? base.color : "#333333",
}),
};

View File

@@ -22,10 +22,15 @@ import { useToast } from "src/hooks";
import { StudioScenesPanel } from "./StudioScenesPanel";
import { StudioChildrenPanel } from "./StudioChildrenPanel";
interface IStudioParams {
id?: string;
tab?: string;
}
export const Studio: React.FC = () => {
const history = useHistory();
const Toast = useToast();
const { tab = "details", id = "new" } = useParams();
const { tab = "details", id = "new" } = useParams<IStudioParams>();
const isNew = id === "new";
// Editing state
@@ -191,7 +196,7 @@ export const Studio: React.FC = () => {
}
const activeTabKey = tab === "childstudios" ? tab : "scenes";
const setActiveTabKey = (newTab: string) => {
const setActiveTabKey = (newTab: string | null) => {
if (tab !== newTab) {
const tabParam = newTab === "scenes" ? "" : `/${newTab}`;
history.replace(`/studios/${id}${tabParam}`);

View File

@@ -21,10 +21,15 @@ import { useToast } from "src/hooks";
import { TagScenesPanel } from "./TagScenesPanel";
import { TagMarkersPanel } from "./TagMarkersPanel";
interface ITabParams {
id?: string;
tab?: string;
}
export const Tag: React.FC = () => {
const history = useHistory();
const Toast = useToast();
const { tab = "scenes", id = "new" } = useParams();
const { tab = "scenes", id = "new" } = useParams<ITabParams>();
const isNew = id === "new";
// Editing state
@@ -45,7 +50,7 @@ export const Tag: React.FC = () => {
const [deleteTag] = useTagDestroy(getTagInput() as GQL.TagUpdateInput);
const activeTabKey = tab === "markers" ? tab : "scenes";
const setActiveTabKey = (newTab: string) => {
const setActiveTabKey = (newTab: string | null) => {
if (tab !== newTab) {
const tabParam = newTab === "scenes" ? "" : `/${newTab}`;
history.replace(`/tags/${id}${tabParam}`);

View File

@@ -1,32 +1,45 @@
import { ApolloCache, DocumentNode } from "@apollo/client";
import {
isField,
resultKeyNameFromField,
getQueryDefinition,
getOperationName,
} from "@apollo/client/utilities";
import { ListFilterModel } from "../models/list-filter/filter";
import * as GQL from "./generated-graphql";
import { createClient } from "./createClient";
const { client, cache } = createClient();
const { client } = createClient();
export const getClient = () => client;
// TODO: Invalidation should happen through apollo client, rather than rewriting cache directly
const invalidateQueries = (queries: string[]) => {
if (cache) {
const keyMatchers = queries.map((query) => {
return new RegExp(`^${query}`, "i");
});
const getQueryNames = (queries: DocumentNode[]): string[] =>
queries.map((q) => getOperationName(q)).filter((n) => n !== null) as string[];
// 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) => {
if (
keyMatchers.some((matcher) => {
return !!key.match(matcher);
})
) {
delete rootQuery[key];
}
// Will delete the entire cache for any queries passed in
const deleteCache = (queries: DocumentNode[]) => {
const fields = queries
.map((q) => {
const field = getQueryDefinition(q).selectionSet.selections[0];
return isField(field) ? resultKeyNameFromField(field) : "";
})
.filter((name) => name !== "")
.reduce(
(prevFields, name) => ({
...prevFields,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[name]: (_items: any, { DELETE }: any) => DELETE,
}),
{}
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (cache: ApolloCache<any>) =>
cache.modify({
id: "ROOT_QUERY",
fields,
});
}
};
export const useFindGalleries = (filter: ListFilterModel) =>
@@ -136,20 +149,28 @@ export const useFindTag = (id: string) => {
return GQL.useFindTagQuery({ variables: { id }, skip });
};
// TODO - scene marker manipulation functions are handled differently
export const sceneMarkerMutationImpactedQueries = [
"findSceneMarkers",
"findScenes",
"markerStrings",
"sceneMarkerTags",
const sceneMarkerMutationImpactedQueries = [
GQL.FindSceneDocument,
GQL.FindScenesDocument,
GQL.FindSceneMarkersDocument,
GQL.MarkerStringsDocument,
];
export const useSceneMarkerCreate = () =>
GQL.useSceneMarkerCreateMutation({ refetchQueries: ["FindScene"] });
GQL.useSceneMarkerCreateMutation({
refetchQueries: getQueryNames([GQL.FindSceneDocument]),
update: deleteCache(sceneMarkerMutationImpactedQueries),
});
export const useSceneMarkerUpdate = () =>
GQL.useSceneMarkerUpdateMutation({ refetchQueries: ["FindScene"] });
GQL.useSceneMarkerUpdateMutation({
refetchQueries: getQueryNames([GQL.FindSceneDocument]),
update: deleteCache(sceneMarkerMutationImpactedQueries),
});
export const useSceneMarkerDestroy = () =>
GQL.useSceneMarkerDestroyMutation({ refetchQueries: ["FindScene"] });
GQL.useSceneMarkerDestroyMutation({
refetchQueries: getQueryNames([GQL.FindSceneDocument]),
update: deleteCache(sceneMarkerMutationImpactedQueries),
});
export const useListPerformerScrapers = () =>
GQL.useListPerformerScrapersQuery();
@@ -199,61 +220,61 @@ export const useConfiguration = () => GQL.useConfigurationQuery();
export const useDirectory = (path?: string) =>
GQL.useDirectoryQuery({ variables: { path } });
export const performerMutationImpactedQueries = [
"FindPerformers",
"FindScenes",
"FindSceneMarkers",
"AllPerformers",
"AllPerformersForFilter",
const performerMutationImpactedQueries = [
GQL.FindPerformersDocument,
GQL.FindSceneDocument,
GQL.FindScenesDocument,
GQL.AllPerformersForFilterDocument,
];
export const usePerformerCreate = () =>
GQL.usePerformerCreateMutation({
refetchQueries: performerMutationImpactedQueries,
update: () => invalidateQueries(performerMutationImpactedQueries),
refetchQueries: getQueryNames([
GQL.FindPerformersDocument,
GQL.AllPerformersForFilterDocument,
]),
update: deleteCache([
GQL.FindPerformersDocument,
GQL.AllPerformersForFilterDocument,
]),
});
export const usePerformerUpdate = () =>
GQL.usePerformerUpdateMutation({
refetchQueries: performerMutationImpactedQueries,
update: () => invalidateQueries(performerMutationImpactedQueries),
update: deleteCache(performerMutationImpactedQueries),
});
export const usePerformerDestroy = () =>
GQL.usePerformerDestroyMutation({
refetchQueries: performerMutationImpactedQueries,
update: () => invalidateQueries(performerMutationImpactedQueries),
refetchQueries: getQueryNames([
GQL.FindPerformersDocument,
GQL.AllPerformersForFilterDocument,
]),
update: deleteCache(performerMutationImpactedQueries),
});
export const sceneMutationImpactedQueries = [
"findPerformers",
"findScenes",
"findSceneMarkers",
"findStudios",
"findMovies",
"allTags",
// TODO - add "findTags" when it is implemented
const sceneMutationImpactedQueries = [
GQL.FindPerformerDocument,
GQL.FindPerformersDocument,
GQL.FindScenesDocument,
GQL.FindSceneMarkersDocument,
GQL.FindStudioDocument,
GQL.FindStudiosDocument,
GQL.FindMovieDocument,
GQL.FindMoviesDocument,
GQL.FindTagDocument,
GQL.FindTagsDocument,
GQL.AllTagsDocument,
];
export const useSceneUpdate = (input: GQL.SceneUpdateInput) =>
GQL.useSceneUpdateMutation({
variables: input,
update: () => invalidateQueries(sceneMutationImpactedQueries),
refetchQueries: ["AllTagsForFilter"],
update: deleteCache(sceneMutationImpactedQueries),
});
// remove findScenes for bulk scene update so that we don't lose
// existing results
export const sceneBulkMutationImpactedQueries = [
"findPerformers",
"findSceneMarkers",
"findStudios",
"findMovies",
"allTags",
];
export const useBulkSceneUpdate = (input: GQL.BulkSceneUpdateInput) =>
GQL.useBulkSceneUpdateMutation({
variables: input,
update: () => invalidateQueries(sceneBulkMutationImpactedQueries),
update: deleteCache(sceneMutationImpactedQueries),
});
export const useScenesUpdate = (input: GQL.SceneUpdateInput[]) =>
@@ -277,107 +298,123 @@ export const useSceneResetO = (id: string) =>
export const useSceneDestroy = (input: GQL.SceneDestroyInput) =>
GQL.useSceneDestroyMutation({
variables: input,
update: () => invalidateQueries(sceneMutationImpactedQueries),
update: deleteCache(sceneMutationImpactedQueries),
});
export const useScenesDestroy = (input: GQL.ScenesDestroyInput) =>
GQL.useScenesDestroyMutation({
variables: input,
update: () => invalidateQueries(sceneMutationImpactedQueries),
update: deleteCache(sceneMutationImpactedQueries),
});
export const useSceneGenerateScreenshot = () =>
GQL.useSceneGenerateScreenshotMutation({
update: () => invalidateQueries(["findScenes"]),
update: deleteCache([GQL.FindScenesDocument]),
});
export const studioMutationImpactedQueries = [
"FindStudios",
"FindScenes",
"AllStudios",
"AllStudiosForFilter",
GQL.FindStudiosDocument,
GQL.FindSceneDocument,
GQL.FindScenesDocument,
GQL.AllStudiosForFilterDocument,
];
export const useStudioCreate = (input: GQL.StudioCreateInput) =>
GQL.useStudioCreateMutation({
variables: input,
refetchQueries: studioMutationImpactedQueries,
update: () => invalidateQueries(studioMutationImpactedQueries),
refetchQueries: getQueryNames([GQL.AllStudiosForFilterDocument]),
update: deleteCache([
GQL.FindStudiosDocument,
GQL.AllStudiosForFilterDocument,
]),
});
export const useStudioUpdate = (input: GQL.StudioUpdateInput) =>
GQL.useStudioUpdateMutation({
variables: input,
update: () => invalidateQueries(studioMutationImpactedQueries),
update: deleteCache(studioMutationImpactedQueries),
});
export const useStudioDestroy = (input: GQL.StudioDestroyInput) =>
GQL.useStudioDestroyMutation({
variables: input,
update: () => invalidateQueries(studioMutationImpactedQueries),
update: deleteCache(studioMutationImpactedQueries),
});
export const movieMutationImpactedQueries = [
"findMovies",
"findScenes",
"allMovies",
GQL.FindSceneDocument,
GQL.FindScenesDocument,
GQL.FindMoviesDocument,
GQL.AllMoviesForFilterDocument,
];
export const useMovieCreate = (input: GQL.MovieCreateInput) =>
GQL.useMovieCreateMutation({
variables: input,
update: () => invalidateQueries(movieMutationImpactedQueries),
update: deleteCache([
GQL.FindMoviesDocument,
GQL.AllMoviesForFilterDocument,
]),
});
export const useMovieUpdate = (input: GQL.MovieUpdateInput) =>
GQL.useMovieUpdateMutation({
variables: input,
update: () => invalidateQueries(movieMutationImpactedQueries),
update: deleteCache(movieMutationImpactedQueries),
});
export const useMovieDestroy = (input: GQL.MovieDestroyInput) =>
GQL.useMovieDestroyMutation({
variables: input,
update: () => invalidateQueries(movieMutationImpactedQueries),
update: deleteCache(movieMutationImpactedQueries),
});
export const tagMutationImpactedQueries = [
"findScenes",
"findSceneMarkers",
"sceneMarkerTags",
"allTags",
"findTags",
GQL.FindSceneDocument,
GQL.FindScenesDocument,
GQL.FindSceneMarkersDocument,
GQL.AllTagsDocument,
GQL.AllTagsForFilterDocument,
GQL.FindTagsDocument,
];
export const useTagCreate = (input: GQL.TagCreateInput) =>
GQL.useTagCreateMutation({
variables: input,
refetchQueries: ["AllTags", "AllTagsForFilter", "FindTags"],
update: () => invalidateQueries(tagMutationImpactedQueries),
refetchQueries: getQueryNames([
GQL.AllTagsDocument,
GQL.AllTagsForFilterDocument,
GQL.FindTagsDocument,
]),
update: deleteCache([
GQL.FindTagsDocument,
GQL.AllTagsDocument,
GQL.AllTagsForFilterDocument,
]),
});
export const useTagUpdate = (input: GQL.TagUpdateInput) =>
GQL.useTagUpdateMutation({
variables: input,
refetchQueries: ["AllTags", "AllTagsForFilter", "FindTags"],
update: () => invalidateQueries(tagMutationImpactedQueries),
update: deleteCache(tagMutationImpactedQueries),
});
export const useTagDestroy = (input: GQL.TagDestroyInput) =>
GQL.useTagDestroyMutation({
variables: input,
refetchQueries: ["AllTags", "AllTagsForFilter", "FindTags"],
update: () => invalidateQueries(tagMutationImpactedQueries),
update: deleteCache(tagMutationImpactedQueries),
});
export const useConfigureGeneral = (input: GQL.ConfigGeneralInput) =>
GQL.useConfigureGeneralMutation({
variables: { input },
refetchQueries: ["Configuration"],
refetchQueries: getQueryNames([GQL.ConfigurationDocument]),
update: deleteCache([GQL.ConfigurationDocument]),
});
export const useConfigureInterface = (input: GQL.ConfigInterfaceInput) =>
GQL.useConfigureInterfaceMutation({
variables: { input },
refetchQueries: ["Configuration"],
refetchQueries: getQueryNames([GQL.ConfigurationDocument]),
update: deleteCache([GQL.ConfigurationDocument]),
});
export const useMetadataUpdate = () => GQL.useMetadataUpdateSubscription();
@@ -466,12 +503,10 @@ export const mutateReloadScrapers = () =>
mutation: GQL.ReloadScrapersDocument,
});
const reloadPluginsMutationImpactedQueries = ["plugins", "pluginTasks"];
export const mutateReloadPlugins = () =>
client.mutate<GQL.ReloadPluginsMutation>({
mutation: GQL.ReloadPluginsDocument,
update: () => invalidateQueries(reloadPluginsMutationImpactedQueries),
refetchQueries: [GQL.refetchPluginsQuery(), GQL.refetchPluginTasksQuery()],
});
export const mutateRunPluginTask = (

View File

@@ -1,11 +1,14 @@
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";
import {
ApolloClient,
InMemoryCache,
split,
from,
ServerError,
HttpLink,
} from "@apollo/client";
import { WebSocketLink } from "@apollo/client/link/ws";
import { onError } from "@apollo/client/link/error";
import { getMainDefinition } from "@apollo/client/utilities";
export const getPlatformURL = (ws?: boolean) => {
const platformUrl = new URL(window.location.origin);

View File

@@ -1,7 +1,7 @@
import _ from "lodash";
import queryString from "query-string";
import React, { useCallback, useRef, useState, useEffect } from "react";
import { ApolloError } from "apollo-client";
import { ApolloError } from "@apollo/client";
import { useHistory, useLocation } from "react-router-dom";
import {
SlimSceneDataFragment,
@@ -58,7 +58,7 @@ const getSelectedData = <I extends IDataItem>(
interface IListHookData {
filter: ListFilterModel;
template: JSX.Element;
template: React.ReactElement;
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
}
@@ -88,15 +88,15 @@ interface IListHookOptions<T, E> {
filter: ListFilterModel,
selectedIds: Set<string>,
zoomIndex: number
) => JSX.Element | undefined;
) => React.ReactNode;
renderEditDialog?: (
selected: E[],
onClose: (applied: boolean) => void
) => JSX.Element | undefined;
) => React.ReactNode;
renderDeleteDialog?: (
selected: E[],
onClose: (confirmed: boolean) => void
) => JSX.Element | undefined;
) => React.ReactNode;
addKeybinds?: (
result: T,
filter: ListFilterModel,

View File

@@ -38,7 +38,7 @@ function useLocalForage<T>(
async function runAsync() {
try {
const serialized = await localForage.getItem<string>(key);
const parsed = JSON.parse(serialized);
const parsed = JSON.parse(serialized ?? "null");
if (!Object.is(parsed, null)) {
setData(parsed);
Cache[key] = parsed;

View File

@@ -3,7 +3,7 @@ import { Toast } from "react-bootstrap";
interface IToast {
header?: string;
content: JSX.Element | string;
content: React.ReactNode | string;
delay?: number;
variant?: "success" | "danger" | "warning";
}

View File

@@ -1,5 +1,5 @@
import React from "react";
import { ApolloProvider } from "react-apollo";
import { ApolloProvider } from "@apollo/client";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { App } from "./App";

View File

@@ -10,6 +10,7 @@ const useFocus = () => {
}
};
// eslint-disable-next-line no-undef
return [htmlElRef, setFocus] as const;
};

File diff suppressed because it is too large Load Diff