From e90d705610e88fb26f4808d6502ee0126cc86ea3 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Mon, 4 Nov 2024 01:26:58 +0100 Subject: [PATCH] Add option to update all updatable extensions --- public/locales/en.json | 15 ++-- src/lib/requests/RequestManager.ts | 69 +++++++++++++++++++ .../components/virtuoso/StyledGroupHeader.tsx | 5 +- .../extension/components/ExtensionCard.tsx | 18 +++-- src/modules/extension/screens/Extensions.tsx | 47 ++++++++++++- .../library/screens/LibraryDuplicates.tsx | 13 ++-- src/modules/updates/screens/Updates.tsx | 6 +- 7 files changed, 153 insertions(+), 20 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index d0d41b68..304ee30c 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -247,16 +247,21 @@ "install": "Install", "install_external": "Install external extension", "uninstall": "Uninstall", - "update": "Update" + "update": "Update", + "update_all": "Update all" } }, "label": { "add_repository_info": "You have to add a extension repository to be able to install extensions", - "installation_failed": "Could not install the extension", - "installed_successfully": "Extension installed", + "installation_failed_one": "Could not install the extension", + "installation_failed_other": "Could not install the extensions", + "installed_successfully_one": "Extension installed", + "installed_successfully_other": "Extensions installed", "installing_file": "Installing extension file…", - "uninstallation_failed": "Could not uninstall the extension", - "update_failed": "Could not update the extension" + "uninstallation_failed_one": "Could not uninstall the extension", + "uninstallation_failed_other": "Could not uninstall the extensions", + "update_failed_one": "Could not update the extension", + "update_failed_other": "Could not update the extensions" }, "language": { "all": "All", diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index e6704311..47c9681b 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -183,6 +183,8 @@ import { UpdateExtensionMutation, UpdateExtensionMutationVariables, UpdateExtensionPatchInput, + UpdateExtensionsMutation, + UpdateExtensionsMutationVariables, UpdateLibraryMangasMutation, UpdateLibraryMangasMutationVariables, UpdateMangaCategoriesMutation, @@ -221,6 +223,7 @@ import { GET_EXTENSIONS_FETCH, INSTALL_EXTERNAL_EXTENSION, UPDATE_EXTENSION, + UPDATE_EXTENSIONS, } from '@/lib/graphql/mutations/ExtensionMutation.ts'; import { GET_MIGRATABLE_SOURCES, GET_SOURCES_LIST } from '@/lib/graphql/queries/SourceQuery.ts'; import { @@ -1427,6 +1430,72 @@ export class RequestManager { return result; } + public updateExtensions( + ids: string[], + { isObsolete = false, ...patch }: UpdateExtensionPatchInput & { isObsolete?: boolean }, + options?: MutationOptions, + ): AbortableApolloMutationResponse { + const result = this.doRequest( + 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>( + EXTENSION_LIST_CACHE_KEY, + undefined, + ); + + if (!cachedExtensions || !cachedExtensions.data) { + return; + } + + const updatedCachedExtensions: MutationResult = { + ...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; + } + public getExtensionIconUrl(extension: string): string { return this.getValidImgUrlFor(`extension/icon/${extension}`); } diff --git a/src/modules/core/components/virtuoso/StyledGroupHeader.tsx b/src/modules/core/components/virtuoso/StyledGroupHeader.tsx index 3bea7650..5449ccc2 100644 --- a/src/modules/core/components/virtuoso/StyledGroupHeader.tsx +++ b/src/modules/core/components/virtuoso/StyledGroupHeader.tsx @@ -7,13 +7,14 @@ */ import { styled } from '@mui/material/styles'; -import Typography, { TypographyProps } from '@mui/material/Typography'; +import { TypographyProps } from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; import { shouldForwardProp } from '@/modules/core/utils/ShouldForwardProp.ts'; type StyledGroupHeaderProps = { isFirstItem: boolean; }; -export const StyledGroupHeader = styled(Typography, { +export const StyledGroupHeader = styled(Stack, { shouldForwardProp: shouldForwardProp(['isFirstItem']), })(({ theme, isFirstItem }) => ({ paddingLeft: theme.spacing(3), diff --git a/src/modules/extension/components/ExtensionCard.tsx b/src/modules/extension/components/ExtensionCard.tsx index 69171de9..3179127e 100644 --- a/src/modules/extension/components/ExtensionCard.tsx +++ b/src/modules/extension/components/ExtensionCard.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import Button from '@mui/material/Button'; @@ -37,6 +37,7 @@ interface IProps { extension: TExtension; handleUpdate: () => void; showSourceRepo: boolean; + forcedState?: ExtensionState; } export function ExtensionCard(props: IProps) { @@ -46,10 +47,16 @@ export function ExtensionCard(props: IProps) { extension: { name, lang, versionName, isInstalled, hasUpdate, isObsolete, pkgName, iconUrl, isNsfw, repo }, handleUpdate, showSourceRepo, + forcedState, } = props; - const [installedState, setInstalledState] = useState( + const [localInstalledState, setInstalledState] = useState( getInstalledState(isInstalled, isObsolete, hasUpdate), ); + const installedState = forcedState ?? localInstalledState; + + useEffect(() => { + setInstalledState(getInstalledState(isInstalled, isObsolete, hasUpdate)); + }, [getInstalledState(isInstalled, isObsolete, hasUpdate)]); const langPress = lang === 'all' ? t('extension.language.all') : lang.toUpperCase(); @@ -77,7 +84,7 @@ export function ExtensionCard(props: IProps) { handleUpdate(); } catch (e) { setInstalledState(getInstalledState(isInstalled, isObsolete, hasUpdate)); - makeToast(t(EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[action]), 'error'); + makeToast(t(EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[action], { count: 1 }), 'error'); } }; @@ -175,7 +182,10 @@ export function ExtensionCard(props: IProps) { + )} ); }} @@ -253,6 +291,9 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) { extension={item} handleUpdate={handleExtensionUpdate} showSourceRepo={areMultipleReposInUse} + forcedState={ + updatingExtensionIds.includes(item.pkgName) ? ExtensionState.UPDATING : undefined + } /> ); diff --git a/src/modules/library/screens/LibraryDuplicates.tsx b/src/modules/library/screens/LibraryDuplicates.tsx index d98de3aa..708d357d 100644 --- a/src/modules/library/screens/LibraryDuplicates.tsx +++ b/src/modules/library/screens/LibraryDuplicates.tsx @@ -14,6 +14,7 @@ import PopupState, { bindMenu, bindTrigger } from 'material-ui-popup-state'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx'; @@ -149,8 +150,10 @@ export const LibraryDuplicates = () => { ( - - {duplicatedTitles[index]} + + + {duplicatedTitles[index]} + )} computeItemKey={computeItemKey} @@ -170,8 +173,10 @@ export const LibraryDuplicates = () => { return duplicatedTitles.map((title, index) => ( - - {title} + + + {title} + { endReached={loadMore} groupCounts={groupCounts} groupContent={(index) => ( - - {groupedUpdates[index][0]} + + + {groupedUpdates[index][0]} + )} computeItemKey={computeItemKey}