[READ-ONLY] Mirror of https://github.com/mrgnw/dlna-rs. Single-binary DLNA media server in Rust. Auto-discovered by VLC on Apple TV.
0

Configure Feed

Select the types of activity you want to include in your feed.

web: AddFolderModal with server-side browser

+77
+77
web/src/AddFolderModal.svelte
··· 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>