Electron-266 (Custom title bar for Windows 10) (#311)

1. Implemented new title bar style for Windows
2. Updated design
3. Updated design and added some safety checks
4. Added title bar double click event
5. Added API to update DOM element
6. Updated style as per the design spec
7. Refactored code and handled full screen scenario
8. Added borders to window
9. Added z-index to make sure border is always on top
10. Updated logic to check Windows 10 version
11. Setting frame property as true for pop-out windows
12. Optimized the code to initiate Windows title bar only after injecting CSS
13. Removed API to update content height
14. Added a global config field to enable or disable custom title bar
15. Reading `isCustomTitleBar` before creating main window
16. Fixed shortcut suggestion in menu items
17. Added two more missing shortcut suggestion in menu items
18. Updated menu template to dynamically assign accelerators
19. Changed the func name closeButtonClick to closeWindow
20. Converted all HEX color code to rgba
This commit is contained in:
Kiran Niranjan
2018-03-09 16:16:47 +05:30
committed by Vishwas Shashidhar
parent af510a5ce1
commit 9991690a7e
10 changed files with 551 additions and 51 deletions

View File

@@ -5,6 +5,7 @@
"alwaysOnTop" : false, "alwaysOnTop" : false,
"bringToFront": false, "bringToFront": false,
"whitelistUrl": "*", "whitelistUrl": "*",
"isCustomTitleBar": true,
"notificationSettings": { "notificationSettings": {
"position": "upper-right", "position": "upper-right",
"display": "" "display": ""

View File

@@ -18,7 +18,8 @@ const cmds = keyMirror({
showNotificationSettings: null, showNotificationSettings: null,
sanitize: null, sanitize: null,
bringToFront: null, bringToFront: null,
openScreenPickerWindow: null openScreenPickerWindow: null,
popupMenu: null
}); });
module.exports = { module.exports = {

View File

@@ -141,6 +141,12 @@ electron.ipcMain.on(apiName, (event, arg) => {
openScreenPickerWindow(event.sender, arg.sources, arg.id); openScreenPickerWindow(event.sender, arg.sources, arg.id);
} }
break; break;
case apiCmds.popupMenu:
var browserWin = electron.BrowserWindow.fromWebContents(event.sender);
if (browserWin && !browserWin.isDestroyed()) {
windowMgr.getMenu().popup(browserWin, { x: 20, y: 15, async: true });
}
break;
default: default:
} }

View File

@@ -3,7 +3,7 @@
const electron = require('electron'); const electron = require('electron');
const { updateConfigField, getMultipleConfigField } = require('../config.js'); const { updateConfigField, getMultipleConfigField } = require('../config.js');
const AutoLaunch = require('auto-launch'); const AutoLaunch = require('auto-launch');
const isMac = require('../utils/misc.js').isMac; const { isMac, isWindowsOS } = require('../utils/misc.js');
const log = require('../log.js'); const log = require('../log.js');
const logLevels = require('../enums/logLevels.js'); const logLevels = require('../enums/logLevels.js');
const eventEmitter = require('../eventEmitter'); const eventEmitter = require('../eventEmitter');
@@ -24,6 +24,22 @@ let bringToFront = false;
let symphonyAutoLauncher; let symphonyAutoLauncher;
const windowsAccelerator = Object.assign({
undo: 'Ctrl+Z',
redo: 'Ctrl+Y',
cut: 'Ctrl+X',
copy: 'Ctrl+C',
paste: 'Ctrl+V',
pasteandmatchstyle: 'Ctrl+Shift+V',
selectall: 'Ctrl+A',
resetzoom: 'Ctrl+0',
zoomin: 'Ctrl+Shift+Plus',
zoomout: 'Ctrl+-',
togglefullscreen: 'F11',
minimize: 'Ctrl+M',
close: 'Ctrl+W',
});
if (isMac) { if (isMac) {
symphonyAutoLauncher = new AutoLaunch({ symphonyAutoLauncher = new AutoLaunch({
name: 'Symphony', name: 'Symphony',
@@ -42,15 +58,15 @@ if (isMac) {
const template = [{ const template = [{
label: 'Edit', label: 'Edit',
submenu: [ submenu: [
{ role: 'undo' }, buildMenuItem('undo'),
{ role: 'redo' }, buildMenuItem('redo'),
{ type: 'separator' }, { type: 'separator' },
{ role: 'cut' }, buildMenuItem('cut'),
{ role: 'copy' }, buildMenuItem('copy'),
{ role: 'paste' }, buildMenuItem('paste'),
{ role: 'pasteandmatchstyle' }, buildMenuItem('pasteandmatchstyle'),
{ role: 'delete' }, buildMenuItem('delete'),
{ role: 'selectall' } buildMenuItem('selectall')
] ]
}, },
{ {
@@ -96,34 +112,19 @@ const template = [{
electron.shell.showItemInFolder(crashesDirectory); electron.shell.showItemInFolder(crashesDirectory);
} }
}, },
{ { type: 'separator' },
type: 'separator' buildMenuItem('resetzoom'),
}, buildMenuItem('zoomin'),
{ buildMenuItem('zoomout'),
role: 'resetzoom' { type: 'separator' },
}, buildMenuItem('togglefullscreen'),
{
role: 'zoomin'
},
{
role: 'zoomout'
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
}
] ]
}, },
{ {
role: 'window', role: 'window',
submenu: [{ submenu: [
role: 'minimize' buildMenuItem('minimize'),
}, buildMenuItem('close'),
{
role: 'close'
}
] ]
}, },
{ {
@@ -341,6 +342,28 @@ function setCheckboxValues() {
}); });
} }
/**
* Sets respective accelerators w.r.t roles for the menu template
*
* @param role {String} The action of the menu item
*
* @return {Object}
* @return {Object}.role The action of the menu item
* @return {Object}.accelerator keyboard shortcuts and modifiers
*/
function buildMenuItem(role) {
if (isMac) {
return { role: role }
}
if (isWindowsOS) {
return { role: role, accelerator: windowsAccelerator[role] || '' }
}
return { role: role }
}
function getMinimizeOnClose() { function getMinimizeOnClose() {
return minimizeOnClose; return minimizeOnClose;
} }

View File

@@ -19,6 +19,8 @@ const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName; const apiName = apiEnums.apiName;
const getMediaSources = require('../desktopCapturer/getSources'); const getMediaSources = require('../desktopCapturer/getSources');
const getMediaSource = require('../desktopCapturer/getSource'); const getMediaSource = require('../desktopCapturer/getSource');
const { TitleBar, updateContentHeight } = require('../windowsTitlebar');
const titleBar = new TitleBar();
require('../downloadManager'); require('../downloadManager');
@@ -387,6 +389,12 @@ function createAPI() {
} }
}); });
// Adds custom title bar style for Windows 10 OS
local.ipcRenderer.on('initiate-windows-title-bar', () => {
titleBar.initiateWindowsTitleBar();
updateContentHeight();
});
function updateOnlineStatus() { function updateOnlineStatus() {
local.ipcRenderer.send(apiName, { local.ipcRenderer.send(apiName, {
cmd: apiCmds.isOnline, cmd: apiCmds.isOnline,

View File

@@ -1,4 +1,5 @@
'use strict'; 'use strict';
const os = require('os');
const isDevEnv = process.env.ELECTRON_DEV ? const isDevEnv = process.env.ELECTRON_DEV ?
process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false; process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false;
@@ -8,9 +9,15 @@ const isWindowsOS = (process.platform === 'win32');
const isNodeEnv = !!process.env.NODE_ENV; const isNodeEnv = !!process.env.NODE_ENV;
function isWindows10() {
const [ major ] = os.release().split('.').map((part) => parseInt(part, 10));
return isWindowsOS && major >= 10;
}
module.exports = { module.exports = {
isDevEnv: isDevEnv, isDevEnv: isDevEnv,
isMac: isMac, isMac: isMac,
isWindowsOS: isWindowsOS, isWindowsOS: isWindowsOS,
isNodeEnv: isNodeEnv isNodeEnv: isNodeEnv,
isWindows10: isWindows10
}; };

View File

@@ -19,8 +19,8 @@ const logLevels = require('./enums/logLevels.js');
const notify = require('./notify/electron-notify.js'); const notify = require('./notify/electron-notify.js');
const eventEmitter = require('./eventEmitter'); const eventEmitter = require('./eventEmitter');
const throttle = require('./utils/throttle.js'); const throttle = require('./utils/throttle.js');
const { getConfigField, updateConfigField } = require('./config.js'); const { getConfigField, updateConfigField, getGlobalConfigField } = require('./config.js');
const { isMac, isNodeEnv } = require('./utils/misc'); const { isMac, isNodeEnv, isWindows10 } = require('./utils/misc');
const { deleteIndexFolder } = require('./search/search.js'); const { deleteIndexFolder } = require('./search/search.js');
const { isWhitelisted } = require('./utils/whitelistHandler'); const { isWhitelisted } = require('./utils/whitelistHandler');
@@ -44,6 +44,9 @@ let sandboxed = false;
let defaultDownloadsDirectory = app.getPath("downloads"); let defaultDownloadsDirectory = app.getPath("downloads");
let downloadsDirectory = defaultDownloadsDirectory; let downloadsDirectory = defaultDownloadsDirectory;
// Application menu
let menu;
// note: this file is built using browserify in prebuild step. // note: this file is built using browserify in prebuild step.
const preloadMainScript = path.join(__dirname, 'preload/_preloadMain.js'); const preloadMainScript = path.join(__dirname, 'preload/_preloadMain.js');
@@ -85,25 +88,28 @@ function getParsedUrl(url) {
* @param initialUrl * @param initialUrl
*/ */
function createMainWindow(initialUrl) { function createMainWindow(initialUrl) {
getConfigField('mainWinPos').then( Promise.all([
function (bounds) { getConfigField('mainWinPos'),
doCreateMainWindow(initialUrl, bounds); getGlobalConfigField('isCustomTitleBar')
}, ]).then((values) => {
function () { doCreateMainWindow(initialUrl, values[ 0 ], values[ 1 ]);
// failed, use default bounds }).catch(() => {
doCreateMainWindow(initialUrl, null); // failed use default bounds and frame
} doCreateMainWindow(initialUrl, null, false);
); });
} }
/** /**
* Creates the main window with bounds * Creates the main window with bounds
* @param initialUrl * @param initialUrl
* @param initialBounds * @param initialBounds
* @param isCustomTitleBar {Boolean} - Global config value weather to enable custom title bar
*/ */
function doCreateMainWindow(initialUrl, initialBounds) { function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
let url = initialUrl; let url = initialUrl;
let key = getGuid(); let key = getGuid();
// condition whether to enable custom Windows 10 title bar
const isCustomTitleBarEnabled = typeof isCustomTitleBar === 'boolean' && isCustomTitleBar && isWindows10();
log.send(logLevels.INFO, 'creating main window url: ' + url); log.send(logLevels.INFO, 'creating main window url: ' + url);
@@ -112,6 +118,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
show: true, show: true,
minWidth: MIN_WIDTH, minWidth: MIN_WIDTH,
minHeight: MIN_HEIGHT, minHeight: MIN_HEIGHT,
frame: !isCustomTitleBarEnabled,
alwaysOnTop: false, alwaysOnTop: false,
webPreferences: { webPreferences: {
sandbox: sandboxed, sandbox: sandboxed,
@@ -174,6 +181,11 @@ function doCreateMainWindow(initialUrl, initialBounds) {
// we might not have network connectivity, so warn the user. // we might not have network connectivity, so warn the user.
mainWindow.webContents.on('did-finish-load', function () { mainWindow.webContents.on('did-finish-load', function () {
url = mainWindow.webContents.getURL(); url = mainWindow.webContents.getURL();
if (isCustomTitleBarEnabled) {
mainWindow.webContents.insertCSS(fs.readFileSync(path.join(__dirname, '/windowsTitleBar/style.css'), 'utf8').toString());
// This is required to initiate Windows title bar only after insertCSS
mainWindow.webContents.send('initiate-windows-title-bar');
}
if (!isOnline) { if (!isOnline) {
loadErrors.showNetworkConnectivityError(mainWindow, url, retry); loadErrors.showNetworkConnectivityError(mainWindow, url, retry);
@@ -215,8 +227,12 @@ function doCreateMainWindow(initialUrl, initialBounds) {
addWindowKey(key, mainWindow); addWindowKey(key, mainWindow);
mainWindow.loadURL(url); mainWindow.loadURL(url);
const menu = electron.Menu.buildFromTemplate(getTemplate(app)); menu = electron.Menu.buildFromTemplate(getTemplate(app));
if (isWindows10()) {
mainWindow.setMenu(menu);
} else {
electron.Menu.setApplicationMenu(menu); electron.Menu.setApplicationMenu(menu);
}
mainWindow.on('close', function (e) { mainWindow.on('close', function (e) {
if (willQuitApp) { if (willQuitApp) {
@@ -371,6 +387,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
newWinOptions.minWidth = MIN_WIDTH; newWinOptions.minWidth = MIN_WIDTH;
newWinOptions.minHeight = MIN_HEIGHT; newWinOptions.minHeight = MIN_HEIGHT;
newWinOptions.alwaysOnTop = alwaysOnTop; newWinOptions.alwaysOnTop = alwaysOnTop;
newWinOptions.frame = true;
let newWinKey = getGuid(); let newWinKey = getGuid();
@@ -516,6 +533,14 @@ function getMainWindow() {
return mainWindow; return mainWindow;
} }
/**
* Gets the application menu
* @returns {*}
*/
function getMenu() {
return menu;
}
/** /**
* Gets a window's size and position * Gets a window's size and position
* @param window * @param window
@@ -839,5 +864,6 @@ module.exports = {
setIsOnline: setIsOnline, setIsOnline: setIsOnline,
activate: activate, activate: activate,
setBoundsChangeWindow: setBoundsChangeWindow, setBoundsChangeWindow: setBoundsChangeWindow,
verifyDisplays: verifyDisplays verifyDisplays: verifyDisplays,
getMenu: getMenu
}; };

View File

@@ -0,0 +1,121 @@
const titleBar = (`
<div id="title-bar">
<div class="title-bar-button-container">
<button title="Menu" id='hamburger-menu-button'>
<svg x='0px' y='0px' viewBox='0 0 15 10'>
<rect fill="rgba(255, 255, 255, 0.9)" width='15' height='1'/>
<rect fill="rgba(255, 255, 255, 0.9)" y='4' width='15' height='1'/>
<rect fill="rgba(255, 255, 255, 0.9)" y='8' width='152' height='1'/>
</svg>
</button>
</div>
<div id="title-container">
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100px" height="32px" x="0px" y="0px"
viewBox="0 0 121 24" enable-background="new 0 0 121 24" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M36.7,10c-0.6-0.5-1.8-1.1-3.4-1.1c-1.1,0-2,0.4-2,1.3c0,1,1.3,1,2.7,1.1c1.7,0.1,4.5,0.3,4.5,3
c0,2.4-2,3.3-4.3,3.3c-2.5,0-4.2-1-5.3-2l1.1-1.3c0.8,0.7,2.2,1.7,4.2,1.7c1.3,0,2.4-0.5,2.4-1.5c0-1.1-1-1.3-2.6-1.4
c-2.1-0.2-4.6-0.3-4.6-2.7c0-2.3,2.1-3,3.9-3c2,0,3.7,0.8,4.5,1.3L36.7,10z"/>
<path fill="#FFFFFF" d="M59.4,17.2v-6.1h-0.1l-2.9,4.9h-0.4L53,11.2h-0.1v6.1h-1.8V7.6h1.9l3.3,5.5h0l3.2-5.5h1.8v9.6H59.4z"/>
<path fill="#FFFFFF" d="M75.6,17.2V7.6h1.9v3.8h4.9V7.6h1.9v9.6h-1.9v-4.2h-4.9v4.2H75.6z"/>
<path fill="#FFFFFF" d="M91.9,15.9c1.9,0,3.3-1.6,3.3-3.5c0-1.9-1.5-3.4-3.3-3.4c-1.8,0-3.3,1.5-3.3,3.4
C88.7,14.3,90.1,15.9,91.9,15.9z M91.9,7.3c2.9,0,5.3,2.3,5.3,5.1c0,2.9-2.3,5.1-5.3,5.1c-2.9,0-5.2-2.3-5.2-5.1
C86.7,9.6,89,7.3,91.9,7.3z"/>
<path fill="#FFFFFF" d="M108.3,7.6v9.6h-1.6l-5.2-6.3h0v6.3h-1.9V7.6h1.6l5.2,6.3h0V7.6H108.3z"/>
<polygon fill="#FFFFFF" points="120.1,7.6 117.9,7.6 115.2,12.2 115.1,12.2 112.4,7.6 110.1,7.6 114.2,14 114.2,17.2 116,17.2
116,14 "/>
<polygon fill="#FFFFFF" points="49.3,7.6 47.1,7.6 44.4,12.2 44.3,12.2 41.6,7.6 39.3,7.6 43.4,14 43.4,17.2 45.2,17.2 45.2,14
"/>
<path fill="#FFFFFF" d="M66.2,14.1l3.1,0c1.4,0,2.3-0.4,2.9-1c0.6-0.6,1-1.4,1-2.3c0-0.8-0.3-1.5-0.8-2.1c-0.7-0.7-1.6-1.1-3.1-1.1
h-5v9.6h1.9V14.1z M66.2,9.2h3.1c0.5,0,1.1,0.1,1.5,0.5c0.3,0.3,0.4,0.7,0.4,1.1c0,0.4-0.2,0.8-0.5,1.1c-0.5,0.4-1,0.5-1.5,0.5
l-3.1,0V9.2z"/>
</g>
<g>
<g>
<path fill="#F48DA1" d="M4,12.2c-0.1,0.5-0.1,0.9-0.1,1.4c0,1.5,0.4,2.9,1,4.1l8.1-4.1L4,12.2z"/>
</g>
<g>
<path fill="#33B9E6" d="M9.7,18.2c0.9,0.7,2.1,1.1,3.3,1.1c0.3,0,0.6,0,0.9-0.1L13,13.6L9.7,18.2z"/>
</g>
<g>
<path fill="#90D095" d="M18.2,1.1c-1.6-0.7-3.4-1-5.2-1c-0.7,0-1.4,0.1-2.1,0.2L13,13.6L18.2,1.1z"/>
</g>
<g>
<path fill="#FED05F" d="M13,13.6h11.3c0-2.5-0.8-4.8-2.2-6.7L13,13.6z"/>
</g>
<g>
<path fill="#727376" d="M2.9,7.4c-1.1,1.8-1.8,3.9-1.8,6.2H13L2.9,7.4z"/>
</g>
<g>
<path fill="#A1A2A3" d="M13,5.7c-1.7,0-3.4,0.6-4.7,1.5l4.7,6.4V5.7z"/>
</g>
<g>
<path fill="#8A8B8C" d="M13,23.8c2.2,0,4.3-0.7,6-1.9l-6-8.2V23.8z"/>
</g>
<g>
<path fill="#636466" d="M20.8,12.4L13,13.6l7.3,3c0.4-0.9,0.6-2,0.6-3C20.9,13.2,20.9,12.8,20.8,12.4z"/>
</g>
</g>
</svg>
</div>
</div>
`);
const button = (`
<div class="action-items">
<div class="title-bar-button-container">
<button class="title-bar-button" id="title-bar-minimize-button" title='Minimize'>
<svg x='0px' y='0px' viewBox='0 0 14 1'>
<rect fill="rgba(255, 255, 255, 0.9)" width='14' height='0.6' />
</svg>
</button>
</div>
<div class="title-bar-button-container">
<button class="title-bar-button" id="title-bar-maximize-button" title='Maximize'>
<svg x='0px' y='0px' viewBox='0 0 14 10.2'>
<path
fill='rgba(255, 255, 255, 0.9)'
d='M0,0v10.1h10.2V0H0z M9.2,9.2H1.1V1h8.1V9.2z'
/>
</svg>
</button>
</div>
<div class="title-bar-button-container">
<button class="title-bar-button" id="title-bar-close-button" title='Close'>
<svg x='0px' y='0px' viewBox='0 0 14 10.2'>
<polygon
fill="rgba(255, 255, 255, 0.9)"
points='10.2,0.7 9.5,0 5.1,4.4 0.7,0 0,0.7 4.4,5.1 0,9.5 0.7,10.2 5.1,5.8 9.5,10.2 10.2,9.5 5.8,5.1 '
/>
</svg>
</button>
</div>
</div>
`);
const unMaximizeButton = (`
<svg x='0px' y='0px' viewBox='0 0 14 10.2'>
<path
fill='rgba(255, 255, 255, 0.9)'
d='M2.1,0v2H0v8.1h8.2v-2h2V0H2.1z M7.2,9.2H1.1V3h6.1V9.2z M9.2,7.1h-1V2H3.1V1h6.1V7.1z'
/>
</svg>
`);
const maximizeButton = (`
<svg x='0px' y='0px' viewBox='0 0 14 10.2'>
<path
fill='rgba(255, 255, 255, 0.9)'
d='M0,0v10.1h10.2V0H0z M9.2,9.2H1.1V1h8.1V9.2z'
/>
</svg>
`);
module.exports = {
titleBar: titleBar,
button: button,
unMaximizeButton: unMaximizeButton,
maximizeButton: maximizeButton
};

209
js/windowsTitleBar/index.js Normal file
View File

@@ -0,0 +1,209 @@
const { ipcRenderer, remote } = require('electron');
const apiEnums = require('../enums/api.js');
const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
const htmlContents = require('./contents');
// Default title bar height
const titleBarHeight = '32px';
class TitleBar {
constructor() {
this.window = remote.getCurrentWindow();
this.domParser = new DOMParser();
const titleBarParsed = this.domParser.parseFromString(htmlContents.titleBar, 'text/html');
this.titleBar = titleBarParsed.getElementById('title-bar');
}
initiateWindowsTitleBar() {
const actionItemsParsed = this.domParser.parseFromString(htmlContents.button, 'text/html');
const buttons = actionItemsParsed.getElementsByClassName('action-items');
let items = Array.from(buttons[0].children);
for (let i of items) {
this.titleBar.appendChild(i);
}
const updateIcon = TitleBar.updateIcons;
const updateTitleBar = TitleBar.updateTitleBar;
// Event to capture and update icons
this.window.on('maximize', updateIcon.bind(this, true));
this.window.on('unmaximize', updateIcon.bind(this, false));
this.window.on('enter-full-screen', updateTitleBar.bind(this, true));
this.window.on('leave-full-screen', updateTitleBar.bind(this, false));
window.addEventListener('beforeunload', () => {
this.window.removeListener('maximize', updateIcon);
this.window.removeListener('unmaximize', updateIcon);
this.window.removeListener('enter-full-screen', updateTitleBar);
this.window.removeListener('leave-full-screen', updateTitleBar);
});
document.body.appendChild(this.titleBar);
TitleBar.addWindowBorders();
this.initiateEventListeners();
}
/**
* Method that attaches Event Listeners for elements
*/
initiateEventListeners() {
const hamburgerMenuButton = document.getElementById('hamburger-menu-button');
const minimizeButton = document.getElementById('title-bar-minimize-button');
const maximizeButton = document.getElementById('title-bar-maximize-button');
const closeButton = document.getElementById('title-bar-close-button');
attachEventListeners(this.titleBar, 'dblclick', this.maximizeOrUnmaximize.bind(this));
attachEventListeners(hamburgerMenuButton, 'click', this.popupMenu.bind(this));
attachEventListeners(closeButton, 'click', this.closeWindow.bind(this));
attachEventListeners(maximizeButton, 'click', this.maximizeOrUnmaximize.bind(this));
attachEventListeners(minimizeButton, 'click', this.minimize.bind(this));
}
/**
* Method that adds borders
*/
static addWindowBorders() {
const borderBottom = document.createElement('div');
borderBottom.className = 'bottom-window-border';
document.body.appendChild(borderBottom);
document.body.classList.add('window-border');
}
/**
* Method that updates the state of the maximize or
* unmaximize icons
* @param isMaximized
*/
static updateIcons(isMaximized) {
const button = document.getElementById('title-bar-maximize-button');
if (!button) {
return
}
if (isMaximized) {
button.innerHTML = htmlContents.unMaximizeButton;
} else {
button.innerHTML = htmlContents.maximizeButton;
}
}
/**
* Method that updates the title bar display property
* based on the full screen event
* @param isFullScreen {Boolean}
*/
static updateTitleBar(isFullScreen) {
if (isFullScreen) {
this.titleBar.style.display = 'none';
updateContentHeight('0px');
} else {
this.titleBar.style.display = 'flex';
updateContentHeight();
}
}
/**
* Method that popup the application menu
*/
popupMenu() {
if (this.isValidWindow()) {
ipcRenderer.send(apiName, {
cmd: apiCmds.popupMenu
});
}
}
/**
* Method that minimizes browser window
*/
minimize() {
if (this.isValidWindow()) {
this.window.minimize();
}
}
/**
* Method that maximize or unmaximize browser window
*/
maximizeOrUnmaximize() {
if (!this.isValidWindow()) {
return;
}
if (this.window.isMaximized()) {
this.window.unmaximize();
} else {
this.window.maximize();
}
}
/**
* Method that closes the browser window
*/
closeWindow() {
if (this.isValidWindow()) {
this.window.close();
}
}
/**
* Verifies if the window exists and is not destroyed
* @returns {boolean}
*/
isValidWindow() {
return !!(this.window && !this.window.isDestroyed());
}
}
/**
* Will attach event listeners for a given element
* @param element
* @param eventName
* @param func
*/
function attachEventListeners(element, eventName, func) {
if (!element || !eventName) {
return;
}
eventName.split(" ").forEach((name) => {
element.addEventListener(name, func, false);
});
}
/**
* Method that adds margin property to the push
* the client content below the title bar
* @param height
*/
function updateContentHeight(height = titleBarHeight) {
const contentWrapper = document.getElementById('content-wrapper');
const titleBar = document.getElementById('title-bar');
if (!titleBar) {
return;
}
if (contentWrapper) {
contentWrapper.style.marginTop = titleBar ? height : '0px';
document.body.style.removeProperty('margin-top');
} else {
document.body.style.marginTop = titleBar ? height : '0px'
}
}
module.exports = {
TitleBar,
updateContentHeight
};

View File

@@ -0,0 +1,98 @@
#title-bar {
display: flex;
position: fixed;
background: rgba(74,74,74,1);
top: 0;
left: 0;
width: 100%;
height: 32px;
padding-left: 0;
justify-content: center;
align-items: center;
-webkit-app-region: drag;
-webkit-user-select: none;
box-sizing: content-box;
z-index: 1000;
}
#hamburger-menu-button {
color: rgba(255,255,255,1);
text-align: center;
width: 40px;
height: 32px;
background: none;
border: none;
border-image: initial;
display: inline-grid;
border-radius: 0;
padding: 11px;
box-sizing: border-box;
cursor: default;
}
#hamburger-menu-button:focus {
outline: none;
}
#title-container {
height: 32px;
flex: 1;
justify-content: center;
align-items: center;
white-space: nowrap;
overflow: hidden;
}
.title-bar-button-container {
justify-content: center;
align-items: center;
right: 0;
color: rgba(255,255,255,1);
-webkit-app-region: no-drag;
text-align: center;
width: 45px;
height: 32px;
background: rgba(74,74,74,1);
margin: 0;
box-sizing: border-box !important;
cursor: default;
}
.title-bar-button {
color: rgba(255,255,255,1);
text-align: center;
width: 45px;
height: 32px;
background: none;
border: none;
border-image: initial;
display: inline-grid;
border-radius: 0;
padding: 10px 15px;
cursor: default;
}
.title-bar-button:hover {
background-color: rgba(51,51,51,1);
}
.title-bar-button:focus {
outline: none;
}
.title-bar-button-container:hover {
background-color: rgba(51,51,51,1);
}
.window-border {
border-left: 1px solid rgba(74,74,74,1);
border-right: 1px solid rgba(74,74,74,1);
}
.bottom-window-border {
position: fixed;
border-bottom: 1px solid rgba(74,74,74,1);
width: 100%;
z-index: 3000;
bottom: 0;
}