diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx
index 94ae27a0..0a710921 100644
--- a/src/renderer/app.tsx
+++ b/src/renderer/app.tsx
@@ -93,7 +93,7 @@ export const App = () => {
useEffect(() => {
const root = document.documentElement;
- root.style.setProperty('--image-fit', nativeImageAspect ? 'scale-down' : 'cover');
+ root.style.setProperty('--image-fit', nativeImageAspect ? 'contain' : 'cover');
}, [nativeImageAspect]);
const providerValue = useMemo(() => {
diff --git a/src/renderer/components/card/card-rows.tsx b/src/renderer/components/card/card-rows.tsx
index 40b1fa99..50df66b4 100644
--- a/src/renderer/components/card/card-rows.tsx
+++ b/src/renderer/components/card/card-rows.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import formatDuration from 'format-duration';
import { generatePath } from 'react-router';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
@@ -6,6 +7,7 @@ import { Album, AlbumArtist, Artist, Playlist, Song } from '/@/renderer/api/type
import { Text } from '/@/renderer/components/text';
import { AppRoute } from '/@/renderer/router/routes';
import { CardRow } from '/@/renderer/types';
+import { formatDateAbsolute, formatDateRelative, formatRating } from '/@/renderer/utils/format';
const Row = styled.div<{ $secondary?: boolean }>`
width: 100%;
@@ -69,7 +71,10 @@ export const CardRows = ({ data, rows }: CardRowsProps) => {
)}
onClick={(e) => e.stopPropagation()}
>
- {row.arrayProperty && item[row.arrayProperty]}
+ {row.arrayProperty &&
+ (row.format
+ ? row.format(item)
+ : item[row.arrayProperty])}
))}
@@ -88,7 +93,8 @@ export const CardRows = ({ data, rows }: CardRowsProps) => {
overflow="hidden"
size={index > 0 ? 'sm' : 'md'}
>
- {row.arrayProperty && item[row.arrayProperty]}
+ {row.arrayProperty &&
+ (row.format ? row.format(item) : item[row.arrayProperty])}
))}
@@ -114,7 +120,7 @@ export const CardRows = ({ data, rows }: CardRowsProps) => {
)}
onClick={(e) => e.stopPropagation()}
>
- {data && data[row.property]}
+ {data && (row.format ? row.format(data) : data[row.property])}
) : (
{
overflow="hidden"
size={index > 0 ? 'sm' : 'md'}
>
- {data && data[row.property]}
+ {data && (row.format ? row.format(data) : data[row.property])}
)}
@@ -151,12 +157,15 @@ export const ALBUM_CARD_ROWS: { [key: string]: CardRow } = {
},
},
createdAt: {
+ format: (song) => formatDateAbsolute(song.createdAt),
property: 'createdAt',
},
duration: {
+ format: (album) => (album.duration === null ? null : formatDuration(album.duration)),
property: 'duration',
},
lastPlayedAt: {
+ format: (album) => formatDateRelative(album.lastPlayedAt),
property: 'lastPlayedAt',
},
name: {
@@ -170,6 +179,7 @@ export const ALBUM_CARD_ROWS: { [key: string]: CardRow } = {
property: 'playCount',
},
rating: {
+ format: (album) => formatRating(album),
property: 'userRating',
},
releaseDate: {
@@ -208,12 +218,15 @@ export const SONG_CARD_ROWS: { [key: string]: CardRow } = {
},
},
createdAt: {
+ format: (song) => formatDateAbsolute(song.createdAt),
property: 'createdAt',
},
duration: {
+ format: (song) => (song.duration === null ? null : formatDuration(song.duration)),
property: 'duration',
},
lastPlayedAt: {
+ format: (song) => formatDateRelative(song.lastPlayedAt),
property: 'lastPlayedAt',
},
name: {
@@ -227,6 +240,7 @@ export const SONG_CARD_ROWS: { [key: string]: CardRow } = {
property: 'playCount',
},
rating: {
+ format: (song) => formatRating(song),
property: 'userRating',
},
releaseDate: {
@@ -242,12 +256,14 @@ export const ALBUMARTIST_CARD_ROWS: { [key: string]: CardRow } = {
property: 'albumCount',
},
duration: {
+ format: (artist) => (artist.duration === null ? null : formatDuration(artist.duration)),
property: 'duration',
},
genres: {
property: 'genres',
},
lastPlayedAt: {
+ format: (artist) => formatDateRelative(artist.lastPlayedAt),
property: 'lastPlayedAt',
},
name: {
@@ -261,6 +277,7 @@ export const ALBUMARTIST_CARD_ROWS: { [key: string]: CardRow } = {
property: 'playCount',
},
rating: {
+ format: (artist) => formatRating(artist),
property: 'userRating',
},
songCount: {
@@ -270,6 +287,8 @@ export const ALBUMARTIST_CARD_ROWS: { [key: string]: CardRow } = {
export const PLAYLIST_CARD_ROWS: { [key: string]: CardRow } = {
duration: {
+ format: (playlist) =>
+ playlist.duration === null ? null : formatDuration(playlist.duration),
property: 'duration',
},
name: {
@@ -295,7 +314,4 @@ export const PLAYLIST_CARD_ROWS: { [key: string]: CardRow } = {
songCount: {
property: 'songCount',
},
- updatedAt: {
- property: 'songCount',
- },
};
diff --git a/src/renderer/components/virtual-table/index.tsx b/src/renderer/components/virtual-table/index.tsx
index 6428e8bc..95a1bdeb 100644
--- a/src/renderer/components/virtual-table/index.tsx
+++ b/src/renderer/components/virtual-table/index.tsx
@@ -15,8 +15,6 @@ 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 { useClickOutside, useMergedRef } from '@mantine/hooks';
-import dayjs from 'dayjs';
-import relativeTime from 'dayjs/plugin/relativeTime';
import formatDuration from 'format-duration';
import { AnimatePresence } from 'framer-motion';
import { generatePath } from 'react-router';
@@ -43,7 +41,7 @@ import { useFixedTableHeader } from '/@/renderer/components/virtual-table/hooks/
import { NoteCell } from '/@/renderer/components/virtual-table/cells/note-cell';
import { RowIndexCell } from '/@/renderer/components/virtual-table/cells/row-index-cell';
import i18n from '/@/i18n/i18n';
-import { formatSizeString } from '/@/renderer/utils/format-size-string';
+import { formatDateAbsolute, formatDateRelative, formatSizeString } from '/@/renderer/utils/format';
export * from './table-config-dropdown';
export * from './table-pagination';
@@ -64,8 +62,6 @@ const DummyHeader = styled.div<{ height?: number }>`
height: ${({ height }) => height || 36}px;
`;
-dayjs.extend(relativeTime);
-
const tableColumns: { [key: string]: ColDef } = {
actions: {
cellClass: 'ag-cell-favorite',
@@ -182,8 +178,7 @@ const tableColumns: { [key: string]: ColDef } = {
GenericTableHeader(params, { position: 'center' }),
headerName: i18n.t('table.column.dateAdded'),
suppressSizeToFit: true,
- valueFormatter: (params: ValueFormatterParams) =>
- params.value ? dayjs(params.value).format('MMM D, YYYY') : '',
+ valueFormatter: (params: ValueFormatterParams) => formatDateAbsolute(params.value),
valueGetter: (params: ValueGetterParams) =>
params.data ? params.data.createdAt : undefined,
width: 130,
@@ -225,8 +220,7 @@ const tableColumns: { [key: string]: ColDef } = {
headerComponent: (params: IHeaderParams) =>
GenericTableHeader(params, { position: 'center' }),
headerName: i18n.t('table.column.lastPlayed'),
- valueFormatter: (params: ValueFormatterParams) =>
- params.value ? dayjs(params.value).fromNow() : '',
+ valueFormatter: (params: ValueFormatterParams) => formatDateRelative(params.value),
valueGetter: (params: ValueGetterParams) =>
params.data ? params.data.lastPlayedAt : undefined,
width: 130,
@@ -258,8 +252,7 @@ const tableColumns: { [key: string]: ColDef } = {
GenericTableHeader(params, { position: 'center' }),
headerName: i18n.t('table.column.releaseDate'),
suppressSizeToFit: true,
- valueFormatter: (params: ValueFormatterParams) =>
- params.value ? dayjs(params.value).format('MMM D, YYYY') : '',
+ valueFormatter: (params: ValueFormatterParams) => formatDateAbsolute(params.value),
valueGetter: (params: ValueGetterParams) =>
params.data ? params.data.releaseDate : undefined,
width: 130,
diff --git a/src/renderer/features/item-details/components/item-details-modal.tsx b/src/renderer/features/item-details/components/item-details-modal.tsx
index 5e36014a..c0553fd3 100644
--- a/src/renderer/features/item-details/components/item-details-modal.tsx
+++ b/src/renderer/features/item-details/components/item-details-modal.tsx
@@ -1,13 +1,11 @@
import { Group, Table } from '@mantine/core';
-import dayjs from 'dayjs';
import { RiCheckFill, RiCloseFill } from 'react-icons/ri';
import { TFunction, useTranslation } from 'react-i18next';
import { ReactNode } from 'react';
import { Album, AlbumArtist, AnyLibraryItem, LibraryItem, Song } from '/@/renderer/api/types';
-import { formatDurationString } from '/@/renderer/utils';
-import { formatSizeString } from '/@/renderer/utils/format-size-string';
+import { formatDurationString, formatSizeString } from '/@/renderer/utils';
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
-import { Rating, Spoiler, Text } from '/@/renderer/components';
+import { Spoiler, Text } from '/@/renderer/components';
import { sanitize } from '/@/renderer/utils/sanitize';
import { SongPath } from '/@/renderer/features/item-details/components/song-path';
import { generatePath } from 'react-router';
@@ -15,6 +13,7 @@ import { Link } from 'react-router-dom';
import { AppRoute } from '/@/renderer/router/routes';
import { Separator } from '/@/renderer/components/separator';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
+import { formatDateRelative, formatRating } from '/@/renderer/utils/format';
export type ItemDetailsModalProps = {
item: Album | AlbumArtist | Song;
@@ -82,8 +81,6 @@ const formatArtists = (isAlbumArtist: boolean) => (item: Album | Song) =>
const formatComment = (item: Album | Song) =>
item.comment ? {replaceURLWithHTMLLinks(item.comment)} : null;
-const formatDate = (key: string | null) => (key ? dayjs(key).fromNow() : '');
-
const FormatGenre = (item: Album | AlbumArtist | Song) => {
const genreRoute = useGenreRoute();
@@ -104,14 +101,6 @@ const FormatGenre = (item: Album | AlbumArtist | Song) => {
));
};
-const formatRating = (item: Album | AlbumArtist | Song) =>
- item.userRating !== null ? (
-
- ) : null;
-
const BoolField = (key: boolean) =>
key ? : ;
@@ -139,11 +128,11 @@ const AlbumPropertyMapping: ItemDetailRow[] = [
{ key: 'playCount', label: 'filter.playCount' },
{
label: 'filter.lastPlayed',
- render: (song) => formatDate(song.lastPlayedAt),
+ render: (song) => formatDateRelative(song.lastPlayedAt),
},
{
label: 'common.modified',
- render: (song) => formatDate(song.updatedAt),
+ render: (song) => formatDateRelative(song.updatedAt),
},
{ label: 'filter.comment', render: formatComment },
{
@@ -178,7 +167,7 @@ const AlbumArtistPropertyMapping: ItemDetailRow[] = [
{ key: 'playCount', label: 'filter.playCount' },
{
label: 'filter.lastPlayed',
- render: (song) => formatDate(song.lastPlayedAt),
+ render: (song) => formatDateRelative(song.lastPlayedAt),
},
{
label: 'common.mbid',
@@ -256,11 +245,11 @@ const SongPropertyMapping: ItemDetailRow[] = [
{ key: 'playCount', label: 'filter.playCount' },
{
label: 'filter.lastPlayed',
- render: (song) => formatDate(song.lastPlayedAt),
+ render: (song) => formatDateRelative(song.lastPlayedAt),
},
{
label: 'common.modified',
- render: (song) => formatDate(song.updatedAt),
+ render: (song) => formatDateRelative(song.updatedAt),
},
{
label: 'common.albumGain',
diff --git a/src/renderer/types.ts b/src/renderer/types.ts
index 4d975548..f40863a4 100644
--- a/src/renderer/types.ts
+++ b/src/renderer/types.ts
@@ -1,3 +1,4 @@
+import { ReactNode } from 'react';
import { ServerFeatures } from '/@/renderer/api/features-types';
import {
Album,
@@ -37,6 +38,7 @@ export type TableType =
export type CardRow = {
arrayProperty?: string;
+ format?: (value: T) => ReactNode;
property: keyof T;
route?: CardRoute;
};
diff --git a/src/renderer/utils/format-duration-string.ts b/src/renderer/utils/format-duration-string.ts
deleted file mode 100644
index 8edbe958..00000000
--- a/src/renderer/utils/format-duration-string.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import formatDuration from 'format-duration';
-
-export const formatDurationString = (duration: number) => {
- const rawDuration = formatDuration(duration).split(':');
-
- let string;
-
- switch (rawDuration.length) {
- case 1:
- string = `${rawDuration[0]} sec`;
- break;
- case 2:
- string = `${rawDuration[0]} min ${rawDuration[1]} sec`;
- break;
- case 3:
- string = `${rawDuration[0]} hr ${rawDuration[1]} min ${rawDuration[2]} sec`;
- break;
- case 4:
- string = `${rawDuration[0]} day ${rawDuration[1]} hr ${rawDuration[2]} min ${rawDuration[3]} sec`;
- break;
- }
-
- return string;
-};
diff --git a/src/renderer/utils/format-size-string.ts b/src/renderer/utils/format-size-string.ts
deleted file mode 100644
index e24f300e..00000000
--- a/src/renderer/utils/format-size-string.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-const SIZES = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
-
-export const formatSizeString = (size?: number): string => {
- let count = 0;
- let finalSize = size ?? 0;
- while (finalSize > 1024) {
- finalSize /= 1024;
- count += 1;
- }
-
- return `${finalSize.toFixed(2)} ${SIZES[count]}`;
-};
diff --git a/src/renderer/utils/format.tsx b/src/renderer/utils/format.tsx
new file mode 100644
index 00000000..572905d2
--- /dev/null
+++ b/src/renderer/utils/format.tsx
@@ -0,0 +1,56 @@
+import dayjs from 'dayjs';
+import relativeTime from 'dayjs/plugin/relativeTime';
+import formatDuration from 'format-duration';
+import type { Album, AlbumArtist, Song } from '/@/renderer/api/types';
+import { Rating } from '/@/renderer/components/rating';
+
+dayjs.extend(relativeTime);
+
+export const formatDateAbsolute = (key: string | null) =>
+ key ? dayjs(key).format('MMM D, YYYY') : '';
+
+export const formatDateRelative = (key: string | null) => (key ? dayjs(key).fromNow() : '');
+
+export const formatDurationString = (duration: number) => {
+ const rawDuration = formatDuration(duration).split(':');
+
+ let string;
+
+ switch (rawDuration.length) {
+ case 1:
+ string = `${rawDuration[0]} sec`;
+ break;
+ case 2:
+ string = `${rawDuration[0]} min ${rawDuration[1]} sec`;
+ break;
+ case 3:
+ string = `${rawDuration[0]} hr ${rawDuration[1]} min ${rawDuration[2]} sec`;
+ break;
+ case 4:
+ string = `${rawDuration[0]} day ${rawDuration[1]} hr ${rawDuration[2]} min ${rawDuration[3]} sec`;
+ break;
+ }
+
+ return string;
+};
+
+export const formatRating = (item: Album | AlbumArtist | Song) =>
+ item.userRating !== null ? (
+
+ ) : null;
+
+const SIZES = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
+
+export const formatSizeString = (size?: number): string => {
+ let count = 0;
+ let finalSize = size ?? 0;
+ while (finalSize > 1024) {
+ finalSize /= 1024;
+ count += 1;
+ }
+
+ return `${finalSize.toFixed(2)} ${SIZES[count]}`;
+};
diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts
index 83770108..46e06c13 100644
--- a/src/renderer/utils/index.ts
+++ b/src/renderer/utils/index.ts
@@ -5,6 +5,6 @@ export * from './constrain-sidebar-width';
export * from './title-case';
export * from './get-header-color';
export * from './parse-search-params';
-export * from './format-duration-string';
export * from './rgb-to-rgba';
export * from './sentence-case';
+export * from './format';