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'); const { build } = require('../../package.json');
exports.default = async function notarizeMacos(context) { exports.default = async function notarizeMacos(context) {

50
package.json

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

4
src/App.tsx

@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import { webFrame } from 'electron'; import { webFrame } from 'electron';
import _ from 'lodash'; import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import settings from 'electron-settings';
import { ThemeProvider } from 'styled-components'; import { ThemeProvider } from 'styled-components';
import { HashRouter as Router, Switch, Route } from 'react-router-dom'; import { HashRouter as Router, Switch, Route } from 'react-router-dom';
import './styles/App.global.css'; import './styles/App.global.css';
@ -33,6 +32,7 @@ import { mockSettings } from './shared/mockSettings';
import MusicList from './components/library/MusicList'; import MusicList from './components/library/MusicList';
import ReleaseNotes from './components/modal/ReleaseNotes'; import ReleaseNotes from './components/modal/ReleaseNotes';
import { notifyToast } from './components/shared/toast'; import { notifyToast } from './components/shared/toast';
import { settings } from './components/shared/setDefaultSettings';
const App = () => { const App = () => {
const [zoomFactor, setZoomFactor] = useState(Number(localStorage.getItem('zoomFactor')) || 1.0); const [zoomFactor, setZoomFactor] = useState(Number(localStorage.getItem('zoomFactor')) || 1.0);
@ -77,7 +77,7 @@ const App = () => {
const themes: any = const themes: any =
process.env.NODE_ENV === 'test' process.env.NODE_ENV === 'test'
? mockSettings.themesDefault ? mockSettings.themesDefault
: _.concat(settings.getSync('themes'), settings.getSync('themesDefault')); : _.concat(settings.get('themes'), settings.get('themesDefault'));
setTheme(getTheme(themes, misc.theme) || defaultDark); setTheme(getTheme(themes, misc.theme) || defaultDark);
}, [misc.theme]); }, [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 React from 'react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
@ -16,6 +31,7 @@ import App from '../App';
import { Server } from '../types'; import { Server } from '../types';
import { ArtistPage } from '../redux/artistSlice'; import { ArtistPage } from '../redux/artistSlice';
import { View } from '../redux/viewSlice'; import { View } from '../redux/viewSlice';
/* eslint-enable import/first */
const middlewares: Middleware<Record<string, unknown>, any, Dispatch<AnyAction>>[] | undefined = []; const middlewares: Middleware<Record<string, unknown>, any, Dispatch<AnyAction>>[] | undefined = [];
const mockStore = configureMockStore(middlewares); const mockStore = configureMockStore(middlewares);

6
src/api/api.ts

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

4
src/api/jellyfinApi.ts

@ -1,7 +1,6 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import axios from 'axios'; import axios from 'axios';
import axiosRetry from 'axios-retry'; import axiosRetry from 'axios-retry';
import settings from 'electron-settings';
import _ from 'lodash'; import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { nanoid } from 'nanoid/non-secure'; import { nanoid } from 'nanoid/non-secure';
@ -10,9 +9,10 @@ import { handleDisconnect } from '../components/settings/DisconnectButton';
import { notifyToast } from '../components/shared/toast'; import { notifyToast } from '../components/shared/toast';
import { GenericItem, Item, Song } from '../types'; import { GenericItem, Item, Song } from '../types';
import { mockSettings } from '../shared/mockSettings'; import { mockSettings } from '../shared/mockSettings';
import { settings } from '../components/shared/setDefaultSettings';
const transcode = 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 = () => { const getAuth = () => {
return { return {

11
src/components/debug/DebugWindow.tsx

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

4
src/components/layout/Layout.tsx

@ -1,5 +1,4 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import settings from 'electron-settings';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { ButtonToolbar, Content, FlexboxGrid, Icon, Nav, Whisper } from 'rsuite'; 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 { setSidebar } from '../../redux/configSlice';
import SearchBar from '../search/SearchBar'; import SearchBar from '../search/SearchBar';
import Popup from '../shared/Popup'; import Popup from '../shared/Popup';
import { settings } from '../shared/setDefaultSettings';
const Layout = ({ footer, children, disableSidebar, font }: any) => { const Layout = ({ footer, children, disableSidebar, font }: any) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -47,7 +47,7 @@ const Layout = ({ footer, children, disableSidebar, font }: any) => {
); );
const handleToggle = () => { 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 })); 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 React, { useCallback, useEffect, useMemo, useState } from 'react';
import settings from 'electron-settings';
import useMeasure from 'react-use/lib/useMeasure'; import useMeasure from 'react-use/lib/useMeasure';
import { LazyLoadImage } from 'react-lazy-load-image-component'; import { LazyLoadImage } from 'react-lazy-load-image-component';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -20,6 +19,7 @@ import { InfoModal } from '../modal/Modal';
import placeholderImg from '../../img/placeholder.png'; import placeholderImg from '../../img/placeholder.png';
import SidebarPlaylists from './SidebarPlaylists'; import SidebarPlaylists from './SidebarPlaylists';
import { setSidebar } from '../../redux/configSlice'; import { setSidebar } from '../../redux/configSlice';
import { settings } from '../shared/setDefaultSettings';
const Sidebar = ({ const Sidebar = ({
expand, expand,
@ -66,7 +66,7 @@ const Sidebar = ({
if (isResizing) { if (isResizing) {
const finalWidth = `${getSidebarWidth(e?.clientX)}px`; const finalWidth = `${getSidebarWidth(e?.clientX)}px`;
dispatch(setSidebar({ width: finalWidth })); dispatch(setSidebar({ width: finalWidth }));
settings.setSync('sidebar.width', finalWidth); settings.set('sidebar.width', finalWidth);
setIsResizing(false); setIsResizing(false);
document.body.style.cursor = 'default'; document.body.style.cursor = 'default';
} }
@ -115,7 +115,7 @@ const Sidebar = ({
size="xs" size="xs"
onClick={() => { onClick={() => {
dispatch(setSidebar({ coverArt: false })); dispatch(setSidebar({ coverArt: false }));
settings.setSync('sidebar.coverArt', false); settings.set('sidebar.coverArt', false);
}} }}
> >
<Icon icon="down" /> <Icon icon="down" />

47
src/components/layout/Titlebar.tsx

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ipcRenderer } from 'electron';
import { import {
TitleHeader, TitleHeader,
DragRegion, DragRegion,
@ -38,6 +39,21 @@ const Titlebar = ({ font }: any) => {
document.title = `${playStatus} ${songTitle}`.trim(); document.title = `${playStatus} ${songTitle}`.trim();
}, [playQueue, player.status, t]); }, [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 the titlebar is native return no custom titlebar
if (misc.titleBar === 'native') { if (misc.titleBar === 'native') {
return null; return null;
@ -57,6 +73,7 @@ const Titlebar = ({ font }: any) => {
minButton minButton
className="button" className="button"
id="min-button" id="min-button"
onClick={() => ipcRenderer.send('minimize')}
onMouseOver={() => setHoverMin(true)} onMouseOver={() => setHoverMin(true)}
onMouseLeave={() => setHoverMin(false)} onMouseLeave={() => setHoverMin(false)}
> >
@ -71,6 +88,7 @@ const Titlebar = ({ font }: any) => {
maxButton maxButton
className="button" className="button"
id="max-button" id="max-button"
onClick={() => ipcRenderer.send('maximize')}
onMouseOver={() => setHoverMax(true)} onMouseOver={() => setHoverMax(true)}
onMouseLeave={() => setHoverMax(false)} onMouseLeave={() => setHoverMax(false)}
> >
@ -85,6 +103,7 @@ const Titlebar = ({ font }: any) => {
restoreButton restoreButton
className="button" className="button"
id="restore-button" id="restore-button"
onClick={() => ipcRenderer.send('unmaximize')}
onMouseOver={() => setHoverMax(true)} onMouseOver={() => setHoverMax(true)}
onMouseLeave={() => setHoverMax(false)} onMouseLeave={() => setHoverMax(false)}
> >
@ -98,6 +117,7 @@ const Titlebar = ({ font }: any) => {
<MacControlButton <MacControlButton
className="button" className="button"
id="close-button" id="close-button"
onClick={() => ipcRenderer.send('close')}
onMouseOver={() => setHoverClose(true)} onMouseOver={() => setHoverClose(true)}
onMouseLeave={() => setHoverClose(false)} onMouseLeave={() => setHoverClose(false)}
> >
@ -121,7 +141,12 @@ const Titlebar = ({ font }: any) => {
</span> </span>
</div> </div>
<WindowControl id="window-controls"> <WindowControl id="window-controls">
<WindowControlButton minButton className="button" id="min-button"> <WindowControlButton
minButton
className="button"
id="min-button"
onClick={() => ipcRenderer.send('minimize')}
>
<img <img
className="icon" 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" 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="" alt=""
/> />
</WindowControlButton> </WindowControlButton>
<WindowControlButton maxButton className="button" id="max-button"> <WindowControlButton
maxButton
className="button"
id="max-button"
onClick={() => ipcRenderer.send('maximize')}
>
<img <img
className="icon" 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" 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="" alt=""
/> />
</WindowControlButton> </WindowControlButton>
<WindowControlButton restoreButton className="button" id="restore-button"> <WindowControlButton
restoreButton
className="button"
id="restore-button"
onClick={() => ipcRenderer.send('unmaximize')}
>
<img <img
className="icon" 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" 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="" alt=""
/> />
</WindowControlButton> </WindowControlButton>
<WindowControlButton className="button" id="close-button"> <WindowControlButton
className="button"
id="close-button"
onClick={() => ipcRenderer.send('close')}
>
<img <img
className="icon" 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" 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 React, { useEffect, useRef, useState } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import settings from 'electron-settings';
import { ButtonToolbar, Nav, Whisper } from 'rsuite'; import { ButtonToolbar, Nav, Whisper } from 'rsuite';
import { useQuery, useQueryClient } from 'react-query'; import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@ -33,6 +32,7 @@ import useListClickHandler from '../../hooks/useListClickHandler';
import Popup from '../shared/Popup'; import Popup from '../shared/Popup';
import useFavorite from '../../hooks/useFavorite'; import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating'; import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
export const ALBUM_SORT_TYPES = [ export const ALBUM_SORT_TYPES = [
{ label: i18n.t('A-Z (Name)'), value: 'alphabeticalByName', role: i18n.t('Default') }, { 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 view = useAppSelector((state) => state.view);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
const [sortTypes, setSortTypes] = useState<any[]>([]); 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 [musicFolder, setMusicFolder] = useState({ loaded: false, id: undefined });
const albumFilterPickerContainerRef = useRef(null); const albumFilterPickerContainerRef = useRef(null);
const [currentQueryKey, setCurrentQueryKey] = useState<any>(['albumList']); const [currentQueryKey, setCurrentQueryKey] = useState<any>(['albumList']);
@ -386,7 +386,7 @@ const AlbumList = () => {
}) })
} }
cacheImages={{ cacheImages={{
enabled: settings.getSync('cacheImages'), enabled: settings.get('cacheImages'),
cacheType: 'album', cacheType: 'album',
cacheIdProperty: 'albumId', cacheIdProperty: 'albumId',
}} }}

10
src/components/library/AlbumView.tsx

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

6
src/components/library/ArtistList.tsx

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

14
src/components/library/ArtistView.tsx

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

10
src/components/library/FolderList.tsx

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

4
src/components/library/MusicList.tsx

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

6
src/components/modal/ReleaseNotes.tsx

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

12
src/components/player/NowPlayingMiniView.tsx

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

16
src/components/player/NowPlayingView.tsx

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

25
src/components/player/Player.tsx

@ -7,7 +7,6 @@ import React, {
useCallback, useCallback,
} from 'react'; } from 'react';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import settings from 'electron-settings';
import ReactAudioPlayer from 'react-audio-player'; import ReactAudioPlayer from 'react-audio-player';
import { Helmet } from 'react-helmet-async'; import { Helmet } from 'react-helmet-async';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
@ -28,6 +27,7 @@ import { isCached, isLinux } from '../../shared/utils';
import { apiController } from '../../api/controller'; import { apiController } from '../../api/controller';
import { Artist, Server } from '../../types'; import { Artist, Server } from '../../types';
import { setStatus } from '../../redux/playerSlice'; import { setStatus } from '../../redux/playerSlice';
import { settings } from '../shared/setDefaultSettings';
const gaplessListenHandler = ( const gaplessListenHandler = (
currentPlayerRef: any, currentPlayerRef: any,
@ -252,7 +252,7 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
const player = useAppSelector((state) => state.player); const player = useAppSelector((state) => state.player);
const misc = useAppSelector((state) => state.misc); const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config); const config = useAppSelector((state) => state.config);
const cacheSongs = settings.getSync('cacheSongs'); const cacheSongs = settings.get('cacheSongs');
const [title] = useState(''); const [title] = useState('');
const [scrobbled, setScrobbled] = useState(false); const [scrobbled, setScrobbled] = useState(false);
@ -472,6 +472,24 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
); );
}, [config.serverType, currentEntryList, dispatch, playQueue, scrobbled]); }, [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(() => { const handleOnEndedPlayer1 = useCallback(() => {
player1Ref.current.audioEl.current.currentTime = 0; player1Ref.current.audioEl.current.currentTime = 0;
if (cacheSongs) { if (cacheSongs) {
@ -521,6 +539,7 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
) )
]; ];
ipcRenderer.send('current-song', nextSong); ipcRenderer.send('current-song', nextSong);
setMetadata(nextSong);
dispatch(setAutoIncremented(false)); dispatch(setAutoIncremented(false));
} }
@ -575,6 +594,7 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
) )
]; ];
ipcRenderer.send('current-song', nextSong); ipcRenderer.send('current-song', nextSong);
setMetadata(nextSong);
dispatch(setAutoIncremented(false)); dispatch(setAutoIncremented(false));
} }
@ -621,6 +641,7 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => {
: playQueue[currentEntryList][playQueue.player2.index]; : playQueue[currentEntryList][playQueue.player2.index];
ipcRenderer.send('current-song', playQueue.current); ipcRenderer.send('current-song', playQueue.current);
setMetadata(playQueue.current);
if (config.player.systemNotifications && currentSong) { if (config.player.systemNotifications && currentSong) {
// eslint-disable-next-line no-new // 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 React, { useEffect, useState, useRef, useMemo } from 'react';
import axios from 'axios'; import axios from 'axios';
import { useQueryClient } from 'react-query'; import { useQueryClient } from 'react-query';
import settings from 'electron-settings';
import { FlexboxGrid, Grid, Row, Col, Whisper, Icon } from 'rsuite'; import { FlexboxGrid, Grid, Row, Col, Whisper, Icon } from 'rsuite';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { LazyLoadImage } from 'react-lazy-load-image-component'; import { LazyLoadImage } from 'react-lazy-load-image-component';
@ -37,6 +36,7 @@ import usePlayQueueHandler from '../../hooks/usePlayQueueHandler';
import { apiController } from '../../api/controller'; import { apiController } from '../../api/controller';
import Slider from '../slider/Slider'; import Slider from '../slider/Slider';
import useDiscordRpc from '../../hooks/useDiscordRpc'; import useDiscordRpc from '../../hooks/useDiscordRpc';
import { settings } from '../shared/setDefaultSettings';
const PlayerBar = () => { const PlayerBar = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -49,7 +49,7 @@ const PlayerBar = () => {
const [currentTime, setCurrentTime] = useState(0); const [currentTime, setCurrentTime] = useState(0);
const [isDraggingVolume, setIsDraggingVolume] = useState(false); const [isDraggingVolume, setIsDraggingVolume] = useState(false);
const [currentEntryList, setCurrentEntryList] = useState('entry'); 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 [muted, setMuted] = useState(false);
const [showCoverArtModal, setShowCoverArtModal] = useState(false); const [showCoverArtModal, setShowCoverArtModal] = useState(false);
const [showLyricsModal, setShowLyricsModal] = useState(false); const [showLyricsModal, setShowLyricsModal] = useState(false);
@ -215,7 +215,7 @@ const PlayerBar = () => {
playersRef.current.player2.audioEl.current.volume = localVolume ** 2; playersRef.current.player2.audioEl.current.volume = localVolume ** 2;
} }
settings.setSync('volume', localVolume); settings.set('volume', localVolume);
} }
setIsDraggingVolume(false); setIsDraggingVolume(false);
}, 100); }, 100);
@ -278,7 +278,7 @@ const PlayerBar = () => {
size="xs" size="xs"
onClick={() => { onClick={() => {
dispatch(setSidebar({ coverArt: true })); dispatch(setSidebar({ coverArt: true }));
settings.setSync('sidebar.coverArt', true); settings.set('sidebar.coverArt', true);
}} }}
> >
<Icon icon="up" /> <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 { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { Form, Whisper } from 'rsuite'; import { Form, Whisper } from 'rsuite';
import settings from 'electron-settings';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useSearchQuery from '../../hooks/useSearchQuery'; import useSearchQuery from '../../hooks/useSearchQuery';
import ListViewType from '../viewtypes/ListViewType'; import ListViewType from '../viewtypes/ListViewType';
@ -21,6 +20,7 @@ import { setSort } from '../../redux/playlistSlice';
import ColumnSortPopover from '../shared/ColumnSortPopover'; import ColumnSortPopover from '../shared/ColumnSortPopover';
import useListClickHandler from '../../hooks/useListClickHandler'; import useListClickHandler from '../../hooks/useListClickHandler';
import Popup from '../shared/Popup'; import Popup from '../shared/Popup';
import { settings } from '../shared/setDefaultSettings';
const PlaylistList = () => { const PlaylistList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -32,7 +32,7 @@ const PlaylistList = () => {
const playlist = useAppSelector((state) => state.playlist); const playlist = useAppSelector((state) => state.playlist);
const playlistTriggerRef = useRef<any>(); const playlistTriggerRef = useRef<any>();
const [newPlaylistName, setNewPlaylistName] = useState(''); const [newPlaylistName, setNewPlaylistName] = useState('');
const [viewType, setViewType] = useState(settings.getSync('playlistViewType') || 'list'); const [viewType, setViewType] = useState(settings.get('playlistViewType') || 'list');
const { const {
isLoading, isLoading,
isError, isError,
@ -201,7 +201,7 @@ const PlaylistList = () => {
rowHeight={config.lookAndFeel.listView.playlist.rowHeight} rowHeight={config.lookAndFeel.listView.playlist.rowHeight}
fontSize={config.lookAndFeel.listView.playlist.fontSize} fontSize={config.lookAndFeel.listView.playlist.fontSize}
cacheImages={{ cacheImages={{
enabled: settings.getSync('cacheImages'), enabled: settings.get('cacheImages'),
cacheType: 'playlist', cacheType: 'playlist',
cacheIdProperty: 'id', cacheIdProperty: 'id',
}} }}

6
src/components/playlist/PlaylistView.tsx

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

4
src/components/scrollingmenu/ScrollingMenu.tsx

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

8
src/components/search/SearchView.tsx

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

2
src/components/settings/Config.tsx

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

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

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

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

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

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

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

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

@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { nanoid } from 'nanoid/non-secure'; import { nanoid } from 'nanoid/non-secure';
import settings from 'electron-settings';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import i18n from '../../../i18n/i18n'; import i18n from '../../../i18n/i18n';
import { import {
@ -21,6 +20,7 @@ import {
} from '../../../redux/configSlice'; } from '../../../redux/configSlice';
import ConfigOption from '../ConfigOption'; import ConfigOption from '../ConfigOption';
import useListClickHandler from '../../../hooks/useListClickHandler'; import useListClickHandler from '../../../hooks/useListClickHandler';
import { settings } from '../../shared/setDefaultSettings';
const columnSelectorColumns = [ const columnSelectorColumns = [
{ {
@ -73,10 +73,7 @@ const ListViewConfig = ({
setSelectedColumns(cols); setSelectedColumns(cols);
settings.setSync( settings.set(settingsConfig.columnList, config.lookAndFeel.listView[columnListType].columns);
settingsConfig.columnList,
config.lookAndFeel.listView[columnListType].columns
);
}, [columnListType, config.lookAndFeel.listView, settingsConfig.columnList]); }, [columnListType, config.lookAndFeel.listView, settingsConfig.columnList]);
const { handleRowClick } = useListClickHandler({}); const { handleRowClick } = useListClickHandler({});
@ -146,7 +143,7 @@ const ListViewConfig = ({
}); });
dispatch(setColumnList({ listType: columnListType, entries: columns })); dispatch(setColumnList({ listType: columnListType, entries: columns }));
settings.setSync(settingsConfig.columnList, cleanColumns); settings.set(settingsConfig.columnList, cleanColumns);
}} }}
labelKey="label" labelKey="label"
valueKey="label" valueKey="label"
@ -189,7 +186,7 @@ const ListViewConfig = ({
max={250} max={250}
width={125} width={125}
onChange={(e: number) => { onChange={(e: number) => {
settings.setSync(settingsConfig.rowHeight, Number(e)); settings.set(settingsConfig.rowHeight, Number(e));
dispatch(setRowHeight({ listType: columnListType, height: Number(e) })); dispatch(setRowHeight({ listType: columnListType, height: Number(e) }));
}} }}
/> />
@ -207,7 +204,7 @@ const ListViewConfig = ({
max={100} max={100}
width={125} width={125}
onChange={(e: number) => { onChange={(e: number) => {
settings.setSync(settingsConfig.fontSize, Number(e)); settings.set(settingsConfig.fontSize, Number(e));
dispatch(setFontSize({ listType: columnListType, size: 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 _ from 'lodash';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { ipcRenderer, shell } from 'electron'; import { ipcRenderer, shell } from 'electron';
import settings from 'electron-settings';
import { Nav, Icon, RadioGroup, Whisper, Divider } from 'rsuite'; import { Nav, Icon, RadioGroup, Whisper, Divider } from 'rsuite';
import { WhisperInstance } from 'rsuite/lib/Whisper'; import { WhisperInstance } from 'rsuite/lib/Whisper';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
@ -54,6 +53,7 @@ import { setPagination } from '../../../redux/viewSlice';
import { MUSIC_SORT_TYPES } from '../../library/MusicList'; import { MUSIC_SORT_TYPES } from '../../library/MusicList';
import Popup from '../../shared/Popup'; import Popup from '../../shared/Popup';
import { apiController } from '../../../api/controller'; import { apiController } from '../../../api/controller';
import { settings } from '../../shared/setDefaultSettings';
export const ListViewConfigPanel = ({ bordered }: any) => { export const ListViewConfigPanel = ({ bordered }: any) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -61,15 +61,15 @@ export const ListViewConfigPanel = ({ bordered }: any) => {
const config = useAppSelector((state) => state.config); const config = useAppSelector((state) => state.config);
const [highlightOnRowHoverChk, setHighlightOnRowHoverChk] = useState( const [highlightOnRowHoverChk, setHighlightOnRowHoverChk] = useState(
Boolean(settings.getSync('highlightOnRowHover')) Boolean(settings.get('highlightOnRowHover'))
); );
const songCols: any = settings.getSync('musicListColumns'); const songCols: any = settings.get('musicListColumns');
const albumCols: any = settings.getSync('albumListColumns'); const albumCols: any = settings.get('albumListColumns');
const playlistCols: any = settings.getSync('playlistListColumns'); const playlistCols: any = settings.get('playlistListColumns');
const artistCols: any = settings.getSync('artistListColumns'); const artistCols: any = settings.get('artistListColumns');
const miniCols: any = settings.getSync('miniListColumns'); const miniCols: any = settings.get('miniListColumns');
const genreCols: any = settings.getSync('genreListColumns'); const genreCols: any = settings.get('genreListColumns');
const currentSongColumns = songCols?.map((column: any) => column.label) || []; const currentSongColumns = songCols?.map((column: any) => column.label) || [];
const currentAlbumColumns = albumCols?.map((column: any) => column.label) || []; const currentAlbumColumns = albumCols?.map((column: any) => column.label) || [];
@ -201,7 +201,7 @@ export const ListViewConfigPanel = ({ bordered }: any) => {
defaultChecked={highlightOnRowHoverChk} defaultChecked={highlightOnRowHoverChk}
checked={highlightOnRowHoverChk} checked={highlightOnRowHoverChk}
onChange={(e: boolean) => { onChange={(e: boolean) => {
settings.setSync('highlightOnRowHover', e); settings.set('highlightOnRowHover', e);
dispatch( dispatch(
setMiscSetting({ setMiscSetting({
setting: 'highlightOnRowHover', setting: 'highlightOnRowHover',
@ -235,7 +235,7 @@ export const GridViewConfigPanel = ({ bordered }: any) => {
max={350} max={350}
width={125} width={125}
onChange={(e: any) => { onChange={(e: any) => {
settings.setSync('gridCardSize', Number(e)); settings.set('gridCardSize', Number(e));
dispatch(setGridCardSize({ size: Number(e) })); dispatch(setGridCardSize({ size: Number(e) }));
}} }}
/> />
@ -253,7 +253,7 @@ export const GridViewConfigPanel = ({ bordered }: any) => {
max={100} max={100}
width={125} width={125}
onChange={(e: any) => { onChange={(e: any) => {
settings.setSync('gridGapSize', Number(e)); settings.set('gridGapSize', Number(e));
dispatch(setGridGapSize({ size: Number(e) })); dispatch(setGridGapSize({ size: Number(e) }));
}} }}
/> />
@ -271,7 +271,7 @@ export const GridViewConfigPanel = ({ bordered }: any) => {
value={config.lookAndFeel.gridView.alignment} value={config.lookAndFeel.gridView.alignment}
onChange={(e: string) => { onChange={(e: string) => {
dispatch(setGridAlignment({ alignment: e })); dispatch(setGridAlignment({ alignment: e }));
settings.setSync('gridAlignment', e); settings.set('gridAlignment', e);
}} }}
> >
<StyledRadio value="flex-start">{t('Left')}</StyledRadio> <StyledRadio value="flex-start">{t('Left')}</StyledRadio>
@ -288,10 +288,10 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const config = useAppSelector((state) => state.config); const config = useAppSelector((state) => state.config);
const [dynamicBackgroundChk, setDynamicBackgroundChk] = useState( 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 languagePickerContainerRef = useRef(null);
const themePickerContainerRef = useRef(null); const themePickerContainerRef = useRef(null);
const fontPickerContainerRef = useRef(null); const fontPickerContainerRef = useRef(null);
@ -302,7 +302,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
const sidebarPickerContainerRef = useRef(null); const sidebarPickerContainerRef = useRef(null);
const titleBarRestartWhisper = React.createRef<WhisperInstance>(); const titleBarRestartWhisper = React.createRef<WhisperInstance>();
const [themeList, setThemeList] = useState( const [themeList, setThemeList] = useState(
_.concat(settings.getSync('themes'), settings.getSync('themesDefault')) _.concat(settings.get('themes'), settings.get('themesDefault'))
); );
const { data: playlists }: any = useQuery(['playlists'], () => const { data: playlists }: any = useQuery(['playlists'], () =>
@ -321,7 +321,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
data={Languages} data={Languages}
width={200} width={200}
cleanable={false} cleanable={false}
defaultValue={String(settings.getSync('language'))} defaultValue={String(settings.get('language'))}
placeholder={t('Select')} placeholder={t('Select')}
onChange={(e: string) => { onChange={(e: string) => {
i18n.changeLanguage(e, (err) => { i18n.changeLanguage(e, (err) => {
@ -329,7 +329,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
notifyToast('error', 'Error while changing the language'); notifyToast('error', 'Error while changing the language');
} }
}); });
settings.setSync('language', e); settings.set('language', e);
}} }}
/> />
</StyledInputPickerContainer> </StyledInputPickerContainer>
@ -346,9 +346,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
onClick={() => { onClick={() => {
dispatch(setTheme('defaultDark')); dispatch(setTheme('defaultDark'));
dispatch(setTheme(selectedTheme)); dispatch(setTheme(selectedTheme));
setThemeList( setThemeList(_.concat(settings.get('themes'), settings.get('themesDefault')));
_.concat(settings.getSync('themes'), settings.getSync('themesDefault'))
);
}} }}
/> />
</> </>
@ -377,7 +375,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
defaultValue={selectedTheme} defaultValue={selectedTheme}
placeholder={t('Select')} placeholder={t('Select')}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('theme', e); settings.set('theme', e);
setSelectedTheme(e); setSelectedTheme(e);
dispatch(setTheme(e)); dispatch(setTheme(e));
}} }}
@ -398,10 +396,10 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
groupBy="role" groupBy="role"
width={200} width={200}
cleanable={false} cleanable={false}
defaultValue={String(settings.getSync('font'))} defaultValue={String(settings.get('font'))}
placeholder={t('Select')} placeholder={t('Select')}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('font', e); settings.set('font', e);
dispatch(setFont(e)); dispatch(setFont(e));
}} }}
/> />
@ -454,11 +452,11 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
}, },
]} ]}
cleanable={false} cleanable={false}
defaultValue={String(settings.getSync('titleBarStyle'))} defaultValue={String(settings.get('titleBarStyle'))}
width={200} width={200}
placeholder={t('Select')} placeholder={t('Select')}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('titleBarStyle', e); settings.set('titleBarStyle', e);
dispatch(setMiscSetting({ setting: 'titleBar', value: e })); dispatch(setMiscSetting({ setting: 'titleBar', value: e }));
titleBarRestartWhisper.current?.open(); titleBarRestartWhisper.current?.open();
}} }}
@ -476,7 +474,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
defaultChecked={dynamicBackgroundChk} defaultChecked={dynamicBackgroundChk}
checked={dynamicBackgroundChk} checked={dynamicBackgroundChk}
onChange={(e: boolean) => { onChange={(e: boolean) => {
settings.setSync('dynamicBackground', e); settings.set('dynamicBackground', e);
dispatch(setDynamicBackground(e)); dispatch(setDynamicBackground(e));
setDynamicBackgroundChk(e); setDynamicBackgroundChk(e);
}} }}
@ -542,11 +540,11 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
)} )}
cleanable={false} cleanable={false}
groupBy="role" groupBy="role"
defaultValue={String(settings.getSync('startPage'))} defaultValue={String(settings.get('startPage'))}
width={200} width={200}
placeholder={t('Select')} placeholder={t('Select')}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('startPage', e); settings.set('startPage', e);
}} }}
/> />
</StyledInputPickerContainer> </StyledInputPickerContainer>
@ -565,10 +563,10 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
config.serverType === Server.Jellyfin ? ['frequent', 'recent'] : [] config.serverType === Server.Jellyfin ? ['frequent', 'recent'] : []
} }
cleanable={false} cleanable={false}
defaultValue={String(settings.getSync('albumSortDefault'))} defaultValue={String(settings.get('albumSortDefault'))}
width={200} width={200}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('albumSortDefault', e); settings.set('albumSortDefault', e);
}} }}
/> />
</StyledInputPickerContainer> </StyledInputPickerContainer>
@ -584,10 +582,10 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
container={() => musicSortDefaultPickerContainerRef.current} container={() => musicSortDefaultPickerContainerRef.current}
data={MUSIC_SORT_TYPES} data={MUSIC_SORT_TYPES}
cleanable={false} cleanable={false}
defaultValue={String(settings.getSync('musicSortDefault'))} defaultValue={String(settings.get('musicSortDefault'))}
width={200} width={200}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('musicSortDefault', e); settings.set('musicSortDefault', e);
}} }}
/> />
</StyledInputPickerContainer> </StyledInputPickerContainer>
@ -601,9 +599,9 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
)} )}
option={ option={
<StyledToggle <StyledToggle
defaultChecked={Boolean(settings.getSync('artistPageLegacy'))} defaultChecked={Boolean(settings.get('artistPageLegacy'))}
onChange={(e: boolean) => { onChange={(e: boolean) => {
settings.setSync('artistPageLegacy', e); settings.set('artistPageLegacy', e);
}} }}
/> />
} }
@ -670,7 +668,7 @@ export const ThemeConfigPanel = ({ bordered }: any) => {
width={250} width={250}
disabledItemValues={config.serverType === Server.Subsonic ? ['songs'] : []} disabledItemValues={config.serverType === Server.Subsonic ? ['songs'] : []}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('sidebar.selected', e); settings.set('sidebar.selected', e);
dispatch(setSidebar({ selected: e })); dispatch(setSidebar({ selected: e }));
}} }}
/> />
@ -711,15 +709,15 @@ export const PaginationConfigPanel = ({ bordered }: any) => {
data: { activePage: 1, recordsPerPage: Number(e) }, data: { activePage: 1, recordsPerPage: Number(e) },
}) })
); );
settings.setSync('pagination.music.recordsPerPage', Number(e)); settings.set('pagination.music.recordsPerPage', Number(e));
}} }}
/> />
{config.serverType === Server.Jellyfin && ( {config.serverType === Server.Jellyfin && (
<StyledCheckbox <StyledCheckbox
defaultChecked={settings.getSync('pagination.music.serverSide')} defaultChecked={settings.get('pagination.music.serverSide')}
checked={view.music.pagination.serverSide} checked={view.music.pagination.serverSide}
onChange={(_v: any, e: boolean) => { 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 } })); dispatch(setPagination({ listType: Item.Music, data: { serverSide: e } }));
}} }}
> >
@ -747,15 +745,15 @@ export const PaginationConfigPanel = ({ bordered }: any) => {
data: { activePage: 1, recordsPerPage: Number(e) }, data: { activePage: 1, recordsPerPage: Number(e) },
}) })
); );
settings.setSync('pagination.album.recordsPerPage', Number(e)); settings.set('pagination.album.recordsPerPage', Number(e));
}} }}
/> />
{config.serverType === Server.Jellyfin && ( {config.serverType === Server.Jellyfin && (
<StyledCheckbox <StyledCheckbox
defaultChecked={settings.getSync('pagination.album.serverSide')} defaultChecked={settings.get('pagination.album.serverSide')}
checked={view.album.pagination.serverSide} checked={view.album.pagination.serverSide}
onChange={(_v: any, e: boolean) => { 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 } })); 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 React, { useRef, useState } from 'react';
import settings from 'electron-settings';
import { ButtonToolbar } from 'rsuite'; import { ButtonToolbar } from 'rsuite';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ConfigPanel } from '../styled'; import { ConfigPanel } from '../styled';
@ -13,22 +12,19 @@ import {
import { useAppDispatch } from '../../../redux/hooks'; import { useAppDispatch } from '../../../redux/hooks';
import { setPlaybackSetting } from '../../../redux/playQueueSlice'; import { setPlaybackSetting } from '../../../redux/playQueueSlice';
import ConfigOption from '../ConfigOption'; import ConfigOption from '../ConfigOption';
import { settings } from '../../shared/setDefaultSettings';
const PlaybackConfig = ({ bordered }: any) => { const PlaybackConfig = ({ bordered }: any) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [crossfadeDuration, setCrossfadeDuration] = useState( const [crossfadeDuration, setCrossfadeDuration] = useState(Number(settings.get('fadeDuration')));
Number(settings.getSync('fadeDuration')) const [pollingInterval, setPollingInterval] = useState(Number(settings.get('pollingInterval')));
); const [volumeFade, setVolumeFade] = useState(Boolean(settings.get('volumeFade')));
const [pollingInterval, setPollingInterval] = useState(
Number(settings.getSync('pollingInterval'))
);
const [volumeFade, setVolumeFade] = useState(Boolean(settings.getSync('volumeFade')));
const crossfadePickerContainerRef = useRef(null); const crossfadePickerContainerRef = useRef(null);
const handleSetCrossfadeDuration = (e: number) => { const handleSetCrossfadeDuration = (e: number) => {
setCrossfadeDuration(e); setCrossfadeDuration(e);
settings.setSync('fadeDuration', Number(e)); settings.set('fadeDuration', Number(e));
dispatch( dispatch(
setPlaybackSetting({ setPlaybackSetting({
setting: 'fadeDuration', setting: 'fadeDuration',
@ -39,7 +35,7 @@ const PlaybackConfig = ({ bordered }: any) => {
const handleSetPollingInterval = (e: number) => { const handleSetPollingInterval = (e: number) => {
setPollingInterval(e); setPollingInterval(e);
settings.setSync('pollingInterval', Number(e)); settings.set('pollingInterval', Number(e));
dispatch( dispatch(
setPlaybackSetting({ setPlaybackSetting({
setting: 'pollingInterval', setting: 'pollingInterval',
@ -50,7 +46,7 @@ const PlaybackConfig = ({ bordered }: any) => {
const handleSetVolumeFade = (e: boolean) => { const handleSetVolumeFade = (e: boolean) => {
setVolumeFade(e); setVolumeFade(e);
settings.setSync('volumeFade', e); settings.set('volumeFade', e);
dispatch(setPlaybackSetting({ setting: 'volumeFade', value: e })); dispatch(setPlaybackSetting({ setting: 'volumeFade', value: e }));
}; };
@ -133,10 +129,10 @@ const PlaybackConfig = ({ bordered }: any) => {
}, },
]} ]}
cleanable={false} cleanable={false}
defaultValue={String(settings.getSync('fadeType'))} defaultValue={String(settings.get('fadeType'))}
placeholder={t('Select')} placeholder={t('Select')}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('fadeType', e); settings.set('fadeType', e);
dispatch(setPlaybackSetting({ setting: 'fadeType', value: e })); dispatch(setPlaybackSetting({ setting: 'fadeType', value: e }));
}} }}
width={200} width={200}

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

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

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

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

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

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

22
src/components/settings/DisconnectButton.tsx

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

33
src/components/settings/Login.tsx

@ -1,11 +1,10 @@
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import md5 from 'md5'; import md5 from 'md5';
import randomstring from 'randomstring'; import randomstring from 'randomstring';
import settings from 'electron-settings';
import { Form, ControlLabel, Message, RadioGroup } from 'rsuite'; import { Form, ControlLabel, Message, RadioGroup } from 'rsuite';
import axios from 'axios'; import axios from 'axios';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import setDefaultSettings from '../shared/setDefaultSettings'; import { settings, setDefaultSettings } from '../shared/setDefaultSettings';
import { import {
StyledButton, StyledButton,
StyledCheckbox, StyledCheckbox,
@ -68,13 +67,13 @@ const Login = () => {
localStorage.setItem('salt', salt); localStorage.setItem('salt', salt);
localStorage.setItem('hash', hash); localStorage.setItem('hash', hash);
settings.setSync('server', cleanServerName); settings.set('server', cleanServerName);
settings.setSync('serverBase64', btoa(cleanServerName)); settings.set('serverBase64', btoa(cleanServerName));
settings.setSync('serverType', 'subsonic'); settings.set('serverType', 'subsonic');
settings.setSync('username', userName); settings.set('username', userName);
settings.setSync('password', password); settings.set('password', password);
settings.setSync('salt', salt); settings.set('salt', salt);
settings.setSync('hash', hash); settings.set('hash', hash);
// Set defaults on login // Set defaults on login
setDefaultSettings(false); setDefaultSettings(false);
@ -107,12 +106,12 @@ const Login = () => {
localStorage.setItem('token', data.AccessToken); localStorage.setItem('token', data.AccessToken);
localStorage.setItem('deviceId', deviceId); localStorage.setItem('deviceId', deviceId);
settings.setSync('server', cleanServerName); settings.set('server', cleanServerName);
settings.setSync('serverBase64', btoa(cleanServerName)); settings.set('serverBase64', btoa(cleanServerName));
settings.setSync('serverType', 'jellyfin'); settings.set('serverType', 'jellyfin');
settings.setSync('username', data.User.Id); settings.set('username', data.User.Id);
settings.setSync('token', data.AccessToken); settings.set('token', data.AccessToken);
settings.setSync('deviceId', deviceId); settings.set('deviceId', deviceId);
} catch (err) { } catch (err) {
if (err instanceof Error) { if (err instanceof Error) {
setMessage(`${err.message}`); setMessage(`${err.message}`);
@ -184,11 +183,11 @@ const Login = () => {
defaultChecked={ defaultChecked={
process.env.NODE_ENV === 'test' process.env.NODE_ENV === 'test'
? mockSettings.legacyAuth ? mockSettings.legacyAuth
: Boolean(settings.getSync('legacyAuth')) : Boolean(settings.get('legacyAuth'))
} }
checked={legacyAuth} checked={legacyAuth}
onChange={(_v: any, e: boolean) => { onChange={(_v: any, e: boolean) => {
settings.setSync('legacyAuth', e); settings.set('legacyAuth', e);
setLegacyAuth(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 { useHistory } from 'react-router';
import { useQuery, useQueryClient } from 'react-query'; import { useQuery, useQueryClient } from 'react-query';
import { Nav } from 'rsuite'; import { Nav } from 'rsuite';
import settings from 'electron-settings';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useSearchQuery from '../../hooks/useSearchQuery'; import useSearchQuery from '../../hooks/useSearchQuery';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
@ -23,6 +22,7 @@ import CenterLoader from '../loader/CenterLoader';
import useListClickHandler from '../../hooks/useListClickHandler'; import useListClickHandler from '../../hooks/useListClickHandler';
import useFavorite from '../../hooks/useFavorite'; import useFavorite from '../../hooks/useFavorite';
import { useRating } from '../../hooks/useRating'; import { useRating } from '../../hooks/useRating';
import { settings } from '../shared/setDefaultSettings';
const StarredView = () => { const StarredView = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -33,7 +33,7 @@ const StarredView = () => {
const favorite = useAppSelector((state) => state.favorite); const favorite = useAppSelector((state) => state.favorite);
const config = useAppSelector((state) => state.config); const config = useAppSelector((state) => state.config);
const misc = useAppSelector((state) => state.misc); 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); const [musicFolder, setMusicFolder] = useState(undefined);
useEffect(() => { useEffect(() => {
@ -283,7 +283,7 @@ const StarredView = () => {
rowHeight={config.lookAndFeel.listView.music.rowHeight} rowHeight={config.lookAndFeel.listView.music.rowHeight}
fontSize={config.lookAndFeel.listView.music.fontSize} fontSize={config.lookAndFeel.listView.music.fontSize}
cacheImages={{ cacheImages={{
enabled: settings.getSync('cacheImages'), enabled: settings.get('cacheImages'),
cacheType: 'album', cacheType: 'album',
cacheIdProperty: 'albumId', cacheIdProperty: 'albumId',
}} }}
@ -332,7 +332,7 @@ const StarredView = () => {
}) })
} }
cacheImages={{ cacheImages={{
enabled: settings.getSync('cacheImages'), enabled: settings.get('cacheImages'),
cacheType: 'album', cacheType: 'album',
cacheIdProperty: 'albumId', 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 // Referenced from: https://codesandbox.io/s/jjkz5y130w?file=/index.js:700-703
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import settings from 'electron-settings';
import { FixedSizeList as List } from 'react-window'; import { FixedSizeList as List } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import Card from '../card/Card'; import Card from '../card/Card';
@ -8,6 +7,7 @@ import 'react-virtualized/styles.css';
import { useAppSelector } from '../../redux/hooks'; import { useAppSelector } from '../../redux/hooks';
import Paginator from '../shared/Paginator'; import Paginator from '../shared/Paginator';
import CenterLoader from '../loader/CenterLoader'; import CenterLoader from '../loader/CenterLoader';
import { settings } from '../shared/setDefaultSettings';
const GridCard = ({ data, index, style }: any) => { const GridCard = ({ data, index, style }: any) => {
const { cardHeight, cardWidth, columnCount, gapSize, itemCount } = data; const { cardHeight, cardWidth, columnCount, gapSize, itemCount } = data;
@ -178,7 +178,7 @@ const GridViewType = ({
loading, loading,
gridRef, gridRef,
}: any) => { }: any) => {
const cacheImages = Boolean(settings.getSync('cacheImages')); const cacheImages = Boolean(settings.get('cacheImages'));
const misc = useAppSelector((state) => state.misc); const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config); const config = useAppSelector((state) => state.config);
const folder = useAppSelector((state) => state.folder); const folder = useAppSelector((state) => state.folder);

9
src/components/viewtypes/ListViewTable.tsx

@ -3,7 +3,6 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useState, useEffect, useMemo, useCallback } from 'react'; import React, { useState, useEffect, useMemo, useCallback } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import settings from 'electron-settings';
import styled from 'styled-components'; import styled from 'styled-components';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
@ -67,6 +66,7 @@ import CoverArtCell from './TableCells/CoverArtCell';
import TextCell from './TableCells/TextCell'; import TextCell from './TableCells/TextCell';
import LinkCell from './TableCells/LinkCell'; import LinkCell from './TableCells/LinkCell';
import CustomCell from './TableCells/CustomCell'; import CustomCell from './TableCells/CustomCell';
import { settings } from '../shared/setDefaultSettings';
const StyledTable = styled(Table)<{ rowHeight: number; $isDragging: boolean }>` const StyledTable = styled(Table)<{ rowHeight: number; $isDragging: boolean }>`
.rs-table-row.selected { .rs-table-row.selected {
@ -521,6 +521,9 @@ const ListViewTable = ({
}} }}
> >
{columns.map((column: any) => ( {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 <Table.Column
key={nanoid()} key={nanoid()}
align={column.alignment} align={column.alignment}
@ -536,9 +539,9 @@ const ListViewTable = ({
); );
if (!miniView) { if (!miniView) {
settings.setSync(`${listType}ListColumns[${resizedColumnIndex}].width`, newWidth); settings.set(`${listType}ListColumns[${resizedColumnIndex}].width`, newWidth);
} else { } else {
settings.setSync(`miniListColumns[${resizedColumnIndex}].width`, newWidth); settings.set(`miniListColumns[${resizedColumnIndex}].width`, newWidth);
} }
const newCols = configState.lookAndFeel.listView[ const newCols = configState.lookAndFeel.listView[

6
src/components/viewtypes/ViewTypeButtons.tsx

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

2
src/hooks/useBrowserDownload.ts

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

2
src/hooks/useFavorite.ts

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

46
src/hooks/usePlayerControls.ts

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

2
src/hooks/useRating.ts

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

5
src/i18n/i18n.js

@ -1,7 +1,8 @@
import settings from 'electron-settings';
import i18n from 'i18next'; import i18n from 'i18next';
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from 'react-i18next';
import { mockSettings } from '../shared/mockSettings'; import { mockSettings } from '../shared/mockSettings';
// eslint-disable-next-line import/no-cycle
import { settings } from '../components/shared/setDefaultSettings';
// the translations // the translations
// (tip move them in a JSON file and import them, // (tip move them in a JSON file and import them,
@ -28,7 +29,7 @@ i18n
.use(initReactI18next) // passes i18n down to react-i18next .use(initReactI18next) // passes i18n down to react-i18next
.init({ .init({
resources, 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 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 // 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 // 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 React from 'react';
import settings from 'electron-settings'; import { createRoot } from 'react-dom/client';
import { render } from 'react-dom';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from 'react-query';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { HelmetProvider } from 'react-helmet-async'; import { HelmetProvider } from 'react-helmet-async';
@ -16,18 +15,13 @@ const queryClient = new QueryClient({
}, },
}); });
settings.configure({ const root = createRoot(document.getElementById('root')!);
prettify: true, root.render(
numSpaces: 2,
});
render(
<Provider store={store}> <Provider store={store}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<HelmetProvider> <HelmetProvider>
<App /> <App />
</HelmetProvider> </HelmetProvider>
</QueryClientProvider> </QueryClientProvider>
</Provider>, </Provider>
document.getElementById('root')
); );

74
src/main.dev.js

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

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

@ -15,18 +15,6 @@ object-assign
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * 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 /** @license React v17.0.2
* react.production.min.js * react.production.min.js
* *
@ -36,6 +24,8 @@ object-assign
* LICENSE file in the root directory of this source tree. * 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
//! moment.js locale configuration //! 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 { nanoid } from 'nanoid/non-secure';
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import settings from 'electron-settings';
import { mockSettings } from '../shared/mockSettings'; import { mockSettings } from '../shared/mockSettings';
import { moveSelectedToIndex } from '../shared/utils'; import { moveSelectedToIndex } from '../shared/utils';
import { Server } from '../types'; 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 { export interface ConfigPage {
active: { active: {

4
src/redux/folderSlice.ts

@ -1,8 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import settings from 'electron-settings';
import { mockSettings } from '../shared/mockSettings'; 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 { export interface FolderSelection {
musicFolder?: string; musicFolder?: string;

4
src/redux/miscSlice.ts

@ -1,9 +1,9 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import settings from 'electron-settings';
import { mockSettings } from '../shared/mockSettings'; import { mockSettings } from '../shared/mockSettings';
import { getImageCachePath, getSongCachePath } from '../shared/utils'; 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 { export interface ModalPage {
pageType: string; pageType: string;

4
src/redux/playQueueSlice.ts

@ -1,6 +1,5 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import _ from 'lodash'; import _ from 'lodash';
import settings from 'electron-settings';
import { nanoid } from 'nanoid/non-secure'; import { nanoid } from 'nanoid/non-secure';
import { import {
filterPlayQueue, filterPlayQueue,
@ -11,8 +10,9 @@ import {
} from '../shared/utils'; } from '../shared/utils';
import { mockSettings } from '../shared/mockSettings'; import { mockSettings } from '../shared/mockSettings';
import { Song } from '../types'; 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 { export interface PlayQueue {
player1: { player1: {

4
src/redux/viewSlice.ts

@ -1,9 +1,9 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import settings from 'electron-settings';
import { mockSettings } from '../shared/mockSettings'; import { mockSettings } from '../shared/mockSettings';
import { Item, Sort, Pagination } from '../types'; 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 { export interface AdvancedFilters {
enabled: boolean; enabled: boolean;

10
src/shared/utils.ts

@ -4,9 +4,11 @@ import os from 'os';
import path from 'path'; import path from 'path';
import moment from 'moment'; import moment from 'moment';
import arrayMove from 'array-move'; import arrayMove from 'array-move';
import settings from 'electron-settings'; // eslint-disable-next-line import/no-cycle
import i18n from '../i18n/i18n'; import i18n from '../i18n/i18n';
import { mockSettings } from './mockSettings'; import { mockSettings } from './mockSettings';
// eslint-disable-next-line import/no-cycle
import { settings } from '../components/shared/setDefaultSettings';
export const isCached = (filePath: string) => { export const isCached = (filePath: string) => {
return fs.existsSync(filePath); return fs.existsSync(filePath);
@ -14,14 +16,12 @@ export const isCached = (filePath: string) => {
export const getRootCachePath = () => { export const getRootCachePath = () => {
const baseCachePath = const baseCachePath =
process.env.NODE_ENV === 'test' process.env.NODE_ENV === 'test' ? mockSettings.cachePath : String(settings.get('cachePath'));
? mockSettings.cachePath
: String(settings.getSync('cachePath'));
const serverBase64 = const serverBase64 =
process.env.NODE_ENV === 'test' process.env.NODE_ENV === 'test'
? mockSettings.serverBase64 ? mockSettings.serverBase64
: String(settings.getSync('serverBase64')); : String(settings.get('serverBase64'));
return path.join(baseCachePath, 'sonixdCache', 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" deep-equal "^1.0.1"
source-map-support "^0.5.11" source-map-support "^0.5.11"
nan@^2.12.1, nan@latest: nan@^2.12.1:
version "2.15.0" version "2.15.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== 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: object-is@^1.0.1:
version "1.1.5" version "1.1.5"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" 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