Improve bulk migration render performance

This commit is contained in:
schroda
2026-05-13 01:22:04 +02:00
parent 72d213c43e
commit 06cc0724df
6 changed files with 377 additions and 360 deletions

View File

@@ -11,25 +11,26 @@ import type { ButtonProps } from '@mui/material/Button';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Collapse from '@mui/material/Collapse'; import Collapse from '@mui/material/Collapse';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import {} from 'react'; import { memo } from 'react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { MigrationEntry } from '@/features/migration/components/migration-entry/MigrationEntry.tsx'; import { MigrationEntry } from '@/features/migration/components/migration-entry/MigrationEntry.tsx';
import { MigrationManager } from '@/features/migration/MigrationManager.ts'; import { MigrationManager } from '@/features/migration/MigrationManager.ts';
export const MigrationEntryGroup = ({ export const MigrationEntryGroup = memo(
({
status, status,
title, title,
entries, entries,
color, color,
isMigrating = false, isMigrating = false,
}: { }: {
status: MigrationEntryStatus; status: MigrationEntryStatus;
title: string; title: string;
entries: TMigrationEntry[]; entries: TMigrationEntry[];
color: ButtonProps['color']; color: ButtonProps['color'];
isMigrating?: boolean; isMigrating?: boolean;
}) => { }) => {
const isExpanded = MigrationManager.useGroupExpandState(status); const isExpanded = MigrationManager.useGroupExpandState(status);
if (!entries.length) { if (!entries.length) {
@@ -65,4 +66,5 @@ export const MigrationEntryGroup = ({
</Collapse> </Collapse>
</Stack> </Stack>
); );
}; },
);

View File

@@ -21,8 +21,9 @@ import { Sources } from '@/features/source/services/Sources';
import type { TMigratableSource } from '@/features/migration/Migration.types.ts'; import type { TMigratableSource } from '@/features/migration/Migration.types.ts';
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts'; import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts'; import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { memo } from 'react';
export const MigrationCard = (source: TMigratableSource) => { export const MigrationCard = memo((source: TMigratableSource) => {
const { id, name, lang, iconUrl, mangaCount } = source; const { id, name, lang, iconUrl, mangaCount } = source;
const { t } = useLingui(); const { t } = useLingui();
@@ -66,4 +67,4 @@ export const MigrationCard = (source: TMigratableSource) => {
</CardActionArea> </CardActionArea>
</Card> </Card>
); );
}; });

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
import { useCallback, useMemo, useState } from 'react'; import { memo, useCallback, useMemo, useState } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Card from '@mui/material/Card'; import Card from '@mui/material/Card';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
@@ -36,19 +36,20 @@ import { languageCodeToName } from '@/base/utils/Languages.ts';
import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx'; import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
const SourceCard = ({ const SourceCard = memo(
({
source, source,
onToggle, onToggle,
isCurrentSource, isCurrentSource,
isSelected, isSelected,
isDragging, isDragging,
}: { }: {
source: SourceItem; source: SourceItem;
onToggle: (id: SourceIdInfo['id']) => void; onToggle: (id: SourceIdInfo['id']) => void;
isCurrentSource: boolean; isCurrentSource: boolean;
isSelected: boolean; isSelected: boolean;
isDragging?: boolean; isDragging?: boolean;
}) => { }) => {
const { t } = useLingui(); const { t } = useLingui();
return ( return (
@@ -84,7 +85,8 @@ const SourceCard = ({
</Card> </Card>
</StyledGroupItemWrapper> </StyledGroupItemWrapper>
); );
}; },
);
export const MigrationSourceList = ({ export const MigrationSourceList = ({
sources, sources,

View File

@@ -9,7 +9,7 @@
import type { MigrationMatch, TMigrationEntry } from '@/features/migration/Migration.types.ts'; import type { MigrationMatch, TMigrationEntry } from '@/features/migration/Migration.types.ts';
import { MigrationManager } from '@/features/migration/MigrationManager.ts'; import { MigrationManager } from '@/features/migration/MigrationManager.ts';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import { useMemo } from 'react'; import { memo, useMemo } from 'react';
import { MediaQuery } from '@/base/utils/MediaQuery.tsx'; import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
import { applyStyles } from '@/base/utils/ApplyStyles.ts'; import { applyStyles } from '@/base/utils/ApplyStyles.ts';
import { MigrationSourceEntry } from '@/features/migration/components/migration-entry/MigrationSourceEntry.tsx'; import { MigrationSourceEntry } from '@/features/migration/components/migration-entry/MigrationSourceEntry.tsx';
@@ -28,7 +28,8 @@ 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';
const MigrationEntryMobile = ({ const MigrationEntryMobile = memo(
({
entry, entry,
entry: { mangaId, mangaTitle, status, error, isExcluded }, entry: { mangaId, mangaTitle, status, error, isExcluded },
destinationEntry, destinationEntry,
@@ -36,14 +37,14 @@ const MigrationEntryMobile = ({
isExpanded, isExpanded,
setIsExpanded, setIsExpanded,
isMigrating, isMigrating,
}: { }: {
entry: TMigrationEntry; entry: TMigrationEntry;
destinationEntry: MigrationMatch | undefined; destinationEntry: MigrationMatch | undefined;
otherSearchMatches: MigrationMatch[]; otherSearchMatches: MigrationMatch[];
isExpanded: boolean; isExpanded: boolean;
setIsExpanded: (expanded: boolean) => void; setIsExpanded: (expanded: boolean) => void;
isMigrating: boolean; isMigrating: boolean;
}) => { }) => {
const { t } = useLingui(); const { t } = useLingui();
return ( return (
@@ -69,9 +70,12 @@ const MigrationEntryMobile = ({
startIcon={<SearchIcon />} startIcon={<SearchIcon />}
variant="text" variant="text"
onClick={() => { onClick={() => {
ReactRouter.navigate(AppRoutes.migrate.childRoutes.manualSearch.path(mangaId, mangaTitle), { ReactRouter.navigate(
AppRoutes.migrate.childRoutes.manualSearch.path(mangaId, mangaTitle),
{
state: { mangaTitle: mangaTitle }, state: { mangaTitle: mangaTitle },
}); },
);
}} }}
>{t`Manual search`}</Button> >{t`Manual search`}</Button>
</Stack> </Stack>
@@ -82,7 +86,11 @@ const MigrationEntryMobile = ({
} }
return ( return (
<MigrationMatchedEntry key={searchMatch.id} sourceMangaId={mangaId} entry={searchMatch} /> <MigrationMatchedEntry
key={searchMatch.id}
sourceMangaId={mangaId}
entry={searchMatch}
/>
); );
})} })}
</Stack> </Stack>
@@ -101,9 +109,11 @@ const MigrationEntryMobile = ({
)} )}
</> </>
); );
}; },
);
export const MigrationEntryDesktop = ({ export const MigrationEntryDesktop = memo(
({
entry, entry,
entry: { mangaId, mangaTitle, status, error }, entry: { mangaId, mangaTitle, status, error },
destinationEntry, destinationEntry,
@@ -111,14 +121,14 @@ export const MigrationEntryDesktop = ({
isExpanded, isExpanded,
setIsExpanded, setIsExpanded,
isMigrating, isMigrating,
}: { }: {
entry: TMigrationEntry; entry: TMigrationEntry;
destinationEntry: MigrationMatch | undefined; destinationEntry: MigrationMatch | undefined;
otherSearchMatches: MigrationMatch[]; otherSearchMatches: MigrationMatch[];
isExpanded: boolean; isExpanded: boolean;
setIsExpanded: (expanded: boolean) => void; setIsExpanded: (expanded: boolean) => void;
isMigrating: boolean; isMigrating: boolean;
}) => { }) => {
const { t } = useLingui(); const { t } = useLingui();
return ( return (
@@ -183,9 +193,11 @@ export const MigrationEntryDesktop = ({
</Collapse> </Collapse>
</> </>
); );
}; },
);
export const MigrationEntry = ({ entry: propEntry, isMigrating }: { entry: TMigrationEntry; isMigrating: boolean }) => { export const MigrationEntry = memo(
({ entry: propEntry, isMigrating }: { entry: TMigrationEntry; isMigrating: boolean }) => {
const isTabletWidth = MediaQuery.useIsTabletWidth(); const isTabletWidth = MediaQuery.useIsTabletWidth();
const entry = useMemo(() => MigrationManager.getUpToDateMigrationEntry(propEntry), [propEntry]); const entry = useMemo(() => MigrationManager.getUpToDateMigrationEntry(propEntry), [propEntry]);
@@ -238,4 +250,5 @@ export const MigrationEntry = ({ entry: propEntry, isMigrating }: { entry: TMigr
/> />
</Paper> </Paper>
); );
}; },
);

View File

@@ -24,14 +24,10 @@ import Button from '@mui/material/Button';
import CardActionArea from '@mui/material/CardActionArea'; import CardActionArea from '@mui/material/CardActionArea';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
import { memo } from 'react';
export const MigrationMatchedEntry = ({ export const MigrationMatchedEntry = memo(
sourceMangaId, ({ sourceMangaId, entry }: { sourceMangaId: MangaIdInfo['id']; entry: MigrationMatch }) => {
entry,
}: {
sourceMangaId: MangaIdInfo['id'];
entry: MigrationMatch;
}) => {
const { t } = useLingui(); const { t } = useLingui();
return ( return (
@@ -81,7 +77,8 @@ export const MigrationMatchedEntry = ({
<Button <Button
variant="outlined" variant="outlined"
{...MUIUtil.preventRippleProp({ {...MUIUtil.preventRippleProp({
onClick: () => MigrationManager.selectMatch(sourceMangaId, entry.id, entry.sourceId), onClick: () =>
MigrationManager.selectMatch(sourceMangaId, entry.id, entry.sourceId),
})} })}
>{t`Select`}</Button> >{t`Select`}</Button>
))()} ))()}
@@ -89,4 +86,5 @@ export const MigrationMatchedEntry = ({
</CardActionArea> </CardActionArea>
</MigrationEntryCard> </MigrationEntryCard>
); );
}; },
);

View File

@@ -20,8 +20,9 @@ import { useLingui } from '@lingui/react/macro';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
import { memo } from 'react';
export const MigrationSourceEntry = (entry: TMigrationEntry) => { export const MigrationSourceEntry = memo((entry: TMigrationEntry) => {
const { const {
mangaId, mangaId,
mangaThumbnailUrl, mangaThumbnailUrl,
@@ -94,4 +95,4 @@ export const MigrationSourceEntry = (entry: TMigrationEntry) => {
</Stack> </Stack>
</Box> </Box>
); );
}; });