add autoFitColumns for item table

This commit is contained in:
jeffvli
2025-11-12 20:01:49 -08:00
parent 9dbe3d8d0f
commit 27e84ce518
3 changed files with 116 additions and 99 deletions
@@ -483,6 +483,7 @@ export interface TableItemProps {
} }
interface ItemTableListProps { interface ItemTableListProps {
autoFitColumns?: boolean;
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>; CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
cellPadding?: 'lg' | 'md' | 'sm' | 'xl' | 'xs'; cellPadding?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
columns: ItemTableListColumnConfig[]; columns: ItemTableListColumnConfig[];
@@ -511,6 +512,7 @@ interface ItemTableListProps {
} }
export const ItemTableList = ({ export const ItemTableList = ({
autoFitColumns = false,
CellComponent, CellComponent,
cellPadding = 'sm', cellPadding = 'sm',
columns, columns,
@@ -539,34 +541,32 @@ export const ItemTableList = ({
const columnCount = parsedColumns.length; const columnCount = parsedColumns.length;
const playerContext = usePlayerContext(); const playerContext = usePlayerContext();
const [centerContainerWidth, setCenterContainerWidth] = useState(0); const [centerContainerWidth, setCenterContainerWidth] = useState(0);
const [totalContainerWidth, setTotalContainerWidth] = 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();
};
}, []);
// Compute distributed widths: unpinned columns with autoWidth will share any remaining space // 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 calculatedColumnWidths = useMemo(() => {
const baseWidths = parsedColumns.map((c) => c.width); 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(); const distributed = baseWidths.slice();
// Identify unpinned columns and auto-width candidates
const unpinnedIndices: number[] = []; const unpinnedIndices: number[] = [];
const autoUnpinnedIndices: number[] = []; const autoUnpinnedIndices: number[] = [];
@@ -597,7 +597,7 @@ export const ItemTableList = ({
}); });
return distributed; return distributed;
}, [parsedColumns, centerContainerWidth]); }, [parsedColumns, centerContainerWidth, autoFitColumns, totalContainerWidth]);
const pinnedLeftColumnCount = parsedColumns.filter((col) => col.pinned === 'left').length; const pinnedLeftColumnCount = parsedColumns.filter((col) => col.pinned === 'left').length;
const pinnedRightColumnCount = parsedColumns.filter((col) => col.pinned === 'right').length; const pinnedRightColumnCount = parsedColumns.filter((col) => col.pinned === 'right').length;
@@ -616,6 +616,49 @@ export const ItemTableList = ({
const handleRef = useRef<ItemListHandle | null>(null); const handleRef = useRef<ItemListHandle | null>(null);
const containerFocusRef = useRef<HTMLDivElement | null>(null); const containerFocusRef = useRef<HTMLDivElement | null>(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<ItemTableListProps['onScrollEnd']>(onScrollEnd); const onScrollEndRef = useRef<ItemTableListProps['onScrollEnd']>(onScrollEnd);
useEffect(() => { useEffect(() => {
onScrollEndRef.current = onScrollEnd; onScrollEndRef.current = onScrollEnd;
@@ -198,6 +198,16 @@ export const TableConfig = ({ extraOptions, listKey, tableColumnsData }: TableCo
postProcess: 'sentenceCase', postProcess: 'sentenceCase',
}), }),
}, },
{
component: (
<ListConfigBooleanControl
onChange={(e) => setList(listKey, { table: { autoFitColumns: e } })}
value={list.table.autoFitColumns}
/>
),
id: 'autoFitColumns',
label: t('table.config.general.autoFitColumns', { postProcess: 'sentenceCase' }),
},
...(extraOptions || []), ...(extraOptions || []),
]; ];
@@ -244,7 +254,8 @@ const TableColumnConfig = ({
const handleChangeEnabled = useCallback( const handleChangeEnabled = useCallback(
(item: ItemTableListColumnConfig, checked: boolean) => { (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 index = value.findIndex((v) => v.id === item.id);
const newValues = [...value]; const newValues = [...value];
newValues[index] = { ...newValues[index], isEnabled: checked }; newValues[index] = { ...newValues[index], isEnabled: checked };
@@ -255,7 +266,8 @@ const TableColumnConfig = ({
const handleMoveUp = useCallback( const handleMoveUp = useCallback(
(item: ItemTableListColumnConfig) => { (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 index = value.findIndex((v) => v.id === item.id);
if (index === 0) return; if (index === 0) return;
const newValues = [...value]; const newValues = [...value];
@@ -267,7 +279,8 @@ const TableColumnConfig = ({
const handleMoveDown = useCallback( const handleMoveDown = useCallback(
(item: ItemTableListColumnConfig) => { (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 index = value.findIndex((v) => v.id === item.id);
if (index === value.length - 1) return; if (index === value.length - 1) return;
const newValues = [...value]; const newValues = [...value];
@@ -279,7 +292,8 @@ const TableColumnConfig = ({
const handlePinToLeft = useCallback( const handlePinToLeft = useCallback(
(item: ItemTableListColumnConfig) => { (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 index = value.findIndex((v) => v.id === item.id);
const newValues = [...value]; const newValues = [...value];
@@ -299,7 +313,8 @@ const TableColumnConfig = ({
const handlePinToRight = useCallback( const handlePinToRight = useCallback(
(item: ItemTableListColumnConfig) => { (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 index = value.findIndex((v) => v.id === item.id);
const newValues = [...value]; const newValues = [...value];
@@ -319,7 +334,8 @@ const TableColumnConfig = ({
const handleAlignLeft = useCallback( const handleAlignLeft = useCallback(
(item: ItemTableListColumnConfig) => { (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 index = value.findIndex((v) => v.id === item.id);
const newValues = [...value]; const newValues = [...value];
newValues[index] = { ...newValues[index], align: 'start' }; newValues[index] = { ...newValues[index], align: 'start' };
@@ -330,7 +346,8 @@ const TableColumnConfig = ({
const handleAlignCenter = useCallback( const handleAlignCenter = useCallback(
(item: ItemTableListColumnConfig) => { (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 index = value.findIndex((v) => v.id === item.id);
const newValues = [...value]; const newValues = [...value];
newValues[index] = { ...newValues[index], align: 'center' }; newValues[index] = { ...newValues[index], align: 'center' };
@@ -341,7 +358,8 @@ const TableColumnConfig = ({
const handleAlignRight = useCallback( const handleAlignRight = useCallback(
(item: ItemTableListColumnConfig) => { (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 index = value.findIndex((v) => v.id === item.id);
const newValues = [...value]; const newValues = [...value];
newValues[index] = { ...newValues[index], align: 'end' }; newValues[index] = { ...newValues[index], align: 'end' };
@@ -352,7 +370,8 @@ const TableColumnConfig = ({
const handleAutoSize = useCallback( const handleAutoSize = useCallback(
(item: ItemTableListColumnConfig, checked: boolean) => { (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 index = value.findIndex((v) => v.id === item.id);
const newValues = [...value]; const newValues = [...value];
newValues[index] = { ...newValues[index], autoSize: checked }; newValues[index] = { ...newValues[index], autoSize: checked };
@@ -375,7 +394,8 @@ const TableColumnConfig = ({
number = 2000; 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 index = value.findIndex((v) => v.id === item.id);
const newValues = [...value]; const newValues = [...value];
newValues[index] = { ...newValues[index], width: number }; newValues[index] = { ...newValues[index], width: number };
+20 -66
View File
@@ -10,6 +10,7 @@ import i18n from '/@/i18n/i18n';
import { import {
ALBUM_TABLE_COLUMNS, ALBUM_TABLE_COLUMNS,
PLAYLIST_SONG_TABLE_COLUMNS, PLAYLIST_SONG_TABLE_COLUMNS,
PLAYLIST_TABLE_COLUMNS,
SONG_TABLE_COLUMNS, SONG_TABLE_COLUMNS,
} from '/@/renderer/components/item-list/item-table-list/default-columns'; } from '/@/renderer/components/item-list/item-table-list/default-columns';
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table/table-config-dropdown'; import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table/table-config-dropdown';
@@ -122,6 +123,7 @@ const ItemTableListColumnConfigSchema = z.object({
}); });
const ItemTableListPropsSchema = z.object({ const ItemTableListPropsSchema = z.object({
autoFitColumns: z.boolean(),
columns: z.array(ItemTableListColumnConfigSchema), columns: z.array(ItemTableListColumnConfigSchema),
enableAlternateRowColors: z.boolean(), enableAlternateRowColors: z.boolean(),
enableHorizontalBorders: z.boolean(), enableHorizontalBorders: z.boolean(),
@@ -650,6 +652,7 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
autoFitColumns: false,
columns: SONG_TABLE_COLUMNS.map((column) => ({ columns: SONG_TABLE_COLUMNS.map((column) => ({
align: column.align, align: column.align,
autoSize: column.autoSize, autoSize: column.autoSize,
@@ -675,6 +678,7 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
autoFitColumns: false,
columns: ALBUM_TABLE_COLUMNS.map((column) => ({ columns: ALBUM_TABLE_COLUMNS.map((column) => ({
align: column.align, align: column.align,
autoSize: column.autoSize, autoSize: column.autoSize,
@@ -700,6 +704,7 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
autoFitColumns: false,
columns: ALBUMARTIST_TABLE_COLUMNS.map((column) => ({ columns: ALBUMARTIST_TABLE_COLUMNS.map((column) => ({
align: 'start' as const, align: 'start' as const,
autoSize: false, autoSize: false,
@@ -725,6 +730,7 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
autoFitColumns: false,
columns: ALBUMARTIST_TABLE_COLUMNS.map((column) => ({ columns: ALBUMARTIST_TABLE_COLUMNS.map((column) => ({
align: 'start' as const, align: 'start' as const,
autoSize: false, autoSize: false,
@@ -750,71 +756,15 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
columns: [ autoFitColumns: false,
{ columns: PLAYLIST_TABLE_COLUMNS.map((column) => ({
align: 'center', align: column.align,
autoSize: false, autoSize: column.autoSize,
id: TableColumn.ROW_INDEX, id: column.value,
isEnabled: true, isEnabled: column.isEnabled,
pinned: 'left', pinned: column.pinned,
width: 80, width: column.width,
}, })),
{
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, enableAlternateRowColors: true,
enableHorizontalBorders: true, enableHorizontalBorders: true,
enableRowHoverHighlight: true, enableRowHoverHighlight: true,
@@ -832,6 +782,7 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
autoFitColumns: false,
columns: PLAYLIST_SONG_TABLE_COLUMNS.map((column) => ({ columns: PLAYLIST_SONG_TABLE_COLUMNS.map((column) => ({
align: column.align, align: column.align,
autoSize: column.autoSize, autoSize: column.autoSize,
@@ -857,6 +808,7 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
autoFitColumns: false,
columns: SONG_TABLE_COLUMNS.map((column) => ({ columns: SONG_TABLE_COLUMNS.map((column) => ({
align: column.align, align: column.align,
autoSize: column.autoSize, autoSize: column.autoSize,
@@ -882,6 +834,7 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
autoFitColumns: false,
columns: SONG_TABLE_COLUMNS.map((column) => ({ columns: SONG_TABLE_COLUMNS.map((column) => ({
align: column.align, align: column.align,
autoSize: column.autoSize, autoSize: column.autoSize,
@@ -897,7 +850,7 @@ const initialState: SettingsState = {
size: 'default', size: 'default',
}, },
}, },
sideQueue: { ['sideQueue']: {
display: ListDisplayType.TABLE, display: ListDisplayType.TABLE,
grid: { grid: {
itemGap: 'md', itemGap: 'md',
@@ -907,6 +860,7 @@ const initialState: SettingsState = {
itemsPerPage: 100, itemsPerPage: 100,
pagination: ListPaginationType.INFINITE, pagination: ListPaginationType.INFINITE,
table: { table: {
autoFitColumns: true,
columns: SONG_TABLE_COLUMNS.map((column) => ({ columns: SONG_TABLE_COLUMNS.map((column) => ({
align: column.align, align: column.align,
autoSize: column.autoSize, autoSize: column.autoSize,