Support library batch selection migration

Closes #1089
This commit is contained in:
schroda
2026-04-23 15:18:35 +02:00
parent 172c522d01
commit 527769ec5e
17 changed files with 172 additions and 144 deletions

View File

@@ -15,7 +15,6 @@ import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import Label from '@mui/icons-material/Label'; import Label from '@mui/icons-material/Label';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import SyncAltIcon from '@mui/icons-material/SyncAlt'; import SyncAltIcon from '@mui/icons-material/SyncAlt';
import { Link } from 'react-router-dom';
import SyncIcon from '@mui/icons-material/Sync'; import SyncIcon from '@mui/icons-material/Sync';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
import { AwaitableComponent } from 'awaitable-component'; import { AwaitableComponent } from 'awaitable-component';
@@ -35,7 +34,6 @@ import { NestedMenuItem } from '@/base/components/menu/NestedMenuItem.tsx';
import type { MangaChapterStatFieldsFragment, MangaType } from '@/lib/graphql/generated/graphql.ts'; import type { MangaChapterStatFieldsFragment, MangaType } from '@/lib/graphql/generated/graphql.ts';
import type { MangaAction, MangaDownloadInfo, MangaIdInfo, MangaUnreadInfo } from '@/features/manga/Manga.types.ts'; import type { MangaAction, MangaDownloadInfo, MangaIdInfo, MangaUnreadInfo } from '@/features/manga/Manga.types.ts';
import { MANGA_ACTION_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts'; import { MANGA_ACTION_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { CategorySelect } from '@/features/category/components/CategorySelect.tsx'; import { CategorySelect } from '@/features/category/components/CategorySelect.tsx';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts'; import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
@@ -57,7 +55,7 @@ type Props =
export const MangaActionMenuItems = ({ export const MangaActionMenuItems = ({
manga, manga,
handleSelection, handleSelection,
selectedMangas: passedSelectedMangas, selectedMangas = STABLE_EMPTY_ARRAY,
onClose, onClose,
setHideMenu, setHideMenu,
}: Props) => { }: Props) => {
@@ -66,7 +64,6 @@ export const MangaActionMenuItems = ({
const [isTrackDialogOpen, setIsTrackDialogOpen] = useState(false); const [isTrackDialogOpen, setIsTrackDialogOpen] = useState(false);
const isSingleMode = !!manga; const isSingleMode = !!manga;
const selectedMangas = passedSelectedMangas ?? STABLE_EMPTY_ARRAY;
const getMenuItemTitle = createGetMenuItemTitle(isSingleMode, MANGA_ACTION_TO_TRANSLATION); const getMenuItemTitle = createGetMenuItemTitle(isSingleMode, MANGA_ACTION_TO_TRANSLATION);
const shouldShowMenuItem = createShouldShowMenuItem(isSingleMode); const shouldShowMenuItem = createShouldShowMenuItem(isSingleMode);
@@ -148,19 +145,11 @@ export const MangaActionMenuItems = ({
title={getMenuItemTitle('mark_as_unread', readMangas.length)} title={getMenuItemTitle('mark_as_unread', readMangas.length)}
/> />
)} )}
{isSingleMode && ( <MenuItem
<Link title={getMenuItemTitle('migrate', selectedMangas.length)}
to={AppRoutes.migrate.childRoutes.singleMangaSearch.path( Icon={SyncAltIcon}
manga?.sourceId ?? -1, onClick={() => performAction('migrate', selectedMangas)}
manga?.id ?? -1, />
manga?.title,
)}
state={{ mangaTitle: manga?.title }}
style={{ textDecoration: 'none', color: 'inherit' }}
>
<MenuItem Icon={SyncAltIcon} title={getMenuItemTitle('migrate', selectedMangas.length)} />
</Link>
)}
{isSingleMode && ( {isSingleMode && (
<MenuItem <MenuItem
onClick={() => { onClick={() => {
@@ -175,7 +164,7 @@ export const MangaActionMenuItems = ({
onClick={() => { onClick={() => {
AwaitableComponent.show(CategorySelect, { AwaitableComponent.show(CategorySelect, {
mangaId: manga?.id, mangaId: manga?.id,
mangaIds: passedSelectedMangas ? Mangas.getIds(selectedMangas) : undefined, mangaIds: selectedMangas ? Mangas.getIds(selectedMangas) : undefined,
addToLibrary: false, addToLibrary: false,
}); });
setHideMenu(true); setHideMenu(true);

View File

@@ -22,13 +22,15 @@ import { GridLayout } from '@/base/Base.types.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts'; import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts'; import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
import { makeToast } from '@/base/utils/Toast.ts'; import { makeToast } from '@/base/utils/Toast.ts';
import { Mangas } from '@/features/manga/services/Mangas.ts';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts'; import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
import { MigrationOptionsDialog } from '@/features/migration/components/MigrationOptionsDialog.tsx'; import { MigrationOptionsDialog } from '@/features/migration/components/MigrationOptionsDialog.tsx';
import { AwaitableComponent } from 'awaitable-component'; import { AwaitableComponent } from 'awaitable-component';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler';
import { MangaMigration } from '@/features/migration/MangaMigration.ts';
import { MANGA_ACTION_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const getMangaLinkTo = ( const getMangaLinkTo = (
mode: MangaCardMode, mode: MangaCardMode,
@@ -123,8 +125,15 @@ export const MangaCard = memo((props: MangaCardProps) => {
optionsDialog.update({ isMigrating: true }); optionsDialog.update({ isMigrating: true });
try { try {
await Mangas.migrate(migrationSourceMangaId, id, options); try {
await MangaMigration.migrate(migrationSourceMangaId, id, options);
} catch (e) {
makeToast(
t(MANGA_ACTION_TO_TRANSLATION['migrate'].error),
'error',
getErrorMessage(e),
);
}
optionsDialog.submit(options); optionsDialog.submit(options);
ReactRouter.navigate(AppRoutes.manga.path(id), { replace: true }); ReactRouter.navigate(AppRoutes.manga.path(id), { replace: true });

View File

@@ -22,8 +22,7 @@ import { Chapters } from '@/features/chapter/services/Chapters.ts';
import { makeToast } from '@/base/utils/Toast.ts'; import { makeToast } from '@/base/utils/Toast.ts';
import { getMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts'; import { getMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
import { GET_MANGAS_BASE } from '@/lib/graphql/manga/MangaQuery.ts'; import { GET_MANGAS_BASE } from '@/lib/graphql/manga/MangaQuery.ts';
import { MANGA_BASE_FIELDS } from '@/lib/graphql/manga/MangaFragments.ts'; import { MANGA_BASE_FIELDS, MANGA_MIGRATION_FIELDS } from '@/lib/graphql/manga/MangaFragments.ts';
import type { MigrateOptions } from '@/features/migration/Migration.types.ts';
import type { import type {
MangaAction, MangaAction,
MangaArtistInfo, MangaArtistInfo,
@@ -49,7 +48,10 @@ import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { assertIsDefined } from '@/base/Asserts.ts'; import { assertIsDefined } from '@/base/Asserts.ts';
import { Confirmation } from '@/base/AppAwaitableComponent.ts'; import { Confirmation } from '@/base/AppAwaitableComponent.ts';
import { UrlUtil } from '@/lib/UrlUtil.ts'; import { UrlUtil } from '@/lib/UrlUtil.ts';
import { MangaMigration } from '@/features/migration/MangaMigration.ts'; import { MigrationManager } from '@/features/migration/MigrationManager.ts';
import uniq from 'lodash/fp/uniq';
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
type DownloadChaptersOptions = { type DownloadChaptersOptions = {
size?: number; size?: number;
@@ -61,23 +63,18 @@ type ChangeCategoriesOptions = { changeCategoriesPatch: UpdateMangaCategoriesPat
type MarkAsReadActionOption = MarkAsReadOptions & type MarkAsReadActionOption = MarkAsReadOptions &
PropertiesNever<ChangeCategoriesOptions> & PropertiesNever<ChangeCategoriesOptions> &
PropertiesNever<MigrateOptions> &
PropertiesNever<DownloadChaptersOptions>; PropertiesNever<DownloadChaptersOptions>;
type ChangeCategoriesActionOption = PropertiesNever<MarkAsReadOptions> & type ChangeCategoriesActionOption = PropertiesNever<MarkAsReadOptions> &
ChangeCategoriesOptions & ChangeCategoriesOptions &
PropertiesNever<MigrateOptions> &
PropertiesNever<DownloadChaptersOptions>; PropertiesNever<DownloadChaptersOptions>;
type MigrateActionOption = PropertiesNever<MarkAsReadOptions> & type MigrateActionOption = PropertiesNever<MarkAsReadOptions> &
PropertiesNever<ChangeCategoriesOptions> & PropertiesNever<ChangeCategoriesOptions> &
MigrateOptions &
PropertiesNever<DownloadChaptersOptions>; PropertiesNever<DownloadChaptersOptions>;
type DownloadActionOption = PropertiesNever<MarkAsReadOptions> & type DownloadActionOption = PropertiesNever<MarkAsReadOptions> &
PropertiesNever<ChangeCategoriesOptions> & PropertiesNever<ChangeCategoriesOptions> &
PropertiesNever<MigrateOptions> &
DownloadChaptersOptions; DownloadChaptersOptions;
type DefaultActionOption = Partial<MarkAsReadOptions> & type DefaultActionOption = Partial<MarkAsReadOptions> &
Partial<ChangeCategoriesOptions> & Partial<ChangeCategoriesOptions> &
Partial<MigrateOptions> &
Partial<DownloadChaptersOptions>; Partial<DownloadChaptersOptions>;
type PerformActionOptions<Action extends MangaAction> = Action extends 'mark_as_read' type PerformActionOptions<Action extends MangaAction> = Action extends 'mark_as_read'
@@ -298,18 +295,22 @@ export class Mangas {
); );
} }
static async migrate( static async migrate(mangaIds: MangaIdInfo['id'][]): Promise<void> {
mangaId: MangaIdInfo['id'], const mangas = mangaIds.map((mangaId) => {
mangaIdToMigrateTo: number, const manga = Mangas.getFromCache(mangaId, MANGA_MIGRATION_FIELDS, 'MANGA_MIGRATION_FIELDS');
options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>, assertIsDefined(manga);
disableConfirmation?: boolean,
): Promise<void> { return manga;
return Mangas.executeAction( });
'migrate', const sourceIds = uniq(mangas.map((manga) => manga.sourceId));
1,
() => MangaMigration.migrate(mangaId, mangaIdToMigrateTo, options), MigrationManager.selectSources(sourceIds);
disableConfirmation, MigrationManager.selectMangas(mangas);
);
const isBulkMigration = mangas.length > 1;
if (isBulkMigration) {
ReactRouter.navigate(AppRoutes.migrate.path);
}
} }
private static async executeAction( private static async executeAction(
@@ -367,11 +368,9 @@ export class Mangas {
{ {
wasManuallyMarkedAsRead, wasManuallyMarkedAsRead,
changeCategoriesPatch, changeCategoriesPatch,
mangaIdToMigrateTo,
downloadAhead, downloadAhead,
onlyUnread, onlyUnread,
size, size,
...migrateOptions
}: PerformActionOptions<Action>, }: PerformActionOptions<Action>,
disableConfirmation?: boolean, disableConfirmation?: boolean,
): Promise<void> { ): Promise<void> {
@@ -389,12 +388,7 @@ export class Mangas {
case 'change_categories': case 'change_categories':
return Mangas.changeCategories(mangaIds, changeCategoriesPatch!, disableConfirmation); return Mangas.changeCategories(mangaIds, changeCategoriesPatch!, disableConfirmation);
case 'migrate': { case 'migrate': {
return Mangas.migrate( return Mangas.migrate(mangaIds);
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}"`);

View File

@@ -34,7 +34,7 @@ export const MAX_SOURCES_IN_PARALLEL = 6;
export const DEFAULT_MIGRATION_STATE: MigrationState = { export const DEFAULT_MIGRATION_STATE: MigrationState = {
phase: MigrationPhase.IDLE, phase: MigrationPhase.IDLE,
sourceId: null, sourceIds: null,
entries: {}, entries: {},
destinationSourceIds: [], destinationSourceIds: [],
migrateOptions: null, migrateOptions: null,

View File

@@ -111,7 +111,7 @@ export type MigrationProgress = { total: number; completed: number; success: num
export interface MigrationState { export interface MigrationState {
phase: MigrationPhase; phase: MigrationPhase;
sourceId: SourceIdInfo['id'] | null; sourceIds: SourceIdInfo['id'][] | null;
entries: Record<MangaIdInfo['id'], TMigrationEntry>; entries: Record<MangaIdInfo['id'], TMigrationEntry>;
destinationSourceIds: SourceIdInfo['id'][]; destinationSourceIds: SourceIdInfo['id'][];
migrateOptions: Omit<MigrateOptions, 'mangaIdToMigrateTo'> | null; migrateOptions: Omit<MigrateOptions, 'mangaIdToMigrateTo'> | null;
@@ -123,3 +123,7 @@ export interface MigrationState {
} }
export interface SourceItem extends SourceIdInfo, SourceNameInfo, SourceLanguageInfo, SourceIconInfo, SourceMetaInfo {} export interface SourceItem extends SourceIdInfo, SourceNameInfo, SourceLanguageInfo, SourceIconInfo, SourceMetaInfo {}
export type TMigratableSource = NonNullable<GetMigratableSourcesQuery['mangas']['nodes'][number]['source']> & {
mangaCount: number;
};

View File

@@ -14,11 +14,11 @@ import { devtools, persist } from 'zustand/middleware';
import { import {
type MigratableEntry, type MigratableEntry,
type MigrateOptions, type MigrateOptions,
type TMigrationEntry,
MigrationEntryStatus, MigrationEntryStatus,
MigrationPhase,
type MigrationMatch, type MigrationMatch,
MigrationPhase,
type MigrationState, type MigrationState,
type TMigrationEntry,
} from '@/features/migration/Migration.types.ts'; } from '@/features/migration/Migration.types.ts';
import { import {
DEFAULT_MIGRATION_STATE, DEFAULT_MIGRATION_STATE,
@@ -153,6 +153,14 @@ export class MigrationManager {
} }
} }
private static ensureIsInValidPhase(phases: MigrationPhase[]): void {
const { phase } = MigrationManager.getState();
if (!phases.includes(phase)) {
throw new Error(`Illegal migration phase "${phase}". Expected: ${phases}`);
}
}
static async goToPreviousPhase(): Promise<boolean> { static async goToPreviousPhase(): Promise<boolean> {
switch (MigrationManager.getState().phase) { switch (MigrationManager.getState().phase) {
case MigrationPhase.IDLE: case MigrationPhase.IDLE:
@@ -265,16 +273,34 @@ export class MigrationManager {
return updatedEntry; return updatedEntry;
} }
static selectSource(sourceId: SourceIdInfo['id']): void { static selectSources(sourceIds: SourceIdInfo['id'][]): void {
MigrationManager.ensureIsInValidPhase([MigrationPhase.IDLE]);
MigrationManager.updateState((draft) => { MigrationManager.updateState((draft) => {
draft.phase = MigrationPhase.SELECT_MANGAS; draft.phase = MigrationPhase.SELECT_MANGAS;
draft.sourceId = sourceId; draft.sourceIds = sourceIds;
draft.entries = {}; draft.entries = {};
}); });
ReactRouter.navigate(AppRoutes.migrate.path);
} }
static selectMangas(mangas: MangaMigrationFieldsFragment[]): void { static selectMangas(mangas: MangaMigrationFieldsFragment[]): void {
MigrationManager.ensureIsInValidPhase([MigrationPhase.SELECT_MANGAS]);
const isSingleManga = mangas.length === 1;
if (isSingleManga) {
const [manga] = mangas;
ReactRouter.navigate(
AppRoutes.migrate.childRoutes.singleMangaSearch.path(manga.sourceId, manga.id, manga.title),
{
state: { mangaTitle: manga.title },
},
);
MigrationManager.reset();
return;
}
MigrationManager.updateState((draft) => { MigrationManager.updateState((draft) => {
draft.phase = MigrationPhase.SELECTING_SOURCES; draft.phase = MigrationPhase.SELECTING_SOURCES;
draft.entries = Object.fromEntries( draft.entries = Object.fromEntries(
@@ -305,6 +331,8 @@ export class MigrationManager {
} }
static async startSearch(destinationSourceIds: SourceIdInfo['id'][]): Promise<void> { static async startSearch(destinationSourceIds: SourceIdInfo['id'][]): Promise<void> {
MigrationManager.ensureIsInValidPhase([MigrationPhase.SELECTING_SOURCES]);
MigrationManager.updateState((draft) => { MigrationManager.updateState((draft) => {
draft.destinationSourceIds = destinationSourceIds; draft.destinationSourceIds = destinationSourceIds;
}); });
@@ -369,6 +397,8 @@ export class MigrationManager {
} }
static async startMigration(options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>): Promise<void> { static async startMigration(options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>): Promise<void> {
MigrationManager.ensureIsInValidPhase([MigrationPhase.SEARCHING]);
const migratableEntries = MigrationManager.getMigratableEntries(); const migratableEntries = MigrationManager.getMigratableEntries();
await Confirmation.show({ await Confirmation.show({
@@ -551,25 +581,49 @@ export class MigrationManager {
return RESUMABLE_PHASES.includes(MigrationManager.getState().phase); return RESUMABLE_PHASES.includes(MigrationManager.getState().phase);
} }
private static getHigherPrioritySourceIds(sourceId: SourceIdInfo['id']): SourceIdInfo['id'][] { private static getDestinationSourceIds(mangaSourceId: SourceIdInfo['id']): MigrationState['destinationSourceIds'] {
const { destinationSourceIds } = MigrationManager.getState(); const { sourceIds, destinationSourceIds } = MigrationManager.getState();
const sourceIdPriority = destinationSourceIds.indexOf(sourceId); // Prevent (most likely) unintentionally matching a manga to itself.
const isMultiSourceMigration = (sourceIds?.length ?? -1) > 1;
if (isMultiSourceMigration) {
const mangaSourceIdIndex = destinationSourceIds.indexOf(mangaSourceId);
return [
...destinationSourceIds.slice(0, mangaSourceIdIndex),
...destinationSourceIds.slice(mangaSourceIdIndex + 1),
mangaSourceId,
];
}
return destinationSourceIds;
}
private static getHigherPrioritySourceIds(
mangaSourceId: SourceIdInfo['id'],
destSourceId: SourceIdInfo['id'],
): SourceIdInfo['id'][] {
const destinationSourceIds = MigrationManager.getDestinationSourceIds(mangaSourceId);
const sourceIdPriority = destinationSourceIds.indexOf(destSourceId);
return destinationSourceIds.slice(0, Math.max(0, sourceIdPriority - 1)); return destinationSourceIds.slice(0, Math.max(0, sourceIdPriority - 1));
} }
private static isHigherPrioritySourceUnsettled(mangaId: MangaIdInfo['id'], sourceId: SourceIdInfo['id']): boolean { private static isHigherPrioritySourceUnsettled(
mangaId: MangaIdInfo['id'],
destSourceId: SourceIdInfo['id'],
): boolean {
const { entries } = MigrationManager.getState(); const { entries } = MigrationManager.getState();
const entry = entries[mangaId]; const entry = entries[mangaId];
assertIsDefined(entry); assertIsDefined(entry);
return MigrationManager.getHigherPrioritySourceIds(sourceId).some( return MigrationManager.getHigherPrioritySourceIds(entry.sourceId, destSourceId).some(
(higherPrioritySourceId) => entry.destSourceIdToSearchState[higherPrioritySourceId] == null, (higherPrioritySourceId) => entry.destSourceIdToSearchState[higherPrioritySourceId] == null,
); );
} }
private static hasHigherSourcePriorityMatch(mangaId: MangaIdInfo['id'], sourceId: SourceIdInfo['id']): boolean { private static hasHigherSourcePriorityMatch(mangaId: MangaIdInfo['id'], destSourceId: SourceIdInfo['id']): boolean {
const { entries } = MigrationManager.getState(); const { entries } = MigrationManager.getState();
const entry = entries[mangaId]; const entry = entries[mangaId];
@@ -577,7 +631,7 @@ export class MigrationManager {
return false; return false;
} }
return MigrationManager.getHigherPrioritySourceIds(sourceId).some( return MigrationManager.getHigherPrioritySourceIds(entry.sourceId, destSourceId).some(
(higherPrioritySourceId) => entry.destSourceIdToSearchState[higherPrioritySourceId], (higherPrioritySourceId) => entry.destSourceIdToSearchState[higherPrioritySourceId],
); );
} }
@@ -658,7 +712,7 @@ export class MigrationManager {
}); });
try { try {
const searchPromises = state.destinationSourceIds.map((destSourceId) => const searchPromises = MigrationManager.getDestinationSourceIds(entry.sourceId).map((destSourceId) =>
MigrationManager.getParallelSourceQueue()(async () => { MigrationManager.getParallelSourceQueue()(async () => {
if (signal.aborted) { if (signal.aborted) {
return null; return null;
@@ -893,8 +947,8 @@ export class MigrationManager {
return useMigrationStore((state) => state.phase); return useMigrationStore((state) => state.phase);
} }
static useSourceId(): SourceIdInfo['id'] | null { static useSourceIds(): MigrationState['sourceIds'] {
return useMigrationStore((state) => state.sourceId); return useMigrationStore((state) => state.sourceIds);
} }
static useEntries(): Record<number, TMigrationEntry> { static useEntries(): Record<number, TMigrationEntry> {

View File

@@ -13,26 +13,29 @@ import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import type { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
import { translateExtensionLanguage } from '@/features/extension/Extensions.utils.ts'; import { translateExtensionLanguage } from '@/features/extension/Extensions.utils.ts';
import { MigrationManager } from '@/features/migration/MigrationManager.ts'; import { MigrationManager } from '@/features/migration/MigrationManager.ts';
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx'; import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx'; import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx';
import { Sources } from '@/features/source/services/Sources';
import type { TMigratableSource } from '@/features/migration/Migration.types.ts';
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
export type TMigratableSource = NonNullable<GetMigratableSourcesQuery['mangas']['nodes'][number]['source']> & { export const MigrationCard = (source: TMigratableSource) => {
mangaCount: number; const { id, name, lang, iconUrl, mangaCount } = source;
};
// TODO - cleanup source/extension components
export const MigrationCard = ({ id, name, lang, iconUrl, mangaCount }: TMigratableSource) => {
const { t } = useLingui(); const { t } = useLingui();
const isLocalSource = Number(id) === 0; const sourceName = Sources.isLocalSource(source) ? t`Local source` : name;
const sourceName = isLocalSource ? t`Local source` : name;
return ( return (
<Card> <Card>
<CardActionArea onClick={() => MigrationManager.selectSource(id)}> <CardActionArea
onClick={() => {
MigrationManager.selectSources([id]);
ReactRouter.navigate(AppRoutes.migrate.path);
}}
>
<ListCardContent sx={{ justifyContent: 'space-between' }}> <ListCardContent sx={{ justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', gap: 1 }}> <Box sx={{ display: 'flex', gap: 1 }}>
<ListCardAvatar <ListCardAvatar

View File

@@ -20,7 +20,7 @@ import { DndSortableItem } from '@/lib/dnd-kit/DndSortableItem.tsx';
import { DndKitUtil } from '@/lib/dnd-kit/DndKitUtil.ts'; import { DndKitUtil } from '@/lib/dnd-kit/DndKitUtil.ts';
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx'; import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx'; import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx';
import type { SourceItem } from '@/features/migration/Migration.types.ts'; import type { MigrationState, SourceItem } from '@/features/migration/Migration.types.ts';
import type { SourceIdInfo } from '@/features/source/Source.types.ts'; import type { SourceIdInfo } from '@/features/source/Source.types.ts';
import type { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts'; import type { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts';
import { assertIsDefined } from '@/base/Asserts.ts'; import { assertIsDefined } from '@/base/Asserts.ts';
@@ -91,13 +91,13 @@ export const MigrationSourceList = ({
handleSelection, handleSelection,
selectedSourceIds, selectedSourceIds,
handlePriorityChange, handlePriorityChange,
currentSourceId, currentSourceIds,
}: { }: {
sources: SourceItem[]; sources: SourceItem[];
handleSelection: SelectableCollectionReturnType<SourceIdInfo['id'], 'default'>['handleSelection']; handleSelection: SelectableCollectionReturnType<SourceIdInfo['id'], 'default'>['handleSelection'];
handlePriorityChange: (oldIndex: number, newIndex: number) => void; handlePriorityChange: (oldIndex: number, newIndex: number) => void;
selectedSourceIds: SourceIdInfo['id'][]; selectedSourceIds: SourceIdInfo['id'][];
currentSourceId: SourceIdInfo['id'] | null; currentSourceIds: MigrationState['sourceIds'];
}) => { }) => {
const { t } = useLingui(); const { t } = useLingui();
const dndSensors = DndKitUtil.useSensorsForDevice(); const dndSensors = DndKitUtil.useSensorsForDevice();
@@ -207,7 +207,7 @@ export const MigrationSourceList = ({
<SourceCard <SourceCard
source={source} source={source}
onToggle={handleToggle} onToggle={handleToggle}
isCurrentSource={source.id === currentSourceId} isCurrentSource={!!currentSourceIds?.includes(source.id)}
isSelected={isSelected} isSelected={isSelected}
/> />
); );
@@ -232,7 +232,7 @@ export const MigrationSourceList = ({
<SourceCard <SourceCard
source={dndActiveSource!} source={dndActiveSource!}
onToggle={noOp} onToggle={noOp}
isCurrentSource={dndActiveSource?.id === currentSourceId} isCurrentSource={!!dndActiveSource && !!currentSourceIds?.includes(dndActiveSource?.id)}
isSelected isSelected
isDragging isDragging
/> />

View File

@@ -68,7 +68,7 @@ export const MigrationExecute = () => {
})} })}
entries={migratingEntries} entries={migratingEntries}
isMigrating isMigrating
color="error" color="info"
/> />
<MigrationEntryGroup <MigrationEntryGroup
status={MigrationEntryStatus.MIGRATION_FAILED} status={MigrationEntryStatus.MIGRATION_FAILED}

View File

@@ -71,7 +71,7 @@ export const MigrationSearch = () => {
other: '# searching', other: '# searching',
})} })}
entries={searchingEntries} entries={searchingEntries}
color="error" color="info"
/> />
<MigrationEntryGroup <MigrationEntryGroup
status={MigrationEntryStatus.SEARCH_FAILED} status={MigrationEntryStatus.SEARCH_FAILED}

View File

@@ -31,7 +31,7 @@ import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
export const MigrationSelectDestinationSources = () => { export const MigrationSelectDestinationSources = () => {
const { t } = useLingui(); const { t } = useLingui();
const currentSourceId = MigrationManager.useSourceId(); const currentSourceIds = MigrationManager.useSourceIds();
const { const {
settings: { browseLanguages, showNsfw }, settings: { browseLanguages, showNsfw },
@@ -156,7 +156,7 @@ export const MigrationSelectDestinationSources = () => {
selectedSourceIds={selectedItemIds} selectedSourceIds={selectedItemIds}
handleSelection={handleSelection} handleSelection={handleSelection}
handlePriorityChange={handlePriorityChange} handlePriorityChange={handlePriorityChange}
currentSourceId={currentSourceId} currentSourceIds={currentSourceIds}
/> />
<Fab <Fab
variant="extended" variant="extended"

View File

@@ -26,8 +26,6 @@ import { SelectableCollectionSelectMode } from '@/base/collection/components/Sel
import { Mangas } from '@/features/manga/services/Mangas.ts'; import { Mangas } from '@/features/manga/services/Mangas.ts';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts'; import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
import { MigrationContinueButton } from '@/features/migration/components/MigrationContinueButton.tsx'; import { MigrationContinueButton } from '@/features/migration/components/MigrationContinueButton.tsx';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
const getSourceError = (error: unknown): unknown => { const getSourceError = (error: unknown): unknown => {
const message = getErrorMessage(error); const message = getErrorMessage(error);
@@ -45,9 +43,11 @@ const getSourceError = (error: unknown): unknown => {
export const MigrationSelectMangas = () => { export const MigrationSelectMangas = () => {
const { t } = useLingui(); const { t } = useLingui();
const sourceId = MigrationManager.useSourceId(); const sourceIds = MigrationManager.useSourceIds();
const selectedMangas = MigrationManager.useEntries(); const selectedMangas = MigrationManager.useEntries();
const sourceId = sourceIds?.[0];
const [gridLayout, setGridLayout] = useLocalStorage('migrateGridLayout', GridLayout.List); const [gridLayout, setGridLayout] = useLocalStorage('migrateGridLayout', GridLayout.List);
const { const {
@@ -58,7 +58,7 @@ export const MigrationSelectMangas = () => {
} = requestManager.useGetSource<GetSourceMigratableQuery, GetSourceMigratableQueryVariables>( } = requestManager.useGetSource<GetSourceMigratableQuery, GetSourceMigratableQueryVariables>(
GET_SOURCE_MIGRATABLE, GET_SOURCE_MIGRATABLE,
sourceId ?? '', sourceId ?? '',
{ skip: !sourceId, notifyOnNetworkStatusChange: true }, { skip: !sourceIds, notifyOnNetworkStatusChange: true },
); );
const { const {
@@ -67,7 +67,7 @@ export const MigrationSelectMangas = () => {
error: mangasError, error: mangasError,
refetch: refetchMangas, refetch: refetchMangas,
} = requestManager.useGetMigratableSourceMangas(sourceId!, { } = requestManager.useGetMigratableSourceMangas(sourceId!, {
skip: !sourceId, skip: !sourceIds,
notifyOnNetworkStatusChange: true, notifyOnNetworkStatusChange: true,
}); });
@@ -86,7 +86,7 @@ export const MigrationSelectMangas = () => {
), ),
}); });
const sourceName = migratableSourceData?.source?.displayName ?? sourceId ?? t`Migrate`; const sourceName = migratableSourceData?.source?.displayName ?? sourceIds ?? t`Migrate`;
useAppTitleAndAction( useAppTitleAndAction(
sourceName, sourceName,
<> <>
@@ -108,22 +108,6 @@ export const MigrationSelectMangas = () => {
const handleContinue = () => { const handleContinue = () => {
const selected = mangas.filter((manga) => selectedItemIds.includes(manga.id)); const selected = mangas.filter((manga) => selectedItemIds.includes(manga.id));
const [entry] = selected;
const isSingleManga = selected.length === 1;
if (isSingleManga && entry) {
ReactRouter.navigate(
AppRoutes.migrate.childRoutes.singleMangaSearch.path(entry.sourceId, entry.id, entry.title),
{
state: { mangaTitle: entry.title },
},
);
MigrationManager.reset();
return;
}
MigrationManager.selectMangas(selected); MigrationManager.selectMangas(selected);
}; };

View File

@@ -19,11 +19,14 @@ import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx'; import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx'; import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
import type { TMigratableSource } from '@/features/migration/components/MigrationCard.tsx';
import { MigrationCard } from '@/features/migration/components/MigrationCard.tsx'; import { MigrationCard } from '@/features/migration/components/MigrationCard.tsx';
import { StyledGroupItemWrapper } from '@/base/components/virtuoso/StyledGroupItemWrapper.tsx'; import { StyledGroupItemWrapper } from '@/base/components/virtuoso/StyledGroupItemWrapper.tsx';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import type { SortSettings, TMigratableSourcesResult } from '@/features/migration/Migration.types.ts'; import type {
SortSettings,
TMigratableSource,
TMigratableSourcesResult,
} from '@/features/migration/Migration.types.ts';
import { SortBy, SortOrder } from '@/features/migration/Migration.types.ts'; import { SortBy, SortOrder } from '@/features/migration/Migration.types.ts';
import { sortByToTranslation, sortOrderToTranslation } from '@/features/migration/Migration.constants.ts'; import { sortByToTranslation, sortOrderToTranslation } from '@/features/migration/Migration.constants.ts';
import { import {

View File

@@ -3599,7 +3599,7 @@ export type GetCategoryMangasQueryVariables = Exact<{
}>; }>;
export type GetCategoryMangasQuery = { __typename?: 'Query', category: { __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } } }; export type GetCategoryMangasQuery = { __typename?: 'Query', category: { __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } } };
export type ChapterMetaFieldsFragment = { __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }; export type ChapterMetaFieldsFragment = { __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string };
@@ -3916,13 +3916,13 @@ export type MangaChapterStatFieldsFragment = { __typename?: 'MangaType', id: num
export type MangaChapterNodeFieldsFragment = { __typename?: 'MangaType', firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }; export type MangaChapterNodeFieldsFragment = { __typename?: 'MangaType', firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null };
export type MangaReaderFieldsFragment = { __typename?: 'MangaType', genre: Array<string>, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string } | null, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number } }; export type MangaReaderFieldsFragment = { __typename?: 'MangaType', genre: Array<string>, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number } };
export type MangaLibraryFieldsFragment = { __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }; export type MangaLibraryFieldsFragment = { __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null };
export type MangaMigrationFieldsFragment = { __typename?: 'MangaType', artist?: string | null, author?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }; export type MangaMigrationFieldsFragment = { __typename?: 'MangaType', artist?: string | null, author?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null };
export type MangaScreenFieldsFragment = { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } }; export type MangaScreenFieldsFragment = { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } };
export type MangaLibraryDuplicateScreenFieldsFragment = { __typename?: 'MangaType', description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number } }; export type MangaLibraryDuplicateScreenFieldsFragment = { __typename?: 'MangaType', description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number } };
@@ -3931,7 +3931,7 @@ export type GetMangaFetchMutationVariables = Exact<{
}>; }>;
export type GetMangaFetchMutation = { __typename?: 'Mutation', fetchManga?: { __typename?: 'FetchMangaPayload', manga: { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } } } | null }; export type GetMangaFetchMutation = { __typename?: 'Mutation', fetchManga?: { __typename?: 'FetchMangaPayload', manga: { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } } } | null };
export type GetMangaToMigrateToFetchMutationVariables = Exact<{ export type GetMangaToMigrateToFetchMutationVariables = Exact<{
id: Scalars['Int']['input']; id: Scalars['Int']['input'];
@@ -3994,14 +3994,14 @@ export type GetMangaScreenQueryVariables = Exact<{
}>; }>;
export type GetMangaScreenQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } } }; export type GetMangaScreenQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } } };
export type GetMangaReaderQueryVariables = Exact<{ export type GetMangaReaderQueryVariables = Exact<{
id: Scalars['Int']['input']; id: Scalars['Int']['input'];
}>; }>;
export type GetMangaReaderQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', genre: Array<string>, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string } | null, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number } } }; export type GetMangaReaderQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', genre: Array<string>, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number } } };
export type GetMangaTrackRecordsQueryVariables = Exact<{ export type GetMangaTrackRecordsQueryVariables = Exact<{
id: Scalars['Int']['input']; id: Scalars['Int']['input'];
@@ -4054,7 +4054,7 @@ export type GetMangasLibraryQueryVariables = Exact<{
}>; }>;
export type GetMangasLibraryQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } }; export type GetMangasLibraryQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } };
export type GetMangasDuplicatesQueryVariables = Exact<{ export type GetMangasDuplicatesQueryVariables = Exact<{
after?: InputMaybe<Scalars['Cursor']['input']>; after?: InputMaybe<Scalars['Cursor']['input']>;
@@ -4185,13 +4185,11 @@ export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typen
export type SourceMetaFieldsFragment = { __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }; export type SourceMetaFieldsFragment = { __typename?: 'SourceMetaType', sourceId: string, key: string, value: string };
export type SourceBaseFieldsFragment = { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string }; export type SourceBaseFieldsFragment = { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string };
export type SourceMigratableFieldsFragment = { __typename?: 'SourceType', lang: string, iconUrl: string, id: string, name: string, displayName: string };
export type SourceListFieldsFragment = { __typename?: 'SourceType', lang: string, iconUrl: string, isNsfw: boolean, isConfigurable: boolean, supportsLatest: boolean, id: string, name: string, displayName: string, meta: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }>, extension: { __typename?: 'ExtensionType', pkgName: string, repo?: string | null } }; export type SourceListFieldsFragment = { __typename?: 'SourceType', lang: string, iconUrl: string, isNsfw: boolean, isConfigurable: boolean, supportsLatest: boolean, id: string, name: string, displayName: string, meta: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }>, extension: { __typename?: 'ExtensionType', pkgName: string, repo?: string | null } };
export type SourceBrowseFieldsFragment = { __typename?: 'SourceType', baseUrl?: string | null, isConfigurable: boolean, supportsLatest: boolean, id: string, name: string, displayName: string, lang: string, meta: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }>, filters: Array< export type SourceBrowseFieldsFragment = { __typename?: 'SourceType', baseUrl?: string | null, isConfigurable: boolean, supportsLatest: boolean, id: string, name: string, displayName: string, lang: string, iconUrl: string, meta: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }>, filters: Array<
| { __typename?: 'CheckBoxFilter', name: string, type: 'CheckBoxFilter', CheckBoxFilterDefault: boolean } | { __typename?: 'CheckBoxFilter', name: string, type: 'CheckBoxFilter', CheckBoxFilterDefault: boolean }
| { __typename?: 'GroupFilter', name: string, type: 'GroupFilter', filters: Array< | { __typename?: 'GroupFilter', name: string, type: 'GroupFilter', filters: Array<
| { __typename?: 'CheckBoxFilter', name: string, type: 'CheckBoxFilter', CheckBoxFilterDefault: boolean } | { __typename?: 'CheckBoxFilter', name: string, type: 'CheckBoxFilter', CheckBoxFilterDefault: boolean }
@@ -4211,7 +4209,7 @@ export type SourceBrowseFieldsFragment = { __typename?: 'SourceType', baseUrl?:
| { __typename?: 'TriStateFilter', name: string, type: 'TriStateFilter', TriStateFilterDefault: TriState } | { __typename?: 'TriStateFilter', name: string, type: 'TriStateFilter', TriStateFilterDefault: TriState }
> }; > };
export type SourceSettingFieldsFragment = { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, preferences: Array< export type SourceSettingFieldsFragment = { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string, preferences: Array<
| { __typename?: 'CheckBoxPreference', summary?: string | null, key?: string | null, type: 'CheckBoxPreference', CheckBoxCheckBoxCurrentValue?: boolean | null, CheckBoxDefault: boolean, CheckBoxTitle?: string | null } | { __typename?: 'CheckBoxPreference', summary?: string | null, key?: string | null, type: 'CheckBoxPreference', CheckBoxCheckBoxCurrentValue?: boolean | null, CheckBoxDefault: boolean, CheckBoxTitle?: string | null }
| { __typename?: 'EditTextPreference', text?: string | null, summary?: string | null, key?: string | null, dialogTitle?: string | null, dialogMessage?: string | null, type: 'EditTextPreference', EditTextPreferenceCurrentValue?: string | null, EditTextPreferenceDefault?: string | null, EditTextPreferenceTitle?: string | null } | { __typename?: 'EditTextPreference', text?: string | null, summary?: string | null, key?: string | null, dialogTitle?: string | null, dialogMessage?: string | null, type: 'EditTextPreference', EditTextPreferenceCurrentValue?: string | null, EditTextPreferenceDefault?: string | null, EditTextPreferenceTitle?: string | null }
| { __typename?: 'ListPreference', summary?: string | null, key?: string | null, entryValues: Array<string>, entries: Array<string>, type: 'ListPreference', ListPreferenceCurrentValue?: string | null, ListPreferenceDefault?: string | null, ListPreferenceTitle?: string | null } | { __typename?: 'ListPreference', summary?: string | null, key?: string | null, entryValues: Array<string>, entries: Array<string>, type: 'ListPreference', ListPreferenceCurrentValue?: string | null, ListPreferenceDefault?: string | null, ListPreferenceTitle?: string | null }
@@ -4238,7 +4236,7 @@ export type UpdateSourcePreferencesMutationVariables = Exact<{
}>; }>;
export type UpdateSourcePreferencesMutation = { __typename?: 'Mutation', updateSourcePreference?: { __typename?: 'UpdateSourcePreferencePayload', source: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, preferences: Array< export type UpdateSourcePreferencesMutation = { __typename?: 'Mutation', updateSourcePreference?: { __typename?: 'UpdateSourcePreferencePayload', source: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string, preferences: Array<
| { __typename?: 'CheckBoxPreference', summary?: string | null, key?: string | null, type: 'CheckBoxPreference', CheckBoxCheckBoxCurrentValue?: boolean | null, CheckBoxDefault: boolean, CheckBoxTitle?: string | null } | { __typename?: 'CheckBoxPreference', summary?: string | null, key?: string | null, type: 'CheckBoxPreference', CheckBoxCheckBoxCurrentValue?: boolean | null, CheckBoxDefault: boolean, CheckBoxTitle?: string | null }
| { __typename?: 'EditTextPreference', text?: string | null, summary?: string | null, key?: string | null, dialogTitle?: string | null, dialogMessage?: string | null, type: 'EditTextPreference', EditTextPreferenceCurrentValue?: string | null, EditTextPreferenceDefault?: string | null, EditTextPreferenceTitle?: string | null } | { __typename?: 'EditTextPreference', text?: string | null, summary?: string | null, key?: string | null, dialogTitle?: string | null, dialogMessage?: string | null, type: 'EditTextPreference', EditTextPreferenceCurrentValue?: string | null, EditTextPreferenceDefault?: string | null, EditTextPreferenceTitle?: string | null }
| { __typename?: 'ListPreference', summary?: string | null, key?: string | null, entryValues: Array<string>, entries: Array<string>, type: 'ListPreference', ListPreferenceCurrentValue?: string | null, ListPreferenceDefault?: string | null, ListPreferenceTitle?: string | null } | { __typename?: 'ListPreference', summary?: string | null, key?: string | null, entryValues: Array<string>, entries: Array<string>, type: 'ListPreference', ListPreferenceCurrentValue?: string | null, ListPreferenceDefault?: string | null, ListPreferenceTitle?: string | null }
@@ -4265,7 +4263,7 @@ export type GetSourceBrowseQueryVariables = Exact<{
}>; }>;
export type GetSourceBrowseQuery = { __typename?: 'Query', source: { __typename?: 'SourceType', baseUrl?: string | null, isConfigurable: boolean, supportsLatest: boolean, id: string, name: string, displayName: string, lang: string, meta: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }>, filters: Array< export type GetSourceBrowseQuery = { __typename?: 'Query', source: { __typename?: 'SourceType', baseUrl?: string | null, isConfigurable: boolean, supportsLatest: boolean, id: string, name: string, displayName: string, lang: string, iconUrl: string, meta: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }>, filters: Array<
| { __typename?: 'CheckBoxFilter', name: string, type: 'CheckBoxFilter', CheckBoxFilterDefault: boolean } | { __typename?: 'CheckBoxFilter', name: string, type: 'CheckBoxFilter', CheckBoxFilterDefault: boolean }
| { __typename?: 'GroupFilter', name: string, type: 'GroupFilter', filters: Array< | { __typename?: 'GroupFilter', name: string, type: 'GroupFilter', filters: Array<
| { __typename?: 'CheckBoxFilter', name: string, type: 'CheckBoxFilter', CheckBoxFilterDefault: boolean } | { __typename?: 'CheckBoxFilter', name: string, type: 'CheckBoxFilter', CheckBoxFilterDefault: boolean }
@@ -4290,7 +4288,7 @@ export type GetSourceSettingsQueryVariables = Exact<{
}>; }>;
export type GetSourceSettingsQuery = { __typename?: 'Query', source: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, preferences: Array< export type GetSourceSettingsQuery = { __typename?: 'Query', source: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string, preferences: Array<
| { __typename?: 'CheckBoxPreference', summary?: string | null, key?: string | null, type: 'CheckBoxPreference', CheckBoxCheckBoxCurrentValue?: boolean | null, CheckBoxDefault: boolean, CheckBoxTitle?: string | null } | { __typename?: 'CheckBoxPreference', summary?: string | null, key?: string | null, type: 'CheckBoxPreference', CheckBoxCheckBoxCurrentValue?: boolean | null, CheckBoxDefault: boolean, CheckBoxTitle?: string | null }
| { __typename?: 'EditTextPreference', text?: string | null, summary?: string | null, key?: string | null, dialogTitle?: string | null, dialogMessage?: string | null, type: 'EditTextPreference', EditTextPreferenceCurrentValue?: string | null, EditTextPreferenceDefault?: string | null, EditTextPreferenceTitle?: string | null } | { __typename?: 'EditTextPreference', text?: string | null, summary?: string | null, key?: string | null, dialogTitle?: string | null, dialogMessage?: string | null, type: 'EditTextPreference', EditTextPreferenceCurrentValue?: string | null, EditTextPreferenceDefault?: string | null, EditTextPreferenceTitle?: string | null }
| { __typename?: 'ListPreference', summary?: string | null, key?: string | null, entryValues: Array<string>, entries: Array<string>, type: 'ListPreference', ListPreferenceCurrentValue?: string | null, ListPreferenceDefault?: string | null, ListPreferenceTitle?: string | null } | { __typename?: 'ListPreference', summary?: string | null, key?: string | null, entryValues: Array<string>, entries: Array<string>, type: 'ListPreference', ListPreferenceCurrentValue?: string | null, ListPreferenceDefault?: string | null, ListPreferenceTitle?: string | null }
@@ -4303,7 +4301,7 @@ export type GetSourceMigratableQueryVariables = Exact<{
}>; }>;
export type GetSourceMigratableQuery = { __typename?: 'Query', source: { __typename?: 'SourceType', lang: string, iconUrl: string, id: string, name: string, displayName: string } }; export type GetSourceMigratableQuery = { __typename?: 'Query', source: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } };
export type GetSourcesListQueryVariables = Exact<{ [key: string]: never; }>; export type GetSourcesListQueryVariables = Exact<{ [key: string]: never; }>;
@@ -4313,7 +4311,7 @@ export type GetSourcesListQuery = { __typename?: 'Query', sources: { __typename?
export type GetMigratableSourcesQueryVariables = Exact<{ [key: string]: never; }>; export type GetMigratableSourcesQueryVariables = Exact<{ [key: string]: never; }>;
export type GetMigratableSourcesQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', nodes: Array<{ __typename?: 'MangaType', sourceId: string, source?: { __typename?: 'SourceType', lang: string, iconUrl: string, id: string, name: string, displayName: string } | null }> } }; export type GetMigratableSourcesQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', nodes: Array<{ __typename?: 'MangaType', sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, iconUrl: string } | null }> } };
export type TrackerBaseFieldsFragment = { __typename?: 'TrackerType', id: number, name: string, icon: string, isLoggedIn: boolean, isTokenExpired: boolean }; export type TrackerBaseFieldsFragment = { __typename?: 'TrackerType', id: number, name: string, icon: string, isLoggedIn: boolean, isTokenExpired: boolean };

View File

@@ -112,6 +112,7 @@ export const MANGA_LIBRARY_FIELDS = gql`
${MANGA_CHAPTER_STAT_FIELDS} ${MANGA_CHAPTER_STAT_FIELDS}
${MANGA_CHAPTER_NODE_FIELDS} ${MANGA_CHAPTER_NODE_FIELDS}
${MANGA_META_FIELDS} ${MANGA_META_FIELDS}
${SOURCE_BASE_FIELDS}
fragment MANGA_LIBRARY_FIELDS on MangaType { fragment MANGA_LIBRARY_FIELDS on MangaType {
...MANGA_BASE_FIELDS ...MANGA_BASE_FIELDS
@@ -132,8 +133,7 @@ export const MANGA_LIBRARY_FIELDS = gql`
} }
source { source {
id ...SOURCE_BASE_FIELDS
displayName
} }
trackRecords { trackRecords {

View File

@@ -22,16 +22,6 @@ export const SOURCE_BASE_FIELDS = gql`
name name
displayName displayName
lang lang
}
`;
export const SOURCE_MIGRATABLE_FIELDS = gql`
${SOURCE_BASE_FIELDS}
fragment SOURCE_MIGRATABLE_FIELDS on SourceType {
...SOURCE_BASE_FIELDS
lang
iconUrl iconUrl
} }
`; `;

View File

@@ -8,9 +8,9 @@
import gql from 'graphql-tag'; import gql from 'graphql-tag';
import { import {
SOURCE_BASE_FIELDS,
SOURCE_BROWSE_FIELDS, SOURCE_BROWSE_FIELDS,
SOURCE_LIST_FIELDS, SOURCE_LIST_FIELDS,
SOURCE_MIGRATABLE_FIELDS,
SOURCE_SETTING_FIELDS, SOURCE_SETTING_FIELDS,
} from '@/lib/graphql/source/SourceFragments.ts'; } from '@/lib/graphql/source/SourceFragments.ts';
@@ -35,11 +35,11 @@ export const GET_SOURCE_SETTINGS = gql`
`; `;
export const GET_SOURCE_MIGRATABLE = gql` export const GET_SOURCE_MIGRATABLE = gql`
${SOURCE_MIGRATABLE_FIELDS} ${SOURCE_BASE_FIELDS}
query GET_SOURCE_MIGRATABLE($id: LongString!) { query GET_SOURCE_MIGRATABLE($id: LongString!) {
source(id: $id) { source(id: $id) {
...SOURCE_MIGRATABLE_FIELDS ...SOURCE_BASE_FIELDS
} }
} }
`; `;
@@ -57,14 +57,14 @@ export const GET_SOURCES_LIST = gql`
`; `;
export const GET_MIGRATABLE_SOURCES = gql` export const GET_MIGRATABLE_SOURCES = gql`
${SOURCE_MIGRATABLE_FIELDS} ${SOURCE_BASE_FIELDS}
query GET_MIGRATABLE_SOURCES { query GET_MIGRATABLE_SOURCES {
mangas(condition: { inLibrary: true }) { mangas(condition: { inLibrary: true }) {
nodes { nodes {
sourceId sourceId
source { source {
...SOURCE_MIGRATABLE_FIELDS ...SOURCE_BASE_FIELDS
} }
} }
} }