Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature]: Show album comment, Last.fm/MusicBrainz links #450

Merged
merged 7 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
"removeFromQueue": "remove from queue",
"setRating": "set rating",
"toggleSmartPlaylistEditor": "toggle $t(entity.smartPlaylist) editor",
"viewPlaylists": "view $t(entity.playlist_other)"
"viewPlaylists": "view $t(entity.playlist_other)",
"openIn": {
"lastfm": "Open in Last.fm",
"musicbrainz": "Open in MusicBrainz"
}
},
"common": {
"action_one": "action",
Expand Down Expand Up @@ -414,6 +418,8 @@
"discordUpdateInterval_description": "the time in seconds between each update (minimum 15 seconds)",
"enableRemote": "enable remote control server",
"enableRemote_description": "enables the remote control server to allow other devices to control the application",
"externalLinks": "show external links",
"externalLinks_description": "enables showing external links (Last.fm, MusicBrainz) on artist/album pages",
"exitToTray": "exit to tray",
"exitToTray_description": "exit the application to the system tray",
"floatingQueueArea": "show floating queue hover area",
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/api/jellyfin/jellyfin-normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ const normalizeAlbum = (
imageSize?: number,
): Album => {
return {
albumArtist: item.AlbumArtist,
albumArtists:
item.AlbumArtists.map((entry) => ({
id: entry.Id,
Expand All @@ -214,6 +215,7 @@ const normalizeAlbum = (
name: entry.Name,
})),
backdropImageUrl: null,
comment: null,
createdAt: item.DateCreated,
duration: item.RunTimeTicks / 10000,
genres: item.GenreItems?.map((entry) => ({
Expand All @@ -232,6 +234,7 @@ const normalizeAlbum = (
isCompilation: null,
itemType: LibraryItem.ALBUM,
lastPlayedAt: null,
mbzId: item.ProviderIds?.MusicBrainzAlbum || null,
name: item.Name,
playCount: item.UserData?.PlayCount || 0,
releaseDate: item.PremiereDate?.split('T')[0] || null,
Expand Down Expand Up @@ -287,6 +290,7 @@ const normalizeAlbumArtist = (
}),
itemType: LibraryItem.ALBUM_ARTIST,
lastPlayedAt: null,
mbz: item.ProviderIds?.MusicBrainzArtist || null,
name: item.Name,
playCount: item.UserData?.PlayCount || 0,
serverId: server?.id || '',
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/api/jellyfin/jellyfin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,11 @@ const song = z.object({
UserData: userData.optional(),
});

const providerIds = z.object({
MusicBrainzAlbum: z.string().optional(),
MusicBrainzArtist: z.string().optional(),
});

const albumArtist = z.object({
BackdropImageTags: z.array(z.string()),
ChannelId: z.null(),
Expand All @@ -435,6 +440,7 @@ const albumArtist = z.object({
LocationType: z.string(),
Name: z.string(),
Overview: z.string(),
ProviderIds: providerIds.optional(),
RunTimeTicks: z.number(),
ServerId: z.string(),
Type: z.string(),
Expand Down Expand Up @@ -466,6 +472,7 @@ const album = z.object({
ParentLogoItemId: z.string(),
PremiereDate: z.string().optional(),
ProductionYear: z.number(),
ProviderIds: providerIds.optional(),
RunTimeTicks: z.number(),
ServerId: z.string(),
Songs: z.array(song).optional(), // This is not a native Jellyfin property -- this is used for combined album detail
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/api/navidrome/navidrome-normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,11 @@ const normalizeAlbum = (
const imageBackdropUrl = imageUrl?.replace(/size=\d+/, 'size=1000') || null;

return {
albumArtist: item.albumArtist,
albumArtists: [{ id: item.albumArtistId, imageUrl: null, name: item.albumArtist }],
artists: [{ id: item.artistId, imageUrl: null, name: item.artist }],
backdropImageUrl: imageBackdropUrl,
comment: item.comment || null,
createdAt: item.createdAt.split('T')[0],
duration: item.duration * 1000 || null,
genres: item.genres?.map((genre) => ({
Expand All @@ -168,6 +170,7 @@ const normalizeAlbum = (
isCompilation: item.compilation,
itemType: LibraryItem.ALBUM,
lastPlayedAt: normalizePlayDate(item),
mbzId: item.mbzAlbumId || null,
name: item.name,
playCount: item.playCount,
releaseDate: new Date(item.minYear, 0, 1).toISOString(),
Expand Down Expand Up @@ -216,6 +219,7 @@ const normalizeAlbumArtist = (
imageUrl: imageUrl || null,
itemType: LibraryItem.ALBUM_ARTIST,
lastPlayedAt: normalizePlayDate(item),
mbz: item.mbzArtistId || null,
name: item.name,
playCount: item.playCount,
serverId: server?.id || 'unknown',
Expand Down
1 change: 1 addition & 0 deletions src/renderer/api/navidrome/navidrome-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const album = z.object({
allArtistIds: z.string(),
artist: z.string(),
artistId: z.string(),
comment: z.string().optional(),
compilation: z.boolean(),
coverArtId: z.string().optional(), // Removed after v0.48.0
coverArtPath: z.string().optional(), // Removed after v0.48.0
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/api/subsonic/subsonic-normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const normalizeAlbumArtist = (
imageUrl,
itemType: LibraryItem.ALBUM_ARTIST,
lastPlayedAt: null,
mbz: null,
name: item.name,
playCount: null,
serverId: server?.id || 'unknown',
Expand All @@ -150,11 +151,13 @@ const normalizeAlbum = (
}) || null;

return {
albumArtist: item.artist,
albumArtists: item.artistId
? [{ id: item.artistId, imageUrl: null, name: item.artist }]
: [],
artists: item.artistId ? [{ id: item.artistId, imageUrl: null, name: item.artist }] : [],
backdropImageUrl: null,
comment: null,
createdAt: item.created,
duration: item.duration,
genres: item.genre
Expand All @@ -173,6 +176,7 @@ const normalizeAlbum = (
isCompilation: null,
itemType: LibraryItem.ALBUM,
lastPlayedAt: null,
mbzId: null,
name: item.name,
playCount: null,
releaseDate: item.year ? new Date(item.year, 0, 1).toISOString() : null,
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@ export type Genre = {
};

export type Album = {
albumArtist: string;
albumArtists: RelatedArtist[];
artists: RelatedArtist[];
backdropImageUrl: string | null;
comment: string | null;
createdAt: string;
duration: number | null;
genres: Genre[];
Expand All @@ -156,6 +158,7 @@ export type Album = {
isCompilation: boolean | null;
itemType: LibraryItem.ALBUM;
lastPlayedAt: string | null;
mbzId: string | null;
name: string;
playCount: number | null;
releaseDate: string | null;
Expand Down Expand Up @@ -228,6 +231,7 @@ export type AlbumArtist = {
imageUrl: string | null;
itemType: LibraryItem.ALBUM_ARTIST;
lastPlayedAt: string | null;
mbz: string | null;
name: string;
playCount: number | null;
serverId: string;
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export * from './select';
export * from './skeleton';
export * from './slider';
export * from './spinner';
export * from './spoiler';
export * from './switch';
export * from './tabs';
export * from './text';
Expand Down
39 changes: 39 additions & 0 deletions src/renderer/components/spoiler/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import clsx from 'clsx';
import { HTMLAttributes, ReactNode, useRef, useState } from 'react';
import styles from './spoiler.module.scss';
import { useIsOverflow } from '/@/renderer/hooks';

interface SpoilerProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
defaultOpened?: boolean;
maxHeight?: number;
}

export const Spoiler = ({ maxHeight, defaultOpened, children, ...props }: SpoilerProps) => {
const ref = useRef(null);
const isOverflow = useIsOverflow(ref);
const [isExpanded, setIsExpanded] = useState(!!defaultOpened);

const spoilerClassNames = clsx(styles.spoiler, {
[styles.canExpand]: isOverflow,
[styles.isExpanded]: isExpanded,
});

const handleToggleExpand = () => {
setIsExpanded((val) => !val);
};

return (
<div
ref={ref}
className={spoilerClassNames}
role="button"
style={{ maxHeight: maxHeight ?? '100px' }}
tabIndex={-1}
onClick={handleToggleExpand}
{...props}
>
{children}
</div>
);
};
31 changes: 31 additions & 0 deletions src/renderer/components/spoiler/spoiler.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.control:hover {
color: var(--btn-subtle-fg-hover);
text-decoration: none;
}

.spoiler {
position: relative;
text-align: justify;
width: 100%;
height: 100%;
overflow: hidden;
}

.spoiler:not(.is-expanded).can-expand:after {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
content: '';
background: linear-gradient(to top, var(--main-bg) 10%, transparent 60%);
pointer-events: none;
}

.spoiler.can-expand {
cursor: pointer;
}

.spoiler.is-expanded {
max-height: 2500px !important;
}
17 changes: 4 additions & 13 deletions src/renderer/components/virtual-table/cells/note-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,7 @@ import { Skeleton } from '/@/renderer/components/skeleton';
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
import { useMemo } from 'react';
import { Text } from '/@/renderer/components/text';

const URL_REGEX =
/((?:https?:\/\/)?(?:[\w-]{1,32}(?:\.[\w-]{1,32})+)(?:\/[\w\-./?%&=][^.|^\s]*)?)/g;

const replaceURLWithHTMLLinks = (text: string) => {
const urlRegex = new RegExp(URL_REGEX, 'g');
return text.replaceAll(
urlRegex,
(url) => `<a href="${url}" target="_blank" rel="noreferrer">${url}</a>`,
);
};
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';

export const NoteCell = ({ value }: ICellRendererParams) => {
const formattedValue = useMemo(() => {
Expand All @@ -39,9 +29,10 @@ export const NoteCell = ({ value }: ICellRendererParams) => {
<CellContainer $position="left">
<Text
$secondary
dangerouslySetInnerHTML={{ __html: formattedValue }}
overflow="hidden"
/>
>
{formattedValue}
</Text>
</CellContainer>
);
};
Loading
Loading