From 278b362366bee15efceedcb0818812c033a5b998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20S=C3=A1nchez-Gallego?= Date: Fri, 9 Dec 2022 10:10:18 -0800 Subject: [PATCH] Allow to remove config and save windows only on request --- src/main/events.ts | 8 +- src/main/main.ts | 55 +++-- src/main/menu.ts | 6 + src/main/preload.ts | 10 +- src/main/store/user.json | 3 +- src/main/util.ts | 31 +++ src/renderer/App.tsx | 5 + src/renderer/Components/IOSwitch.tsx | 12 + .../Preferences/Components/Switch.tsx | 7 +- .../Preferences/Panes/InterfacePane.tsx | 208 ++++++++++++++---- src/renderer/hooks/useStore.ts | 2 +- 11 files changed, 283 insertions(+), 64 deletions(-) diff --git a/src/main/events.ts b/src/main/events.ts index 0f4e1dd..6a0b678 100644 --- a/src/main/events.ts +++ b/src/main/events.ts @@ -89,7 +89,7 @@ export default function loadEvents() { } else if (mode === 'merge') { const def = config[val as keyof typeof config] ?? {}; const user = store.get(val); - event.returnValue = Object.assign(def, user); + event.returnValue = { ...def, ...user }; } else { event.returnValue = undefined; } @@ -100,6 +100,9 @@ export default function loadEvents() { ipcMain.handle('store:delete', async (event, key) => { store.delete(key); }); + ipcMain.handle('store:clear', async () => { + store.clear(); + }); ipcMain.handle( 'store:subscribe', async (event, property, channel: string) => { @@ -145,7 +148,8 @@ export default function loadEvents() { ipcMain.handle( 'dialog:show-message-box', async (event, options: MessageBoxOptions) => { - await dialog.showMessageBox(options); + const result = await dialog.showMessageBox(options); + return result; } ); ipcMain.handle( diff --git a/src/main/main.ts b/src/main/main.ts index aa097ea..533154c 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -46,7 +46,10 @@ if (isDebug) { } function saveWindows() { - return store.get('interface.saveWindows', true); + const save = store.get('interface.saveWindows', true); + const onlyOnRequest = store.get('interface.saveOnlyOnRequest', false); + + return save && !onlyOnRequest; } const installExtensions = async () => { @@ -101,14 +104,13 @@ export async function createWindow(windowName: WindowNames) { let windowParams: WindowParams = config.windows[windowName.startsWith('log') ? 'log' : windowName] ?? {}; - if (saveWindows()) { - const savedWindowParams: WindowParams = store.get(`windows.${name}`) || {}; - windowParams = Object.assign(windowParams, savedWindowParams); - } - + const savedWindowParams: WindowParams = store.get(`windows.${name}`) || {}; + windowParams = { ...windowParams, ...savedWindowParams }; + console.log(name, windowParams); const newWindow = new BrowserWindow({ show: false, icon: getAssetPath('icon.png'), + title: name, titleBarStyle: 'hidden', backgroundColor: nativeTheme.shouldUseDarkColors ? '#37393E' : '#FFFFFF', ...windowParams, @@ -136,21 +138,35 @@ export async function createWindow(windowName: WindowNames) { newWindow.show(); } - const openWindows: string[] = store.get('windows.openWindows', ['main']); - if (!openWindows.includes(name)) openWindows.push(name); - store.set('windows.openWindows', openWindows); + if (saveWindows()) { + const openWindows: string[] = store.get('windows.openWindows', ['main']); + if (!openWindows.includes(name)) openWindows.push(name); + store.set('windows.openWindows', openWindows); + } // Force devtools to not show up on start. // newWindow.webContents.closeDevTools(); + + // This won't show anything on the window itself, but that way we can + // generate a list of window name to BrowserWindow anywhere. + newWindow.setTitle(name); }); + // newWindow.on('did-finish-load', () => { + // newWindow.setTitle(name); + // }); + newWindow.on('resized', () => { + if (!saveWindows()) return; + const size = newWindow.getSize(); store.set(`windows.${name}.width`, size[0]); store.set(`windows.${name}.height`, size[1]); }); newWindow.on('moved', () => { + if (!saveWindows()) return; + const position = newWindow.getPosition(); store.set(`windows.${name}.x`, position[0]); store.set(`windows.${name}.y`, position[1]); @@ -158,11 +174,14 @@ export async function createWindow(windowName: WindowNames) { newWindow.on('closed', () => { windows.delete(name); - const openWindows: string[] = store.get('windows.openWindows', []); - const newOpenWindows = openWindows.filter((value) => { - return value === 'main' || value !== name; - }); - setTimeout(() => store.set('windows.openWindows', newOpenWindows), 3000); + + if (saveWindows()) { + const openWindows: string[] = store.get('windows.openWindows', []); + const newOpenWindows = openWindows.filter((value) => { + return value === 'main' || value !== name; + }); + setTimeout(() => store.set('windows.openWindows', newOpenWindows), 3000); + } }); if (name === 'main') { @@ -199,11 +218,9 @@ app.on('window-all-closed', () => { app .whenReady() .then(() => { - let openWindows: WindowNames[] = ['main']; - if (saveWindows()) { - openWindows = store.get('windows.openWindows', ['main']); - } - + const openWindows: WindowNames[] = store.get('windows.openWindows', [ + 'main', + ]); openWindows.map((key) => createWindow(key)); app.on('activate', () => { diff --git a/src/main/menu.ts b/src/main/menu.ts index cb82d6a..0824eca 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -8,6 +8,7 @@ import { shell, } from 'electron'; import { createWindow } from './main'; +import { saveWindows } from './util'; interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { selector?: string; @@ -70,6 +71,11 @@ export default class MenuBuilder { click: () => createWindow('preferences'), }, { type: 'separator' }, + { + label: 'Save window positions', + click: saveWindows, + }, + { type: 'separator' }, { label: 'Services', submenu: [] }, { type: 'separator' }, { diff --git a/src/main/preload.ts b/src/main/preload.ts index bd8740a..e6fdf5b 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -12,6 +12,7 @@ import { ipcRenderer, IpcRendererEvent, MessageBoxOptions, + MessageBoxReturnValue, shell, } from 'electron'; import Command from './tron/command'; @@ -122,6 +123,9 @@ const ElectronAPI = { delete(key: string) { return ipcRenderer.invoke('store:delete', key); }, + clear() { + return ipcRenderer.invoke('store:clear'); + }, subscribe(property: string, channel: string) { return ipcRenderer.invoke('store:subscribe', property, channel); }, @@ -149,9 +153,11 @@ const ElectronAPI = { }, }, dialog: { - showMessageBox: async (options: MessageBoxOptions) => + showMessageBox: async ( + options: MessageBoxOptions + ): Promise => ipcRenderer.invoke('dialog:show-message-box', options), - showErrorBox: async (title: string, content: string) => + showErrorBox: async (title: string, content: string): Promise => ipcRenderer.invoke('dialog:show-error-box', title, content), }, }; diff --git a/src/main/store/user.json b/src/main/store/user.json index 9184ca6..a2b0cc8 100644 --- a/src/main/store/user.json +++ b/src/main/store/user.json @@ -15,7 +15,8 @@ }, "interface": { "mode": "system", - "saveWindows": true + "saveWindows": true, + "saveOnlyOnRequest": false }, "guider": { "xpaset": "/usr/local/bin/xpaset", diff --git a/src/main/util.ts b/src/main/util.ts index 742a1e4..32f055a 100644 --- a/src/main/util.ts +++ b/src/main/util.ts @@ -1,6 +1,9 @@ /* eslint import/prefer-default-export: off */ +import { BrowserWindow } from 'electron'; import path from 'path'; import { URL } from 'url'; +import store from './store'; +import { WindowParams } from './types'; export function resolveHtmlPath(htmlFileName: string) { if (process.env.NODE_ENV === 'development') { @@ -23,6 +26,34 @@ function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min)) + min; } +export function saveWindows() { + const openWindows = BrowserWindow.getAllWindows(); + + const windows: { [key: string]: WindowParams } = {}; + + openWindows.forEach((win) => { + const name = win.getTitle(); + + const size = win.getSize(); + const position = win.getPosition(); + + windows[name] = { + x: position[0], + y: position[1], + width: size[0], + height: size[1], + }; + }); + + store.set('windows', windows); + + const openWindowsNames = openWindows.map((win) => win.getTitle()); + if (!openWindowsNames.includes('main')) { + openWindowsNames.push('main'); + } + store.set('windows.openWindows', openWindowsNames); +} + export function generateName() { const name1 = [ 'abandoned', diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 5bf6424..bf8fc1f 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -36,6 +36,11 @@ export default function App() { action: { boxBackground: prefersDarkMode ? '#40444B' : '#EBEDEF', }, + error: { + dark: '#d32f2f', + light: '#e57373', + main: '#CC4B41', + }, }, typography: { fontSize: 12, diff --git a/src/renderer/Components/IOSwitch.tsx b/src/renderer/Components/IOSwitch.tsx index 3169b7a..431f81e 100644 --- a/src/renderer/Components/IOSwitch.tsx +++ b/src/renderer/Components/IOSwitch.tsx @@ -47,6 +47,9 @@ const IOSSwitch = styled((props: SwitchProps) => ( opacity: 1, border: 0, }, + '&.Mui-disabled + .MuiSwitch-track': { + opacity: 0.5, + }, '&:hover': { backgroundColor: theme.palette.mode === 'light' && 'white', }, @@ -55,6 +58,15 @@ const IOSSwitch = styled((props: SwitchProps) => ( backgroundColor: theme.palette.action.active, }, }, + '&.Mui-disabled .MuiSwitch-thumb': { + color: + theme.palette.mode === 'light' + ? theme.palette.grey[100] + : theme.palette.grey[600], + }, + '&.Mui-disabled + .MuiSwitch-track': { + opacity: theme.palette.mode === 'light' ? 0.7 : 0.3, + }, '& .MuiSwitch-track': { borderRadius: 26 / 2, backgroundColor: theme.palette.action.disabledBackground, diff --git a/src/renderer/Preferences/Components/Switch.tsx b/src/renderer/Preferences/Components/Switch.tsx index 199b382..63d85fb 100644 --- a/src/renderer/Preferences/Components/Switch.tsx +++ b/src/renderer/Preferences/Components/Switch.tsx @@ -13,10 +13,11 @@ import { useStore } from 'renderer/hooks'; export interface SwitchProps { param: string; + disabled?: boolean; } export default function Switch(props: SwitchProps) { - const { param } = props; + const { param, disabled = false } = props; const [value, setValue] = useStore(param); @@ -27,5 +28,7 @@ export default function Switch(props: SwitchProps) { setValue(checked); }; - return ; + return ( + + ); } diff --git a/src/renderer/Preferences/Panes/InterfacePane.tsx b/src/renderer/Preferences/Panes/InterfacePane.tsx index 50ac3f2..e1f123e 100644 --- a/src/renderer/Preferences/Panes/InterfacePane.tsx +++ b/src/renderer/Preferences/Panes/InterfacePane.tsx @@ -6,6 +6,7 @@ */ import { + Box, Button, Divider, FormControl, @@ -18,6 +19,7 @@ import { Stack } from '@mui/system'; import Grid from '@mui/system/Unstable_Grid'; import React from 'react'; import { ColorModeValues } from 'renderer/App'; +import { useStore } from 'renderer/hooks'; import Pane from '../Components/Pane'; import PreferencesFormControlLabel from '../Components/PreferencesFormControlLabel'; import PreferencesRadioGroup from '../Components/PreferencesRadioGroup'; @@ -32,16 +34,12 @@ function ThemeMode() { window.electron.store.set('interface.mode', event.target.value); }; - const deleteSavedWindows = () => { - window.electron.store.delete('savedState.windows'); - }; - return ( - + Theme mode - + - - - Window management - - - - - Save window positions - + + ); +} + +function WindowManagement() { + const [saveOnlyOnRequest] = useStore('interface.saveOnlyOnRequest'); + + const deleteSavedWindows = () => window.electron.store.delete('windows'); + + return ( + + + + Window management + + + {/* Save window positions */} + + + ({ + minWidth: '150px', + color: theme.palette.text.primary, + userSelect: 'none', + alignSelf: 'center', + })} + gutterBottom + > + Save window positions + + + + + + + + Remembers open windows, dimensions, and positions. + + + + {/* Save only when requested */} + + + + ({ + minWidth: '150px', + color: theme.palette.text.primary, + userSelect: 'none', + alignSelf: 'center', + })} + gutterBottom + > + Save only when requested + + + + + - - + + Windows will be saved only when manually requested + + + + {/* Delete window positions */} + + + + ({ + minWidth: '150px', + color: theme.palette.text.primary, + userSelect: 'none', + alignSelf: 'center', + })} + gutterBottom + > + Delete window positions + + + + + - - - Remembers open windows, dimensions, and positions. - - - - + + Removes all saved window position. + + + + ); +} + +function Configuration() { + const clearConfig = () => { + window.electron.dialog + .showMessageBox({ + message: 'Confirm removal of user configuration?', + type: 'question', + buttons: ['Cancel', 'OK'], + }) + .then(({ response }) => { + if (response === 1) { + window.electron.store.clear(); + } + return true; + }) + .catch(() => {}); + }; + + return ( + + + + User Configuration + + + + + ({ + minWidth: '150px', + color: theme.palette.text.primary, + userSelect: 'none', + alignSelf: 'center', + })} + gutterBottom + > + Reset configuration + + + + + + + + Reverts to the default configuration. An app restart is required. + + ); } @@ -105,8 +235,12 @@ export default function InterfacePane() { - + + + + + diff --git a/src/renderer/hooks/useStore.ts b/src/renderer/hooks/useStore.ts index a6ff83d..d04c87d 100644 --- a/src/renderer/hooks/useStore.ts +++ b/src/renderer/hooks/useStore.ts @@ -30,7 +30,7 @@ export default function useStore( setValue(newValue); } else if (mode === 'merge') { const DEFAULT_VALUE = window.electron.store.get(key, 'default') ?? {}; - setValue(Object.assign(DEFAULT_VALUE, newValue)); + setValue({ ...DEFAULT_VALUE, ...newValue }); } };