Merge branch 'main' into exp/orbit

This commit is contained in:
Psychotoxical
2026-04-24 00:19:14 +02:00
33 changed files with 1255 additions and 88 deletions
+5
View File
@@ -138,6 +138,8 @@ interface AuthState {
mixMinRatingAlbum: number;
/** 0 = ignore; artist rating from payload / nested OpenSubsonic fields or `getArtist`. */
mixMinRatingArtist: number;
/** Show "Lucky Mix" as a regular sidebar/menu item. */
showLuckyMixMenu: boolean;
/** Subsonic music folders for the active server (not persisted; refetched on login / server change). */
musicFolders: Array<{ id: string; name: string }>;
@@ -251,6 +253,7 @@ interface AuthState {
setMixMinRatingSong: (v: number) => void;
setMixMinRatingAlbum: (v: number) => void;
setMixMinRatingArtist: (v: number) => void;
setShowLuckyMixMenu: (v: boolean) => void;
setMusicFolders: (folders: Array<{ id: string; name: string }>) => void;
setMusicLibraryFilter: (folderId: 'all' | string) => void;
@@ -361,6 +364,7 @@ export const useAuthStore = create<AuthState>()(
mixMinRatingSong: 0,
mixMinRatingAlbum: 0,
mixMinRatingArtist: 0,
showLuckyMixMenu: true,
randomNavMode: 'hub',
musicFolders: [],
musicLibraryFilterByServer: {},
@@ -528,6 +532,7 @@ export const useAuthStore = create<AuthState>()(
setMixMinRatingSong: (v) => set({ mixMinRatingSong: clampMixFilterMinStars(v) }),
setMixMinRatingAlbum: (v) => set({ mixMinRatingAlbum: clampMixFilterMinStars(v) }),
setMixMinRatingArtist: (v) => set({ mixMinRatingArtist: clampMixFilterMinStars(v) }),
setShowLuckyMixMenu: (v) => set({ showLuckyMixMenu: v }),
setRandomNavMode: (v) => set({ randomNavMode: v }),
setMusicFolders: (folders) => {
+23
View File
@@ -0,0 +1,23 @@
import { create } from 'zustand';
interface LuckyMixState {
/** True while `buildAndPlayLuckyMix` is actively assembling a mix. */
isRolling: boolean;
/**
* Set by `cancel()` — the build loop polls this between awaits and bails
* out silently when true. Reset to false on `start()` so a new build can
* run after a cancelled one.
*/
cancelRequested: boolean;
start: () => void;
stop: () => void;
cancel: () => void;
}
export const useLuckyMixStore = create<LuckyMixState>((set) => ({
isRolling: false,
cancelRequested: false,
start: () => set({ isRolling: true, cancelRequested: false }),
stop: () => set({ isRolling: false, cancelRequested: false }),
cancel: () => set({ cancelRequested: true }),
}));
+21
View File
@@ -197,6 +197,8 @@ interface PlayerState {
enqueueAt: (tracks: Track[], insertIndex: number) => void;
enqueueRadio: (tracks: Track[], artistId?: string) => void;
setRadioArtistId: (artistId: string) => void;
/** For Lucky Mix: drop upcoming tail; keep the currently playing item only. */
pruneUpcomingToCurrent: () => void;
clearQueue: () => void;
isQueueVisible: boolean;
@@ -1364,6 +1366,25 @@ export const usePlayerStore = create<PlayerState>()(
if (!wasPlaying) get().resume();
},
pruneUpcomingToCurrent: () => {
const s = get();
if (s.currentRadio) return;
if (!s.currentTrack) {
if (s.queue.length === 0) return;
set({ queue: [], queueIndex: 0 });
syncQueueToServer([], null, 0);
return;
}
const at = s.queue.findIndex(t => t.id === s.currentTrack!.id);
const newQueue: Track[] =
at >= 0
? s.queue.slice(0, at + 1)
: [s.currentTrack!];
const newIndex = at >= 0 ? at : 0;
set({ queue: newQueue, queueIndex: newIndex });
syncQueueToServer(newQueue, s.currentTrack, s.currentTime);
},
// ── pause / resume / togglePlay ──────────────────────────────────────────
pause: () => {
clearAllPlaybackScheduleTimers();
+1
View File
@@ -15,6 +15,7 @@ export const DEFAULT_SIDEBAR_ITEMS: SidebarItemConfig[] = [
{ id: 'randomPicker', visible: true },
{ id: 'randomMix', visible: true },
{ id: 'randomAlbums', visible: true },
{ id: 'luckyMix', visible: true },
{ id: 'artists', visible: true },
{ id: 'genres', visible: true },
{ id: 'favorites', visible: true },