mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(orbit): co-locate orbit feature into features/orbit
This commit is contained in:
@@ -20,8 +20,7 @@ import SongInfoModal from '../components/SongInfoModal';
|
|||||||
import { DownloadFolderModal } from '@/features/offline';
|
import { DownloadFolderModal } from '@/features/offline';
|
||||||
import GlobalConfirmModal from '../components/GlobalConfirmModal';
|
import GlobalConfirmModal from '../components/GlobalConfirmModal';
|
||||||
import ThemeMigrationNotice from '../components/ThemeMigrationNotice';
|
import ThemeMigrationNotice from '../components/ThemeMigrationNotice';
|
||||||
import OrbitAccountPicker from '../components/OrbitAccountPicker';
|
import { OrbitAccountPicker, OrbitHelpModal } from '@/features/orbit';
|
||||||
import OrbitHelpModal from '../components/OrbitHelpModal';
|
|
||||||
import TooltipPortal from '@/ui/TooltipPortal';
|
import TooltipPortal from '@/ui/TooltipPortal';
|
||||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||||
import {
|
import {
|
||||||
@@ -33,11 +32,10 @@ import MusicNetworkIndicator from '../components/MusicNetworkIndicator';
|
|||||||
import { OfflineBanner } from '@/features/offline';
|
import { OfflineBanner } from '@/features/offline';
|
||||||
import AppUpdater from '../components/AppUpdater';
|
import AppUpdater from '../components/AppUpdater';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import OrbitSessionBar from '../components/OrbitSessionBar';
|
import { OrbitSessionBar, OrbitStartTrigger } from '@/features/orbit';
|
||||||
import OrbitStartTrigger from '../components/OrbitStartTrigger';
|
import { useOrbitHost } from '@/features/orbit';
|
||||||
import { useOrbitHost } from '../hooks/useOrbitHost';
|
import { useOrbitGuest } from '@/features/orbit';
|
||||||
import { useOrbitGuest } from '../hooks/useOrbitGuest';
|
import { useOrbitBodyAttrs } from '@/features/orbit';
|
||||||
import { useOrbitBodyAttrs } from '../hooks/useOrbitBodyAttrs';
|
|
||||||
import { usePlatformShellSetup } from '../hooks/usePlatformShellSetup';
|
import { usePlatformShellSetup } from '../hooks/usePlatformShellSetup';
|
||||||
import { useOfflineBrowseContext } from '@/features/offline';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { offlineBrowseNavFlags } from '@/features/offline';
|
import { offlineBrowseNavFlags } from '@/features/offline';
|
||||||
@@ -67,7 +65,7 @@ import { useThemeStore } from '../store/themeStore';
|
|||||||
import { useFontStore } from '../store/fontStore';
|
import { useFontStore } from '../store/fontStore';
|
||||||
import { useEqStore } from '../store/eqStore';
|
import { useEqStore } from '../store/eqStore';
|
||||||
import { usePlaybackRateStore } from '../store/playbackRateStore';
|
import { usePlaybackRateStore } from '../store/playbackRateStore';
|
||||||
import { usePlaybackRateOrbitSync } from '../hooks/usePlaybackRateOrbitSync';
|
import { usePlaybackRateOrbitSync } from '@/features/orbit';
|
||||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||||
import {
|
import {
|
||||||
persistSidebarCollapsed,
|
persistSidebarCollapsed,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ vi.mock('@/api/subsonic', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
vi.mock('@/utils/orbitBulkGuard', () => ({
|
vi.mock('@/features/orbit', () => ({
|
||||||
orbitBulkGuard: vi.fn(async () => true),
|
orbitBulkGuard: vi.fn(async () => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import type { Track } from '../store/playerStoreTypes';
|
import type { Track } from '../store/playerStoreTypes';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import type { SubsonicSong } from '../api/subsonicTypes';
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Check, X, Inbox } from 'lucide-react';
|
import { Check, X, Inbox } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import {
|
import {
|
||||||
approveOrbitSuggestion,
|
approveOrbitSuggestion,
|
||||||
declineOrbitSuggestion,
|
declineOrbitSuggestion,
|
||||||
suggestionKey,
|
suggestionKey,
|
||||||
} from '../utils/orbit';
|
} from '@/features/orbit';
|
||||||
import { TrackCoverArtImage } from '../cover/TrackCoverArtImage';
|
import { TrackCoverArtImage } from '../cover/TrackCoverArtImage';
|
||||||
import { ORBIT_DEFAULT_SETTINGS } from '../api/orbit';
|
import { ORBIT_DEFAULT_SETTINGS } from '@/features/orbit';
|
||||||
|
|
||||||
const HOST_APPROVAL_COVER_CSS_PX = 36;
|
const HOST_APPROVAL_COVER_CSS_PX = 36;
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import {
|
|||||||
findSessionPlaylistId,
|
findSessionPlaylistId,
|
||||||
readOrbitState,
|
readOrbitState,
|
||||||
OrbitJoinError,
|
OrbitJoinError,
|
||||||
} from '../utils/orbit';
|
} from '@/features/orbit';
|
||||||
import { switchActiveServer } from '../utils/server/switchActiveServer';
|
import { switchActiveServer } from '../utils/server/switchActiveServer';
|
||||||
import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore';
|
import { useOrbitAccountPickerStore } from '@/features/orbit';
|
||||||
import ConfirmModal from './ConfirmModal';
|
import ConfirmModal from './ConfirmModal';
|
||||||
|
|
||||||
const ORBIT_JOIN_ERROR_KEYS: Record<string, string> = {
|
const ORBIT_JOIN_ERROR_KEYS: Record<string, string> = {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ vi.mock('@/api/subsonic', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
vi.mock('@/utils/orbitBulkGuard', () => ({
|
vi.mock('@/features/orbit', () => ({
|
||||||
orbitBulkGuard: vi.fn(async () => true),
|
orbitBulkGuard: vi.fn(async () => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { songToTrack } from '../utils/playback/songToTrack';
|
|||||||
import type { Track } from '../store/playerStoreTypes';
|
import type { Track } from '../store/playerStoreTypes';
|
||||||
import { useState, useRef, useMemo } from 'react';
|
import { useState, useRef, useMemo } from 'react';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import OrbitGuestQueue from './OrbitGuestQueue';
|
import { OrbitGuestQueue, OrbitQueueHead } from '@/features/orbit';
|
||||||
import OrbitQueueHead from './OrbitQueueHead';
|
|
||||||
import HostApprovalQueue from './HostApprovalQueue';
|
import HostApprovalQueue from './HostApprovalQueue';
|
||||||
import { usePlaylistStore } from '../store/playlistStore';
|
import { usePlaylistStore } from '../store/playlistStore';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useTrackCoverRef } from '../cover/useLibraryCoverRef';
|
|||||||
import { COVER_DENSE_RAIL_CELL_CSS_PX } from '../cover/layoutSizes';
|
import { COVER_DENSE_RAIL_CELL_CSS_PX } from '../cover/layoutSizes';
|
||||||
import { enqueueAndPlay } from '../utils/playback/playSong';
|
import { enqueueAndPlay } from '../utils/playback/playSong';
|
||||||
import { useDragDrop } from '../contexts/DragDropContext';
|
import { useDragDrop } from '../contexts/DragDropContext';
|
||||||
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior';
|
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||||
import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum';
|
import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum';
|
||||||
import { useNavigateToArtist } from '../hooks/useNavigateToArtist';
|
import { useNavigateToArtist } from '../hooks/useNavigateToArtist';
|
||||||
import { OpenArtistRefInline } from './OpenArtistRefInline';
|
import { OpenArtistRefInline } from './OpenArtistRefInline';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
import { enqueueAndPlay } from '../utils/playback/playSong';
|
import { enqueueAndPlay } from '../utils/playback/playSong';
|
||||||
import { useDragDrop } from '../contexts/DragDropContext';
|
import { useDragDrop } from '../contexts/DragDropContext';
|
||||||
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior';
|
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||||
import { formatTrackTime } from '../utils/format/formatDuration';
|
import { formatTrackTime } from '../utils/format/formatDuration';
|
||||||
import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
|
import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
|
||||||
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { AudioLines, ChevronRight, Play, Square } from 'lucide-react';
|
|||||||
import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes';
|
||||||
import { usePlayerStore } from '../../store/playerStore';
|
import { usePlayerStore } from '../../store/playerStore';
|
||||||
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
|
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
|
||||||
import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior';
|
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||||
import { formatTrackTime } from '../../utils/format/formatDuration';
|
import { formatTrackTime } from '../../utils/format/formatDuration';
|
||||||
import ArtistTopTrackCover from './ArtistTopTrackCover';
|
import ArtistTopTrackCover from './ArtistTopTrackCover';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useAuthStore } from '../../store/authStore';
|
|||||||
import { usePlaylistStore } from '../../store/playlistStore';
|
import { usePlaylistStore } from '../../store/playlistStore';
|
||||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||||
import { showToast } from '../../utils/ui/toast';
|
import { showToast } from '../../utils/ui/toast';
|
||||||
import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '../../utils/orbit';
|
import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '@/features/orbit';
|
||||||
import { renderPresetIcon } from '../settings/musicNetwork/presetIcon';
|
import { renderPresetIcon } from '../settings/musicNetwork/presetIcon';
|
||||||
import StarRating from '../StarRating';
|
import StarRating from '../StarRating';
|
||||||
import { AddToPlaylistSubmenu } from './AddToPlaylistSubmenu';
|
import { AddToPlaylistSubmenu } from './AddToPlaylistSubmenu';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom';
|
|||||||
import type { TFunction } from 'i18next';
|
import type { TFunction } from 'i18next';
|
||||||
import { PlaybackRateControls } from '@/features/settings';
|
import { PlaybackRateControls } from '@/features/settings';
|
||||||
import { usePlaybackRateStore } from '../../store/playbackRateStore';
|
import { usePlaybackRateStore } from '../../store/playbackRateStore';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import {
|
import {
|
||||||
clampPlaybackPitch,
|
clampPlaybackPitch,
|
||||||
clampPlaybackSpeed,
|
clampPlaybackSpeed,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
playbackSpeedStep,
|
playbackSpeedStep,
|
||||||
varispeedSpeedFromSemitones,
|
varispeedSpeedFromSemitones,
|
||||||
} from '../../utils/audio/playbackRateHelpers';
|
} from '../../utils/audio/playbackRateHelpers';
|
||||||
import { isOrbitPlaybackSyncActive } from '../../utils/orbit';
|
import { isOrbitPlaybackSyncActive } from '@/features/orbit';
|
||||||
import { usePlayerBarAnchoredPopover } from '../../hooks/usePlayerBarAnchoredPopover';
|
import { usePlayerBarAnchoredPopover } from '../../hooks/usePlayerBarAnchoredPopover';
|
||||||
|
|
||||||
const POPOVER_WIDTH = 320;
|
const POPOVER_WIDTH = 320;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { usePlayerStore } from '../../store/playerStore';
|
|||||||
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
|
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
|
||||||
import { useThemeStore } from '../../store/themeStore';
|
import { useThemeStore } from '../../store/themeStore';
|
||||||
import { useDragDrop } from '../../contexts/DragDropContext';
|
import { useDragDrop } from '../../contexts/DragDropContext';
|
||||||
import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior';
|
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||||
import type { PlaylistSortKey, PlaylistSortDir } from '../../utils/playlist/playlistDisplayedSongs';
|
import type { PlaylistSortKey, PlaylistSortDir } from '../../utils/playlist/playlistDisplayedSongs';
|
||||||
import { AddToPlaylistSubmenu } from '../ContextMenu';
|
import { AddToPlaylistSubmenu } from '../ContextMenu';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type {
|
|||||||
QueueToolbarButtonId,
|
QueueToolbarButtonId,
|
||||||
} from '../../store/queueToolbarStore';
|
} from '../../store/queueToolbarStore';
|
||||||
import { getTransitionMode, setTransitionMode } from '../../utils/playback/playbackTransition';
|
import { getTransitionMode, setTransitionMode } from '../../utils/playback/playbackTransition';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
queue: QueueItemRef[];
|
queue: QueueItemRef[];
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
|
|||||||
import { useSelectionStore } from '@/store/selectionStore';
|
import { useSelectionStore } from '@/store/selectionStore';
|
||||||
import { useThemeStore } from '@/store/themeStore';
|
import { useThemeStore } from '@/store/themeStore';
|
||||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||||
import { useOrbitSongRowBehavior } from '@/hooks/useOrbitSongRowBehavior';
|
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||||
import { appendServerQuery } from '@/utils/navigation/detailServerScope';
|
import { appendServerQuery } from '@/utils/navigation/detailServerScope';
|
||||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { X, User } from 'lucide-react';
|
import { X, User } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore';
|
import { useOrbitAccountPickerStore } from '@/features/orbit/store/orbitAccountPickerStore';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modal shown when joining an Orbit session and the user has more than
|
* Modal shown when joining an Orbit session and the user has more than
|
||||||
+5
-5
@@ -2,16 +2,16 @@ import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Copy, Trash2 } from 'lucide-react';
|
import { Copy, Trash2 } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
import { computeOrbitDriftMs } from '../utils/orbit';
|
import { computeOrbitDriftMs } from '@/features/orbit/utils/orbit';
|
||||||
import {
|
import {
|
||||||
clearOrbitEvents,
|
clearOrbitEvents,
|
||||||
formatOrbitEvents,
|
formatOrbitEvents,
|
||||||
getOrbitEvents,
|
getOrbitEvents,
|
||||||
subscribeOrbitEvents,
|
subscribeOrbitEvents,
|
||||||
} from '../utils/orbitDiag';
|
} from '@/features/orbit/utils/orbitDiag';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
anchorRef: React.RefObject<HTMLElement | null>;
|
anchorRef: React.RefObject<HTMLElement | null>;
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { leaveOrbitSession } from '../utils/orbit';
|
import { leaveOrbitSession } from '@/features/orbit/utils/orbit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orbit — exit notification modal.
|
* Orbit — exit notification modal.
|
||||||
+5
-5
@@ -1,11 +1,11 @@
|
|||||||
import { getSong } from '../api/subsonicLibrary';
|
import { getSong } from '@/api/subsonicLibrary';
|
||||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Radio, Clock } from 'lucide-react';
|
import { Radio, Clock } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { TrackCoverArtImage } from '../cover/TrackCoverArtImage';
|
import { TrackCoverArtImage } from '@/cover/TrackCoverArtImage';
|
||||||
import OrbitQueueHead from './OrbitQueueHead';
|
import OrbitQueueHead from '@/features/orbit/components/OrbitQueueHead';
|
||||||
|
|
||||||
const ORBIT_QUEUE_COVER_LG_CSS_PX = 54;
|
const ORBIT_QUEUE_COVER_LG_CSS_PX = 54;
|
||||||
const ORBIT_QUEUE_COVER_SM_CSS_PX = 48;
|
const ORBIT_QUEUE_COVER_SM_CSS_PX = 48;
|
||||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
|||||||
ListMusic, Inbox, Sliders, LogOut,
|
ListMusic, Inbox, Sliders, LogOut,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHelpModalStore } from '../store/helpModalStore';
|
import { useHelpModalStore } from '@/store/helpModalStore';
|
||||||
import { SettingsSubSection } from '@/features/settings';
|
import { SettingsSubSection } from '@/features/settings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
+5
-5
@@ -2,16 +2,16 @@ import { useState } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { X, LogIn, ClipboardPaste } from 'lucide-react';
|
import { X, LogIn, ClipboardPaste } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import {
|
import {
|
||||||
parseOrbitShareLink,
|
parseOrbitShareLink,
|
||||||
findSessionPlaylistId,
|
findSessionPlaylistId,
|
||||||
readOrbitState,
|
readOrbitState,
|
||||||
joinOrbitSession,
|
joinOrbitSession,
|
||||||
} from '../utils/orbit';
|
} from '@/features/orbit/utils/orbit';
|
||||||
import { switchActiveServer } from '../utils/server/switchActiveServer';
|
import { switchActiveServer } from '@/utils/server/switchActiveServer';
|
||||||
import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore';
|
import { useOrbitAccountPickerStore } from '@/features/orbit/store/orbitAccountPickerStore';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
+3
-3
@@ -2,9 +2,9 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Crown, User, UserMinus, ShieldOff, Mic, MicOff } from 'lucide-react';
|
import { Crown, User, UserMinus, ShieldOff, Mic, MicOff } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { kickOrbitParticipant, removeOrbitParticipant, setOrbitSuggestionBlocked } from '../utils/orbit';
|
import { kickOrbitParticipant, removeOrbitParticipant, setOrbitSuggestionBlocked } from '@/features/orbit/utils/orbit';
|
||||||
import ConfirmModal from './ConfirmModal';
|
import ConfirmModal from '@/components/ConfirmModal';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** Anchor — we position the popover directly below its bottom-right. */
|
/** Anchor — we position the popover directly below its bottom-right. */
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Users, Wifi, WifiOff } from 'lucide-react';
|
import { Users, Wifi, WifiOff } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import type { OrbitState } from '../api/orbit';
|
import type { OrbitState } from '@/features/orbit/api/orbit';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
state: OrbitState;
|
state: OrbitState;
|
||||||
+14
-14
@@ -1,25 +1,25 @@
|
|||||||
import { getSong } from '../api/subsonicLibrary';
|
import { getSong } from '@/api/subsonicLibrary';
|
||||||
import { songToTrack } from '../utils/playback/songToTrack';
|
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react';
|
import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { useHelpModalStore } from '../store/helpModalStore';
|
import { useHelpModalStore } from '@/store/helpModalStore';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import {
|
import {
|
||||||
endOrbitSession,
|
endOrbitSession,
|
||||||
leaveOrbitSession,
|
leaveOrbitSession,
|
||||||
computeOrbitDriftMs,
|
computeOrbitDriftMs,
|
||||||
effectiveShuffleIntervalMs,
|
effectiveShuffleIntervalMs,
|
||||||
} from '../utils/orbit';
|
} from '@/features/orbit/utils/orbit';
|
||||||
import { estimateLivePosition } from '../api/orbit';
|
import { estimateLivePosition } from '@/features/orbit/api/orbit';
|
||||||
import OrbitParticipantsPopover from './OrbitParticipantsPopover';
|
import OrbitParticipantsPopover from '@/features/orbit/components/OrbitParticipantsPopover';
|
||||||
import OrbitExitModal from './OrbitExitModal';
|
import OrbitExitModal from '@/features/orbit/components/OrbitExitModal';
|
||||||
import OrbitSettingsPopover from './OrbitSettingsPopover';
|
import OrbitSettingsPopover from '@/features/orbit/components/OrbitSettingsPopover';
|
||||||
import OrbitSharePopover from './OrbitSharePopover';
|
import OrbitSharePopover from '@/features/orbit/components/OrbitSharePopover';
|
||||||
import OrbitDiagnosticsPopover from './OrbitDiagnosticsPopover';
|
import OrbitDiagnosticsPopover from '@/features/orbit/components/OrbitDiagnosticsPopover';
|
||||||
import ConfirmModal from './ConfirmModal';
|
import ConfirmModal from '@/components/ConfirmModal';
|
||||||
import { formatTrackTime } from '../utils/format/formatDuration';
|
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orbit — top-strip session indicator.
|
* Orbit — top-strip session indicator.
|
||||||
+4
-4
@@ -2,10 +2,10 @@ import { useEffect, useRef } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Shuffle } from 'lucide-react';
|
import { Shuffle } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { updateOrbitSettings, triggerOrbitShuffleNow } from '../utils/orbit';
|
import { updateOrbitSettings, triggerOrbitShuffleNow } from '@/features/orbit/utils/orbit';
|
||||||
import { ORBIT_DEFAULT_SETTINGS, ORBIT_SHUFFLE_INTERVAL_PRESETS_MIN, type OrbitShuffleIntervalMin } from '../api/orbit';
|
import { ORBIT_DEFAULT_SETTINGS, ORBIT_SHUFFLE_INTERVAL_PRESETS_MIN, type OrbitShuffleIntervalMin } from '@/features/orbit/api/orbit';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
anchorRef: React.RefObject<HTMLElement | null>;
|
anchorRef: React.RefObject<HTMLElement | null>;
|
||||||
+4
-4
@@ -2,10 +2,10 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Copy, Check } from 'lucide-react';
|
import { Copy, Check } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { buildOrbitShareLink } from '../utils/orbit';
|
import { buildOrbitShareLink } from '@/features/orbit/utils/orbit';
|
||||||
import { serverShareBaseUrl } from '../utils/server/serverEndpoint';
|
import { serverShareBaseUrl } from '@/utils/server/serverEndpoint';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
anchorRef: React.RefObject<HTMLElement | null>;
|
anchorRef: React.RefObject<HTMLElement | null>;
|
||||||
+6
-6
@@ -9,12 +9,12 @@ import {
|
|||||||
startOrbitSession,
|
startOrbitSession,
|
||||||
buildOrbitShareLink,
|
buildOrbitShareLink,
|
||||||
generateSessionId,
|
generateSessionId,
|
||||||
} from '../utils/orbit';
|
} from '@/features/orbit/utils/orbit';
|
||||||
import { randomOrbitSessionName } from '../utils/orbitNames';
|
import { randomOrbitSessionName } from '@/features/orbit/utils/orbitNames';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import { isLanUrl, serverShareBaseUrl } from '../utils/server/serverEndpoint';
|
import { isLanUrl, serverShareBaseUrl } from '@/utils/server/serverEndpoint';
|
||||||
import { ORBIT_DEFAULT_MAX_USERS } from '../api/orbit';
|
import { ORBIT_DEFAULT_MAX_USERS } from '@/features/orbit/api/orbit';
|
||||||
|
|
||||||
interface Props { onClose: () => void; }
|
interface Props { onClose: () => void; }
|
||||||
|
|
||||||
+6
-6
@@ -2,12 +2,12 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Orbit as OrbitIcon, Plus, LogIn, HelpCircle } from 'lucide-react';
|
import { Orbit as OrbitIcon, Plus, LogIn, HelpCircle } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useHelpModalStore } from '../store/helpModalStore';
|
import { useHelpModalStore } from '@/store/helpModalStore';
|
||||||
import OrbitStartModal from './OrbitStartModal';
|
import OrbitStartModal from '@/features/orbit/components/OrbitStartModal';
|
||||||
import OrbitJoinModal from './OrbitJoinModal';
|
import OrbitJoinModal from '@/features/orbit/components/OrbitJoinModal';
|
||||||
import OrbitWordmark from './OrbitWordmark';
|
import OrbitWordmark from '@/features/orbit/components/OrbitWordmark';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Topbar trigger — opens a small launch popover offering three choices:
|
* Topbar trigger — opens a small launch popover offering three choices:
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirror the live Orbit role + phase onto `<html data-orbit-active>` and
|
* Mirror the live Orbit role + phase onto `<html data-orbit-active>` and
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { getSong } from '../api/subsonicLibrary';
|
import { getSong } from '@/api/subsonicLibrary';
|
||||||
import { songToTrack } from '../utils/playback/songToTrack';
|
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import {
|
import {
|
||||||
readOrbitState,
|
readOrbitState,
|
||||||
applyOrbitTransitionSettings,
|
applyOrbitTransitionSettings,
|
||||||
@@ -13,12 +13,12 @@ import {
|
|||||||
planPendingResends,
|
planPendingResends,
|
||||||
forgetPendingSuggestion,
|
forgetPendingSuggestion,
|
||||||
resetPendingResendState,
|
resetPendingResendState,
|
||||||
} from '../utils/orbit';
|
} from '@/features/orbit/utils/orbit';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
import i18n from '../i18n';
|
import i18n from '@/i18n';
|
||||||
import { estimateLivePosition, type OrbitState } from '../api/orbit';
|
import { estimateLivePosition, type OrbitState } from '@/features/orbit/api/orbit';
|
||||||
import { pushOrbitEvent } from '../utils/orbitDiag';
|
import { pushOrbitEvent } from '@/features/orbit/utils/orbitDiag';
|
||||||
import { useOrbitOutboxHeartbeat } from './useOrbitOutboxHeartbeat';
|
import { useOrbitOutboxHeartbeat } from '@/features/orbit/hooks/useOrbitOutboxHeartbeat';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orbit — guest-side tick hook.
|
* Orbit — guest-side tick hook.
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getSong } from '../api/subsonicLibrary';
|
import { getSong } from '@/api/subsonicLibrary';
|
||||||
import { songToTrack } from '../utils/playback/songToTrack';
|
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import {
|
import {
|
||||||
writeOrbitState,
|
writeOrbitState,
|
||||||
sweepGuestOutboxes,
|
sweepGuestOutboxes,
|
||||||
@@ -12,17 +12,17 @@ import {
|
|||||||
makeCoalescedRunner,
|
makeCoalescedRunner,
|
||||||
readOrbitTransitionSettings,
|
readOrbitTransitionSettings,
|
||||||
suggestionKey,
|
suggestionKey,
|
||||||
} from '../utils/orbit';
|
} from '@/features/orbit/utils/orbit';
|
||||||
import {
|
import {
|
||||||
ORBIT_DEFAULT_SETTINGS,
|
ORBIT_DEFAULT_SETTINGS,
|
||||||
ORBIT_PLAY_QUEUE_LIMIT,
|
ORBIT_PLAY_QUEUE_LIMIT,
|
||||||
type OrbitState,
|
type OrbitState,
|
||||||
type OrbitQueueItem,
|
type OrbitQueueItem,
|
||||||
} from '../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
import i18n from '../i18n';
|
import i18n from '@/i18n';
|
||||||
import { pushOrbitEvent } from '../utils/orbitDiag';
|
import { pushOrbitEvent } from '@/features/orbit/utils/orbitDiag';
|
||||||
import { useOrbitOutboxHeartbeat } from './useOrbitOutboxHeartbeat';
|
import { useOrbitOutboxHeartbeat } from '@/features/orbit/hooks/useOrbitOutboxHeartbeat';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orbit — host-side tick hook.
|
* Orbit — host-side tick hook.
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { writeOrbitHeartbeat } from '../utils/orbit';
|
import { writeOrbitHeartbeat } from '@/features/orbit/utils/orbit';
|
||||||
import { orbitOutboxPlaylistName } from '../api/orbit';
|
import { orbitOutboxPlaylistName } from '@/features/orbit/api/orbit';
|
||||||
|
|
||||||
const HEARTBEAT_TICK_MS = 10_000;
|
const HEARTBEAT_TICK_MS = 10_000;
|
||||||
|
|
||||||
+3
-3
@@ -1,13 +1,13 @@
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import {
|
import {
|
||||||
suggestOrbitTrack,
|
suggestOrbitTrack,
|
||||||
hostEnqueueToOrbit,
|
hostEnqueueToOrbit,
|
||||||
evaluateOrbitSuggestGate,
|
evaluateOrbitSuggestGate,
|
||||||
OrbitSuggestBlockedError,
|
OrbitSuggestBlockedError,
|
||||||
} from '../utils/orbit';
|
} from '@/features/orbit/utils/orbit';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared behaviour for song rows that in "normal mode" swallow a full list
|
* Shared behaviour for song rows that in "normal mode" swallow a full list
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { usePlaybackRateStore } from '../store/playbackRateStore';
|
import { usePlaybackRateStore } from '@/store/playbackRateStore';
|
||||||
|
|
||||||
/** Re-sync playback rate when Orbit enters or leaves shared playback. */
|
/** Re-sync playback rate when Orbit enters or leaves shared playback. */
|
||||||
export function usePlaybackRateOrbitSync() {
|
export function usePlaybackRateOrbitSync() {
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Orbit feature — multi-user listen-together: the shared-session state types
|
||||||
|
* and Navidrome-playlist transport (`api/orbit`), host/guest lifecycle +
|
||||||
|
* moderation + outbox sweep + drift math (`utils/*`), the orbit stores, the
|
||||||
|
* session/account hooks, and all Orbit UI (session bar, modals, popovers,
|
||||||
|
* guest queue, wordmark). Replaces the former `utils/orbit.ts` re-export shim.
|
||||||
|
*
|
||||||
|
* Playback-core (`playTrackAction`, `nextAction`, `queueMutationActions`,
|
||||||
|
* `resumeAction`, `playbackRateStore`, `playbackReportSession`, `previewStore`)
|
||||||
|
* drives orbit and so consumes this barrel — the correct playback→orbit edge,
|
||||||
|
* realized early (playback moves last in M3). `nextActionOrbitRadio.test.ts`
|
||||||
|
* stays out: it tests `nextAction` (playback-core), not orbit.
|
||||||
|
*/
|
||||||
|
export * from './api/orbit';
|
||||||
|
export * from './hooks/useOrbitBodyAttrs';
|
||||||
|
export * from './hooks/useOrbitGuest';
|
||||||
|
export * from './hooks/useOrbitHost';
|
||||||
|
export * from './hooks/useOrbitOutboxHeartbeat';
|
||||||
|
export * from './hooks/useOrbitSongRowBehavior';
|
||||||
|
export * from './hooks/usePlaybackRateOrbitSync';
|
||||||
|
export * from './store/orbitAccountPickerStore';
|
||||||
|
export * from './store/orbitSession';
|
||||||
|
export * from './store/orbitStore';
|
||||||
|
export * from './utils/cleanup';
|
||||||
|
export * from './utils/constants';
|
||||||
|
export * from './utils/guest';
|
||||||
|
export * from './utils/helpers';
|
||||||
|
export * from './utils/host';
|
||||||
|
export * from './utils/moderation';
|
||||||
|
export * from './utils/orbitBulkGuard';
|
||||||
|
export * from './utils/orbitDiag';
|
||||||
|
export * from './utils/orbitNames';
|
||||||
|
export * from './utils/pendingResend';
|
||||||
|
export * from './utils/remote';
|
||||||
|
export * from './utils/sessionActive';
|
||||||
|
export * from './utils/shareLink';
|
||||||
|
export * from './utils/stateMath';
|
||||||
|
export * from './utils/sweep';
|
||||||
|
export * from './utils/transitions';
|
||||||
|
export { default as OrbitAccountPicker } from './components/OrbitAccountPicker';
|
||||||
|
export { default as OrbitDiagnosticsPopover } from './components/OrbitDiagnosticsPopover';
|
||||||
|
export { default as OrbitExitModal } from './components/OrbitExitModal';
|
||||||
|
export { default as OrbitGuestQueue } from './components/OrbitGuestQueue';
|
||||||
|
export { default as OrbitHelpModal } from './components/OrbitHelpModal';
|
||||||
|
export { default as OrbitJoinModal } from './components/OrbitJoinModal';
|
||||||
|
export { default as OrbitParticipantsPopover } from './components/OrbitParticipantsPopover';
|
||||||
|
export { default as OrbitQueueHead } from './components/OrbitQueueHead';
|
||||||
|
export { default as OrbitSessionBar } from './components/OrbitSessionBar';
|
||||||
|
export { default as OrbitSettingsPopover } from './components/OrbitSettingsPopover';
|
||||||
|
export { default as OrbitSharePopover } from './components/OrbitSharePopover';
|
||||||
|
export { default as OrbitStartModal } from './components/OrbitStartModal';
|
||||||
|
export { default as OrbitStartTrigger } from './components/OrbitStartTrigger';
|
||||||
|
export { default as OrbitWordmark } from './components/OrbitWordmark';
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import type { ServerProfile } from './authStoreTypes';
|
import type { ServerProfile } from '@/store/authStoreTypes';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
let _resolve: ((server: ServerProfile | null) => void) | null = null;
|
let _resolve: ((server: ServerProfile | null) => void) | null = null;
|
||||||
|
|
||||||
@@ -7,11 +7,11 @@ const { orbitState } = vi.hoisted(() => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./orbitStore', () => ({
|
vi.mock('@/features/orbit/store/orbitStore', () => ({
|
||||||
useOrbitStore: { getState: () => orbitState },
|
useOrbitStore: { getState: () => orbitState },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { isInOrbitSession } from './orbitSession';
|
import { isInOrbitSession } from '@/features/orbit/store/orbitSession';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
orbitState.role = null;
|
orbitState.role = null;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useOrbitStore } from './orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True when the user is part of an Orbit session (any role, any phase short
|
* True when the user is part of an Orbit session (any role, any phase short
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import type { OrbitState } from '../api/orbit';
|
import type { OrbitState } from '@/features/orbit/api/orbit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orbit — local session store.
|
* Orbit — local session store.
|
||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
orbitOutboxPlaylistName,
|
orbitOutboxPlaylistName,
|
||||||
orbitSessionPlaylistName,
|
orbitSessionPlaylistName,
|
||||||
type OrbitState,
|
type OrbitState,
|
||||||
} from '../../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
import { ORBIT_ORPHAN_TTL_MS } from './constants';
|
import { ORBIT_ORPHAN_TTL_MS } from '@/features/orbit/utils/constants';
|
||||||
|
|
||||||
const { getPlaylists, deletePlaylist } = vi.hoisted(() => ({
|
const { getPlaylists, deletePlaylist } = vi.hoisted(() => ({
|
||||||
getPlaylists: vi.fn(),
|
getPlaylists: vi.fn(),
|
||||||
@@ -19,19 +19,19 @@ const { authState, orbitState } = vi.hoisted(() => ({
|
|||||||
orbitState: { sessionId: null as string | null },
|
orbitState: { sessionId: null as string | null },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicPlaylists', () => ({ getPlaylists, deletePlaylist }));
|
vi.mock('@/api/subsonicPlaylists', () => ({ getPlaylists, deletePlaylist }));
|
||||||
vi.mock('../../store/authStore', () => ({
|
vi.mock('@/store/authStore', () => ({
|
||||||
useAuthStore: {
|
useAuthStore: {
|
||||||
getState: () => ({
|
getState: () => ({
|
||||||
getActiveServer: () => (authState.username ? { username: authState.username } : undefined),
|
getActiveServer: () => (authState.username ? { username: authState.username } : undefined),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
vi.mock('../../store/orbitStore', () => ({
|
vi.mock('@/features/orbit/store/orbitStore', () => ({
|
||||||
useOrbitStore: { getState: () => ({ sessionId: orbitState.sessionId }) },
|
useOrbitStore: { getState: () => ({ sessionId: orbitState.sessionId }) },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { cleanupOrphanedOrbitPlaylists } from './cleanup';
|
import { cleanupOrphanedOrbitPlaylists } from '@/features/orbit/utils/cleanup';
|
||||||
|
|
||||||
type FakePlaylist = {
|
type FakePlaylist = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { deletePlaylist, getPlaylists } from '../../api/subsonicPlaylists';
|
import { deletePlaylist, getPlaylists } from '@/api/subsonicPlaylists';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { ORBIT_PLAYLIST_PREFIX, parseOrbitState } from '../../api/orbit';
|
import { ORBIT_PLAYLIST_PREFIX, parseOrbitState } from '@/features/orbit/api/orbit';
|
||||||
import { ORBIT_ORPHAN_TTL_MS } from './constants';
|
import { ORBIT_ORPHAN_TTL_MS } from '@/features/orbit/utils/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-start sweep: delete our own __psyorbit_* playlists that no longer
|
* App-start sweep: delete our own __psyorbit_* playlists that no longer
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '../../api/subsonicPlaylists';
|
import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '@/api/subsonicPlaylists';
|
||||||
import { getSong } from '../../api/subsonicLibrary';
|
import { getSong } from '@/api/subsonicLibrary';
|
||||||
import { songToTrack } from '../playback/songToTrack';
|
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { usePlayerStore } from '../../store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import {
|
import {
|
||||||
orbitOutboxPlaylistName,
|
orbitOutboxPlaylistName,
|
||||||
type OrbitQueueItem,
|
type OrbitQueueItem,
|
||||||
type OrbitState,
|
type OrbitState,
|
||||||
} from '../../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
import { suggestionKey } from './helpers';
|
import { suggestionKey } from '@/features/orbit/utils/helpers';
|
||||||
import { notePendingSuggestion } from './pendingResend';
|
import { notePendingSuggestion } from '@/features/orbit/utils/pendingResend';
|
||||||
import { findSessionPlaylistId, readOrbitState, writeOrbitHeartbeat } from './remote';
|
import { findSessionPlaylistId, readOrbitState, writeOrbitHeartbeat } from '@/features/orbit/utils/remote';
|
||||||
|
|
||||||
export class OrbitJoinError extends Error {
|
export class OrbitJoinError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -6,13 +6,13 @@ import {
|
|||||||
parseOrbitState,
|
parseOrbitState,
|
||||||
type OrbitQueueItem,
|
type OrbitQueueItem,
|
||||||
type OrbitState,
|
type OrbitState,
|
||||||
} from '../../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
import {
|
import {
|
||||||
makeCoalescedRunner,
|
makeCoalescedRunner,
|
||||||
OrbitStateTooLarge,
|
OrbitStateTooLarge,
|
||||||
serialiseOrbitState,
|
serialiseOrbitState,
|
||||||
serialiseOrbitStateForWire,
|
serialiseOrbitStateForWire,
|
||||||
} from './helpers';
|
} from '@/features/orbit/utils/helpers';
|
||||||
|
|
||||||
function baseState(): OrbitState {
|
function baseState(): OrbitState {
|
||||||
return makeInitialOrbitState({ sid: 'aaaa1111', host: 'host', name: 'sesh' });
|
return makeInitialOrbitState({ sid: 'aaaa1111', host: 'host', name: 'sesh' });
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
type OrbitOutboxMeta,
|
type OrbitOutboxMeta,
|
||||||
type OrbitQueueItem,
|
type OrbitQueueItem,
|
||||||
type OrbitState,
|
type OrbitState,
|
||||||
} from '../../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
|
|
||||||
/** 8 lowercase hex chars — unique enough for concurrent-session collision-free naming. */
|
/** 8 lowercase hex chars — unique enough for concurrent-session collision-free naming. */
|
||||||
export function generateSessionId(): string {
|
export function generateSessionId(): string {
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
ORBIT_DEFAULT_SETTINGS,
|
ORBIT_DEFAULT_SETTINGS,
|
||||||
type OrbitSettings,
|
type OrbitSettings,
|
||||||
type OrbitState,
|
type OrbitState,
|
||||||
} from '../../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
|
|
||||||
const { writeOrbitState } = vi.hoisted(() => ({ writeOrbitState: vi.fn(() => Promise.resolve()) }));
|
const { writeOrbitState } = vi.hoisted(() => ({ writeOrbitState: vi.fn(() => Promise.resolve()) }));
|
||||||
const { orbitStore } = vi.hoisted(() => ({
|
const { orbitStore } = vi.hoisted(() => ({
|
||||||
@@ -17,18 +17,18 @@ const { orbitStore } = vi.hoisted(() => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./remote', () => ({
|
vi.mock('@/features/orbit/utils/remote', () => ({
|
||||||
writeOrbitState,
|
writeOrbitState,
|
||||||
writeOrbitHeartbeat: vi.fn(() => Promise.resolve()),
|
writeOrbitHeartbeat: vi.fn(() => Promise.resolve()),
|
||||||
}));
|
}));
|
||||||
vi.mock('../../store/orbitStore', () => ({ useOrbitStore: { getState: () => orbitStore } }));
|
vi.mock('@/features/orbit/store/orbitStore', () => ({ useOrbitStore: { getState: () => orbitStore } }));
|
||||||
vi.mock('../../store/authStore', () => ({ useAuthStore: { getState: () => ({}) } }));
|
vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => ({}) } }));
|
||||||
vi.mock('../../store/playerStore', () => ({ usePlayerStore: { getState: () => ({ enqueue: vi.fn() }) } }));
|
vi.mock('@/store/playerStore', () => ({ usePlayerStore: { getState: () => ({ enqueue: vi.fn() }) } }));
|
||||||
vi.mock('../../api/subsonicPlaylists', () => ({ createPlaylist: vi.fn(), deletePlaylist: vi.fn() }));
|
vi.mock('@/api/subsonicPlaylists', () => ({ createPlaylist: vi.fn(), deletePlaylist: vi.fn() }));
|
||||||
vi.mock('../../api/subsonicLibrary', () => ({ getSong: vi.fn() }));
|
vi.mock('@/api/subsonicLibrary', () => ({ getSong: vi.fn() }));
|
||||||
vi.mock('../playback/songToTrack', () => ({ songToTrack: vi.fn() }));
|
vi.mock('@/utils/playback/songToTrack', () => ({ songToTrack: vi.fn() }));
|
||||||
|
|
||||||
import { updateOrbitSettings } from './host';
|
import { updateOrbitSettings } from '@/features/orbit/utils/host';
|
||||||
|
|
||||||
function hostStateWith(settings: OrbitSettings | undefined): OrbitState {
|
function hostStateWith(settings: OrbitSettings | undefined): OrbitState {
|
||||||
const base = makeInitialOrbitState({ sid: 'aaaa1111', host: 'host', name: 'sesh' });
|
const base = makeInitialOrbitState({ sid: 'aaaa1111', host: 'host', name: 'sesh' });
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createPlaylist, deletePlaylist } from '../../api/subsonicPlaylists';
|
import { createPlaylist, deletePlaylist } from '@/api/subsonicPlaylists';
|
||||||
import { getSong } from '../../api/subsonicLibrary';
|
import { getSong } from '@/api/subsonicLibrary';
|
||||||
import { songToTrack } from '../playback/songToTrack';
|
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { usePlayerStore } from '../../store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import {
|
import {
|
||||||
makeInitialOrbitState,
|
makeInitialOrbitState,
|
||||||
orbitOutboxPlaylistName,
|
orbitOutboxPlaylistName,
|
||||||
@@ -13,10 +13,10 @@ import {
|
|||||||
type OrbitQueueItem,
|
type OrbitQueueItem,
|
||||||
type OrbitSettings,
|
type OrbitSettings,
|
||||||
type OrbitState,
|
type OrbitState,
|
||||||
} from '../../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
import { generateSessionId } from './helpers';
|
import { generateSessionId } from '@/features/orbit/utils/helpers';
|
||||||
import { readOrbitTransitionSettings } from './transitions';
|
import { readOrbitTransitionSettings } from '@/features/orbit/utils/transitions';
|
||||||
import { writeOrbitHeartbeat, writeOrbitState } from './remote';
|
import { writeOrbitHeartbeat, writeOrbitState } from '@/features/orbit/utils/remote';
|
||||||
|
|
||||||
export interface StartOrbitArgs {
|
export interface StartOrbitArgs {
|
||||||
/** Human-readable name the host chose. */
|
/** Human-readable name the host chose. */
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { deletePlaylist, getPlaylists } from '../../api/subsonicPlaylists';
|
import { deletePlaylist, getPlaylists } from '@/api/subsonicPlaylists';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { orbitOutboxPlaylistName, type OrbitState } from '../../api/orbit';
|
import { orbitOutboxPlaylistName, type OrbitState } from '@/features/orbit/api/orbit';
|
||||||
import { writeOrbitState } from './remote';
|
import { writeOrbitState } from '@/features/orbit/utils/remote';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Host: kick a participant by username.
|
* Host: kick a participant by username.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Orbit — multi-user listen-together feature.
|
* Orbit utils aggregator — re-exports every public symbol from the sibling
|
||||||
*
|
* `utils/*` modules under one specifier. Orbit's own components/hooks import
|
||||||
* This file is a re-export shim. Implementation lives under `utils/orbit/`:
|
* from here; the rest of the app goes through the feature barrel (`index.ts`).
|
||||||
*
|
*
|
||||||
* - `constants.ts` — cadence + TTL ms values.
|
* - `constants.ts` — cadence + TTL ms values.
|
||||||
* - `helpers.ts` — pure utilities (id gen, serialisation, key fns).
|
* - `helpers.ts` — pure utilities (id gen, serialisation, key fns).
|
||||||
@@ -14,9 +14,6 @@
|
|||||||
* approve/decline reactions.
|
* approve/decline reactions.
|
||||||
* - `sweep.ts` — host-side outbox sweep loop.
|
* - `sweep.ts` — host-side outbox sweep loop.
|
||||||
* - `cleanup.ts` — app-start orphan playlist sweep.
|
* - `cleanup.ts` — app-start orphan playlist sweep.
|
||||||
*
|
|
||||||
* Importers everywhere else in the codebase keep using `'../utils/orbit'`;
|
|
||||||
* this shim re-exports every public symbol unchanged.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -24,39 +21,39 @@ export {
|
|||||||
ORBIT_ORPHAN_TTL_MS,
|
ORBIT_ORPHAN_TTL_MS,
|
||||||
ORBIT_REMOVED_TTL_MS,
|
ORBIT_REMOVED_TTL_MS,
|
||||||
ORBIT_SHUFFLE_INTERVAL_MS,
|
ORBIT_SHUFFLE_INTERVAL_MS,
|
||||||
} from './orbit/constants';
|
} from './constants';
|
||||||
export {
|
export {
|
||||||
generateSessionId,
|
generateSessionId,
|
||||||
makeCoalescedRunner,
|
makeCoalescedRunner,
|
||||||
OrbitStateTooLarge,
|
OrbitStateTooLarge,
|
||||||
serialiseOrbitState,
|
serialiseOrbitState,
|
||||||
suggestionKey,
|
suggestionKey,
|
||||||
} from './orbit/helpers';
|
} from './helpers';
|
||||||
export { isOrbitPlaybackSyncActive } from './orbit/sessionActive';
|
export { isOrbitPlaybackSyncActive } from './sessionActive';
|
||||||
export {
|
export {
|
||||||
applyOutboxSnapshotsToState,
|
applyOutboxSnapshotsToState,
|
||||||
computeOrbitDriftMs,
|
computeOrbitDriftMs,
|
||||||
effectiveShuffleIntervalMs,
|
effectiveShuffleIntervalMs,
|
||||||
maybeShuffleQueue,
|
maybeShuffleQueue,
|
||||||
patchOrbitState,
|
patchOrbitState,
|
||||||
} from './orbit/stateMath';
|
} from './stateMath';
|
||||||
export {
|
export {
|
||||||
findSessionPlaylistId,
|
findSessionPlaylistId,
|
||||||
readOrbitState,
|
readOrbitState,
|
||||||
writeOrbitHeartbeat,
|
writeOrbitHeartbeat,
|
||||||
writeOrbitState,
|
writeOrbitState,
|
||||||
} from './orbit/remote';
|
} from './remote';
|
||||||
export {
|
export {
|
||||||
readOrbitTransitionSettings,
|
readOrbitTransitionSettings,
|
||||||
applyOrbitTransitionSettings,
|
applyOrbitTransitionSettings,
|
||||||
saveGuestTransitionsOnce,
|
saveGuestTransitionsOnce,
|
||||||
restoreGuestTransitions,
|
restoreGuestTransitions,
|
||||||
} from './orbit/transitions';
|
} from './transitions';
|
||||||
export {
|
export {
|
||||||
buildOrbitShareLink,
|
buildOrbitShareLink,
|
||||||
parseOrbitShareLink,
|
parseOrbitShareLink,
|
||||||
type OrbitShareLink,
|
type OrbitShareLink,
|
||||||
} from './orbit/shareLink';
|
} from './shareLink';
|
||||||
export {
|
export {
|
||||||
endOrbitSession,
|
endOrbitSession,
|
||||||
hostEnqueueToOrbit,
|
hostEnqueueToOrbit,
|
||||||
@@ -64,12 +61,12 @@ export {
|
|||||||
triggerOrbitShuffleNow,
|
triggerOrbitShuffleNow,
|
||||||
updateOrbitSettings,
|
updateOrbitSettings,
|
||||||
type StartOrbitArgs,
|
type StartOrbitArgs,
|
||||||
} from './orbit/host';
|
} from './host';
|
||||||
export {
|
export {
|
||||||
kickOrbitParticipant,
|
kickOrbitParticipant,
|
||||||
removeOrbitParticipant,
|
removeOrbitParticipant,
|
||||||
setOrbitSuggestionBlocked,
|
setOrbitSuggestionBlocked,
|
||||||
} from './orbit/moderation';
|
} from './moderation';
|
||||||
export {
|
export {
|
||||||
approveOrbitSuggestion,
|
approveOrbitSuggestion,
|
||||||
declineOrbitSuggestion,
|
declineOrbitSuggestion,
|
||||||
@@ -81,11 +78,11 @@ export {
|
|||||||
OrbitSuggestBlockedError,
|
OrbitSuggestBlockedError,
|
||||||
suggestOrbitTrack,
|
suggestOrbitTrack,
|
||||||
type OrbitSuggestGateReason,
|
type OrbitSuggestGateReason,
|
||||||
} from './orbit/guest';
|
} from './guest';
|
||||||
export {
|
export {
|
||||||
forgetPendingSuggestion,
|
forgetPendingSuggestion,
|
||||||
planPendingResends,
|
planPendingResends,
|
||||||
resetPendingResendState,
|
resetPendingResendState,
|
||||||
} from './orbit/pendingResend';
|
} from './pendingResend';
|
||||||
export { sweepGuestOutboxes } from './orbit/sweep';
|
export { sweepGuestOutboxes } from './sweep';
|
||||||
export { cleanupOrphanedOrbitPlaylists } from './orbit/cleanup';
|
export { cleanupOrphanedOrbitPlaylists } from './cleanup';
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import { useConfirmModalStore } from '../store/confirmModalStore';
|
import { useConfirmModalStore } from '@/store/confirmModalStore';
|
||||||
import i18n from '../i18n';
|
import i18n from '@/i18n';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ask the user before dropping many tracks into the shared Orbit queue.
|
* Ask the user before dropping many tracks into the shared Orbit queue.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
* for power users who want a persistent file.
|
* for power users who want a persistent file.
|
||||||
*/
|
*/
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
|
|
||||||
/** Hard cap so a long session doesn't spend memory unbounded. ~200 entries. */
|
/** Hard cap so a long session doesn't spend memory unbounded. ~200 entries. */
|
||||||
const MAX_EVENTS = 200;
|
const MAX_EVENTS = 200;
|
||||||
+1
-1
@@ -8,7 +8,7 @@ import {
|
|||||||
pendingResendTrackedCount,
|
pendingResendTrackedCount,
|
||||||
planPendingResends,
|
planPendingResends,
|
||||||
resetPendingResendState,
|
resetPendingResendState,
|
||||||
} from './pendingResend';
|
} from '@/features/orbit/utils/pendingResend';
|
||||||
|
|
||||||
const T0 = 1_000_000;
|
const T0 = 1_000_000;
|
||||||
const GRACE = ORBIT_SUGGESTION_RESEND_GRACE_MS;
|
const GRACE = ORBIT_SUGGESTION_RESEND_GRACE_MS;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { getPlaylist, getPlaylists, updatePlaylistMeta } from '../../api/subsonicPlaylists';
|
import { getPlaylist, getPlaylists, updatePlaylistMeta } from '@/api/subsonicPlaylists';
|
||||||
import {
|
import {
|
||||||
orbitSessionPlaylistName,
|
orbitSessionPlaylistName,
|
||||||
parseOrbitState,
|
parseOrbitState,
|
||||||
type OrbitOutboxMeta,
|
type OrbitOutboxMeta,
|
||||||
type OrbitState,
|
type OrbitState,
|
||||||
} from '../../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
import { serialiseOrbitStateForWire, serialiseOutboxMeta } from './helpers';
|
import { serialiseOrbitStateForWire, serialiseOutboxMeta } from '@/features/orbit/utils/helpers';
|
||||||
|
|
||||||
/** Pull + parse the canonical state from the session playlist. Null on miss or parse error. */
|
/** Pull + parse the canonical state from the session playlist. Null on miss or parse error. */
|
||||||
export async function readOrbitState(sessionPlaylistId: string): Promise<OrbitState | null> {
|
export async function readOrbitState(sessionPlaylistId: string): Promise<OrbitState | null> {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { OrbitPhase, OrbitRole } from '../../store/orbitStore';
|
import type { OrbitPhase, OrbitRole } from '@/features/orbit/store/orbitStore';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True while this client is in an Orbit session on the shared playback path
|
* True while this client is in an Orbit session on the shared playback path
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { buildOrbitShareLink, parseOrbitShareLink } from './shareLink';
|
import { buildOrbitShareLink, parseOrbitShareLink } from '@/features/orbit/utils/shareLink';
|
||||||
|
|
||||||
describe('parseOrbitShareLink', () => {
|
describe('parseOrbitShareLink', () => {
|
||||||
it('round-trips an https invite', () => {
|
it('round-trips an https invite', () => {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { decodeOrbitSharePayloadFromText, encodeSharePayload } from '../share/shareLink';
|
import { decodeOrbitSharePayloadFromText, encodeSharePayload } from '@/utils/share/shareLink';
|
||||||
|
|
||||||
export interface OrbitShareLink {
|
export interface OrbitShareLink {
|
||||||
/** Base URL of the Navidrome server (decoded). */
|
/** Base URL of the Navidrome server (decoded). */
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
vi.mock('../../store/orbitStore', () => ({
|
vi.mock('@/features/orbit/store/orbitStore', () => ({
|
||||||
useOrbitStore: { getState: () => ({ state: null, setState: vi.fn() }) },
|
useOrbitStore: { getState: () => ({ state: null, setState: vi.fn() }) },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { makeInitialOrbitState, type OrbitQueueItem, type OrbitState } from '../../api/orbit';
|
import { makeInitialOrbitState, type OrbitQueueItem, type OrbitState } from '@/features/orbit/api/orbit';
|
||||||
import { ORBIT_QUEUE_HISTORY_LIMIT } from './constants';
|
import { ORBIT_QUEUE_HISTORY_LIMIT } from '@/features/orbit/utils/constants';
|
||||||
import { applyOutboxSnapshotsToState, type OutboxSnapshot } from './stateMath';
|
import { applyOutboxSnapshotsToState, type OutboxSnapshot } from '@/features/orbit/utils/stateMath';
|
||||||
|
|
||||||
function stateWithQueue(queue: OrbitQueueItem[]): OrbitState {
|
function stateWithQueue(queue: OrbitQueueItem[]): OrbitState {
|
||||||
return { ...makeInitialOrbitState({ sid: 'aaaa1111', host: 'host', name: 'sesh' }), queue };
|
return { ...makeInitialOrbitState({ sid: 'aaaa1111', host: 'host', name: 'sesh' }), queue };
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||||
import type {
|
import type {
|
||||||
OrbitParticipant,
|
OrbitParticipant,
|
||||||
OrbitQueueItem,
|
OrbitQueueItem,
|
||||||
OrbitState,
|
OrbitState,
|
||||||
} from '../../api/orbit';
|
} from '@/features/orbit/api/orbit';
|
||||||
import {
|
import {
|
||||||
ORBIT_HEARTBEAT_ALIVE_MS,
|
ORBIT_HEARTBEAT_ALIVE_MS,
|
||||||
ORBIT_QUEUE_HISTORY_LIMIT,
|
ORBIT_QUEUE_HISTORY_LIMIT,
|
||||||
ORBIT_REMOVED_TTL_MS,
|
ORBIT_REMOVED_TTL_MS,
|
||||||
ORBIT_SHUFFLE_INTERVAL_MS,
|
ORBIT_SHUFFLE_INTERVAL_MS,
|
||||||
} from './constants';
|
} from '@/features/orbit/utils/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keep `OrbitState.queue` bounded. Drops the oldest suggestions (by `addedAt`)
|
* Keep `OrbitState.queue` bounded. Drops the oldest suggestions (by `addedAt`)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getPlaylist, getPlaylists, updatePlaylist } from '../../api/subsonicPlaylists';
|
import { getPlaylist, getPlaylists, updatePlaylist } from '@/api/subsonicPlaylists';
|
||||||
import { type OrbitOutboxMeta } from '../../api/orbit';
|
import { type OrbitOutboxMeta } from '@/features/orbit/api/orbit';
|
||||||
import { parseOutboxPlaylistName } from './helpers';
|
import { parseOutboxPlaylistName } from '@/features/orbit/utils/helpers';
|
||||||
import { type OutboxSnapshot } from './stateMath';
|
import { type OutboxSnapshot } from '@/features/orbit/utils/stateMath';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Host: list all guest outbox playlists for the current session.
|
* Host: list all guest outbox playlists for the current session.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import type { OrbitTransitionSettings } from '../../api/orbit';
|
import type { OrbitTransitionSettings } from '@/features/orbit/api/orbit';
|
||||||
|
|
||||||
const { store, setState } = vi.hoisted(() => {
|
const { store, setState } = vi.hoisted(() => {
|
||||||
const store = {
|
const store = {
|
||||||
@@ -20,7 +20,7 @@ const { store, setState } = vi.hoisted(() => {
|
|||||||
return { store, setState };
|
return { store, setState };
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../../store/authStore', () => ({
|
vi.mock('@/store/authStore', () => ({
|
||||||
useAuthStore: { getState: () => store.state, setState },
|
useAuthStore: { getState: () => store.state, setState },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
readOrbitTransitionSettings,
|
readOrbitTransitionSettings,
|
||||||
restoreGuestTransitions,
|
restoreGuestTransitions,
|
||||||
saveGuestTransitionsOnce,
|
saveGuestTransitionsOnce,
|
||||||
} from './transitions';
|
} from '@/features/orbit/utils/transitions';
|
||||||
|
|
||||||
const GUEST_OWN: OrbitTransitionSettings = {
|
const GUEST_OWN: OrbitTransitionSettings = {
|
||||||
crossfadeEnabled: false,
|
crossfadeEnabled: false,
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import type { OrbitTransitionSettings } from '../../api/orbit';
|
import type { OrbitTransitionSettings } from '@/features/orbit/api/orbit';
|
||||||
import {
|
import {
|
||||||
sanitizeAutodjOverlapCapMode,
|
sanitizeAutodjOverlapCapMode,
|
||||||
sanitizeAutodjOverlapCapSec,
|
sanitizeAutodjOverlapCapSec,
|
||||||
} from '../playback/autodjOverlapCap';
|
} from '@/utils/playback/autodjOverlapCap';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bridge between the local playback-transition settings (in `authStore`) and
|
* Bridge between the local playback-transition settings (in `authStore`) and
|
||||||
@@ -19,9 +19,9 @@ import {
|
|||||||
type PlaybackStrategy,
|
type PlaybackStrategy,
|
||||||
} from '@/utils/audio/playbackRateHelpers';
|
} from '@/utils/audio/playbackRateHelpers';
|
||||||
import { usePlaybackRateStore } from '@/store/playbackRateStore';
|
import { usePlaybackRateStore } from '@/store/playbackRateStore';
|
||||||
import { useOrbitStore } from '@/store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { isOrbitPlaybackSyncActive } from '@/utils/orbit';
|
import { isOrbitPlaybackSyncActive } from '@/features/orbit';
|
||||||
import { SettingsToggle } from '@/features/settings/components/SettingsToggle';
|
import { SettingsToggle } from '@/features/settings/components/SettingsToggle';
|
||||||
import { SettingsSubCard } from '@/features/settings/components/SettingsSubCard';
|
import { SettingsSubCard } from '@/features/settings/components/SettingsSubCard';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { TFunction } from 'i18next';
|
import type { TFunction } from 'i18next';
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOrbitStore } from '@/store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import {
|
import {
|
||||||
AUTODJ_OVERLAP_CAP_MAX_SEC,
|
AUTODJ_OVERLAP_CAP_MAX_SEC,
|
||||||
AUTODJ_OVERLAP_CAP_MIN_SEC,
|
AUTODJ_OVERLAP_CAP_MIN_SEC,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { playbackReportStopped } from '../../store/playbackReportSession';
|
|||||||
import { getPlaybackProgressSnapshot } from '../../store/playbackProgress';
|
import { getPlaybackProgressSnapshot } from '../../store/playbackProgress';
|
||||||
import { usePlayerStore } from '../../store/playerStore';
|
import { usePlayerStore } from '../../store/playerStore';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '../../store/authStore';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { endOrbitSession, leaveOrbitSession } from '../../utils/orbit';
|
import { endOrbitSession, leaveOrbitSession } from '@/features/orbit';
|
||||||
import {
|
import {
|
||||||
canRunShortcutActionInMiniWindow,
|
canRunShortcutActionInMiniWindow,
|
||||||
executeRuntimeAction,
|
executeRuntimeAction,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { applyServerPlayQueue } from '../store/applyServerPlayQueue';
|
import { applyServerPlayQueue } from '../store/applyServerPlayQueue';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
import {
|
import {
|
||||||
getPlaybackIdleSinceMs,
|
getPlaybackIdleSinceMs,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import type { ConnectionStatus } from '../hooks/useConnectionStatus';
|
import type { ConnectionStatus } from '../hooks/useConnectionStatus';
|
||||||
import { pullPlayQueueFromActiveServer } from '../store/applyServerPlayQueue';
|
import { pullPlayQueueFromActiveServer } from '../store/applyServerPlayQueue';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
import { getPlaybackServerId, queueIsMultiServer } from '../utils/playback/playbackServer';
|
import { getPlaybackServerId, queueIsMultiServer } from '../utils/playback/playbackServer';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
|||||||
import { getMusicFolders } from '../api/subsonicLibrary';
|
import { getMusicFolders } from '../api/subsonicLibrary';
|
||||||
import { probeEntityRatingSupport } from '../api/subsonicStarRating';
|
import { probeEntityRatingSupport } from '../api/subsonicStarRating';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { cleanupOrphanedOrbitPlaylists } from '../utils/orbit';
|
import { cleanupOrphanedOrbitPlaylists } from '@/features/orbit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-server one-shot probe run after login:
|
* Per-server one-shot probe run after login:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useParams, useSearchParams } from 'react-router-dom';
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior';
|
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||||
import { useAlbumDetailData } from '../hooks/useAlbumDetailData';
|
import { useAlbumDetailData } from '../hooks/useAlbumDetailData';
|
||||||
import { useAlbumOfflineState } from '../hooks/useAlbumOfflineState';
|
import { useAlbumOfflineState } from '../hooks/useAlbumOfflineState';
|
||||||
import { useAlbumDetailSort } from '../hooks/useAlbumDetailSort';
|
import { useAlbumDetailSort } from '../hooks/useAlbumDetailSort';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useAuthStore } from '../store/authStore';
|
|||||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useIsMobile } from '../hooks/useIsMobile';
|
import { useIsMobile } from '../hooks/useIsMobile';
|
||||||
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior';
|
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||||
import {
|
import {
|
||||||
fetchRandomMixSongsUntilFull,
|
fetchRandomMixSongsUntilFull,
|
||||||
getMixMinRatingsConfigFromAuth,
|
getMixMinRatingsConfigFromAuth,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
isInfiniteQueueFetching,
|
isInfiniteQueueFetching,
|
||||||
setInfiniteQueueFetching,
|
setInfiniteQueueFetching,
|
||||||
} from './infiniteQueueState';
|
} from './infiniteQueueState';
|
||||||
import { isInOrbitSession } from './orbitSession';
|
import { isInOrbitSession } from '@/features/orbit';
|
||||||
import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes';
|
import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes';
|
||||||
import { toQueueItemRefs } from '../utils/library/queueItemRef';
|
import { toQueueItemRefs } from '../utils/library/queueItemRef';
|
||||||
import { resolveQueueTrack } from '../utils/library/queueTrackView';
|
import { resolveQueueTrack } from '../utils/library/queueTrackView';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const { inOrbit, getSimilarSongs2, getTopSongs } = vi.hoisted(() => ({
|
|||||||
|
|
||||||
vi.mock('../api/subsonicArtists', () => ({ getSimilarSongs2, getTopSongs }));
|
vi.mock('../api/subsonicArtists', () => ({ getSimilarSongs2, getTopSongs }));
|
||||||
vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(() => Promise.resolve()) }));
|
vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(() => Promise.resolve()) }));
|
||||||
vi.mock('./orbitSession', () => ({ isInOrbitSession: () => inOrbit.value }));
|
vi.mock('@/features/orbit', () => ({ isInOrbitSession: () => inOrbit.value }));
|
||||||
vi.mock('./authStore', () => ({
|
vi.mock('./authStore', () => ({
|
||||||
useAuthStore: { getState: () => ({ infiniteQueueEnabled: false }) },
|
useAuthStore: { getState: () => ({ infiniteQueueEnabled: false }) },
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { playbackReportStart, playbackReportStopped } from './playbackReportSess
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { getMusicNetworkRuntimeOrNull } from '../music-network';
|
import { getMusicNetworkRuntimeOrNull } from '../music-network';
|
||||||
import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate';
|
import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate';
|
||||||
import { orbitBulkGuard } from '../utils/orbitBulkGuard';
|
import { orbitBulkGuard } from '@/features/orbit';
|
||||||
import { sameQueueTrackId } from '../utils/playback/queueIdentity';
|
import { sameQueueTrackId } from '../utils/playback/queueIdentity';
|
||||||
import {
|
import {
|
||||||
computeAutodjManualBlendPlan,
|
computeAutodjManualBlendPlan,
|
||||||
@@ -53,7 +53,7 @@ import {
|
|||||||
import { refreshLoudnessForTrack } from './loudnessRefresh';
|
import { refreshLoudnessForTrack } from './loudnessRefresh';
|
||||||
import { fetchWaveformBins, refreshWaveformForTrack } from '@/store/waveformRefresh';
|
import { fetchWaveformBins, refreshWaveformForTrack } from '@/store/waveformRefresh';
|
||||||
import { deriveNormalizationSnapshot } from './normalizationSnapshot';
|
import { deriveNormalizationSnapshot } from './normalizationSnapshot';
|
||||||
import { useOrbitStore } from './orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import {
|
import {
|
||||||
playbackSourceHintForResolvedUrl,
|
playbackSourceHintForResolvedUrl,
|
||||||
recordEnginePlayUrl,
|
recordEnginePlayUrl,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
shouldRestartPlaybackForRateChange,
|
shouldRestartPlaybackForRateChange,
|
||||||
type PlaybackRateSnapshot,
|
type PlaybackRateSnapshot,
|
||||||
} from '../utils/audio/playbackRateRestart';
|
} from '../utils/audio/playbackRateRestart';
|
||||||
import { isOrbitPlaybackSyncActive } from '../utils/orbit';
|
import { isOrbitPlaybackSyncActive } from '@/features/orbit';
|
||||||
|
|
||||||
interface PlaybackRateState extends PlaybackRateSnapshot {
|
interface PlaybackRateState extends PlaybackRateSnapshot {
|
||||||
/** UI-only: smaller slider steps (Advanced). Not sent to the engine. */
|
/** UI-only: smaller slider steps (Advanced). Not sent to the engine. */
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ vi.mock('./playbackRateStore', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
vi.mock('../utils/audio/playbackRateHelpers', () => ({ isPlaybackRateApplied: () => false }));
|
vi.mock('../utils/audio/playbackRateHelpers', () => ({ isPlaybackRateApplied: () => false }));
|
||||||
vi.mock('../utils/orbit', () => ({ isOrbitPlaybackSyncActive: () => false }));
|
vi.mock('@/features/orbit', () => ({ isOrbitPlaybackSyncActive: () => false }));
|
||||||
|
|
||||||
import { reportNowPlaying, reportPlayback } from '../api/subsonicScrobble';
|
import { reportNowPlaying, reportPlayback } from '../api/subsonicScrobble';
|
||||||
import { isFeatureActiveForServer } from '../serverCapabilities/storeView';
|
import { isFeatureActiveForServer } from '../serverCapabilities/storeView';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { PlaybackReportState } from '../api/subsonicTypes';
|
|||||||
import { FEATURE_PLAYBACK_REPORT } from '../serverCapabilities/catalog';
|
import { FEATURE_PLAYBACK_REPORT } from '../serverCapabilities/catalog';
|
||||||
import { isFeatureActiveForServer } from '../serverCapabilities/storeView';
|
import { isFeatureActiveForServer } from '../serverCapabilities/storeView';
|
||||||
import { isPlaybackRateApplied } from '../utils/audio/playbackRateHelpers';
|
import { isPlaybackRateApplied } from '../utils/audio/playbackRateHelpers';
|
||||||
import { isOrbitPlaybackSyncActive } from '../utils/orbit';
|
import { isOrbitPlaybackSyncActive } from '@/features/orbit';
|
||||||
import { useAuthStore } from './authStore';
|
import { useAuthStore } from './authStore';
|
||||||
import { getPlaybackProgressSnapshot } from './playbackProgress';
|
import { getPlaybackProgressSnapshot } from './playbackProgress';
|
||||||
import { usePlaybackRateStore } from './playbackRateStore';
|
import { usePlaybackRateStore } from './playbackRateStore';
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ vi.mock('@/music-network', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('@/utils/orbitBulkGuard', () => ({
|
vi.mock('@/features/orbit', () => ({
|
||||||
orbitBulkGuard: vi.fn(async () => true),
|
orbitBulkGuard: vi.fn(async () => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ vi.mock('@/music-network', () => ({
|
|||||||
getMusicNetworkRuntimeOrNull: () => runtimeMock,
|
getMusicNetworkRuntimeOrNull: () => runtimeMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@/utils/orbitBulkGuard', () => ({
|
vi.mock('@/features/orbit', () => ({
|
||||||
orbitBulkGuard: vi.fn(async () => true),
|
orbitBulkGuard: vi.fn(async () => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ vi.mock('@/music-network', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('@/utils/orbitBulkGuard', () => ({
|
vi.mock('@/features/orbit', () => ({
|
||||||
orbitBulkGuard: vi.fn(async () => true),
|
orbitBulkGuard: vi.fn(async () => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ vi.mock('@/api/subsonic', async () => {
|
|||||||
|
|
||||||
// `enqueue` / `enqueueAt` call `orbitBulkGuard` for multi-track inserts when
|
// `enqueue` / `enqueueAt` call `orbitBulkGuard` for multi-track inserts when
|
||||||
// the caller hasn't pre-confirmed. Force the guard to short-circuit through.
|
// the caller hasn't pre-confirmed. Force the guard to short-circuit through.
|
||||||
vi.mock('@/utils/orbitBulkGuard', () => ({
|
vi.mock('@/features/orbit', () => ({
|
||||||
orbitBulkGuard: vi.fn(async () => true),
|
orbitBulkGuard: vi.fn(async () => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ vi.mock('@/api/subsonic', () => ({
|
|||||||
|
|
||||||
import { usePreviewStore } from './previewStore';
|
import { usePreviewStore } from './previewStore';
|
||||||
import { useAuthStore } from './authStore';
|
import { useAuthStore } from './authStore';
|
||||||
import { useOrbitStore } from './orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { usePlayerStore } from './playerStore';
|
import { usePlayerStore } from './playerStore';
|
||||||
import './previewPlayerVolumeSync';
|
import './previewPlayerVolumeSync';
|
||||||
import { onInvoke, invokeMock } from '@/test/mocks/tauri';
|
import { onInvoke, invokeMock } from '@/test/mocks/tauri';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { create } from 'zustand';
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { usePlayerStore } from './playerStore';
|
import { usePlayerStore } from './playerStore';
|
||||||
import { useAuthStore } from './authStore';
|
import { useAuthStore } from './authStore';
|
||||||
import { isOrbitPlaybackSyncActive } from '../utils/orbit';
|
import { isOrbitPlaybackSyncActive } from '@/features/orbit';
|
||||||
|
|
||||||
/** Minimal track info needed to surface the preview in the player bar UI. */
|
/** Minimal track info needed to surface the preview in the player bar UI. */
|
||||||
export interface PreviewingTrack {
|
export interface PreviewingTrack {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { orbitBulkGuard } from '../utils/orbitBulkGuard';
|
import { orbitBulkGuard } from '@/features/orbit';
|
||||||
import { useAuthStore } from './authStore';
|
import { useAuthStore } from './authStore';
|
||||||
import { setIsAudioPaused } from './engineState';
|
import { setIsAudioPaused } from './engineState';
|
||||||
import { prefetchLoudnessForEnqueuedTracks } from './loudnessPrefetch';
|
import { prefetchLoudnessForEnqueuedTracks } from './loudnessPrefetch';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getSong } from '../api/subsonicLibrary';
|
import { getSong } from '../api/subsonicLibrary';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { estimateLivePosition } from '../api/orbit';
|
import { estimateLivePosition } from '@/features/orbit';
|
||||||
import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate';
|
import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate';
|
||||||
import {
|
import {
|
||||||
getPlaybackCacheServerKey,
|
getPlaybackCacheServerKey,
|
||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
isReplayGainActive,
|
isReplayGainActive,
|
||||||
loudnessGainDbForEngineBind,
|
loudnessGainDbForEngineBind,
|
||||||
} from './loudnessGainCache';
|
} from './loudnessGainCache';
|
||||||
import { useOrbitStore } from './orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import {
|
import {
|
||||||
playbackSourceHintForResolvedUrl,
|
playbackSourceHintForResolvedUrl,
|
||||||
recordEnginePlayUrl,
|
recordEnginePlayUrl,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { _resetTimelineSessionHistoryForTest } from '@/store/timelineSessionHist
|
|||||||
import { usePlayerStore } from '@/store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { usePreviewStore } from '@/store/previewStore';
|
import { usePreviewStore } from '@/store/previewStore';
|
||||||
import { useOrbitStore } from '@/store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
|
|
||||||
const INITIAL_PLAYER_STATE = usePlayerStore.getState();
|
const INITIAL_PLAYER_STATE = usePlayerStore.getState();
|
||||||
const INITIAL_AUTH_STATE = useAuthStore.getState();
|
const INITIAL_AUTH_STATE = useAuthStore.getState();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ vi.mock('./fadeOut', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
import { resolveAlbumForActiveServer } from '@/features/offline';
|
import { resolveAlbumForActiveServer } from '@/features/offline';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { usePlayerStore } from '../../store/playerStore';
|
import { usePlayerStore } from '../../store/playerStore';
|
||||||
import { playAlbum, playAlbumShuffled } from './playAlbum';
|
import { playAlbum, playAlbumShuffled } from './playAlbum';
|
||||||
import * as shuffleModule from './shuffleArray';
|
import * as shuffleModule from './shuffleArray';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { usePlayerStore } from '../../store/playerStore';
|
import { usePlayerStore } from '../../store/playerStore';
|
||||||
import { resolveAlbumForActiveServer } from '@/features/offline';
|
import { resolveAlbumForActiveServer } from '@/features/offline';
|
||||||
import { songToTrack } from './songToTrack';
|
import { songToTrack } from './songToTrack';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { fadeOut } from './fadeOut';
|
import { fadeOut } from './fadeOut';
|
||||||
import { shouldAutodjInterruptBlend } from './autodjManualBlend';
|
import { shouldAutodjInterruptBlend } from './autodjManualBlend';
|
||||||
import type { Track } from '../../store/playerStoreTypes';
|
import type { Track } from '../../store/playerStoreTypes';
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
coverTrafficEndServerSwitch,
|
coverTrafficEndServerSwitch,
|
||||||
} from '../../cover/coverTraffic';
|
} from '../../cover/coverTraffic';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '../../store/authStore';
|
||||||
import { useOrbitStore } from '../../store/orbitStore';
|
import { useOrbitStore } from '@/features/orbit';
|
||||||
import { flushPlayQueueForServer } from '../../store/queueSync';
|
import { flushPlayQueueForServer } from '../../store/queueSync';
|
||||||
import { markQueueHandoffPending } from '../../store/queueSyncUiState';
|
import { markQueueHandoffPending } from '../../store/queueSyncUiState';
|
||||||
import { endOrbitSession, leaveOrbitSession } from '../orbit';
|
import { endOrbitSession, leaveOrbitSession } from '@/features/orbit';
|
||||||
import { ensureConnectUrlResolved } from './serverEndpoint';
|
import { ensureConnectUrlResolved } from './serverEndpoint';
|
||||||
import { syncServerHttpContextForProfile } from './syncServerHttpContext';
|
import { syncServerHttpContextForProfile } from './syncServerHttpContext';
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ vi.mock('../playback/songToTrack', () => ({
|
|||||||
songToTrack: mocks.songToTrack,
|
songToTrack: mocks.songToTrack,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../orbitBulkGuard', () => ({
|
vi.mock('@/features/orbit', () => ({
|
||||||
orbitBulkGuard: mocks.orbitBulkGuard,
|
orbitBulkGuard: mocks.orbitBulkGuard,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { ServerProfile } from '../../store/authStoreTypes';
|
|||||||
import { usePlayerStore } from '../../store/playerStore';
|
import { usePlayerStore } from '../../store/playerStore';
|
||||||
import { songToTrack } from '../playback/songToTrack';
|
import { songToTrack } from '../playback/songToTrack';
|
||||||
import type { Track } from '../../store/playerStoreTypes';
|
import type { Track } from '../../store/playerStoreTypes';
|
||||||
import { orbitBulkGuard } from '../orbitBulkGuard';
|
import { orbitBulkGuard } from '@/features/orbit';
|
||||||
import { findServerIdForShareUrl } from './shareLink';
|
import { findServerIdForShareUrl } from './shareLink';
|
||||||
import { connectBaseUrlForServer } from '../server/serverEndpoint';
|
import { connectBaseUrlForServer } from '../server/serverEndpoint';
|
||||||
import { serverIndexKeyFromUrl } from '../server/serverIndexKey';
|
import { serverIndexKeyFromUrl } from '../server/serverIndexKey';
|
||||||
|
|||||||
Reference in New Issue
Block a user