mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 23:35:44 +00:00
refactor(deviceSync): co-locate device sync feature into features/deviceSync
This commit is contained in:
@@ -31,7 +31,7 @@ const Settings = lazy(() => import('../pages/Settings'));
|
|||||||
const Statistics = lazy(() => import('@/features/stats/pages/Statistics'));
|
const Statistics = lazy(() => import('@/features/stats/pages/Statistics'));
|
||||||
const Help = lazy(() => import('../pages/Help'));
|
const Help = lazy(() => import('../pages/Help'));
|
||||||
const WhatsNew = lazy(() => import('../pages/WhatsNew'));
|
const WhatsNew = lazy(() => import('../pages/WhatsNew'));
|
||||||
const DeviceSync = lazy(() => import('../pages/DeviceSync'));
|
const DeviceSync = lazy(() => import('@/features/deviceSync/pages/DeviceSync'));
|
||||||
const OfflineLibrary = lazy(() => import('../pages/OfflineLibrary'));
|
const OfflineLibrary = lazy(() => import('../pages/OfflineLibrary'));
|
||||||
const LabelAlbums = lazy(() => import('../pages/LabelAlbums'));
|
const LabelAlbums = lazy(() => import('../pages/LabelAlbums'));
|
||||||
const SearchBrowsePage = lazy(() => import('../pages/SearchBrowsePage'));
|
const SearchBrowsePage = lazy(() => import('../pages/SearchBrowsePage'));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom';
|
|||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
import { useOfflineJobStore } from '../store/offlineJobStore';
|
import { useOfflineJobStore } from '../store/offlineJobStore';
|
||||||
import { clearOfflinePinTasks } from '../utils/offline/offlinePinQueue';
|
import { clearOfflinePinTasks } from '../utils/offline/offlinePinQueue';
|
||||||
import { useDeviceSyncJobStore } from '../store/deviceSyncJobStore';
|
import { useDeviceSyncJobStore } from '@/features/deviceSync';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { useSidebarStore } from '../store/sidebarStore';
|
import { useSidebarStore } from '../store/sidebarStore';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|||||||
+4
-4
@@ -6,10 +6,10 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type {
|
import type {
|
||||||
SubsonicAlbum, SubsonicArtist, SubsonicPlaylist,
|
SubsonicAlbum, SubsonicArtist, SubsonicPlaylist,
|
||||||
} from '../../api/subsonicTypes';
|
} from '@/api/subsonicTypes';
|
||||||
import type { DeviceSyncSource } from '../../store/deviceSyncStore';
|
import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
import type { SourceTab } from '../../utils/deviceSync/deviceSyncHelpers';
|
import type { SourceTab } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
import BrowserRow from './BrowserRow';
|
import BrowserRow from '@/features/deviceSync/components/BrowserRow';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
activeTab: SourceTab;
|
activeTab: SourceTab;
|
||||||
+3
-3
@@ -5,9 +5,9 @@ import {
|
|||||||
AlertCircle, CheckCircle2, Clock, HardDriveUpload, Loader2,
|
AlertCircle, CheckCircle2, Clock, HardDriveUpload, Loader2,
|
||||||
Trash2, Undo2,
|
Trash2, Undo2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useDeviceSyncJobStore } from '../../store/deviceSyncJobStore';
|
import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJobStore';
|
||||||
import type { DeviceSyncSource } from '../../store/deviceSyncStore';
|
import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
import type { SyncStatus } from '../../utils/deviceSync/deviceSyncHelpers';
|
import type { SyncStatus } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sources: DeviceSyncSource[];
|
sources: DeviceSyncSource[];
|
||||||
+3
-3
@@ -4,9 +4,9 @@ import {
|
|||||||
AlertCircle, FolderOpen, HardDriveUpload, RefreshCw, Usb,
|
AlertCircle, FolderOpen, HardDriveUpload, RefreshCw, Usb,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import CustomSelect from '@/ui/CustomSelect';
|
import CustomSelect from '@/ui/CustomSelect';
|
||||||
import type { RemovableDrive } from '../../utils/deviceSync/deviceSyncHelpers';
|
import type { RemovableDrive } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
import { formatBytes } from '../../utils/deviceSync/deviceSyncHelpers';
|
import { formatBytes } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
import type { DeviceSyncSource } from '../../store/deviceSyncStore';
|
import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
targetDir: string | null;
|
targetDir: string | null;
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { AlertCircle, CheckCircle2, Loader2 } from 'lucide-react';
|
import { AlertCircle, CheckCircle2, Loader2 } from 'lucide-react';
|
||||||
import type {
|
import type {
|
||||||
MigrationPair, MigrationPhase, MigrationResult,
|
MigrationPair, MigrationPhase, MigrationResult,
|
||||||
} from '../../utils/deviceSync/runDeviceSyncMigration';
|
} from '@/features/deviceSync/utils/runDeviceSyncMigration';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
migrationPhase: MigrationPhase;
|
migrationPhase: MigrationPhase;
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AlertCircle, Loader2 } from 'lucide-react';
|
import { AlertCircle, Loader2 } from 'lucide-react';
|
||||||
import type { SyncDelta } from '../../utils/deviceSync/runDeviceSyncExecution';
|
import type { SyncDelta } from '@/features/deviceSync/utils/runDeviceSyncExecution';
|
||||||
import { formatMb } from '../../utils/format/formatBytes';
|
import { formatMb } from '@/utils/format/formatBytes';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
preSyncOpen: boolean;
|
preSyncOpen: boolean;
|
||||||
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { getPlaylists } from '../api/subsonicPlaylists';
|
import { getPlaylists } from '@/api/subsonicPlaylists';
|
||||||
import { getArtists, getArtist } from '../api/subsonicArtists';
|
import { getArtists, getArtist } from '@/api/subsonicArtists';
|
||||||
import { getAlbumList } from '../api/subsonicLibrary';
|
import { getAlbumList } from '@/api/subsonicLibrary';
|
||||||
import { search as searchSubsonic } from '../api/subsonicSearch';
|
import { search as searchSubsonic } from '@/api/subsonicSearch';
|
||||||
import type {
|
import type {
|
||||||
SubsonicAlbum, SubsonicArtist, SubsonicPlaylist,
|
SubsonicAlbum, SubsonicArtist, SubsonicPlaylist,
|
||||||
} from '../api/subsonicTypes';
|
} from '@/api/subsonicTypes';
|
||||||
import type { SourceTab } from '../utils/deviceSync/deviceSyncHelpers';
|
import type { SourceTab } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
|
|
||||||
export interface DeviceSyncBrowserResult {
|
export interface DeviceSyncBrowserResult {
|
||||||
playlists: SubsonicPlaylist[];
|
playlists: SubsonicPlaylist[];
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import type { TFunction } from 'i18next';
|
import type { TFunction } from 'i18next';
|
||||||
import { useDeviceSyncStore, type DeviceSyncSource } from '../store/deviceSyncStore';
|
import { useDeviceSyncStore, type DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
|
|
||||||
export interface DeviceSyncDeviceScanResult {
|
export interface DeviceSyncDeviceScanResult {
|
||||||
scanDevice: () => Promise<void>;
|
scanDevice: () => Promise<void>;
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import type { RemovableDrive } from '../utils/deviceSync/deviceSyncHelpers';
|
import type { RemovableDrive } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
|
|
||||||
export interface DeviceSyncDrivesResult {
|
export interface DeviceSyncDrivesResult {
|
||||||
drives: RemovableDrive[];
|
drives: RemovableDrive[];
|
||||||
+5
-5
@@ -2,11 +2,11 @@ import { useEffect } from 'react';
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import type { TFunction } from 'i18next';
|
import type { TFunction } from 'i18next';
|
||||||
import { useDeviceSyncJobStore } from '../store/deviceSyncJobStore';
|
import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJobStore';
|
||||||
import { useDeviceSyncStore } from '../store/deviceSyncStore';
|
import { useDeviceSyncStore } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
import { trackToSyncInfo } from '../utils/deviceSync/deviceSyncHelpers';
|
import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
import { fetchTracksForSource } from '../utils/playback/fetchTracksForSource';
|
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
|
||||||
|
|
||||||
export function useDeviceSyncJobEvents(
|
export function useDeviceSyncJobEvents(
|
||||||
t: TFunction,
|
t: TFunction,
|
||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { fetchTracksForSource } from '../utils/playback/fetchTracksForSource';
|
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
|
||||||
import { trackToSyncInfo, type SyncStatus } from '../utils/deviceSync/deviceSyncHelpers';
|
import { trackToSyncInfo, type SyncStatus } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
import type { DeviceSyncSource } from '../store/deviceSyncStore';
|
import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
|
|
||||||
export interface DeviceSyncSourceStatusesResult {
|
export interface DeviceSyncSourceStatusesResult {
|
||||||
sourcePathsMap: Map<string, string[]>;
|
sourcePathsMap: Map<string, string[]>;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Device Sync feature — copies/transcodes library tracks onto external devices
|
||||||
|
* (USB drives, players). The `DeviceSync` page is lazy-loaded by the router via
|
||||||
|
* its deep path, so it is intentionally not re-exported here.
|
||||||
|
*
|
||||||
|
* Note: `audioListenerSetup/eqDeviceSync` is NOT part of this feature — it is the
|
||||||
|
* audio-core EQ-per-output-device profile sync, kept in the audio core.
|
||||||
|
*/
|
||||||
|
export { useDeviceSyncJobStore } from './store/deviceSyncJobStore';
|
||||||
|
export type { DeviceSyncSource } from './store/deviceSyncStore';
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import React, { useState, useCallback, useMemo } from 'react';
|
import React, { useState, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDeviceSyncStore, DeviceSyncSource } from '../store/deviceSyncStore';
|
import { useDeviceSyncStore, DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
import { useDeviceSyncJobStore } from '../store/deviceSyncJobStore';
|
import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJobStore';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type SourceTab,
|
type SourceTab,
|
||||||
} from '../utils/deviceSync/deviceSyncHelpers';
|
} from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
import { useDeviceSyncDrives } from '../hooks/useDeviceSyncDrives';
|
import { useDeviceSyncDrives } from '@/features/deviceSync/hooks/useDeviceSyncDrives';
|
||||||
import { useDeviceSyncSourceStatuses } from '../hooks/useDeviceSyncSourceStatuses';
|
import { useDeviceSyncSourceStatuses } from '@/features/deviceSync/hooks/useDeviceSyncSourceStatuses';
|
||||||
import { useDeviceSyncBrowser } from '../hooks/useDeviceSyncBrowser';
|
import { useDeviceSyncBrowser } from '@/features/deviceSync/hooks/useDeviceSyncBrowser';
|
||||||
import { useDeviceSyncDeviceScan } from '../hooks/useDeviceSyncDeviceScan';
|
import { useDeviceSyncDeviceScan } from '@/features/deviceSync/hooks/useDeviceSyncDeviceScan';
|
||||||
import { useDeviceSyncJobEvents } from '../hooks/useDeviceSyncJobEvents';
|
import { useDeviceSyncJobEvents } from '@/features/deviceSync/hooks/useDeviceSyncJobEvents';
|
||||||
import {
|
import {
|
||||||
runDeviceSyncMigrationPreview,
|
runDeviceSyncMigrationPreview,
|
||||||
runDeviceSyncMigrationExecute,
|
runDeviceSyncMigrationExecute,
|
||||||
type MigrationPhase, type MigrationPair, type MigrationResult,
|
type MigrationPhase, type MigrationPair, type MigrationResult,
|
||||||
} from '../utils/deviceSync/runDeviceSyncMigration';
|
} from '@/features/deviceSync/utils/runDeviceSyncMigration';
|
||||||
import {
|
import {
|
||||||
runDeviceSyncSummaryPrompt,
|
runDeviceSyncSummaryPrompt,
|
||||||
runDeviceSyncExecute,
|
runDeviceSyncExecute,
|
||||||
type SyncDelta,
|
type SyncDelta,
|
||||||
} from '../utils/deviceSync/runDeviceSyncExecution';
|
} from '@/features/deviceSync/utils/runDeviceSyncExecution';
|
||||||
import { runDeviceSyncChooseFolder } from '../utils/deviceSync/runDeviceSyncChooseFolder';
|
import { runDeviceSyncChooseFolder } from '@/features/deviceSync/utils/runDeviceSyncChooseFolder';
|
||||||
import DeviceSyncHeader from '../components/deviceSync/DeviceSyncHeader';
|
import DeviceSyncHeader from '@/features/deviceSync/components/DeviceSyncHeader';
|
||||||
import DeviceSyncPreSyncModal from '../components/deviceSync/DeviceSyncPreSyncModal';
|
import DeviceSyncPreSyncModal from '@/features/deviceSync/components/DeviceSyncPreSyncModal';
|
||||||
import DeviceSyncMigrationModal from '../components/deviceSync/DeviceSyncMigrationModal';
|
import DeviceSyncMigrationModal from '@/features/deviceSync/components/DeviceSyncMigrationModal';
|
||||||
import DeviceSyncBrowserPanel from '../components/deviceSync/DeviceSyncBrowserPanel';
|
import DeviceSyncBrowserPanel from '@/features/deviceSync/components/DeviceSyncBrowserPanel';
|
||||||
import DeviceSyncDevicePanel from '../components/deviceSync/DeviceSyncDevicePanel';
|
import DeviceSyncDevicePanel from '@/features/deviceSync/components/DeviceSyncDevicePanel';
|
||||||
|
|
||||||
// ─── component ───────────────────────────────────────────────────────────────
|
// ─── component ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
|
|
||||||
export type SourceTab = 'playlists' | 'albums' | 'artists';
|
export type SourceTab = 'playlists' | 'albums' | 'artists';
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { IS_WINDOWS } from '../platform';
|
import { IS_WINDOWS } from '@/utils/platform';
|
||||||
|
|
||||||
// Same sanitize rules the Rust side uses (`sanitize_path_component`): strip
|
// Same sanitize rules the Rust side uses (`sanitize_path_component`): strip
|
||||||
// Windows-illegal chars and control chars, trim leading/trailing dots + spaces.
|
// Windows-illegal chars and control chars, trim leading/trailing dots + spaces.
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
||||||
import type { TFunction } from 'i18next';
|
import type { TFunction } from 'i18next';
|
||||||
import { useDeviceSyncStore, type DeviceSyncSource } from '../../store/deviceSyncStore';
|
import { useDeviceSyncStore, type DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
import { showToast } from '../ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
|
|
||||||
export interface RunDeviceSyncChooseFolderDeps {
|
export interface RunDeviceSyncChooseFolderDeps {
|
||||||
t: TFunction;
|
t: TFunction;
|
||||||
+8
-8
@@ -1,12 +1,12 @@
|
|||||||
import type { TFunction } from 'i18next';
|
import type { TFunction } from 'i18next';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { buildDownloadUrl } from '../../api/subsonicStreamUrl';
|
import { buildDownloadUrl } from '@/api/subsonicStreamUrl';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { useDeviceSyncStore, type DeviceSyncSource } from '../../store/deviceSyncStore';
|
import { useDeviceSyncStore, type DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
import { useDeviceSyncJobStore } from '../../store/deviceSyncJobStore';
|
import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJobStore';
|
||||||
import { showToast } from '../ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
import { trackToSyncInfo, uuid } from './deviceSyncHelpers';
|
import { trackToSyncInfo, uuid } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
import { fetchTracksForSource } from '../playback/fetchTracksForSource';
|
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
|
||||||
|
|
||||||
export interface SyncDelta {
|
export interface SyncDelta {
|
||||||
addBytes: number;
|
addBytes: number;
|
||||||
@@ -37,7 +37,7 @@ export async function runDeviceSyncSummaryPrompt(deps: RunDeviceSyncSummaryDeps)
|
|||||||
setPreSyncOpen(true);
|
setPreSyncOpen(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { getClient } = await import('../../api/subsonicClient');
|
const { getClient } = await import('@/api/subsonicClient');
|
||||||
const { baseUrl, params } = getClient();
|
const { baseUrl, params } = getClient();
|
||||||
const payload = await invoke<SyncDelta>('calculate_sync_payload', {
|
const payload = await invoke<SyncDelta>('calculate_sync_payload', {
|
||||||
sources,
|
sources,
|
||||||
+6
-6
@@ -1,11 +1,11 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import type { DeviceSyncSource } from '../../store/deviceSyncStore';
|
import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { applyLegacyTemplate } from './deviceSyncLegacyTemplate';
|
import { applyLegacyTemplate } from '@/features/deviceSync/utils/deviceSyncLegacyTemplate';
|
||||||
import { trackToSyncInfo } from './deviceSyncHelpers';
|
import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||||
import { fetchTracksForSource } from '../playback/fetchTracksForSource';
|
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
|
||||||
import { IS_WINDOWS } from '../platform';
|
import { IS_WINDOWS } from '@/utils/platform';
|
||||||
|
|
||||||
export type MigrationPhase = 'closed' | 'loading' | 'preview' | 'executing' | 'done' | 'nothing';
|
export type MigrationPhase = 'closed' | 'loading' | 'preview' | 'executing' | 'done' | 'nothing';
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||||
import type { DeviceSyncSource } from '../../store/deviceSyncStore';
|
import type { DeviceSyncSource } from '@/features/deviceSync';
|
||||||
import {
|
import {
|
||||||
resolveAlbum,
|
resolveAlbum,
|
||||||
resolveArtist,
|
resolveArtist,
|
||||||
|
|||||||
Reference in New Issue
Block a user