From 59f2a02c362e0f9c1c40d0bcfb6c83dc39f133b2 Mon Sep 17 00:00:00 2001 From: Devin Binnie <52460000+devinbinnie@users.noreply.github.com> Date: Wed, 29 Jan 2020 16:28:32 -0500 Subject: [PATCH] [MM-22073] Added new titlebar to Settings page (#1171) --- src/browser/components/SettingsPage.jsx | 278 +++++++++++++++++++++--- src/browser/css/settings.css | 9 + src/browser/settings.jsx | 34 ++- 3 files changed, 294 insertions(+), 27 deletions(-) diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index a6977d9b..0e5e9152 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -6,15 +6,23 @@ /* eslint-disable react/no-set-state */ import React from 'react'; +import PropTypes from 'prop-types'; import {Button, Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row} from 'react-bootstrap'; import {ipcRenderer, remote} from 'electron'; import {debounce} from 'underscore'; +import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon'; import Config from '../../common/config'; +import restoreButton from '../../assets/titlebar/chrome-restore.svg'; +import maximizeButton from '../../assets/titlebar/chrome-maximize.svg'; +import minimizeButton from '../../assets/titlebar/chrome-minimize.svg'; +import closeButton from '../../assets/titlebar/chrome-close.svg'; + import TeamList from './TeamList.jsx'; import AutoSaveIndicator from './AutoSaveIndicator.jsx'; +import TabBar from './TabBar.jsx'; const CONFIG_TYPE_SERVERS = 'servers'; const CONFIG_TYPE_APP_OPTIONS = 'appOptions'; @@ -32,6 +40,9 @@ export default class SettingsPage extends React.Component { super(props); this.state = this.convertConfigDataToState(config.data); + this.setState({ + maximized: false, + }); this.trayIconThemeRef = React.createRef(); @@ -43,6 +54,7 @@ export default class SettingsPage extends React.Component { } componentDidMount() { + const self = this; config.on('update', (configData) => { this.updateSaveState(); this.setState(this.convertConfigDataToState(configData, this.state)); @@ -60,6 +72,29 @@ export default class SettingsPage extends React.Component { }); }); + function focusListener() { + self.setState({unfocused: false}); + } + + function blurListener() { + self.setState({unfocused: true}); + } + + const currentWindow = remote.getCurrentWindow(); + currentWindow.on('focus', focusListener); + currentWindow.on('blur', blurListener); + if (currentWindow.isMaximized()) { + self.setState({maximized: true}); + } + currentWindow.on('maximize', this.handleMaximizeState); + currentWindow.on('unmaximize', this.handleMaximizeState); + + if (currentWindow.isFullScreen()) { + self.setState({fullScreen: true}); + } + currentWindow.on('enter-full-screen', this.handleFullScreenState); + currentWindow.on('leave-full-screen', this.handleFullScreenState); + // when the config object changes here in the renderer process, tell the main process to reload its config object to get the changes config.on('synchronize', () => { ipcRenderer.send('reload-config'); @@ -157,6 +192,32 @@ export default class SettingsPage extends React.Component { } activeTabWebContents.pasteAndMatchStyle(); }); + + if (process.platform === 'darwin') { + self.setState({ + isDarkMode: remote.systemPreferences.isDarkMode(), + }); + remote.systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => { + self.setState({ + isDarkMode: remote.systemPreferences.isDarkMode(), + }); + }); + } else { + self.setState({ + isDarkMode: this.props.getDarkMode(), + }); + + ipcRenderer.on('set-dark-mode', () => { + this.setDarkMode(); + }); + + this.threeDotMenu = React.createRef(); + ipcRenderer.on('focus-three-dot-menu', () => { + if (this.threeDotMenu.current) { + this.threeDotMenu.current.focus(); + } + }); + } } convertConfigDataToState = (configData, currentState = {}) => { @@ -373,10 +434,158 @@ export default class SettingsPage extends React.Component { }); } + setDarkMode() { + this.setState({ + isDarkMode: this.props.setDarkMode(), + }); + } + + handleClose = () => { + const win = remote.getCurrentWindow(); + win.close(); + } + + handleMinimize = () => { + const win = remote.getCurrentWindow(); + win.minimize(); + } + + handleMaximize = () => { + const win = remote.getCurrentWindow(); + win.maximize(); + } + + handleRestore = () => { + const win = remote.getCurrentWindow(); + win.restore(); + } + + openMenu = () => { + // @eslint-ignore + this.threeDotMenu.current.blur(); + this.props.openMenu(); + } + + handleDoubleClick = () => { + const doubleClickAction = remote.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string'); + const win = remote.getCurrentWindow(); + if (doubleClickAction === 'Minimize') { + win.minimize(); + } else if (doubleClickAction === 'Maximize' && !win.isMaximized()) { + win.maximize(); + } else if (doubleClickAction === 'Maximize' && win.isMaximized()) { + win.unmaximize(); + } + } + + handleMaximizeState = () => { + const win = remote.getCurrentWindow(); + this.setState({maximized: win.isMaximized()}); + } + + handleFullScreenState = () => { + const win = remote.getCurrentWindow(); + this.setState({fullScreen: win.isFullScreen()}); + } + render() { + const tabsRow = ( + + ); + + let topBarClassName = 'topBar'; + if (process.platform === 'darwin') { + topBarClassName += ' macOS'; + } + if (this.state.isDarkMode) { + topBarClassName += ' darkMode'; + } + if (this.state.fullScreen) { + topBarClassName += ' fullScreen'; + } + + let maxButton; + if (this.state.maximized) { + maxButton = ( +
+ +
+ ); + } else { + maxButton = ( +
+ +
+ ); + } + + let overlayGradient; + if (process.platform !== 'darwin') { + overlayGradient = ( + + ); + } + + let titleBarButtons; + if (process.platform !== 'darwin') { + titleBarButtons = ( + +
+ +
+ {maxButton} +
+ +
+
+ ); + } + + const topRow = ( + +
+ + {tabsRow} + {overlayGradient} + {titleBarButtons} +
+
+ ); + const settingsPage = { navbar: { backgroundColor: '#fff', + position: 'relative', }, close: { textDecoration: 'none', @@ -688,35 +897,54 @@ export default class SettingsPage extends React.Component { ) : null; return ( -
- + { topRow } +
-
-

{'Settings'}

- -
- - - { srvMgmt } - { optionsRow } - + +
+

{'Settings'}

+ +
+
+ + { srvMgmt } + { optionsRow } + +
); } } +SettingsPage.propTypes = { + getDarkMode: PropTypes.func.isRequired, + setDarkMode: PropTypes.func.isRequired, + openMenu: PropTypes.func.isRequired, +}; + /* eslint-enable react/no-set-state */ diff --git a/src/browser/css/settings.css b/src/browser/css/settings.css index 36d58b9a..f57d57f6 100644 --- a/src/browser/css/settings.css +++ b/src/browser/css/settings.css @@ -20,3 +20,12 @@ .checkbox > label { width: 100%; } + +body { + overflow: hidden; + height: 100%; +} + +#content { + height: 100%; +} \ No newline at end of file diff --git a/src/browser/settings.jsx b/src/browser/settings.jsx index a8051c4b..c41e658c 100644 --- a/src/browser/settings.jsx +++ b/src/browser/settings.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {remote} from 'electron'; +import {remote, ipcRenderer} from 'electron'; window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-eval throw new Error(`Sorry, ${remote.app.getName()} does not support window.eval() for security reasons.`); @@ -10,13 +10,43 @@ window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-e import React from 'react'; import ReactDOM from 'react-dom'; +import Config from '../common/config'; + import SettingsPage from './components/SettingsPage.jsx'; import contextMenu from './js/contextMenu'; contextMenu.setup(remote.getCurrentWindow()); +const config = new Config(remote.app.getPath('userData') + '/config.json', remote.getCurrentWindow().registryConfigData); + +function getDarkMode() { + if (process.platform !== 'darwin') { + return config.darkMode; + } + return null; +} + +function setDarkMode() { + if (process.platform !== 'darwin') { + const darkMode = Boolean(config.darkMode); + config.set('darkMode', !darkMode); + return !darkMode; + } + return null; +} + +function openMenu() { + if (process.platform !== 'darwin') { + ipcRenderer.send('open-app-menu'); + } +} + ReactDOM.render( - , + , document.getElementById('content') );