[READ-ONLY] Mirror of https://github.com/mrgnw/dlna-rs. Single-binary DLNA media server in Rust. Auto-discovered by VLC on Apple TV.
1<script lang="ts">
2 import { browse, addFolder, type BrowseResp } from './api';
3
4 let { onclose, onadded }: { onclose: () => void; onadded: () => void } = $props();
5
6 let current = $state<BrowseResp | null>(null);
7 let loading = $state(true);
8 let error = $state<string | null>(null);
9 let recursive = $state(true);
10
11 async function go(path?: string) {
12 loading = true;
13 error = null;
14 try { current = await browse(path); }
15 catch (e) { error = String(e); }
16 finally { loading = false; }
17 }
18 go();
19
20 async function select() {
21 if (!current) return;
22 try {
23 await addFolder(current.path, recursive);
24 onadded();
25 } catch (e) { error = String(e); }
26 }
27</script>
28
29<div class="backdrop" onclick={onclose} role="presentation">
30 <div class="modal" onclick={(e) => e.stopPropagation()} role="dialog">
31 <header>
32 <strong>Pick a folder on the server</strong>
33 <button onclick={onclose}>✕</button>
34 </header>
35 {#if loading}
36 <p>Loading…</p>
37 {:else if error}
38 <p class="err">{error}</p>
39 {:else if current}
40 <div class="crumbs">
41 {#if current.parent}
42 <button onclick={() => go(current!.parent!)}>← up</button>
43 {/if}
44 <code>{current.path}</code>
45 </div>
46 <ul>
47 {#each current.entries.filter(e => e.is_dir) as e}
48 <li>
49 <button class="entry" onclick={() => go(`${current!.path === '/' ? '' : current!.path}/${e.name}`)}>
50 📁 {e.name}
51 </button>
52 </li>
53 {/each}
54 </ul>
55 <footer>
56 <label><input type="checkbox" bind:checked={recursive} /> recursive</label>
57 <button class="primary" onclick={select}>Select this folder</button>
58 </footer>
59 {/if}
60 </div>
61</div>
62
63<style>
64 .backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.4); display: flex; align-items: center; justify-content: center; }
65 .modal { background: white; width: 90%; max-width: 600px; max-height: 80vh; display: flex; flex-direction: column; border-radius: 6px; padding: 1rem; }
66 header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; }
67 .crumbs { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; padding: 0.4rem 0; border-bottom: 1px solid #eee; }
68 .crumbs code { font-family: ui-monospace, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
69 ul { list-style: none; padding: 0; margin: 0; overflow-y: auto; flex: 1; }
70 li { padding: 0.1rem 0; }
71 .entry { display: block; width: 100%; text-align: left; background: none; border: none; padding: 0.3rem 0.4rem; cursor: pointer; font-size: 0.9rem; }
72 .entry:hover { background: #f3f3f3; }
73 footer { display: flex; justify-content: space-between; align-items: center; padding-top: 0.6rem; border-top: 1px solid #eee; }
74 button { cursor: pointer; }
75 button.primary { background: #222; color: white; border: none; padding: 0.5rem 0.9rem; border-radius: 4px; }
76 .err { color: #c33; }
77</style>