Add sort options to migration list
This commit is contained in:
@@ -61,6 +61,7 @@ const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
|
||||
'migrateCategories',
|
||||
'migrateTracking',
|
||||
'deleteChapters',
|
||||
'migrateSortSettings',
|
||||
|
||||
// browse
|
||||
'hideLibraryEntries',
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -55,7 +55,7 @@ export function Browse() {
|
||||
<Extensions tabsMenuHeight={tabsMenuHeight} />
|
||||
</TabPanel>
|
||||
<TabPanel index={2} currentIndex={tabNum}>
|
||||
<Migration />
|
||||
<Migration tabsMenuHeight={tabsMenuHeight} />
|
||||
</TabPanel>
|
||||
</TabsWrapper>
|
||||
);
|
||||
|
||||
25
src/screens/Migration.constants.ts
Normal file
25
src/screens/Migration.constants.ts
Normal 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,
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
26
src/screens/Migration.types.ts
Normal file
26
src/screens/Migration.types.ts
Normal 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'];
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user