mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-20 22:12:04 +00:00
feat: better description UI for irc, w2g, and torrent client
fix: navigation with arrow keys on player feat: view schedule without my list
This commit is contained in:
parent
b52d9dccc4
commit
5194b085a3
17 changed files with 148 additions and 75 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "6.4.17",
|
||||
"version": "6.4.18",
|
||||
"license": "BUSL-1.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.14.4",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<Play fill='currentColor' class='mr-2' size={iconSizes[size]} />
|
||||
{@const status = list(media)}
|
||||
{#if status === 'COMPLETED'}
|
||||
Rewatch Now
|
||||
Rewatch
|
||||
{:else if status === 'CURRENT' || status === 'REPEATING' || status === 'PAUSED'}
|
||||
Continue
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
import { Button } from '$lib/components/ui/button'
|
||||
import * as Command from '$lib/components/ui/command'
|
||||
import * as Popover from '$lib/components/ui/popover'
|
||||
import { intputType } from '$lib/modules/navigate'
|
||||
import { inputType } from '$lib/modules/navigate'
|
||||
import { cn } from '$lib/utils.js'
|
||||
|
||||
export let items: readonly value[] = []
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
<Command.Root>
|
||||
<Command.Input {placeholder} class='h-9 placeholder:opacity-50' />
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
{#if $intputType === 'dpad'}
|
||||
{#if $inputType === 'dpad'}
|
||||
<Command.Group class='shrink-0' alwaysRender={true}>
|
||||
<Command.Item
|
||||
alwaysRender={true}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import { Button } from '../button'
|
||||
import { Messages, UserList } from '../chat'
|
||||
import { Separator } from '../separator'
|
||||
|
||||
import type MessageClient from '$lib/modules/irc'
|
||||
|
||||
|
|
@ -47,10 +48,16 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class='flex flex-col w-full relative px-md-4 h-full overflow-hidden'>
|
||||
<div class='flex md:flex-row flex-col-reverse w-full h-full pt-4'>
|
||||
<div class='flex flex-col w-full relative h-full overflow-clip'>
|
||||
<div class='space-y-0.5 px-10 pt-10'>
|
||||
<h2 class='text-2xl font-bold'>Global App Chat</h2>
|
||||
<p class='text-muted-foreground'>
|
||||
Chat with other users of the app, share your thoughts, ask questions and have fun!
|
||||
</p>
|
||||
<Separator class='!my-6' />
|
||||
</div>
|
||||
<div class='flex md:flex-row flex-col-reverse w-full h-full'>
|
||||
<div class='flex flex-col justify-end overflow-clip flex-grow px-4 pb-4 h-full min-h-0'>
|
||||
<div class='mb-auto hidden md:block text-center font-bold text-lg'>Global App Chat</div>
|
||||
<div class='h-full overflow-y-scroll min-h-0 w-full'>
|
||||
<Messages messages={client.messages} />
|
||||
</div>
|
||||
|
|
@ -71,6 +78,5 @@
|
|||
</div>
|
||||
</div>
|
||||
<UserList users={processedUsers} />
|
||||
<div class='md:hidden px-6 text-center font-bold text-lg'>Global App Chat</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
import { authAggregator } from '$lib/modules/auth'
|
||||
import { isPlaying } from '$lib/modules/idle'
|
||||
import native from '$lib/modules/native'
|
||||
import { click, keywrap } from '$lib/modules/navigate'
|
||||
import { click, inputType, keywrap } from '$lib/modules/navigate'
|
||||
import { settings, SUPPORTS } from '$lib/modules/settings'
|
||||
import { server } from '$lib/modules/torrent'
|
||||
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||
|
|
@ -575,7 +575,11 @@
|
|||
desc: 'Cycle Subtitles'
|
||||
},
|
||||
ArrowLeft: {
|
||||
fn: () => {
|
||||
fn: (e) => {
|
||||
if ($inputType === 'dpad') return
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
seek(-Number($settings.playerSeek))
|
||||
},
|
||||
id: 'fast_rewind',
|
||||
|
|
@ -584,7 +588,11 @@
|
|||
desc: 'Rewind'
|
||||
},
|
||||
ArrowRight: {
|
||||
fn: () => {
|
||||
fn: (e) => {
|
||||
if ($inputType === 'dpad') return
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
seek(Number($settings.playerSeek))
|
||||
},
|
||||
id: 'fast_forward',
|
||||
|
|
@ -593,7 +601,11 @@
|
|||
desc: 'Seek'
|
||||
},
|
||||
ArrowUp: {
|
||||
fn: () => {
|
||||
fn: (e) => {
|
||||
if ($inputType === 'dpad') return
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
$volume = Math.min(1, $volume + 0.05)
|
||||
},
|
||||
id: 'volume_up',
|
||||
|
|
@ -602,7 +614,11 @@
|
|||
desc: 'Volume Up'
|
||||
},
|
||||
ArrowDown: {
|
||||
fn: () => {
|
||||
fn: (e) => {
|
||||
if ($inputType === 'dpad') return
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
$volume = Math.max(0, $volume - 0.05)
|
||||
},
|
||||
id: 'volume_down',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</script>
|
||||
|
||||
{#if !$breakpoints.md}
|
||||
<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] group-fullscreen/fullscreen:hidden' class:!w-[152px]={open} class:!h-[140px]={open}>
|
||||
<div class='shrink-0 z-50 bg-black absolute left-4 bottom-4 w-14 h-[52px] flex rounded-md items-end justify-end overflow-clip transition-[width,height] group-fullscreen/fullscreen:hidden' 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 }}>
|
||||
|
|
|
|||
|
|
@ -440,8 +440,8 @@ class AnilistClient {
|
|||
return Object.entries(searchResults).map(([filename, id]) => [filename, search.data!.Page!.media!.find(media => media?.id === id)]) as Array<[string, Media | undefined]>
|
||||
}
|
||||
|
||||
schedule (ids?: number[]) {
|
||||
return queryStore({ client: this.client, query: Schedule, variables: { ids, seasonCurrent: currentSeason, seasonYearCurrent: currentYear, seasonLast: lastSeason, seasonYearLast: lastYear, seasonNext: nextSeason, seasonYearNext: nextYear }, pause: true })
|
||||
schedule (ids?: number[], onList = true) {
|
||||
return queryStore({ client: this.client, query: Schedule, variables: { ids, onList, seasonCurrent: currentSeason, seasonYearCurrent: currentYear, seasonLast: lastSeason, seasonYearLast: lastYear, seasonNext: nextSeason, seasonYearNext: nextYear } })
|
||||
}
|
||||
|
||||
async toggleFav (id: number) {
|
||||
|
|
|
|||
|
|
@ -243,34 +243,34 @@ export const ScheduleMedia = gql(`
|
|||
`)
|
||||
|
||||
export const Schedule = gql(`
|
||||
query Schedule($seasonCurrent: MediaSeason, $seasonYearCurrent: Int, $seasonLast: MediaSeason, $seasonYearLast: Int, $seasonNext: MediaSeason, $seasonYearNext: Int, $ids: [Int]) {
|
||||
query Schedule($seasonCurrent: MediaSeason, $seasonYearCurrent: Int, $seasonLast: MediaSeason, $seasonYearLast: Int, $seasonNext: MediaSeason, $seasonYearNext: Int, $onList: Boolean, $ids: [Int]) {
|
||||
curr1: Page(page: 1) {
|
||||
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {
|
||||
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) {
|
||||
...ScheduleMedia
|
||||
}
|
||||
}
|
||||
curr2: Page(page: 2) {
|
||||
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {
|
||||
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) {
|
||||
...ScheduleMedia
|
||||
}
|
||||
}
|
||||
curr3: Page(page: 3) {
|
||||
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {
|
||||
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) {
|
||||
...ScheduleMedia
|
||||
}
|
||||
}
|
||||
residue: Page(page: 1) {
|
||||
media(type: ANIME, season: $seasonLast, seasonYear: $seasonYearLast, episodes_greater: 16, countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {
|
||||
media(type: ANIME, season: $seasonLast, seasonYear: $seasonYearLast, episodes_greater: 16, countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) {
|
||||
...ScheduleMedia
|
||||
}
|
||||
},
|
||||
next1: Page(page: 1) {
|
||||
media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {
|
||||
media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) {
|
||||
...ScheduleMedia
|
||||
}
|
||||
},
|
||||
next2: Page(page: 2) {
|
||||
media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {
|
||||
media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) {
|
||||
...ScheduleMedia
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,11 +64,12 @@ export default new class AuthAggregator {
|
|||
|
||||
// QUERIES/MUTATIONS
|
||||
|
||||
schedule () {
|
||||
if (this.anilist()) return client.schedule()
|
||||
if (this.kitsu()) return kitsu.schedule()
|
||||
schedule (onList = true) {
|
||||
console.log('re-running')
|
||||
if (this.anilist()) return client.schedule(undefined, onList)
|
||||
if (this.kitsu()) return kitsu.schedule(onList)
|
||||
|
||||
return local.schedule()
|
||||
return local.schedule(onList)
|
||||
}
|
||||
|
||||
toggleFav (id: number) {
|
||||
|
|
|
|||
|
|
@ -393,9 +393,9 @@ export default new class KitsuSync {
|
|||
|
||||
// QUERIES/MUTATIONS
|
||||
|
||||
schedule () {
|
||||
schedule (onList = true) {
|
||||
const ids = Object.keys(this.userlist.value).map(id => parseInt(id))
|
||||
return client.schedule(ids.length ? ids : undefined)
|
||||
return client.schedule(onList && ids.length ? ids : undefined)
|
||||
}
|
||||
|
||||
async toggleFav (id: number) {
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ export default new class LocalSync {
|
|||
}
|
||||
}
|
||||
|
||||
schedule (): ReturnType<typeof client.schedule> {
|
||||
schedule (onList = true): ReturnType<typeof client.schedule> {
|
||||
const ids = Object.values(this.entries.value).map(({ mediaListEntry }) => mediaListEntry?.id).filter(e => e != null)
|
||||
return client.schedule(ids.length ? ids : undefined)
|
||||
return client.schedule(onList && ids.length ? ids : undefined)
|
||||
}
|
||||
|
||||
toggleFav (id: number) {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { writable, type Writable } from 'simple-store-svelte'
|
|||
let lastHoverElement: ((_: boolean) => unknown) | null = null
|
||||
|
||||
type InputType = 'mouse' | 'touch' | 'dpad'
|
||||
export const intputType: Writable<InputType> = writable('touch')
|
||||
export const inputType: Writable<InputType> = writable('touch')
|
||||
|
||||
function pointerEvent ({ pointerType }: PointerEvent) {
|
||||
intputType.value = pointerType === 'mouse' ? 'mouse' : 'touch'
|
||||
inputType.value = pointerType === 'mouse' ? 'mouse' : 'touch'
|
||||
}
|
||||
addEventListener('pointerdown', pointerEvent)
|
||||
addEventListener('pointermove', pointerEvent)
|
||||
|
|
@ -18,9 +18,9 @@ const pointerTypes = [{ pointer: '(pointer: coarse)', value: 'touch' }, { pointe
|
|||
// for stuff like surface tablets, which can dynamically switch between touch and mouse
|
||||
for (const { pointer, value } of pointerTypes) {
|
||||
const media = matchMedia(pointer)
|
||||
if (media.matches) intputType.value = value as InputType
|
||||
if (media.matches) inputType.value = value as InputType
|
||||
media.addEventListener('change', e => {
|
||||
if (e.matches) intputType.value = value as InputType
|
||||
if (e.matches) inputType.value = value as InputType
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ export function clickwrap (cb: (_: MouseEvent) => unknown = noop) {
|
|||
|
||||
export function keywrap (cb: (_: KeyboardEvent) => unknown = noop) {
|
||||
return (e: KeyboardEvent) => {
|
||||
if ((e.key === 'Enter' || e.key === ' ') && intputType.value === 'dpad' && !e.repeat) {
|
||||
if ((e.key === 'Enter' || e.key === ' ') && inputType.value === 'dpad' && !e.repeat) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
|
|
@ -62,7 +62,7 @@ export function click (node: HTMLElement, cb: (_: Event) => unknown = noop) {
|
|||
cb(e)
|
||||
}, { signal: ctrl.signal })
|
||||
node.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter' && intputType.value === 'dpad') {
|
||||
if (e.key === 'Enter' && inputType.value === 'dpad') {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
cb(e)
|
||||
|
|
@ -92,12 +92,12 @@ export function hover (node: HTMLElement, [cb = noop, hoverUpdate = noop]: [type
|
|||
node.addEventListener('pointerenter', () => {
|
||||
lastHoverElement?.(false)
|
||||
hoverUpdate(true)
|
||||
if (intputType.value === 'mouse') lastHoverElement = hoverUpdate
|
||||
if (inputType.value === 'mouse') lastHoverElement = hoverUpdate
|
||||
}, { signal: ctrl.signal })
|
||||
node.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
if (intputType.value === 'dpad') return
|
||||
if (intputType.value === 'mouse') return cb()
|
||||
if (inputType.value === 'dpad') return
|
||||
if (inputType.value === 'mouse') return cb()
|
||||
if (lastHoverElement === hoverUpdate) {
|
||||
lastHoverElement = null
|
||||
navigator.vibrate(15)
|
||||
|
|
@ -108,7 +108,7 @@ export function hover (node: HTMLElement, [cb = noop, hoverUpdate = noop]: [type
|
|||
}
|
||||
}, { signal: ctrl.signal })
|
||||
node.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && intputType.value === 'dpad') {
|
||||
if (e.key === 'Enter' && inputType.value === 'dpad') {
|
||||
e.stopPropagation()
|
||||
lastHoverElement?.(false)
|
||||
if (lastHoverElement === hoverUpdate) {
|
||||
|
|
@ -121,13 +121,13 @@ export function hover (node: HTMLElement, [cb = noop, hoverUpdate = noop]: [type
|
|||
}
|
||||
}, { signal: ctrl.signal })
|
||||
node.addEventListener('pointerleave', () => {
|
||||
if (intputType.value !== 'touch') hoverUpdate(false)
|
||||
if (inputType.value !== 'touch') hoverUpdate(false)
|
||||
}, { signal: ctrl.signal })
|
||||
node.addEventListener('pointermove', () => {
|
||||
if (intputType.value === 'touch') hoverUpdate(false)
|
||||
if (inputType.value === 'touch') hoverUpdate(false)
|
||||
}, { signal: ctrl.signal })
|
||||
node.addEventListener('drag', () => {
|
||||
if (intputType.value === 'mouse') hoverUpdate(false)
|
||||
if (inputType.value === 'mouse') hoverUpdate(false)
|
||||
}, { signal: ctrl.signal })
|
||||
|
||||
return { destroy: () => ctrl.abort() }
|
||||
|
|
@ -277,7 +277,7 @@ document.addEventListener('keydown', e => {
|
|||
if (e.key in DirectionKeyMap) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
intputType.value = 'dpad'
|
||||
inputType.value = 'dpad'
|
||||
navigateDPad(DirectionKeyMap[e.key as 'ArrowDown' | 'ArrowUp' | 'ArrowLeft' | 'ArrowRight'])
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
<meta name='viewport' content='width=device-width, initial-scale={SUPPORTS.isAndroid ? $settings.uiScale : 1}, user-scalable=0' />
|
||||
</svelte:head>
|
||||
|
||||
<div class='w-full h-full flex flex-col backface-hidden bg-black relative overflow-clip border-l-2 [border-image:linear-gradient(to_bottom,white_var(--progress),#2dcf58_var(--progress))_1] preserve-3d' bind:this={root} id='root' style:--progress='{100 - updateProgress}%'>
|
||||
<div class='w-full h-full flex flex-col backface-hidden bg-black relative overflow-clip md:border-l-2 [border-image:linear-gradient(to_bottom,white_var(--progress),#2dcf58_var(--progress))_1] preserve-3d' bind:this={root} id='root' style:--progress='{100 - updateProgress}%'>
|
||||
<ProgressBar zIndex={100} />
|
||||
<Toaster position='top-right' expand={true} />
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@
|
|||
$: mediaId = media.id
|
||||
$: following = authAggregator.following(mediaId)
|
||||
$: followerEntries = $following?.data?.Page?.mediaList?.filter(e => e?.user?.id !== authAggregator.id()) ?? []
|
||||
|
||||
$: nativeTitle = media.title?.native ?? media.title?.romaji ?? ''
|
||||
$: romajiTitle = media.title?.romaji ?? media.title?.native ?? ''
|
||||
</script>
|
||||
|
||||
<div class='min-w-0 -ml-14 pl-14 grow items-center flex flex-col h-full overflow-y-auto z-10 pointer-events-none pb-10' use:dragScroll on:scroll={handleScroll}>
|
||||
|
|
@ -75,30 +78,34 @@
|
|||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
<div class='flex flex-col gap-4 items-center md:items-start justify-end w-full'>
|
||||
<div class='flex flex-col gap-1 text-center md:text-start w-full'>
|
||||
<h3 class='text-lg capitalize leading-none text-muted-foreground'>
|
||||
{season(media)}
|
||||
</h3>
|
||||
<h1 class='font-black text-2xl md:text-4xl line-clamp-2 text-white'>{title(media)}</h1>
|
||||
<h2 class='line-clamp-1 text-sm md:text-lg font-light text-muted-foreground'>{media.title?.romaji ?? ''}</h2>
|
||||
<div class='flex flex-col gap-1.5 text-center md:text-start w-full'>
|
||||
<h2 class='line-clamp-1 text-base md:text-lg font-light text-muted-foreground'>{media.title?.romaji?.toLowerCase().trim() === title(media).toLowerCase().trim() ? nativeTitle : romajiTitle}</h2>
|
||||
<h1 class='font-black text-3xl md:text-4xl line-clamp-2 text-white'>{title(media)}</h1>
|
||||
<div class='flex-wrap w-full justify-start md:pt-1 gap-4 hidden md:flex'>
|
||||
<div class='rounded px-3 font-bold' style:background={media.coverImage?.color ?? '#27272a'}>
|
||||
<div class='rounded px-3.5 font-bold' style:background={media.coverImage?.color ?? '#27272a'}>
|
||||
<div class='text-contrast'>
|
||||
{of(media) ?? duration(media) ?? 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div class='rounded px-3 font-bold' style:background={media.coverImage?.color ?? '#27272a'}>
|
||||
<div class='rounded px-3.5 font-bold' style:background={media.coverImage?.color ?? '#27272a'}>
|
||||
<div class='text-contrast'>
|
||||
{format(media)}
|
||||
</div>
|
||||
</div>
|
||||
<div class='rounded px-3 font-bold' style:background={media.coverImage?.color ?? '#27272a'}>
|
||||
<div class='rounded px-3.5 font-bold' style:background={media.coverImage?.color ?? '#27272a'}>
|
||||
<div class='text-contrast'>
|
||||
{status(media)}
|
||||
</div>
|
||||
</div>
|
||||
{#if season(media)}
|
||||
<div class='rounded px-3.5 font-bold' style:background={media.coverImage?.color ?? '#27272a'}>
|
||||
<div class='text-contrast capitalize'>
|
||||
{season(media)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if media.averageScore}
|
||||
<div class='rounded px-3 font-bold {getColorForRating(media.averageScore)}'>
|
||||
<div class='rounded px-3.5 font-bold {getColorForRating(media.averageScore)}'>
|
||||
<div class='text-contrast'>
|
||||
{media.averageScore}%
|
||||
</div>
|
||||
|
|
@ -106,7 +113,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
<div class='md:block hidden relative pb-6 md:pt-2 md:pb-0'>
|
||||
<div class='line-clamp-4 md:text-start text-center text-xs md:text-md leading-2 font-light antialiased whitespace-pre-wrap text-muted-foreground'>{desc(media)}</div>
|
||||
<div class='line-clamp-4 md:text-start text-center text-sm md:text-md leading-2 font-light antialiased whitespace-pre-wrap text-muted-foreground'>{desc(media)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -152,6 +159,13 @@
|
|||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class='flex gap-2 items-center md:justify-start md:self-start flex-wrap'>
|
||||
{#each media.genres ?? [] as genre (genre)}
|
||||
<div class='bg-secondary text-secondary-foreground text-sm font-medium rounded-md h-9 items-center justify-center flex px-4 text-nowrap'>
|
||||
{genre}
|
||||
</div>
|
||||
{/each}
|
||||
</div> -->
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang='ts'>
|
||||
import { page } from '$app/stores'
|
||||
import SettingsNav from '$lib/components/SettingsNav.svelte'
|
||||
import { Separator } from '$lib/components/ui/separator'
|
||||
import { Globe } from '$lib/components/ui/torrentclient'
|
||||
|
|
@ -11,28 +12,45 @@
|
|||
},
|
||||
{
|
||||
title: 'Files',
|
||||
href: '/app/client/files/'
|
||||
href: '/app/client/files/',
|
||||
overview: {
|
||||
title: 'File List',
|
||||
desc: 'Files in the currently active torrent, their download progress, and amount of active stream selections.'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Peers',
|
||||
href: '/app/client/peers/'
|
||||
href: '/app/client/peers/',
|
||||
overview: {
|
||||
title: 'Peer List',
|
||||
desc: 'Peers connected to the currently active torrent, their statistics, region etc.'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Library',
|
||||
href: '/app/client/library/'
|
||||
href: '/app/client/library/',
|
||||
overview: {
|
||||
title: 'Torrent Library',
|
||||
desc: 'All of your downloaded torrents. If Persist Files is enabled then your previously downloaded torrents will show up here.'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
href: '/app/settings/client/'
|
||||
}
|
||||
]
|
||||
|
||||
$: overview = items.find(({ href }) => href === $page.url.pathname)?.overview ?? {
|
||||
title: 'Torrent Client',
|
||||
desc: 'Monitor your torrents, and configure settings for your torrent client.'
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='space-y-6 p-10 pb-0 w-full h-full flex flex-col min-w-0'>
|
||||
<div class='space-y-0.5'>
|
||||
<h2 class='text-2xl font-bold'>Torrent Client</h2>
|
||||
<h2 class='text-2xl font-bold'>{overview.title}</h2>
|
||||
<p class='text-muted-foreground'>
|
||||
Monitor your torrents, and configure settings for your torrent client.
|
||||
{overview.desc}
|
||||
</p>
|
||||
</div>
|
||||
<Separator class='my-6' />
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { addMonths, endOfMonth, endOfWeek, format, isSameMonth, isToday, startOfMonth, startOfWeek, subMonths } from 'date-fns'
|
||||
import ChevronLeftIcon from 'lucide-svelte/icons/chevron-left'
|
||||
import ChevronRightIcon from 'lucide-svelte/icons/chevron-right'
|
||||
import { persisted } from 'svelte-persisted-store'
|
||||
import Cross2 from 'svelte-radix/Cross2.svelte'
|
||||
|
||||
import type { Schedule, ScheduleMedia } from '$lib/modules/anilist/queries'
|
||||
|
|
@ -11,13 +12,17 @@
|
|||
import StatusDot from '$lib/components/StatusDot.svelte'
|
||||
import { Button } from '$lib/components/ui/button'
|
||||
import * as Drawer from '$lib/components/ui/drawer'
|
||||
import { Label } from '$lib/components/ui/label'
|
||||
import { Switch } from '$lib/components/ui/switch'
|
||||
import * as Tooltip from '$lib/components/ui/tooltip'
|
||||
import { dedupeAiring } from '$lib/modules/anilist'
|
||||
import { authAggregator, list } from '$lib/modules/auth'
|
||||
import { dragScroll } from '$lib/modules/navigate'
|
||||
import { cn, breakpoints } from '$lib/utils'
|
||||
|
||||
const query = authAggregator.schedule()
|
||||
const onList = persisted('schedule-on-list', true)
|
||||
|
||||
$: query = authAggregator.schedule($onList)
|
||||
|
||||
let now = new Date()
|
||||
$: monthName = now.toLocaleString('en-US', { month: 'long' })
|
||||
|
|
@ -43,9 +48,6 @@
|
|||
|
||||
$: dayList = listDays(firstDay, lastDay)
|
||||
|
||||
const paused = query.isPaused$
|
||||
if ($paused) query.resume()
|
||||
|
||||
interface DayAirTimes { day: { date: Date, number: number }, episodes: Array<ResultOf<typeof ScheduleMedia> & { episode: number, airTime: Date }> }
|
||||
|
||||
function aggregate (data: ResultOf<typeof Schedule>, dayList: Array<{ date: Date, number: number }>) {
|
||||
|
|
@ -77,14 +79,24 @@
|
|||
const _list = list
|
||||
</script>
|
||||
|
||||
<div class='flex flex-col items-center w-full h-full overflow-y-auto px-5' use:dragScroll>
|
||||
<div class='my-10 grid grid-cols-7 border rounded-lg [&>*:not(:nth-child(7n+1)):nth-child(n+8)]:border-r [&>*:nth-last-child(n+8)]:border-b [&>*:nth-child(-n+8)]:border-b w-full max-w-[1800px]'>
|
||||
<div class='flex flex-col items-center w-full h-full overflow-y-auto p-10 min-w-0' use:dragScroll>
|
||||
<div class='space-y-0.5 self-start mb-6'>
|
||||
<h2 class='text-2xl font-bold'>Airing Calendar</h2>
|
||||
<p class='text-muted-foreground'>
|
||||
View upcoming episodes and their air times for the current season.
|
||||
</p>
|
||||
</div>
|
||||
<div class='grid grid-cols-7 border rounded-lg [&>*:not(:nth-child(7n+1)):nth-child(n+8)]:border-r [&>*:nth-last-child(n+8)]:border-b [&>*:nth-child(-n+8)]:border-b w-full max-w-[1800px]'>
|
||||
<div class='col-span-full flex justify-between items-center p-4'>
|
||||
<Button size='icon' on:click={prevMonth} variant='outline' class='bg-transparent'>
|
||||
<ChevronLeftIcon class='h-6 w-6' />
|
||||
</Button>
|
||||
<div class='text-center font-bold text-xl'>
|
||||
{monthName}
|
||||
<div class='self-start flex items-center space-x-2 mt-1 text-muted-foreground'>
|
||||
<Switch bind:checked={$onList} id='schedule-on-list' hideState={true} />
|
||||
<Label for='schedule-on-list'>My list</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button size='icon' on:click={nextMonth} variant='outline' class='bg-transparent'>
|
||||
<ChevronRightIcon class='h-6 w-6' />
|
||||
|
|
@ -97,7 +109,7 @@
|
|||
<div class='text-center py-2'>Fri</div>
|
||||
<div class='text-center py-2'>Sat</div>
|
||||
<div class='text-center py-2'>Sun</div>
|
||||
{#if $query.fetching || $paused}
|
||||
{#if $query.fetching}
|
||||
{#each dayList as { date, number } (date)}
|
||||
{@const sameMonth = isSameMonth(now, date)}
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
import { onDestroy } from 'svelte'
|
||||
|
||||
import { goto } from '$app/navigation'
|
||||
import { Separator } from '$lib/components/ui/separator'
|
||||
import native from '$lib/modules/native'
|
||||
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
rows = message.split('\n').length || 1
|
||||
}
|
||||
|
||||
$: prcoessedUsers = Object.values($users).map(({ user }) => user)
|
||||
$: processedUsers = Object.values($users).map(({ user }) => user)
|
||||
|
||||
function quit () {
|
||||
goto('/app/home/')
|
||||
|
|
@ -55,10 +56,16 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<div class='flex flex-col w-full relative px-md-4 h-full overflow-hidden'>
|
||||
<div class='flex md:flex-row flex-col-reverse w-full h-full pt-4'>
|
||||
<div class='flex flex-col w-full relative h-full overflow-clip'>
|
||||
<div class='space-y-0.5 px-10 pt-10'>
|
||||
<h2 class='text-2xl font-bold'>Watch Together <span class='text-muted-foreground text-lg ml-4 font-semibold'>{$w2globby?.code}</span></h2>
|
||||
<p class='text-muted-foreground'>
|
||||
Watch videos together with friends in real-time. You can invite others to your lobby and chat while watching.
|
||||
</p>
|
||||
<Separator class='!my-6' />
|
||||
</div>
|
||||
<div class='flex md:flex-row flex-col-reverse w-full h-full'>
|
||||
<div class='flex flex-col justify-end overflow-clip flex-grow px-4 pb-4 h-full min-h-0'>
|
||||
<div class='mb-auto hidden md:block text-center font-bold text-lg'>Watch Together {$w2globby?.code}</div>
|
||||
<div class='h-full overflow-y-scroll min-h-0 w-full'>
|
||||
<Messages {messages} />
|
||||
</div>
|
||||
|
|
@ -81,7 +88,6 @@
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UserList users={prcoessedUsers} />
|
||||
<div class='md:hidden px-6 text-center font-bold text-lg'>Watch Together {$w2globby?.code}</div>
|
||||
<UserList users={processedUsers} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue