SERIOUS messing around with the navbar
This commit is contained in:
@@ -4,6 +4,10 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
* {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
|
|||||||
55
src/routes/(app)/+layout.ts
Normal file
55
src/routes/(app)/+layout.ts
Normal 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) }
|
||||||
|
}
|
||||||
1
src/routes/(app)/details/+page.svelte
Normal file
1
src/routes/(app)/details/+page.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<h1>Add subroutes for artist, automatically generated playlists, albums, and songs</h1>
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user