mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
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:
committed by
Vishwas Shashidhar
parent
af510a5ce1
commit
9991690a7e
@@ -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": ""
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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));
|
||||||
electron.Menu.setApplicationMenu(menu);
|
if (isWindows10()) {
|
||||||
|
mainWindow.setMenu(menu);
|
||||||
|
} else {
|
||||||
|
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
|
||||||
};
|
};
|
||||||
|
|||||||
121
js/windowsTitleBar/contents.js
Normal file
121
js/windowsTitleBar/contents.js
Normal 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
209
js/windowsTitleBar/index.js
Normal 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
|
||||||
|
};
|
||||||
98
js/windowsTitleBar/style.css
Normal file
98
js/windowsTitleBar/style.css
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user