mirror of
https://github.com/jeffvli/feishin.git
synced 2026-08-08 05:12:57 +02:00
Expanded date fields (#2276)
* added missing date/year fields for other song references * added year table column & sorting * added navidrome track date view for grid, detail & table. * update table column config * Displays date & release date using formatPartialIsoDateUTC, and displays respective year field if no date present added releaseYear player filter Made fullscreen player show formatted date fixed minor year/release year value mismatches added subsonic date/year field to enable the fields in folder view for navidrome using subsonic year field --------- Co-authored-by: Jeff <42182408+jeffvli@users.noreply.github.com>
This commit is contained in:
@@ -268,6 +268,7 @@
|
||||
"comment": "Comment",
|
||||
"communityRating": "Community rating",
|
||||
"criticRating": "Critic rating",
|
||||
"date": "Date",
|
||||
"dateAdded": "Date added",
|
||||
"disc": "Disc",
|
||||
"duration": "Duration",
|
||||
@@ -300,6 +301,7 @@
|
||||
"title": "Title",
|
||||
"toYear": "To year",
|
||||
"trackNumber": "Track",
|
||||
"year": "Year",
|
||||
"explicitStatus": "$t(common.explicitStatus)"
|
||||
},
|
||||
"datetime": {
|
||||
@@ -1293,6 +1295,7 @@
|
||||
"comment": "Comment",
|
||||
"dateAdded": "Date Added",
|
||||
"discNumber": "Disc",
|
||||
"date": "Date",
|
||||
"favorite": "Favorite",
|
||||
"genre": "Genre",
|
||||
"lastPlayed": "Last played",
|
||||
@@ -1300,13 +1303,14 @@
|
||||
"playCount": "Plays",
|
||||
"rating": "Rating",
|
||||
"releaseDate": "Release Date",
|
||||
"releaseYear": "Year",
|
||||
"releaseYear": "Release year",
|
||||
"sampleRate": "Sample Rate",
|
||||
"size": "Size",
|
||||
"songCount": "Tracks",
|
||||
"title": "Title",
|
||||
"trackNumber": "Track",
|
||||
"owner": "Owner"
|
||||
"owner": "Owner",
|
||||
"year": "Year"
|
||||
},
|
||||
"config": {
|
||||
"general": {
|
||||
@@ -1360,6 +1364,7 @@
|
||||
"channels": "$t(common.channel, {\"count\": 2})",
|
||||
"codec": "$t(common.codec)",
|
||||
"composer": "Composer",
|
||||
"date": "Date",
|
||||
"dateAdded": "Date Added",
|
||||
"discNumber": "Disc Number",
|
||||
"duration": "$t(common.duration)",
|
||||
@@ -1374,6 +1379,7 @@
|
||||
"playCount": "Play Count",
|
||||
"rating": "$t(common.rating)",
|
||||
"releaseDate": "Release Date",
|
||||
"releaseYear": "Release Year",
|
||||
"rowIndex": "Row Index",
|
||||
"sampleRate": "$t(common.sampleRate)",
|
||||
"size": "$t(common.size)",
|
||||
|
||||
@@ -1249,6 +1249,24 @@ export const getDataRows = (type?: 'compact' | 'default' | 'poster'): DataRow[]
|
||||
},
|
||||
id: 'releaseDate',
|
||||
},
|
||||
{
|
||||
format: (data) => {
|
||||
if ('year' in data && data.year) {
|
||||
return String(data.year);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
id: 'year',
|
||||
},
|
||||
{
|
||||
format: (data) => {
|
||||
if ('date' in data && data.date) {
|
||||
return formatPartialIsoDateUTC(data.date);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
id: 'date',
|
||||
},
|
||||
{
|
||||
format: (data) => {
|
||||
if ('createdAt' in data && data.createdAt) {
|
||||
|
||||
@@ -59,6 +59,7 @@ const getRowIdFromTableColumn = (tableColumn: TableColumn): null | string => {
|
||||
[TableColumn.CODEC]: null,
|
||||
[TableColumn.COMMENT]: null,
|
||||
[TableColumn.COMPOSER]: null,
|
||||
[TableColumn.DATE]: 'date',
|
||||
[TableColumn.DATE_ADDED]: 'createdAt',
|
||||
[TableColumn.DISC_NUMBER]: null,
|
||||
[TableColumn.DURATION]: 'duration',
|
||||
@@ -73,6 +74,7 @@ const getRowIdFromTableColumn = (tableColumn: TableColumn): null | string => {
|
||||
[TableColumn.PLAY_COUNT]: 'playCount',
|
||||
[TableColumn.PLAYLIST_REORDER]: null,
|
||||
[TableColumn.RELEASE_DATE]: 'releaseDate',
|
||||
[TableColumn.RELEASE_YEAR]: 'releaseYear',
|
||||
[TableColumn.ROW_INDEX]: null,
|
||||
[TableColumn.SAMPLE_RATE]: 'sampleRate',
|
||||
[TableColumn.SIZE]: null,
|
||||
@@ -84,7 +86,7 @@ const getRowIdFromTableColumn = (tableColumn: TableColumn): null | string => {
|
||||
[TableColumn.TRACK_NUMBER]: null,
|
||||
[TableColumn.USER_FAVORITE]: 'userFavorite',
|
||||
[TableColumn.USER_RATING]: 'rating',
|
||||
[TableColumn.YEAR]: 'releaseYear',
|
||||
[TableColumn.YEAR]: 'year',
|
||||
};
|
||||
return columnToRowIdMap[tableColumn] || null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ItemDetailListCellProps } from './types';
|
||||
|
||||
import { formatPartialIsoDateUTC } from '/@/renderer/utils/format';
|
||||
|
||||
export const DateColumn = ({ song }: ItemDetailListCellProps) =>
|
||||
song.date ? formatPartialIsoDateUTC(song.date) : <> </>;
|
||||
@@ -14,6 +14,7 @@ import { CodecColumn } from './codec-column';
|
||||
import { CommentColumn } from './comment-column';
|
||||
import { ComposerColumn } from './composer-column';
|
||||
import { DateAddedColumn } from './date-added-column';
|
||||
import { DateColumn } from './date-column';
|
||||
import { DefaultColumn } from './default-column';
|
||||
import { DiscNumberColumn } from './disc-number-column';
|
||||
import { DurationColumn } from './duration-column';
|
||||
@@ -26,6 +27,7 @@ import { PathColumn } from './path-column';
|
||||
import { PlayCountColumn } from './play-count-column';
|
||||
import { RatingColumn } from './rating-column';
|
||||
import { ReleaseDateColumn } from './release-date-column';
|
||||
import { ReleaseYearColumn } from './release-year-column';
|
||||
import { RowIndexColumn } from './row-index-column';
|
||||
import { ItemDetailRowPlayControlCell } from './row-play-control-cell';
|
||||
import { SampleRateColumn } from './sample-rate-column';
|
||||
@@ -52,6 +54,7 @@ const COLUMN_MAP: Partial<Record<TableColumn, CellComponent>> = {
|
||||
[TableColumn.CODEC]: CodecColumn,
|
||||
[TableColumn.COMMENT]: CommentColumn,
|
||||
[TableColumn.COMPOSER]: ComposerColumn,
|
||||
[TableColumn.DATE]: DateColumn,
|
||||
[TableColumn.DATE_ADDED]: DateAddedColumn,
|
||||
[TableColumn.DISC_NUMBER]: DiscNumberColumn,
|
||||
[TableColumn.DURATION]: DurationColumn,
|
||||
@@ -62,6 +65,7 @@ const COLUMN_MAP: Partial<Record<TableColumn, CellComponent>> = {
|
||||
[TableColumn.PATH]: PathColumn,
|
||||
[TableColumn.PLAY_COUNT]: PlayCountColumn,
|
||||
[TableColumn.RELEASE_DATE]: ReleaseDateColumn,
|
||||
[TableColumn.RELEASE_YEAR]: ReleaseYearColumn,
|
||||
[TableColumn.ROW_INDEX]: RowIndexColumn,
|
||||
[TableColumn.SAMPLE_RATE]: SampleRateColumn,
|
||||
[TableColumn.SIZE]: SizeColumn,
|
||||
@@ -105,6 +109,7 @@ export {
|
||||
CommentColumn,
|
||||
ComposerColumn,
|
||||
DateAddedColumn,
|
||||
DateColumn,
|
||||
DefaultColumn,
|
||||
DiscNumberColumn,
|
||||
DurationColumn,
|
||||
@@ -118,6 +123,7 @@ export {
|
||||
PlayCountColumn,
|
||||
RatingColumn,
|
||||
ReleaseDateColumn,
|
||||
ReleaseYearColumn,
|
||||
RowIndexColumn,
|
||||
SampleRateColumn,
|
||||
SizeColumn,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ItemDetailListCellProps } from './types';
|
||||
|
||||
export const ReleaseYearColumn = ({ song }: ItemDetailListCellProps) =>
|
||||
song.releaseYear ? String(song.releaseYear) : <> </>;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ItemDetailListCellProps } from './types';
|
||||
|
||||
export const YearColumn = ({ song }: ItemDetailListCellProps) =>
|
||||
song.releaseYear ? String(song.releaseYear) : <> </>;
|
||||
song.year ? String(song.year) : <> </>;
|
||||
|
||||
@@ -7,10 +7,12 @@ const FIXED_TRACK_COLUMN_WIDTHS: Partial<Record<TableColumn, number>> = {
|
||||
[TableColumn.BPM]: 56,
|
||||
[TableColumn.CHANNELS]: 80,
|
||||
[TableColumn.CODEC]: 80,
|
||||
[TableColumn.DATE]: 128,
|
||||
[TableColumn.DATE_ADDED]: 128,
|
||||
[TableColumn.DISC_NUMBER]: 36,
|
||||
[TableColumn.DURATION]: 72,
|
||||
[TableColumn.RELEASE_DATE]: 128,
|
||||
[TableColumn.RELEASE_YEAR]: 128,
|
||||
[TableColumn.SAMPLE_RATE]: 112,
|
||||
[TableColumn.TRACK_NUMBER]: 64,
|
||||
[TableColumn.USER_FAVORITE]: 32,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
ColumnNullFallback,
|
||||
ColumnSkeletonFixed,
|
||||
ItemTableListInnerColumn,
|
||||
TableColumnTextContainer,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import { SEPARATOR_STRING } from '/@/shared/api/utils';
|
||||
|
||||
const ReleaseYearColumnBase = (props: ItemTableListInnerColumn) => {
|
||||
const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
|
||||
const item = rowItem as any;
|
||||
|
||||
const yearDisplay = useMemo(() => {
|
||||
if (item && 'releaseYear' in item && item.releaseYear != null) {
|
||||
const releaseYear = item.releaseYear;
|
||||
const originalYear =
|
||||
'originalYear' in item && item.originalYear > 0 ? item.originalYear : null;
|
||||
|
||||
if (originalYear !== null && originalYear !== releaseYear) {
|
||||
return `${originalYear}${SEPARATOR_STRING}${releaseYear}`;
|
||||
}
|
||||
|
||||
if (typeof releaseYear === 'number') {
|
||||
return releaseYear;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [item]);
|
||||
|
||||
if (yearDisplay !== null) {
|
||||
return <TableColumnTextContainer {...props}>{yearDisplay}</TableColumnTextContainer>;
|
||||
}
|
||||
|
||||
const row: number | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
|
||||
|
||||
if (row === null) {
|
||||
return <ColumnNullFallback {...props} />;
|
||||
}
|
||||
|
||||
return <ColumnSkeletonFixed {...props} />;
|
||||
};
|
||||
|
||||
export const ReleaseYearColumn = ReleaseYearColumnBase;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
ColumnNullFallback,
|
||||
ColumnSkeletonFixed,
|
||||
ItemTableListInnerColumn,
|
||||
TableColumnTextContainer,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import { formatPartialIsoDateUTC } from '/@/renderer/utils/format';
|
||||
|
||||
const TrackDateColumnBase = (props: ItemTableListInnerColumn) => {
|
||||
const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
|
||||
const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
|
||||
|
||||
const dateDisplay = useMemo(
|
||||
() => (typeof row === 'string' && row ? formatPartialIsoDateUTC(row) : null),
|
||||
[row],
|
||||
);
|
||||
|
||||
if (dateDisplay !== null) {
|
||||
return <TableColumnTextContainer {...props}>{dateDisplay}</TableColumnTextContainer>;
|
||||
}
|
||||
|
||||
if (row === null) {
|
||||
return <ColumnNullFallback {...props} />;
|
||||
}
|
||||
|
||||
return <ColumnSkeletonFixed {...props} />;
|
||||
};
|
||||
|
||||
export const TrackDateColumn = TrackDateColumnBase;
|
||||
@@ -6,24 +6,16 @@ import {
|
||||
ItemTableListInnerColumn,
|
||||
TableColumnTextContainer,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import { SEPARATOR_STRING } from '/@/shared/api/utils';
|
||||
|
||||
const YearColumnBase = (props: ItemTableListInnerColumn) => {
|
||||
const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
|
||||
const item = rowItem as any;
|
||||
|
||||
const yearDisplay = useMemo(() => {
|
||||
if (item && 'releaseYear' in item && item.releaseYear != null) {
|
||||
const releaseYear = item.releaseYear;
|
||||
const originalYear =
|
||||
'originalYear' in item && item.originalYear > 0 ? item.originalYear : null;
|
||||
|
||||
if (originalYear !== null && originalYear !== releaseYear) {
|
||||
return `${originalYear}${SEPARATOR_STRING}${releaseYear}`;
|
||||
}
|
||||
|
||||
if (typeof releaseYear === 'number') {
|
||||
return releaseYear;
|
||||
if (item && 'year' in item && item.year != null) {
|
||||
const year = item.year;
|
||||
if (typeof year === 'number') {
|
||||
return year;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -112,6 +112,15 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
|
||||
value: TableColumn.COMPOSER,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
isEnabled: false,
|
||||
label: i18n.t('table.config.label.date'),
|
||||
pinned: null,
|
||||
value: TableColumn.DATE,
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
align: 'start',
|
||||
autoSize: false,
|
||||
@@ -139,6 +148,15 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
|
||||
value: TableColumn.YEAR,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
isEnabled: false,
|
||||
label: i18n.t('table.config.label.releaseYear'),
|
||||
pinned: null,
|
||||
value: TableColumn.RELEASE_YEAR,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
@@ -414,15 +432,6 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
|
||||
value: TableColumn.GENRE_BADGE,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
isEnabled: true,
|
||||
label: i18n.t('table.config.label.year'),
|
||||
pinned: null,
|
||||
value: TableColumn.YEAR,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
@@ -432,6 +441,15 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
|
||||
value: TableColumn.RELEASE_DATE,
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
isEnabled: true,
|
||||
label: i18n.t('table.config.label.releaseYear'),
|
||||
pinned: null,
|
||||
value: TableColumn.RELEASE_YEAR,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
|
||||
@@ -48,12 +48,14 @@ import { NumericColumn } from '/@/renderer/components/item-list/item-table-list/
|
||||
import { PathColumn } from '/@/renderer/components/item-list/item-table-list/columns/path-column';
|
||||
import { PlaylistReorderColumn } from '/@/renderer/components/item-list/item-table-list/columns/playlist-reorder-column';
|
||||
import { RatingColumn } from '/@/renderer/components/item-list/item-table-list/columns/rating-column';
|
||||
import { ReleaseYearColumn } from '/@/renderer/components/item-list/item-table-list/columns/release-year-column';
|
||||
import { RowIndexColumn } from '/@/renderer/components/item-list/item-table-list/columns/row-index-column';
|
||||
import { SizeColumn } from '/@/renderer/components/item-list/item-table-list/columns/size-column';
|
||||
import { TextColumn } from '/@/renderer/components/item-list/item-table-list/columns/text-column';
|
||||
import { TitleArtistColumn } from '/@/renderer/components/item-list/item-table-list/columns/title-artist-column';
|
||||
import { TitleColumn } from '/@/renderer/components/item-list/item-table-list/columns/title-column';
|
||||
import { TitleCombinedColumn } from '/@/renderer/components/item-list/item-table-list/columns/title-combined-column';
|
||||
import { TrackDateColumn } from '/@/renderer/components/item-list/item-table-list/columns/track-date-column';
|
||||
import { TrackNumberColumn } from '/@/renderer/components/item-list/item-table-list/columns/track-number-column';
|
||||
import { YearColumn } from '/@/renderer/components/item-list/item-table-list/columns/year-column';
|
||||
import { useItemDragDropState } from '/@/renderer/components/item-list/item-table-list/hooks/use-item-drag-drop-state';
|
||||
@@ -248,6 +250,11 @@ const ItemTableListColumnBase = (props: ItemTableListColumn) => {
|
||||
case TableColumn.COMPOSER:
|
||||
return <ComposerColumn {...props} {...dragProps} controls={controls} type={type} />;
|
||||
|
||||
case TableColumn.DATE:
|
||||
return (
|
||||
<TrackDateColumn {...props} {...dragProps} controls={controls} type={type} />
|
||||
);
|
||||
|
||||
case TableColumn.DATE_ADDED:
|
||||
return <DateColumn {...props} {...dragProps} controls={controls} type={type} />;
|
||||
|
||||
@@ -281,6 +288,11 @@ const ItemTableListColumnBase = (props: ItemTableListColumn) => {
|
||||
<AbsoluteDateColumn {...props} {...dragProps} controls={controls} type={type} />
|
||||
);
|
||||
|
||||
case TableColumn.RELEASE_YEAR:
|
||||
return (
|
||||
<ReleaseYearColumn {...props} {...dragProps} controls={controls} type={type} />
|
||||
);
|
||||
|
||||
case TableColumn.ROW_INDEX:
|
||||
return <RowIndexColumn {...props} {...dragProps} controls={controls} type={type} />;
|
||||
|
||||
@@ -1268,6 +1280,9 @@ export const columnLabelMap: Record<TableColumn, ReactNode | string> = {
|
||||
[TableColumn.COMPOSER]: i18n.t('table.config.label.composer', {
|
||||
postProcess: 'upperCase',
|
||||
}) as string,
|
||||
[TableColumn.DATE]: i18n.t('table.column.date', {
|
||||
postProcess: 'upperCase',
|
||||
}) as string,
|
||||
[TableColumn.DATE_ADDED]: i18n.t('table.column.dateAdded', {
|
||||
postProcess: 'upperCase',
|
||||
}) as string,
|
||||
@@ -1304,6 +1319,9 @@ export const columnLabelMap: Record<TableColumn, ReactNode | string> = {
|
||||
[TableColumn.RELEASE_DATE]: i18n.t('table.column.releaseDate', {
|
||||
postProcess: 'upperCase',
|
||||
}) as string,
|
||||
[TableColumn.RELEASE_YEAR]: i18n.t('table.column.releaseYear', {
|
||||
postProcess: 'upperCase',
|
||||
}) as string,
|
||||
[TableColumn.ROW_INDEX]: (
|
||||
<Flex className={styles.headerIconWrapper}>
|
||||
<Icon icon="hash" />
|
||||
@@ -1339,7 +1357,7 @@ export const columnLabelMap: Record<TableColumn, ReactNode | string> = {
|
||||
<Icon icon="star" />
|
||||
</Flex>
|
||||
),
|
||||
[TableColumn.YEAR]: i18n.t('table.column.releaseYear', { postProcess: 'upperCase' }) as string,
|
||||
[TableColumn.YEAR]: i18n.t('table.column.year', { postProcess: 'upperCase' }) as string,
|
||||
};
|
||||
|
||||
export const ColumnNullFallback = (props: ItemTableListInnerColumn) => {
|
||||
|
||||
@@ -132,7 +132,7 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
|
||||
if (!album) return [];
|
||||
|
||||
const originalDifferentFromRelease =
|
||||
album?.originalDate && album?.originalDate !== album?.releaseDate;
|
||||
album.originalDate && album.originalDate !== album.releaseDate;
|
||||
|
||||
const originalYearDifferentFromRelease =
|
||||
album.originalYear > 0 &&
|
||||
@@ -141,56 +141,41 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
|
||||
|
||||
const playCount = album?.playCount;
|
||||
|
||||
const releasePrefix = originalDifferentFromRelease ? t('page.albumDetail.released') : '♫';
|
||||
const trackYearRangeDifferent = album.trackYearRange?.max !== album.trackYearRange?.min;
|
||||
|
||||
const releaseYearPrefix = originalYearDifferentFromRelease
|
||||
? t('page.albumDetail.released')
|
||||
: '♫';
|
||||
|
||||
if (album.originalDate) {
|
||||
if (originalDifferentFromRelease) {
|
||||
if (album.trackYearRange && trackYearRangeDifferent) {
|
||||
items.push({
|
||||
id: 'originalDate',
|
||||
value: `♫ ${formatPartialIsoDateUTC(album.originalDate)}`,
|
||||
id: 'trackYearRange',
|
||||
value: `${album.trackYearRange.min}-${album.trackYearRange.max}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (album.originalDate && originalDifferentFromRelease) {
|
||||
items.push({
|
||||
id: 'originalDate',
|
||||
value: `${formatPartialIsoDateUTC(album.originalDate)}`,
|
||||
});
|
||||
} else if (album.originalYear > 0 && originalYearDifferentFromRelease) {
|
||||
items.push({
|
||||
id: 'originalYear',
|
||||
value: `${album.originalYear}`,
|
||||
});
|
||||
}
|
||||
|
||||
const prefixNecessary = items.length > 0;
|
||||
const releasePrefix = prefixNecessary ? `${t('page.albumDetail.released')} ` : '';
|
||||
const releaseYearPrefix = prefixNecessary ? `${t('page.albumDetail.released')} ` : '';
|
||||
if (releaseDate) {
|
||||
items.push({
|
||||
id: 'releaseDate',
|
||||
value: `${releasePrefix}${formatPartialIsoDateUTC(releaseDate)}`,
|
||||
});
|
||||
}
|
||||
} else if (album.originalYear > 0) {
|
||||
if (originalYearDifferentFromRelease) {
|
||||
items.push({
|
||||
id: 'originalYear',
|
||||
value: `♫ ${album.originalYear}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (releaseDate) {
|
||||
items.push({
|
||||
id: 'releaseDate',
|
||||
value: `${releaseYearPrefix} ${formatPartialIsoDateUTC(releaseDate)}`,
|
||||
});
|
||||
} else if (releaseYear != null && releaseYear > 0) {
|
||||
items.push({
|
||||
id: 'releaseYear',
|
||||
value: `${releaseYearPrefix}${releaseYear}`,
|
||||
});
|
||||
}
|
||||
} else if (releaseDate) {
|
||||
items.push({
|
||||
id: 'releaseDate',
|
||||
value: `♫ ${formatPartialIsoDateUTC(releaseDate)}`,
|
||||
});
|
||||
} else if (releaseYear != null && releaseYear > 0) {
|
||||
items.push({
|
||||
id: 'releaseYear',
|
||||
value: `♫ ${releaseYear}`,
|
||||
});
|
||||
}
|
||||
|
||||
items.push(
|
||||
...[
|
||||
@@ -278,6 +263,7 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
|
||||
<Group className={styles.metadataGroup} gap="xs">
|
||||
{metadataItems.map((item, index) => (
|
||||
<Fragment key={item.id}>
|
||||
{index === 0 && <Text isNoSelect>♫ </Text>}
|
||||
{index > 0 && (
|
||||
<Text isMuted isNoSelect>
|
||||
<Separator />
|
||||
|
||||
@@ -6,7 +6,11 @@ import { generatePath, Link } from 'react-router';
|
||||
import { SongPath } from '/@/renderer/features/item-details/components/song-path';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { formatDurationString, formatSizeString } from '/@/renderer/utils';
|
||||
import { formatDateRelative, formatRating } from '/@/renderer/utils/format';
|
||||
import {
|
||||
formatDateRelative,
|
||||
formatPartialIsoDateUTC,
|
||||
formatRating,
|
||||
} from '/@/renderer/utils/format';
|
||||
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
|
||||
import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types';
|
||||
import { sanitize } from '/@/renderer/utils/sanitize';
|
||||
@@ -304,7 +308,24 @@ const SongPropertyMapping: ItemDetailRow<Song>[] = [
|
||||
},
|
||||
{ key: 'discNumber', label: 'common.disc' },
|
||||
{ key: 'trackNumber', label: 'common.trackNumber' },
|
||||
{ key: 'releaseYear', label: 'filter.releaseYear' },
|
||||
{
|
||||
key: 'date',
|
||||
label: 'filter.date',
|
||||
render: (song) => (song.date ? formatPartialIsoDateUTC(song.date) : null),
|
||||
},
|
||||
{
|
||||
label: 'filter.year',
|
||||
render: (song) => (!song.date ? song.year : null),
|
||||
},
|
||||
{
|
||||
key: 'releaseDate',
|
||||
label: 'filter.releaseDate',
|
||||
render: (song) => (song.releaseDate ? formatPartialIsoDateUTC(song.releaseDate) : null),
|
||||
},
|
||||
{
|
||||
label: 'filter.releaseYear',
|
||||
render: (song) => (!song.releaseDate ? song.releaseYear : null),
|
||||
},
|
||||
{
|
||||
label: 'filter.explicitStatus',
|
||||
render: (song, t) =>
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
usePlayerData,
|
||||
usePlayerSong,
|
||||
} from '/@/renderer/store';
|
||||
import { formatPartialIsoDateUTC } from '/@/renderer/utils';
|
||||
import { Badge } from '/@/shared/components/badge/badge';
|
||||
import { Center } from '/@/shared/components/center/center';
|
||||
import { Flex } from '/@/shared/components/flex/flex';
|
||||
@@ -181,6 +182,7 @@ export const FullScreenPlayerImage = () => {
|
||||
</Badge>
|
||||
),
|
||||
codec: currentSong?.container && <Badge>{currentSong?.container}</Badge>,
|
||||
date: currentSong?.date && <Badge>{formatPartialIsoDateUTC(currentSong?.date)}</Badge>,
|
||||
disc_number: currentSong?.discNumber && (
|
||||
<Badge>
|
||||
{t('common.disc')} {currentSong?.discNumber}
|
||||
@@ -191,7 +193,9 @@ export const FullScreenPlayerImage = () => {
|
||||
currentSong?.genres
|
||||
.slice(0, 2)
|
||||
.map((genre) => <Badge key={genre.id}>{genre.name}</Badge>),
|
||||
release_date: currentSong?.releaseDate && <Badge>{currentSong?.releaseDate}</Badge>,
|
||||
release_date: currentSong?.releaseDate && (
|
||||
<Badge>{formatPartialIsoDateUTC(currentSong?.releaseDate)}</Badge>
|
||||
),
|
||||
release_type: currentSong?.tags?.releasetype && (
|
||||
<Badge>{currentSong?.tags?.releasetype[0]}</Badge>
|
||||
),
|
||||
@@ -202,6 +206,7 @@ export const FullScreenPlayerImage = () => {
|
||||
{t('common.trackNumber')} {currentSong?.trackNumber}
|
||||
</Badge>
|
||||
),
|
||||
year: currentSong?.year && <Badge>{currentSong?.year}</Badge>,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -80,6 +80,7 @@ export const useMPRIS = () => {
|
||||
compilation: null,
|
||||
container: null,
|
||||
createdAt: '',
|
||||
date: null,
|
||||
discNumber: 0,
|
||||
discSubtitle: null,
|
||||
duration: 0,
|
||||
@@ -109,6 +110,7 @@ export const useMPRIS = () => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
year: null,
|
||||
};
|
||||
}, [isRadioActive, isRadioPlaying, radioMetadata, stationName]);
|
||||
|
||||
|
||||
@@ -356,8 +356,10 @@ const getSongFieldValue = (song: Song, field: string): boolean | null | number |
|
||||
return song.playCount;
|
||||
case 'rating':
|
||||
return song.userRating || 0;
|
||||
case 'year':
|
||||
case 'releaseYear':
|
||||
return song.releaseYear || 0;
|
||||
case 'year':
|
||||
return song.year || 0;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export function playlistSongsToAlbums(songs: Song[]): PlaylistAlbumRow[] {
|
||||
songCount: null,
|
||||
sortName: song.album ?? '',
|
||||
tags: song.tags,
|
||||
trackYearRange: null,
|
||||
updatedAt: song.updatedAt,
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
|
||||
@@ -13,6 +13,7 @@ const PLAYER_ITEMS: Array<[PlayerItem, string]> = [
|
||||
[PlayerItem.BIT_RATE, 'common.bitrate'],
|
||||
[PlayerItem.BPM, 'common.bpm'],
|
||||
[PlayerItem.CODEC, 'common.codec'],
|
||||
[PlayerItem.DATE, 'filter.date'],
|
||||
[PlayerItem.DISC_NUMBER, 'table.config.label.discNumber'],
|
||||
[PlayerItem.GENRES, 'entity.genre_other'],
|
||||
[PlayerItem.RELEASE_DATE, 'filter.releaseDate'],
|
||||
@@ -20,6 +21,7 @@ const PLAYER_ITEMS: Array<[PlayerItem, string]> = [
|
||||
[PlayerItem.RELEASE_YEAR, 'filter.releaseYear'],
|
||||
[PlayerItem.SAMPLE_RATE, 'common.sampleRate'],
|
||||
[PlayerItem.TRACK_NUMBER, 'table.config.label.trackNumber'],
|
||||
[PlayerItem.YEAR, 'filter.year'],
|
||||
];
|
||||
|
||||
export const FullscreenPlayerSettings = memo(() => {
|
||||
|
||||
@@ -66,6 +66,11 @@ const getFilterFields = (t: (key: string, options?: any) => string): FilterField
|
||||
type: 'number',
|
||||
value: 'year',
|
||||
},
|
||||
{
|
||||
label: t('table.config.label.releaseYear'),
|
||||
type: 'number',
|
||||
value: 'releaseYear',
|
||||
},
|
||||
{
|
||||
label: t('table.config.label.note'),
|
||||
type: 'string',
|
||||
|
||||
@@ -214,6 +214,11 @@ export const CLIENT_SIDE_SONG_FILTERS = [
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: i18n.t('filter.releaseYear'),
|
||||
value: SongListSort.RELEASE_YEAR,
|
||||
},
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: i18n.t('filter.year'),
|
||||
value: SongListSort.YEAR,
|
||||
},
|
||||
];
|
||||
@@ -591,6 +596,11 @@ const SONG_LIST_FILTERS: Partial<
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: i18n.t('filter.releaseYear'),
|
||||
value: SongListSort.RELEASE_YEAR,
|
||||
},
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: i18n.t('filter.year'),
|
||||
value: SongListSort.YEAR,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -95,10 +95,12 @@ const PlayerItemSchema = z.enum([
|
||||
'sample_rate',
|
||||
'track_number',
|
||||
'codec',
|
||||
'date',
|
||||
'release_year',
|
||||
'release_type',
|
||||
'release_date',
|
||||
'genres',
|
||||
'year',
|
||||
]);
|
||||
|
||||
const ArtistItemSchema = z.enum([
|
||||
@@ -650,6 +652,7 @@ const PlayerFilterFieldSchema = z.enum([
|
||||
'duration',
|
||||
'genre',
|
||||
'year',
|
||||
'releaseYear',
|
||||
'note',
|
||||
'path',
|
||||
'playCount',
|
||||
@@ -967,6 +970,7 @@ export enum PlayerItem {
|
||||
BIT_RATE = 'bit_rate',
|
||||
BPM = 'bpm',
|
||||
CODEC = 'codec',
|
||||
DATE = 'date',
|
||||
DISC_NUMBER = 'disc_number',
|
||||
GENRES = 'genres',
|
||||
RELEASE_DATE = 'release_date',
|
||||
@@ -974,6 +978,7 @@ export enum PlayerItem {
|
||||
RELEASE_YEAR = 'release_year',
|
||||
SAMPLE_RATE = 'sample_rate',
|
||||
TRACK_NUMBER = 'track_number',
|
||||
YEAR = 'year',
|
||||
}
|
||||
|
||||
export enum PlaylistTarget {
|
||||
@@ -1080,6 +1085,10 @@ export const playerItems: SortableItem<PlayerItem>[] = [
|
||||
disabled: false,
|
||||
id: PlayerItem.CODEC,
|
||||
},
|
||||
{
|
||||
disabled: true,
|
||||
id: PlayerItem.DATE,
|
||||
},
|
||||
{
|
||||
disabled: true,
|
||||
id: PlayerItem.DISC_NUMBER,
|
||||
@@ -1108,6 +1117,10 @@ export const playerItems: SortableItem<PlayerItem>[] = [
|
||||
disabled: true,
|
||||
id: PlayerItem.TRACK_NUMBER,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
id: PlayerItem.YEAR,
|
||||
},
|
||||
];
|
||||
|
||||
export const sidebarItems: SidebarItemType[] = [
|
||||
@@ -1522,10 +1535,14 @@ const initialState: SettingsState = {
|
||||
alignLeftColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ALBUM_ARTIST,
|
||||
TableColumn.YEAR,
|
||||
TableColumn.RELEASE_YEAR,
|
||||
],
|
||||
columns: ALBUM_TABLE_COLUMNS,
|
||||
enabledColumns: [TableColumn.TITLE, TableColumn.ALBUM_ARTIST, TableColumn.YEAR],
|
||||
enabledColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ALBUM_ARTIST,
|
||||
TableColumn.RELEASE_YEAR,
|
||||
],
|
||||
pickColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.DURATION,
|
||||
@@ -1537,8 +1554,8 @@ const initialState: SettingsState = {
|
||||
TableColumn.PLAY_COUNT,
|
||||
TableColumn.SONG_COUNT,
|
||||
TableColumn.RELEASE_DATE,
|
||||
TableColumn.RELEASE_YEAR,
|
||||
TableColumn.LAST_PLAYED,
|
||||
TableColumn.YEAR,
|
||||
],
|
||||
}),
|
||||
size: 'default',
|
||||
@@ -1599,10 +1616,14 @@ const initialState: SettingsState = {
|
||||
alignLeftColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ALBUM_ARTIST,
|
||||
TableColumn.YEAR,
|
||||
TableColumn.RELEASE_YEAR,
|
||||
],
|
||||
columns: ALBUM_TABLE_COLUMNS,
|
||||
enabledColumns: [TableColumn.TITLE, TableColumn.ALBUM_ARTIST, TableColumn.YEAR],
|
||||
enabledColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ALBUM_ARTIST,
|
||||
TableColumn.RELEASE_YEAR,
|
||||
],
|
||||
pickColumns: [
|
||||
TableColumn.TITLE,
|
||||
TableColumn.DURATION,
|
||||
@@ -1614,8 +1635,8 @@ const initialState: SettingsState = {
|
||||
TableColumn.PLAY_COUNT,
|
||||
TableColumn.SONG_COUNT,
|
||||
TableColumn.RELEASE_DATE,
|
||||
TableColumn.RELEASE_YEAR,
|
||||
TableColumn.LAST_PLAYED,
|
||||
TableColumn.YEAR,
|
||||
],
|
||||
}),
|
||||
size: 'default',
|
||||
@@ -1823,15 +1844,17 @@ const initialState: SettingsState = {
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ARTIST,
|
||||
TableColumn.DURATION,
|
||||
TableColumn.YEAR,
|
||||
TableColumn.BIT_RATE,
|
||||
TableColumn.BPM,
|
||||
TableColumn.CODEC,
|
||||
TableColumn.DATE,
|
||||
TableColumn.DATE_ADDED,
|
||||
TableColumn.GENRE,
|
||||
TableColumn.LAST_PLAYED,
|
||||
TableColumn.RELEASE_DATE,
|
||||
TableColumn.RELEASE_YEAR,
|
||||
TableColumn.TRACK_NUMBER,
|
||||
TableColumn.YEAR,
|
||||
],
|
||||
}),
|
||||
size: 'default',
|
||||
@@ -1899,15 +1922,17 @@ const initialState: SettingsState = {
|
||||
TableColumn.TITLE,
|
||||
TableColumn.ARTIST,
|
||||
TableColumn.DURATION,
|
||||
TableColumn.YEAR,
|
||||
TableColumn.BIT_RATE,
|
||||
TableColumn.BPM,
|
||||
TableColumn.CODEC,
|
||||
TableColumn.DATE,
|
||||
TableColumn.DATE_ADDED,
|
||||
TableColumn.GENRE,
|
||||
TableColumn.LAST_PLAYED,
|
||||
TableColumn.RELEASE_DATE,
|
||||
TableColumn.RELEASE_YEAR,
|
||||
TableColumn.TRACK_NUMBER,
|
||||
TableColumn.YEAR,
|
||||
],
|
||||
}),
|
||||
size: 'default',
|
||||
@@ -2714,6 +2739,92 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
|
||||
);
|
||||
}
|
||||
|
||||
if (version < 29) {
|
||||
const dateColumn: ItemTableListColumnConfig = {
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
id: TableColumn.DATE,
|
||||
isEnabled: false,
|
||||
pinned: null,
|
||||
width: 240,
|
||||
};
|
||||
const yearColumn: ItemTableListColumnConfig = {
|
||||
align: 'center',
|
||||
autoSize: false,
|
||||
id: TableColumn.YEAR,
|
||||
isEnabled: false,
|
||||
pinned: null,
|
||||
width: 200,
|
||||
};
|
||||
|
||||
const listKeysToUpdate: ItemListKey[] = [
|
||||
ItemListKey.SONG,
|
||||
ItemListKey.ALBUM_DETAIL,
|
||||
ItemListKey.FOLDER,
|
||||
ItemListKey.PLAYLIST_SONG,
|
||||
ItemListKey.ALBUM_ARTIST_SONG,
|
||||
ItemListKey.GENRE_SONG,
|
||||
ItemListKey.QUEUE_SONG,
|
||||
ItemListKey.FULL_SCREEN,
|
||||
ItemListKey.SIDE_QUEUE,
|
||||
];
|
||||
|
||||
listKeysToUpdate.forEach((listKey) => {
|
||||
const listConfig = state.lists[listKey];
|
||||
if (listConfig?.table?.columns) {
|
||||
const columns = listConfig.table.columns;
|
||||
const hasYear = columns.some((col) => col.id === TableColumn.YEAR);
|
||||
if (!hasYear) {
|
||||
const releaseYearIndex = columns.findIndex(
|
||||
(col) => col.id === TableColumn.RELEASE_YEAR,
|
||||
);
|
||||
if (releaseYearIndex >= 0) {
|
||||
columns.splice(releaseYearIndex, 0, yearColumn);
|
||||
} else {
|
||||
columns.push(yearColumn);
|
||||
}
|
||||
}
|
||||
const hasDate = columns.some((col) => col.id === TableColumn.DATE);
|
||||
if (!hasDate) {
|
||||
const releaseDateIndex = columns.findIndex(
|
||||
(col) => col.id === TableColumn.RELEASE_DATE,
|
||||
);
|
||||
if (releaseDateIndex >= 0) {
|
||||
columns.splice(releaseDateIndex, 0, dateColumn);
|
||||
} else {
|
||||
columns.push(dateColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const listConfig = state.lists[ItemListKey.ALBUM];
|
||||
if (listConfig?.detail?.columns) {
|
||||
const columns = listConfig.detail.columns;
|
||||
const hasYear = columns.some((col) => col.id === TableColumn.YEAR);
|
||||
if (!hasYear) {
|
||||
const releaseYearIndex = columns.findIndex(
|
||||
(col) => col.id === TableColumn.RELEASE_YEAR,
|
||||
);
|
||||
if (releaseYearIndex >= 0) {
|
||||
columns.splice(releaseYearIndex, 0, yearColumn);
|
||||
} else {
|
||||
columns.push(yearColumn);
|
||||
}
|
||||
}
|
||||
const hasDate = columns.some((col) => col.id === TableColumn.DATE);
|
||||
if (!hasDate) {
|
||||
const releaseDateIndex = columns.findIndex(
|
||||
(col) => col.id === TableColumn.RELEASE_DATE,
|
||||
);
|
||||
if (releaseDateIndex >= 0) {
|
||||
columns.splice(releaseDateIndex, 0, dateColumn);
|
||||
} else {
|
||||
columns.push(dateColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (version < 30) {
|
||||
for (const [key, displaySettings] of Object.entries(state.lyricsDisplay)) {
|
||||
const legacySettings = displaySettings as typeof displaySettings & {
|
||||
|
||||
@@ -155,6 +155,7 @@ const normalizeSong = (
|
||||
compilation: null,
|
||||
container,
|
||||
createdAt: item.DateCreated,
|
||||
date: releaseDate || String(releaseYear),
|
||||
discNumber: (item.ParentIndexNumber && item.ParentIndexNumber) || 1,
|
||||
discSubtitle: null,
|
||||
duration: item.RunTimeTicks / TICKS_PER_MS,
|
||||
@@ -209,6 +210,7 @@ const normalizeSong = (
|
||||
updatedAt: item.DateCreated,
|
||||
userFavorite: (item.UserData && item.UserData.IsFavorite) || false,
|
||||
userRating: null,
|
||||
year: releaseYear,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -280,6 +282,7 @@ const normalizeAlbum = (
|
||||
songs: item.Songs?.map((song) => normalizeSong(song, server)),
|
||||
sortName: item.SortName || item.Name,
|
||||
tags: getTags(item),
|
||||
trackYearRange: null,
|
||||
updatedAt: item?.DateLastMediaAdded || item.DateCreated,
|
||||
userFavorite: item.UserData?.IsFavorite || false,
|
||||
userRating: null,
|
||||
|
||||
@@ -212,9 +212,13 @@ const normalizeSong = (
|
||||
|
||||
const fromSongRelease = parsePartialIsoDate(item.releaseDate);
|
||||
const songApiYear = coerceYear(item.year);
|
||||
const fromSongDate = parsePartialIsoDate(item.date);
|
||||
const releaseYear: null | number =
|
||||
fromSongRelease.year > 0 ? fromSongRelease.year : songApiYear > 0 ? songApiYear : null;
|
||||
const releaseDate = fromSongRelease.date ?? (songApiYear > 0 ? String(songApiYear) : null);
|
||||
const releaseDate =
|
||||
fromSongRelease.date ?? fromSongDate.date ?? (songApiYear > 0 ? String(songApiYear) : null);
|
||||
const date = fromSongDate.date ?? (songApiYear > 0 ? String(songApiYear) : null);
|
||||
const year = fromSongDate.year > 0 ? fromSongDate.year : releaseYear;
|
||||
|
||||
return {
|
||||
album: item.album,
|
||||
@@ -233,6 +237,7 @@ const normalizeSong = (
|
||||
compilation: item.compilation,
|
||||
container: item.suffix,
|
||||
createdAt: item.createdAt,
|
||||
date,
|
||||
discNumber: item.discNumber,
|
||||
discSubtitle: item.discSubtitle ? item.discSubtitle : null,
|
||||
duration: item.duration * 1000,
|
||||
@@ -285,6 +290,7 @@ const normalizeSong = (
|
||||
updatedAt: item.updatedAt,
|
||||
userFavorite: item.starred || false,
|
||||
userRating: item.rating || null,
|
||||
year,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -337,6 +343,7 @@ const normalizeAlbum = (
|
||||
): Album => {
|
||||
const releaseDate = normalizeNavidromeReleaseDate(item);
|
||||
const originalDate = normalizeNavidromeOriginalDate(item);
|
||||
const trackYearRange = { max: item.maxYear, min: item.minYear };
|
||||
|
||||
return {
|
||||
...parseAlbumTags(item),
|
||||
@@ -384,6 +391,7 @@ const normalizeAlbum = (
|
||||
songs: item.songs ? item.songs.map((song) => normalizeSong(song, server)) : undefined,
|
||||
sortName: item.orderAlbumName,
|
||||
tags: item.tags || null,
|
||||
trackYearRange,
|
||||
updatedAt: item.updatedAt,
|
||||
userFavorite: item.starred || false,
|
||||
userRating: item.rating || null,
|
||||
|
||||
@@ -59,6 +59,7 @@ export enum NDSongListSort {
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'createdAt',
|
||||
RELEASE_YEAR = 'releaseYear',
|
||||
TITLE = 'title',
|
||||
TRACK = 'track',
|
||||
YEAR = 'year',
|
||||
@@ -555,6 +556,7 @@ const song = z.object({
|
||||
comment: z.string().optional(),
|
||||
compilation: z.boolean(),
|
||||
createdAt: z.string(),
|
||||
date: z.string().optional(),
|
||||
discNumber: z.number(),
|
||||
discSubtitle: z.string().optional(),
|
||||
duration: z.number(),
|
||||
|
||||
@@ -158,6 +158,16 @@ const subsonicReleaseFields = (item: {
|
||||
|
||||
return { releaseDate: null, releaseYear: null };
|
||||
};
|
||||
const subsonicTrackYearField = (item: {
|
||||
year?: number;
|
||||
}): { date: null | string; year: null | number } => {
|
||||
const y = coerceYear(item.year);
|
||||
if (y > 0) {
|
||||
return { date: String(y), year: y };
|
||||
}
|
||||
|
||||
return { date: null, year: null };
|
||||
};
|
||||
|
||||
const normalizeSong = (
|
||||
item: z.infer<typeof ssType._response.song>,
|
||||
@@ -173,6 +183,7 @@ const normalizeSong = (
|
||||
: item.artist || '';
|
||||
|
||||
const { releaseDate, releaseYear } = subsonicReleaseFields(item);
|
||||
const { date, year } = subsonicTrackYearField(item);
|
||||
|
||||
return {
|
||||
_itemType: LibraryItem.SONG,
|
||||
@@ -192,6 +203,7 @@ const normalizeSong = (
|
||||
compilation: null,
|
||||
container: item.contentType.startsWith('audio/') ? item.contentType.split('/')[1] : null,
|
||||
createdAt: item.created,
|
||||
date,
|
||||
discNumber: item.discNumber || 1,
|
||||
discSubtitle: discTitleMap?.get(item.discNumber ?? 1) ?? null,
|
||||
duration: item.duration ? item.duration * 1000 : 0,
|
||||
@@ -239,6 +251,7 @@ const normalizeSong = (
|
||||
updatedAt: '',
|
||||
userFavorite: Boolean(item.starred) || false,
|
||||
userRating: item.userRating || null,
|
||||
year,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -353,6 +366,7 @@ const normalizeAlbum = (
|
||||
) || [],
|
||||
sortName: item.title,
|
||||
tags: null,
|
||||
trackYearRange: null,
|
||||
updatedAt: item.created,
|
||||
userFavorite: Boolean(item.starred) || false,
|
||||
userRating: item.userRating || null,
|
||||
|
||||
@@ -311,6 +311,14 @@ export const sortSongList = (songs: Song[], sortBy: SongListSort, sortOrder: Sor
|
||||
);
|
||||
break;
|
||||
|
||||
case SongListSort.RELEASE_YEAR:
|
||||
results = orderBy(
|
||||
results,
|
||||
['releaseYear', (v) => v.album?.toLowerCase(), 'discNumber', 'trackNumber'],
|
||||
[order, order, order, order],
|
||||
);
|
||||
break;
|
||||
|
||||
case SongListSort.SORT_NAME:
|
||||
results = orderBy(results, [(v) => v.sortName ?? v.name], [order]);
|
||||
break;
|
||||
@@ -318,7 +326,7 @@ export const sortSongList = (songs: Song[], sortBy: SongListSort, sortOrder: Sor
|
||||
case SongListSort.YEAR:
|
||||
results = orderBy(
|
||||
results,
|
||||
['releaseYear', (v) => v.album?.toLowerCase(), 'discNumber', 'trackNumber'],
|
||||
['year', (v) => v.album?.toLowerCase(), 'discNumber', 'trackNumber'],
|
||||
[order, order, order, order],
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -207,6 +207,7 @@ export type Album = {
|
||||
songs?: Song[];
|
||||
sortName: string;
|
||||
tags: null | Record<string, string[]>;
|
||||
trackYearRange: null | { max: number; min: number };
|
||||
updatedAt: string;
|
||||
userFavorite: boolean;
|
||||
userRating: null | number;
|
||||
@@ -387,6 +388,7 @@ export type Song = {
|
||||
compilation: boolean | null;
|
||||
container: null | string;
|
||||
createdAt: string;
|
||||
date: null | PartialIsoDateString;
|
||||
discNumber: number;
|
||||
discSubtitle: null | string;
|
||||
duration: number;
|
||||
@@ -417,6 +419,7 @@ export type Song = {
|
||||
updatedAt: string;
|
||||
userFavorite: boolean;
|
||||
userRating: null | number;
|
||||
year: null | number;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
@@ -620,6 +623,7 @@ export enum SongListSort {
|
||||
RECENTLY_ADDED = 'recentlyAdded',
|
||||
RECENTLY_PLAYED = 'recentlyPlayed',
|
||||
RELEASE_DATE = 'releaseDate',
|
||||
RELEASE_YEAR = 'releaseYear',
|
||||
SORT_NAME = 'sortName',
|
||||
YEAR = 'year',
|
||||
}
|
||||
@@ -688,6 +692,7 @@ export const songListSortMap: SongListSortMap = {
|
||||
recentlyAdded: JFSongListSort.RECENTLY_ADDED,
|
||||
recentlyPlayed: JFSongListSort.RECENTLY_PLAYED,
|
||||
releaseDate: JFSongListSort.RELEASE_DATE,
|
||||
releaseYear: undefined,
|
||||
sortName: JFSongListSort.NAME,
|
||||
year: undefined,
|
||||
},
|
||||
@@ -710,6 +715,7 @@ export const songListSortMap: SongListSortMap = {
|
||||
recentlyAdded: NDSongListSort.RECENTLY_ADDED,
|
||||
recentlyPlayed: NDSongListSort.PLAY_DATE,
|
||||
releaseDate: undefined,
|
||||
releaseYear: NDSongListSort.RELEASE_YEAR,
|
||||
sortName: NDSongListSort.TITLE,
|
||||
year: NDSongListSort.YEAR,
|
||||
},
|
||||
@@ -732,6 +738,7 @@ export const songListSortMap: SongListSortMap = {
|
||||
recentlyAdded: undefined,
|
||||
recentlyPlayed: undefined,
|
||||
releaseDate: undefined,
|
||||
releaseYear: undefined,
|
||||
sortName: undefined,
|
||||
year: undefined,
|
||||
},
|
||||
|
||||
@@ -178,6 +178,7 @@ export enum TableColumn {
|
||||
CODEC = 'container',
|
||||
COMMENT = 'comment',
|
||||
COMPOSER = 'composer',
|
||||
DATE = 'date',
|
||||
DATE_ADDED = 'createdAt',
|
||||
DISC_NUMBER = 'discNumber',
|
||||
DURATION = 'duration',
|
||||
@@ -192,6 +193,7 @@ export enum TableColumn {
|
||||
PLAY_COUNT = 'playCount',
|
||||
PLAYLIST_REORDER = 'playlistReorder',
|
||||
RELEASE_DATE = 'releaseDate',
|
||||
RELEASE_YEAR = 'releaseYear',
|
||||
ROW_INDEX = 'rowIndex',
|
||||
SAMPLE_RATE = 'sampleRate',
|
||||
SIZE = 'size',
|
||||
@@ -203,7 +205,7 @@ export enum TableColumn {
|
||||
TRACK_NUMBER = 'trackNumber',
|
||||
USER_FAVORITE = 'userFavorite',
|
||||
USER_RATING = 'userRating',
|
||||
YEAR = 'releaseYear',
|
||||
YEAR = 'year',
|
||||
}
|
||||
|
||||
export type DiscoveredServerItem = {
|
||||
|
||||
Reference in New Issue
Block a user