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