diff --git a/backend/src/user-data/library/library.controller.ts b/backend/src/user-data/library/library.controller.ts index 363407f..55c29f5 100644 --- a/backend/src/user-data/library/library.controller.ts +++ b/backend/src/user-data/library/library.controller.ts @@ -21,13 +21,13 @@ import { PaginationParamsDto, SuccessResponseDto, } from 'src/common/common.dto'; -import { MediaSourcesService } from 'src/users/media-sources/media-sources.service'; import { CatalogueFilter, LibraryItemDto, - MyListFilter, - MyListSortBy, - SortByDirection, + MyListOrder, + MyListStatusFilter, + MyListTypeFilter, + OrderDirection, } from './library.dto'; import { LibraryService } from './library.service'; @@ -35,33 +35,34 @@ import { LibraryService } from './library.service'; @Controller('users/:userId/library') @UseGuards(UserAccessControl) export class LibraryController { - constructor( - private libraryService: LibraryService, - private mediaSourceService: MediaSourcesService, - ) {} + constructor(private libraryService: LibraryService) {} @Get('my-list') - @ApiQuery({ name: 'filter', enum: MyListFilter, required: false }) - @ApiQuery({ name: 'sortBy', enum: MyListSortBy, required: false }) - @ApiQuery({ name: 'direction', enum: SortByDirection, required: false }) + @ApiQuery({ name: 'status', enum: MyListStatusFilter, required: false }) + @ApiQuery({ name: 'type', enum: MyListTypeFilter, required: false }) + @ApiQuery({ name: 'order', enum: MyListOrder, required: false }) + @ApiQuery({ name: 'direction', enum: OrderDirection, required: false }) @PaginatedApiOkResponse(LibraryItemDto) async getMyList( @GetPaginationParams() pagination: PaginationParamsDto, @Param('userId') userId: string, - @Query('filter', new ParseEnumPipe(MyListFilter, { optional: true })) - filter?: MyListFilter, - @Query('sortBy', new ParseEnumPipe(MyListSortBy, { optional: true })) - sortBy?: MyListSortBy, - @Query('direction', new ParseEnumPipe(SortByDirection, { optional: true })) - direction?: SortByDirection, + @Query('status', new ParseEnumPipe(MyListStatusFilter, { optional: true })) + status?: MyListStatusFilter, + @Query('type', new ParseEnumPipe(MyListTypeFilter, { optional: true })) + type?: MyListTypeFilter, + @Query('order', new ParseEnumPipe(MyListOrder, { optional: true })) + order?: MyListOrder, + @Query('direction', new ParseEnumPipe(OrderDirection, { optional: true })) + direction?: OrderDirection, ): Promise> { // const user = await this.userService.findOne(userId); const response = await this.libraryService.getMyList({ userId, pagination, - filter, - sortBy, + type, + status, + order, direction, }); diff --git a/backend/src/user-data/library/library.dto.ts b/backend/src/user-data/library/library.dto.ts index 778842f..4ee0d7d 100644 --- a/backend/src/user-data/library/library.dto.ts +++ b/backend/src/user-data/library/library.dto.ts @@ -2,20 +2,28 @@ import { ApiProperty, PickType } from '@nestjs/swagger'; import { TmdbItemDto } from 'src/metadata/tmdb/tmdb.dto'; import { LibraryItem } from './library.entity'; -export enum SortByDirection { +export enum OrderDirection { Asc = 'asc', Desc = 'desc', } -export enum MyListSortBy { - DateAdded = 'dateAdded', +export enum MyListOrder { + DateAdded = 'date-added', Name = 'name', - FirstReleaseDate = 'firstReleaseDate', - LastReleaseDate = 'lastReleaseDate', + FirstReleaseDate = 'first-release-date', + LastReleaseDate = 'last-release-date', } -export enum MyListFilter { - Movie = 'movie', +export enum MyListStatusFilter { + All = 'all', + Upcoming = 'upcoming', + Unwatched = 'unwatched', + Watched = 'watched', + ContinueWatching = 'continueWatching', +} + +export enum MyListTypeFilter { + Movies = 'movies', Series = 'series', All = 'all', } diff --git a/backend/src/user-data/library/library.service.ts b/backend/src/user-data/library/library.service.ts index d0acf5c..2947abc 100644 --- a/backend/src/user-data/library/library.service.ts +++ b/backend/src/user-data/library/library.service.ts @@ -6,13 +6,14 @@ import { } from 'src/common/common.dto'; import { MetadataService } from 'src/metadata/metadata.service'; import { MediaSourcesService } from 'src/users/media-sources/media-sources.service'; -import { Repository } from 'typeorm'; +import { Brackets, NotBrackets, Repository } from 'typeorm'; import { PlayState } from '../play-state/play-state.entity'; import { LibraryItemDto, - MyListFilter, - MyListSortBy, - SortByDirection, + MyListTypeFilter, + MyListOrder, + OrderDirection, + MyListStatusFilter, } from './library.dto'; import { LibraryItem } from './library.entity'; import { USER_LIBRARY_REPOSITORY } from './library.providers'; @@ -24,86 +25,212 @@ export class LibraryService { private readonly libraryRepository: Repository, private readonly metadataService: MetadataService, private readonly mediaSourceService: MediaSourcesService, - ) {} + ) { + // Make sure library items are cached + this.libraryRepository.find().then((r) => + r.forEach((i) => { + if (i.mediaType === 'movie') { + this.metadataService.getMovieByTmdbId(i.tmdbId); + } else if (i.mediaType === 'series') { + this.metadataService.getSeriesByTmdbId(i.tmdbId); + } + }), + ); + } /** TODO: decouple librayItem and movie/seriesItem */ async getMyList(options: { userId: string; pagination: PaginationParamsDto; - filter?: MyListFilter; - sortBy?: MyListSortBy; - direction?: SortByDirection; + type?: MyListTypeFilter; + status?: MyListStatusFilter; + order?: MyListOrder; + direction?: OrderDirection; }): Promise> { const { userId, pagination, - filter, - sortBy, - direction = SortByDirection.Desc, + type, + status, + order, + direction = OrderDirection.Desc, } = options; - const directon = direction === SortByDirection.Asc ? 'ASC' : 'DESC'; + // const order = { + // [MyListOrder.DateAdded]: { createdAt: directon } as const, + // [MyListOrder.Name]: { + // seriesMetadata: { + // name: directon, + // }, + // movieMetadata: { + // name: directon, + // }, + // } as const, + // [MyListOrder.FirstReleaseDate]: { + // movieMetadata: { + // releaseDate: directon, + // }, + // seriesMetadata: { + // firstReleaseDate: directon, + // }, + // } as const, + // [MyListOrder.LastReleaseDate]: { + // movieMetadata: { + // releaseDate: directon, + // }, + // seriesMetadata: { + // lastReleaseDate: directon, + // }, + // } as const, + // }[sortBy]; - const order = { - [MyListSortBy.DateAdded]: { createdAt: directon } as const, - [MyListSortBy.Name]: { - seriesMetadata: { - name: directon, - }, - movieMetadata: { - name: directon, - }, - } as const, - [MyListSortBy.FirstReleaseDate]: { - movieMetadata: { - releaseDate: directon, - }, - seriesMetadata: { - firstReleaseDate: directon, - }, - } as const, - [MyListSortBy.LastReleaseDate]: { - movieMetadata: { - releaseDate: directon, - }, - seriesMetadata: { - lastReleaseDate: directon, - }, - } as const, - }[sortBy]; - - const mediaType = filter - ? filter === MyListFilter.Movie + const mediaType = type + ? type === MyListTypeFilter.Movies ? MediaType.Movie : MediaType.Series : undefined; - const [items, total] = await this.libraryRepository.findAndCount({ - relations: { - playStates: true, - seriesMetadata: true, - movieMetadata: true, - }, - select: { - seriesMetadata: { - firstReleaseDate: true, - lastReleaseDate: true, - name: true, - }, - movieMetadata: { - releaseDate: true, - name: true, - }, - }, + // const [items, total] = await this.libraryRepository.findAndCount({ + // relations: { + // playStates: true, + // seriesMetadata: true, + // movieMetadata: true, + // }, + // select: { + // seriesMetadata: { + // firstReleaseDate: true, + // lastReleaseDate: true, + // name: true, + // }, + // movieMetadata: { + // releaseDate: true, + // name: true, + // }, + // }, + // where: { + // userId, + // ...(mediaType ? { mediaType } : {}), + // }, + // order, + // take: pagination.itemsPerPage, + // skip: pagination.itemsPerPage * (pagination.page - 1), + // }); - where: { - userId, - ...(mediaType ? { mediaType } : {}), - }, - order, - take: pagination.itemsPerPage, - skip: pagination.itemsPerPage * (pagination.page - 1), - }); + let builder = this.libraryRepository + .createQueryBuilder('libraryItem') + .leftJoinAndSelect('libraryItem.playStates', 'playStates') + .leftJoinAndSelect('libraryItem.movieMetadata', 'movieMetadata') + .leftJoinAndSelect('libraryItem.seriesMetadata', 'seriesMetadata') + .addSelect('libraryItem.createdAt', 'createdAt') + .addSelect(['libraryItem.createdAt', 'libraryItem.updatedAt']) + .where('libraryItem.userId = :userId', { userId }); + + if (mediaType) { + builder = builder.andWhere('libraryItem.mediaType = :mediaType', { + mediaType, + }); + } + + const watched = new Brackets((qb) => + qb + .where( + "(libraryItem.mediaType = 'movie' AND playStates.watched = true)", + ) + .orWhere( + new Brackets((qb) => + qb + .where("libraryItem.mediaType = 'series'") + .andWhere( + 'playStates.watched = true AND playStates.season = seriesMetadata.lastSeasonNumber AND playStates.episode = seriesMetadata.lastEpisodeNumber', + ), + ), + ), + ); + const upcoming = new Brackets((qb) => + qb + .where(watched) + .andWhere( + new Brackets((qb) => + qb + .where('seriesMetadata.nextReleaseDate > date("now")') + .orWhere('movieMetadata.releaseDate > date("now")'), + ), + ), + ); + const watchedAndNotUpcoming = new Brackets((qb) => + qb + .where(watched) + .andWhere( + new Brackets((qb) => + qb + .where('seriesMetadata.nextReleaseDate < date("now")') + .orWhere('movieMetadata.releaseDate < date("now")') + .orWhere( + '(seriesMetadata.nextReleaseDate IS NULL AND movieMetadata.releaseDate IS NULL)', + ), + ), + ), + ); + if (status === MyListStatusFilter.Upcoming) { + builder = builder.andWhere(upcoming); + } else if (status === MyListStatusFilter.Watched) { + builder = builder.andWhere(watchedAndNotUpcoming); + } else if (status === MyListStatusFilter.Unwatched) { + builder = builder.andWhere( + new Brackets((qb) => + qb + .where( + "(libraryItem.mediaType = 'movie' AND playStates.watched = false)", + ) + .orWhere( + "(libraryItem.mediaType = 'movie' AND playStates.watched IS NULL)", + ) + .orWhere( + new Brackets((qb) => + qb + .where("libraryItem.mediaType = 'series'") + .andWhere( + 'playStates.watched = false AND playStates.season = seriesMetadata.lastSeasonNumber AND playStates.episode = seriesMetadata.lastEpisodeNumber', + ), + ), + ) + .orWhere( + new Brackets((qb) => + qb.where("libraryItem.mediaType = 'series'").andWhere( + `NOT EXISTS ( + select 1 from play_state playStates + LEFT JOIN movie_metadata movieMetadata on playStates.tmdbId = movieMetadata.tmdbId + LEFT JOIN series_metadata seriesMetadata on playStates.tmdbId = seriesMetadata.tmdbId + where playStates.tmdbId = libraryItem.tmdbId AND playStates.userId = libraryItem.userId + AND seriesMetadata.lastEpisodeNumber = playStates.episode AND seriesMetadata.lastSeasonNumber = playStates.season + )`, + ), + ), + ), + ), + ); + } + + const DIRECTION = direction === OrderDirection.Asc ? 'ASC' : 'DESC'; + if (order === MyListOrder.Name) { + builder.addOrderBy('seriesMetadata.name', DIRECTION); + builder.addOrderBy('movieMetadata.name', DIRECTION); + } else if (order === MyListOrder.DateAdded) { + builder.addOrderBy('libraryItem.createdAt', DIRECTION); + } else if (order === MyListOrder.FirstReleaseDate) { + builder.addOrderBy('movieMetadata.releaseDate', DIRECTION); + builder.addOrderBy('seriesMetadata.firstReleaseDate', DIRECTION); + } else if (order === MyListOrder.LastReleaseDate) { + builder.addOrderBy('movieMetadata.releaseDate', DIRECTION); + builder.addOrderBy('seriesMetadata.lastReleaseDate', DIRECTION); + } + + const [items, total] = await builder + .take(pagination.itemsPerPage) + .skip(pagination.itemsPerPage * (pagination.page - 1)) + .getManyAndCount(); + + // console.log(builder.getQuery()); return { items, @@ -188,8 +315,9 @@ export class LibraryService { page: 1, }, userId: connection.mediaSource.userId, - filter: MyListFilter.All, - sortBy: MyListSortBy.DateAdded, + type: MyListTypeFilter.All, + status: MyListStatusFilter.All, + order: MyListOrder.DateAdded, }).then((res) => res.items); myListItems.forEach((i) => { diff --git a/package.json b/package.json index bd2fa9e..b9e7e94 100644 --- a/package.json +++ b/package.json @@ -96,4 +96,4 @@ } ] } -} +} \ No newline at end of file diff --git a/src/lib/apis/reiverr/reiverr.openapi.ts b/src/lib/apis/reiverr/reiverr.openapi.ts index 9696c57..f8e8cda 100644 --- a/src/lib/apis/reiverr/reiverr.openapi.ts +++ b/src/lib/apis/reiverr/reiverr.openapi.ts @@ -48,7 +48,7 @@ export interface Settings { export interface PlayState { id: string; tmdbId: number; - mediaType: 'Movie' | 'Series' | 'Episode'; + mediaType: 'movie' | 'series' | 'episode'; userId: string; season?: number; episode?: number; @@ -84,6 +84,9 @@ export interface SeriesMetadata { name?: string; firstReleaseDate?: string; lastReleaseDate?: string; + nextReleaseDate?: string; + lastSeasonNumber?: number; + lastEpisodeNumber?: number; libraryItems?: any[][]; updatedAt: string; } @@ -91,7 +94,7 @@ export interface SeriesMetadata { export interface PlayStateDto { id: string; tmdbId: number; - mediaType: 'Movie' | 'Series' | 'Episode'; + mediaType: 'movie' | 'series' | 'episode'; userId: string; season?: number; episode?: number; @@ -113,7 +116,7 @@ export interface PlayStateDto { export interface LibraryItem { id?: string; tmdbId: string; - mediaType: 'Movie' | 'Series' | 'Episode'; + mediaType: 'movie' | 'series'; movieMetadata?: MovieMetadata; seriesMetadata?: SeriesMetadata; userId: string; @@ -559,7 +562,7 @@ export interface TmdbItemDto { export interface LibraryItemDto { tmdbId: string; - mediaType: 'Movie' | 'Series' | 'Episode'; + mediaType: 'movie' | 'series'; playStates?: PlayStateDto[]; tmdbItem: TmdbItemDto; watched?: boolean; @@ -1602,8 +1605,9 @@ export class Api extends HttpClient extends HttpClient diff --git a/src/lib/pages/LibraryPage/CatalogueTab.svelte b/src/lib/pages/LibraryPage/CatalogueTab.svelte index 1c3660a..040ee02 100644 --- a/src/lib/pages/LibraryPage/CatalogueTab.svelte +++ b/src/lib/pages/LibraryPage/CatalogueTab.svelte @@ -3,11 +3,14 @@ import TmdbCard from '$lib/components/Card/TmdbCard.svelte'; import CardGrid from '$lib/components/CardGrid.svelte'; import Container from '$lib/components/Container.svelte'; + import { getStackRouterControls } from '$lib/components/StackRouter/StackRouter'; import { reiverrApi } from '$lib/stores/user.store'; import TabItem from './TabItem.svelte'; export let source: MediaSourceDto; + const { registrar } = getStackRouterControls(); + let filters: string[] = []; let selectedFilter = ''; @@ -45,7 +48,7 @@ {/each} {#await items then items} - + {#each items.map((i) => i.tmdbItem) as item (item.id)} {/each} diff --git a/src/lib/pages/LibraryPage/LibraryPage.svelte b/src/lib/pages/LibraryPage/LibraryPage.svelte index f47ed42..4d8ec05 100644 --- a/src/lib/pages/LibraryPage/LibraryPage.svelte +++ b/src/lib/pages/LibraryPage/LibraryPage.svelte @@ -3,39 +3,54 @@ import { useTabs } from '$lib/components/Tab/Tab'; import Tab from '$lib/components/Tab/Tab.svelte'; import TabContainer from '$lib/components/Tab/TabContainer.svelte'; + import { setScrollContext } from '$lib/stores/scroll.store'; import { user } from '$lib/stores/user.store'; import CatalogueTab from './CatalogueTab.svelte'; import MyListTab from './MyListTab.svelte'; import TabItem from './TabItem.svelte'; - const tab = useTabs(0, { remount: true }); + const { registrar: scrollRegistrar } = setScrollContext(); + + const tab = useTabs(0); const catalogues = $user?.mediaSources.filter((s) => s.capabilities.catalogues) ?? []; - + { + const el = detail.getHtmlElement(); + + if (el) { + scrollRegistrar(el); + } + }} +> { selectable.activateChild($tab); }} > - tab.set(0)}>My List + tab.set(0)}>My List {#each catalogues as catalogue, index} - tab.set(index + 1)}> + tab.set(index + 1)}> {catalogue.name} {/each} - - - - - {#each catalogues as catalogue, index} - - + + + + - {/each} - + {#each catalogues as catalogue, index} + + + + {/each} + + diff --git a/src/lib/pages/LibraryPage/LibraryPage.ts b/src/lib/pages/LibraryPage/LibraryPage.ts new file mode 100644 index 0000000..ca2867e --- /dev/null +++ b/src/lib/pages/LibraryPage/LibraryPage.ts @@ -0,0 +1,14 @@ +import { createLocalStorageStore } from '$lib/stores/localstorage.store'; + +export type MyListOrder = 'date-added' | 'name' | 'first-release-date' | 'last-release-date'; +export type MyListOrderDirection = 'asc' | 'desc'; + +export const libraryViewSettings = createLocalStorageStore<{ + order: MyListOrder; + direction: MyListOrderDirection; + separateWatched: boolean; +}>('library-view-settings', { + order: 'last-release-date', + direction: 'desc', + separateWatched: true +}); diff --git a/src/lib/pages/LibraryPage/MyListTab.svelte b/src/lib/pages/LibraryPage/MyListTab.svelte index ab18540..dc9ede9 100644 --- a/src/lib/pages/LibraryPage/MyListTab.svelte +++ b/src/lib/pages/LibraryPage/MyListTab.svelte @@ -3,228 +3,176 @@ import Button from '$lib/components/Button.svelte'; import Carousel from '$lib/components/Carousel/Carousel.svelte'; import Container from '$lib/components/Container.svelte'; + import FloatingHeader from '$lib/components/FloatingHeader.svelte'; import { createModal } from '$lib/components/Modal/modal.store'; import { getStackRouterControls } from '$lib/components/StackRouter/StackRouter'; + import TitleText from '$lib/components/TitleText.svelte'; import { scrollIntoView } from '$lib/selectable'; import { libraryItemsDataStore } from '$lib/stores/data.store'; - import { libraryViewSettings, type LibraryViewSettings } from '$lib/stores/localstorage.store'; + import { createLocalStorageStore } from '$lib/stores/localstorage.store'; + import { getScrollContext } from '$lib/stores/scroll.store'; import { MixerHorizontal } from 'radix-icons-svelte'; import { onDestroy } from 'svelte'; - import { derived, writable } from 'svelte/store'; + import { derived } from 'svelte/store'; import TmdbCard from '../../components/Card/TmdbCard.svelte'; import CardGrid from '../../components/CardGrid.svelte'; import OptionsDialog from './OptionsDialog.LibraryPage.svelte'; import TabItem from './TabItem.svelte'; + import { reiverrApi, user } from '$lib/stores/user.store'; + import { libraryViewSettings } from './LibraryPage'; - const { registrar, handleGoBack } = getStackRouterControls(); + const { registrar } = getStackRouterControls(); + const { topVisible } = getScrollContext(); let didMount = false; - let category = writable<'all' | 'series' | 'movies'>('all'); + let category: 'all' | 'series' | 'movies' = 'all'; - const { isLoading, unsubscribe, ...libraryItems } = libraryItemsDataStore.subscribe(); + $: upcoming = + $libraryViewSettings.separateWatched && $user?.id + ? reiverrApi.library + .getMyList($user.id, { + status: 'upcoming', + order: $libraryViewSettings.order, + type: category, + direction: $libraryViewSettings.direction + }) + .then((i) => i.data.items) + : Promise.resolve([]); - const sortedLibraryItems = derived( - [libraryItems, libraryViewSettings, category], - ([items, viewSettings, category]) => sortItems(items, viewSettings, category) - ); + $: watched = + $libraryViewSettings.separateWatched && $user?.id + ? reiverrApi.library + .getMyList($user.id, { + status: 'watched', + type: category, + order: $libraryViewSettings.order, + direction: $libraryViewSettings.direction + }) + .then((i) => i.data.items) + : Promise.resolve([]); - const libraryItemsCategorized = derived( - [sortedLibraryItems, libraryViewSettings], - ([items, viewSettings]) => { - let categorizedItems = { - upcoming: [] as LibraryItemDto[], - main: [] as LibraryItemDto[], - watched: [] as LibraryItemDto[] - }; - - if (!viewSettings.separateUpcoming && !viewSettings.separateWatched) { - return { main: items || [], upcoming: [], watched: [] }; - } - - for (const item of items) { - const releaseDate = new Date( - item.tmdbItem.release_date || - (item.watched && (item.tmdbItem.next_episode_to_air as any)?.air_date) || - 0 - ); - const hasFutureReleases = item.watched - ? item.tmdbItem.seasons?.some((s) => s.air_date === null) - : item.tmdbItem.last_air_date === null; - - if (viewSettings.separateUpcoming && (releaseDate > new Date() || hasFutureReleases)) { - categorizedItems.upcoming.push(item); - } else if (viewSettings.separateWatched && item.watched) { - categorizedItems.watched.push(item); - } else { - categorizedItems.main.push(item); - } - } - - categorizedItems.upcoming.sort((a, b) => { - const aReleaseDate = new Date( - a.tmdbItem.release_date || - a.tmdbItem.next_episode_to_air?.air_date || - new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 20 - ); - - const bReleaseDate = new Date( - b.tmdbItem.release_date || - (b.tmdbItem.next_episode_to_air as any)?.air_date || - new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 20 - ); - - return aReleaseDate > bReleaseDate ? 1 : -1; - }); - - return categorizedItems; - } - ); - - function sortItems( - items: LibraryItemDto[] | undefined, - viewSettings: LibraryViewSettings, - category: 'all' | 'series' | 'movies' - ) { - const filtered = - category === 'all' - ? items?.slice() - : items?.filter((i) => - category === 'series' ? i.mediaType === 'Series' : i.mediaType === 'Movie' - ); - - return ( - filtered?.sort((a, b) => { - // const aCreatedAt = a.createdAt; - // const bCreatedAt = b.createdAt; - - const aReleaseDate = a.tmdbItem.release_date || ''; - const bReleaseDate = b.tmdbItem.release_date || ''; - - const aFirstAirDate = a.tmdbItem.first_air_date || aReleaseDate; - const bFirstAirDate = b.tmdbItem.first_air_date || bReleaseDate; - - const aLastAirDate = a.tmdbItem.last_air_date || aFirstAirDate || aReleaseDate; - const bLastAirDate = b.tmdbItem.last_air_date || bFirstAirDate || bReleaseDate; - - const aTitle = a.tmdbItem.title || a.tmdbItem.name || ''; - - const bTitle = b.tmdbItem.title || b.tmdbItem.name || ''; - - const direction = viewSettings.sortDirection === 'asc' ? 1 : -1; - if (viewSettings.sortBy === 'date-added') { - // return direction * aCreatedAt.localeCompare(bCreatedAt); - return direction * aFirstAirDate.localeCompare(bFirstAirDate); - } else if (viewSettings.sortBy === 'first-release-date') { - return direction * aFirstAirDate.localeCompare(bFirstAirDate); - } else if (viewSettings.sortBy === 'last-release-date') { - return direction * aLastAirDate.localeCompare(bLastAirDate); - } else if (viewSettings.sortBy === 'title') { - return direction * aTitle.localeCompare(bTitle); - } - - return 0; - }) || [] - ); - } + $: items = $user?.id + ? reiverrApi.library + .getMyList($user.id, { + ...($libraryViewSettings.separateWatched ? { status: 'unwatched' } : {}), + type: category, + order: $libraryViewSettings.order, + direction: $libraryViewSettings.direction + }) + .then((i) => i.data.items) + : Promise.resolve([]); $: viewSettingsKey = $libraryViewSettings && Symbol(); - - onDestroy(() => { - unsubscribe(); - }); - - {#if !$isLoading} -
- - { - selectable.activateChild($category === 'all' ? 0 : $category === 'series' ? 1 : 2); - }} - > - category.set('all')}>All - category.set('series')}> - Series - - category.set('movies')}> - Movies - - - - + +

Library

+ +
+ + +
+ (didMount = true)} - focusedChild + class="flex space-x-4" + direction="horizontal" + on:blur={({ detail: selectable }) => { + selectable.activateChild(category === 'all' ? 0 : category === 'series' ? 1 : 2); + }} > - {#if $libraryItemsCategorized.main.length + $libraryItemsCategorized.upcoming.length + $libraryItemsCategorized.watched.length} - {#if $libraryItemsCategorized.upcoming.length} -
- - {#key viewSettingsKey} - {#each $libraryItemsCategorized.upcoming as item (item.tmdbId)} - - {/each} - {/key} - -
- {/if} - {#if $libraryItemsCategorized.main.length} -
-
My List
- - {#key viewSettingsKey} - {#each $libraryItemsCategorized.main as item, index (item.tmdbId)} - - {/each} - {/key} - -
- {/if} - {#if $libraryItemsCategorized.watched.length} -
-
Watched
- - {#key viewSettingsKey} - {#each $libraryItemsCategorized.watched as item (item.tmdbId)} - - {/each} - {/key} - -
- {/if} - {:else} - - You haven't added anything to your library + (category = 'all')}>All + (category = 'series')}> + Series + + (category = 'movies')}> + Movies + + + +
+ { + didMount = true; + registrar(e); + }} + focusedChild + class="flex-1 flex flex-col" + > + {#await upcoming then upcoming} + {#if upcoming.length} +
+ + {#key viewSettingsKey} + {#each upcoming as item (item.tmdbId)} + + {/each} + {/key} + +
+ {/if} + {/await} + {#await items then items} + {#if items.length} +
+
My List
+ + {#key viewSettingsKey} + {#each items as item, index (item.tmdbId)} + + {/each} + {/key} + +
+ {/if} + {/await} + {#await watched then watched} + {#if watched.length} +
+
Watched
+ + {#key viewSettingsKey} + {#each watched as item (item.tmdbId)} + + {/each} + {/key} + +
+ {/if} + {/await} + {#await Promise.all([upcoming, items, watched]) then [upcoming, items, watched]} + {#if !upcoming.length && !items.length && !watched.length} + + Add content to your list to see it here. {/if} -
-
- {/if} + {:catch error} + +
Error loading data: {error.message}
+
+ {/await} +
+
diff --git a/src/lib/pages/LibraryPage/OptionsDialog.LibraryPage.svelte b/src/lib/pages/LibraryPage/OptionsDialog.LibraryPage.svelte index bde6a1f..4c46a95 100644 --- a/src/lib/pages/LibraryPage/OptionsDialog.LibraryPage.svelte +++ b/src/lib/pages/LibraryPage/OptionsDialog.LibraryPage.svelte @@ -2,13 +2,13 @@ import Dialog from '$lib/components/Dialog/Dialog.svelte'; import SelectButtonGroup from '$lib/components/SelectButtonGroup.svelte'; import Toggle from '$lib/components/Toggle.svelte'; - import { libraryViewSettings } from '$lib/stores/localstorage.store'; + import { libraryViewSettings, type MyListOrder, type MyListOrderDirection } from './LibraryPage'; - const sortByOptions = [ + const sortByOptions: { label: string; value: MyListOrder }[] = [ { label: 'Last Release Date', value: 'last-release-date' }, { label: 'First Release Date', value: 'first-release-date' }, { label: 'Date Added', value: 'date-added' }, - { label: 'Title', value: 'title' } + { label: 'Title', value: 'name' } ]; const sortByDirectionOptions = [ @@ -16,12 +16,15 @@ { label: 'Descending', value: 'desc' } ]; - function updateSortBy(sortBy: string) { - libraryViewSettings.update((settings) => ({ ...settings, sortBy: sortBy as any })); + function updateOrder(order: string) { + libraryViewSettings.update((settings) => ({ ...settings, order: order as MyListOrder })); } function updateSortByDirection(direction: string) { - libraryViewSettings.update((settings) => ({ ...settings, sortDirection: direction as any })); + libraryViewSettings.update((settings) => ({ + ...settings, + direction: direction as MyListOrderDirection + })); } @@ -34,31 +37,21 @@ updateSortBy(sortBy)} + selected={$libraryViewSettings.order} + on:select={({ detail: order }) => updateOrder(order)} /> updateSortByDirection(direction)} />
- libraryViewSettings.update((settings) => ({ - ...settings, - separateUpcoming: !separateUpcoming - }))} - /> - - libraryViewSettings.update((settings) => ({ ...settings,