diff --git a/src/renderer/components/virtual-table/headers/generic-table-header.tsx b/src/renderer/components/virtual-table/headers/generic-table-header.tsx index d718a871..ddbd67f9 100644 --- a/src/renderer/components/virtual-table/headers/generic-table-header.tsx +++ b/src/renderer/components/virtual-table/headers/generic-table-header.tsx @@ -3,6 +3,7 @@ import type { IHeaderParams } from '@ag-grid-community/core'; import { AiOutlineNumber } from 'react-icons/ai'; import { FiClock } from 'react-icons/fi'; import styled from 'styled-components'; +import { _Text } from '/@/renderer/components/text'; type Presets = 'duration' | 'rowIndex'; @@ -12,7 +13,7 @@ type Options = { preset?: Presets; }; -const HeaderWrapper = styled.div<{ position: 'left' | 'center' | 'right' }>` +const HeaderWrapper = styled.div<{ position: Options['position'] }>` display: flex; justify-content: ${(props) => props.position === 'right' @@ -25,6 +26,19 @@ const HeaderWrapper = styled.div<{ position: 'left' | 'center' | 'right' }>` text-transform: uppercase; `; +const TextHeaderWrapper = styled(_Text)<{ position: Options['position'] }>` + width: 100%; + color: var(--ag-header-foreground-color); + font-weight: 500; + text-align: ${(props) => + props.position === 'right' + ? 'flex-end' + : props.position === 'center' + ? 'center' + : 'flex-start'}; + text-transform: uppercase; +`; + const headerPresets = { duration: , rowIndex: }; export const GenericTableHeader = ( @@ -32,10 +46,18 @@ export const GenericTableHeader = ( { preset, children, position }: Options, ) => { if (preset) { - return {headerPresets[preset]}; + return {headerPresets[preset]}; } - return {children || displayName}; + return ( + + {children || displayName} + + ); }; GenericTableHeader.defaultProps = { diff --git a/src/renderer/components/virtual-table/index.tsx b/src/renderer/components/virtual-table/index.tsx index 03242535..d4a48198 100644 --- a/src/renderer/components/virtual-table/index.tsx +++ b/src/renderer/components/virtual-table/index.tsx @@ -11,6 +11,8 @@ import type { AgGridReactProps } from '@ag-grid-community/react'; import { AgGridReact } from '@ag-grid-community/react'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { useMergedRef } from '@mantine/hooks'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; import formatDuration from 'format-duration'; import { generatePath } from 'react-router'; import styled from 'styled-components'; @@ -34,6 +36,8 @@ const TableWrapper = styled.div` height: 100%; `; +dayjs.extend(relativeTime); + const tableColumns: { [key: string]: ColDef } = { album: { cellRenderer: (params: ICellRendererParams) => @@ -49,6 +53,7 @@ const tableColumns: { [key: string]: ColDef } = { value: params.data?.album, } : undefined, + width: 200, }, albumArtist: { cellRenderer: AlbumArtistCell, @@ -56,6 +61,7 @@ const tableColumns: { [key: string]: ColDef } = { headerName: 'Album Artist', valueGetter: (params: ValueGetterParams) => params.data ? params.data.albumArtists : undefined, + width: 150, }, albumCount: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), @@ -63,36 +69,42 @@ const tableColumns: { [key: string]: ColDef } = { field: 'albumCount', headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), headerName: 'Albums', + suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.albumCount : undefined), + width: 80, }, artist: { cellRenderer: ArtistCell, colId: TableColumn.ARTIST, headerName: 'Artist', valueGetter: (params: ValueGetterParams) => (params.data ? params.data.artists : undefined), + width: 150, }, biography: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }), colId: TableColumn.BIOGRAPHY, field: 'biography', - headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }), headerName: 'Biography', - valueGetter: (params: ValueGetterParams) => (params.data ? params.data.biography : undefined), + valueGetter: (params: ValueGetterParams) => (params.data ? params.data.biography : ''), + width: 200, }, bitRate: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }), colId: TableColumn.BIT_RATE, field: 'bitRate', - headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }), + suppressSizeToFit: true, valueFormatter: (params: ValueFormatterParams) => `${params.value} kbps`, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.bitRate : undefined), + width: 90, }, bpm: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), colId: TableColumn.BPM, headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), headerName: 'BPM', + suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.bpm : undefined), + width: 60, }, channels: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), @@ -100,60 +112,70 @@ const tableColumns: { [key: string]: ColDef } = { field: 'channels', headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), valueGetter: (params: ValueGetterParams) => (params.data ? params.data.channels : undefined), + width: 100, }, comment: { cellRenderer: GenericCell, colId: TableColumn.COMMENT, headerName: 'Note', valueGetter: (params: ValueGetterParams) => (params.data ? params.data.comment : undefined), + width: 150, }, dateAdded: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }), colId: TableColumn.DATE_ADDED, field: 'createdAt', - headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }), headerName: 'Date Added', - valueFormatter: (params: ValueFormatterParams) => params.value?.split('T')[0], + suppressSizeToFit: true, + valueFormatter: (params: ValueFormatterParams) => + params.value ? dayjs(params.value).format('MMM D, YYYY') : '', valueGetter: (params: ValueGetterParams) => (params.data ? params.data.createdAt : undefined), + width: 110, }, discNumber: { - cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), + cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'right' }), colId: TableColumn.DISC_NUMBER, field: 'discNumber', - headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), + headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'right' }), headerName: 'Disc', - initialWidth: 75, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.discNumber : undefined), + width: 60, }, duration: { - cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), + cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'right' }), colId: TableColumn.DURATION, field: 'duration', headerComponent: (params: IHeaderParams) => - GenericTableHeader(params, { position: 'center', preset: 'duration' }), - initialWidth: 100, + GenericTableHeader(params, { position: 'right', preset: 'duration' }), + suppressSizeToFit: true, valueFormatter: (params: ValueFormatterParams) => formatDuration(params.value * 1000), valueGetter: (params: ValueGetterParams) => (params.data ? params.data.duration : undefined), + width: 60, }, genre: { cellRenderer: GenreCell, colId: TableColumn.GENRE, headerName: 'Genre', valueGetter: (params: ValueGetterParams) => (params.data ? params.data.genres : undefined), + width: 100, }, lastPlayedAt: { cellRenderer: GenericCell, colId: TableColumn.LAST_PLAYED, headerName: 'Last Played', + valueFormatter: (params: ValueFormatterParams) => + params.value ? dayjs(params.value).fromNow() : '', valueGetter: (params: ValueGetterParams) => params.data ? params.data.lastPlayedAt : undefined, + width: 130, }, path: { cellRenderer: GenericCell, colId: TableColumn.PATH, headerName: 'Path', valueGetter: (params: ValueGetterParams) => (params.data ? params.data.path : undefined), + width: 200, }, playCount: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), @@ -161,16 +183,20 @@ const tableColumns: { [key: string]: ColDef } = { field: 'playCount', headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), headerName: 'Plays', + suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.playCount : undefined), + width: 90, }, releaseDate: { - cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), + cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }), colId: TableColumn.RELEASE_DATE, field: 'releaseDate', - headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), headerName: 'Release Date', - valueFormatter: (params: ValueFormatterParams) => params.value?.split('T')[0], + suppressSizeToFit: true, + valueFormatter: (params: ValueFormatterParams) => + params.value ? dayjs(params.value).format('MMM D, YYYY') : '', valueGetter: (params: ValueGetterParams) => (params.data ? params.data.releaseDate : undefined), + width: 130, }, releaseYear: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), @@ -178,26 +204,29 @@ const tableColumns: { [key: string]: ColDef } = { field: 'releaseYear', headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), headerName: 'Year', + suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.releaseYear : undefined), + width: 60, }, rowIndex: { - cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }), + cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'right' }), colId: TableColumn.ROW_INDEX, headerComponent: (params: IHeaderParams) => - GenericTableHeader(params, { position: 'left', preset: 'rowIndex' }), - initialWidth: 50, + GenericTableHeader(params, { position: 'right', preset: 'rowIndex' }), suppressSizeToFit: true, valueGetter: (params) => { return (params.node?.rowIndex || 0) + 1; }, + width: 65, }, songCount: { cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), colId: TableColumn.SONG_COUNT, field: 'songCount', - headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), headerName: 'Songs', + suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.songCount : undefined), + width: 80, }, title: { cellRenderer: (params: ICellRendererParams) => @@ -206,6 +235,7 @@ const tableColumns: { [key: string]: ColDef } = { field: 'name', headerName: 'Title', valueGetter: (params: ValueGetterParams) => (params.data ? params.data.name : undefined), + width: 250, }, titleCombined: { cellRenderer: CombinedTitleCell, @@ -224,16 +254,17 @@ const tableColumns: { [key: string]: ColDef } = { type: params.data?.type, } : undefined, + width: 250, }, trackNumber: { - cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), + cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'right' }), colId: TableColumn.TRACK_NUMBER, field: 'trackNumber', - headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), + headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'right' }), headerName: 'Track', - initialWidth: 75, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.trackNumber : undefined), + width: 80, }, }; diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index bc75edba..01912c04 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -42,13 +42,6 @@ const ContentContainer = styled.div` .ag-header-container { z-index: 1000; } - - .ag-header-cell-resize { - top: 25%; - width: 7px; - height: 50%; - background-color: rgb(70, 70, 70, 20%); - } `; interface AlbumDetailContentProps { diff --git a/src/renderer/features/playlists/components/playlist-detail-content.tsx b/src/renderer/features/playlists/components/playlist-detail-content.tsx index e72983a3..ac52d800 100644 --- a/src/renderer/features/playlists/components/playlist-detail-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-content.tsx @@ -28,13 +28,6 @@ const ContentContainer = styled.div` .ag-header-container { z-index: 1000; } - - .ag-header-cell-resize { - top: 25%; - width: 7px; - height: 50%; - background-color: rgb(70, 70, 70, 20%); - } `; interface PlaylistDetailContentProps { diff --git a/src/renderer/themes/default.scss b/src/renderer/themes/default.scss index a273b68d..7f60590b 100644 --- a/src/renderer/themes/default.scss +++ b/src/renderer/themes/default.scss @@ -116,10 +116,27 @@ --ag-selected-row-background-color: rgba(100, 100, 100, 0.4); } + .ag-header { + border-bottom: 1px solid rgb(50, 50, 50, 0.5); + margin-bottom: 1rem; + } + + .ag-header-cell-comp-wrapper { + margin: 0.5rem 0.5rem; + } + + .ag-header-cell, + .ag-header-group-cell { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + .ag-header-cell-resize { - background-color: rgb(70, 70, 70, 0.4); - height: 50%; - top: 25%; + background-color: transparent; + } + + .ag-header:hover .ag-header-cell-resize { + border-left: 2px rgb(70, 70, 70, 1) solid; width: 7px; }