1
0
mirror of https://github.com/finos/SymphonyElectron.git synced 2025-02-25 18:55:29 -06:00

Typescript (Added logic to clean up child windows) ()

* Typescript - Fix string formatter function and update snack bar style

* Typescript - Update string formatter unit test

* Typescript - Fix screen snippet

* Typescript - Add logic to clean up child windows on before unload

* Typescript - Add logic to check for screen sharing media permissions

* Typescript - Fix display selecting option in notification settings
This commit is contained in:
Kiran Niranjan 2019-04-05 10:50:54 +05:30 committed by Vishwas Shashidhar
parent efe15967b5
commit 1b10648d6b
14 changed files with 147 additions and 57 deletions

View File

@ -223,35 +223,49 @@
window.postMessage({ method: 'notification', data: notf }, '*');
});
var badgeCount = 0;
/**
* Badge Count
*/
let badgeCount = 0;
var incBadgeEl = document.getElementById('inc-badge');
incBadgeEl.addEventListener('click', function() {
const incBadgeEl = document.getElementById('inc-badge');
incBadgeEl.addEventListener('click', () => {
badgeCount++;
ssf.setBadgeCount(badgeCount);
if (window.ssf) {
ssf.setBadgeCount(badgeCount);
} else {
postMessage(apiCmds.setBadgeCount, badgeCount);
}
});
var incBadgeEl = document.getElementById('clear-badge');
incBadgeEl.addEventListener('click', function() {
const clearBadgeCount = document.getElementById('clear-badge');
clearBadgeCount.addEventListener('click', () => {
badgeCount = 0;
ssf.setBadgeCount(0);
if (window.ssf) {
ssf.setBadgeCount(0);
} else {
postMessage(apiCmds.setBadgeCount, 0);
}
});
var snippetButton = document.getElementById('snippet');
snippetButton.addEventListener('click', function() {
window.ssf.openScreenSnippet(gotSnippet.bind(this));
function gotSnippet(rsp) {
if (rsp) {
if (rsp.type && rsp.type === 'ERROR') {
// called when some error occurs during capture
alert('error getting snippet' + rsp.message);
} else if (rsp.data && rsp.type === 'image/jpg;base64') {
var dataUrl = 'data:' + rsp.type + ',' + rsp.data;
var img = document.getElementById('snippet-img');
img.src = dataUrl;
const snippetButton = document.getElementById('snippet');
snippetButton.addEventListener('click', () => {
if (window.ssf) {
const gotSnippet = (rsp) => {
if (rsp) {
if (rsp.type && rsp.type === 'ERROR') {
// called when some error occurs during capture
alert('error getting snippet' + rsp.message);
} else if (rsp.data && rsp.type === 'image/png;base64') {
const dataUrl = 'data:' + rsp.type + ',' + rsp.data;
const img = document.getElementById('snippet-img');
img.src = dataUrl;
}
}
}
};
window.ssf.openScreenSnippet(gotSnippet);
} else {
postMessage(apiCmds.openScreenSnippet)
}
});
@ -353,6 +367,18 @@
console.log(`screen sharing will be stopped`);
}
break;
case 'screen-snippet-callback':
if (data) {
if (data.type && data.type === 'ERROR') {
// called when some error occurs during capture
alert('error getting snippet' + data.message);
} else if (data.data && data.type === 'image/png;base64') {
const dataUrl = 'data:' + data.type + ',' + data.data;
const img = document.getElementById('snippet-img');
img.src = dataUrl;
}
}
break;
default:
console.log(event.data);
}

View File

@ -82,7 +82,8 @@ describe('utils', () => {
describe('`formatString', () => {
const str = 'this will log {time}';
const data = [{time: '1234'}];
const strReplaced = 'this will log time';
const data = { time: '1234' };
it('should return `str` when data is empty', () => {
expect(formatString(str)).toEqual(str);
});
@ -92,9 +93,19 @@ describe('utils', () => {
expect(formatString(str, data)).toBe(expectedValue);
});
it('should replace multiple keys to dynamics values when `formatString` is used', () => {
const dataTest = { multiple: true, placed: 'correct' };
expect(formatString('The string with {multiple} values {placed}', dataTest)).toBe('The string with true values correct');
});
it('should return `str` when `data` not match', () => {
const dataTest = [{test: 123}];
expect(formatString(str, dataTest)).toBe(str);
const dataTest = { test: 123 };
expect(formatString(str, dataTest)).toBe(strReplaced);
});
it('should return `str` when `data` is undefined', () => {
const dataTest = { multiple: 'multiple', invalid: undefined };
expect(formatString('The string with {multiple} values {invalid}', dataTest)).toBe('The string with multiple values invalid');
});
});

View File

@ -18,11 +18,13 @@ const allowMultiInstance: string | boolean = getCommandLineArgs(process.argv, '-
// on windows, we create the protocol handler via the installer
// because electron leaves registry traces upon uninstallation
if (isMac) {
app.setAsDefaultProtocolClient('symphony');
// Sets application version info that will be displayed in about app panel
app.setAboutPanelOptions({ applicationVersion: `${clientVersion}-${version}`, version: buildNumber });
}
// Electron sets the default protocol
app.setAsDefaultProtocolClient('symphony');
/**
* Main function that init the application
*/

View File

@ -117,7 +117,7 @@ class ScreenSnippet {
}
// convert binary data to base64 encoded string
const output = Buffer.from(data).toString('base64');
return { message: 'success', data: output, type: 'image/jpg;base64' };
return { message: 'success', data: output, type: 'image/png;base64' };
} catch (error) {
if (error && error.code === 'ENOENT') {
// no such file exists, user likely aborted

View File

@ -262,6 +262,8 @@ export class WindowHandler {
enableCustomTitleBar: this.isCustomTitleBar,
});
this.appMenu = new AppMenu();
const { permissions } = config.getGlobalConfigFields([ 'permissions' ]);
this.mainWindow.webContents.send('is-screen-share-enabled', permissions.media);
});
this.mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDesc, validatedURL) => {
@ -397,6 +399,25 @@ export class WindowHandler {
}
}
/**
* Finds all the child window and closes it
*/
public async closeAllWindow(): Promise<void> {
const browserWindows = BrowserWindow.getAllWindows();
await notification.cleanUp();
if (browserWindows && browserWindows.length) {
browserWindows.forEach((win) => {
const browserWindow = win as ICustomBrowserWindow;
if (browserWindow && windowExists(browserWindow)) {
// Closes only child windows
if (browserWindow.winName !== apiName.mainWindowName && browserWindow.winName !== apiName.notificationWindowName) {
browserWindow.close();
}
}
});
}
}
/**
* Sets is auto reload when the application
* is auto reloaded for optimizing memory
@ -476,7 +497,7 @@ export class WindowHandler {
this.aboutAppWindow = createComponentWindow(
'about-app',
selectedParentWindow ? { parent: selectedParentWindow } : {},
);
);
this.aboutAppWindow.setVisibleOnAllWorkspaces(true);
this.aboutAppWindow.webContents.once('did-finish-load', () => {
if (!this.aboutAppWindow || !windowExists(this.aboutAppWindow)) {

View File

@ -240,7 +240,7 @@ export const showPopupMenu = (opts: Electron.PopupOptions): void => {
*
* @param windowName {string}
*/
export const sanitize = (windowName: string): void => {
export const sanitize = async (windowName: string): Promise<void> => {
// To make sure the reload event is from the main window
const mainWindow = windowHandler.getMainWindow();
if (mainWindow && windowName === mainWindow.winName) {
@ -251,9 +251,8 @@ export const sanitize = (windowName: string): void => {
if (!isMac) {
screenSnippet.killChildProcess();
}
// TODO: write method to clean up child window
// Closes all the child windows
// windowMgr.cleanUpChildWindows();
await windowHandler.closeAllWindow();
}
};

View File

@ -33,6 +33,7 @@ export enum apiCmds {
export enum apiName {
symphonyApi = 'symphony-api',
mainWindowName = 'main',
notificationWindowName = 'notification-window',
}
export interface IApiArgs {
@ -67,7 +68,7 @@ export interface IBadgeCount {
/**
* Screen snippet
*/
export type ScreenSnippetDataType = 'ERROR' | 'image/jpg;base64';
export type ScreenSnippetDataType = 'ERROR' | 'image/png;base64';
export interface IScreenSnippet {
data?: string;
message?: string;

View File

@ -4,7 +4,7 @@ const localeCodeRegex = /^([a-z]{2})-([A-Z]{2})$/;
export type LocaleType = 'en-US' | 'ja-JP';
type formaterFunction = (...args: any[]) => string;
type formatterFunction = (args?: object) => string;
class Translation {
@ -51,8 +51,8 @@ class Translation {
* @example t('translate and formats {data} ', namespace)({ data: 'string' })
* @returns translate and formats string
*/
public t(value: string, namespace?: string): formaterFunction {
return (...args: any[]): string => {
public t(value: string, namespace?: string): formatterFunction {
return (args?: object): string => {
if (this.loadedResources && this.loadedResources[this.locale]) {
return formatString(Translation.translate(value, this.loadedResources[this.locale], namespace), args);
}

View File

@ -7,7 +7,7 @@ const localeCodeRegex = /^([a-z]{2})-([A-Z]{2})$/;
export type LocaleType = 'en-US' | 'ja-JP';
type formaterFunction = (...args: any[]) => string;
type formatterFunction = (args?: object) => string;
class Translation {
/**
@ -53,8 +53,8 @@ class Translation {
* @example t('translate and formats {data} ', namespace)({ data: 'string' })
* @returns translate and formats string
*/
public t(value: string, namespace?: string): formaterFunction {
return (...args: any[]): string => {
public t(value: string, namespace?: string): formatterFunction {
return (args?: object): string => {
if (this.loadedResources && this.loadedResources[this.locale]) {
return formatString(Translation.translate(value, this.loadedResources[this.locale], namespace), args);
}

View File

@ -182,7 +182,7 @@ export const throttle = (func: (...args) => void, wait: number): (...args) => vo
* @param data {Object} - Data to be added
*
* @example
* StringFormat('this will log {time}', [{ time: '1234' }])
* StringFormat('this will log {time}', { time: '1234' })
*
* result:
* this will log 1234
@ -199,10 +199,7 @@ export const formatString = (str: string, data?: object): string => {
if (Object.prototype.hasOwnProperty.call(data, key)) {
return str.replace(/({([^}]+)})/g, (i) => {
const replacedKey = i.replace(/{/, '').replace(/}/, '');
if (!data[key] || !data[key][replacedKey]) {
return i;
}
return data[key][replacedKey];
return data[replacedKey] ? data[replacedKey] : replacedKey;
});
}
}

View File

@ -54,7 +54,13 @@ export default class NotificationSettings extends React.Component<{}, IState> {
<label>
{i18n.t('Notification shown on Monitor: ', NOTIFICATION_SETTINGS_NAMESPACE)()}
</label>
<select className='selector' id='screen-selector' title='position' onChange={this.eventHandlers.onDisplaySelect}>
<select
className='selector'
id='screen-selector'
title='position'
value={this.state.display}
onChange={this.eventHandlers.onDisplaySelect}
>
{this.renderScreens()}
</select>
</div>
@ -121,10 +127,10 @@ export default class NotificationSettings extends React.Component<{}, IState> {
* Renders the drop down list of available screen
*/
private renderScreens(): JSX.Element[] {
const { screens, display } = this.state;
const { screens } = this.state;
return screens.map((screen, index) => {
return (
<option id={String(screen.id)} key={screen.id} value={display}>{index + 1}</option>
<option id={String(screen.id)} key={screen.id} value={screen.id}>{index + 1}</option>
);
});
}

View File

@ -13,8 +13,7 @@ import { i18n } from '../common/i18n-preload';
const includes = [].includes;
let nextId = 0;
// TODO: add logic to check for permissions
let isScreenShareEnabled = true;
let isScreenShareEnabled = false;
let screenShareArgv: string;
export interface ICustomSourcesOptions extends SourcesOptions {
@ -155,8 +154,8 @@ ipcRenderer.once('screen-share-argv', (_event, arg) => {
});
// event that updates screen share permission
ipcRenderer.on('is-screen-share-enabled', (_event, screenShare) => {
if (typeof screenShare === 'boolean' && screenShare) {
isScreenShareEnabled = true;
ipcRenderer.on('is-screen-share-enabled', (_event, canShareScreen) => {
if (typeof canShareScreen === 'boolean' && canShareScreen) {
isScreenShareEnabled = canShareScreen;
}
});

View File

@ -1,9 +1,9 @@
import { ipcMain } from 'electron';
import { app, ipcMain } from 'electron';
import { config } from '../app/config-handler';
import { createComponentWindow, windowExists } from '../app/window-utils';
import { AnimationQueue } from '../common/animation-queue';
import { INotificationData } from '../common/api-interface';
import { apiName, INotificationData } from '../common/api-interface';
import { logger } from '../common/logger';
import NotificationHandler from './notification-handler';
@ -12,6 +12,7 @@ const CLEAN_UP_INTERVAL = 60 * 1000; // Closes inactive notification
const animationQueue = new AnimationQueue();
interface ICustomBrowserWindow extends Electron.BrowserWindow {
winName: string;
notificationData: INotificationData;
displayTimer: NodeJS.Timer;
clientId: number;
@ -49,11 +50,12 @@ class Notification extends NotificationHandler {
onCleanUpInactiveNotification: () => this.cleanUpInactiveNotification(),
onCreateNotificationWindow: (data: INotificationData) => this.createNotificationWindow(data),
};
private readonly activeNotifications: Electron.BrowserWindow[] = [];
private readonly inactiveWindows: Electron.BrowserWindow[] = [];
private readonly notificationQueue: INotificationData[] = [];
private readonly notificationCallbacks: any[] = [];
private activeNotifications: Electron.BrowserWindow[] = [];
private inactiveWindows: Electron.BrowserWindow[] = [];
private cleanUpTimer: NodeJS.Timer;
private notificationQueue: INotificationData[] = [];
private readonly notificationCallbacks: any[] = [];
constructor(opts) {
super(opts);
@ -65,7 +67,7 @@ class Notification extends NotificationHandler {
this.notificationClicked(windowId);
});
// Update latest notification settings from config
this.updateNotificationSettings();
app.on('ready', () => this.updateNotificationSettings());
this.cleanUpTimer = setInterval(this.funcHandlers.onCleanUpInactiveNotification, CLEAN_UP_INTERVAL);
}
@ -134,6 +136,7 @@ class Notification extends NotificationHandler {
) as ICustomBrowserWindow;
notificationWindow.notificationData = data;
notificationWindow.winName = apiName.notificationWindowName;
notificationWindow.once('closed', () => {
const activeWindowIndex = this.activeNotifications.indexOf(notificationWindow);
const inactiveWindowIndex = this.inactiveWindows.indexOf(notificationWindow);
@ -250,6 +253,29 @@ class Notification extends NotificationHandler {
this.moveNotificationDown(0, this.activeNotifications);
}
/**
* Closes all the notification windows and resets some configurations
*/
public async cleanUp(): Promise<void> {
animationQueue.clear();
this.notificationQueue = [];
const activeNotificationWindows = Object.assign([], this.activeNotifications);
const inactiveNotificationWindows = Object.assign([], this.inactiveWindows);
for (const activeWindow of activeNotificationWindows) {
if (activeWindow && windowExists(activeWindow)) {
await this.hideNotification((activeWindow as ICustomBrowserWindow).clientId);
}
}
for (const inactiveWindow of inactiveNotificationWindows) {
if (inactiveWindow && windowExists(inactiveWindow)) {
inactiveWindow.close();
}
}
this.activeNotifications = [];
this.inactiveWindows = [];
}
/**
* Waits for window to load and resolves
*

View File

@ -1,3 +1,4 @@
@import "theme";
@white: rgba(255,255,255, 1);
@grey: rgba(51,51,51, 1);
@ -22,6 +23,7 @@
}
}
.snackbar {
font-family: @font-family;
visibility: hidden;
min-width: 250px;
margin-left: -135px;