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

Configure Feed

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

refactor: make queue-storage more consistent with image-cache

also upgrade ver to 2

intergrav 8f48b6ab f7731d7d

+90 -48
+4 -1
src/js/image-cache.js
··· 24 24 if (this.db) return this.db; 25 25 try { 26 26 await new Promise((resolve) => { 27 - const req = indexedDB.open("tinysub", 1); 27 + const req = indexedDB.open("tinysub", 2); 28 28 req.onsuccess = () => { 29 29 this.db = req.result; 30 30 resolve(); 31 31 }; 32 32 req.onupgradeneeded = (e) => { 33 33 const db = e.target.result; 34 + if (!db.objectStoreNames.contains("queueStorage")) { 35 + db.createObjectStore("queueStorage"); 36 + } 34 37 if (!db.objectStoreNames.contains("imageCache")) { 35 38 db.createObjectStore("imageCache", { keyPath: "key" }); 36 39 }
+11 -1
src/js/input.js
··· 340 340 case "Escape": { 341 341 e.preventDefault(); 342 342 cleanupContextMenu(); 343 - selectionManager.clear(); 343 + // clear selection only in focused container 344 + if (isInMain) { 345 + selectionManager.clear(); 346 + } else if (isInSidebar) { 347 + if (LibraryNavigator.currentFocusedItem) { 348 + LibraryNavigator.currentFocusedItem.classList.remove( 349 + "library-focused", 350 + ); 351 + LibraryNavigator.currentFocusedItem = null; 352 + } 353 + } 344 354 refocusContext(isInMain, isInSidebar); 345 355 break; 346 356 }
+1 -4
src/js/library.js
··· 339 339 updateQueueDisplay(); 340 340 } 341 341 342 - // build a tree item with callback wrapping 343 342 function buildTreeItem(item, mapped, onToggle, onAction, onPlayNext, artType) { 344 - // store item data by ID for keyboard access 345 343 libraryItemsById.set(item.id, item); 346 344 347 345 return createTreeItem( ··· 481 479 const container = config.container(); 482 480 const items = config.data(); 483 481 484 - // renderTree inline 485 482 container.innerHTML = ""; 486 483 if (!state.expanded[config.expandedKey]) return; 487 484 if (!items.length) { ··· 504 501 }); 505 502 container.appendChild(ul); 506 503 507 - // restore expanded items for artists only (restoreExpandedItems inline) 504 + // restore expanded items for artists only 508 505 if (config.onRestore && items.length) { 509 506 const expandMap = config.expandedMap(); 510 507 items.forEach((item) => {
+74 -42
src/js/queue-storage.js
··· 8 8 async ensureDB() { 9 9 if (this.db) return this.db; 10 10 11 - return new Promise((resolve) => { 12 - const request = indexedDB.open("tinysub", 1); 11 + try { 12 + return await new Promise((resolve) => { 13 + const req = indexedDB.open("tinysub", 2); 13 14 14 - request.onsuccess = () => { 15 - this.db = request.result; 16 - resolve(this.db); 17 - }; 15 + req.onsuccess = () => { 16 + this.db = req.result; 17 + resolve(this.db); 18 + }; 18 19 19 - request.onupgradeneeded = (e) => { 20 - const db = e.target.result; 21 - if (!db.objectStoreNames.contains("queue")) { 22 - db.createObjectStore("queue"); 23 - } 24 - }; 20 + req.onupgradeneeded = (e) => { 21 + const db = e.target.result; 22 + if (!db.objectStoreNames.contains("queueStorage")) { 23 + db.createObjectStore("queueStorage"); 24 + } 25 + if (!db.objectStoreNames.contains("imageCache")) { 26 + db.createObjectStore("imageCache", { keyPath: "key" }); 27 + } 28 + }; 25 29 26 - request.onerror = () => resolve(null); 27 - }); 30 + req.onerror = () => resolve(null); 31 + }); 32 + } catch (error) { 33 + console.error("[QueueStorage] IndexedDB initialization failed:", error); 34 + return null; 35 + } 28 36 } 29 37 30 38 async save(songs, queueIndex) { 31 39 const db = await this.ensureDB(); 32 - if (!db) throw new Error("IndexedDB unavailable"); 40 + if (!db) return; 33 41 34 - return new Promise((resolve, reject) => { 35 - const tx = db.transaction(["queue"], "readwrite"); 36 - const store = tx.objectStore("queue"); 42 + return new Promise((resolve) => { 43 + try { 44 + const tx = db.transaction(["queueStorage"], "readwrite"); 45 + const store = tx.objectStore("queueStorage"); 37 46 38 - store.put(songs, "songs"); 39 - store.put(queueIndex, "queueIndex"); 47 + store.put(songs, "songs"); 48 + store.put(queueIndex, "queueIndex"); 40 49 41 - tx.onerror = () => reject(new Error("Failed to save queue")); 42 - tx.oncomplete = () => resolve(); 50 + tx.onerror = () => { 51 + console.error("[QueueStorage] Failed to save queue"); 52 + resolve(); 53 + }; 54 + tx.oncomplete = () => resolve(); 55 + } catch (error) { 56 + console.error("[QueueStorage] Save failed:", error); 57 + resolve(); 58 + } 43 59 }); 44 60 } 45 61 46 62 async load() { 47 63 const db = await this.ensureDB(); 48 - if (!db) throw new Error("IndexedDB unavailable"); 64 + if (!db) return { songs: [], queueIndex: -1 }; 49 65 50 - return new Promise((resolve, reject) => { 51 - const tx = db.transaction(["queue"], "readonly"); 52 - const store = tx.objectStore("queue"); 66 + return new Promise((resolve) => { 67 + try { 68 + const tx = db.transaction(["queueStorage"], "readonly"); 69 + const store = tx.objectStore("queueStorage"); 53 70 54 - const songsReq = store.get("songs"); 55 - const indexReq = store.get("queueIndex"); 71 + const songsReq = store.get("songs"); 72 + const indexReq = store.get("queueIndex"); 56 73 57 - tx.onerror = () => reject(new Error("Failed to load queue")); 58 - tx.oncomplete = () => { 59 - resolve({ 60 - songs: songsReq.result || [], 61 - queueIndex: indexReq.result ?? -1, 62 - }); 63 - }; 74 + tx.onerror = () => { 75 + console.error("[QueueStorage] Failed to load queue"); 76 + resolve({ songs: [], queueIndex: -1 }); 77 + }; 78 + tx.oncomplete = () => { 79 + resolve({ 80 + songs: songsReq.result || [], 81 + queueIndex: indexReq.result ?? -1, 82 + }); 83 + }; 84 + } catch (error) { 85 + console.error("[QueueStorage] Load failed:", error); 86 + resolve({ songs: [], queueIndex: -1 }); 87 + } 64 88 }); 65 89 } 66 90 67 91 async clear() { 68 92 const db = await this.ensureDB(); 69 - if (!db) throw new Error("IndexedDB unavailable"); 93 + if (!db) return; 70 94 71 - return new Promise((resolve, reject) => { 72 - const tx = db.transaction(["queue"], "readwrite"); 73 - const store = tx.objectStore("queue"); 95 + return new Promise((resolve) => { 96 + try { 97 + const tx = db.transaction(["queueStorage"], "readwrite"); 98 + const store = tx.objectStore("queueStorage"); 74 99 75 - store.clear(); 100 + store.clear(); 76 101 77 - tx.onerror = () => reject(new Error("Failed to clear queue")); 78 - tx.oncomplete = () => resolve(); 102 + tx.onerror = () => { 103 + console.error("[QueueStorage] Failed to clear queue"); 104 + resolve(); 105 + }; 106 + tx.oncomplete = () => resolve(); 107 + } catch (error) { 108 + console.error("[QueueStorage] Clear failed:", error); 109 + resolve(); 110 + } 79 111 }); 80 112 } 81 113 }