move detail list to its own config

This commit is contained in:
jeffvli
2026-02-09 10:29:49 -08:00
parent 72b2dca759
commit ba4b07614c
12 changed files with 182 additions and 50 deletions
@@ -7,14 +7,19 @@ import { ItemListKey, TableColumn } from '/@/shared/types/types';
interface UseItemListColumnReorderProps { interface UseItemListColumnReorderProps {
itemListKey: ItemListKey; itemListKey: ItemListKey;
tableKey?: 'detail' | 'main';
} }
export const useItemListColumnReorder = ({ itemListKey }: UseItemListColumnReorderProps) => { export const useItemListColumnReorder = ({
itemListKey,
tableKey = 'main',
}: UseItemListColumnReorderProps) => {
const { setList } = useSettingsStoreActions(); const { setList } = useSettingsStoreActions();
const handleColumnReordered = useCallback( const handleColumnReordered = useCallback(
(columnIdFrom: TableColumn, columnIdTo: TableColumn, edge: Edge | null) => { (columnIdFrom: TableColumn, columnIdTo: TableColumn, edge: Edge | null) => {
const columns = useSettingsStore.getState().lists[itemListKey]?.table.columns; const list = useSettingsStore.getState().lists[itemListKey];
const columns = tableKey === 'detail' ? list?.detail?.columns : list?.table?.columns;
if (!columns) { if (!columns) {
return; return;
@@ -83,13 +88,20 @@ export const useItemListColumnReorder = ({ itemListKey }: UseItemListColumnReord
// Insert the column at the new position // Insert the column at the new position
newColumns.splice(newIndex, 0, updatedMovedColumn); newColumns.splice(newIndex, 0, updatedMovedColumn);
if (tableKey === 'detail') {
type SetListData = Parameters<
ReturnType<typeof useSettingsStoreActions>['setList']
>[1];
setList(itemListKey, { detail: { columns: newColumns } } as SetListData);
} else {
setList(itemListKey, { setList(itemListKey, {
table: { table: {
columns: newColumns, columns: newColumns,
}, },
}); });
}
}, },
[itemListKey, setList], [itemListKey, setList, tableKey],
); );
return { handleColumnReordered }; return { handleColumnReordered };
@@ -5,11 +5,18 @@ import { ItemListKey, TableColumn } from '/@/shared/types/types';
interface UseItemListColumnResizeProps { interface UseItemListColumnResizeProps {
itemListKey: ItemListKey; itemListKey: ItemListKey;
tableKey?: 'detail' | 'main';
} }
export const useItemListColumnResize = ({ itemListKey }: UseItemListColumnResizeProps) => { export const useItemListColumnResize = ({
itemListKey,
tableKey = 'main',
}: UseItemListColumnResizeProps) => {
const { setList } = useSettingsStoreActions(); const { setList } = useSettingsStoreActions();
const columns = useSettingsStore((state) => state.lists[itemListKey]?.table.columns); const columns = useSettingsStore((state) => {
const list = state.lists[itemListKey];
return tableKey === 'detail' ? list?.detail?.columns : list?.table?.columns;
});
const handleColumnResized = useCallback( const handleColumnResized = useCallback(
(columnId: TableColumn, width: number) => { (columnId: TableColumn, width: number) => {
@@ -19,13 +26,20 @@ export const useItemListColumnResize = ({ itemListKey }: UseItemListColumnResize
column.id === columnId ? { ...column, width } : column, column.id === columnId ? { ...column, width } : column,
); );
if (tableKey === 'detail') {
type SetListData = Parameters<
ReturnType<typeof useSettingsStoreActions>['setList']
>[1];
setList(itemListKey, { detail: { columns: updatedColumns } } as SetListData);
} else {
setList(itemListKey, { setList(itemListKey, {
table: { table: {
columns: updatedColumns, columns: updatedColumns,
}, },
}); });
}
}, },
[columns, itemListKey, setList], [columns, itemListKey, setList, tableKey],
); );
return { handleColumnResized }; return { handleColumnResized };
@@ -66,6 +66,8 @@
} }
.track-header-cell { .track-header-cell {
display: flex;
align-items: center;
min-width: 0; min-width: 0;
padding-right: var(--theme-spacing-sm); padding-right: var(--theme-spacing-sm);
padding-left: var(--theme-spacing-sm); padding-left: var(--theme-spacing-sm);
@@ -724,7 +724,7 @@ export const ItemDetailList = ({
const internalState = useItemListState(getDataFn, extractRowIdSong); const internalState = useItemListState(getDataFn, extractRowIdSong);
const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.ALBUM_DETAIL]?.table); const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.ALBUM]?.detail);
const trackColumns = useMemo((): ItemTableListColumnConfig[] => { const trackColumns = useMemo((): ItemTableListColumnConfig[] => {
const raw = tableConfig?.columns; const raw = tableConfig?.columns;
if (raw && raw.length > 0) { if (raw && raw.length > 0) {
+1 -1
View File
@@ -98,7 +98,7 @@ export interface ItemListTableComponentProps<TQuery> extends ItemListComponentPr
enableRowHoverHighlight?: boolean; enableRowHoverHighlight?: boolean;
enableSelection?: boolean; enableSelection?: boolean;
enableVerticalBorders?: boolean; enableVerticalBorders?: boolean;
size?: 'compact' | 'default'; size?: 'compact' | 'default' | 'large';
} }
export interface ItemTableListColumnConfig { export interface ItemTableListColumnConfig {
@@ -74,13 +74,16 @@ export const AlbumListContent = () => {
}; };
const AlbumListSuspenseContainer = () => { const AlbumListSuspenseContainer = () => {
const { display, grid, itemsPerPage, pagination, table } = useListSettings(ItemListKey.ALBUM); const { detail, display, grid, itemsPerPage, pagination, table } = useListSettings(
ItemListKey.ALBUM,
);
const { customFilters } = useListContext(); const { customFilters } = useListContext();
return ( return (
<Suspense fallback={<Spinner container />}> <Suspense fallback={<Spinner container />}>
<AlbumListView <AlbumListView
detail={detail}
display={display} display={display}
grid={grid} grid={grid}
itemsPerPage={itemsPerPage} itemsPerPage={itemsPerPage}
@@ -95,13 +98,17 @@ const AlbumListSuspenseContainer = () => {
export type OverrideAlbumListQuery = Omit<Partial<AlbumListQuery>, 'limit' | 'startIndex'>; export type OverrideAlbumListQuery = Omit<Partial<AlbumListQuery>, 'limit' | 'startIndex'>;
export const AlbumListView = ({ export const AlbumListView = ({
detail,
display, display,
grid, grid,
itemsPerPage, itemsPerPage,
overrideQuery, overrideQuery,
pagination, pagination,
table, table,
}: ItemListSettings & { overrideQuery?: OverrideAlbumListQuery }) => { }: ItemListSettings & {
detail?: ItemListSettings['detail'];
overrideQuery?: OverrideAlbumListQuery;
}) => {
const server = useCurrentServer(); const server = useCurrentServer();
const { pageKey } = useListContext(); const { pageKey } = useListContext();
@@ -196,6 +203,7 @@ export const AlbumListView = ({
case ListPaginationType.INFINITE: { case ListPaginationType.INFINITE: {
return ( return (
<AlbumListInfiniteDetail <AlbumListInfiniteDetail
enableHeader={detail?.enableHeader}
itemsPerPage={itemsPerPage} itemsPerPage={itemsPerPage}
query={mergedQuery} query={mergedQuery}
serverId={server.id} serverId={server.id}
@@ -205,6 +213,7 @@ export const AlbumListView = ({
case ListPaginationType.PAGINATED: { case ListPaginationType.PAGINATED: {
return ( return (
<AlbumListPaginatedDetail <AlbumListPaginatedDetail
enableHeader={detail?.enableHeader}
itemsPerPage={itemsPerPage} itemsPerPage={itemsPerPage}
query={mergedQuery} query={mergedQuery}
serverId={server.id} serverId={server.id}
@@ -98,11 +98,11 @@ export const AlbumListHeaderFilters = ({ toggleGenreTarget }: { toggleGenreTarge
<ListDisplayTypeToggleButton listKey={ItemListKey.ALBUM} /> <ListDisplayTypeToggleButton listKey={ItemListKey.ALBUM} />
<ListConfigMenu <ListConfigMenu
detailConfig={{ detailConfig={{
listKey: ItemListKey.ALBUM_DETAIL,
optionsConfig: { optionsConfig: {
autoFitColumns: { hidden: true }, autoFitColumns: { hidden: true },
}, },
tableColumnsData: SONG_TABLE_COLUMNS, tableColumnsData: SONG_TABLE_COLUMNS,
tableKey: 'detail',
}} }}
listKey={ItemListKey.ALBUM} listKey={ItemListKey.ALBUM}
tableColumnsData={ALBUM_TABLE_COLUMNS} tableColumnsData={ALBUM_TABLE_COLUMNS}
@@ -14,9 +14,12 @@ import {
} from '/@/shared/types/domain-types'; } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types'; import { ItemListKey } from '/@/shared/types/types';
interface AlbumListInfiniteDetailProps extends ItemListComponentProps<AlbumListQuery> {} interface AlbumListInfiniteDetailProps extends ItemListComponentProps<AlbumListQuery> {
enableHeader?: boolean;
}
export const AlbumListInfiniteDetail = ({ export const AlbumListInfiniteDetail = ({
enableHeader = true,
itemsPerPage = 100, itemsPerPage = 100,
query = { query = {
sortBy: AlbumListSort.NAME, sortBy: AlbumListSort.NAME,
@@ -49,6 +52,7 @@ export const AlbumListInfiniteDetail = ({
return ( return (
<ItemDetailList <ItemDetailList
data={loadedItems} data={loadedItems}
enableHeader={enableHeader}
getItem={getItem} getItem={getItem}
itemCount={itemCount} itemCount={itemCount}
onRangeChanged={onRangeChanged} onRangeChanged={onRangeChanged}
@@ -16,9 +16,12 @@ import {
} from '/@/shared/types/domain-types'; } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types'; import { ItemListKey } from '/@/shared/types/types';
interface AlbumListPaginatedDetailProps extends ItemListComponentProps<AlbumListQuery> {} interface AlbumListPaginatedDetailProps extends ItemListComponentProps<AlbumListQuery> {
enableHeader?: boolean;
}
export const AlbumListPaginatedDetail = ({ export const AlbumListPaginatedDetail = ({
enableHeader = true,
itemsPerPage = 100, itemsPerPage = 100,
query = { query = {
sortBy: AlbumListSort.NAME, sortBy: AlbumListSort.NAME,
@@ -59,7 +62,11 @@ export const AlbumListPaginatedDetail = ({
pageCount={pageCount} pageCount={pageCount}
totalItemCount={totalItemCount} totalItemCount={totalItemCount}
> >
<ItemDetailList currentPage={currentPage} items={data || []} /> <ItemDetailList
currentPage={currentPage}
enableHeader={enableHeader}
items={data || []}
/>
</ItemListWithPagination> </ItemListWithPagination>
); );
}; };
@@ -73,9 +73,9 @@ export const ListConfigBooleanControl = ({
}; };
export interface ListConfigMenuDetailConfig { export interface ListConfigMenuDetailConfig {
listKey: ItemListKey;
optionsConfig?: ListConfigMenuOptionsConfig['detail']; optionsConfig?: ListConfigMenuOptionsConfig['detail'];
tableColumnsData: { label: string; value: string }[]; tableColumnsData: { label: string; value: string }[];
tableKey: 'detail';
} }
export interface ListConfigMenuDisplayTypeConfig { export interface ListConfigMenuDisplayTypeConfig {
@@ -196,9 +196,10 @@ const Config = ({
return ( return (
<TableConfig <TableConfig
enablePinColumnButtons={false} enablePinColumnButtons={false}
listKey={props.detailConfig.listKey} listKey={props.listKey}
optionsConfig={props.detailConfig.optionsConfig} optionsConfig={props.detailConfig.optionsConfig}
tableColumnsData={props.detailConfig.tableColumnsData} tableColumnsData={props.detailConfig.tableColumnsData}
tableKey="detail"
/> />
); );
} }
@@ -21,7 +21,12 @@ import {
ListConfigBooleanControl, ListConfigBooleanControl,
ListConfigTable, ListConfigTable,
} from '/@/renderer/features/shared/components/list-config-menu'; } from '/@/renderer/features/shared/components/list-config-menu';
import { ItemListSettings, useSettingsStore, useSettingsStoreActions } from '/@/renderer/store'; import {
type DataTableProps,
ItemListSettings,
useSettingsStore,
useSettingsStoreActions,
} from '/@/renderer/store';
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
import { Badge } from '/@/shared/components/badge/badge'; import { Badge } from '/@/shared/components/badge/badge';
import { Checkbox } from '/@/shared/components/checkbox/checkbox'; import { Checkbox } from '/@/shared/components/checkbox/checkbox';
@@ -53,6 +58,7 @@ interface TableConfigProps {
}; };
}; };
tableColumnsData: { label: string; value: string }[]; tableColumnsData: { label: string; value: string }[];
tableKey?: 'detail' | 'main';
} }
export const TableConfig = ({ export const TableConfig = ({
@@ -61,12 +67,28 @@ export const TableConfig = ({
listKey, listKey,
optionsConfig, optionsConfig,
tableColumnsData, tableColumnsData,
tableKey = 'main',
}: TableConfigProps) => { }: TableConfigProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings; const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
const { setList } = useSettingsStoreActions(); const { setList } = useSettingsStoreActions();
const table = tableKey === 'detail' ? (list?.detail ?? list?.table) : list?.table;
const setTableUpdate = useCallback(
(patch: Partial<DataTableProps>) => {
if (tableKey === 'detail') {
setList(listKey, { detail: patch } as Parameters<
ReturnType<typeof useSettingsStoreActions>['setList']
>[1]);
} else {
setList(listKey, { table: patch });
}
},
[listKey, setList, tableKey],
);
const advancedSettings = useMemo(() => { const advancedSettings = useMemo(() => {
const allOptions = [ const allOptions = [
{ {
@@ -159,7 +181,7 @@ export const TableConfig = ({
}) })
} }
size="sm" size="sm"
value={list.table.size} value={table.size}
w="100%" w="100%"
/> />
), ),
@@ -171,8 +193,8 @@ export const TableConfig = ({
{ {
component: ( component: (
<ListConfigBooleanControl <ListConfigBooleanControl
onChange={(e) => setList(listKey, { table: { enableHeader: e } })} onChange={(e) => setTableUpdate({ enableHeader: e })}
value={list.table.enableHeader} value={table.enableHeader}
/> />
), ),
id: 'enableHeader', id: 'enableHeader',
@@ -183,10 +205,8 @@ export const TableConfig = ({
{ {
component: ( component: (
<ListConfigBooleanControl <ListConfigBooleanControl
onChange={(e) => onChange={(e) => setTableUpdate({ enableRowHoverHighlight: e })}
setList(listKey, { table: { enableRowHoverHighlight: e } }) value={table.enableRowHoverHighlight}
}
value={list.table.enableRowHoverHighlight}
/> />
), ),
id: 'enableRowHoverHighlight', id: 'enableRowHoverHighlight',
@@ -197,10 +217,8 @@ export const TableConfig = ({
{ {
component: ( component: (
<ListConfigBooleanControl <ListConfigBooleanControl
onChange={(e) => onChange={(e) => setTableUpdate({ enableAlternateRowColors: e })}
setList(listKey, { table: { enableAlternateRowColors: e } }) value={table.enableAlternateRowColors}
}
value={list.table.enableAlternateRowColors}
/> />
), ),
id: 'enableAlternateRowColors', id: 'enableAlternateRowColors',
@@ -211,10 +229,8 @@ export const TableConfig = ({
{ {
component: ( component: (
<ListConfigBooleanControl <ListConfigBooleanControl
onChange={(e) => onChange={(e) => setTableUpdate({ enableHorizontalBorders: e })}
setList(listKey, { table: { enableHorizontalBorders: e } }) value={table.enableHorizontalBorders}
}
value={list.table.enableHorizontalBorders}
/> />
), ),
id: 'enableHorizontalBorders', id: 'enableHorizontalBorders',
@@ -225,8 +241,8 @@ export const TableConfig = ({
{ {
component: ( component: (
<ListConfigBooleanControl <ListConfigBooleanControl
onChange={(e) => setList(listKey, { table: { enableVerticalBorders: e } })} onChange={(e) => setTableUpdate({ enableVerticalBorders: e })}
value={list.table.enableVerticalBorders} value={table.enableVerticalBorders}
/> />
), ),
id: 'enableVerticalBorders', id: 'enableVerticalBorders',
@@ -237,8 +253,10 @@ export const TableConfig = ({
{ {
component: ( component: (
<ListConfigBooleanControl <ListConfigBooleanControl
onChange={(e) => setList(listKey, { table: { autoFitColumns: e } })} onChange={(e) => setTableUpdate({ autoFitColumns: e })}
value={list.table.autoFitColumns} value={
tableKey === 'main' ? (table as DataTableProps).autoFitColumns : false
}
/> />
), ),
id: 'autoFitColumns', id: 'autoFitColumns',
@@ -258,7 +276,18 @@ export const TableConfig = ({
return option; return option;
}) })
.filter((option): option is NonNullable<typeof option> => option !== null); .filter((option): option is NonNullable<typeof option> => option !== null);
}, [extraOptions, listKey, optionsConfig, setList, t, list]); }, [
t,
list.pagination,
list.itemsPerPage,
table,
tableKey,
extraOptions,
setList,
listKey,
setTableUpdate,
optionsConfig,
]);
return ( return (
<> <>
@@ -267,8 +296,8 @@ export const TableConfig = ({
<TableColumnConfig <TableColumnConfig
data={tableColumnsData} data={tableColumnsData}
enablePinColumnButtons={enablePinColumnButtons} enablePinColumnButtons={enablePinColumnButtons}
onChange={(columns) => setList(listKey, { table: { columns } })} onChange={(columns) => setTableUpdate({ columns })}
value={list.table.columns} value={table.columns}
/> />
</> </>
); );
+55 -1
View File
@@ -206,7 +206,18 @@ const ItemTableListPropsSchema = z.object({
size: z.enum(['compact', 'default', 'large']), size: z.enum(['compact', 'default', 'large']),
}); });
const ItemDetailListPropsSchema = z.object({
columns: z.array(ItemTableListColumnConfigSchema),
enableAlternateRowColors: z.boolean(),
enableHeader: z.boolean(),
enableHorizontalBorders: z.boolean(),
enableRowHoverHighlight: z.boolean(),
enableVerticalBorders: z.boolean(),
size: z.enum(['compact', 'default', 'large']),
});
const ItemListConfigSchema = z.object({ const ItemListConfigSchema = z.object({
detail: ItemDetailListPropsSchema.optional(),
display: z.nativeEnum(ListDisplayType), display: z.nativeEnum(ListDisplayType),
grid: z.object({ grid: z.object({
itemGap: z.enum(['lg', 'md', 'sm', 'xl', 'xs']), itemGap: z.enum(['lg', 'md', 'sm', 'xl', 'xs']),
@@ -790,7 +801,9 @@ export type DataGridProps = {
}; };
export type DataTableProps = z.infer<typeof ItemTableListPropsSchema>; export type DataTableProps = z.infer<typeof ItemTableListPropsSchema>;
export type ItemDetailListProps = z.infer<typeof ItemDetailListPropsSchema>;
export type ItemListSettings = { export type ItemListSettings = {
detail?: ItemDetailListProps;
display: ListDisplayType; display: ListDisplayType;
grid: DataGridProps; grid: DataGridProps;
itemsPerPage: number; itemsPerPage: number;
@@ -1163,6 +1176,30 @@ const initialState: SettingsState = {
}, },
}, },
[LibraryItem.ALBUM]: { [LibraryItem.ALBUM]: {
detail: {
columns: pickTableColumns({
autoSizeColumns: [],
columns: SONG_TABLE_COLUMNS,
columnWidths: {
[TableColumn.DURATION]: 100,
[TableColumn.TITLE]: 400,
[TableColumn.TRACK_NUMBER]: 50,
[TableColumn.USER_FAVORITE]: 60,
},
enabledColumns: [
TableColumn.TRACK_NUMBER,
TableColumn.TITLE,
TableColumn.DURATION,
TableColumn.USER_FAVORITE,
],
}),
enableAlternateRowColors: false,
enableHeader: true,
enableHorizontalBorders: false,
enableRowHoverHighlight: true,
enableVerticalBorders: false,
size: 'compact',
},
display: ListDisplayType.GRID, display: ListDisplayType.GRID,
grid: { grid: {
itemGap: 'sm', itemGap: 'sm',
@@ -1737,6 +1774,23 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
delete data.table; delete data.table;
} }
if (listState && data.detail) {
if (!listState.detail) {
const t = listState.table;
listState.detail = {
columns: t.columns,
enableAlternateRowColors: false,
enableHeader: t.enableHeader,
enableHorizontalBorders: t.enableHorizontalBorders,
enableRowHoverHighlight: t.enableRowHoverHighlight,
enableVerticalBorders: t.enableVerticalBorders,
size: t.size,
};
}
Object.assign(listState.detail, data.detail);
delete data.detail;
}
if (listState && data.grid) { if (listState && data.grid) {
Object.assign(listState.grid, data.grid); Object.assign(listState.grid, data.grid);
delete data.grid; delete data.grid;
@@ -2092,7 +2146,7 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
return persistedState; return persistedState;
}, },
name: 'store_settings', name: 'store_settings',
version: 24, version: 25,
}, },
), ),
); );