Experiment to rebuild Diffuse using web applets.
1import type { Track } from "@applets/core/types.js";
2import type { Item, State } from "./types";
3import { arrayShuffle, expose, transfer } from "@scripts/common.ts";
4
5////////////////////////////////////////////
6// STATE
7////////////////////////////////////////////
8
9const QUEUE_SIZE = 25;
10
11const internal: { pool: Track[] } = {
12 pool: [],
13};
14
15////////////////////////////////////////////
16// ACTIONS
17////////////////////////////////////////////
18const actions = expose({
19 add,
20 pool,
21 shift,
22 unshift,
23});
24
25export type Actions = typeof actions;
26
27// Actions
28
29function add(state: State, items: Item[]): State {
30 return transfer({ ...state, future: [...state.future, ...items] });
31}
32
33function pool(state: State, tracks: Track[]): State {
34 internal.pool = tracks;
35
36 // TODO: If the pool changes, only remove non-existing tracks
37 // instead of resetting the whole future queue.
38 //
39 // What about past queue items?
40
41 state = fill({ ...state, future: [] });
42
43 // Automatically insert track if there isn't any
44 if (!state.now) return shift(state);
45 return transfer(state);
46}
47
48function shift(state: State): State {
49 const now = state.future[0] || null;
50 const future = state.future.slice(1);
51 const past = state.now ? [...state.past, state.now] : state.past;
52 const filled = fill({ past, now, future });
53
54 return transfer(filled);
55}
56
57function unshift(state: State): State {
58 if (state.past.length === 0) return state;
59
60 const past = [...state.past];
61 const [last] = past.splice(past.length - 1, 1);
62 const now = last ?? null;
63 const future = state.now ? [state.now, ...state.future] : state.future;
64
65 return transfer({ past, now, future });
66}
67
68// 🛠️
69
70// TODO: Most likely there's a more performant solution
71function fill(state: State): State {
72 if (state.future.length >= QUEUE_SIZE) return state;
73
74 let reducedPool = internal.pool.reduce(
75 ({ past, pool }: { past: Set<string>; pool: Track[] }, track: Track) => {
76 if (past.has(track.id))
77 return {
78 past: past.difference(new Set(track.id)),
79 pool,
80 };
81
82 return {
83 past,
84 pool: [...pool, track],
85 };
86 },
87 { past: new Set(state.past.map((t) => t.id)), pool: [] },
88 ).pool;
89
90 if (reducedPool.length === 0) {
91 reducedPool = internal.pool;
92 }
93
94 const poolSelection = arrayShuffle(reducedPool).slice(0, QUEUE_SIZE - state.future.length);
95 return add(state, poolSelection);
96}