Browse Source

Upgrade to electron 22 (#397)

* add mac min/max

* restore exitfromtray

* fix macos quiting

* upgrade to electron 22

* Remove unused components / react-dnd

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
master
Kendall Garner 2 years ago
committed by GitHub
parent
commit
6f8de461f8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 86
      .erb/mocks/electronStoreMock.js
  2. 2
      .erb/scripts/Notarize.js
  3. 50
      package.json
  4. 4
      src/App.tsx
  5. 16
      src/__tests__/App.test.tsx
  6. 6
      src/api/api.ts
  7. 4
      src/api/jellyfinApi.ts
  8. 11
      src/components/debug/DebugWindow.tsx
  9. 4
      src/components/layout/Layout.tsx
  10. 6
      src/components/layout/Sidebar.tsx
  11. 47
      src/components/layout/Titlebar.tsx
  12. 6
      src/components/library/AlbumList.tsx
  13. 10
      src/components/library/AlbumView.tsx
  14. 6
      src/components/library/ArtistList.tsx
  15. 14
      src/components/library/ArtistView.tsx
  16. 10
      src/components/library/FolderList.tsx
  17. 4
      src/components/library/MusicList.tsx
  18. 6
      src/components/modal/ReleaseNotes.tsx
  19. 12
      src/components/player/NowPlayingMiniView.tsx
  20. 16
      src/components/player/NowPlayingView.tsx
  21. 25
      src/components/player/Player.tsx
  22. 8
      src/components/player/PlayerBar.tsx
  23. 6
      src/components/playlist/PlaylistList.tsx
  24. 6
      src/components/playlist/PlaylistView.tsx
  25. 4
      src/components/scrollingmenu/ScrollingMenu.tsx
  26. 8
      src/components/search/SearchView.tsx
  27. 2
      src/components/settings/Config.tsx
  28. 15
      src/components/settings/ConfigPanels/AdvancedConfig.tsx
  29. 16
      src/components/settings/ConfigPanels/CacheConfig.tsx
  30. 37
      src/components/settings/ConfigPanels/ExternalConfig.tsx
  31. 13
      src/components/settings/ConfigPanels/ListViewConfig.tsx
  32. 80
      src/components/settings/ConfigPanels/LookAndFeelConfig.tsx
  33. 22
      src/components/settings/ConfigPanels/PlaybackConfig.tsx
  34. 42
      src/components/settings/ConfigPanels/PlayerConfig.tsx
  35. 18
      src/components/settings/ConfigPanels/ServerConfig.tsx
  36. 6
      src/components/settings/ConfigPanels/WindowConfig.tsx
  37. 22
      src/components/settings/DisconnectButton.tsx
  38. 33
      src/components/settings/Login.tsx
  39. 1129
      src/components/shared/setDefaultSettings.ts
  40. 8
      src/components/starred/StarredView.tsx
  41. 50
      src/components/table/DraggableCell.tsx
  42. 50
      src/components/table/DraggableHeaderCell.tsx
  43. 4
      src/components/viewtypes/GridViewType.tsx
  44. 9
      src/components/viewtypes/ListViewTable.tsx
  45. 6
      src/components/viewtypes/ViewTypeButtons.tsx
  46. 2
      src/hooks/useBrowserDownload.ts
  47. 2
      src/hooks/useFavorite.ts
  48. 46
      src/hooks/usePlayerControls.ts
  49. 2
      src/hooks/useRating.ts
  50. 5
      src/i18n/i18n.js
  51. 14
      src/index.tsx
  52. 74
      src/main.dev.js
  53. 14
      src/main.prod.js.LICENSE.txt
  54. 50
      src/preload.ts
  55. 4
      src/redux/configSlice.ts
  56. 4
      src/redux/folderSlice.ts
  57. 4
      src/redux/miscSlice.ts
  58. 4
      src/redux/playQueueSlice.ts
  59. 4
      src/redux/viewSlice.ts
  60. 10
      src/shared/utils.ts
  61. 7
      src/yarn.lock
  62. 6924
      yarn.lock

86
.erb/mocks/electronStoreMock.js

@ -0,0 +1,86 @@
export default class Store {
constructor(options={}) {
this.name = options.name || "config.json";
this.settings = options.defaults || {};
}
get store() {
return this.set;
}
set store(value) {
this.settings = value;
}
/**
*
* @param {string} key
*/
_getParentObject(key) {
const path = key.split(".");
const final = path.at(-1);
let object = this.settings;
for (const part of path.slice(0, -1)) {
object = object[part];
if (object === undefined) {
return [undefined, final];
}
}
return [object, final];
}
/**
*
* @param {string} key
*/
has(key) {
if (key.indexOf(".") !== -1) {
let [parent, final] = this._getParentObject(key);
return parent !== undefined && key in parent;
} else {
return key in this.settings;
}
}
/**
*
* @param {string} key
*/
get(key) {
if (key.indexOf(".") !== -1) {
let [parent, final] = this._getParentObject(key);
return parent !== undefined ? parent[final] : undefined;
} else {
return this.settings[key];
}
}
/**
*
* @param {string} key
* @param {any} value
*/
set(key, value) {
if (key.indexOf("." !== -1)) {
const path = key.split(".");
let object = this.settings;
for (const part of path.slice(0, -1)) {
if (!(part in object)) {
object[part] = {};
}
object = object[part];
}
object[path.at(-1)] = value;
} else {
this.settings[key] = value;
}
}
}

2
.erb/scripts/Notarize.js

@ -1,4 +1,4 @@
const { notarize } = require('electron-notarize');
const { notarize } = require('@electron/notarize');
const { build } = require('../../package.json');
exports.default = async function notarizeMacos(context) {

50
package.json

@ -41,7 +41,6 @@
"main.prod.js",
"main.prod.js.map",
"package.json",
"preload.ts",
"img/"
],
"afterSign": ".erb/scripts/Notarize.js",
@ -124,10 +123,13 @@
],
"homepage": "https://github.com/jeffvli/sonixd#readme",
"jest": {
"testURL": "http://localhost/",
"testEnvironmentOptions": {
"url": "http://localhost/"
},
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/.erb/mocks/fileMock.js",
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
"\\.(css|less|sass|scss)$": "identity-obj-proxy",
"electron-store": "<rootDir>/.erb/mocks/electronStoreMock.js"
},
"moduleFileExtensions": [
"js",
@ -146,6 +148,7 @@
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-decorators": "^7.12.1",
"@babel/plugin-proposal-do-expressions": "^7.12.1",
@ -168,30 +171,28 @@
"@babel/preset-react": "^7.12.7",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.1",
"@electron/notarize": "^1.2.3",
"@electron/rebuild": "^3.2.10",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"@teamsupercell/typings-for-css-modules-loader": "^2.4.0",
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@types/discord-rpc": "^4.0.2",
"@types/enzyme": "^3.10.5",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/history": "4.7.6",
"@types/jest": "^26.0.15",
"@types/lodash": "^4.14.172",
"@types/md5": "^2.3.1",
"@types/node": "14.18.0",
"@types/randomstring": "^1.1.7",
"@types/react": "^17.0.2",
"@types/react-chartjs-2": "^2.5.7",
"@types/react-dnd": "^3.0.2",
"@types/react-dom": "^17.0.2",
"@types/react": "^18.0.2",
"@types/react-dom": "^18.0.2",
"@types/react-lazy-load-image-component": "^1.5.2",
"@types/react-redux": "^7.1.18",
"@types/react-router-dom": "^5.1.6",
"@types/react-slider": "^1.3.1",
"@types/react-test-renderer": "^17.0.1",
"@types/react-test-renderer": "^18.0.0",
"@types/react-transition-group": "^4.4.4",
"@types/react-virtualized": "^9.21.13",
"@types/react-virtualized": "^9.21.21",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/redux-mock-store": "^1.0.3",
@ -199,7 +200,6 @@
"@types/webpack-env": "^1.15.2",
"@typescript-eslint/eslint-plugin": "^4.8.1",
"@typescript-eslint/parser": "^4.8.1",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.1.0",
"babel-loader": "^8.2.2",
"babel-plugin-dev-expression": "^0.2.2",
@ -212,14 +212,9 @@
"css-loader": "^5.0.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"detect-port": "^1.3.0",
"electron": "13.6.3",
"electron-builder": "23.0.2",
"electron": "22.3.1",
"electron-builder": "^23.0.2",
"electron-devtools-installer": "git+https://github.com/MarshallOfSound/electron-devtools-installer.git",
"electron-notarize": "^1.0.0",
"electron-rebuild": "2.3.5",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
"enzyme-to-json": "^3.5.0",
"eslint": "^7.5.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-airbnb-typescript": "^12.0.0",
@ -245,7 +240,7 @@
"opencollective-postinstall": "^2.0.3",
"prettier": "^2.0.5",
"react-refresh": "^0.10.0",
"react-test-renderer": "^17.0.1",
"react-test-renderer": "^18.0.1",
"redux-mock-store": "^1.5.4",
"rimraf": "^3.0.0",
"sass-loader": "^12.2.0",
@ -266,13 +261,13 @@
"array-move": "^3.0.1",
"axios": "^0.21.1",
"axios-retry": "^3.1.9",
"chart.js": "^3.7.1",
"chart.js": "^3.9.1",
"discord-rpc": "^4.0.1",
"electron-debug": "^3.1.0",
"electron-localshortcut": "^3.2.1",
"electron-log": "^4.2.4",
"electron-redux": "^1.5.4",
"electron-settings": "^4.0.2",
"electron-store": "^8.1.0",
"electron-updater": "^4.3.4",
"fast-average-color": "^7.0.1",
"format-duration": "^1.4.0",
@ -286,10 +281,10 @@
"nanoid": "^3.1.23",
"nodejs-fs-utils": "^1.2.5",
"randomstring": "^1.2.1",
"react": "^17.0.2",
"react": "^18.0.2",
"react-audio-player": "^0.17.0",
"react-chartjs-2": "^3.0.4",
"react-dom": "^17.0.2",
"react-chartjs-2": "^4.3.1",
"react-dom": "^18.0.2",
"react-helmet-async": "^1.1.2",
"react-hotkeys-hook": "^3.4.3",
"react-i18next": "^11.15.3",
@ -308,6 +303,7 @@
"styled-components": "^5"
},
"resolutions": {
"@types/react": "^18.0.0",
"glob-parent": "^5.1.2",
"styled-components": "^5"
},

4
src/App.tsx

@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import { webFrame } from 'electron';
import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
import settings from 'electron-settings';
import { ThemeProvider } from 'styled-components';
import { HashRouter as Router, Switch, Route } from 'react-router-dom';
import './styles/App.global.css';
@ -33,6 +32,7 @@ import { mockSettings } from './shared/mockSettings';
import MusicList from './components/library/MusicList';
import ReleaseNotes from './components/modal/ReleaseNotes';
import { notifyToast } from './components/shared/toast';
import { settings } from './components/shared/setDefaultSettings';
const App = () => {
const [zoomFactor, setZoomFactor] = useState(Number(localStorage.getItem('zoomFactor')) || 1.0);
@ -77,7 +77,7 @@ const App = () => {
const themes: any =
process.env.NODE_ENV === 'test'
? mockSettings.themesDefault
: _.concat(settings.getSync('themes'), settings.getSync('themesDefault'));
: _.concat(settings.get('themes'), settings.get('themesDefault'));
setTheme(getTheme(themes, misc.theme) || defaultDark);
}, [misc.theme]);

16
src/__tests__/App.test.tsx

@ -1,3 +1,18 @@
jest.mock('electron', () => {
const originalModule = jest.requireActual('electron');
return {
__esModule: true,
...originalModule,
ipcRenderer: {
on: jest.fn(),
sendSync: jest.fn(),
removeAllListeners: jest.fn(),
},
};
});
/* eslint-disable import/first */
import React from 'react';
import '@testing-library/jest-dom';
import { Provider } from 'react-redux';
@ -16,6 +31,7 @@ import App from '../App';
import { Server } from '../types';
import { ArtistPage } from '../redux/artistSlice';
import { View } from '../redux/viewSlice';
/* eslint-enable import/first */
const middlewares: Middleware<Record<string, unknown>, any, Dispatch<AnyAction>>[] | undefined = [];
const mockStore = configureMockStore(middlewares);

6
src/api/api.ts

@ -1,16 +1,14 @@
/* eslint-disable no-await-in-loop */
import axios from 'axios';
import _ from 'lodash';
import settings from 'electron-settings';
import { nanoid } from 'nanoid/non-secure';
import axiosRetry from 'axios-retry';
import { mockSettings } from '../shared/mockSettings';
import { Item } from '../types';
import { settings } from '../components/shared/setDefaultSettings';
const legacyAuth =
process.env.NODE_ENV === 'test'
? mockSettings.legacyAuth
: Boolean(settings.getSync('legacyAuth'));
process.env.NODE_ENV === 'test' ? mockSettings.legacyAuth : Boolean(settings.get('legacyAuth'));
const getAuth = (useLegacyAuth: boolean) => {
if (useLegacyAuth) {

4
src/api/jellyfinApi.ts

@ -1,7 +1,6 @@
/* eslint-disable no-await-in-loop */
import axios from 'axios';
import axiosRetry from 'axios-retry';
import settings from 'electron-settings';
import _ from 'lodash';
import moment from 'moment';
import { nanoid } from 'nanoid/non-secure';
@ -10,9 +9,10 @@ import { handleDisconnect } from '../components/settings/DisconnectButton';
import { notifyToast } from '../components/shared/toast';
import { GenericItem, Item, Song } from '../types';
import { mockSettings } from '../shared/mockSettings';
import { settings } from '../components/shared/setDefaultSettings';
const transcode =
process.env.NODE_ENV === 'test' ? mockSettings.transcode : Boolean(settings.getSync('transcode'));
process.env.NODE_ENV === 'test' ? mockSettings.transcode : Boolean(settings.get('transcode'));
const getAuth = () => {
return {

11
src/components/debug/DebugWindow.tsx

@ -2,10 +2,21 @@ import React, { useState } from 'react';
import { Line } from 'react-chartjs-2';
import { Button, Checkbox, Divider, FlexboxGrid, Panel, Slider } from 'rsuite';
import { useTranslation } from 'react-i18next';
import {
Chart,
CategoryScale,
Legend,
LinearScale,
LineElement,
PointElement,
Title,
} from 'chart.js';
import { useAppSelector, useAppDispatch } from '../../redux/hooks';
import CustomTooltip from '../shared/CustomTooltip';
import { setFadeData } from '../../redux/playQueueSlice';
Chart.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Legend);
const DebugWindow = ({ ...rest }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();

4
src/components/layout/Layout.tsx

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import settings from 'electron-settings';
import { useHotkeys } from 'react-hotkeys-hook';
import { useHistory } from 'react-router-dom';
import { ButtonToolbar, Content, FlexboxGrid, Icon, Nav, Whisper } from 'rsuite';
@ -26,6 +25,7 @@ import AdvancedConfig from '../settings/ConfigPanels/AdvancedConfig';
import { setSidebar } from '../../redux/configSlice';
import SearchBar from '../search/SearchBar';
import Popup from '../shared/Popup';
import { settings } from '../shared/setDefaultSettings';
const Layout = ({ footer, children, disableSidebar, font }: any) => {
const { t } = useTranslation();
@ -47,7 +47,7 @@ const Layout = ({ footer, children, disableSidebar, font }: any) => {
);
const handleToggle = () => {
settings.setSync('sidebar.expand', !config.lookAndFeel.sidebar.expand);
settings.set('sidebar.expand', !config.lookAndFeel.sidebar.expand);
dispatch(setSidebar({ expand: !config.lookAndFeel.sidebar.expand }));
};

6
src/components/layout/Sidebar.tsx

@ -1,5 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import settings from 'electron-settings';
import useMeasure from 'react-use/lib/useMeasure';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import { useTranslation } from 'react-i18next';
@ -20,6 +19,7 @@ import { InfoModal } from '../modal/Modal';
import placeholderImg from '../../img/placeholder.png';
import SidebarPlaylists from './SidebarPlaylists';
import { setSidebar } from '../../redux/configSlice';
import { settings } from '../shared/setDefaultSettings';
const Sidebar = ({
expand,
@ -66,7 +66,7 @@ const Sidebar = ({
if (isResizing) {
const finalWidth = `${getSidebarWidth(e?.clientX)}px`;
dispatch(setSidebar({ width: finalWidth }));
settings.setSync('sidebar.width', finalWidth);
settings.set('sidebar.width', finalWidth);
setIsResizing(false);
document.body.style.cursor = 'default';
}
@ -115,7 +115,7 @@ const Sidebar = ({
size="xs"
onClick={() => {
dispatch(setSidebar({ coverArt: false }));
settings.setSync('sidebar.coverArt', false);
settings.set('sidebar.coverArt', false);
}}
>
<Icon icon="down" />

47
src/components/layout/Titlebar.tsx

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ipcRenderer } from 'electron';
import {
TitleHeader,
DragRegion,
@ -38,6 +39,21 @@ const Titlebar = ({ font }: any) => {
document.title = `${playStatus} ${songTitle}`.trim();
}, [playQueue, player.status, t]);
useEffect(() => {
ipcRenderer.on('maximize', () => {
document.body.classList.add('maximized');
});
ipcRenderer.on('unmaximize', () => {
document.body.classList.remove('maximized');
});
return () => {
ipcRenderer.removeAllListeners('maximize');
ipcRenderer.removeAllListeners('unmaximize');
};
}, []);
// if the titlebar is native return no custom titlebar
if (misc.titleBar === 'native') {
return null;
@ -57,6 +73,7 @@ const Titlebar = ({ font }: any) => {
minButton
className="button"
id="min-button"
onClick={() => ipcRenderer.send('minimize')}
onMouseOver={() => setHoverMin(true)}
onMouseLeave={() => setHoverMin(false)}
>
@ -71,6 +88,7 @@ const Titlebar = ({ font }: any) => {
maxButton
className="button"
id="max-button"
onClick={() => ipcRenderer.send('maximize')}
onMouseOver={() => setHoverMax(true)}
onMouseLeave={() => setHoverMax(false)}
>
@ -85,6 +103,7 @@ const Titlebar = ({ font }: any) => {
restoreButton
className="button"
id="restore-button"
onClick={() => ipcRenderer.send('unmaximize')}
onMouseOver={() => setHoverMax(true)}
onMouseLeave={() => setHoverMax(false)}
>
@ -98,6 +117,7 @@ const Titlebar = ({ font }: any) => {
<MacControlButton
className="button"
id="close-button"
onClick={() => ipcRenderer.send('close')}
onMouseOver={() => setHoverClose(true)}
onMouseLeave={() => setHoverClose(false)}
>
@ -121,7 +141,12 @@ const Titlebar = ({ font }: any) => {
</span>
</div>
<WindowControl id="window-controls">
<WindowControlButton minButton className="button" id="min-button">
<WindowControlButton
minButton
className="button"
id="min-button"
onClick={() => ipcRenderer.send('minimize')}
>
<img
className="icon"
srcSet="img/icons/min-w-10.png 1x, img/icons/min-w-12.png 1.25x, img/icons/min-w-15.png 1.5x, img/icons/min-w-15.png 1.75x, img/icons/min-w-20.png 2x, img/icons/min-w-20.png 2.25x, img/icons/min-w-24.png 2.5x, img/icons/min-w-30.png 3x, img/icons/min-w-30.png 3.5x"
@ -129,7 +154,12 @@ const Titlebar = ({ font }: any) => {
alt=""
/>
</WindowControlButton>
<WindowControlButton maxButton className="button" id="max-button">
<WindowControlButton
maxButton
className="button"
id="max-button"
onClick={() => ipcRenderer.send('maximize')}
>
<img
className="icon"
srcSet="img/icons/max-w-10.png 1x, img/icons/max-w-12.png 1.25x, img/icons/max-w-15.png 1.5x, img/icons/max-w-15.png 1.75x, img/icons/max-w-20.png 2x, img/icons/max-w-20.png 2.25x, img/icons/max-w-24.png 2.5x, img/icons/max-w-30.png 3x, img/icons/max-w-30.png 3.5x"
@ -137,7 +167,12 @@ const Titlebar = ({ font }: any) => {
alt=""
/>
</WindowControlButton>
<WindowControlButton restoreButton className="button" id="restore-button">
<WindowControlButton
restoreButton
className="button"
id="restore-button"
onClick={() => ipcRenderer.send('unmaximize')}
>
<img
className="icon"
srcSet="img/icons/restore-w-10.png 1x, img/icons/restore-w-12.png 1.25x, img/icons/restore-w-15.png 1.5x, img/icons/restore-w-15.png 1.75x, img/icons/restore-w-20.png 2x, img/icons/restore-w-20.png 2.25x, img/icons/restore-w-24.png 2.5x, img/icons/restore-w-30.png 3x, img/icons/restore-w-30.png 3.5x"
@ -145,7 +180,11 @@ const Titlebar = ({ font }: any) => {
alt=""
/>
</WindowControlButton>
<WindowControlButton className="button" id="close-button">
<WindowControlButton
className="button"
id="close-button"
onClick={() => ipcRenderer.send('close')}
>
<img
className="icon"
srcSet="img/icons/close-w-10.png 1x, img/icons/close-w-12.png 1.25x, img/icons/close-w-15.png 1.5x, img/icons/close-w-15.png 1.75x, img/icons/close-w-20.png 2x, img/icons/close-w-20.png 2.25x, img/icons/close-w-24.png 2.5x, img/icons/close-w-30.png 3x, img/icons/close-w-30.png 3.5x"

6
src/components/library/AlbumList.tsx

@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import _ from 'lodash';
import settings from 'electron-settings';
import { ButtonToolbar, Nav, Whisper } from 'rsuite';
import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom';
@ -33,6 +32,7 @@ import useListClickHandler from '../../hooks/useListClickHandler';
import Popup from '../shared/Popup';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
export const ALBUM_SORT_TYPES = [
{ label: i18n.t('A-Z (Name)'), value: 'alphabeticalByName', role: i18n.t('Default') },
@ -54,7 +54,7 @@ const AlbumList = () => {
const view = useAppSelector((state) => state.view);
const [isRefreshing, setIsRefreshing] = useState(false);
const [sortTypes, setSortTypes] = useState<any[]>([]);
const [viewType, setViewType] = useState(settings.getSync('albumViewType'));
const [viewType, setViewType] = useState(settings.get('albumViewType'));
const [musicFolder, setMusicFolder] = useState({ loaded: false, id: undefined });
const albumFilterPickerContainerRef = useRef(null);
const [currentQueryKey, setCurrentQueryKey] = useState<any>(['albumList']);
@ -386,7 +386,7 @@ const AlbumList = () => {
})
}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}

10
src/components/library/AlbumView.tsx

@ -1,7 +1,6 @@
import React, { useRef } from 'react';
import { nanoid } from 'nanoid/non-secure';
import { clipboard, shell } from 'electron';
import settings from 'electron-settings';
import { ButtonToolbar, Whisper } from 'rsuite';
import { useQuery, useQueryClient } from 'react-query';
import { useParams, useHistory } from 'react-router-dom';
@ -39,6 +38,7 @@ import Popup from '../shared/Popup';
import usePlayQueueHandler from '../../hooks/usePlayQueueHandler';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
interface AlbumParams {
id: string;
@ -209,7 +209,7 @@ const AlbumView = ({ ...rest }: any) => {
/>
}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
id: data.albumId,
}}
@ -444,10 +444,10 @@ const AlbumView = ({ ...rest }: any) => {
handleRating(rowData, { queryKey: ['album', albumId], rating })
}
virtualized
rowHeight={Number(settings.getSync('musicListRowHeight'))}
fontSize={Number(settings.getSync('musicListFontSize'))}
rowHeight={Number(settings.get('musicListRowHeight'))}
fontSize={Number(settings.get('musicListFontSize'))}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}

6
src/components/library/ArtistList.tsx

@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react';
import settings from 'electron-settings';
import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router';
import { ButtonToolbar } from 'rsuite';
@ -20,6 +19,7 @@ import { StyledTag } from '../shared/styled';
import useListClickHandler from '../../hooks/useListClickHandler';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
const ArtistList = () => {
const { t } = useTranslation();
@ -31,7 +31,7 @@ const ArtistList = () => {
const config = useAppSelector((state) => state.config);
const misc = useAppSelector((state) => state.misc);
const [isRefreshing, setIsRefreshing] = useState(false);
const [viewType, setViewType] = useState(settings.getSync('artistViewType'));
const [viewType, setViewType] = useState(settings.get('artistViewType'));
const [musicFolder, setMusicFolder] = useState(undefined);
useEffect(() => {
@ -160,7 +160,7 @@ const ArtistList = () => {
handleRating(rowData, { queryKey: ['artistList', musicFolder], rating })
}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'artist',
cacheIdProperty: 'id',
}}

14
src/components/library/ArtistView.tsx

@ -4,7 +4,6 @@ import _ from 'lodash';
import { nanoid } from 'nanoid/non-secure';
import FastAverageColor from 'fast-average-color';
import { clipboard, shell } from 'electron';
import settings from 'electron-settings';
import { ButtonToolbar, Whisper, ButtonGroup, Icon } from 'rsuite';
import { useQuery, useQueryClient } from 'react-query';
import { useTranslation } from 'react-i18next';
@ -42,6 +41,7 @@ import Popup from '../shared/Popup';
import usePlayQueueHandler from '../../hooks/usePlayQueueHandler';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
const fac = new FastAverageColor();
@ -58,7 +58,7 @@ const ArtistView = ({ ...rest }: any) => {
const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config);
const folder = useAppSelector((state) => state.folder);
const [viewType, setViewType] = useState(settings.getSync('albumViewType') || 'list');
const [viewType, setViewType] = useState(settings.get('albumViewType') || 'list');
const [imageAverageColor, setImageAverageColor] = useState({ color: '', loaded: false });
const [artistDurationTotal, setArtistDurationTotal] = useState('');
const [artistSongTotal, setArtistSongTotal] = useState(0);
@ -125,7 +125,7 @@ const ArtistView = ({ ...rest }: any) => {
]);
useEffect(() => {
if (settings.getSync('artistPageLegacy') && !rest.isModal) {
if (settings.get('artistPageLegacy') && !rest.isModal) {
history.replace(`/library/artist/${artistId}/albums`);
}
}, [artistId, history, rest.isModal]);
@ -357,7 +357,7 @@ const ArtistView = ({ ...rest }: any) => {
/>
}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'artist',
id: data.id,
}}
@ -596,7 +596,7 @@ const ArtistView = ({ ...rest }: any) => {
rowHeight={config.lookAndFeel.listView.music.rowHeight}
fontSize={config.lookAndFeel.listView.music.fontSize}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}
@ -628,7 +628,7 @@ const ArtistView = ({ ...rest }: any) => {
rowHeight={config.lookAndFeel.listView.album.rowHeight}
fontSize={config.lookAndFeel.listView.album.fontSize}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}
@ -685,7 +685,7 @@ const ArtistView = ({ ...rest }: any) => {
rowHeight={config.lookAndFeel.listView.music.rowHeight}
fontSize={config.lookAndFeel.listView.music.fontSize}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}

10
src/components/library/FolderList.tsx

@ -1,5 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import settings from 'electron-settings';
import _ from 'lodash';
import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom';
@ -21,6 +20,7 @@ import CenterLoader from '../loader/CenterLoader';
import useListClickHandler from '../../hooks/useListClickHandler';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
const FolderList = () => {
const { t } = useTranslation();
@ -169,9 +169,9 @@ const FolderList = () => {
: indexData
}
loading={isLoadingFolderData}
tableColumns={settings.getSync('musicListColumns')}
rowHeight={Number(settings.getSync('musicListRowHeight'))}
fontSize={Number(settings.getSync('musicListFontSize'))}
tableColumns={settings.get('musicListColumns')}
rowHeight={Number(settings.get('musicListRowHeight'))}
fontSize={Number(settings.get('musicListFontSize'))}
handleRowClick={handleRowClick}
handleRowDoubleClick={handleRowDoubleClick}
handleFavorite={(rowData: any) =>
@ -195,7 +195,7 @@ const FolderList = () => {
handleRating(rowData, { queryKey: ['folder', folder.currentViewedFolder], rating })
}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'folder',
cacheIdProperty: 'albumId',
}}

4
src/components/library/MusicList.tsx

@ -1,5 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import settings from 'electron-settings';
import { ButtonToolbar } from 'rsuite';
import { useQuery, useQueryClient } from 'react-query';
import { useTranslation } from 'react-i18next';
@ -22,6 +21,7 @@ import useListScroll from '../../hooks/useListScroll';
import useListClickHandler from '../../hooks/useListClickHandler';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
// prettier-ignore
export const MUSIC_SORT_TYPES = [
@ -259,7 +259,7 @@ const MusicList = () => {
handleRating(rowData, { queryKey: currentQueryKey, rating })
}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}

6
src/components/modal/ReleaseNotes.tsx

@ -1,15 +1,15 @@
import React, { useEffect, useState } from 'react';
import { Divider } from 'rsuite';
import settings from 'electron-settings';
import { shell } from 'electron';
import axios from 'axios';
import { InfoModal } from './Modal';
import { StyledButton } from '../shared/styled';
import { ConfigPanel } from '../settings/styled';
import CenterLoader from '../loader/CenterLoader';
import { settings } from '../shared/setDefaultSettings';
const ReleaseNotes = () => {
const [show, setShow] = useState(Boolean(settings.getSync('autoUpdateNotice')));
const [show, setShow] = useState(Boolean(settings.get('autoUpdateNotice')));
const [releaseDetails, setReleaseDetails] = useState<any>();
const [isLoading, setIsLoading] = useState(false);
@ -40,7 +40,7 @@ const ReleaseNotes = () => {
show={show}
handleHide={() => {
setShow(false);
settings.setSync('autoUpdateNotice', false);
settings.set('autoUpdateNotice', false);
}}
>
<ConfigPanel>

12
src/components/player/NowPlayingMiniView.tsx

@ -1,6 +1,5 @@
import React, { useEffect, useState, useRef } from 'react';
import _ from 'lodash';
import settings from 'electron-settings';
import { ButtonToolbar, FlexboxGrid, Icon, Whisper, ControlLabel } from 'rsuite';
import { useHotkeys } from 'react-hotkeys-hook';
import { useQuery } from 'react-query';
@ -52,6 +51,7 @@ import { Server, Song } from '../../types';
import useListClickHandler from '../../hooks/useListClickHandler';
import Popup from '../shared/Popup';
import useFavorite from '../../hooks/useFavorite';
import { settings } from '../shared/setDefaultSettings';
const NowPlayingMiniView = () => {
const { t } = useTranslation();
@ -62,7 +62,7 @@ const NowPlayingMiniView = () => {
const config = useAppSelector((state) => state.config);
const folder = useAppSelector((state) => state.folder);
const [autoPlaylistTrackCount, setRandomPlaylistTrackCount] = useState(
Number(settings.getSync('randomPlaylistTrackCount'))
Number(settings.get('randomPlaylistTrackCount'))
);
const genrePickerContainerRef = useRef(null);
const musicFolderPickerContainerRef = useRef(null);
@ -118,7 +118,7 @@ const NowPlayingMiniView = () => {
useEffect(() => {
if (playQueue.scrollWithCurrentSong) {
const rowHeight = Number(settings.getSync('miniListRowHeight'));
const rowHeight = Number(settings.get('miniListRowHeight'));
const scrollPosition =
rowHeight * playQueue.currentIndex - rowHeight * 2 > 0
? rowHeight * playQueue.currentIndex - rowHeight * 2
@ -273,7 +273,7 @@ const NowPlayingMiniView = () => {
defaultValue={autoPlaylistTrackCount}
value={autoPlaylistTrackCount}
onChange={(e: number) => {
settings.setSync('randomPlaylistTrackCount', Number(e));
settings.set('randomPlaylistTrackCount', Number(e));
setRandomPlaylistTrackCount(Number(e));
}}
/>
@ -425,7 +425,7 @@ const NowPlayingMiniView = () => {
defaultChecked={playQueue.scrollWithCurrentSong}
checked={playQueue.scrollWithCurrentSong}
onChange={(_v: any, e: boolean) => {
settings.setSync('scrollWithCurrentSong', e);
settings.set('scrollWithCurrentSong', e);
dispatch(
setPlaybackSetting({
setting: 'scrollWithCurrentSong',
@ -451,7 +451,7 @@ const NowPlayingMiniView = () => {
rowHeight={config.lookAndFeel.listView.mini.rowHeight}
fontSize={config.lookAndFeel.listView.mini.fontSize}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}

16
src/components/player/NowPlayingView.tsx

@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import _ from 'lodash';
import settings from 'electron-settings';
import { useQuery } from 'react-query';
import { ButtonToolbar, ButtonGroup, ControlLabel, FlexboxGrid, Icon, Whisper } from 'rsuite';
import { useHotkeys } from 'react-hotkeys-hook';
@ -56,6 +55,7 @@ import useListClickHandler from '../../hooks/useListClickHandler';
import Popup from '../shared/Popup';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
const NowPlayingView = () => {
const { t } = useTranslation();
@ -70,14 +70,14 @@ const NowPlayingView = () => {
const folder = useAppSelector((state) => state.folder);
const misc = useAppSelector((state) => state.misc);
const [autoPlaylistTrackCount, setRandomPlaylistTrackCount] = useState(
Number(settings.getSync('randomPlaylistTrackCount'))
Number(settings.get('randomPlaylistTrackCount'))
);
const [autoPlaylistFromYear, setRandomPlaylistFromYear] = useState(0);
const [autoPlaylistToYear, setRandomPlaylistToYear] = useState(0);
const [randomPlaylistGenre, setRandomPlaylistGenre] = useState<string | undefined>(undefined);
const [isLoadingRandom, setIsLoadingRandom] = useState(false);
const [musicFolder, setMusicFolder] = useState(folder.musicFolder);
const [infoMode, setInfoMode] = useState(settings.getSync('infoMode' || false));
const [infoMode, setInfoMode] = useState(settings.get('infoMode' || false));
const { data: musicFolders } = useQuery(['musicFolders'], () =>
apiController({ serverType: config.serverType, endpoint: 'getMusicFolders' })
@ -131,7 +131,7 @@ const NowPlayingView = () => {
useEffect(() => {
if (playQueue.scrollWithCurrentSong) {
setTimeout(() => {
const rowHeight = Number(settings.getSync('musicListRowHeight'));
const rowHeight = Number(settings.get('musicListRowHeight'));
tableRef?.current?.table.current?.scrollTop(
rowHeight * playQueue.currentIndex - rowHeight * 2 > 0
? rowHeight * playQueue.currentIndex - rowHeight * 2
@ -288,7 +288,7 @@ const NowPlayingView = () => {
defaultValue={autoPlaylistTrackCount}
value={autoPlaylistTrackCount}
onChange={(e: number) => {
settings.setSync('randomPlaylistTrackCount', Number(e));
settings.set('randomPlaylistTrackCount', Number(e));
setRandomPlaylistTrackCount(Number(e));
}}
/>
@ -397,7 +397,7 @@ const NowPlayingView = () => {
<StyledButton
size="sm"
onClick={() => {
settings.setSync('infoMode', !infoMode);
settings.set('infoMode', !infoMode);
setInfoMode(!infoMode);
}}
>
@ -456,7 +456,7 @@ const NowPlayingView = () => {
defaultChecked={playQueue.scrollWithCurrentSong}
checked={playQueue.scrollWithCurrentSong}
onChange={(_v: any, e: boolean) => {
settings.setSync('scrollWithCurrentSong', e);
settings.set('scrollWithCurrentSong', e);
dispatch(
setPlaybackSetting({
setting: 'scrollWithCurrentSong',
@ -493,7 +493,7 @@ const NowPlayingView = () => {
rowHeight={config.lookAndFeel.listView.music.rowHeight}
fontSize={config.lookAndFeel.listView.music.fontSize}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}

25
src/components/player/Player.tsx

@ -7,7 +7,6 @@ import React, {
useCallback,
} from 'react';
import { ipcRenderer } from 'electron';
import settings from 'electron-settings';
import ReactAudioPlayer from 'react-audio-player';
import { Helmet } from 'react-helmet-async';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
@ -28,6 +27,7 @@ import { isCached, isLinux } from '../../shared/utils';
import { apiController } from '../../api/controller';
import { Artist, Server } from '../../types';
import { setStatus } from '../../redux/playerSlice';
import { settings } from '../shared/setDefaultSettings';
const gaplessListenHandler = (
currentPlayerRef: any,
@ -252,7 +252,7 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
const player = useAppSelector((state) => state.player);
const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config);
const cacheSongs = settings.getSync('cacheSongs');
const cacheSongs = settings.get('cacheSongs');
const [title] = useState('');
const [scrobbled, setScrobbled] = useState(false);
@ -472,6 +472,24 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
);
}, [config.serverType, currentEntryList, dispatch, playQueue, scrobbled]);
function setMetadata(arg: any) {
navigator.mediaSession.metadata = new MediaMetadata({
title: arg.title || 'Unknown Title',
artist:
arg.artist?.length !== 0
? arg.artist?.map((artist: any) => artist.title).join(', ')
: 'Unknown Artist',
album: 'Unknown Album',
artwork: [
{
src: arg.image.includes('placeholder')
? 'https://raw.githubusercontent.com/jeffvli/sonixd/main/src/img/placeholder.png'
: arg.image,
},
],
});
}
const handleOnEndedPlayer1 = useCallback(() => {
player1Ref.current.audioEl.current.currentTime = 0;
if (cacheSongs) {
@ -521,6 +539,7 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
)
];
ipcRenderer.send('current-song', nextSong);
setMetadata(nextSong);
dispatch(setAutoIncremented(false));
}
@ -575,6 +594,7 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
)
];
ipcRenderer.send('current-song', nextSong);
setMetadata(nextSong);
dispatch(setAutoIncremented(false));
}
@ -621,6 +641,7 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
: playQueue[currentEntryList][playQueue.player2.index];
ipcRenderer.send('current-song', playQueue.current);
setMetadata(playQueue.current);
if (config.player.systemNotifications && currentSong) {
// eslint-disable-next-line no-new

8
src/components/player/PlayerBar.tsx

@ -1,7 +1,6 @@
import React, { useEffect, useState, useRef, useMemo } from 'react';
import axios from 'axios';
import { useQueryClient } from 'react-query';
import settings from 'electron-settings';
import { FlexboxGrid, Grid, Row, Col, Whisper, Icon } from 'rsuite';
import { useHistory } from 'react-router-dom';
import { LazyLoadImage } from 'react-lazy-load-image-component';
@ -37,6 +36,7 @@ import usePlayQueueHandler from '../../hooks/usePlayQueueHandler';
import { apiController } from '../../api/controller';
import Slider from '../slider/Slider';
import useDiscordRpc from '../../hooks/useDiscordRpc';
import { settings } from '../shared/setDefaultSettings';
const PlayerBar = () => {
const { t } = useTranslation();
@ -49,7 +49,7 @@ const PlayerBar = () => {
const [currentTime, setCurrentTime] = useState(0);
const [isDraggingVolume, setIsDraggingVolume] = useState(false);
const [currentEntryList, setCurrentEntryList] = useState('entry');
const [localVolume, setLocalVolume] = useState(Number(settings.getSync('volume')));
const [localVolume, setLocalVolume] = useState(Number(settings.get('volume')));
const [muted, setMuted] = useState(false);
const [showCoverArtModal, setShowCoverArtModal] = useState(false);
const [showLyricsModal, setShowLyricsModal] = useState(false);
@ -215,7 +215,7 @@ const PlayerBar = () => {
playersRef.current.player2.audioEl.current.volume = localVolume ** 2;
}
settings.setSync('volume', localVolume);
settings.set('volume', localVolume);
}
setIsDraggingVolume(false);
}, 100);
@ -278,7 +278,7 @@ const PlayerBar = () => {
size="xs"
onClick={() => {
dispatch(setSidebar({ coverArt: true }));
settings.setSync('sidebar.coverArt', true);
settings.set('sidebar.coverArt', true);
}}
>
<Icon icon="up" />

6
src/components/playlist/PlaylistList.tsx

@ -2,7 +2,6 @@ import React, { useRef, useState } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom';
import { Form, Whisper } from 'rsuite';
import settings from 'electron-settings';
import { useTranslation } from 'react-i18next';
import useSearchQuery from '../../hooks/useSearchQuery';
import ListViewType from '../viewtypes/ListViewType';
@ -21,6 +20,7 @@ import { setSort } from '../../redux/playlistSlice';
import ColumnSortPopover from '../shared/ColumnSortPopover';
import useListClickHandler from '../../hooks/useListClickHandler';
import Popup from '../shared/Popup';
import { settings } from '../shared/setDefaultSettings';
const PlaylistList = () => {
const { t } = useTranslation();
@ -32,7 +32,7 @@ const PlaylistList = () => {
const playlist = useAppSelector((state) => state.playlist);
const playlistTriggerRef = useRef<any>();
const [newPlaylistName, setNewPlaylistName] = useState('');
const [viewType, setViewType] = useState(settings.getSync('playlistViewType') || 'list');
const [viewType, setViewType] = useState(settings.get('playlistViewType') || 'list');
const {
isLoading,
isError,
@ -201,7 +201,7 @@ const PlaylistList = () => {
rowHeight={config.lookAndFeel.listView.playlist.rowHeight}
fontSize={config.lookAndFeel.listView.playlist.fontSize}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'playlist',
cacheIdProperty: 'id',
}}

6
src/components/playlist/PlaylistView.tsx

@ -2,7 +2,6 @@ import React, { useEffect, useState, useRef } from 'react';
import _ from 'lodash';
import fs from 'fs';
import path from 'path';
import settings from 'electron-settings';
import { ButtonToolbar, ControlLabel, Form, Whisper } from 'rsuite';
import { useHotkeys } from 'react-hotkeys-hook';
import { useQuery, useQueryClient } from 'react-query';
@ -55,6 +54,7 @@ import usePlayQueueHandler from '../../hooks/usePlayQueueHandler';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { useBrowserDownload } from '../../hooks/useBrowserDownload';
import { settings } from '../shared/setDefaultSettings';
interface PlaylistParams {
id: string;
@ -410,7 +410,7 @@ const PlaylistView = ({ ...rest }) => {
/>
}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'playlist',
id: data.id,
}}
@ -619,7 +619,7 @@ const PlaylistView = ({ ...rest }) => {
rowHeight={config.lookAndFeel.listView.music.rowHeight}
fontSize={config.lookAndFeel.listView.music.fontSize}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}

4
src/components/scrollingmenu/ScrollingMenu.tsx

@ -1,5 +1,4 @@
import React, { useRef } from 'react';
import settings from 'electron-settings';
import styled from 'styled-components';
import { ButtonGroup, ButtonToolbar, FlexboxGrid, Icon } from 'rsuite';
import Card from '../card/Card';
@ -7,6 +6,7 @@ import { SectionTitleWrapper, SectionTitle, StyledButton } from '../shared/style
import { useAppSelector } from '../../redux/hooks';
import { smoothScroll } from '../../shared/utils';
import { Item } from '../../types';
import { settings } from '../shared/setDefaultSettings';
const ScrollMenuContainer = styled.div<{ $noScrollbar?: boolean; maxWidth: string }>`
overflow-x: auto;
@ -33,7 +33,7 @@ const ScrollingMenu = ({
maxWidth,
noButtons,
}: any) => {
const cacheImages = Boolean(settings.getSync('cacheImages'));
const cacheImages = Boolean(settings.get('cacheImages'));
const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config);
const scrollContainerRef = useRef<any>();

8
src/components/search/SearchView.tsx

@ -1,6 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import _ from 'lodash';
import settings from 'electron-settings';
import { Icon, Nav } from 'rsuite';
import { useHistory } from 'react-router-dom';
import { useInfiniteQuery, useQueryClient } from 'react-query';
@ -24,6 +23,7 @@ import useListClickHandler from '../../hooks/useListClickHandler';
import ListViewType from '../viewtypes/ListViewType';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
const SearchView = () => {
const { t } = useTranslation();
@ -338,7 +338,7 @@ const SearchView = () => {
}
listType="music"
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}
@ -386,7 +386,7 @@ const SearchView = () => {
}
listType="album"
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}
@ -434,7 +434,7 @@ const SearchView = () => {
}
listType="artist"
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'artist',
cacheIdProperty: 'id',
}}

2
src/components/settings/Config.tsx

@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
import GenericPage from '../layout/GenericPage';
import DisconnectButton from './DisconnectButton';
import GenericPageHeader from '../layout/GenericPageHeader';
import setDefaultSettings from '../shared/setDefaultSettings';
import { setDefaultSettings } from '../shared/setDefaultSettings';
import { StyledButton, StyledNavItem } from '../shared/styled';
import PlaybackConfig from './ConfigPanels/PlaybackConfig';
import LookAndFeelConfig from './ConfigPanels/LookAndFeelConfig';

15
src/components/settings/ConfigPanels/AdvancedConfig.tsx

@ -1,21 +1,18 @@
import React, { useState } from 'react';
import settings from 'electron-settings';
import { Icon } from 'rsuite';
import { shell } from 'electron';
import { useTranslation } from 'react-i18next';
import { ConfigPanel } from '../styled';
import { StyledButton, StyledToggle } from '../../shared/styled';
import { useAppDispatch } from '../../../redux/hooks';
import { setPlaybackSetting } from '../../../redux/playQueueSlice';
import ConfigOption from '../ConfigOption';
import { settings } from '../../shared/setDefaultSettings';
const AdvancedConfig = ({ bordered }: any) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const [showDebugWindow, setShowDebugWindow] = useState(
Boolean(settings.getSync('showDebugWindow'))
);
const [autoUpdate, setAutoUpdate] = useState(Boolean(settings.getSync('autoUpdate')));
const [showDebugWindow, setShowDebugWindow] = useState(Boolean(settings.get('showDebugWindow')));
const [autoUpdate, setAutoUpdate] = useState(Boolean(settings.get('autoUpdate')));
return (
<ConfigPanel bordered={bordered} header={t('Advanced')}>
@ -29,7 +26,7 @@ const AdvancedConfig = ({ bordered }: any) => {
defaultChecked={autoUpdate}
checked={autoUpdate}
onChange={(e: boolean) => {
settings.setSync('autoUpdate', e);
settings.set('autoUpdate', e);
setAutoUpdate(e);
}}
/>
@ -44,7 +41,7 @@ const AdvancedConfig = ({ bordered }: any) => {
defaultChecked={showDebugWindow}
checked={showDebugWindow}
onChange={(e: boolean) => {
settings.setSync('showDebugWindow', e);
settings.set('showDebugWindow', e);
dispatch(
setPlaybackSetting({
setting: 'showDebugWindow',
@ -58,7 +55,7 @@ const AdvancedConfig = ({ bordered }: any) => {
/>
<br />
<StyledButton appearance="primary" onClick={() => shell.openPath(settings.file())}>
<StyledButton appearance="primary" onClick={() => settings.openInEditor()}>
{t('Open settings JSON')} <Icon icon="external-link" />
</StyledButton>
</ConfigPanel>

16
src/components/settings/ConfigPanels/CacheConfig.tsx

@ -1,5 +1,4 @@
import React, { useState, useEffect } from 'react';
import settings from 'electron-settings';
import { shell } from 'electron';
import fs from 'fs';
import path from 'path';
@ -20,6 +19,7 @@ import { notifyToast } from '../../shared/toast';
import { setMiscSetting } from '../../../redux/miscSlice';
import { useAppDispatch } from '../../../redux/hooks';
import Popup from '../../shared/Popup';
import { settings } from '../../shared/setDefaultSettings';
const fsUtils = require('nodejs-fs-utils');
@ -31,8 +31,8 @@ const CacheConfig = ({ bordered }: any) => {
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')));
const [cacheSongs, setCacheSongs] = useState(Boolean(settings.get('cacheSongs')));
const [cacheImages, setCacheImages] = useState(Boolean(settings.get('cacheImages')));
useEffect(() => {
// Retrieve cache sizes on render
@ -127,7 +127,7 @@ const CacheConfig = ({ bordered }: any) => {
onClick={() => {
const check = fs.existsSync(newCachePath);
if (check) {
settings.setSync('cachePath', newCachePath);
settings.set('cachePath', newCachePath);
fs.mkdirSync(getSongCachePath(), { recursive: true });
fs.mkdirSync(getImageCachePath(), { recursive: true });
dispatch(
@ -157,8 +157,8 @@ const CacheConfig = ({ bordered }: any) => {
</StyledInputGroupButton>
<StyledInputGroupButton
onClick={() => {
const defaultPath = path.join(path.dirname(settings.file()));
settings.setSync('cachePath', defaultPath);
const defaultPath = path.join(path.dirname(settings.path));
settings.set('cachePath', defaultPath);
dispatch(setMiscSetting({ setting: 'imageCachePath', value: getImageCachePath() }));
dispatch(setMiscSetting({ setting: 'songCachePath', value: getSongCachePath() }));
setErrorMessage('');
@ -187,7 +187,7 @@ const CacheConfig = ({ bordered }: any) => {
<StyledCheckbox
defaultChecked={cacheSongs}
onChange={(_v: any, e: boolean) => {
settings.setSync('cacheSongs', e);
settings.set('cacheSongs', e);
setCacheSongs(e);
}}
>
@ -199,7 +199,7 @@ const CacheConfig = ({ bordered }: any) => {
<StyledCheckbox
defaultChecked={cacheImages}
onChange={(_v: any, e: boolean) => {
settings.setSync('cacheImages', e);
settings.set('cacheImages', e);
setCacheImages(e);
}}
>

37
src/components/settings/ConfigPanels/ExternalConfig.tsx

@ -1,6 +1,5 @@
import React from 'react';
import { shell } from 'electron';
import settings from 'electron-settings';
import { ipcRenderer, shell } from 'electron';
import { Icon, RadioGroup } from 'rsuite';
import { Trans, useTranslation } from 'react-i18next';
import { ConfigOptionDescription, ConfigPanel } from '../styled';
@ -17,8 +16,7 @@ import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { setDiscord, setOBS } from '../../../redux/configSlice';
import ConfigOption from '../ConfigOption';
import { Server } from '../../../types';
const dialog: any = process.env.NODE_ENV === 'test' ? '' : require('electron').remote.dialog;
import { settings } from '../../shared/setDefaultSettings';
const ExternalConfig = ({ bordered }: any) => {
const { t } = useTranslation();
@ -42,7 +40,7 @@ const ExternalConfig = ({ bordered }: any) => {
checked={config.external.discord.enabled}
disabled={config.external.discord.clientId.length !== 18}
onChange={(e: boolean) => {
settings.setSync('discord.enabled', e);
settings.set('discord.enabled', e);
dispatch(setDiscord({ ...config.external.discord, enabled: e }));
}}
/>
@ -68,7 +66,7 @@ const ExternalConfig = ({ bordered }: any) => {
value={config.external.discord.clientId}
disabled={config.external.discord.enabled}
onChange={(e: boolean) => {
settings.setSync('discord.clientId', e);
settings.set('discord.clientId', e);
dispatch(setDiscord({ ...config.external.discord, clientId: e }));
}}
/>
@ -88,7 +86,7 @@ const ExternalConfig = ({ bordered }: any) => {
defaultChecked={config.external.discord.serverImage}
checked={config.external.discord.serverImage}
onChange={(e: boolean) => {
settings.setSync('discord.serverImage', e);
settings.set('discord.serverImage', e);
dispatch(setDiscord({ ...config.external.discord, serverImage: e }));
}}
/>
@ -106,7 +104,7 @@ const ExternalConfig = ({ bordered }: any) => {
defaultValue={config.external.obs.type}
value={config.external.obs.type}
onChange={(e: string) => {
settings.setSync('obs.type', e);
settings.set('obs.type', e);
dispatch(setOBS({ ...config.external.obs, type: e }));
}}
>
@ -124,7 +122,7 @@ const ExternalConfig = ({ bordered }: any) => {
defaultChecked={config.external.obs.enabled}
checked={config.external.obs.enabled}
onChange={(e: boolean) => {
settings.setSync('obs.enabled', e);
settings.set('obs.enabled', e);
dispatch(setOBS({ ...config.external.obs, enabled: e }));
}}
/>
@ -145,7 +143,7 @@ const ExternalConfig = ({ bordered }: any) => {
max={25000}
width={125}
onChange={(e: number) => {
settings.setSync('obs.pollingInterval', e);
settings.set('obs.pollingInterval', e);
dispatch(setOBS({ ...config.external.obs, pollingInterval: e }));
}}
/>
@ -162,7 +160,7 @@ const ExternalConfig = ({ bordered }: any) => {
placeholder="http://localhost:1608"
value={config.external.obs.url}
onChange={(e: string) => {
settings.setSync('obs.url', e);
settings.set('obs.url', e);
dispatch(setOBS({ ...config.external.obs, url: e }));
}}
/>
@ -177,14 +175,17 @@ const ExternalConfig = ({ bordered }: any) => {
<StyledInput disabled width={200} value={config.external.obs.path} />
<StyledInputGroupButton
onClick={() => {
const path = dialog.showOpenDialogSync({
properties: ['openFile', 'openDirectory'],
});
ipcRenderer
.invoke('file-path')
.then((path) => {
if (path) {
settings.set('obs.path', path[0]);
dispatch(setOBS({ ...config.external.obs, path: path[0] }));
}
if (path) {
settings.setSync('obs.path', path[0]);
dispatch(setOBS({ ...config.external.obs, path: path[0] }));
}
return null;
})
.catch((err) => console.log(err));
}}
>
<Icon icon="folder-open" />

13
src/components/settings/ConfigPanels/ListViewConfig.tsx

@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { nanoid } from 'nanoid/non-secure';
import settings from 'electron-settings';
import { useTranslation } from 'react-i18next';
import i18n from '../../../i18n/i18n';
import {
@ -21,6 +20,7 @@ import {
} from '../../../redux/configSlice';
import ConfigOption from '../ConfigOption';
import useListClickHandler from '../../../hooks/useListClickHandler';
import { settings } from '../../shared/setDefaultSettings';
const columnSelectorColumns = [
{
@ -73,10 +73,7 @@ const ListViewConfig = ({
setSelectedColumns(cols);
settings.setSync(
settingsConfig.columnList,
config.lookAndFeel.listView[columnListType].columns
);
settings.set(settingsConfig.columnList, config.lookAndFeel.listView[columnListType].columns);
}, [columnListType, config.lookAndFeel.listView, settingsConfig.columnList]);
const { handleRowClick } = useListClickHandler({});
@ -146,7 +143,7 @@ const ListViewConfig = ({
});
dispatch(setColumnList({ listType: columnListType, entries: columns }));
settings.setSync(settingsConfig.columnList, cleanColumns);
settings.set(settingsConfig.columnList, cleanColumns);
}}
labelKey="label"
valueKey="label"
@ -189,7 +186,7 @@ const ListViewConfig = ({
max={250}
width={125}
onChange={(e: number) => {
settings.setSync(settingsConfig.rowHeight, Number(e));
settings.set(settingsConfig.rowHeight, Number(e));
dispatch(setRowHeight({ listType: columnListType, height: Number(e) }));
}}
/>
@ -207,7 +204,7 @@ const ListViewConfig = ({
max={100}
width={125}
onChange={(e: number) => {
settings.setSync(settingsConfig.fontSize, Number(e));
settings.set(settingsConfig.fontSize, Number(e));
dispatch(setFontSize({ listType: columnListType, size: Number(e) }));
}}
/>

80
src/components/settings/ConfigPanels/LookAndFeelConfig.tsx

@ -2,7 +2,6 @@ import React, { useRef, useState } from 'react';
import _ from 'lodash';
import { useQuery } from 'react-query';
import { ipcRenderer, shell } from 'electron';
import settings from 'electron-settings';
import { Nav, Icon, RadioGroup, Whisper, Divider } from 'rsuite';
import { WhisperInstance } from 'rsuite/lib/Whisper';
import { Trans, useTranslation } from 'react-i18next';
@ -54,6 +53,7 @@ import { setPagination } from '../../../redux/viewSlice';
import { MUSIC_SORT_TYPES } from '../../library/MusicList';
import Popup from '../../shared/Popup';
import { apiController } from '../../../api/controller';
import { settings } from '../../shared/setDefaultSettings';
export const ListViewConfigPanel = ({ bordered }: any) => {
const { t } = useTranslation();
@ -61,15 +61,15 @@ export const ListViewConfigPanel = ({ bordered }: any) => {
const config = useAppSelector((state) => state.config);
const [highlightOnRowHoverChk, setHighlightOnRowHoverChk] = useState(
Boolean(settings.getSync('highlightOnRowHover'))
Boolean(settings.get('highlightOnRowHover'))
);
const songCols: any = settings.getSync('musicListColumns');
const albumCols: any = settings.getSync('albumListColumns');
const playlistCols: any = settings.getSync('playlistListColumns');
const artistCols: any = settings.getSync('artistListColumns');
const miniCols: any = settings.getSync('miniListColumns');
const genreCols: any = settings.getSync('genreListColumns');
const songCols: any = settings.get('musicListColumns');
const albumCols: any = settings.get('albumListColumns');
const playlistCols: any = settings.get('playlistListColumns');
const artistCols: any = settings.get('artistListColumns');
const miniCols: any = settings.get('miniListColumns');
const genreCols: any = settings.get('genreListColumns');
const currentSongColumns = songCols?.map((column: any) => column.label) || [];
const currentAlbumColumns = albumCols?.map((column: any) => column.label) || [];
@ -201,7 +201,7 @@ export const ListViewConfigPanel = ({ bordered }: any) => {
defaultChecked={highlightOnRowHoverChk}
checked={highlightOnRowHoverChk}
onChange={(e: boolean) => {
settings.setSync('highlightOnRowHover', e);
settings.set('highlightOnRowHover', e);
dispatch(
setMiscSetting({
setting: 'highlightOnRowHover',
@ -235,7 +235,7 @@ export const GridViewConfigPanel = ({ bordered }: any) => {
max={350}
width={125}
onChange={(e: any) => {
settings.setSync('gridCardSize', Number(e));
settings.set('gridCardSize', Number(e));
dispatch(setGridCardSize({ size: Number(e) }));
}}
/>
@ -253,7 +253,7 @@ export const GridViewConfigPanel = ({ bordered }: any) => {
max={100}
width={125}
onChange={(e: any) => {
settings.setSync('gridGapSize', Number(e));
settings.set('gridGapSize', Number(e));
dispatch(setGridGapSize({ size: Number(e) }));
}}
/>
@ -271,7 +271,7 @@ export const GridViewConfigPanel = ({ bordered }: any) => {
value={config.lookAndFeel.gridView.alignment}
onChange={(e: string) => {
dispatch(setGridAlignment({ alignment: e }));
settings.setSync('gridAlignment', e);
settings.set('gridAlignment', e);
}}
>
<StyledRadio value="flex-start">{t('Left')}</StyledRadio>
@ -288,10 +288,10 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
const dispatch = useAppDispatch();
const config = useAppSelector((state) => state.config);
const [dynamicBackgroundChk, setDynamicBackgroundChk] = useState(
Boolean(settings.getSync('dynamicBackground'))
Boolean(settings.get('dynamicBackground'))
);
const [selectedTheme, setSelectedTheme] = useState(String(settings.getSync('theme')));
const [selectedTheme, setSelectedTheme] = useState(String(settings.get('theme')));
const languagePickerContainerRef = useRef(null);
const themePickerContainerRef = useRef(null);
const fontPickerContainerRef = useRef(null);
@ -302,7 +302,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
const sidebarPickerContainerRef = useRef(null);
const titleBarRestartWhisper = React.createRef<WhisperInstance>();
const [themeList, setThemeList] = useState(
_.concat(settings.getSync('themes'), settings.getSync('themesDefault'))
_.concat(settings.get('themes'), settings.get('themesDefault'))
);
const { data: playlists }: any = useQuery(['playlists'], () =>
@ -321,7 +321,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
data={Languages}
width={200}
cleanable={false}
defaultValue={String(settings.getSync('language'))}
defaultValue={String(settings.get('language'))}
placeholder={t('Select')}
onChange={(e: string) => {
i18n.changeLanguage(e, (err) => {
@ -329,7 +329,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
notifyToast('error', 'Error while changing the language');
}
});
settings.setSync('language', e);
settings.set('language', e);
}}
/>
</StyledInputPickerContainer>
@ -346,9 +346,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
onClick={() => {
dispatch(setTheme('defaultDark'));
dispatch(setTheme(selectedTheme));
setThemeList(
_.concat(settings.getSync('themes'), settings.getSync('themesDefault'))
);
setThemeList(_.concat(settings.get('themes'), settings.get('themesDefault')));
}}
/>
</>
@ -377,7 +375,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
defaultValue={selectedTheme}
placeholder={t('Select')}
onChange={(e: string) => {
settings.setSync('theme', e);
settings.set('theme', e);
setSelectedTheme(e);
dispatch(setTheme(e));
}}
@ -398,10 +396,10 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
groupBy="role"
width={200}
cleanable={false}
defaultValue={String(settings.getSync('font'))}
defaultValue={String(settings.get('font'))}
placeholder={t('Select')}
onChange={(e: string) => {
settings.setSync('font', e);
settings.set('font', e);
dispatch(setFont(e));
}}
/>
@ -454,11 +452,11 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
},
]}
cleanable={false}
defaultValue={String(settings.getSync('titleBarStyle'))}
defaultValue={String(settings.get('titleBarStyle'))}
width={200}
placeholder={t('Select')}
onChange={(e: string) => {
settings.setSync('titleBarStyle', e);
settings.set('titleBarStyle', e);
dispatch(setMiscSetting({ setting: 'titleBar', value: e }));
titleBarRestartWhisper.current?.open();
}}
@ -476,7 +474,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
defaultChecked={dynamicBackgroundChk}
checked={dynamicBackgroundChk}
onChange={(e: boolean) => {
settings.setSync('dynamicBackground', e);
settings.set('dynamicBackground', e);
dispatch(setDynamicBackground(e));
setDynamicBackgroundChk(e);
}}
@ -542,11 +540,11 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
)}
cleanable={false}
groupBy="role"
defaultValue={String(settings.getSync('startPage'))}
defaultValue={String(settings.get('startPage'))}
width={200}
placeholder={t('Select')}
onChange={(e: string) => {
settings.setSync('startPage', e);
settings.set('startPage', e);
}}
/>
</StyledInputPickerContainer>
@ -565,10 +563,10 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
config.serverType === Server.Jellyfin ? ['frequent', 'recent'] : []
}
cleanable={false}
defaultValue={String(settings.getSync('albumSortDefault'))}
defaultValue={String(settings.get('albumSortDefault'))}
width={200}
onChange={(e: string) => {
settings.setSync('albumSortDefault', e);
settings.set('albumSortDefault', e);
}}
/>
</StyledInputPickerContainer>
@ -584,10 +582,10 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
container={() => musicSortDefaultPickerContainerRef.current}
data={MUSIC_SORT_TYPES}
cleanable={false}
defaultValue={String(settings.getSync('musicSortDefault'))}
defaultValue={String(settings.get('musicSortDefault'))}
width={200}
onChange={(e: string) => {
settings.setSync('musicSortDefault', e);
settings.set('musicSortDefault', e);
}}
/>
</StyledInputPickerContainer>
@ -601,9 +599,9 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
)}
option={
<StyledToggle
defaultChecked={Boolean(settings.getSync('artistPageLegacy'))}
defaultChecked={Boolean(settings.get('artistPageLegacy'))}
onChange={(e: boolean) => {
settings.setSync('artistPageLegacy', e);
settings.set('artistPageLegacy', e);
}}
/>
}
@ -670,7 +668,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
width={250}
disabledItemValues={config.serverType === Server.Subsonic ? ['songs'] : []}
onChange={(e: string) => {
settings.setSync('sidebar.selected', e);
settings.set('sidebar.selected', e);
dispatch(setSidebar({ selected: e }));
}}
/>
@ -711,15 +709,15 @@ export const PaginationConfigPanel = ({ bordered }: any) => {
data: { activePage: 1, recordsPerPage: Number(e) },
})
);
settings.setSync('pagination.music.recordsPerPage', Number(e));
settings.set('pagination.music.recordsPerPage', Number(e));
}}
/>
{config.serverType === Server.Jellyfin && (
<StyledCheckbox
defaultChecked={settings.getSync('pagination.music.serverSide')}
defaultChecked={settings.get('pagination.music.serverSide')}
checked={view.music.pagination.serverSide}
onChange={(_v: any, e: boolean) => {
settings.setSync('pagination.music.serverSide', e);
settings.set('pagination.music.serverSide', e);
dispatch(setPagination({ listType: Item.Music, data: { serverSide: e } }));
}}
>
@ -747,15 +745,15 @@ export const PaginationConfigPanel = ({ bordered }: any) => {
data: { activePage: 1, recordsPerPage: Number(e) },
})
);
settings.setSync('pagination.album.recordsPerPage', Number(e));
settings.set('pagination.album.recordsPerPage', Number(e));
}}
/>
{config.serverType === Server.Jellyfin && (
<StyledCheckbox
defaultChecked={settings.getSync('pagination.album.serverSide')}
defaultChecked={settings.get('pagination.album.serverSide')}
checked={view.album.pagination.serverSide}
onChange={(_v: any, e: boolean) => {
settings.setSync('pagination.album.serverSide', e);
settings.set('pagination.album.serverSide', e);
dispatch(setPagination({ listType: Item.Album, data: { serverSide: e } }));
}}
>

22
src/components/settings/ConfigPanels/PlaybackConfig.tsx

@ -1,5 +1,4 @@
import React, { useRef, useState } from 'react';
import settings from 'electron-settings';
import { ButtonToolbar } from 'rsuite';
import { useTranslation } from 'react-i18next';
import { ConfigPanel } from '../styled';
@ -13,22 +12,19 @@ import {
import { useAppDispatch } from '../../../redux/hooks';
import { setPlaybackSetting } from '../../../redux/playQueueSlice';
import ConfigOption from '../ConfigOption';
import { settings } from '../../shared/setDefaultSettings';
const PlaybackConfig = ({ bordered }: any) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const [crossfadeDuration, setCrossfadeDuration] = useState(
Number(settings.getSync('fadeDuration'))
);
const [pollingInterval, setPollingInterval] = useState(
Number(settings.getSync('pollingInterval'))
);
const [volumeFade, setVolumeFade] = useState(Boolean(settings.getSync('volumeFade')));
const [crossfadeDuration, setCrossfadeDuration] = useState(Number(settings.get('fadeDuration')));
const [pollingInterval, setPollingInterval] = useState(Number(settings.get('pollingInterval')));
const [volumeFade, setVolumeFade] = useState(Boolean(settings.get('volumeFade')));
const crossfadePickerContainerRef = useRef(null);
const handleSetCrossfadeDuration = (e: number) => {
setCrossfadeDuration(e);
settings.setSync('fadeDuration', Number(e));
settings.set('fadeDuration', Number(e));
dispatch(
setPlaybackSetting({
setting: 'fadeDuration',
@ -39,7 +35,7 @@ const PlaybackConfig = ({ bordered }: any) => {
const handleSetPollingInterval = (e: number) => {
setPollingInterval(e);
settings.setSync('pollingInterval', Number(e));
settings.set('pollingInterval', Number(e));
dispatch(
setPlaybackSetting({
setting: 'pollingInterval',
@ -50,7 +46,7 @@ const PlaybackConfig = ({ bordered }: any) => {
const handleSetVolumeFade = (e: boolean) => {
setVolumeFade(e);
settings.setSync('volumeFade', e);
settings.set('volumeFade', e);
dispatch(setPlaybackSetting({ setting: 'volumeFade', value: e }));
};
@ -133,10 +129,10 @@ const PlaybackConfig = ({ bordered }: any) => {
},
]}
cleanable={false}
defaultValue={String(settings.getSync('fadeType'))}
defaultValue={String(settings.get('fadeType'))}
placeholder={t('Select')}
onChange={(e: string) => {
settings.setSync('fadeType', e);
settings.set('fadeType', e);
dispatch(setPlaybackSetting({ setting: 'fadeType', value: e }));
}}
width={200}

42
src/components/settings/ConfigPanels/PlayerConfig.tsx

@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { ipcRenderer, shell } from 'electron';
import settings from 'electron-settings';
import { Form, Whisper } from 'rsuite';
import { WhisperInstance } from 'rsuite/lib/Whisper';
import { Trans, useTranslation } from 'react-i18next';
@ -26,6 +25,7 @@ import ConfigOption from '../ConfigOption';
import { Server } from '../../../types';
import { isWindows, isWindows10 } from '../../../shared/utils';
import Popup from '../../shared/Popup';
import { settings } from '../../shared/setDefaultSettings';
const getAudioDevice = async () => {
const devices = await navigator.mediaDevices.enumerateDevices();
@ -74,21 +74,21 @@ const PlayerConfig = ({ bordered }: any) => {
const multiSelect = useAppSelector((state) => state.multiSelect);
const config = useAppSelector((state) => state.config);
const [newFilter, setNewFilter] = useState({ string: '', valid: false });
const [transcode, setTranscode] = useState(Boolean(settings.getSync('transcode')));
const [transcode, setTranscode] = useState(Boolean(settings.get('transcode')));
const [globalMediaHotkeys, setGlobalMediaHotkeys] = useState(
Boolean(settings.getSync('globalMediaHotkeys'))
Boolean(settings.get('globalMediaHotkeys'))
);
const [systemMediaTransportControls, setSystemMediaTransportControls] = useState(
Boolean(settings.getSync('systemMediaTransportControls'))
Boolean(settings.get('systemMediaTransportControls'))
);
const [resume, setResume] = useState(Boolean(settings.getSync('resume')));
const [scrobble, setScrobble] = useState(Boolean(settings.getSync('scrobble')));
const [resume, setResume] = useState(Boolean(settings.get('resume')));
const [scrobble, setScrobble] = useState(Boolean(settings.get('scrobble')));
const [audioDevices, setAudioDevices] = useState<MediaDeviceInfo[]>();
const audioDevicePickerContainerRef = useRef(null);
const transcodingRestartWhisper = useRef<WhisperInstance>();
useEffect(() => {
settings.setSync('playbackFilters', config.playback.filters);
settings.set('playbackFilters', config.playback.filters);
}, [config.playback.filters]);
useEffect(() => {
@ -121,7 +121,7 @@ const PlayerConfig = ({ bordered }: any) => {
placeholder={t('Select')}
onChange={(e: string) => {
dispatch(setAudioDeviceId(e));
settings.setSync('audioDeviceId', e);
settings.set('audioDeviceId', e);
}}
/>
</StyledInputPickerContainer>
@ -134,13 +134,13 @@ const PlayerConfig = ({ bordered }: any) => {
)}
option={
<StyledInputNumber
defaultValue={String(settings.getSync('seekForwardInterval')) || '0'}
defaultValue={String(settings.get('seekForwardInterval')) || '0'}
step={0.5}
min={0}
max={100}
width={125}
onChange={(e: any) => {
settings.setSync('seekForwardInterval', Number(e));
settings.set('seekForwardInterval', Number(e));
}}
/>
}
@ -152,13 +152,13 @@ const PlayerConfig = ({ bordered }: any) => {
)}
option={
<StyledInputNumber
defaultValue={String(settings.getSync('seekBackwardInterval')) || '0'}
defaultValue={String(settings.get('seekBackwardInterval')) || '0'}
step={0.5}
min={0}
max={100}
width={125}
onChange={(e: any) => {
settings.setSync('seekBackwardInterval', Number(e));
settings.set('seekBackwardInterval', Number(e));
}}
/>
}
@ -178,7 +178,7 @@ const PlayerConfig = ({ bordered }: any) => {
defaultChecked={resume}
checked={resume}
onChange={(e: boolean) => {
settings.setSync('resume', e);
settings.set('resume', e);
setResume(e);
}}
/>
@ -219,7 +219,7 @@ const PlayerConfig = ({ bordered }: any) => {
defaultChecked={transcode}
checked={transcode}
onChange={(e: boolean) => {
settings.setSync('transcode', e);
settings.set('transcode', e);
setTranscode(e);
transcodingRestartWhisper.current?.open();
}}
@ -252,12 +252,12 @@ const PlayerConfig = ({ bordered }: any) => {
defaultChecked={globalMediaHotkeys}
checked={globalMediaHotkeys}
onChange={(e: boolean) => {
settings.setSync('globalMediaHotkeys', e);
settings.set('globalMediaHotkeys', e);
setGlobalMediaHotkeys(e);
if (e) {
ipcRenderer.send('enableGlobalHotkeys');
settings.setSync('systemMediaTransportControls', !e);
settings.set('systemMediaTransportControls', !e);
setSystemMediaTransportControls(!e);
ipcRenderer.send('disableSystemMediaTransportControls');
} else {
@ -283,12 +283,12 @@ const PlayerConfig = ({ bordered }: any) => {
defaultChecked={systemMediaTransportControls}
checked={systemMediaTransportControls}
onChange={(e: boolean) => {
settings.setSync('systemMediaTransportControls', e);
settings.set('systemMediaTransportControls', e);
setSystemMediaTransportControls(e);
if (e) {
ipcRenderer.send('enableSystemMediaTransportControls');
settings.setSync('globalMediaHotkeys', !e);
settings.set('globalMediaHotkeys', !e);
setGlobalMediaHotkeys(!e);
ipcRenderer.send('disableGlobalHotkeys');
} else {
@ -308,7 +308,7 @@ const PlayerConfig = ({ bordered }: any) => {
defaultChecked={config.player.systemNotifications}
checked={config.player.systemNotifications}
onChange={(e: boolean) => {
settings.setSync('systemNotifications', e);
settings.set('systemNotifications', e);
dispatch(setPlayer({ systemNotifications: e }));
}}
/>
@ -325,7 +325,7 @@ const PlayerConfig = ({ bordered }: any) => {
defaultChecked={scrobble}
checked={scrobble}
onChange={(e: boolean) => {
settings.setSync('scrobble', e);
settings.set('scrobble', e);
dispatch(setPlaybackSetting({ setting: 'scrobble', value: e }));
setScrobble(e);
}}
@ -363,7 +363,7 @@ const PlayerConfig = ({ bordered }: any) => {
disabled={newFilter.string === '' || newFilter.valid === false}
onClick={() => {
dispatch(appendPlaybackFilter({ filter: newFilter.string, enabled: true }));
settings.setSync(
settings.set(
'playbackFilters',
config.playback.filters.concat({
filter: newFilter,

18
src/components/settings/ConfigPanels/ServerConfig.tsx

@ -1,5 +1,4 @@
import React, { useRef } from 'react';
import settings from 'electron-settings';
import { useQuery } from 'react-query';
import { CheckboxGroup } from 'rsuite';
import { useTranslation } from 'react-i18next';
@ -11,6 +10,7 @@ import { apiController } from '../../../api/controller';
import { Folder, Server } from '../../../types';
import ConfigOption from '../ConfigOption';
import CenterLoader from '../../loader/CenterLoader';
import { settings } from '../../shared/setDefaultSettings';
const ServerConfig = ({ bordered }: any) => {
const { t } = useTranslation();
@ -45,8 +45,8 @@ const ServerConfig = ({ bordered }: any) => {
placeholder={t('Select')}
onChange={(e: string) => {
const selectedFolder = musicFolders.find((f: Folder) => f.id === e);
settings.setSync('musicFolder.id', e);
settings.setSync('musicFolder.name', selectedFolder?.title);
settings.set('musicFolder.id', e);
settings.set('musicFolder.name', selectedFolder?.title);
dispatch(setMusicFolder({ id: e, name: selectedFolder?.title }));
}}
/>
@ -63,7 +63,7 @@ const ServerConfig = ({ bordered }: any) => {
defaultChecked={folder.applied.albums}
onChange={(_v: any, e: boolean) => {
dispatch(setAppliedFolderViews({ ...folder.applied, albums: e }));
settings.setSync('musicFolder.albums', e);
settings.set('musicFolder.albums', e);
}}
>
{t('Albums')}
@ -72,7 +72,7 @@ const ServerConfig = ({ bordered }: any) => {
defaultChecked={folder.applied.artists}
onChange={(_v: any, e: boolean) => {
dispatch(setAppliedFolderViews({ ...folder.applied, artists: e }));
settings.setSync('musicFolder.artists', e);
settings.set('musicFolder.artists', e);
}}
>
{t('Artists')}
@ -81,7 +81,7 @@ const ServerConfig = ({ bordered }: any) => {
defaultChecked={folder.applied.dashboard}
onChange={(_v: any, e: boolean) => {
dispatch(setAppliedFolderViews({ ...folder.applied, dashboard: e }));
settings.setSync('musicFolder.dashboard', e);
settings.set('musicFolder.dashboard', e);
}}
>
{t('Dashboard')}
@ -90,7 +90,7 @@ const ServerConfig = ({ bordered }: any) => {
defaultChecked={folder.applied.starred}
onChange={(_v: any, e: boolean) => {
dispatch(setAppliedFolderViews({ ...folder.applied, starred: e }));
settings.setSync('musicFolder.starred', e);
settings.set('musicFolder.starred', e);
}}
>
{t('Favorites')}
@ -99,7 +99,7 @@ const ServerConfig = ({ bordered }: any) => {
defaultChecked={folder.applied.search}
onChange={(_v: any, e: boolean) => {
dispatch(setAppliedFolderViews({ ...folder.applied, search: e }));
settings.setSync('musicFolder.search', e);
settings.set('musicFolder.search', e);
}}
>
{t('Search')}
@ -109,7 +109,7 @@ const ServerConfig = ({ bordered }: any) => {
defaultChecked={folder.applied.music}
onChange={(_v: any, e: boolean) => {
dispatch(setAppliedFolderViews({ ...folder.applied, music: e }));
settings.setSync('musicFolder.music', e);
settings.set('musicFolder.music', e);
}}
>
{t('Songs')}

6
src/components/settings/ConfigPanels/WindowConfig.tsx

@ -1,11 +1,11 @@
import React from 'react';
import settings from 'electron-settings';
import { useTranslation } from 'react-i18next';
import { ConfigOptionDescription, ConfigPanel } from '../styled';
import { StyledToggle } from '../../shared/styled';
import ConfigOption from '../ConfigOption';
import { setWindow } from '../../../redux/configSlice';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { settings } from '../../shared/setDefaultSettings';
const WindowConfig = ({ bordered }: any) => {
const { t } = useTranslation();
@ -28,7 +28,7 @@ const WindowConfig = ({ bordered }: any) => {
defaultChecked={config.window.minimizeToTray}
checked={config.window.minimizeToTray}
onChange={(e: boolean) => {
settings.setSync('minimizeToTray', e);
settings.set('minimizeToTray', e);
dispatch(setWindow({ minimizeToTray: e }));
}}
/>
@ -43,7 +43,7 @@ const WindowConfig = ({ bordered }: any) => {
defaultChecked={config.window.exitToTray}
checked={config.window.exitToTray}
onChange={(e: boolean) => {
settings.setSync('exitToTray', e);
settings.set('exitToTray', e);
dispatch(setWindow({ exitToTray: e }));
}}
/>

22
src/components/settings/DisconnectButton.tsx

@ -1,7 +1,7 @@
import React from 'react';
import settings from 'electron-settings';
import { useTranslation } from 'react-i18next';
import { StyledButton } from '../shared/styled';
import { settings } from '../shared/setDefaultSettings';
export const handleDisconnect = () => {
localStorage.removeItem('server');
@ -12,18 +12,18 @@ export const handleDisconnect = () => {
localStorage.removeItem('hash');
localStorage.removeItem('token');
settings.setSync('server', '');
settings.setSync('serverBase64', '');
settings.setSync('username', '');
settings.setSync('userId', '');
settings.setSync('password', '');
settings.setSync('salt', '');
settings.setSync('hash', '');
settings.setSync('token', '');
settings.set('server', '');
settings.set('serverBase64', '');
settings.set('username', '');
settings.set('userId', '');
settings.set('password', '');
settings.set('salt', '');
settings.set('hash', '');
settings.set('token', '');
// Remove the selected musicFolder on disconnect since it will cause conflicts with other servers
settings.setSync('musicFolder.id', null);
settings.setSync('musicFolder.name', null);
settings.set('musicFolder.id', null);
settings.set('musicFolder.name', null);
window.location.reload();
};

33
src/components/settings/Login.tsx

@ -1,11 +1,10 @@
import React, { useRef, useState } from 'react';
import md5 from 'md5';
import randomstring from 'randomstring';
import settings from 'electron-settings';
import { Form, ControlLabel, Message, RadioGroup } from 'rsuite';
import axios from 'axios';
import { useTranslation } from 'react-i18next';
import setDefaultSettings from '../shared/setDefaultSettings';
import { settings, setDefaultSettings } from '../shared/setDefaultSettings';
import {
StyledButton,
StyledCheckbox,
@ -68,13 +67,13 @@ const Login = () => {
localStorage.setItem('salt', salt);
localStorage.setItem('hash', hash);
settings.setSync('server', cleanServerName);
settings.setSync('serverBase64', btoa(cleanServerName));
settings.setSync('serverType', 'subsonic');
settings.setSync('username', userName);
settings.setSync('password', password);
settings.setSync('salt', salt);
settings.setSync('hash', hash);
settings.set('server', cleanServerName);
settings.set('serverBase64', btoa(cleanServerName));
settings.set('serverType', 'subsonic');
settings.set('username', userName);
settings.set('password', password);
settings.set('salt', salt);
settings.set('hash', hash);
// Set defaults on login
setDefaultSettings(false);
@ -107,12 +106,12 @@ const Login = () => {
localStorage.setItem('token', data.AccessToken);
localStorage.setItem('deviceId', deviceId);
settings.setSync('server', cleanServerName);
settings.setSync('serverBase64', btoa(cleanServerName));
settings.setSync('serverType', 'jellyfin');
settings.setSync('username', data.User.Id);
settings.setSync('token', data.AccessToken);
settings.setSync('deviceId', deviceId);
settings.set('server', cleanServerName);
settings.set('serverBase64', btoa(cleanServerName));
settings.set('serverType', 'jellyfin');
settings.set('username', data.User.Id);
settings.set('token', data.AccessToken);
settings.set('deviceId', deviceId);
} catch (err) {
if (err instanceof Error) {
setMessage(`${err.message}`);
@ -184,11 +183,11 @@ const Login = () => {
defaultChecked={
process.env.NODE_ENV === 'test'
? mockSettings.legacyAuth
: Boolean(settings.getSync('legacyAuth'))
: Boolean(settings.get('legacyAuth'))
}
checked={legacyAuth}
onChange={(_v: any, e: boolean) => {
settings.setSync('legacyAuth', e);
settings.set('legacyAuth', e);
setLegacyAuth(e);
}}
>

1129
src/components/shared/setDefaultSettings.ts

File diff suppressed because it is too large

8
src/components/starred/StarredView.tsx

@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router';
import { useQuery, useQueryClient } from 'react-query';
import { Nav } from 'rsuite';
import settings from 'electron-settings';
import { useTranslation } from 'react-i18next';
import useSearchQuery from '../../hooks/useSearchQuery';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
@ -23,6 +22,7 @@ import CenterLoader from '../loader/CenterLoader';
import useListClickHandler from '../../hooks/useListClickHandler';
import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
const StarredView = () => {
const { t } = useTranslation();
@ -33,7 +33,7 @@ const StarredView = () => {
const favorite = useAppSelector((state) => state.favorite);
const config = useAppSelector((state) => state.config);
const misc = useAppSelector((state) => state.misc);
const [viewType, setViewType] = useState(settings.getSync('albumViewType') || 'list');
const [viewType, setViewType] = useState(settings.get('albumViewType') || 'list');
const [musicFolder, setMusicFolder] = useState(undefined);
useEffect(() => {
@ -283,7 +283,7 @@ const StarredView = () => {
rowHeight={config.lookAndFeel.listView.music.rowHeight}
fontSize={config.lookAndFeel.listView.music.fontSize}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}
@ -332,7 +332,7 @@ const StarredView = () => {
})
}
cacheImages={{
enabled: settings.getSync('cacheImages'),
enabled: settings.get('cacheImages'),
cacheType: 'album',
cacheIdProperty: 'albumId',
}}

50
src/components/table/DraggableCell.tsx

@ -1,50 +0,0 @@
import React from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { Table } from 'rsuite';
const ItemTypes = {
COLUMN: 'column',
ROW: 'row',
};
const DraggableCell = ({ children, onDrag, id, rowData, ...rest }: any) => {
const ref = React.useRef(null);
const [{ canDrop, isOver }, drop] = useDrop({
accept: ItemTypes.ROW,
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
drop(item: any) {
onDrag(item.id, rowData.id);
},
});
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.ROW,
item: { id: rowData.id, type: ItemTypes.ROW },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
const isActive = canDrop && isOver;
drag(drop(ref));
const styles = {
opacity: isDragging ? 0.5 : 1,
background: isActive ? '#ddd' : undefined,
};
return (
<Table.Cell {...rest} style={{ padding: 0 }}>
<div ref={ref} style={styles}>
{children}
</div>
</Table.Cell>
);
};
export default DraggableCell;

50
src/components/table/DraggableHeaderCell.tsx

@ -1,50 +0,0 @@
import React from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { Table } from 'rsuite';
const ItemTypes = {
COLUMN: 'column',
ROW: 'row',
};
const DraggableHeaderCell = ({ children, onDrag, id, ...rest }: any) => {
const ref = React.useRef(null);
const [{ canDrop, isOver }, drop] = useDrop({
accept: ItemTypes.COLUMN,
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
drop(item: any) {
onDrag(item.id, id);
},
});
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.COLUMN,
item: { id, type: ItemTypes.COLUMN },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
const isActive = canDrop && isOver;
drag(drop(ref));
return (
<Table.HeaderCell {...rest} style={{ padding: 0 }}>
<div
ref={ref}
style={{
opacity: isDragging ? 0 : 1,
backgroundColor: isActive ? '#3B4552' : undefined,
}}
>
{children}
</div>
</Table.HeaderCell>
);
};
export default DraggableHeaderCell;

4
src/components/viewtypes/GridViewType.tsx

@ -1,6 +1,5 @@
// Referenced from: https://codesandbox.io/s/jjkz5y130w?file=/index.js:700-703
import React, { useEffect, useMemo, useState } from 'react';
import settings from 'electron-settings';
import { FixedSizeList as List } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
import Card from '../card/Card';
@ -8,6 +7,7 @@ import 'react-virtualized/styles.css';
import { useAppSelector } from '../../redux/hooks';
import Paginator from '../shared/Paginator';
import CenterLoader from '../loader/CenterLoader';
import { settings } from '../shared/setDefaultSettings';
const GridCard = ({ data, index, style }: any) => {
const { cardHeight, cardWidth, columnCount, gapSize, itemCount } = data;
@ -178,7 +178,7 @@ const GridViewType = ({
loading,
gridRef,
}: any) => {
const cacheImages = Boolean(settings.getSync('cacheImages'));
const cacheImages = Boolean(settings.get('cacheImages'));
const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config);
const folder = useAppSelector((state) => state.folder);

9
src/components/viewtypes/ListViewTable.tsx

@ -3,7 +3,6 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import _ from 'lodash';
import settings from 'electron-settings';
import styled from 'styled-components';
import { useHotkeys } from 'react-hotkeys-hook';
import { nanoid } from 'nanoid';
@ -67,6 +66,7 @@ import CoverArtCell from './TableCells/CoverArtCell';
import TextCell from './TableCells/TextCell';
import LinkCell from './TableCells/LinkCell';
import CustomCell from './TableCells/CustomCell';
import { settings } from '../shared/setDefaultSettings';
const StyledTable = styled(Table)<{ rowHeight: number; $isDragging: boolean }>`
.rs-table-row.selected {
@ -521,6 +521,9 @@ const ListViewTable = ({
}}
>
{columns.map((column: any) => (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - children does actually exist, but rsuite expects react 16.
// Upgrading to rsuite 5 will come with some new changes (namely, different icons), so I will leave this in for now
<Table.Column
key={nanoid()}
align={column.alignment}
@ -536,9 +539,9 @@ const ListViewTable = ({
);
if (!miniView) {
settings.setSync(`${listType}ListColumns[${resizedColumnIndex}].width`, newWidth);
settings.set(`${listType}ListColumns[${resizedColumnIndex}].width`, newWidth);
} else {
settings.setSync(`miniListColumns[${resizedColumnIndex}].width`, newWidth);
settings.set(`miniListColumns[${resizedColumnIndex}].width`, newWidth);
}
const newCols = configState.lookAndFeel.listView[

6
src/components/viewtypes/ViewTypeButtons.tsx

@ -1,7 +1,7 @@
import React from 'react';
import { ButtonGroup, Icon } from 'rsuite';
import settings from 'electron-settings';
import { StyledButton } from '../shared/styled';
import { settings } from '../shared/setDefaultSettings';
const ViewTypeButtons = ({ handleListClick, handleGridClick, viewTypeSetting }: any) => {
return (
@ -12,7 +12,7 @@ const ViewTypeButtons = ({ handleListClick, handleGridClick, viewTypeSetting }:
onClick={async () => {
handleListClick();
localStorage.setItem(`${viewTypeSetting}ViewType`, 'list');
settings.setSync(`${viewTypeSetting}ViewType`, 'list');
settings.set(`${viewTypeSetting}ViewType`, 'list');
}}
>
<Icon icon="list" />
@ -23,7 +23,7 @@ const ViewTypeButtons = ({ handleListClick, handleGridClick, viewTypeSetting }:
onClick={async () => {
handleGridClick();
localStorage.setItem(`${viewTypeSetting}ViewType`, 'grid');
settings.setSync(`${viewTypeSetting}ViewType`, 'grid');
settings.set(`${viewTypeSetting}ViewType`, 'grid');
}}
>
<Icon icon="th-large" />

2
src/hooks/useBrowserDownload.ts

@ -12,7 +12,7 @@ export const useBrowserDownload = () => {
const config = useAppSelector((state) => state.config);
const handleDownload = useCallback(
async (data, type: 'copy' | 'download', playlist?: boolean) => {
async (data: any, type: 'copy' | 'download', playlist?: boolean) => {
const downloadUrls = [];
if (config.serverType === Server.Jellyfin) {

2
src/hooks/useFavorite.ts

@ -12,7 +12,7 @@ const useFavorite = () => {
const queryClient = useQueryClient();
const handleFavorite = useCallback(
async (rowData, options?: { queryKey?: any; custom?: any }) => {
async (rowData: any, options?: { queryKey?: any; custom?: any }) => {
const favorite = !rowData.starred;
await apiController({

46
src/hooks/usePlayerControls.ts

@ -1,5 +1,4 @@
import { useCallback, useEffect } from 'react';
import settings from 'electron-settings';
import { ipcRenderer } from 'electron';
import { deflate, inflate } from 'zlib';
import { join } from 'path';
@ -19,6 +18,7 @@ import {
import { setStatus } from '../redux/playerSlice';
import { apiController } from '../api/controller';
import { Server } from '../types';
import { settings } from '../components/shared/setDefaultSettings';
const usePlayerControls = (
config: any,
@ -120,6 +120,8 @@ const usePlayerControls = (
const handlePlayPause = useCallback(() => {
if (playQueue[currentEntryList].length > 0) {
if (player.status === 'PAUSED') {
navigator.mediaSession.playbackState = 'playing';
dispatch(setStatus('PLAYING'));
ipcRenderer.send('playpause', {
@ -130,6 +132,8 @@ const usePlayerControls = (
: Math.floor(playersRef.current.player2.audioEl.current.currentTime * 1000000),
});
} else {
navigator.mediaSession.playbackState = 'paused';
dispatch(setStatus('PAUSED'));
ipcRenderer.send('playpause', {
status: 'PAUSED',
@ -143,6 +147,8 @@ const usePlayerControls = (
}, [currentEntryList, dispatch, playQueue, player.status, playersRef]);
const handlePlay = useCallback(() => {
navigator.mediaSession.playbackState = 'playing';
ipcRenderer.send('playpause', {
status: 'PLAYING',
position:
@ -155,6 +161,8 @@ const usePlayerControls = (
}, [dispatch, playQueue.currentPlayer, playersRef]);
const handlePause = useCallback(() => {
navigator.mediaSession.playbackState = 'paused';
ipcRenderer.send('playpause', {
status: 'PAUSED',
position:
@ -173,6 +181,8 @@ const usePlayerControls = (
playersRef.current.player1.audioEl.current.currentTime = 0;
setCurrentTime(0);
navigator.mediaSession.playbackState = 'paused';
ipcRenderer.send('playpause', {
status: 'PAUSED',
position: 0,
@ -184,7 +194,7 @@ const usePlayerControls = (
}, [dispatch, playersRef, setCurrentTime]);
const handleSeekBackward = useCallback(() => {
const seekBackwardInterval = Number(settings.getSync('seekBackwardInterval'));
const seekBackwardInterval = Number(settings.get('seekBackwardInterval'));
if (playQueue[currentEntryList].length > 0) {
if (playQueue.isFading) {
if (playQueue.currentPlayer === 1) {
@ -214,7 +224,7 @@ const usePlayerControls = (
const handleSeekForward = useCallback(() => {
if (playQueue[currentEntryList].length > 0) {
const seekForwardInterval = Number(settings.getSync('seekForwardInterval'));
const seekForwardInterval = Number(settings.get('seekForwardInterval'));
if (playQueue.isFading) {
if (playQueue.currentPlayer === 1) {
@ -320,15 +330,15 @@ const usePlayerControls = (
);
const handleRepeat = useCallback(() => {
const currentRepeat = settings.getSync('repeat');
const currentRepeat = settings.get('repeat');
const newRepeat = currentRepeat === 'none' ? 'all' : currentRepeat === 'all' ? 'one' : 'none';
dispatch(toggleRepeat());
settings.setSync('repeat', newRepeat);
settings.set('repeat', newRepeat);
}, [dispatch]);
const handleShuffle = useCallback(() => {
dispatch(toggleShuffle());
settings.setSync('shuffle', !settings.getSync('shuffle'));
settings.set('shuffle', !settings.get('shuffle'));
}, [dispatch]);
const handleDisplayQueue = () => {
@ -477,6 +487,30 @@ const usePlayerControls = (
handleRestoreQueue,
]);
useEffect(() => {
const media = navigator.mediaSession;
media.setActionHandler('play', () => {
handlePlay();
});
media.setActionHandler('pause', () => {
handlePause();
});
media.setActionHandler('stop', () => {
handleStop();
});
media.setActionHandler('nexttrack', () => {
handleNextTrack();
});
media.setActionHandler('previoustrack', () => {
handlePrevTrack();
});
}, [handlePlay, handlePause, handleStop, handleNextTrack, handlePrevTrack]);
useEffect(() => {
ipcRenderer.on('current-position-request', (_event, arg) => {
if (arg.currentPlayer === 1) {

2
src/hooks/useRating.ts

@ -12,7 +12,7 @@ export const useRating = () => {
const config = useAppSelector((state) => state.config);
const handleRating = useCallback(
async (rowData, options: { queryKey?: any; rating: number; custom?: any }) => {
async (rowData: any, options: { queryKey?: any; rating: number; custom?: any }) => {
await apiController({
serverType: config.serverType,
endpoint: 'setRating',

5
src/i18n/i18n.js

@ -1,7 +1,8 @@
import settings from 'electron-settings';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { mockSettings } from '../shared/mockSettings';
// eslint-disable-next-line import/no-cycle
import { settings } from '../components/shared/setDefaultSettings';
// the translations
// (tip move them in a JSON file and import them,
@ -28,7 +29,7 @@ i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
lng: process.env.NODE_ENV === 'test' ? mockSettings.language : settings.getSync('language'),
lng: process.env.NODE_ENV === 'test' ? mockSettings.language : settings.get('language'),
fallbackLng: 'en', // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
// if you're using a language detector, do not define the lng option

14
src/index.tsx

@ -1,6 +1,5 @@
import React from 'react';
import settings from 'electron-settings';
import { render } from 'react-dom';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Provider } from 'react-redux';
import { HelmetProvider } from 'react-helmet-async';
@ -16,18 +15,13 @@ const queryClient = new QueryClient({
},
});
settings.configure({
prettify: true,
numSpaces: 2,
});
render(
const root = createRoot(document.getElementById('root')!);
root.render(
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<HelmetProvider>
<App />
</HelmetProvider>
</QueryClientProvider>
</Provider>,
document.getElementById('root')
</Provider>
);

74
src/main.dev.js

@ -12,8 +12,7 @@ import 'core-js/stable';
import 'regenerator-runtime/runtime';
import Player from 'mpris-service';
import path from 'path';
import settings from 'electron-settings';
import { ipcMain, app, BrowserWindow, shell, globalShortcut, Menu, Tray } from 'electron';
import { ipcMain, app, BrowserWindow, shell, globalShortcut, Menu, Tray, dialog } from 'electron';
import electronLocalshortcut from 'electron-localshortcut';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
@ -25,12 +24,7 @@ import multiSelectReducer from './redux/multiSelectSlice';
import configReducer from './redux/configSlice';
import MenuBuilder from './menu';
import { isWindows, isWindows10, isMacOS, isLinux } from './shared/utils';
import setDefaultSettings from './components/shared/setDefaultSettings';
settings.configure({
prettify: true,
numSpaces: 2,
});
import { settings, setDefaultSettings } from './components/shared/setDefaultSettings';
setDefaultSettings(false);
@ -187,25 +181,25 @@ if (isLinux()) {
mprisPlayer.on('shuffle', () => {
store.dispatch(toggleShuffle());
settings.setSync('shuffle', !settings.getSync('shuffle'));
mprisPlayer.shuffle = Boolean(settings.getSync('shuffle'));
settings.set('shuffle', !settings.get('shuffle'));
mprisPlayer.shuffle = Boolean(settings.get('shuffle'));
});
mprisPlayer.on('volume', (event) => {
const volume = Math.min(1, Math.max(0, event));
store.dispatch(setVolume(volume));
settings.setSync('volume', volume);
settings.set('volume', volume);
});
mprisPlayer.on('loopStatus', () => {
const currentRepeat = settings.getSync('repeat');
const currentRepeat = settings.get('repeat');
const newRepeat = currentRepeat === 'none' ? 'all' : currentRepeat === 'all' ? 'one' : 'none';
store.dispatch(toggleRepeat());
mprisPlayer.loopStatus =
newRepeat === 'none' ? 'None' : newRepeat === 'all' ? 'Playlist' : 'Track';
settings.setSync('repeat', newRepeat);
settings.set('repeat', newRepeat);
});
mprisPlayer.on('position', (event) => {
@ -426,15 +420,14 @@ const createWindow = async () => {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false,
preload: path.join(__dirname, 'preload.ts'), // Add custom titlebar functionality
},
autoHideMenuBar: true,
minWidth: 768,
minHeight: 600,
frame: settings.getSync('titleBarStyle') === 'native',
frame: settings.get('titleBarStyle') === 'native',
});
if (settings.getSync('globalMediaHotkeys')) {
if (settings.get('globalMediaHotkeys')) {
globalShortcut.register('MediaStop', () => {
stop();
});
@ -450,7 +443,7 @@ const createWindow = async () => {
globalShortcut.register('MediaPreviousTrack', () => {
previousTrack();
});
} else if (!settings.getSync('systemMediaTransportControls')) {
} else if (!settings.get('systemMediaTransportControls')) {
electronLocalshortcut.register(mainWindow, 'MediaStop', () => {
stop();
});
@ -491,7 +484,7 @@ const createWindow = async () => {
ipcMain.on('disableGlobalHotkeys', () => {
globalShortcut.unregisterAll();
if (!settings.getSync('systemMediaTransportControls')) {
if (!settings.get('systemMediaTransportControls')) {
electronLocalshortcut.register(mainWindow, 'MediaStop', () => {
stop();
});
@ -510,7 +503,7 @@ const createWindow = async () => {
}
});
mainWindow.loadURL(`file://${__dirname}/index.html#${settings.getSync('startPage')}`);
mainWindow.loadURL(`file://${__dirname}/index.html#${settings.get('startPage')}`);
// @TODO: Use 'ready-to-show' event
// https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event
@ -527,7 +520,7 @@ const createWindow = async () => {
createWinThumbarButtons();
}
if (settings.getSync('resume')) {
if (settings.get('resume')) {
restoreQueue();
}
});
@ -540,13 +533,14 @@ const createWindow = async () => {
});
mainWindow.on('close', (event) => {
if (!exitFromTray && store.getState().config.window.exitToTray) {
if (!exitFromTray && !forceQuit && store.getState().config.window.exitToTray) {
exitFromTray = true;
event.preventDefault();
mainWindow.hide();
}
// If we have enabled saving the queue, we need to defer closing the main window until it has finished saving.
if (!saved && settings.getSync('resume')) {
if (!saved && settings.get('resume')) {
event.preventDefault();
saved = true;
saveQueue(() => {
@ -582,15 +576,47 @@ const createWindow = async () => {
});
// Remove this if your app does not use auto updates
if (settings.getSync('autoUpdate') === true) {
if (settings.get('autoUpdate') === true) {
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on('update-downloaded', () => {
settings.setSync('autoUpdateNotice', true);
settings.set('autoUpdateNotice', true);
});
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ipcMain.handle('file-path', async (_, argument) => {
const filePath = dialog.showOpenDialogSync({
properties: ['openFile', 'openDirectory'],
});
return filePath;
});
ipcMain.on('minimize', () => {
mainWindow.minimize();
});
ipcMain.on('maximize', () => {
mainWindow.maximize();
});
ipcMain.on('unmaximize', () => {
mainWindow.unmaximize();
});
ipcMain.on('close', () => {
mainWindow.close();
});
mainWindow.on('maximize', () => {
mainWindow.webContents.send('maximize');
});
mainWindow.on('unmaximize', () => {
mainWindow.webContents.send('unmaximize');
});
};
const createTray = () => {

14
src/main.prod.js.LICENSE.txt

@ -15,18 +15,6 @@ object-assign
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/**
* @preserve
* JS Implementation of incremental MurmurHash3 (r150) (as of May 10, 2013)
*
* @author <a href="mailto:jensyt@gmail.com">Jens Taylor</a>
* @see http://github.com/homebrewing/brauhaus-diff
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
* @see http://github.com/garycourt/murmurhash-js
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
* @see http://sites.google.com/site/murmurhash/
*/
/** @license React v17.0.2
* react.production.min.js
*
@ -36,6 +24,8 @@ object-assign
* LICENSE file in the root directory of this source tree.
*/
/** @license URI.js v4.4.1 (c) 2011 Gary Court. License: http://github.com/garycourt/uri-js */
//! moment.js
//! moment.js locale configuration

50
src/preload.ts

@ -1,50 +0,0 @@
const { remote } = require('electron');
const win = remote.getCurrentWindow(); /* Note this is different to the
html global `window` variable */
window.onbeforeunload = () => {
/* If window is reloaded, remove win event listeners
(DOM element listeners get auto garbage collected but not
Electron win listeners as the win is not dereferenced unless closed) */
win.removeAllListeners();
};
function handleWindowControls() {
// Make minimise/maximise/restore/close buttons work when they are clicked
document.getElementById('min-button')?.addEventListener('click', () => {
win.minimize();
});
document.getElementById('max-button')?.addEventListener('click', () => {
win.maximize();
});
document.getElementById('restore-button')?.addEventListener('click', () => {
win.unmaximize();
});
document.getElementById('close-button')?.addEventListener('click', () => {
win.close();
});
function toggleMaxRestoreButtons() {
if (win.isMaximized()) {
document.body.classList.add('maximized');
} else {
document.body.classList.remove('maximized');
}
}
// Toggle maximise/restore buttons when maximisation/unmaximisation occurs
toggleMaxRestoreButtons();
win.on('maximize', toggleMaxRestoreButtons);
win.on('unmaximize', toggleMaxRestoreButtons);
}
// When document has loaded, initialise
document.onreadystatechange = () => {
if (document.readyState === 'complete') {
handleWindowControls();
}
};

4
src/redux/configSlice.ts

@ -1,11 +1,11 @@
import { nanoid } from 'nanoid/non-secure';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import settings from 'electron-settings';
import { mockSettings } from '../shared/mockSettings';
import { moveSelectedToIndex } from '../shared/utils';
import { Server } from '../types';
import { settings } from '../components/shared/setDefaultSettings';
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.getSync();
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.store;
export interface ConfigPage {
active: {

4
src/redux/folderSlice.ts

@ -1,8 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import settings from 'electron-settings';
import { mockSettings } from '../shared/mockSettings';
import { settings } from '../components/shared/setDefaultSettings';
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.getSync();
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.store;
export interface FolderSelection {
musicFolder?: string;

4
src/redux/miscSlice.ts

@ -1,9 +1,9 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import settings from 'electron-settings';
import { mockSettings } from '../shared/mockSettings';
import { getImageCachePath, getSongCachePath } from '../shared/utils';
import { settings } from '../components/shared/setDefaultSettings';
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.getSync();
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.store;
export interface ModalPage {
pageType: string;

4
src/redux/playQueueSlice.ts

@ -1,6 +1,5 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import _ from 'lodash';
import settings from 'electron-settings';
import { nanoid } from 'nanoid/non-secure';
import {
filterPlayQueue,
@ -11,8 +10,9 @@ import {
} from '../shared/utils';
import { mockSettings } from '../shared/mockSettings';
import { Song } from '../types';
import { settings } from '../components/shared/setDefaultSettings';
const parsedSettings = process.env.NODE_ENV === 'test' ? mockSettings : settings.getSync();
const parsedSettings = process.env.NODE_ENV === 'test' ? mockSettings : settings.store;
export interface PlayQueue {
player1: {

4
src/redux/viewSlice.ts

@ -1,9 +1,9 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import settings from 'electron-settings';
import { mockSettings } from '../shared/mockSettings';
import { Item, Sort, Pagination } from '../types';
import { settings } from '../components/shared/setDefaultSettings';
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.getSync();
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.store;
export interface AdvancedFilters {
enabled: boolean;

10
src/shared/utils.ts

@ -4,9 +4,11 @@ import os from 'os';
import path from 'path';
import moment from 'moment';
import arrayMove from 'array-move';
import settings from 'electron-settings';
// eslint-disable-next-line import/no-cycle
import i18n from '../i18n/i18n';
import { mockSettings } from './mockSettings';
// eslint-disable-next-line import/no-cycle
import { settings } from '../components/shared/setDefaultSettings';
export const isCached = (filePath: string) => {
return fs.existsSync(filePath);
@ -14,14 +16,12 @@ export const isCached = (filePath: string) => {
export const getRootCachePath = () => {
const baseCachePath =
process.env.NODE_ENV === 'test'
? mockSettings.cachePath
: String(settings.getSync('cachePath'));
process.env.NODE_ENV === 'test' ? mockSettings.cachePath : String(settings.get('cachePath'));
const serverBase64 =
process.env.NODE_ENV === 'test'
? mockSettings.serverBase64
: String(settings.getSync('serverBase64'));
: String(settings.get('serverBase64'));
return path.join(baseCachePath, 'sonixdCache', serverBase64);
};

7
src/yarn.lock

@ -210,11 +210,16 @@ mpris-service@^2.1.2:
deep-equal "^1.0.1"
source-map-support "^0.5.11"
nan@^2.12.1, nan@latest:
nan@^2.12.1:
version "2.15.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
nan@latest:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
object-is@^1.0.1:
version "1.1.5"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"

6924
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save