SERIOUS messing around with the navbar

This commit is contained in:
Eclypsed
2024-01-28 02:41:13 -05:00
parent bee4c903ec
commit 5bd5b603b0
7 changed files with 200 additions and 146 deletions

View File

@@ -4,6 +4,10 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
* {
flex-shrink: 0;
}
img { img {
max-width: 100%; max-width: 100%;
} }

View File

@@ -12,7 +12,7 @@
export let transitionTime: number = 200 export let transitionTime: number = 200
import { fade } from 'svelte/transition' import { fade } from 'svelte/transition'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@@ -25,41 +25,39 @@
return newPageIndex > currentPageIndex ? 'right' : 'left' return newPageIndex > currentPageIndex ? 'right' : 'left'
} }
let activeTab: HTMLButtonElement, indicatorBar: HTMLDivElement, tabList: HTMLDivElement let indicatorBar: HTMLDivElement, tabList: HTMLDivElement
$: calculateBar(activeTab)
const calculateBar = (activeTab: HTMLButtonElement) => { const calculateBar = (newPathname: string) => {
if (activeTab && indicatorBar && tabList) { const newTab = document.querySelector(`button[data-tab="${newPathname}"]`)
const listRect = tabList.getBoundingClientRect() if (newTab && indicatorBar && tabList) {
const tabRec = activeTab.getBoundingClientRect() const listRect = tabList.getBoundingClientRect(),
tabRec = newTab.getBoundingClientRect()
const shiftRight = () => (indicatorBar.style.right = `${listRect.right - tabRec.right}px`),
shiftLeft = () => (indicatorBar.style.left = `${tabRec.left - listRect.left}px`)
if (direction === 'right') { if (direction === 'right') {
indicatorBar.style.right = `${listRect.right - tabRec.right}px` shiftRight()
setTimeout(() => (indicatorBar.style.left = `${tabRec.left - listRect.left}px`), transitionTime) setTimeout(shiftLeft, transitionTime)
} else { } else {
indicatorBar.style.left = `${tabRec.left - listRect.left}px` shiftLeft()
setTimeout(() => (indicatorBar.style.right = `${listRect.right - tabRec.right}px`), transitionTime) setTimeout(shiftRight, transitionTime)
} }
} }
} }
</script> </script>
<div bind:this={tabList} id="bottom-tab-list" class="relative flex w-full items-center justify-around bg-black"> <div bind:this={tabList} id="tab-list" class="relative flex w-full items-center justify-around bg-black">
{#each navTabs as tabData} {#each navTabs as tabData}
{#if currentPathname === tabData.pathname} <button
<button bind:this={activeTab} class="pointer-events-none text-white transition-colors" disabled> class="transition-colors"
<i class={tabData.icon} /> data-tab={tabData.pathname}
</button> disabled={currentPathname === tabData.pathname}
{:else} on:click={() => {
<button direction = calculateDirection(tabData.pathname, currentPathname)
class="text-neutral-400 transition-colors hover:text-lazuli-primary" dispatch('navigate', { direction, pathname: tabData.pathname })
on:click={() => { }}
direction = calculateDirection(tabData.pathname, currentPathname) >
dispatch('navigate', { direction, pathname: tabData.pathname }) <i class="{tabData.icon} pointer-events-none" />
}} </button>
>
<i class={tabData.icon} />
</button>
{/if}
{/each} {/each}
{#if navTabs.some((tab) => tab.pathname === currentPathname)} {#if navTabs.some((tab) => tab.pathname === currentPathname)}
<div bind:this={indicatorBar} transition:fade class="absolute bottom-0 h-1 rounded-full bg-white transition-all duration-300 ease-in-out" /> <div bind:this={indicatorBar} transition:fade class="absolute bottom-0 h-1 rounded-full bg-white transition-all duration-300 ease-in-out" />
@@ -67,9 +65,16 @@
</div> </div>
<style> <style>
#bottom-tab-list { #tab-list {
padding: 16px 0px; padding: 16px 0px;
font-size: 20px; font-size: 20px;
line-height: 28px; line-height: 28px;
} }
button:not(:disabled) {
cursor: pointer;
color: rgb(163 163, 163);
}
button:not(:disabled):hover {
color: var(--lazuli-primary);
}
</style> </style>

View File

@@ -1,73 +0,0 @@
<script context="module" lang="ts">
export interface NavTab {
pathname: string
name: string
icon: string
}
</script>
<script lang="ts">
export let navTabs: NavTab[]
export let currentPathname: string
export let transitionTime: number = 200
import { fade } from 'svelte/transition'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
type PageTransitionDirection = 'up' | 'down'
let direction: PageTransitionDirection = 'down'
const calculateDirection = (newPage: string, currentPage: string): PageTransitionDirection => {
const newPageIndex = navTabs.findIndex((tab) => tab.pathname === newPage)
const currentPageIndex = navTabs.findIndex((tab) => tab.pathname === currentPage)
return newPageIndex > currentPageIndex ? 'down' : 'up'
}
let activeTab: HTMLButtonElement, indicatorBar: HTMLDivElement, tabList: HTMLDivElement
$: calculateBar(activeTab)
const calculateBar = (activeTab: HTMLButtonElement) => {
if (activeTab && indicatorBar && tabList) {
const listRect = tabList.getBoundingClientRect()
const tabRec = activeTab.getBoundingClientRect()
if (direction === 'down') {
indicatorBar.style.bottom = `${listRect.bottom - tabRec.bottom}px`
setTimeout(() => (indicatorBar.style.top = `${tabRec.top - listRect.top}px`), transitionTime)
} else {
indicatorBar.style.top = `${tabRec.top - listRect.top}px`
setTimeout(() => (indicatorBar.style.bottom = `${listRect.bottom - tabRec.bottom}px`), transitionTime)
}
}
}
</script>
<div class="relative flex h-full flex-col gap-6 rounded-lg px-3 py-12" bind:this={tabList}>
{#each navTabs as tabData}
{#if currentPathname === tabData.pathname}
<button bind:this={activeTab} class="pointer-events-none grid aspect-square w-14 place-items-center text-white transition-colors" disabled>
<span class="flex flex-col gap-2 text-xs">
<i class="{tabData.icon} text-xl" />
{tabData.name}
</span>
</button>
{:else}
<button
class="grid aspect-square w-14 place-items-center text-neutral-400 transition-colors hover:text-lazuli-primary"
on:click={() => {
direction = calculateDirection(tabData.pathname, currentPathname)
dispatch('navigate', { direction, pathname: tabData.pathname })
}}
>
<span class="flex flex-col gap-2 text-xs">
<i class="{tabData.icon} text-xl" />
{tabData.name}
</span>
</button>
{/if}
{/each}
{#if navTabs.some((tab) => tab.pathname === currentPathname)}
<div bind:this={indicatorBar} transition:fade class="absolute left-0 w-[0.2rem] rounded-full bg-white transition-all duration-300 ease-in-out" />
{/if}
</div>

View File

@@ -1,56 +1,100 @@
<script lang="ts"> <script lang="ts">
import { fly } from 'svelte/transition' import { fly } from 'svelte/transition'
import { goto } from '$app/navigation' import { goto, beforeNavigate } from '$app/navigation'
import { pageWidth } from '$lib/stores' import { pageWidth } from '$lib/stores'
import NavbarSide, { type NavTab } from '$lib/components/util/navbarSide.svelte' import type { LayoutData } from './$types'
import NavbarFoot from '$lib/components/util/navbarFoot.svelte' import type { Tab } from './+layout'
import { page } from '$app/stores' import { onMount } from 'svelte'
let currentPathname = $page.url.pathname export let data: LayoutData
const contentTabs: NavTab[] = [
{
pathname: '/',
name: 'Home',
icon: 'fa-solid fa-house',
},
{
pathname: '/user',
name: 'User',
icon: 'fa-solid fa-user', // This would be a cool spot for a user-uploaded pfp
},
{
pathname: '/search',
name: 'Search',
icon: 'fa-solid fa-search',
},
{
pathname: '/library',
name: 'Libray',
icon: 'fa-solid fa-bars-staggered',
},
]
const pageTransitionTime: number = 200 const pageTransitionTime: number = 200
let currentTabIndex = -1
type PageTransitionDirection = 1 | -1 type PageTransitionDirection = 1 | -1
let directionMultiplier: PageTransitionDirection = 1 let directionMultiplier: PageTransitionDirection = 1
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)
})
</script> </script>
{#if $pageWidth >= 768} {#if $pageWidth >= 768}
<div id="content-grid" class="grid h-full grid-rows-1 gap-8 overflow-hidden"> <div id="content-grid" class="grid h-full grid-rows-1 gap-8 overflow-hidden">
<NavbarSide <div id="sidebar" class="relative grid h-full grid-cols-1 gap-6 overflow-clip p-3 pt-12" bind:this={tabList}>
navTabs={contentTabs} {#each data.tabs.filter((tab) => tab.type === 'nav') as nav, index}
{currentPathname} <button
transitionTime={pageTransitionTime} class="navTab grid aspect-square w-14 place-items-center transition-colors"
on:navigate={(event) => { bind:this={data.tabs[index].button}
event.detail.direction === 'up' ? (directionMultiplier = 1) : (directionMultiplier = -1) disabled={inPathnameHeirarchy(data.url.pathname, nav.pathname)}
currentPathname = event.detail.pathname on:click={() => {
goto(currentPathname) calculateDirection(nav)
}} goto(nav.pathname)
/> }}
>
<span class="pointer-events-none flex flex-col gap-2 text-xs">
<i class="{nav.icon} text-xl" />
{nav.name}
</span>
</button>
{/each}
<section class="no-scrollbar flex flex-col gap-4 overflow-y-scroll px-1">
{#each data.tabs.filter((tab) => tab.type === 'playlist') as playlist}
<button
title={playlist.name}
disabled={new URLSearchParams(data.url.search).get('playlist') === new URLSearchParams(playlist.pathname.split('?')[1]).get('playlist')}
class="playlistTab aspect-square w-full rounded-lg border-white bg-cover bg-center transition-all"
style="background-image: url({playlist.icon});"
on:click={() => {
calculateDirection(playlist)
goto(playlist.pathname)
}}
></button>
{/each}
</section>
<div bind:this={indicatorBar} class="absolute left-0 w-[0.2rem] rounded-full bg-white transition-all duration-300 ease-in-out" />
</div>
<section class="no-scrollbar relative overflow-y-scroll"> <section class="no-scrollbar relative overflow-y-scroll">
{#key currentPathname} {#key data.url}
<div <div
in:fly={{ y: -50 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }} in:fly={{ y: -50 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }}
out:fly={{ y: 50 * directionMultiplier, duration: pageTransitionTime }} out:fly={{ y: 50 * directionMultiplier, duration: pageTransitionTime }}
@@ -66,7 +110,7 @@
</div> </div>
{:else} {:else}
<div class="h-full overflow-hidden"> <div class="h-full overflow-hidden">
{#key currentPathname} {#key data.url.pathname}
<section <section
in:fly={{ x: 200 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }} in:fly={{ x: 200 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }}
out:fly={{ x: -200 * directionMultiplier, duration: pageTransitionTime }} out:fly={{ x: -200 * directionMultiplier, duration: pageTransitionTime }}
@@ -77,8 +121,7 @@
{/key} {/key}
<footer class="fixed bottom-0 flex w-full flex-col items-center justify-center"> <footer class="fixed bottom-0 flex w-full flex-col items-center justify-center">
<!-- <MiniPlayer displayMode={'vertical'} /> --> <!-- <MiniPlayer displayMode={'vertical'} /> -->
<NavbarFoot <!-- <NavbarFoot
navTabs={contentTabs}
{currentPathname} {currentPathname}
transitionTime={pageTransitionTime} transitionTime={pageTransitionTime}
on:navigate={(event) => { on:navigate={(event) => {
@@ -86,7 +129,7 @@
currentPathname = event.detail.pathname currentPathname = event.detail.pathname
goto(currentPathname) goto(currentPathname)
}} }}
/> /> -->
</footer> </footer>
</div> </div>
{/if} {/if}
@@ -95,4 +138,16 @@
#content-grid { #content-grid {
grid-template-columns: max-content auto; grid-template-columns: max-content auto;
} }
#sidebar {
grid-template-rows: repeat(4, min-content) auto;
}
.navTab:not(:disabled) {
color: rgb(163 163, 163);
}
.navTab:not(:disabled):hover {
color: var(--lazuli-primary);
}
.playlistTab:not(:disabled):not(:hover) {
filter: brightness(50%);
}
</style> </style>

View File

@@ -0,0 +1,55 @@
import type { LayoutLoad } from './$types'
export interface Tab {
type: 'nav' | 'playlist'
pathname: string
name: string
icon: string
button?: HTMLButtonElement
}
export const load: LayoutLoad = ({ url }) => {
const navTabs: Tab[] = [
{
type: 'nav',
pathname: '/',
name: 'Home',
icon: 'fa-solid fa-house',
},
{
type: 'nav',
pathname: '/user',
name: 'User',
icon: 'fa-solid fa-user', // This would be a cool spot for a user-uploaded pfp
},
{
type: 'nav',
pathname: '/search',
name: 'Search',
icon: 'fa-solid fa-search',
},
{
type: 'nav',
pathname: '/library',
name: 'Libray',
icon: 'fa-solid fa-bars-staggered',
},
]
const playlistTabs: Tab[] = [
{
type: 'playlist',
pathname: '/library?playlist=AD:TRANCE 10',
name: 'AD:TRANCE 10',
icon: 'https://www.diverse.direct/wp/wp-content/uploads/470_artwork.jpg',
},
{
type: 'playlist',
pathname: '/library?playlist=Fionaredica',
name: 'Fionaredica',
icon: 'https://f4.bcbits.com/img/a2436961975_10.jpg',
},
]
return { tabs: navTabs.concat(playlistTabs) }
}

View File

@@ -0,0 +1 @@
<h1>Add subroutes for artist, automatically generated playlists, albums, and songs</h1>

View File

@@ -1,5 +1,12 @@
import type { LayoutServerLoad } from './$types' import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = ({ locals }) => { export const load: LayoutServerLoad = ({ url, locals }) => {
return { user: locals.user } const { pathname, search } = url
return {
url: {
pathname,
search,
},
user: locals.user,
}
} }