Typescript - Fix some issues with custom title bar, loading screen & screen snippet

This commit is contained in:
Kiran Niranjan 2019-01-08 15:51:52 +05:30
parent 02c4dd4319
commit a923da3667
13 changed files with 260 additions and 203 deletions

View File

@ -12,7 +12,7 @@ Typically you'll want to set background-image: url(""); and background-size: cov
```
*/
#title-bar {
.title-bar {
display: flex;
position: fixed;
background: rgba(74,74,74,1);
@ -62,7 +62,7 @@ Typically you'll want to set content: url("") and adjust the width property
height: 32px;
}
#hamburger-menu-button {
.hamburger-menu-button {
color: rgba(255,255,255,1);
text-align: center;
width: 40px;
@ -77,11 +77,11 @@ Typically you'll want to set content: url("") and adjust the width property
cursor: default;
}
#hamburger-menu-button:focus {
.hamburger-menu-button:focus {
outline: none;
}
#title-container {
.title-container {
height: 32px;
flex: 1;
display: flex;
@ -91,7 +91,7 @@ Typically you'll want to set content: url("") and adjust the width property
overflow: hidden;
}
#title-bar-title {
.title-bar-title {
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
color: white;
margin: 0;

View File

@ -28,7 +28,8 @@ gulp.task('less', function () {
gulp.task('copy', function () {
return gulp.src([
'./src/renderer/assets/*',
'./src/renderer/*.html'
'./src/renderer/*.html',
'./src/locale/*'
], {
"base": "./src"
}).pipe(gulp.dest('lib/src'))

View File

@ -1,6 +1,6 @@
{
"name": "Symphony",
"productName": "Symphony-dev",
"productName": "Symphony",
"version": "4.5.0",
"clientVersion": "1.55.0",
"buildNumber": "0",
@ -12,7 +12,7 @@
"compile": "npm run lint && gulp build",
"lint": "tslint --project tsconfig.json",
"start": "npm run compile && npm run browserify-preload && cross-env ELECTRON_DEV=true electron .",
"prebuild": "npm run rebuild && npm run browserify-preload",
"prebuild": "npm run compile && npm run rebuild && npm run browserify-preload",
"browserify-preload": "browserify -o lib/src/renderer/_preload-main.js -x electron --insert-global-vars=__filename,__dirname lib/src/renderer/preload-main.js",
"rebuild": "electron-rebuild -f",
"dev": "npm run prebuild && cross-env ELECTRON_DEV=true electron .",

View File

@ -1,14 +1,12 @@
import { BrowserWindow, WebContents } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import { parse as parseQuerystring } from 'querystring';
import { format, parse, Url } from 'url';
import { isWindowsOS } from '../common/env';
import { getGuid } from '../common/utils';
import { enterFullScreen, leaveFullScreen, throttledWindowChanges } from './window-actions';
import { monitorWindowActions, removeWindowEventListener } from './window-actions';
import { ICustomBrowserWindow, windowHandler } from './window-handler';
import { getBounds, preventWindowNavigation } from './window-utils';
import { getBounds, injectStyles, preventWindowNavigation } from './window-utils';
const DEFAULT_POP_OUT_WIDTH = 300;
const DEFAULT_POP_OUT_HEIGHT = 600;
@ -95,7 +93,7 @@ export const handleChildWindow = (webContents: WebContents): void => {
newWinOptions.frame = true;
newWinOptions.winKey = newWinKey;
const childWebContents = newWinOptions.webContents;
const childWebContents: WebContents = newWinOptions.webContents;
// Event needed to hide native menu bar
childWebContents.once('did-start-loading', () => {
const browserWin = BrowserWindow.fromWebContents(childWebContents);
@ -104,38 +102,25 @@ export const handleChildWindow = (webContents: WebContents): void => {
}
});
childWebContents.once('did-finish-load', () => {
const browserWin = BrowserWindow.fromWebContents(childWebContents) as ICustomBrowserWindow;
childWebContents.once('did-finish-load', async () => {
const browserWin: ICustomBrowserWindow = BrowserWindow.fromWebContents(childWebContents) as ICustomBrowserWindow;
if (!browserWin) return;
windowHandler.addWindow(newWinKey, browserWin);
browserWin.webContents.send('page-load', { isWindowsOS });
browserWin.webContents.insertCSS(
fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'), 'utf8').toString(),
);
// Inserts css on to the window
await injectStyles(browserWin, false);
browserWin.winName = frameName;
browserWin.setAlwaysOnTop(mainWindow.isAlwaysOnTop());
// prevents window from navigating
preventWindowNavigation(browserWin, true);
// Monitor window for events
const eventNames = [ 'move', 'resize', 'maximize', 'unmaximize' ];
eventNames.forEach((e: string) => {
// @ts-ignore
if (this.mainWindow) this.mainWindow.on(e, throttledWindowChanges);
});
browserWin.on('enter-full-screen', enterFullScreen);
browserWin.on('leave-full-screen', leaveFullScreen);
// Remove the attached event listeners when the window is about to close
browserWin.once('close', () => {
browserWin.removeListener('close', throttledWindowChanges);
browserWin.removeListener('resize', throttledWindowChanges);
browserWin.removeListener('maximize', throttledWindowChanges);
browserWin.removeListener('unmaximize', throttledWindowChanges);
browserWin.removeListener('enter-full-screen', leaveFullScreen);
browserWin.removeListener('leave-full-screen', leaveFullScreen);
// Monitor window actions
monitorWindowActions(browserWin);
// Remove all attached event listeners
browserWin.on('close', () => {
removeWindowEventListener(browserWin);
});
// TODO: handle Permission Requests & setCertificateVerifyProc
});
} else {

View File

@ -1,5 +1,5 @@
import { app } from 'electron';
import * as fs from 'fs-extra';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
@ -104,6 +104,9 @@ class ScreenSnippet {
*/
private async convertFileToData(): Promise<IScreenSnippet> {
try {
if (!this.outputFileName) {
return { message: 'output file name is required', type: 'ERROR' };
}
const data = await readFile(this.outputFileName);
if (!data) {
return { message: `no file data provided`, type: 'ERROR' };
@ -122,13 +125,15 @@ class ScreenSnippet {
}
} finally {
// remove tmp file (async)
fs.unlink(this.outputFileName, (removeErr) => {
// note: node complains if calling async
// func without callback.
if (removeErr) {
logger.error(`ScreenSnippet: error removing temp snippet file: ${this.outputFileName}, err: ${removeErr}`);
}
});
if (this.outputFileName) {
fs.unlink(this.outputFileName, (removeErr) => {
// note: node complains if calling async
// func without callback.
if (removeErr) {
logger.error(`ScreenSnippet: error removing temp snippet file: ${this.outputFileName}, err: ${removeErr}`);
}
});
}
}
}
}

View File

@ -123,3 +123,35 @@ export const handleKeyPress = (key: number): void => {
break;
}
};
/**
* Monitors window actions
*
* @param window {BrowserWindow}
*/
export const monitorWindowActions = (window: BrowserWindow): void => {
if (!window || window.isDestroyed()) return;
const eventNames = [ 'move', 'resize', 'maximize', 'unmaximize' ];
eventNames.forEach((event: string) => {
// @ts-ignore
if (window) window.on(event, throttledWindowChanges);
});
window.on('enter-full-screen', enterFullScreen);
window.on('leave-full-screen', leaveFullScreen);
};
/**
* Removes attached event listeners
*
* @param window
*/
export const removeWindowEventListener = (window: BrowserWindow): void => {
if (!window || window.isDestroyed()) return;
const eventNames = [ 'move', 'resize', 'maximize', 'unmaximize' ];
eventNames.forEach((event: string) => {
// @ts-ignore
if (window) window.removeListener(event, throttledWindowChanges);
});
window.removeListener('enter-full-screen', enterFullScreen);
window.removeListener('leave-full-screen', leaveFullScreen);
};

View File

@ -1,6 +1,5 @@
import * as electron from 'electron';
import { BrowserWindow, crashReporter, ipcMain } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import { format, parse } from 'url';
@ -14,8 +13,8 @@ import { AppMenu } from './app-menu';
import { config, IConfig } from './config-handler';
import { showNetworkConnectivityError } from './dialog-handler';
import { handleChildWindow } from './pop-out-window-handler';
import { enterFullScreen, leaveFullScreen, throttledWindowChanges } from './window-actions';
import { createComponentWindow, getBounds, handleDownloadManager } from './window-utils';
import { monitorWindowActions } from './window-actions';
import { createComponentWindow, getBounds, handleDownloadManager, injectStyles } from './window-utils';
interface ICustomBrowserWindowConstructorOpts extends Electron.BrowserWindowConstructorOptions {
winKey: string;
@ -200,7 +199,7 @@ export class WindowHandler {
// loads the main window with url from config/cmd line
this.mainWindow.loadURL(this.url);
this.mainWindow.webContents.on('did-finish-load', () => {
this.mainWindow.webContents.on('did-finish-load', async () => {
// Displays a dialog if network connectivity has been lost
const retry = () => {
@ -210,33 +209,34 @@ export class WindowHandler {
};
if (!this.isOnline && this.mainWindow) showNetworkConnectivityError(this.mainWindow, this.url, retry);
// early exit if the window has already been destroyed
if (!this.mainWindow || this.mainWindow.isDestroyed()) return;
this.url = this.mainWindow.webContents.getURL();
// Injects custom title bar css into the webContents
await injectStyles(this.mainWindow, this.isCustomTitleBarAndWindowOS);
this.mainWindow.webContents.send('initiate-custom-title-bar');
this.mainWindow.webContents.send('page-load', {
isWindowsOS,
locale: i18n.getLocale(),
resources: i18n.loadedResources,
});
this.appMenu = new AppMenu();
// close the loading window when
// the main windows finished loading
if (this.loadingWindow) {
this.loadingWindow.destroy();
this.loadingWindow = null;
}
// early exit if the window has already been destroyed
if (!this.mainWindow || this.mainWindow.isDestroyed()) return;
this.url = this.mainWindow.webContents.getURL();
// Injects custom title bar css into the webContents
if (this.mainWindow && this.isCustomTitleBarAndWindowOS) {
this.mainWindow.webContents.insertCSS(
fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/title-bar.css'), 'utf8').toString(),
);
this.mainWindow.webContents.send('initiate-custom-title-bar');
}
this.mainWindow.webContents.insertCSS(
fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'), 'utf8').toString(),
);
this.mainWindow.webContents.send('page-load', { isWindowsOS, locale: i18n.getLocale(), resources: i18n.loadedResources });
this.appMenu = new AppMenu();
this.monitorWindowActions();
// Ready to show the window
this.mainWindow.show();
});
// Start monitoring window actions
monitorWindowActions(this.mainWindow);
// Download manager
this.mainWindow.webContents.session.on('will-download', handleDownloadManager);
@ -372,7 +372,7 @@ export class WindowHandler {
* @param id {number}
*/
public createScreenSharingIndicatorWindow(screenSharingWebContents: Electron.webContents, displayId: string, id: number): void {
const indicatorScreen = (displayId && electron.screen.getAllDisplays().filter((d) => displayId.includes(d.id.toString()))[0]) || electron.screen.getPrimaryDisplay();
const indicatorScreen = (displayId && electron.screen.getAllDisplays().filter((d) => displayId.includes(d.id.toString()))[ 0 ]) || electron.screen.getPrimaryDisplay();
const screenRect = indicatorScreen.workArea;
let opts = WindowHandler.getScreenSharingIndicatorOpts();
if (opts.width && opts.height) {
@ -485,21 +485,6 @@ export class WindowHandler {
delete this.windows[ key ];
}
/**
* Saves the main window bounds
*/
private monitorWindowActions(): void {
const eventNames = [ 'move', 'resize', 'maximize', 'unmaximize' ];
eventNames.forEach((event: string) => {
// @ts-ignore
if (this.mainWindow) this.mainWindow.on(event, throttledWindowChanges);
});
if (this.mainWindow) {
this.mainWindow.on('enter-full-screen', enterFullScreen);
this.mainWindow.on('leave-full-screen', leaveFullScreen);
}
}
/**
* Main window opts
*/

View File

@ -1,15 +1,16 @@
import * as electron from 'electron';
import { app, BrowserWindow, nativeImage } from 'electron';
import * as filesize from 'filesize';
import * as fs from 'fs';
import * as path from 'path';
import * as url from 'url';
import { isMac } from '../common/env';
import { isDevEnv, isMac } from '../common/env';
import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger';
import { getGuid } from '../common/utils';
import { screenSnippet } from './screen-snippet-handler';
import { windowHandler } from './window-handler';
import { ICustomBrowserWindow, windowHandler } from './window-handler';
const checkValidWindow = true;
@ -65,9 +66,10 @@ export const createComponentWindow = (
},
};
const browserWindow = new BrowserWindow(options);
const browserWindow: ICustomBrowserWindow = new BrowserWindow(options) as ICustomBrowserWindow;
browserWindow.on('ready-to-show', () => browserWindow.show());
browserWindow.webContents.once('did-finish-load', () => {
if (!browserWindow || browserWindow.isDestroyed()) return;
browserWindow.webContents.send('set-locale-resource', { locale: i18n.getLocale(), resource: i18n.loadedResources });
});
browserWindow.setMenu(null as any);
@ -282,3 +284,46 @@ export const handleDownloadManager = (_event, item: Electron.DownloadItem, webCo
}
});
};
/**
* Inserts css in to the window
*
* @param window {BrowserWindow}
* @param paths {string[]}
*/
const readAndInsertCSS = async (window, paths): Promise<void> => {
return paths.map((filePath) => window.webContents.insertCSS(fs.readFileSync(filePath, 'utf8').toString()));
};
/**
* Inserts all the required css on to the specified windows
*
* @param mainWindow {BrowserWindow}
* @param isCustomTitleBarAndWindowOS {boolean} - whether custom title bar enabled
*/
export const injectStyles = async (mainWindow: BrowserWindow, isCustomTitleBarAndWindowOS: boolean): Promise<void> => {
const paths: string[] = [];
if (isCustomTitleBarAndWindowOS) {
let titleBarStylesPath;
const stylesFileName = path.join('config', 'titleBarStyles.css');
if (isDevEnv) {
titleBarStylesPath = path.join(app.getAppPath(), stylesFileName);
} else {
const execPath = path.dirname(app.getPath('exe'));
titleBarStylesPath = path.join(execPath, stylesFileName);
}
// Window custom title bar styles
if (fs.existsSync(titleBarStylesPath)) {
paths.push(titleBarStylesPath);
} else {
paths.push(path.join(__dirname, '..', '/renderer/styles/title-bar.css'));
}
} else {
paths.push(path.join(__dirname, '..', '/renderer/styles/title-bar.css'));
}
// Snack bar styles
paths.push(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'));
return await readAndInsertCSS(mainWindow, paths);
};

View File

@ -13,7 +13,7 @@ export default class LoadingScreen extends React.PureComponent {
const appName = remote.app.getName() || 'Symphony';
return (
<div className='LoadingScreen'>
<img className='LoadingScreen-logo' src='../assets/symphony-logo.png' />
<img className='LoadingScreen-logo' src='../renderer/assets/symphony-logo.png' />
<span className='LoadingScreen-name'>{appName}</span>
<svg width='100%' height='100%' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 200' preserveAspectRatio='xMidYMid'>
<circle cx='50' cy='50' fill='none' ng-attr-stroke='{{config.color}}' ng-attr-stroke-width='{{config.width}}' ng-attr-r='{{config.radius}}' ng-attr-stroke-dasharray='{{config.dasharray}}' stroke='#ffffff' stroke-width='10' r='35' stroke-dasharray='164.93361431346415 56.97787143782138' transform='rotate(59.6808 50 50)'>

View File

@ -119,6 +119,7 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
</svg>
</button>
</div>
<div className='branding-logo' />
</div>
);
}

View File

@ -39,19 +39,15 @@ const createAPI = () => {
createAPI();
// When the window is completely loaded
ipcRenderer.on('page-load', (_event, { isWindowsOS, locale, resources }) => {
ipcRenderer.on('page-load', (_event, { locale, resources }) => {
i18n.setResource(locale, resources);
if (isWindowsOS) {
// injects custom window title bar
const titleBar = React.createElement(WindowsTitleBar);
ReactDOM.render(titleBar, document.body.appendChild(document.createElement( 'div' )));
}
// injects snack bar
const snackBar = React.createElement(SnackBar);
ReactDOM.render(snackBar, document.body.appendChild(document.createElement( 'div' )));
const snackBarContainer = document.createElement( 'div' );
document.body.appendChild(snackBarContainer);
ReactDOM.render(snackBar, snackBarContainer);
// injects download manager contents
const downloadManager = React.createElement(DownloadManager);
@ -64,5 +60,7 @@ ipcRenderer.on('page-load', (_event, { isWindowsOS, locale, resources }) => {
// Creates a custom tile bar for Windows
ipcRenderer.on('initiate-custom-title-bar', () => {
const element = React.createElement(WindowsTitleBar);
ReactDOM.render(element, document.body);
const div = document.createElement( 'div' );
document.body.appendChild(div);
ReactDOM.render(element, div);
});

View File

@ -1,111 +0,0 @@
.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;
display: flex;
justify-content: flex-start;
align-items: center;
white-space: nowrap;
overflow: hidden;
}
.title-bar-title {
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
color: white;
margin: 0;
padding-left: 10px;
font-size: 13px;
}
.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;
}
.symphony-logo {
content: url("../src/renderer/assets/symphony-title-bar-logo.png");
}

View File

@ -0,0 +1,116 @@
@color_1: rgba(255, 255, 255, 1);
@color_2: white;
@font_family_1: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
@background_color_1: rgba(51, 51, 51, 1);
.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: @color_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;
&:focus {
outline: none;
}
}
.title-container {
height: 32px;
flex: 1;
display: flex;
justify-content: flex-start;
align-items: center;
white-space: nowrap;
overflow: hidden;
}
.title-bar-title {
font-family: @font_family_1;
color: @color_2;
margin: 0;
padding-left: 10px;
font-size: 13px;
}
.title-bar-button-container {
justify-content: center;
align-items: center;
right: 0;
color: @color_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;
&:hover {
background-color: @background_color_1;
}
}
.title-bar-button {
color: @color_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;
&:hover {
background-color: @background_color_1;
}
&:focus {
outline: none;
}
}
.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;
}
.symphony-logo {
background-image: url("../src/renderer/assets/symphony-title-bar-logo.png");
}