From 26d635791ab546502f76862770b4bb03c1fb3bc9 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 13 Oct 2025 18:34:46 -0700 Subject: [PATCH] update settings store for album/song lists --- src/renderer/store/settings.store.ts | 502 +++++++++++++++++---------- src/shared/types/types.ts | 1 + 2 files changed, 321 insertions(+), 182 deletions(-) diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 64e959a18..8b16cdf84 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -7,6 +7,12 @@ import { shallow } from 'zustand/shallow'; import { createWithEqualityFn } from 'zustand/traditional'; import i18n from '/@/i18n/i18n'; +import { + ALBUM_TABLE_COLUMNS, + PLAYLIST_SONG_TABLE_COLUMNS, + SONG_TABLE_COLUMNS, +} from '/@/renderer/components/item-list/item-table-list/default-columns'; +import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table/table-config-dropdown'; import { ContextMenuItemType } from '/@/renderer/features/context-menu/events'; import { AppRoute } from '/@/renderer/router/routes'; import { mergeOverridingColumns } from '/@/renderer/store/utils'; @@ -18,14 +24,20 @@ import { LibraryItem, LyricSource } from '/@/shared/types/domain-types'; import { CrossfadeStyle, FontType, + ItemListKey, + ListDisplayType, + ListPaginationType, Platform, Play, PlayerStyle, PlayerType, TableColumn, - TableType, } from '/@/shared/types/types'; +type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; +}; + const HomeItemSchema = z.enum([ 'mostPlayed', 'random', @@ -100,17 +112,34 @@ const SortableItemSchema = (itemSchema: T) => id: itemSchema, }); -const PersistedTableColumnSchema = z.object({ - column: z.nativeEnum(TableColumn), - extraProps: z.record(z.any()).optional(), +const ItemTableListColumnConfigSchema = z.object({ + align: z.enum(['center', 'end', 'start']), + autoSize: z.boolean().optional(), + id: z.nativeEnum(TableColumn), + isEnabled: z.boolean(), + pinned: z.union([z.literal('left'), z.literal('right'), z.literal(null)]), width: z.number(), }); -const DataTablePropsSchema = z.object({ - autoFit: z.boolean(), - columns: z.array(PersistedTableColumnSchema), - followCurrentSong: z.boolean().optional(), - rowHeight: z.number(), +const ItemTableListPropsSchema = z.object({ + columns: z.array(ItemTableListColumnConfigSchema), + enableAlternateRowColors: z.boolean(), + enableHorizontalBorders: z.boolean(), + enableRowHoverHighlight: z.boolean(), + enableVerticalBorders: z.boolean(), + size: z.enum(['compact', 'default']), +}); + +const ItemListConfigSchema = z.object({ + display: z.nativeEnum(ListDisplayType), + grid: z.object({ + itemGap: z.enum(['lg', 'md', 'sm', 'xl', 'xs']), + itemsPerRow: z.number(), + itemsPerRowEnabled: z.boolean(), + }), + itemsPerPage: z.number(), + pagination: z.nativeEnum(ListPaginationType), + table: ItemTableListPropsSchema, }); const TranscodingConfigSchema = z.object({ @@ -270,15 +299,6 @@ const RemoteSettingsSchema = z.object({ username: z.string(), }); -const TablesSettingsSchema = z.object({ - albumDetail: DataTablePropsSchema, - fullScreen: DataTablePropsSchema, - nowPlaying: DataTablePropsSchema, - sideDrawerQueue: DataTablePropsSchema, - sideQueue: DataTablePropsSchema, - songs: DataTablePropsSchema, -}); - const WindowSettingsSchema = z.object({ disableAutoUpdate: z.boolean(), exitToTray: z.boolean(), @@ -299,6 +319,7 @@ export const ValidationSettingsStateSchema = z.object({ font: FontSettingsSchema, general: GeneralSettingsSchema, hotkeys: HotkeysSettingsSchema, + lists: z.record(z.nativeEnum(ItemListKey), ItemListConfigSchema), lyrics: LyricsSettingsSchema, playback: PlaybackSettingsSchema, remote: RemoteSettingsSchema, @@ -315,9 +336,7 @@ export const ValidationSettingsStateSchema = z.object({ /** * This schema is merged below to create the full SettingsSchema but not used during import validation */ -export const NonValidatedSettingsStateSchema = z.object({ - tables: TablesSettingsSchema, -}); +export const NonValidatedSettingsStateSchema = z.object({}); export const SettingsStateSchema = ValidationSettingsStateSchema.merge( NonValidatedSettingsStateSchema, @@ -394,9 +413,20 @@ export enum HomeItem { RECENTLY_RELEASED = 'recentlyReleased', } -export type DataTableProps = z.infer; +export type DataGridProps = { + itemGap: 'lg' | 'md' | 'sm' | 'xl' | 'xs'; + itemsPerRow: number; + itemsPerRowEnabled: boolean; +}; -export type PersistedTableColumn = z.infer; +export type DataTableProps = z.infer; +export type ItemListSettings = { + display: ListDisplayType; + grid: DataGridProps; + itemsPerPage: number; + pagination: ListPaginationType; + table: DataTableProps; +}; export interface SettingsSlice extends z.infer { actions: { @@ -405,9 +435,10 @@ export interface SettingsSlice extends z.infer { setArtistItems: (item: SortableItem[]) => void; setGenreBehavior: (target: GenreTarget) => void; setHomeItems: (item: SortableItem[]) => void; + setList: (type: ItemListKey, data: DeepPartial) => void; setSettings: (data: Partial) => void; setSidebarItems: (items: SidebarItemType[]) => void; - setTable: (type: TableType, data: DataTableProps) => void; + setTable: (type: ItemListKey, data: DataTableProps) => void; setTranscodingConfig: (config: TranscodingConfig) => void; toggleContextMenuItem: (item: ContextMenuItemType) => void; toggleMediaSession: () => void; @@ -608,6 +639,240 @@ const initialState: SettingsState = { }, globalMediaHotkeys: false, }, + lists: { + [LibraryItem.ALBUM]: { + display: ListDisplayType.TABLE, + grid: { + itemGap: 'md', + itemsPerRow: 6, + itemsPerRowEnabled: false, + }, + itemsPerPage: 100, + pagination: ListPaginationType.INFINITE, + table: { + columns: ALBUM_TABLE_COLUMNS.map((column) => ({ + align: column.align, + autoSize: column.autoSize, + id: column.value, + isEnabled: column.isEnabled, + pinned: column.pinned, + width: column.width, + })), + enableAlternateRowColors: true, + enableHorizontalBorders: true, + enableRowHoverHighlight: true, + enableVerticalBorders: false, + size: 'default', + }, + }, + [LibraryItem.ALBUM_ARTIST]: { + display: ListDisplayType.TABLE, + grid: { + itemGap: 'md', + itemsPerRow: 6, + itemsPerRowEnabled: false, + }, + itemsPerPage: 100, + pagination: ListPaginationType.INFINITE, + table: { + columns: ALBUMARTIST_TABLE_COLUMNS.map((column) => ({ + align: 'start' as const, + autoSize: false, + id: column.value, + isEnabled: true, + pinned: null, + width: 200, + })), + enableAlternateRowColors: true, + enableHorizontalBorders: true, + enableRowHoverHighlight: true, + enableVerticalBorders: false, + size: 'default', + }, + }, + [LibraryItem.ARTIST]: { + display: ListDisplayType.TABLE, + grid: { + itemGap: 'md', + itemsPerRow: 6, + itemsPerRowEnabled: false, + }, + itemsPerPage: 100, + pagination: ListPaginationType.INFINITE, + table: { + columns: ALBUMARTIST_TABLE_COLUMNS.map((column) => ({ + align: 'start' as const, + autoSize: false, + id: column.value, + isEnabled: true, + pinned: null, + width: 200, + })), + enableAlternateRowColors: true, + enableHorizontalBorders: true, + enableRowHoverHighlight: true, + enableVerticalBorders: false, + size: 'default', + }, + }, + [LibraryItem.PLAYLIST]: { + display: ListDisplayType.TABLE, + grid: { + itemGap: 'md', + itemsPerRow: 6, + itemsPerRowEnabled: false, + }, + itemsPerPage: 100, + pagination: ListPaginationType.INFINITE, + table: { + columns: [ + { + align: 'center', + autoSize: false, + id: TableColumn.ROW_INDEX, + isEnabled: true, + pinned: 'left', + width: 80, + }, + { + align: 'center', + autoSize: false, + id: TableColumn.IMAGE, + isEnabled: true, + pinned: 'left', + width: 70, + }, + { + align: 'start', + autoSize: false, + id: TableColumn.TITLE, + isEnabled: true, + pinned: 'left', + width: 300, + }, + { + align: 'start', + autoSize: false, + id: TableColumn.TITLE_COMBINED, + isEnabled: false, + pinned: 'left', + width: 300, + }, + { + align: 'center', + autoSize: false, + id: TableColumn.DURATION, + isEnabled: true, + pinned: null, + width: 100, + }, + { + align: 'center', + autoSize: false, + id: TableColumn.OWNER, + isEnabled: true, + pinned: null, + width: 150, + }, + { + align: 'center', + autoSize: false, + id: TableColumn.SONG_COUNT, + isEnabled: true, + pinned: null, + width: 100, + }, + { + align: 'center', + id: TableColumn.ACTIONS, + isEnabled: true, + pinned: 'right', + width: 60, + }, + ], + enableAlternateRowColors: true, + enableHorizontalBorders: true, + enableRowHoverHighlight: true, + enableVerticalBorders: false, + size: 'default', + }, + }, + [LibraryItem.PLAYLIST_SONG]: { + display: ListDisplayType.TABLE, + grid: { + itemGap: 'md', + itemsPerRow: 6, + itemsPerRowEnabled: false, + }, + itemsPerPage: 100, + pagination: ListPaginationType.INFINITE, + table: { + columns: PLAYLIST_SONG_TABLE_COLUMNS.map((column) => ({ + align: column.align, + autoSize: column.autoSize, + id: column.value, + isEnabled: column.isEnabled, + pinned: column.pinned, + width: column.width, + })), + enableAlternateRowColors: true, + enableHorizontalBorders: true, + enableRowHoverHighlight: true, + enableVerticalBorders: false, + size: 'default', + }, + }, + [LibraryItem.QUEUE_SONG]: { + display: ListDisplayType.TABLE, + grid: { + itemGap: 'md', + itemsPerRow: 6, + itemsPerRowEnabled: false, + }, + itemsPerPage: 100, + pagination: ListPaginationType.INFINITE, + table: { + columns: SONG_TABLE_COLUMNS.map((column) => ({ + align: column.align, + autoSize: column.autoSize, + id: column.value, + isEnabled: column.isEnabled, + pinned: column.pinned, + width: column.width, + })), + enableAlternateRowColors: true, + enableHorizontalBorders: true, + enableRowHoverHighlight: true, + enableVerticalBorders: false, + size: 'default', + }, + }, + [LibraryItem.SONG]: { + display: ListDisplayType.TABLE, + grid: { + itemGap: 'md', + itemsPerRow: 6, + itemsPerRowEnabled: false, + }, + itemsPerPage: 100, + pagination: ListPaginationType.INFINITE, + table: { + columns: SONG_TABLE_COLUMNS.map((column) => ({ + align: column.align, + autoSize: column.autoSize, + id: column.value, + isEnabled: column.isEnabled, + pinned: column.pinned, + width: column.width, + })), + enableAlternateRowColors: true, + enableHorizontalBorders: true, + enableRowHoverHighlight: true, + enableVerticalBorders: false, + size: 'default', + }, + }, + }, lyrics: { alignment: 'center', delayMs: 0, @@ -665,160 +930,6 @@ const initialState: SettingsState = { username: 'feishin', }, tab: 'general', - tables: { - albumDetail: { - autoFit: true, - columns: [ - { - column: TableColumn.TRACK_NUMBER, - width: 50, - }, - { - column: TableColumn.TITLE_COMBINED, - width: 500, - }, - { - column: TableColumn.DURATION, - width: 100, - }, - { - column: TableColumn.PLAY_COUNT, - width: 100, - }, - { - column: TableColumn.LAST_PLAYED, - width: 100, - }, - { - column: TableColumn.USER_FAVORITE, - width: 100, - }, - ], - rowHeight: 60, - }, - fullScreen: { - autoFit: true, - columns: [ - { - column: TableColumn.ROW_INDEX, - width: 80, - }, - { - column: TableColumn.TITLE_COMBINED, - width: 500, - }, - { - column: TableColumn.DURATION, - width: 100, - }, - { - column: TableColumn.USER_FAVORITE, - width: 100, - }, - ], - followCurrentSong: true, - rowHeight: 60, - }, - nowPlaying: { - autoFit: true, - columns: [ - { - column: TableColumn.ROW_INDEX, - width: 80, - }, - { - column: TableColumn.TITLE, - width: 500, - }, - { - column: TableColumn.DURATION, - width: 100, - }, - { - column: TableColumn.ALBUM, - width: 100, - }, - { - column: TableColumn.ALBUM_ARTIST, - width: 100, - }, - { - column: TableColumn.GENRE, - width: 100, - }, - { - column: TableColumn.YEAR, - width: 100, - }, - ], - followCurrentSong: true, - rowHeight: 30, - }, - sideDrawerQueue: { - autoFit: true, - columns: [ - { - column: TableColumn.TITLE_COMBINED, - width: 500, - }, - { - column: TableColumn.DURATION, - width: 100, - }, - ], - followCurrentSong: true, - rowHeight: 60, - }, - sideQueue: { - autoFit: true, - columns: [ - { - column: TableColumn.ROW_INDEX, - width: 50, - }, - { - column: TableColumn.TITLE_COMBINED, - width: 500, - }, - { - column: TableColumn.DURATION, - width: 100, - }, - ], - followCurrentSong: true, - rowHeight: 60, - }, - songs: { - autoFit: true, - columns: [ - { - column: TableColumn.ROW_INDEX, - width: 50, - }, - { - column: TableColumn.TITLE_COMBINED, - width: 500, - }, - { - column: TableColumn.DURATION, - width: 100, - }, - { - column: TableColumn.ALBUM, - width: 300, - }, - { - column: TableColumn.ARTIST, - width: 100, - }, - { - column: TableColumn.YEAR, - width: 100, - }, - ], - rowHeight: 60, - }, - }, window: { disableAutoUpdate: false, exitToTray: false, @@ -869,6 +980,25 @@ export const useSettingsStore = createWithEqualityFn()( state.general.homeItems = items; }); }, + setList: (type: ItemListKey, data: DeepPartial) => { + set((state) => { + const listState = state.lists[type]; + + if (listState && data.table) { + Object.assign(listState.table, data.table); + delete data.table; + } + + if (listState && data.grid) { + Object.assign(listState.grid, data.grid); + delete data.grid; + } + + if (listState) { + Object.assign(listState, data); + } + }); + }, setSettings: (data) => { set({ ...get(), ...data }); }, @@ -877,9 +1007,12 @@ export const useSettingsStore = createWithEqualityFn()( state.general.sidebarItems = items; }); }, - setTable: (type: TableType, data: DataTableProps) => { + setTable: (type: ItemListKey, data: DataTableProps) => { set((state) => { - state.tables[type] = data; + const listState = state.lists[type]; + if (listState) { + listState.table = data; + } }); }, setTranscodingConfig: (config) => { @@ -945,6 +1078,8 @@ export const useSettingsStore = createWithEqualityFn()( } state.window.windowBarStyle = Platform.LINUX; + + return state; } return persistedState; @@ -959,8 +1094,8 @@ export const useSettingsStoreActions = () => useSettingsStore((state) => state.a export const usePlaybackSettings = () => useSettingsStore((state) => state.playback, shallow); -export const useTableSettings = (type: TableType) => - useSettingsStore((state) => state.tables[type]); +export const useTableSettings = (type: ItemListKey) => + useSettingsStore((state) => state.lists[type as keyof typeof state.lists]); export const useGeneralSettings = () => useSettingsStore((state) => state.general, shallow); @@ -1003,3 +1138,6 @@ export const useSettingsForExport = (): SettingsState & { version: number } => export const migrateSettings = (settings: SettingsState, settingsVersion: number): SettingsState => useSettingsStore.persist.getOptions().migrate!(settings, settingsVersion) as SettingsState; + +export const useListSettings = (type: ItemListKey) => + useSettingsStore((state) => state.lists[type as keyof typeof state.lists], shallow); diff --git a/src/shared/types/types.ts b/src/shared/types/types.ts index 94462e3fa..8510e1755 100644 --- a/src/shared/types/types.ts +++ b/src/shared/types/types.ts @@ -22,6 +22,7 @@ export enum ItemListKey { GENRE = LibraryItem.GENRE, NOW_PLAYING = 'nowPlaying', PLAYLIST = LibraryItem.PLAYLIST, + PLAYLIST_SONG = LibraryItem.PLAYLIST_SONG, QUEUE_SONG = LibraryItem.QUEUE_SONG, SIDE_DRAWER_QUEUE = 'sideDrawerQueue', SIDE_QUEUE = 'sideQueue',