Files
suwayomi-material-you-webui/src/lib/requests/client/GraphQLClient.ts
schroda 446deeae06 Feature/library manga actions (#506)
* Extend "IMangaGridProps" from "DefaultGridProps"

* Move library manga filtering into hook

* Add logic to select mangas

* Add manga actions

* Remove cache only policy for category mangas

Unclear why this was added

* Prevent SelectionFAB from being hidden by the mobile footer

On e.g. the library page, the footer is visible and thus, the fab was not completely visible

* Update categories of manga only after clicking "OK"

Previously, selecting a category resulted in an immediate mutation.
To be able to reuse the "CategorySelect" component to change the categories of multiple mangas at once, this behaviour is not suited.

* Add action to change categories of multiple mangas

* Cancel selection mode after removal from library

The mangas would still be selected and other action could get performed. However, this should only be possible for mangas that are in the library

* Remove unintentionally added console log

Accidentally added with 980da657d9
2023-12-25 21:37:45 +01:00

164 lines
6.0 KiB
TypeScript

/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { ApolloClient, ApolloClientOptions, InMemoryCache, NormalizedCacheObject, split } from '@apollo/client';
import { createUploadLink } from 'apollo-upload-client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { Client, createClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
import { TypePolicies } from '@apollo/client/cache';
import { BaseClient } from '@/lib/requests/client/BaseClient.ts';
import { StrictTypedTypePolicies } from '@/lib/graphql/generated/apollo-helpers.ts';
/* eslint-disable no-underscore-dangle */
const typePolicies: StrictTypedTypePolicies = {
GlobalMetaType: { keyFields: ['key'] },
ExtensionType: { keyFields: ['pkgName'] },
AboutServerPayload: { keyFields: [] },
AboutWebUI: { keyFields: [] },
WebUIUpdateInfo: { keyFields: [] },
WebUIUpdateCheck: { keyFields: [] },
SettingsType: { keyFields: [] },
DownloadStatus: { keyFields: [] },
DownloadType: { keyFields: ['chapter'] },
Query: {
fields: {
manga(_, { args, toReference }) {
return toReference({
__typename: 'MangaType',
id: args?.id,
});
},
category(_, { args, toReference }) {
return toReference({
__typename: 'CategoryType',
id: args?.id,
});
},
source(_, { args, toReference }) {
return toReference({
__typename: 'SourceType',
id: args?.id,
});
},
extension(_, { args, toReference }) {
return toReference({
__typename: 'ExtensionType',
apkName: args?.pkgName,
});
},
meta(_, { args, toReference }) {
return toReference({
__typename: 'GlobalMetaType',
key: args?.key,
});
},
chapters: {
keyArgs: ['condition', 'filter', 'orderBy', 'orderByType'],
merge(existing, incoming) {
if (existing == null) {
return incoming;
}
const isReFetch = !incoming.pageInfo.hasPreviousPage;
const hasLessItems = existing.nodes.length > incoming.nodes.length;
const useIncomingResponse = isReFetch && !hasLessItems;
if (useIncomingResponse) {
return incoming;
}
const replaceExistingItems = isReFetch && hasLessItems;
if (replaceExistingItems) {
const existingWithReplacedIncoming: typeof incoming = {
...existing,
pageInfo: {
...existing.pageInfo,
startCursor: incoming.pageInfo.startCursor,
},
nodes: [...incoming.nodes, ...existing.nodes.slice(incoming.nodes.length)],
};
return existingWithReplacedIncoming;
}
const existingWithAppendedIncoming: typeof incoming = {
...existing,
pageInfo: {
...existing.pageInfo,
endCursor: incoming.pageInfo.endCursor,
hasNextPage: incoming.pageInfo.hasNextPage,
},
nodes: [...existing.nodes, ...incoming.nodes],
};
return existingWithAppendedIncoming;
},
},
},
},
};
/* eslint-enable no-underscore-dangle */
export class GraphQLClient extends BaseClient<
ApolloClient<NormalizedCacheObject>,
ApolloClientOptions<NormalizedCacheObject>,
null
> {
readonly fetcher = null;
public declare client: ApolloClient<NormalizedCacheObject>;
private wsClient!: Client;
public override getBaseUrl(): string {
return `${super.getBaseUrl()}/api/graphql`;
}
private createUploadLink() {
return createUploadLink({ uri: () => this.getBaseUrl() });
}
private createWSLink() {
return new GraphQLWsLink(this.wsClient);
}
private createLink() {
return split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
this.createWSLink(),
this.createUploadLink(),
);
}
protected createClient() {
this.wsClient = createClient({
url: () => this.getBaseUrl().replace(/http(|s)/g, 'ws'),
keepAlive: 20000,
});
this.client = new ApolloClient({
cache: new InMemoryCache({
// for whatever reason there is some weird TypeError complaining that
// "FieldReadFunction<Reference, Reference, FieldFunctionOptions<SomeObject, Record<string, any>>"
// is not compatible with "FieldReadFunction<any, any, FieldFunctionOptions<Record<string, any, Record<string, any>>"
// Since "typePolicies" is correctly typed as StrictTypedTypePolicies, and it is working as expected,
// the TypeError can just be ignored
typePolicies: typePolicies as TypePolicies,
}),
connectToDevTools: true,
link: this.createLink(),
});
}
public override updateConfig() {}
}