mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(favorites): co-locate favorites feature into features/favorites
This commit is contained in:
@@ -16,7 +16,7 @@ const ArtistDetail = lazy(() => import('../pages/ArtistDetail'));
|
||||
const Composers = lazy(() => import('../pages/Composers'));
|
||||
const ComposerDetail = lazy(() => import('../pages/ComposerDetail'));
|
||||
const NewReleases = lazy(() => import('../pages/NewReleases'));
|
||||
const Favorites = lazy(() => import('../pages/Favorites'));
|
||||
const Favorites = lazy(() => import('@/features/favorites/pages/Favorites'));
|
||||
const RandomMix = lazy(() => import('../pages/RandomMix'));
|
||||
const RandomLanding = lazy(() => import('../pages/RandomLanding'));
|
||||
const AlbumDetail = lazy(() => import('../pages/AlbumDetail'));
|
||||
|
||||
+9
-9
@@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AudioLines, ChevronRight, Play, Square, X } from 'lucide-react';
|
||||
import type { ColDef } from '../../utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||
import { codecLabel } from '../../utils/componentHelpers/playlistDetailHelpers';
|
||||
import { formatLastSeen } from '../../utils/componentHelpers/userMgmtHelpers';
|
||||
import i18n from '../../i18n';
|
||||
import { formatTrackTime } from '../../utils/format/formatDuration';
|
||||
import StarRating from '../StarRating';
|
||||
import { OpenArtistRefInline } from '../OpenArtistRefInline';
|
||||
import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs';
|
||||
import type { ColDef } from '@/utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers';
|
||||
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
|
||||
import i18n from '@/i18n';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import StarRating from '@/components/StarRating';
|
||||
import { OpenArtistRefInline } from '@/components/OpenArtistRefInline';
|
||||
import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs';
|
||||
|
||||
export interface FavoriteSongRowCallbacks {
|
||||
activate: (song: SubsonicSong, index: number, e: React.MouseEvent) => void;
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
import { HardDrive } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import {
|
||||
useFavoritesOfflineStatus,
|
||||
type FavoritesOfflineSemaphore,
|
||||
} from '../../hooks/useFavoritesOfflineStatus';
|
||||
} from '@/features/favorites/hooks/useFavoritesOfflineStatus';
|
||||
import {
|
||||
disableFavoritesOfflineSync,
|
||||
scheduleFavoritesOfflineSync,
|
||||
} from '../../utils/offline/favoritesOfflineSync';
|
||||
} from '@/utils/offline/favoritesOfflineSync';
|
||||
|
||||
function semaphoreTooltipKey(semaphore: FavoritesOfflineSemaphore): string {
|
||||
switch (semaphore) {
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ListPlus, Play, SlidersHorizontal, X } from 'lucide-react';
|
||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { useSelectionStore } from '../../store/selectionStore';
|
||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||
import { AddToPlaylistSubmenu } from '../ContextMenu';
|
||||
import GenreFilterBar from '../GenreFilterBar';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { AddToPlaylistSubmenu } from '@/components/ContextMenu';
|
||||
import GenreFilterBar from '@/components/GenreFilterBar';
|
||||
|
||||
interface Props {
|
||||
visibleSongs: SubsonicSong[];
|
||||
+15
-15
@@ -1,22 +1,22 @@
|
||||
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import FavoriteSongRow, { type FavoriteSongRowCallbacks } from './FavoriteSongRow';
|
||||
import { TracklistColumnPicker } from '../albumTrackList/TracklistColumnPicker';
|
||||
import FavoriteSongRow, { type FavoriteSongRowCallbacks } from '@/features/favorites/components/FavoriteSongRow';
|
||||
import { TracklistColumnPicker } from '@/components/albumTrackList/TracklistColumnPicker';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { ColDef } from '../../utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
|
||||
import { useSelectionStore } from '../../store/selectionStore';
|
||||
import { useThemeStore } from '../../store/themeStore';
|
||||
import { useDragDrop } from '../../contexts/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior';
|
||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||
import { appendServerQuery } from '../../utils/navigation/detailServerScope';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll';
|
||||
import { useElementClientHeightById } from '../../hooks/useResizeClientHeight';
|
||||
import { SORTABLE_COLUMNS } from '../../hooks/useFavoritesSongFiltering';
|
||||
import type { ColDef } from '@/utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/hooks/useOrbitSongRowBehavior';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { appendServerQuery } from '@/utils/navigation/detailServerScope';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useElementClientHeightById } from '@/hooks/useResizeClientHeight';
|
||||
import { SORTABLE_COLUMNS } from '@/features/favorites/hooks/useFavoritesSongFiltering';
|
||||
|
||||
interface Props {
|
||||
visibleSongs: SubsonicSong[];
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Cast, ChevronLeft, ChevronRight, Heart, 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 { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '../../cover/layoutSizes';
|
||||
import type { InternetRadioStation } from '@/api/subsonicTypes';
|
||||
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 RadioStationRowProps {
|
||||
title: string;
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChevronLeft, ChevronRight, Users } from 'lucide-react';
|
||||
import { ArtistCoverArtImage } from '../../cover/ArtistCoverArtImage';
|
||||
import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '../../cover/layoutSizes';
|
||||
import { coverServerScopeForServerId } from '../../cover/serverScope';
|
||||
import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage';
|
||||
import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes';
|
||||
import { coverServerScopeForServerId } from '@/cover/serverScope';
|
||||
|
||||
export interface TopFavoriteArtist {
|
||||
id: string;
|
||||
@@ -1,20 +1,20 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getInternetRadioStations } from '../api/subsonicRadio';
|
||||
import { getStarred } from '../api/subsonicStarRating';
|
||||
import { getInternetRadioStations } from '@/api/subsonicRadio';
|
||||
import { getStarred } from '@/api/subsonicStarRating';
|
||||
import type {
|
||||
InternetRadioStation, SubsonicAlbum, SubsonicArtist, SubsonicSong,
|
||||
} from '../api/subsonicTypes';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import type { TopFavoriteArtist } from '../components/favorites/TopFavoriteArtists';
|
||||
import { useConnectionStatus } from './useConnectionStatus';
|
||||
import { isActiveServerReachable } from '../utils/network/activeServerReachability';
|
||||
import { useOfflineBrowseContext } from './useOfflineBrowseContext';
|
||||
import { useOfflineBrowseReloadToken } from './useOfflineBrowseReloadToken';
|
||||
} from '@/api/subsonicTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import type { TopFavoriteArtist } from '@/features/favorites/components/TopFavoriteArtists';
|
||||
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
|
||||
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
|
||||
import { useOfflineBrowseContext } from '@/hooks/useOfflineBrowseContext';
|
||||
import { useOfflineBrowseReloadToken } from '@/hooks/useOfflineBrowseReloadToken';
|
||||
import {
|
||||
loadStarredFromAllLibraryIndexes,
|
||||
loadStarredFromAllServersOnline,
|
||||
} from '../utils/offline/offlineStarredLoad';
|
||||
} from '@/utils/offline/offlineStarredLoad';
|
||||
|
||||
export interface FavoritesDataResult {
|
||||
albums: SubsonicAlbum[];
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useFavoritesOfflineSyncStore } from '../store/favoritesOfflineSyncStore';
|
||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
||||
import { useOfflineJobStore } from '../store/offlineJobStore';
|
||||
import { FAVORITES_OFFLINE_JOB_ID } from '../utils/offline/favoritesOfflineConstants';
|
||||
import { entryBelongsToServer } from '../utils/offline/offlineLibraryHelpers';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useFavoritesOfflineSyncStore } from '@/store/favoritesOfflineSyncStore';
|
||||
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||
import { useOfflineJobStore } from '@/store/offlineJobStore';
|
||||
import { FAVORITES_OFFLINE_JOB_ID } from '@/utils/offline/favoritesOfflineConstants';
|
||||
import { entryBelongsToServer } from '@/utils/offline/offlineLibraryHelpers';
|
||||
|
||||
export type FavoritesOfflineUiStatus =
|
||||
| 'disabled'
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import { useSelectionStore } from '../store/selectionStore';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
|
||||
export interface FavoritesSelectionResult {
|
||||
toggleSelect: (id: string, idx: number, shift: boolean) => void;
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ArrowDown, ArrowUp } from 'lucide-react';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
|
||||
const CURRENT_YEAR = new Date().getFullYear();
|
||||
const MIN_YEAR = 1950;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Favorites feature — the Favorites page (starred songs/artists + favourite
|
||||
* radio stations) and its data/selection/filtering hooks. The `Favorites` page
|
||||
* is lazy-loaded by the router via its deep path, so it is not re-exported here.
|
||||
*
|
||||
* Note: the offline integration layer (`utils/offline/favorites*`,
|
||||
* `store/favoritesOfflineSyncStore`) stays in the offline layer — the offline
|
||||
* core imports it, so it cannot live under this feature without inverting the
|
||||
* core→feature dependency.
|
||||
*/
|
||||
export { useFavoritesData } from './hooks/useFavoritesData';
|
||||
export { useFavoritesSelection } from './hooks/useFavoritesSelection';
|
||||
export { useFavoritesOfflineStatus } from './hooks/useFavoritesOfflineStatus';
|
||||
@@ -1,20 +1,20 @@
|
||||
import { queueSongStar, queueSongRating } from '../store/pendingStarSync';
|
||||
import { queueSongStar, queueSongRating } from '@/store/pendingStarSync';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTracklistColumns, type ColDef } from '../utils/useTracklistColumns';
|
||||
import { TopFavoriteArtistsRow } from '../components/favorites/TopFavoriteArtists';
|
||||
import { RadioStationRow } from '../components/favorites/RadioFavorites';
|
||||
import FavoritesSongsSectionHeader from '../components/favorites/FavoritesSongsSectionHeader';
|
||||
import FavoritesSongsTracklist from '../components/favorites/FavoritesSongsTracklist';
|
||||
import { useFavoritesData } from '../hooks/useFavoritesData';
|
||||
import { useFavoritesSongFiltering } from '../hooks/useFavoritesSongFiltering';
|
||||
import { useFavoritesSelection } from '../hooks/useFavoritesSelection';
|
||||
import { useBulkPlPickerOutsideClick } from '../hooks/useBulkPlPickerOutsideClick';
|
||||
import AlbumRow from '../components/AlbumRow';
|
||||
import ArtistRow from '../components/ArtistRow';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useTracklistColumns, type ColDef } from '@/utils/useTracklistColumns';
|
||||
import { TopFavoriteArtistsRow } from '@/features/favorites/components/TopFavoriteArtists';
|
||||
import { RadioStationRow } from '@/features/favorites/components/RadioFavorites';
|
||||
import FavoritesSongsSectionHeader from '@/features/favorites/components/FavoritesSongsSectionHeader';
|
||||
import FavoritesSongsTracklist from '@/features/favorites/components/FavoritesSongsTracklist';
|
||||
import { useFavoritesData } from '@/features/favorites/hooks/useFavoritesData';
|
||||
import { useFavoritesSongFiltering } from '@/features/favorites/hooks/useFavoritesSongFiltering';
|
||||
import { useFavoritesSelection } from '@/features/favorites/hooks/useFavoritesSelection';
|
||||
import { useBulkPlPickerOutsideClick } from '@/hooks/useBulkPlPickerOutsideClick';
|
||||
import AlbumRow from '@/components/AlbumRow';
|
||||
import ArtistRow from '@/components/ArtistRow';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelectionStore } from '../store/selectionStore';
|
||||
import FavoritesOfflineHeader from '../components/favorites/FavoritesOfflineHeader';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import FavoritesOfflineHeader from '@/features/favorites/components/FavoritesOfflineHeader';
|
||||
|
||||
const FAV_COLUMNS: readonly ColDef[] = [
|
||||
{ key: 'num', i18nKey: null, minWidth: 60, defaultWidth: 60, required: true },
|
||||
Reference in New Issue
Block a user