code refactoring, added router, moved download api path
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,4 +13,5 @@ yt-dlp-webui
|
||||
session.dat
|
||||
config.yml
|
||||
cookies.txt
|
||||
__debug*
|
||||
__debug*
|
||||
app/
|
||||
@@ -30,6 +30,8 @@ func ApplyRouter(db *sql.DB, mdb *internal.MemoryDB, mq *internal.MessageQueue)
|
||||
r.Post("/template", h.AddTemplate())
|
||||
r.Get("/template/all", h.GetTemplates())
|
||||
r.Delete("/template/{id}", h.DeleteTemplate())
|
||||
|
||||
r.Get("/tree", h.DirectoryTree())
|
||||
r.Get("/d/{id}", h.DownloadFile())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||
@@ -173,3 +176,36 @@ func (h *Handler) DirectoryTree() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) DownloadFile() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
path, err := h.service.DownloadFile(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add(
|
||||
"Content-Disposition",
|
||||
"inline; filename="+filepath.Base(*path),
|
||||
)
|
||||
w.Header().Set(
|
||||
"Content-Type",
|
||||
"application/octet-stream",
|
||||
)
|
||||
|
||||
fd, err := os.Open(*path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
io.Copy(w, fd)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,3 +123,12 @@ func (s *Service) DeleteTemplate(ctx context.Context, id string) error {
|
||||
func (s *Service) DirectoryTree(ctx context.Context) (*internal.Stack[sys.FSNode], error) {
|
||||
return sys.DirectoryTree()
|
||||
}
|
||||
|
||||
func (s *Service) DownloadFile(ctx context.Context, id string) (*string, error) {
|
||||
p, err := s.mdb.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &p.Output.Path, nil
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.0.8",
|
||||
"fp-ts": "^2.16.2",
|
||||
"lucide-svelte": "^0.323.0"
|
||||
"lucide-svelte": "^0.323.0",
|
||||
"svelte-spa-router": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
14
ui/pnpm-lock.yaml
generated
14
ui/pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ dependencies:
|
||||
lucide-svelte:
|
||||
specifier: ^0.323.0
|
||||
version: 0.323.0(svelte@4.2.10)
|
||||
svelte-spa-router:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
|
||||
devDependencies:
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
@@ -1254,6 +1257,11 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/regexparam@2.0.2:
|
||||
resolution: {integrity: sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1498,6 +1506,12 @@ packages:
|
||||
typescript: 5.3.3
|
||||
dev: true
|
||||
|
||||
/svelte-spa-router@4.0.1:
|
||||
resolution: {integrity: sha512-2JkmUQ2f9jRluijL58LtdQBIpynSbem2eBGp4zXdi7aDY1znbR6yjw0KsonD0aq2QLwf4Yx4tBJQjxIjgjXHKg==}
|
||||
dependencies:
|
||||
regexparam: 2.0.2
|
||||
dev: false
|
||||
|
||||
/svelte@4.2.10:
|
||||
resolution: {integrity: sha512-Ep06yCaCdgG1Mafb/Rx8sJ1QS3RW2I2BxGp2Ui9LBHSZ2/tO/aGLc5WqPjgiAP6KAnLJGaIr/zzwQlOo1b8MxA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||
import Router from 'svelte-spa-router';
|
||||
import { wrap } from 'svelte-spa-router/wrap';
|
||||
import Footer from './lib/Footer.svelte';
|
||||
import Home from './lib/Home.svelte';
|
||||
import Home from './views/Home.svelte';
|
||||
import Navbar from './lib/Navbar.svelte';
|
||||
|
||||
const routes = {
|
||||
'/': Home,
|
||||
'/settings': wrap({
|
||||
asyncComponent: () => import('./views/SettingsView.svelte'),
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
||||
<main
|
||||
class="bg-neutral-50 dark:bg-neutral-900 h-screen text-neutral-950 dark:text-neutral-50"
|
||||
>
|
||||
<Navbar />
|
||||
<Home />
|
||||
<Router {routes} />
|
||||
<Footer />
|
||||
<SvelteToast />
|
||||
<!-- <FloatingAction /> -->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={`px-2.5 py-2 rounded-lg bg-orange-300 hover:bg-orange-400 hover:duration-150 text-sm font-semibold ${
|
||||
class={`px-2.5 py-2 rounded-lg bg-blue-300 hover:bg-blue-400 hover:duration-150 text-sm font-semibold ${
|
||||
disabled && 'bg-neutral-300 hover:bg-neutral-300'
|
||||
} ${clazz}`}
|
||||
{disabled}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-1.5 p-1 bg-orange-200 rounded-lg text-neutral-900"
|
||||
class="flex items-center gap-1.5 p-1 bg-blue-200 rounded-lg text-neutral-900"
|
||||
>
|
||||
<slot />
|
||||
{text}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { get } from 'svelte/store';
|
||||
import Chip from './Chip.svelte';
|
||||
import type { RPCResult } from './types';
|
||||
import { formatSpeedMiB, mapProcessStatus, roundMiB } from './utils';
|
||||
import { rpcClient } from './store';
|
||||
import Button from './Button.svelte';
|
||||
import Chip from './Chip.svelte';
|
||||
import { rpcClient, serverApiEndpoint } from './store';
|
||||
import type { RPCResult } from './types';
|
||||
import { formatSpeedMiB, roundMiB } from './utils';
|
||||
|
||||
export let download: RPCResult;
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
<div
|
||||
class="flex gap-4
|
||||
bg-neutral-100 dark:bg-neutral-800
|
||||
pt-2 md:p-4
|
||||
p-2 md:p-4
|
||||
rounded-lg shadow-lg
|
||||
border dark:border-neutral-700"
|
||||
>
|
||||
<div
|
||||
class="h-full w-96 bg-cover bg-center rounded"
|
||||
class="h-full hidden sm:block w-96 bg-cover bg-center rounded"
|
||||
style="background-image: url({download.info.thumbnail})"
|
||||
/>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-end gap-2 select-none flex-wrap">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<div class="hidden sm:flex items-center gap-2 text-sm">
|
||||
{#if download.info.vcodec}
|
||||
<Chip text={download.info.vcodec} />
|
||||
{/if}
|
||||
@@ -50,9 +50,9 @@
|
||||
{#if download.info.filesize_approx}
|
||||
<Chip text={roundMiB(download.info.filesize_approx)} />
|
||||
{/if}
|
||||
{#if download.progress.process_status}
|
||||
<!-- {#if download.progress.process_status}
|
||||
<Chip text={mapProcessStatus(download.progress.process_status)} />
|
||||
{/if}
|
||||
{/if} -->
|
||||
{#if download.progress.speed}
|
||||
<Chip text={formatSpeedMiB(download.progress.speed)} />
|
||||
{/if}
|
||||
@@ -62,6 +62,7 @@
|
||||
<Button class="w-14" on:click={() => remove(download.id)}>Stop</Button>
|
||||
{#if download.progress.process_status === 2}
|
||||
<Button class="w-18">Download</Button>
|
||||
<!-- <a href={`${$serverApiEndpoint}/api/v1/d/${download.id}`}>d</a> -->
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -72,7 +73,7 @@
|
||||
class={`h-2 rounded-full ${
|
||||
download.progress.process_status === 2
|
||||
? 'bg-green-600'
|
||||
: 'bg-orange-500'
|
||||
: 'bg-blue-500'
|
||||
}`}
|
||||
style="width: {download.progress.percentage}"
|
||||
/>
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
<div class="absolute bottom-10 right-10">
|
||||
<!-- <div class="relative mb-4 flex flex-col justify-center items-center gap-2">
|
||||
<button
|
||||
class="relative flex items-center justify-center bg-orange-500 h-8 w-8 z-10 rounded-2xl shadow-xl text-neutral-100"
|
||||
class="relative flex items-center justify-center bg-blue-500 h-8 w-8 z-10 rounded-2xl shadow-xl text-neutral-100"
|
||||
>
|
||||
<Plus size={18} />
|
||||
</button>
|
||||
<button
|
||||
class="relative flex items-center justify-center bg-orange-500 h-8 w-8 z-10 rounded-2xl shadow-xl text-neutral-100"
|
||||
class="relative flex items-center justify-center bg-blue-500 h-8 w-8 z-10 rounded-2xl shadow-xl text-neutral-100"
|
||||
>
|
||||
<Plus size={18} />
|
||||
</button>
|
||||
</div> -->
|
||||
<button
|
||||
class="relative bg-orange-500 p-5 z-10 rounded-2xl shadow-xl text-neutral-100"
|
||||
class="relative bg-blue-500 p-5 z-10 rounded-2xl shadow-xl text-neutral-100"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { ChevronUp, ChevronDown } from 'lucide-svelte';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-svelte';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { tweened } from 'svelte/motion';
|
||||
import Settings from './Settings.svelte';
|
||||
import NewDownload from './NewDownload.svelte';
|
||||
|
||||
const height = tweened(52, {
|
||||
@@ -11,7 +10,7 @@
|
||||
});
|
||||
|
||||
const minHeight = 52;
|
||||
const maxHeight = window.innerHeight - 60;
|
||||
const maxHeight = window.innerHeight / 1.5;
|
||||
|
||||
let open = false;
|
||||
$: open = $height > minHeight;
|
||||
@@ -19,7 +18,7 @@
|
||||
|
||||
<footer
|
||||
class="
|
||||
absolute bottom-0
|
||||
fixed bottom-0 z-10
|
||||
w-full
|
||||
p-2
|
||||
bg-neutral-100 dark:bg-neutral-800
|
||||
@@ -44,7 +43,6 @@
|
||||
{#if $height > 100}
|
||||
<div class="mt-2">
|
||||
<NewDownload />
|
||||
<Settings />
|
||||
</div>
|
||||
{/if}
|
||||
</footer>
|
||||
|
||||
@@ -24,16 +24,16 @@
|
||||
for="formats"
|
||||
class="
|
||||
[&_p]:text-gray-900 [&_span]:text-gray-500
|
||||
peer-checked:[&_p]:text-white peer-checked:[&_span]:text-orange-100
|
||||
peer-checked:[&_p]:text-white peer-checked:[&_span]:text-blue-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
|
||||
peer-focus:ring-opacity-60 peer-focus:ring-offset-2 peer-focus:ring-offset-blue-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:bg-blue-700/75
|
||||
peer-checked:text-white"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { ArrowDownUp, Github, HardDrive, Network } from 'lucide-svelte';
|
||||
import {
|
||||
ArrowDownUp,
|
||||
Github,
|
||||
HardDrive,
|
||||
Network,
|
||||
Settings,
|
||||
} from 'lucide-svelte';
|
||||
import { downloads, rpcClient, serverApiEndpoint } from './store';
|
||||
import { formatGiB, formatSpeedMiB } from './utils';
|
||||
import * as O from 'fp-ts/Option';
|
||||
import { pipe } from 'fp-ts/lib/function';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { link } from 'svelte-spa-router';
|
||||
|
||||
let downloadSpeed = 0;
|
||||
|
||||
@@ -34,13 +41,13 @@
|
||||
shadow-lg
|
||||
select-none"
|
||||
>
|
||||
<div class="font-semibold text-lg">yt-dlp WebUI</div>
|
||||
<a use:link={'/'} href="/" class="font-semibold text-lg">yt-dlp WebUI</a>
|
||||
|
||||
<div />
|
||||
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<div
|
||||
class="flex items-center gap-1.5 p-1 text-neutral-900 bg-orange-200 rounded-lg"
|
||||
class="hidden sm:flex items-center gap-1.5 p-1 text-neutral-900 bg-blue-200 rounded-lg"
|
||||
>
|
||||
<ArrowDownUp size={18} />
|
||||
<div>
|
||||
@@ -50,7 +57,7 @@
|
||||
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<div
|
||||
class="flex items-center gap-1.5 p-1 text-neutral-900 bg-orange-200 rounded-lg"
|
||||
class="flex items-center gap-1.5 p-1 text-neutral-900 bg-blue-200 rounded-lg"
|
||||
>
|
||||
<HardDrive size={18} />
|
||||
<div>
|
||||
@@ -63,7 +70,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-1.5 p-1 text-neutral-900 bg-orange-200 rounded-lg"
|
||||
class="flex items-center gap-1.5 p-1 text-neutral-900 bg-blue-200 rounded-lg"
|
||||
>
|
||||
<Network size={18} />
|
||||
<div>
|
||||
@@ -73,10 +80,18 @@
|
||||
|
||||
<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"
|
||||
class="flex items-center gap-1.5 p-1 text-neutral-900 bg-blue-200 rounded-lg"
|
||||
>
|
||||
<Github size={18} />
|
||||
</a>
|
||||
|
||||
<a
|
||||
use:link={'/settings'}
|
||||
href="/settings"
|
||||
class="flex items-center gap-1.5 p-1 text-neutral-900 bg-blue-200 rounded-lg"
|
||||
>
|
||||
<Settings size={18} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
border rounded-lg
|
||||
appearance-none
|
||||
text-sm font-semibold
|
||||
focus:outline-orange-300
|
||||
focus:outline-blue-300
|
||||
"
|
||||
bind:value
|
||||
{disabled}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
const update = () => (loading = get(rpcClient).updateExecutable());
|
||||
</script>
|
||||
|
||||
<div class="w-full px-8 mt-8">
|
||||
<div class="w-full">
|
||||
<div class="font-semibold text-lg mb-4">Settings</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-orange-400"
|
||||
class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-400"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -12,7 +12,13 @@
|
||||
<label for=""> {label} </label>
|
||||
<input
|
||||
type="text"
|
||||
class={`p-2 bg-neutral-50 border rounded-lg focus:outline-orange-300 ${clazz}`}
|
||||
class={`p-2
|
||||
bg-neutral-50 border
|
||||
rounded-lg
|
||||
focus:outline-blue-300
|
||||
dark:bg-neutral-700 dark:border-neutral-900
|
||||
${clazz}
|
||||
`}
|
||||
on:keyup
|
||||
bind:value
|
||||
{placeholder}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
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';
|
||||
import DownloadCard from '../lib/DownloadCard.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);
|
||||
13
ui/src/views/SettingsView.svelte
Normal file
13
ui/src/views/SettingsView.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import Settings from '../lib/Settings.svelte';
|
||||
</script>
|
||||
|
||||
<main
|
||||
class="bg-neutral-100 dark:bg-neutral-800
|
||||
rounded-xl
|
||||
border dark:border-neutral-700
|
||||
shadow-lg
|
||||
m-8 p-4"
|
||||
>
|
||||
<Settings />
|
||||
</main>
|
||||
Reference in New Issue
Block a user