2023-05-18 13:25:19 +02:00
|
|
|
/*
|
|
|
|
|
* 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/.
|
|
|
|
|
*/
|
|
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
import {
|
2023-09-12 02:36:11 +02:00
|
|
|
ApolloError,
|
2023-09-30 16:55:35 +02:00
|
|
|
ApolloQueryResult,
|
2023-09-12 02:36:11 +02:00
|
|
|
DocumentNode,
|
2023-09-01 20:21:59 +02:00
|
|
|
FetchResult,
|
2023-10-02 16:58:32 +02:00
|
|
|
MutationHookOptions as ApolloMutationHookOptions,
|
|
|
|
|
MutationOptions as ApolloMutationOptions,
|
2023-11-24 03:20:16 +01:00
|
|
|
MutationResult,
|
2023-09-01 20:21:59 +02:00
|
|
|
MutationTuple,
|
2023-10-02 16:58:32 +02:00
|
|
|
QueryHookOptions as ApolloQueryHookOptions,
|
|
|
|
|
QueryOptions as ApolloQueryOptions,
|
2023-09-01 20:21:59 +02:00
|
|
|
QueryResult,
|
2023-10-04 22:34:32 +02:00
|
|
|
SubscriptionHookOptions as ApolloSubscriptionHookOptions,
|
|
|
|
|
SubscriptionResult,
|
2023-09-01 20:21:59 +02:00
|
|
|
TypedDocumentNode,
|
|
|
|
|
useMutation,
|
|
|
|
|
useQuery,
|
2023-10-04 22:34:32 +02:00
|
|
|
useSubscription,
|
2023-09-01 20:21:59 +02:00
|
|
|
} from '@apollo/client';
|
2024-12-24 00:28:37 +01:00
|
|
|
import { MaybeMasked, OperationVariables, Reference } from '@apollo/client/core';
|
2023-11-18 19:55:35 +01:00
|
|
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
2025-11-09 01:13:31 +01:00
|
|
|
import { d } from 'koration';
|
2023-10-04 01:48:19 +02:00
|
|
|
import { IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts';
|
2023-09-01 20:21:59 +02:00
|
|
|
import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts';
|
2025-10-15 02:50:43 +02:00
|
|
|
import { BaseClient } from '@/lib/requests/client/BaseClient.ts';
|
2023-09-01 20:24:40 +02:00
|
|
|
import {
|
2023-09-01 20:39:30 +02:00
|
|
|
CategoryOrderBy,
|
2024-07-28 22:21:11 +02:00
|
|
|
ChapterConditionInput,
|
2023-09-30 16:55:35 +02:00
|
|
|
ChapterOrderBy,
|
2023-09-01 20:26:06 +02:00
|
|
|
CheckForServerUpdatesQuery,
|
|
|
|
|
CheckForServerUpdatesQueryVariables,
|
2023-12-09 23:08:27 +01:00
|
|
|
CheckForWebuiUpdateQuery,
|
|
|
|
|
CheckForWebuiUpdateQueryVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
ClearCachedImagesInput,
|
2023-09-01 20:40:24 +02:00
|
|
|
ClearDownloaderMutation,
|
|
|
|
|
ClearDownloaderMutationVariables,
|
2023-12-09 14:16:52 +01:00
|
|
|
ClearServerCacheMutation,
|
|
|
|
|
ClearServerCacheMutationVariables,
|
2023-09-01 20:39:30 +02:00
|
|
|
CreateCategoryInput,
|
|
|
|
|
CreateCategoryMutation,
|
|
|
|
|
CreateCategoryMutationVariables,
|
2024-10-26 17:25:04 +02:00
|
|
|
DeleteCategoryMetadataMutation,
|
|
|
|
|
DeleteCategoryMetadataMutationVariables,
|
2023-09-01 20:39:30 +02:00
|
|
|
DeleteCategoryMutation,
|
|
|
|
|
DeleteCategoryMutationVariables,
|
2024-10-26 17:25:04 +02:00
|
|
|
DeleteChapterMetadataMutation,
|
|
|
|
|
DeleteChapterMetadataMutationVariables,
|
2023-09-01 20:36:40 +02:00
|
|
|
DeleteDownloadedChapterMutation,
|
|
|
|
|
DeleteDownloadedChapterMutationVariables,
|
|
|
|
|
DeleteDownloadedChaptersMutation,
|
|
|
|
|
DeleteDownloadedChaptersMutationVariables,
|
2024-10-26 17:25:04 +02:00
|
|
|
DeleteGlobalMetadataMutation,
|
|
|
|
|
DeleteGlobalMetadataMutationVariables,
|
|
|
|
|
DeleteMangaMetadataMutation,
|
|
|
|
|
DeleteMangaMetadataMutationVariables,
|
|
|
|
|
DeleteSourceMetadataMutation,
|
|
|
|
|
DeleteSourceMetadataMutationVariables,
|
2023-09-01 20:40:24 +02:00
|
|
|
DequeueChapterDownloadMutation,
|
|
|
|
|
DequeueChapterDownloadMutationVariables,
|
|
|
|
|
DequeueChapterDownloadsMutation,
|
|
|
|
|
DequeueChapterDownloadsMutationVariables,
|
2023-10-04 22:34:32 +02:00
|
|
|
DownloadStatusSubscription,
|
|
|
|
|
DownloadStatusSubscriptionVariables,
|
2024-08-21 21:29:01 +02:00
|
|
|
DownloadUpdateType,
|
2023-09-01 20:40:24 +02:00
|
|
|
EnqueueChapterDownloadMutation,
|
|
|
|
|
EnqueueChapterDownloadMutationVariables,
|
|
|
|
|
EnqueueChapterDownloadsMutation,
|
|
|
|
|
EnqueueChapterDownloadsMutationVariables,
|
2023-09-08 00:06:24 +02:00
|
|
|
FetchSourceMangaInput,
|
|
|
|
|
FetchSourceMangaType,
|
|
|
|
|
FilterChangeInput,
|
2023-09-01 20:25:46 +02:00
|
|
|
GetAboutQuery,
|
|
|
|
|
GetAboutQueryVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
GetCategoriesSettingsQuery,
|
|
|
|
|
GetCategoriesSettingsQueryVariables,
|
2023-10-27 20:15:03 +02:00
|
|
|
GetCategoryMangasQuery,
|
|
|
|
|
GetCategoryMangasQueryVariables,
|
2023-09-30 16:55:35 +02:00
|
|
|
GetChapterPagesFetchMutation,
|
|
|
|
|
GetChapterPagesFetchMutationVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
GetChaptersMangaQuery,
|
|
|
|
|
GetChaptersMangaQueryVariables,
|
|
|
|
|
GetChaptersUpdatesQuery,
|
|
|
|
|
GetChaptersUpdatesQueryVariables,
|
Add history page (#897)
* Add History module and integrate it into the app
Introduce the History module with components for displaying chapter updates, including UI for grouped updates by date. Update routing and constants to incorporate the new /history path.
* Replace "Updates" and "History" with unified "Recent" module
Merged the "Updates" and "History" screens into a single unified "Recent" module with corresponding navigation updates. Adjusted GraphQL types and localization files to reflect the changes. This simplifies navigation and improves user experience.
* Refactor History screen to remove unused components.
Removed unused states, layout effects, and props related to last update timestamp. Renamed variables for better clarity and updated labels and error messages to align with the history context. Simplified component structure for improved maintainability.
* ```
Refactor tab handling and integrate dynamic height adjustments.
Renamed enums for better clarity and consistency. Implemented a `tabsMenuHeight` prop to dynamically adjust component layouts based on observed height changes using `useResizeObserver`. Updated the `Updates` component to consider the calculated heights for improved rendering.
```
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* "Optimize GraphQL type definition formatting in generated file
Condensed the formatting of the GetChaptersHistoryQuery type to a single line for improved readability and consistency. This change does not affect functionality but enhances maintainability of the generated code."
* Remove UpdateChecker component from History screen
The UpdateChecker component and its related navbar action have been removed from the History screen. This simplifies the navbar setup and eliminates unused functionality in this context.
* Refactor chapter fragment and type references
Updated `ChapterRecentListFieldsFragment` to `ChapterUpdateListFieldsFragment` for better alignment with naming conventions. Adjusted type imports and query structures to maintain consistency across files. No functional changes introduced.
* Refactor padding calculation logic in Updates screen.
Renamed state variable and refactored its logic for clarity and consistency. The updated logic now uses a more descriptive name, `listPadding`, to enhance code readability and maintainability.
* Pass `tabsMenuHeight` prop to History component
Added `tabsMenuHeight` prop to the `History` component to properly calculate the height for the `StyledGroupedVirtuoso` element. This ensures consistency with other components and improves layout rendering.
* Revert package.json
* Add last page read tracking to chapter history
Introduced "lastPageRead" and "pageCount" fields to track progress in chapter history. Updated UI to display the last page read along with total pages for chapters. Adjusted localization files to support the new "page" label.
* Remove last page read display from ChapterHistoryCard
The "last page read" information was removed from the ChapterHistoryCard component for a cleaner UI. This change simplifies the card's design by showing only the chapter name and relevant download state.
* Forcing an empty commit.
* Replace "Recent" screen with separate "Updates" and "History".
The "Recent" feature has been split into two distinct screens, "Updates" and "History", for improved clarity and functionality. Routes, icons, and components have been updated accordingly, and redundant code related to "Recent" has been removed.
* Clean
* Revert vi.json
* Remove unnecessary `useLocation` hook and update state logic.
The `useLocation` hook was removed as it was not used elsewhere in the component. The state property for the routing logic was updated to use `Chapters.getReaderOpenChapterLocationState` for better encapsulation and clarity.
* Update history localization by removing unused "page" key
Removed the "page" key from the "history" section in the localization file as it is no longer in use. This cleanup helps keep the translation file consistent and easier to maintain.
---------
Co-authored-by: NamNH <namitonguyen@gmail.com>
2025-03-11 03:53:40 +07:00
|
|
|
GetChaptersHistoryQuery,
|
|
|
|
|
GetChaptersHistoryQueryVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
GetDownloadStatusQuery,
|
|
|
|
|
GetDownloadStatusQueryVariables,
|
2023-10-16 01:38:53 +02:00
|
|
|
GetExtensionsFetchMutation,
|
|
|
|
|
GetExtensionsFetchMutationVariables,
|
2023-09-01 20:28:23 +02:00
|
|
|
GetExtensionsQuery,
|
|
|
|
|
GetExtensionsQueryVariables,
|
2023-09-01 20:24:40 +02:00
|
|
|
GetGlobalMetadatasQuery,
|
|
|
|
|
GetGlobalMetadatasQueryVariables,
|
2023-11-19 22:43:29 +01:00
|
|
|
GetLastUpdateTimestampQuery,
|
|
|
|
|
GetLastUpdateTimestampQueryVariables,
|
2023-09-30 16:55:35 +02:00
|
|
|
GetMangaChaptersFetchMutation,
|
|
|
|
|
GetMangaChaptersFetchMutationVariables,
|
2023-09-08 00:44:01 +02:00
|
|
|
GetMangaFetchMutation,
|
|
|
|
|
GetMangaFetchMutationVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
GetMangasChapterIdsWithStateQuery,
|
|
|
|
|
GetMangasChapterIdsWithStateQueryVariables,
|
|
|
|
|
GetMangasLibraryQuery,
|
|
|
|
|
GetMangasLibraryQueryVariables,
|
|
|
|
|
GetMangaToMigrateQuery,
|
|
|
|
|
GetMangaToMigrateQueryVariables,
|
|
|
|
|
GetMangaToMigrateToFetchMutation,
|
|
|
|
|
GetMangaToMigrateToFetchMutationVariables,
|
|
|
|
|
GetMigratableSourceMangasQuery,
|
|
|
|
|
GetMigratableSourceMangasQueryVariables,
|
|
|
|
|
GetMigratableSourcesQuery,
|
|
|
|
|
GetMigratableSourcesQueryVariables,
|
2023-10-29 01:49:27 +02:00
|
|
|
GetRestoreStatusQuery,
|
|
|
|
|
GetRestoreStatusQueryVariables,
|
2023-10-27 23:39:05 +02:00
|
|
|
GetServerSettingsQuery,
|
2024-07-28 22:21:11 +02:00
|
|
|
GetServerSettingsQueryVariables,
|
2023-09-08 00:06:24 +02:00
|
|
|
GetSourceMangasFetchMutation,
|
|
|
|
|
GetSourceMangasFetchMutationVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
GetSourcesListQuery,
|
|
|
|
|
GetSourcesListQueryVariables,
|
2023-09-01 20:40:39 +02:00
|
|
|
GetUpdateStatusQuery,
|
|
|
|
|
GetUpdateStatusQueryVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
GetWebuiUpdateStatusQuery,
|
|
|
|
|
GetWebuiUpdateStatusQueryVariables,
|
2023-09-01 20:28:23 +02:00
|
|
|
InstallExternalExtensionMutation,
|
|
|
|
|
InstallExternalExtensionMutationVariables,
|
2023-09-01 20:40:24 +02:00
|
|
|
ReorderChapterDownloadMutation,
|
|
|
|
|
ReorderChapterDownloadMutationVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
ResetWebuiUpdateStatusMutation,
|
|
|
|
|
ResetWebuiUpdateStatusMutationVariables,
|
2023-10-04 01:17:10 +02:00
|
|
|
RestoreBackupMutation,
|
|
|
|
|
RestoreBackupMutationVariables,
|
2023-09-01 20:39:30 +02:00
|
|
|
SetCategoryMetadataMutation,
|
|
|
|
|
SetCategoryMetadataMutationVariables,
|
2023-09-01 20:36:40 +02:00
|
|
|
SetChapterMetadataMutation,
|
|
|
|
|
SetChapterMetadataMutationVariables,
|
2023-09-01 20:24:40 +02:00
|
|
|
SetGlobalMetadataMutation,
|
|
|
|
|
SetGlobalMetadataMutationVariables,
|
2023-09-01 20:33:35 +02:00
|
|
|
SetMangaMetadataMutation,
|
2023-10-09 20:41:58 +02:00
|
|
|
SetMangaMetadataMutationVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
SetSourceMetadataMutation,
|
|
|
|
|
SetSourceMetadataMutationVariables,
|
2024-09-26 13:59:37 +02:00
|
|
|
SettingsType,
|
2023-09-30 16:55:35 +02:00
|
|
|
SortOrder,
|
2023-09-25 23:32:26 +02:00
|
|
|
SourcePreferenceChangeInput,
|
2023-09-01 20:40:24 +02:00
|
|
|
StartDownloaderMutation,
|
|
|
|
|
StartDownloaderMutationVariables,
|
|
|
|
|
StopDownloaderMutation,
|
|
|
|
|
StopDownloaderMutationVariables,
|
2023-09-01 20:40:39 +02:00
|
|
|
StopUpdaterMutation,
|
|
|
|
|
StopUpdaterMutationVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
TrackerBindMutation,
|
|
|
|
|
TrackerBindMutationVariables,
|
|
|
|
|
TrackerFetchBindMutation,
|
|
|
|
|
TrackerFetchBindMutationVariables,
|
|
|
|
|
TrackerLoginCredentialsMutation,
|
|
|
|
|
TrackerLoginCredentialsMutationVariables,
|
|
|
|
|
TrackerLoginOauthMutation,
|
|
|
|
|
TrackerLoginOauthMutationVariables,
|
|
|
|
|
TrackerLogoutMutation,
|
|
|
|
|
TrackerLogoutMutationVariables,
|
|
|
|
|
TrackerSearchQuery,
|
|
|
|
|
TrackerSearchQueryVariables,
|
|
|
|
|
TrackerUnbindMutation,
|
|
|
|
|
TrackerUnbindMutationVariables,
|
|
|
|
|
TrackerUpdateBindMutation,
|
|
|
|
|
TrackerUpdateBindMutationVariables,
|
2023-09-01 20:39:30 +02:00
|
|
|
UpdateCategoryMutation,
|
|
|
|
|
UpdateCategoryMutationVariables,
|
|
|
|
|
UpdateCategoryOrderMutation,
|
|
|
|
|
UpdateCategoryOrderMutationVariables,
|
|
|
|
|
UpdateCategoryPatchInput,
|
2023-09-01 20:36:40 +02:00
|
|
|
UpdateChapterMutation,
|
|
|
|
|
UpdateChapterMutationVariables,
|
|
|
|
|
UpdateChapterPatchInput,
|
|
|
|
|
UpdateChaptersMutation,
|
|
|
|
|
UpdateChaptersMutationVariables,
|
2023-09-01 20:28:23 +02:00
|
|
|
UpdateExtensionMutation,
|
|
|
|
|
UpdateExtensionMutationVariables,
|
|
|
|
|
UpdateExtensionPatchInput,
|
2024-11-04 01:26:58 +01:00
|
|
|
UpdateExtensionsMutation,
|
|
|
|
|
UpdateExtensionsMutationVariables,
|
2023-09-01 20:33:35 +02:00
|
|
|
UpdateMangaCategoriesMutation,
|
|
|
|
|
UpdateMangaCategoriesMutationVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
UpdateMangaCategoriesPatchInput,
|
2023-09-01 20:33:35 +02:00
|
|
|
UpdateMangaMutation,
|
|
|
|
|
UpdateMangaMutationVariables,
|
|
|
|
|
UpdateMangaPatchInput,
|
2024-07-28 22:21:11 +02:00
|
|
|
UpdateMangasCategoriesMutation,
|
|
|
|
|
UpdateMangasCategoriesMutationVariables,
|
|
|
|
|
UpdateMangasMutation,
|
|
|
|
|
UpdateMangasMutationVariables,
|
2023-10-04 22:34:32 +02:00
|
|
|
UpdaterSubscription,
|
|
|
|
|
UpdaterSubscriptionVariables,
|
2023-10-27 23:39:05 +02:00
|
|
|
UpdateServerSettingsMutation,
|
|
|
|
|
UpdateServerSettingsMutationVariables,
|
2023-09-25 23:32:26 +02:00
|
|
|
UpdateSourcePreferencesMutation,
|
|
|
|
|
UpdateSourcePreferencesMutationVariables,
|
2024-07-28 22:21:11 +02:00
|
|
|
UpdateTrackInput,
|
2023-12-09 23:08:27 +01:00
|
|
|
UpdateWebuiMutation,
|
|
|
|
|
UpdateWebuiMutationVariables,
|
2023-10-04 01:17:10 +02:00
|
|
|
ValidateBackupQuery,
|
|
|
|
|
ValidateBackupQueryVariables,
|
2023-12-09 23:08:27 +01:00
|
|
|
WebuiUpdateSubscription,
|
2025-03-16 21:01:24 +01:00
|
|
|
UpdateLibraryMutation,
|
|
|
|
|
UpdateLibraryMutationVariables,
|
2025-06-22 23:12:54 +02:00
|
|
|
GetExtensionQuery,
|
|
|
|
|
GetExtensionQueryVariables,
|
2025-06-28 00:43:41 +02:00
|
|
|
DownloaderState,
|
2025-07-27 00:03:51 +02:00
|
|
|
UserLoginMutation,
|
|
|
|
|
UserLoginMutationVariables,
|
|
|
|
|
UserRefreshMutation,
|
|
|
|
|
UserRefreshMutationVariables,
|
2025-10-02 20:04:00 +02:00
|
|
|
CreateBackupInput,
|
|
|
|
|
CreateBackupMutation,
|
|
|
|
|
CreateBackupMutationVariables,
|
2025-10-04 21:48:44 +02:00
|
|
|
RestoreBackupInput,
|
2023-09-01 20:24:40 +02:00
|
|
|
} from '@/lib/graphql/generated/graphql.ts';
|
2023-10-16 00:05:51 +02:00
|
|
|
import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
|
2024-10-26 17:25:04 +02:00
|
|
|
import { DELETE_GLOBAL_METADATA, SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
|
2024-01-01 22:40:39 +01:00
|
|
|
import {
|
|
|
|
|
CHECK_FOR_SERVER_UPDATES,
|
|
|
|
|
CHECK_FOR_WEBUI_UPDATE,
|
|
|
|
|
GET_ABOUT,
|
|
|
|
|
GET_WEBUI_UPDATE_STATUS,
|
|
|
|
|
} from '@/lib/graphql/queries/ServerInfoQuery.ts';
|
2025-06-22 23:12:54 +02:00
|
|
|
import { GET_EXTENSION, GET_EXTENSIONS } from '@/lib/graphql/queries/ExtensionQuery.ts';
|
2023-09-01 20:28:23 +02:00
|
|
|
import {
|
|
|
|
|
GET_EXTENSIONS_FETCH,
|
|
|
|
|
INSTALL_EXTERNAL_EXTENSION,
|
|
|
|
|
UPDATE_EXTENSION,
|
2024-11-04 01:26:58 +01:00
|
|
|
UPDATE_EXTENSIONS,
|
2023-09-01 20:28:23 +02:00
|
|
|
} from '@/lib/graphql/mutations/ExtensionMutation.ts';
|
2024-07-03 23:15:50 +02:00
|
|
|
import { GET_MIGRATABLE_SOURCES, GET_SOURCES_LIST } from '@/lib/graphql/queries/SourceQuery.ts';
|
2023-09-08 00:44:01 +02:00
|
|
|
import {
|
2024-10-26 17:25:04 +02:00
|
|
|
DELETE_MANGA_METADATA,
|
2023-09-08 00:44:01 +02:00
|
|
|
GET_MANGA_FETCH,
|
2024-01-26 20:50:51 +01:00
|
|
|
GET_MANGA_TO_MIGRATE_TO_FETCH,
|
2023-09-08 00:44:01 +02:00
|
|
|
SET_MANGA_METADATA,
|
|
|
|
|
UPDATE_MANGA,
|
|
|
|
|
UPDATE_MANGA_CATEGORIES,
|
2023-12-25 21:37:45 +01:00
|
|
|
UPDATE_MANGAS,
|
|
|
|
|
UPDATE_MANGAS_CATEGORIES,
|
2023-09-08 00:44:01 +02:00
|
|
|
} from '@/lib/graphql/mutations/MangaMutation.ts';
|
2024-01-26 20:50:51 +01:00
|
|
|
import {
|
|
|
|
|
GET_MANGA_TO_MIGRATE,
|
2024-07-08 18:02:50 +02:00
|
|
|
GET_MANGA_TRACK_RECORDS,
|
|
|
|
|
GET_MANGAS_LIBRARY,
|
2024-01-26 20:50:51 +01:00
|
|
|
GET_MIGRATABLE_SOURCE_MANGAS,
|
|
|
|
|
} from '@/lib/graphql/queries/MangaQuery.ts';
|
2024-07-01 21:40:27 +02:00
|
|
|
import {
|
|
|
|
|
GET_CATEGORIES_BASE,
|
|
|
|
|
GET_CATEGORIES_LIBRARY,
|
|
|
|
|
GET_CATEGORIES_SETTINGS,
|
|
|
|
|
GET_CATEGORY_MANGAS,
|
|
|
|
|
} from '@/lib/graphql/queries/CategoryQuery.ts';
|
2024-05-08 04:01:23 +02:00
|
|
|
import {
|
2024-10-26 17:25:04 +02:00
|
|
|
DELETE_SOURCE_METADATA,
|
2024-05-08 04:01:23 +02:00
|
|
|
GET_SOURCE_MANGAS_FETCH,
|
|
|
|
|
SET_SOURCE_METADATA,
|
|
|
|
|
UPDATE_SOURCE_PREFERENCES,
|
|
|
|
|
} from '@/lib/graphql/mutations/SourceMutation.ts';
|
2023-09-01 20:40:24 +02:00
|
|
|
import {
|
|
|
|
|
CLEAR_DOWNLOADER,
|
|
|
|
|
DELETE_DOWNLOADED_CHAPTER,
|
|
|
|
|
DELETE_DOWNLOADED_CHAPTERS,
|
|
|
|
|
DEQUEUE_CHAPTER_DOWNLOAD,
|
|
|
|
|
DEQUEUE_CHAPTER_DOWNLOADS,
|
|
|
|
|
ENQUEUE_CHAPTER_DOWNLOAD,
|
|
|
|
|
ENQUEUE_CHAPTER_DOWNLOADS,
|
|
|
|
|
REORDER_CHAPTER_DOWNLOAD,
|
|
|
|
|
START_DOWNLOADER,
|
|
|
|
|
STOP_DOWNLOADER,
|
|
|
|
|
} from '@/lib/graphql/mutations/DownloaderMutation.ts';
|
2024-07-02 16:20:28 +02:00
|
|
|
import {
|
Add history page (#897)
* Add History module and integrate it into the app
Introduce the History module with components for displaying chapter updates, including UI for grouped updates by date. Update routing and constants to incorporate the new /history path.
* Replace "Updates" and "History" with unified "Recent" module
Merged the "Updates" and "History" screens into a single unified "Recent" module with corresponding navigation updates. Adjusted GraphQL types and localization files to reflect the changes. This simplifies navigation and improves user experience.
* Refactor History screen to remove unused components.
Removed unused states, layout effects, and props related to last update timestamp. Renamed variables for better clarity and updated labels and error messages to align with the history context. Simplified component structure for improved maintainability.
* ```
Refactor tab handling and integrate dynamic height adjustments.
Renamed enums for better clarity and consistency. Implemented a `tabsMenuHeight` prop to dynamically adjust component layouts based on observed height changes using `useResizeObserver`. Updated the `Updates` component to consider the calculated heights for improved rendering.
```
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* "Optimize GraphQL type definition formatting in generated file
Condensed the formatting of the GetChaptersHistoryQuery type to a single line for improved readability and consistency. This change does not affect functionality but enhances maintainability of the generated code."
* Remove UpdateChecker component from History screen
The UpdateChecker component and its related navbar action have been removed from the History screen. This simplifies the navbar setup and eliminates unused functionality in this context.
* Refactor chapter fragment and type references
Updated `ChapterRecentListFieldsFragment` to `ChapterUpdateListFieldsFragment` for better alignment with naming conventions. Adjusted type imports and query structures to maintain consistency across files. No functional changes introduced.
* Refactor padding calculation logic in Updates screen.
Renamed state variable and refactored its logic for clarity and consistency. The updated logic now uses a more descriptive name, `listPadding`, to enhance code readability and maintainability.
* Pass `tabsMenuHeight` prop to History component
Added `tabsMenuHeight` prop to the `History` component to properly calculate the height for the `StyledGroupedVirtuoso` element. This ensures consistency with other components and improves layout rendering.
* Revert package.json
* Add last page read tracking to chapter history
Introduced "lastPageRead" and "pageCount" fields to track progress in chapter history. Updated UI to display the last page read along with total pages for chapters. Adjusted localization files to support the new "page" label.
* Remove last page read display from ChapterHistoryCard
The "last page read" information was removed from the ChapterHistoryCard component for a cleaner UI. This change simplifies the card's design by showing only the chapter name and relevant download state.
* Forcing an empty commit.
* Replace "Recent" screen with separate "Updates" and "History".
The "Recent" feature has been split into two distinct screens, "Updates" and "History", for improved clarity and functionality. Routes, icons, and components have been updated accordingly, and redundant code related to "Recent" has been removed.
* Clean
* Revert vi.json
* Remove unnecessary `useLocation` hook and update state logic.
The `useLocation` hook was removed as it was not used elsewhere in the component. The state property for the routing logic was updated to use `Chapters.getReaderOpenChapterLocationState` for better encapsulation and clarity.
* Update history localization by removing unused "page" key
Removed the "page" key from the "history" section in the localization file as it is no longer in use. This cleanup helps keep the translation file consistent and easier to maintain.
---------
Co-authored-by: NamNH <namitonguyen@gmail.com>
2025-03-11 03:53:40 +07:00
|
|
|
GET_CHAPTERS_HISTORY,
|
2024-07-02 16:20:28 +02:00
|
|
|
GET_CHAPTERS_MANGA,
|
|
|
|
|
GET_CHAPTERS_UPDATES,
|
|
|
|
|
GET_MANGAS_CHAPTER_IDS_WITH_STATE,
|
|
|
|
|
} from '@/lib/graphql/queries/ChapterQuery.ts';
|
2023-09-30 16:55:35 +02:00
|
|
|
import {
|
2024-10-26 17:25:04 +02:00
|
|
|
DELETE_CHAPTER_METADATA,
|
2023-09-30 16:55:35 +02:00
|
|
|
GET_CHAPTER_PAGES_FETCH,
|
|
|
|
|
GET_MANGA_CHAPTERS_FETCH,
|
|
|
|
|
SET_CHAPTER_METADATA,
|
|
|
|
|
UPDATE_CHAPTER,
|
|
|
|
|
UPDATE_CHAPTERS,
|
|
|
|
|
} from '@/lib/graphql/mutations/ChapterMutation.ts';
|
2023-09-01 20:39:30 +02:00
|
|
|
import {
|
|
|
|
|
CREATE_CATEGORY,
|
|
|
|
|
DELETE_CATEGORY,
|
2024-10-26 17:25:04 +02:00
|
|
|
DELETE_CATEGORY_METADATA,
|
2023-09-01 20:39:30 +02:00
|
|
|
SET_CATEGORY_METADATA,
|
|
|
|
|
UPDATE_CATEGORY,
|
|
|
|
|
UPDATE_CATEGORY_ORDER,
|
|
|
|
|
} from '@/lib/graphql/mutations/CategoryMutation.ts';
|
2025-03-16 21:01:24 +01:00
|
|
|
import { STOP_UPDATER, UPDATE_LIBRARY } from '@/lib/graphql/mutations/UpdaterMutation.ts';
|
2023-11-19 22:43:29 +01:00
|
|
|
import { GET_LAST_UPDATE_TIMESTAMP, GET_UPDATE_STATUS } from '@/lib/graphql/queries/UpdaterQuery.ts';
|
2024-10-05 23:22:42 +02:00
|
|
|
import { CustomCache } from '@/lib/storage/CustomCache.ts';
|
2025-10-02 20:04:00 +02:00
|
|
|
import { CREATE_BACKUP, RESTORE_BACKUP } from '@/lib/graphql/mutations/BackupMutation.ts';
|
2023-10-29 01:49:27 +02:00
|
|
|
import { GET_RESTORE_STATUS, VALIDATE_BACKUP } from '@/lib/graphql/queries/BackupQuery.ts';
|
2023-10-04 22:34:32 +02:00
|
|
|
import { DOWNLOAD_STATUS_SUBSCRIPTION } from '@/lib/graphql/subscriptions/DownloaderSubscription.ts';
|
|
|
|
|
import { UPDATER_SUBSCRIPTION } from '@/lib/graphql/subscriptions/UpdaterSubscription.ts';
|
2023-10-27 23:39:05 +02:00
|
|
|
import { GET_SERVER_SETTINGS } from '@/lib/graphql/queries/SettingsQuery.ts';
|
|
|
|
|
import { UPDATE_SERVER_SETTINGS } from '@/lib/graphql/mutations/SettingsMutation.ts';
|
2023-12-09 14:16:52 +01:00
|
|
|
import { CLEAR_SERVER_CACHE } from '@/lib/graphql/mutations/ImageMutation.ts';
|
2023-12-09 23:08:27 +01:00
|
|
|
import { RESET_WEBUI_UPDATE_STATUS, UPDATE_WEBUI } from '@/lib/graphql/mutations/ServerInfoMutation.ts';
|
|
|
|
|
import { WEBUI_UPDATE_SUBSCRIPTION } from '@/lib/graphql/subscriptions/ServerInfoSubscription.ts';
|
2023-12-13 02:05:03 +01:00
|
|
|
import { GET_DOWNLOAD_STATUS } from '@/lib/graphql/queries/DownloaderQuery.ts';
|
2024-10-05 16:09:34 +02:00
|
|
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
2024-03-24 19:08:36 +01:00
|
|
|
import { Queue, QueuePriority } from '@/lib/Queue.ts';
|
2024-07-04 22:00:39 +02:00
|
|
|
import { TRACKER_SEARCH } from '@/lib/graphql/queries/TrackerQuery.ts';
|
2024-02-16 19:55:54 +01:00
|
|
|
import {
|
2024-03-10 17:36:22 +01:00
|
|
|
TRACKER_BIND,
|
2024-04-01 02:06:35 +02:00
|
|
|
TRACKER_FETCH_BIND,
|
2024-02-16 19:55:54 +01:00
|
|
|
TRACKER_LOGIN_CREDENTIALS,
|
|
|
|
|
TRACKER_LOGIN_OAUTH,
|
|
|
|
|
TRACKER_LOGOUT,
|
2024-04-01 00:45:44 +02:00
|
|
|
TRACKER_UNBIND,
|
2024-03-17 18:42:48 +01:00
|
|
|
TRACKER_UPDATE_BIND,
|
2024-02-16 19:55:54 +01:00
|
|
|
} from '@/lib/graphql/mutations/TrackerMutation.ts';
|
2024-03-28 22:40:55 +01:00
|
|
|
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
2024-07-06 15:28:27 +02:00
|
|
|
import { DOWNLOAD_STATUS_FIELDS } from '@/lib/graphql/fragments/DownloadFragments.ts';
|
2024-07-06 15:55:58 +02:00
|
|
|
import { EXTENSION_LIST_FIELDS } from '@/lib/graphql/fragments/ExtensionFragments.ts';
|
2024-09-22 02:46:01 +02:00
|
|
|
import { MANGA_BASE_FIELDS, MANGA_META_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
2024-10-05 16:09:34 +02:00
|
|
|
import { GLOBAL_METADATA } from '@/lib/graphql/fragments/Fragments.ts';
|
2024-09-22 02:46:01 +02:00
|
|
|
import { CATEGORY_META_FIELDS } from '@/lib/graphql/fragments/CategoryFragments.ts';
|
|
|
|
|
import { SOURCE_META_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
|
|
|
|
import { CHAPTER_META_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts';
|
2025-08-15 22:02:58 +02:00
|
|
|
import { MetadataMigrationSettings } from '@/features/migration/Migration.types.ts';
|
|
|
|
|
import { MangaIdInfo } from '@/features/manga/Manga.types.ts';
|
|
|
|
|
import { updateMetadataList } from '@/features/metadata/services/MetadataApolloCacheHandler.ts';
|
2025-07-27 00:03:51 +02:00
|
|
|
import { USER_LOGIN, USER_REFRESH } from '@/lib/graphql/mutations/UserMutation.ts';
|
|
|
|
|
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
2025-10-19 20:22:31 +02:00
|
|
|
import { useLocalStorage } from '@/base/hooks/useStorage.tsx';
|
2025-11-22 16:56:53 +01:00
|
|
|
import { ImageCache } from '@/lib/service-worker/ImageCache.ts';
|
2023-05-18 13:25:19 +02:00
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
enum GQLMethod {
|
2023-09-30 16:55:35 +02:00
|
|
|
QUERY = 'QUERY',
|
2023-09-01 20:21:59 +02:00
|
|
|
USE_QUERY = 'USE_QUERY',
|
|
|
|
|
USE_MUTATION = 'USE_MUTATION',
|
|
|
|
|
MUTATION = 'MUTATION',
|
2023-10-04 22:34:32 +02:00
|
|
|
USE_SUBSCRIPTION = 'USE_SUBSCRIPTION',
|
2023-09-01 20:21:59 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-02 16:58:32 +02:00
|
|
|
type CustomApolloOptions = {
|
|
|
|
|
/**
|
|
|
|
|
* This is a workaround for an apollo bug (?).
|
|
|
|
|
*
|
|
|
|
|
* A new abort signal gets passed on every hook call.
|
|
|
|
|
* This causes the passed arguments to change (due to updating the "context" option, which is only relevant for the actual request),
|
|
|
|
|
* which - I assume - results in apollo to handle this as a completely new hook call.
|
|
|
|
|
* Due to this, when e.g. calling "fetchMore", "loading" and "networkStatus" do not get updated when enabling "notifyOnNetworkStatusChange".
|
|
|
|
|
*
|
2023-11-11 23:42:58 +01:00
|
|
|
* It also causes apollo to spam requests in case of request failures on every rerender.
|
|
|
|
|
*
|
|
|
|
|
* Instead of adding the abort signal by default, it has to be added manually which will cause these stated issues (and potentially more?)
|
2023-10-02 16:58:32 +02:00
|
|
|
*/
|
2023-11-11 23:42:58 +01:00
|
|
|
addAbortSignal?: boolean;
|
2023-10-02 16:58:32 +02:00
|
|
|
};
|
2023-10-04 22:34:32 +02:00
|
|
|
type QueryOptions<Variables extends OperationVariables = OperationVariables, Data = any> = Partial<
|
|
|
|
|
ApolloQueryOptions<Variables, Data>
|
2023-10-02 16:58:32 +02:00
|
|
|
> &
|
|
|
|
|
CustomApolloOptions;
|
2023-10-04 22:34:32 +02:00
|
|
|
type QueryHookOptions<Data = any, Variables extends OperationVariables = OperationVariables> = Partial<
|
|
|
|
|
ApolloQueryHookOptions<Data, Variables>
|
2023-10-02 16:58:32 +02:00
|
|
|
> &
|
|
|
|
|
CustomApolloOptions;
|
|
|
|
|
type MutationHookOptions<Data = any, Variables extends OperationVariables = OperationVariables> = Partial<
|
|
|
|
|
ApolloMutationHookOptions<Data, Variables>
|
|
|
|
|
> &
|
|
|
|
|
CustomApolloOptions;
|
|
|
|
|
type MutationOptions<Data = any, Variables extends OperationVariables = OperationVariables> = Partial<
|
|
|
|
|
ApolloMutationOptions<Data, Variables>
|
|
|
|
|
> &
|
|
|
|
|
CustomApolloOptions;
|
2023-10-04 22:34:32 +02:00
|
|
|
type ApolloPaginatedMutationOptions<Data = any, Variables extends OperationVariables = OperationVariables> = Partial<
|
|
|
|
|
MutationHookOptions<Data, Variables>
|
|
|
|
|
> & { skipRequest?: boolean };
|
|
|
|
|
type SubscriptionHookOptions<Data = any, Variables extends OperationVariables = OperationVariables> = Partial<
|
|
|
|
|
ApolloSubscriptionHookOptions<Data, Variables>
|
|
|
|
|
> &
|
2023-11-11 23:42:58 +01:00
|
|
|
Omit<CustomApolloOptions, 'addAbortSignal'> & { addAbortSignal?: never };
|
2023-09-12 02:36:11 +02:00
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
type AbortableRequest = { abortRequest: AbortController['abort'] };
|
|
|
|
|
|
2025-11-22 16:56:53 +01:00
|
|
|
export type ImageRequest = { response: Promise<string>; cleanup: () => void; fromCache: boolean } & AbortableRequest;
|
2025-11-22 17:28:56 +01:00
|
|
|
type ImageRequestOptions = {
|
|
|
|
|
priority?: QueuePriority;
|
|
|
|
|
shouldDecode?: boolean;
|
|
|
|
|
disableCors?: boolean;
|
|
|
|
|
ignoreQueue?: boolean;
|
|
|
|
|
};
|
2024-03-28 22:40:55 +01:00
|
|
|
|
2023-09-30 16:55:35 +02:00
|
|
|
export type AbortabaleApolloQueryResponse<Data = any> = {
|
2024-12-24 00:28:37 +01:00
|
|
|
response: Promise<ApolloQueryResult<MaybeMasked<Data>>>;
|
2023-09-30 16:55:35 +02:00
|
|
|
} & AbortableRequest;
|
2023-09-12 02:36:11 +02:00
|
|
|
export type AbortableApolloUseQueryResponse<
|
2023-09-01 20:21:59 +02:00
|
|
|
Data = any,
|
|
|
|
|
Variables extends OperationVariables = OperationVariables,
|
2024-12-24 00:28:37 +01:00
|
|
|
> = QueryResult<MaybeMasked<Data>, Variables> & AbortableRequest;
|
2023-09-12 02:36:11 +02:00
|
|
|
export type AbortableApolloUseMutationResponse<
|
|
|
|
|
Data = any,
|
|
|
|
|
Variables extends OperationVariables = OperationVariables,
|
|
|
|
|
> = [MutationTuple<Data, Variables>[0], MutationTuple<Data, Variables>[1] & AbortableRequest];
|
|
|
|
|
export type AbortableApolloUseMutationPaginatedResponse<
|
2023-09-08 00:06:24 +02:00
|
|
|
Data = any,
|
|
|
|
|
Variables extends OperationVariables = OperationVariables,
|
|
|
|
|
> = [
|
2024-12-24 00:28:37 +01:00
|
|
|
(page: number) => Promise<FetchResult<MaybeMasked<Data>>>,
|
2023-09-12 02:36:11 +02:00
|
|
|
(Omit<MutationTuple<Data, Variables>[1], 'loading'> &
|
|
|
|
|
AbortableRequest & {
|
|
|
|
|
size: number;
|
|
|
|
|
/**
|
|
|
|
|
* Indicates whether any request is currently active.
|
|
|
|
|
* In case only "isLoading" is true, it means that it's the initial request
|
|
|
|
|
*/
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
/**
|
|
|
|
|
* Indicates if a next page is being fetched, which is not part of the initial pages
|
|
|
|
|
*/
|
|
|
|
|
isLoadingMore: boolean;
|
|
|
|
|
/**
|
|
|
|
|
* Indicates if the cached pages are currently getting revalidated
|
|
|
|
|
*/
|
|
|
|
|
isValidating: boolean;
|
|
|
|
|
})[],
|
2023-09-08 00:06:24 +02:00
|
|
|
];
|
2024-12-24 00:28:37 +01:00
|
|
|
export type AbortableApolloMutationResponse<Data = any> = {
|
|
|
|
|
response: Promise<FetchResult<MaybeMasked<Data>>>;
|
|
|
|
|
} & AbortableRequest;
|
2023-09-01 20:21:59 +02:00
|
|
|
|
2023-11-05 22:37:28 +01:00
|
|
|
const EXTENSION_LIST_CACHE_KEY = 'useExtensionListFetch';
|
|
|
|
|
|
2023-11-11 23:43:22 +01:00
|
|
|
const CACHE_INITIAL_PAGES_FETCHING_KEY = 'GET_SOURCE_MANGAS_FETCH_FETCHING_INITIAL_PAGES';
|
|
|
|
|
const CACHE_PAGES_KEY = 'GET_SOURCE_MANGAS_FETCH_PAGES';
|
|
|
|
|
const CACHE_RESULTS_KEY = 'GET_SOURCE_MANGAS_FETCH';
|
|
|
|
|
|
|
|
|
|
export const SPECIAL_ED_SOURCES = {
|
|
|
|
|
REVALIDATION: [
|
|
|
|
|
'57122881048805941', // e-hentai
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
2023-12-13 02:05:03 +01:00
|
|
|
// TODO - extract logic to reduce the size of this file... grew waaaaaaaaaaaaay too big peepoFat
|
2023-10-16 00:05:51 +02:00
|
|
|
// TODO - correctly update cache after all mutations instead of refetching queries
|
2023-05-18 13:25:19 +02:00
|
|
|
export class RequestManager {
|
2023-06-04 00:50:58 +02:00
|
|
|
public static readonly API_VERSION = '/api/v1/';
|
2023-05-18 13:25:19 +02:00
|
|
|
|
2025-07-27 00:03:51 +02:00
|
|
|
public readonly graphQLClient = new GraphQLClient(this.refreshUser.bind(this));
|
2023-09-01 20:21:59 +02:00
|
|
|
|
2025-07-27 00:03:51 +02:00
|
|
|
private readonly restClient: RestClient = new RestClient(this.refreshUser.bind(this));
|
2023-05-18 13:25:19 +02:00
|
|
|
|
2023-09-12 02:36:11 +02:00
|
|
|
private readonly cache = new CustomCache();
|
|
|
|
|
|
2024-03-24 19:08:36 +01:00
|
|
|
private readonly imageQueue = new Queue(5);
|
2024-03-05 02:11:40 +01:00
|
|
|
|
2025-10-15 02:50:43 +02:00
|
|
|
constructor() {
|
|
|
|
|
BaseClient.setTokenRefreshCompleteCallback(() => {
|
|
|
|
|
this.processQueues();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public getClient(): IRestClient {
|
|
|
|
|
return this.restClient;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-05 02:07:05 +01:00
|
|
|
public updateClient(config: RequestInit): void {
|
2023-05-18 13:25:19 +02:00
|
|
|
this.restClient.updateConfig(config);
|
2023-10-04 20:50:11 +02:00
|
|
|
this.graphQLClient.updateConfig();
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-10 01:23:08 +02:00
|
|
|
public reset(): void {
|
2025-07-27 00:03:51 +02:00
|
|
|
AuthManager.setAuthRequired(null);
|
2025-10-15 02:50:43 +02:00
|
|
|
AuthManager.setAuthInitialized(false);
|
2025-07-27 00:03:51 +02:00
|
|
|
AuthManager.removeTokens();
|
2025-11-07 01:25:36 +01:00
|
|
|
|
|
|
|
|
this.graphQLClient.reset();
|
|
|
|
|
this.restClient.reset();
|
|
|
|
|
|
2024-04-10 01:23:08 +02:00
|
|
|
this.cache.clear();
|
|
|
|
|
this.imageQueue.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 02:50:43 +02:00
|
|
|
public processQueues(): void {
|
|
|
|
|
this.graphQLClient.processQueue();
|
|
|
|
|
this.restClient.processQueue();
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public getBaseUrl(): string {
|
2024-03-05 02:07:05 +01:00
|
|
|
return this.restClient.getBaseUrl();
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-19 20:22:31 +02:00
|
|
|
public useBaseUrl() {
|
|
|
|
|
return useLocalStorage(BaseClient.BASE_URL_KEY, () => this.getBaseUrl());
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public getValidUrlFor(endpoint: string, apiVersion: string = RequestManager.API_VERSION): string {
|
|
|
|
|
return `${this.getBaseUrl()}${apiVersion}${endpoint}`;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-19 19:09:22 +02:00
|
|
|
public getWebviewUrl(url: string): string {
|
|
|
|
|
return `${this.getValidUrlFor('webview')}#${url}`;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-11 23:43:22 +01:00
|
|
|
public clearBrowseCacheFor(sourceId: string) {
|
|
|
|
|
const cacheKeys = this.cache.getMatchingKeys(
|
|
|
|
|
new RegExp(`${CACHE_INITIAL_PAGES_FETCHING_KEY}|${CACHE_PAGES_KEY}|${CACHE_RESULTS_KEY}.*${sourceId}`),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.cache.clearFor(...cacheKeys);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-15 23:22:25 +01:00
|
|
|
public clearExtensionCache() {
|
|
|
|
|
this.cache.clearFor(this.cache.getKeyFor(EXTENSION_LIST_CACHE_KEY, undefined));
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 02:36:11 +02:00
|
|
|
private createAbortController(): { signal: AbortSignal } & AbortableRequest {
|
|
|
|
|
const abortController = new AbortController();
|
|
|
|
|
const abortRequest = (reason?: any): void => {
|
|
|
|
|
if (!abortController.signal.aborted) {
|
|
|
|
|
abortController.abort(reason);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { signal: abortController.signal, abortRequest };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private createPaginatedResult<Result extends AbortableApolloUseMutationPaginatedResponse[1][number]>(
|
|
|
|
|
result: Partial<Result> | undefined | null,
|
|
|
|
|
defaultPage: number,
|
|
|
|
|
page?: number,
|
|
|
|
|
): Result {
|
|
|
|
|
const isLoading = !result?.error && (result?.isLoading || !result?.called);
|
|
|
|
|
const size = page ?? result?.size ?? defaultPage;
|
|
|
|
|
return {
|
|
|
|
|
client: this.graphQLClient.client,
|
|
|
|
|
abortRequest: () => {},
|
|
|
|
|
reset: () => {},
|
|
|
|
|
called: false,
|
|
|
|
|
data: undefined,
|
|
|
|
|
error: undefined,
|
|
|
|
|
size,
|
|
|
|
|
isLoading,
|
|
|
|
|
isLoadingMore: isLoading && size > 1,
|
|
|
|
|
isValidating: !!result?.isValidating,
|
|
|
|
|
...result,
|
|
|
|
|
} as Result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async revalidatePage<Data = any, Variables extends OperationVariables = OperationVariables>(
|
2023-11-11 23:43:22 +01:00
|
|
|
sourceId: string,
|
2023-09-12 02:36:11 +02:00
|
|
|
cacheResultsKey: string,
|
|
|
|
|
cachePagesKey: string,
|
|
|
|
|
getVariablesFor: (page: number) => Variables,
|
|
|
|
|
options: ApolloPaginatedMutationOptions<Data, Variables> | undefined,
|
|
|
|
|
checkIfCachedPageIsInvalid: (
|
|
|
|
|
cachedResult: AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number] | undefined,
|
2024-12-24 00:28:37 +01:00
|
|
|
revalidatedResult: FetchResult<MaybeMasked<Data>>,
|
2023-09-12 02:36:11 +02:00
|
|
|
) => boolean,
|
2024-12-24 00:28:37 +01:00
|
|
|
hasNextPage: (revalidatedResult: FetchResult<MaybeMasked<Data>>) => boolean,
|
2023-09-12 02:36:11 +02:00
|
|
|
pageToRevalidate: number,
|
|
|
|
|
maxPage: number,
|
|
|
|
|
signal: AbortSignal,
|
|
|
|
|
): Promise<void> {
|
2023-11-11 23:43:22 +01:00
|
|
|
if (SPECIAL_ED_SOURCES.REVALIDATION.includes(sourceId)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-23 23:34:20 +01:00
|
|
|
const isFirstPage = pageToRevalidate === 1;
|
|
|
|
|
const isTtlReached =
|
|
|
|
|
Date.now() - (this.cache.getFetchTimestampFor(cacheResultsKey, getVariablesFor(pageToRevalidate)) ?? 0) >=
|
2025-11-09 01:13:31 +01:00
|
|
|
d(5).minutes.inWholeMilliseconds;
|
2024-03-23 23:34:20 +01:00
|
|
|
|
|
|
|
|
if (isFirstPage && !isTtlReached) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
const { response: revalidationRequest } = this.doRequest(
|
2023-09-12 02:36:11 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
GET_SOURCE_MANGAS_FETCH,
|
|
|
|
|
getVariablesFor(pageToRevalidate),
|
|
|
|
|
{
|
|
|
|
|
...options,
|
|
|
|
|
context: { fetchOptions: { signal } },
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const revalidationResponse = await revalidationRequest;
|
|
|
|
|
const cachedPageData = this.cache.getResponseFor<
|
|
|
|
|
AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number]
|
|
|
|
|
>(cacheResultsKey, getVariablesFor(pageToRevalidate));
|
|
|
|
|
const isCachedPageInvalid = checkIfCachedPageIsInvalid(cachedPageData, revalidationResponse);
|
2024-03-23 23:34:20 +01:00
|
|
|
|
|
|
|
|
this.cache.cacheResponse(cacheResultsKey, getVariablesFor(pageToRevalidate), revalidationResponse);
|
2023-09-12 02:36:11 +02:00
|
|
|
|
|
|
|
|
if (!hasNextPage(revalidationResponse)) {
|
|
|
|
|
const currentCachedPages = this.cache.getResponseFor<Set<number>>(cachePagesKey, getVariablesFor(0))!;
|
|
|
|
|
this.cache.cacheResponse(
|
|
|
|
|
cachePagesKey,
|
|
|
|
|
getVariablesFor(0),
|
|
|
|
|
[...currentCachedPages].filter((cachedPage) => cachedPage <= pageToRevalidate),
|
|
|
|
|
);
|
|
|
|
|
[...currentCachedPages]
|
|
|
|
|
.filter((cachedPage) => cachedPage > pageToRevalidate)
|
|
|
|
|
.forEach((cachedPage) =>
|
|
|
|
|
this.cache.cacheResponse(cacheResultsKey, getVariablesFor(cachedPage), undefined),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isCachedPageInvalid && pageToRevalidate < maxPage) {
|
|
|
|
|
await this.revalidatePage(
|
2023-11-11 23:43:22 +01:00
|
|
|
sourceId,
|
2023-09-12 02:36:11 +02:00
|
|
|
cacheResultsKey,
|
|
|
|
|
cachePagesKey,
|
|
|
|
|
getVariablesFor,
|
|
|
|
|
options,
|
|
|
|
|
checkIfCachedPageIsInvalid,
|
|
|
|
|
hasNextPage,
|
|
|
|
|
pageToRevalidate + 1,
|
|
|
|
|
maxPage,
|
|
|
|
|
signal,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async revalidatePages<Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
activeRevalidationRef:
|
|
|
|
|
| [ForInput: Variables, Request: Promise<unknown>, AbortRequest: AbortableRequest['abortRequest']]
|
|
|
|
|
| null,
|
|
|
|
|
setRevalidationDone: (isDone: boolean) => void,
|
|
|
|
|
setActiveRevalidation: (
|
|
|
|
|
activeRevalidation:
|
|
|
|
|
| [ForInput: Variables, Request: Promise<unknown>, AbortRequest: AbortableRequest['abortRequest']]
|
|
|
|
|
| null,
|
|
|
|
|
) => void,
|
|
|
|
|
getVariablesFor: (page: number) => Variables,
|
|
|
|
|
setValidating: (isValidating: boolean) => void,
|
|
|
|
|
revalidatePage: (pageToRevalidate: number, maxPage: number, signal: AbortSignal) => Promise<void>,
|
|
|
|
|
maxPage: number,
|
|
|
|
|
abortRequest: AbortableRequest['abortRequest'],
|
|
|
|
|
signal: AbortSignal,
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
setRevalidationDone(true);
|
|
|
|
|
|
|
|
|
|
const [currRevVars, currRevPromise, currRevAbortRequest] = activeRevalidationRef ?? [];
|
|
|
|
|
|
|
|
|
|
const isActiveRevalidationForInput = JSON.stringify(currRevVars) === JSON.stringify(getVariablesFor(0));
|
|
|
|
|
|
|
|
|
|
setValidating(true);
|
|
|
|
|
|
|
|
|
|
if (!isActiveRevalidationForInput) {
|
|
|
|
|
currRevAbortRequest?.(new Error('Abort revalidation for different input'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let revalidationPromise = currRevPromise;
|
|
|
|
|
if (!isActiveRevalidationForInput) {
|
|
|
|
|
revalidationPromise = revalidatePage(1, maxPage, signal);
|
|
|
|
|
setActiveRevalidation([getVariablesFor(0), revalidationPromise, abortRequest]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await revalidationPromise;
|
|
|
|
|
setActiveRevalidation(null);
|
|
|
|
|
} catch (e) {
|
2023-12-31 01:42:54 +01:00
|
|
|
defaultPromiseErrorHandler(`RequestManager..revalidatePages(${getVariablesFor(0)})`)(e);
|
2023-09-12 02:36:11 +02:00
|
|
|
} finally {
|
|
|
|
|
setValidating(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async fetchPaginatedMutationPage<
|
|
|
|
|
Data = any,
|
|
|
|
|
Variables extends OperationVariables = OperationVariables,
|
|
|
|
|
ResultIdInfo extends Record<string, any> = any,
|
|
|
|
|
>(
|
|
|
|
|
getVariablesFor: (page: number) => Variables,
|
|
|
|
|
setAbortRequest: (abortRequest: AbortableRequest['abortRequest']) => void,
|
|
|
|
|
getResultIdInfo: () => ResultIdInfo,
|
|
|
|
|
createPaginatedResult: (
|
|
|
|
|
result: Partial<AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number]>,
|
|
|
|
|
) => AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number],
|
|
|
|
|
setResult: (
|
|
|
|
|
result: AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number] & ResultIdInfo,
|
|
|
|
|
) => void,
|
|
|
|
|
revalidate: (
|
|
|
|
|
maxPage: number,
|
|
|
|
|
abortRequest: AbortableRequest['abortRequest'],
|
|
|
|
|
signal: AbortSignal,
|
|
|
|
|
) => Promise<void>,
|
|
|
|
|
options: ApolloPaginatedMutationOptions<Data, Variables> | undefined,
|
|
|
|
|
documentNode: DocumentNode,
|
|
|
|
|
cachePagesKey: string,
|
|
|
|
|
cacheResultsKey: string,
|
|
|
|
|
cachedPages: Set<number>,
|
|
|
|
|
newPage: number,
|
2024-12-24 00:28:37 +01:00
|
|
|
): Promise<FetchResult<MaybeMasked<Data>>> {
|
2023-09-12 02:36:11 +02:00
|
|
|
const basePaginatedResult: Partial<AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number]> = {
|
|
|
|
|
size: newPage,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
isLoadingMore: false,
|
|
|
|
|
called: true,
|
|
|
|
|
};
|
|
|
|
|
|
2024-12-24 00:28:37 +01:00
|
|
|
let response: FetchResult<MaybeMasked<Data>> = {};
|
2023-09-12 02:36:11 +02:00
|
|
|
try {
|
|
|
|
|
const { signal, abortRequest } = this.createAbortController();
|
|
|
|
|
setAbortRequest(abortRequest);
|
|
|
|
|
|
2024-10-06 16:15:01 +02:00
|
|
|
const isRefetch = newPage === [...cachedPages][cachedPages.size - 1];
|
|
|
|
|
if (isRefetch) {
|
|
|
|
|
this.cache.cacheResponse(
|
|
|
|
|
cachePagesKey,
|
|
|
|
|
getVariablesFor(0),
|
|
|
|
|
new Set([...cachedPages].slice(0, cachedPages.size - 1)),
|
|
|
|
|
);
|
|
|
|
|
this.cache.clearFor(this.cache.getKeyFor(cacheResultsKey, getVariablesFor(newPage)));
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 02:36:11 +02:00
|
|
|
setResult({
|
|
|
|
|
...getResultIdInfo(),
|
|
|
|
|
...createPaginatedResult({ isLoading: true, abortRequest, size: newPage, called: true }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (newPage !== 1 && cachedPages.size) {
|
|
|
|
|
await revalidate(newPage, abortRequest, signal);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
const { response: request } = this.doRequest<Data, Variables>(
|
2023-09-12 02:36:11 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
documentNode,
|
|
|
|
|
getVariablesFor(newPage),
|
|
|
|
|
{ ...options, context: { fetchOptions: { signal } } },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
response = await request;
|
|
|
|
|
|
|
|
|
|
basePaginatedResult.data = response.data;
|
|
|
|
|
} catch (error: any) {
|
2024-10-06 16:15:01 +02:00
|
|
|
defaultPromiseErrorHandler('RequestManager::fetchPaginatedMutationPage')(error);
|
2023-09-12 02:36:11 +02:00
|
|
|
if (error instanceof ApolloError) {
|
|
|
|
|
basePaginatedResult.error = error;
|
|
|
|
|
} else {
|
|
|
|
|
basePaginatedResult.error = new ApolloError({
|
|
|
|
|
errorMessage: error?.message ?? error.toString(),
|
|
|
|
|
extraInfo: error,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchPaginatedResult = {
|
|
|
|
|
...getResultIdInfo(),
|
|
|
|
|
...createPaginatedResult(basePaginatedResult),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setResult(fetchPaginatedResult);
|
|
|
|
|
|
2024-10-06 16:15:01 +02:00
|
|
|
const currentCachedPages = this.cache.getResponseFor<Set<number>>(cachePagesKey, getVariablesFor(0)) ?? [];
|
|
|
|
|
this.cache.cacheResponse(cachePagesKey, getVariablesFor(0), new Set([...currentCachedPages, newPage]));
|
|
|
|
|
this.cache.cacheResponse(cacheResultsKey, getVariablesFor(newPage), fetchPaginatedResult);
|
2023-09-12 02:36:11 +02:00
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fetchInitialPages<Data = any, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
options: ApolloPaginatedMutationOptions<Data, Variables> | undefined,
|
|
|
|
|
areFetchingInitialPages: boolean,
|
|
|
|
|
areInitialPagesFetched: boolean,
|
|
|
|
|
setRevalidationDone: (isDone: boolean) => void,
|
2023-11-11 23:42:58 +01:00
|
|
|
cacheFetchingInitialPagesKey: string,
|
2023-09-12 02:36:11 +02:00
|
|
|
getVariablesFor: (page: number) => Variables,
|
|
|
|
|
initialPages: number,
|
|
|
|
|
fetchPage: (page: number) => Promise<FetchResult<Data>>,
|
|
|
|
|
hasNextPage: (result: FetchResult<Data>) => boolean,
|
|
|
|
|
): void {
|
2023-11-11 23:42:58 +01:00
|
|
|
useEffect(() => {
|
|
|
|
|
const shouldFetchInitialPages =
|
|
|
|
|
!options?.skipRequest && !areFetchingInitialPages && !areInitialPagesFetched;
|
|
|
|
|
if (!shouldFetchInitialPages) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 02:36:11 +02:00
|
|
|
setRevalidationDone(true);
|
2023-11-11 23:42:58 +01:00
|
|
|
this.cache.cacheResponse(cacheFetchingInitialPagesKey, getVariablesFor(0), true);
|
2023-09-12 02:36:11 +02:00
|
|
|
|
|
|
|
|
const loadInitialPages = async (initialPage: number) => {
|
|
|
|
|
const areAllPagesFetched = initialPage > initialPages;
|
|
|
|
|
if (areAllPagesFetched) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pageResult = await fetchPage(initialPage);
|
|
|
|
|
|
|
|
|
|
if (hasNextPage(pageResult)) {
|
|
|
|
|
await loadInitialPages(initialPage + 1);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-11 23:42:58 +01:00
|
|
|
loadInitialPages(1).finally(() =>
|
|
|
|
|
this.cache.cacheResponse(cacheFetchingInitialPagesKey, getVariablesFor(0), false),
|
|
|
|
|
);
|
|
|
|
|
}, [!options?.skipRequest, !areFetchingInitialPages, !areInitialPagesFetched]);
|
2023-09-12 02:36:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private returnPaginatedMutationResult<Data = any, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
areInitialPagesFetched: boolean,
|
|
|
|
|
cachedResults: AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number][],
|
|
|
|
|
getVariablesFor: (page: number) => Variables,
|
|
|
|
|
paginatedResult: AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number],
|
2024-12-24 00:28:37 +01:00
|
|
|
fetchPage: (page: number) => Promise<FetchResult<MaybeMasked<Data>>>,
|
2023-09-12 02:36:11 +02:00
|
|
|
hasCachedResult: boolean,
|
|
|
|
|
createPaginatedResult: (
|
|
|
|
|
result: Partial<AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number]>,
|
|
|
|
|
) => AbortableApolloUseMutationPaginatedResponse<Data, Variables>[1][number],
|
|
|
|
|
): AbortableApolloUseMutationPaginatedResponse<Data, Variables> {
|
|
|
|
|
const doCachedResultsExist = areInitialPagesFetched && cachedResults.length;
|
|
|
|
|
if (!doCachedResultsExist) {
|
|
|
|
|
return [fetchPage, [paginatedResult]];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const areAllPagesCached = doCachedResultsExist && hasCachedResult;
|
|
|
|
|
if (!areAllPagesCached) {
|
|
|
|
|
return [fetchPage, [...cachedResults, paginatedResult]];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
fetchPage,
|
|
|
|
|
[
|
|
|
|
|
...cachedResults.slice(0, cachedResults.length - 1),
|
|
|
|
|
createPaginatedResult({
|
|
|
|
|
...cachedResults[cachedResults.length - 1],
|
|
|
|
|
isValidating: paginatedResult.isValidating,
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private revalidateInitialPages<Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
isRevalidationDone: boolean,
|
|
|
|
|
cachedResultsLength: number,
|
|
|
|
|
cachedPages: Set<number>,
|
|
|
|
|
setRevalidationDone: (isDone: boolean) => void,
|
|
|
|
|
getVariablesFor: (page: number) => Variables,
|
|
|
|
|
triggerRerender: () => void,
|
|
|
|
|
revalidate: (
|
|
|
|
|
maxPage: number,
|
|
|
|
|
abortRequest: AbortableRequest['abortRequest'],
|
|
|
|
|
signal: AbortSignal,
|
|
|
|
|
) => Promise<void>,
|
|
|
|
|
): void {
|
|
|
|
|
const isMountedRef = useRef(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const isRevalidationRequired = isMountedRef.current && cachedResultsLength;
|
|
|
|
|
if (!isRevalidationRequired) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setRevalidationDone(false);
|
|
|
|
|
triggerRerender();
|
|
|
|
|
}, [JSON.stringify(getVariablesFor(0))]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const shouldRevalidateData = isMountedRef.current && !isRevalidationDone && cachedResultsLength;
|
|
|
|
|
if (shouldRevalidateData) {
|
|
|
|
|
setRevalidationDone(true);
|
|
|
|
|
|
|
|
|
|
const { signal, abortRequest } = this.createAbortController();
|
|
|
|
|
revalidate(Math.max(...cachedPages), abortRequest, signal);
|
|
|
|
|
}
|
|
|
|
|
}, [isMountedRef.current, isRevalidationDone]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
isMountedRef.current = true;
|
|
|
|
|
}, []);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public getValidImgUrlFor(imageUrl: string, apiVersion: string = ''): string {
|
|
|
|
|
// server provided image urls already contain the api version
|
2024-03-22 03:33:09 +01:00
|
|
|
return `${this.getValidUrlFor(imageUrl, apiVersion)}`;
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-13 14:27:00 +01:00
|
|
|
/**
|
|
|
|
|
* Aborts pending image requests
|
|
|
|
|
*
|
|
|
|
|
* prevents aborting image requests that are already in progress
|
|
|
|
|
* e.g. for source image requests, ongoing requests are already handled by the server and aborting them
|
|
|
|
|
* will just cause new source image requests to be sent to the server, which then will cause the server
|
|
|
|
|
* to become really slow for image requests to the same source
|
|
|
|
|
*/
|
|
|
|
|
private abortImageRequest(key: string, abort: () => void): void {
|
|
|
|
|
if (this.imageQueue.isProcessing(key)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-26 17:18:27 +01:00
|
|
|
private async optionallyDecodeImage(url: string, shouldDecode?: boolean, disableCors?: boolean): Promise<string> {
|
2024-11-13 14:27:00 +01:00
|
|
|
if (!shouldDecode) {
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const decodePromise = new ControlledPromise();
|
|
|
|
|
|
|
|
|
|
const img = new Image();
|
2025-01-26 03:51:43 +01:00
|
|
|
|
2025-01-26 17:18:27 +01:00
|
|
|
if (!disableCors) {
|
2025-01-26 03:51:43 +01:00
|
|
|
img.crossOrigin = 'anonymous';
|
|
|
|
|
}
|
2024-11-13 14:27:00 +01:00
|
|
|
img.src = url;
|
|
|
|
|
|
|
|
|
|
img.onload = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await img.decode();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
decodePromise.reject(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
decodePromise.resolve();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
img.onerror = (error) => decodePromise.reject(error);
|
|
|
|
|
img.onabort = (error) => decodePromise.reject(error);
|
|
|
|
|
|
|
|
|
|
await decodePromise.promise;
|
|
|
|
|
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-22 16:56:53 +01:00
|
|
|
private async maybeEnqueueImageRequest<T>(
|
|
|
|
|
url: string,
|
|
|
|
|
request: () => Promise<T>,
|
|
|
|
|
priority?: QueuePriority,
|
2025-11-22 17:28:56 +01:00
|
|
|
ignoreQueue?: boolean,
|
2025-11-22 16:56:53 +01:00
|
|
|
): Promise<ReturnType<typeof this.imageQueue.enqueue<T>> & { fromCache?: boolean }> {
|
|
|
|
|
try {
|
2025-11-22 17:28:56 +01:00
|
|
|
const isCached = await ImageCache.has(url);
|
|
|
|
|
if (!!ignoreQueue || isCached) {
|
2025-11-22 16:56:53 +01:00
|
|
|
return {
|
|
|
|
|
key: `image-cache-${url}`,
|
|
|
|
|
promise: request(),
|
2025-11-22 17:28:56 +01:00
|
|
|
fromCache: isCached,
|
2025-11-22 16:56:53 +01:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.imageQueue.enqueue(url, request, priority);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return this.imageQueue.enqueue(url, request, priority);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async fetchImageViaTag(
|
2024-11-13 14:27:00 +01:00
|
|
|
url: string,
|
2025-11-22 17:28:56 +01:00
|
|
|
{ priority, shouldDecode, disableCors, ignoreQueue }: ImageRequestOptions = {},
|
2025-11-22 16:56:53 +01:00
|
|
|
): Promise<ImageRequest> {
|
2024-03-28 22:40:55 +01:00
|
|
|
const imgRequest = new ControlledPromise<string>();
|
2024-04-26 17:29:58 +02:00
|
|
|
imgRequest.promise.catch(() => {});
|
2024-03-28 22:40:55 +01:00
|
|
|
|
|
|
|
|
const img = new Image();
|
|
|
|
|
const abortRequest = (reason?: any) => {
|
|
|
|
|
img.src = '';
|
|
|
|
|
img.onload = null;
|
|
|
|
|
img.onerror = null;
|
|
|
|
|
img.onabort = null;
|
|
|
|
|
imgRequest.reject(reason);
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-22 16:56:53 +01:00
|
|
|
const {
|
|
|
|
|
key,
|
|
|
|
|
promise: response,
|
|
|
|
|
fromCache,
|
|
|
|
|
} = await this.maybeEnqueueImageRequest(
|
2024-03-28 22:40:55 +01:00
|
|
|
url,
|
|
|
|
|
async () => {
|
|
|
|
|
// throws error in case request was already aborted
|
|
|
|
|
await Promise.race([imgRequest.promise, Promise.resolve()]);
|
|
|
|
|
|
2025-01-26 17:18:27 +01:00
|
|
|
if (!disableCors) {
|
2025-01-26 03:51:43 +01:00
|
|
|
img.crossOrigin = 'anonymous';
|
|
|
|
|
}
|
2024-03-28 22:40:55 +01:00
|
|
|
img.src = url;
|
|
|
|
|
|
2024-11-13 14:27:00 +01:00
|
|
|
img.onload = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await this.optionallyDecodeImage(url, shouldDecode);
|
|
|
|
|
imgRequest.resolve(url);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
imgRequest.reject(error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-28 22:40:55 +01:00
|
|
|
img.onerror = (error) => imgRequest.reject(error);
|
|
|
|
|
img.onabort = (error) => imgRequest.reject(error);
|
|
|
|
|
|
|
|
|
|
return imgRequest.promise;
|
|
|
|
|
},
|
|
|
|
|
priority,
|
2025-11-22 17:28:56 +01:00
|
|
|
ignoreQueue,
|
2024-03-28 22:40:55 +01:00
|
|
|
);
|
|
|
|
|
|
2024-05-06 02:32:31 +02:00
|
|
|
return {
|
|
|
|
|
response,
|
2024-11-13 14:25:58 +01:00
|
|
|
abortRequest: (reason?: any) => this.abortImageRequest(key, () => abortRequest(reason)),
|
2024-05-06 02:32:31 +02:00
|
|
|
cleanup: () => {},
|
2025-11-22 16:56:53 +01:00
|
|
|
fromCache: !!fromCache,
|
2024-05-06 02:32:31 +02:00
|
|
|
};
|
2024-03-28 22:40:55 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-01 18:27:46 +01:00
|
|
|
/**
|
|
|
|
|
* After the image has been handled, {@see URL#revokeObjectURL} has to be called.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
*
|
|
|
|
|
* const imageRequest = requestManager.requestImage("someUrl");
|
|
|
|
|
* const imageUrl = await imageRequest.response
|
|
|
|
|
*
|
|
|
|
|
* const img = new Image();
|
2024-03-28 22:40:55 +01:00
|
|
|
* img.onLoad = () => imageRequest.cleanup();
|
2024-03-01 18:27:46 +01:00
|
|
|
* img.src = imageUrl;
|
|
|
|
|
*
|
|
|
|
|
*/
|
2025-11-22 16:56:53 +01:00
|
|
|
private async fetchImageViaFetchApi(
|
2024-11-13 14:27:00 +01:00
|
|
|
url: string,
|
2025-11-22 17:28:56 +01:00
|
|
|
{ priority, shouldDecode, disableCors, ignoreQueue }: ImageRequestOptions = {},
|
2025-11-22 16:56:53 +01:00
|
|
|
): Promise<ImageRequest> {
|
2024-03-28 22:40:55 +01:00
|
|
|
let objectUrl: string = '';
|
2024-03-01 01:17:23 +01:00
|
|
|
const { abortRequest, signal } = this.createAbortController();
|
2025-11-22 16:56:53 +01:00
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
key,
|
|
|
|
|
promise: response,
|
|
|
|
|
fromCache,
|
|
|
|
|
} = await this.maybeEnqueueImageRequest(
|
2024-03-24 19:08:36 +01:00
|
|
|
url,
|
|
|
|
|
() =>
|
|
|
|
|
this.restClient
|
|
|
|
|
.fetcher(url, {
|
|
|
|
|
checkResponseIsJson: false,
|
|
|
|
|
config: {
|
|
|
|
|
signal,
|
|
|
|
|
priority: 'low',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.then((data) => data.blob())
|
2024-03-28 22:40:55 +01:00
|
|
|
.then((data) => URL.createObjectURL(data))
|
2024-11-13 14:27:00 +01:00
|
|
|
.then(async (imageUrl) => {
|
2024-03-28 22:40:55 +01:00
|
|
|
objectUrl = imageUrl;
|
2024-11-13 14:27:00 +01:00
|
|
|
|
2025-01-26 17:18:27 +01:00
|
|
|
await this.optionallyDecodeImage(imageUrl, shouldDecode, disableCors);
|
2024-11-13 14:27:00 +01:00
|
|
|
|
2024-03-28 22:40:55 +01:00
|
|
|
return imageUrl;
|
|
|
|
|
}),
|
2024-03-24 19:08:36 +01:00
|
|
|
priority,
|
2025-11-22 17:28:56 +01:00
|
|
|
ignoreQueue,
|
2024-03-05 02:11:40 +01:00
|
|
|
);
|
2024-03-01 01:17:23 +01:00
|
|
|
|
2024-05-06 02:32:31 +02:00
|
|
|
return {
|
|
|
|
|
response,
|
2024-11-13 14:25:58 +01:00
|
|
|
abortRequest: (reason?: any) => this.abortImageRequest(key, () => abortRequest(reason)),
|
2024-05-06 02:32:31 +02:00
|
|
|
cleanup: () => URL.revokeObjectURL(objectUrl),
|
2025-11-22 16:56:53 +01:00
|
|
|
fromCache: !!fromCache,
|
2024-05-06 02:32:31 +02:00
|
|
|
};
|
2024-03-28 22:40:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Make sure to call "cleanup" once the image is not needed anymore (only required if fetched via "fetch api")
|
2024-11-13 14:27:00 +01:00
|
|
|
*
|
|
|
|
|
* options:
|
|
|
|
|
* - shouldDecode: decodes the image in case the browser is Firefox to prevent a flickering/blinking when an image gets visible for the first time
|
2024-03-28 22:40:55 +01:00
|
|
|
*/
|
2025-11-22 16:56:53 +01:00
|
|
|
public async requestImage(
|
2024-11-13 14:27:00 +01:00
|
|
|
url: string,
|
2025-11-22 17:28:56 +01:00
|
|
|
options: ImageRequestOptions & { useFetchApi?: boolean } = {},
|
2025-11-22 16:56:53 +01:00
|
|
|
): Promise<ImageRequest> {
|
2025-01-26 03:51:43 +01:00
|
|
|
const finalOptions = {
|
2025-07-27 00:03:51 +02:00
|
|
|
useFetchApi: AuthManager.isAuthRequired(),
|
2024-11-13 14:27:00 +01:00
|
|
|
shouldDecode: false,
|
2025-01-26 17:18:27 +01:00
|
|
|
disableCors: false,
|
2025-11-22 17:28:56 +01:00
|
|
|
ignoreQueue: false,
|
2025-01-26 03:51:43 +01:00
|
|
|
...Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined)),
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-13 14:27:00 +01:00
|
|
|
// on firefox images are decoded async which causes a "flicker/blinking" when they're getting visible for the first time
|
|
|
|
|
// this is an issue especially in the reader because pages that should not be shown are rendered but
|
|
|
|
|
// not displayed, which then causes this issue once they get displayed
|
2025-01-26 03:51:43 +01:00
|
|
|
const shouldDecode = finalOptions.shouldDecode && navigator.userAgent.toLowerCase().includes('firefox');
|
2024-11-13 14:27:00 +01:00
|
|
|
|
2025-01-26 03:51:43 +01:00
|
|
|
if (finalOptions.useFetchApi) {
|
|
|
|
|
return this.fetchImageViaFetchApi(url, { ...finalOptions, shouldDecode });
|
2024-03-28 22:40:55 +01:00
|
|
|
}
|
|
|
|
|
|
2025-01-26 03:51:43 +01:00
|
|
|
return this.fetchImageViaTag(url, { ...finalOptions, shouldDecode });
|
2024-03-01 01:17:23 +01:00
|
|
|
}
|
|
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
private doRequest<Data, Variables extends OperationVariables = OperationVariables>(
|
2023-09-30 16:55:35 +02:00
|
|
|
method: GQLMethod.QUERY,
|
2023-10-04 22:34:32 +02:00
|
|
|
operation: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables | undefined,
|
|
|
|
|
options?: QueryOptions<Variables, Data>,
|
2023-09-30 16:55:35 +02:00
|
|
|
): AbortabaleApolloQueryResponse<Data>;
|
|
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
private doRequest<Data, Variables extends OperationVariables = OperationVariables>(
|
2023-09-01 20:21:59 +02:00
|
|
|
method: GQLMethod.USE_QUERY,
|
2023-10-04 22:34:32 +02:00
|
|
|
operation: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables | undefined,
|
|
|
|
|
options?: QueryHookOptions<Data, Variables>,
|
2023-09-01 20:21:59 +02:00
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables>;
|
|
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
private doRequest<Data, Variables extends OperationVariables = OperationVariables>(
|
2023-09-01 20:21:59 +02:00
|
|
|
method: GQLMethod.USE_MUTATION,
|
2023-10-04 22:34:32 +02:00
|
|
|
operation: DocumentNode | TypedDocumentNode<Data, Variables>,
|
2023-09-01 20:21:59 +02:00
|
|
|
variables: Variables | undefined,
|
2023-10-04 22:34:32 +02:00
|
|
|
options?: MutationHookOptions<Data, Variables>,
|
2023-09-01 20:21:59 +02:00
|
|
|
): AbortableApolloUseMutationResponse<Data, Variables>;
|
|
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
private doRequest<Data, Variables extends OperationVariables = OperationVariables>(
|
2023-09-01 20:21:59 +02:00
|
|
|
method: GQLMethod.MUTATION,
|
2023-10-04 22:34:32 +02:00
|
|
|
operation: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables | undefined,
|
|
|
|
|
options?: MutationOptions<Data, Variables>,
|
2023-09-01 20:21:59 +02:00
|
|
|
): AbortableApolloMutationResponse<Data>;
|
|
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
private doRequest<Data, Variables extends OperationVariables = OperationVariables>(
|
2023-10-04 22:34:32 +02:00
|
|
|
method: GQLMethod.USE_SUBSCRIPTION,
|
|
|
|
|
operation: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables | undefined,
|
|
|
|
|
options?: SubscriptionHookOptions<Data, Variables>,
|
|
|
|
|
): SubscriptionResult<Data, Variables>;
|
|
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
private doRequest<Data, Variables extends OperationVariables = OperationVariables>(
|
2023-09-01 20:21:59 +02:00
|
|
|
method: GQLMethod,
|
2023-10-04 22:34:32 +02:00
|
|
|
operation: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables | undefined,
|
2023-09-01 20:21:59 +02:00
|
|
|
options?:
|
2023-09-30 16:55:35 +02:00
|
|
|
| QueryOptions<Variables, Data>
|
2023-09-01 20:21:59 +02:00
|
|
|
| QueryHookOptions<Data, Variables>
|
|
|
|
|
| MutationHookOptions<Data, Variables>
|
2023-10-04 22:34:32 +02:00
|
|
|
| MutationOptions<Data, Variables>
|
|
|
|
|
| SubscriptionHookOptions<Data, Variables>,
|
2023-09-01 20:21:59 +02:00
|
|
|
):
|
2023-09-30 16:55:35 +02:00
|
|
|
| AbortabaleApolloQueryResponse<Data>
|
2023-09-01 20:21:59 +02:00
|
|
|
| AbortableApolloUseQueryResponse<Data, Variables>
|
|
|
|
|
| AbortableApolloUseMutationResponse<Data, Variables>
|
2023-10-04 22:34:32 +02:00
|
|
|
| AbortableApolloMutationResponse<Data>
|
|
|
|
|
| SubscriptionResult<Data, Variables> {
|
2023-09-12 02:36:11 +02:00
|
|
|
const { signal, abortRequest } = this.createAbortController();
|
2023-09-01 20:21:59 +02:00
|
|
|
switch (method) {
|
2023-09-30 16:55:35 +02:00
|
|
|
case GQLMethod.QUERY:
|
|
|
|
|
return {
|
|
|
|
|
response: this.graphQLClient.client.query<Data, Variables>({
|
|
|
|
|
query: operation,
|
|
|
|
|
variables,
|
|
|
|
|
...(options as QueryOptions<Variables, Data>),
|
|
|
|
|
context: {
|
|
|
|
|
...options?.context,
|
|
|
|
|
fetchOptions: {
|
2023-11-11 23:42:58 +01:00
|
|
|
signal: options?.addAbortSignal ? signal : undefined,
|
2023-09-30 16:55:35 +02:00
|
|
|
...options?.context?.fetchOptions,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
abortRequest,
|
|
|
|
|
};
|
2023-09-01 20:21:59 +02:00
|
|
|
case GQLMethod.USE_QUERY:
|
|
|
|
|
return {
|
|
|
|
|
...useQuery<Data, Variables>(operation, {
|
|
|
|
|
variables,
|
|
|
|
|
client: this.graphQLClient.client,
|
|
|
|
|
...options,
|
|
|
|
|
context: {
|
|
|
|
|
...options?.context,
|
|
|
|
|
fetchOptions: {
|
2023-11-11 23:42:58 +01:00
|
|
|
signal: options?.addAbortSignal ? signal : undefined,
|
2023-09-01 20:21:59 +02:00
|
|
|
...options?.context?.fetchOptions,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
abortRequest,
|
|
|
|
|
};
|
|
|
|
|
case GQLMethod.USE_MUTATION:
|
|
|
|
|
// eslint-disable-next-line no-case-declarations
|
|
|
|
|
const mutationResult = useMutation<Data, Variables>(operation, {
|
|
|
|
|
variables,
|
|
|
|
|
client: this.graphQLClient.client,
|
|
|
|
|
...(options as MutationHookOptions<Data, Variables>),
|
|
|
|
|
context: {
|
|
|
|
|
...options?.context,
|
|
|
|
|
fetchOptions: {
|
2023-11-11 23:42:58 +01:00
|
|
|
signal: options?.addAbortSignal ? signal : undefined,
|
2023-09-01 20:21:59 +02:00
|
|
|
...options?.context?.fetchOptions,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return [mutationResult[0], { ...mutationResult[1], abortRequest }];
|
|
|
|
|
case GQLMethod.MUTATION:
|
|
|
|
|
return {
|
|
|
|
|
response: this.graphQLClient.client.mutate<Data, Variables>({
|
|
|
|
|
mutation: operation,
|
|
|
|
|
variables,
|
|
|
|
|
...(options as MutationOptions<Data, Variables>),
|
|
|
|
|
context: {
|
|
|
|
|
...options?.context,
|
|
|
|
|
fetchOptions: {
|
2023-11-11 23:42:58 +01:00
|
|
|
signal: options?.addAbortSignal ? signal : undefined,
|
2023-09-01 20:21:59 +02:00
|
|
|
...options?.context?.fetchOptions,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
abortRequest,
|
|
|
|
|
};
|
2023-10-04 22:34:32 +02:00
|
|
|
case GQLMethod.USE_SUBSCRIPTION:
|
2025-11-06 02:41:58 +01:00
|
|
|
// eslint-disable-next-line no-case-declarations
|
|
|
|
|
const subscription = useSubscription<Data, Variables>(operation, {
|
2023-10-04 22:34:32 +02:00
|
|
|
client: this.graphQLClient.client,
|
|
|
|
|
variables,
|
|
|
|
|
...(options as SubscriptionHookOptions<Data, Variables>),
|
|
|
|
|
});
|
2025-11-06 02:41:58 +01:00
|
|
|
|
|
|
|
|
this.graphQLClient.useRestartSubscription(subscription.restart);
|
|
|
|
|
|
|
|
|
|
return subscription;
|
2023-09-01 20:21:59 +02:00
|
|
|
default:
|
|
|
|
|
throw new Error(`unexpected GQLRequest type "${method}"`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-25 21:37:45 +01:00
|
|
|
public getGlobalMeta(
|
|
|
|
|
options?: QueryOptions<GetGlobalMetadatasQueryVariables, GetGlobalMetadatasQuery>,
|
|
|
|
|
): AbortabaleApolloQueryResponse<GetGlobalMetadatasQuery> {
|
|
|
|
|
return this.doRequest(GQLMethod.QUERY, GET_GLOBAL_METADATAS, {}, options);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:24:40 +02:00
|
|
|
public useGetGlobalMeta(
|
|
|
|
|
options?: QueryHookOptions<GetGlobalMetadatasQuery, GetGlobalMetadatasQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetGlobalMetadatasQuery, GetGlobalMetadatasQueryVariables> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_GLOBAL_METADATAS, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public setGlobalMetadata(
|
|
|
|
|
key: string,
|
|
|
|
|
value: any,
|
|
|
|
|
options?: MutationOptions<SetGlobalMetadataMutation, SetGlobalMetadataMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<SetGlobalMetadataMutation> {
|
2024-04-27 22:28:55 +02:00
|
|
|
return this.doRequest<SetGlobalMetadataMutation, SetGlobalMetadataMutationVariables>(
|
2023-09-01 20:24:40 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
SET_GLOBAL_METADATA,
|
|
|
|
|
{ input: { meta: { key, value: `${value}` } } },
|
2024-04-27 22:28:55 +02:00
|
|
|
{
|
2024-09-21 20:01:06 +02:00
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
setGlobalMeta: {
|
|
|
|
|
__typename: 'SetGlobalMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'GlobalMetaType',
|
|
|
|
|
key,
|
|
|
|
|
value: `${value}`,
|
2024-04-27 22:28:55 +02:00
|
|
|
},
|
2024-09-21 20:01:06 +02:00
|
|
|
},
|
2024-04-27 22:28:55 +02:00
|
|
|
},
|
2024-09-22 02:46:01 +02:00
|
|
|
update(cache, { data }) {
|
|
|
|
|
cache.modify({
|
|
|
|
|
fields: {
|
2024-09-22 03:04:03 +02:00
|
|
|
metas(existingMetas, { readField }) {
|
|
|
|
|
if (!existingMetas) {
|
|
|
|
|
return existingMetas;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!data?.setGlobalMeta) {
|
|
|
|
|
return existingMetas;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const exists = existingMetas.nodes.some(
|
|
|
|
|
(meta: Reference) => readField('key', meta) === key,
|
2024-09-22 02:46:01 +02:00
|
|
|
);
|
2024-09-22 03:04:03 +02:00
|
|
|
if (exists) {
|
|
|
|
|
return existingMetas;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newMetaRef = cache.writeFragment({
|
|
|
|
|
data: data!.setGlobalMeta.meta,
|
|
|
|
|
fragment: GLOBAL_METADATA,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...existingMetas,
|
|
|
|
|
nodes: [...existingMetas.nodes, newMetaRef],
|
|
|
|
|
};
|
2024-09-22 02:46:01 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
2024-04-27 22:28:55 +02:00
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-26 17:25:04 +02:00
|
|
|
public deleteGlobalMeta(
|
|
|
|
|
key: string,
|
|
|
|
|
options?: MutationOptions<DeleteGlobalMetadataMutation, DeleteGlobalMetadataMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DeleteGlobalMetadataMutation> {
|
|
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_GLOBAL_METADATA,
|
|
|
|
|
{ input: { key } },
|
|
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
deleteGlobalMeta: {
|
|
|
|
|
__typename: 'DeleteGlobalMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'GlobalMetaType',
|
|
|
|
|
key,
|
|
|
|
|
value: '',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
update(cache) {
|
|
|
|
|
cache.evict({ id: cache.identify({ __typename: 'GlobalMetaType', key }) });
|
|
|
|
|
},
|
|
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:25:46 +02:00
|
|
|
public useGetAbout(
|
|
|
|
|
options?: QueryHookOptions<GetAboutQuery, GetAboutQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetAboutQuery, GetAboutQueryVariables> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_ABOUT, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-09 23:08:27 +01:00
|
|
|
public useCheckForServerUpdate(
|
2023-09-01 20:26:06 +02:00
|
|
|
options?: QueryHookOptions<CheckForServerUpdatesQuery, CheckForServerUpdatesQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<CheckForServerUpdatesQuery, CheckForServerUpdatesQueryVariables> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, CHECK_FOR_SERVER_UPDATES, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-09 23:08:27 +01:00
|
|
|
public useCheckForWebUIUpdate(
|
|
|
|
|
options?: QueryHookOptions<CheckForWebuiUpdateQuery, CheckForWebuiUpdateQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<CheckForWebuiUpdateQuery, CheckForWebuiUpdateQueryVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, CHECK_FOR_WEBUI_UPDATE, {}, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public updateWebUI(
|
|
|
|
|
options?: MutationOptions<UpdateWebuiMutation, UpdateWebuiMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateWebuiMutation> {
|
|
|
|
|
return this.doRequest(GQLMethod.MUTATION, UPDATE_WEBUI, undefined, options);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-22 23:12:54 +02:00
|
|
|
public useGetExtension(
|
|
|
|
|
pkgName: string,
|
|
|
|
|
options?: QueryHookOptions<GetExtensionQuery, GetExtensionQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetExtensionQuery, GetExtensionQueryVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_EXTENSION, { pkgName }, options);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:28:23 +02:00
|
|
|
public useGetExtensionList(
|
|
|
|
|
options?: QueryHookOptions<GetExtensionsQuery, GetExtensionsQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetExtensionsQuery, GetExtensionsQueryVariables> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_EXTENSIONS, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:28:23 +02:00
|
|
|
public useExtensionListFetch(
|
|
|
|
|
options?: MutationHookOptions<GetExtensionsFetchMutation, GetExtensionsFetchMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<GetExtensionsFetchMutation, GetExtensionsFetchMutationVariables> {
|
2023-11-04 22:40:38 +01:00
|
|
|
const [mutate, result] = this.doRequest(
|
2023-10-16 00:05:51 +02:00
|
|
|
GQLMethod.USE_MUTATION,
|
|
|
|
|
GET_EXTENSIONS_FETCH,
|
|
|
|
|
{},
|
|
|
|
|
{ refetchQueries: [GET_EXTENSIONS], ...options },
|
|
|
|
|
);
|
2023-11-18 19:55:35 +01:00
|
|
|
const [, setUpdatedCache] = useState({});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (result.loading) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 15:24:29 +02:00
|
|
|
if (!result.data?.fetchExtensions?.extensions) {
|
2023-11-18 19:55:35 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2023-11-04 22:40:38 +01:00
|
|
|
|
2023-11-05 22:37:28 +01:00
|
|
|
this.cache.cacheResponse(EXTENSION_LIST_CACHE_KEY, undefined, result);
|
2023-11-18 19:55:35 +01:00
|
|
|
setUpdatedCache({});
|
|
|
|
|
}, [result.loading]);
|
|
|
|
|
|
2025-11-09 01:13:31 +01:00
|
|
|
const cachedResult = this.cache.getResponseFor<typeof result>(
|
|
|
|
|
EXTENSION_LIST_CACHE_KEY,
|
|
|
|
|
undefined,
|
|
|
|
|
d(1).minutes.inWholeMilliseconds,
|
|
|
|
|
);
|
2023-11-18 19:55:35 +01:00
|
|
|
const normalizedCachedResult = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
!cachedResult
|
|
|
|
|
? result
|
|
|
|
|
: {
|
|
|
|
|
...cachedResult,
|
2024-06-02 15:24:29 +02:00
|
|
|
data: !cachedResult?.data?.fetchExtensions?.extensions
|
2023-11-18 19:55:35 +01:00
|
|
|
? cachedResult?.data
|
|
|
|
|
: {
|
|
|
|
|
...cachedResult.data,
|
|
|
|
|
fetchExtensions: {
|
|
|
|
|
...cachedResult.data.fetchExtensions,
|
|
|
|
|
extensions: cachedResult.data.fetchExtensions.extensions.map(
|
|
|
|
|
(extension) =>
|
|
|
|
|
this.graphQLClient.client.cache.readFragment<
|
2024-06-02 15:24:29 +02:00
|
|
|
NonNullable<
|
|
|
|
|
GetExtensionsFetchMutation['fetchExtensions']
|
|
|
|
|
>['extensions'][0]
|
2023-11-18 19:55:35 +01:00
|
|
|
>({
|
|
|
|
|
id: this.graphQLClient.client.cache.identify(extension),
|
2024-07-06 15:55:58 +02:00
|
|
|
fragment: EXTENSION_LIST_FIELDS,
|
2023-11-18 19:55:35 +01:00
|
|
|
}) ?? extension,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-27 22:28:55 +02:00
|
|
|
[this.cache.getFetchTimestampFor(EXTENSION_LIST_CACHE_KEY, undefined), result.loading],
|
2023-11-18 19:55:35 +01:00
|
|
|
);
|
2023-11-04 22:40:38 +01:00
|
|
|
|
2023-11-05 22:37:28 +01:00
|
|
|
const wrappedMutate = async (mutateOptions: Parameters<typeof mutate>[0]) => {
|
2023-11-04 22:40:38 +01:00
|
|
|
if (cachedResult) {
|
2023-11-05 22:37:28 +01:00
|
|
|
return normalizedCachedResult;
|
2023-11-04 22:40:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mutate(mutateOptions);
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-05 22:37:28 +01:00
|
|
|
return [wrappedMutate, normalizedCachedResult];
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:28:23 +02:00
|
|
|
public installExternalExtension(
|
|
|
|
|
extensionFile: File,
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<InstallExternalExtensionMutation, InstallExternalExtensionMutationVariables>,
|
2023-09-01 20:28:23 +02:00
|
|
|
): AbortableApolloMutationResponse<InstallExternalExtensionMutation> {
|
2024-01-29 20:59:27 +01:00
|
|
|
const result = this.doRequest<InstallExternalExtensionMutation, InstallExternalExtensionMutationVariables>(
|
2023-09-01 20:28:23 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
INSTALL_EXTERNAL_EXTENSION,
|
|
|
|
|
{ file: extensionFile },
|
2023-10-16 00:05:51 +02:00
|
|
|
{ refetchQueries: [GET_EXTENSIONS], ...options },
|
2023-09-01 20:28:23 +02:00
|
|
|
);
|
2024-01-29 20:59:27 +01:00
|
|
|
|
|
|
|
|
result.response.then((response) => {
|
2024-06-02 15:24:29 +02:00
|
|
|
if (!response.data?.installExternalExtension?.extension) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-29 20:59:27 +01:00
|
|
|
this.graphQLClient.client.cache.evict({ fieldName: 'sources' });
|
|
|
|
|
const cachedExtensions = this.cache.getResponseFor<MutationResult<GetExtensionsFetchMutation>>(
|
|
|
|
|
EXTENSION_LIST_CACHE_KEY,
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const installedExtension = response.data?.installExternalExtension.extension;
|
|
|
|
|
|
|
|
|
|
if (!cachedExtensions || !cachedExtensions.data) {
|
|
|
|
|
this.cache.cacheResponse(EXTENSION_LIST_CACHE_KEY, undefined, {
|
|
|
|
|
data: {
|
|
|
|
|
fetchExtensions: {
|
|
|
|
|
extensions: [installedExtension],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 15:24:29 +02:00
|
|
|
const isExtensionCached = !!cachedExtensions.data.fetchExtensions?.extensions.find(
|
2024-01-29 20:59:27 +01:00
|
|
|
(extension) => installedExtension?.pkgName === extension.pkgName,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const updatedCachedExtensions: MutationResult<GetExtensionsFetchMutation> = {
|
|
|
|
|
...cachedExtensions,
|
|
|
|
|
data: {
|
|
|
|
|
...cachedExtensions.data,
|
|
|
|
|
fetchExtensions: {
|
|
|
|
|
...cachedExtensions.data.fetchExtensions,
|
|
|
|
|
extensions: isExtensionCached
|
2024-06-02 15:24:29 +02:00
|
|
|
? cachedExtensions.data.fetchExtensions!.extensions.map((extension) => {
|
2024-01-29 20:59:27 +01:00
|
|
|
const isUpdatedExtension = installedExtension?.pkgName === extension.pkgName;
|
|
|
|
|
if (!isUpdatedExtension) {
|
|
|
|
|
return extension;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...extension,
|
|
|
|
|
...installedExtension,
|
|
|
|
|
hasUpdate: installedExtension?.versionCode < extension.versionCode,
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
: [
|
2024-06-02 15:24:29 +02:00
|
|
|
...(cachedExtensions.data.fetchExtensions?.extensions ?? []),
|
|
|
|
|
installedExtension as NonNullable<
|
|
|
|
|
GetExtensionsFetchMutation['fetchExtensions']
|
|
|
|
|
>['extensions'][number],
|
2024-01-29 20:59:27 +01:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.cache.cacheResponse(EXTENSION_LIST_CACHE_KEY, undefined, updatedCachedExtensions);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:28:23 +02:00
|
|
|
public updateExtension(
|
|
|
|
|
id: string,
|
2024-01-29 20:56:09 +01:00
|
|
|
{ isObsolete = false, ...patch }: UpdateExtensionPatchInput & { isObsolete?: boolean },
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<UpdateExtensionMutation, UpdateExtensionMutationVariables>,
|
2023-09-01 20:28:23 +02:00
|
|
|
): AbortableApolloMutationResponse<UpdateExtensionMutation> {
|
2023-10-16 00:05:51 +02:00
|
|
|
const result = this.doRequest<UpdateExtensionMutation, UpdateExtensionMutationVariables>(
|
2023-09-01 20:28:23 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_EXTENSION,
|
|
|
|
|
{ input: { id, patch } },
|
2023-11-05 22:37:28 +01:00
|
|
|
options,
|
2023-09-01 20:28:23 +02:00
|
|
|
);
|
2023-10-16 00:05:51 +02:00
|
|
|
|
2023-11-24 03:20:16 +01:00
|
|
|
result.response.then((response) => {
|
2024-06-02 15:24:29 +02:00
|
|
|
if (response.errors) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-16 00:05:51 +02:00
|
|
|
this.graphQLClient.client.cache.evict({ fieldName: 'sources' });
|
2023-11-24 03:20:16 +01:00
|
|
|
const cachedExtensions = this.cache.getResponseFor<MutationResult<GetExtensionsFetchMutation>>(
|
|
|
|
|
EXTENSION_LIST_CACHE_KEY,
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!cachedExtensions || !cachedExtensions.data) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedCachedExtensions: MutationResult<GetExtensionsFetchMutation> = {
|
|
|
|
|
...cachedExtensions,
|
|
|
|
|
data: {
|
|
|
|
|
...cachedExtensions.data,
|
|
|
|
|
fetchExtensions: {
|
|
|
|
|
...cachedExtensions.data.fetchExtensions,
|
2024-06-02 15:24:29 +02:00
|
|
|
extensions:
|
|
|
|
|
cachedExtensions.data.fetchExtensions?.extensions
|
|
|
|
|
.filter((extension) => {
|
|
|
|
|
if (!isObsolete) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isUpdatedExtension = id === extension.pkgName;
|
|
|
|
|
return !isUpdatedExtension;
|
|
|
|
|
})
|
|
|
|
|
.map((extension) => {
|
|
|
|
|
const isUpdatedExtension = id === extension.pkgName;
|
|
|
|
|
if (!isUpdatedExtension) {
|
|
|
|
|
return extension;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...extension,
|
|
|
|
|
...(response.data?.updateExtension?.extension ?? []),
|
|
|
|
|
};
|
|
|
|
|
}) ?? [],
|
2023-11-24 03:20:16 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.cache.cacheResponse(EXTENSION_LIST_CACHE_KEY, undefined, updatedCachedExtensions);
|
2023-10-16 00:05:51 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-04 01:26:58 +01:00
|
|
|
public updateExtensions(
|
|
|
|
|
ids: string[],
|
|
|
|
|
{ isObsolete = false, ...patch }: UpdateExtensionPatchInput & { isObsolete?: boolean },
|
|
|
|
|
options?: MutationOptions<UpdateExtensionsMutation, UpdateExtensionsMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateExtensionsMutation> {
|
|
|
|
|
const result = this.doRequest<UpdateExtensionsMutation, UpdateExtensionsMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_EXTENSIONS,
|
|
|
|
|
{ input: { ids, patch } },
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
result.response.then((response) => {
|
|
|
|
|
if (response.errors) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.graphQLClient.client.cache.evict({ fieldName: 'sources' });
|
|
|
|
|
const cachedExtensions = this.cache.getResponseFor<MutationResult<GetExtensionsFetchMutation>>(
|
|
|
|
|
EXTENSION_LIST_CACHE_KEY,
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!cachedExtensions || !cachedExtensions.data) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedCachedExtensions: MutationResult<GetExtensionsFetchMutation> = {
|
|
|
|
|
...cachedExtensions,
|
|
|
|
|
data: {
|
|
|
|
|
...cachedExtensions.data,
|
|
|
|
|
fetchExtensions: {
|
|
|
|
|
...cachedExtensions.data.fetchExtensions,
|
|
|
|
|
extensions:
|
|
|
|
|
cachedExtensions.data.fetchExtensions?.extensions
|
|
|
|
|
.filter((extension) => {
|
|
|
|
|
if (!isObsolete) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isUpdatedExtension = ids.includes(extension.pkgName);
|
|
|
|
|
return !isUpdatedExtension;
|
|
|
|
|
})
|
|
|
|
|
.map((extension) => {
|
|
|
|
|
const isUpdatedExtension = ids.includes(extension.pkgName);
|
|
|
|
|
if (!isUpdatedExtension) {
|
|
|
|
|
return extension;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...extension,
|
|
|
|
|
...(response.data?.updateExtensions?.extensions.find(
|
|
|
|
|
(updatedExtension) => updatedExtension.pkgName === extension.pkgName,
|
|
|
|
|
) ?? []),
|
|
|
|
|
};
|
|
|
|
|
}) ?? [],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.cache.cacheResponse(EXTENSION_LIST_CACHE_KEY, undefined, updatedCachedExtensions);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public getExtensionIconUrl(extension: string): string {
|
|
|
|
|
return this.getValidImgUrlFor(`extension/icon/${extension}`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:29:19 +02:00
|
|
|
public useGetSourceList(
|
2024-07-03 23:15:50 +02:00
|
|
|
options?: QueryHookOptions<GetSourcesListQuery, GetSourcesListQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetSourcesListQuery, GetSourcesListQueryVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_SOURCES_LIST, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-03 23:15:50 +02:00
|
|
|
public useGetSource<Data, Variables extends OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
2023-09-01 20:29:19 +02:00
|
|
|
id: string,
|
2024-07-03 23:15:50 +02:00
|
|
|
options?: QueryHookOptions<Data, Variables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, document, { id } as unknown as Variables, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-05-08 04:01:23 +02:00
|
|
|
public setSourceMeta(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
key: string,
|
|
|
|
|
value: any,
|
|
|
|
|
options?: MutationOptions<SetSourceMetadataMutation, SetSourceMetadataMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<SetSourceMetadataMutation> {
|
|
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
SET_SOURCE_METADATA,
|
|
|
|
|
{
|
|
|
|
|
input: { meta: { sourceId, key, value: `${value}` } },
|
|
|
|
|
},
|
2024-09-21 20:01:06 +02:00
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
setSourceMeta: {
|
|
|
|
|
__typename: 'SetSourceMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'SourceMetaType',
|
|
|
|
|
sourceId,
|
|
|
|
|
key,
|
|
|
|
|
value: `${value}`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-09-22 02:46:01 +02:00
|
|
|
update(cache, { data }) {
|
|
|
|
|
cache.modify({
|
|
|
|
|
id: cache.identify({ __typename: 'SourceType', id: sourceId }),
|
|
|
|
|
fields: {
|
|
|
|
|
meta(existingMetas, { readField }) {
|
2024-11-04 18:00:35 +01:00
|
|
|
return updateMetadataList(key, existingMetas, readField, () =>
|
2024-09-22 02:46:01 +02:00
|
|
|
cache.writeFragment({
|
|
|
|
|
data: data!.setSourceMeta!.meta,
|
|
|
|
|
fragment: SOURCE_META_FIELDS,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
2024-09-21 20:01:06 +02:00
|
|
|
...options,
|
|
|
|
|
},
|
2024-05-08 04:01:23 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 17:25:04 +02:00
|
|
|
public deleteSourceMeta(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
key: string,
|
|
|
|
|
options?: MutationOptions<DeleteSourceMetadataMutation, DeleteSourceMetadataMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DeleteSourceMetadataMutation> {
|
|
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_SOURCE_METADATA,
|
|
|
|
|
{ input: { sourceId, key } },
|
|
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
deleteSourceMeta: {
|
|
|
|
|
__typename: 'DeleteSourceMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'SourceMetaType',
|
|
|
|
|
sourceId,
|
|
|
|
|
key,
|
|
|
|
|
value: '',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
update(cache) {
|
2024-11-04 18:00:35 +01:00
|
|
|
cache.evict({ id: cache.identify({ __typename: 'SourceMetaType', sourceId, key }) });
|
2024-10-26 17:25:04 +02:00
|
|
|
},
|
|
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-08 00:06:24 +02:00
|
|
|
public useGetSourceMangas(
|
|
|
|
|
input: FetchSourceMangaInput,
|
2023-09-12 02:36:11 +02:00
|
|
|
initialPages: number = 1,
|
|
|
|
|
options?: ApolloPaginatedMutationOptions<GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables>,
|
2023-09-08 00:06:24 +02:00
|
|
|
): AbortableApolloUseMutationPaginatedResponse<
|
|
|
|
|
GetSourceMangasFetchMutation,
|
|
|
|
|
GetSourceMangasFetchMutationVariables
|
|
|
|
|
> {
|
2023-09-12 02:36:11 +02:00
|
|
|
type MutationResult = AbortableApolloUseMutationPaginatedResponse<
|
|
|
|
|
GetSourceMangasFetchMutation,
|
|
|
|
|
GetSourceMangasFetchMutationVariables
|
|
|
|
|
>[1];
|
|
|
|
|
type MutationDataResult = MutationResult[number];
|
|
|
|
|
|
2023-09-08 00:06:24 +02:00
|
|
|
const createPaginatedResult = (
|
2023-09-12 02:36:11 +02:00
|
|
|
result?: Partial<AbortableApolloUseMutationPaginatedResponse[1][number]> | null,
|
|
|
|
|
page?: number,
|
|
|
|
|
) => this.createPaginatedResult(result, input.page, page);
|
|
|
|
|
|
|
|
|
|
const getVariablesFor = (page: number): GetSourceMangasFetchMutationVariables => ({
|
|
|
|
|
input: {
|
|
|
|
|
...input,
|
|
|
|
|
page,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isRevalidationDoneRef = useRef(false);
|
|
|
|
|
const activeRevalidationRef = useRef<
|
|
|
|
|
| [
|
|
|
|
|
ForInput: GetSourceMangasFetchMutationVariables,
|
|
|
|
|
Request: Promise<unknown>,
|
|
|
|
|
AbortRequest: AbortableRequest['abortRequest'],
|
|
|
|
|
]
|
|
|
|
|
| null
|
|
|
|
|
>(null);
|
|
|
|
|
const abortRequestRef = useRef<AbortableRequest['abortRequest']>(() => {});
|
|
|
|
|
const resultRef = useRef<(MutationDataResult & { forInput: string }) | null>(null);
|
|
|
|
|
const result = resultRef.current;
|
|
|
|
|
|
|
|
|
|
const [, setTriggerRerender] = useState(0);
|
|
|
|
|
const triggerRerender = () => setTriggerRerender((prev) => prev + 1);
|
|
|
|
|
const setResult = (nextResult: typeof resultRef.current) => {
|
|
|
|
|
resultRef.current = nextResult;
|
|
|
|
|
triggerRerender();
|
2023-09-08 00:06:24 +02:00
|
|
|
};
|
|
|
|
|
|
2023-09-12 02:36:11 +02:00
|
|
|
const cachedPages = this.cache.getResponseFor<Set<number>>(CACHE_PAGES_KEY, getVariablesFor(0)) ?? new Set();
|
|
|
|
|
const cachedResults = [...cachedPages]
|
|
|
|
|
.map(
|
|
|
|
|
(cachedPage) =>
|
|
|
|
|
this.cache.getResponseFor<MutationDataResult>(CACHE_RESULTS_KEY, getVariablesFor(cachedPage))!,
|
|
|
|
|
)
|
|
|
|
|
.sort((a, b) => a.size - b.size);
|
|
|
|
|
const areFetchingInitialPages = !!this.cache.getResponseFor<boolean>(
|
|
|
|
|
CACHE_INITIAL_PAGES_FETCHING_KEY,
|
|
|
|
|
getVariablesFor(0),
|
2023-09-08 00:06:24 +02:00
|
|
|
);
|
|
|
|
|
|
2023-10-29 20:51:53 +01:00
|
|
|
const areInitialPagesFetched =
|
|
|
|
|
cachedResults.length >= initialPages ||
|
2024-06-02 15:24:29 +02:00
|
|
|
(!!cachedResults.length && !cachedResults[cachedResults.length - 1].data?.fetchSourceManga?.hasNextPage);
|
2023-09-12 02:36:11 +02:00
|
|
|
const isResultForCurrentInput = result?.forInput === JSON.stringify(getVariablesFor(0));
|
|
|
|
|
const lastPage = cachedPages.size ? Math.max(...cachedPages) : input.page;
|
|
|
|
|
const nextPage = isResultForCurrentInput ? result.size : lastPage;
|
|
|
|
|
|
|
|
|
|
const paginatedResult =
|
|
|
|
|
isResultForCurrentInput && areInitialPagesFetched ? result : createPaginatedResult(undefined, nextPage);
|
|
|
|
|
paginatedResult.abortRequest = abortRequestRef.current;
|
|
|
|
|
|
|
|
|
|
// make sure that the result is always for the current input
|
|
|
|
|
resultRef.current = { forInput: JSON.stringify(getVariablesFor(0)), ...paginatedResult };
|
|
|
|
|
|
|
|
|
|
const hasCachedResult = !!this.cache.getResponseFor(CACHE_RESULTS_KEY, getVariablesFor(nextPage));
|
|
|
|
|
|
|
|
|
|
const revalidatePage = async (pageToRevalidate: number, maxPage: number, signal: AbortSignal) =>
|
|
|
|
|
this.revalidatePage(
|
2023-11-11 23:43:22 +01:00
|
|
|
input.source,
|
2023-09-12 02:36:11 +02:00
|
|
|
CACHE_RESULTS_KEY,
|
|
|
|
|
CACHE_PAGES_KEY,
|
|
|
|
|
getVariablesFor,
|
|
|
|
|
options,
|
|
|
|
|
(cachedResult, revalidatedResult) =>
|
|
|
|
|
!cachedResult ||
|
2024-06-02 15:24:29 +02:00
|
|
|
!cachedResult.data?.fetchSourceManga?.mangas.length ||
|
2023-09-12 02:36:11 +02:00
|
|
|
cachedResult.data.fetchSourceManga.mangas.some(
|
2024-06-02 15:24:29 +02:00
|
|
|
(manga, index) => manga.id !== revalidatedResult.data?.fetchSourceManga?.mangas[index]?.id,
|
2023-09-12 02:36:11 +02:00
|
|
|
),
|
2024-06-02 15:24:29 +02:00
|
|
|
(revalidatedResult) => !!revalidatedResult.data?.fetchSourceManga?.hasNextPage,
|
2023-09-12 02:36:11 +02:00
|
|
|
pageToRevalidate,
|
|
|
|
|
maxPage,
|
|
|
|
|
signal,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const revalidate = async (
|
|
|
|
|
maxPage: number,
|
|
|
|
|
abortRequest: AbortableRequest['abortRequest'],
|
|
|
|
|
signal: AbortSignal,
|
|
|
|
|
) =>
|
|
|
|
|
this.revalidatePages(
|
|
|
|
|
activeRevalidationRef.current,
|
|
|
|
|
(isDone) => {
|
|
|
|
|
isRevalidationDoneRef.current = isDone;
|
|
|
|
|
},
|
|
|
|
|
(activeRevalidation) => {
|
|
|
|
|
activeRevalidationRef.current = activeRevalidation;
|
|
|
|
|
},
|
|
|
|
|
getVariablesFor,
|
|
|
|
|
(isValidating) => {
|
|
|
|
|
setResult({
|
|
|
|
|
...createPaginatedResult(resultRef.current),
|
|
|
|
|
isValidating,
|
|
|
|
|
forInput: JSON.stringify(getVariablesFor(0)),
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
revalidatePage,
|
|
|
|
|
maxPage,
|
|
|
|
|
abortRequest,
|
|
|
|
|
signal,
|
|
|
|
|
);
|
2023-09-08 00:06:24 +02:00
|
|
|
|
|
|
|
|
// wrap "mutate" function to align with the expected type, which allows only passing a "page" argument
|
2023-09-12 02:36:11 +02:00
|
|
|
const wrappedMutate = async (newPage: number) =>
|
|
|
|
|
this.fetchPaginatedMutationPage<GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables>(
|
|
|
|
|
getVariablesFor,
|
|
|
|
|
(abortRequest) => {
|
|
|
|
|
abortRequestRef.current = abortRequest;
|
2023-09-08 00:06:24 +02:00
|
|
|
},
|
2023-09-12 02:36:11 +02:00
|
|
|
() => ({ forType: input.type, forQuery: input.query }),
|
|
|
|
|
createPaginatedResult,
|
|
|
|
|
setResult,
|
|
|
|
|
revalidate,
|
|
|
|
|
options,
|
|
|
|
|
GET_SOURCE_MANGAS_FETCH,
|
|
|
|
|
CACHE_PAGES_KEY,
|
|
|
|
|
CACHE_RESULTS_KEY,
|
|
|
|
|
cachedPages,
|
|
|
|
|
newPage,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.fetchInitialPages(
|
|
|
|
|
options,
|
|
|
|
|
areFetchingInitialPages,
|
|
|
|
|
areInitialPagesFetched,
|
|
|
|
|
(isDone) => {
|
|
|
|
|
isRevalidationDoneRef.current = isDone;
|
|
|
|
|
},
|
|
|
|
|
CACHE_INITIAL_PAGES_FETCHING_KEY,
|
|
|
|
|
getVariablesFor,
|
|
|
|
|
initialPages,
|
|
|
|
|
wrappedMutate,
|
2024-06-02 15:24:29 +02:00
|
|
|
(fetchedResult) => !!fetchedResult.data?.fetchSourceManga?.hasNextPage,
|
2023-09-12 02:36:11 +02:00
|
|
|
);
|
2023-09-08 00:06:24 +02:00
|
|
|
|
2023-09-12 02:36:11 +02:00
|
|
|
this.revalidateInitialPages(
|
|
|
|
|
isRevalidationDoneRef.current,
|
|
|
|
|
cachedResults.length,
|
|
|
|
|
cachedPages,
|
|
|
|
|
(isDone) => {
|
|
|
|
|
isRevalidationDoneRef.current = isDone;
|
|
|
|
|
},
|
|
|
|
|
getVariablesFor,
|
|
|
|
|
triggerRerender,
|
|
|
|
|
revalidate,
|
|
|
|
|
);
|
2023-09-08 00:06:24 +02:00
|
|
|
|
2023-11-05 22:40:35 +01:00
|
|
|
const normalizedCachedResults = cachedResults.map((cachedResult) => {
|
2024-06-02 15:24:29 +02:00
|
|
|
const hasResults = !!cachedResult.data?.fetchSourceManga?.mangas;
|
2023-11-05 22:40:35 +01:00
|
|
|
if (!hasResults) {
|
|
|
|
|
return cachedResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...cachedResult,
|
|
|
|
|
data: {
|
|
|
|
|
...cachedResult.data,
|
|
|
|
|
fetchSourceManga: {
|
|
|
|
|
...cachedResult.data?.fetchSourceManga,
|
2024-06-02 15:24:29 +02:00
|
|
|
mangas: cachedResult.data?.fetchSourceManga?.mangas.map(
|
2023-11-05 22:40:35 +01:00
|
|
|
(manga) =>
|
|
|
|
|
this.graphQLClient.client.cache.readFragment<typeof manga>({
|
|
|
|
|
id: this.graphQLClient.client.cache.identify(manga),
|
2024-07-08 18:02:50 +02:00
|
|
|
fragment: MANGA_BASE_FIELDS,
|
|
|
|
|
fragmentName: 'MANGA_BASE_FIELDS',
|
2023-11-05 22:40:35 +01:00
|
|
|
}) ?? manga,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2023-09-12 02:36:11 +02:00
|
|
|
return this.returnPaginatedMutationResult(
|
|
|
|
|
areInitialPagesFetched,
|
2023-11-05 22:40:35 +01:00
|
|
|
normalizedCachedResults,
|
2023-09-12 02:36:11 +02:00
|
|
|
getVariablesFor,
|
|
|
|
|
paginatedResult,
|
|
|
|
|
wrappedMutate,
|
|
|
|
|
hasCachedResult,
|
|
|
|
|
createPaginatedResult,
|
|
|
|
|
);
|
2023-09-08 00:06:24 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public useGetSourcePopularMangas(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
initialPages?: number,
|
2023-09-12 02:36:11 +02:00
|
|
|
options?: ApolloPaginatedMutationOptions<GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationPaginatedResponse<
|
|
|
|
|
GetSourceMangasFetchMutation,
|
|
|
|
|
GetSourceMangasFetchMutationVariables
|
|
|
|
|
> {
|
|
|
|
|
return this.useGetSourceMangas(
|
|
|
|
|
{ type: FetchSourceMangaType.Popular, source: sourceId, page: 1 },
|
|
|
|
|
initialPages,
|
|
|
|
|
options,
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetSourceLatestMangas(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
initialPages?: number,
|
2023-09-12 02:36:11 +02:00
|
|
|
options?: ApolloPaginatedMutationOptions<GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationPaginatedResponse<
|
|
|
|
|
GetSourceMangasFetchMutation,
|
|
|
|
|
GetSourceMangasFetchMutationVariables
|
|
|
|
|
> {
|
|
|
|
|
return this.useGetSourceMangas(
|
|
|
|
|
{ type: FetchSourceMangaType.Latest, source: sourceId, page: 1 },
|
|
|
|
|
initialPages,
|
|
|
|
|
options,
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-25 23:32:26 +02:00
|
|
|
public setSourcePreferences(
|
|
|
|
|
source: string,
|
|
|
|
|
change: SourcePreferenceChangeInput,
|
|
|
|
|
options?: MutationOptions<UpdateSourcePreferencesMutation, UpdateSourcePreferencesMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateSourcePreferencesMutation> {
|
2023-10-16 00:05:51 +02:00
|
|
|
return this.doRequest(GQLMethod.MUTATION, UPDATE_SOURCE_PREFERENCES, { input: { source, change } }, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useSourceSearch(
|
2023-09-08 00:06:24 +02:00
|
|
|
source: string,
|
2023-09-12 02:36:11 +02:00
|
|
|
query?: string,
|
2023-09-08 00:06:24 +02:00
|
|
|
filters?: FilterChangeInput[],
|
2023-09-12 02:36:11 +02:00
|
|
|
initialPages?: number,
|
|
|
|
|
options?: ApolloPaginatedMutationOptions<GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables>,
|
2023-09-08 00:06:24 +02:00
|
|
|
): AbortableApolloUseMutationPaginatedResponse<
|
|
|
|
|
GetSourceMangasFetchMutation,
|
|
|
|
|
GetSourceMangasFetchMutationVariables
|
|
|
|
|
> {
|
2023-09-12 02:36:11 +02:00
|
|
|
return this.useGetSourceMangas(
|
|
|
|
|
{ type: FetchSourceMangaType.Search, source, query, filters, page: 1 },
|
|
|
|
|
initialPages,
|
|
|
|
|
options,
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-08 18:02:50 +02:00
|
|
|
public useGetManga<Data, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
2023-05-18 13:25:19 +02:00
|
|
|
mangaId: number | string,
|
2024-07-08 18:02:50 +02:00
|
|
|
options?: QueryHookOptions<Data, Variables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, document, { id: Number(mangaId) } as unknown as Variables, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-08 18:02:50 +02:00
|
|
|
public getManga<Data, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
2024-01-26 20:50:51 +01:00
|
|
|
mangaId: number | string,
|
2024-07-08 18:02:50 +02:00
|
|
|
options?: QueryOptions<Variables, Data>,
|
|
|
|
|
): AbortabaleApolloQueryResponse<Data> {
|
|
|
|
|
return this.doRequest(GQLMethod.QUERY, document, { id: Number(mangaId) } as unknown as Variables, options);
|
2024-01-26 20:50:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getMangaToMigrate(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
{
|
|
|
|
|
migrateChapters = false,
|
|
|
|
|
migrateCategories = false,
|
2024-04-20 23:51:47 +02:00
|
|
|
migrateTracking = false,
|
2024-03-16 13:45:54 +01:00
|
|
|
deleteChapters = false,
|
2024-01-26 20:50:51 +01:00
|
|
|
apolloOptions: options,
|
2024-04-20 23:51:47 +02:00
|
|
|
}: Partial<MetadataMigrationSettings> & {
|
2024-01-26 20:50:51 +01:00
|
|
|
apolloOptions?: QueryOptions<GetMangaToMigrateQueryVariables, GetMangaToMigrateQuery>;
|
|
|
|
|
} = {},
|
|
|
|
|
): AbortabaleApolloQueryResponse<GetMangaToMigrateQuery> {
|
|
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.QUERY,
|
|
|
|
|
GET_MANGA_TO_MIGRATE,
|
2024-04-20 23:51:47 +02:00
|
|
|
{
|
|
|
|
|
id: Number(mangaId),
|
|
|
|
|
getChapterData: migrateChapters || deleteChapters,
|
|
|
|
|
migrateCategories,
|
|
|
|
|
migrateTracking,
|
|
|
|
|
},
|
2024-01-26 20:50:51 +01:00
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-08 00:44:01 +02:00
|
|
|
public getMangaFetch(
|
2023-05-18 13:25:19 +02:00
|
|
|
mangaId: number | string,
|
2023-09-08 00:44:01 +02:00
|
|
|
options?: MutationOptions<GetMangaFetchMutation, GetMangaFetchMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<GetMangaFetchMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<GetMangaFetchMutation, GetMangaFetchMutationVariables>(
|
2023-09-08 00:44:01 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
GET_MANGA_FETCH,
|
|
|
|
|
{
|
|
|
|
|
input: {
|
|
|
|
|
id: Number(mangaId),
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-08 00:44:01 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 20:50:51 +01:00
|
|
|
public getMangaToMigrateToFetch(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
{
|
|
|
|
|
migrateChapters = false,
|
|
|
|
|
migrateCategories = false,
|
2024-04-20 23:51:47 +02:00
|
|
|
migrateTracking = false,
|
2024-01-26 20:50:51 +01:00
|
|
|
apolloOptions: options,
|
2024-04-20 23:51:47 +02:00
|
|
|
}: Partial<Omit<MetadataMigrationSettings, 'deleteChapters'>> & {
|
2024-01-26 20:50:51 +01:00
|
|
|
apolloOptions?: MutationOptions<
|
|
|
|
|
GetMangaToMigrateToFetchMutation,
|
|
|
|
|
GetMangaToMigrateToFetchMutationVariables
|
|
|
|
|
>;
|
|
|
|
|
} = {},
|
|
|
|
|
): AbortableApolloMutationResponse<GetMangaToMigrateToFetchMutation> {
|
|
|
|
|
return this.doRequest<GetMangaToMigrateToFetchMutation, GetMangaToMigrateToFetchMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
GET_MANGA_TO_MIGRATE_TO_FETCH,
|
|
|
|
|
{
|
|
|
|
|
id: Number(mangaId),
|
|
|
|
|
migrateChapters,
|
|
|
|
|
migrateCategories,
|
2024-04-20 23:51:47 +02:00
|
|
|
migrateTracking,
|
2024-01-26 20:50:51 +01:00
|
|
|
},
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-08 18:02:50 +02:00
|
|
|
public useGetMangas<Data, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables,
|
|
|
|
|
options?: QueryHookOptions<Data, Variables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, document, variables, options);
|
2023-10-19 21:01:21 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-08 18:02:50 +02:00
|
|
|
public getMangas<Data, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables,
|
|
|
|
|
options?: QueryOptions<Variables, Data>,
|
|
|
|
|
): AbortabaleApolloQueryResponse<Data> {
|
|
|
|
|
return this.doRequest(GQLMethod.QUERY, document, variables, options);
|
2023-12-27 18:17:41 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 20:50:51 +01:00
|
|
|
public useGetMigratableSourceMangas(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
options?: QueryHookOptions<GetMigratableSourceMangasQuery, GetMigratableSourceMangasQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetMigratableSourceMangasQuery, GetMigratableSourceMangasQueryVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_MIGRATABLE_SOURCE_MANGAS, { sourceId }, options);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:33:35 +02:00
|
|
|
public useUpdateMangaCategories(
|
|
|
|
|
options?: MutationHookOptions<UpdateMangaCategoriesMutation, UpdateMangaCategoriesMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<UpdateMangaCategoriesMutation, UpdateMangaCategoriesMutationVariables> {
|
2023-10-16 00:05:51 +02:00
|
|
|
const [mutate, result] = this.doRequest(GQLMethod.USE_MUTATION, UPDATE_MANGA_CATEGORIES, undefined, options);
|
|
|
|
|
|
|
|
|
|
const wrappedMutate = (mutateOptions: Parameters<typeof mutate>[0]) =>
|
|
|
|
|
mutate({
|
|
|
|
|
onCompleted: () => {
|
2023-12-25 21:37:45 +01:00
|
|
|
this.graphQLClient.client.cache.evict({ broadcast: true, fieldName: 'categories' });
|
|
|
|
|
this.graphQLClient.client.cache.evict({ broadcast: true, fieldName: 'mangas' });
|
2023-10-16 00:05:51 +02:00
|
|
|
},
|
|
|
|
|
...mutateOptions,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return [wrappedMutate, result];
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-25 21:37:45 +01:00
|
|
|
public updateMangasCategories(
|
|
|
|
|
mangaIds: number[],
|
|
|
|
|
patch: UpdateMangaCategoriesPatchInput,
|
|
|
|
|
options?: MutationOptions<UpdateMangasCategoriesMutation, UpdateMangasCategoriesMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateMangasCategoriesMutation> {
|
|
|
|
|
const response = this.doRequest(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_MANGAS_CATEGORIES,
|
|
|
|
|
{ input: { ids: mangaIds, patch } },
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
response.response.then(() => {
|
|
|
|
|
this.graphQLClient.client.cache.evict({ broadcast: true, fieldName: 'categories' });
|
|
|
|
|
this.graphQLClient.client.cache.evict({ broadcast: true, fieldName: 'mangas' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:33:35 +02:00
|
|
|
public updateManga(
|
|
|
|
|
id: number,
|
2024-03-02 15:26:15 +01:00
|
|
|
patch: { updateManga: UpdateMangaPatchInput; updateMangaCategories?: UpdateMangaCategoriesPatchInput },
|
2023-10-16 00:05:51 +02:00
|
|
|
options?: MutationOptions<UpdateMangaMutation, UpdateMangaMutationVariables>,
|
2023-09-01 20:33:35 +02:00
|
|
|
): AbortableApolloMutationResponse<UpdateMangaMutation> {
|
2023-10-16 00:05:51 +02:00
|
|
|
const result = this.doRequest<UpdateMangaMutation, UpdateMangaMutationVariables>(
|
2023-09-01 20:33:35 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_MANGA,
|
2024-03-02 15:26:15 +01:00
|
|
|
{
|
|
|
|
|
input: { id, patch: patch.updateManga },
|
|
|
|
|
updateCategoryInput: { id, patch: patch.updateMangaCategories ?? {} },
|
|
|
|
|
updateCategories: !!patch.updateMangaCategories,
|
|
|
|
|
},
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:33:35 +02:00
|
|
|
);
|
2023-10-16 00:05:51 +02:00
|
|
|
|
|
|
|
|
result.response.then(() => {
|
2024-01-17 13:45:30 +01:00
|
|
|
this.graphQLClient.client.cache.evict({ broadcast: true, fieldName: 'categories' });
|
|
|
|
|
this.graphQLClient.client.cache.evict({ broadcast: true, fieldName: 'mangas' });
|
2023-10-16 00:05:51 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-25 21:37:45 +01:00
|
|
|
public updateMangas(
|
|
|
|
|
ids: number[],
|
2024-03-02 15:28:58 +01:00
|
|
|
patch: { updateMangas: UpdateMangaPatchInput; updateMangasCategories?: UpdateMangaCategoriesPatchInput },
|
2023-12-25 21:37:45 +01:00
|
|
|
options?: MutationOptions<UpdateMangasMutation, UpdateMangasMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateMangasMutation> {
|
|
|
|
|
const result = this.doRequest<UpdateMangasMutation, UpdateMangasMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_MANGAS,
|
2024-03-02 15:28:58 +01:00
|
|
|
{
|
|
|
|
|
input: { ids, patch: patch.updateMangas },
|
|
|
|
|
updateCategoryInput: { ids, patch: patch.updateMangasCategories ?? {} },
|
|
|
|
|
updateCategories: !!patch.updateMangasCategories,
|
|
|
|
|
},
|
2023-12-25 21:37:45 +01:00
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
result.response.then(() => {
|
|
|
|
|
this.graphQLClient.client.cache.evict({ fieldName: 'categories' });
|
|
|
|
|
this.graphQLClient.client.cache.evict({ fieldName: 'category' });
|
|
|
|
|
this.graphQLClient.client.cache.evict({ fieldName: 'mangas' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:33:35 +02:00
|
|
|
public setMangaMeta(
|
|
|
|
|
mangaId: number,
|
|
|
|
|
key: string,
|
|
|
|
|
value: any,
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<SetMangaMetadataMutation, SetMangaMetadataMutationVariables>,
|
2023-09-01 20:33:35 +02:00
|
|
|
): AbortableApolloMutationResponse<SetMangaMetadataMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest(
|
2023-09-01 20:33:35 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
SET_MANGA_METADATA,
|
|
|
|
|
{
|
|
|
|
|
input: { meta: { mangaId, key, value: `${value}` } },
|
|
|
|
|
},
|
2024-09-21 20:01:06 +02:00
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
setMangaMeta: {
|
|
|
|
|
__typename: 'SetMangaMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'MangaMetaType',
|
|
|
|
|
mangaId,
|
|
|
|
|
key,
|
|
|
|
|
value: `${value}`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-09-22 02:46:01 +02:00
|
|
|
update(cache, { data }) {
|
|
|
|
|
cache.modify({
|
|
|
|
|
id: cache.identify({ __typename: 'MangaType', id: mangaId }),
|
|
|
|
|
fields: {
|
|
|
|
|
meta(existingMetas, { readField }) {
|
2024-11-04 18:00:35 +01:00
|
|
|
return updateMetadataList(key, existingMetas, readField, () =>
|
2024-09-22 02:46:01 +02:00
|
|
|
cache.writeFragment({
|
|
|
|
|
data: data!.setMangaMeta!.meta,
|
|
|
|
|
fragment: MANGA_META_FIELDS,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
2024-09-21 20:01:06 +02:00
|
|
|
...options,
|
|
|
|
|
},
|
2023-09-01 20:33:35 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-26 17:25:04 +02:00
|
|
|
public deleteMangaMeta(
|
|
|
|
|
mangaId: number,
|
|
|
|
|
key: string,
|
|
|
|
|
options?: MutationOptions<DeleteMangaMetadataMutation, DeleteMangaMetadataMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DeleteMangaMetadataMutation> {
|
|
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_MANGA_METADATA,
|
|
|
|
|
{ input: { mangaId, key } },
|
|
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
deleteMangaMeta: {
|
|
|
|
|
__typename: 'DeleteMangaMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'MangaMetaType',
|
|
|
|
|
mangaId,
|
|
|
|
|
key,
|
|
|
|
|
value: '',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
update(cache) {
|
2024-11-04 18:00:35 +01:00
|
|
|
cache.evict({ id: cache.identify({ __typename: 'MangaMetaType', mangaId, key }) });
|
2024-10-26 17:25:04 +02:00
|
|
|
},
|
|
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-02 16:20:28 +02:00
|
|
|
public useGetChapters<Data, Variables extends OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables,
|
|
|
|
|
options?: QueryHookOptions<Data, Variables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, document, variables, options);
|
2023-09-30 16:55:35 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-02 16:20:28 +02:00
|
|
|
public getChapters<Data, Variables extends OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables,
|
|
|
|
|
options?: QueryOptions<Variables, Data>,
|
|
|
|
|
): AbortabaleApolloQueryResponse<Data> {
|
|
|
|
|
return this.doRequest(GQLMethod.QUERY, document, variables, options);
|
2023-12-30 04:04:59 +01:00
|
|
|
}
|
|
|
|
|
|
2024-09-28 03:24:16 +02:00
|
|
|
public useGetMangaChapters<Data, Variables extends OperationVariables = OperationVariables>(
|
2024-07-02 16:20:28 +02:00
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
2023-05-18 13:25:19 +02:00
|
|
|
mangaId: number | string,
|
2024-07-02 16:20:28 +02:00
|
|
|
options?: QueryHookOptions<Data, Variables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
2023-09-30 16:55:35 +02:00
|
|
|
return this.useGetChapters(
|
2024-07-02 16:20:28 +02:00
|
|
|
document,
|
2023-09-30 16:55:35 +02:00
|
|
|
{
|
|
|
|
|
condition: { mangaId: Number(mangaId) },
|
2024-07-29 14:10:37 +02:00
|
|
|
order: [{ by: ChapterOrderBy.SourceOrder, byType: SortOrder.Desc }],
|
|
|
|
|
} satisfies GetChaptersMangaQueryVariables as unknown as Variables,
|
2023-09-30 16:55:35 +02:00
|
|
|
options,
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-25 21:37:45 +01:00
|
|
|
public getMangasChapterIdsWithState(
|
|
|
|
|
mangaIds: number[],
|
|
|
|
|
states: Pick<ChapterConditionInput, 'isRead' | 'isDownloaded' | 'isBookmarked'>,
|
|
|
|
|
options?: QueryOptions<GetMangasChapterIdsWithStateQueryVariables, GetMangasChapterIdsWithStateQuery>,
|
|
|
|
|
): AbortabaleApolloQueryResponse<GetMangasChapterIdsWithStateQuery> {
|
2024-04-29 12:50:58 +02:00
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.QUERY,
|
|
|
|
|
GET_MANGAS_CHAPTER_IDS_WITH_STATE,
|
|
|
|
|
{ mangaIds, ...states },
|
|
|
|
|
{
|
|
|
|
|
fetchPolicy: 'no-cache',
|
|
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-12-25 21:37:45 +01:00
|
|
|
}
|
|
|
|
|
|
2023-09-30 16:55:35 +02:00
|
|
|
public getMangaChaptersFetch(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
options?: MutationOptions<GetMangaChaptersFetchMutation, GetMangaChaptersFetchMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<GetMangaChaptersFetchMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<GetMangaChaptersFetchMutation, GetMangaChaptersFetchMutationVariables>(
|
2023-09-30 16:55:35 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
GET_MANGA_CHAPTERS_FETCH,
|
|
|
|
|
{ input: { mangaId: Number(mangaId) } },
|
2024-07-02 16:20:28 +02:00
|
|
|
{ refetchQueries: [GET_CHAPTERS_MANGA], ...options },
|
2023-09-30 16:55:35 +02:00
|
|
|
);
|
2023-06-04 00:50:58 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-30 16:55:35 +02:00
|
|
|
public useGetMangaChapter(
|
2023-05-18 13:25:19 +02:00
|
|
|
mangaId: number | string,
|
|
|
|
|
chapterIndex: number | string,
|
2024-07-02 16:20:28 +02:00
|
|
|
options?: QueryHookOptions<GetChaptersMangaQuery, GetChaptersMangaQueryVariables>,
|
2023-09-30 16:55:35 +02:00
|
|
|
): AbortableApolloUseQueryResponse<
|
2024-07-02 16:20:28 +02:00
|
|
|
Omit<GetChaptersMangaQuery, 'chapters'> & { chapter: GetChaptersMangaQuery['chapters']['nodes'][number] },
|
|
|
|
|
GetChaptersMangaQueryVariables
|
2023-09-30 16:55:35 +02:00
|
|
|
> {
|
|
|
|
|
type Response = AbortableApolloUseQueryResponse<
|
2024-07-02 16:20:28 +02:00
|
|
|
Omit<GetChaptersMangaQuery, 'chapters'> & { chapter: GetChaptersMangaQuery['chapters']['nodes'][number] },
|
|
|
|
|
GetChaptersMangaQueryVariables
|
2023-09-30 16:55:35 +02:00
|
|
|
>;
|
|
|
|
|
|
2024-07-02 16:20:28 +02:00
|
|
|
const chapterResponse = this.useGetChapters<GetChaptersMangaQuery, GetChaptersMangaQueryVariables>(
|
|
|
|
|
GET_CHAPTERS_MANGA,
|
2023-09-30 16:55:35 +02:00
|
|
|
{ condition: { mangaId: Number(mangaId), sourceOrder: Number(chapterIndex) } },
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!chapterResponse.data) {
|
|
|
|
|
return chapterResponse as unknown as Response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...chapterResponse,
|
|
|
|
|
data: {
|
|
|
|
|
chapter: chapterResponse.data.chapters.nodes[0],
|
|
|
|
|
},
|
|
|
|
|
} as unknown as Response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetChapterPagesFetch(
|
|
|
|
|
chapterId: string | number,
|
|
|
|
|
options?: MutationHookOptions<GetChapterPagesFetchMutation, GetChapterPagesFetchMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<GetChapterPagesFetchMutation, GetChapterPagesFetchMutationVariables> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest(
|
2023-09-30 16:55:35 +02:00
|
|
|
GQLMethod.USE_MUTATION,
|
|
|
|
|
GET_CHAPTER_PAGES_FETCH,
|
|
|
|
|
{
|
|
|
|
|
input: { chapterId: Number(chapterId) },
|
|
|
|
|
},
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-30 16:55:35 +02:00
|
|
|
);
|
2023-06-04 00:50:58 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public deleteDownloadedChapter(
|
|
|
|
|
id: number,
|
|
|
|
|
options?: MutationOptions<DeleteDownloadedChapterMutation, DeleteDownloadedChapterMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DeleteDownloadedChapterMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<DeleteDownloadedChapterMutation, DeleteDownloadedChapterMutationVariables>(
|
2023-09-01 20:36:40 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_DOWNLOADED_CHAPTER,
|
|
|
|
|
{ input: { id } },
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:36:40 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public deleteDownloadedChapters(
|
|
|
|
|
ids: number[],
|
|
|
|
|
options?: MutationOptions<DeleteDownloadedChaptersMutation, DeleteDownloadedChaptersMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DeleteDownloadedChaptersMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<DeleteDownloadedChaptersMutation, DeleteDownloadedChaptersMutationVariables>(
|
2023-09-01 20:36:40 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_DOWNLOADED_CHAPTERS,
|
|
|
|
|
{ input: { ids } },
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:36:40 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public updateChapter(
|
2023-09-01 20:36:40 +02:00
|
|
|
id: number,
|
2023-12-05 22:20:13 +01:00
|
|
|
patch: UpdateChapterPatchInput & {
|
|
|
|
|
chapterIdToDelete?: number;
|
2024-04-01 01:22:49 +02:00
|
|
|
trackProgressMangaId?: number;
|
2023-12-05 22:20:13 +01:00
|
|
|
},
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<UpdateChapterMutation, UpdateChapterMutationVariables>,
|
2023-09-01 20:36:40 +02:00
|
|
|
): AbortableApolloMutationResponse<UpdateChapterMutation> {
|
2024-04-01 01:22:49 +02:00
|
|
|
const { chapterIdToDelete = -1, trackProgressMangaId = -1, ...updatePatch } = patch;
|
2023-11-24 03:17:49 +01:00
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<UpdateChapterMutation, UpdateChapterMutationVariables>(
|
2023-09-01 20:36:40 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_CHAPTER,
|
2023-11-24 03:17:49 +01:00
|
|
|
{
|
|
|
|
|
input: { id, patch: updatePatch },
|
|
|
|
|
getBookmarked: patch.isBookmarked != null,
|
|
|
|
|
getRead: patch.isRead != null,
|
|
|
|
|
getLastPageRead: patch.lastPageRead != null,
|
2023-12-05 22:20:13 +01:00
|
|
|
chapterIdToDelete,
|
|
|
|
|
deleteChapter: chapterIdToDelete >= 0,
|
2024-04-01 01:22:49 +02:00
|
|
|
mangaId: trackProgressMangaId,
|
|
|
|
|
trackProgress: trackProgressMangaId >= 0,
|
2023-11-24 03:17:49 +01:00
|
|
|
},
|
2023-11-11 23:42:33 +01:00
|
|
|
options,
|
2023-09-01 20:36:40 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public setChapterMeta(
|
2023-09-01 20:36:40 +02:00
|
|
|
chapterId: number,
|
2023-05-18 13:25:19 +02:00
|
|
|
key: string,
|
|
|
|
|
value: any,
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<SetChapterMetadataMutation, SetChapterMetadataMutationVariables>,
|
2023-09-01 20:36:40 +02:00
|
|
|
): AbortableApolloMutationResponse<SetChapterMetadataMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<SetChapterMetadataMutation, SetChapterMetadataMutationVariables>(
|
2023-09-01 20:36:40 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
SET_CHAPTER_METADATA,
|
|
|
|
|
{ input: { meta: { chapterId, key, value: `${value}` } } },
|
2024-09-21 20:01:06 +02:00
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
setChapterMeta: {
|
|
|
|
|
__typename: 'SetChapterMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'ChapterMetaType',
|
|
|
|
|
chapterId,
|
|
|
|
|
key,
|
|
|
|
|
value: `${value}`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-09-22 02:46:01 +02:00
|
|
|
update(cache, { data }) {
|
|
|
|
|
cache.modify({
|
|
|
|
|
id: cache.identify({ __typename: 'ChapterType', id: chapterId }),
|
|
|
|
|
fields: {
|
|
|
|
|
meta(existingMetas, { readField }) {
|
2024-11-04 18:00:35 +01:00
|
|
|
return updateMetadataList(key, existingMetas, readField, () =>
|
2024-09-22 02:46:01 +02:00
|
|
|
cache.writeFragment({
|
|
|
|
|
data: data!.setChapterMeta!.meta,
|
|
|
|
|
fragment: CHAPTER_META_FIELDS,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
2024-09-21 20:01:06 +02:00
|
|
|
...options,
|
|
|
|
|
},
|
2023-09-01 20:36:40 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-26 17:25:04 +02:00
|
|
|
public deleteChapterMeta(
|
|
|
|
|
chapterId: number,
|
|
|
|
|
key: string,
|
|
|
|
|
options?: MutationOptions<DeleteChapterMetadataMutation, DeleteChapterMetadataMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DeleteChapterMetadataMutation> {
|
|
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_CHAPTER_METADATA,
|
|
|
|
|
{ input: { chapterId, key } },
|
|
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
deleteChapterMeta: {
|
|
|
|
|
__typename: 'DeleteChapterMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'ChapterMetaType',
|
|
|
|
|
chapterId,
|
|
|
|
|
key,
|
|
|
|
|
value: '',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
update(cache) {
|
2024-11-04 18:00:35 +01:00
|
|
|
cache.evict({ id: cache.identify({ __typename: 'ChapterMetaType', chapterId, key }) });
|
2024-10-26 17:25:04 +02:00
|
|
|
},
|
|
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public getChapterPageUrl(mangaId: number | string, chapterIndex: number | string, page: number): string {
|
|
|
|
|
return this.getValidImgUrlFor(
|
|
|
|
|
`manga/${mangaId}/chapter/${chapterIndex}/page/${page}`,
|
|
|
|
|
RequestManager.API_VERSION,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:36:40 +02:00
|
|
|
public updateChapters(
|
|
|
|
|
ids: number[],
|
2024-07-08 18:02:50 +02:00
|
|
|
patch: UpdateChapterPatchInput & { chapterIdsToDelete?: number[]; trackProgressMangaId?: MangaIdInfo['id'] },
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<UpdateChaptersMutation, UpdateChaptersMutationVariables>,
|
2023-09-01 20:36:40 +02:00
|
|
|
): AbortableApolloMutationResponse<UpdateChaptersMutation> {
|
2024-04-20 23:15:02 +02:00
|
|
|
const { chapterIdsToDelete = [], trackProgressMangaId = -1, ...updatePatch } = patch;
|
2023-11-24 03:17:49 +01:00
|
|
|
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<UpdateChaptersMutation, UpdateChaptersMutationVariables>(
|
2023-09-01 20:36:40 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_CHAPTERS,
|
2023-11-24 03:17:49 +01:00
|
|
|
{
|
|
|
|
|
input: { ids, patch: updatePatch },
|
|
|
|
|
getBookmarked: patch.isBookmarked != null,
|
|
|
|
|
getRead: patch.isRead != null,
|
|
|
|
|
getLastPageRead: patch.lastPageRead != null,
|
|
|
|
|
chapterIdsToDelete,
|
|
|
|
|
deleteChapters: !!chapterIdsToDelete.length,
|
2024-04-20 23:15:02 +02:00
|
|
|
mangaId: trackProgressMangaId,
|
|
|
|
|
trackProgress: trackProgressMangaId >= 0,
|
2023-11-24 03:17:49 +01:00
|
|
|
},
|
2023-12-23 15:15:08 +01:00
|
|
|
options,
|
2023-09-01 20:36:40 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-01 21:40:27 +02:00
|
|
|
public useGetCategories<Data, Variables extends OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
options?: QueryHookOptions<Data, Variables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
|
|
|
|
return this.doRequest<Data, Variables>(
|
2023-10-09 20:41:58 +02:00
|
|
|
GQLMethod.USE_QUERY,
|
2024-07-01 21:40:27 +02:00
|
|
|
document,
|
2023-10-09 20:41:58 +02:00
|
|
|
{
|
2024-07-29 14:10:37 +02:00
|
|
|
order: [{ by: CategoryOrderBy.Order }],
|
|
|
|
|
} satisfies GetCategoriesSettingsQueryVariables as unknown as Variables,
|
2023-10-09 20:41:58 +02:00
|
|
|
options,
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-01 21:40:27 +02:00
|
|
|
public getCategories<Data, Variables extends OperationVariables>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
options?: QueryOptions<Variables, Data>,
|
|
|
|
|
): AbortabaleApolloQueryResponse<Data> {
|
|
|
|
|
return this.doRequest<Data, Variables>(
|
2024-05-08 13:45:42 +02:00
|
|
|
GQLMethod.QUERY,
|
2024-07-01 21:40:27 +02:00
|
|
|
document,
|
2024-05-08 13:45:42 +02:00
|
|
|
{
|
2024-07-29 14:10:37 +02:00
|
|
|
order: [{ by: CategoryOrderBy.Order }],
|
|
|
|
|
} satisfies GetCategoriesSettingsQueryVariables as unknown as Variables,
|
2024-05-08 13:45:42 +02:00
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public createCategory(
|
|
|
|
|
input: CreateCategoryInput,
|
|
|
|
|
options?: MutationOptions<CreateCategoryMutation, CreateCategoryMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<CreateCategoryMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<CreateCategoryMutation, CreateCategoryMutationVariables>(
|
2023-09-01 20:39:30 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
CREATE_CATEGORY,
|
|
|
|
|
{ input },
|
2024-07-01 21:40:27 +02:00
|
|
|
{ refetchQueries: [GET_CATEGORIES_BASE, GET_CATEGORIES_LIBRARY, GET_CATEGORIES_SETTINGS], ...options },
|
2023-09-01 20:39:30 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-16 01:38:53 +02:00
|
|
|
public useReorderCategory(
|
|
|
|
|
options?: MutationHookOptions<UpdateCategoryOrderMutation, UpdateCategoryOrderMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<UpdateCategoryOrderMutation, UpdateCategoryOrderMutationVariables> {
|
|
|
|
|
const [mutate, result] = this.doRequest<UpdateCategoryOrderMutation, UpdateCategoryOrderMutationVariables>(
|
|
|
|
|
GQLMethod.USE_MUTATION,
|
2023-09-01 20:39:30 +02:00
|
|
|
UPDATE_CATEGORY_ORDER,
|
2023-10-16 01:38:53 +02:00
|
|
|
undefined,
|
2024-07-01 21:40:27 +02:00
|
|
|
{ refetchQueries: [GET_CATEGORIES_BASE, GET_CATEGORIES_LIBRARY], ...options },
|
2023-09-01 20:39:30 +02:00
|
|
|
);
|
2023-10-16 01:38:53 +02:00
|
|
|
|
|
|
|
|
const wrappedMutate = (mutateOptions: Parameters<typeof mutate>[0]) => {
|
|
|
|
|
const variables = mutateOptions?.variables?.input;
|
|
|
|
|
const cachedCategories = this.graphQLClient.client.readQuery<
|
2024-07-01 21:40:27 +02:00
|
|
|
GetCategoriesSettingsQuery,
|
|
|
|
|
GetCategoriesSettingsQueryVariables
|
2023-10-16 01:38:53 +02:00
|
|
|
>({
|
2024-07-01 21:40:27 +02:00
|
|
|
query: GET_CATEGORIES_SETTINGS,
|
2024-07-28 22:21:11 +02:00
|
|
|
variables: { order: [{ by: CategoryOrderBy.Order }] },
|
2023-10-16 01:38:53 +02:00
|
|
|
})?.categories.nodes;
|
|
|
|
|
|
|
|
|
|
if (!variables) {
|
|
|
|
|
throw new Error('useReorderCategory: no variables passed');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!cachedCategories) {
|
|
|
|
|
throw new Error('useReorderCategory: there are no cached results');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const movedIndex = cachedCategories.findIndex((category) => category.id === variables.id);
|
|
|
|
|
const newData = [...cachedCategories.map((category) => ({ ...category }))];
|
|
|
|
|
const [removed] = newData.splice(movedIndex, 1);
|
|
|
|
|
newData.splice(variables.position, 0, removed);
|
|
|
|
|
removed.order = variables.position;
|
|
|
|
|
newData[movedIndex].order = movedIndex;
|
|
|
|
|
|
|
|
|
|
return mutate({
|
|
|
|
|
update: (cache) => {
|
2024-07-01 21:40:27 +02:00
|
|
|
cache.updateQuery<GetCategoriesSettingsQuery, GetCategoriesSettingsQueryVariables>(
|
2023-10-16 01:38:53 +02:00
|
|
|
{
|
|
|
|
|
id: cache.identify({ __typename: 'CategoryNodeList' }),
|
2024-07-01 21:40:27 +02:00
|
|
|
query: GET_CATEGORIES_SETTINGS,
|
2024-07-28 22:21:11 +02:00
|
|
|
variables: { order: [{ by: CategoryOrderBy.Order }] },
|
2023-10-16 01:38:53 +02:00
|
|
|
},
|
|
|
|
|
(data) => ({
|
|
|
|
|
...data!,
|
|
|
|
|
categories: {
|
|
|
|
|
...data!.categories,
|
|
|
|
|
nodes: newData,
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
updateCategoryOrder: {
|
|
|
|
|
__typename: 'UpdateCategoryOrderPayload',
|
|
|
|
|
categories: newData,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
...mutateOptions,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return [wrappedMutate, result];
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-06 21:47:13 +02:00
|
|
|
public useGetCategoryMangas(
|
|
|
|
|
id: number,
|
2024-07-08 18:02:50 +02:00
|
|
|
options?: QueryHookOptions<GetMangasLibraryQuery, GetMangasLibraryQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetMangasLibraryQuery, GetMangasLibraryQueryVariables> {
|
2023-10-27 20:15:03 +02:00
|
|
|
const isDefaultCategory = id === 0;
|
|
|
|
|
if (isDefaultCategory) {
|
|
|
|
|
// hacky way of loading the default category mangas - some stuff won't work but since that is not used anyway, it won't be a problem
|
|
|
|
|
// can't be loaded via "useGetMangas" because mangas are not actually mapped to the default category in the database
|
|
|
|
|
const { data, ...result } = this.doRequest<GetCategoryMangasQuery, GetCategoryMangasQueryVariables>(
|
|
|
|
|
GQLMethod.USE_QUERY,
|
|
|
|
|
GET_CATEGORY_MANGAS,
|
|
|
|
|
{ id },
|
2024-04-27 22:28:55 +02:00
|
|
|
options as QueryHookOptions<GetCategoryMangasQuery, GetCategoryMangasQueryVariables>,
|
2023-10-27 20:15:03 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
data: data
|
|
|
|
|
? {
|
|
|
|
|
...data?.category,
|
|
|
|
|
__typename: 'Query',
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
2024-07-08 18:02:50 +02:00
|
|
|
} as unknown as AbortableApolloUseQueryResponse<GetMangasLibraryQuery, GetMangasLibraryQueryVariables>;
|
2023-10-27 20:15:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-08 18:02:50 +02:00
|
|
|
return this.useGetMangas(GET_MANGAS_LIBRARY, { condition: { inLibrary: true, categoryIds: [id] } }, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public deleteCategory(
|
|
|
|
|
categoryId: number,
|
|
|
|
|
options?: MutationOptions<DeleteCategoryMutation, DeleteCategoryMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DeleteCategoryMutation> {
|
2023-10-16 00:05:51 +02:00
|
|
|
const result = this.doRequest<DeleteCategoryMutation, DeleteCategoryMutationVariables>(
|
2023-09-01 20:39:30 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_CATEGORY,
|
|
|
|
|
{ input: { categoryId } },
|
2023-10-16 00:05:51 +02:00
|
|
|
{
|
2024-07-01 21:40:27 +02:00
|
|
|
refetchQueries: [GET_CATEGORIES_BASE, GET_CATEGORIES_LIBRARY, GET_CATEGORIES_SETTINGS],
|
2023-10-16 00:05:51 +02:00
|
|
|
...options,
|
|
|
|
|
},
|
2023-09-01 20:39:30 +02:00
|
|
|
);
|
2023-10-16 00:05:51 +02:00
|
|
|
|
|
|
|
|
result.response.then(() => {
|
|
|
|
|
this.graphQLClient.client.cache.evict({ fieldName: 'category', id: categoryId.toString() });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public updateCategory(
|
2023-09-01 20:39:30 +02:00
|
|
|
id: number,
|
|
|
|
|
patch: UpdateCategoryPatchInput,
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<UpdateCategoryMutation, UpdateCategoryMutationVariables>,
|
2023-09-01 20:39:30 +02:00
|
|
|
): AbortableApolloMutationResponse<UpdateCategoryMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<UpdateCategoryMutation, UpdateCategoryMutationVariables>(
|
2023-09-01 20:39:30 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_CATEGORY,
|
2023-11-24 03:17:49 +01:00
|
|
|
{
|
|
|
|
|
input: { id, patch },
|
|
|
|
|
getIncludeInUpdate: patch.includeInUpdate != null,
|
2024-01-21 13:31:04 -08:00
|
|
|
getIncludeInDownload: patch.includeInDownload != null,
|
2023-11-24 03:17:49 +01:00
|
|
|
getDefault: patch.default != null,
|
|
|
|
|
getName: patch.name != null,
|
|
|
|
|
},
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:39:30 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:39:30 +02:00
|
|
|
public setCategoryMeta(
|
|
|
|
|
categoryId: number,
|
|
|
|
|
key: string,
|
|
|
|
|
value: any,
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<SetCategoryMetadataMutation, SetCategoryMetadataMutationVariables>,
|
2023-09-01 20:39:30 +02:00
|
|
|
): AbortableApolloMutationResponse<SetCategoryMetadataMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<SetCategoryMetadataMutation, SetCategoryMetadataMutationVariables>(
|
2023-09-01 20:39:30 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
SET_CATEGORY_METADATA,
|
|
|
|
|
{ input: { meta: { categoryId, key, value: `${value}` } } },
|
2024-09-21 20:01:06 +02:00
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
setCategoryMeta: {
|
|
|
|
|
__typename: 'SetCategoryMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'CategoryMetaType',
|
|
|
|
|
categoryId,
|
|
|
|
|
key,
|
|
|
|
|
value: `${value}`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-09-22 02:46:01 +02:00
|
|
|
update(cache, { data }) {
|
|
|
|
|
cache.modify({
|
|
|
|
|
id: cache.identify({ __typename: 'CategoryType', id: categoryId }),
|
|
|
|
|
fields: {
|
|
|
|
|
meta(existingMetas, { readField }) {
|
2024-11-04 18:00:35 +01:00
|
|
|
return updateMetadataList(key, existingMetas, readField, () =>
|
2024-09-22 02:46:01 +02:00
|
|
|
cache.writeFragment({
|
|
|
|
|
data: data!.setCategoryMeta!.meta,
|
|
|
|
|
fragment: CATEGORY_META_FIELDS,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
2024-09-21 20:01:06 +02:00
|
|
|
...options,
|
|
|
|
|
},
|
2023-09-01 20:39:30 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-26 17:25:04 +02:00
|
|
|
public deleteCategoryMeta(
|
|
|
|
|
categoryId: number,
|
|
|
|
|
key: string,
|
|
|
|
|
options?: MutationOptions<DeleteCategoryMetadataMutation, DeleteCategoryMetadataMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DeleteCategoryMetadataMutation> {
|
|
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_CATEGORY_METADATA,
|
|
|
|
|
{ input: { categoryId, key } },
|
|
|
|
|
{
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
deleteCategoryMeta: {
|
|
|
|
|
__typename: 'DeleteCategoryMetaPayload',
|
|
|
|
|
meta: {
|
|
|
|
|
__typename: 'CategoryMetaType',
|
|
|
|
|
categoryId,
|
|
|
|
|
key,
|
|
|
|
|
value: '',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
update(cache) {
|
2024-11-04 18:00:35 +01:00
|
|
|
cache.evict({ id: cache.identify({ __typename: 'CategoryMetaType', categoryId, key }) });
|
2024-10-26 17:25:04 +02:00
|
|
|
},
|
|
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-02 20:04:00 +02:00
|
|
|
public createBackupFile(
|
|
|
|
|
input: CreateBackupInput,
|
|
|
|
|
options?: MutationOptions<CreateBackupMutation, CreateBackupMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<CreateBackupMutation> {
|
|
|
|
|
return this.doRequest<CreateBackupMutation, CreateBackupMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
CREATE_BACKUP,
|
|
|
|
|
{ input },
|
|
|
|
|
{
|
|
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 01:17:10 +02:00
|
|
|
public restoreBackupFile(
|
2025-10-04 21:48:44 +02:00
|
|
|
input: RestoreBackupInput,
|
2023-10-04 01:17:10 +02:00
|
|
|
options?: MutationOptions<RestoreBackupMutation, RestoreBackupMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<RestoreBackupMutation> {
|
2025-06-01 22:56:06 +02:00
|
|
|
return this.doRequest<RestoreBackupMutation, RestoreBackupMutationVariables>(
|
2023-10-04 01:17:10 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
RESTORE_BACKUP,
|
2025-10-04 21:48:44 +02:00
|
|
|
input,
|
2023-10-16 00:05:51 +02:00
|
|
|
{
|
|
|
|
|
...options,
|
|
|
|
|
},
|
2023-10-04 01:17:10 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-29 01:45:08 +02:00
|
|
|
public validateBackupFile(
|
2023-05-18 13:25:19 +02:00
|
|
|
file: File,
|
2023-10-04 01:17:10 +02:00
|
|
|
options?: QueryOptions<ValidateBackupQueryVariables, ValidateBackupQuery>,
|
|
|
|
|
): AbortabaleApolloQueryResponse<ValidateBackupQuery> {
|
2023-10-29 01:45:08 +02:00
|
|
|
return this.doRequest(GQLMethod.QUERY, VALIDATE_BACKUP, { backup: file }, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-29 01:49:27 +02:00
|
|
|
public useGetBackupRestoreStatus(
|
2023-11-02 13:01:01 +01:00
|
|
|
id: string,
|
2023-10-29 01:49:27 +02:00
|
|
|
options?: QueryHookOptions<GetRestoreStatusQuery, GetRestoreStatusQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetRestoreStatusQuery, GetRestoreStatusQueryVariables> {
|
2023-11-02 13:01:01 +01:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_RESTORE_STATUS, { id }, options);
|
2023-10-29 01:49:27 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public startDownloads(
|
|
|
|
|
options?: MutationOptions<StartDownloaderMutation, StartDownloaderMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<StartDownloaderMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<StartDownloaderMutation, StartDownloaderMutationVariables>(
|
2023-09-01 20:40:24 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
START_DOWNLOADER,
|
|
|
|
|
{},
|
2023-10-09 20:41:58 +02:00
|
|
|
options,
|
2023-09-01 20:40:24 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public stopDownloads(
|
|
|
|
|
options?: MutationOptions<StopDownloaderMutation, StopDownloaderMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<StopDownloaderMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<StopDownloaderMutation, StopDownloaderMutationVariables>(
|
2023-09-01 20:40:24 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
STOP_DOWNLOADER,
|
|
|
|
|
{},
|
2023-10-09 20:41:58 +02:00
|
|
|
options,
|
2023-09-01 20:40:24 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public clearDownloads(
|
|
|
|
|
options?: MutationOptions<ClearDownloaderMutation, ClearDownloaderMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<ClearDownloaderMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<ClearDownloaderMutation, ClearDownloaderMutationVariables>(
|
2023-09-01 20:40:24 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
CLEAR_DOWNLOADER,
|
|
|
|
|
{},
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:40:24 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public addChapterToDownloadQueue(
|
|
|
|
|
id: number,
|
|
|
|
|
options?: MutationOptions<EnqueueChapterDownloadMutation, EnqueueChapterDownloadMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<EnqueueChapterDownloadMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<EnqueueChapterDownloadMutation, EnqueueChapterDownloadMutationVariables>(
|
2023-09-01 20:40:24 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
ENQUEUE_CHAPTER_DOWNLOAD,
|
|
|
|
|
{ input: { id } },
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:40:24 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public removeChapterFromDownloadQueue(
|
|
|
|
|
id: number,
|
|
|
|
|
options?: MutationOptions<DequeueChapterDownloadMutation, DequeueChapterDownloadMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<DequeueChapterDownloadMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<DequeueChapterDownloadMutation, DequeueChapterDownloadMutationVariables>(
|
2023-09-01 20:40:24 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DEQUEUE_CHAPTER_DOWNLOAD,
|
|
|
|
|
{ input: { id } },
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:40:24 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-13 02:05:03 +01:00
|
|
|
public useReorderChapterInDownloadQueue(
|
|
|
|
|
options?: MutationHookOptions<ReorderChapterDownloadMutation, ReorderChapterDownloadMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<ReorderChapterDownloadMutation, ReorderChapterDownloadMutationVariables> {
|
|
|
|
|
const [mutate, result] = this.doRequest(GQLMethod.USE_MUTATION, REORDER_CHAPTER_DOWNLOAD, undefined, options);
|
|
|
|
|
|
|
|
|
|
const wrappedMutate = (mutationOptions: Parameters<typeof mutate>[0]) => {
|
|
|
|
|
const variables = mutationOptions?.variables?.input;
|
|
|
|
|
const cachedDownloadStatus = this.graphQLClient.client.readFragment<
|
2024-08-21 21:29:01 +02:00
|
|
|
GetDownloadStatusQuery['downloadStatus']
|
2023-12-13 02:05:03 +01:00
|
|
|
>({
|
|
|
|
|
id: 'DownloadStatus:{}',
|
2024-07-06 15:28:27 +02:00
|
|
|
fragment: DOWNLOAD_STATUS_FIELDS,
|
2024-08-21 21:29:01 +02:00
|
|
|
fragmentName: 'DOWNLOAD_STATUS_FIELDS',
|
2023-12-13 02:05:03 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!variables) {
|
|
|
|
|
throw new Error('useReorderChapterInDownloadQueue: no variables passed');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!cachedDownloadStatus) {
|
|
|
|
|
throw new Error('useReorderChapterInDownloadQueue: there are no cached results');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const movedIndex = cachedDownloadStatus.queue.findIndex(
|
|
|
|
|
({ chapter }) => chapter.id === variables.chapterId,
|
|
|
|
|
);
|
|
|
|
|
const chapterDownload = cachedDownloadStatus.queue[movedIndex];
|
|
|
|
|
const queueWithoutChapterDownload = cachedDownloadStatus.queue.toSpliced(movedIndex, 1);
|
|
|
|
|
const updatedQueue = queueWithoutChapterDownload.toSpliced(variables.to, 0, chapterDownload);
|
|
|
|
|
|
|
|
|
|
return mutate({
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
reorderChapterDownload: {
|
|
|
|
|
__typename: 'ReorderChapterDownloadPayload',
|
|
|
|
|
downloadStatus: {
|
|
|
|
|
...cachedDownloadStatus,
|
|
|
|
|
queue: updatedQueue,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
...mutationOptions,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return [wrappedMutate, result];
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public addChaptersToDownloadQueue(
|
|
|
|
|
ids: number[],
|
|
|
|
|
options?: MutationOptions<EnqueueChapterDownloadsMutation, EnqueueChapterDownloadsMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<EnqueueChapterDownloadsMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<EnqueueChapterDownloadsMutation, EnqueueChapterDownloadsMutationVariables>(
|
2023-09-01 20:40:24 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
ENQUEUE_CHAPTER_DOWNLOADS,
|
|
|
|
|
{ input: { ids } },
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:40:24 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:40:24 +02:00
|
|
|
public removeChaptersFromDownloadQueue(
|
|
|
|
|
ids: number[],
|
2023-10-09 20:41:58 +02:00
|
|
|
options?: MutationOptions<DequeueChapterDownloadsMutation, DequeueChapterDownloadsMutationVariables>,
|
2023-09-01 20:40:24 +02:00
|
|
|
): AbortableApolloMutationResponse<DequeueChapterDownloadsMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<DequeueChapterDownloadsMutation, DequeueChapterDownloadsMutationVariables>(
|
2023-09-01 20:40:24 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DEQUEUE_CHAPTER_DOWNLOADS,
|
|
|
|
|
{ input: { ids } },
|
2023-10-16 00:05:51 +02:00
|
|
|
options,
|
2023-09-01 20:40:24 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetRecentlyUpdatedChapters(
|
2023-09-30 16:55:35 +02:00
|
|
|
initialPages: number = 1,
|
2024-07-02 16:20:28 +02:00
|
|
|
options?: QueryHookOptions<GetChaptersUpdatesQuery, GetChaptersUpdatesQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetChaptersUpdatesQuery, GetChaptersUpdatesQueryVariables> {
|
2023-09-30 16:55:35 +02:00
|
|
|
const PAGE_SIZE = 50;
|
|
|
|
|
const CACHE_KEY = 'useGetRecentlyUpdatedChapters';
|
|
|
|
|
|
|
|
|
|
const offset = this.cache.getResponseFor<number>(CACHE_KEY, undefined) ?? 0;
|
|
|
|
|
const [lastOffset] = useState(offset);
|
|
|
|
|
|
2024-07-02 16:20:28 +02:00
|
|
|
const result = this.useGetChapters<GetChaptersUpdatesQuery, GetChaptersUpdatesQueryVariables>(
|
|
|
|
|
GET_CHAPTERS_UPDATES,
|
2023-09-30 16:55:35 +02:00
|
|
|
{
|
|
|
|
|
filter: { inLibrary: { equalTo: true } },
|
2024-07-28 22:24:50 +02:00
|
|
|
order: [
|
|
|
|
|
{ by: ChapterOrderBy.FetchedAt, byType: SortOrder.Desc },
|
|
|
|
|
{ by: ChapterOrderBy.SourceOrder, byType: SortOrder.Desc },
|
|
|
|
|
],
|
2023-09-30 16:55:35 +02:00
|
|
|
first: initialPages * PAGE_SIZE + lastOffset,
|
|
|
|
|
},
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
fetchMore: (...args: Parameters<(typeof result)['fetchMore']>) => {
|
|
|
|
|
const fetchMoreOptions = args[0] ?? {};
|
|
|
|
|
this.cache.cacheResponse(CACHE_KEY, undefined, fetchMoreOptions.variables?.offset);
|
Add history page (#897)
* Add History module and integrate it into the app
Introduce the History module with components for displaying chapter updates, including UI for grouped updates by date. Update routing and constants to incorporate the new /history path.
* Replace "Updates" and "History" with unified "Recent" module
Merged the "Updates" and "History" screens into a single unified "Recent" module with corresponding navigation updates. Adjusted GraphQL types and localization files to reflect the changes. This simplifies navigation and improves user experience.
* Refactor History screen to remove unused components.
Removed unused states, layout effects, and props related to last update timestamp. Renamed variables for better clarity and updated labels and error messages to align with the history context. Simplified component structure for improved maintainability.
* ```
Refactor tab handling and integrate dynamic height adjustments.
Renamed enums for better clarity and consistency. Implemented a `tabsMenuHeight` prop to dynamically adjust component layouts based on observed height changes using `useResizeObserver`. Updated the `Updates` component to consider the calculated heights for improved rendering.
```
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* Refactor `groupByDate` parameter and add `lastReadAt` field
Renamed `updates` to `histories` in `groupByDate` for clarity and added a debugging log for input data. Updated GraphQL fragment to include the `lastReadAt` field to support additional functionality.
* "Optimize GraphQL type definition formatting in generated file
Condensed the formatting of the GetChaptersHistoryQuery type to a single line for improved readability and consistency. This change does not affect functionality but enhances maintainability of the generated code."
* Remove UpdateChecker component from History screen
The UpdateChecker component and its related navbar action have been removed from the History screen. This simplifies the navbar setup and eliminates unused functionality in this context.
* Refactor chapter fragment and type references
Updated `ChapterRecentListFieldsFragment` to `ChapterUpdateListFieldsFragment` for better alignment with naming conventions. Adjusted type imports and query structures to maintain consistency across files. No functional changes introduced.
* Refactor padding calculation logic in Updates screen.
Renamed state variable and refactored its logic for clarity and consistency. The updated logic now uses a more descriptive name, `listPadding`, to enhance code readability and maintainability.
* Pass `tabsMenuHeight` prop to History component
Added `tabsMenuHeight` prop to the `History` component to properly calculate the height for the `StyledGroupedVirtuoso` element. This ensures consistency with other components and improves layout rendering.
* Revert package.json
* Add last page read tracking to chapter history
Introduced "lastPageRead" and "pageCount" fields to track progress in chapter history. Updated UI to display the last page read along with total pages for chapters. Adjusted localization files to support the new "page" label.
* Remove last page read display from ChapterHistoryCard
The "last page read" information was removed from the ChapterHistoryCard component for a cleaner UI. This change simplifies the card's design by showing only the chapter name and relevant download state.
* Forcing an empty commit.
* Replace "Recent" screen with separate "Updates" and "History".
The "Recent" feature has been split into two distinct screens, "Updates" and "History", for improved clarity and functionality. Routes, icons, and components have been updated accordingly, and redundant code related to "Recent" has been removed.
* Clean
* Revert vi.json
* Remove unnecessary `useLocation` hook and update state logic.
The `useLocation` hook was removed as it was not used elsewhere in the component. The state property for the routing logic was updated to use `Chapters.getReaderOpenChapterLocationState` for better encapsulation and clarity.
* Update history localization by removing unused "page" key
Removed the "page" key from the "history" section in the localization file as it is no longer in use. This cleanup helps keep the translation file consistent and easier to maintain.
---------
Co-authored-by: NamNH <namitonguyen@gmail.com>
2025-03-11 03:53:40 +07:00
|
|
|
return result.fetchMore({
|
|
|
|
|
...fetchMoreOptions,
|
|
|
|
|
variables: { first: PAGE_SIZE, ...fetchMoreOptions.variables },
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
} as typeof result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetRecentlyReadChapters(
|
|
|
|
|
initialPages: number = 1,
|
|
|
|
|
options?: QueryHookOptions<GetChaptersHistoryQuery, GetChaptersHistoryQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetChaptersHistoryQuery, GetChaptersHistoryQueryVariables> {
|
|
|
|
|
const PAGE_SIZE = 50;
|
|
|
|
|
const CACHE_KEY = 'useGetRecentlyReadChapters';
|
|
|
|
|
|
|
|
|
|
const offset = this.cache.getResponseFor<number>(CACHE_KEY, undefined) ?? 0;
|
|
|
|
|
const [lastOffset] = useState(offset);
|
|
|
|
|
|
|
|
|
|
const result = this.useGetChapters<GetChaptersHistoryQuery, GetChaptersHistoryQueryVariables>(
|
|
|
|
|
GET_CHAPTERS_HISTORY,
|
|
|
|
|
{
|
|
|
|
|
filter: { lastReadAt: { isNull: false, notEqualToAll: ['0'] } },
|
|
|
|
|
order: [
|
|
|
|
|
{ by: ChapterOrderBy.LastReadAt, byType: SortOrder.Desc },
|
|
|
|
|
{ by: ChapterOrderBy.SourceOrder, byType: SortOrder.Desc },
|
|
|
|
|
],
|
|
|
|
|
first: initialPages * PAGE_SIZE + lastOffset,
|
|
|
|
|
},
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
fetchMore: (...args: Parameters<(typeof result)['fetchMore']>) => {
|
|
|
|
|
const fetchMoreOptions = args[0] ?? {};
|
|
|
|
|
this.cache.cacheResponse(CACHE_KEY, undefined, fetchMoreOptions.variables?.offset);
|
2023-09-30 16:55:35 +02:00
|
|
|
return result.fetchMore({
|
|
|
|
|
...fetchMoreOptions,
|
|
|
|
|
variables: { first: PAGE_SIZE, ...fetchMoreOptions.variables },
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
} as typeof result;
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:40:39 +02:00
|
|
|
public startGlobalUpdate(
|
2024-06-26 12:42:03 +02:00
|
|
|
categories?: number[],
|
2025-03-16 21:01:24 +01:00
|
|
|
options?: MutationOptions<UpdateLibraryMutation, UpdateLibraryMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateLibraryMutation> {
|
|
|
|
|
return this.doRequest(GQLMethod.MUTATION, UPDATE_LIBRARY, { input: { categories } }, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:41:58 +02:00
|
|
|
public resetGlobalUpdate(
|
|
|
|
|
options?: MutationOptions<StopUpdaterMutation, StopUpdaterMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<StopUpdaterMutation> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest<StopUpdaterMutation, StopUpdaterMutationVariables>(
|
2023-09-01 20:40:39 +02:00
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
STOP_UPDATER,
|
|
|
|
|
{},
|
2023-10-09 20:41:58 +02:00
|
|
|
options,
|
2023-09-01 20:40:39 +02:00
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:40:39 +02:00
|
|
|
public useGetGlobalUpdateSummary(
|
|
|
|
|
options?: QueryHookOptions<GetUpdateStatusQuery, GetUpdateStatusQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetUpdateStatusQuery, GetUpdateStatusQueryVariables> {
|
2023-10-15 21:01:46 +02:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_UPDATE_STATUS, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
2023-10-04 22:34:32 +02:00
|
|
|
|
2023-12-13 02:05:03 +01:00
|
|
|
public useGetDownloadStatus(
|
2024-01-01 22:40:39 +01:00
|
|
|
options?: QueryHookOptions<GetDownloadStatusQuery, GetDownloadStatusQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetDownloadStatusQuery, GetDownloadStatusQueryVariables> {
|
2023-12-13 02:05:03 +01:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_DOWNLOAD_STATUS, {}, options);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 22:34:32 +02:00
|
|
|
public useDownloadSubscription(
|
|
|
|
|
options?: SubscriptionHookOptions<DownloadStatusSubscription, DownloadStatusSubscriptionVariables>,
|
|
|
|
|
): SubscriptionResult<DownloadStatusSubscription, DownloadStatusSubscriptionVariables> {
|
2024-08-21 21:29:01 +02:00
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.USE_SUBSCRIPTION,
|
|
|
|
|
DOWNLOAD_STATUS_SUBSCRIPTION,
|
|
|
|
|
{ input: { maxUpdates: 30 } },
|
|
|
|
|
{
|
|
|
|
|
...options,
|
|
|
|
|
onData: (onDataOptions) => {
|
|
|
|
|
const downloadChanged = onDataOptions.data.data?.downloadStatusChanged;
|
|
|
|
|
|
|
|
|
|
const { cache } = this.graphQLClient.client;
|
|
|
|
|
|
|
|
|
|
if (downloadChanged?.omittedUpdates) {
|
2025-01-12 16:42:22 +01:00
|
|
|
cache.gc();
|
2024-08-21 21:29:01 +02:00
|
|
|
cache.evict({ broadcast: true, id: 'DownloadStatus:{}' });
|
2025-01-12 16:42:22 +01:00
|
|
|
cache.evict({ broadcast: true, fieldName: 'downloadStatus' });
|
2024-08-21 21:29:01 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 21:26:35 +02:00
|
|
|
if (downloadChanged?.state) {
|
|
|
|
|
cache.modify({
|
|
|
|
|
id: 'DownloadStatus:{}',
|
|
|
|
|
fields: {
|
|
|
|
|
state() {
|
|
|
|
|
return downloadChanged.state;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-28 00:43:41 +02:00
|
|
|
const downloadStatusQueryCache = cache.readQuery<GetDownloadStatusQuery>({
|
|
|
|
|
query: GET_DOWNLOAD_STATUS,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const downloadsToAdd = downloadChanged?.updates.filter((update) => {
|
|
|
|
|
const removeDownload = [DownloadUpdateType.Dequeued, DownloadUpdateType.Finished].includes(
|
|
|
|
|
update.type,
|
|
|
|
|
);
|
|
|
|
|
if (removeDownload) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isAlreadyKnown = downloadStatusQueryCache?.downloadStatus.queue.some(
|
|
|
|
|
(download) => download.chapter.id === update.download.chapter.id,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return !isAlreadyKnown;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (downloadsToAdd?.length) {
|
|
|
|
|
cache.writeQuery<GetDownloadStatusQuery>({
|
|
|
|
|
query: GET_DOWNLOAD_STATUS,
|
|
|
|
|
data: {
|
|
|
|
|
...downloadStatusQueryCache,
|
|
|
|
|
downloadStatus: {
|
|
|
|
|
__typename: 'DownloadStatus',
|
|
|
|
|
state:
|
|
|
|
|
downloadChanged?.state ??
|
|
|
|
|
downloadStatusQueryCache?.downloadStatus.state ??
|
|
|
|
|
DownloaderState.Stopped,
|
|
|
|
|
queue: [
|
|
|
|
|
...(downloadStatusQueryCache?.downloadStatus?.queue ?? []),
|
|
|
|
|
...(downloadsToAdd?.map((update) => update.download) ?? []),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-12 16:42:22 +01:00
|
|
|
downloadChanged?.updates.forEach((update) => {
|
2025-06-28 00:43:41 +02:00
|
|
|
const removeDownload = [DownloadUpdateType.Dequeued, DownloadUpdateType.Finished].includes(
|
|
|
|
|
update.type,
|
|
|
|
|
);
|
|
|
|
|
if (!removeDownload) {
|
2025-01-12 16:42:22 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cache.evict({
|
|
|
|
|
id: cache.identify({
|
|
|
|
|
__typename: 'DownloadType',
|
|
|
|
|
chapter: {
|
|
|
|
|
__ref: cache.identify({
|
|
|
|
|
__typename: 'ChapterType',
|
|
|
|
|
id: update.download.chapter.id,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-08-21 21:29:01 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-10-04 22:34:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useUpdaterSubscription(
|
|
|
|
|
options?: SubscriptionHookOptions<UpdaterSubscription, UpdaterSubscriptionVariables>,
|
|
|
|
|
): SubscriptionResult<UpdaterSubscription, UpdaterSubscriptionVariables> {
|
2025-03-16 21:01:24 +01:00
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.USE_SUBSCRIPTION,
|
|
|
|
|
UPDATER_SUBSCRIPTION,
|
|
|
|
|
{ input: { maxUpdates: 30 } },
|
|
|
|
|
{
|
|
|
|
|
...options,
|
|
|
|
|
onData: (onDataOptions) => {
|
|
|
|
|
const updatesChanged = onDataOptions.data.data?.libraryUpdateStatusChanged;
|
|
|
|
|
|
|
|
|
|
if (!updatesChanged?.omittedUpdates) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { cache } = this.graphQLClient.client;
|
|
|
|
|
|
|
|
|
|
cache.gc();
|
|
|
|
|
cache.evict({ broadcast: true, id: 'LibraryUpdateStatus' });
|
|
|
|
|
cache.evict({ broadcast: true, fieldName: 'libraryUpdateStatus' });
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-10-04 22:34:32 +02:00
|
|
|
}
|
2023-10-27 23:39:05 +02:00
|
|
|
|
|
|
|
|
public useGetServerSettings(
|
2024-05-08 04:01:23 +02:00
|
|
|
options?: QueryHookOptions<GetServerSettingsQuery, GetServerSettingsQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetServerSettingsQuery, GetServerSettingsQueryVariables> {
|
2023-10-27 23:39:05 +02:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_SERVER_SETTINGS, undefined, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useUpdateServerSettings(
|
|
|
|
|
options?: MutationHookOptions<UpdateServerSettingsMutation, UpdateServerSettingsMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<UpdateServerSettingsMutation, UpdateServerSettingsMutationVariables> {
|
2024-09-26 13:59:37 +02:00
|
|
|
const [mutate, result] = this.doRequest(GQLMethod.USE_MUTATION, UPDATE_SERVER_SETTINGS, undefined, options);
|
|
|
|
|
|
|
|
|
|
const wrappedMutate = async (mutateOptions: Parameters<typeof mutate>[0]) =>
|
|
|
|
|
mutate({
|
|
|
|
|
optimisticResponse: {
|
|
|
|
|
__typename: 'Mutation',
|
|
|
|
|
setSettings: {
|
|
|
|
|
__typename: 'SetSettingsPayload',
|
|
|
|
|
settings: {
|
|
|
|
|
__typename: 'SettingsType',
|
|
|
|
|
...(mutateOptions?.variables?.input.settings ?? {}),
|
|
|
|
|
} as SettingsType,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
...mutateOptions,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return [wrappedMutate, result];
|
2023-10-27 23:39:05 +02:00
|
|
|
}
|
2023-11-19 21:17:35 +01:00
|
|
|
|
2023-11-19 22:43:29 +01:00
|
|
|
public useGetLastGlobalUpdateTimestamp(
|
|
|
|
|
options?: QueryHookOptions<GetLastUpdateTimestampQuery, GetLastUpdateTimestampQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetLastUpdateTimestampQuery, GetLastUpdateTimestampQueryVariables> {
|
2023-11-25 15:35:36 +01:00
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_LAST_UPDATE_TIMESTAMP, {}, options);
|
2023-11-19 22:43:29 +01:00
|
|
|
}
|
2023-12-09 14:16:52 +01:00
|
|
|
|
|
|
|
|
public useClearServerCache(
|
2024-04-27 00:03:37 +02:00
|
|
|
input: ClearCachedImagesInput = { cachedPages: true, cachedThumbnails: true },
|
2023-12-09 14:16:52 +01:00
|
|
|
options?: MutationHookOptions<ClearServerCacheMutation, ClearServerCacheMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<ClearServerCacheMutation, ClearServerCacheMutationVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_MUTATION, CLEAR_SERVER_CACHE, { input }, options);
|
|
|
|
|
}
|
2023-12-09 23:08:27 +01:00
|
|
|
|
|
|
|
|
public useWebUIUpdateSubscription(
|
|
|
|
|
options?: SubscriptionHookOptions<WebuiUpdateSubscription, WebuiUpdateSubscription>,
|
|
|
|
|
): SubscriptionResult<WebuiUpdateSubscription, WebuiUpdateSubscription> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_SUBSCRIPTION, WEBUI_UPDATE_SUBSCRIPTION, undefined, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public resetWebUIUpdateStatus(
|
|
|
|
|
options?: MutationOptions<ResetWebuiUpdateStatusMutation, ResetWebuiUpdateStatusMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<ResetWebuiUpdateStatusMutation> {
|
|
|
|
|
return this.doRequest(GQLMethod.MUTATION, RESET_WEBUI_UPDATE_STATUS, undefined, options);
|
|
|
|
|
}
|
2024-01-01 22:40:39 +01:00
|
|
|
|
|
|
|
|
public useGetWebUIUpdateStatus(
|
|
|
|
|
options?: QueryHookOptions<GetWebuiUpdateStatusQuery, GetWebuiUpdateStatusQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetWebuiUpdateStatusQuery, GetWebuiUpdateStatusQueryVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_WEBUI_UPDATE_STATUS, undefined, options);
|
|
|
|
|
}
|
2024-01-26 20:50:51 +01:00
|
|
|
|
|
|
|
|
public useGetMigratableSources(
|
|
|
|
|
options?: QueryHookOptions<GetMigratableSourcesQuery, GetMigratableSourcesQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetMigratableSourcesQuery, GetMigratableSourcesQueryVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, GET_MIGRATABLE_SOURCES, undefined, options);
|
|
|
|
|
}
|
2024-02-16 19:55:54 +01:00
|
|
|
|
2024-07-04 22:00:39 +02:00
|
|
|
public useGetTrackerList<Data, Variables extends OperationVariables = never>(
|
|
|
|
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
|
|
|
|
options?: QueryHookOptions<Data, Variables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, document, undefined, options);
|
2024-02-16 19:55:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useLogoutFromTracker(
|
|
|
|
|
options?: MutationHookOptions<TrackerLogoutMutation, TrackerLogoutMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<TrackerLogoutMutation, TrackerLogoutMutationVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_MUTATION, TRACKER_LOGOUT, undefined, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useLoginToTrackerOauth(
|
|
|
|
|
options?: MutationHookOptions<TrackerLoginOauthMutation, TrackerLoginOauthMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<TrackerLoginOauthMutation, TrackerLoginOauthMutationVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_MUTATION, TRACKER_LOGIN_OAUTH, undefined, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useLoginToTrackerCredentials(
|
|
|
|
|
options?: MutationHookOptions<TrackerLoginCredentialsMutation, TrackerLoginCredentialsMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<TrackerLoginCredentialsMutation, TrackerLoginCredentialsMutationVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_MUTATION, TRACKER_LOGIN_CREDENTIALS, undefined, options);
|
|
|
|
|
}
|
2024-03-10 17:36:22 +01:00
|
|
|
|
|
|
|
|
public useTrackerSearch(
|
|
|
|
|
trackerId: number,
|
|
|
|
|
query: string,
|
|
|
|
|
options?: QueryHookOptions<TrackerSearchQuery, TrackerSearchQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<TrackerSearchQuery, TrackerSearchQueryVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_QUERY, TRACKER_SEARCH, { trackerId, query }, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useBindTracker(
|
|
|
|
|
options?: MutationHookOptions<TrackerBindMutation, TrackerBindMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<TrackerBindMutation, TrackerBindMutationVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_MUTATION, TRACKER_BIND, undefined, options);
|
|
|
|
|
}
|
2024-03-17 18:42:48 +01:00
|
|
|
|
2024-04-20 23:51:47 +02:00
|
|
|
public bindTracker(
|
|
|
|
|
mangaId: number,
|
|
|
|
|
trackerId: number,
|
|
|
|
|
remoteId: string,
|
2025-07-19 18:33:33 +02:00
|
|
|
asPrivate: boolean,
|
2024-04-20 23:51:47 +02:00
|
|
|
options?: MutationOptions<TrackerBindMutation, TrackerBindMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<TrackerBindMutation> {
|
2025-07-19 18:33:33 +02:00
|
|
|
return this.doRequest(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
TRACKER_BIND,
|
|
|
|
|
{ input: { mangaId, remoteId, trackerId, private: asPrivate } },
|
|
|
|
|
options,
|
|
|
|
|
);
|
2024-04-20 23:51:47 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-01 00:45:44 +02:00
|
|
|
public unbindTracker(
|
|
|
|
|
recordId: number,
|
|
|
|
|
deleteRemoteTrack?: boolean,
|
|
|
|
|
options?: MutationHookOptions<TrackerUnbindMutation, TrackerUnbindMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<TrackerUnbindMutation> {
|
|
|
|
|
return this.doRequest<TrackerUnbindMutation, TrackerUnbindMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
TRACKER_UNBIND,
|
|
|
|
|
{ input: { recordId, deleteRemoteTrack } },
|
2024-07-08 18:02:50 +02:00
|
|
|
{ refetchQueries: [GET_MANGA_TRACK_RECORDS], ...options },
|
2024-04-01 00:45:44 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-17 18:42:48 +01:00
|
|
|
public updateTrackerBind(
|
|
|
|
|
id: number,
|
|
|
|
|
patch: Omit<UpdateTrackInput, 'clientMutationId' | 'recordId'>,
|
|
|
|
|
options?: MutationOptions<TrackerUpdateBindMutation, TrackerUpdateBindMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<TrackerUpdateBindMutation> {
|
2024-04-01 00:45:44 +02:00
|
|
|
return this.doRequest(GQLMethod.MUTATION, TRACKER_UPDATE_BIND, { input: { ...patch, recordId: id } }, options);
|
2024-03-17 18:42:48 +01:00
|
|
|
}
|
2024-04-01 02:06:35 +02:00
|
|
|
|
|
|
|
|
public fetchTrackBind(
|
|
|
|
|
recordId: number,
|
|
|
|
|
options?: MutationOptions<TrackerFetchBindMutation, TrackerFetchBindMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<TrackerFetchBindMutation> {
|
|
|
|
|
return this.doRequest<TrackerFetchBindMutation, TrackerFetchBindMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
TRACKER_FETCH_BIND,
|
|
|
|
|
{ recordId },
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-27 00:03:51 +02:00
|
|
|
|
|
|
|
|
public useLoginUser(
|
|
|
|
|
options?: MutationHookOptions<UserLoginMutation, UserLoginMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<UserLoginMutation, UserLoginMutationVariables> {
|
|
|
|
|
return this.doRequest(GQLMethod.USE_MUTATION, USER_LOGIN, undefined, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public refreshUser(
|
|
|
|
|
refreshToken: string,
|
|
|
|
|
options?: MutationOptions<UserRefreshMutation, UserRefreshMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<UserRefreshMutation> {
|
|
|
|
|
return this.doRequest<UserRefreshMutation, UserRefreshMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
USER_REFRESH,
|
|
|
|
|
{ refreshToken: refreshToken ?? undefined },
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-28 00:32:02 +02:00
|
|
|
export const requestManager = new RequestManager();
|