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}
+

+ {: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}
-