From 1dc60af67c1df2b49b864a8ea6c93b6ad48150ba Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Wed, 13 Dec 2023 02:05:03 +0100 Subject: [PATCH] Add logic to reorder downloads (#495) --- src/lib/requests/RequestManager.ts | 68 +++++++++++++++++++----- src/lib/requests/client/GraphQLClient.ts | 2 + src/screens/DownloadQueue.tsx | 31 +++++++++-- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index a59ac915..ba6273ab 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -160,6 +160,8 @@ import { WebuiUpdateSubscription, ResetWebuiUpdateStatusMutation, ResetWebuiUpdateStatusMutationVariables, + GetDownloadStatusQuery, + GetDownloadStatusQueryVariables, } from '@/lib/graphql/generated/graphql.ts'; import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts'; import { SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts'; @@ -221,10 +223,11 @@ import { DOWNLOAD_STATUS_SUBSCRIPTION } from '@/lib/graphql/subscriptions/Downlo import { UPDATER_SUBSCRIPTION } from '@/lib/graphql/subscriptions/UpdaterSubscription.ts'; import { GET_SERVER_SETTINGS } from '@/lib/graphql/queries/SettingsQuery.ts'; import { UPDATE_SERVER_SETTINGS } from '@/lib/graphql/mutations/SettingsMutation.ts'; -import { BASE_MANGA_FIELDS, FULL_EXTENSION_FIELDS } from '@/lib/graphql/Fragments.ts'; +import { BASE_MANGA_FIELDS, FULL_DOWNLOAD_STATUS, FULL_EXTENSION_FIELDS } from '@/lib/graphql/Fragments.ts'; import { CLEAR_SERVER_CACHE } from '@/lib/graphql/mutations/ImageMutation.ts'; import { RESET_WEBUI_UPDATE_STATUS, UPDATE_WEBUI } from '@/lib/graphql/mutations/ServerInfoMutation.ts'; import { WEBUI_UPDATE_SUBSCRIPTION } from '@/lib/graphql/subscriptions/ServerInfoSubscription.ts'; +import { GET_DOWNLOAD_STATUS } from '@/lib/graphql/queries/DownloaderQuery.ts'; enum GQLMethod { QUERY = 'QUERY', @@ -323,6 +326,7 @@ export const SPECIAL_ED_SOURCES = { ], }; +// TODO - extract logic to reduce the size of this file... grew waaaaaaaaaaaaay too big peepoFat // TODO - correctly update cache after all mutations instead of refetching queries export class RequestManager { public static readonly API_VERSION = '/api/v1/'; @@ -1865,17 +1869,51 @@ export class RequestManager { ); } - public reorderChapterInDownloadQueue( - chapterId: number, - position: number, - options?: MutationOptions, - ): AbortableApolloMutationResponse { - return this.doRequest( - GQLMethod.MUTATION, - REORDER_CHAPTER_DOWNLOAD, - { input: { chapterId, to: position } }, - options, - ); + public useReorderChapterInDownloadQueue( + options?: MutationHookOptions, + ): AbortableApolloUseMutationResponse { + const [mutate, result] = this.doRequest(GQLMethod.USE_MUTATION, REORDER_CHAPTER_DOWNLOAD, undefined, options); + + const wrappedMutate = (mutationOptions: Parameters[0]) => { + const variables = mutationOptions?.variables?.input; + const cachedDownloadStatus = this.graphQLClient.client.readFragment< + DownloadStatusSubscription['downloadChanged'] + >({ + id: 'DownloadStatus:{}', + fragment: FULL_DOWNLOAD_STATUS, + }); + + 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]; } public addChaptersToDownloadQueue( @@ -1983,6 +2021,12 @@ export class RequestManager { return this.doRequest(GQLMethod.USE_QUERY, GET_UPDATE_STATUS, {}, options); } + public useGetDownloadStatus( + options?: SubscriptionHookOptions, + ): SubscriptionResult { + return this.doRequest(GQLMethod.USE_QUERY, GET_DOWNLOAD_STATUS, {}, options); + } + public useDownloadSubscription( options?: SubscriptionHookOptions, ): SubscriptionResult { diff --git a/src/lib/requests/client/GraphQLClient.ts b/src/lib/requests/client/GraphQLClient.ts index d8503526..fa4f843a 100644 --- a/src/lib/requests/client/GraphQLClient.ts +++ b/src/lib/requests/client/GraphQLClient.ts @@ -23,6 +23,8 @@ const typePolicies: StrictTypedTypePolicies = { WebUIUpdateInfo: { keyFields: [] }, WebUIUpdateCheck: { keyFields: [] }, SettingsType: { keyFields: [] }, + DownloadStatus: { keyFields: [] }, + DownloadType: { keyFields: ['chapter'] }, Query: { fields: { chapters: { diff --git a/src/screens/DownloadQueue.tsx b/src/screens/DownloadQueue.tsx index a1195bae..abd3051b 100644 --- a/src/screens/DownloadQueue.tsx +++ b/src/screens/DownloadQueue.tsx @@ -13,7 +13,7 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import { Card, CardActionArea, Stack, Box, Tooltip } from '@mui/material'; import IconButton from '@mui/material/IconButton'; import React, { useContext, useEffect } from 'react'; -import { DragDropContext, Draggable, DraggableProvided } from 'react-beautiful-dnd'; +import { DragDropContext, Draggable, DraggableProvided, DropResult } from 'react-beautiful-dnd'; import Typography from '@mui/material/Typography'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -97,9 +97,14 @@ const DownloadChapterItem = ({ export const DownloadQueue: React.FC = () => { const { t } = useTranslation(); - const { data: downloaderData, loading: isLoading } = requestManager.useDownloadSubscription(); - const queue = (downloaderData?.downloadChanged.queue as DownloadType[]) ?? []; - const status = downloaderData?.downloadChanged.state ?? 'STARTED'; + requestManager.useDownloadSubscription(); + const [reorderDownload, { reset: revertReorder }] = requestManager.useReorderChapterInDownloadQueue(); + + const { data: downloadStatusData, loading: isLoading } = requestManager.useGetDownloadStatus(); + const downloaderData = downloadStatusData?.downloadStatus; + + const queue = (downloaderData?.queue as DownloadType[]) ?? []; + const status = downloaderData?.state ?? 'STARTED'; const isQueueEmpty = !queue.length; const { setTitle, setAction } = useContext(NavBarContext); @@ -156,7 +161,23 @@ export const DownloadQueue: React.FC = () => { return () => window.removeEventListener('error', ignoreError); }, []); - const onDragEnd = () => {}; + const categoryReorder = (list: DownloadType[], from: number, to: number) => { + if (from === to) { + return; + } + + reorderDownload({ variables: { input: { chapterId: list[from].chapter.id, to } } }).catch(() => { + revertReorder(); + }); + }; + + const onDragEnd = (result: DropResult) => { + if (!result.destination) { + return; + } + + categoryReorder(queue, result.source.index, result.destination.index); + }; const handleDelete = async (chapter: TChapter) => { const isRunning = status === 'STARTED';