[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.

dlna-rs / web / src / AddFolderModal.svelte
2.9 kB 77 lines
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>