Add sort options to migration list

This commit is contained in:
schroda
2024-09-30 04:01:23 +02:00
parent c2fbf0253c
commit 6ca16b9449
8 changed files with 174 additions and 21 deletions

View File

@@ -423,10 +423,12 @@
},
"sort": {
"label": {
"asc": "Ascending",
"by_chapter_number": "By chapter number",
"by_fetch_date": "By date fetched",
"by_source": "By source",
"by_upload_date": "By upload date"
"by_upload_date": "By upload date",
"desc": "Descending"
}
},
"time": {
@@ -735,6 +737,10 @@
"search": {
"title": "$t(migrate.title) \"{{title}}\""
},
"sort": {
"by_manga_count": "By manga count",
"by_source_name": "By source name"
},
"title": "Migrate"
},
"reader": {

View File

@@ -61,6 +61,7 @@ const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
'migrateCategories',
'migrateTracking',
'deleteChapters',
'migrateSortSettings',
// browse
'hideLibraryEntries',

View File

@@ -10,6 +10,7 @@ import {
AllowedMetadataValueTypes,
AppMetadataKeys,
Metadata,
MetadataMigrationSettings,
MetadataServerSettingKeys,
MetadataServerSettings,
MetadataThemeSettings,
@@ -19,6 +20,7 @@ import { convertFromGqlMeta, getMetadataFrom, requestUpdateServerMetadata } from
import { jsonSaveParse } from '@/util/HelperFunctions.ts';
import { DEFAULT_DEVICE } from '@/util/device.ts';
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
import { DEFAULT_SORT_SETTINGS } from '@/screens/Migration.constants.ts';
export const getDefaultSettings = (): MetadataServerSettings => ({
// downloads
@@ -41,6 +43,7 @@ export const getDefaultSettings = (): MetadataServerSettings => ({
migrateCategories: true,
migrateTracking: true,
deleteChapters: true,
migrateSortSettings: DEFAULT_SORT_SETTINGS,
// browse
hideLibraryEntries: false,
@@ -64,6 +67,7 @@ export const convertSettingsToMetadata = (
...settings,
devices: JSON.stringify(settings.devices),
customThemes: JSON.stringify(settings.customThemes),
migrateSortSettings: JSON.stringify(settings.migrateSortSettings),
});
export const convertMetadataToSettings = (
@@ -76,6 +80,10 @@ export const convertMetadataToSettings = (
customThemes:
jsonSaveParse<MetadataThemeSettings['customThemes']>((metadata.customThemes as string) ?? '') ??
getDefaultSettings().customThemes,
migrateSortSettings:
jsonSaveParse<MetadataMigrationSettings['migrateSortSettings']>(
(metadata.migrateSortSettings as string) ?? '',
) ?? getDefaultSettings().migrateSortSettings,
}) satisfies MetadataServerSettings;
const getMetadataServerSettingsWithDefaultFallback = (

View File

@@ -55,7 +55,7 @@ export function Browse() {
<Extensions tabsMenuHeight={tabsMenuHeight} />
</TabPanel>
<TabPanel index={2} currentIndex={tabNum}>
<Migration />
<Migration tabsMenuHeight={tabsMenuHeight} />
</TabPanel>
</TabsWrapper>
);

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { SortBy, SortOrder } from '@/screens/Migration.types.ts';
import { TranslationKey } from '@/typings.ts';
export const sortByToTranslationKey: Record<SortBy, TranslationKey> = {
[SortBy.SOURCE_NAME]: 'migrate.sort.by_source_name',
[SortBy.MANGA_COUNT]: 'migrate.sort.by_manga_count',
};
export const sortOrderToTranslationKey: Record<SortBy, TranslationKey> = {
[SortOrder.ASC]: 'global.sort.label.asc',
[SortOrder.DESC]: 'global.sort.label.desc',
};
export const DEFAULT_SORT_SETTINGS = {
sortBy: SortBy.SOURCE_NAME,
sortOrder: SortOrder.ASC,
};

View File

@@ -9,45 +9,89 @@
import { useTranslation } from 'react-i18next';
import { useMemo } from 'react';
import List from '@mui/material/List';
import Stack from '@mui/material/Stack';
import IconButton from '@mui/material/IconButton';
import SortByAlphaIcon from '@mui/icons-material/SortByAlpha';
import TagIcon from '@mui/icons-material/Tag';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import Tooltip from '@mui/material/Tooltip';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
import { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
import { MigrationCard, TMigratableSource } from '@/components/MigrationCard.tsx';
import { StyledGroupItemWrapper } from '@/components/virtuoso/StyledGroupItemWrapper.tsx';
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
import { SortBy, SortOrder, SortSettings, TMigratableSourcesResult } from '@/screens/Migration.types.ts';
import { sortByToTranslationKey, sortOrderToTranslationKey } from '@/screens/Migration.constants';
import {
createUpdateMetadataServerSettings,
useMetadataServerSettings,
} from '@/lib/metadata/metadataServerSettings.ts';
import { makeToast } from '@/components/util/Toast.tsx';
import { useNavBarContext } from '@/components/context/NavbarContext.tsx';
type TMigratableSourcesResult = GetMigratableSourcesQuery['mangas']['nodes'];
type TMigratableSources = Record<string, TMigratableSource>;
const getMigratableSources = (mangas?: TMigratableSourcesResult): TMigratableSources => {
const getMigratableSources = (
mangas: TMigratableSourcesResult | undefined,
{ sortBy, sortOrder }: SortSettings,
): TMigratableSource[] => {
if (!mangas) {
return {};
return [];
}
const uniqueSources: TMigratableSources = {};
const sourceBySourceId: Record<string, TMigratableSource> = {};
mangas.forEach(({ sourceId, source }) => {
const uniqueSource = uniqueSources[sourceId] ?? {
const uniqueSource = sourceBySourceId[sourceId] ?? {
...{ id: sourceId, name: sourceId, lang: 'unknown', iconUrl: null, mangaCount: 0, ...source },
};
uniqueSources[sourceId] = {
sourceBySourceId[sourceId] = {
...uniqueSource,
mangaCount: uniqueSource.mangaCount + 1,
};
});
return uniqueSources;
const sourcesSortedBy = Object.values(sourceBySourceId).toSorted((a, b) => {
switch (sortBy) {
case SortBy.SOURCE_NAME:
return a.name.localeCompare(b.name);
case SortBy.MANGA_COUNT:
return a.mangaCount - b.mangaCount;
default:
throw new Error(`Unexpected "sortBy" "${sortBy}"`);
}
});
switch (sortOrder) {
case SortOrder.ASC:
return sourcesSortedBy;
case SortOrder.DESC:
return sourcesSortedBy.toReversed();
default:
throw new Error(`Unexpected "sortOrder" "${sortOrder}"`);
}
};
export const Migration = () => {
export const Migration = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => {
const { t } = useTranslation();
const { appBarHeight } = useNavBarContext();
const {
settings: { migrateSortSettings },
} = useMetadataServerSettings();
const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>(() =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'),
);
const { sortBy, sortOrder } = migrateSortSettings;
const { data, loading, error, refetch } = requestManager.useGetMigratableSources({
notifyOnNetworkStatusChange: true,
});
const migratableSources = useMemo(() => getMigratableSources(data?.mangas.nodes), [data?.mangas.nodes]);
const migratableSources = useMemo(
() => getMigratableSources(data?.mangas.nodes, migrateSortSettings),
[data?.mangas.nodes, migrateSortSettings],
);
if (loading) {
return <LoadingPlaceholder />;
@@ -64,12 +108,53 @@ export const Migration = () => {
}
return (
<List>
{Object.values(migratableSources).map((migratableSource) => (
<StyledGroupItemWrapper key={migratableSource.id}>
<MigrationCard {...migratableSource} />
</StyledGroupItemWrapper>
))}
</List>
<>
<Stack
sx={{
position: 'sticky',
top: `${appBarHeight + tabsMenuHeight}px`,
flexDirection: 'row',
justifyContent: 'end',
alignItems: 'center',
gap: 1,
p: 1,
backgroundColor: 'background.default',
zIndex: 1,
}}
>
<Tooltip title={t(sortByToTranslationKey[sortBy])}>
<IconButton
size="large"
color="inherit"
onClick={() =>
updateMetadataServerSettings('migrateSortSettings', { sortBy: (sortBy + 1) % 2, sortOrder })
}
>
{sortBy ? <TagIcon /> : <SortByAlphaIcon />}
</IconButton>
</Tooltip>
<Tooltip title={t(sortOrderToTranslationKey[sortOrder])}>
<IconButton
size="large"
color="inherit"
onClick={() =>
updateMetadataServerSettings('migrateSortSettings', {
sortBy,
sortOrder: (sortOrder + 1) % 2,
})
}
>
{sortOrder ? <ArrowDownwardIcon /> : <ArrowUpwardIcon />}
</IconButton>
</Tooltip>
</Stack>
<List sx={{ p: 0 }}>
{migratableSources.map((migratableSource) => (
<StyledGroupItemWrapper key={migratableSource.id}>
<MigrationCard {...migratableSource} />
</StyledGroupItemWrapper>
))}
</List>
</>
);
};

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
export enum SortBy {
SOURCE_NAME,
MANGA_COUNT,
}
export enum SortOrder {
ASC,
DESC,
}
export interface SortSettings {
sortBy: SortBy;
sortOrder: SortOrder;
}
export type TMigratableSourcesResult = GetMigratableSourcesQuery['mangas']['nodes'];

View File

@@ -22,6 +22,7 @@ import {
TrackerType,
} from '@/lib/graphql/generated/graphql.ts';
import { AppTheme } from '@/lib/ui/AppThemes.ts';
import { SortSettings } from '@/screens/Migration.types.ts';
type GenericLocation<State = any> = Omit<Location, 'state'> & { state?: State };
@@ -152,6 +153,7 @@ export type MetadataMigrationSettings = {
migrateCategories: boolean;
migrateTracking: boolean;
deleteChapters: boolean;
migrateSortSettings: SortSettings;
};
export type MetadataBrowseSettings = {