Add option to abort migration entry
This commit is contained in:
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
- (**Migration**) Add a search option to ignore outdated matches
|
- (**Migration**) Add a search option to ignore outdated matches
|
||||||
- (**Migration**) Add a search option to ignore matches with missing chapters
|
- (**Migration**) Add a search option to ignore matches with missing chapters
|
||||||
- (**Migration**) Add "local source" as a possible destination source
|
- (**Migration**) Add "local source" as a possible destination source
|
||||||
|
- (**Migration**) Add option to abort entries that are searching or are waiting to get migrated
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
import type { MessageDescriptor } from '@lingui/core';
|
import type { MessageDescriptor } from '@lingui/core';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { MigrationEntryStatus, MigrationPhase, SortBy, SortOrder } from '@/features/migration/Migration.types.ts';
|
|
||||||
import type { MigrationState } from '@/features/migration/Migration.types.ts';
|
import type { MigrationState } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MigrationEntryStatus, MigrationPhase, SortBy, SortOrder } from '@/features/migration/Migration.types.ts';
|
||||||
|
|
||||||
export const sortByToTranslation: Record<SortBy, MessageDescriptor> = {
|
export const sortByToTranslation: Record<SortBy, MessageDescriptor> = {
|
||||||
[SortBy.SOURCE_NAME]: msg`By source name`,
|
[SortBy.SOURCE_NAME]: msg`By source name`,
|
||||||
@@ -48,21 +48,24 @@ export const DEFAULT_MIGRATION_STATE: MigrationState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ENTRY_STATUS_TRANSLATION: Record<MigrationEntryStatus, MessageDescriptor> = {
|
export const ENTRY_STATUS_TRANSLATION: Record<MigrationEntryStatus, MessageDescriptor> = {
|
||||||
[MigrationEntryStatus.PENDING]: msg`Pending…`,
|
[MigrationEntryStatus.SEARCH_PENDING]: msg`Pending…`,
|
||||||
[MigrationEntryStatus.SEARCHING]: msg`Searching…`,
|
[MigrationEntryStatus.SEARCHING]: msg`Searching…`,
|
||||||
[MigrationEntryStatus.SEARCH_COMPLETE]: msg`Match found`,
|
[MigrationEntryStatus.SEARCH_COMPLETE]: msg`Match found`,
|
||||||
[MigrationEntryStatus.SEARCH_FAILED]: msg`Search failed`,
|
[MigrationEntryStatus.SEARCH_FAILED]: msg`Search failed`,
|
||||||
[MigrationEntryStatus.NO_MATCH]: msg`No match found`,
|
[MigrationEntryStatus.NO_MATCH]: msg`No match found`,
|
||||||
[MigrationEntryStatus.OUTDATED]: msg`Only outdated matches found`,
|
[MigrationEntryStatus.OUTDATED]: msg`Only outdated matches found`,
|
||||||
|
[MigrationEntryStatus.MIGRATION_PENDING]: msg`Pending…`,
|
||||||
[MigrationEntryStatus.MIGRATING]: msg`Migrating…`,
|
[MigrationEntryStatus.MIGRATING]: msg`Migrating…`,
|
||||||
[MigrationEntryStatus.MIGRATION_COMPLETE]: msg`Successfully migrated`,
|
[MigrationEntryStatus.MIGRATION_COMPLETE]: msg`Successfully migrated`,
|
||||||
[MigrationEntryStatus.MIGRATION_FAILED]: msg`Migration failed`,
|
[MigrationEntryStatus.MIGRATION_FAILED]: msg`Migration failed`,
|
||||||
[MigrationEntryStatus.EXCLUDED]: msg`Excluded`,
|
[MigrationEntryStatus.EXCLUDED]: msg`Excluded`,
|
||||||
|
[MigrationEntryStatus.ABORTED]: msg`Aborted`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MIGRATE_SEARCH_ENTRY_GROUPS = [
|
export const MIGRATE_SEARCH_ENTRY_GROUPS = [
|
||||||
MigrationEntryStatus.SEARCHING,
|
MigrationEntryStatus.SEARCHING,
|
||||||
MigrationEntryStatus.SEARCH_FAILED,
|
MigrationEntryStatus.SEARCH_FAILED,
|
||||||
|
MigrationEntryStatus.ABORTED,
|
||||||
MigrationEntryStatus.NO_MATCH,
|
MigrationEntryStatus.NO_MATCH,
|
||||||
MigrationEntryStatus.OUTDATED,
|
MigrationEntryStatus.OUTDATED,
|
||||||
MigrationEntryStatus.SEARCH_COMPLETE,
|
MigrationEntryStatus.SEARCH_COMPLETE,
|
||||||
@@ -74,6 +77,7 @@ export const MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record<
|
|||||||
> = {
|
> = {
|
||||||
[MigrationEntryStatus.SEARCHING]: true,
|
[MigrationEntryStatus.SEARCHING]: true,
|
||||||
[MigrationEntryStatus.SEARCH_FAILED]: false,
|
[MigrationEntryStatus.SEARCH_FAILED]: false,
|
||||||
|
[MigrationEntryStatus.ABORTED]: false,
|
||||||
[MigrationEntryStatus.NO_MATCH]: false,
|
[MigrationEntryStatus.NO_MATCH]: false,
|
||||||
[MigrationEntryStatus.OUTDATED]: false,
|
[MigrationEntryStatus.OUTDATED]: false,
|
||||||
[MigrationEntryStatus.SEARCH_COMPLETE]: false,
|
[MigrationEntryStatus.SEARCH_COMPLETE]: false,
|
||||||
@@ -82,6 +86,7 @@ export const MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record<
|
|||||||
export const MIGRATE_EXECUTE_ENTRY_GROUPS = [
|
export const MIGRATE_EXECUTE_ENTRY_GROUPS = [
|
||||||
MigrationEntryStatus.MIGRATING,
|
MigrationEntryStatus.MIGRATING,
|
||||||
MigrationEntryStatus.MIGRATION_FAILED,
|
MigrationEntryStatus.MIGRATION_FAILED,
|
||||||
|
MigrationEntryStatus.ABORTED,
|
||||||
MigrationEntryStatus.NO_MATCH,
|
MigrationEntryStatus.NO_MATCH,
|
||||||
MigrationEntryStatus.OUTDATED,
|
MigrationEntryStatus.OUTDATED,
|
||||||
MigrationEntryStatus.EXCLUDED,
|
MigrationEntryStatus.EXCLUDED,
|
||||||
@@ -94,6 +99,7 @@ export const MIGRATE_EXECUTE_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record<
|
|||||||
> = {
|
> = {
|
||||||
[MigrationEntryStatus.MIGRATING]: true,
|
[MigrationEntryStatus.MIGRATING]: true,
|
||||||
[MigrationEntryStatus.MIGRATION_FAILED]: false,
|
[MigrationEntryStatus.MIGRATION_FAILED]: false,
|
||||||
|
[MigrationEntryStatus.ABORTED]: false,
|
||||||
[MigrationEntryStatus.NO_MATCH]: false,
|
[MigrationEntryStatus.NO_MATCH]: false,
|
||||||
[MigrationEntryStatus.OUTDATED]: false,
|
[MigrationEntryStatus.OUTDATED]: false,
|
||||||
[MigrationEntryStatus.EXCLUDED]: false,
|
[MigrationEntryStatus.EXCLUDED]: false,
|
||||||
|
|||||||
@@ -87,16 +87,18 @@ export enum MigrationPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum MigrationEntryStatus {
|
export enum MigrationEntryStatus {
|
||||||
PENDING = 'pending',
|
SEARCH_PENDING = 'search_pending',
|
||||||
SEARCHING = 'searching',
|
SEARCHING = 'searching',
|
||||||
SEARCH_COMPLETE = 'search_complete',
|
SEARCH_COMPLETE = 'search_complete',
|
||||||
SEARCH_FAILED = 'search_failed',
|
SEARCH_FAILED = 'search_failed',
|
||||||
OUTDATED = 'outdated',
|
OUTDATED = 'outdated',
|
||||||
NO_MATCH = 'no_match',
|
NO_MATCH = 'no_match',
|
||||||
|
MIGRATION_PENDING = 'migration_pending',
|
||||||
MIGRATING = 'migrating',
|
MIGRATING = 'migrating',
|
||||||
MIGRATION_COMPLETE = 'migration_complete',
|
MIGRATION_COMPLETE = 'migration_complete',
|
||||||
MIGRATION_FAILED = 'migration_failed',
|
MIGRATION_FAILED = 'migration_failed',
|
||||||
EXCLUDED = 'excluded',
|
EXCLUDED = 'excluded',
|
||||||
|
ABORTED = 'aborted',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MigrationMatch
|
export interface MigrationMatch
|
||||||
|
|||||||
@@ -10,16 +10,35 @@ import type { MigratableEntry, TMigrationEntry } from '@/features/migration/Migr
|
|||||||
import { MigrationEntryStatus } from '@/features/migration/Migration.types.ts';
|
import { MigrationEntryStatus } from '@/features/migration/Migration.types.ts';
|
||||||
|
|
||||||
export class MigrationEntries {
|
export class MigrationEntries {
|
||||||
|
public static isAbortable(entry: TMigrationEntry): boolean {
|
||||||
|
return (
|
||||||
|
MigrationEntries.isSearching(entry) ||
|
||||||
|
MigrationEntries.hasStatus(entry, MigrationEntryStatus.MIGRATION_PENDING)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static isSearching(entry: TMigrationEntry): boolean {
|
public static isSearching(entry: TMigrationEntry): boolean {
|
||||||
return [MigrationEntryStatus.PENDING, MigrationEntryStatus.SEARCHING].includes(entry.status);
|
return MigrationEntries.hasStatus(entry, MigrationEntryStatus.SEARCH_PENDING, MigrationEntryStatus.SEARCHING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isMigrating(entry: TMigrationEntry): boolean {
|
||||||
|
return MigrationEntries.hasStatus(
|
||||||
|
entry,
|
||||||
|
MigrationEntryStatus.MIGRATION_PENDING,
|
||||||
|
MigrationEntryStatus.MIGRATING,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getExcluded(entries: TMigrationEntry[]): TMigrationEntry[] {
|
public static getExcluded(entries: TMigrationEntry[]): TMigrationEntry[] {
|
||||||
return entries.filter((entry) => entry.isExcluded);
|
return entries.filter((entry) => entry.isExcluded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static hasStatus(entry: TMigrationEntry, ...statuses: MigrationEntryStatus[]): boolean {
|
||||||
|
return statuses.includes(entry.status);
|
||||||
|
}
|
||||||
|
|
||||||
public static getHaveStatus(entries: TMigrationEntry[], ...statuses: MigrationEntryStatus[]): TMigrationEntry[] {
|
public static getHaveStatus(entries: TMigrationEntry[], ...statuses: MigrationEntryStatus[]): TMigrationEntry[] {
|
||||||
return entries.filter((entry) => statuses.includes(entry.status));
|
return entries.filter((entry) => MigrationEntries.hasStatus(entry, ...statuses));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getHaveStatusSorted(
|
public static getHaveStatusSorted(
|
||||||
@@ -55,6 +74,7 @@ export class MigrationEntries {
|
|||||||
MigrationEntries.hasStatus(
|
MigrationEntries.hasStatus(
|
||||||
entry,
|
entry,
|
||||||
MigrationEntryStatus.SEARCH_COMPLETE,
|
MigrationEntryStatus.SEARCH_COMPLETE,
|
||||||
|
MigrationEntryStatus.MIGRATION_PENDING,
|
||||||
MigrationEntryStatus.MIGRATING,
|
MigrationEntryStatus.MIGRATING,
|
||||||
) &&
|
) &&
|
||||||
!entry.isExcluded &&
|
!entry.isExcluded &&
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export class MigrationManager {
|
|||||||
private static mangaProcessQueue = pLimit(MAX_MANGAS_IN_PARALLEL);
|
private static mangaProcessQueue = pLimit(MAX_MANGAS_IN_PARALLEL);
|
||||||
private static parallelSourcesQueue: LimitFunction | undefined;
|
private static parallelSourcesQueue: LimitFunction | undefined;
|
||||||
private static queueBySource = new Map<SourceIdInfo['id'], LimitFunction>();
|
private static queueBySource = new Map<SourceIdInfo['id'], LimitFunction>();
|
||||||
private static searchAbortControllerByManga = new Map<MangaIdInfo['id'], AbortController>();
|
private static abortControllerByManga = new Map<MangaIdInfo['id'], AbortController>();
|
||||||
|
|
||||||
private static abortAndResetAbortController(reason: unknown): void {
|
private static abortAndResetAbortController(reason: unknown): void {
|
||||||
MigrationManager.abortController?.abort(reason);
|
MigrationManager.abortController?.abort(reason);
|
||||||
@@ -344,7 +344,7 @@ export class MigrationManager {
|
|||||||
mangaThumbnailUrl: manga.thumbnailUrl,
|
mangaThumbnailUrl: manga.thumbnailUrl,
|
||||||
sourceId: manga.sourceId,
|
sourceId: manga.sourceId,
|
||||||
sourceTitle: manga.source?.displayName,
|
sourceTitle: manga.source?.displayName,
|
||||||
status: MigrationEntryStatus.PENDING,
|
status: MigrationEntryStatus.SEARCH_PENDING,
|
||||||
searchMatches: [],
|
searchMatches: [],
|
||||||
manualMatches: [],
|
manualMatches: [],
|
||||||
selectedMatchMangaId: null,
|
selectedMatchMangaId: null,
|
||||||
@@ -384,10 +384,16 @@ export class MigrationManager {
|
|||||||
private static async search(entries: TMigrationEntry[], options: MigrationBulkSearchSettings): Promise<void> {
|
private static async search(entries: TMigrationEntry[], options: MigrationBulkSearchSettings): Promise<void> {
|
||||||
const { signal } = MigrationManager.abortAndCreateAbortController('search');
|
const { signal } = MigrationManager.abortAndCreateAbortController('search');
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) =>
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
draft.entries[entry.mangaId].status = MigrationEntryStatus.SEARCH_PENDING;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const searchPromises = entries.map((entry) =>
|
const searchPromises = entries.map((entry) =>
|
||||||
MigrationManager.mangaProcessQueue(async () => {
|
MigrationManager.mangaProcessQueue(async () => {
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
return;
|
throw new Error(signal.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
await MigrationManager.searchForManga(entry.mangaId, entry.mangaTitle, signal, options);
|
await MigrationManager.searchForManga(entry.mangaId, entry.mangaTitle, signal, options);
|
||||||
@@ -447,13 +453,19 @@ export class MigrationManager {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { signal } = MigrationManager.abortAndCreateAbortController('migrate');
|
const { signal } = MigrationManager.abortAndCreateAbortController('migrate');
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
draft.entries[entry.mangaId].status = MigrationEntryStatus.MIGRATION_PENDING;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const entriesBySource = Object.groupBy(entries, (entry) => entry.selectedMatchSourceId);
|
const entriesBySource = Object.groupBy(entries, (entry) => entry.selectedMatchSourceId);
|
||||||
|
|
||||||
const migrationPromises = Object.values(entriesBySource).map((sourceEntries = []) =>
|
const migrationPromises = Object.values(entriesBySource).map((sourceEntries = []) =>
|
||||||
MigrationManager.mangaProcessQueue(async () => {
|
MigrationManager.mangaProcessQueue(async () => {
|
||||||
for (const entry of sourceEntries) {
|
for (const entry of sourceEntries) {
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
return;
|
throw new Error(signal.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
// oxlint-disable-next-line no-await-in-loop
|
// oxlint-disable-next-line no-await-in-loop
|
||||||
@@ -530,7 +542,7 @@ export class MigrationManager {
|
|||||||
|
|
||||||
MigrationManager.updateState((draft) => {
|
MigrationManager.updateState((draft) => {
|
||||||
migratableEntries.forEach((entry) => {
|
migratableEntries.forEach((entry) => {
|
||||||
draft.entries[entry.mangaId].status = MigrationEntryStatus.SEARCH_COMPLETE;
|
draft.entries[entry.mangaId].status = MigrationEntryStatus.MIGRATION_PENDING;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -543,13 +555,13 @@ export class MigrationManager {
|
|||||||
|
|
||||||
const pendingEntries = MigrationEntries.getHaveStatus(
|
const pendingEntries = MigrationEntries.getHaveStatus(
|
||||||
Object.values(entries),
|
Object.values(entries),
|
||||||
MigrationEntryStatus.PENDING,
|
MigrationEntryStatus.SEARCH_PENDING,
|
||||||
MigrationEntryStatus.SEARCHING,
|
MigrationEntryStatus.SEARCHING,
|
||||||
);
|
);
|
||||||
|
|
||||||
MigrationManager.updateState((draft) => {
|
MigrationManager.updateState((draft) => {
|
||||||
pendingEntries.forEach((entry) => {
|
pendingEntries.forEach((entry) => {
|
||||||
draft.entries[entry.mangaId].status = MigrationEntryStatus.PENDING;
|
draft.entries[entry.mangaId].status = MigrationEntryStatus.SEARCH_PENDING;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -581,6 +593,28 @@ export class MigrationManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static abortEntry(mangaId: MangaIdInfo['id']): void {
|
||||||
|
const { entries, phase } = MigrationManager.getState();
|
||||||
|
const entry = entries[mangaId];
|
||||||
|
|
||||||
|
assertIsDefined(entry);
|
||||||
|
|
||||||
|
MigrationManager.abortControllerByManga.get(mangaId)?.abort('User aborted');
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const draftEntry = draft.entries[mangaId];
|
||||||
|
|
||||||
|
assertIsDefined(draftEntry);
|
||||||
|
|
||||||
|
draftEntry.status = MigrationEntryStatus.ABORTED;
|
||||||
|
|
||||||
|
if (phase === MigrationPhase.SEARCHING) {
|
||||||
|
draft.searchProgress.completed += 1;
|
||||||
|
} else {
|
||||||
|
draft.migrationProgress.completed += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static selectMatch(
|
static selectMatch(
|
||||||
mangaId: MangaIdInfo['id'],
|
mangaId: MangaIdInfo['id'],
|
||||||
targetMangaId: MangaIdInfo['id'],
|
targetMangaId: MangaIdInfo['id'],
|
||||||
@@ -628,7 +662,7 @@ export class MigrationManager {
|
|||||||
if (isSearching) {
|
if (isSearching) {
|
||||||
draft.searchProgress.completed += 1;
|
draft.searchProgress.completed += 1;
|
||||||
draft.searchProgress.success += 1;
|
draft.searchProgress.success += 1;
|
||||||
MigrationManager.searchAbortControllerByManga.get(mangaId)?.abort('Manual match selected');
|
MigrationManager.abortControllerByManga.get(mangaId)?.abort('Manual match selected');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -792,6 +826,7 @@ export class MigrationManager {
|
|||||||
() =>
|
() =>
|
||||||
requestManager.refreshManga(match.id, {
|
requestManager.refreshManga(match.id, {
|
||||||
awaitRefetchQueries: true,
|
awaitRefetchQueries: true,
|
||||||
|
context: { fetchOptions: { signal } },
|
||||||
}).response,
|
}).response,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -836,13 +871,13 @@ export class MigrationManager {
|
|||||||
const searchController = new AbortController();
|
const searchController = new AbortController();
|
||||||
const signal = AbortSignal.any([mainSignal, searchController.signal]);
|
const signal = AbortSignal.any([mainSignal, searchController.signal]);
|
||||||
|
|
||||||
MigrationManager.searchAbortControllerByManga.get(mangaId)?.abort('search');
|
if (!entry || MigrationEntries.hasStatus(entry, MigrationEntryStatus.ABORTED)) {
|
||||||
MigrationManager.searchAbortControllerByManga.set(mangaId, searchController);
|
|
||||||
|
|
||||||
if (!entry) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MigrationManager.abortControllerByManga.get(mangaId)?.abort('search');
|
||||||
|
MigrationManager.abortControllerByManga.set(mangaId, searchController);
|
||||||
|
|
||||||
MigrationManager.updateState((draft) => {
|
MigrationManager.updateState((draft) => {
|
||||||
draft.entries[mangaId].status = MigrationEntryStatus.SEARCHING;
|
draft.entries[mangaId].status = MigrationEntryStatus.SEARCHING;
|
||||||
});
|
});
|
||||||
@@ -853,7 +888,7 @@ export class MigrationManager {
|
|||||||
.map((destSourceId) =>
|
.map((destSourceId) =>
|
||||||
MigrationManager.getParallelSourceQueue()(async () => {
|
MigrationManager.getParallelSourceQueue()(async () => {
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
return null;
|
throw new Error(signal.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -1017,35 +1052,48 @@ export class MigrationManager {
|
|||||||
MigrationManager.updateState((draft) => {
|
MigrationManager.updateState((draft) => {
|
||||||
const draftEntry = draft.entries[mangaId];
|
const draftEntry = draft.entries[mangaId];
|
||||||
|
|
||||||
draftEntry.status = MigrationEntryStatus.SEARCH_FAILED;
|
if (!MigrationEntries.hasStatus(draftEntry, MigrationEntryStatus.ABORTED)) {
|
||||||
draftEntry.error = getErrorMessage(error);
|
draftEntry.status = MigrationEntryStatus.SEARCH_FAILED;
|
||||||
draft.searchProgress.completed += 1;
|
draftEntry.error = getErrorMessage(error);
|
||||||
draft.searchProgress.failed += 1;
|
draft.searchProgress.completed += 1;
|
||||||
|
draft.searchProgress.failed += 1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
MigrationManager.searchAbortControllerByManga.delete(mangaId);
|
MigrationManager.abortControllerByManga.delete(mangaId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async migrateSingleEntry(
|
private static async migrateSingleEntry(
|
||||||
mangaId: MangaIdInfo['id'],
|
mangaId: MangaIdInfo['id'],
|
||||||
options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
|
options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
|
||||||
signal: AbortSignal,
|
mainSignal: AbortSignal,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const state = MigrationManager.getState();
|
const state = MigrationManager.getState();
|
||||||
const entry = state.entries[mangaId];
|
const entry = state.entries[mangaId];
|
||||||
|
|
||||||
if (!entry || !entry.selectedMatchSourceId || entry.selectedMatchMangaId == null) {
|
const migrateController = new AbortController();
|
||||||
|
const signal = AbortSignal.any([mainSignal, migrateController.signal]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!entry ||
|
||||||
|
!entry.selectedMatchSourceId ||
|
||||||
|
entry.selectedMatchMangaId == null ||
|
||||||
|
MigrationEntries.hasStatus(entry, MigrationEntryStatus.ABORTED)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MigrationManager.abortControllerByManga.get(mangaId)?.abort('migrate');
|
||||||
|
MigrationManager.abortControllerByManga.set(mangaId, migrateController);
|
||||||
|
|
||||||
MigrationManager.updateState((draft) => {
|
MigrationManager.updateState((draft) => {
|
||||||
draft.entries[mangaId].status = MigrationEntryStatus.MIGRATING;
|
draft.entries[mangaId].status = MigrationEntryStatus.MIGRATING;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
return;
|
throw new Error(signal.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
await MigrationManager.getParallelSourceQueue()(() => {
|
await MigrationManager.getParallelSourceQueue()(() => {
|
||||||
@@ -1053,7 +1101,7 @@ export class MigrationManager {
|
|||||||
|
|
||||||
return MigrationManager.getOrCreateSourceQueue(entry.selectedMatchSourceId)(async () => {
|
return MigrationManager.getOrCreateSourceQueue(entry.selectedMatchSourceId)(async () => {
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
return;
|
throw new Error(signal.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertIsDefined(entry.selectedMatchMangaId);
|
assertIsDefined(entry.selectedMatchMangaId);
|
||||||
@@ -1063,21 +1111,33 @@ export class MigrationManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
MigrationManager.updateState((draft) => {
|
MigrationManager.updateState((draft) => {
|
||||||
|
if (!MigrationEntries.isMigrating(entry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
draft.entries[mangaId].status = MigrationEntryStatus.MIGRATION_COMPLETE;
|
draft.entries[mangaId].status = MigrationEntryStatus.MIGRATION_COMPLETE;
|
||||||
draft.migrationProgress.success += 1;
|
draft.migrationProgress.success += 1;
|
||||||
draft.migrationProgress.completed += 1;
|
draft.migrationProgress.completed += 1;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (signal.aborted) {
|
if (mainSignal.aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MigrationManager.updateState((draft) => {
|
MigrationManager.updateState((draft) => {
|
||||||
draft.entries[mangaId].status = MigrationEntryStatus.MIGRATION_FAILED;
|
const draftEntry = draft.entries[mangaId];
|
||||||
draft.entries[mangaId].error = error instanceof Error ? error.message : String(error);
|
|
||||||
draft.migrationProgress.failed += 1;
|
assertIsDefined(draftEntry);
|
||||||
draft.migrationProgress.completed += 1;
|
|
||||||
|
if (!MigrationEntries.hasStatus(draftEntry, MigrationEntryStatus.ABORTED)) {
|
||||||
|
draftEntry.status = MigrationEntryStatus.MIGRATION_FAILED;
|
||||||
|
draftEntry.error = getErrorMessage(error);
|
||||||
|
draft.migrationProgress.failed += 1;
|
||||||
|
draft.migrationProgress.completed += 1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
MigrationManager.abortControllerByManga.delete(mangaId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,14 @@ export const MigrationEntryGroup = memo(
|
|||||||
entries,
|
entries,
|
||||||
color,
|
color,
|
||||||
isMigrating = false,
|
isMigrating = false,
|
||||||
|
isAborted = false,
|
||||||
}: {
|
}: {
|
||||||
status: MigrationEntryStatus;
|
status: MigrationEntryStatus;
|
||||||
title: string;
|
title: string;
|
||||||
entries: TMigrationEntry[];
|
entries: TMigrationEntry[];
|
||||||
color: ButtonProps['color'];
|
color: ButtonProps['color'];
|
||||||
isMigrating?: boolean;
|
isMigrating?: boolean;
|
||||||
|
isAborted?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const isExpanded = MigrationManager.useGroupExpandState(status);
|
const isExpanded = MigrationManager.useGroupExpandState(status);
|
||||||
|
|
||||||
@@ -60,7 +62,12 @@ export const MigrationEntryGroup = memo(
|
|||||||
<Collapse in={isExpanded} unmountOnExit>
|
<Collapse in={isExpanded} unmountOnExit>
|
||||||
<Stack sx={{ gap: 1 }}>
|
<Stack sx={{ gap: 1 }}>
|
||||||
{entries.map((entry) => (
|
{entries.map((entry) => (
|
||||||
<MigrationEntry key={entry.mangaId} entry={entry} isMigrating={isMigrating} />
|
<MigrationEntry
|
||||||
|
key={entry.mangaId}
|
||||||
|
entry={entry}
|
||||||
|
isMigrating={isMigrating}
|
||||||
|
isAborted={isAborted}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import Collapse from '@mui/material/Collapse';
|
|||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
import { MigrationEntryStatusIndicator } from '@/features/migration/components/migration-entry/MigrationEntryStatusIndicator.tsx';
|
import { MigrationEntryStatusIndicator } from '@/features/migration/components/migration-entry/MigrationEntryStatusIndicator.tsx';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import { MigrationEntries } from '@/features/migration/MigrationEntries.ts';
|
||||||
|
|
||||||
const MigrationEntryMobile = memo(
|
const MigrationEntryMobile = memo(
|
||||||
({
|
({
|
||||||
@@ -35,6 +36,7 @@ const MigrationEntryMobile = memo(
|
|||||||
isExpanded,
|
isExpanded,
|
||||||
setIsExpanded,
|
setIsExpanded,
|
||||||
isMigrating,
|
isMigrating,
|
||||||
|
isAborted,
|
||||||
}: {
|
}: {
|
||||||
entry: TMigrationEntry;
|
entry: TMigrationEntry;
|
||||||
destinationEntry: MigrationMatch | undefined;
|
destinationEntry: MigrationMatch | undefined;
|
||||||
@@ -42,6 +44,7 @@ const MigrationEntryMobile = memo(
|
|||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
setIsExpanded: (expanded: boolean) => void;
|
setIsExpanded: (expanded: boolean) => void;
|
||||||
isMigrating: boolean;
|
isMigrating: boolean;
|
||||||
|
isAborted: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
@@ -89,17 +92,17 @@ const MigrationEntryMobile = memo(
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
{!isMigrating && (
|
<MigrationEntrySearchExcludeActions
|
||||||
<MigrationEntrySearchExcludeActions
|
hasSelectedMatch={!!destinationEntry}
|
||||||
hasSelectedMatch={!!destinationEntry}
|
otherResultsCount={otherSearchMatches.length}
|
||||||
otherResultsCount={otherSearchMatches.length}
|
isExpanded={isExpanded}
|
||||||
isExpanded={isExpanded}
|
setIsExpanded={setIsExpanded}
|
||||||
setIsExpanded={setIsExpanded}
|
isExcluded={isExcluded}
|
||||||
isExcluded={isExcluded}
|
mangaId={mangaId}
|
||||||
mangaId={mangaId}
|
mangaTitle={mangaTitle}
|
||||||
mangaTitle={mangaTitle}
|
isAbortable={!isAborted && MigrationEntries.isAbortable(entry)}
|
||||||
/>
|
isMigrating={isMigrating}
|
||||||
)}
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -114,6 +117,7 @@ export const MigrationEntryDesktop = memo(
|
|||||||
isExpanded,
|
isExpanded,
|
||||||
setIsExpanded,
|
setIsExpanded,
|
||||||
isMigrating,
|
isMigrating,
|
||||||
|
isAborted,
|
||||||
}: {
|
}: {
|
||||||
entry: TMigrationEntry;
|
entry: TMigrationEntry;
|
||||||
destinationEntry: MigrationMatch | undefined;
|
destinationEntry: MigrationMatch | undefined;
|
||||||
@@ -121,6 +125,7 @@ export const MigrationEntryDesktop = memo(
|
|||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
setIsExpanded: (expanded: boolean) => void;
|
setIsExpanded: (expanded: boolean) => void;
|
||||||
isMigrating: boolean;
|
isMigrating: boolean;
|
||||||
|
isAborted: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
@@ -151,17 +156,17 @@ export const MigrationEntryDesktop = memo(
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{!isMigrating && (
|
<MigrationEntrySearchExcludeActions
|
||||||
<MigrationEntrySearchExcludeActions
|
hasSelectedMatch={!!destinationEntry}
|
||||||
hasSelectedMatch={!!destinationEntry}
|
otherResultsCount={otherSearchMatches.length}
|
||||||
otherResultsCount={otherSearchMatches.length}
|
isExpanded={isExpanded}
|
||||||
isExpanded={isExpanded}
|
setIsExpanded={setIsExpanded}
|
||||||
setIsExpanded={setIsExpanded}
|
isExcluded={entry.isExcluded}
|
||||||
isExcluded={entry.isExcluded}
|
mangaId={entry.mangaId}
|
||||||
mangaId={entry.mangaId}
|
mangaTitle={entry.mangaTitle}
|
||||||
mangaTitle={entry.mangaTitle}
|
isAbortable={!isAborted && MigrationEntries.isAbortable(entry)}
|
||||||
/>
|
isMigrating={isMigrating}
|
||||||
)}
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -190,7 +195,15 @@ export const MigrationEntryDesktop = memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const MigrationEntry = memo(
|
export const MigrationEntry = memo(
|
||||||
({ entry: propEntry, isMigrating }: { entry: TMigrationEntry; isMigrating: boolean }) => {
|
({
|
||||||
|
entry: propEntry,
|
||||||
|
isMigrating,
|
||||||
|
isAborted,
|
||||||
|
}: {
|
||||||
|
entry: TMigrationEntry;
|
||||||
|
isMigrating: boolean;
|
||||||
|
isAborted: boolean;
|
||||||
|
}) => {
|
||||||
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
|
|
||||||
const entry = useMemo(() => MigrationManager.getUpToDateMigrationEntry(propEntry), [propEntry]);
|
const entry = useMemo(() => MigrationManager.getUpToDateMigrationEntry(propEntry), [propEntry]);
|
||||||
@@ -255,6 +268,7 @@ export const MigrationEntry = memo(
|
|||||||
}
|
}
|
||||||
otherSearchMatches={otherMatches}
|
otherSearchMatches={otherMatches}
|
||||||
isMigrating={isMigrating}
|
isMigrating={isMigrating}
|
||||||
|
isAborted={isAborted}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
|||||||
import ButtonGroup from '@mui/material/ButtonGroup';
|
import ButtonGroup from '@mui/material/ButtonGroup';
|
||||||
import RemoveCircleOutlineOutlinedIcon from '@mui/icons-material/RemoveCircleOutlineOutlined';
|
import RemoveCircleOutlineOutlinedIcon from '@mui/icons-material/RemoveCircleOutlineOutlined';
|
||||||
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
||||||
|
import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
|
||||||
|
|
||||||
export const MigrationEntrySearchExcludeActions = ({
|
export const MigrationEntrySearchExcludeActions = ({
|
||||||
hasSelectedMatch,
|
hasSelectedMatch,
|
||||||
@@ -31,6 +32,8 @@ export const MigrationEntrySearchExcludeActions = ({
|
|||||||
isExcluded,
|
isExcluded,
|
||||||
mangaId,
|
mangaId,
|
||||||
mangaTitle,
|
mangaTitle,
|
||||||
|
isAbortable,
|
||||||
|
isMigrating,
|
||||||
}: {
|
}: {
|
||||||
hasSelectedMatch: boolean;
|
hasSelectedMatch: boolean;
|
||||||
otherResultsCount: number;
|
otherResultsCount: number;
|
||||||
@@ -39,6 +42,8 @@ export const MigrationEntrySearchExcludeActions = ({
|
|||||||
isExcluded: boolean;
|
isExcluded: boolean;
|
||||||
mangaId: MangaIdInfo['id'];
|
mangaId: MangaIdInfo['id'];
|
||||||
mangaTitle: string;
|
mangaTitle: string;
|
||||||
|
isAbortable: boolean;
|
||||||
|
isMigrating: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
@@ -46,7 +51,7 @@ export const MigrationEntrySearchExcludeActions = ({
|
|||||||
if (isTabletWidth) {
|
if (isTabletWidth) {
|
||||||
return (
|
return (
|
||||||
<ButtonGroup variant="contained">
|
<ButtonGroup variant="contained">
|
||||||
{!!otherResultsCount && (
|
{!isMigrating && !!otherResultsCount && (
|
||||||
<Button
|
<Button
|
||||||
sx={{ flexGrow: 1 }}
|
sx={{ flexGrow: 1 }}
|
||||||
startIcon={isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
startIcon={isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||||
@@ -58,7 +63,7 @@ export const MigrationEntrySearchExcludeActions = ({
|
|||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isExpanded && (
|
{!isMigrating && !isExpanded && (
|
||||||
<CustomTooltip title={t`Manual search`}>
|
<CustomTooltip title={t`Manual search`}>
|
||||||
<CustomButtonIcon
|
<CustomButtonIcon
|
||||||
sx={{
|
sx={{
|
||||||
@@ -72,7 +77,7 @@ export const MigrationEntrySearchExcludeActions = ({
|
|||||||
</CustomButtonIcon>
|
</CustomButtonIcon>
|
||||||
</CustomTooltip>
|
</CustomTooltip>
|
||||||
)}
|
)}
|
||||||
{hasSelectedMatch && (
|
{!isMigrating && hasSelectedMatch && (
|
||||||
<CustomTooltip title={isExcluded ? t`Include` : t`Exclude`}>
|
<CustomTooltip title={isExcluded ? t`Include` : t`Exclude`}>
|
||||||
<CustomButtonIcon
|
<CustomButtonIcon
|
||||||
sx={{
|
sx={{
|
||||||
@@ -88,13 +93,32 @@ export const MigrationEntrySearchExcludeActions = ({
|
|||||||
</CustomButtonIcon>
|
</CustomButtonIcon>
|
||||||
</CustomTooltip>
|
</CustomTooltip>
|
||||||
)}
|
)}
|
||||||
|
{isAbortable && (
|
||||||
|
<CustomTooltip title={t`Abort`}>
|
||||||
|
<CustomButtonIcon
|
||||||
|
sx={{
|
||||||
|
flexGrow: Number(!otherResultsCount),
|
||||||
|
}}
|
||||||
|
onClick={() => MigrationManager.abortEntry(mangaId)}
|
||||||
|
>
|
||||||
|
<CancelOutlinedIcon />
|
||||||
|
</CustomButtonIcon>
|
||||||
|
</CustomTooltip>
|
||||||
|
)}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ gap: 1, justifyContent: 'center' }}>
|
<Stack sx={{ gap: 1, justifyContent: 'center' }}>
|
||||||
{hasSelectedMatch && (
|
{isAbortable && (
|
||||||
|
<CustomTooltip title={t`Abort`} placement="auto">
|
||||||
|
<IconButton onClick={() => MigrationManager.abortEntry(mangaId)}>
|
||||||
|
<CancelOutlinedIcon />
|
||||||
|
</IconButton>
|
||||||
|
</CustomTooltip>
|
||||||
|
)}
|
||||||
|
{!isMigrating && hasSelectedMatch && (
|
||||||
<CustomTooltip title={isExcluded ? t`Include` : t`Exclude`} placement="auto">
|
<CustomTooltip title={isExcluded ? t`Include` : t`Exclude`} placement="auto">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@@ -105,15 +129,17 @@ export const MigrationEntrySearchExcludeActions = ({
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</CustomTooltip>
|
</CustomTooltip>
|
||||||
)}
|
)}
|
||||||
<CustomTooltip title={t`Manual search`} placement="auto">
|
{!isMigrating && (
|
||||||
<IconButton
|
<CustomTooltip title={t`Manual search`} placement="auto">
|
||||||
onClick={() => {
|
<IconButton
|
||||||
MigrationManager.openManualSearch(mangaId, mangaTitle);
|
onClick={() => {
|
||||||
}}
|
MigrationManager.openManualSearch(mangaId, mangaTitle);
|
||||||
>
|
}}
|
||||||
<SearchIcon />
|
>
|
||||||
</IconButton>
|
<SearchIcon />
|
||||||
</CustomTooltip>
|
</IconButton>
|
||||||
|
</CustomTooltip>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { MigrationProgressBar } from '@/features/migration/components/MigrationP
|
|||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
|
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
|
||||||
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
import { MigrationEntryStatus } from '@/features/migration/Migration.types.ts';
|
import { MigrationEntryStatus, MigrationPhase } from '@/features/migration/Migration.types.ts';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx';
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx';
|
||||||
import { MigrationContinueButton } from '@/features/migration/components/MigrationContinueButton.tsx';
|
import { MigrationContinueButton } from '@/features/migration/components/MigrationContinueButton.tsx';
|
||||||
@@ -21,6 +21,8 @@ import { MigrationEntries } from '@/features/migration/MigrationEntries.ts';
|
|||||||
|
|
||||||
export const MigrationExecute = () => {
|
export const MigrationExecute = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
const phase = MigrationManager.usePhase();
|
||||||
const entries = MigrationManager.useEntries();
|
const entries = MigrationManager.useEntries();
|
||||||
const progress = MigrationManager.useMigrationProgress();
|
const progress = MigrationManager.useMigrationProgress();
|
||||||
|
|
||||||
@@ -40,13 +42,15 @@ export const MigrationExecute = () => {
|
|||||||
[MigrationManager.isPhaseComplete()],
|
[MigrationManager.isPhaseComplete()],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isMigrating = phase === MigrationPhase.MIGRATING;
|
||||||
|
|
||||||
const entryList = useMemo(() => Object.values(entries), [entries]);
|
const entryList = useMemo(() => Object.values(entries), [entries]);
|
||||||
const migratingEntries = useMemo(
|
const migratingEntries = useMemo(
|
||||||
() =>
|
() =>
|
||||||
MigrationEntries.getActiveEntriesSorted(
|
MigrationEntries.getActiveEntriesSorted(
|
||||||
entryList,
|
entryList,
|
||||||
MigrationEntryStatus.MIGRATING,
|
MigrationEntryStatus.MIGRATING,
|
||||||
MigrationEntryStatus.SEARCH_COMPLETE,
|
MigrationEntryStatus.MIGRATION_PENDING,
|
||||||
MigrationEntryStatus.MIGRATING,
|
MigrationEntryStatus.MIGRATING,
|
||||||
),
|
),
|
||||||
[entryList],
|
[entryList],
|
||||||
@@ -59,6 +63,10 @@ export const MigrationExecute = () => {
|
|||||||
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.MIGRATION_FAILED),
|
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.MIGRATION_FAILED),
|
||||||
[entryList],
|
[entryList],
|
||||||
);
|
);
|
||||||
|
const abortedEntries = useMemo(
|
||||||
|
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.ABORTED),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
const excludedEntries = useMemo(() => MigrationEntries.getExcluded(entryList), [entryList]);
|
const excludedEntries = useMemo(() => MigrationEntries.getExcluded(entryList), [entryList]);
|
||||||
const noMatchEntries = useMemo(
|
const noMatchEntries = useMemo(
|
||||||
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.NO_MATCH),
|
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.NO_MATCH),
|
||||||
@@ -87,7 +95,8 @@ export const MigrationExecute = () => {
|
|||||||
other: '# migrating entries',
|
other: '# migrating entries',
|
||||||
})}
|
})}
|
||||||
entries={migratingEntries}
|
entries={migratingEntries}
|
||||||
isMigrating
|
isMigrating={isMigrating}
|
||||||
|
isAborted={MigrationManager.getState().isAborted}
|
||||||
color="info"
|
color="info"
|
||||||
/>
|
/>
|
||||||
<MigrationEntryGroup
|
<MigrationEntryGroup
|
||||||
@@ -97,9 +106,21 @@ export const MigrationExecute = () => {
|
|||||||
other: '# failed entries',
|
other: '# failed entries',
|
||||||
})}
|
})}
|
||||||
entries={failedEntries}
|
entries={failedEntries}
|
||||||
isMigrating
|
isMigrating={isMigrating}
|
||||||
|
isAborted={MigrationManager.getState().isAborted}
|
||||||
color="error"
|
color="error"
|
||||||
/>
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.ABORTED}
|
||||||
|
title={plural(abortedEntries.length, {
|
||||||
|
one: '1 aborted entry',
|
||||||
|
other: '# aborted entries',
|
||||||
|
})}
|
||||||
|
entries={abortedEntries}
|
||||||
|
color="warning"
|
||||||
|
isMigrating={isMigrating}
|
||||||
|
isAborted={MigrationManager.getState().isAborted}
|
||||||
|
/>
|
||||||
<MigrationEntryGroup
|
<MigrationEntryGroup
|
||||||
status={MigrationEntryStatus.NO_MATCH}
|
status={MigrationEntryStatus.NO_MATCH}
|
||||||
title={plural(noMatchEntries.length, {
|
title={plural(noMatchEntries.length, {
|
||||||
@@ -108,7 +129,8 @@ export const MigrationExecute = () => {
|
|||||||
})}
|
})}
|
||||||
entries={noMatchEntries}
|
entries={noMatchEntries}
|
||||||
color="warning"
|
color="warning"
|
||||||
isMigrating
|
isMigrating={isMigrating}
|
||||||
|
isAborted={MigrationManager.getState().isAborted}
|
||||||
/>
|
/>
|
||||||
<MigrationEntryGroup
|
<MigrationEntryGroup
|
||||||
status={MigrationEntryStatus.OUTDATED}
|
status={MigrationEntryStatus.OUTDATED}
|
||||||
@@ -117,6 +139,7 @@ export const MigrationExecute = () => {
|
|||||||
other: '# entries with only outdated matches',
|
other: '# entries with only outdated matches',
|
||||||
})}
|
})}
|
||||||
entries={outdatedEntries}
|
entries={outdatedEntries}
|
||||||
|
isAborted={MigrationManager.getState().isAborted}
|
||||||
color="warning"
|
color="warning"
|
||||||
/>
|
/>
|
||||||
<MigrationEntryGroup
|
<MigrationEntryGroup
|
||||||
@@ -127,7 +150,8 @@ export const MigrationExecute = () => {
|
|||||||
})}
|
})}
|
||||||
entries={excludedEntries}
|
entries={excludedEntries}
|
||||||
color="info"
|
color="info"
|
||||||
isMigrating
|
isMigrating={isMigrating}
|
||||||
|
isAborted={MigrationManager.getState().isAborted}
|
||||||
/>
|
/>
|
||||||
<MigrationEntryGroup
|
<MigrationEntryGroup
|
||||||
status={MigrationEntryStatus.MIGRATION_COMPLETE}
|
status={MigrationEntryStatus.MIGRATION_COMPLETE}
|
||||||
@@ -137,7 +161,8 @@ export const MigrationExecute = () => {
|
|||||||
})}
|
})}
|
||||||
entries={migratedEntries}
|
entries={migratedEntries}
|
||||||
color="success"
|
color="success"
|
||||||
isMigrating
|
isMigrating={isMigrating}
|
||||||
|
isAborted={MigrationManager.getState().isAborted}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const MigrationSearch = () => {
|
|||||||
MigrationEntries.getActiveEntriesSorted(
|
MigrationEntries.getActiveEntriesSorted(
|
||||||
entryList,
|
entryList,
|
||||||
MigrationEntryStatus.SEARCHING,
|
MigrationEntryStatus.SEARCHING,
|
||||||
MigrationEntryStatus.PENDING,
|
MigrationEntryStatus.SEARCH_PENDING,
|
||||||
MigrationEntryStatus.SEARCHING,
|
MigrationEntryStatus.SEARCHING,
|
||||||
),
|
),
|
||||||
[entryList],
|
[entryList],
|
||||||
@@ -54,6 +54,10 @@ export const MigrationSearch = () => {
|
|||||||
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.OUTDATED),
|
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.OUTDATED),
|
||||||
[entryList],
|
[entryList],
|
||||||
);
|
);
|
||||||
|
const abortedEntries = useMemo(
|
||||||
|
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.ABORTED),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
const matchedEntries = useMemo(
|
const matchedEntries = useMemo(
|
||||||
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.SEARCH_COMPLETE),
|
() => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.SEARCH_COMPLETE),
|
||||||
[entryList],
|
[entryList],
|
||||||
@@ -108,6 +112,15 @@ export const MigrationSearch = () => {
|
|||||||
entries={outdatedEntries}
|
entries={outdatedEntries}
|
||||||
color="warning"
|
color="warning"
|
||||||
/>
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.ABORTED}
|
||||||
|
title={plural(abortedEntries.length, {
|
||||||
|
one: '1 aborted entry',
|
||||||
|
other: '# aborted entries',
|
||||||
|
})}
|
||||||
|
entries={abortedEntries}
|
||||||
|
color="warning"
|
||||||
|
/>
|
||||||
<MigrationEntryGroup
|
<MigrationEntryGroup
|
||||||
status={MigrationEntryStatus.SEARCH_COMPLETE}
|
status={MigrationEntryStatus.SEARCH_COMPLETE}
|
||||||
title={plural(matchedEntries.length, {
|
title={plural(matchedEntries.length, {
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ msgstr "{0, plural, one {# Source} other {# Sources}}"
|
|||||||
msgid "{0, plural, one {# Tracker} other {# Tracker}}"
|
msgid "{0, plural, one {# Tracker} other {# Tracker}}"
|
||||||
msgstr "{0, plural, one {# Tracker} other {# Tracker}}"
|
msgstr "{0, plural, one {# Tracker} other {# Tracker}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: abortedEntries.length
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
|
msgid "{0, plural, one {1 aborted entry} other {# aborted entries}}"
|
||||||
|
msgstr "{0, plural, one {1 aborted entry} other {# aborted entries}}"
|
||||||
|
|
||||||
#. placeholder {0}: noMatchEntries.length
|
#. placeholder {0}: noMatchEntries.length
|
||||||
#: src/features/migration/screens/MigrationExecute.tsx
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
#: src/features/migration/screens/MigrationSearch.tsx
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
@@ -325,6 +331,7 @@ msgstr "A preview focused web frontend built with svelte"
|
|||||||
msgid "A-Z"
|
msgid "A-Z"
|
||||||
msgstr "A-Z"
|
msgstr "A-Z"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx
|
||||||
#: src/features/migration/screens/MigrationExecute.tsx
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
msgid "Abort"
|
msgid "Abort"
|
||||||
msgstr "Abort"
|
msgstr "Abort"
|
||||||
@@ -333,6 +340,7 @@ msgstr "Abort"
|
|||||||
msgid "Abort migration"
|
msgid "Abort migration"
|
||||||
msgstr "Abort migration"
|
msgstr "Abort migration"
|
||||||
|
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
#: src/features/migration/screens/MigrationExecute.tsx
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
msgid "Aborted"
|
msgid "Aborted"
|
||||||
msgstr "Aborted"
|
msgstr "Aborted"
|
||||||
|
|||||||
Reference in New Issue
Block a user