AutoImage replaced LazyImage && general improvements to components with style props
This commit is contained in:
@@ -9,25 +9,32 @@
|
||||
|
||||
$: currentlyPlaying = $queue.current
|
||||
$: shuffled = $queue.isShuffled
|
||||
|
||||
let playerWidth: number
|
||||
</script>
|
||||
|
||||
<main id="grid-wrapper" class="h-full">
|
||||
<main id="grid-wrapper" class="relative h-full">
|
||||
<Navbar on:opensidebar={sidebar.open} />
|
||||
<Sidebar bind:this={sidebar} />
|
||||
<section id="content-wrapper" class="overflow-y-scroll no-scrollbar">
|
||||
<slot />
|
||||
<div class="my-8">
|
||||
<slot />
|
||||
</div>
|
||||
{#if currentlyPlaying}
|
||||
<div bind:clientWidth={playerWidth} class="sticky {playerWidth > 800 ? 'bottom-0' : 'bottom-3 mx-3'} transition-all">
|
||||
<MediaPlayer
|
||||
mediaItem={currentlyPlaying}
|
||||
{shuffled}
|
||||
mediaSession={'mediaSession' in navigator ? navigator.mediaSession : null}
|
||||
--border-radius={playerWidth > 800 ? '0' : '0.5rem'}
|
||||
on:stop={() => $queue.clear()}
|
||||
on:next={() => $queue.next()}
|
||||
on:previous={() => $queue.previous()}
|
||||
on:toggleShuffle={() => $queue.toggleShuffle()}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{#if currentlyPlaying}
|
||||
<MediaPlayer
|
||||
mediaItem={currentlyPlaying}
|
||||
{shuffled}
|
||||
mediaSession={'mediaSession' in navigator ? navigator.mediaSession : null}
|
||||
on:stop={() => $queue.clear()}
|
||||
on:next={() => $queue.next()}
|
||||
on:previous={() => $queue.previous()}
|
||||
on:toggleShuffle={() => $queue.toggleShuffle()}
|
||||
/>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
@@ -35,4 +42,8 @@
|
||||
display: grid;
|
||||
grid-template-rows: min-content auto;
|
||||
}
|
||||
#content-wrapper {
|
||||
display: grid;
|
||||
grid-template-rows: auto min-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types'
|
||||
import Loader from '$lib/components/util/loader.svelte'
|
||||
import MediaCard from '$lib/components/media/mediaCard.svelte'
|
||||
import AlbumCard from '$lib/components/media/albumCard.svelte'
|
||||
import PlaylistCard from '$lib/components/media/playlistCard.svelte'
|
||||
|
||||
export let data: PageData
|
||||
</script>
|
||||
@@ -11,9 +11,13 @@
|
||||
{#await data.recommendations}
|
||||
<Loader />
|
||||
{:then recommendations}
|
||||
<div id="card-wrapper" class="grid w-full gap-4 justify-self-center px-[5%] pt-8">
|
||||
{#each recommendations.filter((item) => item.type === 'album') as album}
|
||||
<AlbumCard {album} />
|
||||
<div id="card-wrapper" class="grid w-full gap-4 justify-self-center px-[5%]">
|
||||
{#each recommendations as mediaItem}
|
||||
{#if mediaItem.type === 'album'}
|
||||
<AlbumCard album={mediaItem} />
|
||||
{:else if mediaItem.type === 'playlist'}
|
||||
<PlaylistCard playlist={mediaItem} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
14
src/routes/(app)/mixes/+page.svelte
Normal file
14
src/routes/(app)/mixes/+page.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import IconButton from '$lib/components/util/iconButton.svelte'
|
||||
import IcBaselinePlus from '~icons/ic/baseline-plus'
|
||||
</script>
|
||||
|
||||
<main class="flex flex-wrap justify-center">
|
||||
<div class="grid aspect-square h-56 place-items-center">
|
||||
<div class="aspect-square h-16">
|
||||
<IconButton halo={true}>
|
||||
<IcBaselinePlus slot="icon" class="text-4xl text-neutral-300" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -7,7 +7,6 @@
|
||||
import { newestAlert } from '$lib/stores.js'
|
||||
import type { PageServerData } from './$types.js'
|
||||
import type { SubmitFunction } from '@sveltejs/kit'
|
||||
import { getDeviceUUID } from '$lib/utils'
|
||||
import { SvelteComponent, type ComponentType } from 'svelte'
|
||||
import ConnectionProfile from './connectionProfile.svelte'
|
||||
import { enhance } from '$app/forms'
|
||||
@@ -21,6 +20,15 @@
|
||||
|
||||
data.connections.then((userConnections) => ('error' in userConnections ? (errorMessage = userConnections.error) : (connections = userConnections)))
|
||||
|
||||
function getDeviceUUID(): string {
|
||||
const existingUUID = localStorage.getItem('deviceUUID')
|
||||
if (existingUUID) return existingUUID
|
||||
|
||||
const newUUID = '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c: any) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16))
|
||||
localStorage.setItem('deviceUUID', newUUID)
|
||||
return newUUID
|
||||
}
|
||||
|
||||
const authenticateJellyfin: SubmitFunction = ({ formData, cancel }) => {
|
||||
const { serverUrl, username, password } = Object.fromEntries(formData)
|
||||
|
||||
|
||||
@@ -64,12 +64,12 @@ function modifyImageURL(imageURL: URL, options?: { maxWidth?: number; maxHeight?
|
||||
switch (imageURL.origin) {
|
||||
case 'https://i.ytimg.com':
|
||||
case 'https://www.gstatic.com':
|
||||
// These two origins correspond to images that can't have their size modified with search params, so we just return them at the default res
|
||||
case 'https://music.youtube.com':
|
||||
// These origins correspond to images that can't have their size modified with search params, so we just return them at the default res
|
||||
return baseURL
|
||||
case 'https://lh3.googleusercontent.com':
|
||||
case 'https://yt3.googleusercontent.com':
|
||||
case 'https://yt3.ggpht.com':
|
||||
case 'https://music.youtube.com':
|
||||
const fakeQueryParams = []
|
||||
if (maxWidth) fakeQueryParams.push(`w${Math.min(maxWidth, MAX_YOUTUBE_THUMBNAIL_SCALAR_SIZE)}`)
|
||||
if (maxHeight) fakeQueryParams.push(`h${Math.min(maxHeight, MAX_YOUTUBE_THUMBNAIL_SCALAR_SIZE)}`)
|
||||
|
||||
19
src/routes/api/v1/connections/+server.ts
Normal file
19
src/routes/api/v1/connections/+server.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit'
|
||||
import { ConnectionFactory } from '$lib/server/api-helper'
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
const ids = url.searchParams.get('id')?.replace(/\s/g, '').split(',')
|
||||
if (!ids) return new Response('Missing id query parameter', { status: 400 })
|
||||
|
||||
const connections = (await Promise.all(ids.map((id) => ConnectionFactory.getConnection(id).catch(() => null)))).filter((result): result is Connection => result !== null)
|
||||
|
||||
const getConnectionInfo = (connection: Connection) =>
|
||||
connection.getConnectionInfo().catch((reason) => {
|
||||
console.error(`Failed to fetch connection info: ${reason}`)
|
||||
return null
|
||||
})
|
||||
|
||||
const connectionInfo = (await Promise.all(connections.map(getConnectionInfo))).filter((connectionInfo): connectionInfo is ConnectionInfo => connectionInfo !== null)
|
||||
|
||||
return Response.json({ connections: connectionInfo })
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit'
|
||||
import { ConnectionFactory } from '$lib/server/api-helper'
|
||||
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
const connection = await ConnectionFactory.getConnection(params.connectionId!)
|
||||
|
||||
const albumId = url.searchParams.get('id')
|
||||
if (!albumId) return new Response(`Missing id search parameter`, { status: 400 })
|
||||
|
||||
const album = await connection.getAlbum(albumId).catch(() => undefined)
|
||||
if (!album) return new Response(`Failed to fetch album with id: ${albumId}`, { status: 400 })
|
||||
|
||||
return Response.json({ album })
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit'
|
||||
import { ConnectionFactory } from '$lib/server/api-helper'
|
||||
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
const connection = await ConnectionFactory.getConnection(params.connectionId!)
|
||||
|
||||
const items = await connection.getAlbumItems(params.albumId!).catch(() => null)
|
||||
if (!items) return new Response(`Failed to fetch album with id: ${params.albumId!}`, { status: 400 })
|
||||
|
||||
return Response.json({ items })
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit'
|
||||
import { ConnectionFactory } from '$lib/server/api-helper'
|
||||
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
const connection = await ConnectionFactory.getConnection(params.connectionId!)
|
||||
|
||||
const playlistId = url.searchParams.get('id')
|
||||
if (!playlistId) return new Response(`Missing id search parameter`, { status: 400 })
|
||||
|
||||
const response = await connection
|
||||
.getPlaylistItems(playlistId)
|
||||
.then((playlist) => Response.json({ playlist }))
|
||||
.catch((error: TypeError | Error) => {
|
||||
if (error instanceof TypeError) return new Response('Bad Request', { status: 400 })
|
||||
return new Response('Failed to fetch playlist items', { status: 502 })
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit'
|
||||
import { ConnectionFactory } from '$lib/server/api-helper'
|
||||
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
const connection = await ConnectionFactory.getConnection(params.connectionId!)
|
||||
|
||||
const startIndexString = url.searchParams.get('startIndex')
|
||||
const limitString = url.searchParams.get('limit')
|
||||
|
||||
const numberStartIndex = Number(startIndexString)
|
||||
const numberLimit = Number(limitString)
|
||||
|
||||
const startIndex = Number.isInteger(numberStartIndex) && numberStartIndex > 0 ? numberStartIndex : undefined
|
||||
const limit = Number.isInteger(numberLimit) && numberLimit > 0 ? numberLimit : undefined
|
||||
|
||||
const response = await connection
|
||||
.getPlaylistItems(params.playlistId!, { startIndex, limit })
|
||||
.then((items) => Response.json({ items }))
|
||||
.catch((error: TypeError | Error) => {
|
||||
if (error instanceof TypeError) return new Response('Bad Request', { status: 400 })
|
||||
return new Response('Failed to fetch playlist items', { status: 502 })
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
Reference in New Issue
Block a user