More components

This commit is contained in:
Eclypsed
2024-01-29 12:29:32 -05:00
parent 4ae54aa14c
commit 098ac487ec
7 changed files with 206 additions and 209 deletions

View File

@@ -0,0 +1,40 @@
<script lang="ts" context="module">
export interface NavTab {
pathname: string
name: string
icon: string
}
</script>
<script lang="ts">
export let disabled = false
export let nav: NavTab
import { createEventDispatcher } from "svelte";
import { goto } from "$app/navigation";
const dispatch = createEventDispatcher()
</script>
<button
class="grid aspect-square w-full place-items-center transition-colors"
{disabled}
on:click={() => {
dispatch('click')
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>
<style>
button:not(:disabled) {
color: rgb(163 163, 163);
}
button:not(:disabled):hover {
color: var(--lazuli-primary);
}
</style>

View File

@@ -0,0 +1,38 @@
<script lang="ts" context="module">
export interface PlaylistTab {
id: string
name: string
thumbnail: string
}
</script>
<script lang="ts">
export let disabled = false
export let playlist: PlaylistTab
import { createEventDispatcher } from "svelte";
import { goto } from "$app/navigation";
const dispatch = createEventDispatcher()
</script>
<div class="flex items-center gap-1">
<button
title={playlist.name}
{disabled}
class="relative aspect-square w-full rounded-lg bg-cover bg-center transition-all"
style="background-image: url({playlist.thumbnail});"
on:click={() => {
dispatch('click')
goto(`/library?playlist=${playlist.id}}`)
}}
>
</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>
<style>
button:not(:disabled):not(:hover) {
filter: brightness(50%);
}
</style>

View File

@@ -1,80 +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, onMount } from 'svelte'
const dispatch = createEventDispatcher()
type PageTransitionDirection = 'left' | 'right'
let direction: PageTransitionDirection = 'right'
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 ? 'right' : 'left'
}
let indicatorBar: HTMLDivElement, tabList: HTMLDivElement
const calculateBar = (newPathname: string) => {
const newTab = document.querySelector(`button[data-tab="${newPathname}"]`)
if (newTab && indicatorBar && tabList) {
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') {
shiftRight()
setTimeout(shiftLeft, transitionTime)
} else {
shiftLeft()
setTimeout(shiftRight, transitionTime)
}
}
}
</script>
<div bind:this={tabList} id="tab-list" class="relative flex w-full items-center justify-around bg-black">
{#each navTabs as tabData}
<button
class="transition-colors"
data-tab={tabData.pathname}
disabled={currentPathname === tabData.pathname}
on:click={() => {
direction = calculateDirection(tabData.pathname, currentPathname)
dispatch('navigate', { direction, pathname: tabData.pathname })
}}
>
<i class="{tabData.icon} pointer-events-none" />
</button>
{/each}
{#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" />
{/if}
</div>
<style>
#tab-list {
padding: 16px 0px;
font-size: 20px;
line-height: 28px;
}
button:not(:disabled) {
cursor: pointer;
color: rgb(163 163, 163);
}
button:not(:disabled):hover {
color: var(--lazuli-primary);
}
</style>

View File

@@ -15,9 +15,7 @@ type UserQueryParams = {
includePassword?: boolean
}
type ServiceType = 'jellyfin' | 'youtube-music'
interface Service {
interface DBServiceData {
id: string
type: ServiceType
userId: string
@@ -31,10 +29,10 @@ interface DBServiceRow {
url: string
}
interface Connection {
interface DBConnectionData {
id: string
user: User
service: Service
service: DBServiceData
accessToken: string
refreshToken: string | null
expiry: number | null
@@ -81,13 +79,13 @@ export class Users {
}
export class Services {
static getService = (id: string): Service => {
static getService = (id: string): DBServiceData => {
const { type, userId, url } = db.prepare('SELECT * FROM Users WHERE id = ?').get(id) as DBServiceRow
const service: Service = { id, type: type as ServiceType, userId, url: new URL(url) }
const service: DBServiceData = { id, type: type as ServiceType, userId, url: new URL(url) }
return service
}
static addService = (type: ServiceType, userId: string, url: URL): Service => {
static addService = (type: ServiceType, userId: string, url: URL): DBServiceData => {
const serviceId = generateUUID()
db.prepare('INSERT INTO Services(id, type, userId, url) VALUES(?, ?, ?, ?)').run(serviceId, type, userId, url.origin)
return this.getService(serviceId)
@@ -100,15 +98,15 @@ export class Services {
}
export class Connections {
static getConnection = (id: string): Connection => {
static getConnection = (id: string): DBConnectionData => {
const { userId, serviceId, accessToken, refreshToken, expiry } = db.prepare('SELECT * FROM Connections WHERE id = ?').get(id) as DBConnectionRow
const connection: Connection = { id, user: Users.getUser(userId)!, service: Services.getService(serviceId), accessToken, refreshToken, expiry }
const connection: DBConnectionData = { id, user: Users.getUser(userId)!, service: Services.getService(serviceId), accessToken, refreshToken, expiry }
return connection
}
static getUserConnections = (userId: string): Connection[] => {
static getUserConnections = (userId: string): DBConnectionData[] => {
const connectionRows = db.prepare('SELECT * FROM Connections WHERE userId = ?').all(userId) as DBConnectionRow[]
const connections: Connection[] = []
const connections: DBConnectionData[] = []
const user = Users.getUser(userId)!
connectionRows.forEach((row) => {
const { id, serviceId, accessToken, refreshToken, expiry } = row
@@ -117,7 +115,7 @@ export class Connections {
return connections
}
static addConnection = (userId: string, serviceId: string, accessToken: string, refreshToken: string | null, expiry: number | null): Connection => {
static addConnection = (userId: string, serviceId: string, accessToken: string, refreshToken: string | null, expiry: number | null): DBConnectionData => {
const connectionId = generateUUID()
db.prepare('INSERT INTO Connections(id, userId, serviceId, accessToken, refreshToken, expiry) VALUES(?, ?, ?, ?, ?, ?)').run(connectionId, userId, serviceId, accessToken, refreshToken, expiry)
return this.getConnection(connectionId)