mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add grid card row config
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { ItemGridListRowConfig, ItemTableListColumnConfig } from '/@/renderer/store';
|
||||
import { TableColumn } from '/@/shared/types/types';
|
||||
|
||||
type DefaultTableColumn = {
|
||||
export type DefaultTableColumn = {
|
||||
align: 'center' | 'end' | 'start';
|
||||
autoSize: boolean;
|
||||
isEnabled: boolean;
|
||||
@@ -638,6 +639,24 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
|
||||
value: TableColumn.TITLE,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
isEnabled: true,
|
||||
label: i18n.t('table.config.label.songCount', { postProcess: 'titleCase' }),
|
||||
pinned: null,
|
||||
value: TableColumn.SONG_COUNT,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
isEnabled: true,
|
||||
label: i18n.t('table.config.label.albumCount', { postProcess: 'titleCase' }),
|
||||
pinned: null,
|
||||
value: TableColumn.ALBUM_COUNT,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
@@ -650,38 +669,77 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
|
||||
];
|
||||
|
||||
export const pickTableColumns = (options: {
|
||||
alignCenterColumns?: TableColumn[];
|
||||
alignLeftColumns?: TableColumn[];
|
||||
alignRightColumns?: TableColumn[];
|
||||
autoSizeColumns?: TableColumn[];
|
||||
columns: DefaultTableColumn[];
|
||||
enabledColumns: TableColumn[];
|
||||
pickColumns?: TableColumn[];
|
||||
pinnedLeftColumns?: TableColumn[];
|
||||
pinnedRightColumns?: TableColumn[];
|
||||
}) => {
|
||||
}): ItemTableListColumnConfig[] => {
|
||||
const {
|
||||
alignCenterColumns = [],
|
||||
alignLeftColumns = [],
|
||||
alignRightColumns = [],
|
||||
autoSizeColumns = [],
|
||||
columns,
|
||||
enabledColumns,
|
||||
pickColumns = [],
|
||||
pinnedLeftColumns = [],
|
||||
pinnedRightColumns = [],
|
||||
} = options;
|
||||
|
||||
return columns.map((column) => {
|
||||
const pinned: 'left' | 'right' | null = pinnedLeftColumns.includes(column.value)
|
||||
? 'left'
|
||||
: pinnedRightColumns.includes(column.value)
|
||||
? 'right'
|
||||
: null;
|
||||
const columnsToPick: ItemTableListColumnConfig[] = [];
|
||||
|
||||
columns.forEach((column) => {
|
||||
if (pickColumns.length > 0 && !pickColumns?.includes(column.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pinned: 'left' | 'right' | null = column.pinned;
|
||||
|
||||
if (pinnedLeftColumns.includes(column.value)) {
|
||||
pinned = 'left';
|
||||
} else if (pinnedRightColumns.includes(column.value)) {
|
||||
pinned = 'right';
|
||||
}
|
||||
|
||||
let align: 'center' | 'end' | 'start' = column.align;
|
||||
|
||||
if (alignCenterColumns.includes(column.value)) {
|
||||
align = 'center';
|
||||
} else if (alignLeftColumns.includes(column.value)) {
|
||||
align = 'start';
|
||||
} else if (alignRightColumns.includes(column.value)) {
|
||||
align = 'end';
|
||||
}
|
||||
|
||||
const isEnabled = enabledColumns.includes(column.value);
|
||||
|
||||
const autoSize = autoSizeColumns.includes(column.value);
|
||||
|
||||
return {
|
||||
align: column.align,
|
||||
columnsToPick.push({
|
||||
align,
|
||||
autoSize,
|
||||
id: column.value,
|
||||
isEnabled,
|
||||
pinned,
|
||||
width: column.width,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return columnsToPick;
|
||||
};
|
||||
|
||||
export const pickGridRows = (
|
||||
options: Parameters<typeof pickTableColumns>[0],
|
||||
): ItemGridListRowConfig[] => {
|
||||
const columns = pickTableColumns(options);
|
||||
return columns.map((column) => ({
|
||||
align: column.align,
|
||||
id: column.id as TableColumn,
|
||||
isEnabled: column.isEnabled,
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import { ListFilters } from '/@/renderer/features/shared/components/list-filters';
|
||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { ALBUM_ARTIST_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
||||
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
||||
@@ -33,7 +33,7 @@ export const AlbumArtistListHeaderFilters = () => {
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<ListConfigMenu
|
||||
listKey={ItemListKey.ALBUM_ARTIST}
|
||||
tableColumnsData={ALBUMARTIST_TABLE_COLUMNS}
|
||||
tableColumnsData={ALBUM_ARTIST_TABLE_COLUMNS}
|
||||
/>
|
||||
</Group>
|
||||
</Flex>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { ARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
||||
@@ -52,7 +52,7 @@ export const ArtistListHeaderFilters = () => {
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<ListConfigMenu
|
||||
listKey={ItemListKey.ARTIST}
|
||||
tableColumnsData={ARTIST_TABLE_COLUMNS}
|
||||
tableColumnsData={ALBUMARTIST_TABLE_COLUMNS}
|
||||
/>
|
||||
</Group>
|
||||
</Flex>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import { ListFilters } from '/@/renderer/features/shared/components/list-filters';
|
||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { type MutableRefObject } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||
import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { usePlayerContext } from '/@/renderer/features/player/context/player-context';
|
||||
import { updateSong } from '/@/renderer/features/player/update-remote-song';
|
||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
attachClosestEdge,
|
||||
type Edge,
|
||||
extractClosestEdge,
|
||||
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
||||
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
||||
import {
|
||||
draggable,
|
||||
dropTargetForElements,
|
||||
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
|
||||
import { useDebouncedState } from '@mantine/hooks';
|
||||
import clsx from 'clsx';
|
||||
import Fuse from 'fuse.js';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import styles from './table-config.module.css';
|
||||
|
||||
import { ListConfigTable } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import {
|
||||
DataGridProps,
|
||||
ItemGridListRowConfig,
|
||||
ItemListSettings,
|
||||
useSettingsStore,
|
||||
useSettingsStoreActions,
|
||||
} from '/@/renderer/store';
|
||||
import { ActionIcon } 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 { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
||||
import { Divider } from '/@/shared/components/divider/divider';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||
import { Slider } from '/@/shared/components/slider/slider';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { dndUtils, DragData, DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
|
||||
import { ItemListKey, ListPaginationType } from '/@/shared/types/types';
|
||||
|
||||
type GridConfigProps = {
|
||||
@@ -22,10 +44,11 @@ type GridConfigProps = {
|
||||
id: string;
|
||||
label: string;
|
||||
}[];
|
||||
gridRowsData: { label: string; value: string }[];
|
||||
listKey: ItemListKey;
|
||||
};
|
||||
|
||||
export const GridConfig = ({ extraOptions, listKey }: GridConfigProps) => {
|
||||
export const GridConfig = ({ extraOptions, gridRowsData, listKey }: GridConfigProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
|
||||
@@ -193,5 +216,405 @@ export const GridConfig = ({ extraOptions, listKey }: GridConfigProps) => {
|
||||
];
|
||||
}, [list, t, grid, extraOptions, setList, listKey]);
|
||||
|
||||
return <ListConfigTable options={options} />;
|
||||
return (
|
||||
<>
|
||||
<ListConfigTable options={options} />
|
||||
<Divider />
|
||||
<GridRowConfig
|
||||
data={gridRowsData}
|
||||
listKey={listKey}
|
||||
onChange={(rows) => setList(listKey, { ...list, grid: { ...grid, rows } })}
|
||||
value={grid.rows}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const GridRowConfig = ({
|
||||
data,
|
||||
listKey,
|
||||
onChange,
|
||||
value,
|
||||
}: {
|
||||
data: { label: string; value: string }[];
|
||||
listKey: ItemListKey;
|
||||
onChange: (value: ItemGridListRowConfig[]) => void;
|
||||
value: ItemGridListRowConfig[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const labelMap = useMemo(() => {
|
||||
return data.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.value] = item.label;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
console.log('data', data);
|
||||
console.log(labelMap);
|
||||
|
||||
const handleChangeEnabled = useCallback(
|
||||
(item: ItemGridListRowConfig, checked: boolean) => {
|
||||
const value = useSettingsStore.getState().lists[listKey]?.grid.rows;
|
||||
if (!value) return;
|
||||
const index = value.findIndex((v) => v.id === item.id);
|
||||
const newValues = [...value];
|
||||
newValues[index] = { ...newValues[index], isEnabled: checked };
|
||||
onChange(newValues);
|
||||
},
|
||||
[listKey, onChange],
|
||||
);
|
||||
|
||||
const handleMoveUp = useCallback(
|
||||
(item: ItemGridListRowConfig) => {
|
||||
const value = useSettingsStore.getState().lists[listKey]?.grid.rows;
|
||||
if (!value) return;
|
||||
const index = value.findIndex((v) => v.id === item.id);
|
||||
if (index === 0) return;
|
||||
const newValues = [...value];
|
||||
[newValues[index], newValues[index - 1]] = [newValues[index - 1], newValues[index]];
|
||||
onChange(newValues);
|
||||
},
|
||||
[listKey, onChange],
|
||||
);
|
||||
|
||||
const handleMoveDown = useCallback(
|
||||
(item: ItemGridListRowConfig) => {
|
||||
const value = useSettingsStore.getState().lists[listKey]?.grid.rows;
|
||||
if (!value) return;
|
||||
const index = value.findIndex((v) => v.id === item.id);
|
||||
if (index === value.length - 1) return;
|
||||
const newValues = [...value];
|
||||
[newValues[index], newValues[index + 1]] = [newValues[index + 1], newValues[index]];
|
||||
onChange(newValues);
|
||||
},
|
||||
[listKey, onChange],
|
||||
);
|
||||
|
||||
const handleAlignLeft = useCallback(
|
||||
(item: ItemGridListRowConfig) => {
|
||||
const value = useSettingsStore.getState().lists[listKey]?.grid.rows;
|
||||
if (!value) return;
|
||||
const index = value.findIndex((v) => v.id === item.id);
|
||||
const newValues = [...value];
|
||||
newValues[index] = { ...newValues[index], align: 'start' };
|
||||
onChange(newValues);
|
||||
},
|
||||
[listKey, onChange],
|
||||
);
|
||||
|
||||
const handleAlignCenter = useCallback(
|
||||
(item: ItemGridListRowConfig) => {
|
||||
const value = useSettingsStore.getState().lists[listKey]?.grid.rows;
|
||||
if (!value) return;
|
||||
const index = value.findIndex((v) => v.id === item.id);
|
||||
const newValues = [...value];
|
||||
newValues[index] = { ...newValues[index], align: 'center' };
|
||||
onChange(newValues);
|
||||
},
|
||||
[listKey, onChange],
|
||||
);
|
||||
|
||||
const handleAlignRight = useCallback(
|
||||
(item: ItemGridListRowConfig) => {
|
||||
const value = useSettingsStore.getState().lists[listKey]?.grid.rows;
|
||||
if (!value) return;
|
||||
const index = value.findIndex((v) => v.id === item.id);
|
||||
const newValues = [...value];
|
||||
newValues[index] = { ...newValues[index], align: 'end' };
|
||||
onChange(newValues);
|
||||
},
|
||||
[listKey, onChange],
|
||||
);
|
||||
|
||||
const [searchRows, setSearchRows] = useDebouncedState('', 300);
|
||||
|
||||
const fuse = useMemo(() => {
|
||||
return new Fuse(value, {
|
||||
getFn: (obj) => {
|
||||
return labelMap[obj.id] || '';
|
||||
},
|
||||
includeMatches: true,
|
||||
includeScore: true,
|
||||
keys: ['id', 'label'],
|
||||
threshold: 0.3,
|
||||
});
|
||||
}, [value, labelMap]);
|
||||
|
||||
const filteredRows = useMemo(() => {
|
||||
if (!searchRows.trim()) {
|
||||
return value.map((item) => ({ item, matches: null }));
|
||||
}
|
||||
|
||||
const results = fuse.search(searchRows);
|
||||
const resultMap = new Map(results.map((result) => [result.item.id, result.matches]));
|
||||
|
||||
return value.map((item) => ({
|
||||
item,
|
||||
matches: resultMap.get(item.id) || null,
|
||||
}));
|
||||
}, [value, searchRows, fuse]);
|
||||
|
||||
const handleReorder = useCallback(
|
||||
(idFrom: string, idTo: string, edge: Edge | null) => {
|
||||
const idList = value.map((item) => item.id);
|
||||
const newIdOrder = dndUtils.reorderById({
|
||||
edge,
|
||||
idFrom,
|
||||
idTo,
|
||||
list: idList,
|
||||
});
|
||||
|
||||
// Map the new ID order back to full items
|
||||
const newOrder = newIdOrder.map((id) => value.find((item) => item.id === id)!);
|
||||
onChange(newOrder);
|
||||
},
|
||||
[onChange, value],
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Text size="sm">{t('common.gridRows', { postProcess: 'sentenceCase' })}</Text>
|
||||
<TextInput
|
||||
onChange={(e) => setSearchRows(e.currentTarget.value)}
|
||||
placeholder={t('common.search', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
size="xs"
|
||||
/>
|
||||
</Group>
|
||||
<div style={{ userSelect: 'none' }}>
|
||||
{filteredRows.map(({ item, matches }) => (
|
||||
<GridRowItem
|
||||
handleAlignCenter={handleAlignCenter}
|
||||
handleAlignLeft={handleAlignLeft}
|
||||
handleAlignRight={handleAlignRight}
|
||||
handleChangeEnabled={handleChangeEnabled}
|
||||
handleMoveDown={handleMoveDown}
|
||||
handleMoveUp={handleMoveUp}
|
||||
handleReorder={handleReorder}
|
||||
item={item}
|
||||
key={item.id}
|
||||
label={labelMap[item.id]}
|
||||
matches={matches}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const DragHandle = ({ dragHandleRef }: { dragHandleRef: React.RefObject<HTMLButtonElement> }) => {
|
||||
return (
|
||||
<ActionIcon
|
||||
icon="dragVertical"
|
||||
iconProps={{
|
||||
size: 'md',
|
||||
}}
|
||||
ref={dragHandleRef}
|
||||
size="xs"
|
||||
style={{ cursor: 'grab' }}
|
||||
variant="default"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const GridRowItem = memo(
|
||||
({
|
||||
handleAlignCenter,
|
||||
handleAlignLeft,
|
||||
handleAlignRight,
|
||||
handleChangeEnabled,
|
||||
handleMoveDown,
|
||||
handleMoveUp,
|
||||
handleReorder,
|
||||
item,
|
||||
label,
|
||||
matches,
|
||||
}: {
|
||||
handleAlignCenter: (item: ItemGridListRowConfig) => void;
|
||||
handleAlignLeft: (item: ItemGridListRowConfig) => void;
|
||||
handleAlignRight: (item: ItemGridListRowConfig) => void;
|
||||
handleChangeEnabled: (item: ItemGridListRowConfig, checked: boolean) => void;
|
||||
handleMoveDown: (item: ItemGridListRowConfig) => void;
|
||||
handleMoveUp: (item: ItemGridListRowConfig) => void;
|
||||
handleReorder: (idFrom: string, idTo: string, edge: Edge | null) => void;
|
||||
item: ItemGridListRowConfig;
|
||||
label: string;
|
||||
matches: null | readonly Fuse.FuseResultMatch[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const dragHandleRef = useRef<HTMLButtonElement>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isDraggedOver, setIsDraggedOver] = useState<Edge | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current || !dragHandleRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
return combine(
|
||||
draggable({
|
||||
element: dragHandleRef.current,
|
||||
getInitialData: () => {
|
||||
const data = dndUtils.generateDragData({
|
||||
id: [item.id],
|
||||
operation: [DragOperation.REORDER],
|
||||
type: DragTarget.GRID_ROW,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
onDragStart: () => {
|
||||
setIsDragging(true);
|
||||
},
|
||||
onDrop: () => {
|
||||
setIsDragging(false);
|
||||
},
|
||||
onGenerateDragPreview: (data) => {
|
||||
disableNativeDragPreview({ nativeSetDragImage: data.nativeSetDragImage });
|
||||
},
|
||||
}),
|
||||
dropTargetForElements({
|
||||
canDrop: (args) => {
|
||||
const data = args.source.data as unknown as DragData;
|
||||
const isSelf = (args.source.data.id as string[])[0] === item.id;
|
||||
return dndUtils.isDropTarget(data.type, [DragTarget.GRID_ROW]) && !isSelf;
|
||||
},
|
||||
element: ref.current,
|
||||
getData: ({ element, input }) => {
|
||||
const data = dndUtils.generateDragData({
|
||||
id: [item.id],
|
||||
operation: [DragOperation.REORDER],
|
||||
type: DragTarget.GRID_ROW,
|
||||
});
|
||||
|
||||
return attachClosestEdge(data, {
|
||||
allowedEdges: ['top', 'bottom'],
|
||||
element,
|
||||
input,
|
||||
});
|
||||
},
|
||||
onDrag: (args) => {
|
||||
const closestEdgeOfTarget: Edge | null = extractClosestEdge(args.self.data);
|
||||
setIsDraggedOver(closestEdgeOfTarget);
|
||||
},
|
||||
onDragLeave: () => {
|
||||
setIsDraggedOver(null);
|
||||
},
|
||||
onDrop: (args) => {
|
||||
const closestEdgeOfTarget: Edge | null = extractClosestEdge(args.self.data);
|
||||
|
||||
const from = args.source.data.id as string[];
|
||||
const to = args.self.data.id as string[];
|
||||
|
||||
handleReorder(from[0], to[0], closestEdgeOfTarget);
|
||||
setIsDraggedOver(null);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}, [item.id, handleReorder]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.item, {
|
||||
[styles.draggedOverBottom]: isDraggedOver === 'bottom',
|
||||
[styles.draggedOverTop]: isDraggedOver === 'top',
|
||||
[styles.dragging]: isDragging,
|
||||
[styles.matched]: matches && matches.length > 0,
|
||||
})}
|
||||
ref={ref}
|
||||
>
|
||||
<Group wrap="nowrap">
|
||||
<DragHandle dragHandleRef={dragHandleRef} />
|
||||
<Checkbox
|
||||
checked={item.isEnabled}
|
||||
id={item.id}
|
||||
label={label}
|
||||
onChange={(e) => handleChangeEnabled(item, e.currentTarget.checked)}
|
||||
size="sm"
|
||||
/>
|
||||
</Group>
|
||||
<Group wrap="nowrap">
|
||||
<ActionIconGroup className={styles.group}>
|
||||
<ActionIcon
|
||||
icon="arrowUp"
|
||||
iconProps={{ size: 'md' }}
|
||||
onClick={() => handleMoveUp(item)}
|
||||
size="xs"
|
||||
tooltip={{
|
||||
label: t('table.config.general.moveUp', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
}}
|
||||
variant="subtle"
|
||||
/>
|
||||
<ActionIcon
|
||||
icon="arrowDown"
|
||||
iconProps={{ size: 'md' }}
|
||||
onClick={() => handleMoveDown(item)}
|
||||
size="xs"
|
||||
tooltip={{
|
||||
label: t('table.config.general.moveDown', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
}}
|
||||
variant="subtle"
|
||||
/>
|
||||
</ActionIconGroup>
|
||||
<ActionIconGroup className={styles.group}>
|
||||
<ActionIcon
|
||||
icon="alignLeft"
|
||||
iconProps={{ size: 'md' }}
|
||||
onClick={() => handleAlignLeft(item)}
|
||||
size="xs"
|
||||
tooltip={{
|
||||
label: t('table.config.general.alignLeft', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
}}
|
||||
variant={item.align === 'start' ? 'filled' : 'subtle'}
|
||||
/>
|
||||
<ActionIcon
|
||||
icon="alignCenter"
|
||||
iconProps={{ size: 'md' }}
|
||||
onClick={() => handleAlignCenter(item)}
|
||||
size="xs"
|
||||
tooltip={{
|
||||
label: t('table.config.general.alignCenter', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
}}
|
||||
variant={item.align === 'center' ? 'filled' : 'subtle'}
|
||||
/>
|
||||
<ActionIcon
|
||||
icon="alignRight"
|
||||
iconProps={{ size: 'md' }}
|
||||
onClick={() => handleAlignRight(item)}
|
||||
size="xs"
|
||||
tooltip={{
|
||||
label: t('table.config.general.alignRight', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
}}
|
||||
variant={item.align === 'end' ? 'filled' : 'subtle'}
|
||||
/>
|
||||
</ActionIconGroup>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.item.id === nextProps.item.id &&
|
||||
prevProps.item.isEnabled === nextProps.item.isEnabled &&
|
||||
prevProps.item.align === nextProps.item.align &&
|
||||
prevProps.label === nextProps.label &&
|
||||
prevProps.matches === nextProps.matches
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -122,14 +122,15 @@ export const ListConfigMenu = (props: ListConfigMenuProps) => {
|
||||
|
||||
const Config = ({
|
||||
displayType,
|
||||
tableColumnsData,
|
||||
...props
|
||||
}: ListConfigMenuProps & { displayType: ListDisplayType }) => {
|
||||
switch (displayType) {
|
||||
case ListDisplayType.GRID:
|
||||
return <GridConfig {...props} />;
|
||||
return <GridConfig {...props} gridRowsData={tableColumnsData} />;
|
||||
|
||||
case ListDisplayType.TABLE:
|
||||
return <TableConfig {...props} />;
|
||||
return <TableConfig {...props} tableColumnsData={tableColumnsData} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ALBUM_ARTIST_TABLE_COLUMNS,
|
||||
ALBUM_TABLE_COLUMNS,
|
||||
GENRE_TABLE_COLUMNS,
|
||||
pickGridRows,
|
||||
pickTableColumns,
|
||||
PLAYLIST_SONG_TABLE_COLUMNS,
|
||||
PLAYLIST_TABLE_COLUMNS,
|
||||
@@ -124,6 +125,16 @@ const ItemTableListColumnConfigSchema = z.object({
|
||||
width: z.number(),
|
||||
});
|
||||
|
||||
export type ItemTableListColumnConfig = z.infer<typeof ItemTableListColumnConfigSchema>;
|
||||
|
||||
const ItemGridListRowConfigSchema = z.object({
|
||||
align: z.enum(['center', 'end', 'start']),
|
||||
id: z.nativeEnum(TableColumn),
|
||||
isEnabled: z.boolean(),
|
||||
});
|
||||
|
||||
export type ItemGridListRowConfig = z.infer<typeof ItemGridListRowConfigSchema>;
|
||||
|
||||
const ItemTableListPropsSchema = z.object({
|
||||
autoFitColumns: z.boolean(),
|
||||
columns: z.array(ItemTableListColumnConfigSchema),
|
||||
@@ -140,6 +151,7 @@ const ItemListConfigSchema = z.object({
|
||||
itemGap: z.enum(['lg', 'md', 'sm', 'xl', 'xs']),
|
||||
itemsPerRow: z.number(),
|
||||
itemsPerRowEnabled: z.boolean(),
|
||||
rows: z.array(ItemGridListRowConfigSchema),
|
||||
}),
|
||||
itemsPerPage: z.number(),
|
||||
pagination: z.nativeEnum(ListPaginationType),
|
||||
@@ -421,6 +433,7 @@ export type DataGridProps = {
|
||||
itemGap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||
itemsPerRow: number;
|
||||
itemsPerRowEnabled: boolean;
|
||||
rows: ItemGridListRowConfig[];
|
||||
};
|
||||
|
||||
export type DataTableProps = z.infer<typeof ItemTableListPropsSchema>;
|
||||
@@ -650,6 +663,7 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: [],
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -676,6 +690,28 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: pickGridRows({
|
||||
alignLeftColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ALBUM_ARTIST,
|
||||
TableColumn.YEAR,
|
||||
],
|
||||
columns: ALBUM_TABLE_COLUMNS,
|
||||
enabledColumns: [TableColumn.TITLE, TableColumn.ALBUM_ARTIST, TableColumn.YEAR],
|
||||
pickColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.DURATION,
|
||||
TableColumn.ALBUM_ARTIST,
|
||||
TableColumn.BIT_RATE,
|
||||
TableColumn.BPM,
|
||||
TableColumn.DATE_ADDED,
|
||||
TableColumn.DURATION,
|
||||
TableColumn.GENRE,
|
||||
TableColumn.PLAY_COUNT,
|
||||
TableColumn.SONG_COUNT,
|
||||
TableColumn.YEAR,
|
||||
],
|
||||
}),
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -702,6 +738,17 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: pickGridRows({
|
||||
alignLeftColumns: [TableColumn.TITLE],
|
||||
columns: ALBUM_ARTIST_TABLE_COLUMNS,
|
||||
enabledColumns: [TableColumn.TITLE],
|
||||
pickColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.PLAY_COUNT,
|
||||
TableColumn.ALBUM_COUNT,
|
||||
TableColumn.SONG_COUNT,
|
||||
],
|
||||
}),
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -728,6 +775,17 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: pickGridRows({
|
||||
alignLeftColumns: [TableColumn.TITLE],
|
||||
columns: ALBUM_ARTIST_TABLE_COLUMNS,
|
||||
enabledColumns: [TableColumn.TITLE],
|
||||
pickColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.PLAY_COUNT,
|
||||
TableColumn.ALBUM_COUNT,
|
||||
TableColumn.SONG_COUNT,
|
||||
],
|
||||
}),
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -754,6 +812,24 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: pickGridRows({
|
||||
alignLeftColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.SONG_COUNT,
|
||||
TableColumn.ALBUM_COUNT,
|
||||
],
|
||||
columns: GENRE_TABLE_COLUMNS,
|
||||
enabledColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.SONG_COUNT,
|
||||
TableColumn.ALBUM_COUNT,
|
||||
],
|
||||
pickColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ALBUM_COUNT,
|
||||
TableColumn.SONG_COUNT,
|
||||
],
|
||||
}),
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -780,6 +856,12 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: pickGridRows({
|
||||
alignLeftColumns: [TableColumn.TITLE, TableColumn.SONG_COUNT],
|
||||
columns: PLAYLIST_TABLE_COLUMNS,
|
||||
enabledColumns: [TableColumn.TITLE],
|
||||
pickColumns: [TableColumn.TITLE, TableColumn.SONG_COUNT],
|
||||
}),
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -806,6 +888,7 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: [],
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -832,6 +915,7 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: [],
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -858,6 +942,25 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: pickGridRows({
|
||||
alignLeftColumns: [TableColumn.TITLE, TableColumn.ARTIST],
|
||||
columns: SONG_TABLE_COLUMNS,
|
||||
enabledColumns: [TableColumn.TITLE, TableColumn.ARTIST],
|
||||
pickColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ARTIST,
|
||||
TableColumn.DURATION,
|
||||
TableColumn.YEAR,
|
||||
TableColumn.BIT_RATE,
|
||||
TableColumn.BPM,
|
||||
TableColumn.CODEC,
|
||||
TableColumn.DATE_ADDED,
|
||||
TableColumn.GENRE,
|
||||
TableColumn.LAST_PLAYED,
|
||||
TableColumn.RELEASE_DATE,
|
||||
TableColumn.TRACK_NUMBER,
|
||||
],
|
||||
}),
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
@@ -884,6 +987,7 @@ const initialState: SettingsState = {
|
||||
itemGap: 'md',
|
||||
itemsPerRow: 6,
|
||||
itemsPerRowEnabled: false,
|
||||
rows: [],
|
||||
},
|
||||
itemsPerPage: 100,
|
||||
pagination: ListPaginationType.INFINITE,
|
||||
|
||||
Reference in New Issue
Block a user