feat: add anime specific sorting and add help box to sort menu

This commit is contained in:
Viren070
2025-06-14 22:40:53 +01:00
parent 73b4d0b99f
commit 77ee7b48c4
4 changed files with 111 additions and 19 deletions
+3
View File
@@ -314,6 +314,7 @@ export const UserDataSchema = z.object({
// results must be from either a movie or series search, so we can safely apply different sort criteria.
movies: z.array(SortCriterion).optional(),
series: z.array(SortCriterion).optional(),
anime: z.array(SortCriterion).optional(),
// cached and uncached results are a sort criteria themselves, so this can only be applied when cache is high enough in the global
// sort criteria, and we would have to split the results into two (cached and uncached) lists, and then apply both sort criteria below
// and then merge the results.
@@ -323,6 +324,8 @@ export const UserDataSchema = z.object({
uncachedMovies: z.array(SortCriterion).optional(),
cachedSeries: z.array(SortCriterion).optional(),
uncachedSeries: z.array(SortCriterion).optional(),
cachedAnime: z.array(SortCriterion).optional(),
uncachedAnime: z.array(SortCriterion).optional(),
}),
rpdbApiKey: z.string().optional(),
formatter: Formatter,
+30 -13
View File
@@ -2391,15 +2391,16 @@ ${errorStreams.length > 0 ? ` ❌ Errors : ${errorStreams.map((s) => `
}
private sortStreams(streams: ParsedStream[], type: string): ParsedStream[] {
let sortCriteria = this.userData.sortCriteria.global;
let primarySortCriteria = this.userData.sortCriteria.global;
let cachedSortCriteria = this.userData.sortCriteria.cached;
let uncachedSortCriteria = this.userData.sortCriteria.uncached;
const start = Date.now();
if (type === 'movie' && this.userData.sortCriteria?.movies?.length) {
logger.info('Using movie sort criteria');
sortCriteria = this.userData.sortCriteria?.movies;
if (type === 'movie') {
if (this.userData.sortCriteria?.movies?.length) {
primarySortCriteria = this.userData.sortCriteria?.movies;
}
if (this.userData.sortCriteria?.cachedMovies?.length) {
cachedSortCriteria = this.userData.sortCriteria?.cachedMovies;
}
@@ -2408,9 +2409,10 @@ ${errorStreams.length > 0 ? ` ❌ Errors : ${errorStreams.map((s) => `
}
}
if (type === 'series' && this.userData.sortCriteria?.series?.length) {
logger.info('Using series sort criteria');
sortCriteria = this.userData.sortCriteria?.series;
if (type === 'series') {
if (this.userData.sortCriteria?.series?.length) {
primarySortCriteria = this.userData.sortCriteria?.series;
}
if (this.userData.sortCriteria?.cachedSeries?.length) {
cachedSortCriteria = this.userData.sortCriteria?.cachedSeries;
}
@@ -2418,13 +2420,26 @@ ${errorStreams.length > 0 ? ` ❌ Errors : ${errorStreams.map((s) => `
uncachedSortCriteria = this.userData.sortCriteria?.uncachedSeries;
}
}
if (type === 'anime') {
if (this.userData.sortCriteria?.anime?.length) {
primarySortCriteria = this.userData.sortCriteria?.anime;
}
if (this.userData.sortCriteria?.cachedAnime?.length) {
cachedSortCriteria = this.userData.sortCriteria?.cachedAnime;
}
if (this.userData.sortCriteria?.uncachedAnime?.length) {
uncachedSortCriteria = this.userData.sortCriteria?.uncachedAnime;
}
}
let sortedStreams = [];
if (
cachedSortCriteria?.length &&
uncachedSortCriteria?.length &&
sortCriteria.length > 0 &&
sortCriteria[0].key === 'cached'
primarySortCriteria.length > 0 &&
primarySortCriteria[0].key === 'cached'
) {
logger.info(
'Splitting streams into cached and uncached and using separate sort criteria'
@@ -2457,16 +2472,18 @@ ${errorStreams.length > 0 ? ` ❌ Errors : ${errorStreams.map((s) => `
return 0;
});
if (sortCriteria[0].direction === 'desc') {
if (primarySortCriteria[0].direction === 'desc') {
sortedStreams = [...cachedSorted, ...uncachedSorted];
} else {
sortedStreams = [...uncachedSorted, ...cachedSorted];
}
} else {
logger.debug(`using sort criteria: ${JSON.stringify(sortCriteria)}`);
logger.debug(
`using sort criteria: ${JSON.stringify(primarySortCriteria)}`
);
sortedStreams = streams.slice().sort((a, b) => {
const aKey = this.dynamicSortKey(a, sortCriteria, type);
const bKey = this.dynamicSortKey(b, sortCriteria, type);
const aKey = this.dynamicSortKey(a, primarySortCriteria, type);
const bKey = this.dynamicSortKey(b, primarySortCriteria, type);
for (let i = 0; i < aKey.length; i++) {
if (aKey[i] < bKey[i]) return -1;
@@ -1437,11 +1437,6 @@ function SortableCatalogItem({
{/* Desktop Controls - only visible on medium screens and up */}
<div className="hidden md:flex items-center justify-end gap-2 absolute top-4 right-4">
<Switch
value={catalog.enabled ?? true}
onValueChange={onToggleEnabled}
moreHelp="Enable or disable this catalog from being used"
/>
<div className="flex items-center gap-1">
<IconButton
rounded
@@ -1458,6 +1453,11 @@ function SortableCatalogItem({
title="Move to bottom"
/>
</div>
<Switch
value={catalog.enabled ?? true}
onValueChange={onToggleEnabled}
moreHelp="Enable or disable this catalog from being used"
/>
</div>
</div>
@@ -28,6 +28,12 @@ import {
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { CSS } from '@dnd-kit/utilities';
import { ArrowDownAZ, ArrowUpAZ } from 'lucide-react';
import {
Accordion,
AccordionTrigger,
AccordionContent,
AccordionItem,
} from '../ui/accordion';
export function SortingMenu() {
return (
@@ -212,9 +218,72 @@ function Content() {
</div>
</div>
<div className="space-y-4">
<Accordion
type="single"
collapsible
className="border rounded-[--radius-md]"
triggerClass="dark:bg-[--paper]"
contentClass="!pt-2 dark:bg-[--paper]"
>
<AccordionItem value="how-sorting-works">
<AccordionTrigger className="bg-gray-900 rounded-[--radius-md]">
How Sorting Works
</AccordionTrigger>
<AccordionContent>
<div className="space-y-4">
<p>
The sorting system uses a hierarchical approach to determine
how content is sorted:
</p>
<ol className="list-decimal list-inside space-y-2">
<li>
<span className="font-medium">Primary Sorts:</span> Define
the main sorting order for different content types:
<ul className="list-disc list-inside ml-4 mt-1 space-y-1">
<li>
Global: Used as a fallback when no specific type sort is
defined (You most likely just want to define this and
ignore everything else.)
</li>
<li>Movies: Specific sorting for movies</li>
<li>Series: Specific sorting for TV series</li>
<li>Anime: Specific sorting for anime content</li>
</ul>
</li>
<li>
<span className="font-medium">
Cached/Uncached Sorting:
</span>{' '}
If your primary sort has "cached" at the top:
<ul className="list-disc list-inside ml-4 mt-1 space-y-1">
<li>Content is split into cached and uncached groups</li>
<li>
Each group is sorted separately using its specific sort
criteria
</li>
<li>
Groups are then combined based on the cached sort
direction
</li>
<li>
Type-specific cached/uncached sorts (e.g., "Cached
Movies") take priority over global cached/uncached sorts
</li>
</ul>
</li>
</ol>
<p className="text-sm text-[--muted]">
Note: The system will use the most specific sort criteria
available for the content type and cache status.
</p>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
<SettingsCard
title="Type"
description="Here, you can define different sort criteria for different types of content. Most people will only need to define the global sort criteria, and nothing else. However, if you wanted different sorting for cached and uncached content, you can do that here. You have to define the 'main' criteria to have cached at the top, and then separately define the cached and uncached criteria. If you also want to define different sorting for movies and series, you would do the same."
description="Choose the type of sort criteria you want to currently edit."
>
<Select
label="Sort Order Type"
@@ -222,12 +291,15 @@ function Content() {
{ label: 'Global', value: 'global' },
{ label: 'Series', value: 'series' },
{ label: 'Movies', value: 'movies' },
{ label: 'Anime', value: 'anime' },
{ label: 'Cached', value: 'cached' },
{ label: 'Uncached', value: 'uncached' },
{ label: 'Cached Movies', value: 'cachedMovies' },
{ label: 'Uncached Movies', value: 'uncachedMovies' },
{ label: 'Cached Series', value: 'cachedSeries' },
{ label: 'Uncached Series', value: 'uncachedSeries' },
{ label: 'Cached Anime', value: 'cachedAnime' },
{ label: 'Uncached Anime', value: 'uncachedAnime' },
]}
value={currentSortType}
onValueChange={setCurrentSortType}