feat: mobile sidebar

This commit is contained in:
ThaUnknown 2025-03-20 17:30:10 +01:00
parent 6616b9347e
commit 3846de3d9d
No known key found for this signature in database
9 changed files with 110 additions and 75 deletions

View file

@ -15,6 +15,8 @@
let isFlying = false
let timeout: number
// WE LOVE RACE CONDITIONS WOOOO YEAAH MY SANITY
async function start () {
if (isAnimating) return
isAnimating = true
@ -52,13 +54,14 @@
if (lockedState === 'locked' || visibilityState === 'hidden') return reset()
if (idleState === 'active' && activityState === 'active') return reset()
if ($page.url.pathname === '/app/player/') return reset()
clearTimeout(timeout)
timeout = setTimeout(start, 20_000)
}
$: checkIdleState(idleState, lockedState, activityState, visibilityState)
</script>
<svelte:document bind:visibilityState on:mouseleave={() => { if (!document.hasFocus()) activityState = 'inactive' }} on:mouseenter={() => { console.log('uwu'); activityState = 'active' }} />
<svelte:document bind:visibilityState on:mouseleave={() => { if (!document.hasFocus()) activityState = 'inactive' }} on:mouseenter={() => { activityState = 'active' }} />
<svelte:window on:focus={() => { activityState = 'active' }} on:blur={() => { activityState = 'inactive' }}
on:pointermove={() => { idleState = 'active'; activityState = 'active' }} />

View file

@ -68,7 +68,7 @@
</script>
<Pagination count={episodeCount} {perPage} bind:currentPage let:pages let:hasNext let:hasPrev let:range let:setPage siblingCount={1}>
<div class='overflow-y-auto pt-3 -mx-14 px-14 pointer-events-none'>
<div class='overflow-y-auto pt-3 -mx-14 px-14 pointer-events-none -mb-3 pb-3'>
<div class='grid grid-cols-1 sm:grid-cols-[repeat(auto-fit,minmax(500px,1fr))] place-items-center gap-x-10 gap-y-7 justify-center align-middle pointer-events-auto'>
{#each getPage(currentPage) as { episode, image, title, summary, airingAt, airdate, filler, length } (episode)}
{@const watched = _progress >= episode}

View file

@ -52,5 +52,7 @@
</Dialog.Header>
</Dialog.Content>
</Dialog.Root>
<Switch bind:checked={$exopts[config.id].enabled} hideState={true} />
{#if $exopts[config.id]}
<Switch bind:checked={$exopts[config.id].enabled} hideState={true} />
{/if}
</div>

View file

@ -28,7 +28,7 @@
$: isActive = matchPath(href, $page)
</script>
<Button variant='ghost' {href} class={cn(className, 'px-2 w-full relative')} {...$$restProps}>
<Button variant='ghost' {href} class={cn(className, 'px-2 w-10 relative')} {...$$restProps}>
{#if isActive}
<div class='bg-white absolute inset-0 rounded-md' in:send={{ key }} out:receive={{ key }} />
{/if}

View file

@ -1,5 +1,5 @@
import Root from './sidebar.svelte'
export {
Root as Sidebar
Root as Sidebar
}

View file

@ -1,71 +1,26 @@
<script lang='ts'>
import { Home, Search, Calendar, Users, MessagesSquare, Heart, Settings, LogIn } from 'lucide-svelte'
import Hub from '$lib/components/icons/Hub.svelte'
import SidebarButton from './SidebarButton.svelte'
import { BannerImage } from '../banner'
import native from '$lib/modules/native'
import { isMobile } from '$lib/utils'
import { Menu, X } from 'lucide-svelte'
import { Button } from '../button'
import client from '$lib/modules/auth/client'
import * as Avatar from '$lib/components/ui/avatar'
const auth = client.hasAuth
$: hasAuth = $auth
let open = false // 152 x 140
</script>
<div class='w-14 p-2 flex flex-col z-10 shrink-0 bg-black'>
<BannerImage class='absolute top-0 left-0 w-14 -z-10' />
<img src='/logo_cropped.png' alt='logo' class='mb-3 cursor-pointer h-10 object-contain px-1' loading='lazy' decoding='async' />
<SidebarButton class='mb-2' href='/app/home/'>
<Home size={18} />
</SidebarButton>
<SidebarButton class='mb-2' href='/app/search/'>
<Search size={18} />
</SidebarButton>
<SidebarButton class='mb-2' href='/app/schedule/'>
<Calendar size={18} />
</SidebarButton>
<SidebarButton class='mb-2' href='/app/w2g/'>
<Users size={18} />
</SidebarButton>
<SidebarButton class='mb-2' href='/app/chat/'>
<MessagesSquare size={18} />
</SidebarButton>
<SidebarButton class='mb-2' href='/app/client/'>
<Hub size={18} fill='currentColor' />
</SidebarButton>
<Button variant='ghost' on:click={() => native.openURL('https://github.com/sponsors/ThaUnknown/')} class='px-2 w-full relative mb-2 mt-auto select:!bg-transparent text-[#fa68b6] select:text-[#fa68b6]'>
<Heart size={18} fill='currentColor' class='absolute' />
<Heart size={18} fill='currentColor' class='donate' />
</Button>
<SidebarButton class='mb-2' href='/app/settings/'>
<Settings size={18} />
</SidebarButton>
<SidebarButton href='/app/profile/'>
{#if hasAuth}
{@const viewer = client.profile()}
<Avatar.Root class='size-6 rounded-md'>
<Avatar.Image src={viewer?.avatar?.medium ?? ''} alt={viewer?.name} />
<Avatar.Fallback>{viewer?.name}</Avatar.Fallback>
</Avatar.Root>
{:else}
<LogIn size={18} />
{/if}
</SidebarButton>
</div>
<style>
:global(.donate) {
filter: drop-shadow(0 0 1rem #fa68b6);
animation: glow 1s ease-in-out infinite alternate;
}
@keyframes glow {
from {
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(1) scaleY(1);
}
to {
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(0.5) scaleY(0.5);
}
}
</style>
{#if $isMobile}
<div class='shrink-0 z-50 bg-black absolute right-4 bottom-4 w-14 h-[52px] flex rounded-md items-end justify-end overflow-clip transition-[width,height]' class:w-[152px]={open} class:h-[140px]={open}>
<div class='p-2 grid grid-cols-3 gap-2 shrink-0'>
<slot />
<Button variant='ghost' class='px-2 w-full relative' on:click={() => { open = !open }}>
{#if open}
<X size={18} fill='currentColor' />
{:else}
<Menu size={18} fill='currentColor' />
{/if}
</Button>
</div>
</div>
{:else}
<div class='w-14 p-2 flex flex-col z-10 shrink-0 bg-black gap-2'>
<slot />
</div>
{/if}

View file

@ -0,0 +1,69 @@
<script lang='ts'>
import { Home, Search, Calendar, Users, MessagesSquare, Heart, Settings, LogIn } from 'lucide-svelte'
import Hub from '$lib/components/icons/Hub.svelte'
import SidebarButton from './SidebarButton.svelte'
import { BannerImage } from '../banner'
import native from '$lib/modules/native'
import { Button } from '../button'
import client from '$lib/modules/auth/client'
import * as Avatar from '$lib/components/ui/avatar'
const auth = client.hasAuth
$: hasAuth = $auth
</script>
<BannerImage class='absolute top-0 left-0 w-14 -z-10 hidden md:block' />
<img src='/logo_cropped.png' alt='logo' class='mb-3 cursor-pointer h-10 object-contain px-1 hidden md:block' loading='lazy' decoding='async' />
<SidebarButton href='/app/home/'>
<Home size={18} />
</SidebarButton>
<SidebarButton href='/app/search/'>
<Search size={18} />
</SidebarButton>
<SidebarButton href='/app/schedule/'>
<Calendar size={18} />
</SidebarButton>
<SidebarButton href='/app/w2g/'>
<Users size={18} />
</SidebarButton>
<SidebarButton href='/app/chat/'>
<MessagesSquare size={18} />
</SidebarButton>
<SidebarButton href='/app/client/'>
<Hub size={18} fill='currentColor' />
</SidebarButton>
<Button variant='ghost' on:click={() => native.openURL('https://github.com/sponsors/ThaUnknown/')} class='px-2 w-full relative mt-auto select:!bg-transparent text-[#fa68b6] select:text-[#fa68b6]'>
<Heart size={18} fill='currentColor' class='absolute' />
<Heart size={18} fill='currentColor' class='donate' />
</Button>
<SidebarButton href='/app/settings/'>
<Settings size={18} />
</SidebarButton>
<SidebarButton href='/app/profile/' class='hidden md:block'>
{#if hasAuth}
{@const viewer = client.profile()}
<Avatar.Root class='size-6 rounded-md'>
<Avatar.Image src={viewer?.avatar?.medium ?? ''} alt={viewer?.name} />
<Avatar.Fallback>{viewer?.name}</Avatar.Fallback>
</Avatar.Root>
{:else}
<LogIn size={18} />
{/if}
</SidebarButton>
<style>
:global(.donate) {
filter: drop-shadow(0 0 1rem #fa68b6);
animation: glow 1s ease-in-out infinite alternate;
}
@keyframes glow {
from {
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(1) scaleY(1);
}
to {
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(0.5) scaleY(0.5);
}
}
</style>

View file

@ -48,6 +48,10 @@ export function coverMedium (media: Pick<Media, 'trailer' | 'bannerImage' | 'cov
return media.coverImage?.medium?.replace('/small/', '/medium/') ?? banner(media)
}
export function coverSmall (media: Pick<Media, 'trailer' | 'bannerImage' | 'coverImage'>): string | undefined {
return media.coverImage?.medium ?? banner(media)
}
export function title (media: Pick<Media, 'title'>): string {
return media.title?.userPreferred ?? 'TBA'
}

View file

@ -1,15 +1,17 @@
<script lang='ts'>
import { BannerImage } from '$lib/components/ui/banner'
import { isMobile } from '$lib/utils'
import { Sidebar } from '$lib/components/ui/sidebar'
import SearchModal from '$lib/components/SearchModal.svelte'
import Sidebarlist from '$lib/components/ui/sidebar/sidebarlist.svelte'
</script>
<BannerImage class='absolute top-0 left-0' />
<SearchModal />
<div class='flex flex-row grow h-full overflow-hidden relative'>
{#if !$isMobile}
<Sidebar />
{/if}
<Sidebar>
<Sidebarlist />
</Sidebar>
<slot />
</div>