a simple web player for subsonic tinysub.devins.page
subsonic navidrome javascript
9
fork

Configure Feed

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

feat: mobile improvements

+35 -43
+5 -1
index.html
··· 2 2 <html lang="en"> 3 3 <head> 4 4 <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 5 + <meta 6 + name="viewport" 7 + content="width=device-width, initial-scale=1, viewport-fit=cover" 8 + /> 6 9 <meta name="application-name" content="tinysub" /> 10 + <meta name="mobile-web-app-capable" content="yes" /> 7 11 <link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" /> 8 12 <link rel="apple-touch-icon" href="/assets/tinysub-192.png" /> 9 13 <link rel="manifest" href="/assets/manifest.json" />
+20 -14
src/app.css
··· 9 9 --playing: light-dark(hsl(200 90% 50% / 0.25), hsl(200 90% 50% / 0.4)); 10 10 } 11 11 12 - html { 13 - font-size: 0.8125rem; 14 - user-select: none; 15 - -webkit-user-select: none; 16 - overscroll-behavior: none; 17 - } 18 - @media (pointer: coarse) { 19 - html { 20 - font-size: 1rem; 21 - } 22 - } 23 - 24 12 body { 25 13 background-color: var(--bg); 26 14 color: var(--text); ··· 40 28 "actions actions"; 41 29 grid-template-columns: var(--sidebar-width, 20rem) 1fr; 42 30 grid-template-rows: 1fr auto auto; 43 - block-size: 100dvh; 44 - inline-size: 100dvw; 31 + block-size: 100%; 32 + inline-size: 100%; 45 33 } 46 34 47 35 @media screen and (max-width: 32rem) { ··· 65 53 --border: transparent; 66 54 --border-subtle: transparent; 67 55 } 56 + 57 + /* web app/pwa stuff */ 58 + body { 59 + block-size: 100dvh; 60 + inline-size: 100dvw; 61 + padding-top: env(safe-area-inset-top); 62 + padding-right: env(safe-area-inset-right); 63 + border-left: env(safe-area-inset-left) solid var(--bg-secondary); 64 + border-bottom: env(safe-area-inset-bottom) solid var(--bg-tertiary); 65 + -webkit-tap-highlight-color: transparent; 66 + } 67 + 68 + html { 69 + font-size: 0.8125rem; 70 + user-select: none; 71 + -webkit-user-select: none; 72 + overscroll-behavior: none; 73 + }
-9
src/lib/Library.svelte
··· 61 61 elements[focusedIndex]?.dispatchEvent( 62 62 new CustomEvent("actionadd", { detail: alt }), 63 63 ); 64 - } else if (code === "KeyU") { 65 - e.preventDefault(); 66 - elements[focusedIndex]?.dispatchEvent(new CustomEvent("actionupdate")); 67 - } else if (code === "KeyR") { 68 - e.preventDefault(); 69 - elements[focusedIndex]?.dispatchEvent(new CustomEvent("actionrename")); 70 - } else if (code === "KeyD" || key === "Delete") { 71 - e.preventDefault(); 72 - elements[focusedIndex]?.dispatchEvent(new CustomEvent("actiondelete")); 73 64 } else if ( 74 65 key === "ContextMenu" || 75 66 key === "`" ||
+8 -9
src/lib/Queue.svelte
··· 132 132 <div class="body"> 133 133 <VirtualList 134 134 items={queue.tracks} 135 - itemHeight={Math.max(18, settings.artSong)} 135 + itemHeight={Math.max(18, settings.artSong + 2)} 136 + // +2 for 1px padding top and bottom around icon 136 137 bind:scrollToIndex 137 138 > 138 139 {#snippet children(track, index)} ··· 145 146 class:odd={index % 2 !== 0} 146 147 class:over-a={drag?.index === index && !drag.isAfter} 147 148 class:over-b={drag?.index === index && drag.isAfter} 148 - style="block-size: {Math.max(18, settings.artSong)}px;" 149 + style="block-size: {Math.max(18, settings.artSong + 2)}px;" 150 + // +2 for 1px padding top and bottom around icon 149 151 draggable={true} 150 152 ondragstart={(e) => onDragStart(e, index)} 151 153 ondragover={(e) => onDragOver(e, index)} ··· 174 176 srcset="{api.art(artId, settings.artSong)} 1x, {api.art( 175 177 artId, 176 178 settings.artSong * 2, 177 - )} 2x, {api.art(artId, settings.artSong * 4)} 3x" 179 + )} 2x" 178 180 style="inline-size: {settings.artSong}px; block-size: {settings.artSong}px;" 179 181 alt="" 180 182 loading="lazy" ··· 306 308 { label: t("move_up"), action: moveUp }, 307 309 { label: t("move_down"), action: moveDown }, 308 310 { 309 - label: 310 - queue.sel.length > 1 311 - ? t("clear_count", { count: queue.sel.length }) 312 - : t("clear"), 311 + label: t("clear"), 313 312 action: remove, 314 313 }, 315 314 ]} ··· 405 404 } 406 405 .col-touch { 407 406 display: none; 408 - inline-size: 4rem; 407 + inline-size: 6rem; 409 408 justify-content: flex-end; 410 409 } 411 410 @media (pointer: coarse) { ··· 422 421 display: flex; 423 422 align-items: center; 424 423 justify-content: center; 425 - inline-size: 1rem; 424 + inline-size: 1.5rem; 426 425 block-size: 1rem; 427 426 } 428 427 :global(body.rounded-art) #queue .art,
+2 -8
src/lib/TreeItem.svelte
··· 133 133 function keyboardActions(node: HTMLElement) { 134 134 const onAdd = (e: any) => doAdd(e.detail); 135 135 node.addEventListener("actionadd", onAdd); 136 - node.addEventListener("actionupdate", doUpdate); 137 - node.addEventListener("actionrename", doRename); 138 - node.addEventListener("actiondelete", doDelete); 139 136 return { 140 137 destroy() { 141 138 node.removeEventListener("actionadd", onAdd); 142 - node.removeEventListener("actionupdate", doUpdate); 143 - node.removeEventListener("actionrename", doRename); 144 - node.removeEventListener("actiondelete", doDelete); 145 139 }, 146 140 }; 147 141 } ··· 179 173 srcset="{api.art(artId, artSize)} 1x, {api.art( 180 174 artId, 181 175 artSize * 2, 182 - )} 2x, {api.art(artId, artSize * 4)} 3x" 176 + )} 2x" 183 177 style="inline-size: {artSize}px; block-size: {artSize}px;" 184 178 alt="" 185 179 loading="lazy" ··· 336 330 color: inherit; 337 331 } 338 332 :global(body.rounded-art) .art { 339 - border-radius: 4px; 333 + border-radius: 2px; 340 334 } 341 335 </style>
-1
src/lib/lang/locales/en.ts
··· 40 40 add_next: "add next", 41 41 add_to_queue: "add to queue", 42 42 clear_all: "clear all", 43 - clear_count: "clear ({count})", 44 43 clear_queue: "clear queue", 45 44 delete_confirm: 'are you sure you want to delete "{name}"?', 46 45 delete_playlist: "delete playlist",
-1
src/lib/lang/locales/es.ts
··· 44 44 add_next: "añadir siguiente", 45 45 add_to_queue: "añadir a la cola", 46 46 clear_all: "limpiar todo", 47 - clear_count: "eliminar ({count})", 48 47 clear_queue: "limpiar cola", 49 48 delete_confirm: '¿estás seguro de que quieres borrar "{name}"?', 50 49 delete_playlist: "borrar lista",