From f05183d4122ef12ca6ad44ffd17fe6db1977d136 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:08:15 +0200 Subject: [PATCH] refactor(randomMix): co-locate random/lucky-mix feature into features/randomMix --- src/app/AppRoutes.tsx | 8 ++++---- src/components/MobileMoreOverlay.tsx | 2 +- src/features/queue/components/QueueList.tsx | 2 +- src/features/queue/components/QueuePanel.tsx | 2 +- .../components}/RandomMixFiltersPanel.tsx | 2 +- .../components}/RandomMixGenrePanel.tsx | 0 .../randomMix/components}/RandomMixHeader.tsx | 0 .../randomMix/components}/RandomMixTrackRow.tsx | 2 +- .../randomMix}/hooks/useLuckyMixAvailable.ts | 2 +- src/features/randomMix/index.ts | 13 +++++++++++++ src/{ => features/randomMix}/pages/LuckyMix.tsx | 2 +- .../randomMix}/pages/RandomLanding.tsx | 2 +- src/{ => features/randomMix}/pages/RandomMix.tsx | 16 ++++++++-------- .../randomMix}/store/luckyMixStore.ts | 0 .../mix => features/randomMix/utils}/luckyMix.ts | 12 ++++++------ .../randomMix/utils}/luckyMixHelpers.ts | 2 +- .../randomMix/utils}/randomMixHelpers.test.ts | 2 +- .../randomMix/utils}/randomMixHelpers.ts | 2 +- .../settings/components/SidebarCustomizer.tsx | 2 +- src/features/sidebar/components/Sidebar.tsx | 2 +- 20 files changed, 44 insertions(+), 31 deletions(-) rename src/{components/randomMix => features/randomMix/components}/RandomMixFiltersPanel.tsx (99%) rename src/{components/randomMix => features/randomMix/components}/RandomMixGenrePanel.tsx (100%) rename src/{components/randomMix => features/randomMix/components}/RandomMixHeader.tsx (100%) rename src/{components/randomMix => features/randomMix/components}/RandomMixTrackRow.tsx (98%) rename src/{ => features/randomMix}/hooks/useLuckyMixAvailable.ts (97%) create mode 100644 src/features/randomMix/index.ts rename src/{ => features/randomMix}/pages/LuckyMix.tsx (84%) rename src/{ => features/randomMix}/pages/RandomLanding.tsx (95%) rename src/{ => features/randomMix}/pages/RandomMix.tsx (96%) rename src/{ => features/randomMix}/store/luckyMixStore.ts (100%) rename src/{utils/mix => features/randomMix/utils}/luckyMix.ts (97%) rename src/{utils/mix => features/randomMix/utils}/luckyMixHelpers.ts (99%) rename src/{utils/componentHelpers => features/randomMix/utils}/randomMixHelpers.test.ts (94%) rename src/{utils/componentHelpers => features/randomMix/utils}/randomMixHelpers.ts (94%) diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index 5469e8b6..5e62235e 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -4,7 +4,7 @@ import MobilePlayerView from '../components/MobilePlayerView'; import { useIsMobile } from '@/lib/hooks/useIsMobile'; import { useSidebarStore } from '@/features/sidebar'; import { useAuthStore } from '../store/authStore'; -import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable'; +import { useLuckyMixAvailable } from '@/features/randomMix'; import { resolveStartRoute } from '@/features/sidebar'; // Route-level lazy loading: keeps the non-page graph (shell, player, stores) in @@ -17,13 +17,13 @@ const Composers = lazy(() => import('@/features/composers/pages/Composers')); const ComposerDetail = lazy(() => import('@/features/composers/pages/ComposerDetail')); const NewReleases = lazy(() => import('@/features/album/pages/NewReleases')); const Favorites = lazy(() => import('@/features/favorites/pages/Favorites')); -const RandomMix = lazy(() => import('../pages/RandomMix')); -const RandomLanding = lazy(() => import('../pages/RandomLanding')); +const RandomMix = lazy(() => import('@/features/randomMix/pages/RandomMix')); +const RandomLanding = lazy(() => import('@/features/randomMix/pages/RandomLanding')); const AlbumDetail = lazy(() => import('@/features/album/pages/AlbumDetail')); const MostPlayed = lazy(() => import('@/features/album/pages/MostPlayed')); const LosslessAlbums = lazy(() => import('@/features/album/pages/LosslessAlbums')); const RandomAlbums = lazy(() => import('@/features/album/pages/RandomAlbums')); -const LuckyMixPage = lazy(() => import('../pages/LuckyMix')); +const LuckyMixPage = lazy(() => import('@/features/randomMix/pages/LuckyMix')); const Playlists = lazy(() => import('@/features/playlist/pages/Playlists')); const PlaylistDetail = lazy(() => import('@/features/playlist/pages/PlaylistDetail')); const NowPlayingPage = lazy(() => import('@/features/nowPlaying/pages/NowPlaying')); diff --git a/src/components/MobileMoreOverlay.tsx b/src/components/MobileMoreOverlay.tsx index a961cac3..50fcb711 100644 --- a/src/components/MobileMoreOverlay.tsx +++ b/src/components/MobileMoreOverlay.tsx @@ -5,7 +5,7 @@ import { Settings, HardDriveDownload } from 'lucide-react'; import { useSidebarStore } from '@/features/sidebar'; import { useAuthStore } from '../store/authStore'; import { ALL_NAV_ITEMS } from '../config/navItems'; -import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable'; +import { useLuckyMixAvailable } from '@/features/randomMix'; import { isOfflineSidebarNavAllowed } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline'; import { offlineBrowseNavFlags } from '@/features/offline'; diff --git a/src/features/queue/components/QueueList.tsx b/src/features/queue/components/QueueList.tsx index e82d00e8..098bfe94 100644 --- a/src/features/queue/components/QueueList.tsx +++ b/src/features/queue/components/QueueList.tsx @@ -4,7 +4,7 @@ import { Play } from 'lucide-react'; import type { TFunction } from 'i18next'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; import { usePlayerStore } from '@/features/playback/store/playerStore'; -import { useLuckyMixStore } from '@/store/luckyMixStore'; +import { useLuckyMixStore } from '@/features/randomMix'; import type { QueueItemRef } from '@/lib/media/trackTypes'; import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import type { QueueDisplayMode } from '@/store/authStoreTypes'; diff --git a/src/features/queue/components/QueuePanel.tsx b/src/features/queue/components/QueuePanel.tsx index 07316254..2848fe0a 100644 --- a/src/features/queue/components/QueuePanel.tsx +++ b/src/features/queue/components/QueuePanel.tsx @@ -20,7 +20,7 @@ import { useThemeStore } from '@/store/themeStore'; import { useLyricsStore } from '@/store/lyricsStore'; import LyricsPane from '@/components/LyricsPane'; import { NowPlayingInfo } from '@/features/nowPlaying'; -import { useLuckyMixStore } from '@/store/luckyMixStore'; +import { useLuckyMixStore } from '@/features/randomMix'; import { useQueueToolbarStore } from '@/store/queueToolbarStore'; import { SavePlaylistModal } from '@/features/queue/components/SavePlaylistModal'; import { LoadPlaylistModal } from '@/features/queue/components/LoadPlaylistModal'; diff --git a/src/components/randomMix/RandomMixFiltersPanel.tsx b/src/features/randomMix/components/RandomMixFiltersPanel.tsx similarity index 99% rename from src/components/randomMix/RandomMixFiltersPanel.tsx rename to src/features/randomMix/components/RandomMixFiltersPanel.tsx index 117a7be6..a5c788b2 100644 --- a/src/components/randomMix/RandomMixFiltersPanel.tsx +++ b/src/features/randomMix/components/RandomMixFiltersPanel.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { ChevronDown, ChevronUp } from 'lucide-react'; -import { RANDOM_MIX_SIZE_OPTIONS } from '../../store/authStoreDefaults'; +import { RANDOM_MIX_SIZE_OPTIONS } from '@/store/authStoreDefaults'; interface Props { isMobile: boolean; diff --git a/src/components/randomMix/RandomMixGenrePanel.tsx b/src/features/randomMix/components/RandomMixGenrePanel.tsx similarity index 100% rename from src/components/randomMix/RandomMixGenrePanel.tsx rename to src/features/randomMix/components/RandomMixGenrePanel.tsx diff --git a/src/components/randomMix/RandomMixHeader.tsx b/src/features/randomMix/components/RandomMixHeader.tsx similarity index 100% rename from src/components/randomMix/RandomMixHeader.tsx rename to src/features/randomMix/components/RandomMixHeader.tsx diff --git a/src/components/randomMix/RandomMixTrackRow.tsx b/src/features/randomMix/components/RandomMixTrackRow.tsx similarity index 98% rename from src/components/randomMix/RandomMixTrackRow.tsx rename to src/features/randomMix/components/RandomMixTrackRow.tsx index abbe8c8a..94a05f65 100644 --- a/src/components/randomMix/RandomMixTrackRow.tsx +++ b/src/features/randomMix/components/RandomMixTrackRow.tsx @@ -5,7 +5,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import type { Track } from '@/lib/media/trackTypes'; import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; -import { formatRandomMixDuration } from '../../utils/componentHelpers/randomMixHelpers'; +import { formatRandomMixDuration } from '@/features/randomMix/utils/randomMixHelpers'; interface Props { song: SubsonicSong; diff --git a/src/hooks/useLuckyMixAvailable.ts b/src/features/randomMix/hooks/useLuckyMixAvailable.ts similarity index 97% rename from src/hooks/useLuckyMixAvailable.ts rename to src/features/randomMix/hooks/useLuckyMixAvailable.ts index 2c2af063..7f1d4a2c 100644 --- a/src/hooks/useLuckyMixAvailable.ts +++ b/src/features/randomMix/hooks/useLuckyMixAvailable.ts @@ -1,4 +1,4 @@ -import { useAuthStore } from '../store/authStore'; +import { useAuthStore } from '@/store/authStore'; /** * Whether "Lucky Mix" should be exposed as a navigable menu/card entry. diff --git a/src/features/randomMix/index.ts b/src/features/randomMix/index.ts new file mode 100644 index 00000000..4276d2d8 --- /dev/null +++ b/src/features/randomMix/index.ts @@ -0,0 +1,13 @@ +/** + * Random / Lucky Mix feature — the random landing, random-mix browser, and + * lucky-mix trigger pages, the random-mix panels (filters/genre/header/row), + * the lucky-mix availability hook + session store, and the lucky-mix / + * random-mix queue-build helpers. The pages are lazy-loaded by the router via + * their deep paths, so they are not re-exported here. + * + * Stays OUT (shared infra, consumed by the playback core too, not owned): + * `utils/mix/mixRatingFilter` (a generic rating-window filter used by the + * infinite-queue builder + home/album/CLI → a `lib/` candidate). + */ +export { useLuckyMixAvailable, isLuckyMixAvailable } from './hooks/useLuckyMixAvailable'; +export { useLuckyMixStore } from './store/luckyMixStore'; diff --git a/src/pages/LuckyMix.tsx b/src/features/randomMix/pages/LuckyMix.tsx similarity index 84% rename from src/pages/LuckyMix.tsx rename to src/features/randomMix/pages/LuckyMix.tsx index a362542b..f021c29a 100644 --- a/src/pages/LuckyMix.tsx +++ b/src/features/randomMix/pages/LuckyMix.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; -import { buildAndPlayLuckyMix } from '../utils/mix/luckyMix'; +import { buildAndPlayLuckyMix } from '@/features/randomMix/utils/luckyMix'; export default function LuckyMixPage() { const navigate = useNavigate(); diff --git a/src/pages/RandomLanding.tsx b/src/features/randomMix/pages/RandomLanding.tsx similarity index 95% rename from src/pages/RandomLanding.tsx rename to src/features/randomMix/pages/RandomLanding.tsx index da844fa2..e97c04d8 100644 --- a/src/pages/RandomLanding.tsx +++ b/src/features/randomMix/pages/RandomLanding.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Shuffle, Dices, Sparkles } from 'lucide-react'; -import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable'; +import { useLuckyMixAvailable } from '@/features/randomMix/hooks/useLuckyMixAvailable'; interface MixCard { icon: React.ElementType; diff --git a/src/pages/RandomMix.tsx b/src/features/randomMix/pages/RandomMix.tsx similarity index 96% rename from src/pages/RandomMix.tsx rename to src/features/randomMix/pages/RandomMix.tsx index b96c4874..93a648d6 100644 --- a/src/pages/RandomMix.tsx +++ b/src/features/randomMix/pages/RandomMix.tsx @@ -4,21 +4,21 @@ import { songToTrack } from '@/lib/media/songToTrack'; import React, { useEffect, useMemo, useState } from 'react'; import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePreviewStore } from '@/features/playback/store/previewStore'; -import { useAuthStore } from '../store/authStore'; -import { useLibraryIndexStore } from '../store/libraryIndexStore'; +import { useAuthStore } from '@/store/authStore'; +import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useTranslation } from 'react-i18next'; import { useIsMobile } from '@/lib/hooks/useIsMobile'; import { useOrbitSongRowBehavior } from '@/features/orbit'; import { fetchRandomMixSongsUntilFull, getMixMinRatingsConfigFromAuth, -} from '../utils/mix/mixRatingFilter'; +} from '@/utils/mix/mixRatingFilter'; import { fetchGenreCatalog } from '@/features/playback/utils/playback/genreBrowsePlayback'; -import { AUDIOBOOK_GENRES, filterRandomMixSongs } from '../utils/componentHelpers/randomMixHelpers'; -import RandomMixHeader from '../components/randomMix/RandomMixHeader'; -import RandomMixFiltersPanel from '../components/randomMix/RandomMixFiltersPanel'; -import RandomMixGenrePanel from '../components/randomMix/RandomMixGenrePanel'; -import RandomMixTrackRow from '../components/randomMix/RandomMixTrackRow'; +import { AUDIOBOOK_GENRES, filterRandomMixSongs } from '@/features/randomMix/utils/randomMixHelpers'; +import RandomMixHeader from '@/features/randomMix/components/RandomMixHeader'; +import RandomMixFiltersPanel from '@/features/randomMix/components/RandomMixFiltersPanel'; +import RandomMixGenrePanel from '@/features/randomMix/components/RandomMixGenrePanel'; +import RandomMixTrackRow from '@/features/randomMix/components/RandomMixTrackRow'; export default function RandomMix() { const { t } = useTranslation(); diff --git a/src/store/luckyMixStore.ts b/src/features/randomMix/store/luckyMixStore.ts similarity index 100% rename from src/store/luckyMixStore.ts rename to src/features/randomMix/store/luckyMixStore.ts diff --git a/src/utils/mix/luckyMix.ts b/src/features/randomMix/utils/luckyMix.ts similarity index 97% rename from src/utils/mix/luckyMix.ts rename to src/features/randomMix/utils/luckyMix.ts index 2d29f666..1622b23f 100644 --- a/src/utils/mix/luckyMix.ts +++ b/src/features/randomMix/utils/luckyMix.ts @@ -5,12 +5,12 @@ import type { QueueItemRef } from '@/lib/media/trackTypes'; import { songToTrack } from '@/lib/media/songToTrack'; import { invoke } from '@tauri-apps/api/core'; import i18n from '@/lib/i18n'; -import { useAuthStore } from '../../store/authStore'; +import { useAuthStore } from '@/store/authStore'; import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo'; import { usePlayerStore } from '@/features/playback/store/playerStore'; -import { useLuckyMixStore } from '../../store/luckyMixStore'; -import { isLuckyMixAvailable } from '../../hooks/useLuckyMixAvailable'; -import { showToast } from '../ui/toast'; +import { useLuckyMixStore } from '@/features/randomMix/store/luckyMixStore'; +import { isLuckyMixAvailable } from '@/features/randomMix/hooks/useLuckyMixAvailable'; +import { showToast } from '@/utils/ui/toast'; import { bindQueueServerForPlayback, playbackServerDiffersFromActive, @@ -21,7 +21,7 @@ import { filterSongsForLuckyMixRatings, filterTopArtistsForMixRatings, getMixMinRatingsConfigFromAuth, -} from './mixRatingFilter'; +} from '@/utils/mix/mixRatingFilter'; import { MIX_TARGET_SIZE, SEED_TARGET_SIZE, @@ -33,7 +33,7 @@ import { pickSongsForArtist, pickSongsForAlbum, pickGoodRatedSongs, -} from './luckyMixHelpers'; +} from '@/features/randomMix/utils/luckyMixHelpers'; /** * Sentinel thrown inside the build loop when `useLuckyMixStore.cancelRequested` diff --git a/src/utils/mix/luckyMixHelpers.ts b/src/features/randomMix/utils/luckyMixHelpers.ts similarity index 99% rename from src/utils/mix/luckyMixHelpers.ts rename to src/features/randomMix/utils/luckyMixHelpers.ts index 4d7ac033..276d6ad3 100644 --- a/src/utils/mix/luckyMixHelpers.ts +++ b/src/features/randomMix/utils/luckyMixHelpers.ts @@ -5,7 +5,7 @@ import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes'; import { filterSongsForLuckyMixRatings, type MixMinRatingsConfig, -} from './mixRatingFilter'; +} from '@/utils/mix/mixRatingFilter'; export interface TopArtist { id: string; diff --git a/src/utils/componentHelpers/randomMixHelpers.test.ts b/src/features/randomMix/utils/randomMixHelpers.test.ts similarity index 94% rename from src/utils/componentHelpers/randomMixHelpers.test.ts rename to src/features/randomMix/utils/randomMixHelpers.test.ts index 53636fb0..a3f5e520 100644 --- a/src/utils/componentHelpers/randomMixHelpers.test.ts +++ b/src/features/randomMix/utils/randomMixHelpers.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { filterRandomMixSongs } from './randomMixHelpers'; +import { filterRandomMixSongs } from '@/features/randomMix/utils/randomMixHelpers'; function song(id: string): SubsonicSong { return { diff --git a/src/utils/componentHelpers/randomMixHelpers.ts b/src/features/randomMix/utils/randomMixHelpers.ts similarity index 94% rename from src/utils/componentHelpers/randomMixHelpers.ts rename to src/features/randomMix/utils/randomMixHelpers.ts index 70d64395..260492be 100644 --- a/src/utils/componentHelpers/randomMixHelpers.ts +++ b/src/features/randomMix/utils/randomMixHelpers.ts @@ -1,5 +1,5 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { passesMixMinRatings, type MixMinRatingsConfig } from '../mix/mixRatingFilter'; +import { passesMixMinRatings, type MixMinRatingsConfig } from '@/utils/mix/mixRatingFilter'; export const AUDIOBOOK_GENRES = [ 'hörbuch', 'hoerbuch', 'hörspiel', 'hoerspiel', diff --git a/src/features/settings/components/SidebarCustomizer.tsx b/src/features/settings/components/SidebarCustomizer.tsx index 1fad85e1..f49cca1e 100644 --- a/src/features/settings/components/SidebarCustomizer.tsx +++ b/src/features/settings/components/SidebarCustomizer.tsx @@ -2,7 +2,7 @@ import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '@/store/authStore'; import { useSidebarStore, SidebarItemConfig, CONSERVED_SIDEBAR_NAV_IDS } from '@/features/sidebar'; -import { useLuckyMixAvailable } from '@/hooks/useLuckyMixAvailable'; +import { useLuckyMixAvailable } from '@/features/randomMix'; import { ALL_NAV_ITEMS } from '@/config/navItems'; import { applySidebarReorderById } from '@/features/sidebar'; import { useListReorderDnd } from '@/hooks/useListReorderDnd'; diff --git a/src/features/sidebar/components/Sidebar.tsx b/src/features/sidebar/components/Sidebar.tsx index abd6774a..8fef119f 100644 --- a/src/features/sidebar/components/Sidebar.tsx +++ b/src/features/sidebar/components/Sidebar.tsx @@ -17,7 +17,7 @@ import { getLibraryItemsForReorder, getSystemItemsForReorder, } from '@/features/sidebar/utils/sidebarNavReorder'; -import { useLuckyMixAvailable } from '@/hooks/useLuckyMixAvailable'; +import { useLuckyMixAvailable } from '@/features/randomMix'; import { usePerfProbeFlags } from '@/utils/perf/perfFlags'; import { useSidebarNewReleasesUnread } from '@/features/sidebar/hooks/useSidebarNewReleasesUnread'; import { useSidebarNavDnd } from '@/features/sidebar/hooks/useSidebarNavDnd';