mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(search): co-locate search feature into features/search
This commit is contained in:
@@ -34,7 +34,7 @@ const WhatsNew = lazy(() => import('../pages/WhatsNew'));
|
||||
const DeviceSync = lazy(() => import('@/features/deviceSync/pages/DeviceSync'));
|
||||
const OfflineLibrary = lazy(() => import('../pages/OfflineLibrary'));
|
||||
const LabelAlbums = lazy(() => import('../pages/LabelAlbums'));
|
||||
const SearchBrowsePage = lazy(() => import('../pages/SearchBrowsePage'));
|
||||
const SearchBrowsePage = lazy(() => import('@/features/search/pages/SearchBrowsePage'));
|
||||
const FolderBrowser = lazy(() => import('../pages/FolderBrowser'));
|
||||
const InternetRadio = lazy(() => import('@/features/radio/pages/InternetRadio'));
|
||||
const Genres = lazy(() => import('../pages/Genres'));
|
||||
|
||||
@@ -9,7 +9,7 @@ import Sidebar from '@/features/sidebar';
|
||||
import PlayerBar from '../components/PlayerBar';
|
||||
import BottomNav from '../components/BottomNav';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import LiveSearch from '../components/LiveSearch';
|
||||
import { LiveSearch } from '@/features/search';
|
||||
import DevNetworkModeToggle from '../components/DevNetworkModeToggle';
|
||||
import { NowPlayingDropdown } from '@/features/nowPlaying';
|
||||
import QueuePanel from '../components/QueuePanel';
|
||||
@@ -51,7 +51,7 @@ import { useGlobalDndAndSelectionBlockers } from '../hooks/useGlobalDndAndSelect
|
||||
import { useAppActivityTracking } from '../hooks/useAppActivityTracking';
|
||||
import { useMainScrollingIndicator } from '../hooks/useMainScrollingIndicator';
|
||||
import { useCoverNavigationPriority } from '../hooks/useCoverNavigationPriority';
|
||||
import { useLiveSearchRouteScope } from '../hooks/useLiveSearchRouteScope';
|
||||
import { useLiveSearchRouteScope } from '@/features/search';
|
||||
import { useNowPlayingPrewarm } from '@/features/nowPlaying';
|
||||
import { useOfflineAutoNav } from '../hooks/useOfflineAutoNav';
|
||||
import { useOfflineLibraryFilterSuspend } from '../hooks/useOfflineLibraryFilterSuspend';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { NavLink } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Disc3, Search, Music4, AudioLines, MoreHorizontal } from 'lucide-react';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import MobileSearchOverlay from './MobileSearchOverlay';
|
||||
import { MobileSearchOverlay } from '@/features/search';
|
||||
import MobileMoreOverlay from './MobileMoreOverlay';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
|
||||
@@ -8,8 +8,8 @@ import { decodeServerMagicStringFromText } from '../utils/server/serverMagicStri
|
||||
import { applySharePastePayload, applySharePasteQueue } from '../utils/share/applySharePaste';
|
||||
import { shareQueueServerContext } from '../utils/share/shareServerOriginLabel';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { useShareQueuePreview } from '../hooks/useShareQueuePreview';
|
||||
import ShareQueuePreviewModal from './search/ShareQueuePreviewModal';
|
||||
import { useShareQueuePreview } from '@/features/search';
|
||||
import { ShareQueuePreviewModal } from '@/features/search';
|
||||
import {
|
||||
parseOrbitShareLink,
|
||||
joinOrbitSession,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { subscribeLibrarySyncIdle, subscribeLibrarySyncProgress } from '../api/library';
|
||||
import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import { subscribeLibrarySyncIdle, subscribeLibrarySyncProgress } from '@/api/library';
|
||||
import type { SearchResults, SubsonicArtist } from '@/api/subsonicTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import {
|
||||
LIVE_SEARCH_DEBOUNCE_NETWORK_MS,
|
||||
LIVE_SEARCH_DEBOUNCE_RACE_MS,
|
||||
@@ -9,40 +9,40 @@ import {
|
||||
mergeLiveSearchResults,
|
||||
runLocalLiveSearch,
|
||||
runNetworkLiveSearch,
|
||||
} from '../utils/library/liveSearchLocal';
|
||||
import { raceLiveSearch } from '../utils/library/searchRace';
|
||||
import { libraryIsReady } from '../utils/library/libraryReady';
|
||||
} from '@/utils/library/liveSearchLocal';
|
||||
import { raceLiveSearch } from '@/utils/library/searchRace';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
import {
|
||||
emitLiveSearchDebug,
|
||||
searchHitCounts,
|
||||
searchResultSamples,
|
||||
} from '../utils/library/liveSearchDebug';
|
||||
} from '@/utils/library/liveSearchDebug';
|
||||
import {
|
||||
logLibrarySearch,
|
||||
} from '../utils/library/libraryDevLog';
|
||||
} from '@/utils/library/libraryDevLog';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/hooks/useNavigateToAlbum';
|
||||
import { Search, Disc3, Users, Music, TextSearch, Database, Globe } from 'lucide-react';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { albumArtistDisplayName } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { albumArtistDisplayName } from '@/utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from '@/ui/CachedImage';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import { AlbumCoverArtImage } from '../cover/AlbumCoverArtImage';
|
||||
import { ArtistCoverArtImage } from '../cover/ArtistCoverArtImage';
|
||||
import { CoverArtImage } from '../cover/CoverArtImage';
|
||||
import { COVER_DENSE_SEARCH_CSS_PX } from '../cover/layoutSizes';
|
||||
import { albumCoverRef } from '../cover/ref';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { useShareSearch } from '../hooks/useShareSearch';
|
||||
import ShareSearchResults from './search/ShareSearchResults';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||
import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes';
|
||||
import { albumCoverRef } from '@/cover/ref';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { useShareSearch } from '@/features/search/hooks/useShareSearch';
|
||||
import ShareSearchResults from '@/features/search/components/ShareSearchResults';
|
||||
import {
|
||||
LiveSearchScopeBadge,
|
||||
LiveSearchScopeGhostBadge,
|
||||
} from './search/liveSearchScopeUi';
|
||||
} from '@/features/search/components/liveSearchScopeUi';
|
||||
import {
|
||||
createLiveSearchScopeBackspaceState,
|
||||
handleLiveSearchScopeBackspace,
|
||||
@@ -52,9 +52,9 @@ import {
|
||||
noteLiveSearchScopeQueryInput,
|
||||
resetLiveSearchScopeBackspaceState,
|
||||
resolveLiveSearchScopeGhost,
|
||||
} from './search/liveSearchScope';
|
||||
import { useLiveSearchScopeStore } from '../store/liveSearchScopeStore';
|
||||
import { resolveIndexKey } from '../utils/server/serverIndexKey';
|
||||
} from '@/features/search/components/liveSearchScope';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
import { resolveIndexKey } from '@/utils/server/serverIndexKey';
|
||||
|
||||
type LiveSearchSource = 'local' | 'network';
|
||||
|
||||
+8
-8
@@ -1,21 +1,21 @@
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderWithProviders } from '../test/helpers/renderWithProviders';
|
||||
import MobileSearchOverlay from './MobileSearchOverlay';
|
||||
import { useLiveSearchScopeStore } from '../store/liveSearchScopeStore';
|
||||
import { renderWithProviders } from '@/test/helpers/renderWithProviders';
|
||||
import MobileSearchOverlay from '@/features/search/components/MobileSearchOverlay';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
|
||||
// The overlay's only behaviour-bearing change in PR #1165 was renaming the
|
||||
// recent-search handler `useRecent` → `applyRecentSearch` (it was a plain
|
||||
// function mis-flagged as a hook). Smoke-test that the recent-search path still
|
||||
// applies the term to the live-search store. Heavy collaborators are stubbed.
|
||||
vi.mock('../hooks/useShareSearch', () => ({ useShareSearch: () => ({ shareMatch: null }) }));
|
||||
vi.mock('../api/subsonicSearch', () => ({
|
||||
vi.mock('@/features/search/hooks/useShareSearch', () => ({ useShareSearch: () => ({ shareMatch: null }) }));
|
||||
vi.mock('@/api/subsonicSearch', () => ({
|
||||
search: vi.fn(() => Promise.resolve({ artists: [], albums: [], songs: [] })),
|
||||
}));
|
||||
vi.mock('../cover/AlbumCoverArtImage', () => ({ AlbumCoverArtImage: () => null }));
|
||||
vi.mock('../cover/ArtistCoverArtImage', () => ({ ArtistCoverArtImage: () => null }));
|
||||
vi.mock('../cover/CoverArtImage', () => ({ CoverArtImage: () => null }));
|
||||
vi.mock('@/cover/AlbumCoverArtImage', () => ({ AlbumCoverArtImage: () => null }));
|
||||
vi.mock('@/cover/ArtistCoverArtImage', () => ({ ArtistCoverArtImage: () => null }));
|
||||
vi.mock('@/cover/CoverArtImage', () => ({ CoverArtImage: () => null }));
|
||||
|
||||
const RECENT_KEY = 'psysonic_recent_searches';
|
||||
|
||||
+17
-17
@@ -1,28 +1,28 @@
|
||||
import { search } from '../api/subsonicSearch';
|
||||
import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import { useLiveSearchScopeStore } from '../store/liveSearchScopeStore';
|
||||
import { search } from '@/api/subsonicSearch';
|
||||
import type { SearchResults, SubsonicArtist } from '@/api/subsonicTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { navigatePathWithAlbumReturnTo } from '../utils/navigation/albumDetailNavigation';
|
||||
import { navigatePathWithAlbumReturnTo } from '@/utils/navigation/albumDetailNavigation';
|
||||
import { X, Search, Disc3, Users, Music, Music2, Clock, ChevronRight } from 'lucide-react';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from '@/ui/CachedImage';
|
||||
import { AlbumCoverArtImage } from '../cover/AlbumCoverArtImage';
|
||||
import { ArtistCoverArtImage } from '../cover/ArtistCoverArtImage';
|
||||
import { CoverArtImage } from '../cover/CoverArtImage';
|
||||
import { albumCoverRefForSong } from '../cover/ref';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { albumArtistDisplayName } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { useShareSearch } from '../hooks/useShareSearch';
|
||||
import ShareSearchResults from './search/ShareSearchResults';
|
||||
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||
import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { albumCoverRefForSong } from '@/cover/ref';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { albumArtistDisplayName } from '@/utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { useShareSearch } from '@/features/search/hooks/useShareSearch';
|
||||
import ShareSearchResults from '@/features/search/components/ShareSearchResults';
|
||||
import {
|
||||
LiveSearchScopeBadge,
|
||||
LiveSearchScopeGhostBadge,
|
||||
} from './search/liveSearchScopeUi';
|
||||
} from '@/features/search/components/liveSearchScopeUi';
|
||||
import {
|
||||
createLiveSearchScopeBackspaceState,
|
||||
handleLiveSearchScopeBackspace,
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
noteLiveSearchScopeQueryInput,
|
||||
resetLiveSearchScopeBackspaceState,
|
||||
resolveLiveSearchScopeGhost,
|
||||
} from './search/liveSearchScope';
|
||||
} from '@/features/search/components/liveSearchScope';
|
||||
|
||||
const STORAGE_KEY = 'psysonic_recent_searches';
|
||||
const MAX_RECENT = 6;
|
||||
+9
-9
@@ -2,16 +2,16 @@ import React, { useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Music, X } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||
import type { ServerProfile } from '../../store/authStoreTypes';
|
||||
import { formatTrackTime } from '../../utils/format/formatDuration';
|
||||
import type { ShareQueuePreviewState } from '../../hooks/useShareQueuePreview';
|
||||
import { sharePayloadTotal, type QueueableShareSearchPayload } from '../../utils/share/shareSearch';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { ServerProfile } from '@/store/authStoreTypes';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import type { ShareQueuePreviewState } from '@/features/search/hooks/useShareQueuePreview';
|
||||
import { sharePayloadTotal, type QueueableShareSearchPayload } from '@/utils/share/shareSearch';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { COVER_DENSE_SEARCH_CSS_PX } from '../../cover/layoutSizes';
|
||||
import { COVER_SCOPE_ACTIVE, type CoverServerScope } from '../../cover/types';
|
||||
import { AlbumCoverArtImage } from '../../cover/AlbumCoverArtImage';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes';
|
||||
import { COVER_SCOPE_ACTIVE, type CoverServerScope } from '@/cover/types';
|
||||
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||
|
||||
type ShareQueuePreviewModalProps = {
|
||||
open: boolean;
|
||||
+12
-12
@@ -2,19 +2,19 @@ import React, { useEffect, useState } from 'react';
|
||||
import { Disc3, Eye, Link2, ListPlus, Music, Users } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { SubsonicArtist } from '../../api/subsonicTypes';
|
||||
import type { ServerProfile } from '../../store/authStoreTypes';
|
||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||
import { activateShareSearchServer } from '../../utils/share/enqueueShareSearchPayload';
|
||||
import { sharePayloadTotal, type ShareSearchMatch } from '../../utils/share/shareSearch';
|
||||
import type { ShareSearchPreviewState } from '../../hooks/useShareSearchPreview';
|
||||
import type { SubsonicArtist } from '@/api/subsonicTypes';
|
||||
import type { ServerProfile } from '@/store/authStoreTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { activateShareSearchServer } from '@/utils/share/enqueueShareSearchPayload';
|
||||
import { sharePayloadTotal, type ShareSearchMatch } from '@/utils/share/shareSearch';
|
||||
import type { ShareSearchPreviewState } from '@/features/search/hooks/useShareSearchPreview';
|
||||
import { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from '@/ui/CachedImage';
|
||||
import { AlbumCoverArtImage } from '../../cover/AlbumCoverArtImage';
|
||||
import { ArtistCoverArtImage } from '../../cover/ArtistCoverArtImage';
|
||||
import { COVER_DENSE_SEARCH_CSS_PX } from '../../cover/layoutSizes';
|
||||
import { COVER_SCOPE_ACTIVE, type CoverServerScope } from '../../cover/types';
|
||||
import { useShareQueuePreview } from '../../hooks/useShareQueuePreview';
|
||||
import ShareQueuePreviewModal from './ShareQueuePreviewModal';
|
||||
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||
import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage';
|
||||
import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes';
|
||||
import { COVER_SCOPE_ACTIVE, type CoverServerScope } from '@/cover/types';
|
||||
import { useShareQueuePreview } from '@/features/search/hooks/useShareQueuePreview';
|
||||
import ShareQueuePreviewModal from '@/features/search/components/ShareQueuePreviewModal';
|
||||
|
||||
type ShareSearchResultsProps = {
|
||||
variant: 'desktop' | 'mobile';
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
import type { KeyboardEvent, MouseEvent } from 'react';
|
||||
import { ALL_NAV_ITEMS } from '../../config/navItems';
|
||||
import type { LiveSearchScope } from '../../store/liveSearchScopeStore';
|
||||
import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '../../store/albumBrowseSessionStore';
|
||||
import { isTracksBrowsePath } from '../../store/advancedSearchSessionStore';
|
||||
import { isArtistsBrowsePath } from '../../store/artistBrowseSessionStore';
|
||||
import { isComposersBrowsePath } from '../../store/composerBrowseSessionStore';
|
||||
import { ALL_NAV_ITEMS } from '@/config/navItems';
|
||||
import type { LiveSearchScope } from '@/store/liveSearchScopeStore';
|
||||
import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '@/store/albumBrowseSessionStore';
|
||||
import { isTracksBrowsePath } from '@/store/advancedSearchSessionStore';
|
||||
import { isArtistsBrowsePath } from '@/store/artistBrowseSessionStore';
|
||||
import { isComposersBrowsePath } from '@/store/composerBrowseSessionStore';
|
||||
|
||||
export const SCOPE_NAV_ITEM: Record<LiveSearchScope, keyof typeof ALL_NAV_ITEMS> = {
|
||||
artists: 'artists',
|
||||
+1
-1
@@ -11,7 +11,7 @@ import {
|
||||
noteLiveSearchScopeQueryInput,
|
||||
resetLiveSearchScopeBackspaceState,
|
||||
resolveLiveSearchScopeGhost,
|
||||
} from './liveSearchScope';
|
||||
} from '@/features/search/components/liveSearchScope';
|
||||
|
||||
function keyEvent(
|
||||
key: string,
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ALL_NAV_ITEMS } from '../../config/navItems';
|
||||
import type { LiveSearchScope } from '../../store/liveSearchScopeStore';
|
||||
import { ALL_NAV_ITEMS } from '@/config/navItems';
|
||||
import type { LiveSearchScope } from '@/store/liveSearchScopeStore';
|
||||
import {
|
||||
SCOPE_NAV_ITEM,
|
||||
handleLiveSearchScopeBadgeClick,
|
||||
handleLiveSearchScopeGhostClick,
|
||||
liveSearchScopeBadgeTooltipKey,
|
||||
liveSearchScopeGhostTooltipKey,
|
||||
} from './liveSearchScope';
|
||||
} from '@/features/search/components/liveSearchScope';
|
||||
|
||||
type LiveSearchScopeIconProps = {
|
||||
scope: LiveSearchScope;
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, beforeEach } from 'vitest';
|
||||
import { useLiveSearchScopeStore } from '../store/liveSearchScopeStore';
|
||||
import { syncLiveSearchRouteScope } from './useLiveSearchRouteScope';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
import { syncLiveSearchRouteScope } from '@/features/search/hooks/useLiveSearchRouteScope';
|
||||
|
||||
describe('syncLiveSearchRouteScope', () => {
|
||||
beforeEach(() => {
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '../store/albumBrowseSessionStore';
|
||||
import { isArtistsBrowsePath } from '../store/artistBrowseSessionStore';
|
||||
import { isTracksBrowsePath } from '../store/advancedSearchSessionStore';
|
||||
import { isComposersBrowsePath } from '../store/composerBrowseSessionStore';
|
||||
import { useLiveSearchScopeStore } from '../store/liveSearchScopeStore';
|
||||
import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '@/store/albumBrowseSessionStore';
|
||||
import { isArtistsBrowsePath } from '@/store/artistBrowseSessionStore';
|
||||
import { isTracksBrowsePath } from '@/store/advancedSearchSessionStore';
|
||||
import { isComposersBrowsePath } from '@/store/composerBrowseSessionStore';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
|
||||
/** Keep scope badge in sync with browse routes; clear field text when leaving browse. */
|
||||
export function syncLiveSearchRouteScope(pathname: string): void {
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import {
|
||||
resolveShareSearchPayload,
|
||||
type ShareSearchResolveResult,
|
||||
} from '../utils/share/enqueueShareSearchPayload';
|
||||
import type { QueueableShareSearchPayload } from '../utils/share/shareSearch';
|
||||
} from '@/utils/share/enqueueShareSearchPayload';
|
||||
import type { QueueableShareSearchPayload } from '@/utils/share/shareSearch';
|
||||
|
||||
export type ShareQueuePreviewState =
|
||||
| { status: 'idle' }
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigateToAlbum } from './useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/hooks/useNavigateToAlbum';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import {
|
||||
activateShareSearchServer,
|
||||
enqueueShareSearchPayload,
|
||||
} from '../utils/share/enqueueShareSearchPayload';
|
||||
import type { ServerProfile } from '../store/authStoreTypes';
|
||||
import { findServerIdForShareUrl } from '../utils/share/shareLink';
|
||||
import { shareServerOriginLabel } from '../utils/share/shareServerOriginLabel';
|
||||
import { parseShareSearchText } from '../utils/share/shareSearch';
|
||||
import { serverIndexKeyFromUrl } from '../utils/server/serverIndexKey';
|
||||
import { useShareSearchPreview } from './useShareSearchPreview';
|
||||
} from '@/utils/share/enqueueShareSearchPayload';
|
||||
import type { ServerProfile } from '@/store/authStoreTypes';
|
||||
import { findServerIdForShareUrl } from '@/utils/share/shareLink';
|
||||
import { shareServerOriginLabel } from '@/utils/share/shareServerOriginLabel';
|
||||
import { parseShareSearchText } from '@/utils/share/shareSearch';
|
||||
import { serverIndexKeyFromUrl } from '@/utils/server/serverIndexKey';
|
||||
import { useShareSearchPreview } from '@/features/search/hooks/useShareSearchPreview';
|
||||
|
||||
export function useShareSearch(query: string, onSuccess?: () => void) {
|
||||
const { t } = useTranslation();
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import {
|
||||
resolveShareSearchAlbum,
|
||||
resolveShareSearchArtist,
|
||||
resolveShareSearchPayload,
|
||||
} from '../utils/share/enqueueShareSearchPayload';
|
||||
import type { ShareSearchMatch } from '../utils/share/shareSearch';
|
||||
} from '@/utils/share/enqueueShareSearchPayload';
|
||||
import type { ShareSearchMatch } from '@/utils/share/shareSearch';
|
||||
|
||||
export interface ShareSearchPreviewState {
|
||||
shareTrackSong: SubsonicSong | null;
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Search feature — the live search dropdown, the mobile search overlay, the
|
||||
* search/browse page (lazy via the deep path `pages/SearchBrowsePage`), live
|
||||
* search scope UI/state, and the share-link search/queue-preview surfaces.
|
||||
*
|
||||
* The local-index query engine (`utils/library/*search*`), the shared Subsonic
|
||||
* search API, the cross-cutting `liveSearchScopeStore`, and the
|
||||
* `advancedSearch*` session/scroll state live outside this feature
|
||||
* (library-core / cross-cutting) — this feature consumes them.
|
||||
*/
|
||||
export { default as LiveSearch } from './components/LiveSearch';
|
||||
export { default as MobileSearchOverlay } from './components/MobileSearchOverlay';
|
||||
export { default as ShareQueuePreviewModal } from './components/ShareQueuePreviewModal';
|
||||
export { useLiveSearchRouteScope } from './hooks/useLiveSearchRouteScope';
|
||||
export { useShareQueuePreview } from './hooks/useShareQueuePreview';
|
||||
@@ -1,32 +1,32 @@
|
||||
import { getGenres, getAlbumsByGenre } from '../api/subsonicGenres';
|
||||
import { search, searchSongsPaged } from '../api/subsonicSearch';
|
||||
import { getRandomSongs } from '../api/subsonicLibrary';
|
||||
import type { SubsonicGenre, SubsonicArtist, SubsonicAlbum, SubsonicSong } from '../api/subsonicTypes';
|
||||
import { getGenres, getAlbumsByGenre } from '@/api/subsonicGenres';
|
||||
import { search, searchSongsPaged } from '@/api/subsonicSearch';
|
||||
import { getRandomSongs } from '@/api/subsonicLibrary';
|
||||
import type { SubsonicGenre, SubsonicArtist, SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation, useNavigate, useNavigationType, useSearchParams } from 'react-router-dom';
|
||||
import { SlidersVertical, Search, X } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AlbumRow from '../components/AlbumRow';
|
||||
import ArtistRow from '../components/ArtistRow';
|
||||
import PagedSongList from '../components/PagedSongList';
|
||||
import AlbumRow from '@/components/AlbumRow';
|
||||
import ArtistRow from '@/components/ArtistRow';
|
||||
import PagedSongList from '@/components/PagedSongList';
|
||||
import CustomSelect from '@/ui/CustomSelect';
|
||||
import StarFilterButton from '../components/StarFilterButton';
|
||||
import StarFilterButton from '@/components/StarFilterButton';
|
||||
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { isAdvancedSearchLeaveTargetPath } from '../store/albumBrowseSessionStore';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { isAdvancedSearchLeaveTargetPath } from '@/store/albumBrowseSessionStore';
|
||||
import {
|
||||
isAdvancedSearchPath,
|
||||
isAdvancedSearchPanelPath,
|
||||
isTracksBrowsePath,
|
||||
useAdvancedSearchSessionStore,
|
||||
type AdvancedSearchSessionStash,
|
||||
} from '../store/advancedSearchSessionStore';
|
||||
} from '@/store/advancedSearchSessionStore';
|
||||
import {
|
||||
readAdvancedSearchRestore,
|
||||
shouldRestoreAdvancedSearchSession,
|
||||
} from '../utils/navigation/albumDetailNavigation';
|
||||
} from '@/utils/navigation/albumDetailNavigation';
|
||||
import {
|
||||
clearAdvancedSearchLeaveSnapshots,
|
||||
consumeAdvancedSearchLeavingForDetail,
|
||||
@@ -35,36 +35,36 @@ import {
|
||||
registerAdvancedSearchSessionProvider,
|
||||
resolveAdvancedSearchLeaveSnapshot,
|
||||
type AdvancedSearchLeaveSnapshot,
|
||||
} from '../utils/navigation/advancedSearchScrollSnapshot';
|
||||
import { restoreMainViewportScroll } from '../utils/navigation/restoreMainViewportScroll';
|
||||
} from '@/utils/navigation/advancedSearchScrollSnapshot';
|
||||
import { restoreMainViewportScroll } from '@/utils/navigation/restoreMainViewportScroll';
|
||||
import {
|
||||
loadMoreLocalSongs,
|
||||
runNetworkAdvancedTextSearch,
|
||||
runNetworkAdvancedYearAlbums,
|
||||
tryRunLocalAdvancedSearch,
|
||||
} from '../utils/library/advancedSearchLocal';
|
||||
import { isLosslessSuffix } from '../utils/library/losslessFormats';
|
||||
import { LOSSLESS_MODE_QUERY } from '../utils/library/losslessMode';
|
||||
import { OXIMEDIA_MOOD_SEARCH_ENABLED } from '../utils/library/trackEnrichment';
|
||||
import { raceSearchSources } from '../utils/library/searchRace';
|
||||
import { logLibrarySearch } from '../utils/library/libraryDevLog';
|
||||
} from '@/utils/library/advancedSearchLocal';
|
||||
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
import { OXIMEDIA_MOOD_SEARCH_ENABLED } from '@/utils/library/trackEnrichment';
|
||||
import { raceSearchSources } from '@/utils/library/searchRace';
|
||||
import { logLibrarySearch } from '@/utils/library/libraryDevLog';
|
||||
import {
|
||||
browseRaceCountsFullSearch,
|
||||
loadMoreLocalBrowseSongs,
|
||||
raceBrowseWithLocalFallback,
|
||||
runLocalBrowseFullSearch,
|
||||
runNetworkBrowseFullSearch,
|
||||
} from '../utils/library/browseTextSearch';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import { MOOD_GROUP_IDS } from '../config/moodGroups';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { useSongBrowseList, type SongBrowseListRestore } from '../hooks/useSongBrowseList';
|
||||
import TracksPageChrome from '../components/tracks/TracksPageChrome';
|
||||
import SongBrowseSection from '../components/tracks/SongBrowseSection';
|
||||
} from '@/utils/library/browseTextSearch';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { MOOD_GROUP_IDS } from '@/config/moodGroups';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { useSongBrowseList, type SongBrowseListRestore } from '@/hooks/useSongBrowseList';
|
||||
import TracksPageChrome from '@/components/tracks/TracksPageChrome';
|
||||
import SongBrowseSection from '@/components/tracks/SongBrowseSection';
|
||||
import {
|
||||
useLiveSearchScopeStore,
|
||||
useScopedBrowseSearchQuery,
|
||||
} from '../store/liveSearchScopeStore';
|
||||
} from '@/store/liveSearchScopeStore';
|
||||
|
||||
const MOOD_UI_ENABLED = OXIMEDIA_MOOD_SEARCH_ENABLED;
|
||||
|
||||
Reference in New Issue
Block a user