diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index d54287ca0..3b717c657 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -275,10 +275,10 @@
"explicitStatus": "$t(common.explicitStatus)"
},
"datetime": {
- "minuteShort": "min",
- "secondShort": "sec",
- "hourShort": "hr",
- "dayShort": "day"
+ "minuteShort": "m",
+ "secondShort": "s",
+ "hourShort": "h",
+ "dayShort": "d"
},
"filterOperator": {
"after": "is after",
diff --git a/src/renderer/components/item-card/item-card.tsx b/src/renderer/components/item-card/item-card.tsx
index 7c7dc8917..4bc1b308a 100644
--- a/src/renderer/components/item-card/item-card.tsx
+++ b/src/renderer/components/item-card/item-card.tsx
@@ -1,11 +1,11 @@
import clsx from 'clsx';
-import formatDuration from 'format-duration';
import { AnimatePresence } from 'motion/react';
import { Fragment, memo, ReactNode, useState } from 'react';
import { generatePath, Link } from 'react-router';
import styles from './item-card.module.css';
+import i18n from '/@/i18n/i18n';
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
import { ItemImage } from '/@/renderer/components/item-image/item-image';
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
@@ -19,7 +19,15 @@ import { ItemControls } from '/@/renderer/components/item-list/types';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
import { AppRoute } from '/@/renderer/router/routes';
import { useGeneralSettings } from '/@/renderer/store';
-import { formatDateAbsolute, formatDateRelative, formatRating } from '/@/renderer/utils/format';
+import {
+ formatDateAbsolute,
+ formatDateAbsoluteUTC,
+ formatDateRelative,
+ formatDurationString,
+ formatRating,
+} from '/@/renderer/utils/format';
+import { Group } from '/@/shared/components/group/group';
+import { Icon } from '/@/shared/components/icon/icon';
import { Separator } from '/@/shared/components/separator/separator';
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Text } from '/@/shared/components/text/text';
@@ -991,7 +999,7 @@ export const getDataRows = (): DataRow[] => {
{
format: (data) => {
if ('duration' in data && data.duration !== null) {
- return formatDuration(data.duration * 1000);
+ return formatDurationString(data.duration);
}
return '';
},
@@ -1009,7 +1017,7 @@ export const getDataRows = (): DataRow[] => {
{
format: (data) => {
if ('releaseDate' in data && data.releaseDate) {
- return data.releaseDate;
+ return formatDateAbsoluteUTC(data.releaseDate);
}
return '';
},
@@ -1027,7 +1035,12 @@ export const getDataRows = (): DataRow[] => {
{
format: (data) => {
if ('lastPlayedAt' in data && data.lastPlayedAt) {
- return formatDateRelative(data.lastPlayedAt);
+ return (
+
+
+ {formatDateRelative(data.lastPlayedAt)}
+
+ );
}
return '';
},
@@ -1036,7 +1049,7 @@ export const getDataRows = (): DataRow[] => {
{
format: (data) => {
if ('playCount' in data && data.playCount !== null) {
- return String(data.playCount);
+ return i18n.t('entity.play', { count: data.playCount });
}
return '';
},
@@ -1085,7 +1098,7 @@ export const getDataRows = (): DataRow[] => {
{
format: (data) => {
if ('songCount' in data && data.songCount !== null) {
- return String(data.songCount);
+ return i18n.t('entity.trackWithCount', { count: data.songCount });
}
return '';
},
diff --git a/src/renderer/components/item-list/item-table-list/columns/title-combined-column.tsx b/src/renderer/components/item-list/item-table-list/columns/title-combined-column.tsx
index 32c89f467..070ea6d65 100644
--- a/src/renderer/components/item-list/item-table-list/columns/title-combined-column.tsx
+++ b/src/renderer/components/item-list/item-table-list/columns/title-combined-column.tsx
@@ -74,8 +74,8 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
};
const artists = useMemo(() => {
- if (row && 'artists' in row && Array.isArray(row.artists)) {
- return (row.artists as RelatedAlbumArtist[]).map((artist) => {
+ if (row && 'artists' in item && Array.isArray(item.artists)) {
+ return (item.artists as RelatedAlbumArtist[]).map((artist) => {
const path = generatePath(AppRoute.LIBRARY_ARTISTS_DETAIL, {
artistId: artist.id,
});
@@ -83,9 +83,9 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
});
}
return [];
- }, [row]);
+ }, [item, row]);
- if (row && 'name' in row && 'imageUrl' in row && 'artists' in row) {
+ if (item && 'name' in item && 'imageUrl' in item && 'artists' in item) {
const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (props.data[props.rowIndex] as any).id as string);
@@ -143,7 +143,7 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
})}
>
- {row.name as string}
+ {item.name as string}
{artists.map((artist, index) => (
diff --git a/src/renderer/components/item-list/item-table-list/default-columns.ts b/src/renderer/components/item-list/item-table-list/default-columns.ts
index 9a781d33a..e5c058719 100644
--- a/src/renderer/components/item-list/item-table-list/default-columns.ts
+++ b/src/renderer/components/item-list/item-table-list/default-columns.ts
@@ -18,34 +18,34 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
autoSize: false,
isEnabled: true,
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }),
- pinned: 'left',
+ pinned: null,
value: TableColumn.ROW_INDEX,
- width: 80,
+ width: 60,
},
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }),
- pinned: 'left',
+ pinned: null,
value: TableColumn.IMAGE,
width: 70,
},
{
align: 'start',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }),
- pinned: 'left',
+ pinned: null,
value: TableColumn.TITLE,
width: 300,
},
{
align: 'start',
autoSize: false,
- isEnabled: false,
+ isEnabled: true,
label: i18n.t('table.config.label.titleCombined', { postProcess: 'titleCase' }),
- pinned: 'left',
+ pinned: null,
value: TableColumn.TITLE_COMBINED,
width: 300,
},
@@ -61,7 +61,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'start',
autoSize: false,
- isEnabled: false,
+ isEnabled: true,
label: i18n.t('table.config.label.album', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.ALBUM,
@@ -70,7 +70,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'start',
autoSize: true,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.albumArtist', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.ALBUM_ARTIST,
@@ -115,7 +115,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.releaseDate', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.RELEASE_DATE,
@@ -178,7 +178,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.lastPlayed', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.LAST_PLAYED,
@@ -214,7 +214,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.dateAdded', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.DATE_ADDED,
@@ -232,7 +232,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.playCount', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.PLAY_COUNT,
@@ -252,7 +252,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
autoSize: false,
isEnabled: true,
label: i18n.t('table.config.label.favorite', { postProcess: 'titleCase' }),
- pinned: 'right',
+ pinned: null,
value: TableColumn.USER_FAVORITE,
width: 60,
},
@@ -268,9 +268,9 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }),
- pinned: 'right',
+ pinned: null,
value: TableColumn.ACTIONS,
width: 60,
},
@@ -284,34 +284,34 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
autoSize: false,
isEnabled: true,
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }),
- pinned: 'left',
+ pinned: null,
value: TableColumn.ROW_INDEX,
- width: 80,
+ width: 60,
},
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }),
- pinned: 'left',
+ pinned: null,
value: TableColumn.IMAGE,
width: 70,
},
{
align: 'start',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }),
- pinned: 'left',
+ pinned: null,
value: TableColumn.TITLE,
width: 300,
},
{
align: 'start',
autoSize: false,
- isEnabled: false,
+ isEnabled: true,
label: i18n.t('table.config.label.titleCombined', { postProcess: 'titleCase' }),
- pinned: 'left',
+ pinned: null,
value: TableColumn.TITLE_COMBINED,
width: 300,
},
@@ -327,7 +327,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'start',
autoSize: true,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.albumArtist', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.ALBUM_ARTIST,
@@ -381,7 +381,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.releaseDate', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.RELEASE_DATE,
@@ -390,7 +390,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.lastPlayed', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.LAST_PLAYED,
@@ -399,7 +399,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.dateAdded', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.DATE_ADDED,
@@ -408,7 +408,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.playCount', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.PLAY_COUNT,
@@ -419,7 +419,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
autoSize: false,
isEnabled: true,
label: i18n.t('table.config.label.favorite', { postProcess: 'titleCase' }),
- pinned: 'right',
+ pinned: null,
value: TableColumn.USER_FAVORITE,
width: 60,
},
@@ -435,9 +435,9 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }),
- pinned: 'right',
+ pinned: null,
value: TableColumn.ACTIONS,
width: 60,
},
@@ -451,7 +451,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.ROW_INDEX,
- width: 80,
+ width: 60,
},
{
align: 'center',
@@ -539,7 +539,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
autoSize: false,
isEnabled: true,
label: i18n.t('table.config.label.favorite', { postProcess: 'titleCase' }),
- pinned: 'right',
+ pinned: null,
value: TableColumn.USER_FAVORITE,
width: 60,
},
@@ -555,9 +555,9 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }),
- pinned: 'right',
+ pinned: null,
value: TableColumn.ACTIONS,
width: 60,
},
@@ -571,7 +571,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.ROW_INDEX,
- width: 80,
+ width: 60,
},
{
align: 'center',
@@ -630,9 +630,9 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }),
- pinned: 'right',
+ pinned: null,
value: TableColumn.ACTIONS,
width: 60,
},
@@ -646,7 +646,7 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.ROW_INDEX,
- width: 80,
+ width: 60,
},
{
align: 'start',
@@ -678,9 +678,9 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'center',
autoSize: false,
- isEnabled: true,
+ isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }),
- pinned: 'right',
+ pinned: null,
value: TableColumn.ACTIONS,
width: 60,
},
@@ -728,6 +728,15 @@ export const pickTableColumns = (options: {
const enabledSet = new Set(enabledColumns);
const remaining = columns.filter((col) => !enabledSet.has(col.value));
columnsToProcess = [...columnsToProcess, ...remaining];
+ } else {
+ // When pickColumns is provided, include pickColumns that aren't in enabledColumns
+ // so they can be added as disabled entries
+ const enabledSet = new Set(enabledColumns);
+ const pickColumnsNotEnabled = pickColumns
+ .filter((col) => !enabledSet.has(col))
+ .map((col) => columnMap.get(col))
+ .filter((col): col is DefaultTableColumn => col !== undefined);
+ columnsToProcess = [...columnsToProcess, ...pickColumnsNotEnabled];
}
} else {
columnsToProcess = columns;
diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts
index 45bcbf916..cbda16253 100644
--- a/src/renderer/store/settings.store.ts
+++ b/src/renderer/store/settings.store.ts
@@ -969,13 +969,11 @@ const initialState: SettingsState = {
[TableColumn.TITLE]: 400,
[TableColumn.TRACK_NUMBER]: 50,
[TableColumn.USER_FAVORITE]: 60,
- [TableColumn.USER_RATING]: 100,
},
enabledColumns: [
TableColumn.TRACK_NUMBER,
TableColumn.TITLE,
TableColumn.DURATION,
- TableColumn.USER_RATING,
TableColumn.USER_FAVORITE,
],
}),
@@ -1035,10 +1033,11 @@ const initialState: SettingsState = {
TableColumn.BIT_RATE,
TableColumn.BPM,
TableColumn.DATE_ADDED,
- TableColumn.DURATION,
TableColumn.GENRE,
TableColumn.PLAY_COUNT,
TableColumn.SONG_COUNT,
+ TableColumn.RELEASE_DATE,
+ TableColumn.LAST_PLAYED,
TableColumn.YEAR,
],
}),
@@ -1047,7 +1046,7 @@ const initialState: SettingsState = {
itemsPerPage: 100,
pagination: ListPaginationType.INFINITE,
table: {
- autoFitColumns: false,
+ autoFitColumns: true,
columns: ALBUM_TABLE_COLUMNS.map((column) => ({
align: column.align,
autoSize: column.autoSize,
@@ -1085,7 +1084,7 @@ const initialState: SettingsState = {
itemsPerPage: 100,
pagination: ListPaginationType.INFINITE,
table: {
- autoFitColumns: false,
+ autoFitColumns: true,
columns: pickTableColumns({
autoSizeColumns: [TableColumn.TITLE],
columns: ALBUM_ARTIST_TABLE_COLUMNS,
@@ -1093,12 +1092,7 @@ const initialState: SettingsState = {
TableColumn.ROW_INDEX,
TableColumn.IMAGE,
TableColumn.TITLE,
- TableColumn.ALBUM_COUNT,
- TableColumn.SONG_COUNT,
- TableColumn.PLAY_COUNT,
- TableColumn.LAST_PLAYED,
TableColumn.USER_FAVORITE,
- TableColumn.USER_RATING,
],
}),
enableAlternateRowColors: false,
diff --git a/src/renderer/utils/format.tsx b/src/renderer/utils/format.tsx
index 2f00b4ee0..c449f85a9 100644
--- a/src/renderer/utils/format.tsx
+++ b/src/renderer/utils/format.tsx
@@ -99,20 +99,25 @@ export const formatDateRelative = (key: null | string) => (key ? dayjs(key).from
export const formatDurationString = (duration: number) => {
const rawDuration = formatDuration(duration, { leading: false }).split(':');
- let string;
+ const formattedDuration = rawDuration.map((part) => {
+ // Remove leading zero
+ return part.replace(/^0/, '');
+ });
+
+ let string: string = '';
switch (rawDuration.length) {
case 1:
- string = `${rawDuration[0]} ${i18n.t('datetime.secondShort')}`;
+ string = `${formattedDuration[0]}${i18n.t('datetime.secondShort')}`;
break;
case 2:
- string = `${rawDuration[0]} ${i18n.t('datetime.minuteShort')} ${rawDuration[1]} ${i18n.t('datetime.secondShort')}`;
+ string = `${formattedDuration[0]}${i18n.t('datetime.minuteShort')} ${formattedDuration[1]}${i18n.t('datetime.secondShort')}`;
break;
case 3:
- string = `${rawDuration[0]} ${i18n.t('datetime.hourShort')} ${rawDuration[1]} ${i18n.t('datetime.minuteShort')} ${rawDuration[2]} ${i18n.t('datetime.secondShort')}`;
+ string = `${formattedDuration[0]}${i18n.t('datetime.hourShort')} ${formattedDuration[1]}${i18n.t('datetime.minuteShort')} ${formattedDuration[2]}${i18n.t('datetime.secondShort')}`;
break;
case 4:
- string = `${rawDuration[0]} ${i18n.t('datetime.dayShort')} ${rawDuration[1]} ${i18n.t('datetime.hourShort')} ${rawDuration[2]} ${i18n.t('datetime.minuteShort')} ${rawDuration[3]} ${i18n.t('datetime.secondShort')}`;
+ string = `${formattedDuration[0]}${i18n.t('datetime.dayShort')} ${formattedDuration[1]}${i18n.t('datetime.hourShort')} ${formattedDuration[2]}${i18n.t('datetime.minuteShort')} ${formattedDuration[3]}${i18n.t('datetime.secondShort')}`;
break;
}
diff --git a/src/shared/components/icon/icon.tsx b/src/shared/components/icon/icon.tsx
index 969ce13bb..d17cce019 100644
--- a/src/shared/components/icon/icon.tsx
+++ b/src/shared/components/icon/icon.tsx
@@ -49,6 +49,7 @@ import {
LuGripVertical,
LuHardDrive,
LuHash,
+ LuHeadphones,
LuHeart,
LuHeartCrack,
LuImage,
@@ -180,6 +181,7 @@ export const AppIcon = {
itemAlbum: LuDisc3,
itemSong: LuMusic,
keyboard: LuKeyboard,
+ lastPlayed: LuHeadphones,
layoutGrid: LuLayoutGrid,
layoutList: LuList,
layoutTable: LuTable,