mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
feat(settings): library card grid max columns in Appearance (4–12, default 6)
Persist libraryGridMaxColumns, wire useCardGridMetrics and card grid layout, add i18n and settings search index; document performance hint in copy.
This commit is contained in:
@@ -30,6 +30,10 @@ vi.mock('@/api/subsonic', () => ({
|
||||
import { useAuthStore } from './authStore';
|
||||
import { resetAuthStore, resetPlayerStore } from '@/test/helpers/storeReset';
|
||||
import { onInvoke } from '@/test/mocks/tauri';
|
||||
import {
|
||||
LIBRARY_GRID_MAX_COLUMNS_MAX,
|
||||
LIBRARY_GRID_MAX_COLUMNS_MIN,
|
||||
} from './authStoreDefaults';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAuthStore();
|
||||
@@ -120,6 +124,17 @@ describe('setters with validation / clamping', () => {
|
||||
expect(useAuthStore.getState().trackPreviewDurationSec).toBe(31);
|
||||
});
|
||||
|
||||
it('setLibraryGridMaxColumns clamps to the allowed column range', () => {
|
||||
useAuthStore.getState().setLibraryGridMaxColumns(99);
|
||||
expect(useAuthStore.getState().libraryGridMaxColumns).toBe(LIBRARY_GRID_MAX_COLUMNS_MAX);
|
||||
|
||||
useAuthStore.getState().setLibraryGridMaxColumns(1);
|
||||
expect(useAuthStore.getState().libraryGridMaxColumns).toBe(LIBRARY_GRID_MAX_COLUMNS_MIN);
|
||||
|
||||
useAuthStore.getState().setLibraryGridMaxColumns(6);
|
||||
expect(useAuthStore.getState().libraryGridMaxColumns).toBe(6);
|
||||
});
|
||||
|
||||
it('setTrackPreviewsEnabled coerces truthy/falsy to boolean', () => {
|
||||
useAuthStore.getState().setTrackPreviewsEnabled('yes' as unknown as boolean);
|
||||
expect(useAuthStore.getState().trackPreviewsEnabled).toBe(true);
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB,
|
||||
DEFAULT_LYRICS_SOURCES,
|
||||
DEFAULT_TRACK_PREVIEW_LOCATIONS,
|
||||
DEFAULT_LIBRARY_GRID_MAX_COLUMNS,
|
||||
} from './authStoreDefaults';
|
||||
import { computeAuthStoreRehydration } from './authStoreRehydrate';
|
||||
import type { AuthState } from './authStoreTypes';
|
||||
@@ -58,6 +59,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
infiniteQueueEnabled: false,
|
||||
preservePlayNextOrder: false,
|
||||
showArtistImages: false,
|
||||
libraryGridMaxColumns: DEFAULT_LIBRARY_GRID_MAX_COLUMNS,
|
||||
showTrayIcon: true,
|
||||
minimizeToTray: false,
|
||||
showOrbitTrigger: true,
|
||||
|
||||
@@ -38,3 +38,8 @@ export const DEFAULT_LYRICS_SOURCES: LyricsSourceConfig[] = [
|
||||
export const MIX_MIN_RATING_FILTER_MAX_STARS = 3;
|
||||
|
||||
export const RANDOM_MIX_SIZE_OPTIONS: readonly number[] = [50, 75, 100, 125, 150];
|
||||
|
||||
/** Default max columns for album/artist/playlist card grids (Settings → Library). */
|
||||
export const DEFAULT_LIBRARY_GRID_MAX_COLUMNS = 6;
|
||||
export const LIBRARY_GRID_MAX_COLUMNS_MIN = 4;
|
||||
export const LIBRARY_GRID_MAX_COLUMNS_MAX = 12;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/audio/loudnessPreAnalysisSlider';
|
||||
import {
|
||||
DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB,
|
||||
DEFAULT_LIBRARY_GRID_MAX_COLUMNS,
|
||||
LIBRARY_GRID_MAX_COLUMNS_MAX,
|
||||
LIBRARY_GRID_MAX_COLUMNS_MIN,
|
||||
LOUDNESS_LUFS_PRESETS,
|
||||
MIX_MIN_RATING_FILTER_MAX_STARS,
|
||||
RANDOM_MIX_SIZE_OPTIONS,
|
||||
@@ -41,6 +44,13 @@ export function clampRandomMixSize(v: number): number {
|
||||
return nearest;
|
||||
}
|
||||
|
||||
/** Persisted max columns for library card grids (albums, artists, playlists, …). */
|
||||
export function clampLibraryGridMaxColumns(v: unknown): number {
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
if (!Number.isFinite(n)) return DEFAULT_LIBRARY_GRID_MAX_COLUMNS;
|
||||
return Math.max(LIBRARY_GRID_MAX_COLUMNS_MIN, Math.min(LIBRARY_GRID_MAX_COLUMNS_MAX, Math.round(n)));
|
||||
}
|
||||
|
||||
export function clampSkipStarThreshold(v: number): number {
|
||||
if (!Number.isFinite(v)) return 3;
|
||||
return Math.max(1, Math.min(99, Math.round(v)));
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB } from './authStoreDefault
|
||||
import {
|
||||
clampMixFilterMinStars,
|
||||
clampRandomMixSize,
|
||||
clampLibraryGridMaxColumns,
|
||||
sanitizeLoudnessLufsPreset,
|
||||
sanitizeLoudnessPreAnalysisFromStorage,
|
||||
sanitizeSkipStarCounts,
|
||||
@@ -116,6 +117,9 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
|
||||
mixMinRatingAlbum: clampMixFilterMinStars(state.mixMinRatingAlbum as number),
|
||||
mixMinRatingArtist: clampMixFilterMinStars(state.mixMinRatingArtist as number),
|
||||
randomMixSize: clampRandomMixSize(state.randomMixSize as number),
|
||||
libraryGridMaxColumns: clampLibraryGridMaxColumns(
|
||||
(state as { libraryGridMaxColumns?: unknown }).libraryGridMaxColumns,
|
||||
),
|
||||
skipStarManualSkipCountsByKey: sanitizeSkipStarCounts(
|
||||
(state as { skipStarManualSkipCountsByKey?: unknown }).skipStarManualSkipCountsByKey,
|
||||
),
|
||||
|
||||
@@ -80,6 +80,11 @@ export interface AuthState {
|
||||
infiniteQueueEnabled: boolean;
|
||||
preservePlayNextOrder: boolean;
|
||||
showArtistImages: boolean;
|
||||
/**
|
||||
* Max columns for album/artist/playlist-style card grids (Settings → Library).
|
||||
* Clamped 2…12; higher values mean more tiles per row and more layout/paint work.
|
||||
*/
|
||||
libraryGridMaxColumns: number;
|
||||
showTrayIcon: boolean;
|
||||
minimizeToTray: boolean;
|
||||
/** Whether the "Orbit" topbar trigger is rendered. Users who never
|
||||
@@ -252,6 +257,7 @@ export interface AuthState {
|
||||
setInfiniteQueueEnabled: (v: boolean) => void;
|
||||
setPreservePlayNextOrder: (v: boolean) => void;
|
||||
setShowArtistImages: (v: boolean) => void;
|
||||
setLibraryGridMaxColumns: (v: number) => void;
|
||||
setShowTrayIcon: (v: boolean) => void;
|
||||
setMinimizeToTray: (v: boolean) => void;
|
||||
setShowOrbitTrigger: (v: boolean) => void;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AuthState } from './authStoreTypes';
|
||||
import { clampLibraryGridMaxColumns } from './authStoreHelpers';
|
||||
|
||||
type SetState = (
|
||||
partial: Partial<AuthState> | ((state: AuthState) => Partial<AuthState>),
|
||||
@@ -12,6 +13,7 @@ type SetState = (
|
||||
export function createUiAppearanceActions(set: SetState): Pick<
|
||||
AuthState,
|
||||
| 'setShowArtistImages'
|
||||
| 'setLibraryGridMaxColumns'
|
||||
| 'setShowTrayIcon'
|
||||
| 'setMinimizeToTray'
|
||||
| 'setShowOrbitTrigger'
|
||||
@@ -30,6 +32,7 @@ export function createUiAppearanceActions(set: SetState): Pick<
|
||||
> {
|
||||
return {
|
||||
setShowArtistImages: (v) => set({ showArtistImages: v }),
|
||||
setLibraryGridMaxColumns: (v) => set({ libraryGridMaxColumns: clampLibraryGridMaxColumns(v) }),
|
||||
setShowTrayIcon: (v) => set({ showTrayIcon: v }),
|
||||
setMinimizeToTray: (v) => set({ minimizeToTray: v }),
|
||||
setShowOrbitTrigger: (v) => set({ showOrbitTrigger: v }),
|
||||
|
||||
Reference in New Issue
Block a user