first commit
This commit is contained in:
6
src/routes/+layout.svelte
Normal file
6
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<script>
|
||||
import '../app.css'
|
||||
import '@fortawesome/fontawesome-free/css/all.min.css'
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
2
src/routes/+page.svelte
Normal file
2
src/routes/+page.svelte
Normal file
@@ -0,0 +1,2 @@
|
||||
<h1 class="underline text-green-400">Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
|
||||
40
src/routes/album/[id]/+page.js
Normal file
40
src/routes/album/[id]/+page.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { generateURL } from '$lib/Jellyfin-api.js'
|
||||
|
||||
export async function load({ fetch, params }) {
|
||||
const response = await fetch(
|
||||
generateURL({
|
||||
type: 'Items',
|
||||
queryParams: { albumIds: params.id, recursive: true },
|
||||
})
|
||||
)
|
||||
const albumItemsData = await response.json()
|
||||
|
||||
// This handles rare circumstances where a song is part of an album but is not meant to be included in the track list
|
||||
// Example: Xronial Xero (Laur Remix) - Is tagged with the album Xronial Xero, but was a bonus track and not included as part of the album's track list.
|
||||
const items = albumItemsData.Items.filter((item) => 'IndexNumber' in item)
|
||||
|
||||
// Idk if it's efficient, but this is a beautiful one liner that accomplishes 1. Checking whether or not there are multiple discs, 2. Sorting the Items
|
||||
// primarily by disc number, and secondarily by track number, and 3. Defaulting to just sorting by track number if the album is only one disc.
|
||||
items.sort((a, b) =>
|
||||
a?.ParentIndexNumber !== b?.ParentIndexNumber
|
||||
? a.ParentIndexNumber - b.ParentIndexNumber
|
||||
: a.IndexNumber - b.IndexNumber
|
||||
)
|
||||
|
||||
const albumData = {
|
||||
name: items[0].Album,
|
||||
id: items[0].AlbumId,
|
||||
artists: items[0].AlbumArtists,
|
||||
year: items[0].ProductionYear,
|
||||
discCount: Math.max(...items.map((x) => x?.ParentIndexNumber)),
|
||||
length: items
|
||||
.map((x) => x.RunTimeTicks)
|
||||
.reduce((accumulator, currentValue) => accumulator + currentValue),
|
||||
}
|
||||
|
||||
return {
|
||||
id: params.id,
|
||||
albumItemsData: items,
|
||||
albumData: albumData,
|
||||
}
|
||||
}
|
||||
57
src/routes/album/[id]/+page.svelte
Normal file
57
src/routes/album/[id]/+page.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script>
|
||||
import { fly, slide } from 'svelte/transition'
|
||||
import { cubicIn, cubicOut } from 'svelte/easing'
|
||||
|
||||
import { generateURL } from '$lib/Jellyfin-api.js'
|
||||
import AlbumBg from '$lib/albumBG.svelte'
|
||||
import Navbar from '$lib/navbar.svelte'
|
||||
import ListItem from '$lib/listItem.svelte'
|
||||
import Header from '$lib/header.svelte'
|
||||
import MediaPlayer from '$lib/mediaPlayer.svelte'
|
||||
|
||||
export let data
|
||||
let albumImg = generateURL({ type: 'Image', pathParams: { id: data.id } })
|
||||
// console.log(generateURL({type: 'Items', queryParams: {'albumIds': data.albumData.Id, 'recursive': true}}))
|
||||
let discArray = Array.from({ length: data.albumData?.discCount ? data.albumData.discCount : 1 }, (_, i) => {
|
||||
return data.albumItemsData.filter((item) => item?.ParentIndexNumber === i + 1 || !item?.ParentIndexNumber)
|
||||
})
|
||||
let mediaPlayerOpen = false
|
||||
let currentMediaPlayerItem
|
||||
|
||||
const playSong = (event) => {
|
||||
currentMediaPlayerItem = event.detail.item
|
||||
mediaPlayerOpen = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="main" class="flex h-screen flex-col" in:fly={{ easing: cubicOut, y: 10, duration: 1000, delay: 400 }} out:fly={{ easing: cubicIn, y: -10, duration: 500 }}>
|
||||
<AlbumBg {albumImg} />
|
||||
<Navbar />
|
||||
<div id="layout" class="no-scrollbar relative m-[0_auto] grid w-full max-w-[92vw] grid-cols-1 gap-0 overflow-y-scroll pt-8 md:grid-cols-[1fr_2fr] md:gap-[4%] md:pt-48">
|
||||
<img class="rounded object-cover" src={albumImg} alt="Jacket" draggable="false" />
|
||||
<div class="my-12 flex flex-col gap-4 pr-2">
|
||||
<Header header={data.albumData.name} artists={data.albumData.artists} year={data.albumData.year} length={data.albumData.length} />
|
||||
<div class="flex flex-col gap-4">
|
||||
{#each discArray as disc}
|
||||
<div>
|
||||
{#if data.albumData.discCount}
|
||||
<span class="m-3 block font-notoSans text-lg text-neutral-300">DISC {discArray.indexOf(disc) + 1}</span>
|
||||
{/if}
|
||||
<div class="flex w-full flex-col items-center divide-y-[1px] divide-[#353535]">
|
||||
{#each disc as song}
|
||||
<ListItem item={song} on:startPlayback={playSong} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed bottom-0 z-20 w-full">
|
||||
{#if mediaPlayerOpen}
|
||||
<div transition:slide={{ duration: 400 }} class="h-screen">
|
||||
<MediaPlayer currentlyPlaying={currentMediaPlayerItem} playlistItems={data.albumItemsData} on:closeMediaPlayer={() => (mediaPlayerOpen = false)} on:startPlayback={playSong} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
5
src/routes/artist/[id]/+page.js
Normal file
5
src/routes/artist/[id]/+page.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const load = ({ params }) => {
|
||||
return {
|
||||
id: params.id,
|
||||
}
|
||||
}
|
||||
39
src/routes/artist/[id]/+page.svelte
Normal file
39
src/routes/artist/[id]/+page.svelte
Normal file
@@ -0,0 +1,39 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { fetchArtistItems } from '$lib/Jellyfin-api.js';
|
||||
import AlbumCard from '$lib/albumCard.svelte';
|
||||
export let data;
|
||||
|
||||
let artistItems = {
|
||||
albums: [],
|
||||
singles: [],
|
||||
appearances: []
|
||||
};
|
||||
onMount(async () => {
|
||||
artistItems = await fetchArtistItems(data.id);
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
{ #each artistItems.albums as item }
|
||||
<AlbumCard {item} cardType="albums"/>
|
||||
{ /each }
|
||||
</div>
|
||||
<div class="grid">
|
||||
{ #each artistItems.singles as item }
|
||||
<AlbumCard {item} cardType="singles"/>
|
||||
{ /each }
|
||||
</div>
|
||||
<div class="grid">
|
||||
{ #each artistItems.appearances as item }
|
||||
<AlbumCard {item} cardType="appearances"/>
|
||||
{ /each }
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(9, 200px);
|
||||
}
|
||||
</style>
|
||||
5
src/routes/song/[id]/+page.js
Normal file
5
src/routes/song/[id]/+page.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const load = ({ params }) => {
|
||||
return {
|
||||
id: params.id,
|
||||
}
|
||||
}
|
||||
25
src/routes/song/[id]/+page.svelte
Normal file
25
src/routes/song/[id]/+page.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { fetchSong } from '$lib/Jellyfin-api.js'
|
||||
|
||||
export let data;
|
||||
|
||||
let fetchedData = {};
|
||||
|
||||
onMount(async () => {
|
||||
fetchedData = await fetchSong(data.id);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{ #await fetchedData}
|
||||
<p>Loading</p>
|
||||
{:then songData}
|
||||
<p>{songData.Name}</p>
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user