Added random sorting option for library (#1129)

* Update useGetVisibleLibraryMangas.ts

Begin implementation of random sort

* Update LibraryOptionsPanel.tsx

Added random sort option

* Update Library.types.ts

Adding random to LibrarySortMode types

* Moved sorting process to before filter

Changed the order of the sorting/filter process. Now sorts through entries before filtering them.

* Implementing Reviewer Feedback: Replacing Random Icon

Replaced icon with Replay icon from mui.

* Update SortRadioInput.tsx

Replaced random icon with replay icon from mui to replicate mihon functionality

* Fixed Formatting

* Update CHANGELOG.md

Added proposed changes from pull request.

* Update CHANGELOG.md

Co-authored-by: schroda <50052685+schroda@users.noreply.github.com>

---------

Co-authored-by: jburkh10 <jburkh10@terpmail.umd.edu>
Co-authored-by: schroda <50052685+schroda@users.noreply.github.com>
This commit is contained in:
themaskedshell
2026-07-20 19:01:28 -04:00
committed by GitHub
parent 8b8da30335
commit 08dcd932dc
6 changed files with 53 additions and 22 deletions

View File

@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- (**Reader**) Add setting to keep the screen on while reading - (**Reader**) Add setting to keep the screen on while reading
- (**Extension**) Support installing external JARs - (**Extension**) Support installing external JARs
- (**Reader**) Add auto background color setting - (**Reader**) Add auto background color setting
- (**Library**) Add random sort option to library
### Changed ### Changed

View File

@@ -8,6 +8,7 @@
import ArrowDownward from '@mui/icons-material/ArrowDownward'; import ArrowDownward from '@mui/icons-material/ArrowDownward';
import ArrowUpward from '@mui/icons-material/ArrowUpward'; import ArrowUpward from '@mui/icons-material/ArrowUpward';
import Replay from '@mui/icons-material/Replay';
import { memo } from 'react'; import { memo } from 'react';
import type { RadioInputProps } from '@/base/components/inputs/RadioInput.tsx'; import type { RadioInputProps } from '@/base/components/inputs/RadioInput.tsx';
import { RadioInput } from '@/base/components/inputs/RadioInput.tsx'; import { RadioInput } from '@/base/components/inputs/RadioInput.tsx';
@@ -22,3 +23,7 @@ export const SortRadioInput = memo(({ sortDescending, ...rest }: IProps) => (
{...rest} {...rest}
/> />
)); ));
export const SortRadioInputRandom = memo(({ sortDescending, ...rest }: IProps) => (
<RadioInput checkedIcon={<Replay color="primary" />} {...rest} />
));

View File

@@ -28,7 +28,8 @@ export type LibrarySortMode =
| 'dateAdded' | 'dateAdded'
| 'lastRead' | 'lastRead'
| 'latestFetchedChapter' | 'latestFetchedChapter'
| 'latestUploadedChapter'; | 'latestUploadedChapter'
| 'random';
export interface LibraryOptions { export interface LibraryOptions {
// sort options // sort options

View File

@@ -15,7 +15,7 @@ import { useMemo } from 'react';
import uniqBy from 'lodash/fp/uniqBy'; import uniqBy from 'lodash/fp/uniqBy';
import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx'; import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx';
import { RadioInput } from '@/base/components/inputs/RadioInput.tsx'; import { RadioInput } from '@/base/components/inputs/RadioInput.tsx';
import { SortRadioInput } from '@/base/components/inputs/SortRadioInput.tsx'; import { SortRadioInput, SortRadioInputRandom } from '@/base/components/inputs/SortRadioInput.tsx';
import { ThreeStateCheckboxInput } from '@/base/components/inputs/ThreeStateCheckboxInput.tsx'; import { ThreeStateCheckboxInput } from '@/base/components/inputs/ThreeStateCheckboxInput.tsx';
import { OptionsTabs } from '@/base/components/modals/OptionsTabs.tsx'; import { OptionsTabs } from '@/base/components/modals/OptionsTabs.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
@@ -51,6 +51,7 @@ const SORT_OPTIONS: [LibrarySortMode, MessageDescriptor][] = [
['lastRead', msg`Recently read`], ['lastRead', msg`Recently read`],
['latestFetchedChapter', msg`Latest fetched chapter`], ['latestFetchedChapter', msg`Latest fetched chapter`],
['latestUploadedChapter', msg`Latest uploaded chapter`], ['latestUploadedChapter', msg`Latest uploaded chapter`],
['random', msg`Random`],
]; ];
export const LibraryOptionsPanel = ({ export const LibraryOptionsPanel = ({
@@ -174,19 +175,33 @@ export const LibraryOptionsPanel = ({
); );
} }
if (key === 'sort') { if (key === 'sort') {
return SORT_OPTIONS.map(([mode, label]) => ( return SORT_OPTIONS.map(([mode, label]) =>
<SortRadioInput mode === 'random' ? (
key={mode} <SortRadioInputRandom
label={t(label)} key={mode}
checked={categoryLibraryOptions.sortBy === mode} label={t(label)}
sortDescending={categoryLibraryOptions.sortDesc} checked={categoryLibraryOptions.sortBy === mode}
onClick={() => sortDescending={categoryLibraryOptions.sortDesc}
mode !== categoryLibraryOptions.sortBy onClick={() =>
? updateCategoryLibraryOptions('sortBy', mode) mode !== categoryLibraryOptions.sortBy
: updateCategoryLibraryOptions('sortDesc', !categoryLibraryOptions.sortDesc) ? updateCategoryLibraryOptions('sortBy', mode)
} : updateCategoryLibraryOptions('sortDesc', !categoryLibraryOptions.sortDesc)
/> }
)); />
) : (
<SortRadioInput
key={mode}
label={t(label)}
checked={categoryLibraryOptions.sortBy === mode}
sortDescending={categoryLibraryOptions.sortDesc}
onClick={() =>
mode !== categoryLibraryOptions.sortBy
? updateCategoryLibraryOptions('sortBy', mode)
: updateCategoryLibraryOptions('sortDesc', !categoryLibraryOptions.sortDesc)
}
/>
),
);
} }
if (key === 'display') { if (key === 'display') {
return ( return (

View File

@@ -182,6 +182,8 @@ const sortByNumber = (a: number | string = 0, b: number | string = 0) => Number(
const sortByString = (a: string, b: string): number => a.localeCompare(b, undefined, { sensitivity: 'base' }); const sortByString = (a: string, b: string): number => a.localeCompare(b, undefined, { sensitivity: 'base' });
const sortByRandom = () => Math.floor(Math.random() * 3 - 1);
type TMangaSort = MangaTitleInfo & type TMangaSort = MangaTitleInfo &
MangaInLibraryInfo & MangaInLibraryInfo &
MangaUnreadInfo & MangaUnreadInfo &
@@ -213,6 +215,8 @@ const sortManga = <Manga extends TMangaSort>(
return (a, b) => sortByNumber(a.latestFetchedChapter?.fetchedAt, b.latestFetchedChapter?.fetchedAt); return (a, b) => sortByNumber(a.latestFetchedChapter?.fetchedAt, b.latestFetchedChapter?.fetchedAt);
case 'totalChapters': case 'totalChapters':
return (a, b) => sortByNumber(a.chapters.totalCount, b.chapters.totalCount); return (a, b) => sortByNumber(a.chapters.totalCount, b.chapters.totalCount);
case 'random':
return () => sortByRandom();
default: default:
return () => 0; return () => 0;
} }
@@ -256,14 +260,19 @@ export const useGetVisibleLibraryMangas = <Manga extends MangaIdInfo & TMangasFi
} = options; } = options;
const { settings } = useMetadataServerSettings(); const { settings } = useMetadataServerSettings();
const sortedMangas = useMemo(
() => sortManga(mangas, options.sortBy, options.sortDesc),
[mangas, options.sortBy, options.sortDesc],
);
const filteredMangas = useMemo( const filteredMangas = useMemo(
() => () =>
filterMangas(mangas, query, { filterMangas(sortedMangas, query, {
...options, ...options,
ignoreFilters: settings.ignoreFilters, ignoreFilters: settings.ignoreFilters,
}), }),
[ [
mangas, sortedMangas,
query, query,
hasUnreadChapters, hasUnreadChapters,
hasReadChapters, hasReadChapters,
@@ -276,10 +285,6 @@ export const useGetVisibleLibraryMangas = <Manga extends MangaIdInfo & TMangasFi
settings.ignoreFilters, settings.ignoreFilters,
], ],
); );
const sortedMangas = useMemo(
() => sortManga(filteredMangas, options.sortBy, options.sortDesc),
[filteredMangas, options.sortBy, options.sortDesc],
);
const isATrackFilterActive = Object.values(options.hasTrackerBinding).some( const isATrackFilterActive = Object.values(options.hasTrackerBinding).some(
(trackFilterState) => trackFilterState != null, (trackFilterState) => trackFilterState != null,
@@ -299,7 +304,7 @@ export const useGetVisibleLibraryMangas = <Manga extends MangaIdInfo & TMangasFi
mangas.length > 0; mangas.length > 0;
return { return {
visibleMangas: sortedMangas, visibleMangas: filteredMangas,
showFilteredOutMessage, showFilteredOutMessage,
filterKey: `${JSON.stringify(options)}${settings.ignoreFilters}`, filterKey: `${JSON.stringify(options)}${settings.ignoreFilters}`,
}; };

View File

@@ -2942,6 +2942,10 @@ msgstr "Queued"
msgid "Quick settings" msgid "Quick settings"
msgstr "Quick settings" msgstr "Quick settings"
#: src/features/library/components/LibraryOptionsPanel.tsx
msgid "Random"
msgstr "Random"
#: src/features/reader/settings/screens/GlobalReaderSettings.tsx #: src/features/reader/settings/screens/GlobalReaderSettings.tsx
#: src/features/settings/screens/Settings.tsx #: src/features/settings/screens/Settings.tsx
msgid "Reader" msgid "Reader"