mirror of
https://github.com/jeffvli/sonixd.git
synced 2026-05-01 19:52:36 -04:00
split config panels out
This commit is contained in:
@@ -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 = () => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ConfigPanel header="Playback" bordered>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If volume fade is disabled, then the fading-in track will start at the
|
||||
specified crossfade duration at full volume.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Setting the crossfade duration to <code>0</code> will enable{' '}
|
||||
<strong>gapless playback</strong>. All other playback settings except
|
||||
the polling interval will be ignored. It is recommended that you use a
|
||||
polling interval between <code>1</code> and <code>20</code> for
|
||||
increased transition accuracy.
|
||||
</p>
|
||||
<p style={{ fontSize: 'smaller' }}>
|
||||
*Enable the debug window if you want to view the differences between
|
||||
each fade type
|
||||
</p>
|
||||
|
||||
<div style={{ width: '300px', paddingTop: '20px' }}>
|
||||
<ControlLabel>Crossfade duration (s)</ControlLabel>
|
||||
<StyledInputNumber
|
||||
defaultValue={String(settings.getSync('fadeDuration')) || '0'}
|
||||
step={0.05}
|
||||
min={0}
|
||||
max={100}
|
||||
width={150}
|
||||
onChange={(e: any) => {
|
||||
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 })
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ControlLabel>Polling interval (ms)</ControlLabel>
|
||||
<StyledInputNumber
|
||||
defaultValue={String(settings.getSync('pollingInterval'))}
|
||||
step={1}
|
||||
min={1}
|
||||
max={1000}
|
||||
width={150}
|
||||
onChange={(e: any) => {
|
||||
settings.setSync('pollingInterval', Number(e));
|
||||
dispatch(
|
||||
setPlaybackSetting({
|
||||
setting: 'pollingInterval',
|
||||
value: Number(e),
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<ControlLabel>Crossfade type</ControlLabel>
|
||||
<RadioGroup
|
||||
name="fadeTypeRadioList"
|
||||
appearance="default"
|
||||
defaultValue={String(settings.getSync('fadeType'))}
|
||||
onChange={(e) => {
|
||||
settings.setSync('fadeType', e);
|
||||
dispatch(setPlaybackSetting({ setting: 'fadeType', value: e }));
|
||||
}}
|
||||
>
|
||||
<StyledRadio value="equalPower">Equal Power</StyledRadio>
|
||||
<StyledRadio value="linear">Linear</StyledRadio>
|
||||
<StyledRadio value="dipped">Dipped</StyledRadio>
|
||||
<StyledRadio value="constantPower">Constant Power</StyledRadio>
|
||||
<StyledRadio value="constantPowerSlowFade">
|
||||
Constant Power (slow fade)
|
||||
</StyledRadio>
|
||||
<StyledRadio value="constantPowerSlowCut">
|
||||
Constant Power (slow cut)
|
||||
</StyledRadio>
|
||||
<StyledRadio value="constantPowerFastCut">
|
||||
Constant Power (fast cut)
|
||||
</StyledRadio>
|
||||
</RadioGroup>
|
||||
<br />
|
||||
<ControlLabel>Volume fade</ControlLabel>
|
||||
<RadioGroup
|
||||
name="volumeFadeRadioList"
|
||||
appearance="default"
|
||||
defaultValue={Boolean(settings.getSync('volumeFade'))}
|
||||
onChange={(e) => {
|
||||
settings.setSync('volumeFade', e);
|
||||
dispatch(setPlaybackSetting({ setting: 'volumeFade', value: e }));
|
||||
}}
|
||||
>
|
||||
<StyledRadio value>Enabled</StyledRadio>
|
||||
<StyledRadio value={false}>Disabled</StyledRadio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</ConfigPanel>
|
||||
<ConfigPanel header="Look & Feel" bordered>
|
||||
<div style={{ width: '300px' }}>
|
||||
<p>Select the main application theme.</p>
|
||||
<RadioGroup
|
||||
name="themeRadioList"
|
||||
appearance="default"
|
||||
defaultValue={String(settings.getSync('theme'))}
|
||||
onChange={(e) => {
|
||||
settings.setSync('theme', e);
|
||||
dispatch(setTheme(e));
|
||||
}}
|
||||
>
|
||||
<StyledRadio value="defaultDark">Default Dark</StyledRadio>
|
||||
<StyledRadio value="defaultLight">Default Light</StyledRadio>
|
||||
</RadioGroup>
|
||||
<br />
|
||||
<ControlLabel>Font</ControlLabel>
|
||||
<br />
|
||||
<StyledInputPicker
|
||||
data={Fonts}
|
||||
groupBy="role"
|
||||
defaultValue={String(settings.getSync('font'))}
|
||||
onChange={(e: string) => {
|
||||
settings.setSync('font', e);
|
||||
dispatch(setFont(e));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<ConfigPanel bordered>
|
||||
<p>
|
||||
Select the columns you want displayed on pages with a list-view.
|
||||
</p>
|
||||
<Nav
|
||||
style={{ paddingTop: '10px' }}
|
||||
activeKey={currentLAFTab}
|
||||
onSelect={(e) => setCurrentLAFTab(e)}
|
||||
>
|
||||
<StyledNavItem eventKey="songList">Song List</StyledNavItem>
|
||||
<StyledNavItem eventKey="albumList">Album List</StyledNavItem>
|
||||
<StyledNavItem eventKey="playlistList">Playlist List</StyledNavItem>
|
||||
<StyledNavItem eventKey="miniList">Miniplayer List</StyledNavItem>
|
||||
</Nav>
|
||||
{currentLAFTab === 'songList' && (
|
||||
<ListViewConfig
|
||||
title="Song List"
|
||||
defaultColumns={currentSongColumns}
|
||||
columnPicker={songColumnPicker}
|
||||
columnList={songColumnList}
|
||||
settingsConfig={{
|
||||
columnList: 'songListColumns',
|
||||
rowHeight: 'songListRowHeight',
|
||||
fontSize: 'songListFontSize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentLAFTab === 'albumList' && (
|
||||
<ListViewConfig
|
||||
title="Album List"
|
||||
defaultColumns={currentAlbumColumns}
|
||||
columnPicker={albumColumnPicker}
|
||||
columnList={albumColumnList}
|
||||
settingsConfig={{
|
||||
columnList: 'albumListColumns',
|
||||
rowHeight: 'albumListRowHeight',
|
||||
fontSize: 'albumListFontSize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentLAFTab === 'playlistList' && (
|
||||
<ListViewConfig
|
||||
title="Playlist List"
|
||||
defaultColumns={currentPlaylistColumns}
|
||||
columnPicker={playlistColumnPicker}
|
||||
columnList={playlistColumnList}
|
||||
settingsConfig={{
|
||||
columnList: 'playlistListColumns',
|
||||
rowHeight: 'playlistListRowHeight',
|
||||
fontSize: 'playlistListFontSize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{currentLAFTab === 'miniList' && (
|
||||
<ListViewConfig
|
||||
title="Miniplayer List"
|
||||
defaultColumns={currentMiniColumns}
|
||||
columnPicker={songColumnPicker}
|
||||
columnList={songColumnList}
|
||||
settingsConfig={{
|
||||
columnList: 'miniListColumns',
|
||||
rowHeight: 'miniListRowHeight',
|
||||
fontSize: 'miniListFontSize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ConfigPanel>
|
||||
</ConfigPanel>
|
||||
<ConfigPanel header="Player" bordered>
|
||||
<p>
|
||||
Configure the number of seconds to skip forwards/backwards by when
|
||||
clicking the seek forward/backward buttons.
|
||||
</p>
|
||||
<br />
|
||||
<ControlLabel>Seek forward (s)</ControlLabel>
|
||||
<StyledInputNumber
|
||||
defaultValue={String(settings.getSync('seekForwardInterval')) || '0'}
|
||||
step={0.5}
|
||||
min={0}
|
||||
max={100}
|
||||
width={150}
|
||||
onChange={(e: any) => {
|
||||
settings.setSync('seekForwardInterval', Number(e));
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<ControlLabel>Seek backward (s)</ControlLabel>
|
||||
<StyledInputNumber
|
||||
defaultValue={String(settings.getSync('seekBackwardInterval')) || '0'}
|
||||
step={0.5}
|
||||
min={0}
|
||||
max={100}
|
||||
width={150}
|
||||
onChange={(e: any) => {
|
||||
settings.setSync('seekBackwardInterval', Number(e));
|
||||
}}
|
||||
/>
|
||||
</ConfigPanel>
|
||||
<ConfigPanel header="Cache" bordered>
|
||||
{errorMessage !== '' && (
|
||||
<>
|
||||
<Message showIcon type="error" description={errorMessage} />
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<br />
|
||||
{isEditingCachePath && (
|
||||
<>
|
||||
<InputGroup>
|
||||
<StyledInput
|
||||
value={newCachePath}
|
||||
onChange={(e: string) => setNewCachePath(e)}
|
||||
/>
|
||||
<InputGroup.Button
|
||||
onClick={() => {
|
||||
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.`
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Icon icon="check" />
|
||||
</InputGroup.Button>
|
||||
<InputGroup.Button
|
||||
onClick={() => {
|
||||
setIsEditingCachePath(false);
|
||||
setErrorMessage('');
|
||||
}}
|
||||
>
|
||||
<Icon icon="close" />
|
||||
</InputGroup.Button>
|
||||
</InputGroup>
|
||||
<p style={{ fontSize: 'smaller' }}>
|
||||
*You will need to manually move any existing cached files to their
|
||||
new location.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{!isEditingCachePath && (
|
||||
<div style={{ overflow: 'auto' }}>
|
||||
Location:{' '}
|
||||
<code>
|
||||
{path.join(
|
||||
String(settings.getSync('cachePath')),
|
||||
'sonixdCache',
|
||||
String(localStorage.getItem('serverBase64'))
|
||||
)}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ width: '300px', marginTop: '20px' }}>
|
||||
<StyledCheckbox
|
||||
defaultChecked={cacheSongs}
|
||||
onChange={() => {
|
||||
settings.setSync('cacheSongs', !settings.getSync('cacheSongs'));
|
||||
setCacheSongs(!cacheSongs);
|
||||
}}
|
||||
>
|
||||
Songs{' '}
|
||||
<Tag>
|
||||
{songCacheSize} MB{' '}
|
||||
{imgCacheSize === 9999999 && '- Folder not found'}
|
||||
</Tag>
|
||||
</StyledCheckbox>
|
||||
<StyledCheckbox
|
||||
defaultChecked={cacheImages}
|
||||
onChange={() => {
|
||||
settings.setSync('cacheImages', !settings.getSync('cacheImages'));
|
||||
setCacheImages(!cacheImages);
|
||||
}}
|
||||
>
|
||||
Images{' '}
|
||||
<Tag>
|
||||
{imgCacheSize} MB{' '}
|
||||
{imgCacheSize === 9999999 && '- Folder not found'}
|
||||
</Tag>
|
||||
</StyledCheckbox>
|
||||
<br />
|
||||
<Button onClick={() => setIsEditingCachePath(true)}>
|
||||
Edit cache location
|
||||
</Button>
|
||||
</div>
|
||||
</ConfigPanel>
|
||||
<ConfigPanel header="Advanced" bordered>
|
||||
<StyledCheckbox
|
||||
defaultChecked={showDebugWindow}
|
||||
onChange={() => {
|
||||
settings.setSync(
|
||||
'showDebugWindow',
|
||||
!settings.getSync('showDebugWindow')
|
||||
);
|
||||
dispatch(
|
||||
setPlaybackSetting({
|
||||
setting: 'showDebugWindow',
|
||||
value: settings.getSync('showDebugWindow'),
|
||||
})
|
||||
);
|
||||
setShowDebugWindow(!showDebugWindow);
|
||||
}}
|
||||
>
|
||||
Show debug window
|
||||
</StyledCheckbox>
|
||||
</ConfigPanel>
|
||||
<PlaybackConfig />
|
||||
<LookAndFeelConfig />
|
||||
<PlayerConfig />
|
||||
<CacheConfig />
|
||||
<DebugConfig />
|
||||
</GenericPage>
|
||||
);
|
||||
};
|
||||
|
||||
146
src/components/settings/ConfigPanels/CacheConfig.tsx
Normal file
146
src/components/settings/ConfigPanels/CacheConfig.tsx
Normal file
@@ -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 (
|
||||
<ConfigPanel header="Cache" bordered>
|
||||
{errorMessage !== '' && (
|
||||
<>
|
||||
<Message showIcon type="error" description={errorMessage} />
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<br />
|
||||
{isEditingCachePath && (
|
||||
<>
|
||||
<InputGroup>
|
||||
<StyledInput
|
||||
value={newCachePath}
|
||||
onChange={(e: string) => setNewCachePath(e)}
|
||||
/>
|
||||
<InputGroup.Button
|
||||
onClick={() => {
|
||||
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.`
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Icon icon="check" />
|
||||
</InputGroup.Button>
|
||||
<InputGroup.Button
|
||||
onClick={() => {
|
||||
setIsEditingCachePath(false);
|
||||
setErrorMessage('');
|
||||
}}
|
||||
>
|
||||
<Icon icon="close" />
|
||||
</InputGroup.Button>
|
||||
</InputGroup>
|
||||
<p style={{ fontSize: 'smaller' }}>
|
||||
*You will need to manually move any existing cached files to their
|
||||
new location.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{!isEditingCachePath && (
|
||||
<div style={{ overflow: 'auto' }}>
|
||||
Location:{' '}
|
||||
<code>
|
||||
{path.join(
|
||||
String(settings.getSync('cachePath')),
|
||||
'sonixdCache',
|
||||
String(localStorage.getItem('serverBase64'))
|
||||
)}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ width: '300px', marginTop: '20px' }}>
|
||||
<StyledCheckbox
|
||||
defaultChecked={cacheSongs}
|
||||
onChange={() => {
|
||||
settings.setSync('cacheSongs', !settings.getSync('cacheSongs'));
|
||||
setCacheSongs(!cacheSongs);
|
||||
}}
|
||||
>
|
||||
Songs{' '}
|
||||
<Tag>
|
||||
{songCacheSize} MB{' '}
|
||||
{imgCacheSize === 9999999 && '- Folder not found'}
|
||||
</Tag>
|
||||
</StyledCheckbox>
|
||||
<StyledCheckbox
|
||||
defaultChecked={cacheImages}
|
||||
onChange={() => {
|
||||
settings.setSync('cacheImages', !settings.getSync('cacheImages'));
|
||||
setCacheImages(!cacheImages);
|
||||
}}
|
||||
>
|
||||
Images{' '}
|
||||
<Tag>
|
||||
{imgCacheSize} MB {imgCacheSize === 9999999 && '- Folder not found'}
|
||||
</Tag>
|
||||
</StyledCheckbox>
|
||||
<br />
|
||||
<Button onClick={() => setIsEditingCachePath(true)}>
|
||||
Edit cache location
|
||||
</Button>
|
||||
</div>
|
||||
</ConfigPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default CacheConfig;
|
||||
37
src/components/settings/ConfigPanels/DebugConfig.tsx
Normal file
37
src/components/settings/ConfigPanels/DebugConfig.tsx
Normal file
@@ -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 (
|
||||
<ConfigPanel header="Advanced" bordered>
|
||||
<StyledCheckbox
|
||||
defaultChecked={showDebugWindow}
|
||||
onChange={() => {
|
||||
settings.setSync(
|
||||
'showDebugWindow',
|
||||
!settings.getSync('showDebugWindow')
|
||||
);
|
||||
dispatch(
|
||||
setPlaybackSetting({
|
||||
setting: 'showDebugWindow',
|
||||
value: settings.getSync('showDebugWindow'),
|
||||
})
|
||||
);
|
||||
setShowDebugWindow(!showDebugWindow);
|
||||
}}
|
||||
>
|
||||
Show debug window
|
||||
</StyledCheckbox>
|
||||
</ConfigPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugConfig;
|
||||
@@ -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,
|
||||
139
src/components/settings/ConfigPanels/LookAndFeelConfig.tsx
Normal file
139
src/components/settings/ConfigPanels/LookAndFeelConfig.tsx
Normal file
@@ -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 (
|
||||
<ConfigPanel header="Look & Feel" bordered>
|
||||
<div style={{ width: '300px' }}>
|
||||
<p>Select the main application theme.</p>
|
||||
<RadioGroup
|
||||
name="themeRadioList"
|
||||
appearance="default"
|
||||
defaultValue={String(settings.getSync('theme'))}
|
||||
onChange={(e) => {
|
||||
settings.setSync('theme', e);
|
||||
dispatch(setTheme(e));
|
||||
}}
|
||||
>
|
||||
<StyledRadio value="defaultDark">Default Dark</StyledRadio>
|
||||
<StyledRadio value="defaultLight">Default Light</StyledRadio>
|
||||
</RadioGroup>
|
||||
<br />
|
||||
<ControlLabel>Font</ControlLabel>
|
||||
<br />
|
||||
<StyledInputPicker
|
||||
data={Fonts}
|
||||
groupBy="role"
|
||||
defaultValue={String(settings.getSync('font'))}
|
||||
onChange={(e: string) => {
|
||||
settings.setSync('font', e);
|
||||
dispatch(setFont(e));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<ConfigPanel bordered>
|
||||
<p>Select the columns you want displayed on pages with a list-view.</p>
|
||||
<Nav
|
||||
style={{ paddingTop: '10px' }}
|
||||
activeKey={currentLAFTab}
|
||||
onSelect={(e) => setCurrentLAFTab(e)}
|
||||
>
|
||||
<StyledNavItem eventKey="songList">Song List</StyledNavItem>
|
||||
<StyledNavItem eventKey="albumList">Album List</StyledNavItem>
|
||||
<StyledNavItem eventKey="playlistList">Playlist List</StyledNavItem>
|
||||
<StyledNavItem eventKey="miniList">Miniplayer List</StyledNavItem>
|
||||
</Nav>
|
||||
{currentLAFTab === 'songList' && (
|
||||
<ListViewConfig
|
||||
title="Song List"
|
||||
defaultColumns={currentSongColumns}
|
||||
columnPicker={songColumnPicker}
|
||||
columnList={songColumnList}
|
||||
settingsConfig={{
|
||||
columnList: 'songListColumns',
|
||||
rowHeight: 'songListRowHeight',
|
||||
fontSize: 'songListFontSize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentLAFTab === 'albumList' && (
|
||||
<ListViewConfig
|
||||
title="Album List"
|
||||
defaultColumns={currentAlbumColumns}
|
||||
columnPicker={albumColumnPicker}
|
||||
columnList={albumColumnList}
|
||||
settingsConfig={{
|
||||
columnList: 'albumListColumns',
|
||||
rowHeight: 'albumListRowHeight',
|
||||
fontSize: 'albumListFontSize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentLAFTab === 'playlistList' && (
|
||||
<ListViewConfig
|
||||
title="Playlist List"
|
||||
defaultColumns={currentPlaylistColumns}
|
||||
columnPicker={playlistColumnPicker}
|
||||
columnList={playlistColumnList}
|
||||
settingsConfig={{
|
||||
columnList: 'playlistListColumns',
|
||||
rowHeight: 'playlistListRowHeight',
|
||||
fontSize: 'playlistListFontSize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{currentLAFTab === 'miniList' && (
|
||||
<ListViewConfig
|
||||
title="Miniplayer List"
|
||||
defaultColumns={currentMiniColumns}
|
||||
columnPicker={songColumnPicker}
|
||||
columnList={songColumnList}
|
||||
settingsConfig={{
|
||||
columnList: 'miniListColumns',
|
||||
rowHeight: 'miniListRowHeight',
|
||||
fontSize: 'miniListFontSize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ConfigPanel>
|
||||
</ConfigPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default LookAndFeelConfig;
|
||||
132
src/components/settings/ConfigPanels/PlaybackConfig.tsx
Normal file
132
src/components/settings/ConfigPanels/PlaybackConfig.tsx
Normal file
@@ -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 (
|
||||
<ConfigPanel header="Playback" bordered>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If volume fade is disabled, then the fading-in track will start at the
|
||||
specified crossfade duration at full volume.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Setting the crossfade duration to <code>0</code> will enable{' '}
|
||||
<strong>gapless playback</strong>. All other playback settings except
|
||||
the polling interval will be ignored. It is recommended that you use a
|
||||
polling interval between <code>1</code> and <code>20</code> for
|
||||
increased transition accuracy.
|
||||
</p>
|
||||
<p style={{ fontSize: 'smaller' }}>
|
||||
*Enable the debug window if you want to view the differences between
|
||||
each fade type
|
||||
</p>
|
||||
|
||||
<div style={{ width: '300px', paddingTop: '20px' }}>
|
||||
<ControlLabel>Crossfade duration (s)</ControlLabel>
|
||||
<StyledInputNumber
|
||||
defaultValue={String(settings.getSync('fadeDuration')) || '0'}
|
||||
step={0.05}
|
||||
min={0}
|
||||
max={100}
|
||||
width={150}
|
||||
onChange={(e: any) => {
|
||||
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 })
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ControlLabel>Polling interval (ms)</ControlLabel>
|
||||
<StyledInputNumber
|
||||
defaultValue={String(settings.getSync('pollingInterval'))}
|
||||
step={1}
|
||||
min={1}
|
||||
max={1000}
|
||||
width={150}
|
||||
onChange={(e: any) => {
|
||||
settings.setSync('pollingInterval', Number(e));
|
||||
dispatch(
|
||||
setPlaybackSetting({
|
||||
setting: 'pollingInterval',
|
||||
value: Number(e),
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<ControlLabel>Crossfade type</ControlLabel>
|
||||
<RadioGroup
|
||||
name="fadeTypeRadioList"
|
||||
appearance="default"
|
||||
defaultValue={String(settings.getSync('fadeType'))}
|
||||
onChange={(e) => {
|
||||
settings.setSync('fadeType', e);
|
||||
dispatch(setPlaybackSetting({ setting: 'fadeType', value: e }));
|
||||
}}
|
||||
>
|
||||
<StyledRadio value="equalPower">Equal Power</StyledRadio>
|
||||
<StyledRadio value="linear">Linear</StyledRadio>
|
||||
<StyledRadio value="dipped">Dipped</StyledRadio>
|
||||
<StyledRadio value="constantPower">Constant Power</StyledRadio>
|
||||
<StyledRadio value="constantPowerSlowFade">
|
||||
Constant Power (slow fade)
|
||||
</StyledRadio>
|
||||
<StyledRadio value="constantPowerSlowCut">
|
||||
Constant Power (slow cut)
|
||||
</StyledRadio>
|
||||
<StyledRadio value="constantPowerFastCut">
|
||||
Constant Power (fast cut)
|
||||
</StyledRadio>
|
||||
</RadioGroup>
|
||||
<br />
|
||||
<ControlLabel>Volume fade</ControlLabel>
|
||||
<RadioGroup
|
||||
name="volumeFadeRadioList"
|
||||
appearance="default"
|
||||
defaultValue={Boolean(settings.getSync('volumeFade'))}
|
||||
onChange={(e) => {
|
||||
settings.setSync('volumeFade', e);
|
||||
dispatch(setPlaybackSetting({ setting: 'volumeFade', value: e }));
|
||||
}}
|
||||
>
|
||||
<StyledRadio value>Enabled</StyledRadio>
|
||||
<StyledRadio value={false}>Disabled</StyledRadio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</ConfigPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlaybackConfig;
|
||||
42
src/components/settings/ConfigPanels/PlayerConfig.tsx
Normal file
42
src/components/settings/ConfigPanels/PlayerConfig.tsx
Normal file
@@ -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 (
|
||||
<ConfigPanel header="Player" bordered>
|
||||
<p>
|
||||
Configure the number of seconds to skip forwards/backwards by when
|
||||
clicking the seek forward/backward buttons.
|
||||
</p>
|
||||
<br />
|
||||
<ControlLabel>Seek forward (s)</ControlLabel>
|
||||
<StyledInputNumber
|
||||
defaultValue={String(settings.getSync('seekForwardInterval')) || '0'}
|
||||
step={0.5}
|
||||
min={0}
|
||||
max={100}
|
||||
width={150}
|
||||
onChange={(e: any) => {
|
||||
settings.setSync('seekForwardInterval', Number(e));
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<ControlLabel>Seek backward (s)</ControlLabel>
|
||||
<StyledInputNumber
|
||||
defaultValue={String(settings.getSync('seekBackwardInterval')) || '0'}
|
||||
step={0.5}
|
||||
min={0}
|
||||
max={100}
|
||||
width={150}
|
||||
onChange={(e: any) => {
|
||||
settings.setSync('seekBackwardInterval', Number(e));
|
||||
}}
|
||||
/>
|
||||
</ConfigPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayerConfig;
|
||||
Reference in New Issue
Block a user