mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
add large table size
This commit is contained in:
+8
-2
@@ -1,12 +1,13 @@
|
|||||||
import { ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
|
import { ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts table columns by their pinned position:
|
* Sorts table columns by their pinned position and filters out disabled columns:
|
||||||
* - Left pinned columns come first (maintaining their original order)
|
* - Left pinned columns come first (maintaining their original order)
|
||||||
* - Unpinned columns come next (maintaining their original order)
|
* - Unpinned columns come next (maintaining their original order)
|
||||||
* - Right pinned columns come last (maintaining their original order)
|
* - Right pinned columns come last (maintaining their original order)
|
||||||
|
* - Columns with isEnabled: false are removed
|
||||||
*/
|
*/
|
||||||
export const sortTableColumns = (
|
export const parseTableColumns = (
|
||||||
columns: ItemTableListColumnConfig[],
|
columns: ItemTableListColumnConfig[],
|
||||||
): ItemTableListColumnConfig[] => {
|
): ItemTableListColumnConfig[] => {
|
||||||
const leftPinned: ItemTableListColumnConfig[] = [];
|
const leftPinned: ItemTableListColumnConfig[] = [];
|
||||||
@@ -14,7 +15,12 @@ export const sortTableColumns = (
|
|||||||
const rightPinned: ItemTableListColumnConfig[] = [];
|
const rightPinned: ItemTableListColumnConfig[] = [];
|
||||||
|
|
||||||
// Separate columns by pinned position while maintaining original order
|
// Separate columns by pinned position while maintaining original order
|
||||||
|
// Only include columns that are enabled (isEnabled !== false)
|
||||||
columns.forEach((column) => {
|
columns.forEach((column) => {
|
||||||
|
if (column.isEnabled === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (column.pinned) {
|
switch (column.pinned) {
|
||||||
case 'left':
|
case 'left':
|
||||||
leftPinned.push(column);
|
leftPinned.push(column);
|
||||||
+8
-1
@@ -11,7 +11,14 @@
|
|||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
line-height: 1.4;
|
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
color: var(--theme-colors-foreground-muted);
|
color: var(--theme-colors-foreground-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artists-container.compact {
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artists-container.large {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { generatePath, Link } from 'react-router-dom';
|
import { generatePath, Link } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -31,7 +32,12 @@ const AlbumArtistsColumn = (props: ItemTableListInnerColumn) => {
|
|||||||
if (Array.isArray(row)) {
|
if (Array.isArray(row)) {
|
||||||
return (
|
return (
|
||||||
<TableColumnContainer {...props}>
|
<TableColumnContainer {...props}>
|
||||||
<div className={styles.artistsContainer}>
|
<div
|
||||||
|
className={clsx(styles.artistsContainer, {
|
||||||
|
[styles.compact]: props.size === 'compact',
|
||||||
|
[styles.large]: props.size === 'large',
|
||||||
|
})}
|
||||||
|
>
|
||||||
{albumArtists.map((albumArtist, index) => (
|
{albumArtists.map((albumArtist, index) => (
|
||||||
<Text
|
<Text
|
||||||
component={Link}
|
component={Link}
|
||||||
|
|||||||
@@ -11,7 +11,14 @@
|
|||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
line-height: 1.4;
|
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
color: var(--theme-colors-foreground-muted);
|
color: var(--theme-colors-foreground-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artists-container.compact {
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artists-container.large {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { generatePath, Link } from 'react-router-dom';
|
import { generatePath, Link } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -31,7 +32,12 @@ const ArtistsColumn = (props: ItemTableListInnerColumn) => {
|
|||||||
if (Array.isArray(row)) {
|
if (Array.isArray(row)) {
|
||||||
return (
|
return (
|
||||||
<TableColumnContainer {...props}>
|
<TableColumnContainer {...props}>
|
||||||
<div className={styles.artistsContainer}>
|
<div
|
||||||
|
className={clsx(styles.artistsContainer, {
|
||||||
|
[styles.compact]: props.size === 'compact',
|
||||||
|
[styles.large]: props.size === 'large',
|
||||||
|
})}
|
||||||
|
>
|
||||||
{artists.map((artist, index) => (
|
{artists.map((artist, index) => (
|
||||||
<Text
|
<Text
|
||||||
component={Link}
|
component={Link}
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
.group {
|
||||||
|
gap: var(--theme-spacing-sm) var(--theme-spacing-xs);
|
||||||
|
padding: var(--theme-spacing-xs) 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group a {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
import { generatePath, Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import styles from './genre-badge-column.module.css';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ItemTableListInnerColumn,
|
||||||
|
TableColumnContainer,
|
||||||
|
TableColumnTextContainer,
|
||||||
|
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { Badge } from '/@/shared/components/badge/badge';
|
||||||
|
import { Group } from '/@/shared/components/group/group';
|
||||||
|
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||||
|
import { Genre } from '/@/shared/types/domain-types';
|
||||||
|
import { stringToColor } from '/@/shared/utils/string-to-color';
|
||||||
|
|
||||||
|
const MAX_GENRES = 4;
|
||||||
|
|
||||||
|
const GenreBadgeColumn = (props: ItemTableListInnerColumn) => {
|
||||||
|
const row: Genre[] | undefined = (props.data as (Genre[] | undefined)[])[props.rowIndex]?.[
|
||||||
|
'genres'
|
||||||
|
];
|
||||||
|
|
||||||
|
const genres = useMemo(() => {
|
||||||
|
if (!row) return [];
|
||||||
|
return row.map((genre) => {
|
||||||
|
const { color, isLight } = stringToColor(genre.name);
|
||||||
|
const path = generatePath(AppRoute.LIBRARY_GENRES_ALBUMS, { genreId: genre.id });
|
||||||
|
return { ...genre, color, isLight, path };
|
||||||
|
});
|
||||||
|
}, [row]);
|
||||||
|
|
||||||
|
if (Array.isArray(row)) {
|
||||||
|
return (
|
||||||
|
<TableColumnContainer {...props}>
|
||||||
|
<Group className={styles.group} wrap="wrap">
|
||||||
|
{genres.slice(0, MAX_GENRES).map((genre) => (
|
||||||
|
<Badge
|
||||||
|
component={Link}
|
||||||
|
key={genre.id}
|
||||||
|
style={{
|
||||||
|
backgroundColor: genre.color,
|
||||||
|
color: genre.isLight ? 'black' : 'white',
|
||||||
|
}}
|
||||||
|
to={genre.path}
|
||||||
|
>
|
||||||
|
{genre.name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</TableColumnContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableColumnTextContainer {...props}>
|
||||||
|
<Skeleton />
|
||||||
|
</TableColumnTextContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GenreColumnMemo = memo(GenreBadgeColumn);
|
||||||
|
|
||||||
|
export { GenreColumnMemo as GenreBadgeColumn };
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
.group {
|
.genres-container {
|
||||||
gap: var(--theme-spacing-sm) var(--theme-spacing-xs);
|
display: -webkit-box;
|
||||||
padding: var(--theme-spacing-xs) 0;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
color: var(--theme-colors-foreground-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.group a {
|
.genres-container.compact {
|
||||||
cursor: pointer;
|
-webkit-line-clamp: 1;
|
||||||
user-select: none;
|
}
|
||||||
|
|
||||||
|
.genres-container.large {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { generatePath, Link } from 'react-router-dom';
|
import { generatePath, Link } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -9,13 +10,9 @@ import {
|
|||||||
TableColumnTextContainer,
|
TableColumnTextContainer,
|
||||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { Badge } from '/@/shared/components/badge/badge';
|
|
||||||
import { Group } from '/@/shared/components/group/group';
|
|
||||||
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||||
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { Genre } from '/@/shared/types/domain-types';
|
import { Genre } from '/@/shared/types/domain-types';
|
||||||
import { stringToColor } from '/@/shared/utils/string-to-color';
|
|
||||||
|
|
||||||
const MAX_GENRES = 4;
|
|
||||||
|
|
||||||
const GenreColumn = (props: ItemTableListInnerColumn) => {
|
const GenreColumn = (props: ItemTableListInnerColumn) => {
|
||||||
const row: Genre[] | undefined = (props.data as (Genre[] | undefined)[])[props.rowIndex]?.[
|
const row: Genre[] | undefined = (props.data as (Genre[] | undefined)[])[props.rowIndex]?.[
|
||||||
@@ -25,30 +22,36 @@ const GenreColumn = (props: ItemTableListInnerColumn) => {
|
|||||||
const genres = useMemo(() => {
|
const genres = useMemo(() => {
|
||||||
if (!row) return [];
|
if (!row) return [];
|
||||||
return row.map((genre) => {
|
return row.map((genre) => {
|
||||||
const { color, isLight } = stringToColor(genre.name);
|
const path = generatePath(AppRoute.LIBRARY_GENRES_ALBUMS, {
|
||||||
const path = generatePath(AppRoute.LIBRARY_GENRES_ALBUMS, { genreId: genre.id });
|
genreId: genre.id,
|
||||||
return { ...genre, color, isLight, path };
|
});
|
||||||
|
return { ...genre, path };
|
||||||
});
|
});
|
||||||
}, [row]);
|
}, [row]);
|
||||||
|
|
||||||
if (Array.isArray(row)) {
|
if (Array.isArray(row)) {
|
||||||
return (
|
return (
|
||||||
<TableColumnContainer {...props}>
|
<TableColumnContainer {...props}>
|
||||||
<Group className={styles.group} wrap="wrap">
|
<div
|
||||||
{genres.slice(0, MAX_GENRES).map((genre) => (
|
className={clsx(styles.genresContainer, {
|
||||||
<Badge
|
[styles.compact]: props.size === 'compact',
|
||||||
|
[styles.large]: props.size === 'large',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{genres.map((genre, index) => (
|
||||||
|
<Text
|
||||||
component={Link}
|
component={Link}
|
||||||
|
isLink
|
||||||
|
isMuted
|
||||||
|
isNoSelect
|
||||||
key={genre.id}
|
key={genre.id}
|
||||||
style={{
|
|
||||||
backgroundColor: genre.color,
|
|
||||||
color: genre.isLight ? 'black' : 'white',
|
|
||||||
}}
|
|
||||||
to={genre.path}
|
to={genre.path}
|
||||||
>
|
>
|
||||||
{genre.name}
|
{genre.name}
|
||||||
</Badge>
|
{index < genres.length - 1 && ', '}
|
||||||
|
</Text>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</div>
|
||||||
</TableColumnContainer>
|
</TableColumnContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.text-container {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container.compact {
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container.large {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import styles from './text-column.module.css';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ItemTableListInnerColumn,
|
ItemTableListInnerColumn,
|
||||||
TableColumnTextContainer,
|
TableColumnTextContainer,
|
||||||
@@ -10,10 +14,17 @@ export const TextColumn = (props: ItemTableListInnerColumn) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (typeof row === 'string' && row) {
|
if (typeof row === 'string' && row) {
|
||||||
const maxLength = 100;
|
return (
|
||||||
const displayText = row.length > maxLength ? `${row.slice(0, maxLength)}...` : row;
|
<TableColumnTextContainer
|
||||||
|
className={clsx(styles.textContainer, {
|
||||||
return <TableColumnTextContainer {...props}>{displayText}</TableColumnTextContainer>;
|
[styles.compact]: props.size === 'compact',
|
||||||
|
[styles.large]: props.size === 'large',
|
||||||
|
})}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{row}
|
||||||
|
</TableColumnTextContainer>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import { UseSuspenseQueryOptions } from '@tanstack/react-query';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
|
||||||
|
import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
||||||
|
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||||
|
import { ItemListHandle, ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
|
||||||
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
import { AlbumListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
import { TableColumn } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
const columns: ItemTableListColumnConfig[] = [
|
||||||
|
{ align: 'center', id: TableColumn.ROW_INDEX, pinned: 'left', width: 50 },
|
||||||
|
{ align: 'center', id: TableColumn.IMAGE, pinned: 'left', width: 60 },
|
||||||
|
{ align: 'start', id: TableColumn.TITLE, pinned: 'left', width: 300 },
|
||||||
|
{ align: 'start', id: TableColumn.GENRE, pinned: null, width: 200 },
|
||||||
|
{ align: 'center', id: TableColumn.YEAR, pinned: null, width: 100 },
|
||||||
|
{ align: 'start', autoWidth: true, id: TableColumn.ALBUM_ARTIST, pinned: null, width: 150 },
|
||||||
|
{ align: 'center', id: TableColumn.DURATION, pinned: null, width: 100 },
|
||||||
|
{ align: 'start', id: TableColumn.LAST_PLAYED, pinned: null, width: 250 },
|
||||||
|
{ align: 'center', id: TableColumn.USER_RATING, pinned: null, width: 120 },
|
||||||
|
{ align: 'center', id: TableColumn.USER_FAVORITE, pinned: null, width: 50 },
|
||||||
|
{ align: 'center', id: TableColumn.ACTIONS, pinned: 'right', width: 50 },
|
||||||
|
{ align: 'center', id: TableColumn.SIZE, pinned: null, width: 100 },
|
||||||
|
{ align: 'center', id: TableColumn.DATE_ADDED, pinned: null, width: 100 },
|
||||||
|
{ align: 'center', id: TableColumn.RELEASE_DATE, pinned: null, width: 100 },
|
||||||
|
{ align: 'center', id: TableColumn.PLAY_COUNT, pinned: null, width: 100 },
|
||||||
|
{ align: 'center', id: TableColumn.SONG_COUNT, pinned: null, width: 100 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// export const Demo = () => {
|
||||||
|
// const server = useCurrentServer();
|
||||||
|
// const recentlyAdded = useSuspenseQuery(
|
||||||
|
// albumQueries.list({
|
||||||
|
// options: {
|
||||||
|
// gcTime: 1000 * 60 * 5,
|
||||||
|
// },
|
||||||
|
// query: {
|
||||||
|
// limit: 100,
|
||||||
|
// sortBy: AlbumListSort.RECENTLY_ADDED,
|
||||||
|
// sortOrder: SortOrder.DESC,
|
||||||
|
// startIndex: 0,
|
||||||
|
// },
|
||||||
|
// serverId: server.id,
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const data = [
|
||||||
|
// Object.fromEntries(columns.map((x) => [x.id, x.label])),
|
||||||
|
// ...recentlyAdded.data.items.map((item, index) => ({
|
||||||
|
// ...item,
|
||||||
|
// rowIndex: index,
|
||||||
|
// })),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <ItemTableList
|
||||||
|
// CellComponent={ItemTableListColumn}
|
||||||
|
// columnCount={columns.length}
|
||||||
|
// columns={columns}
|
||||||
|
// columnWidth={(index) => {
|
||||||
|
// return columns[index].width;
|
||||||
|
// }}
|
||||||
|
// data={data}
|
||||||
|
// itemType={LibraryItem.ALBUM}
|
||||||
|
// onCellsRendered={() => {}}
|
||||||
|
// rowHeight={60}
|
||||||
|
// stickyColumnCount={3}
|
||||||
|
// stickyRowCount={1}
|
||||||
|
// totalItemCount={data.length}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const Demo = () => {
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
sortBy: AlbumListSort.NAME,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
};
|
||||||
|
|
||||||
|
const serverId = server.id;
|
||||||
|
|
||||||
|
const listCountQuery = albumQueries.listCount({
|
||||||
|
query: { ...query },
|
||||||
|
serverId: serverId,
|
||||||
|
}) as UseSuspenseQueryOptions<number, Error, number, readonly unknown[]>;
|
||||||
|
|
||||||
|
const listQueryFn = api.controller.getAlbumList;
|
||||||
|
|
||||||
|
const { data, onRangeChanged } = useItemListInfiniteLoader({
|
||||||
|
itemsPerPage: 100,
|
||||||
|
listCountQuery,
|
||||||
|
listQueryFn,
|
||||||
|
query,
|
||||||
|
serverId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// const itemsPerPage = 100;
|
||||||
|
|
||||||
|
// const { currentPage, onChange } = useItemListPagination({ initialPage: 0 });
|
||||||
|
|
||||||
|
// const { data, pageCount, totalItemCount } = useItemListPaginatedLoader({
|
||||||
|
// currentPage,
|
||||||
|
// itemsPerPage,
|
||||||
|
// listCountQuery,
|
||||||
|
// listQueryFn,
|
||||||
|
// query,
|
||||||
|
// serverId,
|
||||||
|
// });
|
||||||
|
|
||||||
|
const ref = useRef<ItemListHandle>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemTableList
|
||||||
|
CellComponent={ItemTableListColumn}
|
||||||
|
columns={columns}
|
||||||
|
data={data || []}
|
||||||
|
enableAlternateRowColors
|
||||||
|
enableExpansion
|
||||||
|
enableHeader
|
||||||
|
enableHorizontalBorders
|
||||||
|
enableRowHoverHighlight
|
||||||
|
enableSelection
|
||||||
|
itemType={LibraryItem.ALBUM}
|
||||||
|
onRangeChanged={onRangeChanged}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <ItemListWithPagination
|
||||||
|
// currentPage={currentPage}
|
||||||
|
// itemsPerPage={itemsPerPage}
|
||||||
|
// onChange={onChange}
|
||||||
|
// pageCount={pageCount}
|
||||||
|
// totalItemCount={totalItemCount}
|
||||||
|
// >
|
||||||
|
// <ItemTableList
|
||||||
|
// CellComponent={ItemTableListColumn}
|
||||||
|
// columnCount={columns.length}
|
||||||
|
// columns={columns}
|
||||||
|
// columnWidth={(index) => {
|
||||||
|
// return columns[index].width;
|
||||||
|
// }}
|
||||||
|
// data={data || []}
|
||||||
|
// enableHeader
|
||||||
|
// itemType={LibraryItem.ALBUM}
|
||||||
|
// onCellsRendered={() => {}}
|
||||||
|
// pinnedLeftColumnCount={2}
|
||||||
|
// // onRangeChanged={onRangeChanged}
|
||||||
|
// rowHeight={60}
|
||||||
|
// totalItemCount={data?.length || 0}
|
||||||
|
// />
|
||||||
|
// </ItemListWithPagination>
|
||||||
|
// );
|
||||||
|
};
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
|
||||||
import { TableColumn } from '/@/shared/types/types';
|
|
||||||
|
|
||||||
export const TableColumnHeader = (
|
|
||||||
props: ItemTableListColumn & {
|
|
||||||
children: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
containerClassName?: string;
|
|
||||||
type: TableColumn;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
return <div className={clsx(props.className)}>{props.children}</div>;
|
|
||||||
};
|
|
||||||
+48
-7
@@ -4,9 +4,28 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.padding-xs {
|
||||||
padding: var(--theme-spacing-xs);
|
padding: var(--theme-spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container.padding-sm {
|
||||||
|
padding: var(--theme-spacing-xs) var(--theme-spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.padding-md {
|
||||||
|
padding: var(--theme-spacing-xs) var(--theme-spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.padding-lg {
|
||||||
|
padding: var(--theme-spacing-xs) var(--theme-spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.padding-xl {
|
||||||
|
padding: var(--theme-spacing-xs) var(--theme-spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.container.center {
|
.container.center {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -29,7 +48,6 @@
|
|||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
line-clamp: 2;
|
line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
line-height: 1.3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content.compact {
|
.content.compact {
|
||||||
@@ -37,8 +55,9 @@
|
|||||||
line-clamp: 1;
|
line-clamp: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container.compact {
|
.content.large {
|
||||||
padding: var(--theme-spacing-xs);
|
-webkit-line-clamp: 3;
|
||||||
|
line-clamp: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container.with-horizontal-border {
|
.container.with-horizontal-border {
|
||||||
@@ -86,20 +105,42 @@
|
|||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-container.padding-xs {
|
||||||
|
padding: 0 var(--theme-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container.padding-sm {
|
||||||
|
padding: 0 var(--theme-spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container.padding-md {
|
||||||
|
padding: 0 var(--theme-spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container.padding-lg {
|
||||||
|
padding: 0 var(--theme-spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container.padding-xl {
|
||||||
|
padding: 0 var(--theme-spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
display: flex;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content.center {
|
.header-content.center {
|
||||||
justify-content: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content.left {
|
.header-content.left {
|
||||||
justify-content: flex-start;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content.right {
|
.header-content.right {
|
||||||
justify-content: flex-end;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container :global(.hover-only),
|
.container :global(.hover-only),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
import { DefaultColumn } from '/@/renderer/components/item-list/item-table-list/columns/default-column';
|
import { DefaultColumn } from '/@/renderer/components/item-list/item-table-list/columns/default-column';
|
||||||
import { DurationColumn } from '/@/renderer/components/item-list/item-table-list/columns/duration-column';
|
import { DurationColumn } from '/@/renderer/components/item-list/item-table-list/columns/duration-column';
|
||||||
import { FavoriteColumn } from '/@/renderer/components/item-list/item-table-list/columns/favorite-column';
|
import { FavoriteColumn } from '/@/renderer/components/item-list/item-table-list/columns/favorite-column';
|
||||||
|
import { GenreBadgeColumn } from '/@/renderer/components/item-list/item-table-list/columns/genre-badge-column';
|
||||||
import { GenreColumn } from '/@/renderer/components/item-list/item-table-list/columns/genre-column';
|
import { GenreColumn } from '/@/renderer/components/item-list/item-table-list/columns/genre-column';
|
||||||
import { ImageColumn } from '/@/renderer/components/item-list/item-table-list/columns/image-column';
|
import { ImageColumn } from '/@/renderer/components/item-list/item-table-list/columns/image-column';
|
||||||
import { NumericColumn } from '/@/renderer/components/item-list/item-table-list/columns/numeric-column';
|
import { NumericColumn } from '/@/renderer/components/item-list/item-table-list/columns/numeric-column';
|
||||||
@@ -109,6 +110,9 @@ export const ItemTableListColumn = (props: ItemTableListColumn) => {
|
|||||||
case TableColumn.GENRE:
|
case TableColumn.GENRE:
|
||||||
return <GenreColumn {...props} controls={controls} type={type} />;
|
return <GenreColumn {...props} controls={controls} type={type} />;
|
||||||
|
|
||||||
|
case TableColumn.GENRE_BADGE:
|
||||||
|
return <GenreBadgeColumn {...props} controls={controls} type={type} />;
|
||||||
|
|
||||||
case TableColumn.IMAGE:
|
case TableColumn.IMAGE:
|
||||||
return <ImageColumn {...props} controls={controls} type={type} />;
|
return <ImageColumn {...props} controls={controls} type={type} />;
|
||||||
|
|
||||||
@@ -190,7 +194,13 @@ export const TableColumnTextContainer = (
|
|||||||
[styles.center]: props.columns[props.columnIndex].align === 'center',
|
[styles.center]: props.columns[props.columnIndex].align === 'center',
|
||||||
[styles.compact]: props.size === 'compact',
|
[styles.compact]: props.size === 'compact',
|
||||||
[styles.dataRow]: isDataRow,
|
[styles.dataRow]: isDataRow,
|
||||||
|
[styles.large]: props.size === 'large',
|
||||||
[styles.left]: props.columns[props.columnIndex].align === 'start',
|
[styles.left]: props.columns[props.columnIndex].align === 'start',
|
||||||
|
[styles.paddingLg]: props.cellPadding === 'lg',
|
||||||
|
[styles.paddingMd]: props.cellPadding === 'md',
|
||||||
|
[styles.paddingSm]: props.cellPadding === 'sm',
|
||||||
|
[styles.paddingXl]: props.cellPadding === 'xl',
|
||||||
|
[styles.paddingXs]: props.cellPadding === 'xs',
|
||||||
[styles.right]: props.columns[props.columnIndex].align === 'end',
|
[styles.right]: props.columns[props.columnIndex].align === 'end',
|
||||||
[styles.rowHoverHighlightEnabled]: isDataRow && props.enableRowHoverHighlight,
|
[styles.rowHoverHighlightEnabled]: isDataRow && props.enableRowHoverHighlight,
|
||||||
[styles.withHorizontalBorder]:
|
[styles.withHorizontalBorder]:
|
||||||
@@ -204,6 +214,7 @@ export const TableColumnTextContainer = (
|
|||||||
<Text
|
<Text
|
||||||
className={clsx(styles.content, props.className, {
|
className={clsx(styles.content, props.className, {
|
||||||
[styles.compact]: props.size === 'compact',
|
[styles.compact]: props.size === 'compact',
|
||||||
|
[styles.large]: props.size === 'large',
|
||||||
})}
|
})}
|
||||||
isMuted={!NonMutedColumns.includes(props.type)}
|
isMuted={!NonMutedColumns.includes(props.type)}
|
||||||
isNoSelect
|
isNoSelect
|
||||||
@@ -264,7 +275,13 @@ export const TableColumnContainer = (
|
|||||||
[styles.center]: props.columns[props.columnIndex].align === 'center',
|
[styles.center]: props.columns[props.columnIndex].align === 'center',
|
||||||
[styles.compact]: props.size === 'compact',
|
[styles.compact]: props.size === 'compact',
|
||||||
[styles.dataRow]: isDataRow,
|
[styles.dataRow]: isDataRow,
|
||||||
|
[styles.large]: props.size === 'large',
|
||||||
[styles.left]: props.columns[props.columnIndex].align === 'start',
|
[styles.left]: props.columns[props.columnIndex].align === 'start',
|
||||||
|
[styles.paddingLg]: props.cellPadding === 'lg',
|
||||||
|
[styles.paddingMd]: props.cellPadding === 'md',
|
||||||
|
[styles.paddingSm]: props.cellPadding === 'sm',
|
||||||
|
[styles.paddingXl]: props.cellPadding === 'xl',
|
||||||
|
[styles.paddingXs]: props.cellPadding === 'xs',
|
||||||
[styles.right]: props.columns[props.columnIndex].align === 'end',
|
[styles.right]: props.columns[props.columnIndex].align === 'end',
|
||||||
[styles.rowHoverHighlightEnabled]: isDataRow && props.enableRowHoverHighlight,
|
[styles.rowHoverHighlightEnabled]: isDataRow && props.enableRowHoverHighlight,
|
||||||
[styles.withHorizontalBorder]:
|
[styles.withHorizontalBorder]:
|
||||||
@@ -290,7 +307,13 @@ export const TableColumnHeaderContainer = (
|
|||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, styles.headerContainer, props.containerClassName, {})}
|
className={clsx(styles.container, styles.headerContainer, props.containerClassName, {
|
||||||
|
[styles.paddingLg]: props.cellPadding === 'lg',
|
||||||
|
[styles.paddingMd]: props.cellPadding === 'md',
|
||||||
|
[styles.paddingSm]: props.cellPadding === 'sm',
|
||||||
|
[styles.paddingXl]: props.cellPadding === 'xl',
|
||||||
|
[styles.paddingXs]: props.cellPadding === 'xs',
|
||||||
|
})}
|
||||||
style={props.style}
|
style={props.style}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
@@ -333,6 +356,9 @@ const columnLabelMap: Record<TableColumn, ReactNode | string> = {
|
|||||||
}) as string,
|
}) as string,
|
||||||
[TableColumn.DURATION]: <Icon icon="duration" size="md" />,
|
[TableColumn.DURATION]: <Icon icon="duration" size="md" />,
|
||||||
[TableColumn.GENRE]: i18n.t('table.column.genre', { postProcess: 'upperCase' }) as string,
|
[TableColumn.GENRE]: i18n.t('table.column.genre', { postProcess: 'upperCase' }) as string,
|
||||||
|
[TableColumn.GENRE_BADGE]: i18n.t('table.column.genre', {
|
||||||
|
postProcess: 'upperCase',
|
||||||
|
}) as string,
|
||||||
[TableColumn.ID]: 'ID',
|
[TableColumn.ID]: 'ID',
|
||||||
[TableColumn.IMAGE]: '',
|
[TableColumn.IMAGE]: '',
|
||||||
[TableColumn.LAST_PLAYED]: i18n.t('table.column.lastPlayed', {
|
[TableColumn.LAST_PLAYED]: i18n.t('table.column.lastPlayed', {
|
||||||
|
|||||||
@@ -29,23 +29,25 @@ import { ItemListHandle, ItemTableListColumnConfig } from '/@/renderer/component
|
|||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export interface TableItemProps {
|
export interface TableItemProps {
|
||||||
|
cellPadding?: ItemTableListProps['cellPadding'];
|
||||||
columns: ItemTableListColumnConfig[];
|
columns: ItemTableListColumnConfig[];
|
||||||
data: unknown[];
|
data: ItemTableListProps['data'];
|
||||||
enableAlternateRowColors?: boolean;
|
enableAlternateRowColors?: ItemTableListProps['enableAlternateRowColors'];
|
||||||
enableExpansion?: boolean;
|
enableExpansion?: ItemTableListProps['enableExpansion'];
|
||||||
enableHeader?: boolean;
|
enableHeader?: ItemTableListProps['enableHeader'];
|
||||||
enableHorizontalBorders?: boolean;
|
enableHorizontalBorders?: ItemTableListProps['enableHorizontalBorders'];
|
||||||
enableRowHoverHighlight?: boolean;
|
enableRowHoverHighlight?: ItemTableListProps['enableRowHoverHighlight'];
|
||||||
enableSelection?: boolean;
|
enableSelection?: ItemTableListProps['enableSelection'];
|
||||||
enableVerticalBorders?: boolean;
|
enableVerticalBorders?: ItemTableListProps['enableVerticalBorders'];
|
||||||
getRowHeight: (index: number, cellProps: TableItemProps) => number;
|
getRowHeight: (index: number, cellProps: TableItemProps) => number;
|
||||||
internalState: ItemListStateActions;
|
internalState: ItemListStateActions;
|
||||||
itemType: LibraryItem;
|
itemType: ItemTableListProps['itemType'];
|
||||||
size?: 'compact' | 'default';
|
size?: ItemTableListProps['size'];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ItemTableListProps {
|
interface ItemTableListProps {
|
||||||
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
|
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
|
||||||
|
cellPadding?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
columns: ItemTableListColumnConfig[];
|
columns: ItemTableListColumnConfig[];
|
||||||
data: unknown[];
|
data: unknown[];
|
||||||
enableAlternateRowColors?: boolean;
|
enableAlternateRowColors?: boolean;
|
||||||
@@ -72,11 +74,12 @@ interface ItemTableListProps {
|
|||||||
onStartReached?: (index: number, internalState: ItemListStateActions) => void;
|
onStartReached?: (index: number, internalState: ItemListStateActions) => void;
|
||||||
ref?: Ref<ItemListHandle>;
|
ref?: Ref<ItemListHandle>;
|
||||||
rowHeight?: ((index: number, cellProps: TableItemProps) => number) | number;
|
rowHeight?: ((index: number, cellProps: TableItemProps) => number) | number;
|
||||||
size?: 'compact' | 'default';
|
size?: 'compact' | 'default' | 'large';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ItemTableList = ({
|
export const ItemTableList = ({
|
||||||
CellComponent,
|
CellComponent,
|
||||||
|
cellPadding = 'sm',
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
enableAlternateRowColors = false,
|
enableAlternateRowColors = false,
|
||||||
@@ -99,8 +102,8 @@ export const ItemTableList = ({
|
|||||||
size = 'default',
|
size = 'default',
|
||||||
}: ItemTableListProps) => {
|
}: ItemTableListProps) => {
|
||||||
const totalItemCount = data.length;
|
const totalItemCount = data.length;
|
||||||
const sortedColumns = useMemo(() => parseTableColumns(columns), [columns]);
|
const parsedColumns = useMemo(() => parseTableColumns(columns), [columns]);
|
||||||
const columnCount = sortedColumns.length;
|
const columnCount = parsedColumns.length;
|
||||||
|
|
||||||
const [centerContainerWidth, setCenterContainerWidth] = useState(0);
|
const [centerContainerWidth, setCenterContainerWidth] = useState(0);
|
||||||
|
|
||||||
@@ -127,14 +130,14 @@ export const ItemTableList = ({
|
|||||||
|
|
||||||
// Compute distributed widths: unpinned columns with autoWidth will share any remaining space
|
// Compute distributed widths: unpinned columns with autoWidth will share any remaining space
|
||||||
const calculatedColumnWidths = useMemo(() => {
|
const calculatedColumnWidths = useMemo(() => {
|
||||||
const baseWidths = sortedColumns.map((c) => c.width);
|
const baseWidths = parsedColumns.map((c) => c.width);
|
||||||
const distributed = baseWidths.slice();
|
const distributed = baseWidths.slice();
|
||||||
|
|
||||||
// Identify unpinned columns and auto-width candidates
|
// Identify unpinned columns and auto-width candidates
|
||||||
const unpinnedIndices: number[] = [];
|
const unpinnedIndices: number[] = [];
|
||||||
const autoUnpinnedIndices: number[] = [];
|
const autoUnpinnedIndices: number[] = [];
|
||||||
|
|
||||||
sortedColumns.forEach((col, idx) => {
|
parsedColumns.forEach((col, idx) => {
|
||||||
if (col.pinned === null) {
|
if (col.pinned === null) {
|
||||||
unpinnedIndices.push(idx);
|
unpinnedIndices.push(idx);
|
||||||
if (col.autoSize) {
|
if (col.autoSize) {
|
||||||
@@ -161,13 +164,13 @@ export const ItemTableList = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return distributed;
|
return distributed;
|
||||||
}, [sortedColumns, centerContainerWidth]);
|
}, [parsedColumns, centerContainerWidth]);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const columnWidth = (index: number, _cellProps: TableItemProps) =>
|
const columnWidth = (index: number, _cellProps: TableItemProps) =>
|
||||||
calculatedColumnWidths[index];
|
calculatedColumnWidths[index];
|
||||||
const pinnedLeftColumnCount = sortedColumns.filter((col) => col.pinned === 'left').length;
|
const pinnedLeftColumnCount = parsedColumns.filter((col) => col.pinned === 'left').length;
|
||||||
const pinnedRightColumnCount = sortedColumns.filter((col) => col.pinned === 'right').length;
|
const pinnedRightColumnCount = parsedColumns.filter((col) => col.pinned === 'right').length;
|
||||||
|
|
||||||
const pinnedRowCount = enableHeader ? 1 : 0;
|
const pinnedRowCount = enableHeader ? 1 : 0;
|
||||||
const totalRowCount = totalItemCount - pinnedRowCount;
|
const totalRowCount = totalItemCount - pinnedRowCount;
|
||||||
@@ -527,7 +530,7 @@ export const ItemTableList = ({
|
|||||||
|
|
||||||
const getRowHeight = useCallback(
|
const getRowHeight = useCallback(
|
||||||
(index: number, cellProps: TableItemProps) => {
|
(index: number, cellProps: TableItemProps) => {
|
||||||
const height = size === 'compact' ? 40 : 64;
|
const height = size === 'compact' ? 40 : size === 'large' ? 88 : 64;
|
||||||
|
|
||||||
const baseHeight =
|
const baseHeight =
|
||||||
typeof rowHeight === 'number' ? rowHeight : rowHeight?.(index, cellProps) || height;
|
typeof rowHeight === 'number' ? rowHeight : rowHeight?.(index, cellProps) || height;
|
||||||
@@ -657,7 +660,8 @@ export const ItemTableList = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const itemProps: TableItemProps = {
|
const itemProps: TableItemProps = {
|
||||||
columns: sortedColumns,
|
cellPadding,
|
||||||
|
columns: parsedColumns,
|
||||||
data,
|
data,
|
||||||
enableAlternateRowColors,
|
enableAlternateRowColors,
|
||||||
enableExpansion,
|
enableExpansion,
|
||||||
|
|||||||
Reference in New Issue
Block a user