From 27e84ce518f26fc6668db98b1ee9fd7817c58cd1 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Wed, 12 Nov 2025 20:01:49 -0800 Subject: [PATCH] add autoFitColumns for item table --- .../item-table-list/item-table-list.tsx | 89 ++++++++++++++----- .../shared/components/table-config.tsx | 40 ++++++--- src/renderer/store/settings.store.ts | 86 +++++------------- 3 files changed, 116 insertions(+), 99 deletions(-) diff --git a/src/renderer/components/item-list/item-table-list/item-table-list.tsx b/src/renderer/components/item-list/item-table-list/item-table-list.tsx index 32365054c..be637efd6 100644 --- a/src/renderer/components/item-list/item-table-list/item-table-list.tsx +++ b/src/renderer/components/item-list/item-table-list/item-table-list.tsx @@ -483,6 +483,7 @@ export interface TableItemProps { } interface ItemTableListProps { + autoFitColumns?: boolean; CellComponent: JSXElementConstructor>; cellPadding?: 'lg' | 'md' | 'sm' | 'xl' | 'xs'; columns: ItemTableListColumnConfig[]; @@ -511,6 +512,7 @@ interface ItemTableListProps { } export const ItemTableList = ({ + autoFitColumns = false, CellComponent, cellPadding = 'sm', columns, @@ -539,34 +541,32 @@ export const ItemTableList = ({ const columnCount = parsedColumns.length; const playerContext = usePlayerContext(); const [centerContainerWidth, setCenterContainerWidth] = useState(0); - - useEffect(() => { - const el = rowRef.current; - if (!el) return; - - const updateWidth = () => { - setCenterContainerWidth(el.clientWidth || 0); - }; - - updateWidth(); - - const resizeObserver = new ResizeObserver(() => { - updateWidth(); - }); - - resizeObserver.observe(el); - - return () => { - resizeObserver.disconnect(); - }; - }, []); + const [totalContainerWidth, setTotalContainerWidth] = useState(0); // Compute distributed widths: unpinned columns with autoWidth will share any remaining space + // When autoSizeColumns is true, all column widths are treated as proportions and scaled to fit the container const calculatedColumnWidths = useMemo(() => { const baseWidths = parsedColumns.map((c) => c.width); + + // When autoSizeColumns is enabled, treat all widths as proportions and scale to fit container + if (autoFitColumns) { + // Calculate total reference width (sum of all base widths) + const totalReferenceWidth = baseWidths.reduce((sum, width) => sum + width, 0); + + if (totalReferenceWidth === 0 || totalContainerWidth === 0) { + return baseWidths; + } + + // Scale factor to fit all columns proportionally within the total container width + const scaleFactor = totalContainerWidth / totalReferenceWidth; + + // Apply scale factor to all columns proportionally + return baseWidths.map((width) => width * scaleFactor); + } + + // Original behavior: distribute extra space to auto-size columns const distributed = baseWidths.slice(); - // Identify unpinned columns and auto-width candidates const unpinnedIndices: number[] = []; const autoUnpinnedIndices: number[] = []; @@ -597,7 +597,7 @@ export const ItemTableList = ({ }); return distributed; - }, [parsedColumns, centerContainerWidth]); + }, [parsedColumns, centerContainerWidth, autoFitColumns, totalContainerWidth]); const pinnedLeftColumnCount = parsedColumns.filter((col) => col.pinned === 'left').length; const pinnedRightColumnCount = parsedColumns.filter((col) => col.pinned === 'right').length; @@ -616,6 +616,49 @@ export const ItemTableList = ({ const handleRef = useRef(null); const containerFocusRef = useRef(null); + useEffect(() => { + const el = rowRef.current; + if (!el) return; + + const updateWidth = () => { + setCenterContainerWidth(el.clientWidth || 0); + }; + + updateWidth(); + + const resizeObserver = new ResizeObserver(() => { + updateWidth(); + }); + + resizeObserver.observe(el); + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + // Track total container width for autoSizeColumns + useEffect(() => { + const el = containerFocusRef.current; + if (!el || !autoFitColumns) return; + + const updateWidth = () => { + setTotalContainerWidth(el.clientWidth || 0); + }; + + updateWidth(); + + const resizeObserver = new ResizeObserver(() => { + updateWidth(); + }); + + resizeObserver.observe(el); + + return () => { + resizeObserver.disconnect(); + }; + }, [autoFitColumns]); + const onScrollEndRef = useRef(onScrollEnd); useEffect(() => { onScrollEndRef.current = onScrollEnd; diff --git a/src/renderer/features/shared/components/table-config.tsx b/src/renderer/features/shared/components/table-config.tsx index 994a1dbb3..9dc8bab17 100644 --- a/src/renderer/features/shared/components/table-config.tsx +++ b/src/renderer/features/shared/components/table-config.tsx @@ -198,6 +198,16 @@ export const TableConfig = ({ extraOptions, listKey, tableColumnsData }: TableCo postProcess: 'sentenceCase', }), }, + { + component: ( + setList(listKey, { table: { autoFitColumns: e } })} + value={list.table.autoFitColumns} + /> + ), + id: 'autoFitColumns', + label: t('table.config.general.autoFitColumns', { postProcess: 'sentenceCase' }), + }, ...(extraOptions || []), ]; @@ -244,7 +254,8 @@ const TableColumnConfig = ({ const handleChangeEnabled = useCallback( (item: ItemTableListColumnConfig, checked: boolean) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); const newValues = [...value]; newValues[index] = { ...newValues[index], isEnabled: checked }; @@ -255,7 +266,8 @@ const TableColumnConfig = ({ const handleMoveUp = useCallback( (item: ItemTableListColumnConfig) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); if (index === 0) return; const newValues = [...value]; @@ -267,7 +279,8 @@ const TableColumnConfig = ({ const handleMoveDown = useCallback( (item: ItemTableListColumnConfig) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); if (index === value.length - 1) return; const newValues = [...value]; @@ -279,7 +292,8 @@ const TableColumnConfig = ({ const handlePinToLeft = useCallback( (item: ItemTableListColumnConfig) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); const newValues = [...value]; @@ -299,7 +313,8 @@ const TableColumnConfig = ({ const handlePinToRight = useCallback( (item: ItemTableListColumnConfig) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); const newValues = [...value]; @@ -319,7 +334,8 @@ const TableColumnConfig = ({ const handleAlignLeft = useCallback( (item: ItemTableListColumnConfig) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); const newValues = [...value]; newValues[index] = { ...newValues[index], align: 'start' }; @@ -330,7 +346,8 @@ const TableColumnConfig = ({ const handleAlignCenter = useCallback( (item: ItemTableListColumnConfig) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); const newValues = [...value]; newValues[index] = { ...newValues[index], align: 'center' }; @@ -341,7 +358,8 @@ const TableColumnConfig = ({ const handleAlignRight = useCallback( (item: ItemTableListColumnConfig) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); const newValues = [...value]; newValues[index] = { ...newValues[index], align: 'end' }; @@ -352,7 +370,8 @@ const TableColumnConfig = ({ const handleAutoSize = useCallback( (item: ItemTableListColumnConfig, checked: boolean) => { - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); const newValues = [...value]; newValues[index] = { ...newValues[index], autoSize: checked }; @@ -375,7 +394,8 @@ const TableColumnConfig = ({ number = 2000; } - const value = useSettingsStore.getState().lists[listKey].table.columns; + const value = useSettingsStore.getState().lists[listKey]?.table.columns; + if (!value) return; const index = value.findIndex((v) => v.id === item.id); const newValues = [...value]; newValues[index] = { ...newValues[index], width: number }; diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 65c8539e4..a408636ed 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -10,6 +10,7 @@ import i18n from '/@/i18n/i18n'; import { ALBUM_TABLE_COLUMNS, PLAYLIST_SONG_TABLE_COLUMNS, + PLAYLIST_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'; @@ -122,6 +123,7 @@ const ItemTableListColumnConfigSchema = z.object({ }); const ItemTableListPropsSchema = z.object({ + autoFitColumns: z.boolean(), columns: z.array(ItemTableListColumnConfigSchema), enableAlternateRowColors: z.boolean(), enableHorizontalBorders: z.boolean(), @@ -650,6 +652,7 @@ const initialState: SettingsState = { itemsPerPage: 100, pagination: ListPaginationType.INFINITE, table: { + autoFitColumns: false, columns: SONG_TABLE_COLUMNS.map((column) => ({ align: column.align, autoSize: column.autoSize, @@ -675,6 +678,7 @@ const initialState: SettingsState = { itemsPerPage: 100, pagination: ListPaginationType.INFINITE, table: { + autoFitColumns: false, columns: ALBUM_TABLE_COLUMNS.map((column) => ({ align: column.align, autoSize: column.autoSize, @@ -700,6 +704,7 @@ const initialState: SettingsState = { itemsPerPage: 100, pagination: ListPaginationType.INFINITE, table: { + autoFitColumns: false, columns: ALBUMARTIST_TABLE_COLUMNS.map((column) => ({ align: 'start' as const, autoSize: false, @@ -725,6 +730,7 @@ const initialState: SettingsState = { itemsPerPage: 100, pagination: ListPaginationType.INFINITE, table: { + autoFitColumns: false, columns: ALBUMARTIST_TABLE_COLUMNS.map((column) => ({ align: 'start' as const, autoSize: false, @@ -750,71 +756,15 @@ const initialState: SettingsState = { 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, - }, - ], + autoFitColumns: false, + columns: PLAYLIST_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, @@ -832,6 +782,7 @@ const initialState: SettingsState = { itemsPerPage: 100, pagination: ListPaginationType.INFINITE, table: { + autoFitColumns: false, columns: PLAYLIST_SONG_TABLE_COLUMNS.map((column) => ({ align: column.align, autoSize: column.autoSize, @@ -857,6 +808,7 @@ const initialState: SettingsState = { itemsPerPage: 100, pagination: ListPaginationType.INFINITE, table: { + autoFitColumns: false, columns: SONG_TABLE_COLUMNS.map((column) => ({ align: column.align, autoSize: column.autoSize, @@ -882,6 +834,7 @@ const initialState: SettingsState = { itemsPerPage: 100, pagination: ListPaginationType.INFINITE, table: { + autoFitColumns: false, columns: SONG_TABLE_COLUMNS.map((column) => ({ align: column.align, autoSize: column.autoSize, @@ -897,7 +850,7 @@ const initialState: SettingsState = { size: 'default', }, }, - sideQueue: { + ['sideQueue']: { display: ListDisplayType.TABLE, grid: { itemGap: 'md', @@ -907,6 +860,7 @@ const initialState: SettingsState = { itemsPerPage: 100, pagination: ListPaginationType.INFINITE, table: { + autoFitColumns: true, columns: SONG_TABLE_COLUMNS.map((column) => ({ align: column.align, autoSize: column.autoSize,