@@ -59,6 +59,8 @@
|
|||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"action": "Remove bookmark",
|
"action": "Remove bookmark",
|
||||||
|
"confirmation_one": "You are about to remove one bookmark",
|
||||||
|
"confirmation_other": "You are about to remove {{count}} bookmarks",
|
||||||
"error_one": "Could not remove the bookmark",
|
"error_one": "Could not remove the bookmark",
|
||||||
"error_other": "Could not remove the bookmarks",
|
"error_other": "Could not remove the bookmarks",
|
||||||
"success_one": "Chapter bookmark removed",
|
"success_one": "Chapter bookmark removed",
|
||||||
@@ -75,6 +77,8 @@
|
|||||||
"action": "Download",
|
"action": "Download",
|
||||||
"ahead": "$t(download.settings.download_ahead.title) ({{count}})",
|
"ahead": "$t(download.settings.download_ahead.title) ({{count}})",
|
||||||
"all": "$t(extension.language.all)",
|
"all": "$t(extension.language.all)",
|
||||||
|
"confirmation_one": "You are about to download one chapter",
|
||||||
|
"confirmation_other": "You are about to download {{count}} chapters.\nSuwayomi is not a mass downloader and too many downloads can get you banned from sources and/or cause performance issues.",
|
||||||
"error_one": "Could not add the download",
|
"error_one": "Could not add the download",
|
||||||
"error_other": "Could not add downloads",
|
"error_other": "Could not add downloads",
|
||||||
"next": "$t(reader.button.next_chapter)",
|
"next": "$t(reader.button.next_chapter)",
|
||||||
@@ -92,6 +96,8 @@
|
|||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"action": "Delete",
|
"action": "Delete",
|
||||||
|
"confirmation_one": "You are about to delete one download",
|
||||||
|
"confirmation_other": "You are about to delete {{count}} downloads",
|
||||||
"error_one": "Could not delete the chapter",
|
"error_one": "Could not delete the chapter",
|
||||||
"error_other": "Could not delete chapters",
|
"error_other": "Could not delete chapters",
|
||||||
"success_one": "Chapter deleted",
|
"success_one": "Chapter deleted",
|
||||||
@@ -113,6 +119,8 @@
|
|||||||
"current": "Mark as read",
|
"current": "Mark as read",
|
||||||
"previous": "Mark previous as read"
|
"previous": "Mark previous as read"
|
||||||
},
|
},
|
||||||
|
"confirmation_one": "You are about to mark one chapter as read",
|
||||||
|
"confirmation_other": "You are about to mark {{count}} chapters as read",
|
||||||
"error_one": "Could not mark the chapter as read",
|
"error_one": "Could not mark the chapter as read",
|
||||||
"error_other": "Could not mark chapters as read",
|
"error_other": "Could not mark chapters as read",
|
||||||
"success_one": "Chapter marked as read",
|
"success_one": "Chapter marked as read",
|
||||||
@@ -125,6 +133,8 @@
|
|||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"action": "Mark as unread",
|
"action": "Mark as unread",
|
||||||
|
"confirmation_one": "You are about to mark one chapter as unread",
|
||||||
|
"confirmation_other": "You are about to mark {{count}} chapters as unread",
|
||||||
"error_one": "Could not mark the chapter as unread",
|
"error_one": "Could not mark the chapter as unread",
|
||||||
"error_other": "Could not mark chapters as unread",
|
"error_other": "Could not mark chapters as unread",
|
||||||
"success_one": "Chapter marked as unread",
|
"success_one": "Chapter marked as unread",
|
||||||
@@ -720,6 +730,8 @@
|
|||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"action": "Change categories",
|
"action": "Change categories",
|
||||||
|
"confirmation_one": "You are about to change the category of one entry",
|
||||||
|
"confirmation_other": "You are about to change the category of {{count}} entries",
|
||||||
"error_one": "Could not change the categories of the manga",
|
"error_one": "Could not change the categories of the manga",
|
||||||
"error_other": "Could not change the categories of the manga",
|
"error_other": "Could not change the categories of the manga",
|
||||||
"success_one": "Changed categories of manga",
|
"success_one": "Changed categories of manga",
|
||||||
@@ -748,6 +760,8 @@
|
|||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"action": "Remove from the library",
|
"action": "Remove from the library",
|
||||||
|
"confirmation_one": "You are about to remove one entry from your library",
|
||||||
|
"confirmation_other": "You are about to remove {{count}} entries from your library",
|
||||||
"error_one": "Could not remove manga from the library",
|
"error_one": "Could not remove manga from the library",
|
||||||
"error_other": "Could not remove manga from the library",
|
"error_other": "Could not remove manga from the library",
|
||||||
"success_one": "Removed manga from the library",
|
"success_one": "Removed manga from the library",
|
||||||
|
|||||||
@@ -28,12 +28,25 @@ export const CHAPTER_SORT_OPTIONS_TO_TRANSLATION_KEY: Record<ChapterSortMode, Tr
|
|||||||
fetchedAt: 'global.sort.label.by_fetch_date',
|
fetchedAt: 'global.sort.label.by_fetch_date',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CHAPTER_ACTION_TO_CONFIRMATION_REQUIRED: Record<
|
||||||
|
ChapterAction,
|
||||||
|
{ always: boolean; bulkAction: boolean; bulkActionCountForce?: number }
|
||||||
|
> = {
|
||||||
|
download: { always: false, bulkAction: false, bulkActionCountForce: 300 },
|
||||||
|
delete: { always: true, bulkAction: true },
|
||||||
|
bookmark: { always: false, bulkAction: false },
|
||||||
|
unbookmark: { always: false, bulkAction: true },
|
||||||
|
mark_as_read: { always: false, bulkAction: true },
|
||||||
|
mark_as_unread: { always: false, bulkAction: true },
|
||||||
|
};
|
||||||
|
|
||||||
export const CHAPTER_ACTION_TO_TRANSLATION: {
|
export const CHAPTER_ACTION_TO_TRANSLATION: {
|
||||||
[key in ChapterAction]: {
|
[key in ChapterAction]: {
|
||||||
action: {
|
action: {
|
||||||
single: TranslationKey;
|
single: TranslationKey;
|
||||||
selected: TranslationKey;
|
selected: TranslationKey;
|
||||||
};
|
};
|
||||||
|
confirmation?: TranslationKey;
|
||||||
success: TranslationKey;
|
success: TranslationKey;
|
||||||
error: TranslationKey;
|
error: TranslationKey;
|
||||||
};
|
};
|
||||||
@@ -43,6 +56,7 @@ export const CHAPTER_ACTION_TO_TRANSLATION: {
|
|||||||
single: 'chapter.action.download.add.label.action',
|
single: 'chapter.action.download.add.label.action',
|
||||||
selected: 'chapter.action.download.add.button.selected',
|
selected: 'chapter.action.download.add.button.selected',
|
||||||
},
|
},
|
||||||
|
confirmation: 'chapter.action.download.add.label.confirmation',
|
||||||
success: 'chapter.action.download.add.label.success',
|
success: 'chapter.action.download.add.label.success',
|
||||||
error: 'chapter.action.download.add.label.error',
|
error: 'chapter.action.download.add.label.error',
|
||||||
},
|
},
|
||||||
@@ -51,6 +65,7 @@ export const CHAPTER_ACTION_TO_TRANSLATION: {
|
|||||||
single: 'chapter.action.download.delete.label.action',
|
single: 'chapter.action.download.delete.label.action',
|
||||||
selected: 'chapter.action.download.delete.button.selected',
|
selected: 'chapter.action.download.delete.button.selected',
|
||||||
},
|
},
|
||||||
|
confirmation: 'chapter.action.download.delete.label.confirmation',
|
||||||
success: 'chapter.action.download.delete.label.success',
|
success: 'chapter.action.download.delete.label.success',
|
||||||
error: 'chapter.action.download.delete.label.error',
|
error: 'chapter.action.download.delete.label.error',
|
||||||
},
|
},
|
||||||
@@ -67,6 +82,7 @@ export const CHAPTER_ACTION_TO_TRANSLATION: {
|
|||||||
single: 'chapter.action.bookmark.remove.label.action',
|
single: 'chapter.action.bookmark.remove.label.action',
|
||||||
selected: 'chapter.action.bookmark.remove.button.selected',
|
selected: 'chapter.action.bookmark.remove.button.selected',
|
||||||
},
|
},
|
||||||
|
confirmation: 'chapter.action.bookmark.remove.label.confirmation',
|
||||||
success: 'chapter.action.bookmark.remove.label.success',
|
success: 'chapter.action.bookmark.remove.label.success',
|
||||||
error: 'chapter.action.bookmark.remove.label.error',
|
error: 'chapter.action.bookmark.remove.label.error',
|
||||||
},
|
},
|
||||||
@@ -75,6 +91,7 @@ export const CHAPTER_ACTION_TO_TRANSLATION: {
|
|||||||
single: 'chapter.action.mark_as_read.add.label.action.current',
|
single: 'chapter.action.mark_as_read.add.label.action.current',
|
||||||
selected: 'chapter.action.mark_as_read.add.button.selected',
|
selected: 'chapter.action.mark_as_read.add.button.selected',
|
||||||
},
|
},
|
||||||
|
confirmation: 'chapter.action.mark_as_read.add.label.confirmation',
|
||||||
success: 'chapter.action.mark_as_read.add.label.success',
|
success: 'chapter.action.mark_as_read.add.label.success',
|
||||||
error: 'chapter.action.mark_as_read.add.label.error',
|
error: 'chapter.action.mark_as_read.add.label.error',
|
||||||
},
|
},
|
||||||
@@ -83,6 +100,7 @@ export const CHAPTER_ACTION_TO_TRANSLATION: {
|
|||||||
single: 'chapter.action.mark_as_read.remove.label.action',
|
single: 'chapter.action.mark_as_read.remove.label.action',
|
||||||
selected: 'chapter.action.mark_as_read.remove.button.selected',
|
selected: 'chapter.action.mark_as_read.remove.button.selected',
|
||||||
},
|
},
|
||||||
|
confirmation: 'chapter.action.mark_as_read.remove.label.confirmation',
|
||||||
success: 'chapter.action.mark_as_read.remove.label.success',
|
success: 'chapter.action.mark_as_read.remove.label.success',
|
||||||
error: 'chapter.action.mark_as_read.remove.label.error',
|
error: 'chapter.action.mark_as_read.remove.label.error',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
|
|||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
import { DOWNLOAD_TYPE_FIELDS } from '@/lib/graphql/fragments/DownloadFragments.ts';
|
import { DOWNLOAD_TYPE_FIELDS } from '@/lib/graphql/fragments/DownloadFragments.ts';
|
||||||
import { epochToDate, getDateString } from '@/util/DateHelper.ts';
|
import { epochToDate, getDateString } from '@/util/DateHelper.ts';
|
||||||
import { CHAPTER_ACTION_TO_TRANSLATION } from '@/modules/chapter/Chapter.constants.ts';
|
import {
|
||||||
|
CHAPTER_ACTION_TO_CONFIRMATION_REQUIRED,
|
||||||
|
CHAPTER_ACTION_TO_TRANSLATION,
|
||||||
|
} from '@/modules/chapter/Chapter.constants.ts';
|
||||||
import {
|
import {
|
||||||
ChapterAction,
|
ChapterAction,
|
||||||
ChapterBookmarkInfo,
|
ChapterBookmarkInfo,
|
||||||
@@ -38,6 +41,8 @@ import {
|
|||||||
ChapterScanlatorInfo,
|
ChapterScanlatorInfo,
|
||||||
ChapterSourceOrderInfo,
|
ChapterSourceOrderInfo,
|
||||||
} from '@/modules/chapter/Chapter.types.ts';
|
} from '@/modules/chapter/Chapter.types.ts';
|
||||||
|
import { assertIsDefined } from '@/Asserts.ts';
|
||||||
|
import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx';
|
||||||
|
|
||||||
export class Chapters {
|
export class Chapters {
|
||||||
static getIds(chapters: { id: number }[]): number[] {
|
static getIds(chapters: { id: number }[]): number[] {
|
||||||
@@ -183,19 +188,21 @@ export class Chapters {
|
|||||||
.filter((matchingChapters): matchingChapters is [Chapter, Chapter] => matchingChapters !== null);
|
.filter((matchingChapters): matchingChapters is [Chapter, Chapter] => matchingChapters !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async download(chapterIds: number[]): Promise<void> {
|
static async download(chapterIds: number[], disableConfirmation?: boolean): Promise<void> {
|
||||||
return Chapters.executeAction(
|
return Chapters.executeAction(
|
||||||
'download',
|
'download',
|
||||||
chapterIds.length,
|
chapterIds.length,
|
||||||
() => requestManager.addChaptersToDownloadQueue(chapterIds).response,
|
() => requestManager.addChaptersToDownloadQueue(chapterIds).response,
|
||||||
|
disableConfirmation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async delete(chapterIds: number[]): Promise<void> {
|
static async delete(chapterIds: number[], disableConfirmation?: boolean): Promise<void> {
|
||||||
return Chapters.executeAction(
|
return Chapters.executeAction(
|
||||||
'delete',
|
'delete',
|
||||||
chapterIds.length,
|
chapterIds.length,
|
||||||
() => requestManager.deleteDownloadedChapters(chapterIds).response,
|
() => requestManager.deleteDownloadedChapters(chapterIds).response,
|
||||||
|
disableConfirmation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +210,7 @@ export class Chapters {
|
|||||||
chapters: (ChapterIdInfo & ChapterDownloadInfo & ChapterBookmarkInfo)[],
|
chapters: (ChapterIdInfo & ChapterDownloadInfo & ChapterBookmarkInfo)[],
|
||||||
wasManuallyMarkedAsRead: boolean = false,
|
wasManuallyMarkedAsRead: boolean = false,
|
||||||
trackProgressMangaId?: MangaIdInfo['id'],
|
trackProgressMangaId?: MangaIdInfo['id'],
|
||||||
|
disableConfirmation?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { deleteChaptersManuallyMarkedRead, deleteChaptersWithBookmark, updateProgressManualMarkRead } =
|
const { deleteChaptersManuallyMarkedRead, deleteChaptersWithBookmark, updateProgressManualMarkRead } =
|
||||||
await getMetadataServerSettings();
|
await getMetadataServerSettings();
|
||||||
@@ -221,14 +229,16 @@ export class Chapters {
|
|||||||
trackProgressMangaId:
|
trackProgressMangaId:
|
||||||
updateProgressManualMarkRead && wasManuallyMarkedAsRead ? trackProgressMangaId : undefined,
|
updateProgressManualMarkRead && wasManuallyMarkedAsRead ? trackProgressMangaId : undefined,
|
||||||
}).response,
|
}).response,
|
||||||
|
disableConfirmation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async markAsUnread(chapterIds: number[]): Promise<void> {
|
static async markAsUnread(chapterIds: number[], disableConfirmation?: boolean): Promise<void> {
|
||||||
return Chapters.executeAction(
|
return Chapters.executeAction(
|
||||||
'mark_as_unread',
|
'mark_as_unread',
|
||||||
chapterIds.length,
|
chapterIds.length,
|
||||||
() => requestManager.updateChapters(chapterIds, { isRead: false }).response,
|
() => requestManager.updateChapters(chapterIds, { isRead: false }).response,
|
||||||
|
disableConfirmation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,8 +262,33 @@ export class Chapters {
|
|||||||
action: ChapterAction,
|
action: ChapterAction,
|
||||||
itemCount: number,
|
itemCount: number,
|
||||||
fnToExecute: () => Promise<unknown>,
|
fnToExecute: () => Promise<unknown>,
|
||||||
|
disableConfirmation?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { always, bulkAction, bulkActionCountForce } = CHAPTER_ACTION_TO_CONFIRMATION_REQUIRED[action];
|
||||||
|
const requiresConfirmation =
|
||||||
|
(!disableConfirmation && (always || (bulkAction && itemCount > 1))) ||
|
||||||
|
(bulkActionCountForce && itemCount >= bulkActionCountForce);
|
||||||
|
const confirmationMessage = CHAPTER_ACTION_TO_TRANSLATION[action].confirmation;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (requiresConfirmation) {
|
||||||
|
assertIsDefined(confirmationMessage);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await awaitConfirmation({
|
||||||
|
title: translate('global.label.are_you_sure'),
|
||||||
|
message: translate(confirmationMessage, { count: itemCount }),
|
||||||
|
actions: {
|
||||||
|
confirm: {
|
||||||
|
title: translate('global.button.ok'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await fnToExecute();
|
await fnToExecute();
|
||||||
makeToast(translate(CHAPTER_ACTION_TO_TRANSLATION[action].success, { count: itemCount }), 'success');
|
makeToast(translate(CHAPTER_ACTION_TO_TRANSLATION[action].success, { count: itemCount }), 'success');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
import { MangaStatus } from '@/lib/graphql/generated/graphql.ts';
|
import { MangaStatus } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { TranslationKey } from '@/Base.types.ts';
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
import { MangaAction, MangaIdInfo, MangaType } from '@/modules/manga/Manga.types.ts';
|
import { MangaAction, MangaIdInfo, MangaType } from '@/modules/manga/Manga.types.ts';
|
||||||
|
import {
|
||||||
|
CHAPTER_ACTION_TO_CONFIRMATION_REQUIRED,
|
||||||
|
CHAPTER_ACTION_TO_TRANSLATION,
|
||||||
|
} from '@/modules/chapter/Chapter.constants.ts';
|
||||||
|
|
||||||
export const FALLBACK_MANGA: MangaIdInfo = { id: -1 };
|
export const FALLBACK_MANGA: MangaIdInfo = { id: -1 };
|
||||||
|
|
||||||
@@ -26,53 +30,35 @@ export const MANGA_STATUS_TO_TRANSLATION: Record<MangaStatus, TranslationKey> =
|
|||||||
[MangaStatus.Unknown]: 'manga.status.unknown',
|
[MangaStatus.Unknown]: 'manga.status.unknown',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MANGA_ACTION_TO_CONFIRMATION_REQUIRED: Record<
|
||||||
|
MangaAction,
|
||||||
|
{ always: boolean; bulkAction: boolean; bulkActionCountForce?: number }
|
||||||
|
> = {
|
||||||
|
...CHAPTER_ACTION_TO_CONFIRMATION_REQUIRED,
|
||||||
|
remove_from_library: { always: true, bulkAction: true },
|
||||||
|
change_categories: { always: false, bulkAction: false },
|
||||||
|
migrate: { always: false, bulkAction: false },
|
||||||
|
track: { always: false, bulkAction: false },
|
||||||
|
};
|
||||||
|
|
||||||
export const MANGA_ACTION_TO_TRANSLATION: {
|
export const MANGA_ACTION_TO_TRANSLATION: {
|
||||||
[key in MangaAction]: {
|
[key in MangaAction]: {
|
||||||
action: {
|
action: {
|
||||||
single: TranslationKey;
|
single: TranslationKey;
|
||||||
selected: TranslationKey;
|
selected: TranslationKey;
|
||||||
};
|
};
|
||||||
|
confirmation?: TranslationKey;
|
||||||
success: TranslationKey;
|
success: TranslationKey;
|
||||||
error: TranslationKey;
|
error: TranslationKey;
|
||||||
};
|
};
|
||||||
} = {
|
} = {
|
||||||
download: {
|
...CHAPTER_ACTION_TO_TRANSLATION,
|
||||||
action: {
|
|
||||||
single: 'chapter.action.download.add.label.action',
|
|
||||||
selected: 'chapter.action.download.add.button.selected',
|
|
||||||
},
|
|
||||||
success: 'chapter.action.download.add.label.success',
|
|
||||||
error: 'chapter.action.download.add.label.error',
|
|
||||||
},
|
|
||||||
delete: {
|
|
||||||
action: {
|
|
||||||
single: 'chapter.action.download.delete.label.action',
|
|
||||||
selected: 'chapter.action.download.delete.button.selected',
|
|
||||||
},
|
|
||||||
success: 'chapter.action.download.delete.label.success',
|
|
||||||
error: 'chapter.action.download.delete.label.error',
|
|
||||||
},
|
|
||||||
mark_as_read: {
|
|
||||||
action: {
|
|
||||||
single: 'chapter.action.mark_as_read.add.label.action.current',
|
|
||||||
selected: 'chapter.action.mark_as_read.add.button.selected',
|
|
||||||
},
|
|
||||||
success: 'chapter.action.mark_as_read.add.label.success',
|
|
||||||
error: 'chapter.action.mark_as_read.add.label.error',
|
|
||||||
},
|
|
||||||
mark_as_unread: {
|
|
||||||
action: {
|
|
||||||
single: 'chapter.action.mark_as_read.remove.label.action',
|
|
||||||
selected: 'chapter.action.mark_as_read.remove.button.selected',
|
|
||||||
},
|
|
||||||
success: 'chapter.action.mark_as_read.remove.label.success',
|
|
||||||
error: 'chapter.action.mark_as_read.remove.label.error',
|
|
||||||
},
|
|
||||||
remove_from_library: {
|
remove_from_library: {
|
||||||
action: {
|
action: {
|
||||||
single: 'manga.action.library.remove.label.action',
|
single: 'manga.action.library.remove.label.action',
|
||||||
selected: 'manga.action.library.remove.button.selected',
|
selected: 'manga.action.library.remove.button.selected',
|
||||||
},
|
},
|
||||||
|
confirmation: 'manga.action.library.remove.label.confirmation',
|
||||||
success: 'manga.action.library.remove.label.success',
|
success: 'manga.action.library.remove.label.success',
|
||||||
error: 'manga.action.library.remove.label.error',
|
error: 'manga.action.library.remove.label.error',
|
||||||
},
|
},
|
||||||
@@ -81,6 +67,7 @@ export const MANGA_ACTION_TO_TRANSLATION: {
|
|||||||
single: 'manga.action.category.label.action',
|
single: 'manga.action.category.label.action',
|
||||||
selected: 'manga.action.category.button.selected',
|
selected: 'manga.action.category.button.selected',
|
||||||
},
|
},
|
||||||
|
confirmation: 'manga.action.category.label.confirmation',
|
||||||
success: 'manga.action.category.label.success',
|
success: 'manga.action.category.label.success',
|
||||||
error: 'manga.action.category.label.error',
|
error: 'manga.action.category.label.error',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const useManageMangaLibraryState = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await Mangas.removeFromLibrary([manga.id]);
|
await Mangas.removeFromLibrary([manga.id], true);
|
||||||
setIsInLibrary(false);
|
setIsInLibrary(false);
|
||||||
}, [manga.id, confirmRemoval]);
|
}, [manga.id, confirmRemoval]);
|
||||||
|
|
||||||
|
|||||||
@@ -43,11 +43,14 @@ import {
|
|||||||
MigrateMode,
|
MigrateMode,
|
||||||
} from '@/modules/manga/Manga.types.ts';
|
} from '@/modules/manga/Manga.types.ts';
|
||||||
import {
|
import {
|
||||||
|
MANGA_ACTION_TO_CONFIRMATION_REQUIRED,
|
||||||
MANGA_ACTION_TO_TRANSLATION,
|
MANGA_ACTION_TO_TRANSLATION,
|
||||||
MANGA_TAGS_BY_MANGA_TYPE,
|
MANGA_TAGS_BY_MANGA_TYPE,
|
||||||
SOURCES_BY_MANGA_TYPE,
|
SOURCES_BY_MANGA_TYPE,
|
||||||
} from '@/modules/manga/Manga.constants.ts';
|
} from '@/modules/manga/Manga.constants.ts';
|
||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
|
import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx';
|
||||||
|
import { assertIsDefined } from '@/Asserts.ts';
|
||||||
|
|
||||||
type MangaToMigrate = NonNullable<GetMangaToMigrateQuery['manga']>;
|
type MangaToMigrate = NonNullable<GetMangaToMigrateQuery['manga']>;
|
||||||
type MangaToMigrateTo = NonNullable<GetMangaToMigrateToFetchMutation['fetchManga']>['manga'];
|
type MangaToMigrateTo = NonNullable<GetMangaToMigrateToFetchMutation['fetchManga']>['manga'];
|
||||||
@@ -194,6 +197,7 @@ export class Mangas {
|
|||||||
static async downloadChapters(
|
static async downloadChapters(
|
||||||
mangaIds: number[],
|
mangaIds: number[],
|
||||||
{ size, onlyUnread, downloadAhead = false }: DownloadChaptersOptions = {},
|
{ size, onlyUnread, downloadAhead = false }: DownloadChaptersOptions = {},
|
||||||
|
disableConfirmation?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const [chaptersToConsider, unReadDownloadedChapters] = await Promise.all([
|
const [chaptersToConsider, unReadDownloadedChapters] = await Promise.all([
|
||||||
Mangas.getChapterIdsWithState(mangaIds, {
|
Mangas.getChapterIdsWithState(mangaIds, {
|
||||||
@@ -250,25 +254,34 @@ export class Mangas {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Chapters.download(Chapters.getIds(chapterIdsToDownload));
|
return Chapters.download(Chapters.getIds(chapterIdsToDownload), disableConfirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteChapters(mangaIds: number[]): Promise<void> {
|
static async deleteChapters(mangaIds: number[], disableConfirmation?: boolean): Promise<void> {
|
||||||
const chapters = await Mangas.getChapterIdsWithState(mangaIds, { isDownloaded: true });
|
const chapters = await Mangas.getChapterIdsWithState(mangaIds, { isDownloaded: true });
|
||||||
return Chapters.delete(Chapters.getIds(chapters));
|
return Chapters.delete(Chapters.getIds(chapters), disableConfirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async markAsRead(mangaIds: number[], wasManuallyMarkedAsRead: boolean = false): Promise<void> {
|
static async markAsRead(
|
||||||
|
mangaIds: number[],
|
||||||
|
wasManuallyMarkedAsRead: boolean = false,
|
||||||
|
disableConfirmation?: boolean,
|
||||||
|
): Promise<void> {
|
||||||
const chapters = await Mangas.getChapterIdsWithState(mangaIds, { isRead: false });
|
const chapters = await Mangas.getChapterIdsWithState(mangaIds, { isRead: false });
|
||||||
return Chapters.markAsRead(chapters, wasManuallyMarkedAsRead, mangaIds.length === 1 ? mangaIds[0] : undefined);
|
return Chapters.markAsRead(
|
||||||
|
chapters,
|
||||||
|
wasManuallyMarkedAsRead,
|
||||||
|
mangaIds.length === 1 ? mangaIds[0] : undefined,
|
||||||
|
disableConfirmation,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async markAsUnread(mangaIds: number[]): Promise<void> {
|
static async markAsUnread(mangaIds: number[], disableConfirmation?: boolean): Promise<void> {
|
||||||
const chapters = await Mangas.getChapterIdsWithState(mangaIds, { isRead: true });
|
const chapters = await Mangas.getChapterIdsWithState(mangaIds, { isRead: true });
|
||||||
return Chapters.markAsUnread(Chapters.getIds(chapters));
|
return Chapters.markAsUnread(Chapters.getIds(chapters), disableConfirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeFromLibrary(mangaIds: number[]): Promise<void> {
|
static async removeFromLibrary(mangaIds: number[], disableConfirmation?: boolean): Promise<void> {
|
||||||
const { removeMangaFromCategories } = await getMetadataServerSettings();
|
const { removeMangaFromCategories } = await getMetadataServerSettings();
|
||||||
return Mangas.executeAction(
|
return Mangas.executeAction(
|
||||||
'remove_from_library',
|
'remove_from_library',
|
||||||
@@ -278,14 +291,20 @@ export class Mangas {
|
|||||||
updateMangas: { inLibrary: false },
|
updateMangas: { inLibrary: false },
|
||||||
updateMangasCategories: removeMangaFromCategories ? { clearCategories: true } : undefined,
|
updateMangasCategories: removeMangaFromCategories ? { clearCategories: true } : undefined,
|
||||||
}).response,
|
}).response,
|
||||||
|
disableConfirmation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async changeCategories(mangaIds: number[], patch: UpdateMangaCategoriesPatchInput): Promise<void> {
|
static async changeCategories(
|
||||||
|
mangaIds: number[],
|
||||||
|
patch: UpdateMangaCategoriesPatchInput,
|
||||||
|
disableConfirmation?: boolean,
|
||||||
|
): Promise<void> {
|
||||||
return Mangas.executeAction(
|
return Mangas.executeAction(
|
||||||
'change_categories',
|
'change_categories',
|
||||||
mangaIds.length,
|
mangaIds.length,
|
||||||
() => requestManager.updateMangasCategories(mangaIds, patch).response,
|
() => requestManager.updateMangasCategories(mangaIds, patch).response,
|
||||||
|
disableConfirmation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,88 +437,119 @@ export class Mangas {
|
|||||||
migrateTracking,
|
migrateTracking,
|
||||||
deleteChapters,
|
deleteChapters,
|
||||||
}: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
|
}: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
|
||||||
|
disableConfirmation?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return Mangas.executeAction('migrate', 1, async () => {
|
return Mangas.executeAction(
|
||||||
const [{ data: mangaToMigrateData }, { data: mangaToMigrateToData }, { removeMangaFromCategories }] =
|
'migrate',
|
||||||
await Promise.all([
|
1,
|
||||||
requestManager.getMangaToMigrate(mangaId, {
|
async () => {
|
||||||
migrateChapters,
|
const [{ data: mangaToMigrateData }, { data: mangaToMigrateToData }, { removeMangaFromCategories }] =
|
||||||
migrateCategories,
|
await Promise.all([
|
||||||
migrateTracking,
|
requestManager.getMangaToMigrate(mangaId, {
|
||||||
deleteChapters,
|
migrateChapters,
|
||||||
}).response,
|
migrateCategories,
|
||||||
requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, {
|
migrateTracking,
|
||||||
migrateChapters,
|
deleteChapters,
|
||||||
migrateCategories,
|
}).response,
|
||||||
migrateTracking,
|
requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, {
|
||||||
apolloOptions: { errorPolicy: 'all' },
|
migrateChapters,
|
||||||
}).response,
|
migrateCategories,
|
||||||
getMetadataServerSettings(),
|
migrateTracking,
|
||||||
]);
|
apolloOptions: { errorPolicy: 'all' },
|
||||||
|
}).response,
|
||||||
|
getMetadataServerSettings(),
|
||||||
|
]);
|
||||||
|
|
||||||
if (!mangaToMigrateData.manga || !mangaToMigrateToData?.fetchManga?.manga) {
|
if (!mangaToMigrateData.manga || !mangaToMigrateToData?.fetchManga?.manga) {
|
||||||
throw new Error('Mangas::migrate: missing manga data');
|
throw new Error('Mangas::migrate: missing manga data');
|
||||||
}
|
|
||||||
|
|
||||||
if (migrateChapters && !mangaToMigrateData.manga.chapters) {
|
|
||||||
throw new Error('Mangas::migrate: missing chapters data');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mangaToMigrateToData.fetchChapters?.chapters) {
|
|
||||||
mangaToMigrateToData.fetchChapters = { chapters: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
const performMigrationAction = async (
|
|
||||||
migrateAction: keyof MigrateFuncReturn,
|
|
||||||
...actions: [boolean | undefined, MigrateFuncReturn][]
|
|
||||||
) =>
|
|
||||||
Promise.all(
|
|
||||||
actions
|
|
||||||
.filter(([performAction]) => performAction)
|
|
||||||
.map(([, action]) => action[migrateAction]())
|
|
||||||
.flat(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const performMigrationActions = async (...actions: [boolean | undefined, MigrateFuncReturn][]) => {
|
|
||||||
const migrationActions: TupleUnion<keyof MigrateFuncReturn> = ['copy', 'cleanup'];
|
|
||||||
|
|
||||||
for (const migrationAction of migrationActions) {
|
|
||||||
// the migration actions (copy, cleanup) are supposed to be run sequentially to ensure that the cleanup
|
|
||||||
// only happens in case the copy succeeded
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await performMigrationAction(migrationAction, ...actions);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
await performMigrationActions(
|
if (migrateChapters && !mangaToMigrateData.manga.chapters) {
|
||||||
[
|
throw new Error('Mangas::migrate: missing chapters data');
|
||||||
migrateChapters,
|
}
|
||||||
Mangas.migrateChapters(mode, mangaToMigrateData.manga, mangaToMigrateToData, !!deleteChapters),
|
|
||||||
],
|
if (!mangaToMigrateToData.fetchChapters?.chapters) {
|
||||||
[
|
mangaToMigrateToData.fetchChapters = { chapters: [] };
|
||||||
migrateTracking,
|
}
|
||||||
Mangas.migrateTracking(mode, mangaToMigrateData.manga, mangaToMigrateToData.fetchManga.manga),
|
|
||||||
],
|
const performMigrationAction = async (
|
||||||
[
|
migrateAction: keyof MigrateFuncReturn,
|
||||||
true,
|
...actions: [boolean | undefined, MigrateFuncReturn][]
|
||||||
Mangas.migrateManga(
|
) =>
|
||||||
mode,
|
Promise.all(
|
||||||
mangaToMigrateData.manga,
|
actions
|
||||||
mangaToMigrateToData.fetchManga.manga,
|
.filter(([performAction]) => performAction)
|
||||||
!!migrateCategories,
|
.map(([, action]) => action[migrateAction]())
|
||||||
removeMangaFromCategories,
|
.flat(),
|
||||||
),
|
);
|
||||||
],
|
|
||||||
);
|
const performMigrationActions = async (...actions: [boolean | undefined, MigrateFuncReturn][]) => {
|
||||||
});
|
const migrationActions: TupleUnion<keyof MigrateFuncReturn> = ['copy', 'cleanup'];
|
||||||
|
|
||||||
|
for (const migrationAction of migrationActions) {
|
||||||
|
// the migration actions (copy, cleanup) are supposed to be run sequentially to ensure that the cleanup
|
||||||
|
// only happens in case the copy succeeded
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await performMigrationAction(migrationAction, ...actions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await performMigrationActions(
|
||||||
|
[
|
||||||
|
migrateChapters,
|
||||||
|
Mangas.migrateChapters(mode, mangaToMigrateData.manga, mangaToMigrateToData, !!deleteChapters),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
migrateTracking,
|
||||||
|
Mangas.migrateTracking(mode, mangaToMigrateData.manga, mangaToMigrateToData.fetchManga.manga),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
true,
|
||||||
|
Mangas.migrateManga(
|
||||||
|
mode,
|
||||||
|
mangaToMigrateData.manga,
|
||||||
|
mangaToMigrateToData.fetchManga.manga,
|
||||||
|
!!migrateCategories,
|
||||||
|
removeMangaFromCategories,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
disableConfirmation,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async executeAction(
|
private static async executeAction(
|
||||||
action: MangaAction,
|
action: MangaAction,
|
||||||
itemCount: number,
|
itemCount: number,
|
||||||
fnToExecute: () => Promise<unknown>,
|
fnToExecute: () => Promise<unknown>,
|
||||||
|
disableConfirmation?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { always, bulkAction, bulkActionCountForce } = MANGA_ACTION_TO_CONFIRMATION_REQUIRED[action];
|
||||||
|
const requiresConfirmation =
|
||||||
|
!disableConfirmation &&
|
||||||
|
(always || (bulkAction && itemCount > 1) || (bulkActionCountForce && itemCount >= bulkActionCountForce));
|
||||||
|
const confirmationMessage = MANGA_ACTION_TO_TRANSLATION[action].confirmation;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (requiresConfirmation) {
|
||||||
|
assertIsDefined(confirmationMessage);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await awaitConfirmation({
|
||||||
|
title: translate('global.label.are_you_sure'),
|
||||||
|
message: translate(confirmationMessage, { count: itemCount }),
|
||||||
|
actions: {
|
||||||
|
confirm: {
|
||||||
|
title: translate('global.button.ok'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await fnToExecute();
|
await fnToExecute();
|
||||||
makeToast(translate(MANGA_ACTION_TO_TRANSLATION[action].success, { count: itemCount }), 'success');
|
makeToast(translate(MANGA_ACTION_TO_TRANSLATION[action].success, { count: itemCount }), 'success');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -524,22 +574,28 @@ export class Mangas {
|
|||||||
size,
|
size,
|
||||||
...migrateOptions
|
...migrateOptions
|
||||||
}: PerformActionOptions<Action>,
|
}: PerformActionOptions<Action>,
|
||||||
|
disableConfirmation?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'download':
|
case 'download':
|
||||||
return Mangas.downloadChapters(mangaIds, { downloadAhead, onlyUnread, size });
|
return Mangas.downloadChapters(mangaIds, { downloadAhead, onlyUnread, size }, disableConfirmation);
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return Mangas.deleteChapters(mangaIds);
|
return Mangas.deleteChapters(mangaIds, disableConfirmation);
|
||||||
case 'mark_as_read':
|
case 'mark_as_read':
|
||||||
return Mangas.markAsRead(mangaIds, wasManuallyMarkedAsRead!);
|
return Mangas.markAsRead(mangaIds, wasManuallyMarkedAsRead!, disableConfirmation);
|
||||||
case 'mark_as_unread':
|
case 'mark_as_unread':
|
||||||
return Mangas.markAsUnread(mangaIds);
|
return Mangas.markAsUnread(mangaIds, disableConfirmation);
|
||||||
case 'remove_from_library':
|
case 'remove_from_library':
|
||||||
return Mangas.removeFromLibrary(mangaIds);
|
return Mangas.removeFromLibrary(mangaIds, disableConfirmation);
|
||||||
case 'change_categories':
|
case 'change_categories':
|
||||||
return Mangas.changeCategories(mangaIds, changeCategoriesPatch!);
|
return Mangas.changeCategories(mangaIds, changeCategoriesPatch!, disableConfirmation);
|
||||||
case 'migrate': {
|
case 'migrate': {
|
||||||
return Mangas.migrate(mangaIds[0], mangaIdToMigrateTo!, migrateOptions as unknown as MigrateOptions);
|
return Mangas.migrate(
|
||||||
|
mangaIds[0],
|
||||||
|
mangaIdToMigrateTo!,
|
||||||
|
migrateOptions as unknown as MigrateOptions,
|
||||||
|
disableConfirmation,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Mangas::performAction: unknown action "${action}"`);
|
throw new Error(`Mangas::performAction: unknown action "${action}"`);
|
||||||
|
|||||||
Reference in New Issue
Block a user