feat(cli): completions, library/audio commands, server switcher

Embed bash and zsh completion scripts and add a `completions` subcommand.
Extend the player CLI (library, audio device, instant mix) and surface
`music_library` in snapshots. Add a shared active-server switcher in the
header and locales; instant mix can reseed the queue from CLI and UI.
This commit is contained in:
Maxim Isaev
2026-04-15 00:56:40 +03:00
parent 19b7c8ec06
commit c75297fcf6
24 changed files with 1533 additions and 53 deletions
+19 -1
View File
@@ -64,7 +64,7 @@ export function songToTrack(song: SubsonicSong): Track {
};
}
function shuffleArray<T>(items: T[]): T[] {
export function shuffleArray<T>(items: T[]): T[] {
const arr = [...items];
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
@@ -131,6 +131,8 @@ interface PlayerState {
playRadio: (station: InternetRadioStation) => void;
playTrack: (track: Track, queue?: Track[], manual?: boolean) => void;
/** Queue becomes `[track]` only; if already on this track, does not restart `audio_play`. */
reseedQueueForInstantMix: (track: Track) => void;
pause: () => void;
resume: () => void;
stop: () => void;
@@ -942,6 +944,22 @@ export const usePlayerStore = create<PlayerState>()(
touchHotCacheOnPlayback(track.id, authState.activeServerId ?? '');
},
reseedQueueForInstantMix: (track) => {
const s = get();
if (s.currentTrack?.id !== track.id) {
get().playTrack(track, [track]);
return;
}
const wasPlaying = s.isPlaying;
set({
queue: [track],
queueIndex: 0,
currentTrack: track,
});
syncQueueToServer([track], track, s.currentTime);
if (!wasPlaying) get().resume();
},
// ── pause / resume / togglePlay ──────────────────────────────────────────
pause: () => {
if (get().currentRadio) {