mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
Adds a per-element visibility toggle for the playlist detail page (Add
Songs, Import CSV, Download ZIP, Cache Offline, Suggestions) and reworks
the way uncommon options are surfaced: instead of a per-tab collapsible
group, a global "Advanced" toggle in the Settings header reveals all
`advanced` sub-sections across every tab and marks each one with a small
badge. Sets the pattern up so any future advanced option lives in its
natural tab, gated by the same switch.
- New `advancedSettingsEnabled` boolean on `authStore`
(UiAppearance slice, persisted with the rest of the store).
- `SettingsSubSection` gains an `advanced?: boolean` prop. Hidden when
the toggle is off; renders an "Advanced" pill in the header when on.
- Settings header gets a Toggle-Switch next to the search lupe.
- `PersonalisationTab` flattens — Sidebar + Home stay always visible;
Artist sections, Queue Toolbar, and the new Playlist layout get
`advanced` and disappear by default. `PersonalisationAdvancedGroup`
component + CSS removed.
- New `playlistLayoutStore` (Zustand + persist, items[{id,visible}] +
rehydrate sanitize) following the queueToolbarStore pattern.
- `PlaylistHero` and `PlaylistSuggestions` gate the four toolbar buttons
and the suggestions rail on the store directly.
- One-time migration in MainApp on mount: if the user had opened the
old per-tab Advanced group (`psysonic_personalisation_advanced_open
=== 'true'`) OR already customised any of the three sub-sections,
Advanced Mode auto-enables on first launch. Idempotent via a
localStorage flag; legacy key removed afterwards.
- New i18n keys `settings.advancedMode`, `settings.advancedModeTooltip`,
`settings.advancedBadge`, `settings.playlistLayout*` in all 9 locales.
Reuses kveld9's design from PR #556; not merged because the locale split
landed afterwards. Credited under the existing kveld9 entry in
settingsCredits.ts.
Co-authored-by: Kveld. <kveld912@proton.me>
This commit is contained in:
committed by
GitHub
parent
e1283f4068
commit
651a3f276a
@@ -86,6 +86,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
fsPortraitDim: 28,
|
||||
showChangelogOnUpdate: true,
|
||||
lastSeenChangelogVersion: '',
|
||||
advancedSettingsEnabled: false,
|
||||
seekbarStyle: 'truewave',
|
||||
queueNowPlayingCollapsed: false,
|
||||
enableHiRes: false,
|
||||
|
||||
@@ -130,6 +130,8 @@ export interface AuthState {
|
||||
fsPortraitDim: number;
|
||||
showChangelogOnUpdate: boolean;
|
||||
lastSeenChangelogVersion: string;
|
||||
/** Reveals sub-sections marked `advanced` across all Settings tabs. */
|
||||
advancedSettingsEnabled: boolean;
|
||||
|
||||
seekbarStyle: SeekbarStyle;
|
||||
/** Persisted UI toggle: is the Now Playing section in queue panel collapsed */
|
||||
@@ -284,6 +286,7 @@ export interface AuthState {
|
||||
setFsPortraitDim: (v: number) => void;
|
||||
setShowChangelogOnUpdate: (v: boolean) => void;
|
||||
setLastSeenChangelogVersion: (v: string) => void;
|
||||
setAdvancedSettingsEnabled: (v: boolean) => void;
|
||||
setSeekbarStyle: (v: SeekbarStyle) => void;
|
||||
setQueueNowPlayingCollapsed: (v: boolean) => void;
|
||||
setEnableHiRes: (v: boolean) => void;
|
||||
|
||||
@@ -29,6 +29,7 @@ export function createUiAppearanceActions(set: SetState): Pick<
|
||||
| 'setFsPortraitDim'
|
||||
| 'setShowChangelogOnUpdate'
|
||||
| 'setLastSeenChangelogVersion'
|
||||
| 'setAdvancedSettingsEnabled'
|
||||
> {
|
||||
return {
|
||||
setShowArtistImages: (v) => set({ showArtistImages: v }),
|
||||
@@ -48,5 +49,6 @@ export function createUiAppearanceActions(set: SetState): Pick<
|
||||
setFsPortraitDim: (v) => set({ fsPortraitDim: v }),
|
||||
setShowChangelogOnUpdate: (v) => set({ showChangelogOnUpdate: v }),
|
||||
setLastSeenChangelogVersion: (v) => set({ lastSeenChangelogVersion: v }),
|
||||
setAdvancedSettingsEnabled: (v) => set({ advancedSettingsEnabled: v }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type PlaylistLayoutItemId =
|
||||
| 'addSongs'
|
||||
| 'importCsv'
|
||||
| 'downloadZip'
|
||||
| 'offlineCache'
|
||||
| 'suggestions';
|
||||
|
||||
export interface PlaylistLayoutItemConfig {
|
||||
id: PlaylistLayoutItemId;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_PLAYLIST_LAYOUT_ITEMS: PlaylistLayoutItemConfig[] = [
|
||||
{ id: 'addSongs', visible: true },
|
||||
{ id: 'importCsv', visible: true },
|
||||
{ id: 'downloadZip', visible: true },
|
||||
{ id: 'offlineCache', visible: true },
|
||||
{ id: 'suggestions', visible: true },
|
||||
];
|
||||
|
||||
interface PlaylistLayoutStore {
|
||||
items: PlaylistLayoutItemConfig[];
|
||||
setItems: (items: PlaylistLayoutItemConfig[]) => void;
|
||||
toggleItem: (id: PlaylistLayoutItemId) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export const usePlaylistLayoutStore = create<PlaylistLayoutStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
items: DEFAULT_PLAYLIST_LAYOUT_ITEMS,
|
||||
|
||||
setItems: (items) => set({ items }),
|
||||
|
||||
toggleItem: (id) => set((s) => ({
|
||||
items: s.items.map(it => it.id === id ? { ...it, visible: !it.visible } : it),
|
||||
})),
|
||||
|
||||
reset: () => set({ items: DEFAULT_PLAYLIST_LAYOUT_ITEMS }),
|
||||
}),
|
||||
{
|
||||
name: 'psysonic_playlist_layout',
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (!state) return;
|
||||
const knownIds = new Set(DEFAULT_PLAYLIST_LAYOUT_ITEMS.map(i => i.id));
|
||||
const safe = (state.items ?? [])
|
||||
.filter((i): i is PlaylistLayoutItemConfig =>
|
||||
i != null && typeof i.id === 'string' && knownIds.has(i.id as PlaylistLayoutItemId));
|
||||
const seen = new Set(safe.map(i => i.id));
|
||||
const missing = DEFAULT_PLAYLIST_LAYOUT_ITEMS.filter(i => !seen.has(i.id));
|
||||
state.items = missing.length > 0 ? [...safe, ...missing] : safe;
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export function isPlaylistLayoutCustomized(items: PlaylistLayoutItemConfig[]): boolean {
|
||||
if (items.length !== DEFAULT_PLAYLIST_LAYOUT_ITEMS.length) return true;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const cur = items[i];
|
||||
const def = DEFAULT_PLAYLIST_LAYOUT_ITEMS[i];
|
||||
if (cur.id !== def.id || cur.visible !== def.visible) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user