2024-01-25 19:50:26 -05:00
|
|
|
<script lang="ts">
|
2024-01-27 01:38:04 -05:00
|
|
|
import { fly } from 'svelte/transition'
|
2024-01-28 02:41:13 -05:00
|
|
|
import { goto, beforeNavigate } from '$app/navigation'
|
2024-01-26 01:30:18 -05:00
|
|
|
import { pageWidth } from '$lib/stores'
|
2024-01-28 02:41:13 -05:00
|
|
|
import type { LayoutData } from './$types'
|
|
|
|
|
import type { Tab } from './+layout'
|
|
|
|
|
import { onMount } from 'svelte'
|
|
|
|
|
|
|
|
|
|
export let data: LayoutData
|
2024-01-25 19:50:26 -05:00
|
|
|
|
|
|
|
|
const pageTransitionTime: number = 200
|
|
|
|
|
|
2024-01-28 02:41:13 -05:00
|
|
|
let currentTabIndex = -1
|
|
|
|
|
|
2024-01-25 19:50:26 -05:00
|
|
|
type PageTransitionDirection = 1 | -1
|
2024-01-27 01:38:04 -05:00
|
|
|
let directionMultiplier: PageTransitionDirection = 1
|
2024-01-28 02:41:13 -05:00
|
|
|
|
|
|
|
|
let indicatorBar: HTMLDivElement, tabList: HTMLDivElement
|
|
|
|
|
|
|
|
|
|
const inPathnameHeirarchy = (pathname: string, rootPathname: string): boolean => {
|
|
|
|
|
return (pathname.startsWith(rootPathname) && rootPathname !== '/') || (pathname === '/' && rootPathname === '/')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const calculateDirection = (newTab: Tab): void => {
|
|
|
|
|
const newTabIndex = data.tabs.findIndex((tab) => tab === newTab)
|
|
|
|
|
directionMultiplier = newTabIndex > currentTabIndex ? -1 : 1
|
|
|
|
|
currentTabIndex = newTabIndex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const navigate = (newPathname: string): void => {
|
|
|
|
|
const newTabIndex = data.tabs.findIndex((tab) => inPathnameHeirarchy(newPathname, tab.pathname))
|
|
|
|
|
|
|
|
|
|
if (newTabIndex < 0) indicatorBar.style.opacity = '0'
|
|
|
|
|
|
|
|
|
|
const newTab = data.tabs[newTabIndex]
|
|
|
|
|
if (!newTab?.button) return
|
|
|
|
|
|
|
|
|
|
const tabRec = newTab.button.getBoundingClientRect(),
|
|
|
|
|
listRect = tabList.getBoundingClientRect()
|
|
|
|
|
|
|
|
|
|
const shiftTop = () => (indicatorBar.style.top = `${tabRec.top - listRect.top}px`),
|
|
|
|
|
shiftBottom = () => (indicatorBar.style.bottom = `${listRect.bottom - tabRec.bottom}px`)
|
|
|
|
|
|
|
|
|
|
if (directionMultiplier > 0) {
|
|
|
|
|
shiftTop()
|
|
|
|
|
setTimeout(shiftBottom, pageTransitionTime)
|
|
|
|
|
} else {
|
|
|
|
|
shiftBottom()
|
|
|
|
|
setTimeout(shiftTop, pageTransitionTime)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setTimeout(() => (indicatorBar.style.opacity = '100%'), pageTransitionTime + 300)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMount(() => setTimeout(() => navigate(data.url.pathname))) // More stupid fucking non-blocking event loop shit
|
|
|
|
|
beforeNavigate(({ to }) => {
|
|
|
|
|
if (to) navigate(to.url.pathname)
|
|
|
|
|
})
|
2024-01-26 01:30:18 -05:00
|
|
|
</script>
|
|
|
|
|
|
2024-01-27 01:38:04 -05:00
|
|
|
{#if $pageWidth >= 768}
|
2024-01-29 01:54:21 -05:00
|
|
|
<div class="grid h-full grid-rows-1 gap-8 overflow-hidden">
|
|
|
|
|
<div class="no-scrollbar fixed left-0 top-0 z-10 grid h-full grid-cols-1 grid-rows-[min-content_auto] gap-6 p-3 pt-12" bind:this={tabList}>
|
|
|
|
|
<div class="flex flex-col gap-6">
|
|
|
|
|
{#each data.tabs.filter((tab) => tab.type === 'nav') as nav, index}
|
2024-01-28 02:41:13 -05:00
|
|
|
<button
|
2024-01-29 01:54:21 -05:00
|
|
|
class="navTab grid aspect-square w-14 place-items-center transition-colors"
|
|
|
|
|
bind:this={data.tabs[index].button}
|
|
|
|
|
disabled={inPathnameHeirarchy(data.url.pathname, nav.pathname)}
|
2024-01-28 02:41:13 -05:00
|
|
|
on:click={() => {
|
2024-01-29 01:54:21 -05:00
|
|
|
calculateDirection(nav)
|
|
|
|
|
goto(nav.pathname)
|
2024-01-28 02:41:13 -05:00
|
|
|
}}
|
2024-01-29 01:54:21 -05:00
|
|
|
>
|
|
|
|
|
<span class="pointer-events-none flex flex-col gap-2 text-xs">
|
|
|
|
|
<i class="{nav.icon} text-xl" />
|
|
|
|
|
{nav.name}
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
2024-01-28 02:41:13 -05:00
|
|
|
{/each}
|
2024-01-29 01:54:21 -05:00
|
|
|
<div bind:this={indicatorBar} class="absolute left-0 w-[0.2rem] rounded-full bg-white transition-all duration-300 ease-in-out" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="no-scrollbar flex flex-col gap-6 overflow-y-scroll">
|
|
|
|
|
{#each data.tabs.filter((tab) => tab.type === 'playlist') as playlist}
|
|
|
|
|
<div class="playlistTab-wrapper flex items-center gap-1">
|
|
|
|
|
<button
|
|
|
|
|
title={playlist.name}
|
|
|
|
|
disabled={new URLSearchParams(data.url.search).get('playlist') === new URLSearchParams(playlist.pathname.split('?')[1]).get('playlist')}
|
|
|
|
|
class="playlistTab relative aspect-square w-14 rounded-lg bg-cover bg-center transition-all"
|
|
|
|
|
style="background-image: url({playlist.icon});"
|
|
|
|
|
on:click={() => {
|
|
|
|
|
calculateDirection(playlist)
|
|
|
|
|
goto(playlist.pathname)
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
</button>
|
|
|
|
|
<span class="translate-x-3 overflow-clip text-ellipsis whitespace-nowrap rounded-md bg-slate-600 px-2 py-1 text-sm">{playlist.name}</span>
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
2024-01-28 02:41:13 -05:00
|
|
|
</div>
|
2024-01-27 01:38:04 -05:00
|
|
|
<section class="no-scrollbar relative overflow-y-scroll">
|
2024-01-28 02:41:13 -05:00
|
|
|
{#key data.url}
|
2024-01-27 01:38:04 -05:00
|
|
|
<div
|
|
|
|
|
in:fly={{ y: -50 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }}
|
|
|
|
|
out:fly={{ y: 50 * directionMultiplier, duration: pageTransitionTime }}
|
2024-01-29 01:54:21 -05:00
|
|
|
class="absolute w-full px-[clamp(7rem,_5vw,_24rem)] pt-16"
|
2024-01-27 01:38:04 -05:00
|
|
|
>
|
|
|
|
|
<slot />
|
|
|
|
|
</div>
|
|
|
|
|
{/key}
|
|
|
|
|
</section>
|
|
|
|
|
<footer class="fixed bottom-0 flex w-full flex-col items-center justify-center">
|
|
|
|
|
<!-- <MiniPlayer displayMode={'horizontal'} /> -->
|
|
|
|
|
</footer>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
2024-01-26 01:30:18 -05:00
|
|
|
<div class="h-full overflow-hidden">
|
2024-01-28 02:41:13 -05:00
|
|
|
{#key data.url.pathname}
|
2024-01-26 01:30:18 -05:00
|
|
|
<section
|
2024-01-27 01:38:04 -05:00
|
|
|
in:fly={{ x: 200 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }}
|
|
|
|
|
out:fly={{ x: -200 * directionMultiplier, duration: pageTransitionTime }}
|
2024-01-26 01:30:18 -05:00
|
|
|
class="no-scrollbar h-full overflow-y-scroll px-[5vw] pt-16"
|
|
|
|
|
>
|
|
|
|
|
<slot />
|
|
|
|
|
</section>
|
|
|
|
|
{/key}
|
|
|
|
|
<footer class="fixed bottom-0 flex w-full flex-col items-center justify-center">
|
2024-01-27 01:38:04 -05:00
|
|
|
<!-- <MiniPlayer displayMode={'vertical'} /> -->
|
2024-01-28 02:41:13 -05:00
|
|
|
<!-- <NavbarFoot
|
2024-01-27 01:38:04 -05:00
|
|
|
{currentPathname}
|
|
|
|
|
transitionTime={pageTransitionTime}
|
|
|
|
|
on:navigate={(event) => {
|
|
|
|
|
event.detail.direction === 'right' ? (directionMultiplier = 1) : (directionMultiplier = -1)
|
|
|
|
|
currentPathname = event.detail.pathname
|
|
|
|
|
goto(currentPathname)
|
|
|
|
|
}}
|
2024-01-28 02:41:13 -05:00
|
|
|
/> -->
|
2024-01-26 01:30:18 -05:00
|
|
|
</footer>
|
|
|
|
|
</div>
|
2024-01-27 01:38:04 -05:00
|
|
|
{/if}
|
2024-01-26 01:30:18 -05:00
|
|
|
|
|
|
|
|
<style>
|
2024-01-28 02:41:13 -05:00
|
|
|
.navTab:not(:disabled) {
|
|
|
|
|
color: rgb(163 163, 163);
|
|
|
|
|
}
|
|
|
|
|
.navTab:not(:disabled):hover {
|
|
|
|
|
color: var(--lazuli-primary);
|
|
|
|
|
}
|
2024-01-29 01:54:21 -05:00
|
|
|
.playlistTab-wrapper:hover > span {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
2024-01-28 02:41:13 -05:00
|
|
|
.playlistTab:not(:disabled):not(:hover) {
|
|
|
|
|
filter: brightness(50%);
|
|
|
|
|
}
|
2024-01-26 01:30:18 -05:00
|
|
|
</style>
|