miru/src/lib/components/ui/extensions/extensions.svelte
ThaUnknown 6616b9347e
fix: image decoding perf
feat: relative touch seeking on seekbar
feat: idle animation
fix: disable zoom with touch
2025-05-22 16:05:32 +02:00

157 lines
6.8 KiB
Svelte

<script lang='ts'>
import * as Tabs from '$lib/components/ui/tabs'
import { Input } from '$lib/components/ui/input'
import { saved, storage } from '$lib/modules/extensions'
import ExtensionSettings from './ExtensionSettings.svelte'
import * as Tooltip from '$lib/components/ui/tooltip'
import { Github, Globe, Plus } from 'lucide-svelte'
import { Button, iconSizes } from '../button'
import { MagnifyingGlass } from 'svelte-radix'
import { toast } from 'svelte-sonner'
const typeMap = {
nzb: 'NZB',
torrent: 'Torrent',
url: 'URL'
}
let value = 'extensions'
let inputText = ''
function filterSearch <T extends Array<[string, unknown]>> (repositories: T, input: string): T {
if (!input) return repositories
return repositories.filter(([id, _]) => id.toLowerCase().includes(input.toLowerCase())) as T
}
let extensionInput = ''
let importPromise = Promise.resolve()
function importExtension () {
importPromise = (async () => {
try {
await storage.import(extensionInput)
} catch (err) {
const error = err as Error
toast.error(error.cause as string, { description: error.message })
}
})()
}
// TODO: import files
</script>
<Tabs.Root bind:value class='w-full'>
<div class='flex justify-between items-center gap-3 sm:flex-row flex-col'>
<Tabs.List class='grid w-full grid-cols-2 md:max-w-72'>
<Tabs.Trigger value='extensions'>Extensions</Tabs.Trigger>
<Tabs.Trigger value='repositories'>Repositories</Tabs.Trigger>
</Tabs.List>
<div class='flex items-center relative scale-parent md:max-w-72 w-full'>
<Input
class='pl-9 bg-neutral-950 select:bg-accent select:text-accent-foreground shadow-sm no-scale placeholder:opacity-50'
placeholder='Search {value}...'
bind:value={inputText} />
<MagnifyingGlass class='h-4 w-4 shrink-0 opacity-50 absolute left-3 text-muted-foreground z-10 pointer-events-none' />
</div>
</div>
<Tabs.Content value='extensions' tabindex={-1}>
<div class='flex flex-col gap-y-2 justify-center py-3'>
{#each filterSearch(Object.entries($saved), inputText) as [id, config] (id)}
<div class='bg-neutral-950 px-4 py-3 rounded-md flex flex-row space-x-3 justify-between w-full border border-border'>
<div class='flex flex-col space-y-3'>
<div class='flex flex-row space-x-3'>
<img src={config.icon} alt='ico' class='size-10 rounded-md bg-neutral-900' loading='lazy' decoding='async' />
<div class='flex flex-col'>
<div class='text-md font-bold'>
{config.name}
</div>
<div class='text-xs text-muted-foreground'>
{id}
</div>
</div>
</div>
<div class='flex-wrap w-full justify-start gap-2 flex text-neutral-300 text-sm'>
<div class='rounded px-3 py-0.5 font-bold bg-neutral-900 leading-snug'>
{config.version}
</div>
<div class='rounded px-3 py-0.5 font-bold bg-neutral-900 leading-snug'>
{typeMap[config.type]}
</div>
<div class='rounded px-3 py-0.5 font-bold bg-neutral-900 leading-snug capitalize'>
{config.accuracy} Accuracy
</div>
{#if config.ratio}
<div class='rounded px-3 py-0.5 font-bold bg-neutral-900 leading-snug'>
{config.ratio} Ratio
</div>
{/if}
</div>
</div>
<ExtensionSettings {config} />
</div>
{:else}
<div class='px-4 py-6 gap-y-4 flex flex-col items-center'>
<div class='text-bold text-2xl'>
Looks like there's nothing here...
</div>
<div class='text-sm text-muted-foreground'>
Import some extensions in the Repositories tab.
</div>
</div>
{/each}
</div>
</Tabs.Content>
<Tabs.Content value='repositories' tabindex={-1}>
<div class='gap-3 flex py-3 sm:flex-row flex-col'>
<Tooltip.Root>
<Tooltip.Trigger class='w-full'>
<Input class='bg-neutral-950' type='url' placeholder='https://example.com/manifest.json' bind:value={extensionInput} />
</Tooltip.Trigger>
<Tooltip.Content class='max-w-full w-52'>
Destination URL of the extension manifest to import extensions from. This can be a direct URL to a .json file, an npm package prefixed with npm:, a file in a github repository prefixed with gh: or a file with the file: prefix and the code inlined as text.
</Tooltip.Content>
</Tooltip.Root>
{#await importPromise}
<Button class='font-bold flex items-center justify-center w-full sm:w-56 max-w-full shrink-0 !pointer-events-auto cursor-wait' disabled size='default'>
<Plus size={iconSizes.lg} class='mr-2' />
Importing extensions....
</Button>
{:then _}
<Button class='font-bold flex items-center justify-center w-full sm:w-56 max-w-full shrink-0' size='default' on:click={importExtension}>
<Plus size={iconSizes.lg} class='mr-2' />
Import Extensions
</Button>
{/await}
</div>
<div class='flex flex-col gap-y-2 justify-center py-3'>
{#each filterSearch(Object.entries(Object.groupBy(Object.values($saved), saved => saved.update ?? '')), inputText) as [id, extensions] (id) }
{@const url = new URL(id)}
<div class='bg-neutral-950 px-4 py-3 rounded-md flex flex-row space-x-3 justify-between items-center w-full border border-border'>
<div class='flex space-x-2 items-center'>
{#if url.protocol === 'gh:'}
<Github class='w-5 h-5 text-muted-foreground' />
{:else if url.protocol === 'npm:'}
<svg class='w-5 h-5 text-muted-foreground' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'><path fill='currentColor' d='M2 38.5h124v43.71H64v7.29H36.44v-7.29H2Zm6.89 36.43h13.78V53.07h6.89v21.86h6.89V45.79H8.89Zm34.44-29.14v36.42h13.78v-7.28h13.78V45.79Zm13.78 7.29H64v14.56h-6.89Zm20.67-7.29v29.14h13.78V53.07h6.89v21.86h6.89V53.07h6.89v21.86h6.89V45.79Z' /></svg>
{:else}
<Globe class='w-5 h-5 text-muted-foreground' strokeWidth='1px' />
{/if}
<div>
{url.pathname}
</div>
</div>
<div class='text-xs text-muted-foreground'>
{extensions?.length ?? 0} Extensions
</div>
</div>
{:else}
<div class='px-4 py-6 gap-y-4 flex flex-col items-center'>
<div class='text-bold text-2xl'>
Looks like there's nothing here...
</div>
<div class='text-sm text-muted-foreground'>
Import some extensions in the field above.
</div>
</div>
{/each}
</div>
</Tabs.Content>
</Tabs.Root>