mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(lib): move generic hooks, DnD engine, shortcut contract to lib
Continue M4 lib/ de-flattening with domain-agnostic infra: - lib/hooks/: 9 pure generic hooks (useDebouncedValue, useIsMobile, useLongPressAction, useRangeSelection, useResizeClientHeight, useWindowVisibility, useSystemPrefersDark, useVirtualizerScrollMargin, useRemeasureGridVirtualizer) — all PURE (only react/zustand/tanstack deps). - lib/dnd/DragDropContext.tsx: the generic mouse-event DnD engine (WebKitGTK HTML5-DnD workaround, ~24 importers, self-contained) — empties src/contexts/. - lib/shortcuts/: shortcutActions + shortcutTypes (the action-id + binding contract; registry/dispatch/bindings stay app-level and import the contract, app->lib direction). useWindowFullscreenState deliberately NOT moved — kept in hooks/ as app-shell per the handoff iron-rule list (overrides its pure-helper appearance). Pure move via deep @/lib/* specifiers. tsc 0, lint 0/0, full suite 319 files / 2353 tests green.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { lazy } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import MobilePlayerView from '../components/MobilePlayerView';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useIsMobile } from '@/lib/hooks/useIsMobile';
|
||||
import { useSidebarStore } from '@/features/sidebar';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import Sidebar from '@/features/sidebar';
|
||||
import PlayerBar from '../components/PlayerBar';
|
||||
import BottomNav from '../components/BottomNav';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useIsMobile } from '@/lib/hooks/useIsMobile';
|
||||
import { LiveSearch } from '@/features/search';
|
||||
import DevNetworkModeToggle from '../components/DevNetworkModeToggle';
|
||||
import { NowPlayingDropdown } from '@/features/nowPlaying';
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ import { lazy, Suspense, useEffect, useState } from 'react';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { WindowVisibilityProvider } from '../hooks/useWindowVisibility';
|
||||
import { DragDropProvider } from '../contexts/DragDropContext';
|
||||
import { WindowVisibilityProvider } from '@/lib/hooks/useWindowVisibility';
|
||||
import { DragDropProvider } from '@/lib/dnd/DragDropContext';
|
||||
import PasteClipboardHandler from '../components/PasteClipboardHandler';
|
||||
import ExportPickerModal from '../components/ExportPickerModal';
|
||||
import { ZipDownloadOverlay } from '@/features/offline';
|
||||
|
||||
@@ -32,7 +32,7 @@ vi.mock('@/features/miniPlayer', () => ({ default: () => <div data-testid="mini-
|
||||
vi.mock('../components/GlobalConfirmModal', () => ({ default: () => <div data-testid="confirm-modal" /> }));
|
||||
vi.mock('@/ui/TooltipPortal', () => ({ default: () => <div data-testid="tooltip-portal" /> }));
|
||||
vi.mock('../components/FpsOverlay', () => ({ default: () => <div data-testid="fps-overlay" /> }));
|
||||
vi.mock('../contexts/DragDropContext', () => ({
|
||||
vi.mock('@/lib/dnd/DragDropContext', () => ({
|
||||
DragDropProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { DragDropProvider } from '../contexts/DragDropContext';
|
||||
import { DragDropProvider } from '@/lib/dnd/DragDropContext';
|
||||
import MiniPlayer from '@/features/miniPlayer';
|
||||
import GlobalConfirmModal from '../components/GlobalConfirmModal';
|
||||
import TooltipPortal from '@/ui/TooltipPortal';
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
|
||||
import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration';
|
||||
import { AlbumRow } from '@/features/album';
|
||||
|
||||
@@ -13,14 +13,14 @@ import { useHeroBackdrop } from '../cover/useHeroBackdrop';
|
||||
import { useCachedUrl } from '@/ui/CachedImage';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useWindowVisibility } from '../hooks/useWindowVisibility';
|
||||
import { useIsMobile } from '@/lib/hooks/useIsMobile';
|
||||
import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useThemeStore } from '../store/themeStore';
|
||||
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
|
||||
import { albumArtistDisplayName, deriveAlbumArtistRefs } from '@/features/album';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
|
||||
import { useWindowVisibility } from '../hooks/useWindowVisibility';
|
||||
import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility';
|
||||
|
||||
export interface PlaybackScheduleBadgeProps {
|
||||
/** Anchor element (usually the play/pause button wrapper) — the ring centres on it. */
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useCoverArt } from '../cover/useCoverArt';
|
||||
import { useTrackCoverRef } from '../cover/useLibraryCoverRef';
|
||||
import { COVER_DENSE_RAIL_CELL_CSS_PX } from '../cover/layoutSizes';
|
||||
import { enqueueAndPlay } from '../utils/playback/playSong';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { useNavigateToArtist } from '@/features/artist';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Play, ListPlus } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { enqueueAndPlay } from '../utils/playback/playSong';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
|
||||
|
||||
@@ -3,10 +3,10 @@ import { GRID_COVER_WARM_LIMIT } from '../cover/layoutSizes';
|
||||
import { useWarmGridCovers } from '../hooks/useWarmGridCovers';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
|
||||
import { useElementClientHeightById } from '../hooks/useResizeClientHeight';
|
||||
import { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight';
|
||||
import { useCardGridMetrics } from '../hooks/useCardGridMetrics';
|
||||
import { useRemeasureGridVirtualizer } from '../hooks/useRemeasureGridVirtualizer';
|
||||
import { useVirtualizerScrollMargin } from '../hooks/useVirtualizerScrollMargin';
|
||||
import { useRemeasureGridVirtualizer } from '@/lib/hooks/useRemeasureGridVirtualizer';
|
||||
import { useVirtualizerScrollMargin } from '@/lib/hooks/useVirtualizerScrollMargin';
|
||||
import type { CardGridRowHeightVariant } from '../utils/cardGridLayout';
|
||||
|
||||
export type VirtualCardGridProps<T> = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AudioLines, ChevronRight, Heart, Play, Square } from 'lucide-react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '../../store/playerStoreTypes';
|
||||
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
|
||||
import { useDragDrop } from '../../contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { formatRandomMixDuration } from '../../utils/componentHelpers/randomMixHelpers';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePreviewStore } from '../store/previewStore';
|
||||
import { useLyricsStore } from '../store/lyricsStore';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import type { ActionContext, ShortcutSlot, ShortcutActionMeta } from './shortcutTypes';
|
||||
import type { ActionContext, ShortcutSlot, ShortcutActionMeta } from '@/lib/shortcuts/shortcutTypes';
|
||||
|
||||
let cliPremuteVolume: number | null = null;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ShortcutSlot } from './shortcutTypes';
|
||||
import type { ShortcutSlot } from '@/lib/shortcuts/shortcutTypes';
|
||||
import {
|
||||
SHORTCUT_ACTION_REGISTRY,
|
||||
type ShortcutAction,
|
||||
|
||||
@@ -3,7 +3,7 @@ import i18n from '@/lib/i18n';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { playByOpaqueId } from '../utils/playback/playByOpaqueId';
|
||||
import type { ActionContext, CliContext } from './shortcutTypes';
|
||||
import type { ActionContext, CliContext } from '@/lib/shortcuts/shortcutTypes';
|
||||
import {
|
||||
SHORTCUT_ACTION_REGISTRY,
|
||||
type ShortcutAction,
|
||||
|
||||
@@ -17,9 +17,9 @@ import { resolveCoverDisplayTier } from '@/cover/tiers';
|
||||
import { acquireUrl } from '@/utils/imageCache/urlPool';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { fetchAlbumTracks, playAlbum, playAlbumShuffled } from '@/utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '@/hooks/useLongPressAction';
|
||||
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from '@/components/LongPressWaveOverlay';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { isAlbumRecentlyAdded } from '@/features/album/utils/albumRecency';
|
||||
import { albumArtistDisplayName, deriveAlbumArtistRefs } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
import { coverServerScopeForServerId } from '@/cover/serverScope';
|
||||
|
||||
@@ -16,7 +16,7 @@ vi.mock('react-router-dom', async importActual => {
|
||||
vi.mock('@/cover/useLibraryCoverRef', () => ({ useAlbumCoverRef: () => undefined }));
|
||||
vi.mock('@/cover/lightbox', () => ({ useCoverLightboxSrc: () => ({ open: vi.fn(), lightbox: null }) }));
|
||||
vi.mock('@/features/album/hooks/useAlbumDetailBack', () => ({ useAlbumDetailBack: () => vi.fn() }));
|
||||
vi.mock('@/hooks/useIsMobile', () => ({ useIsMobile: () => false }));
|
||||
vi.mock('@/lib/hooks/useIsMobile', () => ({ useIsMobile: () => false }));
|
||||
vi.mock('@/store/themeStore', () => ({ useThemeStore: () => false }));
|
||||
vi.mock('@/components/StarRating', () => ({ default: () => null }));
|
||||
vi.mock('@/features/artist', () => ({ OpenArtistRefInline: () => null }));
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { useAlbumCoverRef } from '@/cover/useLibraryCoverRef';
|
||||
import { useCoverLightboxSrc } from '@/cover/lightbox';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useIsMobile } from '@/lib/hooks/useIsMobile';
|
||||
import { useAlbumDetailBack } from '@/features/album/hooks/useAlbumDetailBack';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import StarRating from '@/components/StarRating';
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { useTracklistColumns } from '@/utils/useTracklistColumns';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useIsMobile } from '@/lib/hooks/useIsMobile';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import {
|
||||
COLUMNS,
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
} from '@/utils/library/albumBrowseCatalogChunk';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { useClientSliceInfiniteScroll } from '@/hooks/useClientSliceInfiniteScroll';
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { useDebouncedValue } from '@/lib/hooks/useDebouncedValue';
|
||||
import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel';
|
||||
|
||||
const PAGE_SIZE = 30;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
|
||||
interface UseAlbumTrackListSelectionArgs {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { Download, HardDriveDownload, Disc3, ListPlus } from 'lucide-react';
|
||||
import SelectionToggleButton from '@/components/SelectionToggleButton';
|
||||
import FilterQuickClear from '@/components/FilterQuickClear';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { useRangeSelection } from '@/hooks/useRangeSelection';
|
||||
import { useRangeSelection } from '@/lib/hooks/useRangeSelection';
|
||||
import { useMainstageInpageHeaderTight } from '@/hooks/useMainstageInpageHeaderTight';
|
||||
import { useInpageScrollViewport } from '@/hooks/useInpageScrollViewport';
|
||||
import InpageScrollSentinel from '@/components/InpageScrollSentinel';
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useOfflineStore } from '@/features/offline';
|
||||
import { useDownloadModalStore } from '@/features/offline';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
import { useRangeSelection } from '@/hooks/useRangeSelection';
|
||||
import { useRangeSelection } from '@/lib/hooks/useRangeSelection';
|
||||
import { useMainstageInpageHeaderTight } from '@/hooks/useMainstageInpageHeaderTight';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
|
||||
@@ -22,7 +22,7 @@ import { invoke } from '@tauri-apps/api/core';
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
import { useRangeSelection } from '@/hooks/useRangeSelection';
|
||||
import { useRangeSelection } from '@/lib/hooks/useRangeSelection';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers, COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes';
|
||||
import {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useOfflineStore } from '@/features/offline';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useArtistOfflineState } from '@/features/artist/hooks/useArtistOfflineState';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useIsMobile } from '@/lib/hooks/useIsMobile';
|
||||
import { ArtistHeroCover } from '@/cover/artistHero';
|
||||
import { useArtistBanner, useArtistFanart } from '@/cover/useArtistFanart';
|
||||
import { backdropFromConfig } from '@/cover/artistBackdrop';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useIsMobile } from '@/lib/hooks/useIsMobile';
|
||||
|
||||
interface Props {
|
||||
marginTop: string;
|
||||
|
||||
@@ -9,8 +9,8 @@ import { useAuthStore } from '@/store/authStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID, ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useElementClientHeightById, useElementClientHeightForElement } from '@/hooks/useResizeClientHeight';
|
||||
import { useVirtualizerScrollMargin } from '@/hooks/useVirtualizerScrollMargin';
|
||||
import { useElementClientHeightById, useElementClientHeightForElement } from '@/lib/hooks/useResizeClientHeight';
|
||||
import { useVirtualizerScrollMargin } from '@/lib/hooks/useVirtualizerScrollMargin';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import {
|
||||
ALL_SENTINEL,
|
||||
|
||||
@@ -10,12 +10,12 @@ 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 { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
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 { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight';
|
||||
import { SORTABLE_COLUMNS } from '@/features/favorites/hooks/useFavoritesSongFiltering';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { emit } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { registerQueueDragHitTest } from '@/contexts/DragDropContext';
|
||||
import { registerQueueDragHitTest } from '@/lib/dnd/DragDropContext';
|
||||
import MiniContextMenu from '@/features/miniPlayer/components/MiniContextMenu';
|
||||
import type { MiniSyncPayload, MiniControlAction, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
|
||||
interface Args {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { emit, listen } from '@tauri-apps/api/event';
|
||||
import { useWindowVisibility } from '@/hooks/useWindowVisibility';
|
||||
import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility';
|
||||
import type { MiniSyncPayload } from '@/features/miniPlayer/utils/miniPlayerBridge';
|
||||
|
||||
interface ProgressPayload {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useDragSource } from '@/contexts/DragDropContext';
|
||||
import { useDragSource } from '@/lib/dnd/DragDropContext';
|
||||
import type { NpCardId } from '@/features/nowPlaying/store/nowPlayingLayoutStore';
|
||||
|
||||
interface NpCardWrapProps {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useAuthStore } from '@/store/authStore';
|
||||
import { useLyricsStore } from '@/store/lyricsStore';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { useRadioMetadata } from '@/features/radio';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
import {
|
||||
useNpLayoutStore, NP_CARD_IDS,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
displayPlaylistName, isSmartPlaylistName, type PendingSmartPlaylist,
|
||||
} from '@/features/playlist/utils/playlistsSmart';
|
||||
import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration';
|
||||
import { useDragSource } from '@/contexts/DragDropContext';
|
||||
import { useDragSource } from '@/lib/dnd/DragDropContext';
|
||||
import { PlaylistCardMainCover, PlaylistSmartCoverCell } from '@/features/playlist/components/PlaylistCoverImages';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ChevronRight, Folder, Pencil, Trash2 } from 'lucide-react';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { usePlaylistFolderStore } from '@/features/playlist/store/playlistFolderStore';
|
||||
import type { PlaylistFolder } from '@/features/playlist/utils/playlistFolders';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -4,7 +4,7 @@ import PlaylistRow, { type PlaylistRowCallbacks } from '@/features/playlist/comp
|
||||
import { TracklistColumnPicker } from '@/components/albumTrackList/TracklistColumnPicker';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useElementClientHeightById } from '@/hooks/useResizeClientHeight';
|
||||
import { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
ListPlus, Search, Trash2, X,
|
||||
@@ -15,7 +15,7 @@ import type { Track } from '@/store/playerStoreTypes';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import type { PlaylistSortKey, PlaylistSortDir } from '@/features/playlist/utils/playlistDisplayedSongs';
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { EMPTY_SERVER_FOLDERS, usePlaylistFolderStore } from '@/features/playlist/store/playlistFolderStore';
|
||||
import { groupPlaylistsByFolder } from '@/features/playlist/utils/playlistFolders';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import PlaylistFolderSection from '@/features/playlist/components/PlaylistFolderSection';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { runPlaylistReorderDrop } from '@/features/playlist/utils/runPlaylistReorderDrop';
|
||||
|
||||
export interface PlaylistDnDReorderDeps {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useAlbumOfflineState } from '@/features/album';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useDownloadModalStore } from '@/features/offline';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { SpotifyCsvTrack } from '@/features/playlist/utils/spotifyCsvImport';
|
||||
import { runPlaylistCsvImport } from '@/features/playlist/utils/runPlaylistCsvImport';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { usePlayerStore } from '@/store/playerStore';
|
||||
import { usePlaylistStore } from '@/features/playlist/store/playlistStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRangeSelection } from '@/hooks/useRangeSelection';
|
||||
import { useRangeSelection } from '@/lib/hooks/useRangeSelection';
|
||||
|
||||
import {
|
||||
defaultSmartFilters,
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 '@/lib/api/subsonicTypes';
|
||||
import { useDragDrop, useDragSource } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop, useDragSource } from '@/lib/dnd/DragDropContext';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { albumCoverRef } from '@/cover/ref';
|
||||
import { coverArtIdFromRadio } from '@/cover/ids';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ChevronDown, ChevronUp, GripVertical } from 'lucide-react';
|
||||
import { useDragDrop, useDragSource } from '@/contexts/DragDropContext';
|
||||
import { useDragDrop, useDragSource } from '@/lib/dnd/DragDropContext';
|
||||
import type { BackdropSource, BackdropSourcePref } from '@/cover/artistBackdrop';
|
||||
import type { BackdropSurface } from '@/store/themeStore';
|
||||
import { moveSourceTo, dropSourceBefore } from '@/features/settings/components/backdropReorder';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Keyboard, RotateCcw, X } from 'lucide-react';
|
||||
import { IN_APP_SHORTCUT_ACTIONS, GLOBAL_SHORTCUT_ACTIONS } from '@/config/shortcutActions';
|
||||
import { IN_APP_SHORTCUT_ACTIONS, GLOBAL_SHORTCUT_ACTIONS } from '@/lib/shortcuts/shortcutActions';
|
||||
import { useGlobalShortcutsStore, type GlobalAction, buildGlobalShortcut, formatGlobalShortcut } from '@/store/globalShortcutsStore';
|
||||
import { useKeybindingsStore, type KeyAction, buildInAppBinding, formatBinding } from '@/store/keybindingsStore';
|
||||
import SettingsSubSection from '@/features/settings/components/SettingsSubSection';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GripVertical } from 'lucide-react';
|
||||
import { useDragSource } from '@/contexts/DragDropContext';
|
||||
import { useDragSource } from '@/lib/dnd/DragDropContext';
|
||||
|
||||
/**
|
||||
* Drag handle shared by the reorder customizers. Emits an id-based payload
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TFunction } from 'i18next';
|
||||
import { GLOBAL_SHORTCUT_ACTIONS, IN_APP_SHORTCUT_ACTIONS } from '@/config/shortcutActions';
|
||||
import { GLOBAL_SHORTCUT_ACTIONS, IN_APP_SHORTCUT_ACTIONS } from '@/lib/shortcuts/shortcutActions';
|
||||
import { SETTINGS_INDEX, matchScore, type Tab } from '@/features/settings/components/settingsTabs';
|
||||
|
||||
export type SettingsSearchHit = {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { switchActiveServer } from '../../utils/server/switchActiveServer';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { executeCliPlayerCommand } from '../../config/shortcutActions';
|
||||
import { executeCliPlayerCommand } from '@/lib/shortcuts/shortcutActions';
|
||||
|
||||
/** The full `cli:*` listener surface forwarded from the Rust single-instance
|
||||
* handler: audio-device, instant-mix, library / server resolution, search and
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import { useKeybindingsStore, buildInAppBinding } from '../../store/keybindingsStore';
|
||||
import { useGlobalShortcutsStore } from '../../store/globalShortcutsStore';
|
||||
import { DEFAULT_IN_APP_BINDINGS, executeRuntimeAction } from '../../config/shortcutActions';
|
||||
import { DEFAULT_IN_APP_BINDINGS, executeRuntimeAction } from '@/lib/shortcuts/shortcutActions';
|
||||
import { matchInAppShortcutAction } from '../../shortcuts/runtime';
|
||||
|
||||
/** Configurable in-app keybindings: matches keydown chords against the user's
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
executeRuntimeAction,
|
||||
isGlobalShortcutActionId,
|
||||
isShortcutAction,
|
||||
} from '../../config/shortcutActions';
|
||||
} from '@/lib/shortcuts/shortcutActions';
|
||||
|
||||
/** Media keys, tray actions, global / cross-window shortcut events, relative &
|
||||
* absolute seek + volume, and the window-close / force-quit exit flow. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import type { ListReorderDropTarget } from '../utils/componentHelpers/listReorder';
|
||||
|
||||
interface Options {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import { useDragDrop, registerQueueDragHitTest } from '../contexts/DragDropContext';
|
||||
import { useDragDrop, registerQueueDragHitTest } from '@/lib/dnd/DragDropContext';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useThemeStore, getScheduledTheme } from '../store/themeStore';
|
||||
import { useSystemPrefersDark } from './useSystemPrefersDark';
|
||||
import { useSystemPrefersDark } from '@/lib/hooks/useSystemPrefersDark';
|
||||
|
||||
/**
|
||||
* Effective theme id for `data-theme` — scheduler-aware when enabled.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useDebouncedValue } from './useDebouncedValue';
|
||||
import { useDebouncedValue } from '@/lib/hooks/useDebouncedValue';
|
||||
|
||||
describe('useDebouncedValue', () => {
|
||||
beforeEach(() => {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import type { MouseEvent, PointerEvent } from 'react';
|
||||
import { useLongPressAction } from './useLongPressAction';
|
||||
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
|
||||
|
||||
function makePointerDown(overrides: Partial<PointerEvent> = {}): PointerEvent {
|
||||
return {
|
||||
@@ -48,14 +48,14 @@ export type {
|
||||
ActionContext,
|
||||
CliContext,
|
||||
ShortcutActionMeta,
|
||||
} from './shortcutTypes';
|
||||
} from '@/lib/shortcuts/shortcutTypes';
|
||||
|
||||
export {
|
||||
SHORTCUT_ACTION_REGISTRY,
|
||||
type ShortcutAction,
|
||||
type KeyAction,
|
||||
type GlobalAction,
|
||||
} from './shortcutActionRegistry';
|
||||
} from '@/config/shortcutActionRegistry';
|
||||
|
||||
export {
|
||||
isShortcutAction,
|
||||
@@ -64,11 +64,11 @@ export {
|
||||
executeRuntimeAction,
|
||||
executeCliPlayerCommand,
|
||||
type RuntimeAction,
|
||||
} from './shortcutDispatch';
|
||||
} from '@/config/shortcutDispatch';
|
||||
|
||||
export {
|
||||
IN_APP_SHORTCUT_ACTIONS,
|
||||
GLOBAL_SHORTCUT_ACTIONS,
|
||||
DEFAULT_IN_APP_BINDINGS,
|
||||
DEFAULT_GLOBAL_SHORTCUTS,
|
||||
} from './shortcutBindings';
|
||||
} from '@/config/shortcutBindings';
|
||||
@@ -9,7 +9,7 @@ import { useAuthStore } from '../store/authStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID, COMPOSERS_INPAGE_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
|
||||
import { useElementClientHeightById, useElementClientHeightForElement } from '../hooks/useResizeClientHeight';
|
||||
import { useElementClientHeightById, useElementClientHeightForElement } from '@/lib/hooks/useResizeClientHeight';
|
||||
import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight';
|
||||
import { useBrowseArtistTextSearch } from '@/features/artist';
|
||||
import { useComposersBrowseFilters, type ComposerBrowseScrollSnapshot } from '../hooks/useComposersBrowseFilters';
|
||||
@@ -25,7 +25,7 @@ import { useLibraryIgnoredArticles } from '../hooks/useLibraryIgnoredArticles';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
import { useVirtualizerScrollMargin } from '../hooks/useVirtualizerScrollMargin';
|
||||
import { useVirtualizerScrollMargin } from '@/lib/hooks/useVirtualizerScrollMargin';
|
||||
import { useClientSliceInfiniteScroll } from '../hooks/useClientSliceInfiniteScroll';
|
||||
import { useInpageScrollViewport } from '../hooks/useInpageScrollViewport';
|
||||
import InpageScrollSentinel from '../components/InpageScrollSentinel';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useGenreAlbumBrowse } from '@/features/album';
|
||||
import { useAlbumBrowseScrollRestore } from '@/features/album';
|
||||
import { useGenreDetailBrowse } from '../hooks/useGenreDetailBrowse';
|
||||
import { useInpageScrollViewport } from '../hooks/useInpageScrollViewport';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
|
||||
import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
|
||||
@@ -10,7 +10,7 @@ import { usePlayerStore } from '../store/playerStore';
|
||||
import { AlbumCoverArtImage } from '../cover/AlbumCoverArtImage';
|
||||
import { ArtistCoverArtImage } from '../cover/ArtistCoverArtImage';
|
||||
import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from '../components/LongPressWaveOverlay';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { albumArtistDisplayName } from '@/features/album';
|
||||
|
||||
@@ -18,7 +18,7 @@ import { invoke } from '@tauri-apps/api/core';
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
import { useRangeSelection } from '../hooks/useRangeSelection';
|
||||
import { useRangeSelection } from '@/lib/hooks/useRangeSelection';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight';
|
||||
import { albumGridWarmCovers } from '../cover/layoutSizes';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { usePreviewStore } from '../store/previewStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useIsMobile } from '@/lib/hooks/useIsMobile';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import {
|
||||
fetchRandomMixSongsUntilFull,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { matchInAppBinding, type Bindings } from '../store/keybindingsStore';
|
||||
import type { KeyAction } from '../config/shortcutActions';
|
||||
import type { KeyAction } from '@/lib/shortcuts/shortcutActions';
|
||||
|
||||
export function matchInAppShortcutAction(
|
||||
event: KeyboardEvent,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { MODIFIER_KEY_CODES, formatBinding } from './keybindingsStore';
|
||||
import { DEFAULT_GLOBAL_SHORTCUTS, isGlobalShortcutActionId, type GlobalAction } from '../config/shortcutActions';
|
||||
import { DEFAULT_GLOBAL_SHORTCUTS, isGlobalShortcutActionId, type GlobalAction } from '@/lib/shortcuts/shortcutActions';
|
||||
|
||||
/** Dev builds run alongside release — OS-level grabs stay on the release instance. */
|
||||
const GLOBAL_SHORTCUTS_OS_ENABLED = !import.meta.env.DEV;
|
||||
@@ -101,4 +101,4 @@ export const useGlobalShortcutsStore = create<GlobalShortcutsState>()(
|
||||
)
|
||||
);
|
||||
|
||||
export type { GlobalAction } from '../config/shortcutActions';
|
||||
export type { GlobalAction } from '@/lib/shortcuts/shortcutActions';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { DEFAULT_IN_APP_BINDINGS, type KeyAction } from '../config/shortcutActions';
|
||||
import { DEFAULT_IN_APP_BINDINGS, type KeyAction } from '@/lib/shortcuts/shortcutActions';
|
||||
|
||||
/** Physical keys only — ignore for binding capture */
|
||||
export const MODIFIER_KEY_CODES = [
|
||||
@@ -12,7 +12,7 @@ export const MODIFIER_KEY_CODES = [
|
||||
export type Bindings = Record<KeyAction, string | null>;
|
||||
|
||||
export const DEFAULT_BINDINGS: Bindings = { ...DEFAULT_IN_APP_BINDINGS };
|
||||
export type { KeyAction } from '../config/shortcutActions';
|
||||
export type { KeyAction } from '@/lib/shortcuts/shortcutActions';
|
||||
|
||||
function normalizeBindings(
|
||||
bindings: Partial<Record<KeyAction, string | null>> | undefined
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { useWindowVisibility } from '../../hooks/useWindowVisibility';
|
||||
import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility';
|
||||
|
||||
/** Remaining time until wall-clock `deadlineMs` (m:ss or h:mm:ss). */
|
||||
export function formatPlaybackScheduleRemaining(deadlineMs: number | null, nowMs: number): string {
|
||||
|
||||
Reference in New Issue
Block a user