diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx index 7debc48..fc9a6c4 100644 --- a/src/__tests__/App.test.tsx +++ b/src/__tests__/App.test.tsx @@ -102,6 +102,11 @@ const miscState: General = { modalPages: [], isProcessingPlaylist: [], dynamicBackground: false, + retainWindowSize: false, + savedWindowSize: [1024, 728], + savedWindowPos: [50, 50], + defaultWindowWidth: 1024, + defaultWindowHeight: 728, highlightOnRowHover: true, imageCachePath: '', songCachePath: '', diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx index 96fc410..1d58070 100644 --- a/src/components/player/Player.tsx +++ b/src/components/player/Player.tsx @@ -643,6 +643,13 @@ const Player = ({ currentEntryList, muted, children }: any, ref: any) => { ipcRenderer.send('current-song', playQueue.current); setMetadata(playQueue.current); + // Save the queue 2.5 seconds after fade length + if (settings.get('resume')) { + setTimeout(() => { + ipcRenderer.send('quicksave'); + }, playQueue.fadeDuration * 1000 + 2500); + } + if (config.player.systemNotifications && currentSong) { // eslint-disable-next-line no-new new Notification(currentSong.title, { diff --git a/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx b/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx index 7e545e0..48a9db8 100644 --- a/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx +++ b/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx @@ -24,7 +24,14 @@ import ListViewConfig from './ListViewConfig'; import Fonts from '../Fonts'; import { ALBUM_SORT_TYPES } from '../../library/AlbumList'; import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; -import { setTheme, setDynamicBackground, setMiscSetting } from '../../../redux/miscSlice'; +import { + setTheme, + setDynamicBackground, + setMiscSetting, + setRetainWindowSize, + setDefaultWindowWidth, + setDefaultWindowHeight, +} from '../../../redux/miscSlice'; import { songColumnPicker, songColumnListAuto, @@ -290,6 +297,15 @@ export const ThemeConfigPanel = ({ bordered }: any) => { const [dynamicBackgroundChk, setDynamicBackgroundChk] = useState( Boolean(settings.get('dynamicBackground')) ); + const [retainWindowSizeChk, setRetainWindowSizeChk] = useState( + Boolean(settings.get('retainWindowSize')) + ); + const [defaultWindowHeight, setDefaultWindowHeightValue] = useState( + Number(settings.get('defaultWindowHeight')) + ); + const [defaultWindowWidth, setDefaultWindowWidthValue] = useState( + Number(settings.get('defaultWindowWidth')) + ); const [selectedTheme, setSelectedTheme] = useState(String(settings.get('theme'))); const languagePickerContainerRef = useRef(null); @@ -482,6 +498,68 @@ export const ThemeConfigPanel = ({ bordered }: any) => { } /> + { + settings.set('retainWindowSize', e); + dispatch(setRetainWindowSize(e)); + setRetainWindowSizeChk(e); + }} + /> + } + /> + + { + settings.set('defaultWindowWidth', Number(e)); + dispatch(setDefaultWindowWidth(Number(e))); + setDefaultWindowWidthValue(Number(e)); + }} + /> + } + /> + + { + settings.set('defaultWindowHeight', Number(e)); + dispatch(setDefaultWindowHeight(Number(e))); + setDefaultWindowHeightValue(Number(e)); + }} + /> + } + /> + { /> - Remember play queue on startup. The current Now Playing queue will be saved on exiting, - and will be restored when you reopen Sonixd. Be warned that you should manually close - Sonixd for the queue to be saved. An improper shutdown (such as the app closing during a - shutdown or force quitting) may result in history not being saved. - - } + description={t('Resumes the player queue on startup.')} option={ { settings.set('titleBarStyle', isMacOS() ? 'mac' : 'windows'); } + if (force || !settings.has('retainWindowSize')) { + settings.set('retainWindowSize', false); + } + + if (force || !settings.has('defaultWindowWidth')) { + settings.set('defaultWindowWidth', 1280); + } + + if (force || !settings.has('defaultWindowHeight')) { + settings.set('defaultWindowHeight', 720); + } + + if (force || !settings.has('savedWindowSize')) { + settings.set('savedWindowSize', [1280, 720]); + } + + if (force || !settings.has('savedWindowPos')) { + settings.set('savedWindowPos', [50, 50]); + } + if (force || !settings.has('musicListColumns')) { settings.set('musicListColumns', [ { diff --git a/src/main.dev.js b/src/main.dev.js index f98e893..464cf99 100644 --- a/src/main.dev.js +++ b/src/main.dev.js @@ -100,6 +100,10 @@ const previousTrack = () => { mainWindow.webContents.send('player-prev-track'); }; +const quickSave = () => { + mainWindow.webContents.send('save-queue-state', app.getPath('userData')); +}; + if (isLinux()) { const mprisPlayer = Player({ name: 'Sonixd', @@ -279,7 +283,7 @@ if (isWindows() && isWindows10()) { const Controls = windowsMediaPlayback.BackgroundMediaPlayer.current.systemMediaTransportControls; - if (settings.getSync('systemMediaTransportControls')) { + if (settings.get('systemMediaTransportControls')) { Controls.isEnabled = true; } else { Controls.isEnabled = false; @@ -411,10 +415,26 @@ const createWindow = async () => { await installExtensions(); } + let windowDimensions = []; + let windowPos = []; + let isCentered = true; + + // If retained window size is enabled, use saved dimensions and position. Otherwise, use defined defaults + if (settings.get('retainWindowSize')) { + windowDimensions = settings.get('savedWindowSize'); + windowPos = settings.get('savedWindowPos'); + isCentered = false; + } else { + windowDimensions = [settings.get('defaultWindowWidth'), settings.get('defaultWindowHeight')]; + } + mainWindow = new BrowserWindow({ show: false, - width: 1024, - height: 728, + width: windowDimensions[0], + height: windowDimensions[1], + center: isCentered, + x: windowPos[0], + y: windowPos[1], icon: getAssetPath('icon.png'), webPreferences: { nodeIntegration: true, @@ -461,6 +481,10 @@ const createWindow = async () => { }); } + ipcMain.on('quicksave', () => { + quickSave(); + }); + ipcMain.on('enableGlobalHotkeys', () => { electronLocalshortcut.unregisterAll(mainWindow); @@ -532,6 +556,12 @@ const createWindow = async () => { } }); + mainWindow.on('moved', () => { + if (settings.get('retainWindowSize')) { + settings.set('savedWindowPos', mainWindow.getPosition()); + } + }); + mainWindow.on('close', (event) => { if (!exitFromTray && !forceQuit && store.getState().config.window.exitToTray) { exitFromTray = true; @@ -539,6 +569,12 @@ const createWindow = async () => { mainWindow.hide(); } + // If retain window size is enabled, save the dimensions + if (settings.get('retainWindowSize')) { + const curSize = mainWindow.getSize(); + settings.set('savedWindowSize', [curSize[0], curSize[1]]); + } + // If we have enabled saving the queue, we need to defer closing the main window until it has finished saving. if (!saved && settings.get('resume')) { event.preventDefault(); diff --git a/src/redux/miscSlice.ts b/src/redux/miscSlice.ts index 174e519..e8db576 100644 --- a/src/redux/miscSlice.ts +++ b/src/redux/miscSlice.ts @@ -52,6 +52,11 @@ export interface General { isProcessingPlaylist: string[]; contextMenu: ContextMenu; dynamicBackground: boolean; + retainWindowSize: boolean; + savedWindowSize: number[]; + savedWindowPos: number[]; + defaultWindowWidth: number; + defaultWindowHeight: number; highlightOnRowHover: boolean; imageCachePath: string; songCachePath: string; @@ -75,6 +80,11 @@ const initialState: General = { show: false, }, dynamicBackground: Boolean(parsedSettings.dynamicBackground), + retainWindowSize: Boolean(parsedSettings.retainWindowSize), + savedWindowSize: Array(parsedSettings.savedWindowSize), + savedWindowPos: Array(parsedSettings.savedWindowPos), + defaultWindowWidth: Number(parsedSettings.defaultWindowWidth), + defaultWindowHeight: Number(parsedSettings.defaultWindowHeight), highlightOnRowHover: Boolean(parsedSettings.highlightOnRowHover), imageCachePath: getImageCachePath(), songCachePath: getSongCachePath(), @@ -90,6 +100,22 @@ const miscSlice = createSlice({ state.dynamicBackground = action.payload; }, + setRetainWindowSize: (state, action: PayloadAction) => { + state.retainWindowSize = action.payload; + }, + savedWindowSize: (state, action: PayloadAction>) => { + state.savedWindowSize = action.payload; + }, + savedWindowPos: (state, action: PayloadAction>) => { + state.savedWindowPos = action.payload; + }, + setDefaultWindowWidth: (state, action: PayloadAction) => { + state.defaultWindowWidth = action.payload; + }, + setDefaultWindowHeight: (state, action: PayloadAction) => { + state.defaultWindowHeight = action.payload; + }, + setMiscSetting: (state, action: PayloadAction<{ setting: string; value: any }>) => { switch (action.payload.setting) { case 'imageCachePath': @@ -196,7 +222,12 @@ export const { removeProcessingPlaylist, setContextMenu, setDynamicBackground, + savedWindowSize, + savedWindowPos, + setDefaultWindowWidth, + setDefaultWindowHeight, setMiscSetting, setImgModal, + setRetainWindowSize, } = miscSlice.actions; export default miscSlice.reducer; diff --git a/src/shared/mockSettings.ts b/src/shared/mockSettings.ts index 1d774b4..66de56a 100644 --- a/src/shared/mockSettings.ts +++ b/src/shared/mockSettings.ts @@ -332,6 +332,11 @@ export const mockSettings = { server: 'http://192.168.14.11:4040', serverBase64: 'aHR0cDovLzE5Mi4xNjguMTQuMTE6NDA0MA==', dynamicBackground: false, + retainWindowSize: false, + savedWindowSize: [1024, 728], + savedWindowPos: [50, 50], + defaultWindowWidth: 1024, + defaultWindowHeight: 728, minimizeToTray: true, exitToTray: true, windowPosition: { x: 0, y: 0, width: 960, height: 1560 },