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) (#627)
* 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:
parent
efe15967b5
commit
1b10648d6b
@ -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);
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user