mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
improve domain types to better match OS, update normalizer functions
This commit is contained in:
@@ -1,42 +1,179 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
import { z } from 'zod';
|
||||
import { components } from './subsonic-schema.d';
|
||||
|
||||
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
||||
import { Album } from '/@/shared/types/domain/album-domain-types';
|
||||
import { AlbumArtist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Genre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { QueueSong } from '/@/shared/types/domain/player-domain-types';
|
||||
import { Artist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Genre, RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
|
||||
import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { Song } from '/@/shared/types/domain/song-domain-types';
|
||||
import { formatDate } from '/@/shared/utils/format-date';
|
||||
|
||||
const getCoverArtUrl = (args: {
|
||||
baseUrl: string | undefined;
|
||||
coverArtId?: string;
|
||||
credential: string | undefined;
|
||||
size: number;
|
||||
}) => {
|
||||
const size = args.size ? args.size : 250;
|
||||
export const normalize = {
|
||||
album: (
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
server: ServerListItem,
|
||||
): Album => {
|
||||
const imageUrl = item.coverArt ? getCoverArtUrl(item.coverArt, server) : null;
|
||||
|
||||
if (!args.coverArtId || args.coverArtId.match('2a96cbd8b46e442fc41c2b86b821562f')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
`${args.baseUrl}/rest/getCoverArt.view` +
|
||||
`?id=${args.coverArtId}` +
|
||||
`&${args.credential}` +
|
||||
'&v=1.13.0' +
|
||||
'&c=Feishin' +
|
||||
`&size=${size}`
|
||||
);
|
||||
return {
|
||||
_itemType: LibraryItem.ALBUM,
|
||||
_serverId: server?.id || 'unknown',
|
||||
_serverType: ServerType.SUBSONIC,
|
||||
artistName: item.artist || null,
|
||||
artists: getArtistList(item.artists, item.artistId, item.artist),
|
||||
comment: null,
|
||||
createdDate: item.created,
|
||||
discTitles: getDiscTitles(item),
|
||||
displayArtist: null,
|
||||
duration: getDuration(item.duration),
|
||||
explicit: item.explicitStatus === 'explicit',
|
||||
genres: getGenres(item),
|
||||
id: item.id.toString(),
|
||||
imagePlaceholderUrl: null,
|
||||
imageUrl,
|
||||
isCompilation: item.isCompilation || null,
|
||||
mbzId: item.musicBrainzId || null,
|
||||
mbzReleaseGroupId: null,
|
||||
missing: null,
|
||||
moods: getMoods(item),
|
||||
name: item.name,
|
||||
originalReleaseDate: getOriginalReleaseDate(item),
|
||||
participants: {},
|
||||
recordLabels: getRecordLabels(item),
|
||||
releaseDate: getReleaseDate(item),
|
||||
releaseTypes: getReleaseTypes(item),
|
||||
releaseYear: item.year || null,
|
||||
size: null,
|
||||
songCount: item.songCount,
|
||||
sortName: item.sortName || item.name,
|
||||
tags: {},
|
||||
updatedDate: null,
|
||||
userFavorite: Boolean(item.starred),
|
||||
userFavoriteDate: item.starred || null,
|
||||
userLastPlayedDate: item.played || null,
|
||||
userPlayCount: item.playCount ?? null,
|
||||
userRating: item.userRating || null,
|
||||
version: item.version || null,
|
||||
};
|
||||
},
|
||||
albumArtist: (
|
||||
item: components['schemas']['ArtistID3'] & components['schemas']['ArtistInfo2'],
|
||||
server: ServerListItem,
|
||||
): Artist => {
|
||||
return {
|
||||
_serverId: server?.id || 'unknown',
|
||||
_serverType: ServerType.SUBSONIC,
|
||||
albumCount: item.albumCount ? Number(item.albumCount) : 0,
|
||||
biography: item.biography || null,
|
||||
duration: null,
|
||||
genres: [],
|
||||
id: item.id.toString(),
|
||||
imageUrl: item.coverArt ? getCoverArtUrl(item.coverArt.toString(), server) : null,
|
||||
itemType: LibraryItem.ALBUM_ARTIST,
|
||||
mbzId: item.musicBrainzId || null,
|
||||
name: item.name,
|
||||
playCount: null,
|
||||
similarArtists: [],
|
||||
songCount: null,
|
||||
userFavorite: false,
|
||||
userLastPlayedDate: null,
|
||||
userRating: null,
|
||||
};
|
||||
},
|
||||
genre: (item: components['schemas']['Genre'], server: ServerListItem): Genre => {
|
||||
return {
|
||||
_itemType: LibraryItem.GENRE,
|
||||
_serverId: server.id,
|
||||
_serverType: ServerType.SUBSONIC,
|
||||
albumCount: item.albumCount,
|
||||
id: item.value,
|
||||
imageUrl: null,
|
||||
name: item.value,
|
||||
songCount: item.songCount,
|
||||
};
|
||||
},
|
||||
playlist: (item: components['schemas']['Playlist'], server: ServerListItem): Playlist => {
|
||||
return {
|
||||
_itemType: LibraryItem.PLAYLIST,
|
||||
_serverId: server.id,
|
||||
_serverType: ServerType.SUBSONIC,
|
||||
createdDate: item.created || null,
|
||||
description: item.comment || null,
|
||||
duration: getDuration(item.duration),
|
||||
genres: [],
|
||||
id: item.id.toString(),
|
||||
imageUrl: item.coverArt ? getCoverArtUrl(item.coverArt.toString(), server) : null,
|
||||
name: item.name,
|
||||
owner: item.owner || null,
|
||||
ownerId: item.owner || null,
|
||||
public: item.public || null,
|
||||
size: null,
|
||||
songCount: item.songCount,
|
||||
updatedDate: item.changed,
|
||||
};
|
||||
},
|
||||
song: (item: components['schemas']['Child'], server: ServerListItem): Song => {
|
||||
return {
|
||||
_itemType: LibraryItem.SONG,
|
||||
_serverId: server.id,
|
||||
_serverType: ServerType.SUBSONIC,
|
||||
album: item.album || null,
|
||||
albumArtistName: item.displayAlbumArtist || null,
|
||||
albumArtists: getArtistList(item.albumArtists, item.artistId, item.artist),
|
||||
albumId: item.albumId || null,
|
||||
artistName: item.displayArtist || item.artist || null,
|
||||
artists: getArtistList(item.artists, item.artistId, item.artist),
|
||||
bitDepth: item.bitDepth || null,
|
||||
bitRate: item.bitRate || null,
|
||||
bpm: item.bpm || null,
|
||||
channels: item.channelCount || null,
|
||||
comment: item.comment || null,
|
||||
composer: item.displayComposer || null,
|
||||
container: item.contentType || null,
|
||||
createdDate: item.created || null,
|
||||
discNumber: item.discNumber || 1,
|
||||
discSubtitle: null,
|
||||
duration: getDuration(item.duration),
|
||||
explicit: item.explicitStatus === 'explicit',
|
||||
gain: getGainInfo(item),
|
||||
genres: getGenres(item),
|
||||
id: item.id.toString(),
|
||||
imageUrl: item.coverArt ? getCoverArtUrl(item.coverArt, server) : null,
|
||||
isCompilation: null,
|
||||
isrc: item.isrc || [],
|
||||
lyrics: null,
|
||||
mbzId: item.musicBrainzId || null,
|
||||
missing: false,
|
||||
moods: getMoods(item),
|
||||
name: item.title,
|
||||
participants: getParticipants(item),
|
||||
path: item.path || null,
|
||||
peak: getPeakInfo(item),
|
||||
playCount: item?.playCount || 0,
|
||||
releaseDate: null,
|
||||
releaseYear: item.year ?? null,
|
||||
samplingRate: item.samplingRate || null,
|
||||
size: item.size ?? 0,
|
||||
sortName: item.sortName || item.title,
|
||||
streamUrl: getStreamUrl(item.id, server),
|
||||
tags: {},
|
||||
trackNumber: item.track || 1,
|
||||
updatedDate: null,
|
||||
userFavorite: Boolean(item.starred),
|
||||
userFavoriteDate: item.starred || null,
|
||||
userLastPlayedDate: item.played || null,
|
||||
userRating: item.userRating || null,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const getArtistList = (
|
||||
function getArtistList(
|
||||
artists?: typeof ssType._response.song._type.artists,
|
||||
artistId?: number | string,
|
||||
artistName?: string,
|
||||
) => {
|
||||
) {
|
||||
return artists
|
||||
? artists.map((item) => ({
|
||||
id: item.id.toString(),
|
||||
@@ -50,19 +187,94 @@ const getArtistList = (
|
||||
name: artistName || '',
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
const getParticipants = (
|
||||
function getCoverArtUrl(id: string, server: ServerListItem) {
|
||||
return (
|
||||
`${server.url}/rest/getCoverArt.view` +
|
||||
`?id=${id}` +
|
||||
`&${server.credential}` +
|
||||
'&v=1.16.1' +
|
||||
'&c=Feishin'
|
||||
);
|
||||
}
|
||||
|
||||
function getDiscTitles(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
) {
|
||||
return (item.discTitles || []).map((discTitle) => ({
|
||||
disc: discTitle.disc,
|
||||
title: discTitle.title,
|
||||
}));
|
||||
}
|
||||
|
||||
function getDuration(duration?: number) {
|
||||
// Transform from seconds to milliseconds
|
||||
return duration ? duration * 1000 : 0;
|
||||
}
|
||||
|
||||
function getGainInfo(item: components['schemas']['Child']) {
|
||||
return item.replayGain && (item.replayGain.albumGain || item.replayGain.trackGain)
|
||||
? {
|
||||
album: item.replayGain.albumGain,
|
||||
track: item.replayGain.trackGain,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
function getGenres(
|
||||
item:
|
||||
| z.infer<typeof ssType._response.album>
|
||||
| z.infer<typeof ssType._response.albumListEntry>
|
||||
| z.infer<typeof ssType._response.song>,
|
||||
) => {
|
||||
let participants: null | Record<string, RelatedArtist[]> = null;
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
): RelatedGenre[] {
|
||||
if (item.genres) {
|
||||
return item.genres.map((genre) => ({
|
||||
id: genre.name,
|
||||
imageUrl: null,
|
||||
name: genre.name,
|
||||
}));
|
||||
}
|
||||
|
||||
if (item.genre) {
|
||||
return [
|
||||
{
|
||||
id: item.genre,
|
||||
imageUrl: null,
|
||||
name: item.genre,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getMoods(
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
return (item.moods || []).map((mood) => ({
|
||||
id: mood,
|
||||
name: mood,
|
||||
}));
|
||||
}
|
||||
|
||||
function getOriginalReleaseDate(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
) {
|
||||
return item.originalReleaseDate
|
||||
? formatDate.toUTCDate(
|
||||
`${item.originalReleaseDate.year}-${item.originalReleaseDate.month}-${item.originalReleaseDate.day}`,
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
function getParticipants(item: components['schemas']['Child']) {
|
||||
const participants: Record<string, RelatedArtist[]> = {};
|
||||
|
||||
if (item.contributors) {
|
||||
participants = {};
|
||||
|
||||
for (const contributor of item.contributors) {
|
||||
const artist = {
|
||||
id: contributor.artist.id?.toString() || '',
|
||||
@@ -74,248 +286,54 @@ const getParticipants = (
|
||||
? `${contributor.role} (${contributor.subRole})`
|
||||
: contributor.role;
|
||||
|
||||
if (role in participants) {
|
||||
participants[role].push(artist);
|
||||
} else {
|
||||
participants[role] = [artist];
|
||||
if (!participants[role]) {
|
||||
participants[role] = [];
|
||||
}
|
||||
|
||||
participants[role].push(artist);
|
||||
}
|
||||
}
|
||||
|
||||
return participants;
|
||||
};
|
||||
}
|
||||
|
||||
const getGenres = (
|
||||
item:
|
||||
| z.infer<typeof ssType._response.album>
|
||||
| z.infer<typeof ssType._response.albumListEntry>
|
||||
| z.infer<typeof ssType._response.song>,
|
||||
): Genre[] => {
|
||||
return item.genres
|
||||
? item.genres.map((genre) => ({
|
||||
id: genre.name,
|
||||
imageUrl: null,
|
||||
itemType: LibraryItem.GENRE,
|
||||
name: genre.name,
|
||||
}))
|
||||
: item.genre
|
||||
? [
|
||||
{
|
||||
id: item.genre,
|
||||
imageUrl: null,
|
||||
itemType: LibraryItem.GENRE,
|
||||
name: item.genre,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
};
|
||||
function getPeakInfo(item: components['schemas']['Child']) {
|
||||
return item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
|
||||
? {
|
||||
album: item.replayGain.albumPeak,
|
||||
track: item.replayGain.trackPeak,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
const normalizeSong = (
|
||||
item: z.infer<typeof ssType._response.song>,
|
||||
server: null | ServerListItem,
|
||||
size?: number,
|
||||
): QueueSong => {
|
||||
const imageUrl =
|
||||
getCoverArtUrl({
|
||||
baseUrl: server?.url,
|
||||
coverArtId: item.coverArt?.toString(),
|
||||
credential: server?.credential,
|
||||
size: size || 300,
|
||||
}) || null;
|
||||
function getRecordLabels(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
) {
|
||||
return (item.recordLabels || []).map((recordLabel) => ({
|
||||
id: recordLabel.name,
|
||||
name: recordLabel.name,
|
||||
}));
|
||||
}
|
||||
|
||||
const streamUrl = `${server?.url}/rest/stream.view?id=${item.id}&v=1.13.0&c=Feishin&${server?.credential}`;
|
||||
function getReleaseDate(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
) {
|
||||
return item.releaseDate
|
||||
? formatDate.toUTCDate(
|
||||
`${item.releaseDate.year}-${item.releaseDate.month}-${item.releaseDate.day}`,
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
return {
|
||||
album: item.album || '',
|
||||
albumArtists: getArtistList(item.albumArtists, item.artistId, item.artist),
|
||||
albumId: item.albumId?.toString() || '',
|
||||
artistName: item.artist || '',
|
||||
artists: getArtistList(item.artists, item.artistId, item.artist),
|
||||
bitDepth: item.bitDepth || null,
|
||||
bitRate: item.bitRate || 0,
|
||||
bpm: item.bpm || null,
|
||||
channels: item.channelCount || null,
|
||||
comment: null,
|
||||
compilation: null,
|
||||
container: item.contentType,
|
||||
createdAt: item.created,
|
||||
discNumber: item.discNumber || 1,
|
||||
discSubtitle: null,
|
||||
duration: item.duration ? item.duration * 1000 : 0,
|
||||
gain:
|
||||
item.replayGain && (item.replayGain.albumGain || item.replayGain.trackGain)
|
||||
? {
|
||||
album: item.replayGain.albumGain,
|
||||
track: item.replayGain.trackGain,
|
||||
}
|
||||
: null,
|
||||
genres: getGenres(item),
|
||||
id: item.id.toString(),
|
||||
imagePlaceholderUrl: null,
|
||||
imageUrl,
|
||||
itemType: LibraryItem.SONG,
|
||||
lastPlayedAt: null,
|
||||
lyrics: null,
|
||||
name: item.title,
|
||||
participants: getParticipants(item),
|
||||
path: item.path,
|
||||
peak:
|
||||
item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
|
||||
? {
|
||||
album: item.replayGain.albumPeak,
|
||||
track: item.replayGain.trackPeak,
|
||||
}
|
||||
: null,
|
||||
playCount: item?.playCount || 0,
|
||||
releaseDate: null,
|
||||
releaseYear: item.year ? String(item.year) : null,
|
||||
sampleRate: item.samplingRate || null,
|
||||
serverId: server?.id || 'unknown',
|
||||
serverType: ServerType.SUBSONIC,
|
||||
size: item.size,
|
||||
streamUrl,
|
||||
tags: null,
|
||||
trackNumber: item.track || 1,
|
||||
uniqueId: nanoid(),
|
||||
updatedAt: '',
|
||||
userFavorite: item.starred || false,
|
||||
userRating: item.userRating || null,
|
||||
};
|
||||
};
|
||||
function getReleaseTypes(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
) {
|
||||
return (item.releaseTypes || []).map((releaseType) => ({
|
||||
id: releaseType,
|
||||
name: releaseType,
|
||||
}));
|
||||
}
|
||||
|
||||
const normalizeAlbumArtist = (
|
||||
item:
|
||||
| z.infer<typeof ssType._response.albumArtist>
|
||||
| z.infer<typeof ssType._response.artistListEntry>,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): AlbumArtist => {
|
||||
const imageUrl =
|
||||
getCoverArtUrl({
|
||||
baseUrl: server?.url,
|
||||
coverArtId: item.coverArt?.toString(),
|
||||
credential: server?.credential,
|
||||
size: imageSize || 100,
|
||||
}) || null;
|
||||
|
||||
return {
|
||||
albumCount: item.albumCount ? Number(item.albumCount) : 0,
|
||||
backgroundImageUrl: null,
|
||||
biography: null,
|
||||
duration: null,
|
||||
genres: [],
|
||||
id: item.id.toString(),
|
||||
imageUrl,
|
||||
itemType: LibraryItem.ALBUM_ARTIST,
|
||||
lastPlayedAt: null,
|
||||
mbz: null,
|
||||
name: item.name,
|
||||
playCount: null,
|
||||
serverId: server?.id || 'unknown',
|
||||
serverType: ServerType.SUBSONIC,
|
||||
similarArtists: [],
|
||||
songCount: null,
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeAlbum = (
|
||||
item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): Album => {
|
||||
const imageUrl =
|
||||
getCoverArtUrl({
|
||||
baseUrl: server?.url,
|
||||
coverArtId: item.coverArt?.toString(),
|
||||
credential: server?.credential,
|
||||
size: imageSize || 300,
|
||||
}) || null;
|
||||
|
||||
return {
|
||||
albumArtist: item.artist,
|
||||
albumArtists: getArtistList(item.artists, item.artistId, item.artist),
|
||||
artists: [],
|
||||
backdropImageUrl: null,
|
||||
comment: null,
|
||||
createdAt: item.created,
|
||||
duration: item.duration * 1000,
|
||||
genres: getGenres(item),
|
||||
id: item.id.toString(),
|
||||
imagePlaceholderUrl: null,
|
||||
imageUrl,
|
||||
isCompilation: null,
|
||||
itemType: LibraryItem.ALBUM,
|
||||
lastPlayedAt: null,
|
||||
mbzId: null,
|
||||
name: item.name,
|
||||
originalDate: null,
|
||||
participants: getParticipants(item),
|
||||
playCount: null,
|
||||
releaseDate: item.year ? new Date(Date.UTC(item.year, 0, 1)).toISOString() : null,
|
||||
releaseYear: item.year ? Number(item.year) : null,
|
||||
serverId: server?.id || 'unknown',
|
||||
serverType: ServerType.SUBSONIC,
|
||||
size: null,
|
||||
songCount: item.songCount,
|
||||
songs:
|
||||
(item as z.infer<typeof ssType._response.album>).song?.map((song) =>
|
||||
normalizeSong(song, server),
|
||||
) || [],
|
||||
tags: null,
|
||||
uniqueId: nanoid(),
|
||||
updatedAt: item.created,
|
||||
userFavorite: item.starred || false,
|
||||
userRating: item.userRating || null,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizePlaylist = (
|
||||
item:
|
||||
| z.infer<typeof ssType._response.playlist>
|
||||
| z.infer<typeof ssType._response.playlistListEntry>,
|
||||
server: null | ServerListItem,
|
||||
): Playlist => {
|
||||
return {
|
||||
description: item.comment || null,
|
||||
duration: item.duration * 1000,
|
||||
genres: [],
|
||||
id: item.id.toString(),
|
||||
imagePlaceholderUrl: null,
|
||||
imageUrl: getCoverArtUrl({
|
||||
baseUrl: server?.url,
|
||||
coverArtId: item.coverArt?.toString(),
|
||||
credential: server?.credential,
|
||||
size: 300,
|
||||
}),
|
||||
itemType: LibraryItem.PLAYLIST,
|
||||
name: item.name,
|
||||
owner: item.owner,
|
||||
ownerId: item.owner,
|
||||
public: item.public,
|
||||
serverId: server?.id || 'unknown',
|
||||
serverType: ServerType.SUBSONIC,
|
||||
size: null,
|
||||
songCount: item.songCount,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeGenre = (item: z.infer<typeof ssType._response.genre>): Genre => {
|
||||
return {
|
||||
albumCount: item.albumCount,
|
||||
id: item.value,
|
||||
imageUrl: null,
|
||||
itemType: LibraryItem.GENRE,
|
||||
name: item.value,
|
||||
songCount: item.songCount,
|
||||
};
|
||||
};
|
||||
|
||||
export const ssNormalize = {
|
||||
album: normalizeAlbum,
|
||||
albumArtist: normalizeAlbumArtist,
|
||||
genre: normalizeGenre,
|
||||
playlist: normalizePlaylist,
|
||||
song: normalizeSong,
|
||||
};
|
||||
function getStreamUrl(id: string, server: ServerListItem) {
|
||||
return `${server.url}/rest/stream.view?id=${id}&v=1.16.1&c=Feishin&${server.credential}`;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,17 @@ import { NDAlbumListSort } from '/@/shared/api/navidrome.types';
|
||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Genre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
import {
|
||||
LibraryItem,
|
||||
ListSortOrder,
|
||||
Mood,
|
||||
Participants,
|
||||
RecordLabel,
|
||||
ReleaseType,
|
||||
Tags,
|
||||
} from '/@/shared/types/domain/shared-domain-types';
|
||||
import { Song } from '/@/shared/types/domain/song-domain-types';
|
||||
|
||||
export enum AlbumListSortOptions {
|
||||
@@ -150,52 +158,45 @@ export const albumListSortMap: AlbumListSortMap = {
|
||||
};
|
||||
|
||||
export type Album = {
|
||||
albumArtist: string;
|
||||
albumArtists: RelatedArtist[];
|
||||
_itemType: LibraryItem.ALBUM;
|
||||
_serverId: string;
|
||||
_serverType: ServerType;
|
||||
artistName: null | string;
|
||||
artists: RelatedArtist[];
|
||||
backdropImageUrl: null | string;
|
||||
comment: null | string;
|
||||
createdDate: string;
|
||||
description: null | string;
|
||||
discTitles: {
|
||||
disc: number;
|
||||
title: string;
|
||||
}[];
|
||||
createdDate: null | string;
|
||||
discTitles: DiscTitle[];
|
||||
displayArtist: null | string;
|
||||
duration: null | number;
|
||||
genres: Genre[];
|
||||
explicit: boolean | null;
|
||||
genres: RelatedGenre[];
|
||||
id: string;
|
||||
imagePlaceholderUrl: null | string;
|
||||
imageUrl: null | string;
|
||||
isCompilation: boolean | null;
|
||||
itemType: LibraryItem.ALBUM;
|
||||
mbzAlbumId: null | string;
|
||||
mbzId: null | string;
|
||||
mbzReleaseGroupId: null | string;
|
||||
missing: boolean;
|
||||
missing: boolean | null;
|
||||
moods: Mood[];
|
||||
name: string;
|
||||
originalReleaseDate: null | string;
|
||||
participants: null | Record<string, RelatedArtist[]>;
|
||||
participants: Participants;
|
||||
recordLabels: RecordLabel[];
|
||||
releaseDate: null | string;
|
||||
releaseTypes: {
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
releaseTypes: ReleaseType[];
|
||||
releaseYear: null | number;
|
||||
serverId: string;
|
||||
serverType: ServerType;
|
||||
size: null | number;
|
||||
songCount: null | number;
|
||||
songs?: Song[];
|
||||
sortName: string;
|
||||
tags: Record<string, string[]>;
|
||||
uniqueId: string;
|
||||
updatedDate: string;
|
||||
tags: Tags;
|
||||
updatedDate: null | string;
|
||||
userFavorite: boolean;
|
||||
userFavoriteDate: null | string;
|
||||
userLastPlayedDate: null | string;
|
||||
userPlayCount: null | number;
|
||||
userRating: null | number;
|
||||
} & { songs?: Song[] };
|
||||
version: null | string;
|
||||
};
|
||||
|
||||
export type AlbumDetailQuery = { id: string };
|
||||
|
||||
@@ -208,6 +209,13 @@ export type AlbumInfo = {
|
||||
notes: null | string;
|
||||
};
|
||||
|
||||
export type AlbumWithSongs = Album & { songs: Song[] };
|
||||
|
||||
export type DiscTitle = {
|
||||
disc: number;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export const sortAlbumList = (albums: Album[], sortBy: AlbumListSort, sortOrder: ListSortOrder) => {
|
||||
let results = albums;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
||||
import { NDAlbumArtistListSort } from '/@/shared/api/navidrome.types';
|
||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import { Genre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
|
||||
@@ -31,45 +31,26 @@ export const ArtistListSortOptionsLabels = {
|
||||
[ArtistListSortOptions.TRACK_COUNT]: i18n.t('filter.trackCount'),
|
||||
};
|
||||
|
||||
export type AlbumArtist = {
|
||||
export type Artist = {
|
||||
_serverId: string;
|
||||
_serverType: ServerType;
|
||||
albumCount: null | number;
|
||||
backgroundImageUrl: null | string;
|
||||
biography: null | string;
|
||||
duration: null | number;
|
||||
genres: Genre[];
|
||||
genres: RelatedGenre[];
|
||||
id: string;
|
||||
imageUrl: null | string;
|
||||
itemType: LibraryItem.ALBUM_ARTIST;
|
||||
lastPlayedAt: null | string;
|
||||
mbz: null | string;
|
||||
mbzId: null | string;
|
||||
name: string;
|
||||
playCount: null | number;
|
||||
serverId: string;
|
||||
serverType: ServerType;
|
||||
similarArtists: null | RelatedArtist[];
|
||||
songCount: null | number;
|
||||
userFavorite: boolean;
|
||||
userLastPlayedDate: null | string;
|
||||
userRating: null | number;
|
||||
};
|
||||
|
||||
export type Artist = {
|
||||
biography: null | string;
|
||||
createdAt: string;
|
||||
id: string;
|
||||
itemType: LibraryItem.ARTIST;
|
||||
name: string;
|
||||
remoteCreatedAt: null | string;
|
||||
serverFolderId: string;
|
||||
serverId: string;
|
||||
serverType: ServerType;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type RelatedAlbumArtist = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type RelatedArtist = {
|
||||
id: string;
|
||||
imageUrl: null | string;
|
||||
@@ -142,7 +123,7 @@ export type AlbumArtistDetailQuery = { id: string };
|
||||
|
||||
export type AlbumArtistDetailRequest = { query: AlbumArtistDetailQuery };
|
||||
|
||||
export type AlbumArtistDetailResponse = AlbumArtist | null;
|
||||
export type AlbumArtistDetailResponse = Artist | null;
|
||||
|
||||
export interface ArtistListQuery extends BaseQuery<ArtistListSort> {
|
||||
_custom?: {
|
||||
@@ -158,7 +139,7 @@ export interface ArtistListQuery extends BaseQuery<ArtistListSort> {
|
||||
|
||||
export type ArtistListRequest = { query: ArtistListQuery };
|
||||
|
||||
export type ArtistListResponse = BasePaginatedResponse<AlbumArtist[]> | null | undefined;
|
||||
export type ArtistListResponse = BasePaginatedResponse<Artist[]> | null | undefined;
|
||||
type ArtistListSortMap = {
|
||||
jellyfin: Record<ArtistListSort, JFArtistListSort | undefined>;
|
||||
navidrome: Record<ArtistListSort, undefined>;
|
||||
@@ -233,7 +214,7 @@ export interface AlbumArtistListQuery extends BaseQuery<AlbumArtistListSort> {
|
||||
|
||||
export type AlbumArtistListRequest = { query: AlbumArtistListQuery };
|
||||
|
||||
export type AlbumArtistListResponse = BasePaginatedResponse<AlbumArtist[]> | null | undefined;
|
||||
export type AlbumArtistListResponse = BasePaginatedResponse<Artist[]> | null | undefined;
|
||||
|
||||
export type ArtistInfoQuery = {
|
||||
artistId: string;
|
||||
@@ -244,7 +225,7 @@ export type ArtistInfoQuery = {
|
||||
export type ArtistInfoRequest = { query: ArtistInfoQuery };
|
||||
|
||||
export const sortAlbumArtistList = (
|
||||
artists: AlbumArtist[],
|
||||
artists: Artist[],
|
||||
sortBy: AlbumArtistListSort | ArtistListSort,
|
||||
sortOrder: ListSortOrder,
|
||||
) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import i18n from '/@/i18n/i18n';
|
||||
import { JFGenreListSort } from '/@/shared/api/jellyfin.types';
|
||||
import { NDGenreListSort } from '/@/shared/api/navidrome.types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { UserListSort } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
@@ -18,12 +19,14 @@ export const GenreListSortOptionsLabels = {
|
||||
};
|
||||
|
||||
export type Genre = {
|
||||
albumCount?: number;
|
||||
_itemType: LibraryItem.GENRE;
|
||||
_serverId: string;
|
||||
_serverType: ServerType;
|
||||
albumCount: null | number;
|
||||
id: string;
|
||||
imageUrl: null | string;
|
||||
itemType: LibraryItem.GENRE;
|
||||
name: string;
|
||||
songCount?: number;
|
||||
songCount: null | number;
|
||||
};
|
||||
|
||||
export interface GenreListQuery extends BaseQuery<GenreListSort> {
|
||||
@@ -41,6 +44,12 @@ export type GenreListRequest = { query: GenreListQuery };
|
||||
|
||||
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
|
||||
|
||||
export type RelatedGenre = {
|
||||
id: string;
|
||||
imageUrl: null | string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type GenreListSortMap = {
|
||||
jellyfin: Record<GenreListSort, JFGenreListSort | undefined>;
|
||||
navidrome: Record<GenreListSort, NDGenreListSort | undefined>;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
BasePaginatedResponse,
|
||||
BaseQuery,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { Genre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { Genre, RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { Song, SongListSort } from '/@/shared/types/domain/song-domain-types';
|
||||
@@ -89,24 +89,26 @@ export type DeletePlaylistRequest = {
|
||||
export type DeletePlaylistResponse = null | undefined;
|
||||
|
||||
export type Playlist = {
|
||||
_itemType: LibraryItem.PLAYLIST;
|
||||
_serverId: string;
|
||||
_serverType: ServerType;
|
||||
createdDate: null | string;
|
||||
description: null | string;
|
||||
duration: null | number;
|
||||
genres: Genre[];
|
||||
genres: RelatedGenre[];
|
||||
id: string;
|
||||
imagePlaceholderUrl: null | string;
|
||||
imageUrl: null | string;
|
||||
itemType: LibraryItem.PLAYLIST;
|
||||
name: string;
|
||||
owner: null | string;
|
||||
ownerId: null | string;
|
||||
public: boolean | null;
|
||||
rules?: null | Record<string, any>;
|
||||
serverId: string;
|
||||
serverType: ServerType;
|
||||
size: null | number;
|
||||
songCount: null | number;
|
||||
sync?: boolean | null;
|
||||
updatedDate: null | string;
|
||||
};
|
||||
|
||||
export interface PlaylistListQuery extends BaseQuery<PlaylistListSort> {
|
||||
_custom?: {
|
||||
jellyfin?: Partial<z.infer<typeof jfType._parameters.playlistList>>;
|
||||
@@ -121,6 +123,10 @@ export type PlaylistListRequest = { query: PlaylistListQuery };
|
||||
|
||||
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]> | null | undefined;
|
||||
|
||||
export type PlaylistSong = Song & {
|
||||
playlistItemId: string;
|
||||
};
|
||||
|
||||
export type RemoveFromPlaylistQuery = {
|
||||
id: string;
|
||||
songId: string[];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Album } from '/@/shared/types/domain/album-domain-types';
|
||||
import { AlbumArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Artist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Song } from '/@/shared/types/domain/song-domain-types';
|
||||
|
||||
export type SearchAlbumArtistsQuery = {
|
||||
@@ -30,7 +30,7 @@ export type SearchQuery = {
|
||||
export type SearchRequest = { query: SearchQuery };
|
||||
|
||||
export type SearchResponse = {
|
||||
albumArtists: AlbumArtist[];
|
||||
albumArtists: Artist[];
|
||||
albums: Album[];
|
||||
songs: Song[];
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { JFSortOrder } from '/@/shared/api/jellyfin.types';
|
||||
import { NDSortOrder } from '/@/shared/api/navidrome.types';
|
||||
import { Album } from '/@/shared/types/domain/album-domain-types';
|
||||
import { AlbumArtist, Artist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Artist, Artist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { QueueSong } from '/@/shared/types/domain/player-domain-types';
|
||||
import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
|
||||
import { Song } from '/@/shared/types/domain/song-domain-types';
|
||||
@@ -19,15 +19,9 @@ export enum ListSortOrder {
|
||||
DESC = 'DESC',
|
||||
}
|
||||
|
||||
export type AnyLibraryItem = Album | AlbumArtist | Artist | Playlist | QueueSong | Song;
|
||||
export type AnyLibraryItem = Album | Artist | Artist | Playlist | QueueSong | Song;
|
||||
|
||||
export type AnyLibraryItems =
|
||||
| Album[]
|
||||
| AlbumArtist[]
|
||||
| Artist[]
|
||||
| Playlist[]
|
||||
| QueueSong[]
|
||||
| Song[];
|
||||
export type AnyLibraryItems = Album[] | Artist[] | Artist[] | Playlist[] | QueueSong[] | Song[];
|
||||
type SortOrderMap = {
|
||||
jellyfin: Record<ListSortOrder, JFSortOrder>;
|
||||
navidrome: Record<ListSortOrder, NDSortOrder>;
|
||||
@@ -56,3 +50,22 @@ export type FontData = {
|
||||
postscriptName: string;
|
||||
style: string;
|
||||
};
|
||||
|
||||
export type Mood = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Participants = Record<string, RelatedArtist[]>;
|
||||
|
||||
export type RecordLabel = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ReleaseType = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Tags = Record<string, string[]>;
|
||||
|
||||
@@ -8,10 +8,16 @@ import { NDSongListSort } from '/@/shared/api/navidrome.types';
|
||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Genre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { Played, QueueSong } from '/@/shared/types/domain/player-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
import {
|
||||
LibraryItem,
|
||||
ListSortOrder,
|
||||
Mood,
|
||||
Participants,
|
||||
Tags,
|
||||
} from '/@/shared/types/domain/shared-domain-types';
|
||||
|
||||
export enum SongListSortOptions {
|
||||
ALBUM = 'album',
|
||||
@@ -77,46 +83,54 @@ export enum SongListSort {
|
||||
}
|
||||
|
||||
export type Song = {
|
||||
_itemType: LibraryItem.SONG;
|
||||
_serverId: string;
|
||||
_serverType: ServerType;
|
||||
album: null | string;
|
||||
albumArtistName: null | string;
|
||||
albumArtists: RelatedArtist[];
|
||||
albumId: string;
|
||||
artistName: string;
|
||||
albumId: null | string;
|
||||
artistName: null | string;
|
||||
artists: RelatedArtist[];
|
||||
bitRate: number;
|
||||
bitDepth: null | number;
|
||||
bitRate: null | number;
|
||||
bpm: null | number;
|
||||
channels: null | number;
|
||||
comment: null | string;
|
||||
compilation: boolean | null;
|
||||
composer: null | string;
|
||||
container: null | string;
|
||||
createdAt: string;
|
||||
createdDate: null | string;
|
||||
discNumber: number;
|
||||
discSubtitle: null | string;
|
||||
duration: number;
|
||||
explicit: boolean | null;
|
||||
gain: GainInfo | null;
|
||||
genres: Genre[];
|
||||
genres: RelatedGenre[];
|
||||
id: string;
|
||||
imagePlaceholderUrl: null | string;
|
||||
imageUrl: null | string;
|
||||
itemType: LibraryItem.SONG;
|
||||
lastPlayedAt: null | string;
|
||||
isCompilation: boolean | null;
|
||||
isrc: string[];
|
||||
lyrics: null | string;
|
||||
mbzId: null | string;
|
||||
missing: boolean | null;
|
||||
moods: Mood[];
|
||||
name: string;
|
||||
participants: null | Record<string, RelatedArtist[]>;
|
||||
participants: Participants;
|
||||
path: null | string;
|
||||
peak: GainInfo | null;
|
||||
playCount: number;
|
||||
playlistItemId?: string;
|
||||
releaseDate: null | string;
|
||||
releaseYear: null | string;
|
||||
serverId: string;
|
||||
serverType: ServerType;
|
||||
releaseYear: null | number;
|
||||
samplingRate: null | number;
|
||||
size: number;
|
||||
sortName: string;
|
||||
streamUrl: string;
|
||||
tags: null | Record<string, string[]>;
|
||||
tags: Tags;
|
||||
trackNumber: number;
|
||||
uniqueId: string;
|
||||
updatedAt: string;
|
||||
updatedDate: null | string;
|
||||
userFavorite: boolean;
|
||||
userFavoriteDate: null | string;
|
||||
userLastPlayedDate: null | string;
|
||||
userRating: null | number;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ReactNode } from 'react';
|
||||
import { Song } from 'src/main/features/core/lyrics/netease';
|
||||
|
||||
import { Album } from '/@/shared/types/domain/album-domain-types';
|
||||
import { AlbumArtist, Artist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Artist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { QueueSong } from '/@/shared/types/domain/player-domain-types';
|
||||
import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
@@ -159,7 +159,7 @@ export enum TableColumn {
|
||||
|
||||
export type GridCardData = {
|
||||
cardControls: any;
|
||||
cardRows: CardRow<Album | AlbumArtist | Artist | Playlist | Song>[];
|
||||
cardRows: CardRow<Album | Artist | Artist | Playlist | Song>[];
|
||||
columnCount: number;
|
||||
display: ListDisplayType;
|
||||
handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
|
||||
|
||||
Reference in New Issue
Block a user