From a4e73a327ddb262570f54fbc1ea4e27a56c434e2 Mon Sep 17 00:00:00 2001 From: Kevin Frei Date: Sun, 23 Jun 2024 15:41:48 -0700 Subject: [PATCH] Removed all circular dependencies --- src/Recoil/MusicLibrarySchema.ts | 70 ++++++++++++++++++++++++++ src/Recoil/PlaybackOrder.ts | 16 ++++++ src/Recoil/Preferences.ts | 17 +++++++ src/Recoil/ReadOnly.ts | 71 +++++---------------------- src/Recoil/ReadWrite.ts | 31 +----------- src/Recoil/SongPlaying.ts | 2 +- src/Recoil/api.ts | 3 +- src/UI/DetailPanel/MetadataEditor.tsx | 16 +----- src/UI/DetailPanel/MetadataProps.ts | 16 ++++++ src/UI/MenuHandler.ts | 3 +- src/UI/MenuHelpers.ts | 26 ++++++++++ src/UI/PlaybackControls.tsx | 5 +- src/UI/Sidebar.tsx | 2 +- src/UI/SongPlaying.tsx | 3 +- src/UI/Utilities.tsx | 27 +--------- src/UI/Views/NowPlaying.tsx | 2 +- src/UI/Views/Settings.tsx | 10 ++-- 17 files changed, 176 insertions(+), 144 deletions(-) create mode 100644 src/Recoil/MusicLibrarySchema.ts create mode 100644 src/Recoil/PlaybackOrder.ts create mode 100644 src/Recoil/Preferences.ts create mode 100644 src/UI/DetailPanel/MetadataProps.ts create mode 100644 src/UI/MenuHelpers.ts diff --git a/src/Recoil/MusicLibrarySchema.ts b/src/Recoil/MusicLibrarySchema.ts new file mode 100644 index 00000000..90c7cc10 --- /dev/null +++ b/src/Recoil/MusicLibrarySchema.ts @@ -0,0 +1,70 @@ +import { FlatAudioDatabase } from '@freik/audiodb'; +import { + Album, + AlbumKey, + Artist, + ArtistKey, + Song, + SongKey, +} from '@freik/media-core'; +import { + chkArrayOf, + chkObjectOfType, + isArrayOfString, + isNumber, + isString, +} from '@freik/typechk'; + +export type SongMap = Map; +export type AlbumMap = Map; +export type ArtistMap = Map; +export type MusicLibrary = { + songs: SongMap; + albums: AlbumMap; + artists: ArtistMap; +}; + +const isSong = chkObjectOfType( + { + key: isString, + track: isNumber, + title: isString, + albumId: isString, + artistIds: isArrayOfString, + secondaryIds: isArrayOfString, + }, + { path: isString, variations: isArrayOfString }, +); + +const isAlbum = chkObjectOfType( + { + key: isString, + year: isNumber, + title: isString, + primaryArtists: isArrayOfString, + songs: isArrayOfString, + vatype: (o: unknown) => o === '' || o === 'ost' || o === 'va', + }, + { + diskNames: isArrayOfString, + }, +); + +const isArtist = chkObjectOfType({ + key: isString, + name: isString, + albums: isArrayOfString, + songs: isArrayOfString, +}); + +export const isFlatAudioDatabase = chkObjectOfType({ + songs: chkArrayOf(isSong), + albums: chkArrayOf(isAlbum), + artists: chkArrayOf(isArtist), +}); + +export const emptyLibrary = { + songs: new Map(), + albums: new Map(), + artists: new Map(), +}; diff --git a/src/Recoil/PlaybackOrder.ts b/src/Recoil/PlaybackOrder.ts new file mode 100644 index 00000000..93925609 --- /dev/null +++ b/src/Recoil/PlaybackOrder.ts @@ -0,0 +1,16 @@ +import { Effects } from '@freik/electron-render'; +import { atom } from 'recoil'; + +// const log = MakeLogger('ReadWrite'); +// const err = MakeError('ReadWrite-err'); +export const shuffleState = atom({ + key: 'shuffle', + default: false, + effects: [Effects.syncWithMain()], +}); + +export const repeatState = atom({ + key: 'repeat', + default: false, + effects: [Effects.syncWithMain()], +}); diff --git a/src/Recoil/Preferences.ts b/src/Recoil/Preferences.ts new file mode 100644 index 00000000..ef2f899d --- /dev/null +++ b/src/Recoil/Preferences.ts @@ -0,0 +1,17 @@ +import { Effects } from '@freik/electron-render'; +import { atom } from 'recoil'; + +// Only show artists in the list who appear on full albums + +export const showArtistsWithFullAlbumsState = atom({ + key: 'FullAlbumsOnly', + default: false, + effects: [Effects.syncWithMain()], +}); +// The minimum # of songs an artist needs to show up in the artist list + +export const minSongCountForArtistListState = atom({ + key: 'MinSongCount', + default: 1, + effects: [Effects.syncWithMain()], +}); diff --git a/src/Recoil/ReadOnly.ts b/src/Recoil/ReadOnly.ts index 58a95709..ed419e61 100644 --- a/src/Recoil/ReadOnly.ts +++ b/src/Recoil/ReadOnly.ts @@ -12,23 +12,24 @@ import { SongKey, isSongKey, } from '@freik/media-core'; -import { - chkArrayOf, - chkObjectOfType, - hasFieldType, - isArrayOfString, - isNumber, - isString, -} from '@freik/typechk'; +import { hasFieldType, isArrayOfString, isString } from '@freik/typechk'; import { Catch, Fail } from '@freik/web-utils'; import { atom, selector, selectorFamily } from 'recoil'; import { SetDB } from '../MyWindow'; -import { MetadataProps } from '../UI/DetailPanel/MetadataEditor'; +import { MetadataProps } from '../UI/DetailPanel/MetadataProps'; import * as ipc from '../ipc'; +import { + AlbumMap, + ArtistMap, + MusicLibrary, + SongMap, + emptyLibrary, + isFlatAudioDatabase, +} from './MusicLibrarySchema'; import { minSongCountForArtistListState, showArtistsWithFullAlbumsState, -} from './ReadWrite'; +} from './Preferences'; import { songListState } from './SongPlaying'; const { wrn, log } = MakeLog('EMP:render:ReadOnly:log'); @@ -77,56 +78,6 @@ export const picForKeyFam = selectorFamily({ }, }); -type SongMap = Map; -type AlbumMap = Map; -type ArtistMap = Map; -type MusicLibrary = { songs: SongMap; albums: AlbumMap; artists: ArtistMap }; - -const isSong = chkObjectOfType( - { - key: isString, - track: isNumber, - title: isString, - albumId: isString, - artistIds: isArrayOfString, - secondaryIds: isArrayOfString, - }, - { path: isString, variations: isArrayOfString }, -); - -const isAlbum = chkObjectOfType( - { - key: isString, - year: isNumber, - title: isString, - primaryArtists: isArrayOfString, - songs: isArrayOfString, - vatype: (o: unknown) => o === '' || o === 'ost' || o === 'va', - }, - { - diskNames: isArrayOfString, - }, -); - -const isArtist = chkObjectOfType({ - key: isString, - name: isString, - albums: isArrayOfString, - songs: isArrayOfString, -}); - -const isFlatAudioDatabase = chkObjectOfType({ - songs: chkArrayOf(isSong), - albums: chkArrayOf(isAlbum), - artists: chkArrayOf(isArtist), -}); - -const emptyLibrary = { - songs: new Map(), - albums: new Map(), - artists: new Map(), -}; - function MakeMusicLibraryFromFlatAudioDatabase(fad: FlatAudioDatabase) { // For debugging, this is helpful sometimes: SetDB(fad); diff --git a/src/Recoil/ReadWrite.ts b/src/Recoil/ReadWrite.ts index 8f8ddfcc..c73dd2d5 100644 --- a/src/Recoil/ReadWrite.ts +++ b/src/Recoil/ReadWrite.ts @@ -1,7 +1,7 @@ -import { Effects } from '@freik/electron-render'; import { isAlbumKey, isArtistKey, isSongKey, SongKey } from '@freik/media-core'; import { atom, selector, selectorFamily } from 'recoil'; import { ShuffleArray } from '../Tools'; +import { shuffleState } from './PlaybackOrder'; import { maybeAlbumByKeyFuncFam, maybeArtistByKeyFuncFam } from './ReadOnly'; import { currentIndexState, @@ -10,21 +10,6 @@ import { songPlaybackOrderState, } from './SongPlaying'; -// const log = MakeLogger('ReadWrite'); -// const err = MakeError('ReadWrite-err'); - -const shuffleState = atom({ - key: 'shuffle', - default: false, - effects: [Effects.syncWithMain()], -}); - -export const repeatState = atom({ - key: 'repeat', - default: false, - effects: [Effects.syncWithMain()], -}); - // This handles dealing with the playback order along with the state change export const shuffleFunc = selector({ key: 'shuffleFunc', @@ -73,20 +58,6 @@ export const shuffleFunc = selector({ }, }); -// Only show artists in the list who appear on full albums -export const showArtistsWithFullAlbumsState = atom({ - key: 'FullAlbumsOnly', - default: false, - effects: [Effects.syncWithMain()], -}); - -// The minimum # of songs an artist needs to show up in the artist list -export const minSongCountForArtistListState = atom({ - key: 'MinSongCount', - default: 1, - effects: [Effects.syncWithMain()], -}); - // For these things: // n= Track #, r= Artist, l= Album, t= Title, y= Year // Capital = descending, lowercase = ascending diff --git a/src/Recoil/SongPlaying.ts b/src/Recoil/SongPlaying.ts index 1cb212c4..2f53345c 100644 --- a/src/Recoil/SongPlaying.ts +++ b/src/Recoil/SongPlaying.ts @@ -2,7 +2,7 @@ import { Effects } from '@freik/electron-render'; import { SongKey } from '@freik/media-core'; import { isNumber } from '@freik/typechk'; import { atom, selector } from 'recoil'; -import { repeatState } from './ReadWrite'; +import { repeatState } from './PlaybackOrder'; // The position in the active playlist of the current song // For 'ordered' playback, it's the index in the songList diff --git a/src/Recoil/api.ts b/src/Recoil/api.ts index af3b683b..bb2c5eca 100644 --- a/src/Recoil/api.ts +++ b/src/Recoil/api.ts @@ -26,9 +26,10 @@ import { onlyPlayLikesState, songHateFuncFam, } from './Likes'; +import { repeatState } from './PlaybackOrder'; import { playlistFuncFam, playlistNamesFunc } from './PlaylistsState'; import { albumByKeyFuncFam, artistByKeyFuncFam } from './ReadOnly'; -import { repeatState, shuffleFunc } from './ReadWrite'; +import { shuffleFunc } from './ReadWrite'; import { activePlaylistState, currentIndexState, diff --git a/src/UI/DetailPanel/MetadataEditor.tsx b/src/UI/DetailPanel/MetadataEditor.tsx index 3515cdcf..d59f64db 100644 --- a/src/UI/DetailPanel/MetadataEditor.tsx +++ b/src/UI/DetailPanel/MetadataEditor.tsx @@ -34,24 +34,10 @@ import { import { picCacheAvoiderStateFam } from '../../Recoil/cacheAvoider'; import { getAlbumImageUrl } from '../../Tools'; import { SetMediaInfo } from '../../ipc'; +import { MetadataProps } from './MetadataProps'; const { log } = MakeLog('EMP:render:MetadataEditor'); -export type MetadataProps = { - forSong?: SongKey; - forSongs?: SongKey[]; - artist?: string; - album?: string; - track?: string; - title?: string; - year?: string; - va?: string; - variations?: string; - moreArtists?: string; - albumId?: AlbumKey; - diskName?: string; -}; - export function MetadataEditor(props: MetadataProps): JSX.Element { const [artist, setArtist] = useState(false); const [album, setAlbum] = useState(false); diff --git a/src/UI/DetailPanel/MetadataProps.ts b/src/UI/DetailPanel/MetadataProps.ts new file mode 100644 index 00000000..8ef615cb --- /dev/null +++ b/src/UI/DetailPanel/MetadataProps.ts @@ -0,0 +1,16 @@ +import { AlbumKey, SongKey } from '@freik/media-core'; + +export type MetadataProps = { + forSong?: SongKey; + forSongs?: SongKey[]; + artist?: string; + album?: string; + track?: string; + title?: string; + year?: string; + va?: string; + variations?: string; + moreArtists?: string; + albumId?: AlbumKey; + diskName?: string; +}; diff --git a/src/UI/MenuHandler.ts b/src/UI/MenuHandler.ts index 89c195be..90f2cf15 100644 --- a/src/UI/MenuHandler.ts +++ b/src/UI/MenuHandler.ts @@ -8,8 +8,9 @@ import { mediaTimePercentFunc, mediaTimeState } from '../Jotai/MediaPlaying'; import { mutedState, volumeState } from '../Jotai/SimpleSettings'; import { MyStore, getStore } from '../Jotai/Storage'; import { FocusSearch } from '../MyWindow'; +import { repeatState } from '../Recoil/PlaybackOrder'; import { playlistFuncFam } from '../Recoil/PlaylistsState'; -import { repeatState, shuffleFunc } from '../Recoil/ReadWrite'; +import { shuffleFunc } from '../Recoil/ReadWrite'; import { activePlaylistState, songListState } from '../Recoil/SongPlaying'; import { MaybePlayNext, MaybePlayPrev } from '../Recoil/api'; import { onClickPlayPause } from './PlaybackControls'; diff --git a/src/UI/MenuHelpers.ts b/src/UI/MenuHelpers.ts new file mode 100644 index 00000000..05fe2d1b --- /dev/null +++ b/src/UI/MenuHelpers.ts @@ -0,0 +1,26 @@ +import { Keys } from '@freik/emp-shared'; + +const HostOs: 'mac' | 'windows' | 'linux' = (() => { + const ua = window.navigator.userAgent; + if (ua.indexOf('Mac') >= 0) { + return 'mac'; + } + if (ua.indexOf('Windows') >= 0) { + return 'windows'; + } + return 'linux'; +})(); + +const accPrefix = HostOs === 'mac' ? '⌘' : 'Ctrl'; + +export function GetHelperText(key: Keys) { + if (key.length === 1) { + return `${accPrefix}-${key}`; + } + if (key === Keys.PreviousTrack) { + return accPrefix + '-←'; + } + if (key === Keys.NextTrack) { + return accPrefix + '-→'; + } +} diff --git a/src/UI/PlaybackControls.tsx b/src/UI/PlaybackControls.tsx index 86d6ad6b..6e2b800c 100644 --- a/src/UI/PlaybackControls.tsx +++ b/src/UI/PlaybackControls.tsx @@ -6,7 +6,8 @@ import { ForwardedRef, MouseEventHandler } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { playingState } from '../Jotai/MediaPlaying'; import { MyStore } from '../Jotai/Storage'; -import { repeatState, shuffleFunc } from '../Recoil/ReadWrite'; +import { repeatState } from '../Recoil/PlaybackOrder'; +import { shuffleFunc } from '../Recoil/ReadWrite'; import { hasAnySongsFunc, hasNextSongFunc, @@ -14,7 +15,7 @@ import { } from '../Recoil/SongPlaying'; import { MaybePlayNext, MaybePlayPrev } from '../Recoil/api'; import { isMutableRefObject } from '../Tools'; -import { GetHelperText } from './Utilities'; +import { GetHelperText } from './MenuHelpers'; import './styles/PlaybackControls.css'; const { log, wrn } = MakeLog('EMP:render:SongControls'); diff --git a/src/UI/Sidebar.tsx b/src/UI/Sidebar.tsx index 771b07ac..7623b2ee 100644 --- a/src/UI/Sidebar.tsx +++ b/src/UI/Sidebar.tsx @@ -6,8 +6,8 @@ import { useRecoilCallback } from 'recoil'; import { curViewFunc } from '../Jotai/CurrentView'; import { SetSearch, isHostMac } from '../MyWindow'; import { searchTermState } from '../Recoil/ReadOnly'; +import { GetHelperText } from './MenuHelpers'; import { Notifier } from './Notifier'; -import { GetHelperText } from './Utilities'; import './styles/Sidebar.css'; type ViewEntry = { name: CurrentView; title: StrId; accelerator: Keys }; diff --git a/src/UI/SongPlaying.tsx b/src/UI/SongPlaying.tsx index a7c0c810..864b895d 100644 --- a/src/UI/SongPlaying.tsx +++ b/src/UI/SongPlaying.tsx @@ -22,6 +22,7 @@ import { playingState, } from '../Jotai/MediaPlaying'; import { mutedState, volumeState } from '../Jotai/SimpleSettings'; +import { repeatState } from '../Recoil/PlaybackOrder'; import { SongDescription, albumKeyForSongKeyFuncFam, @@ -29,7 +30,7 @@ import { dataForSongFuncFam, picForKeyFam, } from '../Recoil/ReadOnly'; -import { repeatState, shuffleFunc } from '../Recoil/ReadWrite'; +import { shuffleFunc } from '../Recoil/ReadWrite'; import { currentSongKeyFunc, songListState } from '../Recoil/SongPlaying'; import { MaybePlayNext } from '../Recoil/api'; import { getAlbumImageUrl, isMutableRefObject } from '../Tools'; diff --git a/src/UI/Utilities.tsx b/src/UI/Utilities.tsx index 2bc9332d..e751dcd7 100644 --- a/src/UI/Utilities.tsx +++ b/src/UI/Utilities.tsx @@ -7,7 +7,7 @@ import { SpinButton, } from '@fluentui/react'; import { Ipc, useListener, useMediaEffect } from '@freik/electron-render'; -import { IpcId, Keys } from '@freik/emp-shared'; +import { IpcId } from '@freik/emp-shared'; import { MakeLog } from '@freik/logger'; import { DebouncedDelay } from '@freik/sync'; import { isNumber, isUndefined } from '@freik/typechk'; @@ -141,31 +141,6 @@ export const mySliderStyles: Partial = { }, }; -export const HostOs: 'mac' | 'windows' | 'linux' = (() => { - const ua = window.navigator.userAgent; - if (ua.indexOf('Mac') >= 0) { - return 'mac'; - } - if (ua.indexOf('Windows') >= 0) { - return 'windows'; - } - return 'linux'; -})(); - -const accPrefix = HostOs === 'mac' ? '⌘' : 'Ctrl'; - -export function GetHelperText(key: Keys) { - if (key.length === 1) { - return `${accPrefix}-${key}`; - } - if (key === Keys.PreviousTrack) { - return accPrefix + '-←'; - } - if (key === Keys.NextTrack) { - return accPrefix + '-→'; - } -} - export function useRecoilBoolState(st: RecoilState): BoolState { const [value, setter] = useRecoilState(st); return [value, () => setter(true), () => setter(false)]; diff --git a/src/UI/Views/NowPlaying.tsx b/src/UI/Views/NowPlaying.tsx index 52074080..cee7a968 100644 --- a/src/UI/Views/NowPlaying.tsx +++ b/src/UI/Views/NowPlaying.tsx @@ -54,6 +54,7 @@ import { import { RemoveSongFromNowPlaying, StopAndClear } from '../../Recoil/api'; import { SortKey, SortSongList } from '../../Sorting'; import { isPlaylist } from '../../Tools'; +import { GetHelperText } from '../MenuHelpers'; import { AlbumForSongRender, ArtistsForSongRender, @@ -61,7 +62,6 @@ import { } from '../SimpleTags'; import { MakeColumns, altRowRenderer } from '../SongList'; import { SongListMenu, SongListMenuData } from '../SongMenus'; -import { GetHelperText } from '../Utilities'; import { LikeOrHate } from './MixedSongs'; import './styles/NowPlaying.css'; diff --git a/src/UI/Views/Settings.tsx b/src/UI/Views/Settings.tsx index bbc409fa..c2e085fb 100644 --- a/src/UI/Views/Settings.tsx +++ b/src/UI/Views/Settings.tsx @@ -38,17 +38,17 @@ import { saveAlbumArtworkWithMusicState, } from '../../Jotai/SimpleSettings'; import { neverPlayHatesState, onlyPlayLikesState } from '../../Recoil/Likes'; +import { + minSongCountForArtistListState, + showArtistsWithFullAlbumsState, +} from '../../Recoil/Preferences'; import { allAlbumsFunc, allArtistsFunc, allSongsFunc, ignoreItemsState, } from '../../Recoil/ReadOnly'; -import { - minSongCountForArtistListState, - showArtistsWithFullAlbumsState, -} from '../../Recoil/ReadWrite'; -import { GetHelperText } from '../Utilities'; +import { GetHelperText } from '../MenuHelpers'; import './styles/Settings.css'; const btnWidth: React.CSSProperties = { width: '155px', padding: 0 };