diff --git a/package.json b/package.json index 050cf70d..cba7c979 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "react-i18next": "^13.2.2", "react-router-dom": "^6.16.0", "react-virtuoso": "^4.5.1", - "swr": "^2.2.2", "use-query-params": "^2.2.1", "vite": "^4.4.9", "vite-tsconfig-paths": "^4.2.1" diff --git a/src/components/context/AppContext.tsx b/src/components/context/AppContext.tsx index dd793768..f79322a6 100644 --- a/src/components/context/AppContext.tsx +++ b/src/components/context/AppContext.tsx @@ -9,7 +9,6 @@ import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; import React, { useMemo } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import { SWRConfig } from 'swr'; import { QueryParamProvider } from 'use-query-params'; import { ReactRouter6Adapter } from 'use-query-params/adapters/react-router-6'; import createTheme from '@/theme'; @@ -36,21 +35,19 @@ const AppContext: React.FC = ({ children }) => { const theme = useMemo(() => createTheme(darkTheme), [darkTheme]); return ( - - - - - - - - {children} - - - - - - - + + + + + + + {children} + + + + + + ); }; diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index 75e8cb8a..aab0b147 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -6,9 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { AxiosInstance, AxiosRequestConfig } from 'axios'; -import useSWR, { Middleware, SWRConfiguration, SWRResponse } from 'swr'; -import useSWRInfinite, { SWRInfiniteConfiguration, SWRInfiniteResponse } from 'swr/infinite'; +import { AxiosInstance } from 'axios'; import { ApolloError, ApolloQueryResult, @@ -26,7 +24,7 @@ import { } from '@apollo/client'; import { OperationVariables } from '@apollo/client/core'; import { useEffect, useRef, useState } from 'react'; -import { HttpMethod as DefaultHttpMethod, IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts'; +import { IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts'; import storage from '@/util/localStorage.tsx'; import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts'; import { @@ -191,13 +189,6 @@ import { CustomCache } from '@/lib/requests/CustomCache.ts'; import { RESTORE_BACKUP } from '@/lib/graphql/mutations/BackupMutation.ts'; import { VALIDATE_BACKUP } from '@/lib/graphql/queries/BackupQuery.ts'; -enum SWRHttpMethod { - SWR_GET, - SWR_GET_INFINITE, - SWR_POST, - SWR_POST_INFINITE, -} - enum GQLMethod { QUERY = 'QUERY', USE_QUERY = 'USE_QUERY', @@ -205,23 +196,6 @@ enum GQLMethod { MUTATION = 'MUTATION', } -type HttpMethodType = DefaultHttpMethod | SWRHttpMethod; -const HttpMethod = { ...SWRHttpMethod, ...DefaultHttpMethod }; - -type CustomSWROptions = { - skipRequest?: boolean; - getEndpoint?: (index: number, previousData: Data | null) => string | null; - disableCache?: boolean; -}; - -type SWROptions = SWRConfiguration & CustomSWROptions; -type SWRInfiniteOptions = SWRInfiniteConfiguration & CustomSWROptions; - -type SWRInfiniteResponseLoadInfo = { - isInitialLoad: boolean; - isLoadMore: boolean; -}; - type CustomApolloOptions = { /** * This is a workaround for an apollo bug (?). @@ -259,11 +233,6 @@ type ApolloPaginatedMutationOptions< > = MutationHookOptions & { skipRequest?: boolean }; type AbortableRequest = { abortRequest: AbortController['abort'] }; -export type AbortableAxiosResponse = { response: Promise } & AbortableRequest; -export type AbortableSWRResponse = SWRResponse & AbortableRequest; -export type AbortableSWRInfiniteResponse = SWRInfiniteResponse & - AbortableRequest & - SWRInfiniteResponseLoadInfo; export type AbortabaleApolloQueryResponse = { response: Promise>; @@ -301,36 +270,6 @@ export type AbortableApolloUseMutationPaginatedResponse< ]; export type AbortableApolloMutationResponse = { response: Promise> } & AbortableRequest; -const isLoadingMore = (swrResult: SWRInfiniteResponse): boolean => { - const isNextPageMissing = !!swrResult.data && typeof swrResult.data[swrResult.size - 1] === 'undefined'; - const isRequestActive = swrResult.isValidating; - // SWR "isLoading" state is only updated for the first load, for every subsequent load it's "false" - return !swrResult.isLoading && swrResult.size > 0 && isNextPageMissing && isRequestActive; -}; - -const disableSwrInfiniteCache: Middleware = (useSWRNext) => (key, fetcher, config) => { - const swr = useSWRNext(key, fetcher, config) as unknown as SWRInfiniteResponse; - const { size, data, isLoading, isValidating } = swr; - const isActuallyValidating = !isLoading && !isLoadingMore(swr) && isValidating; - return { - ...swr, - isLoading: isActuallyValidating ? true : isLoading, - data: isActuallyValidating ? undefined : data, - size: isActuallyValidating ? 1 : size, - } as SWRResponse; -}; - -const disableSwrCache: Middleware = (useSWRNext) => (key, fetcher, config) => { - const swr = useSWRNext(key, fetcher, config); - const { data, isLoading, isValidating } = swr; - return { ...swr, isLoading: isValidating ? true : isLoading, data: isValidating ? undefined : data }; -}; - -// the following endpoints have not been implemented: -// - PUT /api/v1/manga/{mangaId}/chapter/{chapterIndex} - modify chapter # PATCH endpoint used instead -// - POST /api/v1/backup/import - import backup # "import backup file" endpoint used instead -// - POST /api/v1/backup/validate - validate backup # "validate backup file" endpoint used instead -// - GET /api/v1/backup/export - export backup # no function needed, url gets called via link triggering the download export class RequestManager { public static readonly API_VERSION = '/api/v1/'; @@ -826,165 +765,6 @@ export class RequestManager { } } - private useSwr< - Data = any, - ErrorResponse = any, - OptionsSWR extends SWROptions = SWROptions, - >( - url: string, - httpMethod: DefaultHttpMethod, - { - data, - axiosOptions, - swrOptions, - }: { - data?: Data; - axiosOptions?: AxiosRequestConfig; - swrOptions?: OptionsSWR; - } = {}, - ): SWRResponse { - const { skipRequest, disableCache, ...swrConfig } = swrOptions ?? {}; - - // in case "null" gets passed as the url, SWR won't do the request - return useSWR(skipRequest ? null : url, { - fetcher: (path: string) => this.restClient.fetcher(path, { data, httpMethod, config: axiosOptions }), - use: disableCache ? [disableSwrCache] : undefined, - ...swrConfig, - }); - } - - public useSwrInfinite< - Data = any, - ErrorResponse = any, - OptionsSWR extends SWRInfiniteOptions = SWRInfiniteOptions, - >( - getEndpoint: Required>['getEndpoint'], - httpMethod: DefaultHttpMethod, - { - data, - axiosOptions, - swrOptions, - }: { data?: any; axiosOptions?: AxiosRequestConfig; swrOptions?: OptionsSWR } = {}, - ): SWRInfiniteResponse & SWRInfiniteResponseLoadInfo { - const { skipRequest, disableCache, ...swrConfig } = swrOptions ?? {}; - - // useSWRInfinite will (by default) revalidate the first page, to check if the other pages have to be revalidated as well - const swrResult = useSWRInfinite( - (index, previousData) => { - const pageEndpoint = getEndpoint(index, previousData); - return pageEndpoint !== null && !skipRequest ? this.getValidUrlFor(pageEndpoint) : null; - }, - { - fetcher: (path: string) => this.restClient.fetcher(path, { httpMethod, data, config: axiosOptions }), - use: disableCache ? [disableSwrInfiniteCache] : undefined, - ...swrConfig, - }, - ); - - const customSwrResult = { - ...swrResult, - isInitialLoad: swrResult.isLoading, - isLoadMore: isLoadingMore(swrResult), - }; - customSwrResult.isLoading = customSwrResult.isInitialLoad || customSwrResult.isLoadMore; - - return customSwrResult; - } - - /** - * Performs the actual server request. - * - * In case {@link HttpMethod.GET_SWR} gets passed, the "useSWR" hook gets called. - * In case {@link HttpMethod.GET_SWR_INFINITE} gets passed, the "useSWRInfinite" hook gets called. - * In that case "getEndpoint" has to be passed, which gets used over "endpoint" - * - * Pass "skipRequest" to make SWR skip sending the request to the server. - * In case "formData" is passed, "data" gets ignored. - */ - private doRequest< - Result extends AbortableAxiosResponse | AbortableSWRResponse | AbortableSWRInfiniteResponse, - OptionsSWR extends SWROptions | SWRInfiniteOptions, - >( - httpMethod: HttpMethodType, - endpoint: string, - { - apiVersion = RequestManager.API_VERSION, - data: dataToSend, - formData, - axiosOptions, - swrOptions, - }: { - apiVersion?: string; - data?: any; - formData?: { [key: string]: any }; - axiosOptions?: AxiosRequestConfig; - swrOptions?: OptionsSWR; - } = {}, - ): Result { - const url = `${apiVersion}${endpoint}`; - - let data = dataToSend; - if (formData) { - data = new FormData(); - - Object.entries(formData).forEach(([key, value]) => { - if (value !== undefined) data.append(key, value); // "append" automatically converts non string or blob values to strings - }); - } - - const abortController = new AbortController(); - const abortRequest = (reason?: any): void => { - if (!abortController.signal.aborted) { - abortController.abort(reason); - } - }; - const axiosOptionsWithAbortController = { ...axiosOptions, signal: abortController.signal }; - switch (httpMethod) { - case HttpMethod.SWR_GET: - return { - ...(this.useSwr(url, HttpMethod.GET, { axiosOptions, swrOptions }) as Result), - abortRequest, - }; - case HttpMethod.SWR_GET_INFINITE: - // throw TypeError in case options aren't correctly passed - return { - ...(this.useSwrInfinite(swrOptions!.getEndpoint!, HttpMethod.GET, { - axiosOptions: axiosOptionsWithAbortController, - swrOptions, - }) as Result), - abortRequest, - }; - case SWRHttpMethod.SWR_POST_INFINITE: - return { - ...(this.useSwrInfinite(swrOptions!.getEndpoint!, HttpMethod.POST, { - data, - axiosOptions: axiosOptionsWithAbortController, - swrOptions, - }) as Result), - abortRequest, - }; - case HttpMethod.SWR_POST: - return { - ...(this.useSwr(url, HttpMethod.POST, { - data, - axiosOptions: axiosOptionsWithAbortController, - swrOptions, - }) as Result), - controller: abortController, - }; - default: - return { - response: this.restClient.fetcher(url, { - data, - httpMethod, - config: axiosOptionsWithAbortController, - checkResponseIsJson: false, - }), - abortRequest, - } as Result; - } - } - public useGetGlobalMeta( options?: QueryHookOptions, ): AbortableApolloUseQueryResponse { diff --git a/yarn.lock b/yarn.lock index 69fa2542..57fda819 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2433,11 +2433,6 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -client-only@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" - integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== - cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -5298,14 +5293,6 @@ swap-case@^2.0.2: dependencies: tslib "^2.0.3" -swr@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.2.tgz#abcb1f9c97e10527789884169d58b878472d4c98" - integrity sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ== - dependencies: - client-only "^0.0.1" - use-sync-external-store "^1.2.0" - symbol-observable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" @@ -5588,11 +5575,6 @@ use-query-params@^2.2.1: dependencies: serialize-query-params "^2.0.2" -use-sync-external-store@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== - util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"