feat(queue): switchable queue display mode (Queue vs Playlist) (#922)

* feat(queue): add queueDisplayMode setting with rehydrate (default queue)

* feat(queue): render upcoming-only or full timeline by mode, with header toggle

* feat(settings): add queue display mode toggle to Personalisation

* i18n: queue display mode strings across all locales

* test(queue): display-mode rendering and absolute index mapping

* docs: changelog + credits for queue display mode (#922)
This commit is contained in:
Frank Stellmacher
2026-05-30 00:26:07 +02:00
committed by GitHub
parent 7b06be5ba2
commit 1de2b0e850
30 changed files with 342 additions and 32 deletions
+1
View File
@@ -99,6 +99,7 @@ export const useAuthStore = create<AuthState>()(
seekbarStyle: 'truewave',
queueNowPlayingCollapsed: false,
queueDurationDisplayMode: 'total',
queueDisplayMode: 'queue',
enableHiRes: false,
audioOutputDevice: null,
hotCacheEnabled: false,
+11
View File
@@ -17,6 +17,7 @@ import type {
DiscordCoverSource,
DurationMode,
LyricsSourceConfig,
QueueDisplayMode,
SeekbarStyle,
} from './authStoreTypes';
@@ -103,6 +104,15 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
? {}
: { queueDurationDisplayMode: 'total' as DurationMode };
// Missing key (pre-feature persist) / garbage maps to 'queue' — the default
// mode, which lists only upcoming tracks.
const VALID_QUEUE_DISPLAY_MODES = new Set<string>(['playlist', 'queue']);
const queueDisplayModeMigrated = VALID_QUEUE_DISPLAY_MODES.has(
(state as { queueDisplayMode?: unknown }).queueDisplayMode as string,
)
? {}
: { queueDisplayMode: 'queue' as QueueDisplayMode };
const VALID_WAYLAND_TEXT_PROFILE = new Set<string>(['balanced', 'sharp', 'gpu', 'minimal']);
const rawWaylandProfile = (state as { linuxWaylandTextRenderProfile?: unknown }).linuxWaylandTextRenderProfile;
const linuxWaylandTextRenderProfileMigrated = VALID_WAYLAND_TEXT_PROFILE.has(rawWaylandProfile as string)
@@ -162,6 +172,7 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
...wheelSmoothOneTime,
...seekbarStyleMigrated,
...queueDurationDisplayModeMigrated,
...queueDisplayModeMigrated,
...linuxWaylandTextRenderProfileMigrated,
...discordCoverSourceMigrated,
};
+13
View File
@@ -33,6 +33,16 @@ export interface ServerProfile {
export type SeekbarStyle = 'truewave' | 'pseudowave' | 'linedot' | 'bar' | 'thick' | 'segmented' | 'neon' | 'pulsewave' | 'particletrail' | 'liquidfill' | 'retrotape';
/** Queue header duration chip: total duration / time left / ETA finish clock. */
export type DurationMode = 'total' | 'remaining' | 'eta';
/**
* Queue panel render mode.
* - `playlist`: the full queue stays visible; the now-playing row sits at the
* top of the list, the highlight wanders down as tracks play, and the list
* only re-pins the current track once it scrolls out of view.
* - `queue`: the list shows upcoming tracks only — the current track lives in
* the header and drops out of the list once it has played.
*/
export type QueueDisplayMode = 'playlist' | 'queue';
export type LoggingMode = 'off' | 'normal' | 'debug';
/**
* Wall-clock format for ETA / sleep-timer labels. `'auto'` follows the user's
@@ -181,6 +191,8 @@ export interface AuthState {
queueNowPlayingCollapsed: boolean;
/** Queue header duration chip mode (cycle: total → remaining → ETA). */
queueDurationDisplayMode: DurationMode;
/** Queue panel render mode: full timeline (`playlist`) vs. upcoming-only (`queue`). */
queueDisplayMode: QueueDisplayMode;
/** Alpha: native hi-res sample rate output (disabled = safe 44.1 kHz mode) */
enableHiRes: boolean;
@@ -339,6 +351,7 @@ export interface AuthState {
setSeekbarStyle: (v: SeekbarStyle) => void;
setQueueNowPlayingCollapsed: (v: boolean) => void;
setQueueDurationDisplayMode: (v: DurationMode) => void;
setQueueDisplayMode: (v: QueueDisplayMode) => void;
setEnableHiRes: (v: boolean) => void;
setAudioOutputDevice: (v: string | null) => void;
setHotCacheEnabled: (v: boolean) => void;
+2
View File
@@ -26,6 +26,7 @@ export function createUiAppearanceActions(set: SetState): Pick<
| 'setSeekbarStyle'
| 'setQueueNowPlayingCollapsed'
| 'setQueueDurationDisplayMode'
| 'setQueueDisplayMode'
| 'setShowFullscreenLyrics'
| 'setFsLyricsStyle'
| 'setSidebarLyricsStyle'
@@ -50,6 +51,7 @@ export function createUiAppearanceActions(set: SetState): Pick<
setSeekbarStyle: (v) => set({ seekbarStyle: v }),
setQueueNowPlayingCollapsed: (v) => set({ queueNowPlayingCollapsed: v }),
setQueueDurationDisplayMode: (v) => set({ queueDurationDisplayMode: v }),
setQueueDisplayMode: (v) => set({ queueDisplayMode: v }),
setShowFullscreenLyrics: (v) => set({ showFullscreenLyrics: v }),
setFsLyricsStyle: (v) => set({ fsLyricsStyle: v }),
setSidebarLyricsStyle: (v) => set({ sidebarLyricsStyle: v }),