mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-01-12 00:03:44 +00:00
feat: library and files filtering
feat: library rescan and delete buttons [disabled for now]
This commit is contained in:
parent
7e8ac47cdf
commit
9e1cdb7017
21 changed files with 1232 additions and 511 deletions
51
package.json
51
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "6.4.111",
|
||||
"version": "6.4.112",
|
||||
"license": "BUSL-1.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.15.5",
|
||||
|
|
@ -20,8 +20,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@gql.tada/svelte-support": "^1.0.1",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.21.0",
|
||||
"@sveltejs/adapter-static": "^3.0.9",
|
||||
"@sveltejs/kit": "^2.37.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/semver": "^7.7.0",
|
||||
|
|
@ -29,8 +29,8 @@
|
|||
"autoprefixer": "^10.4.21",
|
||||
"bits-ui": "^0.22.0",
|
||||
"cmdk-sv": "^0.0.19",
|
||||
"eslint-config-standard-universal": "^1.0.8",
|
||||
"gql.tada": "^1.8.10",
|
||||
"eslint-config-standard-universal": "^1.0.9",
|
||||
"gql.tada": "^1.8.13",
|
||||
"hayase-extensions": "github:hayase-app/extensions",
|
||||
"jassub": "^1.8.6",
|
||||
"ms": "^2.1.3",
|
||||
|
|
@ -38,44 +38,45 @@
|
|||
"rollup-plugin-license": "^3.6.0",
|
||||
"simple-copy": "^2.2.1",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-check": "^4.2.1",
|
||||
"svelte-check": "^4.3.1",
|
||||
"svelte-radix": "^1.1.1",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript": "^5.9.2",
|
||||
"vaul-svelte": "^0.3.2",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-static-copy": "^3.0.2"
|
||||
"vite-plugin-devtools-json": "^1.0.0",
|
||||
"vite-plugin-static-copy": "^3.1.2"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@cloudflare/speedtest": "^1.4.1",
|
||||
"@fontsource-variable/nunito": "^5.2.5",
|
||||
"@cloudflare/speedtest": "^1.6.0",
|
||||
"@fontsource-variable/nunito": "^5.2.6",
|
||||
"@fontsource/geist-mono": "^5.2.6",
|
||||
"@prgm/sveltekit-progress-bar": "2.0.0",
|
||||
"@thaunknown/web-irc": "^1.0.3",
|
||||
"@urql/core": "^5.2.0",
|
||||
"@urql/exchange-auth": "^2.2.1",
|
||||
"@urql/exchange-graphcache": "^7.2.3",
|
||||
"@urql/exchange-refocus": "^1.1.1",
|
||||
"@urql/exchange-request-policy": "^1.2.1",
|
||||
"@urql/exchange-retry": "^1.3.1",
|
||||
"@urql/svelte": "^4.2.3",
|
||||
"abslink": "^1.1.0",
|
||||
"@urql/core": "^6.0.1",
|
||||
"@urql/exchange-auth": "^3.0.0",
|
||||
"@urql/exchange-graphcache": "^8.1.0",
|
||||
"@urql/exchange-refocus": "^2.0.0",
|
||||
"@urql/exchange-request-policy": "^2.0.0",
|
||||
"@urql/exchange-retry": "^2.0.0",
|
||||
"@urql/svelte": "^5.0.0",
|
||||
"abslink": "^1.1.2",
|
||||
"anitomyscript": "github:thaunknown/anitomyscript",
|
||||
"bittorrent-tracker": "10.0.12",
|
||||
"bittorrent-tracker": "11.2.1",
|
||||
"bottleneck": "^2.19.5",
|
||||
"clsx": "^2.1.1",
|
||||
"cobe": "0.6.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"debug": "^4.4.1",
|
||||
"doc999tor-fast-geoip": "^1.1.335",
|
||||
"dompurify": "^3.2.5",
|
||||
"doc999tor-fast-geoip": "^1.1.360",
|
||||
"dompurify": "^3.2.6",
|
||||
"events": "^3.3.0",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"lucide-svelte": "^0.511.0",
|
||||
"marked": "^15.0.11",
|
||||
"lucide-svelte": "^0.542.0",
|
||||
"marked": "^16.2.1",
|
||||
"overtype": "^1.2.3",
|
||||
"p2pt": "github:ThaUnknown/p2pt#modernise",
|
||||
"semver": "^7.7.2",
|
||||
|
|
@ -83,10 +84,10 @@
|
|||
"svelte-headless-table": "^0.18.3",
|
||||
"svelte-keybinds": "^1.0.9",
|
||||
"svelte-persisted-store": "^0.12.0",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"uint8-util": "^2.2.5",
|
||||
"urql": "^4.2.2",
|
||||
"urql": "^5.0.1",
|
||||
"video-deband": "^1.0.9",
|
||||
"wonka": "^6.3.5",
|
||||
"workbox-core": "^7.3.0",
|
||||
|
|
|
|||
1456
pnpm-lock.yaml
1456
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -167,7 +167,7 @@ a {
|
|||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
:fullscreen {
|
||||
#episodeListTarget:fullscreen {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
const key = 'active-settings-tab'
|
||||
</script>
|
||||
|
||||
<nav class={cn('md:flex grid grid-cols-2 md:flex-row lg:flex-col gap-y-1 gap-x-2', className)}>
|
||||
<nav class={cn('md:flex grid grid-cols-2 md:flex-row lg:flex-col gap-y-1 gap-x-2 pb-2 sm:pb-0', className)}>
|
||||
{#each items as { href, title }, i (i)}
|
||||
{@const isActive = $page.url.pathname === href}
|
||||
<Button {href} variant='ghost' data-sveltekit-noscroll class='relative font-semibold justify-start last:odd:col-span-2'>
|
||||
|
|
|
|||
43
src/lib/components/icons/animated/foldersync.svelte
Normal file
43
src/lib/components/icons/animated/foldersync.svelte
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<script lang='ts'>
|
||||
import { cn } from '$lib/utils'
|
||||
|
||||
export let color = 'currentColor'
|
||||
export let size = 24
|
||||
export let strokeWidth = 2
|
||||
let className = ''
|
||||
|
||||
export { className as class }
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke={color}
|
||||
stroke-width={strokeWidth}
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
class={cn(className, 'overflow-visible')}
|
||||
{...$$restProps}
|
||||
>
|
||||
<path
|
||||
d='M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v.5'
|
||||
/>
|
||||
<g class='target-animated-icon'>
|
||||
<path d='M12 10v4h4' />
|
||||
<path d='m12 14 1.535-1.605a5 5 0 0 1 8 1.5' />
|
||||
<path d='M22 22v-4h-4' />
|
||||
<path d='m22 18-1.535 1.605a5 5 0 0 1-8-1.5' />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<style>
|
||||
.target-animated-icon {
|
||||
transform-origin: center;
|
||||
transform-box: fill-box;
|
||||
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
transform: rotate(-50deg);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -16,3 +16,4 @@ export { default as Trash } from './trash.svelte'
|
|||
export { default as FileImage } from './fileimage.svelte'
|
||||
export { default as Minimize } from './minimize.svelte'
|
||||
export { default as Maximize } from './maximize.svelte'
|
||||
export { default as FolderSync } from './foldersync.svelte'
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<slot />
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Content tabindex={null} class='gap-4 bottom-0 border-b-0 !translate-y-[unset] p-0 top-[unset] !pb-4 flex flex-col h-full sm:h-1/2'>
|
||||
<Dialog.Content tabindex={null} class='gap-4 bottom-0 border-b-0 !translate-y-[unset] p-0 top-[unset] !pb-4 flex flex-col h-[90%] sm:h-1/2'>
|
||||
<Markdown class='form-control w-full shrink-0 min-h-56 rounded-none flex-grow' {placeholder} bind:value />
|
||||
<div class='flex gap-2 justify-end flex-grow-0 px-4'>
|
||||
<Dialog.Close asChild let:builder>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<script lang='ts'>
|
||||
import { Render, Subscribe, createRender, createTable } from 'svelte-headless-table'
|
||||
import { addSortBy } from 'svelte-headless-table/plugins'
|
||||
import { addSortBy, addTableFilter } from 'svelte-headless-table/plugins'
|
||||
import MagnifyingGlass from 'svelte-radix/MagnifyingGlass.svelte'
|
||||
|
||||
import { Input } from '../../input'
|
||||
import Columnheader from '../columnheader.svelte'
|
||||
|
||||
import { NameCell, ProgressCell } from './cells'
|
||||
|
|
@ -11,7 +13,10 @@
|
|||
import { cn, fastPrettyBytes } from '$lib/utils'
|
||||
|
||||
const table = createTable(server.files, {
|
||||
sort: addSortBy({ toggleOrder: ['asc', 'desc'] })
|
||||
sort: addSortBy({ toggleOrder: ['asc', 'desc'] }),
|
||||
filter: addTableFilter({
|
||||
fn: ({ filterValue, value }) => value.toLowerCase().includes(filterValue.toLowerCase())
|
||||
})
|
||||
})
|
||||
|
||||
const columns = table.createColumns([
|
||||
|
|
@ -25,12 +30,14 @@
|
|||
accessor: 'size',
|
||||
header: 'Size',
|
||||
id: 'size',
|
||||
plugins: { filter: { exclude: true } },
|
||||
cell: ({ value }) => fastPrettyBytes(value)
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'progress',
|
||||
header: 'Progress',
|
||||
id: 'progress',
|
||||
plugins: { filter: { exclude: true } },
|
||||
cell: ({ value }) => createRender(ProgressCell, { value })
|
||||
}),
|
||||
table.column({ accessor: 'selections', header: 'Streams', id: 'selections' })
|
||||
|
|
@ -38,9 +45,18 @@
|
|||
|
||||
const tableModel = table.createViewModel(columns)
|
||||
|
||||
const { headerRows, pageRows, tableAttrs, tableBodyAttrs } = tableModel
|
||||
const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates } = tableModel
|
||||
|
||||
const filterValue = pluginStates.filter.filterValue
|
||||
</script>
|
||||
|
||||
<div class='flex items-center scale-parent relative pb-2 overflow-visible'>
|
||||
<Input
|
||||
class='pl-9 bg-black select:bg-accent select:text-accent-foreground shadow-sm no-scale placeholder:opacity-50'
|
||||
placeholder='Search by File Name...'
|
||||
bind:value={$filterValue} />
|
||||
<MagnifyingGlass class='h-4 w-4 shrink-0 opacity-50 absolute left-3 text-muted-foreground z-10 pointer-events-none' />
|
||||
</div>
|
||||
<div class='rounded-md border size-full overflow-clip contain-strict'>
|
||||
<Table.Root {...$tableAttrs} class='max-h-full'>
|
||||
<Table.Header class='px-5'>
|
||||
|
|
@ -74,7 +90,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='h-12 [content-visibility:auto] [contain-intrinsic-height:auto_48px] contain-strict'>
|
||||
{#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 === 'name' && 'text-wrap break-all')}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<script lang='ts'>
|
||||
import type { HTMLButtonAttributes } from 'svelte/elements'
|
||||
import type { Writable } from 'svelte/store'
|
||||
|
||||
import { Checkbox } from '$lib/components/ui/checkbox'
|
||||
|
||||
type $$Props = HTMLButtonAttributes & {
|
||||
checked: Writable<boolean>
|
||||
}
|
||||
export let checked: Writable<boolean>
|
||||
|
||||
</script>
|
||||
|
||||
<div class='contents' on:click|stopPropagation|stopImmediatePropagation>
|
||||
<Checkbox bind:checked={$checked} {...$$restProps} class='mx-4' />
|
||||
</div>
|
||||
|
|
@ -2,3 +2,4 @@ export { default as StatusCell } from './status.svelte'
|
|||
export { default as NameCell } from './name.svelte'
|
||||
export { default as MediaCell } from './mediatitle.svelte'
|
||||
export { default as DateCell } from './date.svelte'
|
||||
export { default as CheckboxCell } from './checkboxcell.svelte'
|
||||
|
|
|
|||
|
|
@ -1,23 +1,33 @@
|
|||
<script lang='ts'>
|
||||
import { DataBodyRow, Render, Subscribe, createRender, createTable } from 'svelte-headless-table'
|
||||
import { addSortBy } from 'svelte-headless-table/plugins'
|
||||
import { addSelectedRows, addSortBy, addTableFilter } from 'svelte-headless-table/plugins'
|
||||
import MagnifyingGlass from 'svelte-radix/MagnifyingGlass.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
|
||||
import { Button } from '../../button'
|
||||
import Columnheader from '../columnheader.svelte'
|
||||
|
||||
import { MediaCell, NameCell, StatusCell, DateCell } from './cells'
|
||||
import { MediaCell, NameCell, StatusCell, DateCell, CheckboxCell } from './cells'
|
||||
|
||||
import type { LibraryEntry } from 'native'
|
||||
|
||||
import { goto } from '$app/navigation'
|
||||
import { FolderSync, Trash } from '$lib/components/icons/animated'
|
||||
import { Input } from '$lib/components/ui/input'
|
||||
import * as Table from '$lib/components/ui/table'
|
||||
import { client } from '$lib/modules/anilist'
|
||||
import native from '$lib/modules/native'
|
||||
import { server } from '$lib/modules/torrent'
|
||||
import { cn, fastPrettyBytes } from '$lib/utils'
|
||||
|
||||
const lib = server.library
|
||||
|
||||
const table = createTable(lib, {
|
||||
sort: addSortBy({ toggleOrder: ['asc', 'desc'] })
|
||||
select: addSelectedRows(),
|
||||
sort: addSortBy({ toggleOrder: ['asc', 'desc'] }),
|
||||
filter: addTableFilter({
|
||||
fn: ({ filterValue, value }) => value.toLowerCase().includes(filterValue.toLowerCase())
|
||||
})
|
||||
})
|
||||
|
||||
const columns = table.createColumns([
|
||||
|
|
@ -25,36 +35,36 @@
|
|||
accessor: 'mediaID',
|
||||
header: 'Series',
|
||||
id: 'series',
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 } },
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 }, filter: { exclude: true } },
|
||||
cell: ({ value }) => value ? createRender(MediaCell, { value }) : '?'
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'episode',
|
||||
header: 'Episode',
|
||||
id: 'episode',
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 } },
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 }, filter: { exclude: true } },
|
||||
cell: ({ value }) => value?.toString() ?? '?'
|
||||
}),
|
||||
table.column({ accessor: 'files', header: 'Files', id: 'files' }),
|
||||
table.column({ accessor: 'files', header: 'Files', id: 'files', plugins: { filter: { exclude: true } } }),
|
||||
table.column({
|
||||
accessor: 'size',
|
||||
header: 'Size',
|
||||
id: 'size',
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 } },
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 }, filter: { exclude: true } },
|
||||
cell: ({ value }) => value ? fastPrettyBytes(value) : '?'
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'progress',
|
||||
header: 'Status',
|
||||
id: 'completed',
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 } },
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 }, filter: { exclude: true } },
|
||||
cell: ({ value }) => value ? createRender(StatusCell, { value: value === 1 }) : '?'
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'date',
|
||||
header: 'Date',
|
||||
id: 'date',
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 } },
|
||||
plugins: { sort: { getSortValue: e => e ?? 0 }, filter: { exclude: true } },
|
||||
cell: ({ value }) => value ? createRender(DateCell, { value }) : '?'
|
||||
}),
|
||||
table.column({
|
||||
|
|
@ -63,12 +73,35 @@
|
|||
id: 'name',
|
||||
plugins: { sort: { getSortValue: e => e ?? '' } },
|
||||
cell: ({ value }) => createRender(NameCell, { value })
|
||||
}),
|
||||
table.display({
|
||||
id: 'select',
|
||||
header: (_, { pluginStates }) => {
|
||||
const { allPageRowsSelected } = pluginStates.select
|
||||
return createRender(CheckboxCell, {
|
||||
checked: allPageRowsSelected,
|
||||
'aria-label': 'Select all'
|
||||
})
|
||||
},
|
||||
cell: ({ row }, { pluginStates }) => {
|
||||
const { getRowState } = pluginStates.select
|
||||
const { isSelected } = getRowState(row)
|
||||
return createRender(CheckboxCell, {
|
||||
checked: isSelected,
|
||||
'aria-label': 'Select row'
|
||||
})
|
||||
},
|
||||
plugins: {
|
||||
sort: {
|
||||
disable: true
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
const tableModel = table.createViewModel(columns)
|
||||
|
||||
const { headerRows, pageRows, tableAttrs, tableBodyAttrs } = tableModel
|
||||
const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates } = tableModel
|
||||
|
||||
async function playEntry ({ mediaID, episode, hash }: LibraryEntry) {
|
||||
if (!mediaID || !hash) return
|
||||
|
|
@ -78,10 +111,60 @@
|
|||
goto('/app/player/')
|
||||
}
|
||||
|
||||
const { filterValue } = pluginStates.filter
|
||||
const { selectedDataIds } = pluginStates.select
|
||||
|
||||
function getSelected () {
|
||||
return Object.keys($selectedDataIds).map(id => $lib[id as unknown as number]?.hash).filter(e => e) as string[]
|
||||
}
|
||||
|
||||
// TODO: enable
|
||||
function rescanTorrents () {
|
||||
return null
|
||||
// eslint-disable-next-line no-unreachable
|
||||
toast.promise(native.rescanTorrents(getSelected()), {
|
||||
loading: 'Rescanning torrents...',
|
||||
success: 'Rescan complete',
|
||||
error: e => {
|
||||
console.error(e)
|
||||
return 'Failed to rescan torrents\n' + ('stack' in (e as object) ? (e as Error).stack : 'Unknown error')
|
||||
},
|
||||
description: 'This may take a long while depending on the number of torrents.'
|
||||
})
|
||||
}
|
||||
function deleteTorrents () {
|
||||
return null
|
||||
// eslint-disable-next-line no-unreachable
|
||||
toast.promise(native.deleteTorrents(getSelected()), {
|
||||
loading: 'Deleting torrents...',
|
||||
success: 'Torrents deleted',
|
||||
error: e => {
|
||||
console.error(e)
|
||||
return 'Failed to delete torrents\n' + ('stack' in (e as object) ? (e as Error).stack : 'Unknown error')
|
||||
},
|
||||
description: 'This may take a while depending on the number of torrents.'
|
||||
})
|
||||
}
|
||||
|
||||
// TODO once new resolver is implemented
|
||||
// $: allIDsPromise = client.multiple($lib.map(e => e.mediaID))
|
||||
</script>
|
||||
|
||||
<div class='flex gap-2'>
|
||||
<div class='flex items-center scale-parent relative pb-2 overflow-visible grow'>
|
||||
<Input
|
||||
class='pl-9 bg-black select:bg-accent select:text-accent-foreground shadow-sm no-scale placeholder:opacity-50'
|
||||
placeholder='Search by Torrent Name...'
|
||||
bind:value={$filterValue} />
|
||||
<MagnifyingGlass class='h-4 w-4 shrink-0 opacity-50 absolute left-3 text-muted-foreground z-10 pointer-events-none' />
|
||||
</div>
|
||||
<Button variant='secondary' size='icon' class='border-0 animated-icon !pointer-events-auto cursor-not-allowed' disabled on:click={rescanTorrents}>
|
||||
<FolderSync class={cn('size-4')} />
|
||||
</Button>
|
||||
<Button variant='destructive' size='icon' class='border-0 animated-icon !pointer-events-auto cursor-not-allowed' disabled on:click={deleteTorrents}>
|
||||
<Trash class={cn('size-4')} />
|
||||
</Button>
|
||||
</div>
|
||||
<div class='rounded-md border size-full overflow-clip contain-strict'>
|
||||
<Table.Root {...$tableAttrs} class='max-h-full'>
|
||||
<Table.Header class='px-5'>
|
||||
|
|
@ -109,10 +192,15 @@
|
|||
{#if $pageRows.length}
|
||||
{#each $pageRows as row (row.id)}
|
||||
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
|
||||
<Table.Row {...rowAttrs} class={cn('h-14', (row instanceof DataBodyRow) && row.original.mediaID ? 'cursor-pointer' : 'cursor-not-allowed')} on:click={() => { if (row instanceof DataBodyRow) playEntry(row.original) }}>
|
||||
<Table.Row {...rowAttrs} class={cn('h-14 [content-visibility:auto] [contain-intrinsic-height:auto_56px] contain-strict', (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 min-h-14 first:pl-6 last:pr-6 text-nowrap', (cell.id === 'episode') && 'text-muted-foreground', (cell.id === 'series' || cell.id === 'name') && 'min-w-80 text-wrap break-all')}>
|
||||
<Table.Cell {...attrs} class={cn(
|
||||
'px-4 min-h-14 first:pl-6 last:pr-6 text-nowrap',
|
||||
(cell.id === 'episode') && 'text-muted-foreground',
|
||||
(cell.id === 'series' || cell.id === 'name') && 'min-w-80 text-wrap break-all',
|
||||
cell.id === 'select' && 'p-0'
|
||||
)}>
|
||||
<Render of={cell.render()} />
|
||||
</Table.Cell>
|
||||
</Subscribe>
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export default new class URQLClient extends Client {
|
|||
constructor () {
|
||||
super({
|
||||
url: 'https://graphql.anilist.co',
|
||||
preferGetMethod: false,
|
||||
// fetch: dev ? fetch : (req: RequestInfo | URL, opts?: RequestInit) => this.handleRequest(req, opts),
|
||||
fetch: (req: RequestInfo | URL, opts?: RequestInit) => this.handleRequest(req, opts),
|
||||
exchanges: [
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ const ENDPOINTS = {
|
|||
API_ANIME: 'https://api.myanimelist.net/v2/anime'
|
||||
} as const
|
||||
|
||||
const clientID = 'd93b624a92e431a9b6dfe7a66c0c5bbb'
|
||||
|
||||
export default new class MALSync {
|
||||
auth = persisted<MALOAuth | undefined>('malAuth', undefined)
|
||||
viewer = persisted<ResultOf<typeof UserFrag> | undefined>('malViewer', undefined)
|
||||
|
|
@ -169,7 +171,7 @@ export default new class MALSync {
|
|||
if (auth) {
|
||||
const expiresAt = (auth.created_at + auth.expires_in) * 1000
|
||||
|
||||
if (expiresAt < Date.now() - 1000 * 60 * 5) { // 5 minutes before expiry
|
||||
if (expiresAt < Date.now() - 1000 * 60 * 5 && !body?.get('refresh_token')) { // 5 minutes before expiry
|
||||
await this._refresh()
|
||||
}
|
||||
}
|
||||
|
|
@ -248,6 +250,7 @@ export default new class MALSync {
|
|||
const data = await this._post<MALOAuth>(
|
||||
ENDPOINTS.API_OAUTH,
|
||||
{
|
||||
client_id: clientID,
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: auth.refresh_token
|
||||
}
|
||||
|
|
@ -265,7 +268,6 @@ export default new class MALSync {
|
|||
debug('Logging in to MAL')
|
||||
const state = crypto.randomUUID().replaceAll('-', '')
|
||||
const challenge = (crypto.randomUUID() + crypto.randomUUID()).replaceAll('-', '')
|
||||
const clientID = 'd93b624a92e431a9b6dfe7a66c0c5bbb'
|
||||
|
||||
const redirect = dev ? 'http://localhost:7344/authorize' : 'https://hayase.app/authorize'
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export async function encryptMessage (message: string) {
|
|||
iv: key
|
||||
},
|
||||
await derived,
|
||||
text2arr(message)
|
||||
text2arr(message).buffer as ArrayBuffer
|
||||
))))
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +47,6 @@ export async function decryptMessage (encryptedMessage: string) {
|
|||
iv: key
|
||||
},
|
||||
await derived,
|
||||
hex2arr(bin2hex(encryptedMessage))
|
||||
hex2arr(bin2hex(encryptedMessage)).buffer as ArrayBuffer
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ export default Object.assign<Native, Partial<Native>>({
|
|||
updatePeerCounts: async () => [],
|
||||
isApp: false,
|
||||
playTorrent: async () => dummyFiles,
|
||||
rescanTorrents: async () => undefined,
|
||||
deleteTorrents: async () => undefined,
|
||||
library: async () => [],
|
||||
attachments: async () => [],
|
||||
tracks: async () => [],
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@
|
|||
<aside class='lg:grow lg:max-w-60 flex flex-col'>
|
||||
<SettingsNav {items} />
|
||||
<div class='mt-auto text-xs text-muted-foreground px-4 sm:px-2 py-3 md:py-5 flex-row lg:flex-col font-light gap-0.5 gap-x-4 flex-wrap hidden sm:flex'>
|
||||
<div>WebTorrent v2.6.8</div>
|
||||
<div>WebTorrent v2.8.4</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class='flex-1 overflow-y-scroll' use:dragScroll>
|
||||
<div class='flex-1' use:dragScroll>
|
||||
{#if !SUPPORTS.isUnderPowered}
|
||||
<Globe />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
import { Overview } from '$lib/components/ui/torrentclient'
|
||||
</script>
|
||||
|
||||
<div class='flex flex-col h-full'>
|
||||
<div class='flex flex-col h-full overflow-y-scroll'>
|
||||
<Overview />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,4 +2,6 @@
|
|||
import { FilesTable } from '$lib/components/ui/torrentclient'
|
||||
</script>
|
||||
|
||||
<FilesTable />
|
||||
<div class='flex flex-col size-full'>
|
||||
<FilesTable />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,4 +2,6 @@
|
|||
import { LibraryTable } from '$lib/components/ui/torrentclient'
|
||||
</script>
|
||||
|
||||
<LibraryTable />
|
||||
<div class='flex flex-col size-full'>
|
||||
<LibraryTable />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@
|
|||
import native from '$lib/modules/native'
|
||||
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||
|
||||
export let data
|
||||
|
||||
$: users = $w2globby!.peers
|
||||
$: messages = $w2globby!.messages
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { resolve } from 'node:path'
|
|||
import { sveltekit } from '@sveltejs/kit/vite'
|
||||
import license from 'rollup-plugin-license'
|
||||
import { defineConfig } from 'vite'
|
||||
import devtoolsJson from 'vite-plugin-devtools-json'
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||
|
||||
export default defineConfig({
|
||||
|
|
@ -22,7 +23,8 @@ export default defineConfig({
|
|||
dest: 'geoip/'
|
||||
}
|
||||
]
|
||||
})
|
||||
}),
|
||||
devtoolsJson()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue