diff --git a/packages/core/src/db/schemas.ts b/packages/core/src/db/schemas.ts index 121d9db3..1a71b7e3 100644 --- a/packages/core/src/db/schemas.ts +++ b/packages/core/src/db/schemas.ts @@ -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, diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index ff22d0bb..5bb41a45 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -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; diff --git a/packages/frontend/src/components/menu/addons.tsx b/packages/frontend/src/components/menu/addons.tsx index b3f75cad..bc463870 100644 --- a/packages/frontend/src/components/menu/addons.tsx +++ b/packages/frontend/src/components/menu/addons.tsx @@ -1437,11 +1437,6 @@ function SortableCatalogItem({ {/* Desktop Controls - only visible on medium screens and up */}
-
+
diff --git a/packages/frontend/src/components/menu/sorting.tsx b/packages/frontend/src/components/menu/sorting.tsx index 9ad96ef5..20e0c1a4 100644 --- a/packages/frontend/src/components/menu/sorting.tsx +++ b/packages/frontend/src/components/menu/sorting.tsx @@ -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() {
+ + + + How Sorting Works + + +
+

+ The sorting system uses a hierarchical approach to determine + how content is sorted: +

+
    +
  1. + Primary Sorts: Define + the main sorting order for different content types: +
      +
    • + Global: Used as a fallback when no specific type sort is + defined (You most likely just want to define this and + ignore everything else.) +
    • +
    • Movies: Specific sorting for movies
    • +
    • Series: Specific sorting for TV series
    • +
    • Anime: Specific sorting for anime content
    • +
    +
  2. +
  3. + + Cached/Uncached Sorting: + {' '} + If your primary sort has "cached" at the top: +
      +
    • Content is split into cached and uncached groups
    • +
    • + Each group is sorted separately using its specific sort + criteria +
    • +
    • + Groups are then combined based on the cached sort + direction +
    • +
    • + Type-specific cached/uncached sorts (e.g., "Cached + Movies") take priority over global cached/uncached sorts +
    • +
    +
  4. +
+

+ Note: The system will use the most specific sort criteria + available for the content type and cache status. +

+
+
+
+
+