From c710f80178442c828a9c42a7855c7f4122c7a2ed Mon Sep 17 00:00:00 2001 From: Eclypsed Date: Sun, 4 Feb 2024 01:01:37 -0500 Subject: [PATCH] I'm tired --- src/app.d.ts | 58 +++++++---- src/lib/components/media/mediaCard.svelte | 96 +++++++++++++++++++ .../media/scrollableCardMenu.svelte | 39 ++++++++ src/lib/service-managers/jellyfin.ts | 83 +++++++++++++--- src/routes/(app)/+layout.svelte | 2 +- src/routes/(app)/+page.server.ts | 12 +++ src/routes/(app)/+page.svelte | 15 +-- .../users/[userId]/recommendations/+server.ts | 10 +- 8 files changed, 279 insertions(+), 36 deletions(-) create mode 100644 src/lib/components/media/scrollableCardMenu.svelte create mode 100644 src/routes/(app)/+page.server.ts diff --git a/src/app.d.ts b/src/app.d.ts index d0bc5e8..5c74c8f 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -11,6 +11,10 @@ declare global { // interface Platform {} } + // General Interface Desing tips: + // Use possibly undefined `?:` for when a property is optional, meaning it could be there, or it could be not applicable + // Use possibly null `| nulll` for when the property is expected to be there but could possbily be explicitly empty + interface User { id: string username: string @@ -30,16 +34,25 @@ declare global { accessToken: string } + // These Schemas should only contain general info data that is necessary for data fetching purposes. + // They are NOT meant to be stores for large amounts of data, i.e. Don't include the data for every single song the Playlist type. + // Big data should be fetched as needed in the app, these exist to ensure that the info necessary to fetch that data is there. interface MediaItem { - connection: Connection + connectionId: string + service: Service + type: 'song' | 'album' | 'playlist' | 'artist' id: string name: string - duration: number thumbnail?: string } interface Song extends MediaItem { - artists?: Artist[] + type: 'song' + duration: number + artists: { + id: string + name: string + }[] albumId?: string audio: string video?: string @@ -47,20 +60,27 @@ declare global { } interface Album extends MediaItem { - artists: Artist[] - songs: Song[] + type: 'album' + duration: number + albumArtists: { + id: string + name: string + }[] + artists: { + id: string + name: string + }[] releaseDate: string } interface Playlist extends MediaItem { - songs: Song[] + type: 'playlist' + duration: number description?: string } - interface Artist { - id: string - name: string - // Add more here in the future + interface Artist extends MediaItem { + type: 'artist' } namespace Jellyfin { @@ -96,46 +116,52 @@ declare global { interface MediaItem { Name: string Id: string - RunTimeTicks: number - Type: 'Audio' | 'MusicAlbum' | 'Playlist' + Type: 'Audio' | 'MusicAlbum' | 'Playlist' | 'MusicArtist' ImageTags?: { Primary?: string } } interface Song extends Jellyfin.MediaItem { + RunTimeTicks: number ProductionYear: number Type: 'Audio' - ArtistItems?: { + ArtistItems: { Name: string Id: string }[] Album?: string AlbumId?: string AlbumPrimaryImageTag?: string - AlbumArtists?: { + AlbumArtists: { Name: string Id: string }[] } interface Album extends Jellyfin.MediaItem { + RunTimeTicks: number ProductionYear: number Type: 'MusicAlbum' - ArtistItems?: { + ArtistItems: { Name: string Id: string }[] - AlbumArtists?: { + AlbumArtists: { Name: string Id: string }[] } interface Playlist extends Jellyfin.MediaItem { + RunTimeTicks: number Type: 'Playlist' ChildCount: number } + + interface Artist extends Jellyfin.MediaItem { + Type: 'MusicArtist' + } } namespace YouTubeMusic { diff --git a/src/lib/components/media/mediaCard.svelte b/src/lib/components/media/mediaCard.svelte index e69de29..916bd32 100644 --- a/src/lib/components/media/mediaCard.svelte +++ b/src/lib/components/media/mediaCard.svelte @@ -0,0 +1,96 @@ + + +
+
rotateCard(event)} on:mouseleave={() => (card.style.transform = '')} role="menuitem" tabindex="0"> + {#if mediaItem.thumbnail} + {mediaItem.name} thumbnail + {:else} +
+ +
+ {/if} +
+ + + + + +
+
+
+
{mediaItem.name}
+
+ + {#if checkSong(mediaItem) || checkAlbum(mediaItem)} + {#each mediaItem.artists as artist} + {@const listIndex = mediaItem.artists.indexOf(artist)} + {artist.name} + {#if listIndex === mediaItem.artists.length - 2} + & + {:else if listIndex < mediaItem.artists.length - 2} + , + {/if} + {/each} + {/if} + + {#if mediaItem.type} + + + {/if} +
+
+
+ + diff --git a/src/lib/components/media/scrollableCardMenu.svelte b/src/lib/components/media/scrollableCardMenu.svelte new file mode 100644 index 0000000..a46c176 --- /dev/null +++ b/src/lib/components/media/scrollableCardMenu.svelte @@ -0,0 +1,39 @@ + + +
+
+

{header}

+
+ (scrollable.scrollLeft -= scrollable.clientWidth)}> + + + 0.99 || !isScrollable} on:click={() => (scrollable.scrollLeft += scrollable.clientWidth)}> + + +
+
+
(scrollpos = scrollable.scrollLeft / (scrollable.scrollWidth - scrollable.clientWidth))} + class="no-scrollbar flex gap-6 overflow-y-hidden overflow-x-scroll scroll-smooth p-4" + > + {#each cardDataList as mediaItem} + + {/each} +
+
diff --git a/src/lib/service-managers/jellyfin.ts b/src/lib/service-managers/jellyfin.ts index 9af8375..0acfa9f 100644 --- a/src/lib/service-managers/jellyfin.ts +++ b/src/lib/service-managers/jellyfin.ts @@ -10,24 +10,27 @@ export class Jellyfin { } } - static mediaItemFactory = (item: Jellyfin.MediaItem, connection: Connection): MediaItem => {} - - static songFactory = (song: Jellyfin.Song, connection: Connection): Song => { + static songFactory = (song: Jellyfin.Song, connection: Jellyfin.JFConnection): Song => { const { id, service } = connection + const thumbnail = song.ImageTags?.Primary + ? new URL(`Items/${song.Id}/Images/Primary`, service.urlOrigin).href + : song.AlbumPrimaryImageTag + ? new URL(`Items/${song.AlbumId}/Images/Primary`, service.urlOrigin).href + : undefined - const artists: Artist[] | undefined = song.ArtistItems + const artists = song.ArtistItems ? Array.from(song.ArtistItems, (artist) => { - return { name: artist.Name, id: artist.Id } + return { id: artist.Id, name: artist.Name } }) - : undefined - - const thumbnail = song.ImageTags?.Primary ? new URL(`Items/${song.Id}/Images/Primary`, service.urlOrigin).href : song.AlbumPrimaryImageTag ? new URL(`Items/${song.AlbumId}/Images/Primary`).href : undefined + : [] const audoSearchParams = new URLSearchParams(this.audioPresets(service.userId)) const audioSource = new URL(`Audio/${song.Id}/universal?${audoSearchParams.toString()}`, service.urlOrigin).href - const factorySong: Song = { - connection, + return { + connectionId: id, + service, + type: 'song', id: song.Id, name: song.Name, duration: Math.floor(song.RunTimeTicks / 10000), @@ -37,6 +40,64 @@ export class Jellyfin { audio: audioSource, releaseDate: String(song.ProductionYear), } - return factorySong + } + + static albumFactory = (album: Jellyfin.Album, connection: Jellyfin.JFConnection): Album => { + const { id, service } = connection + const thumbnail = album.ImageTags?.Primary ? new URL(`Items/${album.Id}/Images/Primary`, service.urlOrigin).href : undefined + + const albumArtists = album.AlbumArtists + ? Array.from(album.AlbumArtists, (artist) => { + return { id: artist.Id, name: artist.Name } + }) + : [] + + const artists = album.ArtistItems + ? Array.from(album.ArtistItems, (artist) => { + return { id: artist.Id, name: artist.Name } + }) + : [] + + return { + connectionId: id, + service, + type: 'album', + id: album.Id, + name: album.Name, + duration: Math.floor(album.RunTimeTicks / 10000), + thumbnail, + albumArtists, + artists, + releaseDate: String(album.ProductionYear), + } + } + + static playListFactory = (playlist: Jellyfin.Playlist, connection: Jellyfin.JFConnection): Playlist => { + const { id, service } = connection + const thumbnail = playlist.ImageTags?.Primary ? new URL(`Items/${playlist.Id}/Images/Primary`, service.urlOrigin).href : undefined + + return { + connectionId: id, + service, + type: 'playlist', + id: playlist.Id, + name: playlist.Name, + duration: Math.floor(playlist.RunTimeTicks / 10000), + thumbnail, + } + } + + static artistFactory = (artist: Jellyfin.Artist, connection: Jellyfin.JFConnection): Artist => { + const { id, service } = connection + const thumbnail = artist.ImageTags?.Primary ? new URL(`Items/${artist.Id}/Images/Primary`, service.urlOrigin).href : undefined + + return { + connectionId: id, + service, + type: 'artist', + id: artist.Id, + name: artist.Name, + thumbnail, + } } } diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 4eb337c..40039fd 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -38,7 +38,7 @@
Playlist • {data.user.username}
-
+