refactor(artist): co-locate artist feature into features/artist

This commit is contained in:
Psychotoxical
2026-06-30 01:27:01 +02:00
parent 17ea96dcdb
commit 9ffc42688c
103 changed files with 301 additions and 254 deletions
@@ -1,12 +1,12 @@
import React from 'react';
import type { SubsonicArtist } from '../../api/subsonicTypes';
import { ArtistCoverArtImage } from '../../cover/ArtistCoverArtImage';
import type { SubsonicArtist } from '@/api/subsonicTypes';
import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage';
import {
COVER_DENSE_ARTIST_LIST_CSS_PX,
COVER_DENSE_GRID_MIN_CELL_CSS_PX,
} from '../../cover/layoutSizes';
import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '../../constants/appScroll';
import { nameColor, nameInitial } from '../../utils/componentHelpers/artistsHelpers';
} from '@/cover/layoutSizes';
import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
import { nameColor, nameInitial } from '@/features/artist/utils/artistsHelpers';
interface AvatarProps {
artist: SubsonicArtist;
@@ -1,13 +1,13 @@
import type { SubsonicArtist } from '../api/subsonicTypes';
import type { SubsonicArtist } from '@/api/subsonicTypes';
import React, { useMemo } from 'react';
import { Users } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { CoverArtImage } from '../cover/CoverArtImage';
import { useArtistCoverRef } from '../cover/useLibraryCoverRef';
import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '../cover/layoutSizes';
import { useNavigateToArtist } from '../hooks/useNavigateToArtist';
import { coverServerScopeForServerId } from '../cover/serverScope';
import { appendServerQuery } from '../utils/navigation/detailServerScope';
import { CoverArtImage } from '@/cover/CoverArtImage';
import { useArtistCoverRef } from '@/cover/useLibraryCoverRef';
import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes';
import { useNavigateToArtist } from '@/features/artist/hooks/useNavigateToArtist';
import { coverServerScopeForServerId } from '@/cover/serverScope';
import { appendServerQuery } from '@/utils/navigation/detailServerScope';
interface Props {
artist: SubsonicArtist;
@@ -1,26 +1,26 @@
import React, { useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAlbumDetailBack } from '../../hooks/useAlbumDetailBack';
import { useAlbumDetailBack } from '@/hooks/useAlbumDetailBack';
import {
ArrowLeft, Camera, Check, HardDriveDownload, Heart,
Loader2, Play, Radio, Share2, Shuffle, Users,
} from 'lucide-react';
import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo } from '../../api/subsonicTypes';
import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo } from '@/api/subsonicTypes';
import { useOfflineStore } from '@/features/offline';
import { useAuthStore } from '../../store/authStore';
import { useThemeStore } from '../../store/themeStore';
import { useArtistOfflineState } from '../../hooks/useArtistOfflineState';
import { useIsMobile } from '../../hooks/useIsMobile';
import { ArtistHeroCover } from '../../cover/artistHero';
import { useArtistBanner, useArtistFanart } from '../../cover/useArtistFanart';
import { backdropFromConfig } from '../../cover/artistBackdrop';
import { usePlaybackCoverArt } from '../../cover/usePlaybackCoverArt';
import { useAuthStore } from '@/store/authStore';
import { useThemeStore } from '@/store/themeStore';
import { useArtistOfflineState } from '@/features/artist/hooks/useArtistOfflineState';
import { useIsMobile } from '@/hooks/useIsMobile';
import { ArtistHeroCover } from '@/cover/artistHero';
import { useArtistBanner, useArtistFanart } from '@/cover/useArtistFanart';
import { backdropFromConfig } from '@/cover/artistBackdrop';
import { usePlaybackCoverArt } from '@/cover/usePlaybackCoverArt';
import { useCachedUrl } from '@/ui/CachedImage';
import { useCoverLightboxSrc } from '../../cover/lightbox';
import type { CoverArtRef } from '../../cover/types';
import LastfmIcon from '../LastfmIcon';
import WikipediaIcon from '../WikipediaIcon';
import StarRating from '../StarRating';
import { useCoverLightboxSrc } from '@/cover/lightbox';
import type { CoverArtRef } from '@/cover/types';
import LastfmIcon from '@/components/LastfmIcon';
import WikipediaIcon from '@/components/WikipediaIcon';
import StarRating from '@/components/StarRating';
import { tooltipAttrs } from '@/ui/tooltipAttrs';
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
@@ -2,8 +2,8 @@ import React, { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ChevronDown, ChevronUp } from 'lucide-react';
import type { SubsonicArtist } from '../../api/subsonicTypes';
import { useIsMobile } from '../../hooks/useIsMobile';
import type { SubsonicArtist } from '@/api/subsonicTypes';
import { useIsMobile } from '@/hooks/useIsMobile';
interface Props {
marginTop: string;
@@ -1,14 +1,14 @@
import React, { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { AudioLines, ChevronRight, Play, Square } from 'lucide-react';
import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes';
import { usePlayerStore } from '../../store/playerStore';
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes';
import { usePlayerStore } from '@/store/playerStore';
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
import { useOrbitSongRowBehavior } from '@/features/orbit';
import { songToTrack } from '../../utils/playback/songToTrack';
import { formatTrackTime } from '../../utils/format/formatDuration';
import ArtistTopTrackCover from './ArtistTopTrackCover';
import { topSongAlbumForCover } from './topSongAlbumForCover';
import { songToTrack } from '@/utils/playback/songToTrack';
import { formatTrackTime } from '@/utils/format/formatDuration';
import ArtistTopTrackCover from '@/features/artist/components/ArtistTopTrackCover';
import { topSongAlbumForCover } from '@/features/artist/components/topSongAlbumForCover';
interface Props {
topSongs: SubsonicSong[];
+2 -2
View File
@@ -1,6 +1,6 @@
import type { SubsonicArtist } from '../api/subsonicTypes';
import type { SubsonicArtist } from '@/api/subsonicTypes';
import React, { useRef, useState, useEffect, useLayoutEffect } from 'react';
import ArtistCardLocal from './ArtistCardLocal';
import ArtistCardLocal from '@/features/artist/components/ArtistCardLocal';
import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
@@ -1,8 +1,8 @@
import React from 'react';
import { CoverArtImage } from '../../cover/CoverArtImage';
import { useAlbumCoverRef } from '../../cover/useLibraryCoverRef';
import { COVER_ARTIST_TOP_TRACK_CSS_PX } from '../../cover/layoutSizes';
import type { TopSongAlbumCoverSource } from './topSongAlbumForCover';
import { CoverArtImage } from '@/cover/CoverArtImage';
import { useAlbumCoverRef } from '@/cover/useLibraryCoverRef';
import { COVER_ARTIST_TOP_TRACK_CSS_PX } from '@/cover/layoutSizes';
import type { TopSongAlbumCoverSource } from '@/features/artist/components/topSongAlbumForCover';
/** 32px album thumb — same cover ref path as {@link AlbumCard} on artist pages. */
export default function ArtistTopTrackCover({ album }: { album: TopSongAlbumCoverSource }) {
@@ -1,11 +1,11 @@
import React from 'react';
import { Check } from 'lucide-react';
import type { TFunction } from 'i18next';
import type { SubsonicArtist } from '../../api/subsonicTypes';
import type { PlayerState } from '../../store/playerStoreTypes';
import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '../../constants/appScroll';
import { VirtualCardGrid } from '../VirtualCardGrid';
import { ArtistCardAvatar } from './ArtistAvatars';
import type { SubsonicArtist } from '@/api/subsonicTypes';
import type { PlayerState } from '@/store/playerStoreTypes';
import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
import { ArtistCardAvatar } from '@/features/artist/components/ArtistAvatars';
interface TileProps {
artist: SubsonicArtist;
@@ -1,10 +1,10 @@
import React from 'react';
import type { Virtualizer } from '@tanstack/react-virtual';
import type { TFunction } from 'i18next';
import type { SubsonicArtist } from '../../api/subsonicTypes';
import type { PlayerState } from '../../store/playerStoreTypes';
import { OTHER_BUCKET, type ArtistListFlatRow } from '../../utils/componentHelpers/artistsHelpers';
import { ArtistRowAvatar } from './ArtistAvatars';
import type { SubsonicArtist } from '@/api/subsonicTypes';
import type { PlayerState } from '@/store/playerStoreTypes';
import { OTHER_BUCKET, type ArtistListFlatRow } from '@/features/artist/utils/artistsHelpers';
import { ArtistRowAvatar } from '@/features/artist/components/ArtistAvatars';
interface RowProps {
artist: SubsonicArtist;
@@ -1,5 +1,5 @@
import React, { Fragment } from 'react';
import type { SubsonicOpenArtistRef } from '../api/subsonicTypes';
import type { SubsonicOpenArtistRef } from '@/api/subsonicTypes';
interface Props {
refs: SubsonicOpenArtistRef[];
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { topSongAlbumForCover, topSongAlbumsForCoverWarm, artistDetailCoverWarmAlbums } from './topSongAlbumForCover';
import { topSongAlbumForCover, topSongAlbumsForCoverWarm, artistDetailCoverWarmAlbums } from '@/features/artist/components/topSongAlbumForCover';
describe('topSongAlbumForCover', () => {
it('uses the artist album row when albumId matches', () => {
@@ -1,4 +1,4 @@
import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes';
import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes';
export type TopSongAlbumCoverSource = Pick<SubsonicAlbum, 'id' | 'coverArt' | 'name'>;