A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

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

feat: scoped tracks filter by disabled sources

+280
+20
src/components/orchestrator/scoped-tracks/element.js
··· 51 51 return col.data.filter((p) => p.playlist === playlist); 52 52 }); 53 53 54 + #disabledSources = computed(() => { 55 + const col = this.#output.value?.settings.collection(); 56 + if (!col || col.state !== "loaded") return []; 57 + const setting = col.data.find((s) => s.key === "sh.diffuse.input.disabled.uris"); 58 + if (!setting) return []; 59 + try { 60 + const parsed = JSON.parse(setting.value); 61 + return Array.isArray(parsed) ? /** @type {string[]} */ (parsed) : []; 62 + } catch { 63 + return []; 64 + } 65 + }); 66 + 54 67 #tracksAvailable = signal(/** @type {Track[]} */ ([])); 55 68 #tracksSearch = signal(/** @type {Track[]} */ ([])); 56 69 #tracksFinal = signal(/** @type {Track[]} */ ([])); ··· 199 212 this.effect(async () => { 200 213 const tracks = this.#tracksSearch.value; 201 214 const playlistItems = this.#selectedPlaylistItems(); 215 + const disabledSources = this.#disabledSources(); 202 216 const sortBy = this.#scope.value?.sortBy(); 203 217 const sortDirection = this.#scope.value?.sortDirection(); 204 218 ··· 207 221 let final = playlistItems?.length 208 222 ? filterByPlaylist(tracks, playlistItems) 209 223 : tracks; 224 + 225 + if (disabledSources.length) { 226 + final = final.filter((t) => 227 + !disabledSources.some((source) => t.uri.startsWith(source)) 228 + ); 229 + } 210 230 211 231 if (sortBy?.length) { 212 232 const dir = sortDirection === "desc" ? -1 : 1;
+260
tests/components/orchestrator/scoped-tracks/test.ts
··· 209 209 expect(ids).toEqual(["track-01", "track-02", "track-03"]); 210 210 }); 211 211 }); 212 + 213 + describe("components/orchestrator/scoped-tracks – disabled sources", () => { 214 + it("includes all tracks when no disabled sources setting is present", async () => { 215 + const ids = await testWeb(async () => { 216 + const Output = await import( 217 + "~/components/output/polymorphic/indexed-db/element.js" 218 + ); 219 + const ScopedTracks = await import( 220 + "~/components/orchestrator/scoped-tracks/element.js" 221 + ); 222 + 223 + class TestInput extends HTMLElement { 224 + SCHEME = "test"; 225 + sources() { return []; } 226 + async groupConsult(uris: string[]) { 227 + return { all: { available: true, scheme: "test", uris } }; 228 + } 229 + } 230 + customElements.define("di-test-input-1", TestInput); 231 + 232 + const output = new Output.CLASS(); 233 + output.id = "test-output-ds-1"; 234 + document.body.append(output); 235 + 236 + await output.tracks.save([ 237 + { $type: "sh.diffuse.output.track", id: "ds-alpha-1", uri: "https://alpha.example/song-1.mp3", tags: {} }, 238 + { $type: "sh.diffuse.output.track", id: "ds-alpha-2", uri: "https://alpha.example/song-2.mp3", tags: {} }, 239 + { $type: "sh.diffuse.output.track", id: "ds-beta-1", uri: "https://beta.example/song-3.mp3", tags: {} }, 240 + ]); 241 + // No settings saved — disabled sources key is absent 242 + 243 + const input = new TestInput(); 244 + input.id = "test-input-ds-1"; 245 + document.body.append(input); 246 + 247 + const el = new ScopedTracks.CLASS(); 248 + el.setAttribute("output-selector", "#test-output-ds-1"); 249 + el.setAttribute("input-selector", "#test-input-ds-1"); 250 + document.body.append(el); 251 + 252 + const deadline = Date.now() + 2000; 253 + while (Date.now() < deadline) { 254 + if (el.tracks().length === 3) break; 255 + await new Promise((r) => setTimeout(r, 20)); 256 + } 257 + 258 + return el.tracks().map((t: { id: string }) => t.id).sort(); 259 + }); 260 + 261 + expect(ids).toEqual(["ds-alpha-1", "ds-alpha-2", "ds-beta-1"]); 262 + }); 263 + 264 + it("excludes tracks whose URI starts with a disabled source", async () => { 265 + const ids = await testWeb(async () => { 266 + const Output = await import( 267 + "~/components/output/polymorphic/indexed-db/element.js" 268 + ); 269 + const ScopedTracks = await import( 270 + "~/components/orchestrator/scoped-tracks/element.js" 271 + ); 272 + 273 + class TestInput extends HTMLElement { 274 + SCHEME = "test"; 275 + sources() { return []; } 276 + async groupConsult(uris: string[]) { 277 + return { all: { available: true, scheme: "test", uris } }; 278 + } 279 + } 280 + customElements.define("di-test-input-2", TestInput); 281 + 282 + const output = new Output.CLASS(); 283 + output.id = "test-output-ds-2"; 284 + document.body.append(output); 285 + 286 + await output.tracks.save([ 287 + { $type: "sh.diffuse.output.track", id: "ds-alpha-1", uri: "https://alpha.example/song-1.mp3", tags: {} }, 288 + { $type: "sh.diffuse.output.track", id: "ds-alpha-2", uri: "https://alpha.example/song-2.mp3", tags: {} }, 289 + { $type: "sh.diffuse.output.track", id: "ds-beta-1", uri: "https://beta.example/song-3.mp3", tags: {} }, 290 + ]); 291 + await output.settings.save([ 292 + { $type: "sh.diffuse.output.setting", id: "ds-setting", key: "sh.diffuse.input.disabled.uris", value: JSON.stringify(["https://alpha.example"]) }, 293 + ]); 294 + 295 + const input = new TestInput(); 296 + input.id = "test-input-ds-2"; 297 + document.body.append(input); 298 + 299 + const el = new ScopedTracks.CLASS(); 300 + el.setAttribute("output-selector", "#test-output-ds-2"); 301 + el.setAttribute("input-selector", "#test-input-ds-2"); 302 + document.body.append(el); 303 + 304 + const deadline = Date.now() + 2000; 305 + while (Date.now() < deadline) { 306 + if (el.tracks().length === 1) break; 307 + await new Promise((r) => setTimeout(r, 20)); 308 + } 309 + 310 + return el.tracks().map((t: { id: string }) => t.id); 311 + }); 312 + 313 + expect(ids).toEqual(["ds-beta-1"]); 314 + }); 315 + 316 + it("handles multiple disabled sources", async () => { 317 + const ids = await testWeb(async () => { 318 + const Output = await import( 319 + "~/components/output/polymorphic/indexed-db/element.js" 320 + ); 321 + const ScopedTracks = await import( 322 + "~/components/orchestrator/scoped-tracks/element.js" 323 + ); 324 + 325 + class TestInput extends HTMLElement { 326 + SCHEME = "test"; 327 + sources() { return []; } 328 + async groupConsult(uris: string[]) { 329 + return { all: { available: true, scheme: "test", uris } }; 330 + } 331 + } 332 + customElements.define("di-test-input-3", TestInput); 333 + 334 + const output = new Output.CLASS(); 335 + output.id = "test-output-ds-3"; 336 + document.body.append(output); 337 + 338 + await output.tracks.save([ 339 + { $type: "sh.diffuse.output.track", id: "ds-alpha-1", uri: "https://alpha.example/song-1.mp3", tags: {} }, 340 + { $type: "sh.diffuse.output.track", id: "ds-beta-1", uri: "https://beta.example/song-2.mp3", tags: {} }, 341 + { $type: "sh.diffuse.output.track", id: "ds-gamma-1", uri: "https://gamma.example/song-3.mp3", tags: {} }, 342 + ]); 343 + await output.settings.save([ 344 + { $type: "sh.diffuse.output.setting", id: "ds-setting", key: "sh.diffuse.input.disabled.uris", value: JSON.stringify(["https://alpha.example", "https://beta.example"]) }, 345 + ]); 346 + 347 + const input = new TestInput(); 348 + input.id = "test-input-ds-3"; 349 + document.body.append(input); 350 + 351 + const el = new ScopedTracks.CLASS(); 352 + el.setAttribute("output-selector", "#test-output-ds-3"); 353 + el.setAttribute("input-selector", "#test-input-ds-3"); 354 + document.body.append(el); 355 + 356 + const deadline = Date.now() + 2000; 357 + while (Date.now() < deadline) { 358 + if (el.tracks().length === 1) break; 359 + await new Promise((r) => setTimeout(r, 20)); 360 + } 361 + 362 + return el.tracks().map((t: { id: string }) => t.id); 363 + }); 364 + 365 + expect(ids).toEqual(["ds-gamma-1"]); 366 + }); 367 + 368 + it("includes all tracks when disabled sources value is an empty array", async () => { 369 + const ids = await testWeb(async () => { 370 + const Output = await import( 371 + "~/components/output/polymorphic/indexed-db/element.js" 372 + ); 373 + const ScopedTracks = await import( 374 + "~/components/orchestrator/scoped-tracks/element.js" 375 + ); 376 + 377 + class TestInput extends HTMLElement { 378 + SCHEME = "test"; 379 + sources() { return []; } 380 + async groupConsult(uris: string[]) { 381 + return { all: { available: true, scheme: "test", uris } }; 382 + } 383 + } 384 + customElements.define("di-test-input-4", TestInput); 385 + 386 + const output = new Output.CLASS(); 387 + output.id = "test-output-ds-4"; 388 + document.body.append(output); 389 + 390 + await output.tracks.save([ 391 + { $type: "sh.diffuse.output.track", id: "ds-alpha-1", uri: "https://alpha.example/song-1.mp3", tags: {} }, 392 + { $type: "sh.diffuse.output.track", id: "ds-alpha-2", uri: "https://alpha.example/song-2.mp3", tags: {} }, 393 + { $type: "sh.diffuse.output.track", id: "ds-beta-1", uri: "https://beta.example/song-3.mp3", tags: {} }, 394 + ]); 395 + await output.settings.save([ 396 + { $type: "sh.diffuse.output.setting", id: "ds-setting", key: "sh.diffuse.input.disabled.uris", value: "[]" }, 397 + ]); 398 + 399 + const input = new TestInput(); 400 + input.id = "test-input-ds-4"; 401 + document.body.append(input); 402 + 403 + const el = new ScopedTracks.CLASS(); 404 + el.setAttribute("output-selector", "#test-output-ds-4"); 405 + el.setAttribute("input-selector", "#test-input-ds-4"); 406 + document.body.append(el); 407 + 408 + const deadline = Date.now() + 2000; 409 + while (Date.now() < deadline) { 410 + if (el.tracks().length === 3) break; 411 + await new Promise((r) => setTimeout(r, 20)); 412 + } 413 + 414 + return el.tracks().map((t: { id: string }) => t.id).sort(); 415 + }); 416 + 417 + expect(ids).toEqual(["ds-alpha-1", "ds-alpha-2", "ds-beta-1"]); 418 + }); 419 + 420 + it("includes all tracks when disabled sources value is invalid JSON", async () => { 421 + const ids = await testWeb(async () => { 422 + const Output = await import( 423 + "~/components/output/polymorphic/indexed-db/element.js" 424 + ); 425 + const ScopedTracks = await import( 426 + "~/components/orchestrator/scoped-tracks/element.js" 427 + ); 428 + 429 + class TestInput extends HTMLElement { 430 + SCHEME = "test"; 431 + sources() { return []; } 432 + async groupConsult(uris: string[]) { 433 + return { all: { available: true, scheme: "test", uris } }; 434 + } 435 + } 436 + customElements.define("di-test-input-5", TestInput); 437 + 438 + const output = new Output.CLASS(); 439 + output.id = "test-output-ds-5"; 440 + document.body.append(output); 441 + 442 + await output.tracks.save([ 443 + { $type: "sh.diffuse.output.track", id: "ds-alpha-1", uri: "https://alpha.example/song-1.mp3", tags: {} }, 444 + { $type: "sh.diffuse.output.track", id: "ds-alpha-2", uri: "https://alpha.example/song-2.mp3", tags: {} }, 445 + { $type: "sh.diffuse.output.track", id: "ds-beta-1", uri: "https://beta.example/song-3.mp3", tags: {} }, 446 + ]); 447 + await output.settings.save([ 448 + { $type: "sh.diffuse.output.setting", id: "ds-setting", key: "sh.diffuse.input.disabled.uris", value: "not-valid-json" }, 449 + ]); 450 + 451 + const input = new TestInput(); 452 + input.id = "test-input-ds-5"; 453 + document.body.append(input); 454 + 455 + const el = new ScopedTracks.CLASS(); 456 + el.setAttribute("output-selector", "#test-output-ds-5"); 457 + el.setAttribute("input-selector", "#test-input-ds-5"); 458 + document.body.append(el); 459 + 460 + const deadline = Date.now() + 2000; 461 + while (Date.now() < deadline) { 462 + if (el.tracks().length === 3) break; 463 + await new Promise((r) => setTimeout(r, 20)); 464 + } 465 + 466 + return el.tracks().map((t: { id: string }) => t.id).sort(); 467 + }); 468 + 469 + expect(ids).toEqual(["ds-alpha-1", "ds-alpha-2", "ds-beta-1"]); 470 + }); 471 + });