mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(radio): co-locate internet radio feature into features/radio
This commit is contained in:
@@ -36,7 +36,7 @@ const OfflineLibrary = lazy(() => import('../pages/OfflineLibrary'));
|
||||
const LabelAlbums = lazy(() => import('../pages/LabelAlbums'));
|
||||
const SearchBrowsePage = lazy(() => import('../pages/SearchBrowsePage'));
|
||||
const FolderBrowser = lazy(() => import('../pages/FolderBrowser'));
|
||||
const InternetRadio = lazy(() => import('../pages/InternetRadio'));
|
||||
const InternetRadio = lazy(() => import('@/features/radio/pages/InternetRadio'));
|
||||
const Genres = lazy(() => import('../pages/Genres'));
|
||||
const GenreDetail = lazy(() => import('../pages/GenreDetail'));
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import { useThemeStore } from '../store/themeStore';
|
||||
import Equalizer from './Equalizer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePlaybackLibraryNavigate } from '../hooks/usePlaybackLibraryNavigate';
|
||||
import { useRadioMetadata } from '../hooks/useRadioMetadata';
|
||||
import { useRadioMprisSync } from '../hooks/useRadioMprisSync';
|
||||
import { useRadioMetadata } from '@/features/radio';
|
||||
import { useRadioMprisSync } from '@/features/radio';
|
||||
import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress';
|
||||
import PlaybackDelayModal from './PlaybackDelayModal';
|
||||
import { usePlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Cast, Clock, Radio, SkipForward, Users } from 'lucide-react';
|
||||
import type { useRadioMetadata } from '../../hooks/useRadioMetadata';
|
||||
import type { useRadioMetadata } from '@/features/radio';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { formatTrackTime } from '../../utils/format/formatDuration';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ArrowLeftRight } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { RadioMetadata } from '../../hooks/useRadioMetadata';
|
||||
import type { RadioMetadata } from '@/features/radio';
|
||||
import { useThemeStore } from '../../store/themeStore';
|
||||
import { formatTrackTime, playbarMinuteFieldWidth } from '../../utils/format/formatDuration';
|
||||
import { WaveformSeek } from '@/features/waveform';
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { TFunction } from 'i18next';
|
||||
import { queueSongRating } from '../../store/pendingStarSync';
|
||||
import type { InternetRadioStation, SubsonicOpenArtistRef } from '../../api/subsonicTypes';
|
||||
import type { PlayerState, Track } from '../../store/playerStoreTypes';
|
||||
import type { RadioMetadata } from '../../hooks/useRadioMetadata';
|
||||
import type { RadioMetadata } from '@/features/radio';
|
||||
import type { PreviewingTrack } from '../../store/previewStore';
|
||||
import { CoverArtImage } from '../../cover/CoverArtImage';
|
||||
import { albumCoverRef } from '../../cover/ref';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getInternetRadioStations } from '@/api/subsonicRadio';
|
||||
import { getInternetRadioStations } from '@/features/radio';
|
||||
import { getStarred } from '@/api/subsonicStarRating';
|
||||
import type {
|
||||
InternetRadioStation, SubsonicAlbum, SubsonicArtist, SubsonicSong,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { api } from './subsonicClient';
|
||||
import type { InternetRadioStation, RadioBrowserStation } from './subsonicTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { api } from '@/api/subsonicClient';
|
||||
import type { InternetRadioStation, RadioBrowserStation } from '@/api/subsonicTypes';
|
||||
|
||||
export async function getInternetRadioStations(): Promise<InternetRadioStation[]> {
|
||||
try {
|
||||
+6
-6
@@ -2,12 +2,12 @@ import React, { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Cast, Globe, Heart, Square, Trash2, X } from 'lucide-react';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import type { InternetRadioStation } from '../../api/subsonicTypes';
|
||||
import { useDragDrop, useDragSource } from '../../contexts/DragDropContext';
|
||||
import { CoverArtImage } from '../../cover/CoverArtImage';
|
||||
import { albumCoverRef } from '../../cover/ref';
|
||||
import { coverArtIdFromRadio } from '../../cover/ids';
|
||||
import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '../../cover/layoutSizes';
|
||||
import type { InternetRadioStation } from '@/api/subsonicTypes';
|
||||
import { useDragDrop, useDragSource } from '@/contexts/DragDropContext';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { albumCoverRef } from '@/cover/ref';
|
||||
import { coverArtIdFromRadio } from '@/cover/ids';
|
||||
import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes';
|
||||
|
||||
interface RadioCardProps {
|
||||
s: InternetRadioStation;
|
||||
+3
-3
@@ -5,11 +5,11 @@ import { Cast, Check, Loader2, Plus, X } from 'lucide-react';
|
||||
import {
|
||||
createInternetRadioStation, fetchUrlBytes, getInternetRadioStations,
|
||||
getTopRadioStations, searchRadioBrowser, uploadRadioCoverArtBytes,
|
||||
} from '../../api/subsonicRadio';
|
||||
} from '@/features/radio/api/subsonicRadio';
|
||||
import {
|
||||
type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE,
|
||||
} from '../../api/subsonicTypes';
|
||||
import { showToast } from '../../utils/ui/toast';
|
||||
} from '@/api/subsonicTypes';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
|
||||
interface RadioDirectoryModalProps {
|
||||
onClose: () => void;
|
||||
+4
-4
@@ -2,10 +2,10 @@ import React, { useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Camera, Cast, Loader2, X } from 'lucide-react';
|
||||
import type { InternetRadioStation } from '../../api/subsonicTypes';
|
||||
import { CoverArtImage } from '../../cover/CoverArtImage';
|
||||
import { albumCoverRef } from '../../cover/ref';
|
||||
import { coverArtIdFromRadio } from '../../cover/ids';
|
||||
import type { InternetRadioStation } from '@/api/subsonicTypes';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { albumCoverRef } from '@/cover/ref';
|
||||
import { coverArtIdFromRadio } from '@/cover/ids';
|
||||
|
||||
interface RadioEditModalProps {
|
||||
station: InternetRadioStation | null; // null = create new
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { InternetRadioStation } from '../api/subsonicTypes';
|
||||
import type { InternetRadioStation } from '@/api/subsonicTypes';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import {
|
||||
guessAzuraCastApiUrl,
|
||||
normaliseAzuraCastHomepageUrl,
|
||||
fetchAzuraCastNowPlaying,
|
||||
type AzuraCastNowPlaying,
|
||||
type AzuraCastSong,
|
||||
} from '../api/azuracast';
|
||||
} from '@/api/azuracast';
|
||||
|
||||
// ─── Public types ─────────────────────────────────────────────────────────────
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useRadioMprisSync } from './useRadioMprisSync';
|
||||
import type { RadioMetadata } from './useRadioMetadata';
|
||||
import type { InternetRadioStation } from '../api/subsonicTypes';
|
||||
import { useRadioMprisSync } from '@/features/radio/hooks/useRadioMprisSync';
|
||||
import type { RadioMetadata } from '@/features/radio/hooks/useRadioMetadata';
|
||||
import type { InternetRadioStation } from '@/api/subsonicTypes';
|
||||
|
||||
vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(async () => undefined) }));
|
||||
const invokeMock = vi.mocked(invoke);
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { InternetRadioStation } from '../api/subsonicTypes';
|
||||
import type { RadioMetadata } from './useRadioMetadata';
|
||||
import type { InternetRadioStation } from '@/api/subsonicTypes';
|
||||
import type { RadioMetadata } from '@/features/radio/hooks/useRadioMetadata';
|
||||
|
||||
/**
|
||||
* Internet radio → OS media controls (MPRIS / SMTC / Now Playing).
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Internet radio feature — station browse/edit UI (directory, cards, toolbar),
|
||||
* the InternetRadio page, and the live ICY/AzuraCast metadata hooks. The page is
|
||||
* lazy-loaded by the router via its deep path, so it is not re-exported here.
|
||||
*
|
||||
* Note: radio *playback* state (`store/radioPlayer`, `store/radioSessionState`)
|
||||
* and the ICY→MPRIS bridge (`audioListenerSetup/radioMprisMetadata`) stay in the
|
||||
* playback/audio core — the player core drives them, so they are not part of
|
||||
* this UI feature.
|
||||
*/
|
||||
export { getInternetRadioStations } from './api/subsonicRadio';
|
||||
export { useRadioMetadata } from './hooks/useRadioMetadata';
|
||||
export type { RadioMetadata } from './hooks/useRadioMetadata';
|
||||
export { useRadioMprisSync } from './hooks/useRadioMprisSync';
|
||||
@@ -1,21 +1,21 @@
|
||||
import { getInternetRadioStations, createInternetRadioStation, updateInternetRadioStation, deleteInternetRadioStation, uploadRadioCoverArt, deleteRadioCoverArt } from '../api/subsonicRadio';
|
||||
import { type InternetRadioStation } from '../api/subsonicTypes';
|
||||
import { getInternetRadioStations, createInternetRadioStation, updateInternetRadioStation, deleteInternetRadioStation, uploadRadioCoverArt, deleteRadioCoverArt } from '@/features/radio/api/subsonicRadio';
|
||||
import { type InternetRadioStation } from '@/api/subsonicTypes';
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { Plus, Search } from 'lucide-react';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { setRadioVolume } from '../store/radioPlayer';
|
||||
import { fadeOut } from '../utils/playback/fadeOut';
|
||||
import { invalidateCoverArt } from '../utils/imageCache';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { setRadioVolume } from '@/store/radioPlayer';
|
||||
import { fadeOut } from '@/utils/playback/fadeOut';
|
||||
import { invalidateCoverArt } from '@/utils/imageCache';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import RadioToolbar from '../components/internetRadio/RadioToolbar';
|
||||
import AlphabetFilterBar from '../components/internetRadio/AlphabetFilterBar';
|
||||
import RadioCard from '../components/internetRadio/RadioCard';
|
||||
import RadioEditModal from '../components/internetRadio/RadioEditModal';
|
||||
import RadioDirectoryModal from '../components/internetRadio/RadioDirectoryModal';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
import { useNavidromeAdminRole, canManageNavidromeRadio } from '../hooks/useNavidromeAdminRole';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import RadioToolbar from '@/features/radio/components/RadioToolbar';
|
||||
import AlphabetFilterBar from '@/features/radio/components/AlphabetFilterBar';
|
||||
import RadioCard from '@/features/radio/components/RadioCard';
|
||||
import RadioEditModal from '@/features/radio/components/RadioEditModal';
|
||||
import RadioDirectoryModal from '@/features/radio/components/RadioDirectoryModal';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
import { useNavidromeAdminRole, canManageNavidromeRadio } from '@/hooks/useNavidromeAdminRole';
|
||||
|
||||
export default function InternetRadio() {
|
||||
const { t } = useTranslation();
|
||||
@@ -12,7 +12,7 @@ import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useLyricsStore } from '../store/lyricsStore';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import { useRadioMetadata } from '../hooks/useRadioMetadata';
|
||||
import { useRadioMetadata } from '@/features/radio';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
import {
|
||||
|
||||
Reference in New Issue
Block a user