diff --git a/src/components/settings/Config.tsx b/src/components/settings/Config.tsx index 1620412..ac22c86 100644 --- a/src/components/settings/Config.tsx +++ b/src/components/settings/Config.tsx @@ -1,106 +1,21 @@ import React, { useEffect, useState } from 'react'; -import fs from 'fs'; -import path from 'path'; -import settings from 'electron-settings'; -import { - Button, - ControlLabel, - Tag, - Nav, - Icon, - InputGroup, - Message, - Whisper, - Popover, - RadioGroup, -} from 'rsuite'; -import { ConfigPanel } from './styled'; +import { Button, Whisper, Popover } from 'rsuite'; import { startScan, getScanStatus } from '../../api/api'; import GenericPage from '../layout/GenericPage'; import DisconnectButton from './DisconnectButton'; import GenericPageHeader from '../layout/GenericPageHeader'; -import ListViewConfig from './ListViewConfig'; -import { - songColumnList, - songColumnPicker, - albumColumnList, - albumColumnPicker, - playlistColumnList, - playlistColumnPicker, -} from './ListViewColumns'; -import { getImageCachePath, getSongCachePath } from '../../shared/utils'; import setDefaultSettings from '../shared/setDefaultSettings'; -import { - HeaderButton, - StyledCheckbox, - StyledInput, - StyledInputNumber, - StyledInputPicker, - StyledNavItem, - StyledRadio, -} from '../shared/styled'; -import { useAppDispatch, useAppSelector } from '../../redux/hooks'; -import { - setPlaybackSetting, - setPlayerVolume, -} from '../../redux/playQueueSlice'; -import { Fonts } from './Fonts'; -import { setFont, setTheme } from '../../redux/miscSlice'; - -const fsUtils = require('nodejs-fs-utils'); +import { HeaderButton } from '../shared/styled'; +import PlaybackConfig from './ConfigPanels/PlaybackConfig'; +import LookAndFeelConfig from './ConfigPanels/LookAndFeelConfig'; +import PlayerConfig from './ConfigPanels/PlayerConfig'; +import CacheConfig from './ConfigPanels/CacheConfig'; +import DebugConfig from './ConfigPanels/DebugConfig'; const Config = () => { - const dispatch = useAppDispatch(); - const playQueue = useAppSelector((state) => state.playQueue); const [isScanning, setIsScanning] = useState(false); const [scanProgress, setScanProgress] = useState(0); - const [imgCacheSize, setImgCacheSize] = useState(0); - const [songCacheSize, setSongCacheSize] = useState(0); - const [currentLAFTab, setCurrentLAFTab] = useState('songList'); - const [isEditingCachePath, setIsEditingCachePath] = useState(false); - const [newCachePath, setNewCachePath] = useState(''); - const [errorMessage, setErrorMessage] = useState(''); const [requiresReload] = useState(false); - const [cacheSongs, setCacheSongs] = useState( - Boolean(settings.getSync('cacheSongs')) - ); - const [cacheImages, setCacheImages] = useState( - Boolean(settings.getSync('cacheImages')) - ); - const [showDebugWindow, setShowDebugWindow] = useState( - Boolean(settings.getSync('showDebugWindow')) - ); - - const songCols: any = settings.getSync('songListColumns'); - const albumCols: any = settings.getSync('albumListColumns'); - const playlistCols: any = settings.getSync('playlistListColumns'); - const miniCols: any = settings.getSync('miniListColumns'); - const currentSongColumns = songCols?.map((column: any) => column.label) || []; - const currentAlbumColumns = - albumCols?.map((column: any) => column.label) || []; - const currentPlaylistColumns = - playlistCols?.map((column: any) => column.label) || []; - const currentMiniColumns = miniCols?.map((column: any) => column.label) || []; - - useEffect(() => { - // Retrieve cache sizes on render - try { - setImgCacheSize( - Number( - (fsUtils.fsizeSync(getImageCachePath()) / 1000 / 1000).toFixed(0) - ) - ); - - setSongCacheSize( - Number((fsUtils.fsizeSync(getSongCachePath()) / 1000 / 1000).toFixed(0)) - ); - } catch (err) { - setImgCacheSize(0); - setSongCacheSize(0); - fs.mkdirSync(getSongCachePath(), { recursive: true }); - fs.mkdirSync(getImageCachePath(), { recursive: true }); - } - }, []); useEffect(() => { // Check scan status on render @@ -194,366 +109,11 @@ const Config = () => { /> } > - -

- Fading works by polling the audio player on an interval to determine - when to start fading to the next track. Due to this, you may notice - the fade timing may not be 100% perfect. Lowering the player polling - interval can increase the accuracy of the fade, but may also decrease - application performance as calculations are running for the fade. -

- -

- If volume fade is disabled, then the fading-in track will start at the - specified crossfade duration at full volume. -

- -

- Setting the crossfade duration to 0 will enable{' '} - gapless playback. All other playback settings except - the polling interval will be ignored. It is recommended that you use a - polling interval between 1 and 20 for - increased transition accuracy. -

-

- *Enable the debug window if you want to view the differences between - each fade type -

- -
- Crossfade duration (s) - { - settings.setSync('fadeDuration', Number(e)); - dispatch( - setPlaybackSetting({ - setting: 'fadeDuration', - value: Number(e), - }) - ); - - if (Number(e) === 0) { - dispatch( - setPlayerVolume({ player: 1, volume: playQueue.volume }) - ); - dispatch( - setPlayerVolume({ player: 2, volume: playQueue.volume }) - ); - } - }} - /> - -
- Polling interval (ms) - { - settings.setSync('pollingInterval', Number(e)); - dispatch( - setPlaybackSetting({ - setting: 'pollingInterval', - value: Number(e), - }) - ); - }} - /> -
- Crossfade type - { - settings.setSync('fadeType', e); - dispatch(setPlaybackSetting({ setting: 'fadeType', value: e })); - }} - > - Equal Power - Linear - Dipped - Constant Power - - Constant Power (slow fade) - - - Constant Power (slow cut) - - - Constant Power (fast cut) - - -
- Volume fade - { - settings.setSync('volumeFade', e); - dispatch(setPlaybackSetting({ setting: 'volumeFade', value: e })); - }} - > - Enabled - Disabled - -
-
- -
-

Select the main application theme.

- { - settings.setSync('theme', e); - dispatch(setTheme(e)); - }} - > - Default Dark - Default Light - -
- Font -
- { - settings.setSync('font', e); - dispatch(setFont(e)); - }} - /> -
-
- -

- Select the columns you want displayed on pages with a list-view. -

- - {currentLAFTab === 'songList' && ( - - )} - - {currentLAFTab === 'albumList' && ( - - )} - - {currentLAFTab === 'playlistList' && ( - - )} - {currentLAFTab === 'miniList' && ( - - )} -
-
- -

- Configure the number of seconds to skip forwards/backwards by when - clicking the seek forward/backward buttons. -

-
- Seek forward (s) - { - settings.setSync('seekForwardInterval', Number(e)); - }} - /> -
- Seek backward (s) - { - settings.setSync('seekBackwardInterval', Number(e)); - }} - /> -
- - {errorMessage !== '' && ( - <> - -
- - )} -

- Songs are cached only when playback for the track fully completes and - ends. Skipping to the next or previous track after only partially - completing the track will not begin the caching process. -

-
- {isEditingCachePath && ( - <> - - setNewCachePath(e)} - /> - { - const check = fs.existsSync(newCachePath); - if (check) { - settings.setSync('cachePath', newCachePath); - fs.mkdirSync(getSongCachePath(), { recursive: true }); - fs.mkdirSync(getImageCachePath(), { recursive: true }); - setErrorMessage(''); - return setIsEditingCachePath(false); - } - - return setErrorMessage( - `Path: ${newCachePath} not found. Enter a valid path.` - ); - }} - > - - - { - setIsEditingCachePath(false); - setErrorMessage(''); - }} - > - - - -

- *You will need to manually move any existing cached files to their - new location. -

- - )} - {!isEditingCachePath && ( -
- Location:{' '} - - {path.join( - String(settings.getSync('cachePath')), - 'sonixdCache', - String(localStorage.getItem('serverBase64')) - )} - -
- )} -
- { - settings.setSync('cacheSongs', !settings.getSync('cacheSongs')); - setCacheSongs(!cacheSongs); - }} - > - Songs{' '} - - {songCacheSize} MB{' '} - {imgCacheSize === 9999999 && '- Folder not found'} - - - { - settings.setSync('cacheImages', !settings.getSync('cacheImages')); - setCacheImages(!cacheImages); - }} - > - Images{' '} - - {imgCacheSize} MB{' '} - {imgCacheSize === 9999999 && '- Folder not found'} - - -
- -
-
- - { - settings.setSync( - 'showDebugWindow', - !settings.getSync('showDebugWindow') - ); - dispatch( - setPlaybackSetting({ - setting: 'showDebugWindow', - value: settings.getSync('showDebugWindow'), - }) - ); - setShowDebugWindow(!showDebugWindow); - }} - > - Show debug window - - + + + + + ); }; diff --git a/src/components/settings/ConfigPanels/CacheConfig.tsx b/src/components/settings/ConfigPanels/CacheConfig.tsx new file mode 100644 index 0000000..74122b7 --- /dev/null +++ b/src/components/settings/ConfigPanels/CacheConfig.tsx @@ -0,0 +1,146 @@ +import React, { useState, useEffect } from 'react'; +import settings from 'electron-settings'; +import fs from 'fs'; +import path from 'path'; +import { InputGroup, Button, Tag, Message, Icon } from 'rsuite'; +import { ConfigPanel } from '../styled'; +import { StyledInput, StyledCheckbox } from '../../shared/styled'; +import { getSongCachePath, getImageCachePath } from '../../../shared/utils'; + +const fsUtils = require('nodejs-fs-utils'); + +const CacheConfig = () => { + const [imgCacheSize, setImgCacheSize] = useState(0); + const [songCacheSize, setSongCacheSize] = useState(0); + const [isEditingCachePath, setIsEditingCachePath] = useState(false); + const [newCachePath, setNewCachePath] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [cacheSongs, setCacheSongs] = useState( + Boolean(settings.getSync('cacheSongs')) + ); + const [cacheImages, setCacheImages] = useState( + Boolean(settings.getSync('cacheImages')) + ); + + useEffect(() => { + // Retrieve cache sizes on render + try { + setImgCacheSize( + Number( + (fsUtils.fsizeSync(getImageCachePath()) / 1000 / 1000).toFixed(0) + ) + ); + + setSongCacheSize( + Number((fsUtils.fsizeSync(getSongCachePath()) / 1000 / 1000).toFixed(0)) + ); + } catch (err) { + setImgCacheSize(0); + setSongCacheSize(0); + fs.mkdirSync(getSongCachePath(), { recursive: true }); + fs.mkdirSync(getImageCachePath(), { recursive: true }); + } + }, []); + + return ( + + {errorMessage !== '' && ( + <> + +
+ + )} +

+ Songs are cached only when playback for the track fully completes and + ends. Skipping to the next or previous track after only partially + completing the track will not begin the caching process. +

+
+ {isEditingCachePath && ( + <> + + setNewCachePath(e)} + /> + { + const check = fs.existsSync(newCachePath); + if (check) { + settings.setSync('cachePath', newCachePath); + fs.mkdirSync(getSongCachePath(), { recursive: true }); + fs.mkdirSync(getImageCachePath(), { recursive: true }); + setErrorMessage(''); + return setIsEditingCachePath(false); + } + + return setErrorMessage( + `Path: ${newCachePath} not found. Enter a valid path.` + ); + }} + > + + + { + setIsEditingCachePath(false); + setErrorMessage(''); + }} + > + + + +

+ *You will need to manually move any existing cached files to their + new location. +

+ + )} + {!isEditingCachePath && ( +
+ Location:{' '} + + {path.join( + String(settings.getSync('cachePath')), + 'sonixdCache', + String(localStorage.getItem('serverBase64')) + )} + +
+ )} +
+ { + settings.setSync('cacheSongs', !settings.getSync('cacheSongs')); + setCacheSongs(!cacheSongs); + }} + > + Songs{' '} + + {songCacheSize} MB{' '} + {imgCacheSize === 9999999 && '- Folder not found'} + + + { + settings.setSync('cacheImages', !settings.getSync('cacheImages')); + setCacheImages(!cacheImages); + }} + > + Images{' '} + + {imgCacheSize} MB {imgCacheSize === 9999999 && '- Folder not found'} + + +
+ +
+
+ ); +}; + +export default CacheConfig; diff --git a/src/components/settings/ConfigPanels/DebugConfig.tsx b/src/components/settings/ConfigPanels/DebugConfig.tsx new file mode 100644 index 0000000..a37a803 --- /dev/null +++ b/src/components/settings/ConfigPanels/DebugConfig.tsx @@ -0,0 +1,37 @@ +import React, { useState } from 'react'; +import settings from 'electron-settings'; +import { ConfigPanel } from '../styled'; +import { StyledCheckbox } from '../../shared/styled'; +import { useAppDispatch } from '../../../redux/hooks'; +import { setPlaybackSetting } from '../../../redux/playQueueSlice'; + +const DebugConfig = () => { + const dispatch = useAppDispatch(); + const [showDebugWindow, setShowDebugWindow] = useState( + Boolean(settings.getSync('showDebugWindow')) + ); + return ( + + { + settings.setSync( + 'showDebugWindow', + !settings.getSync('showDebugWindow') + ); + dispatch( + setPlaybackSetting({ + setting: 'showDebugWindow', + value: settings.getSync('showDebugWindow'), + }) + ); + setShowDebugWindow(!showDebugWindow); + }} + > + Show debug window + + + ); +}; + +export default DebugConfig; diff --git a/src/components/settings/ListViewConfig.tsx b/src/components/settings/ConfigPanels/ListViewConfig.tsx similarity index 97% rename from src/components/settings/ListViewConfig.tsx rename to src/components/settings/ConfigPanels/ListViewConfig.tsx index b96abf6..2de310f 100644 --- a/src/components/settings/ListViewConfig.tsx +++ b/src/components/settings/ConfigPanels/ListViewConfig.tsx @@ -1,7 +1,7 @@ import React from 'react'; import settings from 'electron-settings'; import { TagPicker, ControlLabel } from 'rsuite'; -import { StyledInputNumber } from '../shared/styled'; +import { StyledInputNumber } from '../../shared/styled'; const ListViewConfig = ({ defaultColumns, diff --git a/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx b/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx new file mode 100644 index 0000000..6e2e948 --- /dev/null +++ b/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx @@ -0,0 +1,139 @@ +import React, { useState } from 'react'; +import settings from 'electron-settings'; +import { RadioGroup, ControlLabel, Nav } from 'rsuite'; +import { ConfigPanel } from '../styled'; +import { + StyledRadio, + StyledInputPicker, + StyledNavItem, +} from '../../shared/styled'; +import ListViewConfig from './ListViewConfig'; +import { Fonts } from '../Fonts'; +import { useAppDispatch } from '../../../redux/hooks'; +import { setTheme, setFont } from '../../../redux/miscSlice'; +import { + songColumnPicker, + songColumnList, + albumColumnPicker, + albumColumnList, + playlistColumnPicker, + playlistColumnList, +} from '../ListViewColumns'; + +const LookAndFeelConfig = () => { + const dispatch = useAppDispatch(); + const [currentLAFTab, setCurrentLAFTab] = useState('songList'); + + const songCols: any = settings.getSync('songListColumns'); + const albumCols: any = settings.getSync('albumListColumns'); + const playlistCols: any = settings.getSync('playlistListColumns'); + const miniCols: any = settings.getSync('miniListColumns'); + const currentSongColumns = songCols?.map((column: any) => column.label) || []; + const currentAlbumColumns = + albumCols?.map((column: any) => column.label) || []; + const currentPlaylistColumns = + playlistCols?.map((column: any) => column.label) || []; + const currentMiniColumns = miniCols?.map((column: any) => column.label) || []; + + return ( + +
+

Select the main application theme.

+ { + settings.setSync('theme', e); + dispatch(setTheme(e)); + }} + > + Default Dark + Default Light + +
+ Font +
+ { + settings.setSync('font', e); + dispatch(setFont(e)); + }} + /> +
+
+ +

Select the columns you want displayed on pages with a list-view.

+ + {currentLAFTab === 'songList' && ( + + )} + + {currentLAFTab === 'albumList' && ( + + )} + + {currentLAFTab === 'playlistList' && ( + + )} + {currentLAFTab === 'miniList' && ( + + )} +
+
+ ); +}; + +export default LookAndFeelConfig; diff --git a/src/components/settings/ConfigPanels/PlaybackConfig.tsx b/src/components/settings/ConfigPanels/PlaybackConfig.tsx new file mode 100644 index 0000000..e20afcd --- /dev/null +++ b/src/components/settings/ConfigPanels/PlaybackConfig.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import settings from 'electron-settings'; +import { ControlLabel, RadioGroup } from 'rsuite'; +import { ConfigPanel } from '../styled'; +import { StyledInputNumber, StyledRadio } from '../../shared/styled'; +import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; +import { + setPlaybackSetting, + setPlayerVolume, +} from '../../../redux/playQueueSlice'; + +const PlaybackConfig = () => { + const dispatch = useAppDispatch(); + const playQueue = useAppSelector((state) => state.playQueue); + return ( + +

+ Fading works by polling the audio player on an interval to determine + when to start fading to the next track. Due to this, you may notice the + fade timing may not be 100% perfect. Lowering the player polling + interval can increase the accuracy of the fade, but may also decrease + application performance as calculations are running for the fade. +

+ +

+ If volume fade is disabled, then the fading-in track will start at the + specified crossfade duration at full volume. +

+ +

+ Setting the crossfade duration to 0 will enable{' '} + gapless playback. All other playback settings except + the polling interval will be ignored. It is recommended that you use a + polling interval between 1 and 20 for + increased transition accuracy. +

+

+ *Enable the debug window if you want to view the differences between + each fade type +

+ +
+ Crossfade duration (s) + { + settings.setSync('fadeDuration', Number(e)); + dispatch( + setPlaybackSetting({ + setting: 'fadeDuration', + value: Number(e), + }) + ); + + if (Number(e) === 0) { + dispatch( + setPlayerVolume({ player: 1, volume: playQueue.volume }) + ); + dispatch( + setPlayerVolume({ player: 2, volume: playQueue.volume }) + ); + } + }} + /> + +
+ Polling interval (ms) + { + settings.setSync('pollingInterval', Number(e)); + dispatch( + setPlaybackSetting({ + setting: 'pollingInterval', + value: Number(e), + }) + ); + }} + /> +
+ Crossfade type + { + settings.setSync('fadeType', e); + dispatch(setPlaybackSetting({ setting: 'fadeType', value: e })); + }} + > + Equal Power + Linear + Dipped + Constant Power + + Constant Power (slow fade) + + + Constant Power (slow cut) + + + Constant Power (fast cut) + + +
+ Volume fade + { + settings.setSync('volumeFade', e); + dispatch(setPlaybackSetting({ setting: 'volumeFade', value: e })); + }} + > + Enabled + Disabled + +
+
+ ); +}; + +export default PlaybackConfig; diff --git a/src/components/settings/ConfigPanels/PlayerConfig.tsx b/src/components/settings/ConfigPanels/PlayerConfig.tsx new file mode 100644 index 0000000..304c388 --- /dev/null +++ b/src/components/settings/ConfigPanels/PlayerConfig.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import settings from 'electron-settings'; +import { ControlLabel } from 'rsuite'; +import { ConfigPanel } from '../styled'; +import { StyledInputNumber } from '../../shared/styled'; + +const PlayerConfig = () => { + return ( + +

+ Configure the number of seconds to skip forwards/backwards by when + clicking the seek forward/backward buttons. +

+
+ Seek forward (s) + { + settings.setSync('seekForwardInterval', Number(e)); + }} + /> +
+ Seek backward (s) + { + settings.setSync('seekBackwardInterval', Number(e)); + }} + /> +
+ ); +}; + +export default PlayerConfig;