diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index 85f41e0b..3e840dba 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -27,7 +27,7 @@ const LuckyMixPage = lazy(() => import('../pages/LuckyMix')); const Playlists = lazy(() => import('../pages/Playlists')); const PlaylistDetail = lazy(() => import('../pages/PlaylistDetail')); const NowPlayingPage = lazy(() => import('@/features/nowPlaying/pages/NowPlaying')); -const Settings = lazy(() => import('../pages/Settings')); +const Settings = lazy(() => import('@/features/settings/pages/Settings')); const Statistics = lazy(() => import('@/features/stats/pages/Statistics')); const Help = lazy(() => import('../pages/Help')); const WhatsNew = lazy(() => import('../pages/WhatsNew')); diff --git a/src/components/OrbitHelpModal.tsx b/src/components/OrbitHelpModal.tsx index 5a84f216..1970e44c 100644 --- a/src/components/OrbitHelpModal.tsx +++ b/src/components/OrbitHelpModal.tsx @@ -6,7 +6,7 @@ import { } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useHelpModalStore } from '../store/helpModalStore'; -import SettingsSubSection from './SettingsSubSection'; +import { SettingsSubSection } from '@/features/settings'; /** * Orbit help modal. Rendered once at the app root; triggered from the diff --git a/src/components/playerBar/PlayerPlaybackRate.tsx b/src/components/playerBar/PlayerPlaybackRate.tsx index 6c80c418..0498f20d 100644 --- a/src/components/playerBar/PlayerPlaybackRate.tsx +++ b/src/components/playerBar/PlayerPlaybackRate.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; import { createPortal } from 'react-dom'; import type { TFunction } from 'i18next'; -import { PlaybackRateControls } from '../settings/audio/PlaybackRateBlock'; +import { PlaybackRateControls } from '@/features/settings'; import { usePlaybackRateStore } from '../../store/playbackRateStore'; import { useOrbitStore } from '../../store/orbitStore'; import { diff --git a/src/components/settings/AddServerForm.test.tsx b/src/features/settings/components/AddServerForm.test.tsx similarity index 98% rename from src/components/settings/AddServerForm.test.tsx rename to src/features/settings/components/AddServerForm.test.tsx index d9044bfd..4051e2af 100644 --- a/src/components/settings/AddServerForm.test.tsx +++ b/src/features/settings/components/AddServerForm.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '@/test/helpers/renderWithProviders'; -import { AddServerForm } from './AddServerForm'; +import { AddServerForm } from '@/features/settings/components/AddServerForm'; import { encodeServerMagicString } from '@/utils/server/serverMagicString'; // resolve_host_addresses Tauri command — hint-only, must not block save. diff --git a/src/components/settings/AddServerForm.tsx b/src/features/settings/components/AddServerForm.tsx similarity index 97% rename from src/components/settings/AddServerForm.tsx rename to src/features/settings/components/AddServerForm.tsx index 7d477c31..7be144cd 100644 --- a/src/components/settings/AddServerForm.tsx +++ b/src/features/settings/components/AddServerForm.tsx @@ -1,22 +1,22 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import type { CustomHeaderEntry, CustomHeadersApplyTo, ServerProfile } from '../../store/authStoreTypes'; -import { showToast } from '../../utils/ui/toast'; +import type { CustomHeaderEntry, CustomHeadersApplyTo, ServerProfile } from '@/store/authStoreTypes'; +import { showToast } from '@/utils/ui/toast'; import { DEFAULT_CUSTOM_HEADERS_APPLY_TO, serverCustomHeadersFromForm, validateCustomHeaders, -} from '../../utils/server/serverHttpHeaders'; -import { CustomHttpHeadersEditor } from './CustomHttpHeadersEditor'; +} from '@/utils/server/serverHttpHeaders'; +import { CustomHttpHeadersEditor } from '@/features/settings/components/CustomHttpHeadersEditor'; import { decodeServerMagicString, encodeServerMagicString, DECODED_PASSWORD_VISUAL_MASK, type ServerMagicPayload, -} from '../../utils/server/serverMagicString'; -import { shortHostFromServerUrl } from '../../utils/server/serverDisplayName'; -import { isLanUrl } from '../../utils/server/serverEndpoint'; -import { resolveHostAddresses } from '../../api/network'; +} from '@/utils/server/serverMagicString'; +import { shortHostFromServerUrl } from '@/utils/server/serverDisplayName'; +import { isLanUrl } from '@/utils/server/serverEndpoint'; +import { resolveHostAddresses } from '@/api/network'; type FormState = { name: string; diff --git a/src/components/settings/AnalyticsStrategySection.tsx b/src/features/settings/components/AnalyticsStrategySection.tsx similarity index 97% rename from src/components/settings/AnalyticsStrategySection.tsx rename to src/features/settings/components/AnalyticsStrategySection.tsx index 1bcdb767..646bb26f 100644 --- a/src/components/settings/AnalyticsStrategySection.tsx +++ b/src/features/settings/components/AnalyticsStrategySection.tsx @@ -4,11 +4,11 @@ import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { save as saveDialog } from '@tauri-apps/plugin-dialog'; import { writeFile } from '@tauri-apps/plugin-fs'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsSubCard } from './SettingsSubCard'; -import { useAnalysisStrategyStore } from '../../store/analysisStrategyStore'; -import { useAuthStore } from '../../store/authStore'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsSubCard } from '@/features/settings/components/SettingsSubCard'; +import { useAnalysisStrategyStore } from '@/store/analysisStrategyStore'; +import { useAuthStore } from '@/store/authStore'; import { analysisClearFailedTracks, analysisDeleteAllForServer, @@ -18,18 +18,18 @@ import { type AnalysisFailedTrackDto, libraryAnalysisProgress, type LibraryAnalysisProgressDto, -} from '../../api/analysis'; -import { libraryGetTracksBatch, type LibraryTrackDto, type TrackRefDto } from '../../api/library'; -import { buildStreamUrlForServer } from '../../api/subsonicStreamUrl'; -import { serverListDisplayLabel } from '../../utils/server/serverDisplayName'; -import { serverIndexKeyForProfile } from '../../utils/server/serverIndexKey'; -import { showToast } from '../../utils/ui/toast'; +} from '@/api/analysis'; +import { libraryGetTracksBatch, type LibraryTrackDto, type TrackRefDto } from '@/api/library'; +import { buildStreamUrlForServer } from '@/api/subsonicStreamUrl'; +import { serverListDisplayLabel } from '@/utils/server/serverDisplayName'; +import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; +import { showToast } from '@/utils/ui/toast'; import { ANALYTICS_STRATEGIES, ADVANCED_PARALLELISM_MAX, ADVANCED_PARALLELISM_MIN, type AnalyticsStrategy, -} from '../../utils/library/analysisStrategy'; +} from '@/utils/library/analysisStrategy'; type ClearTarget = { serverId: string; diff --git a/src/components/settings/AnimatedThemeBadge.tsx b/src/features/settings/components/AnimatedThemeBadge.tsx similarity index 100% rename from src/components/settings/AnimatedThemeBadge.tsx rename to src/features/settings/components/AnimatedThemeBadge.tsx diff --git a/src/components/settings/AppearanceTab.tsx b/src/features/settings/components/AppearanceTab.tsx similarity index 94% rename from src/components/settings/AppearanceTab.tsx rename to src/features/settings/components/AppearanceTab.tsx index 0c81da2f..40e60668 100644 --- a/src/components/settings/AppearanceTab.tsx +++ b/src/features/settings/components/AppearanceTab.tsx @@ -2,22 +2,22 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { invoke } from '@tauri-apps/api/core'; import { LayoutGrid, Maximize2, Palette, Sliders, Type, ZoomIn } from 'lucide-react'; -import { useAuthStore } from '../../store/authStore'; +import { useAuthStore } from '@/store/authStore'; import { LIBRARY_GRID_MAX_COLUMNS_MAX, LIBRARY_GRID_MAX_COLUMNS_MIN, -} from '../../store/authStoreDefaults'; -import type { SeekbarStyle, WindowButtonStyle } from '../../store/authStoreTypes'; -import { useFontStore, FontId } from '../../store/fontStore'; -import { useThemeStore } from '../../store/themeStore'; -import { IS_LINUX, IS_WINDOWS } from '../../utils/platform'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsToggle } from './SettingsToggle'; -import { SettingsSubCard, SettingsField, SettingsValue } from './SettingsSubCard'; -import { SettingsSegmented, type SegmentedOption } from './SettingsSegmented'; +} from '@/store/authStoreDefaults'; +import type { SeekbarStyle, WindowButtonStyle } from '@/store/authStoreTypes'; +import { useFontStore, FontId } from '@/store/fontStore'; +import { useThemeStore } from '@/store/themeStore'; +import { IS_LINUX, IS_WINDOWS } from '@/utils/platform'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSubCard, SettingsField, SettingsValue } from '@/features/settings/components/SettingsSubCard'; +import { SettingsSegmented, type SegmentedOption } from '@/features/settings/components/SettingsSegmented'; import { SeekbarPreview } from '@/features/waveform'; -import WindowButtonPreview from '../WindowButtonPreview'; +import WindowButtonPreview from '@/components/WindowButtonPreview'; export function AppearanceTab() { const { t } = useTranslation(); diff --git a/src/components/settings/ArtistLayoutCustomizer.tsx b/src/features/settings/components/ArtistLayoutCustomizer.tsx similarity index 91% rename from src/components/settings/ArtistLayoutCustomizer.tsx rename to src/features/settings/components/ArtistLayoutCustomizer.tsx index 718c34dc..e77c4c5d 100644 --- a/src/components/settings/ArtistLayoutCustomizer.tsx +++ b/src/features/settings/components/ArtistLayoutCustomizer.tsx @@ -1,9 +1,9 @@ import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { useArtistLayoutStore, type ArtistSectionConfig, type ArtistSectionId } from '../../store/artistLayoutStore'; -import { useListReorderDnd } from '../../hooks/useListReorderDnd'; -import { applyListReorderById, type ListReorderDropTarget } from '../../utils/componentHelpers/listReorder'; -import { ReorderGripHandle } from './ReorderGripHandle'; +import { useArtistLayoutStore, type ArtistSectionConfig, type ArtistSectionId } from '@/store/artistLayoutStore'; +import { useListReorderDnd } from '@/hooks/useListReorderDnd'; +import { applyListReorderById, type ListReorderDropTarget } from '@/utils/componentHelpers/listReorder'; +import { ReorderGripHandle } from '@/features/settings/components/ReorderGripHandle'; const ARTIST_SECTION_LABEL_KEYS: Record = { bio: 'settings.artistLayoutBio', diff --git a/src/components/settings/AudioTab.tsx b/src/features/settings/components/AudioTab.tsx similarity index 75% rename from src/components/settings/AudioTab.tsx rename to src/features/settings/components/AudioTab.tsx index cd4277a0..78dbe8b9 100644 --- a/src/components/settings/AudioTab.tsx +++ b/src/features/settings/components/AudioTab.tsx @@ -1,20 +1,20 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Blend, Gauge, Sliders, Volume2, Waves } from 'lucide-react'; -import { useAuthStore } from '../../store/authStore'; -import Equalizer from '../Equalizer'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsToggle } from './SettingsToggle'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider'; -import { useAudioDevicesProbe } from '../../hooks/useAudioDevicesProbe'; -import { IS_MACOS } from '../../utils/platform'; -import { AudioOutputDeviceSection } from './audio/AudioOutputDeviceSection'; -import { NormalizationBlock } from './audio/NormalizationBlock'; -import { PlaybackRateBlock } from './audio/PlaybackRateBlock'; -import { TrackTransitionsBlock } from './audio/TrackTransitionsBlock'; -import { TrackPreviewsSection } from './audio/TrackPreviewsSection'; -import { HiResCrossfadeResampleBlock } from './audio/HiResCrossfadeResampleBlock'; +import { useAuthStore } from '@/store/authStore'; +import Equalizer from '@/components/Equalizer'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider'; +import { useAudioDevicesProbe } from '@/hooks/useAudioDevicesProbe'; +import { IS_MACOS } from '@/utils/platform'; +import { AudioOutputDeviceSection } from '@/features/settings/components/audio/AudioOutputDeviceSection'; +import { NormalizationBlock } from '@/features/settings/components/audio/NormalizationBlock'; +import { PlaybackRateBlock } from '@/features/settings/components/audio/PlaybackRateBlock'; +import { TrackTransitionsBlock } from '@/features/settings/components/audio/TrackTransitionsBlock'; +import { TrackPreviewsSection } from '@/features/settings/components/audio/TrackPreviewsSection'; +import { HiResCrossfadeResampleBlock } from '@/features/settings/components/audio/HiResCrossfadeResampleBlock'; export function AudioTab() { const { t } = useTranslation(); diff --git a/src/components/settings/BackdropSourceList.tsx b/src/features/settings/components/BackdropSourceList.tsx similarity index 94% rename from src/components/settings/BackdropSourceList.tsx rename to src/features/settings/components/BackdropSourceList.tsx index ac007ee2..312624b0 100644 --- a/src/components/settings/BackdropSourceList.tsx +++ b/src/features/settings/components/BackdropSourceList.tsx @@ -1,9 +1,9 @@ import { useEffect, useRef, useState } from 'react'; import { ChevronDown, ChevronUp, GripVertical } from 'lucide-react'; -import { useDragDrop, useDragSource } from '../../contexts/DragDropContext'; -import type { BackdropSource, BackdropSourcePref } from '../../cover/artistBackdrop'; -import type { BackdropSurface } from '../../store/themeStore'; -import { moveSourceTo, dropSourceBefore } from './backdropReorder'; +import { useDragDrop, useDragSource } from '@/contexts/DragDropContext'; +import type { BackdropSource, BackdropSourcePref } from '@/cover/artistBackdrop'; +import type { BackdropSurface } from '@/store/themeStore'; +import { moveSourceTo, dropSourceBefore } from '@/features/settings/components/backdropReorder'; const DRAG_TYPE = 'backdrop-source'; diff --git a/src/components/settings/BackupSection.tsx b/src/features/settings/components/BackupSection.tsx similarity index 96% rename from src/components/settings/BackupSection.tsx rename to src/features/settings/components/BackupSection.tsx index 8fd2e806..297c1b7e 100644 --- a/src/components/settings/BackupSection.tsx +++ b/src/features/settings/components/BackupSection.tsx @@ -2,15 +2,15 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Clock3, Download, Upload } from 'lucide-react'; import { createPortal } from 'react-dom'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsSubCard, SettingsField } from './SettingsSubCard'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsSubCard, SettingsField } from '@/features/settings/components/SettingsSubCard'; import { exportBackupToPath, importAnyBackupFromPath, pickBackupExportPath, pickBackupImportPath, -} from '../../utils/export/backup'; -import { showToast } from '../../utils/ui/toast'; +} from '@/features/settings/utils/backup'; +import { showToast } from '@/utils/ui/toast'; type BackupMode = 'full' | 'library' | 'config'; type BackupAction = 'export' | 'import'; diff --git a/src/components/settings/CoverCacheStrategySection.tsx b/src/features/settings/components/CoverCacheStrategySection.tsx similarity index 94% rename from src/components/settings/CoverCacheStrategySection.tsx rename to src/features/settings/components/CoverCacheStrategySection.tsx index c5527716..bea3adab 100644 --- a/src/components/settings/CoverCacheStrategySection.tsx +++ b/src/features/settings/components/CoverCacheStrategySection.tsx @@ -2,28 +2,28 @@ import { Image, Trash2 } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState, type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; import { listen } from '@tauri-apps/api/event'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsSubCard } from './SettingsSubCard'; -import { useCoverStrategyStore } from '../../store/coverStrategyStore'; -import { useAuthStore } from '../../store/authStore'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsSubCard } from '@/features/settings/components/SettingsSubCard'; +import { useCoverStrategyStore } from '@/store/coverStrategyStore'; +import { useAuthStore } from '@/store/authStore'; import { coverCacheClearServer, coverCacheStatsServer, libraryCoverCatalogSize, libraryCoverProgress, -} from '../../api/coverCache'; -import { clearDiskSrcCacheForServer } from '../../cover/diskSrcCache'; -import { serverListDisplayLabel } from '../../utils/server/serverDisplayName'; -import { serverIndexKeyForProfile } from '../../utils/server/serverIndexKey'; -import { showToast } from '../../utils/ui/toast'; -import { formatBytes } from '../../utils/format/formatBytes'; -import { clearImageCache, getImageCacheSize } from '../../utils/imageCache'; -import { wakeLibraryCoverBackfill } from '../../utils/library/coverBackfillWake'; +} from '@/api/coverCache'; +import { clearDiskSrcCacheForServer } from '@/cover/diskSrcCache'; +import { serverListDisplayLabel } from '@/utils/server/serverDisplayName'; +import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; +import { showToast } from '@/utils/ui/toast'; +import { formatBytes } from '@/utils/format/formatBytes'; +import { clearImageCache, getImageCacheSize } from '@/utils/imageCache'; +import { wakeLibraryCoverBackfill } from '@/utils/library/coverBackfillWake'; import { COVER_CACHE_STRATEGIES, type CoverCacheStrategy, -} from '../../utils/library/coverStrategy'; +} from '@/utils/library/coverStrategy'; type ClearTarget = | { kind: 'image' } diff --git a/src/components/settings/CustomHttpHeadersEditor.tsx b/src/features/settings/components/CustomHttpHeadersEditor.tsx similarity index 97% rename from src/components/settings/CustomHttpHeadersEditor.tsx rename to src/features/settings/components/CustomHttpHeadersEditor.tsx index 9a1c79bb..c07940f4 100644 --- a/src/components/settings/CustomHttpHeadersEditor.tsx +++ b/src/features/settings/components/CustomHttpHeadersEditor.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import type { CustomHeaderEntry, CustomHeadersApplyTo } from '../../store/authStoreTypes'; +import type { CustomHeaderEntry, CustomHeadersApplyTo } from '@/store/authStoreTypes'; export type CustomHttpHeadersEditorProps = { headers: CustomHeaderEntry[]; diff --git a/src/components/settings/HomeCustomizer.tsx b/src/features/settings/components/HomeCustomizer.tsx similarity index 94% rename from src/components/settings/HomeCustomizer.tsx rename to src/features/settings/components/HomeCustomizer.tsx index 780560de..80208ac5 100644 --- a/src/components/settings/HomeCustomizer.tsx +++ b/src/features/settings/components/HomeCustomizer.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next'; -import { useHomeStore, HomeSectionId } from '../../store/homeStore'; +import { useHomeStore, HomeSectionId } from '@/store/homeStore'; export function HomeCustomizer() { const { t } = useTranslation(); diff --git a/src/components/settings/InputTab.tsx b/src/features/settings/components/InputTab.tsx similarity index 96% rename from src/components/settings/InputTab.tsx rename to src/features/settings/components/InputTab.tsx index 113ed9e2..95183664 100644 --- a/src/components/settings/InputTab.tsx +++ b/src/features/settings/components/InputTab.tsx @@ -1,12 +1,12 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Keyboard, RotateCcw, X } from 'lucide-react'; -import { IN_APP_SHORTCUT_ACTIONS, GLOBAL_SHORTCUT_ACTIONS } from '../../config/shortcutActions'; -import { useGlobalShortcutsStore, type GlobalAction, buildGlobalShortcut, formatGlobalShortcut } from '../../store/globalShortcutsStore'; -import { useKeybindingsStore, type KeyAction, buildInAppBinding, formatBinding } from '../../store/keybindingsStore'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsSubCard } from './SettingsSubCard'; +import { IN_APP_SHORTCUT_ACTIONS, GLOBAL_SHORTCUT_ACTIONS } from '@/config/shortcutActions'; +import { useGlobalShortcutsStore, type GlobalAction, buildGlobalShortcut, formatGlobalShortcut } from '@/store/globalShortcutsStore'; +import { useKeybindingsStore, type KeyAction, buildInAppBinding, formatBinding } from '@/store/keybindingsStore'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsSubCard } from '@/features/settings/components/SettingsSubCard'; export function InputTab() { const { t } = useTranslation(); diff --git a/src/components/settings/InstalledThemes.tsx b/src/features/settings/components/InstalledThemes.tsx similarity index 92% rename from src/components/settings/InstalledThemes.tsx rename to src/features/settings/components/InstalledThemes.tsx index dd56574e..15be6891 100644 --- a/src/components/settings/InstalledThemes.tsx +++ b/src/features/settings/components/InstalledThemes.tsx @@ -1,15 +1,15 @@ import { useMemo, useState } from 'react'; import { Check, RefreshCw, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { useThemeStore } from '../../store/themeStore'; -import { useInstalledThemesStore } from '../../store/installedThemesStore'; -import { uninstallTheme } from '../../utils/themes/uninstallTheme'; -import { installThemeFromRegistry } from '../../utils/themes/installThemeFromRegistry'; -import { useThemeUpdates } from '../../hooks/useThemeUpdates'; -import { useThemeAnimationRisk } from '../../hooks/useThemeAnimationRisk'; -import { showToast } from '../../utils/ui/toast'; -import { AnimatedThemeBadge } from './AnimatedThemeBadge'; -import { FIXED_THEMES } from './fixedThemes'; +import { useThemeStore } from '@/store/themeStore'; +import { useInstalledThemesStore } from '@/store/installedThemesStore'; +import { uninstallTheme } from '@/utils/themes/uninstallTheme'; +import { installThemeFromRegistry } from '@/utils/themes/installThemeFromRegistry'; +import { useThemeUpdates } from '@/hooks/useThemeUpdates'; +import { useThemeAnimationRisk } from '@/hooks/useThemeAnimationRisk'; +import { showToast } from '@/utils/ui/toast'; +import { AnimatedThemeBadge } from '@/features/settings/components/AnimatedThemeBadge'; +import { FIXED_THEMES } from '@/components/settings/fixedThemes'; /** Pull a 3-band swatch (bg / card / accent) out of an installed theme's CSS. */ function swatch(css: string): { bg: string; card: string; accent: string } { diff --git a/src/components/settings/IntegrationsTab.tsx b/src/features/settings/components/IntegrationsTab.tsx similarity index 92% rename from src/components/settings/IntegrationsTab.tsx rename to src/features/settings/components/IntegrationsTab.tsx index 62da8aba..65e2ef39 100644 --- a/src/components/settings/IntegrationsTab.tsx +++ b/src/features/settings/components/IntegrationsTab.tsx @@ -1,16 +1,16 @@ import { useTranslation } from 'react-i18next'; import { AlertTriangle, Check, Image as ImageIcon, Info, Sparkles, Wifi } from 'lucide-react'; -import { useAuthStore } from '../../store/authStore'; -import { useThemeStore } from '../../store/themeStore'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsToggle } from './SettingsToggle'; -import { SettingsSubCard, SettingsField } from './SettingsSubCard'; -import { BackdropSourceList } from './BackdropSourceList'; -import type { BackdropSurface } from '../../store/themeStore'; -import type { BackdropSource } from '../../cover/artistBackdrop'; -import { MusicNetworkSection } from './musicNetwork/MusicNetworkSection'; -import { purgeExternalArtworkAllServers } from '../../api/coverCache'; +import { useAuthStore } from '@/store/authStore'; +import { useThemeStore } from '@/store/themeStore'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSubCard, SettingsField } from '@/features/settings/components/SettingsSubCard'; +import { BackdropSourceList } from '@/features/settings/components/BackdropSourceList'; +import type { BackdropSurface } from '@/store/themeStore'; +import type { BackdropSource } from '@/cover/artistBackdrop'; +import { MusicNetworkSection } from '@/features/settings/components/musicNetwork/MusicNetworkSection'; +import { purgeExternalArtworkAllServers } from '@/api/coverCache'; export function IntegrationsTab() { const { t } = useTranslation(); diff --git a/src/components/settings/LibraryTab.tsx b/src/features/settings/components/LibraryTab.tsx similarity index 93% rename from src/components/settings/LibraryTab.tsx rename to src/features/settings/components/LibraryTab.tsx index d28f6d2c..9d543b80 100644 --- a/src/components/settings/LibraryTab.tsx +++ b/src/features/settings/components/LibraryTab.tsx @@ -1,14 +1,14 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Shuffle, Star } from 'lucide-react'; -import { useAuthStore } from '../../store/authStore'; -import { MIX_MIN_RATING_FILTER_MAX_STARS } from '../../store/authStoreDefaults'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsToggle } from './SettingsToggle'; -import { SettingsSubCard, SettingsField } from './SettingsSubCard'; -import StarRating from '../StarRating'; -import AnalyticsStrategySection from './AnalyticsStrategySection'; +import { useAuthStore } from '@/store/authStore'; +import { MIX_MIN_RATING_FILTER_MAX_STARS } from '@/store/authStoreDefaults'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSubCard, SettingsField } from '@/features/settings/components/SettingsSubCard'; +import StarRating from '@/components/StarRating'; +import AnalyticsStrategySection from '@/features/settings/components/AnalyticsStrategySection'; const AUDIOBOOK_GENRES_DISPLAY = ['Hörbuch', 'Hoerbuch', 'Hörspiel', 'Hoerspiel', 'Audiobook', 'Audio Book', 'Spoken Word', 'Spokenword', 'Podcast', 'Kapitel', 'Thriller', 'Krimi', 'Speech', 'Fantasy', 'Comedy', 'Literature']; export function LibraryTab() { diff --git a/src/components/settings/LoudnessLufsButtonGroup.tsx b/src/features/settings/components/LoudnessLufsButtonGroup.tsx similarity index 90% rename from src/components/settings/LoudnessLufsButtonGroup.tsx rename to src/features/settings/components/LoudnessLufsButtonGroup.tsx index ea918ba1..6f39f512 100644 --- a/src/components/settings/LoudnessLufsButtonGroup.tsx +++ b/src/features/settings/components/LoudnessLufsButtonGroup.tsx @@ -1,4 +1,4 @@ -import type { LoudnessLufsPreset } from '../../store/authStoreTypes'; +import type { LoudnessLufsPreset } from '@/store/authStoreTypes'; const LOUDNESS_LUFS_BUTTON_ORDER: LoudnessLufsPreset[] = [-10, -12, -14, -16]; diff --git a/src/components/settings/LyricsSourcesCustomizer.tsx b/src/features/settings/components/LyricsSourcesCustomizer.tsx similarity index 91% rename from src/components/settings/LyricsSourcesCustomizer.tsx rename to src/features/settings/components/LyricsSourcesCustomizer.tsx index 2145bb8e..33b357bc 100644 --- a/src/components/settings/LyricsSourcesCustomizer.tsx +++ b/src/features/settings/components/LyricsSourcesCustomizer.tsx @@ -1,12 +1,12 @@ import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useShallow } from 'zustand/react/shallow'; -import { useAuthStore } from '../../store/authStore'; -import type { LyricsSourceId } from '../../store/authStoreTypes'; -import { useListReorderDnd } from '../../hooks/useListReorderDnd'; -import { applyListReorderById, type ListReorderDropTarget } from '../../utils/componentHelpers/listReorder'; -import { ReorderGripHandle } from './ReorderGripHandle'; -import { SettingsToggle } from './SettingsToggle'; +import { useAuthStore } from '@/store/authStore'; +import type { LyricsSourceId } from '@/store/authStoreTypes'; +import { useListReorderDnd } from '@/hooks/useListReorderDnd'; +import { applyListReorderById, type ListReorderDropTarget } from '@/utils/componentHelpers/listReorder'; +import { ReorderGripHandle } from '@/features/settings/components/ReorderGripHandle'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; const LYRICS_SOURCE_LABEL_KEYS: Record = { server: 'settings.lyricsSourceServer', diff --git a/src/components/settings/LyricsTab.tsx b/src/features/settings/components/LyricsTab.tsx similarity index 74% rename from src/components/settings/LyricsTab.tsx rename to src/features/settings/components/LyricsTab.tsx index 28d13c78..d7ba9883 100644 --- a/src/components/settings/LyricsTab.tsx +++ b/src/features/settings/components/LyricsTab.tsx @@ -1,11 +1,11 @@ import { useTranslation } from 'react-i18next'; import { AudioLines, Music2 } from 'lucide-react'; -import { useAuthStore } from '../../store/authStore'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { LyricsSourcesCustomizer } from './LyricsSourcesCustomizer'; -import { SettingsSegmented, type SegmentedOption } from './SettingsSegmented'; -import { SettingsSubCard, SettingsField } from './SettingsSubCard'; +import { useAuthStore } from '@/store/authStore'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { LyricsSourcesCustomizer } from '@/features/settings/components/LyricsSourcesCustomizer'; +import { SettingsSegmented, type SegmentedOption } from '@/features/settings/components/SettingsSegmented'; +import { SettingsSubCard, SettingsField } from '@/features/settings/components/SettingsSubCard'; export function LyricsTab() { const { t } = useTranslation(); diff --git a/src/components/settings/PersonalisationTab.tsx b/src/features/settings/components/PersonalisationTab.tsx similarity index 81% rename from src/components/settings/PersonalisationTab.tsx rename to src/features/settings/components/PersonalisationTab.tsx index c7864f21..fd850345 100644 --- a/src/components/settings/PersonalisationTab.tsx +++ b/src/features/settings/components/PersonalisationTab.tsx @@ -1,24 +1,24 @@ import { useTranslation } from 'react-i18next'; import { Disc3, LayoutGrid, ListOrdered, ListTodo, PanelLeft, RotateCcw, Users } from 'lucide-react'; -import { useArtistLayoutStore } from '../../store/artistLayoutStore'; -import { useAuthStore } from '../../store/authStore'; -import type { QueueDisplayMode } from '../../store/authStoreTypes'; -import { useHomeStore } from '../../store/homeStore'; -import { usePlayerBarLayoutStore } from '../../store/playerBarLayoutStore'; -import { usePlaylistLayoutStore } from '../../store/playlistLayoutStore'; -import { useQueueToolbarStore } from '../../store/queueToolbarStore'; +import { useArtistLayoutStore } from '@/store/artistLayoutStore'; +import { useAuthStore } from '@/store/authStore'; +import type { QueueDisplayMode } from '@/store/authStoreTypes'; +import { useHomeStore } from '@/store/homeStore'; +import { usePlayerBarLayoutStore } from '@/store/playerBarLayoutStore'; +import { usePlaylistLayoutStore } from '@/store/playlistLayoutStore'; +import { useQueueToolbarStore } from '@/store/queueToolbarStore'; import { useSidebarStore } from '@/features/sidebar'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsToggle } from './SettingsToggle'; -import { SettingsSegmented, type SegmentedOption } from './SettingsSegmented'; -import { SettingsSubCard, SettingsField } from './SettingsSubCard'; -import { ArtistLayoutCustomizer } from './ArtistLayoutCustomizer'; -import { HomeCustomizer } from './HomeCustomizer'; -import { PlayerBarLayoutCustomizer } from './PlayerBarLayoutCustomizer'; -import { PlaylistLayoutCustomizer } from './PlaylistLayoutCustomizer'; -import { QueueToolbarCustomizer } from './QueueToolbarCustomizer'; -import { SidebarCustomizer } from './SidebarCustomizer'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSegmented, type SegmentedOption } from '@/features/settings/components/SettingsSegmented'; +import { SettingsSubCard, SettingsField } from '@/features/settings/components/SettingsSubCard'; +import { ArtistLayoutCustomizer } from '@/features/settings/components/ArtistLayoutCustomizer'; +import { HomeCustomizer } from '@/features/settings/components/HomeCustomizer'; +import { PlayerBarLayoutCustomizer } from '@/features/settings/components/PlayerBarLayoutCustomizer'; +import { PlaylistLayoutCustomizer } from '@/features/settings/components/PlaylistLayoutCustomizer'; +import { QueueToolbarCustomizer } from '@/features/settings/components/QueueToolbarCustomizer'; +import { SidebarCustomizer } from '@/features/settings/components/SidebarCustomizer'; export function PersonalisationTab() { const { t } = useTranslation(); diff --git a/src/components/settings/PlayerBarLayoutCustomizer.tsx b/src/features/settings/components/PlayerBarLayoutCustomizer.tsx similarity index 96% rename from src/components/settings/PlayerBarLayoutCustomizer.tsx rename to src/features/settings/components/PlayerBarLayoutCustomizer.tsx index b3442304..5e323ae5 100644 --- a/src/components/settings/PlayerBarLayoutCustomizer.tsx +++ b/src/features/settings/components/PlayerBarLayoutCustomizer.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Gauge, Heart, PictureInPicture2, SlidersVertical, Star } from 'lucide-react'; -import LastfmIcon from '../LastfmIcon'; +import LastfmIcon from '@/components/LastfmIcon'; import { usePlayerBarLayoutStore, type PlayerBarLayoutItemId, -} from '../../store/playerBarLayoutStore'; +} from '@/store/playerBarLayoutStore'; const PLAYER_BAR_LAYOUT_LABEL_KEYS: Record = { starRating: 'settings.playerBarStarRating', diff --git a/src/components/settings/PlaylistLayoutCustomizer.tsx b/src/features/settings/components/PlaylistLayoutCustomizer.tsx similarity index 97% rename from src/components/settings/PlaylistLayoutCustomizer.tsx rename to src/features/settings/components/PlaylistLayoutCustomizer.tsx index 553e2942..0d4d19c1 100644 --- a/src/components/settings/PlaylistLayoutCustomizer.tsx +++ b/src/features/settings/components/PlaylistLayoutCustomizer.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import { Download, FileUp, HardDrive, Lightbulb, Search } from 'lucide-react'; -import { usePlaylistLayoutStore, type PlaylistLayoutItemId } from '../../store/playlistLayoutStore'; +import { usePlaylistLayoutStore, type PlaylistLayoutItemId } from '@/store/playlistLayoutStore'; const PLAYLIST_LAYOUT_ICONS: Record = { addSongs: Search, diff --git a/src/components/settings/QueueToolbarCustomizer.tsx b/src/features/settings/components/QueueToolbarCustomizer.tsx similarity index 92% rename from src/components/settings/QueueToolbarCustomizer.tsx rename to src/features/settings/components/QueueToolbarCustomizer.tsx index 282c472d..a858f2bf 100644 --- a/src/components/settings/QueueToolbarCustomizer.tsx +++ b/src/features/settings/components/QueueToolbarCustomizer.tsx @@ -1,10 +1,10 @@ import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Blend, Infinity as InfinityIcon, ListMusic, MoveRight, Share2, Shuffle, Trash2, Waves } from 'lucide-react'; -import { useQueueToolbarStore, QueueToolbarButtonId } from '../../store/queueToolbarStore'; -import { useListReorderDnd } from '../../hooks/useListReorderDnd'; -import { applyListReorderById, type ListReorderDropTarget } from '../../utils/componentHelpers/listReorder'; -import { ReorderGripHandle } from './ReorderGripHandle'; +import { useQueueToolbarStore, QueueToolbarButtonId } from '@/store/queueToolbarStore'; +import { useListReorderDnd } from '@/hooks/useListReorderDnd'; +import { applyListReorderById, type ListReorderDropTarget } from '@/utils/componentHelpers/listReorder'; +import { ReorderGripHandle } from '@/features/settings/components/ReorderGripHandle'; const QUEUE_TOOLBAR_BUTTON_ICONS: Record = { shuffle: Shuffle, diff --git a/src/components/settings/ReorderGripHandle.tsx b/src/features/settings/components/ReorderGripHandle.tsx similarity index 92% rename from src/components/settings/ReorderGripHandle.tsx rename to src/features/settings/components/ReorderGripHandle.tsx index 72320866..83588ecf 100644 --- a/src/components/settings/ReorderGripHandle.tsx +++ b/src/features/settings/components/ReorderGripHandle.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import { GripVertical } from 'lucide-react'; -import { useDragSource } from '../../contexts/DragDropContext'; +import { useDragSource } from '@/contexts/DragDropContext'; /** * Drag handle shared by the reorder customizers. Emits an id-based payload diff --git a/src/components/settings/ServerCapabilityHeaderBadge.tsx b/src/features/settings/components/ServerCapabilityHeaderBadge.tsx similarity index 87% rename from src/components/settings/ServerCapabilityHeaderBadge.tsx rename to src/features/settings/components/ServerCapabilityHeaderBadge.tsx index 5c344814..39794e31 100644 --- a/src/components/settings/ServerCapabilityHeaderBadge.tsx +++ b/src/features/settings/components/ServerCapabilityHeaderBadge.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; -import { getCapabilityDefinition } from '../../serverCapabilities/catalog'; -import { isFeatureActiveForServer, resolveFeatureForServer } from '../../serverCapabilities/storeView'; +import { getCapabilityDefinition } from '@/serverCapabilities/catalog'; +import { isFeatureActiveForServer, resolveFeatureForServer } from '@/serverCapabilities/storeView'; /** Inline badge for auto-managed server capabilities (Settings → Servers header row). */ export function ServerCapabilityHeaderBadge({ diff --git a/src/components/settings/ServerLibraryIndexControls.tsx b/src/features/settings/components/ServerLibraryIndexControls.tsx similarity index 95% rename from src/components/settings/ServerLibraryIndexControls.tsx rename to src/features/settings/components/ServerLibraryIndexControls.tsx index 7f3d711f..a48079ce 100644 --- a/src/components/settings/ServerLibraryIndexControls.tsx +++ b/src/features/settings/components/ServerLibraryIndexControls.tsx @@ -1,11 +1,11 @@ import { RefreshCw, ShieldCheck, WifiOff, Zap } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import type { SyncStateDto } from '../../api/library'; +import type { SyncStateDto } from '@/api/library'; import { libraryStatusDisplayTrackCount, libraryStatusIsReady, -} from '../../utils/library/libraryReady'; -import type { LibraryServerConnection } from '../../hooks/useLibraryIndexSync'; +} from '@/utils/library/libraryReady'; +import type { LibraryServerConnection } from '@/hooks/useLibraryIndexSync'; interface ServerLibraryIndexControlsProps { status: SyncStateDto | null; diff --git a/src/components/settings/ServersTab.tsx b/src/features/settings/components/ServersTab.tsx similarity index 93% rename from src/components/settings/ServersTab.tsx rename to src/features/settings/components/ServersTab.tsx index 2a352fa2..eaad76a5 100644 --- a/src/components/settings/ServersTab.tsx +++ b/src/features/settings/components/ServersTab.tsx @@ -3,43 +3,43 @@ import { Trans, useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { open as openUrl } from '@tauri-apps/plugin-shell'; import { AlertTriangle, CheckCircle2, Info, Lock, LogOut, Pencil, Plus, Power, Server, Sparkles, Wifi, WifiOff } from 'lucide-react'; -import { useAuthStore } from '../../store/authStore'; -import { formatServerSoftware, isNavidromeAudiomuseSoftwareEligible, type InstantMixProbeResult, type SubsonicServerIdentity } from '../../utils/server/subsonicServerIdentity'; -import { buildCapabilityContext } from '../../serverCapabilities/context'; -import { useLibraryIndexStore } from '../../store/libraryIndexStore'; -import { libraryDeleteServerData, librarySyncClearSession } from '../../api/library'; -import { bootstrapIndexedServer } from '../../utils/library/librarySession'; -import { useLibraryIndexSync } from '../../hooks/useLibraryIndexSync'; -import ServerLibraryIndexControls from './ServerLibraryIndexControls'; -import type { ServerProfile } from '../../store/authStoreTypes'; -import { pingWithCredentialsForProfile, scheduleInstantMixProbeForServer } from '../../api/subsonic'; +import { useAuthStore } from '@/store/authStore'; +import { formatServerSoftware, isNavidromeAudiomuseSoftwareEligible, type InstantMixProbeResult, type SubsonicServerIdentity } from '@/utils/server/subsonicServerIdentity'; +import { buildCapabilityContext } from '@/serverCapabilities/context'; +import { useLibraryIndexStore } from '@/store/libraryIndexStore'; +import { libraryDeleteServerData, librarySyncClearSession } from '@/api/library'; +import { bootstrapIndexedServer } from '@/utils/library/librarySession'; +import { useLibraryIndexSync } from '@/hooks/useLibraryIndexSync'; +import ServerLibraryIndexControls from '@/features/settings/components/ServerLibraryIndexControls'; +import type { ServerProfile } from '@/store/authStoreTypes'; +import { pingWithCredentialsForProfile, scheduleInstantMixProbeForServer } from '@/api/subsonic'; import { clearServerHttpContext, syncServerHttpContextForProfile, -} from '../../utils/server/syncServerHttpContext'; -import { type ServerMagicPayload } from '../../utils/server/serverMagicString'; -import { ensureConnectUrlResolved, invalidateReachableEndpointCache } from '../../utils/server/serverEndpoint'; +} from '@/utils/server/syncServerHttpContext'; +import { type ServerMagicPayload } from '@/utils/server/serverMagicString'; +import { ensureConnectUrlResolved, invalidateReachableEndpointCache } from '@/utils/server/serverEndpoint'; import { verifySameServerEndpoints, type VerifySameServerResult, -} from '../../utils/server/serverFingerprint'; +} from '@/utils/server/serverFingerprint'; import { indexKeyRemapForUrlChange, runIndexKeyRemigration, -} from '../../utils/server/serverUrlRemigration'; -import { useConfirmModalStore } from '../../store/confirmModalStore'; -import { showToast } from '../../utils/ui/toast'; -import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS } from '../../serverCapabilities/catalog'; -import { isFeatureActiveForServer, resolveFeatureForServer } from '../../serverCapabilities/storeView'; -import type { ResolvedCapability } from '../../serverCapabilities/types'; -import { serverIdentityLabel, serverListDisplayLabel, serverSettingsEntryTitle } from '../../utils/server/serverDisplayName'; -import { serverIndexKeyForProfile } from '../../utils/server/serverIndexKey'; -import { switchActiveServer } from '../../utils/server/switchActiveServer'; -import { AddServerForm } from './AddServerForm'; -import { ServerCapabilityHeaderBadge } from './ServerCapabilityHeaderBadge'; -import { useListReorderDnd } from '../../hooks/useListReorderDnd'; -import { applyListReorderById, type ListReorderDropTarget } from '../../utils/componentHelpers/listReorder'; -import { ReorderGripHandle } from './ReorderGripHandle'; +} from '@/utils/server/serverUrlRemigration'; +import { useConfirmModalStore } from '@/store/confirmModalStore'; +import { showToast } from '@/utils/ui/toast'; +import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS } from '@/serverCapabilities/catalog'; +import { isFeatureActiveForServer, resolveFeatureForServer } from '@/serverCapabilities/storeView'; +import type { ResolvedCapability } from '@/serverCapabilities/types'; +import { serverIdentityLabel, serverListDisplayLabel, serverSettingsEntryTitle } from '@/utils/server/serverDisplayName'; +import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; +import { switchActiveServer } from '@/utils/server/switchActiveServer'; +import { AddServerForm } from '@/features/settings/components/AddServerForm'; +import { ServerCapabilityHeaderBadge } from '@/features/settings/components/ServerCapabilityHeaderBadge'; +import { useListReorderDnd } from '@/hooks/useListReorderDnd'; +import { applyListReorderById, type ListReorderDropTarget } from '@/utils/componentHelpers/listReorder'; +import { ReorderGripHandle } from '@/features/settings/components/ReorderGripHandle'; import { tooltipAttrs } from '@/ui/tooltipAttrs'; const AUDIOMUSE_NV_PLUGIN_URL = 'https://github.com/NeptuneHub/AudioMuse-AI-NV-plugin'; diff --git a/src/components/settings/SettingsGroup.tsx b/src/features/settings/components/SettingsGroup.tsx similarity index 100% rename from src/components/settings/SettingsGroup.tsx rename to src/features/settings/components/SettingsGroup.tsx diff --git a/src/components/settings/SettingsSegmented.tsx b/src/features/settings/components/SettingsSegmented.tsx similarity index 100% rename from src/components/settings/SettingsSegmented.tsx rename to src/features/settings/components/SettingsSegmented.tsx diff --git a/src/components/settings/SettingsSubCard.tsx b/src/features/settings/components/SettingsSubCard.tsx similarity index 100% rename from src/components/settings/SettingsSubCard.tsx rename to src/features/settings/components/SettingsSubCard.tsx diff --git a/src/components/SettingsSubSection.tsx b/src/features/settings/components/SettingsSubSection.tsx similarity index 97% rename from src/components/SettingsSubSection.tsx rename to src/features/settings/components/SettingsSubSection.tsx index 55cad140..b8ffec32 100644 --- a/src/components/SettingsSubSection.tsx +++ b/src/features/settings/components/SettingsSubSection.tsx @@ -1,7 +1,7 @@ import React, { useId } from 'react'; import { ChevronDown } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { useAuthStore } from '../store/authStore'; +import { useAuthStore } from '@/store/authStore'; interface SettingsSubSectionProps { title: string; diff --git a/src/components/settings/SettingsToggle.tsx b/src/features/settings/components/SettingsToggle.tsx similarity index 100% rename from src/components/settings/SettingsToggle.tsx rename to src/features/settings/components/SettingsToggle.tsx diff --git a/src/components/settings/SidebarCustomizer.tsx b/src/features/settings/components/SidebarCustomizer.tsx similarity index 89% rename from src/components/settings/SidebarCustomizer.tsx rename to src/features/settings/components/SidebarCustomizer.tsx index bad5cc30..1fad85e1 100644 --- a/src/components/settings/SidebarCustomizer.tsx +++ b/src/features/settings/components/SidebarCustomizer.tsx @@ -1,15 +1,15 @@ import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { useAuthStore } from '../../store/authStore'; +import { useAuthStore } from '@/store/authStore'; import { useSidebarStore, SidebarItemConfig, CONSERVED_SIDEBAR_NAV_IDS } from '@/features/sidebar'; -import { useLuckyMixAvailable } from '../../hooks/useLuckyMixAvailable'; -import { ALL_NAV_ITEMS } from '../../config/navItems'; +import { useLuckyMixAvailable } from '@/hooks/useLuckyMixAvailable'; +import { ALL_NAV_ITEMS } from '@/config/navItems'; import { applySidebarReorderById } from '@/features/sidebar'; -import { useListReorderDnd } from '../../hooks/useListReorderDnd'; -import type { ListReorderDropTarget } from '../../utils/componentHelpers/listReorder'; -import { ReorderGripHandle } from './ReorderGripHandle'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsToggle } from './SettingsToggle'; +import { useListReorderDnd } from '@/hooks/useListReorderDnd'; +import type { ListReorderDropTarget } from '@/utils/componentHelpers/listReorder'; +import { ReorderGripHandle } from '@/features/settings/components/ReorderGripHandle'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; const REORDER_TYPE = 'sidebar_reorder'; diff --git a/src/components/settings/StorageTab.tsx b/src/features/settings/components/StorageTab.tsx similarity index 92% rename from src/components/settings/StorageTab.tsx rename to src/features/settings/components/StorageTab.tsx index 924697b5..dea83f6a 100644 --- a/src/components/settings/StorageTab.tsx +++ b/src/features/settings/components/StorageTab.tsx @@ -3,15 +3,15 @@ import { useTranslation } from 'react-i18next'; import { invoke } from '@tauri-apps/api/core'; import { open as openDialog } from '@tauri-apps/plugin-dialog'; import { Download, FolderOpen, Trash2, X } from 'lucide-react'; -import { useAuthStore } from '../../store/authStore'; -import { countHotCacheTracks } from '../../store/hotCacheStore'; -import { useLocalPlaybackStore } from '../../store/localPlaybackStore'; -import { formatBytes, snapHotCacheMb } from '../../utils/format/formatBytes'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsToggle } from './SettingsToggle'; -import { SettingsSubCard, SettingsField, SettingsValue } from './SettingsSubCard'; -import CoverCacheStrategySection from './CoverCacheStrategySection'; +import { useAuthStore } from '@/store/authStore'; +import { countHotCacheTracks } from '@/store/hotCacheStore'; +import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; +import { formatBytes, snapHotCacheMb } from '@/utils/format/formatBytes'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSubCard, SettingsField, SettingsValue } from '@/features/settings/components/SettingsSubCard'; +import CoverCacheStrategySection from '@/features/settings/components/CoverCacheStrategySection'; export function StorageTab() { const { t } = useTranslation(); diff --git a/src/components/settings/SystemTab.tsx b/src/features/settings/components/SystemTab.tsx similarity index 94% rename from src/components/settings/SystemTab.tsx rename to src/features/settings/components/SystemTab.tsx index 5b9654cf..7d2738cd 100644 --- a/src/components/settings/SystemTab.tsx +++ b/src/features/settings/components/SystemTab.tsx @@ -5,21 +5,21 @@ import { invoke } from '@tauri-apps/api/core'; import { save as saveDialog } from '@tauri-apps/plugin-dialog'; import { open as openUrl } from '@tauri-apps/plugin-shell'; import { AppWindow, ChevronDown, Download, ExternalLink, Globe, HardDrive, Info, Scale, Sliders, Users } from 'lucide-react'; -import { version as appVersion } from '../../../package.json'; -import i18n from '../../i18n'; -import { useAuthStore } from '../../store/authStore'; -import type { ClockFormat, LinuxWaylandTextRenderProfile, LoggingMode } from '../../store/authStoreTypes'; -import { IS_LINUX } from '../../utils/platform'; -import { showToast } from '../../utils/ui/toast'; -import { AboutPsysonicBrandHeader } from '../AboutPsysonicLol'; +import { version as appVersion } from '@/../package.json'; +import i18n from '@/i18n'; +import { useAuthStore } from '@/store/authStore'; +import type { ClockFormat, LinuxWaylandTextRenderProfile, LoggingMode } from '@/store/authStoreTypes'; +import { IS_LINUX } from '@/utils/platform'; +import { showToast } from '@/utils/ui/toast'; +import { AboutPsysonicBrandHeader } from '@/components/AboutPsysonicLol'; import CustomSelect from '@/ui/CustomSelect'; -import LicensesPanel from '../LicensesPanel'; -import SettingsSubSection from '../SettingsSubSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsToggle } from './SettingsToggle'; -import { SettingsSubCard, SettingsField } from './SettingsSubCard'; -import { BackupSection } from './BackupSection'; -import { CONTRIBUTORS, MAINTAINERS } from '../../config/settingsCredits'; +import LicensesPanel from '@/components/LicensesPanel'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSubCard, SettingsField } from '@/features/settings/components/SettingsSubCard'; +import { BackupSection } from '@/features/settings/components/BackupSection'; +import { CONTRIBUTORS, MAINTAINERS } from '@/config/settingsCredits'; export function SystemTab() { const { t } = useTranslation(); diff --git a/src/components/settings/ThemeImportSection.tsx b/src/features/settings/components/ThemeImportSection.tsx similarity index 95% rename from src/components/settings/ThemeImportSection.tsx rename to src/features/settings/components/ThemeImportSection.tsx index 05a6f17b..a4521e70 100644 --- a/src/components/settings/ThemeImportSection.tsx +++ b/src/features/settings/components/ThemeImportSection.tsx @@ -3,10 +3,10 @@ import { Upload, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { invoke } from '@tauri-apps/api/core'; import { open } from '@tauri-apps/plugin-dialog'; -import { useInstalledThemesStore } from '../../store/installedThemesStore'; -import { validateThemePackage, type ValidatedTheme } from '../../utils/themes/validateThemePackage'; -import { showToast } from '../../utils/ui/toast'; -import ConfirmModal from '../ConfirmModal'; +import { useInstalledThemesStore } from '@/store/installedThemesStore'; +import { validateThemePackage, type ValidatedTheme } from '@/utils/themes/validateThemePackage'; +import { showToast } from '@/utils/ui/toast'; +import ConfirmModal from '@/components/ConfirmModal'; /** * Import a community theme from a local `.zip` (manifest.json + theme.css). diff --git a/src/components/settings/ThemeStoreSection.test.tsx b/src/features/settings/components/ThemeStoreSection.test.tsx similarity index 99% rename from src/components/settings/ThemeStoreSection.test.tsx rename to src/features/settings/components/ThemeStoreSection.test.tsx index d16eb0c6..29589ea2 100644 --- a/src/components/settings/ThemeStoreSection.test.tsx +++ b/src/features/settings/components/ThemeStoreSection.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { screen, waitFor, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '@/test/helpers/renderWithProviders'; -import { ThemeStoreSection } from './ThemeStoreSection'; +import { ThemeStoreSection } from '@/features/settings/components/ThemeStoreSection'; import type { FetchRegistryResult, Registry, RegistryTheme } from '@/utils/themes/themeRegistry'; // Control the registry the store browses so pagination/refresh are deterministic. diff --git a/src/components/settings/ThemeStoreSection.tsx b/src/features/settings/components/ThemeStoreSection.tsx similarity index 96% rename from src/components/settings/ThemeStoreSection.tsx rename to src/features/settings/components/ThemeStoreSection.tsx index f1f6c11a..7b57995f 100644 --- a/src/components/settings/ThemeStoreSection.tsx +++ b/src/features/settings/components/ThemeStoreSection.tsx @@ -2,21 +2,21 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Check, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, WifiOff } from 'lucide-react'; import { open as openUrl } from '@tauri-apps/plugin-shell'; -import CoverLightbox from '../CoverLightbox'; -import { useThemeAnimationRisk } from '../../hooks/useThemeAnimationRisk'; -import { AnimatedThemeBadge } from './AnimatedThemeBadge'; +import CoverLightbox from '@/components/CoverLightbox'; +import { useThemeAnimationRisk } from '@/hooks/useThemeAnimationRisk'; +import { AnimatedThemeBadge } from '@/features/settings/components/AnimatedThemeBadge'; import CustomSelect from '@/ui/CustomSelect'; -import { formatRelativeTime } from '../../utils/format/relativeTime'; -import { useThemeStore } from '../../store/themeStore'; -import { useInstalledThemesStore, type InstalledTheme } from '../../store/installedThemesStore'; +import { formatRelativeTime } from '@/utils/format/relativeTime'; +import { useThemeStore } from '@/store/themeStore'; +import { useInstalledThemesStore, type InstalledTheme } from '@/store/installedThemesStore'; import { assetUrl, fetchRegistry, type RegistryTheme, -} from '../../utils/themes/themeRegistry'; -import { installThemeFromRegistry } from '../../utils/themes/installThemeFromRegistry'; -import { uninstallTheme } from '../../utils/themes/uninstallTheme'; -import { isNewer } from '../../utils/componentHelpers/appUpdaterHelpers'; +} from '@/utils/themes/themeRegistry'; +import { installThemeFromRegistry } from '@/utils/themes/installThemeFromRegistry'; +import { uninstallTheme } from '@/utils/themes/uninstallTheme'; +import { isNewer } from '@/utils/componentHelpers/appUpdaterHelpers'; type ModeFilter = 'all' | 'dark' | 'light'; type SortMode = 'newest' | 'name'; diff --git a/src/components/settings/ThemesTab.tsx b/src/features/settings/components/ThemesTab.tsx similarity index 92% rename from src/components/settings/ThemesTab.tsx rename to src/features/settings/components/ThemesTab.tsx index 1c22bf74..8c8a728f 100644 --- a/src/components/settings/ThemesTab.tsx +++ b/src/features/settings/components/ThemesTab.tsx @@ -1,16 +1,16 @@ import type { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { Clock, Palette, Store, Upload } from 'lucide-react'; -import { useThemeStore } from '../../store/themeStore'; -import { useInstalledThemesStore } from '../../store/installedThemesStore'; +import { useThemeStore } from '@/store/themeStore'; +import { useInstalledThemesStore } from '@/store/installedThemesStore'; import CustomSelect from '@/ui/CustomSelect'; import BackToTopButton from '@/ui/BackToTopButton'; -import { FIXED_THEMES } from './fixedThemes'; -import { InstalledThemes } from './InstalledThemes'; -import { ThemeImportSection } from './ThemeImportSection'; -import { ThemeStoreSection } from './ThemeStoreSection'; -import { SettingsGroup } from './SettingsGroup'; -import { SettingsSubCard, SettingsField } from './SettingsSubCard'; +import { FIXED_THEMES } from '@/components/settings/fixedThemes'; +import { InstalledThemes } from '@/features/settings/components/InstalledThemes'; +import { ThemeImportSection } from '@/features/settings/components/ThemeImportSection'; +import { ThemeStoreSection } from '@/features/settings/components/ThemeStoreSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsSubCard, SettingsField } from '@/features/settings/components/SettingsSubCard'; /** * A flat, always-visible section. The Themes tab has a single purpose, so its diff --git a/src/components/settings/UserForm.tsx b/src/features/settings/components/UserForm.tsx similarity index 97% rename from src/components/settings/UserForm.tsx rename to src/features/settings/components/UserForm.tsx index 4080cf8d..37d838d3 100644 --- a/src/components/settings/UserForm.tsx +++ b/src/features/settings/components/UserForm.tsx @@ -1,15 +1,15 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Shield, Wand2 } from 'lucide-react'; -import { ndUpdateUser, type NdLibrary, type NdUser } from '../../api/navidromeAdmin'; -import { showToast } from '../../utils/ui/toast'; +import { ndUpdateUser, type NdLibrary, type NdUser } from '@/api/navidromeAdmin'; +import { showToast } from '@/utils/ui/toast'; import { copyTextToClipboard, encodeServerMagicString, magicPayloadAddressFields, -} from '../../utils/server/serverMagicString'; -import { shortHostFromServerUrl } from '../../utils/server/serverDisplayName'; -import { useAuthStore } from '../../store/authStore'; +} from '@/utils/server/serverMagicString'; +import { shortHostFromServerUrl } from '@/utils/server/serverDisplayName'; +import { useAuthStore } from '@/store/authStore'; export interface UserFormState { userName: string; diff --git a/src/components/settings/UserManagementSection.tsx b/src/features/settings/components/UserManagementSection.tsx similarity index 90% rename from src/components/settings/UserManagementSection.tsx rename to src/features/settings/components/UserManagementSection.tsx index 5e4e9180..2af34102 100644 --- a/src/components/settings/UserManagementSection.tsx +++ b/src/features/settings/components/UserManagementSection.tsx @@ -1,13 +1,13 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { RotateCcw, UserPlus, Users } from 'lucide-react'; -import type { NdUser } from '../../api/navidromeAdmin'; -import ConfirmModal from '../ConfirmModal'; -import { useUserMgmtData } from '../../hooks/useUserMgmtData'; -import { useUserMgmtActions } from '../../hooks/useUserMgmtActions'; -import { UserForm } from './UserForm'; -import { UserMgmtRow } from './userMgmt/UserMgmtRow'; -import { MagicStringModal } from './userMgmt/MagicStringModal'; +import type { NdUser } from '@/api/navidromeAdmin'; +import ConfirmModal from '@/components/ConfirmModal'; +import { useUserMgmtData } from '@/features/settings/hooks/useUserMgmtData'; +import { useUserMgmtActions } from '@/features/settings/hooks/useUserMgmtActions'; +import { UserForm } from '@/features/settings/components/UserForm'; +import { UserMgmtRow } from '@/features/settings/components/userMgmt/UserMgmtRow'; +import { MagicStringModal } from '@/features/settings/components/userMgmt/MagicStringModal'; export function UserManagementSection({ serverUrl, diff --git a/src/components/settings/audio/AudioOutputDeviceSection.tsx b/src/features/settings/components/audio/AudioOutputDeviceSection.tsx similarity index 89% rename from src/components/settings/audio/AudioOutputDeviceSection.tsx rename to src/features/settings/components/audio/AudioOutputDeviceSection.tsx index bd4f3455..e5024227 100644 --- a/src/components/settings/audio/AudioOutputDeviceSection.tsx +++ b/src/features/settings/components/audio/AudioOutputDeviceSection.tsx @@ -3,12 +3,12 @@ import { invoke } from '@tauri-apps/api/core'; import { AudioLines, RotateCcw } from 'lucide-react'; import type { TFunction } from 'i18next'; import CustomSelect from '@/ui/CustomSelect'; -import SettingsSubSection from '../../SettingsSubSection'; -import { SettingsGroup } from '../SettingsGroup'; -import { SettingsToggle } from '../SettingsToggle'; -import { useAuthStore } from '../../../store/authStore'; -import { useEqStore } from '../../../store/eqStore'; -import { buildAudioDeviceSelectOptions } from '../../../utils/audio/audioDeviceLabels'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { useAuthStore } from '@/store/authStore'; +import { useEqStore } from '@/store/eqStore'; +import { buildAudioDeviceSelectOptions } from '@/utils/audio/audioDeviceLabels'; interface Props { audioDevices: string[]; diff --git a/src/components/settings/audio/HiResCrossfadeResampleBlock.tsx b/src/features/settings/components/audio/HiResCrossfadeResampleBlock.tsx similarity index 91% rename from src/components/settings/audio/HiResCrossfadeResampleBlock.tsx rename to src/features/settings/components/audio/HiResCrossfadeResampleBlock.tsx index 7e508d5c..71f3c7e8 100644 --- a/src/components/settings/audio/HiResCrossfadeResampleBlock.tsx +++ b/src/features/settings/components/audio/HiResCrossfadeResampleBlock.tsx @@ -2,9 +2,9 @@ import { HI_RES_CROSSFADE_RESAMPLE_OPTIONS, type HiResCrossfadeResampleHz, sanitizeHiResCrossfadeResampleHz, -} from '../../../utils/audio/hiResCrossfadeResample'; +} from '@/utils/audio/hiResCrossfadeResample'; import type { TFunction } from 'i18next'; -import { SettingsSubCard, SettingsField } from '../SettingsSubCard'; +import { SettingsSubCard, SettingsField } from '@/features/settings/components/SettingsSubCard'; interface Props { enabled: boolean; diff --git a/src/components/settings/audio/NormalizationBlock.tsx b/src/features/settings/components/audio/NormalizationBlock.tsx similarity index 94% rename from src/components/settings/audio/NormalizationBlock.tsx rename to src/features/settings/components/audio/NormalizationBlock.tsx index 744912a2..1fa73981 100644 --- a/src/components/settings/audio/NormalizationBlock.tsx +++ b/src/features/settings/components/audio/NormalizationBlock.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { RotateCcw } from 'lucide-react'; import type { TFunction } from 'i18next'; -import { useAuthStore } from '../../../store/authStore'; -import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB } from '../../../store/authStoreDefaults'; -import { LoudnessLufsButtonGroup } from '../LoudnessLufsButtonGroup'; -import { SettingsGroup } from '../SettingsGroup'; -import { SettingsSubCard, SettingsField, SettingsValue, SettingsCallout } from '../SettingsSubCard'; +import { useAuthStore } from '@/store/authStore'; +import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB } from '@/store/authStoreDefaults'; +import { LoudnessLufsButtonGroup } from '@/features/settings/components/LoudnessLufsButtonGroup'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsSubCard, SettingsField, SettingsValue, SettingsCallout } from '@/features/settings/components/SettingsSubCard'; interface Props { preAnalysisEffectiveDb: number; diff --git a/src/components/settings/audio/PlaybackRateBlock.tsx b/src/features/settings/components/audio/PlaybackRateBlock.tsx similarity index 96% rename from src/components/settings/audio/PlaybackRateBlock.tsx rename to src/features/settings/components/audio/PlaybackRateBlock.tsx index 242be702..8bf46fce 100644 --- a/src/components/settings/audio/PlaybackRateBlock.tsx +++ b/src/features/settings/components/audio/PlaybackRateBlock.tsx @@ -17,13 +17,13 @@ import { playbackSpeedStep, varispeedSpeedFromSemitones, type PlaybackStrategy, -} from '../../../utils/audio/playbackRateHelpers'; -import { usePlaybackRateStore } from '../../../store/playbackRateStore'; -import { useOrbitStore } from '../../../store/orbitStore'; -import { useAuthStore } from '../../../store/authStore'; -import { isOrbitPlaybackSyncActive } from '../../../utils/orbit'; -import { SettingsToggle } from '../SettingsToggle'; -import { SettingsSubCard } from '../SettingsSubCard'; +} from '@/utils/audio/playbackRateHelpers'; +import { usePlaybackRateStore } from '@/store/playbackRateStore'; +import { useOrbitStore } from '@/store/orbitStore'; +import { useAuthStore } from '@/store/authStore'; +import { isOrbitPlaybackSyncActive } from '@/utils/orbit'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSubCard } from '@/features/settings/components/SettingsSubCard'; interface Props { t: TFunction; diff --git a/src/components/settings/audio/TrackPreviewsSection.tsx b/src/features/settings/components/audio/TrackPreviewsSection.tsx similarity index 87% rename from src/components/settings/audio/TrackPreviewsSection.tsx rename to src/features/settings/components/audio/TrackPreviewsSection.tsx index 19f42303..05fd24e8 100644 --- a/src/components/settings/audio/TrackPreviewsSection.tsx +++ b/src/features/settings/components/audio/TrackPreviewsSection.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { Play } from 'lucide-react'; import type { TFunction } from 'i18next'; -import { useAuthStore } from '../../../store/authStore'; -import { TRACK_PREVIEW_LOCATIONS } from '../../../store/authStoreDefaults'; -import type { TrackPreviewLocation } from '../../../store/authStoreTypes'; -import SettingsSubSection from '../../SettingsSubSection'; -import { SettingsGroup } from '../SettingsGroup'; -import { SettingsToggle } from '../SettingsToggle'; -import { SettingsSubCard, SettingsField, SettingsValue } from '../SettingsSubCard'; +import { useAuthStore } from '@/store/authStore'; +import { TRACK_PREVIEW_LOCATIONS } from '@/store/authStoreDefaults'; +import type { TrackPreviewLocation } from '@/store/authStoreTypes'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSubCard, SettingsField, SettingsValue } from '@/features/settings/components/SettingsSubCard'; interface Props { t: TFunction; diff --git a/src/components/settings/audio/TrackTransitionsBlock.tsx b/src/features/settings/components/audio/TrackTransitionsBlock.tsx similarity index 89% rename from src/components/settings/audio/TrackTransitionsBlock.tsx rename to src/features/settings/components/audio/TrackTransitionsBlock.tsx index ba37da23..fce6731c 100644 --- a/src/components/settings/audio/TrackTransitionsBlock.tsx +++ b/src/features/settings/components/audio/TrackTransitionsBlock.tsx @@ -1,20 +1,20 @@ import React from 'react'; import type { TFunction } from 'i18next'; -import { useAuthStore } from '../../../store/authStore'; -import { useOrbitStore } from '../../../store/orbitStore'; +import { useAuthStore } from '@/store/authStore'; +import { useOrbitStore } from '@/store/orbitStore'; import { AUTODJ_OVERLAP_CAP_MAX_SEC, AUTODJ_OVERLAP_CAP_MIN_SEC, -} from '../../../utils/playback/autodjOverlapCap'; +} from '@/utils/playback/autodjOverlapCap'; import { getTransitionMode, setTransitionMode, type TransitionMode, -} from '../../../utils/playback/playbackTransition'; -import { SettingsGroup } from '../SettingsGroup'; -import { SettingsToggle } from '../SettingsToggle'; -import { SettingsSubCard, SettingsField, SettingsRow, SettingsValue } from '../SettingsSubCard'; -import { SettingsSegmented, type SegmentedOption } from '../SettingsSegmented'; +} from '@/utils/playback/playbackTransition'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; +import { SettingsSubCard, SettingsField, SettingsRow, SettingsValue } from '@/features/settings/components/SettingsSubCard'; +import { SettingsSegmented, type SegmentedOption } from '@/features/settings/components/SettingsSegmented'; interface Props { t: TFunction; diff --git a/src/components/settings/backdropReorder.test.ts b/src/features/settings/components/backdropReorder.test.ts similarity index 90% rename from src/components/settings/backdropReorder.test.ts rename to src/features/settings/components/backdropReorder.test.ts index 8c36a381..5ea254fd 100644 --- a/src/components/settings/backdropReorder.test.ts +++ b/src/features/settings/components/backdropReorder.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { moveSourceTo, dropSourceBefore } from './backdropReorder'; -import type { BackdropSource, BackdropSourcePref } from '../../cover/artistBackdrop'; +import { moveSourceTo, dropSourceBefore } from '@/features/settings/components/backdropReorder'; +import type { BackdropSource, BackdropSourcePref } from '@/cover/artistBackdrop'; const S = (...names: BackdropSource[]): BackdropSourcePref[] => names.map((source) => ({ source, enabled: true })); diff --git a/src/components/settings/backdropReorder.ts b/src/features/settings/components/backdropReorder.ts similarity index 94% rename from src/components/settings/backdropReorder.ts rename to src/features/settings/components/backdropReorder.ts index 14f49379..32867981 100644 --- a/src/components/settings/backdropReorder.ts +++ b/src/features/settings/components/backdropReorder.ts @@ -1,4 +1,4 @@ -import type { BackdropSourcePref } from '../../cover/artistBackdrop'; +import type { BackdropSourcePref } from '@/cover/artistBackdrop'; /** * Move the source at `from` so it lands at exactly index `to` — the ↑/↓ buttons, diff --git a/src/components/settings/musicNetwork/ConnectProviderForm.tsx b/src/features/settings/components/musicNetwork/ConnectProviderForm.tsx similarity index 97% rename from src/components/settings/musicNetwork/ConnectProviderForm.tsx rename to src/features/settings/components/musicNetwork/ConnectProviderForm.tsx index 60df9ccd..7760eed4 100644 --- a/src/components/settings/musicNetwork/ConnectProviderForm.tsx +++ b/src/features/settings/components/musicNetwork/ConnectProviderForm.tsx @@ -6,8 +6,8 @@ import { listPresets, type BuiltinPreset, type PresetId, -} from '../../../music-network'; -import { renderPresetIcon } from './presetIcon'; +} from '@/music-network'; +import { renderPresetIcon } from '@/components/settings/musicNetwork/presetIcon'; /** * "Add a service" list, driven entirely by the preset registry. Token-poll diff --git a/src/components/settings/musicNetwork/EnrichmentPrimarySelect.tsx b/src/features/settings/components/musicNetwork/EnrichmentPrimarySelect.tsx similarity index 91% rename from src/components/settings/musicNetwork/EnrichmentPrimarySelect.tsx rename to src/features/settings/components/musicNetwork/EnrichmentPrimarySelect.tsx index d63a27ea..658b8eb7 100644 --- a/src/components/settings/musicNetwork/EnrichmentPrimarySelect.tsx +++ b/src/features/settings/components/musicNetwork/EnrichmentPrimarySelect.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next'; import CustomSelect from '@/ui/CustomSelect'; -import { SettingsGroup } from '../SettingsGroup'; -import type { Account } from '../../../music-network'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import type { Account } from '@/music-network'; /** * Picks the single enrichment primary (love / similar / stats source). Only diff --git a/src/components/settings/musicNetwork/MalojaProxyWarning.tsx b/src/features/settings/components/musicNetwork/MalojaProxyWarning.tsx similarity index 94% rename from src/components/settings/musicNetwork/MalojaProxyWarning.tsx rename to src/features/settings/components/musicNetwork/MalojaProxyWarning.tsx index b4efacfa..65385a30 100644 --- a/src/components/settings/musicNetwork/MalojaProxyWarning.tsx +++ b/src/features/settings/components/musicNetwork/MalojaProxyWarning.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import { AlertTriangle } from 'lucide-react'; -import type { Account } from '../../../music-network'; +import type { Account } from '@/music-network'; /** * Shown when a Maloja account is connected AND Last.fm scrobbling is enabled — diff --git a/src/components/settings/musicNetwork/MusicNetworkSection.tsx b/src/features/settings/components/musicNetwork/MusicNetworkSection.tsx similarity index 85% rename from src/components/settings/musicNetwork/MusicNetworkSection.tsx rename to src/features/settings/components/musicNetwork/MusicNetworkSection.tsx index cb23da14..85803cc9 100644 --- a/src/components/settings/musicNetwork/MusicNetworkSection.tsx +++ b/src/features/settings/components/musicNetwork/MusicNetworkSection.tsx @@ -1,22 +1,22 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Share2 } from 'lucide-react'; -import SettingsSubSection from '../../SettingsSubSection'; -import { SettingsGroup } from '../SettingsGroup'; -import { showToast } from '../../../utils/ui/toast'; -import { useAuthStore } from '../../../store/authStore'; +import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; +import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; +import { showToast } from '@/utils/ui/toast'; +import { useAuthStore } from '@/store/authStore'; import { errorI18nKey, getMusicNetworkRuntime, isMusicNetworkError, type PresetId, type UserProfile, -} from '../../../music-network'; -import { useMusicNetworkState } from './useMusicNetworkState'; -import { ScrobbleDestinationCard } from './ScrobbleDestinationCard'; -import { EnrichmentPrimarySelect } from './EnrichmentPrimarySelect'; -import { ConnectProviderForm } from './ConnectProviderForm'; -import { MalojaProxyWarning } from './MalojaProxyWarning'; +} from '@/music-network'; +import { useMusicNetworkState } from '@/features/settings/components/musicNetwork/useMusicNetworkState'; +import { ScrobbleDestinationCard } from '@/features/settings/components/musicNetwork/ScrobbleDestinationCard'; +import { EnrichmentPrimarySelect } from '@/features/settings/components/musicNetwork/EnrichmentPrimarySelect'; +import { ConnectProviderForm } from '@/features/settings/components/musicNetwork/ConnectProviderForm'; +import { MalojaProxyWarning } from '@/features/settings/components/musicNetwork/MalojaProxyWarning'; /** * Integrations UI for the Music Network framework — replaces the old Last.fm diff --git a/src/components/settings/musicNetwork/ScrobbleDestinationCard.tsx b/src/features/settings/components/musicNetwork/ScrobbleDestinationCard.tsx similarity index 95% rename from src/components/settings/musicNetwork/ScrobbleDestinationCard.tsx rename to src/features/settings/components/musicNetwork/ScrobbleDestinationCard.tsx index c64fbff6..1dff4e2c 100644 --- a/src/components/settings/musicNetwork/ScrobbleDestinationCard.tsx +++ b/src/features/settings/components/musicNetwork/ScrobbleDestinationCard.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; -import { getPreset, type Account, type UserProfile } from '../../../music-network'; -import { renderPresetIcon } from './presetIcon'; +import { getPreset, type Account, type UserProfile } from '@/music-network'; +import { renderPresetIcon } from '@/components/settings/musicNetwork/presetIcon'; /** * One connected account as a single self-contained block: header (icon, label, diff --git a/src/components/settings/musicNetwork/useMusicNetworkState.ts b/src/features/settings/components/musicNetwork/useMusicNetworkState.ts similarity index 90% rename from src/components/settings/musicNetwork/useMusicNetworkState.ts rename to src/features/settings/components/musicNetwork/useMusicNetworkState.ts index 63ff97ec..3c7f6524 100644 --- a/src/components/settings/musicNetwork/useMusicNetworkState.ts +++ b/src/features/settings/components/musicNetwork/useMusicNetworkState.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { useShallow } from 'zustand/react/shallow'; -import { useAuthStore } from '../../../store/authStore'; -import { getPreset, type Account } from '../../../music-network'; +import { useAuthStore } from '@/store/authStore'; +import { getPreset, type Account } from '@/music-network'; /** * Reactive view of the persisted Music Network state for the Integrations UI. diff --git a/src/components/settings/settingsSearch.test.ts b/src/features/settings/components/settingsSearch.test.ts similarity index 91% rename from src/components/settings/settingsSearch.test.ts rename to src/features/settings/components/settingsSearch.test.ts index 639995d5..5760ffac 100644 --- a/src/components/settings/settingsSearch.test.ts +++ b/src/features/settings/components/settingsSearch.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; import type { TFunction } from 'i18next'; -import { matchScore } from './settingsTabs'; -import { searchSettings } from './settingsSearch'; +import { matchScore } from '@/features/settings/components/settingsTabs'; +import { searchSettings } from '@/features/settings/components/settingsSearch'; const t = vi.fn((key: string) => { const labels: Record = { diff --git a/src/components/settings/settingsSearch.ts b/src/features/settings/components/settingsSearch.ts similarity index 93% rename from src/components/settings/settingsSearch.ts rename to src/features/settings/components/settingsSearch.ts index 367e7eb1..bd4c8884 100644 --- a/src/components/settings/settingsSearch.ts +++ b/src/features/settings/components/settingsSearch.ts @@ -1,6 +1,6 @@ import type { TFunction } from 'i18next'; -import { GLOBAL_SHORTCUT_ACTIONS, IN_APP_SHORTCUT_ACTIONS } from '../../config/shortcutActions'; -import { SETTINGS_INDEX, matchScore, type Tab } from './settingsTabs'; +import { GLOBAL_SHORTCUT_ACTIONS, IN_APP_SHORTCUT_ACTIONS } from '@/config/shortcutActions'; +import { SETTINGS_INDEX, matchScore, type Tab } from '@/features/settings/components/settingsTabs'; export type SettingsSearchHit = { tab: Tab; diff --git a/src/components/settings/settingsTabs.ts b/src/features/settings/components/settingsTabs.ts similarity index 100% rename from src/components/settings/settingsTabs.ts rename to src/features/settings/components/settingsTabs.ts diff --git a/src/components/settings/userMgmt/MagicStringModal.tsx b/src/features/settings/components/userMgmt/MagicStringModal.tsx similarity index 94% rename from src/components/settings/userMgmt/MagicStringModal.tsx rename to src/features/settings/components/userMgmt/MagicStringModal.tsx index c07ab3b2..8c54afaf 100644 --- a/src/components/settings/userMgmt/MagicStringModal.tsx +++ b/src/features/settings/components/userMgmt/MagicStringModal.tsx @@ -2,15 +2,15 @@ import React, { useState } from 'react'; import { createPortal } from 'react-dom'; import { X } from 'lucide-react'; import type { TFunction } from 'i18next'; -import { ndUpdateUser, type NdUser } from '../../../api/navidromeAdmin'; -import { showToast } from '../../../utils/ui/toast'; +import { ndUpdateUser, type NdUser } from '@/api/navidromeAdmin'; +import { showToast } from '@/utils/ui/toast'; import { copyTextToClipboard, encodeServerMagicString, magicPayloadAddressFields, -} from '../../../utils/server/serverMagicString'; -import { shortHostFromServerUrl } from '../../../utils/server/serverDisplayName'; -import { useAuthStore } from '../../../store/authStore'; +} from '@/utils/server/serverMagicString'; +import { shortHostFromServerUrl } from '@/utils/server/serverDisplayName'; +import { useAuthStore } from '@/store/authStore'; interface Props { user: NdUser; diff --git a/src/components/settings/userMgmt/UserMgmtRow.tsx b/src/features/settings/components/userMgmt/UserMgmtRow.tsx similarity index 96% rename from src/components/settings/userMgmt/UserMgmtRow.tsx rename to src/features/settings/components/userMgmt/UserMgmtRow.tsx index 82d64502..6d451034 100644 --- a/src/components/settings/userMgmt/UserMgmtRow.tsx +++ b/src/features/settings/components/userMgmt/UserMgmtRow.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { Shield, Trash2, User, Wand2 } from 'lucide-react'; import type { i18n as I18nType, TFunction } from 'i18next'; -import type { NdLibrary, NdUser } from '../../../api/navidromeAdmin'; -import { formatLastSeen } from '../../../utils/componentHelpers/userMgmtHelpers'; +import type { NdLibrary, NdUser } from '@/api/navidromeAdmin'; +import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; interface Props { user: NdUser; diff --git a/src/hooks/useUserMgmtActions.ts b/src/features/settings/hooks/useUserMgmtActions.ts similarity index 95% rename from src/hooks/useUserMgmtActions.ts rename to src/features/settings/hooks/useUserMgmtActions.ts index 8244596b..3883f32e 100644 --- a/src/hooks/useUserMgmtActions.ts +++ b/src/features/settings/hooks/useUserMgmtActions.ts @@ -7,14 +7,14 @@ import { ndUpdateUser, type NdLibrary, type NdUser, -} from '../api/navidromeAdmin'; -import { showToast } from '../utils/ui/toast'; +} from '@/api/navidromeAdmin'; +import { showToast } from '@/utils/ui/toast'; import { copyTextToClipboard, encodeServerMagicString, -} from '../utils/server/serverMagicString'; -import { shortHostFromServerUrl } from '../utils/server/serverDisplayName'; -import type { UserFormState } from '../components/settings/UserForm'; +} from '@/utils/server/serverMagicString'; +import { shortHostFromServerUrl } from '@/utils/server/serverDisplayName'; +import type { UserFormState } from '@/features/settings/components/UserForm'; interface UseUserMgmtActionsArgs { serverUrl: string; diff --git a/src/hooks/useUserMgmtData.ts b/src/features/settings/hooks/useUserMgmtData.ts similarity index 98% rename from src/hooks/useUserMgmtData.ts rename to src/features/settings/hooks/useUserMgmtData.ts index 64564cca..a12100a7 100644 --- a/src/hooks/useUserMgmtData.ts +++ b/src/features/settings/hooks/useUserMgmtData.ts @@ -5,7 +5,7 @@ import { ndListUsers, type NdLibrary, type NdUser, -} from '../api/navidromeAdmin'; +} from '@/api/navidromeAdmin'; interface UseUserMgmtDataResult { users: NdUser[]; diff --git a/src/features/settings/index.ts b/src/features/settings/index.ts new file mode 100644 index 00000000..c641c70a --- /dev/null +++ b/src/features/settings/index.ts @@ -0,0 +1,16 @@ +/** + * Settings feature — the Settings page (lazy via the deep path `pages/Settings`) + * and all its tabs/sections (servers, audio, input, library, lyrics, storage, + * personalisation, integrations, system, the theme management UI, music-network + * config, user management), plus the settings layout primitives. + * + * Cross-cutting state this feature consumes but does NOT own: `authStore` and its + * `auth*SettingsActions` slices, the theme stores + theme infra utils, the + * library-index sync hook, and `settingsCredits` — all live in the core/global + * layer. + * + * Public surface (consumed by other features / app shell): + */ +export { default as SettingsSubSection } from './components/SettingsSubSection'; +export { PlaybackRateControls } from './components/audio/PlaybackRateBlock'; +export { CustomHttpHeadersEditor } from './components/CustomHttpHeadersEditor'; diff --git a/src/pages/Settings.tsx b/src/features/settings/pages/Settings.tsx similarity index 92% rename from src/pages/Settings.tsx rename to src/features/settings/pages/Settings.tsx index b2c7ae51..8bed021f 100644 --- a/src/pages/Settings.tsx +++ b/src/features/settings/pages/Settings.tsx @@ -4,24 +4,24 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { AudioLines, HardDrive, Info, Keyboard, LayoutGrid, Music2, Paintbrush, Palette, Search, Server, Sparkles, Users, X, } from 'lucide-react'; -import { useAuthStore } from '../store/authStore'; -import { IS_MACOS } from '../utils/platform'; -import { AppearanceTab } from '../components/settings/AppearanceTab'; -import { ThemesTab } from '../components/settings/ThemesTab'; -import { AudioTab } from '../components/settings/AudioTab'; -import { InputTab } from '../components/settings/InputTab'; -import { IntegrationsTab } from '../components/settings/IntegrationsTab'; -import { LibraryTab } from '../components/settings/LibraryTab'; -import { LyricsTab } from '../components/settings/LyricsTab'; -import { PersonalisationTab } from '../components/settings/PersonalisationTab'; -import { ServersTab } from '../components/settings/ServersTab'; -import { StorageTab } from '../components/settings/StorageTab'; -import { SystemTab } from '../components/settings/SystemTab'; -import { searchSettings, type SettingsSearchHit } from '../components/settings/settingsSearch'; -import { type Tab, resolveTab } from '../components/settings/settingsTabs'; -import { UserManagementSection } from '../components/settings/UserManagementSection'; -import { ndLogin } from '../api/navidromeAdmin'; -import { type ServerMagicPayload } from '../utils/server/serverMagicString'; +import { useAuthStore } from '@/store/authStore'; +import { IS_MACOS } from '@/utils/platform'; +import { AppearanceTab } from '@/features/settings/components/AppearanceTab'; +import { ThemesTab } from '@/features/settings/components/ThemesTab'; +import { AudioTab } from '@/features/settings/components/AudioTab'; +import { InputTab } from '@/features/settings/components/InputTab'; +import { IntegrationsTab } from '@/features/settings/components/IntegrationsTab'; +import { LibraryTab } from '@/features/settings/components/LibraryTab'; +import { LyricsTab } from '@/features/settings/components/LyricsTab'; +import { PersonalisationTab } from '@/features/settings/components/PersonalisationTab'; +import { ServersTab } from '@/features/settings/components/ServersTab'; +import { StorageTab } from '@/features/settings/components/StorageTab'; +import { SystemTab } from '@/features/settings/components/SystemTab'; +import { searchSettings, type SettingsSearchHit } from '@/features/settings/components/settingsSearch'; +import { type Tab, resolveTab } from '@/features/settings/components/settingsTabs'; +import { UserManagementSection } from '@/features/settings/components/UserManagementSection'; +import { ndLogin } from '@/api/navidromeAdmin'; +import { type ServerMagicPayload } from '@/utils/server/serverMagicString'; export default function Settings() { diff --git a/src/utils/export/backup.ts b/src/features/settings/utils/backup.ts similarity index 98% rename from src/utils/export/backup.ts rename to src/features/settings/utils/backup.ts index edaf5589..c7644359 100644 --- a/src/utils/export/backup.ts +++ b/src/features/settings/utils/backup.ts @@ -1,7 +1,7 @@ import { save, open as openDialog } from '@tauri-apps/plugin-dialog'; import { writeFile, readTextFile } from '@tauri-apps/plugin-fs'; import { invoke } from '@tauri-apps/api/core'; -import { version as appVersion } from '../../../package.json'; +import { version as appVersion } from '@/../package.json'; const BACKUP_VERSION = 1; export type ImportedBackupKind = 'config' | 'databases' | 'full'; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 6e5a0d67..7a4f79ac 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -4,7 +4,7 @@ import { Wifi, WifiOff, Eye, EyeOff, Server, Globe } from 'lucide-react'; import { useAuthStore } from '../store/authStore'; import type { CustomHeaderEntry, CustomHeadersApplyTo, ServerProfile } from '../store/authStoreTypes'; import { pingWithCredentialsForProfile, scheduleInstantMixProbeForServer } from '../api/subsonic'; -import { CustomHttpHeadersEditor } from '../components/settings/CustomHttpHeadersEditor'; +import { CustomHttpHeadersEditor } from '@/features/settings'; import { DEFAULT_CUSTOM_HEADERS_APPLY_TO, serverCustomHeadersFromForm,