mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-20 00:32:04 +00:00
This commit is contained in:
parent
f481bf7c09
commit
4969644374
9 changed files with 95 additions and 80 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "6.4.3",
|
||||
"version": "6.4.4",
|
||||
"license": "BUSL-1.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.14.4",
|
||||
|
|
|
|||
14
src/app.d.ts
vendored
14
src/app.d.ts
vendored
|
|
@ -100,6 +100,17 @@ export interface TorrentSettings {
|
|||
torrentPeX: boolean
|
||||
}
|
||||
|
||||
export interface LibraryEntry {
|
||||
mediaID: number
|
||||
episode: number
|
||||
files: number
|
||||
hash: string
|
||||
progress: number
|
||||
date: number
|
||||
size: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface Native {
|
||||
authAL: (url: string) => Promise<AuthResponse>
|
||||
restart: () => Promise<void>
|
||||
|
|
@ -127,7 +138,8 @@ export interface Native {
|
|||
checkAvailableSpace: (_?: unknown) => Promise<number>
|
||||
checkIncomingConnections: (port: number) => Promise<boolean>
|
||||
updatePeerCounts: (hashes: string[]) => Promise<Array<{ hash: string, complete: string, downloaded: string, incomplete: string }>>
|
||||
playTorrent: (id: string) => Promise<TorrentFile[]>
|
||||
playTorrent: (id: string, mediaID: number, episode: number) => Promise<TorrentFile[]>
|
||||
library: () => Promise<LibraryEntry[]>
|
||||
attachments: (hash: string, id: number) => Promise<Attachment[]>
|
||||
tracks: (hash: string, id: number) => Promise<Array<{ number: string, language?: string, type: string, header?: string, name?: string }>>
|
||||
subtitles: (hash: string, id: number, cb: (subtitle: { text: string, time: number, duration: number }, trackNumber: number) => void) => Promise<void>
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@
|
|||
return list.slice((page - 1) * perPage, page * perPage)
|
||||
}
|
||||
|
||||
$: _progress = progress(media) ?? 0
|
||||
$: completed = list(media) === 'COMPLETED'
|
||||
$: _progress = completed ? 0 : progress(media) ?? 0
|
||||
|
||||
$: currentPage = Math.floor((!completed ? _progress : 0) / perPage) + 1
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export { default as StatusCell } from './status.svelte'
|
||||
export { default as NameCell } from './name.svelte'
|
||||
export { default as MediaCell } from './mediatitle.svelte'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<script lang='ts'>
|
||||
import { client } from '$lib/modules/anilist'
|
||||
|
||||
export let value: number
|
||||
</script>
|
||||
|
||||
{#await client.single(value)}
|
||||
?
|
||||
{:then query}
|
||||
{query.data?.Media?.title?.userPreferred ?? '?'}
|
||||
{/await}
|
||||
|
|
@ -1,64 +1,76 @@
|
|||
<script lang='ts'>
|
||||
import { writable } from 'svelte/store'
|
||||
import { Render, Subscribe, createRender, createTable } from 'svelte-headless-table'
|
||||
import { DataBodyRow, Render, Subscribe, createRender, createTable } from 'svelte-headless-table'
|
||||
import { addSortBy } from 'svelte-headless-table/plugins'
|
||||
|
||||
import Columnheader from '../columnheader.svelte'
|
||||
|
||||
import { NameCell, StatusCell } from './cells'
|
||||
import { MediaCell, NameCell, StatusCell } from './cells'
|
||||
|
||||
import type { LibraryEntry } from '$lib/../app'
|
||||
|
||||
import * as Table from '$lib/components/ui/table'
|
||||
import { client } from '$lib/modules/anilist'
|
||||
import { server } from '$lib/modules/torrent'
|
||||
import { cn, fastPrettyBytes } from '$lib/utils'
|
||||
|
||||
interface LibraryEntry {
|
||||
series: string
|
||||
episode: string
|
||||
name: string
|
||||
files: number
|
||||
size: number
|
||||
completed: boolean
|
||||
downloaded: number
|
||||
}
|
||||
const lib = server.library
|
||||
|
||||
const data = writable<LibraryEntry[]>([])
|
||||
|
||||
const table = createTable(data, {
|
||||
const table = createTable(lib, {
|
||||
sort: addSortBy({ toggleOrder: ['asc', 'desc'] })
|
||||
})
|
||||
|
||||
const columns = table.createColumns([
|
||||
table.column({ accessor: 'series', header: 'Series', id: 'series' }),
|
||||
table.column({ accessor: 'episode', header: 'Episode', id: 'episode' }),
|
||||
table.column({
|
||||
accessor: 'name',
|
||||
header: 'Torrent Name',
|
||||
id: 'name',
|
||||
cell: ({ value }) => createRender(NameCell, { value })
|
||||
accessor: 'mediaID',
|
||||
header: 'Series',
|
||||
id: 'series',
|
||||
cell: ({ value }) => value ? createRender(MediaCell, { value }) : '?'
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'episode',
|
||||
header: 'Episode',
|
||||
id: 'episode',
|
||||
cell: ({ value }) => value ?? '?'
|
||||
}),
|
||||
table.column({ accessor: 'files', header: 'Files', id: 'files' }),
|
||||
table.column({
|
||||
accessor: 'size',
|
||||
header: 'Size',
|
||||
id: 'size',
|
||||
cell: ({ value }) => fastPrettyBytes(value)
|
||||
cell: ({ value }) => value ? fastPrettyBytes(value) : '?'
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'completed',
|
||||
accessor: 'progress',
|
||||
header: 'Status',
|
||||
id: 'completed',
|
||||
cell: ({ value }) => createRender(StatusCell, { value })
|
||||
cell: ({ value }) => value ? createRender(StatusCell, { value: value === 1 }) : '?'
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'downloaded',
|
||||
header: 'Downloaded',
|
||||
id: 'downloaded',
|
||||
cell: ({ value }) => new Date(value).toLocaleDateString()
|
||||
accessor: 'date',
|
||||
header: 'Date',
|
||||
id: 'date',
|
||||
cell: ({ value }) => value ? new Date(value).toLocaleDateString() : '?'
|
||||
}),
|
||||
table.column({
|
||||
accessor: e => e?.name ?? e.hash,
|
||||
header: 'Torrent Name',
|
||||
id: 'name',
|
||||
cell: ({ value }) => createRender(NameCell, { value })
|
||||
})
|
||||
])
|
||||
|
||||
const tableModel = table.createViewModel(columns)
|
||||
|
||||
const { headerRows, pageRows, tableAttrs, tableBodyAttrs } = tableModel
|
||||
|
||||
async function playEntry ({ mediaID, episode, hash }: LibraryEntry) {
|
||||
if (!mediaID || !hash) return
|
||||
const media = await client.single(mediaID)
|
||||
server.play(hash, media.data!.Media!, episode)
|
||||
}
|
||||
|
||||
// TODO
|
||||
// $: allIDsPromise = client.multiple($lib.map(e => e.mediaID))
|
||||
</script>
|
||||
|
||||
<div class='rounded-md border max-w-screen-xl h-full overflow-clip contain-strict'>
|
||||
|
|
@ -94,7 +106,7 @@
|
|||
{#if $pageRows.length}
|
||||
{#each $pageRows as row (row.id)}
|
||||
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
|
||||
<Table.Row {...rowAttrs} class='h-12'>
|
||||
<Table.Row {...rowAttrs} class={cn('h-12', (row instanceof DataBodyRow) && row.original.mediaID ? 'cursor-pointer' : 'cursor-not-allowed')} on:click={() => { if (row instanceof DataBodyRow) playEntry(row.original) }}>
|
||||
{#each row.cells as cell (cell.id)}
|
||||
<Subscribe attrs={cell.attrs()} let:attrs>
|
||||
<Table.Cell {...attrs} class={cn('px-4 h-14 first:pl-6 last:pr-6 text-nowrap', (cell.id === 'downloaded' || cell.id === 'episode') && 'text-muted-foreground')}>
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ export default Object.assign<Native, Partial<Native>>({
|
|||
updatePeerCounts: async () => [],
|
||||
isApp: false,
|
||||
playTorrent: async () => dummyFiles,
|
||||
library: async () => [],
|
||||
attachments: async () => [],
|
||||
tracks: async () => [],
|
||||
subtitles: async () => undefined,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { persisted } from 'svelte-persisted-store'
|
|||
import native from '../native'
|
||||
import { w2globby } from '../w2g/lobby'
|
||||
|
||||
import type { FileInfo, PeerInfo, TorrentFile, TorrentInfo } from '$lib/../app'
|
||||
import type { TorrentFile, TorrentInfo } from '$lib/../app'
|
||||
import type { Media } from '../anilist'
|
||||
|
||||
const defaultTorrentInfo: TorrentInfo = {
|
||||
|
|
@ -22,60 +22,38 @@ const defaultTorrentInfo: TorrentInfo = {
|
|||
const defaultProtocolStatus = { dht: false, lsd: false, pex: false, nat: false, forwarding: false, persisting: false, streaming: false }
|
||||
|
||||
export const server = new class ServerClient {
|
||||
last = persisted<{media: Media, id: string, episode: number} | null>('last-torrent', null)
|
||||
active = writable<Promise<{media: Media, id: string, episode: number, files: TorrentFile[]}| null>>()
|
||||
last = persisted<{ media: Media, id: string, episode: number } | null>('last-torrent', null)
|
||||
active = writable<Promise<{ media: Media, id: string, episode: number, files: TorrentFile[] } | null>>()
|
||||
downloaded = writable(this.cachedSet())
|
||||
|
||||
stats = readable(defaultTorrentInfo, set => {
|
||||
let listener = 0
|
||||
stats = this._timedSafeReadable(defaultTorrentInfo, native.torrentInfo, 200)
|
||||
|
||||
const update = async () => {
|
||||
const id = (await get(this.active))?.id
|
||||
if (id) set(await native.torrentInfo(id))
|
||||
listener = setTimeout(update, 200)
|
||||
}
|
||||
protocol = this._timedSafeReadable(defaultProtocolStatus, native.protocolStatus)
|
||||
|
||||
update()
|
||||
return () => clearTimeout(listener)
|
||||
})
|
||||
peers = this._timedSafeReadable([], native.peerInfo)
|
||||
|
||||
protocol = readable(defaultProtocolStatus, set => {
|
||||
let listener = 0
|
||||
files = this._timedSafeReadable([], native.fileInfo)
|
||||
|
||||
const update = async () => {
|
||||
const id = (await get(this.active))?.id
|
||||
if (id) set(await native.protocolStatus(id))
|
||||
listener = setTimeout(update, 5000)
|
||||
}
|
||||
library = this._timedSafeReadable([], native.library, 120_000)
|
||||
|
||||
update()
|
||||
return () => clearTimeout(listener)
|
||||
})
|
||||
_timedSafeReadable<T> (defaultData: T, fn: (id: string) => Promise<T>, duration = 5000) {
|
||||
return readable<T>(defaultData, set => {
|
||||
let listener = 0
|
||||
|
||||
peers = readable<PeerInfo[]>([], set => {
|
||||
let listener = 0
|
||||
const update = async () => {
|
||||
try {
|
||||
const id = (await get(this.active))?.id
|
||||
if (id) set(await fn(id))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
listener = setTimeout(update, duration)
|
||||
}
|
||||
|
||||
const update = async () => {
|
||||
const id = (await get(this.active))?.id
|
||||
if (id) set(await native.peerInfo(id))
|
||||
listener = setTimeout(update, 5000)
|
||||
}
|
||||
|
||||
update()
|
||||
return () => clearTimeout(listener)
|
||||
})
|
||||
|
||||
files = readable<FileInfo[]>([], set => {
|
||||
let listener = 0
|
||||
const update = async () => {
|
||||
const id = (await get(this.active))?.id
|
||||
if (id) set(await native.fileInfo(id))
|
||||
listener = setTimeout(update, 5000)
|
||||
}
|
||||
|
||||
update()
|
||||
return () => clearTimeout(listener)
|
||||
})
|
||||
update()
|
||||
return () => clearTimeout(listener)
|
||||
})
|
||||
}
|
||||
|
||||
constructor () {
|
||||
const last = get(this.last)
|
||||
|
|
@ -98,7 +76,7 @@ export const server = new class ServerClient {
|
|||
}
|
||||
|
||||
async _play (id: string, media: Media, episode: number) {
|
||||
const result = { id, media, episode, files: await native.playTorrent(id) }
|
||||
const result = { id, media, episode, files: await native.playTorrent(id, media.id, episode) }
|
||||
this.downloaded.value = this.cachedSet()
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex gap-2 items-center justify-center md:justify-start w-full lex-wrap'>
|
||||
<div class='flex gap-2 items-center md:justify-start md:self-start'>
|
||||
<div class='flex md:mr-3 w-full min-[380px]:w-[180px]'>
|
||||
<PlayButton size='default' {media} class='rounded-r-none w-full' />
|
||||
<EntryEditor {media} />
|
||||
|
|
|
|||
Loading…
Reference in a new issue