mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-02 00:29:56 +02:00
feat: album group has a config and can set the image size (#2153)
* Created a new album group configuration which includes (for now) an option to set the image size of the album group artwork.
This commit is contained in:
@@ -1218,6 +1218,8 @@
|
||||
"config": {
|
||||
"general": {
|
||||
"advancedSettings": "Advanced settings",
|
||||
"albumGroupConfig": "Album Group configuration",
|
||||
"albumImageSize": "Album image size",
|
||||
"autoFitColumns": "Auto fit columns",
|
||||
"autosize": "Autosize",
|
||||
"moveUp": "Move up",
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
LONG_PRESS_PLAY_BEHAVIOR,
|
||||
PlayTooltip,
|
||||
} from '/@/renderer/features/shared/components/play-button-group';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store';
|
||||
import { useAlbumGroupImageSize, usePlayButtonBehavior } from '/@/renderer/store';
|
||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
@@ -29,12 +29,33 @@ export const AlbumGroupHeader = ({
|
||||
}: AlbumGroupHeaderProps): ReactElement => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
const albumImageSize = useAlbumGroupImageSize();
|
||||
const rowHeight = {
|
||||
compact: TableItemSize.COMPACT,
|
||||
large: TableItemSize.LARGE,
|
||||
normal: TableItemSize.DEFAULT,
|
||||
}[size];
|
||||
const infoHeight = groupRowCount !== undefined ? groupRowCount * rowHeight : undefined;
|
||||
// The album group spans the combined row height, but when the image is
|
||||
// enlarged the group's last row is grown so the total reaches the img size.
|
||||
const infoHeight =
|
||||
groupRowCount !== undefined
|
||||
? albumImageSize > 0
|
||||
? Math.max(albumImageSize, groupRowCount * rowHeight)
|
||||
: groupRowCount * rowHeight
|
||||
: undefined;
|
||||
|
||||
const imageContainerStyle =
|
||||
albumImageSize > 0
|
||||
? {
|
||||
aspectRatio: 'auto',
|
||||
height: `${albumImageSize}px`,
|
||||
paddingBottom: 'var(--theme-spacing-xs)',
|
||||
paddingTop: 'var(--theme-spacing-xs)',
|
||||
position: 'relative' as const,
|
||||
width: `${albumImageSize}px`,
|
||||
zIndex: 1,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
@@ -42,6 +63,7 @@ export const AlbumGroupHeader = ({
|
||||
className={styles.imageContainer}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={imageContainerStyle}
|
||||
>
|
||||
<ItemImage
|
||||
className={imageColumnStyles.compactImage}
|
||||
|
||||
@@ -64,6 +64,12 @@ export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
|
||||
...(needsBorder
|
||||
? { borderBottom: '1px solid var(--theme-colors-border)' }
|
||||
: {}),
|
||||
// When the cover is enlarged it overflows down from the
|
||||
// group's first row into these cells; let hover/click pass
|
||||
// through to reach it.
|
||||
...((props.albumGroupImageSize ?? 0) > 0
|
||||
? { pointerEvents: 'none' as const }
|
||||
: {}),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -31,6 +31,11 @@
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.container.no-vertical-padding {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.container.center {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
@@ -57,7 +57,10 @@ import { TitleCombinedColumn } from '/@/renderer/components/item-list/item-table
|
||||
import { TrackNumberColumn } from '/@/renderer/components/item-list/item-table-list/columns/track-number-column';
|
||||
import { YearColumn } from '/@/renderer/components/item-list/item-table-list/columns/year-column';
|
||||
import { useItemDragDropState } from '/@/renderer/components/item-list/item-table-list/hooks/use-item-drag-drop-state';
|
||||
import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
||||
import {
|
||||
TableItemProps,
|
||||
TableItemSize,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
||||
import { useItemTableListColumnResizeLive } from '/@/renderer/components/item-list/item-table-list/item-table-list-context';
|
||||
import { ItemControls, ItemListItem } from '/@/renderer/components/item-list/types';
|
||||
import { Flex } from '/@/shared/components/flex/flex';
|
||||
@@ -381,6 +384,36 @@ export const ItemTableListColumn = memo(ItemTableListColumnBase, (prevProps, nex
|
||||
|
||||
const NonMutedColumns = [TableColumn.TITLE, TableColumn.TITLE_ARTIST, TableColumn.TITLE_COMBINED];
|
||||
|
||||
// Counts how many consecutive rows belong to the same album group as `rowIndex`.
|
||||
export function getAlbumGroupRowCount(
|
||||
rowIndex: number,
|
||||
getRowItem: ((index: number) => unknown) | undefined,
|
||||
enableHeader: boolean | undefined,
|
||||
dataLength: number,
|
||||
): number {
|
||||
const item = getRowItem?.(rowIndex) as null | undefined | { album?: string };
|
||||
if (!item?.album) return 1;
|
||||
|
||||
const firstDataRow = enableHeader ? 1 : 0;
|
||||
const maxRow = enableHeader ? dataLength + 1 : dataLength;
|
||||
|
||||
let start = rowIndex;
|
||||
while (start > firstDataRow) {
|
||||
const prevItem = getRowItem?.(start - 1) as null | undefined | { album?: string };
|
||||
if (!prevItem || prevItem.album !== item.album) break;
|
||||
start--;
|
||||
}
|
||||
|
||||
let end = rowIndex;
|
||||
while (end + 1 < maxRow) {
|
||||
const nextItem = getRowItem?.(end + 1) as null | undefined | { album?: string };
|
||||
if (!nextItem || nextItem.album !== item.album) break;
|
||||
end++;
|
||||
}
|
||||
|
||||
return end - start + 1;
|
||||
}
|
||||
|
||||
export function isAlbumGroupingActive(columns: { id: string; isEnabled?: boolean }[]): boolean {
|
||||
return columns.some((col) => col.id === TableColumn.ALBUM_GROUP && col.isEnabled);
|
||||
}
|
||||
@@ -402,6 +435,106 @@ export function isLastInAlbumGroup(
|
||||
return !nextItem || nextItem.album !== item.album;
|
||||
}
|
||||
|
||||
function baseRowHeightForSize(size: ItemTableListColumn['size']): number {
|
||||
if (size === 'compact') return TableItemSize.COMPACT;
|
||||
if (size === 'large') return TableItemSize.LARGE;
|
||||
return TableItemSize.DEFAULT;
|
||||
}
|
||||
|
||||
// Wraps a clamped cell with the spacer that fills the reserved (grown) height
|
||||
// below it. The spacer carries the group's bottom/right borders so they align
|
||||
// across all columns.
|
||||
function ClampedCell({
|
||||
cell,
|
||||
clampHeight,
|
||||
outerStyle,
|
||||
showHorizontalBorder,
|
||||
showVerticalBorder,
|
||||
}: {
|
||||
cell: ReactElement;
|
||||
clampHeight: null | number;
|
||||
outerStyle?: CSSProperties;
|
||||
showHorizontalBorder: boolean;
|
||||
showVerticalBorder: boolean;
|
||||
}): ReactElement {
|
||||
const grownHeight = typeof outerStyle?.height === 'number' ? outerStyle.height : 0;
|
||||
const spacerHeight = clampHeight !== null ? grownHeight - clampHeight : 0;
|
||||
|
||||
if (clampHeight === null || spacerHeight <= 0) return cell;
|
||||
|
||||
return (
|
||||
<div style={outerStyle}>
|
||||
{cell}
|
||||
<div
|
||||
aria-hidden
|
||||
style={{
|
||||
borderBottom: showHorizontalBorder
|
||||
? '1px solid var(--theme-colors-border)'
|
||||
: undefined,
|
||||
borderRight: showVerticalBorder
|
||||
? '1px solid var(--theme-colors-border)'
|
||||
: undefined,
|
||||
height: spacerHeight,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// When an enlarged album image extends past the album group's combined row
|
||||
// height, the last row of the group is grown (in getRowHeight) to reserve the
|
||||
// leftover space. This returns the standard (un-grown) height to clamp that
|
||||
// row's non-album cells to, so the track content + hover/selection stay at
|
||||
// standard height and the reserved space below is left empty (uniform
|
||||
// background) for the overflowing album image.
|
||||
function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | number {
|
||||
const albumImageSize = props.albumGroupImageSize ?? 0;
|
||||
|
||||
if (albumImageSize <= 0) return null;
|
||||
if (props.type === TableColumn.ALBUM_GROUP) return null;
|
||||
if (!isAlbumGroupingActive(props.columns)) return null;
|
||||
|
||||
const isDataRow = props.enableHeader ? props.rowIndex > 0 : true;
|
||||
if (!isDataRow) return null;
|
||||
|
||||
const item = props.getRowItem?.(props.rowIndex) as null | undefined | { album?: string };
|
||||
if (!item?.album) return null;
|
||||
|
||||
if (
|
||||
!isLastInAlbumGroup(props.rowIndex, props.getRowItem, props.enableHeader, props.data.length)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const baseHeight = baseRowHeightForSize(props.size);
|
||||
const groupRowCount = getAlbumGroupRowCount(
|
||||
props.rowIndex,
|
||||
props.getRowItem,
|
||||
props.enableHeader,
|
||||
props.data.length,
|
||||
);
|
||||
|
||||
// Only clamp when the row was actually grown to fit the image.
|
||||
if (albumImageSize <= groupRowCount * baseHeight) return null;
|
||||
|
||||
return baseHeight;
|
||||
}
|
||||
|
||||
function showHorizontalBorderFor(props: ItemTableListInnerColumn, isLastRow: boolean): boolean {
|
||||
if (!props.enableHorizontalBorders || !props.enableHeader || props.rowIndex <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (isAlbumGroupingActive(props.columns)) {
|
||||
return isLastInAlbumGroup(
|
||||
props.rowIndex,
|
||||
props.getRowItem,
|
||||
!!props.enableHeader,
|
||||
props.data.length,
|
||||
);
|
||||
}
|
||||
return props.rowIndex === 1 || !isLastRow;
|
||||
}
|
||||
|
||||
export const TableColumnTextContainer = (
|
||||
props: ItemTableListColumn & {
|
||||
children: React.ReactNode;
|
||||
@@ -425,6 +558,7 @@ export const TableColumnTextContainer = (
|
||||
? props.internalState.extractRowId(item)
|
||||
: undefined;
|
||||
const isSelected = useItemSelectionState(props.internalState, itemRowId || undefined);
|
||||
const clampHeight = getAlbumGroupClampHeight(props);
|
||||
|
||||
const isDragging = props.isDragging ?? false;
|
||||
const mergedRef = useMergedRef(containerRef, props.dragRef ?? null);
|
||||
@@ -507,7 +641,10 @@ export const TableColumnTextContainer = (
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
const showHorizontalBorder = showHorizontalBorderFor(props, isLastRow);
|
||||
const showVerticalBorder = !!props.enableVerticalBorders && !isLastColumn;
|
||||
|
||||
const cell = (
|
||||
<div
|
||||
className={clsx(styles.container, props.containerClassName, {
|
||||
[styles.alternateRowEven]:
|
||||
@@ -529,25 +666,16 @@ export const TableColumnTextContainer = (
|
||||
[styles.right]: props.columns[props.columnIndex].align === 'end',
|
||||
[styles.rowHoverHighlightEnabled]: isDataRow && props.enableRowHoverHighlight,
|
||||
[styles.rowSelected]: isDataRow && isSelected,
|
||||
[styles.withHorizontalBorder]:
|
||||
props.enableHorizontalBorders &&
|
||||
props.enableHeader &&
|
||||
props.rowIndex > 0 &&
|
||||
(isAlbumGroupingActive(props.columns)
|
||||
? isLastInAlbumGroup(
|
||||
props.rowIndex,
|
||||
props.getRowItem,
|
||||
!!props.enableHeader,
|
||||
props.data.length,
|
||||
)
|
||||
: props.rowIndex === 1 || !isLastRow),
|
||||
[styles.withVerticalBorder]: props.enableVerticalBorders && !isLastColumn,
|
||||
// When clamped, the bottom border is drawn on the spacer below
|
||||
// instead.
|
||||
[styles.withHorizontalBorder]: showHorizontalBorder && clampHeight === null,
|
||||
[styles.withVerticalBorder]: showVerticalBorder,
|
||||
})}
|
||||
data-row-index={isDataRow ? `${props.tableId}-${props.rowIndex}` : undefined}
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
ref={mergedRef}
|
||||
style={props.style}
|
||||
style={clampHeight !== null ? { height: clampHeight } : props.style}
|
||||
>
|
||||
<Text
|
||||
className={clsx(styles.content, props.className, {
|
||||
@@ -561,6 +689,16 @@ export const TableColumnTextContainer = (
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<ClampedCell
|
||||
cell={cell}
|
||||
clampHeight={clampHeight}
|
||||
outerStyle={props.style}
|
||||
showHorizontalBorder={showHorizontalBorder}
|
||||
showVerticalBorder={showVerticalBorder}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableColumnContainer = (
|
||||
@@ -586,6 +724,7 @@ export const TableColumnContainer = (
|
||||
? props.internalState.extractRowId(item)
|
||||
: undefined;
|
||||
const isSelected = useItemSelectionState(props.internalState, itemRowId || undefined);
|
||||
const clampHeight = getAlbumGroupClampHeight(props);
|
||||
|
||||
const isDragging = props.isDragging ?? false;
|
||||
const mergedRef = useMergedRef(containerRef, props.dragRef ?? null);
|
||||
@@ -668,7 +807,10 @@ export const TableColumnContainer = (
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
const showHorizontalBorder = showHorizontalBorderFor(props, isLastRow);
|
||||
const showVerticalBorder = !!props.enableVerticalBorders && !isLastColumn;
|
||||
|
||||
const cell = (
|
||||
<div
|
||||
className={clsx(styles.container, props.className, {
|
||||
[styles.alternateRowEven]:
|
||||
@@ -682,6 +824,8 @@ export const TableColumnContainer = (
|
||||
[styles.large]: props.size === 'large',
|
||||
[styles.left]: props.columns[props.columnIndex].align === 'start',
|
||||
[styles.noHorizontalPadding]: isNoHorizontalPaddingColumn(props.type),
|
||||
[styles.noVerticalPadding]:
|
||||
props.type === TableColumn.ALBUM_GROUP && (props.albumGroupImageSize ?? 0) > 0,
|
||||
[styles.paddingLg]: props.cellPadding === 'lg',
|
||||
[styles.paddingMd]: props.cellPadding === 'md',
|
||||
[styles.paddingSm]: props.cellPadding === 'sm',
|
||||
@@ -694,29 +838,33 @@ export const TableColumnContainer = (
|
||||
props.type !== TableColumn.ALBUM_GROUP,
|
||||
[styles.rowSelected]:
|
||||
isDataRow && isSelected && props.type !== TableColumn.ALBUM_GROUP,
|
||||
[styles.withHorizontalBorder]:
|
||||
props.enableHorizontalBorders &&
|
||||
props.enableHeader &&
|
||||
props.rowIndex > 0 &&
|
||||
(isAlbumGroupingActive(props.columns)
|
||||
? isLastInAlbumGroup(
|
||||
props.rowIndex,
|
||||
props.getRowItem,
|
||||
!!props.enableHeader,
|
||||
props.data.length,
|
||||
)
|
||||
: props.rowIndex === 1 || !isLastRow),
|
||||
[styles.withVerticalBorder]: props.enableVerticalBorders && !isLastColumn,
|
||||
// When clamped, the bottom border is drawn on the spacer below instead.
|
||||
[styles.withHorizontalBorder]: showHorizontalBorder && clampHeight === null,
|
||||
[styles.withVerticalBorder]: showVerticalBorder,
|
||||
})}
|
||||
data-row-index={isDataRow ? `${props.tableId}-${props.rowIndex}` : undefined}
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
ref={mergedRef}
|
||||
style={{ ...props.containerStyle, ...props.style }}
|
||||
style={
|
||||
clampHeight !== null
|
||||
? { ...props.containerStyle, height: clampHeight }
|
||||
: { ...props.containerStyle, ...props.style }
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<ClampedCell
|
||||
cell={cell}
|
||||
clampHeight={clampHeight}
|
||||
outerStyle={props.style}
|
||||
showHorizontalBorder={showHorizontalBorder}
|
||||
showVerticalBorder={showVerticalBorder}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface ColumnResizeHandleProps {
|
||||
|
||||
@@ -44,7 +44,11 @@ import { useTableKeyboardNavigation } from '/@/renderer/components/item-list/ite
|
||||
import { useTablePaneSync } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-pane-sync';
|
||||
import { useTableRowModel } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-row-model';
|
||||
import { useTableScrollToIndex } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index';
|
||||
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import {
|
||||
getAlbumGroupRowCount,
|
||||
isLastInAlbumGroup,
|
||||
ItemTableListColumn,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import {
|
||||
ItemTableListColumnResizeLiveProvider,
|
||||
type ItemTableListConfig,
|
||||
@@ -66,7 +70,7 @@ import {
|
||||
ItemTableListColumnConfig,
|
||||
} from '/@/renderer/components/item-list/types';
|
||||
import { PlayerContext, usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { usePlayerStore } from '/@/renderer/store';
|
||||
import { useAlbumGroupImageSize, usePlayerStore } from '/@/renderer/store';
|
||||
import { animationProps } from '/@/shared/components/animations/animation-props';
|
||||
import { useFocusWithin } from '/@/shared/hooks/use-focus-within';
|
||||
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
|
||||
@@ -215,6 +219,7 @@ const VirtualizedTableGrid = ({
|
||||
totalRowCount,
|
||||
}: VirtualizedTableGridProps) => {
|
||||
const { enableHeader, enableRowHoverHighlight, getRowHeight, groups } = tableConfig;
|
||||
const albumGroupImageSize = useAlbumGroupImageSize();
|
||||
const hoverDelegateRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useRowInteractionDelegate({
|
||||
@@ -403,6 +408,7 @@ const VirtualizedTableGrid = ({
|
||||
|
||||
const itemProps: TableItemProps = useMemo(
|
||||
() => ({
|
||||
albumGroupImageSize,
|
||||
cellPadding: tableConfig.cellPadding,
|
||||
columns: tableConfig.columns,
|
||||
controls: tableConfig.controls,
|
||||
@@ -427,7 +433,7 @@ const VirtualizedTableGrid = ({
|
||||
tableId: tableConfig.tableId,
|
||||
...gridOnlyProps,
|
||||
}),
|
||||
[gridOnlyProps, tableConfig],
|
||||
[albumGroupImageSize, gridOnlyProps, tableConfig],
|
||||
);
|
||||
|
||||
const pinnedLeftGridMinWidthPx = useMemo(() => {
|
||||
@@ -760,6 +766,7 @@ export interface TableGroupHeader {
|
||||
|
||||
export interface TableItemProps {
|
||||
adjustedRowIndexMap?: Map<number, number>;
|
||||
albumGroupImageSize?: number;
|
||||
calculatedColumnWidths?: number[];
|
||||
cellPadding?: ItemTableListProps['cellPadding'];
|
||||
columns: ItemTableListColumnConfig[];
|
||||
@@ -1275,6 +1282,7 @@ const BaseItemTableList = ({
|
||||
}: ItemTableListProps) => {
|
||||
const { playlistId: routePlaylistId } = useParams() as { playlistId?: string };
|
||||
const tableId = useId();
|
||||
const albumGroupImageSize = useAlbumGroupImageSize();
|
||||
const baseItemCount = itemCount ?? data.length;
|
||||
const totalItemCount = enableHeader ? baseItemCount + 1 : baseItemCount;
|
||||
const [centerContainerWidth, setCenterContainerWidth] = useState(0);
|
||||
@@ -1434,9 +1442,38 @@ const BaseItemTableList = ({
|
||||
return headerHeight;
|
||||
}
|
||||
|
||||
// When an album image is enlarged beyond the album group's combined
|
||||
// row height, grow the group's LAST row to reserve the leftover
|
||||
// space (so the following album isn't clipped). Other rows keep
|
||||
// their standard height.
|
||||
if (
|
||||
albumGroupImageSize > baseHeight &&
|
||||
cellProps?.hasAlbumGroupColumn &&
|
||||
isLastInAlbumGroup(
|
||||
index,
|
||||
cellProps.getRowItem,
|
||||
cellProps.enableHeader,
|
||||
cellProps.data.length,
|
||||
)
|
||||
) {
|
||||
const item = cellProps.getRowItem?.(index) as null | undefined | { album?: string };
|
||||
if (item?.album) {
|
||||
const groupRowCount = getAlbumGroupRowCount(
|
||||
index,
|
||||
cellProps.getRowItem,
|
||||
cellProps.enableHeader,
|
||||
cellProps.data.length,
|
||||
);
|
||||
const lastRowHeight = albumGroupImageSize - (groupRowCount - 1) * baseHeight;
|
||||
if (lastRowHeight > baseHeight) {
|
||||
return lastRowHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return baseHeight;
|
||||
},
|
||||
[enableHeader, headerHeight, rowHeight, pinnedRowCount, size],
|
||||
[albumGroupImageSize, enableHeader, headerHeight, rowHeight, pinnedRowCount, size],
|
||||
);
|
||||
|
||||
// Create a wrapper for getRowHeight that doesn't require cellProps (for sticky group rows hook)
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from '/@/renderer/store';
|
||||
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Badge } from '/@/shared/components/badge/badge';
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
||||
import { Divider } from '/@/shared/components/divider/divider';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
@@ -41,7 +42,7 @@ import { Text } from '/@/shared/components/text/text';
|
||||
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
|
||||
import { useDebouncedState } from '/@/shared/hooks/use-debounced-state';
|
||||
import { dndUtils, DragData, DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
|
||||
import { ItemListKey, ListPaginationType } from '/@/shared/types/types';
|
||||
import { ItemListKey, ListPaginationType, TableColumn } from '/@/shared/types/types';
|
||||
|
||||
interface TableConfigProps {
|
||||
enablePinColumnButtons?: boolean;
|
||||
@@ -72,10 +73,18 @@ export const TableConfig = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
|
||||
const { setList } = useSettingsStoreActions();
|
||||
const albumGroupImageSize = useSettingsStore((state) => state.general.albumGroupImageSize);
|
||||
const imageResTable = useSettingsStore((state) => state.general.imageRes.table);
|
||||
const { setList, setSettings } = useSettingsStoreActions();
|
||||
const [albumGroupOpen, setAlbumGroupOpen] = useState(false);
|
||||
|
||||
const table = tableKey === 'detail' ? (list?.detail ?? list?.table) : list?.table;
|
||||
|
||||
const hasAlbumGroupColumn = useMemo(
|
||||
() => table.columns.some((column) => column.id === TableColumn.ALBUM_GROUP),
|
||||
[table.columns],
|
||||
);
|
||||
|
||||
const setTableUpdate = useCallback(
|
||||
(patch: Partial<DataTableProps>) => {
|
||||
if (tableKey === 'detail') {
|
||||
@@ -90,6 +99,73 @@ export const TableConfig = ({
|
||||
);
|
||||
|
||||
const advancedSettings = useMemo(() => {
|
||||
const albumGroupOptions =
|
||||
hasAlbumGroupColumn && tableKey === 'main'
|
||||
? [
|
||||
{
|
||||
component: (
|
||||
<Group justify="flex-end" w="100%">
|
||||
<Button
|
||||
onClick={() => setAlbumGroupOpen((prev) => !prev)}
|
||||
size="compact-md"
|
||||
variant={albumGroupOpen ? 'subtle' : 'filled'}
|
||||
>
|
||||
{t(albumGroupOpen ? 'common.close' : 'common.edit')}
|
||||
</Button>
|
||||
</Group>
|
||||
),
|
||||
id: 'albumGroupConfig',
|
||||
label: t('table.config.general.albumGroupConfig'),
|
||||
},
|
||||
...(albumGroupOpen
|
||||
? [
|
||||
{
|
||||
component: (
|
||||
<Group justify="flex-end" w="100%">
|
||||
<NumberInput
|
||||
max={2000}
|
||||
min={0}
|
||||
onChange={(value) => {
|
||||
const size = Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
2000,
|
||||
typeof value === 'number' ? value : 0,
|
||||
),
|
||||
);
|
||||
setSettings({
|
||||
general: {
|
||||
albumGroupImageSize: size,
|
||||
// Source table art must be at least as
|
||||
// large as the displayed album image.
|
||||
...(size >= imageResTable
|
||||
? { imageRes: { table: size } }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
}}
|
||||
rightSection={
|
||||
<Text isMuted isNoSelect pr="lg" size="sm">
|
||||
px
|
||||
</Text>
|
||||
}
|
||||
value={albumGroupImageSize}
|
||||
width={90}
|
||||
/>
|
||||
</Group>
|
||||
),
|
||||
id: 'albumImageSize',
|
||||
label: (
|
||||
<Text pl="md">
|
||||
{t('table.config.general.albumImageSize')}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]
|
||||
: [];
|
||||
|
||||
const allOptions = [
|
||||
{
|
||||
component: (
|
||||
@@ -238,6 +314,7 @@ export const TableConfig = ({
|
||||
id: 'autoFitColumns',
|
||||
label: t('table.config.general.autoFitColumns'),
|
||||
},
|
||||
...albumGroupOptions,
|
||||
...(extraOptions || []),
|
||||
];
|
||||
|
||||
@@ -262,6 +339,11 @@ export const TableConfig = ({
|
||||
listKey,
|
||||
setTableUpdate,
|
||||
optionsConfig,
|
||||
hasAlbumGroupColumn,
|
||||
albumGroupOpen,
|
||||
albumGroupImageSize,
|
||||
imageResTable,
|
||||
setSettings,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -474,6 +474,7 @@ export const GeneralSettingsSchema = z.object({
|
||||
),
|
||||
albumBackground: z.boolean(),
|
||||
albumBackgroundBlur: z.number(),
|
||||
albumGroupImageSize: z.number(),
|
||||
artistBackground: z.boolean(),
|
||||
artistBackgroundBlur: z.number(),
|
||||
artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
|
||||
@@ -1166,6 +1167,7 @@ const initialState: SettingsState = {
|
||||
accent: 'rgb(53, 116, 252)',
|
||||
albumBackground: false,
|
||||
albumBackgroundBlur: 3,
|
||||
albumGroupImageSize: 0,
|
||||
artistBackground: true,
|
||||
artistBackgroundBlur: 3,
|
||||
artistItems,
|
||||
@@ -2645,6 +2647,9 @@ export const useSkipButtons = () => useSettingsStore((state) => state.general.sk
|
||||
|
||||
export const useImageRes = () => useSettingsStore((state) => state.general.imageRes, shallow);
|
||||
|
||||
export const useAlbumGroupImageSize = () =>
|
||||
useSettingsStore((state) => state.general.albumGroupImageSize);
|
||||
|
||||
export const useVolumeWidth = () => useSettingsStore((state) => state.general.volumeWidth, shallow);
|
||||
|
||||
export const useFollowCurrentSong = () =>
|
||||
|
||||
Reference in New Issue
Block a user