layout refactoring
This commit is contained in:
@@ -1,63 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||
import * as O from 'fp-ts/Option';
|
||||
import { pipe } from 'fp-ts/lib/function';
|
||||
import { onDestroy } from 'svelte';
|
||||
import DownloadCard from './lib/DownloadCard.svelte';
|
||||
import Footer from './lib/Footer.svelte';
|
||||
import Home from './lib/Home.svelte';
|
||||
import Navbar from './lib/Navbar.svelte';
|
||||
import Spinner from './lib/Spinner.svelte';
|
||||
import { downloads, rpcClient } from './lib/store';
|
||||
import { datetimeCompareFunc, isRPCResponse } from './lib/utils';
|
||||
|
||||
const unsubscribe = rpcClient.subscribe(($client) => {
|
||||
setInterval(() => $client.running(), 750);
|
||||
|
||||
$client.socket.onmessage = (ev: any) => {
|
||||
const event = JSON.parse(ev.data);
|
||||
// guards
|
||||
if (!isRPCResponse(event)) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(event.result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.result) {
|
||||
return downloads.set(
|
||||
O.of(
|
||||
event.result
|
||||
.filter((f) => !!f.info.url)
|
||||
.sort((a, b) =>
|
||||
datetimeCompareFunc(b.info.created_at, a.info.created_at),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
downloads.set(O.none);
|
||||
};
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
</script>
|
||||
|
||||
<main
|
||||
class="bg-neutral-50 dark:bg-neutral-900 h-screen text-neutral-950 dark:text-neutral-50"
|
||||
>
|
||||
<Navbar />
|
||||
{#if O.isNone($downloads)}
|
||||
<div class="h-[90vh] w-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2 p-8">
|
||||
{#each pipe( $downloads, O.getOrElseW(() => []), ) as download}
|
||||
<DownloadCard {download} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<!-- <FloatingAction /> -->
|
||||
<Home />
|
||||
<Footer />
|
||||
<SvelteToast />
|
||||
<!-- <FloatingAction /> -->
|
||||
</main>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
border-t dark:border-t-neutral-700
|
||||
shadow-lg
|
||||
rounded-t-xl"
|
||||
style="height: {$height}px;"
|
||||
style="min-height: {$height}px;"
|
||||
>
|
||||
<button
|
||||
class="p-1 bg-neutral-200 dark:bg-neutral-700 rounded-lg border dark:border-neutral-700"
|
||||
@@ -44,7 +44,7 @@
|
||||
{#if $height > 100}
|
||||
<div class="mt-2">
|
||||
<NewDownload />
|
||||
<!-- <Settings /> -->
|
||||
<Settings />
|
||||
</div>
|
||||
{/if}
|
||||
</footer>
|
||||
|
||||
@@ -3,61 +3,68 @@
|
||||
|
||||
let group = '';
|
||||
export let formats: DLFormat[];
|
||||
|
||||
$: console.log(group);
|
||||
</script>
|
||||
|
||||
<div class="flex w-full flex-col items-center justify-center">
|
||||
<div class="w-full px-4 py-16">
|
||||
<div class="mx-auto w-full max-w-md">
|
||||
<fieldset class="flex flex-col space-y-2">
|
||||
{#each formats as format}
|
||||
<div class="relative">
|
||||
<input
|
||||
id="startup"
|
||||
class="absolute opacity-0 w-0 h-0 peer"
|
||||
type="radio"
|
||||
bind:group
|
||||
name="type"
|
||||
value="startup"
|
||||
/>
|
||||
<label
|
||||
for="startup"
|
||||
class="[&_p]:text-gray-900 [&_span]:text-gray-500 peer-checked:[&_p]:text-white peer-checked:[&_span]:text-sky-100 peer-focus:ring-2 peer-focus:ring-white peer-focus:ring-opacity-60 peer-focus:ring-offset-2 peer-focus:ring-offset-sky-300 bg-white relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none peer-checked:bg-sky-900/75 peer-checked:text-white"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="text-sm">
|
||||
<p class="font-medium" id="headlessui-label-:R5mm:">
|
||||
{format.resolution}
|
||||
</p>
|
||||
<span class="inline" id="headlessui-description-:R9mm:">
|
||||
<span>{format.vcodec}</span>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{format.vcodec}</span></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0 text-white">
|
||||
<svg viewBox="0 0 24 24" fill="none" class="h-6 w-6">
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="12"
|
||||
fill="#fff"
|
||||
opacity="0.2"
|
||||
/><path
|
||||
d="M7 13l3 3 7-7"
|
||||
stroke="#fff"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div class="w-full mt-4">
|
||||
<div class="mx-auto w-full">
|
||||
<fieldset class="grid grid-cols-7 gap-2">
|
||||
{#each formats as format}
|
||||
<div class="relative">
|
||||
<input
|
||||
id="formats"
|
||||
class="absolute opacity-0 w-0 h-0 peer"
|
||||
type="radio"
|
||||
bind:group
|
||||
name="type"
|
||||
value="formats"
|
||||
/>
|
||||
<label
|
||||
for="formats"
|
||||
class="
|
||||
[&_p]:text-gray-900 [&_span]:text-gray-500
|
||||
peer-checked:[&_p]:text-white peer-checked:[&_span]:text-orange-100
|
||||
peer-focus:ring-2 peer-focus:ring-white
|
||||
peer-focus:ring-opacity-60 peer-focus:ring-offset-2 peer-focus:ring-offset-orange-300
|
||||
bg-white
|
||||
relative flex
|
||||
cursor-pointer
|
||||
rounded-lg px-5 py-4
|
||||
shadow-md
|
||||
focus:outline-none
|
||||
peer-checked:bg-orange-700/75
|
||||
peer-checked:text-white"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="text-sm">
|
||||
<p class="font-medium" id={format.format_id}>
|
||||
{format.resolution}
|
||||
</p>
|
||||
<span class="inline" id={format.format_id}>
|
||||
<span>{format.vcodec}</span>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{format.acodec}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{/each}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="shrink-0 text-white">
|
||||
<svg viewBox="0 0 24 24" fill="none" class="h-6 w-6">
|
||||
<circle cx="12" cy="12" r="12" fill="#fff" opacity="0.2" />
|
||||
<path
|
||||
d="M7 13l3 3 7-7"
|
||||
stroke="#fff"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{/each}
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
9
ui/src/lib/FullscreenSpinner.svelte
Normal file
9
ui/src/lib/FullscreenSpinner.svelte
Normal file
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Spinner from './Spinner.svelte';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="top-0 left-0 absolute w-full h-full bg-neutral-950/20 flex items-center justify-center z-50"
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
52
ui/src/lib/Home.svelte
Normal file
52
ui/src/lib/Home.svelte
Normal file
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import * as O from 'fp-ts/Option';
|
||||
import { pipe } from 'fp-ts/lib/function';
|
||||
import { onDestroy } from 'svelte';
|
||||
import DownloadCard from './DownloadCard.svelte';
|
||||
import Spinner from './Spinner.svelte';
|
||||
import { downloads, rpcClient } from './store';
|
||||
import { datetimeCompareFunc, isRPCResponse } from './utils';
|
||||
|
||||
const unsubscribe = rpcClient.subscribe(($client) => {
|
||||
setInterval(() => $client.running(), 750);
|
||||
|
||||
$client.socket.onmessage = (ev: any) => {
|
||||
const event = JSON.parse(ev.data);
|
||||
// guards
|
||||
if (!isRPCResponse(event)) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(event.result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.result) {
|
||||
return downloads.set(
|
||||
O.of(
|
||||
event.result
|
||||
.filter((f) => !!f.info.url)
|
||||
.sort((a, b) =>
|
||||
datetimeCompareFunc(b.info.created_at, a.info.created_at),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
downloads.set(O.none);
|
||||
};
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
</script>
|
||||
|
||||
{#if O.isNone($downloads)}
|
||||
<div class="h-[90vh] w-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-2 p-8">
|
||||
{#each pipe( $downloads, O.getOrElseW(() => []), ) as download}
|
||||
<DownloadCard {download} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ArrowDownUp, HardDrive, Network } from 'lucide-svelte';
|
||||
import { ArrowDownUp, Github, HardDrive, Network } from 'lucide-svelte';
|
||||
import { downloads, rpcClient, serverApiEndpoint } from './store';
|
||||
import { formatGiB, formatSpeedMiB } from './utils';
|
||||
import * as O from 'fp-ts/Option';
|
||||
@@ -70,6 +70,13 @@
|
||||
{$serverApiEndpoint.split('//')[1]}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://github.com/marcopeocchi/yt-dlp-web-ui"
|
||||
class="flex items-center gap-1.5 p-1 text-neutral-900 bg-orange-200 rounded-lg"
|
||||
>
|
||||
<Github size={18} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<div class="w-full px-8">
|
||||
<div class="my-4 font-semibold text-xl">New download</div>
|
||||
<div class="grid grid-cols-2 gap-2 w-full mb-2">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 w-full mb-2">
|
||||
<TextField placeholder="https://..." label="URL" bind:value={url} />
|
||||
<TextField
|
||||
placeholder="arguments separated by space"
|
||||
|
||||
@@ -7,7 +7,14 @@
|
||||
</script>
|
||||
|
||||
<select
|
||||
class="p-2 bg-neutral-50 border rounded-lg appearance-none text-sm font-semibold"
|
||||
class="
|
||||
p-2
|
||||
bg-neutral-50
|
||||
border rounded-lg
|
||||
appearance-none
|
||||
text-sm font-semibold
|
||||
focus:outline-orange-300
|
||||
"
|
||||
bind:value
|
||||
{disabled}
|
||||
{placeholder}
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
<script lang="ts">
|
||||
import CookiesTextField from './CookiesTextField.svelte';
|
||||
import { rpcHost, rpcPort } from './store';
|
||||
import { get } from 'svelte/store';
|
||||
import Button from './Button.svelte';
|
||||
import TextField from './TextField.svelte';
|
||||
import { rpcClient, rpcHost, rpcPort } from './store';
|
||||
import FullscreenSpinner from './FullscreenSpinner.svelte';
|
||||
|
||||
let loading: Promise<any>;
|
||||
|
||||
const update = () => (loading = get(rpcClient).updateExecutable());
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="font-semibold text-lg">Settings</div>
|
||||
<div class="w-full px-8 mt-8">
|
||||
<div class="font-semibold text-lg mb-4">Settings</div>
|
||||
|
||||
<input type="text" bind:value={$rpcHost} />
|
||||
<input type="text" bind:value={$rpcPort} />
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<TextField
|
||||
label="Server address"
|
||||
bind:value={$rpcHost}
|
||||
placeholder="localhost"
|
||||
/>
|
||||
<TextField label="Server port" bind:value={$rpcPort} placeholder="3033" />
|
||||
</div>
|
||||
|
||||
<CookiesTextField />
|
||||
<Button class="mt-4" on:click={update}>Update yt-dlp</Button>
|
||||
|
||||
{#if loading}
|
||||
{#await loading}
|
||||
<FullscreenSpinner />
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
<!-- <CookiesTextField /> -->
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<label for=""> {label} </label>
|
||||
<input
|
||||
type="text"
|
||||
class={`p-2 bg-neutral-50 border rounded-lg ${clazz}`}
|
||||
class={`p-2 bg-neutral-50 border rounded-lg focus:outline-orange-300 ${clazz}`}
|
||||
on:keyup
|
||||
bind:value
|
||||
{placeholder}
|
||||
|
||||
Reference in New Issue
Block a user