mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
format all files with prettier
This commit is contained in:
@@ -19,8 +19,11 @@ interface IApp {
|
||||
on(eventName: any, cb: any): void;
|
||||
once(eventName: any, cb: any): void;
|
||||
setPath(value: string, path: string): void;
|
||||
setLoginItemSettings(settings: { openAtLogin: boolean, path: string }): void;
|
||||
getLoginItemSettings(options?: { path: string, args: string[] }): ILoginItemSettings;
|
||||
setLoginItemSettings(settings: { openAtLogin: boolean; path: string }): void;
|
||||
getLoginItemSettings(options?: {
|
||||
path: string;
|
||||
args: string[];
|
||||
}): ILoginItemSettings;
|
||||
setAppLogsPath(): void;
|
||||
}
|
||||
interface ILoginItemSettings {
|
||||
|
||||
@@ -28,10 +28,14 @@ describe('activity detection', () => {
|
||||
|
||||
it('should call `setWindowAndThreshold` correctly', () => {
|
||||
// mocking startActivityMonitor
|
||||
const spy: jest.SpyInstance = jest.spyOn(activityDetectionInstance, 'setWindowAndThreshold');
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
activityDetectionInstance,
|
||||
'setWindowAndThreshold',
|
||||
);
|
||||
const idleThresholdMock: number = 1000;
|
||||
|
||||
jest.spyOn(activityDetectionInstance, 'startActivityMonitor')
|
||||
jest
|
||||
.spyOn(activityDetectionInstance, 'startActivityMonitor')
|
||||
.mockImplementation(() => jest.fn());
|
||||
|
||||
activityDetectionInstance.setWindowAndThreshold({}, idleThresholdMock);
|
||||
@@ -41,7 +45,8 @@ describe('activity detection', () => {
|
||||
|
||||
it('should start activity monitor when `setWindowAndThreshold` is called', () => {
|
||||
const idleThresholdMock: number = 1000;
|
||||
const spy: jest.SpyInstance = jest.spyOn(activityDetectionInstance, 'startActivityMonitor')
|
||||
const spy: jest.SpyInstance = jest
|
||||
.spyOn(activityDetectionInstance, 'startActivityMonitor')
|
||||
.mockImplementation(() => jest.fn());
|
||||
|
||||
activityDetectionInstance.setWindowAndThreshold({}, idleThresholdMock);
|
||||
@@ -50,7 +55,10 @@ describe('activity detection', () => {
|
||||
});
|
||||
|
||||
it('should call `activity` when `startActivityMonitor` is called', () => {
|
||||
const spy: jest.SpyInstance = jest.spyOn(activityDetectionInstance, 'activity');
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
activityDetectionInstance,
|
||||
'activity',
|
||||
);
|
||||
|
||||
activityDetectionInstance.startActivityMonitor();
|
||||
|
||||
@@ -62,12 +70,14 @@ describe('activity detection', () => {
|
||||
it('should call `sendActivity` when period was greater than idleTime', () => {
|
||||
// period is this.idleThreshold = 60 * 60 * 1000;
|
||||
const mockIdleTime: number = 50;
|
||||
const spy: jest.SpyInstance = jest.spyOn(activityDetectionInstance, 'sendActivity');
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
activityDetectionInstance,
|
||||
'sendActivity',
|
||||
);
|
||||
const mockIdleTimeinMillis: number = mockIdleTime * 1000;
|
||||
|
||||
activityDetectionInstance.activity(mockIdleTime);
|
||||
|
||||
expect(spy).toBeCalledWith(mockIdleTimeinMillis);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -38,13 +38,15 @@ describe('<AnnotateArea/>', () => {
|
||||
const area = wrapper.find('[data-testid="annotate-area"]');
|
||||
area.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
area.simulate('mouseup');
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith([{
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith([
|
||||
{
|
||||
color: 'rgba(38, 196, 58, 1)',
|
||||
key: 'path0',
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: true,
|
||||
strokeWidth: 5,
|
||||
}]);
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call onChange with correct highlight props if drawn drawn on annotate area with highlight', () => {
|
||||
@@ -62,24 +64,28 @@ describe('<AnnotateArea/>', () => {
|
||||
const area = wrapper.find('[data-testid="annotate-area"]');
|
||||
area.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
area.simulate('mouseup');
|
||||
expect(highlightProps.onChange).toHaveBeenCalledWith([{
|
||||
expect(highlightProps.onChange).toHaveBeenCalledWith([
|
||||
{
|
||||
color: 'rgba(233, 0, 0, 0.64)',
|
||||
key: 'path0',
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: true,
|
||||
strokeWidth: 28,
|
||||
}]);
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should render path if path is provided in props', () => {
|
||||
const pathProps = {
|
||||
paths: [{
|
||||
paths: [
|
||||
{
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: true,
|
||||
strokeWidth: 5,
|
||||
color: 'rgba(233, 0, 0, 0.64)',
|
||||
key: 'path0',
|
||||
}],
|
||||
},
|
||||
],
|
||||
highlightColor: 'rgba(233, 0, 0, 0.64)',
|
||||
penColor: 'rgba(38, 196, 58, 1)',
|
||||
onChange: jest.fn(),
|
||||
@@ -99,13 +105,15 @@ describe('<AnnotateArea/>', () => {
|
||||
|
||||
it('should call onChange with hidden path if clicked on path with tool eraser', () => {
|
||||
const pathProps = {
|
||||
paths: [{
|
||||
paths: [
|
||||
{
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: true,
|
||||
strokeWidth: 5,
|
||||
color: 'rgba(233, 0, 0, 0.64)',
|
||||
key: 'path0',
|
||||
}],
|
||||
},
|
||||
],
|
||||
highlightColor: 'rgba(233, 0, 0, 0.64)',
|
||||
penColor: 'rgba(38, 196, 58, 1)',
|
||||
onChange: jest.fn(),
|
||||
@@ -117,25 +125,29 @@ describe('<AnnotateArea/>', () => {
|
||||
const wrapper = mount(<AnnotateArea {...pathProps} />);
|
||||
const path = wrapper.find('[data-testid="path0"]');
|
||||
path.simulate('click');
|
||||
expect(pathProps.onChange).toHaveBeenCalledWith([{
|
||||
expect(pathProps.onChange).toHaveBeenCalledWith([
|
||||
{
|
||||
color: 'rgba(233, 0, 0, 0.64)',
|
||||
key: 'path0',
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: false,
|
||||
strokeWidth: 5,
|
||||
}]);
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should send annotate_erased if clicked on path with tool eraser', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, 'send');
|
||||
const pathProps = {
|
||||
paths: [{
|
||||
paths: [
|
||||
{
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: true,
|
||||
strokeWidth: 5,
|
||||
color: 'rgba(233, 0, 0, 0.64)',
|
||||
key: 'path0',
|
||||
}],
|
||||
},
|
||||
],
|
||||
highlightColor: 'rgba(233, 0, 0, 0.64)',
|
||||
penColor: 'rgba(38, 196, 58, 1)',
|
||||
onChange: jest.fn(),
|
||||
@@ -146,14 +158,20 @@ describe('<AnnotateArea/>', () => {
|
||||
};
|
||||
const wrapper = mount(<AnnotateArea {...pathProps} />);
|
||||
const path = wrapper.find('[data-testid="path0"]');
|
||||
const expectedValue = { type: 'annotate_erased', element: 'screen_capture_annotate' };
|
||||
const expectedValue = {
|
||||
type: 'annotate_erased',
|
||||
element: 'screen_capture_annotate',
|
||||
};
|
||||
path.simulate('click');
|
||||
expect(spy).toBeCalledWith('snippet-analytics-data', expectedValue);
|
||||
});
|
||||
|
||||
it('should send annotate_added_pen event when drawn with pen', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, 'send');
|
||||
const expectedValue = { type: 'annotate_added_pen', element: 'screen_capture_annotate' };
|
||||
const expectedValue = {
|
||||
type: 'annotate_added_pen',
|
||||
element: 'screen_capture_annotate',
|
||||
};
|
||||
const wrapper = mount(<AnnotateArea {...defaultProps} />);
|
||||
const area = wrapper.find('[data-testid="annotate-area"]');
|
||||
area.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
@@ -173,7 +191,10 @@ describe('<AnnotateArea/>', () => {
|
||||
screenSnippetPath: 'very-nice-path',
|
||||
};
|
||||
const spy = jest.spyOn(ipcRenderer, 'send');
|
||||
const expectedValue = { type: 'annotate_added_highlight', element: 'screen_capture_annotate' };
|
||||
const expectedValue = {
|
||||
type: 'annotate_added_highlight',
|
||||
element: 'screen_capture_annotate',
|
||||
};
|
||||
const wrapper = mount(<AnnotateArea {...highlightProps} />);
|
||||
const area = wrapper.find('[data-testid="annotate-area"]');
|
||||
area.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as rimraf from 'rimraf';
|
||||
import { cleanAppCacheOnInstall, cleanUpAppCache, createAppCacheFile } from '../src/app/app-cache-handler';
|
||||
import {
|
||||
cleanAppCacheOnInstall,
|
||||
cleanUpAppCache,
|
||||
createAppCacheFile,
|
||||
} from '../src/app/app-cache-handler';
|
||||
import { app, session } from './__mocks__/electron';
|
||||
|
||||
jest.mock('fs', () => ({
|
||||
writeFileSync: jest.fn(),
|
||||
existsSync: jest.fn(() => true),
|
||||
unlinkSync: jest.fn(),
|
||||
readdirSync: jest.fn(() => ['Cache', 'GPUCache', 'Symphony.config', 'cloudConfig.config']),
|
||||
readdirSync: jest.fn(() => [
|
||||
'Cache',
|
||||
'GPUCache',
|
||||
'Symphony.config',
|
||||
'cloudConfig.config',
|
||||
]),
|
||||
lstatSync: jest.fn(() => {
|
||||
return {
|
||||
isDirectory: jest.fn(() => true),
|
||||
@@ -62,7 +71,6 @@ describe('app cache handler', () => {
|
||||
|
||||
describe('clean app cache on install', () => {
|
||||
it('should clean app cache and cookies on install', () => {
|
||||
|
||||
const pathSpy = jest.spyOn(path, 'join');
|
||||
|
||||
const fsReadDirSpy = jest.spyOn(fs, 'readdirSync');
|
||||
@@ -82,5 +90,4 @@ describe('app cache handler', () => {
|
||||
expect(rimrafSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -139,7 +139,8 @@ describe('app menu', () => {
|
||||
describe('`popupMenu`', () => {
|
||||
it('should fail when `appMenu.menu` is null', () => {
|
||||
const spy = jest.spyOn(logger, 'error');
|
||||
const expectedValue = 'app-menu: tried popup menu, but failed, menu not defined';
|
||||
const expectedValue =
|
||||
'app-menu: tried popup menu, but failed, menu not defined';
|
||||
appMenu.menu = null;
|
||||
appMenu.popupMenu();
|
||||
expect(spy).toBeCalledWith(expectedValue);
|
||||
@@ -212,7 +213,9 @@ describe('app menu', () => {
|
||||
let autoLaunchMenuItem;
|
||||
|
||||
beforeAll(() => {
|
||||
autoLaunchMenuItem = findMenuItemBuildWindowMenu('Auto Launch On Startup');
|
||||
autoLaunchMenuItem = findMenuItemBuildWindowMenu(
|
||||
'Auto Launch On Startup',
|
||||
);
|
||||
});
|
||||
|
||||
it('should disable `AutoLaunch` when click is triggered', async () => {
|
||||
@@ -257,7 +260,9 @@ describe('app menu', () => {
|
||||
it('should update `bringToFront` value when click is triggered', async () => {
|
||||
const spyConfig = jest.spyOn(config, updateUserFnLabel);
|
||||
const expectedValue = { bringToFront: 'ENABLED' };
|
||||
const menuItem = findMenuItemBuildWindowMenu('Bring to Front on Notifications');
|
||||
const menuItem = findMenuItemBuildWindowMenu(
|
||||
'Bring to Front on Notifications',
|
||||
);
|
||||
await menuItem.click(item);
|
||||
expect(spyConfig).lastCalledWith(expectedValue);
|
||||
});
|
||||
@@ -267,7 +272,9 @@ describe('app menu', () => {
|
||||
env.isWindowsOS = true;
|
||||
env.isLinux = false;
|
||||
env.isMac = false;
|
||||
const menuItem = findMenuItemBuildWindowMenu('Flash Notification in Taskbar');
|
||||
const menuItem = findMenuItemBuildWindowMenu(
|
||||
'Flash Notification in Taskbar',
|
||||
);
|
||||
expect(menuItem.label).toEqual(expectedValue);
|
||||
});
|
||||
});
|
||||
@@ -315,7 +322,9 @@ describe('app menu', () => {
|
||||
});
|
||||
|
||||
it('should call `Show crash dump in Finder` correctly', () => {
|
||||
const menuItem = findHelpTroubleshootingMenuItem('Show crash dump in Finder');
|
||||
const menuItem = findHelpTroubleshootingMenuItem(
|
||||
'Show crash dump in Finder',
|
||||
);
|
||||
menuItem.click();
|
||||
expect(exportCrashDumps).toBeCalled();
|
||||
});
|
||||
@@ -330,7 +339,9 @@ describe('app menu', () => {
|
||||
},
|
||||
};
|
||||
const spy = jest.spyOn(focusedWindow.webContents, 'toggleDevTools');
|
||||
const menuItem = findHelpTroubleshootingMenuItem('Toggle Developer Tools');
|
||||
const menuItem = findHelpTroubleshootingMenuItem(
|
||||
'Toggle Developer Tools',
|
||||
);
|
||||
menuItem.click({}, focusedWindow);
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
@@ -342,9 +353,12 @@ describe('app menu', () => {
|
||||
type: 'warning',
|
||||
buttons: ['Ok'],
|
||||
title: 'Dev Tools disabled',
|
||||
message: 'Dev Tools has been disabled. Please contact your system administrator',
|
||||
message:
|
||||
'Dev Tools has been disabled. Please contact your system administrator',
|
||||
};
|
||||
const menuItem = findHelpTroubleshootingMenuItem('Toggle Developer Tools');
|
||||
const menuItem = findHelpTroubleshootingMenuItem(
|
||||
'Toggle Developer Tools',
|
||||
);
|
||||
menuItem.click({}, focusedWindow);
|
||||
expect(spy).not.toBeCalledWith(null, expectedValue);
|
||||
});
|
||||
|
||||
@@ -24,7 +24,6 @@ jest.mock('../src/app/config-handler', () => {
|
||||
});
|
||||
|
||||
describe('auto launch controller', async () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(config, 'getConfigFields').mockImplementation(() => {
|
||||
return {
|
||||
@@ -58,7 +57,9 @@ describe('auto launch controller', async () => {
|
||||
it('should enable AutoLaunch when `handleAutoLaunch` is called', async () => {
|
||||
const spyFn = 'enableAutoLaunch';
|
||||
const spy = jest.spyOn(autoLaunchInstance, spyFn);
|
||||
jest.spyOn(autoLaunchInstance,'isAutoLaunchEnabled').mockImplementation(() => false);
|
||||
jest
|
||||
.spyOn(autoLaunchInstance, 'isAutoLaunchEnabled')
|
||||
.mockImplementation(() => false);
|
||||
await autoLaunchInstance.handleAutoLaunch();
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
@@ -71,7 +72,9 @@ describe('auto launch controller', async () => {
|
||||
});
|
||||
const spyFn = 'disableAutoLaunch';
|
||||
const spy = jest.spyOn(autoLaunchInstance, spyFn);
|
||||
jest.spyOn(autoLaunchInstance,'isAutoLaunchEnabled').mockImplementation(() => ({openAtLogin: true}));
|
||||
jest
|
||||
.spyOn(autoLaunchInstance, 'isAutoLaunchEnabled')
|
||||
.mockImplementation(() => ({ openAtLogin: true }));
|
||||
await autoLaunchInstance.handleAutoLaunch();
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -3,8 +3,12 @@ import * as React from 'react';
|
||||
import BasicAuth from '../src/renderer/components/basic-auth';
|
||||
|
||||
describe('basic auth', () => {
|
||||
const usernameTargetMock: object = { target: { id: 'username', value: 'foo' }};
|
||||
const passwordTargetMock: object = { target: { id: 'password', value: '123456' }};
|
||||
const usernameTargetMock: object = {
|
||||
target: { id: 'username', value: 'foo' },
|
||||
};
|
||||
const passwordTargetMock: object = {
|
||||
target: { id: 'password', value: '123456' },
|
||||
};
|
||||
const basicAuthMock: object = {
|
||||
hostname: 'example',
|
||||
isValidCredentials: true,
|
||||
@@ -35,7 +39,10 @@ describe('basic auth', () => {
|
||||
wrapper.find('#username').simulate('change', usernameTargetMock);
|
||||
wrapper.find('#password').simulate('change', passwordTargetMock);
|
||||
wrapper.find('#basicAuth').simulate('submit');
|
||||
expect(spy).lastCalledWith('basic-auth-login', { ...usernameMock, ...passwordMock });
|
||||
expect(spy).lastCalledWith('basic-auth-login', {
|
||||
...usernameMock,
|
||||
...passwordMock,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call `basic-auth-closed` event when cancel button is clicked', () => {
|
||||
@@ -59,7 +66,10 @@ describe('basic auth', () => {
|
||||
});
|
||||
|
||||
it('should remove listen `basic-auth-data` when component is unmounted', () => {
|
||||
const spy: jest.SpyInstance = jest.spyOn(ipcRenderer, removeListenerLabelEvent);
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
ipcRenderer,
|
||||
removeListenerLabelEvent,
|
||||
);
|
||||
const wrapper: ShallowWrapper = shallow(React.createElement(BasicAuth));
|
||||
wrapper.unmount();
|
||||
expect(spy).toBeCalledWith(basicAuthDataLabel, expect.any(Function));
|
||||
|
||||
@@ -168,7 +168,11 @@ describe('chrome flags', () => {
|
||||
setChromeFlags();
|
||||
expect(spy).nthCalledWith(1, 'auth-negotiate-delegate-whitelist', 'url');
|
||||
expect(spy).nthCalledWith(2, 'auth-server-whitelist', 'whitelist');
|
||||
expect(spy).nthCalledWith(3, 'disable-background-timer-throttling', 'true');
|
||||
expect(spy).nthCalledWith(
|
||||
3,
|
||||
'disable-background-timer-throttling',
|
||||
'true',
|
||||
);
|
||||
expect(spy).nthCalledWith(4, 'disable-d3d11', true);
|
||||
expect(spy).nthCalledWith(5, 'disable-gpu', true);
|
||||
expect(spy).nthCalledWith(6, 'disable-gpu-compositing', true);
|
||||
|
||||
@@ -28,7 +28,9 @@ describe('<ColorPickerPill/>', () => {
|
||||
|
||||
it('should render chosen dots as larger', () => {
|
||||
const chosenColorProps = {
|
||||
availableColors: [{ rgbaColor: 'rgba(0, 0, 0, 0.64)', chosen: true }, { rgbaColor: 'rgba(233, 0, 0, 0.64)' },
|
||||
availableColors: [
|
||||
{ rgbaColor: 'rgba(0, 0, 0, 0.64)', chosen: true },
|
||||
{ rgbaColor: 'rgba(233, 0, 0, 0.64)' },
|
||||
],
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
@@ -38,7 +40,8 @@ describe('<ColorPickerPill/>', () => {
|
||||
|
||||
it('should render outlined dot if provided in props', () => {
|
||||
const outlinedColorProps = {
|
||||
availableColors: [{ rgbaColor: 'rgba(246, 178, 2, 1)' },
|
||||
availableColors: [
|
||||
{ rgbaColor: 'rgba(246, 178, 2, 1)' },
|
||||
{ rgbaColor: 'rgba(255, 255, 255, 1)', outline: 'rgba(0, 0, 0, 1)' },
|
||||
],
|
||||
onChange: jest.fn(),
|
||||
|
||||
@@ -75,7 +75,9 @@ describe('config', () => {
|
||||
configInstance.readUserConfig();
|
||||
configInstance.readGlobalConfig();
|
||||
|
||||
const configField: IGlobalConfig = configInstance.getGlobalConfigFields(fieldMock);
|
||||
const configField: IGlobalConfig = configInstance.getGlobalConfigFields(
|
||||
fieldMock,
|
||||
);
|
||||
|
||||
expect(configField.url).toBe('something');
|
||||
});
|
||||
@@ -118,7 +120,10 @@ describe('config', () => {
|
||||
configInstance.readUserConfig();
|
||||
|
||||
configInstance.updateUserConfig(newValue);
|
||||
expect(configInstance.userConfig).toEqual({ configVersion: '4.0.0', test: 'test' });
|
||||
expect(configInstance.userConfig).toEqual({
|
||||
configVersion: '4.0.0',
|
||||
test: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when invalid path is used', () => {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { showLoadFailure, showNetworkConnectivityError } from '../src/app/dialog-handler';
|
||||
import {
|
||||
showLoadFailure,
|
||||
showNetworkConnectivityError,
|
||||
} from '../src/app/dialog-handler';
|
||||
import { windowHandler } from '../src/app/window-handler';
|
||||
import { BrowserWindow, dialog, ipcRenderer } from './__mocks__/electron';
|
||||
|
||||
@@ -35,8 +38,20 @@ describe('dialog handler', () => {
|
||||
const authInfoMocked = {
|
||||
host: 'symphony.com',
|
||||
};
|
||||
ipcRenderer.send('login', webContentsMocked, requestMocked, authInfoMocked, callbackMocked);
|
||||
expect(spy).toBeCalledWith(webContentsMocked, 'symphony.com', true, expect.any(Function), callbackMocked);
|
||||
ipcRenderer.send(
|
||||
'login',
|
||||
webContentsMocked,
|
||||
requestMocked,
|
||||
authInfoMocked,
|
||||
callbackMocked,
|
||||
);
|
||||
expect(spy).toBeCalledWith(
|
||||
webContentsMocked,
|
||||
'symphony.com',
|
||||
true,
|
||||
expect.any(Function),
|
||||
callbackMocked,
|
||||
);
|
||||
});
|
||||
|
||||
describe('certificate-error', () => {
|
||||
@@ -51,7 +66,14 @@ describe('dialog handler', () => {
|
||||
dialog.showMessageBox = jest.fn(() => {
|
||||
return { response: 1 };
|
||||
});
|
||||
await ipcRenderer.send('certificate-error', webContentsMocked, urlMocked, errorMocked, certificate, callbackMocked);
|
||||
await ipcRenderer.send(
|
||||
'certificate-error',
|
||||
webContentsMocked,
|
||||
urlMocked,
|
||||
errorMocked,
|
||||
certificate,
|
||||
callbackMocked,
|
||||
);
|
||||
done(expect(callbackMocked).toBeCalledWith(false));
|
||||
});
|
||||
|
||||
@@ -60,9 +82,23 @@ describe('dialog handler', () => {
|
||||
return { isDestroyed: jest.fn(() => false) };
|
||||
});
|
||||
dialog.showMessageBox = jest.fn(() => 2);
|
||||
await ipcRenderer.send('certificate-error', webContentsMocked, urlMocked, errorMocked, certificate, callbackMocked);
|
||||
await ipcRenderer.send(
|
||||
'certificate-error',
|
||||
webContentsMocked,
|
||||
urlMocked,
|
||||
errorMocked,
|
||||
certificate,
|
||||
callbackMocked,
|
||||
);
|
||||
expect(callbackMocked).toBeCalledWith(true);
|
||||
await ipcRenderer.send('certificate-error', webContentsMocked, urlMocked, errorMocked, certificate, callbackMocked);
|
||||
await ipcRenderer.send(
|
||||
'certificate-error',
|
||||
webContentsMocked,
|
||||
urlMocked,
|
||||
errorMocked,
|
||||
certificate,
|
||||
callbackMocked,
|
||||
);
|
||||
done(expect(callbackMocked).toBeCalledWith(true));
|
||||
});
|
||||
});
|
||||
@@ -85,7 +121,14 @@ describe('dialog handler', () => {
|
||||
title: 'Loading Error',
|
||||
message: `Error loading URL:\n${urlMocked}\n\n${errorDescMocked}\n\nError Code: ${errorCodeMocked}`,
|
||||
};
|
||||
showLoadFailure(browserWindowMocked, urlMocked, errorDescMocked, errorCodeMocked, callbackMocked, showDialogMocked);
|
||||
showLoadFailure(
|
||||
browserWindowMocked,
|
||||
urlMocked,
|
||||
errorDescMocked,
|
||||
errorCodeMocked,
|
||||
callbackMocked,
|
||||
showDialogMocked,
|
||||
);
|
||||
expect(spy).toBeCalledWith({ id: 123 }, expectedValue);
|
||||
});
|
||||
|
||||
@@ -94,7 +137,8 @@ describe('dialog handler', () => {
|
||||
const spy = jest.spyOn(dialog, spyFn);
|
||||
const browserWindowMocked: any = { id: 123 };
|
||||
const urlMocked = 'test';
|
||||
const errorDescMocked = 'Network connectivity has been lost. Check your internet connection.';
|
||||
const errorDescMocked =
|
||||
'Network connectivity has been lost. Check your internet connection.';
|
||||
const expectedValue = {
|
||||
type: 'error',
|
||||
buttons: ['Reload', 'Ignore'],
|
||||
@@ -104,7 +148,11 @@ describe('dialog handler', () => {
|
||||
title: 'Loading Error',
|
||||
message: `Error loading URL:\n${urlMocked}\n\n${errorDescMocked}`,
|
||||
};
|
||||
showNetworkConnectivityError(browserWindowMocked, urlMocked, callbackMocked);
|
||||
showNetworkConnectivityError(
|
||||
browserWindowMocked,
|
||||
urlMocked,
|
||||
callbackMocked,
|
||||
);
|
||||
expect(spy).toBeCalledWith({ id: 123 }, expectedValue);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,7 +29,8 @@ describe('download handler', () => {
|
||||
});
|
||||
|
||||
it('should call `sendDownloadCompleted` when download succeeds', () => {
|
||||
const spy: jest.SpyInstance = jest.spyOn(downloadHandlerInstance, 'sendDownloadCompleted')
|
||||
const spy: jest.SpyInstance = jest
|
||||
.spyOn(downloadHandlerInstance, 'sendDownloadCompleted')
|
||||
.mockImplementation(() => jest.fn());
|
||||
|
||||
const data: any = {
|
||||
@@ -44,11 +45,11 @@ describe('download handler', () => {
|
||||
});
|
||||
|
||||
it('should call `sendDownloadFailed` when download fails', () => {
|
||||
const spy: jest.SpyInstance = jest.spyOn(downloadHandlerInstance, 'sendDownloadFailed')
|
||||
const spy: jest.SpyInstance = jest
|
||||
.spyOn(downloadHandlerInstance, 'sendDownloadFailed')
|
||||
.mockImplementation(() => jest.fn());
|
||||
|
||||
downloadHandlerInstance.onDownloadFailed();
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -45,7 +45,9 @@ describe('logger', () => {
|
||||
it('should call `logger.error` correctly', () => {
|
||||
const spy = jest.spyOn(instance, 'log');
|
||||
instance.error('test error', { error: 'test error' });
|
||||
expect(spy).toBeCalledWith('error', 'test error', [{ error: 'test error' }]);
|
||||
expect(spy).toBeCalledWith('error', 'test error', [
|
||||
{ error: 'test error' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call `logger.warn` correctly', () => {
|
||||
@@ -57,7 +59,9 @@ describe('logger', () => {
|
||||
it('should call `logger.debug` correctly', () => {
|
||||
const spy = jest.spyOn(instance, 'log');
|
||||
instance.debug('test debug', { debug: 'test debug' });
|
||||
expect(spy).toBeCalledWith('debug', 'test debug', [{ debug: 'test debug' }]);
|
||||
expect(spy).toBeCalledWith('debug', 'test debug', [
|
||||
{ debug: 'test debug' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call `logger.info` correctly', () => {
|
||||
@@ -69,13 +73,17 @@ describe('logger', () => {
|
||||
it('should call `logger.verbose` correctly', () => {
|
||||
const spy = jest.spyOn(instance, 'log');
|
||||
instance.verbose('test verbose', { verbose: 'test verbose' });
|
||||
expect(spy).toBeCalledWith('verbose', 'test verbose', [{ verbose: 'test verbose' }]);
|
||||
expect(spy).toBeCalledWith('verbose', 'test verbose', [
|
||||
{ verbose: 'test verbose' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call `logger.silly` correctly', () => {
|
||||
const spy = jest.spyOn(instance, 'log');
|
||||
instance.silly('test silly', { silly: 'test silly' });
|
||||
expect(spy).toBeCalledWith('silly', 'test silly', [{ silly: 'test silly' }]);
|
||||
expect(spy).toBeCalledWith('silly', 'test silly', [
|
||||
{ silly: 'test silly' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call `logger.sendToCloud` when `logger.debug` is called', () => {
|
||||
@@ -93,5 +101,4 @@ describe('logger', () => {
|
||||
const queue = instance.getLogQueue();
|
||||
expect(queue.length).toBe(100);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ jest.mock('../src/app/window-utils', () => {
|
||||
showBadgeCount: jest.fn(),
|
||||
showPopupMenu: jest.fn(),
|
||||
updateLocale: jest.fn(),
|
||||
windowExists: jest.fn( () => true),
|
||||
windowExists: jest.fn(() => true),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -119,14 +119,12 @@ jest.mock('../src/app/notifications/notification-helper', () => {
|
||||
jest.mock('../src/common/i18n');
|
||||
|
||||
describe('main api handler', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(utils.isValidWindow as any) = jest.fn(() => true);
|
||||
});
|
||||
|
||||
describe('symphony-api events', () => {
|
||||
|
||||
it('should call `isOnline` correctly', () => {
|
||||
const value = {
|
||||
cmd: apiCmds.isOnline,
|
||||
@@ -184,7 +182,7 @@ describe('main api handler', () => {
|
||||
dataUrl: 'https://symphony.com',
|
||||
count: 3,
|
||||
};
|
||||
const expectedValue = [ 'https://symphony.com', 3 ];
|
||||
const expectedValue = ['https://symphony.com', 3];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
@@ -218,7 +216,7 @@ describe('main api handler', () => {
|
||||
cmd: apiCmds.registerActivityDetection,
|
||||
period: 3,
|
||||
};
|
||||
const expectedValue = [ { send: expect.any(Function) }, 3 ];
|
||||
const expectedValue = [{ send: expect.any(Function) }, 3];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
@@ -228,7 +226,7 @@ describe('main api handler', () => {
|
||||
const value = {
|
||||
cmd: apiCmds.registerDownloadHandler,
|
||||
};
|
||||
const expectedValue = [ { send: expect.any(Function) } ];
|
||||
const expectedValue = [{ send: expect.any(Function) }];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
@@ -313,7 +311,7 @@ describe('main api handler', () => {
|
||||
reason: 'notification',
|
||||
windowName: 'notification',
|
||||
};
|
||||
const expectedValue = [ 'notification', false ];
|
||||
const expectedValue = ['notification', false];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
@@ -325,7 +323,7 @@ describe('main api handler', () => {
|
||||
sources: [],
|
||||
id: 3,
|
||||
};
|
||||
const expectedValue = [ {send: expect.any(Function)}, [], 3 ];
|
||||
const expectedValue = [{ send: expect.any(Function) }, [], 3];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
@@ -386,20 +384,23 @@ describe('main api handler', () => {
|
||||
windowType: 2,
|
||||
winKey: 'main',
|
||||
};
|
||||
const expectedValue = [ 2, 'main' ];
|
||||
const expectedValue = [2, 'main'];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
|
||||
it('should call `openScreenSharingIndicator` correctly', () => {
|
||||
const spy = jest.spyOn(windowHandler, 'createScreenSharingIndicatorWindow');
|
||||
const spy = jest.spyOn(
|
||||
windowHandler,
|
||||
'createScreenSharingIndicatorWindow',
|
||||
);
|
||||
const value = {
|
||||
cmd: apiCmds.openScreenSharingIndicator,
|
||||
displayId: 'main',
|
||||
id: 3,
|
||||
streamId: '3',
|
||||
};
|
||||
const expectedValue = [ { send: expect.any(Function) }, 'main', 3, '3' ];
|
||||
const expectedValue = [{ send: expect.any(Function) }, 'main', 3, '3'];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
@@ -411,7 +412,10 @@ describe('main api handler', () => {
|
||||
type: 2,
|
||||
path: '/Users/symphony/SymphonyElectron/src/app/main-api-handler.ts',
|
||||
};
|
||||
const expectedValue = [ 2, '/Users/symphony/SymphonyElectron/src/app/main-api-handler.ts' ];
|
||||
const expectedValue = [
|
||||
2,
|
||||
'/Users/symphony/SymphonyElectron/src/app/main-api-handler.ts',
|
||||
];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('Notification Settings', () => {
|
||||
},
|
||||
],
|
||||
display: '6713899',
|
||||
theme: 'light'
|
||||
theme: 'light',
|
||||
};
|
||||
const onLabelEvent = 'on';
|
||||
const sendEvent = 'send';
|
||||
@@ -31,7 +31,10 @@ describe('Notification Settings', () => {
|
||||
it('should call `notification-settings-data` event when component is mounted', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, onLabelEvent);
|
||||
shallow(React.createElement(NotificationSettings));
|
||||
expect(spy).toBeCalledWith(notificationSettingsLabel, expect.any(Function));
|
||||
expect(spy).toBeCalledWith(
|
||||
notificationSettingsLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should call `updateState` when component is mounted', () => {
|
||||
@@ -48,10 +51,16 @@ describe('Notification Settings', () => {
|
||||
const spyUnmount = jest.spyOn(ipcRenderer, removeListenerLabelEvent);
|
||||
|
||||
const wrapper = shallow(React.createElement(NotificationSettings));
|
||||
expect(spyMount).toBeCalledWith(notificationSettingsLabel, expect.any(Function));
|
||||
expect(spyMount).toBeCalledWith(
|
||||
notificationSettingsLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
wrapper.unmount();
|
||||
expect(spyUnmount).toBeCalledWith(notificationSettingsLabel, expect.any(Function));
|
||||
expect(spyUnmount).toBeCalledWith(
|
||||
notificationSettingsLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,7 +73,10 @@ describe('Notification Settings', () => {
|
||||
};
|
||||
|
||||
const spy = jest.spyOn(NotificationSettings.prototype, 'setState');
|
||||
const selectDisplaySpy = jest.spyOn(NotificationSettings.prototype, 'selectDisplay');
|
||||
const selectDisplaySpy = jest.spyOn(
|
||||
NotificationSettings.prototype,
|
||||
'selectDisplay',
|
||||
);
|
||||
|
||||
const wrapper = shallow(React.createElement(NotificationSettings));
|
||||
ipcRenderer.send('notification-settings-data', notificationSettingsMock);
|
||||
@@ -77,7 +89,6 @@ describe('Notification Settings', () => {
|
||||
expect(selectDisplaySpy).toBeCalled();
|
||||
expect(spy).toBeCalledWith(notificationSettingsMock);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('should set display position', () => {
|
||||
@@ -90,7 +101,10 @@ describe('Notification Settings', () => {
|
||||
};
|
||||
|
||||
const spy = jest.spyOn(NotificationSettings.prototype, 'setState');
|
||||
const togglePositionButtonSpy = jest.spyOn(NotificationSettings.prototype, 'togglePosition');
|
||||
const togglePositionButtonSpy = jest.spyOn(
|
||||
NotificationSettings.prototype,
|
||||
'togglePosition',
|
||||
);
|
||||
|
||||
const wrapper = shallow(React.createElement(NotificationSettings));
|
||||
ipcRenderer.send('notification-settings-data', notificationSettingsMock);
|
||||
@@ -111,7 +125,10 @@ describe('Notification Settings', () => {
|
||||
};
|
||||
|
||||
const spy = jest.spyOn(NotificationSettings.prototype, 'setState');
|
||||
const togglePositionButtonSpy = jest.spyOn(NotificationSettings.prototype, 'togglePosition');
|
||||
const togglePositionButtonSpy = jest.spyOn(
|
||||
NotificationSettings.prototype,
|
||||
'togglePosition',
|
||||
);
|
||||
|
||||
const wrapper = shallow(React.createElement(NotificationSettings));
|
||||
ipcRenderer.send('notification-settings-data', notificationSettingsMock);
|
||||
@@ -132,7 +149,10 @@ describe('Notification Settings', () => {
|
||||
};
|
||||
|
||||
const spy = jest.spyOn(NotificationSettings.prototype, 'setState');
|
||||
const togglePositionButtonSpy = jest.spyOn(NotificationSettings.prototype, 'togglePosition');
|
||||
const togglePositionButtonSpy = jest.spyOn(
|
||||
NotificationSettings.prototype,
|
||||
'togglePosition',
|
||||
);
|
||||
|
||||
const wrapper = shallow(React.createElement(NotificationSettings));
|
||||
ipcRenderer.send('notification-settings-data', notificationSettingsMock);
|
||||
@@ -153,7 +173,10 @@ describe('Notification Settings', () => {
|
||||
};
|
||||
|
||||
const spy = jest.spyOn(NotificationSettings.prototype, 'setState');
|
||||
const togglePositionButtonSpy = jest.spyOn(NotificationSettings.prototype, 'togglePosition');
|
||||
const togglePositionButtonSpy = jest.spyOn(
|
||||
NotificationSettings.prototype,
|
||||
'togglePosition',
|
||||
);
|
||||
|
||||
const wrapper = shallow(React.createElement(NotificationSettings));
|
||||
ipcRenderer.send('notification-settings-data', notificationSettingsMock);
|
||||
@@ -176,7 +199,10 @@ describe('Notification Settings', () => {
|
||||
};
|
||||
|
||||
const spy = jest.spyOn(ipcRenderer, sendEvent);
|
||||
const closeButtonSpy = jest.spyOn(NotificationSettings.prototype, 'close');
|
||||
const closeButtonSpy = jest.spyOn(
|
||||
NotificationSettings.prototype,
|
||||
'close',
|
||||
);
|
||||
|
||||
const wrapper = shallow(React.createElement(NotificationSettings));
|
||||
ipcRenderer.send('notification-settings-data', notificationSettingsMock);
|
||||
@@ -201,7 +227,10 @@ describe('Notification Settings', () => {
|
||||
};
|
||||
|
||||
const spy = jest.spyOn(ipcRenderer, sendEvent);
|
||||
const closeButtonSpy = jest.spyOn(NotificationSettings.prototype, 'close');
|
||||
const closeButtonSpy = jest.spyOn(
|
||||
NotificationSettings.prototype,
|
||||
'close',
|
||||
);
|
||||
|
||||
const wrapper = shallow(React.createElement(NotificationSettings));
|
||||
ipcRenderer.send('notification-settings-data', notificationSettingsMock);
|
||||
@@ -218,5 +247,4 @@ describe('Notification Settings', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -44,13 +44,18 @@ describe('protocol handler', () => {
|
||||
});
|
||||
|
||||
it('protocol uri should be null by default', () => {
|
||||
expect(protocolHandlerInstance.protocolUri).toBe('symphony://?userId=22222');
|
||||
expect(protocolHandlerInstance.protocolUri).toBe(
|
||||
'symphony://?userId=22222',
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol action should be called when uri is correct', () => {
|
||||
protocolHandlerInstance.preloadWebContents = { send: jest.fn() };
|
||||
|
||||
const spy: jest.SpyInstance = jest.spyOn(protocolHandlerInstance.preloadWebContents, 'send');
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
protocolHandlerInstance.preloadWebContents,
|
||||
'send',
|
||||
);
|
||||
const uri: string = 'symphony://?userId=123456';
|
||||
const protocolAction: string = 'protocol-action';
|
||||
|
||||
@@ -87,7 +92,10 @@ describe('protocol handler', () => {
|
||||
it('protocol action not should be called when uri is incorrect', () => {
|
||||
protocolHandlerInstance.preloadWebContents = { send: jest.fn() };
|
||||
|
||||
const spy: jest.SpyInstance = jest.spyOn(protocolHandlerInstance.preloadWebContents, 'send');
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
protocolHandlerInstance.preloadWebContents,
|
||||
'send',
|
||||
);
|
||||
const uri: string = 'symphony---://?userId=123456';
|
||||
const protocolAction: string = 'protocol-action';
|
||||
|
||||
@@ -108,7 +116,10 @@ describe('protocol handler', () => {
|
||||
const env = require('../src/common/env');
|
||||
env.isWindowsOS = true;
|
||||
|
||||
const spy: jest.SpyInstance = jest.spyOn(protocolHandlerInstance, 'sendProtocol');
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
protocolHandlerInstance,
|
||||
'sendProtocol',
|
||||
);
|
||||
|
||||
protocolHandlerInstance.processArgv('');
|
||||
|
||||
@@ -119,7 +130,10 @@ describe('protocol handler', () => {
|
||||
protocolHandlerInstance.preloadWebContents = { send: jest.fn() };
|
||||
protocolHandlerInstance.protocolUri = 'symphony://?userId=123456';
|
||||
|
||||
const spy: jest.SpyInstance = jest.spyOn(protocolHandlerInstance, 'sendProtocol');
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
protocolHandlerInstance,
|
||||
'sendProtocol',
|
||||
);
|
||||
|
||||
protocolHandlerInstance.setPreloadWebContents({ send: jest.fn() });
|
||||
expect(spy).toBeCalledWith('symphony://?userId=123456');
|
||||
@@ -129,10 +143,12 @@ describe('protocol handler', () => {
|
||||
protocolHandlerInstance.preloadWebContents = { send: jest.fn() };
|
||||
protocolHandlerInstance.protocolUri = null;
|
||||
|
||||
const spy: jest.SpyInstance = jest.spyOn(protocolHandlerInstance, 'sendProtocol');
|
||||
const spy: jest.SpyInstance = jest.spyOn(
|
||||
protocolHandlerInstance,
|
||||
'sendProtocol',
|
||||
);
|
||||
|
||||
protocolHandlerInstance.setPreloadWebContents({ send: jest.fn() });
|
||||
expect(spy).not.toBeCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -36,11 +36,31 @@ describe('screen picker', () => {
|
||||
};
|
||||
const stateMock = {
|
||||
sources: [
|
||||
{ display_id: '0', id: '0', name: 'Application screen 0', thumbnail: undefined },
|
||||
{ display_id: '1', id: '1', name: 'Application screen 1', thumbnail: undefined },
|
||||
{ display_id: '2', id: '2', name: 'Application screen 2', thumbnail: undefined },
|
||||
{
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '2',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
],
|
||||
selectedSource: { display_id: '1', id: '1', name: 'Application screen 1', thumbnail: undefined },
|
||||
selectedSource: {
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
it('should render correctly', () => {
|
||||
@@ -61,7 +81,12 @@ describe('screen picker', () => {
|
||||
it('should call `submit` correctly', () => {
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
const spy = jest.spyOn(ipcRenderer, sendEventLabel);
|
||||
const selectedSource = { display_id: '1', id: '1', name: 'Entire screen', thumbnail: undefined };
|
||||
const selectedSource = {
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
};
|
||||
const customSelector = 'button.ScreenPicker-share-button';
|
||||
wrapper.setState({ selectedSource });
|
||||
wrapper.find(customSelector).simulate('click');
|
||||
@@ -71,7 +96,14 @@ describe('screen picker', () => {
|
||||
it('should call `updateState` correctly', () => {
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const updateState = {
|
||||
sources: [ { display_id: '0', id: '0', name: 'Entire screen', thumbnail: undefined } ],
|
||||
sources: [
|
||||
{
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
],
|
||||
selectedSource: undefined,
|
||||
selectedTab: 'screens',
|
||||
};
|
||||
@@ -83,15 +115,35 @@ describe('screen picker', () => {
|
||||
describe('`onToggle` event', () => {
|
||||
const entireScreenStateMock = {
|
||||
sources: [
|
||||
{ display_id: '0', id: '0', name: 'Entire screen', thumbnail: undefined },
|
||||
{
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
],
|
||||
selectedSource: { display_id: '0', id: '0', name: 'Entire screen', thumbnail: undefined },
|
||||
selectedSource: {
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const applicationScreenStateMock = {
|
||||
sources: [
|
||||
{ display_id: '', id: '1', name: 'Application 1', thumbnail: undefined },
|
||||
{
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Application 1',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
],
|
||||
selectedSource: { display_id: '', id: '1', name: 'Application 1', thumbnail: undefined },
|
||||
selectedSource: {
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Application 1',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
it('should call `onToggle` when screen tab is changed', () => {
|
||||
@@ -117,15 +169,44 @@ describe('screen picker', () => {
|
||||
it('should call `onSelect` when `ScreenPicker-item-container` in Entire screen is clicked', () => {
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '0', fileName: 'fullscreen', id: '0', name: 'Entire screen', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '0',
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const customSelector = '.ScreenPicker-item-container';
|
||||
const applicationScreenStateMock = {
|
||||
sources: [
|
||||
{ display_id: '0', id: '0', name: 'Entire screen', thumbnail: undefined },
|
||||
{ display_id: '1', id: '1', name: 'Application screen 1', thumbnail: undefined },
|
||||
{ display_id: '2', id: '2', name: 'Application screen 2', thumbnail: undefined },
|
||||
{
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '2',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
],
|
||||
selectedSource: { display_id: '1', fileName: 'fullscreen', id: '1', name: 'Application screen 1', thumbnail: undefined },
|
||||
selectedSource: {
|
||||
display_id: '1',
|
||||
fileName: 'fullscreen',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
wrapper.setState(applicationScreenStateMock);
|
||||
wrapper.find(customSelector).first().simulate('click');
|
||||
@@ -135,15 +216,44 @@ describe('screen picker', () => {
|
||||
it('should call `onSelect` when `ScreenPicker-item-container` in Application screen is clicked', () => {
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '2', fileName: 'fullscreen', id: '2', name: 'Application screen 2', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '2',
|
||||
fileName: 'fullscreen',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const customSelector = '.ScreenPicker-item-container';
|
||||
const applicationScreenStateMock = {
|
||||
sources: [
|
||||
{ display_id: '0', id: '0', name: 'Entire screen', thumbnail: undefined },
|
||||
{ display_id: '1', id: '1', name: 'Application screen 1', thumbnail: undefined },
|
||||
{ display_id: '2', id: '2', name: 'Application screen 2', thumbnail: undefined },
|
||||
{
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '2',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
],
|
||||
selectedSource: { display_id: '1', fileName: 'fullscreen', id: '1', name: 'Application screen 1', thumbnail: undefined },
|
||||
selectedSource: {
|
||||
display_id: '1',
|
||||
fileName: 'fullscreen',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
wrapper.setState(applicationScreenStateMock);
|
||||
wrapper.find(customSelector).at(2).simulate('click');
|
||||
@@ -173,7 +283,15 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` pageDown key correctly', () => {
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '2', fileName: 'fullscreen', id: '2', name: 'Application screen 2', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '2',
|
||||
fileName: 'fullscreen',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.pageDown);
|
||||
@@ -182,7 +300,15 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` right arrow key correctly', () => {
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '2', fileName: 'fullscreen', id: '2', name: 'Application screen 2', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '2',
|
||||
fileName: 'fullscreen',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.rightArrow);
|
||||
@@ -191,7 +317,15 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` pageUp key correctly', () => {
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '0', fileName: 'fullscreen', id: '0', name: 'Application screen 0', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '0',
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.pageUp);
|
||||
@@ -200,7 +334,15 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` left arrow key correctly', () => {
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '0', fileName: 'fullscreen', id: '0', name: 'Application screen 0', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '0',
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.leftArrow);
|
||||
@@ -209,7 +351,15 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` down arrow key correctly', () => {
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '0', fileName: 'fullscreen', id: '0', name: 'Application screen 0', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '0',
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.downArrow);
|
||||
@@ -218,7 +368,15 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` up arrow key correctly', () => {
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '2', fileName: 'fullscreen', id: '2', name: 'Application screen 2', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '2',
|
||||
fileName: 'fullscreen',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.upArrow);
|
||||
@@ -227,7 +385,12 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` enter key correctly', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, sendEventLabel);
|
||||
const expectedValue = { display_id: '1', id: '1', name: 'Application screen 1', thumbnail: undefined };
|
||||
const expectedValue = {
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.enterKey);
|
||||
@@ -236,7 +399,10 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` escape key correctly', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, sendEventLabel);
|
||||
const expectedValue = { cmd: 'close-window', windowType: 'screen-picker' };
|
||||
const expectedValue = {
|
||||
cmd: 'close-window',
|
||||
windowType: 'screen-picker',
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.escapeKey);
|
||||
@@ -246,13 +412,20 @@ describe('screen picker', () => {
|
||||
|
||||
it('should call `handleKeyUpPress` end key correctly', () => {
|
||||
const spy = jest.spyOn(ScreenPicker.prototype, 'setState');
|
||||
const expectedValue = { selectedSource: { display_id: '0', fileName: 'fullscreen', id: '0', name: 'Application screen 0', thumbnail: undefined }};
|
||||
const expectedValue = {
|
||||
selectedSource: {
|
||||
display_id: '0',
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
events.keyup(keyCode.endKey);
|
||||
expect(spy).lastCalledWith(expectedValue);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('tab titles', () => {
|
||||
@@ -260,9 +433,24 @@ describe('screen picker', () => {
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
const applicationScreenStateMock = {
|
||||
sources: [
|
||||
{ display_id: '', id: '1', name: 'Application Screen', thumbnail: undefined },
|
||||
{ display_id: '', id: '2', name: 'Application Screen 2', thumbnail: undefined },
|
||||
{ display_id: '', id: '3', name: 'Application Screen 3', thumbnail: undefined },
|
||||
{
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Application Screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '2',
|
||||
name: 'Application Screen 2',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '3',
|
||||
name: 'Application Screen 3',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
wrapper.setState(applicationScreenStateMock);
|
||||
@@ -274,7 +462,12 @@ describe('screen picker', () => {
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
const entireScreenStateMock = {
|
||||
sources: [
|
||||
{ display_id: '1', id: '1', name: 'Entire screen', thumbnail: undefined },
|
||||
{
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{ display_id: '2', id: '2', name: 'Screen 2', thumbnail: undefined },
|
||||
{ display_id: '3', id: '3', name: 'screen 3', thumbnail: undefined },
|
||||
],
|
||||
@@ -289,7 +482,12 @@ describe('screen picker', () => {
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
const entireScreenStateMock = {
|
||||
sources: [
|
||||
{ display_id: '', id: '1', name: 'Entire screen', thumbnail: undefined },
|
||||
{
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{ display_id: '', id: '2', name: 'Screen 2', thumbnail: undefined },
|
||||
{ display_id: '', id: '3', name: 'screen 3', thumbnail: undefined },
|
||||
],
|
||||
@@ -307,7 +505,12 @@ describe('screen picker', () => {
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
const entireScreenStateMock = {
|
||||
sources: [
|
||||
{ display_id: '', id: '1', name: 'Entire screen', thumbnail: undefined },
|
||||
{
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{ display_id: '', id: '2', name: 'Screen 2', thumbnail: undefined },
|
||||
{ display_id: '', id: '3', name: 'screen 3', thumbnail: undefined },
|
||||
],
|
||||
@@ -324,8 +527,18 @@ describe('screen picker', () => {
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
const customState = {
|
||||
sources: [
|
||||
{ display_id: '1', id: '1', name: 'Entire screen', thumbnail: undefined },
|
||||
{ display_id: '', id: '1', name: 'Application screen', thumbnail: undefined },
|
||||
{
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Application screen',
|
||||
thumbnail: undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
wrapper.setState(customState);
|
||||
@@ -336,7 +549,7 @@ describe('screen picker', () => {
|
||||
it('should show `error-message` when source is empty', () => {
|
||||
const errorSelector = '.error-message';
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState({ sources: []});
|
||||
wrapper.setState({ sources: [] });
|
||||
expect(wrapper.find(errorSelector)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,24 +37,35 @@ describe('screen sharing indicator', () => {
|
||||
|
||||
it('should call `updateState` correctly', () => {
|
||||
const setStateEventLabel = 'setState';
|
||||
const spy = jest.spyOn(ScreenSharingIndicator.prototype, setStateEventLabel);
|
||||
const spy = jest.spyOn(
|
||||
ScreenSharingIndicator.prototype,
|
||||
setStateEventLabel,
|
||||
);
|
||||
shallow(React.createElement(ScreenSharingIndicator));
|
||||
ipcRenderer.send(screenSharingIndicatorDataEventLabel, screenSharingIndicatorStateMock);
|
||||
ipcRenderer.send(
|
||||
screenSharingIndicatorDataEventLabel,
|
||||
screenSharingIndicatorStateMock,
|
||||
);
|
||||
expect(spy).lastCalledWith({ id: 10 });
|
||||
});
|
||||
|
||||
describe('`screen-sharing-indicator-data` event', () => {
|
||||
|
||||
it('should call `screen-sharing-indicator-data` when component is mounted', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, onEventLabel);
|
||||
shallow(React.createElement(ScreenSharingIndicator));
|
||||
expect(spy).lastCalledWith(screenSharingIndicatorDataEventLabel, expect.any(Function));
|
||||
expect(spy).lastCalledWith(
|
||||
screenSharingIndicatorDataEventLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should call `screen-sharing-indicator-data` when component is unmounted', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, removeListenerEventLabel);
|
||||
shallow(React.createElement(ScreenSharingIndicator)).unmount();
|
||||
expect(spy).toBeCalledWith(screenSharingIndicatorDataEventLabel, expect.any(Function));
|
||||
expect(spy).toBeCalledWith(
|
||||
screenSharingIndicatorDataEventLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,8 @@ import * as React from 'react';
|
||||
import SnippingTool from '../src/renderer/components/snipping-tool';
|
||||
import { ipcRenderer } from './__mocks__/electron';
|
||||
|
||||
const waitForPromisesToResolve = () => new Promise((resolve) => setTimeout(resolve));
|
||||
const waitForPromisesToResolve = () =>
|
||||
new Promise((resolve) => setTimeout(resolve));
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -23,14 +24,20 @@ describe('Snipping Tool', () => {
|
||||
|
||||
it('should send screenshot_taken BI event on component mount', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, 'send');
|
||||
const expectedValue = { type: 'screenshot_taken', element: 'screen_capture_annotate' };
|
||||
const expectedValue = {
|
||||
type: 'screenshot_taken',
|
||||
element: 'screen_capture_annotate',
|
||||
};
|
||||
mount(React.createElement(SnippingTool));
|
||||
expect(spy).toBeCalledWith('snippet-analytics-data', expectedValue);
|
||||
});
|
||||
|
||||
it('should send capture_sent BI event when clicking done', async () => {
|
||||
const spy = jest.spyOn(ipcRenderer, 'send');
|
||||
const expectedValue = { type: 'annotate_done', element: 'screen_capture_annotate' };
|
||||
const expectedValue = {
|
||||
type: 'annotate_done',
|
||||
element: 'screen_capture_annotate',
|
||||
};
|
||||
const wrapper = mount(React.createElement(SnippingTool));
|
||||
wrapper.find('[data-testid="done-button"]').simulate('click');
|
||||
wrapper.update();
|
||||
@@ -40,7 +47,10 @@ describe('Snipping Tool', () => {
|
||||
|
||||
it('should send annotate_cleared BI event when clicking clear', async () => {
|
||||
const spy = jest.spyOn(ipcRenderer, 'send');
|
||||
const expectedValue = { type: 'annotate_cleared', element: 'screen_capture_annotate' };
|
||||
const expectedValue = {
|
||||
type: 'annotate_cleared',
|
||||
element: 'screen_capture_annotate',
|
||||
};
|
||||
const wrapper = mount(React.createElement(SnippingTool));
|
||||
wrapper.find('[data-testid="clear-button"]').simulate('click');
|
||||
wrapper.update();
|
||||
@@ -57,31 +67,38 @@ describe('Snipping Tool', () => {
|
||||
it('should render highlight color picker when clicked on highlight', () => {
|
||||
const wrapper = shallow(React.createElement(SnippingTool));
|
||||
wrapper.find('[data-testid="highlight-button"]').simulate('click');
|
||||
expect(wrapper.find('[data-testid="highlight-colorpicker"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="highlight-colorpicker"]').exists()).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should clear all paths when clicked on clear', () => {
|
||||
const props = {
|
||||
existingPaths: [{
|
||||
existingPaths: [
|
||||
{
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: true,
|
||||
strokeWidth: 5,
|
||||
color: 'rgba(233, 0, 0, 0.64)',
|
||||
key: 'path0',
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
const wrapper = mount(<SnippingTool {...props} />);
|
||||
const annotateComponent = wrapper.find('[data-testid="annotate-component"]');
|
||||
const annotateComponent = wrapper.find(
|
||||
'[data-testid="annotate-component"]',
|
||||
);
|
||||
wrapper.find('[data-testid="clear-button"]').simulate('click');
|
||||
wrapper.update();
|
||||
expect(annotateComponent.prop('paths')).toEqual(
|
||||
[{
|
||||
expect(annotateComponent.prop('paths')).toEqual([
|
||||
{
|
||||
color: 'rgba(233, 0, 0, 0.64)',
|
||||
key: 'path0',
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: false,
|
||||
strokeWidth: 5,
|
||||
}]);
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should send upload-snippet event with correct data when clicked on done', async () => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as ttlHandler from '../src/app/ttl-handler';
|
||||
|
||||
describe('ttl handler', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
@@ -27,5 +26,4 @@ describe('ttl handler', () => {
|
||||
expiryMock.mockImplementation(() => -1);
|
||||
expect(ttlHandler.checkIfBuildExpired()).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { compareVersions, formatString, getCommandLineArgs, getGuid, throttle, calculatePercentage } from '../src/common/utils';
|
||||
import {
|
||||
compareVersions,
|
||||
formatString,
|
||||
getCommandLineArgs,
|
||||
getGuid,
|
||||
throttle,
|
||||
calculatePercentage,
|
||||
} from '../src/common/utils';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('`compareVersions`', () => {
|
||||
@@ -68,7 +75,9 @@ describe('utils', () => {
|
||||
const myCustomArgs = process.argv;
|
||||
myCustomArgs.push(`--url='https://corporate.symphony.com'`);
|
||||
const expectedValue = `--url='https://corporate.symphony.com'`;
|
||||
expect(getCommandLineArgs(myCustomArgs, argName, false)).toBe(expectedValue);
|
||||
expect(getCommandLineArgs(myCustomArgs, argName, false)).toBe(
|
||||
expectedValue,
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail when the argv is invalid', () => {
|
||||
@@ -95,7 +104,9 @@ describe('utils', () => {
|
||||
|
||||
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');
|
||||
expect(
|
||||
formatString('The string with {multiple} values {placed}', dataTest),
|
||||
).toBe('The string with true values correct');
|
||||
});
|
||||
|
||||
it('should return `str` when `data` not match', () => {
|
||||
@@ -105,7 +116,9 @@ describe('utils', () => {
|
||||
|
||||
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');
|
||||
expect(
|
||||
formatString('The string with {multiple} values {invalid}', dataTest),
|
||||
).toBe('The string with multiple values invalid');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -63,7 +63,9 @@ describe('welcome', () => {
|
||||
const input = wrapper.find(welcomePodUrlBox);
|
||||
|
||||
input.simulate('focus');
|
||||
input.simulate('change', {target: {value: 'https://corporate.symphony.com'}});
|
||||
input.simulate('change', {
|
||||
target: { value: 'https://corporate.symphony.com' },
|
||||
});
|
||||
|
||||
expect(updatePodUrlSpy).toBeCalled();
|
||||
expect(spy).toBeCalledWith(podUrlMock);
|
||||
@@ -87,7 +89,7 @@ describe('welcome', () => {
|
||||
const input = wrapper.find(welcomePodUrlBox);
|
||||
|
||||
input.simulate('focus');
|
||||
input.simulate('change', {target: {value: 'abcdef'}});
|
||||
input.simulate('change', { target: { value: 'abcdef' } });
|
||||
|
||||
expect(updatePodUrlSpy).toBeCalled();
|
||||
expect(spy).toBeCalledWith(podUrlMock);
|
||||
@@ -111,7 +113,7 @@ describe('welcome', () => {
|
||||
const input = wrapper.find(welcomePodUrlBox);
|
||||
|
||||
input.simulate('focus');
|
||||
input.simulate('change', {target: {checked: true}});
|
||||
input.simulate('change', { target: { checked: true } });
|
||||
|
||||
expect(updatePodUrlSpy).toBeCalled();
|
||||
expect(spy).toBeCalledWith(podUrlMock);
|
||||
|
||||
@@ -53,10 +53,26 @@ describe('windows title bar', () => {
|
||||
const spy = jest.spyOn(remote, getCurrentWindowFnLabel);
|
||||
const spyWindow = jest.spyOn(window, onEventLabel);
|
||||
expect(spy).toBeCalled();
|
||||
expect(spyWindow).nthCalledWith(1, maximizeEventLabel, expect.any(Function));
|
||||
expect(spyWindow).nthCalledWith(2, unmaximizeEventLabel, expect.any(Function));
|
||||
expect(spyWindow).nthCalledWith(3, enterFullScreenEventLabel, expect.any(Function));
|
||||
expect(spyWindow).nthCalledWith(4, leaveFullScreenEventLabel, expect.any(Function));
|
||||
expect(spyWindow).nthCalledWith(
|
||||
1,
|
||||
maximizeEventLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(spyWindow).nthCalledWith(
|
||||
2,
|
||||
unmaximizeEventLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(spyWindow).nthCalledWith(
|
||||
3,
|
||||
enterFullScreenEventLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(spyWindow).nthCalledWith(
|
||||
4,
|
||||
leaveFullScreenEventLabel,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should call `close` correctly', () => {
|
||||
|
||||
@@ -2,12 +2,19 @@ import test from 'ava';
|
||||
import * as robot from 'robotjs';
|
||||
import { Application } from 'spectron';
|
||||
import { robotActions } from './fixtures/robot-actions';
|
||||
import { loadURL, podUrl, sleep, startApplication, stopApplication, Timeouts } from './fixtures/spectron-setup';
|
||||
import {
|
||||
loadURL,
|
||||
podUrl,
|
||||
sleep,
|
||||
startApplication,
|
||||
stopApplication,
|
||||
Timeouts,
|
||||
} from './fixtures/spectron-setup';
|
||||
|
||||
let app;
|
||||
|
||||
test.before(async (t) => {
|
||||
app = await startApplication() as Application;
|
||||
app = (await startApplication()) as Application;
|
||||
t.true(app.isRunning());
|
||||
|
||||
await loadURL(app, podUrl);
|
||||
@@ -35,7 +42,9 @@ test('about-app: verify about application feature', async (t) => {
|
||||
test('about-app: verify copy button with few data validation', async (t) => {
|
||||
await sleep(Timeouts.oneSec);
|
||||
await app.client.click('.AboutApp-copy-button');
|
||||
const clipboard = JSON.parse(await app.client.electron.remote.clipboard.readText());
|
||||
const clipboard = JSON.parse(
|
||||
await app.client.electron.remote.clipboard.readText(),
|
||||
);
|
||||
|
||||
t.log(clipboard);
|
||||
t.true(clipboard.hasOwnProperty('appName'));
|
||||
|
||||
@@ -3,7 +3,6 @@ import { isMac } from '../../src/common/env';
|
||||
import { Timeouts } from './spectron-setup';
|
||||
|
||||
class RobotActions {
|
||||
|
||||
constructor() {
|
||||
robot.setKeyboardDelay(Timeouts.oneSec);
|
||||
robot.setMouseDelay(Timeouts.oneSec);
|
||||
@@ -13,7 +12,7 @@ class RobotActions {
|
||||
* Closes window via keyboard action
|
||||
*/
|
||||
public closeWindow(): void {
|
||||
const modifier = isMac ? [ 'command' ] : [ 'control' ];
|
||||
const modifier = isMac ? ['command'] : ['control'];
|
||||
robot.keyToggle('w', 'down', modifier);
|
||||
robot.keyToggle('w', 'up', modifier);
|
||||
}
|
||||
@@ -22,15 +21,15 @@ class RobotActions {
|
||||
* Makes the application fullscreen via keyboard
|
||||
*/
|
||||
public toggleFullscreen(): void {
|
||||
robot.keyToggle('f', 'down', [ 'command', 'control' ]);
|
||||
robot.keyToggle('f', 'up', [ 'command', 'control' ]);
|
||||
robot.keyToggle('f', 'down', ['command', 'control']);
|
||||
robot.keyToggle('f', 'up', ['command', 'control']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom in via keyboard Command/Ctrl +
|
||||
*/
|
||||
public zoomIn(): void {
|
||||
const modifier = isMac ? [ 'command' ] : [ 'control' ];
|
||||
const modifier = isMac ? ['command'] : ['control'];
|
||||
robot.keyToggle('+', 'down', modifier);
|
||||
robot.keyToggle('+', 'up', modifier);
|
||||
}
|
||||
@@ -39,7 +38,7 @@ class RobotActions {
|
||||
* Zoom out via keyboard
|
||||
*/
|
||||
public zoomOut(): void {
|
||||
const modifier = isMac ? [ 'command' ] : [ 'control' ];
|
||||
const modifier = isMac ? ['command'] : ['control'];
|
||||
robot.keyToggle('-', 'down', modifier);
|
||||
robot.keyToggle('-', 'up', modifier);
|
||||
}
|
||||
@@ -48,7 +47,7 @@ class RobotActions {
|
||||
* Zoom reset via keyboard
|
||||
*/
|
||||
public zoomReset(): void {
|
||||
const modifier = isMac ? [ 'command' ] : [ 'control' ];
|
||||
const modifier = isMac ? ['command'] : ['control'];
|
||||
robot.keyToggle('0', 'down', modifier);
|
||||
robot.keyToggle('0', 'up', modifier);
|
||||
}
|
||||
@@ -72,6 +71,4 @@ class RobotActions {
|
||||
|
||||
const robotActions = new RobotActions();
|
||||
|
||||
export {
|
||||
robotActions,
|
||||
};
|
||||
export { robotActions };
|
||||
|
||||
@@ -15,7 +15,15 @@ export enum Timeouts {
|
||||
* Returns the electron executable path
|
||||
*/
|
||||
export const getElectronPath = (): string => {
|
||||
let electronPath = path.join(__dirname, '..', '..', '..', 'node_modules', '.bin', 'electron');
|
||||
let electronPath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'node_modules',
|
||||
'.bin',
|
||||
'electron',
|
||||
);
|
||||
if (process.platform === 'win32') {
|
||||
electronPath += '.cmd';
|
||||
}
|
||||
@@ -26,21 +34,29 @@ export const getElectronPath = (): string => {
|
||||
* Returns the demo application html path
|
||||
*/
|
||||
export const getDemoFilePath = (): string => {
|
||||
return `file://${path.join(__dirname, '..', '..', '..', '/src/demo/index.html')}`;
|
||||
return `file://${path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'/src/demo/index.html',
|
||||
)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns app init file
|
||||
*/
|
||||
export const getArgs = (): string[] => {
|
||||
return [ path.join(__dirname, '..', '..', '/src/app/init.js') ];
|
||||
return [path.join(__dirname, '..', '..', '/src/app/init.js')];
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops the application
|
||||
* @param application
|
||||
*/
|
||||
export const stopApplication = async (application): Promise<Application | undefined> => {
|
||||
export const stopApplication = async (
|
||||
application,
|
||||
): Promise<Application | undefined> => {
|
||||
if (!application || !application.isRunning()) {
|
||||
return;
|
||||
}
|
||||
@@ -88,7 +104,9 @@ export const loadURL = async (app: Application, url: string): Promise<void> => {
|
||||
try {
|
||||
return await app.browserWindow.loadURL(url);
|
||||
} catch (error) {
|
||||
const errorIsNavigatedError: boolean = error.message.includes('Inspected target navigated or closed');
|
||||
const errorIsNavigatedError: boolean = error.message.includes(
|
||||
'Inspected target navigated or closed',
|
||||
);
|
||||
|
||||
if (!errorIsNavigatedError) {
|
||||
throw error;
|
||||
|
||||
@@ -4,7 +4,8 @@ import { Application } from 'spectron';
|
||||
import { robotActions } from './fixtures/robot-actions';
|
||||
|
||||
import {
|
||||
getDemoFilePath, loadURL,
|
||||
getDemoFilePath,
|
||||
loadURL,
|
||||
sleep,
|
||||
startApplication,
|
||||
stopApplication,
|
||||
@@ -14,7 +15,7 @@ import {
|
||||
let app;
|
||||
|
||||
test.before(async (t) => {
|
||||
app = await startApplication() as Application;
|
||||
app = (await startApplication()) as Application;
|
||||
t.true(app.isRunning());
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { startApplication, stopApplication } from './fixtures/spectron-setup';
|
||||
let app;
|
||||
|
||||
test.before(async (t) => {
|
||||
app = await startApplication() as Application;
|
||||
app = (await startApplication()) as Application;
|
||||
t.true(app.isRunning());
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import test from 'ava';
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import { getDemoFilePath, loadURL, sleep, startApplication, stopApplication, Timeouts } from './fixtures/spectron-setup';
|
||||
import {
|
||||
getDemoFilePath,
|
||||
loadURL,
|
||||
sleep,
|
||||
startApplication,
|
||||
stopApplication,
|
||||
Timeouts,
|
||||
} from './fixtures/spectron-setup';
|
||||
|
||||
let app;
|
||||
|
||||
test.before(async (t) => {
|
||||
app = await startApplication(true) as Application;
|
||||
app = (await startApplication(true)) as Application;
|
||||
t.true(app.isRunning());
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import * as robot from 'robotjs';
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import {
|
||||
getDemoFilePath, loadURL,
|
||||
getDemoFilePath,
|
||||
loadURL,
|
||||
sleep,
|
||||
startApplication,
|
||||
stopApplication,
|
||||
@@ -23,7 +24,7 @@ export const openScreenPicker = async (window) => {
|
||||
};
|
||||
|
||||
test.before(async (t) => {
|
||||
app = await startApplication() as Application;
|
||||
app = (await startApplication()) as Application;
|
||||
t.true(app.isRunning());
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import * as robot from 'robotjs';
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import {
|
||||
getDemoFilePath, loadURL,
|
||||
getDemoFilePath,
|
||||
loadURL,
|
||||
sleep,
|
||||
startApplication,
|
||||
stopApplication,
|
||||
@@ -23,7 +24,7 @@ export const openScreenPicker = async (window) => {
|
||||
};
|
||||
|
||||
test.before(async (t) => {
|
||||
app = await startApplication() as Application;
|
||||
app = (await startApplication()) as Application;
|
||||
t.true(app.isRunning());
|
||||
});
|
||||
|
||||
|
||||
@@ -3,12 +3,18 @@ import * as robot from 'robotjs';
|
||||
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import { getDemoFilePath, loadURL, startApplication, stopApplication, Timeouts } from './fixtures/spectron-setup';
|
||||
import {
|
||||
getDemoFilePath,
|
||||
loadURL,
|
||||
startApplication,
|
||||
stopApplication,
|
||||
Timeouts,
|
||||
} from './fixtures/spectron-setup';
|
||||
|
||||
let app;
|
||||
|
||||
test.before(async (t) => {
|
||||
app = await startApplication() as Application;
|
||||
app = (await startApplication()) as Application;
|
||||
t.true(app.isRunning());
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Application } from 'spectron';
|
||||
import { robotActions } from './fixtures/robot-actions';
|
||||
|
||||
import {
|
||||
getDemoFilePath, loadURL,
|
||||
getDemoFilePath,
|
||||
loadURL,
|
||||
sleep,
|
||||
startApplication,
|
||||
stopApplication,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
let app;
|
||||
|
||||
test.before(async (t) => {
|
||||
app = await startApplication() as Application;
|
||||
app = (await startApplication()) as Application;
|
||||
t.true(app.isRunning());
|
||||
});
|
||||
|
||||
|
||||
@@ -22,14 +22,19 @@ class ActivityDetection {
|
||||
* @param window {Electron.BrowserWindow}
|
||||
* @param idleThreshold {number}
|
||||
*/
|
||||
public setWindowAndThreshold(window: Electron.WebContents, idleThreshold: number): void {
|
||||
public setWindowAndThreshold(
|
||||
window: Electron.WebContents,
|
||||
idleThreshold: number,
|
||||
): void {
|
||||
this.window = window;
|
||||
this.idleThreshold = idleThreshold;
|
||||
if (this.queryInterval) {
|
||||
clearInterval(this.queryInterval);
|
||||
}
|
||||
this.startActivityMonitor();
|
||||
logger.info(`activity-detection: Initialized activity detection with an idleThreshold of ${idleThreshold}`);
|
||||
logger.info(
|
||||
`activity-detection: Initialized activity detection with an idleThreshold of ${idleThreshold}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,12 +67,16 @@ class ActivityDetection {
|
||||
// activate func works normally
|
||||
windowHandler.setIsAutoReload(false);
|
||||
this.timer = undefined;
|
||||
logger.info(`activity-detection: activity occurred, updating the client!`);
|
||||
logger.info(
|
||||
`activity-detection: activity occurred, updating the client!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.timer) {
|
||||
logger.info(`activity-detection: user is inactive, started monitoring for every 1 sec`);
|
||||
logger.info(
|
||||
`activity-detection: user is inactive, started monitoring for every 1 sec`,
|
||||
);
|
||||
// starts monitoring for user activity every 1 sec
|
||||
// when user goes inactive
|
||||
this.timer = setInterval(() => {
|
||||
|
||||
@@ -14,7 +14,14 @@ const cacheCheckFilePath: string = path.join(userDataPath, 'CacheCheck');
|
||||
* Cleans old cache
|
||||
*/
|
||||
const cleanOldCache = (): void => {
|
||||
const fileRemovalList = ['blob_storage', 'Cache', 'Cookies', 'temp', 'Cookies-journal', 'GPUCache'];
|
||||
const fileRemovalList = [
|
||||
'blob_storage',
|
||||
'Cache',
|
||||
'Cookies',
|
||||
'temp',
|
||||
'Cookies-journal',
|
||||
'GPUCache',
|
||||
];
|
||||
|
||||
const files = fs.readdirSync(userDataPath);
|
||||
|
||||
@@ -39,12 +46,16 @@ const cleanOldCache = (): void => {
|
||||
export const cleanUpAppCache = async (): Promise<void> => {
|
||||
if (fs.existsSync(cacheCheckFilePath)) {
|
||||
await fs.unlinkSync(cacheCheckFilePath);
|
||||
logger.info(`app-cache-handler: last exit was clean, deleted the app cache file`);
|
||||
logger.info(
|
||||
`app-cache-handler: last exit was clean, deleted the app cache file`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (session.defaultSession) {
|
||||
await session.defaultSession.clearCache();
|
||||
logger.info(`app-cache-handler: we didn't have a clean exit last time, so, cleared the cache that may have been corrupted!`);
|
||||
logger.info(
|
||||
`app-cache-handler: we didn't have a clean exit last time, so, cleared the cache that may have been corrupted!`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,7 +63,9 @@ export const cleanUpAppCache = async (): Promise<void> => {
|
||||
* Creates a new file cache file on app exit
|
||||
*/
|
||||
export const createAppCacheFile = (): void => {
|
||||
logger.info(`app-cache-handler: this is a clean exit, creating app cache file`);
|
||||
logger.info(
|
||||
`app-cache-handler: this is a clean exit, creating app cache file`,
|
||||
);
|
||||
fs.writeFileSync(cacheCheckFilePath, '');
|
||||
};
|
||||
|
||||
@@ -60,7 +73,9 @@ export const createAppCacheFile = (): void => {
|
||||
* Cleans the app cache on new install
|
||||
*/
|
||||
export const cleanAppCacheOnInstall = (): void => {
|
||||
logger.info(`app-cache-handler: cleaning app cache and cookies on new install`);
|
||||
logger.info(
|
||||
`app-cache-handler: cleaning app cache and cookies on new install`,
|
||||
);
|
||||
cleanOldCache();
|
||||
};
|
||||
|
||||
@@ -69,20 +84,30 @@ export const cleanAppCacheOnInstall = (): void => {
|
||||
* @param window Browser window to listen to for crash events
|
||||
*/
|
||||
export const cleanAppCacheOnCrash = (window: BrowserWindow): void => {
|
||||
logger.info(`app-cache-handler: listening to crash events & cleaning app cache`);
|
||||
logger.info(
|
||||
`app-cache-handler: listening to crash events & cleaning app cache`,
|
||||
);
|
||||
const events = ['unresponsive', 'crashed', 'plugin-crashed'];
|
||||
|
||||
events.forEach((windowEvent: any) => {
|
||||
window.webContents.on(windowEvent, async () => {
|
||||
logger.info(`app-cache-handler: Window Event '${windowEvent}' occurred. Clearing cache & restarting app`);
|
||||
logger.info(
|
||||
`app-cache-handler: Window Event '${windowEvent}' occurred. Clearing cache & restarting app`,
|
||||
);
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (!focusedWindow || (typeof focusedWindow.isDestroyed === 'function' && focusedWindow.isDestroyed())) {
|
||||
if (
|
||||
!focusedWindow ||
|
||||
(typeof focusedWindow.isDestroyed === 'function' &&
|
||||
focusedWindow.isDestroyed())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const options = {
|
||||
type: 'question',
|
||||
title: i18n.t('Relaunch Application')(),
|
||||
message: i18n.t('Oops! Something went wrong. Would you like to restart the app?')(),
|
||||
message: i18n.t(
|
||||
'Oops! Something went wrong. Would you like to restart the app?',
|
||||
)(),
|
||||
buttons: [i18n.t('Restart')(), i18n.t('Cancel')()],
|
||||
cancelId: 1,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { app, Menu, MenuItemConstructorOptions, session, shell } from 'electron';
|
||||
import {
|
||||
app,
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
session,
|
||||
shell,
|
||||
} from 'electron';
|
||||
|
||||
import { isLinux, isMac, isWindowsOS } from '../common/env';
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
@@ -13,7 +19,11 @@ import { autoLaunchInstance as autoLaunch } from './auto-launch-controller';
|
||||
import { CloudConfigDataTypes, config, IConfig } from './config-handler';
|
||||
import { gpuRestartDialog, titleBarChangeDialog } from './dialog-handler';
|
||||
import { exportCrashDumps, exportLogs } from './reports-handler';
|
||||
import { registerConsoleMessages, unregisterConsoleMessages, updateAlwaysOnTop } from './window-actions';
|
||||
import {
|
||||
registerConsoleMessages,
|
||||
unregisterConsoleMessages,
|
||||
updateAlwaysOnTop,
|
||||
} from './window-actions';
|
||||
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||
import { reloadWindow, windowExists } from './window-utils';
|
||||
|
||||
@@ -66,8 +76,7 @@ let initialAnalyticsSent = false;
|
||||
|
||||
const menuItemsArray = Object.keys(menuSections)
|
||||
.map((key) => menuSections[key])
|
||||
.filter((value) => isMac ?
|
||||
true : value !== menuSections.about);
|
||||
.filter((value) => (isMac ? true : value !== menuSections.about));
|
||||
|
||||
export class AppMenu {
|
||||
private menu: Electron.Menu | undefined;
|
||||
@@ -82,19 +91,57 @@ export class AppMenu {
|
||||
constructor() {
|
||||
this.menuList = [];
|
||||
this.locale = i18n.getLocale();
|
||||
this.menuItemConfigFields = ['minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'bringToFront', 'memoryRefresh', 'isCustomTitleBar', 'devToolsEnabled'];
|
||||
this.cloudConfig = config.getFilteredCloudConfigFields(this.menuItemConfigFields);
|
||||
this.menuItemConfigFields = [
|
||||
'minimizeOnClose',
|
||||
'launchOnStartup',
|
||||
'alwaysOnTop',
|
||||
'bringToFront',
|
||||
'memoryRefresh',
|
||||
'isCustomTitleBar',
|
||||
'devToolsEnabled',
|
||||
];
|
||||
this.cloudConfig = config.getFilteredCloudConfigFields(
|
||||
this.menuItemConfigFields,
|
||||
);
|
||||
this.disableGpu = config.getConfigFields(['disableGpu']).disableGpu;
|
||||
this.enableRendererLogs = config.getConfigFields(['enableRendererLogs']).enableRendererLogs;
|
||||
this.enableRendererLogs = config.getConfigFields([
|
||||
'enableRendererLogs',
|
||||
]).enableRendererLogs;
|
||||
this.buildMenu();
|
||||
// send initial analytic
|
||||
if (!initialAnalyticsSent) {
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.MINIMIZE_ON_CLOSE, minimizeOnClose === CloudConfigDataTypes.ENABLED);
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.AUTO_LAUNCH_ON_START_UP, launchOnStartup === CloudConfigDataTypes.ENABLED);
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.ALWAYS_ON_TOP, isAlwaysOnTop === CloudConfigDataTypes.ENABLED);
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.FLASH_NOTIFICATION_IN_TASK_BAR, bringToFront === CloudConfigDataTypes.ENABLED);
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.REFRESH_APP_IN_IDLE, memoryRefresh === CloudConfigDataTypes.ENABLED);
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.HAMBURGER_MENU, (isMac || isLinux) ? false : isCustomTitleBar === CloudConfigDataTypes.ENABLED);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.MINIMIZE_ON_CLOSE,
|
||||
minimizeOnClose === CloudConfigDataTypes.ENABLED,
|
||||
);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.AUTO_LAUNCH_ON_START_UP,
|
||||
launchOnStartup === CloudConfigDataTypes.ENABLED,
|
||||
);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.ALWAYS_ON_TOP,
|
||||
isAlwaysOnTop === CloudConfigDataTypes.ENABLED,
|
||||
);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.FLASH_NOTIFICATION_IN_TASK_BAR,
|
||||
bringToFront === CloudConfigDataTypes.ENABLED,
|
||||
);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.REFRESH_APP_IN_IDLE,
|
||||
memoryRefresh === CloudConfigDataTypes.ENABLED,
|
||||
);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.HAMBURGER_MENU,
|
||||
isMac || isLinux
|
||||
? false
|
||||
: isCustomTitleBar === CloudConfigDataTypes.ENABLED,
|
||||
);
|
||||
}
|
||||
initialAnalyticsSent = true;
|
||||
}
|
||||
@@ -106,13 +153,17 @@ export class AppMenu {
|
||||
// updates the global variables
|
||||
this.updateGlobals();
|
||||
|
||||
this.menuList = menuItemsArray.reduce((map: Electron.MenuItemConstructorOptions, key: string) => {
|
||||
this.menuList = menuItemsArray.reduce(
|
||||
(map: Electron.MenuItemConstructorOptions, key: string) => {
|
||||
map[key] = this.buildMenuKey(key);
|
||||
return map;
|
||||
}, this.menuList || {});
|
||||
},
|
||||
this.menuList || {},
|
||||
);
|
||||
|
||||
const template = Object.keys(this.menuList)
|
||||
.map((key) => this.menuList[key]);
|
||||
const template = Object.keys(this.menuList).map(
|
||||
(key) => this.menuList[key],
|
||||
);
|
||||
|
||||
this.menu = Menu.buildFromTemplate(template);
|
||||
logger.info(`app-menu: built menu from the provided template`);
|
||||
@@ -146,7 +197,9 @@ export class AppMenu {
|
||||
* Updates the global variables
|
||||
*/
|
||||
public updateGlobals(): void {
|
||||
const configData = config.getConfigFields(this.menuItemConfigFields) as IConfig;
|
||||
const configData = config.getConfigFields(
|
||||
this.menuItemConfigFields,
|
||||
) as IConfig;
|
||||
minimizeOnClose = configData.minimizeOnClose;
|
||||
launchOnStartup = configData.launchOnStartup;
|
||||
isAlwaysOnTop = configData.alwaysOnTop;
|
||||
@@ -156,7 +209,9 @@ export class AppMenu {
|
||||
devToolsEnabled = configData.devToolsEnabled;
|
||||
|
||||
// fetch updated cloud config
|
||||
this.cloudConfig = config.getFilteredCloudConfigFields(this.menuItemConfigFields);
|
||||
this.cloudConfig = config.getFilteredCloudConfigFields(
|
||||
this.menuItemConfigFields,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +261,9 @@ export class AppMenu {
|
||||
{
|
||||
label: i18n.t('About Symphony')(),
|
||||
click(_menuItem, focusedWindow) {
|
||||
const windowName = focusedWindow ? (focusedWindow as ICustomBrowserWindow).winName : '';
|
||||
const windowName = focusedWindow
|
||||
? (focusedWindow as ICustomBrowserWindow).winName
|
||||
: '';
|
||||
windowHandler.createAboutAppWindow(windowName);
|
||||
},
|
||||
},
|
||||
@@ -229,17 +286,22 @@ export class AppMenu {
|
||||
logger.info(`app-menu: building edit menu`);
|
||||
const menu = {
|
||||
label: i18n.t('Edit')(),
|
||||
submenu:
|
||||
[
|
||||
submenu: [
|
||||
this.assignRoleOrLabel({ role: 'undo', label: i18n.t('Undo')() }),
|
||||
this.assignRoleOrLabel({ role: 'redo', label: i18n.t('Redo')() }),
|
||||
this.buildSeparator(),
|
||||
this.assignRoleOrLabel({ role: 'cut', label: i18n.t('Cut')() }),
|
||||
this.assignRoleOrLabel({ role: 'copy', label: i18n.t('Copy')() }),
|
||||
this.assignRoleOrLabel({ role: 'paste', label: i18n.t('Paste')() }),
|
||||
this.assignRoleOrLabel({ role: 'pasteAndMatchStyle', label: i18n.t('Paste and Match Style')() }),
|
||||
this.assignRoleOrLabel({
|
||||
role: 'pasteAndMatchStyle',
|
||||
label: i18n.t('Paste and Match Style')(),
|
||||
}),
|
||||
this.assignRoleOrLabel({ role: 'delete', label: i18n.t('Delete')() }),
|
||||
this.assignRoleOrLabel({ role: 'selectAll', label: i18n.t('Select All')() }),
|
||||
this.assignRoleOrLabel({
|
||||
role: 'selectAll',
|
||||
label: i18n.t('Select All')(),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -262,17 +324,30 @@ export class AppMenu {
|
||||
logger.info(`app-menu: building view menu`);
|
||||
return {
|
||||
label: i18n.t('View')(),
|
||||
submenu: [{
|
||||
submenu: [
|
||||
{
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click: (_item, focusedWindow) => focusedWindow ? reloadWindow(focusedWindow as ICustomBrowserWindow) : null,
|
||||
click: (_item, focusedWindow) =>
|
||||
focusedWindow
|
||||
? reloadWindow(focusedWindow as ICustomBrowserWindow)
|
||||
: null,
|
||||
label: i18n.t('Reload')(),
|
||||
},
|
||||
this.buildSeparator(),
|
||||
this.assignRoleOrLabel({ role: 'resetZoom', label: i18n.t('Actual Size')() }),
|
||||
this.assignRoleOrLabel({
|
||||
role: 'resetZoom',
|
||||
label: i18n.t('Actual Size')(),
|
||||
}),
|
||||
this.assignRoleOrLabel({ role: 'zoomIn', label: i18n.t('Zoom In')() }),
|
||||
this.assignRoleOrLabel({ role: 'zoomOut', label: i18n.t('Zoom Out')() }),
|
||||
this.assignRoleOrLabel({
|
||||
role: 'zoomOut',
|
||||
label: i18n.t('Zoom Out')(),
|
||||
}),
|
||||
this.buildSeparator(),
|
||||
this.assignRoleOrLabel({ role: 'togglefullscreen', label: i18n.t('Toggle Full Screen')() }),
|
||||
this.assignRoleOrLabel({
|
||||
role: 'togglefullscreen',
|
||||
label: i18n.t('Toggle Full Screen')(),
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -304,73 +379,122 @@ export class AppMenu {
|
||||
} else {
|
||||
autoLaunch.disableAutoLaunch();
|
||||
}
|
||||
launchOnStartup = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET;
|
||||
launchOnStartup = item.checked
|
||||
? CloudConfigDataTypes.ENABLED
|
||||
: CloudConfigDataTypes.NOT_SET;
|
||||
await config.updateUserConfig({ launchOnStartup });
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.AUTO_LAUNCH_ON_START_UP, item.checked);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.AUTO_LAUNCH_ON_START_UP,
|
||||
item.checked,
|
||||
);
|
||||
},
|
||||
label: i18n.t('Auto Launch On Startup')(),
|
||||
type: 'checkbox',
|
||||
visible: !isLinux,
|
||||
enabled: !launchOnStartupCC || launchOnStartupCC === CloudConfigDataTypes.NOT_SET,
|
||||
enabled:
|
||||
!launchOnStartupCC ||
|
||||
launchOnStartupCC === CloudConfigDataTypes.NOT_SET,
|
||||
},
|
||||
{
|
||||
checked: isAlwaysOnTop === CloudConfigDataTypes.ENABLED,
|
||||
click: async (item) => {
|
||||
isAlwaysOnTop = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET;
|
||||
isAlwaysOnTop = item.checked
|
||||
? CloudConfigDataTypes.ENABLED
|
||||
: CloudConfigDataTypes.NOT_SET;
|
||||
await updateAlwaysOnTop(item.checked, true);
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.ALWAYS_ON_TOP, item.checked);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.ALWAYS_ON_TOP,
|
||||
item.checked,
|
||||
);
|
||||
},
|
||||
label: i18n.t('Always on Top')(),
|
||||
type: 'checkbox',
|
||||
visible: !isLinux,
|
||||
enabled: !isAlwaysOnTopCC || isAlwaysOnTopCC === CloudConfigDataTypes.NOT_SET,
|
||||
enabled:
|
||||
!isAlwaysOnTopCC || isAlwaysOnTopCC === CloudConfigDataTypes.NOT_SET,
|
||||
},
|
||||
{
|
||||
checked: minimizeOnClose === CloudConfigDataTypes.ENABLED,
|
||||
click: async (item) => {
|
||||
minimizeOnClose = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET;
|
||||
minimizeOnClose = item.checked
|
||||
? CloudConfigDataTypes.ENABLED
|
||||
: CloudConfigDataTypes.NOT_SET;
|
||||
await config.updateUserConfig({ minimizeOnClose });
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.MINIMIZE_ON_CLOSE, item.checked);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.MINIMIZE_ON_CLOSE,
|
||||
item.checked,
|
||||
);
|
||||
},
|
||||
label: i18n.t('Minimize on Close')(),
|
||||
type: 'checkbox',
|
||||
enabled: !minimizeOnCloseCC || minimizeOnCloseCC === CloudConfigDataTypes.NOT_SET,
|
||||
enabled:
|
||||
!minimizeOnCloseCC ||
|
||||
minimizeOnCloseCC === CloudConfigDataTypes.NOT_SET,
|
||||
},
|
||||
{
|
||||
checked: bringToFront === CloudConfigDataTypes.ENABLED,
|
||||
click: async (item) => {
|
||||
bringToFront = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET;
|
||||
bringToFront = item.checked
|
||||
? CloudConfigDataTypes.ENABLED
|
||||
: CloudConfigDataTypes.NOT_SET;
|
||||
await config.updateUserConfig({ bringToFront });
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.FLASH_NOTIFICATION_IN_TASK_BAR, item.checked);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.FLASH_NOTIFICATION_IN_TASK_BAR,
|
||||
item.checked,
|
||||
);
|
||||
},
|
||||
label: isWindowsOS
|
||||
? i18n.t('Flash Notification in Taskbar')()
|
||||
: i18n.t('Bring to Front on Notifications')(),
|
||||
type: 'checkbox',
|
||||
enabled: !bringToFrontCC || bringToFrontCC === CloudConfigDataTypes.NOT_SET,
|
||||
enabled:
|
||||
!bringToFrontCC || bringToFrontCC === CloudConfigDataTypes.NOT_SET,
|
||||
},
|
||||
this.buildSeparator(),
|
||||
{
|
||||
label: (isCustomTitleBar === CloudConfigDataTypes.DISABLED || isCustomTitleBar === CloudConfigDataTypes.NOT_SET)
|
||||
label:
|
||||
isCustomTitleBar === CloudConfigDataTypes.DISABLED ||
|
||||
isCustomTitleBar === CloudConfigDataTypes.NOT_SET
|
||||
? i18n.t('Enable Hamburger menu')()
|
||||
: i18n.t('Disable Hamburger menu')(),
|
||||
visible: isWindowsOS,
|
||||
click: () => {
|
||||
titleBarChangeDialog(isCustomTitleBar === CloudConfigDataTypes.DISABLED ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.DISABLED);
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.HAMBURGER_MENU, isCustomTitleBar === CloudConfigDataTypes.ENABLED);
|
||||
titleBarChangeDialog(
|
||||
isCustomTitleBar === CloudConfigDataTypes.DISABLED
|
||||
? CloudConfigDataTypes.ENABLED
|
||||
: CloudConfigDataTypes.DISABLED,
|
||||
);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.HAMBURGER_MENU,
|
||||
isCustomTitleBar === CloudConfigDataTypes.ENABLED,
|
||||
);
|
||||
},
|
||||
enabled: !isCustomTitleBarCC || isCustomTitleBarCC === CloudConfigDataTypes.NOT_SET,
|
||||
enabled:
|
||||
!isCustomTitleBarCC ||
|
||||
isCustomTitleBarCC === CloudConfigDataTypes.NOT_SET,
|
||||
},
|
||||
{
|
||||
checked: memoryRefresh === CloudConfigDataTypes.ENABLED,
|
||||
click: async (item) => {
|
||||
memoryRefresh = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET;
|
||||
memoryRefresh = item.checked
|
||||
? CloudConfigDataTypes.ENABLED
|
||||
: CloudConfigDataTypes.NOT_SET;
|
||||
await config.updateUserConfig({ memoryRefresh });
|
||||
this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.REFRESH_APP_IN_IDLE, item.checked);
|
||||
this.sendAnalytics(
|
||||
AnalyticsElements.MENU,
|
||||
MenuActionTypes.REFRESH_APP_IN_IDLE,
|
||||
item.checked,
|
||||
);
|
||||
},
|
||||
label: i18n.t('Refresh app when idle')(),
|
||||
type: 'checkbox',
|
||||
enabled: !memoryRefreshCC || memoryRefreshCC === CloudConfigDataTypes.NOT_SET,
|
||||
enabled:
|
||||
!memoryRefreshCC || memoryRefreshCC === CloudConfigDataTypes.NOT_SET,
|
||||
},
|
||||
{
|
||||
click: async (_item, focusedWindow) => {
|
||||
@@ -388,13 +512,11 @@ export class AppMenu {
|
||||
];
|
||||
|
||||
if (isWindowsOS) {
|
||||
submenu.push(
|
||||
{
|
||||
submenu.push({
|
||||
role: 'quit',
|
||||
visible: isWindowsOS,
|
||||
label: i18n.t('Quit Symphony')(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -424,30 +546,39 @@ export class AppMenu {
|
||||
if (isLinux) {
|
||||
showCrashesLabel = i18n.t('Show crash dump in File Manager')();
|
||||
}
|
||||
const { devToolsEnabled: isDevToolsEnabledCC } = this.cloudConfig as IConfig;
|
||||
const { devToolsEnabled: isDevToolsEnabledCC } = this
|
||||
.cloudConfig as IConfig;
|
||||
|
||||
return {
|
||||
label: i18n.t('Help')(),
|
||||
role: 'help',
|
||||
submenu:
|
||||
[{
|
||||
submenu: [
|
||||
{
|
||||
click: () => shell.openExternal(i18n.t('Help Url')()),
|
||||
label: i18n.t('Symphony Help')(),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
click: () => shell.openExternal(i18n.t('Symphony Url')()),
|
||||
label: i18n.t('Learn More')(),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
label: i18n.t('Troubleshooting')(),
|
||||
submenu: [{
|
||||
submenu: [
|
||||
{
|
||||
click: async () => exportLogs(),
|
||||
label: showLogsLabel,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
click: () => exportCrashDumps(),
|
||||
label: showCrashesLabel,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
label: i18n.t('Toggle Developer Tools')(),
|
||||
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
visible: (typeof isDevToolsEnabledCC === 'boolean' && isDevToolsEnabledCC) || devToolsEnabled,
|
||||
visible:
|
||||
(typeof isDevToolsEnabledCC === 'boolean' &&
|
||||
isDevToolsEnabledCC) ||
|
||||
devToolsEnabled,
|
||||
click(_item, focusedWindow) {
|
||||
if (!focusedWindow || !windowExists(focusedWindow)) {
|
||||
return;
|
||||
@@ -480,17 +611,25 @@ export class AppMenu {
|
||||
}
|
||||
const enableRendererLogs = this.enableRendererLogs;
|
||||
await config.updateUserConfig({ enableRendererLogs });
|
||||
logger.info('New value for enableRendererLogs: ' + this.enableRendererLogs);
|
||||
logger.info(
|
||||
'New value for enableRendererLogs: ' +
|
||||
this.enableRendererLogs,
|
||||
);
|
||||
},
|
||||
}],
|
||||
}, {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: i18n.t('About Symphony')(),
|
||||
visible: isWindowsOS || isLinux,
|
||||
click(_menuItem, focusedWindow) {
|
||||
const windowName = focusedWindow ? (focusedWindow as ICustomBrowserWindow).winName : '';
|
||||
const windowName = focusedWindow
|
||||
? (focusedWindow as ICustomBrowserWindow).winName
|
||||
: '';
|
||||
windowHandler.createAboutAppWindow(windowName);
|
||||
},
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -510,19 +649,26 @@ export class AppMenu {
|
||||
* @return {Object}.role The action of the menu item
|
||||
* @return {Object}.accelerator keyboard shortcuts and modifiers
|
||||
*/
|
||||
private assignRoleOrLabel({ role, label }: MenuItemConstructorOptions): MenuItemConstructorOptions {
|
||||
logger.info(`app-menu: assigning role & label respectively for ${role} & ${label}`);
|
||||
private assignRoleOrLabel({
|
||||
role,
|
||||
label,
|
||||
}: MenuItemConstructorOptions): MenuItemConstructorOptions {
|
||||
logger.info(
|
||||
`app-menu: assigning role & label respectively for ${role} & ${label}`,
|
||||
);
|
||||
if (isLinux) {
|
||||
return label ? { role, label } : { role };
|
||||
}
|
||||
|
||||
if (isMac) {
|
||||
return label ? { role, label, accelerator: role ? macAccelerator[role] : '' }
|
||||
return label
|
||||
? { role, label, accelerator: role ? macAccelerator[role] : '' }
|
||||
: { role, accelerator: role ? macAccelerator[role] : '' };
|
||||
}
|
||||
|
||||
if (isWindowsOS) {
|
||||
return label ? { role, label, accelerator: role ? windowsAccelerator[role] : '' }
|
||||
return label
|
||||
? { role, label, accelerator: role ? windowsAccelerator[role] : '' }
|
||||
: { role, accelerator: role ? windowsAccelerator[role] : '' };
|
||||
}
|
||||
|
||||
@@ -536,11 +682,17 @@ export class AppMenu {
|
||||
* @param type {MenuActionTypes}
|
||||
* @param result {Boolean}
|
||||
*/
|
||||
private sendAnalytics(element: AnalyticsElements, type: MenuActionTypes, result: boolean): void {
|
||||
private sendAnalytics(
|
||||
element: AnalyticsElements,
|
||||
type: MenuActionTypes,
|
||||
result: boolean,
|
||||
): void {
|
||||
analytics.track({
|
||||
element,
|
||||
action_type: type,
|
||||
action_result: result ? AnalyticsActions.ENABLED : AnalyticsActions.DISABLED,
|
||||
action_result: result
|
||||
? AnalyticsActions.ENABLED
|
||||
: AnalyticsActions.DISABLED,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { app } from 'electron';
|
||||
import { logger } from '../common/logger';
|
||||
|
||||
class AppStateHandler {
|
||||
|
||||
/**
|
||||
* Restarts the app with the command line arguments
|
||||
* passed from the previous session
|
||||
@@ -12,7 +11,6 @@ class AppStateHandler {
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const appStateHandler = new AppStateHandler();
|
||||
|
||||
@@ -4,23 +4,24 @@ import { isMac } from '../common/env';
|
||||
import { logger } from '../common/logger';
|
||||
import { CloudConfigDataTypes, config, IConfig } from './config-handler';
|
||||
|
||||
const { autoLaunchPath }: IConfig = config.getConfigFields([ 'autoLaunchPath' ]);
|
||||
const { autoLaunchPath }: IConfig = config.getConfigFields(['autoLaunchPath']);
|
||||
|
||||
const props = isMac ? {
|
||||
const props = isMac
|
||||
? {
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
name: 'Symphony',
|
||||
path: process.execPath,
|
||||
} : {
|
||||
}
|
||||
: {
|
||||
name: 'Symphony',
|
||||
path: autoLaunchPath
|
||||
? autoLaunchPath.replace(/\//g, '\\')
|
||||
: null || process.execPath,
|
||||
};
|
||||
};
|
||||
|
||||
class AutoLaunchController {
|
||||
|
||||
/**
|
||||
* Enable auto launch and displays error dialog on failure
|
||||
*
|
||||
@@ -54,8 +55,12 @@ class AutoLaunchController {
|
||||
* Validates the user config and enables auto launch
|
||||
*/
|
||||
public async handleAutoLaunch(): Promise<void> {
|
||||
const { launchOnStartup }: IConfig = config.getConfigFields([ 'launchOnStartup' ]);
|
||||
const { openAtLogin: isAutoLaunchEnabled }: LoginItemSettings = this.isAutoLaunchEnabled();
|
||||
const { launchOnStartup }: IConfig = config.getConfigFields([
|
||||
'launchOnStartup',
|
||||
]);
|
||||
const {
|
||||
openAtLogin: isAutoLaunchEnabled,
|
||||
}: LoginItemSettings = this.isAutoLaunchEnabled();
|
||||
|
||||
if (launchOnStartup === CloudConfigDataTypes.ENABLED) {
|
||||
if (!isAutoLaunchEnabled) {
|
||||
@@ -67,11 +72,8 @@ class AutoLaunchController {
|
||||
this.disableAutoLaunch();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const autoLaunchInstance = new AutoLaunchController();
|
||||
|
||||
export {
|
||||
autoLaunchInstance,
|
||||
};
|
||||
export { autoLaunchInstance };
|
||||
|
||||
@@ -36,19 +36,25 @@ const MIN_HEIGHT = 300;
|
||||
const verifyProtocolForNewUrl = (url: string): boolean => {
|
||||
const parsedUrl = parse(url);
|
||||
if (!parsedUrl) {
|
||||
logger.info(`child-window-handler: The url ${url} doesn't have a protocol. Returning false for verification!`);
|
||||
logger.info(
|
||||
`child-window-handler: The url ${url} doesn't have a protocol. Returning false for verification!`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// url parse returns protocol with :
|
||||
if (parsedUrl.protocol === 'https:') {
|
||||
logger.info(`child-window-handler: The url ${url} is a https url! Returning true for verification!`);
|
||||
logger.info(
|
||||
`child-window-handler: The url ${url} is a https url! Returning true for verification!`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// url parse returns protocol with :
|
||||
if (parsedUrl.protocol === 'http:') {
|
||||
logger.info(`child-window-handler: The url ${url} is a http url! Returning true for verification!`);
|
||||
logger.info(
|
||||
`child-window-handler: The url ${url} is a http url! Returning true for verification!`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -65,26 +71,42 @@ const getParsedUrl = (url: string): Url => {
|
||||
const parsedUrl = parse(url);
|
||||
|
||||
if (!parsedUrl.protocol || parsedUrl.protocol !== 'https') {
|
||||
logger.info(`child-window-handler: The url ${url} doesn't have a valid protocol. Adding https as protocol.`);
|
||||
logger.info(
|
||||
`child-window-handler: The url ${url} doesn't have a valid protocol. Adding https as protocol.`,
|
||||
);
|
||||
parsedUrl.protocol = 'https:';
|
||||
parsedUrl.slashes = true;
|
||||
}
|
||||
const finalParsedUrl = parse(format(parsedUrl));
|
||||
logger.info(`child-window-handler: The original url ${url} is finally parsed as ${JSON.stringify(finalParsedUrl)}`);
|
||||
logger.info(
|
||||
`child-window-handler: The original url ${url} is finally parsed as ${JSON.stringify(
|
||||
finalParsedUrl,
|
||||
)}`,
|
||||
);
|
||||
return finalParsedUrl;
|
||||
};
|
||||
|
||||
export const handleChildWindow = (webContents: WebContents): void => {
|
||||
const childWindow = (event, newWinUrl, frameName, disposition, newWinOptions): void => {
|
||||
const childWindow = (
|
||||
event,
|
||||
newWinUrl,
|
||||
frameName,
|
||||
disposition,
|
||||
newWinOptions,
|
||||
): void => {
|
||||
logger.info(`child-window-handler: trying to create new child window for url: ${newWinUrl},
|
||||
frame name: ${frameName || undefined}, disposition: ${disposition}`);
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
logger.info(`child-window-handler: main window is not available / destroyed, not creating child window!`);
|
||||
logger.info(
|
||||
`child-window-handler: main window is not available / destroyed, not creating child window!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!windowHandler.url) {
|
||||
logger.info(`child-window-handler: we don't have a valid url, not creating child window!`);
|
||||
logger.info(
|
||||
`child-window-handler: we don't have a valid url, not creating child window!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,22 +125,30 @@ export const handleChildWindow = (webContents: WebContents): void => {
|
||||
const newWinDomainName = `${newWinUrlData.domain}${newWinUrlData.tld}`;
|
||||
const mainWinDomainName = `${mainWinUrlData.domain}${mainWinUrlData.tld}`;
|
||||
|
||||
logger.info(`child-window-handler: main window url: ${mainWinUrlData.subdomain}.${mainWinUrlData.domain}.${mainWinUrlData.tld}`);
|
||||
logger.info(
|
||||
`child-window-handler: main window url: ${mainWinUrlData.subdomain}.${mainWinUrlData.domain}.${mainWinUrlData.tld}`,
|
||||
);
|
||||
|
||||
const emptyUrlString = [ 'about:blank', 'about:blank#blocked' ];
|
||||
const emptyUrlString = ['about:blank', 'about:blank#blocked'];
|
||||
const dispositionWhitelist = ['new-window', 'foreground-tab'];
|
||||
|
||||
// only allow window.open to succeed is if coming from same host,
|
||||
// otherwise open in default browser.
|
||||
if ((newWinDomainName === mainWinDomainName || emptyUrlString.includes(newWinUrl))
|
||||
&& frameName !== ''
|
||||
&& dispositionWhitelist.includes(disposition)) {
|
||||
|
||||
logger.info(`child-window-handler: opening pop-out window for ${newWinUrl}`);
|
||||
if (
|
||||
(newWinDomainName === mainWinDomainName ||
|
||||
emptyUrlString.includes(newWinUrl)) &&
|
||||
frameName !== '' &&
|
||||
dispositionWhitelist.includes(disposition)
|
||||
) {
|
||||
logger.info(
|
||||
`child-window-handler: opening pop-out window for ${newWinUrl}`,
|
||||
);
|
||||
|
||||
const newWinKey = getGuid();
|
||||
if (!frameName) {
|
||||
logger.info(`child-window-handler: frame name missing! not opening the url ${newWinUrl}`);
|
||||
logger.info(
|
||||
`child-window-handler: frame name missing! not opening the url ${newWinUrl}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,14 +156,19 @@ export const handleChildWindow = (webContents: WebContents): void => {
|
||||
const height = newWinOptions.height || DEFAULT_POP_OUT_HEIGHT;
|
||||
|
||||
// try getting x and y position from query parameters
|
||||
const query = newWinParsedUrl && parseQuerystring(newWinParsedUrl.query as string);
|
||||
const query =
|
||||
newWinParsedUrl && parseQuerystring(newWinParsedUrl.query as string);
|
||||
if (query && query.x && query.y) {
|
||||
const newX = Number.parseInt(query.x as string, 10);
|
||||
const newY = Number.parseInt(query.y as string, 10);
|
||||
// only accept if both are successfully parsed.
|
||||
if (Number.isInteger(newX) && Number.isInteger(newY)) {
|
||||
const newWinRect = { x: newX, y: newY, width, height };
|
||||
const { x, y } = getBounds(newWinRect, DEFAULT_POP_OUT_WIDTH, DEFAULT_POP_OUT_HEIGHT);
|
||||
const { x, y } = getBounds(
|
||||
newWinRect,
|
||||
DEFAULT_POP_OUT_WIDTH,
|
||||
DEFAULT_POP_OUT_HEIGHT,
|
||||
);
|
||||
newWinOptions.x = x;
|
||||
newWinOptions.y = y;
|
||||
} else {
|
||||
@@ -159,8 +194,12 @@ export const handleChildWindow = (webContents: WebContents): void => {
|
||||
const childWebContents: WebContents = newWinOptions.webContents;
|
||||
// Event needed to hide native menu bar
|
||||
childWebContents.once('did-start-loading', () => {
|
||||
const browserWin = BrowserWindow.fromWebContents(childWebContents) as ICustomBrowserWindow;
|
||||
const { contextOriginUrl } = config.getGlobalConfigFields([ 'contextOriginUrl' ]);
|
||||
const browserWin = BrowserWindow.fromWebContents(
|
||||
childWebContents,
|
||||
) as ICustomBrowserWindow;
|
||||
const { contextOriginUrl } = config.getGlobalConfigFields([
|
||||
'contextOriginUrl',
|
||||
]);
|
||||
browserWin.setFullScreenable(true);
|
||||
browserWin.origin = contextOriginUrl || windowHandler.url;
|
||||
if (isWindowsOS && browserWin && !browserWin.isDestroyed()) {
|
||||
@@ -169,14 +208,20 @@ export const handleChildWindow = (webContents: WebContents): void => {
|
||||
});
|
||||
|
||||
childWebContents.once('did-finish-load', async () => {
|
||||
logger.info(`child-window-handler: child window content loaded for url ${newWinUrl}!`);
|
||||
const browserWin: ICustomBrowserWindow = BrowserWindow.fromWebContents(childWebContents) as ICustomBrowserWindow;
|
||||
logger.info(
|
||||
`child-window-handler: child window content loaded for url ${newWinUrl}!`,
|
||||
);
|
||||
const browserWin: ICustomBrowserWindow = BrowserWindow.fromWebContents(
|
||||
childWebContents,
|
||||
) as ICustomBrowserWindow;
|
||||
if (!browserWin) {
|
||||
return;
|
||||
}
|
||||
windowHandler.addWindow(newWinKey, browserWin);
|
||||
const { url } = config.getGlobalConfigFields([ 'url' ]);
|
||||
const { enableRendererLogs } = config.getConfigFields([ 'enableRendererLogs' ]);
|
||||
const { url } = config.getGlobalConfigFields(['url']);
|
||||
const { enableRendererLogs } = config.getConfigFields([
|
||||
'enableRendererLogs',
|
||||
]);
|
||||
browserWin.webContents.send('page-load', {
|
||||
isWindowsOS,
|
||||
locale: i18n.getLocale(),
|
||||
@@ -189,7 +234,9 @@ export const handleChildWindow = (webContents: WebContents): void => {
|
||||
await injectStyles(browserWin, false);
|
||||
browserWin.winName = frameName;
|
||||
browserWin.setAlwaysOnTop(mainWindow.isAlwaysOnTop());
|
||||
logger.info(`child-window-handler: setting always on top for child window? ${mainWindow.isAlwaysOnTop()}!`);
|
||||
logger.info(
|
||||
`child-window-handler: setting always on top for child window? ${mainWindow.isAlwaysOnTop()}!`,
|
||||
);
|
||||
|
||||
// prevents window from navigating
|
||||
preventWindowNavigation(browserWin, true);
|
||||
@@ -209,7 +256,9 @@ export const handleChildWindow = (webContents: WebContents): void => {
|
||||
|
||||
// Remove all attached event listeners
|
||||
browserWin.on('close', () => {
|
||||
logger.info(`child-window-handler: close event occurred for window with url ${newWinUrl}!`);
|
||||
logger.info(
|
||||
`child-window-handler: close event occurred for window with url ${newWinUrl}!`,
|
||||
);
|
||||
removeWindowEventListener(browserWin);
|
||||
});
|
||||
|
||||
@@ -223,18 +272,25 @@ export const handleChildWindow = (webContents: WebContents): void => {
|
||||
// }
|
||||
|
||||
// Updates media permissions for preload context
|
||||
const { permissions } = config.getConfigFields([ 'permissions' ]);
|
||||
browserWin.webContents.send('is-screen-share-enabled', permissions.media);
|
||||
const { permissions } = config.getConfigFields(['permissions']);
|
||||
browserWin.webContents.send(
|
||||
'is-screen-share-enabled',
|
||||
permissions.media,
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
event.preventDefault();
|
||||
if (newWinUrl && newWinUrl.length > 2083) {
|
||||
logger.info(`child-window-handler: new window url length is greater than 2083, not performing any action!`);
|
||||
logger.info(
|
||||
`child-window-handler: new window url length is greater than 2083, not performing any action!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!verifyProtocolForNewUrl(newWinUrl)) {
|
||||
logger.info(`child-window-handler: new window url protocol is not http or https, not performing any action!`);
|
||||
logger.info(
|
||||
`child-window-handler: new window url protocol is not http or https, not performing any action!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
logger.info(`child-window-handler: new window url is ${newWinUrl} which is not of the same host,
|
||||
|
||||
@@ -4,11 +4,21 @@ import { logger } from '../common/logger';
|
||||
import { CloudConfigDataTypes, config, IConfig } from './config-handler';
|
||||
|
||||
// Set default flags
|
||||
logger.info(`chrome-flags: Setting mandatory chrome flags`, { flag: { 'ssl-version-fallback-min': 'tls1.2' } });
|
||||
logger.info(`chrome-flags: Setting mandatory chrome flags`, {
|
||||
flag: { 'ssl-version-fallback-min': 'tls1.2' },
|
||||
});
|
||||
app.commandLine.appendSwitch('ssl-version-fallback-min', 'tls1.2');
|
||||
|
||||
// Special args that need to be excluded as part of the chrome command line switch
|
||||
const specialArgs = [ '--url', '--multiInstance', '--userDataPath=', 'symphony://', '--inspect-brk', '--inspect', '--logPath' ];
|
||||
const specialArgs = [
|
||||
'--url',
|
||||
'--multiInstance',
|
||||
'--userDataPath=',
|
||||
'symphony://',
|
||||
'--inspect-brk',
|
||||
'--inspect',
|
||||
'--logPath',
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets chrome flags
|
||||
@@ -16,18 +26,29 @@ const specialArgs = [ '--url', '--multiInstance', '--userDataPath=', 'symphony:/
|
||||
export const setChromeFlags = () => {
|
||||
logger.info(`chrome-flags: Checking if we need to set chrome flags!`);
|
||||
|
||||
const flagsConfig = config.getConfigFields(['customFlags', 'disableGpu']) as IConfig;
|
||||
const { disableThrottling } = config.getCloudConfigFields([ 'disableThrottling' ]) as any;
|
||||
const flagsConfig = config.getConfigFields([
|
||||
'customFlags',
|
||||
'disableGpu',
|
||||
]) as IConfig;
|
||||
const { disableThrottling } = config.getCloudConfigFields([
|
||||
'disableThrottling',
|
||||
]) as any;
|
||||
const configFlags: object = {
|
||||
'auth-negotiate-delegate-whitelist': flagsConfig.customFlags.authServerWhitelist,
|
||||
'auth-server-whitelist': flagsConfig.customFlags.authNegotiateDelegateWhitelist,
|
||||
'auth-negotiate-delegate-whitelist':
|
||||
flagsConfig.customFlags.authServerWhitelist,
|
||||
'auth-server-whitelist':
|
||||
flagsConfig.customFlags.authNegotiateDelegateWhitelist,
|
||||
'disable-background-timer-throttling': 'true',
|
||||
'disable-d3d11': flagsConfig.disableGpu || null,
|
||||
'disable-gpu': flagsConfig.disableGpu || null,
|
||||
'disable-gpu-compositing': flagsConfig.disableGpu || null,
|
||||
'enable-blink-features': 'RTCInsertableStreams',
|
||||
};
|
||||
if (flagsConfig.customFlags.disableThrottling === CloudConfigDataTypes.ENABLED || disableThrottling === CloudConfigDataTypes.ENABLED) {
|
||||
if (
|
||||
flagsConfig.customFlags.disableThrottling ===
|
||||
CloudConfigDataTypes.ENABLED ||
|
||||
disableThrottling === CloudConfigDataTypes.ENABLED
|
||||
) {
|
||||
configFlags['disable-renderer-backgrounding'] = 'true';
|
||||
}
|
||||
|
||||
@@ -37,7 +58,9 @@ export const setChromeFlags = () => {
|
||||
}
|
||||
const val = configFlags[key];
|
||||
if (key && val) {
|
||||
logger.info(`chrome-flags: Setting chrome flag for ${key} with value ${val}!`);
|
||||
logger.info(
|
||||
`chrome-flags: Setting chrome flag for ${key} with value ${val}!`,
|
||||
);
|
||||
app.commandLine.appendSwitch(key, val);
|
||||
}
|
||||
}
|
||||
@@ -65,9 +88,10 @@ export const setChromeFlags = () => {
|
||||
} else {
|
||||
app.commandLine.appendSwitch(argKey);
|
||||
}
|
||||
logger.info( `Appended chrome command line switch ${argKey} with value ${argValue}`);
|
||||
logger.info(
|
||||
`Appended chrome command line switch ${argKey} with value ${argValue}`,
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
@@ -76,9 +100,16 @@ export const setChromeFlags = () => {
|
||||
*/
|
||||
export const setSessionProperties = () => {
|
||||
logger.info(`chrome-flags: Settings session properties`);
|
||||
const { customFlags } = config.getConfigFields([ 'customFlags' ]) as IConfig;
|
||||
const { customFlags } = config.getConfigFields(['customFlags']) as IConfig;
|
||||
|
||||
if (session.defaultSession && customFlags && customFlags.authServerWhitelist && customFlags.authServerWhitelist !== '') {
|
||||
session.defaultSession.allowNTLMCredentialsForDomains(customFlags.authServerWhitelist);
|
||||
if (
|
||||
session.defaultSession &&
|
||||
customFlags &&
|
||||
customFlags.authServerWhitelist &&
|
||||
customFlags.authServerWhitelist !== ''
|
||||
) {
|
||||
session.defaultSession.allowNTLMCredentialsForDomains(
|
||||
customFlags.authServerWhitelist,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,20 +130,51 @@ class Config {
|
||||
constructor() {
|
||||
this.configFileName = 'Symphony.config';
|
||||
this.installVariantFilename = 'InstallVariant.info';
|
||||
this.userConfigPath = path.join(app.getPath('userData'), this.configFileName);
|
||||
this.cloudConfigPath = path.join(app.getPath('userData'), 'cloudConfig.config');
|
||||
this.appPath = isDevEnv ? app.getAppPath() : path.dirname(app.getPath('exe'));
|
||||
this.userConfigPath = path.join(
|
||||
app.getPath('userData'),
|
||||
this.configFileName,
|
||||
);
|
||||
this.cloudConfigPath = path.join(
|
||||
app.getPath('userData'),
|
||||
'cloudConfig.config',
|
||||
);
|
||||
this.appPath = isDevEnv
|
||||
? app.getAppPath()
|
||||
: path.dirname(app.getPath('exe'));
|
||||
this.globalConfigPath = isDevEnv
|
||||
? path.join(this.appPath, path.join('config', this.configFileName))
|
||||
: path.join(this.appPath, (isMac) ? '..' : '', 'config', this.configFileName);
|
||||
: path.join(
|
||||
this.appPath,
|
||||
isMac ? '..' : '',
|
||||
'config',
|
||||
this.configFileName,
|
||||
);
|
||||
|
||||
this.installVariantPath = isDevEnv
|
||||
? path.join(this.appPath, path.join('config', this.installVariantFilename))
|
||||
: path.join(this.appPath, (isMac) ? '..' : '', 'config', this.installVariantFilename);
|
||||
? path.join(
|
||||
this.appPath,
|
||||
path.join('config', this.installVariantFilename),
|
||||
)
|
||||
: path.join(
|
||||
this.appPath,
|
||||
isMac ? '..' : '',
|
||||
'config',
|
||||
this.installVariantFilename,
|
||||
);
|
||||
|
||||
if (isLinux) {
|
||||
this.globalConfigPath = path.join(this.appPath, (isElectronQA) ? '..' : '', 'config', this.configFileName);
|
||||
this.installVariantPath = path.join(this.appPath, (isElectronQA) ? '..' : '', 'config', this.installVariantFilename);
|
||||
this.globalConfigPath = path.join(
|
||||
this.appPath,
|
||||
isElectronQA ? '..' : '',
|
||||
'config',
|
||||
this.configFileName,
|
||||
);
|
||||
this.installVariantPath = path.join(
|
||||
this.appPath,
|
||||
isElectronQA ? '..' : '',
|
||||
'config',
|
||||
this.installVariantFilename,
|
||||
);
|
||||
}
|
||||
|
||||
this.globalConfig = {};
|
||||
@@ -166,8 +197,15 @@ class Config {
|
||||
* @param fields
|
||||
*/
|
||||
public getConfigFields(fields: string[]): IConfig {
|
||||
const configFields = { ...this.getGlobalConfigFields(fields), ...this.getUserConfigFields(fields), ...this.getFilteredCloudConfigFields(fields) } as IConfig;
|
||||
logger.info(`config-handler: getting combined config values for the fields ${fields}`, configFields);
|
||||
const configFields = {
|
||||
...this.getGlobalConfigFields(fields),
|
||||
...this.getUserConfigFields(fields),
|
||||
...this.getFilteredCloudConfigFields(fields),
|
||||
} as IConfig;
|
||||
logger.info(
|
||||
`config-handler: getting combined config values for the fields ${fields}`,
|
||||
configFields,
|
||||
);
|
||||
return configFields;
|
||||
}
|
||||
|
||||
@@ -178,7 +216,10 @@ class Config {
|
||||
*/
|
||||
public getUserConfigFields(fields: string[]): IConfig {
|
||||
const userConfigData = pick(this.userConfig, fields) as IConfig;
|
||||
logger.info(`config-handler: getting user config values for the fields ${fields}`, userConfigData);
|
||||
logger.info(
|
||||
`config-handler: getting user config values for the fields ${fields}`,
|
||||
userConfigData,
|
||||
);
|
||||
return userConfigData;
|
||||
}
|
||||
|
||||
@@ -189,7 +230,10 @@ class Config {
|
||||
*/
|
||||
public getGlobalConfigFields(fields: string[]): IGlobalConfig {
|
||||
const globalConfigData = pick(this.globalConfig, fields) as IGlobalConfig;
|
||||
logger.info(`config-handler: getting global config values for the fields ${fields}`, globalConfigData);
|
||||
logger.info(
|
||||
`config-handler: getting global config values for the fields ${fields}`,
|
||||
globalConfigData,
|
||||
);
|
||||
return globalConfigData;
|
||||
}
|
||||
|
||||
@@ -199,8 +243,14 @@ class Config {
|
||||
* @param fields {Array}
|
||||
*/
|
||||
public getFilteredCloudConfigFields(fields: string[]): IConfig | {} {
|
||||
const filteredCloudConfigData = pick(this.filteredCloudConfig, fields) as IConfig;
|
||||
logger.info(`config-handler: getting filtered cloud config values for the ${fields}`, filteredCloudConfigData);
|
||||
const filteredCloudConfigData = pick(
|
||||
this.filteredCloudConfig,
|
||||
fields,
|
||||
) as IConfig;
|
||||
logger.info(
|
||||
`config-handler: getting filtered cloud config values for the ${fields}`,
|
||||
filteredCloudConfigData,
|
||||
);
|
||||
return filteredCloudConfigData;
|
||||
}
|
||||
|
||||
@@ -209,11 +259,22 @@ class Config {
|
||||
* @param fields
|
||||
*/
|
||||
public getCloudConfigFields(fields: string[]): IConfig {
|
||||
const { acpFeatureLevelEntitlements, podLevelEntitlements, pmpEntitlements } = this.cloudConfig as ICloudConfig;
|
||||
const cloudConfig = { ...acpFeatureLevelEntitlements, ...podLevelEntitlements, ...pmpEntitlements };
|
||||
const {
|
||||
acpFeatureLevelEntitlements,
|
||||
podLevelEntitlements,
|
||||
pmpEntitlements,
|
||||
} = this.cloudConfig as ICloudConfig;
|
||||
const cloudConfig = {
|
||||
...acpFeatureLevelEntitlements,
|
||||
...podLevelEntitlements,
|
||||
...pmpEntitlements,
|
||||
};
|
||||
logger.info(`config-handler: prioritized cloud config data`, cloudConfig);
|
||||
const cloudConfigData = pick(cloudConfig, fields) as IConfig;
|
||||
logger.info(`config-handler: getting prioritized cloud config values for the fields ${fields}`, cloudConfigData);
|
||||
logger.info(
|
||||
`config-handler: getting prioritized cloud config values for the fields ${fields}`,
|
||||
cloudConfigData,
|
||||
);
|
||||
return cloudConfigData;
|
||||
}
|
||||
|
||||
@@ -223,14 +284,33 @@ class Config {
|
||||
* @param data {IConfig}
|
||||
*/
|
||||
public async updateUserConfig(data: Partial<IConfig>): Promise<void> {
|
||||
logger.info(`config-handler: updating user config values with the data`, JSON.stringify(data));
|
||||
logger.info(
|
||||
`config-handler: updating user config values with the data`,
|
||||
JSON.stringify(data),
|
||||
);
|
||||
this.userConfig = { ...this.userConfig, ...data };
|
||||
try {
|
||||
await writeFile(this.userConfigPath, JSON.stringify(this.userConfig, null, 2), { encoding: 'utf8' });
|
||||
logger.info(`config-handler: updated user config values with the data ${JSON.stringify(data)}`);
|
||||
await writeFile(
|
||||
this.userConfigPath,
|
||||
JSON.stringify(this.userConfig, null, 2),
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
logger.info(
|
||||
`config-handler: updated user config values with the data ${JSON.stringify(
|
||||
data,
|
||||
)}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(`config-handler: failed to update user config file with ${JSON.stringify(data)}`, error);
|
||||
dialog.showErrorBox(`Update failed`, `Failed to update user config due to error: ${error}`);
|
||||
logger.error(
|
||||
`config-handler: failed to update user config file with ${JSON.stringify(
|
||||
data,
|
||||
)}`,
|
||||
error,
|
||||
);
|
||||
dialog.showErrorBox(
|
||||
`Update failed`,
|
||||
`Failed to update user config due to error: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,16 +320,29 @@ class Config {
|
||||
* @param data {IConfig}
|
||||
*/
|
||||
public async updateCloudConfig(data: Partial<ICloudConfig>): Promise<void> {
|
||||
logger.info(`config-handler: Updating the cloud config data from SFE: `, data);
|
||||
logger.info(
|
||||
`config-handler: Updating the cloud config data from SFE: `,
|
||||
data,
|
||||
);
|
||||
this.cloudConfig = { ...this.cloudConfig, ...data };
|
||||
// recalculate cloud config when we have data from SFE
|
||||
this.filterCloudConfig();
|
||||
logger.info(`config-handler: prioritized and filtered cloud config: `, this.filteredCloudConfig);
|
||||
logger.info(
|
||||
`config-handler: prioritized and filtered cloud config: `,
|
||||
this.filteredCloudConfig,
|
||||
);
|
||||
try {
|
||||
await writeFile(this.cloudConfigPath, JSON.stringify(this.cloudConfig, null, 2), { encoding: 'utf8' });
|
||||
await writeFile(
|
||||
this.cloudConfigPath,
|
||||
JSON.stringify(this.cloudConfig, null, 2),
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
logger.info(`config-handler: writing cloud config values to file`);
|
||||
} catch (error) {
|
||||
logger.error(`config-handler: failed to update cloud config file with ${data}`, error);
|
||||
logger.error(
|
||||
`config-handler: failed to update cloud config file with ${data}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +360,8 @@ class Config {
|
||||
*/
|
||||
public async setUpFirstTimeLaunch(): Promise<void> {
|
||||
const execPath = path.dirname(this.appPath);
|
||||
const shouldUpdateUserConfig = execPath.indexOf('AppData\\Local\\Programs') !== -1 || isMac;
|
||||
const shouldUpdateUserConfig =
|
||||
execPath.indexOf('AppData\\Local\\Programs') !== -1 || isMac;
|
||||
if (shouldUpdateUserConfig) {
|
||||
const {
|
||||
minimizeOnClose,
|
||||
@@ -276,15 +370,23 @@ class Config {
|
||||
memoryRefresh,
|
||||
bringToFront,
|
||||
isCustomTitleBar,
|
||||
...filteredFields }: IConfig = this.userConfig as IConfig;
|
||||
...filteredFields
|
||||
}: IConfig = this.userConfig as IConfig;
|
||||
// update to the new build number
|
||||
filteredFields.buildNumber = buildNumber;
|
||||
filteredFields.installVariant = this.installVariant;
|
||||
filteredFields.bootCount = 0;
|
||||
logger.info(`config-handler: setting first time launch for build`, buildNumber);
|
||||
logger.info(
|
||||
`config-handler: setting first time launch for build`,
|
||||
buildNumber,
|
||||
);
|
||||
return await this.updateUserConfig(filteredFields);
|
||||
}
|
||||
await this.updateUserConfig({ buildNumber, installVariant: this.installVariant, bootCount: this.bootCount });
|
||||
await this.updateUserConfig({
|
||||
buildNumber,
|
||||
installVariant: this.installVariant,
|
||||
bootCount: this.bootCount,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,7 +402,9 @@ class Config {
|
||||
* @private
|
||||
*/
|
||||
public async updateUserConfigOnStart() {
|
||||
logger.info(`config-handler: updating user config with the latest global config values`);
|
||||
logger.info(
|
||||
`config-handler: updating user config with the latest global config values`,
|
||||
);
|
||||
const latestGlobalConfig = this.globalConfig as IConfig;
|
||||
// The properties set below are typically controlled by IT admins, so, we copy
|
||||
// all the values from global config to the user config on SDA relaunch
|
||||
@@ -320,15 +424,38 @@ class Config {
|
||||
* filters out the cloud config
|
||||
*/
|
||||
private filterCloudConfig(): void {
|
||||
const { acpFeatureLevelEntitlements, podLevelEntitlements, pmpEntitlements } = this.cloudConfig as ICloudConfig;
|
||||
const {
|
||||
acpFeatureLevelEntitlements,
|
||||
podLevelEntitlements,
|
||||
pmpEntitlements,
|
||||
} = this.cloudConfig as ICloudConfig;
|
||||
|
||||
// Filter out some values
|
||||
const filteredACP = filterOutSelectedValues(acpFeatureLevelEntitlements, [true, 'NOT_SET', '', []]);
|
||||
const filteredPod = filterOutSelectedValues(podLevelEntitlements, [true, 'NOT_SET', '', []]);
|
||||
const filteredPMP = filterOutSelectedValues(pmpEntitlements, [true, 'NOT_SET', '', []]);
|
||||
const filteredACP = filterOutSelectedValues(acpFeatureLevelEntitlements, [
|
||||
true,
|
||||
'NOT_SET',
|
||||
'',
|
||||
[],
|
||||
]);
|
||||
const filteredPod = filterOutSelectedValues(podLevelEntitlements, [
|
||||
true,
|
||||
'NOT_SET',
|
||||
'',
|
||||
[],
|
||||
]);
|
||||
const filteredPMP = filterOutSelectedValues(pmpEntitlements, [
|
||||
true,
|
||||
'NOT_SET',
|
||||
'',
|
||||
[],
|
||||
]);
|
||||
|
||||
// priority is PMP > ACP > SDA
|
||||
this.filteredCloudConfig = { ...filteredACP, ...filteredPod, ...filteredPMP };
|
||||
this.filteredCloudConfig = {
|
||||
...filteredACP,
|
||||
...filteredPod,
|
||||
...filteredPMP,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,7 +473,9 @@ class Config {
|
||||
parsedData = JSON.parse(data);
|
||||
logger.info(`config-handler: parsed JSON file with data`, parsedData);
|
||||
} catch (e) {
|
||||
logger.error(`config-handler: parsing JSON file failed due to error ${e}`);
|
||||
logger.error(
|
||||
`config-handler: parsing JSON file failed due to error ${e}`,
|
||||
);
|
||||
throw new Error(e);
|
||||
}
|
||||
return parsedData;
|
||||
@@ -363,11 +492,19 @@ class Config {
|
||||
// Need to wait until app ready event to access user data
|
||||
await app.whenReady();
|
||||
await this.readGlobalConfig();
|
||||
logger.info(`config-handler: user config doesn't exist! will create new one and update config`);
|
||||
logger.info(
|
||||
`config-handler: user config doesn't exist! will create new one and update config`,
|
||||
);
|
||||
const { url, ...rest } = this.globalConfig as IConfig;
|
||||
await this.updateUserConfig({ configVersion: app.getVersion().toString(), buildNumber, ...rest } as IConfig);
|
||||
await this.updateUserConfig({
|
||||
configVersion: app.getVersion().toString(),
|
||||
buildNumber,
|
||||
...rest,
|
||||
} as IConfig);
|
||||
}
|
||||
this.userConfig = this.parseConfigData(fs.readFileSync(this.userConfigPath, 'utf8'));
|
||||
this.userConfig = this.parseConfigData(
|
||||
fs.readFileSync(this.userConfigPath, 'utf8'),
|
||||
);
|
||||
logger.info(`config-handler: User configuration: `, this.userConfig);
|
||||
}
|
||||
|
||||
@@ -376,9 +513,13 @@ class Config {
|
||||
*/
|
||||
private readGlobalConfig() {
|
||||
if (!fs.existsSync(this.globalConfigPath)) {
|
||||
throw new Error(`Global config file missing! App will not run as expected!`);
|
||||
throw new Error(
|
||||
`Global config file missing! App will not run as expected!`,
|
||||
);
|
||||
}
|
||||
this.globalConfig = this.parseConfigData(fs.readFileSync(this.globalConfigPath, 'utf8'));
|
||||
this.globalConfig = this.parseConfigData(
|
||||
fs.readFileSync(this.globalConfigPath, 'utf8'),
|
||||
);
|
||||
logger.info(`config-handler: Global configuration: `, this.globalConfig);
|
||||
}
|
||||
|
||||
@@ -399,9 +540,13 @@ class Config {
|
||||
private async readCloudConfig() {
|
||||
if (!fs.existsSync(this.cloudConfigPath)) {
|
||||
await app.whenReady();
|
||||
await this.updateCloudConfig({ configVersion: app.getVersion().toString() });
|
||||
await this.updateCloudConfig({
|
||||
configVersion: app.getVersion().toString(),
|
||||
});
|
||||
}
|
||||
this.cloudConfig = this.parseConfigData(fs.readFileSync(this.cloudConfigPath, 'utf8'));
|
||||
this.cloudConfig = this.parseConfigData(
|
||||
fs.readFileSync(this.cloudConfigPath, 'utf8'),
|
||||
);
|
||||
// recalculate cloud config when we the application starts
|
||||
this.filterCloudConfig();
|
||||
logger.info(`config-handler: Cloud configuration: `, this.userConfig);
|
||||
@@ -412,22 +557,33 @@ class Config {
|
||||
*/
|
||||
private async checkFirstTimeLaunch() {
|
||||
logger.info('config-handler: checking first time launch');
|
||||
const installVariant = this.userConfig && (this.userConfig as IConfig).installVariant || null;
|
||||
const installVariant =
|
||||
(this.userConfig && (this.userConfig as IConfig).installVariant) || null;
|
||||
|
||||
if (!installVariant) {
|
||||
logger.info(`config-handler: there's no install variant found, this is a first time launch`);
|
||||
logger.info(
|
||||
`config-handler: there's no install variant found, this is a first time launch`,
|
||||
);
|
||||
this.isFirstTime = true;
|
||||
this.bootCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (installVariant && typeof installVariant === 'string' && installVariant !== this.installVariant) {
|
||||
logger.info(`config-handler: install variant found is of a different instance, this is a first time launch`);
|
||||
if (
|
||||
installVariant &&
|
||||
typeof installVariant === 'string' &&
|
||||
installVariant !== this.installVariant
|
||||
) {
|
||||
logger.info(
|
||||
`config-handler: install variant found is of a different instance, this is a first time launch`,
|
||||
);
|
||||
this.isFirstTime = true;
|
||||
this.bootCount = 0;
|
||||
return;
|
||||
}
|
||||
logger.info(`config-handler: install variant is the same as the existing one, not a first time launch`);
|
||||
logger.info(
|
||||
`config-handler: install variant is the same as the existing one, not a first time launch`,
|
||||
);
|
||||
this.isFirstTime = false;
|
||||
this.bootCount = (this.getConfigFields(['bootCount']) as IConfig).bootCount;
|
||||
if (this.bootCount !== undefined) {
|
||||
@@ -441,6 +597,4 @@ class Config {
|
||||
|
||||
const config = new Config();
|
||||
|
||||
export {
|
||||
config,
|
||||
};
|
||||
export { config };
|
||||
|
||||
@@ -9,15 +9,23 @@ import { logger } from '../common/logger';
|
||||
|
||||
const TAG_LENGTH = 16;
|
||||
const arch = process.arch === 'ia32';
|
||||
const winLibraryPath = isDevEnv ? path.join(__dirname, '..', '..', 'library') : path.join(execPath, 'library');
|
||||
const macLibraryPath = isDevEnv ? path.join(__dirname, '..', '..', '..', 'library') : path.join(execPath, '..', 'library');
|
||||
const winLibraryPath = isDevEnv
|
||||
? path.join(__dirname, '..', '..', 'library')
|
||||
: path.join(execPath, 'library');
|
||||
const macLibraryPath = isDevEnv
|
||||
? path.join(__dirname, '..', '..', '..', 'library')
|
||||
: path.join(execPath, '..', 'library');
|
||||
|
||||
const cryptoLibPath = isMac ?
|
||||
path.join(macLibraryPath, 'cryptoLib.dylib') :
|
||||
(arch ? path.join(winLibraryPath, 'libsymphonysearch-x86.dll') : path.join(winLibraryPath, 'libsymphonysearch-x64.dll'));
|
||||
const cryptoLibPath = isMac
|
||||
? path.join(macLibraryPath, 'cryptoLib.dylib')
|
||||
: arch
|
||||
? path.join(winLibraryPath, 'libsymphonysearch-x86.dll')
|
||||
: path.join(winLibraryPath, 'libsymphonysearch-x64.dll');
|
||||
|
||||
const library = new Library((cryptoLibPath), {
|
||||
AESEncryptGCM: [types.int32, [
|
||||
const library = new Library(cryptoLibPath, {
|
||||
AESEncryptGCM: [
|
||||
types.int32,
|
||||
[
|
||||
refType(types.uchar),
|
||||
types.int32,
|
||||
refType(types.uchar),
|
||||
@@ -28,9 +36,12 @@ const library = new Library((cryptoLibPath), {
|
||||
refType(types.uchar),
|
||||
refType(types.uchar),
|
||||
types.uint32,
|
||||
]],
|
||||
],
|
||||
],
|
||||
|
||||
AESDecryptGCM: [types.int32, [
|
||||
AESDecryptGCM: [
|
||||
types.int32,
|
||||
[
|
||||
refType(types.uchar),
|
||||
types.int32,
|
||||
refType(types.uchar),
|
||||
@@ -41,18 +52,29 @@ const library = new Library((cryptoLibPath), {
|
||||
refType(types.uchar),
|
||||
types.uint32,
|
||||
refType(types.uchar),
|
||||
]],
|
||||
],
|
||||
],
|
||||
|
||||
getVersion: [types.CString, []],
|
||||
});
|
||||
|
||||
interface ICryptoLib {
|
||||
AESGCMEncrypt: (name: string, base64IV: string, base64AAD: string, base64Key: string, base64In: string) => string | null;
|
||||
AESGCMDecrypt: (base64IV: string, base64AAD: string, base64Key: string, base64In: string) => string | null;
|
||||
AESGCMEncrypt: (
|
||||
name: string,
|
||||
base64IV: string,
|
||||
base64AAD: string,
|
||||
base64Key: string,
|
||||
base64In: string,
|
||||
) => string | null;
|
||||
AESGCMDecrypt: (
|
||||
base64IV: string,
|
||||
base64AAD: string,
|
||||
base64Key: string,
|
||||
base64In: string,
|
||||
) => string | null;
|
||||
}
|
||||
|
||||
class CryptoLibrary implements ICryptoLib {
|
||||
|
||||
/**
|
||||
* Encrypt / Decrypt
|
||||
*
|
||||
@@ -63,7 +85,13 @@ class CryptoLibrary implements ICryptoLib {
|
||||
* @param base64In {string}
|
||||
* @constructor
|
||||
*/
|
||||
public static EncryptDecrypt(name: string, base64IV: string, base64AAD: string, base64Key: string, base64In: string): string | null {
|
||||
public static EncryptDecrypt(
|
||||
name: string,
|
||||
base64IV: string,
|
||||
base64AAD: string,
|
||||
base64Key: string,
|
||||
base64In: string,
|
||||
): string | null {
|
||||
let base64Input = base64In;
|
||||
|
||||
if (!base64Input) {
|
||||
@@ -79,10 +107,23 @@ class CryptoLibrary implements ICryptoLib {
|
||||
const outPtr: Buffer = Buffer.alloc(IN.length);
|
||||
const TAG: Buffer = Buffer.alloc(TAG_LENGTH);
|
||||
|
||||
const resultCode = library.AESEncryptGCM(IN, IN.length, AAD, AAD.length, KEY, IV, IV.length, outPtr, TAG, TAG_LENGTH);
|
||||
const resultCode = library.AESEncryptGCM(
|
||||
IN,
|
||||
IN.length,
|
||||
AAD,
|
||||
AAD.length,
|
||||
KEY,
|
||||
IV,
|
||||
IV.length,
|
||||
outPtr,
|
||||
TAG,
|
||||
TAG_LENGTH,
|
||||
);
|
||||
|
||||
if (resultCode < 0) {
|
||||
logger.error(`AESEncryptGCM, Failed to encrypt with exit code ${resultCode}`);
|
||||
logger.error(
|
||||
`AESEncryptGCM, Failed to encrypt with exit code ${resultCode}`,
|
||||
);
|
||||
}
|
||||
const bufferArray = [outPtr, TAG];
|
||||
return Buffer.concat(bufferArray).toString('base64');
|
||||
@@ -93,10 +134,23 @@ class CryptoLibrary implements ICryptoLib {
|
||||
const TAG = Buffer.from(IN.slice(IN.length - 16, IN.length));
|
||||
const outPtr = Buffer.alloc(IN.length - TAG_LENGTH);
|
||||
|
||||
const resultCode = library.AESDecryptGCM(IN, cipherTextLen, AAD, AAD.length, TAG, TAG_LENGTH, KEY, IV, IV.length, outPtr);
|
||||
const resultCode = library.AESDecryptGCM(
|
||||
IN,
|
||||
cipherTextLen,
|
||||
AAD,
|
||||
AAD.length,
|
||||
TAG,
|
||||
TAG_LENGTH,
|
||||
KEY,
|
||||
IV,
|
||||
IV.length,
|
||||
outPtr,
|
||||
);
|
||||
|
||||
if (resultCode < 0) {
|
||||
logger.error(`AESDecryptGCM, Failed to decrypt with exit code ${resultCode}`);
|
||||
logger.error(
|
||||
`AESDecryptGCM, Failed to decrypt with exit code ${resultCode}`,
|
||||
);
|
||||
}
|
||||
return outPtr.toString('base64');
|
||||
}
|
||||
@@ -113,8 +167,19 @@ class CryptoLibrary implements ICryptoLib {
|
||||
* @param base64In {string}
|
||||
* @constructor
|
||||
*/
|
||||
public AESGCMEncrypt(base64IV: string, base64AAD: string, base64Key: string, base64In: string): string | null {
|
||||
return CryptoLibrary.EncryptDecrypt('AESGCMEncrypt', base64IV, base64AAD, base64Key, base64In);
|
||||
public AESGCMEncrypt(
|
||||
base64IV: string,
|
||||
base64AAD: string,
|
||||
base64Key: string,
|
||||
base64In: string,
|
||||
): string | null {
|
||||
return CryptoLibrary.EncryptDecrypt(
|
||||
'AESGCMEncrypt',
|
||||
base64IV,
|
||||
base64AAD,
|
||||
base64Key,
|
||||
base64In,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,8 +191,19 @@ class CryptoLibrary implements ICryptoLib {
|
||||
* @param base64In {string}
|
||||
* @constructor
|
||||
*/
|
||||
public AESGCMDecrypt(base64IV: string, base64AAD: string, base64Key: string, base64In: string): string | null {
|
||||
return CryptoLibrary.EncryptDecrypt('AESGCMDecrypt', base64IV, base64AAD, base64Key, base64In);
|
||||
public AESGCMDecrypt(
|
||||
base64IV: string,
|
||||
base64AAD: string,
|
||||
base64Key: string,
|
||||
base64In: string,
|
||||
): string | null {
|
||||
return CryptoLibrary.EncryptDecrypt(
|
||||
'AESGCMDecrypt',
|
||||
base64IV,
|
||||
base64AAD,
|
||||
base64Key,
|
||||
base64In,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ electron.app.on('login', (event, webContents, request, authInfo, callback) => {
|
||||
|
||||
// name of the host to display
|
||||
const hostname = authInfo.host || authInfo.realm;
|
||||
const browserWin: ICustomBrowserWindow = electron.BrowserWindow.fromWebContents(webContents) as ICustomBrowserWindow;
|
||||
const browserWin: ICustomBrowserWindow = electron.BrowserWindow.fromWebContents(
|
||||
webContents,
|
||||
) as ICustomBrowserWindow;
|
||||
|
||||
/**
|
||||
* Method that resets currentAuthURL and tries
|
||||
@@ -40,7 +42,13 @@ electron.app.on('login', (event, webContents, request, authInfo, callback) => {
|
||||
* Opens an electron modal window in which
|
||||
* user can enter credentials fot the host
|
||||
*/
|
||||
windowHandler.createBasicAuthWindow(browserWin, hostname, tries === 0, clearSettings, callback);
|
||||
windowHandler.createBasicAuthWindow(
|
||||
browserWin,
|
||||
hostname,
|
||||
tries === 0,
|
||||
clearSettings,
|
||||
callback,
|
||||
);
|
||||
});
|
||||
|
||||
let ignoreAllCertErrors = false;
|
||||
@@ -53,7 +61,9 @@ let ignoreAllCertErrors = false;
|
||||
* Note: the dialog is synchronous so further processing is blocked until
|
||||
* user provides a response.
|
||||
*/
|
||||
electron.app.on('certificate-error', async (event, webContents, url, error, _certificate, callback) => {
|
||||
electron.app.on(
|
||||
'certificate-error',
|
||||
async (event, webContents, url, error, _certificate, callback) => {
|
||||
// TODO: Add logic verify custom certificate
|
||||
|
||||
if (ignoreAllCertErrors) {
|
||||
@@ -70,11 +80,7 @@ electron.app.on('certificate-error', async (event, webContents, url, error, _cer
|
||||
if (browserWin && windowExists(browserWin)) {
|
||||
const { response } = await electron.dialog.showMessageBox(browserWin, {
|
||||
type: 'warning',
|
||||
buttons: [
|
||||
i18n.t('Allow')(),
|
||||
i18n.t('Deny')(),
|
||||
i18n.t('Ignore All')(),
|
||||
],
|
||||
buttons: [i18n.t('Allow')(), i18n.t('Deny')(), i18n.t('Ignore All')()],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
noLink: true,
|
||||
@@ -87,7 +93,8 @@ electron.app.on('certificate-error', async (event, webContents, url, error, _cer
|
||||
|
||||
callback(response !== 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Show dialog pinned to given window when loading error occurs
|
||||
@@ -99,8 +106,17 @@ electron.app.on('certificate-error', async (event, webContents, url, error, _cer
|
||||
* @param retryCallback {function} Callback when user clicks reload
|
||||
* @param showDialog {Boolean} Indicates if a dialog need to be show to a user
|
||||
*/
|
||||
export const showLoadFailure = async (browserWindow: Electron.BrowserWindow, url: string, errorDesc: string, errorCode: number, retryCallback: () => void, showDialog: boolean): Promise<void> => {
|
||||
let message = url ? `${i18n.t('Error loading URL')()}:\n${url}` : i18n.t('Error loading window')();
|
||||
export const showLoadFailure = async (
|
||||
browserWindow: Electron.BrowserWindow,
|
||||
url: string,
|
||||
errorDesc: string,
|
||||
errorCode: number,
|
||||
retryCallback: () => void,
|
||||
showDialog: boolean,
|
||||
): Promise<void> => {
|
||||
let message = url
|
||||
? `${i18n.t('Error loading URL')()}:\n${url}`
|
||||
: i18n.t('Error loading window')();
|
||||
if (errorDesc) {
|
||||
message += `\n\n${errorDesc}`;
|
||||
}
|
||||
@@ -126,7 +142,9 @@ export const showLoadFailure = async (browserWindow: Electron.BrowserWindow, url
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn(`Load failure msg: ${errorDesc} errorCode: ${errorCode} for url: ${url}`);
|
||||
logger.warn(
|
||||
`Load failure msg: ${errorDesc} errorCode: ${errorCode} for url: ${url}`,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -136,8 +154,14 @@ export const showLoadFailure = async (browserWindow: Electron.BrowserWindow, url
|
||||
* @param url {String} Url that failed
|
||||
* @param retryCallback {function} Callback when user clicks reload
|
||||
*/
|
||||
export const showNetworkConnectivityError = (browserWindow: Electron.BrowserWindow, url: string = '', retryCallback: () => void): void => {
|
||||
const errorDesc = i18n.t('Network connectivity has been lost. Check your internet connection.')();
|
||||
export const showNetworkConnectivityError = (
|
||||
browserWindow: Electron.BrowserWindow,
|
||||
url: string = '',
|
||||
retryCallback: () => void,
|
||||
): void => {
|
||||
const errorDesc = i18n.t(
|
||||
'Network connectivity has been lost. Check your internet connection.',
|
||||
)();
|
||||
showLoadFailure(browserWindow, url, errorDesc, 0, retryCallback, true);
|
||||
};
|
||||
|
||||
@@ -147,7 +171,9 @@ export const showNetworkConnectivityError = (browserWindow: Electron.BrowserWind
|
||||
*
|
||||
* @param isNativeStyle {boolean}
|
||||
*/
|
||||
export const titleBarChangeDialog = async (isNativeStyle: CloudConfigDataTypes) => {
|
||||
export const titleBarChangeDialog = async (
|
||||
isNativeStyle: CloudConfigDataTypes,
|
||||
) => {
|
||||
const focusedWindow = electron.BrowserWindow.getFocusedWindow();
|
||||
if (!focusedWindow || !windowExists(focusedWindow)) {
|
||||
return;
|
||||
@@ -155,12 +181,19 @@ export const titleBarChangeDialog = async (isNativeStyle: CloudConfigDataTypes)
|
||||
const options = {
|
||||
type: 'question',
|
||||
title: i18n.t('Relaunch Application')(),
|
||||
message: i18n.t('Updating Title bar style requires Symphony to relaunch.')(),
|
||||
detail: i18n.t('Note: When Hamburger menu is disabled, you can trigger the main menu by pressing the Alt key.')(),
|
||||
message: i18n.t(
|
||||
'Updating Title bar style requires Symphony to relaunch.',
|
||||
)(),
|
||||
detail: i18n.t(
|
||||
'Note: When Hamburger menu is disabled, you can trigger the main menu by pressing the Alt key.',
|
||||
)(),
|
||||
buttons: [i18n.t('Relaunch')(), i18n.t('Cancel')()],
|
||||
cancelId: 1,
|
||||
};
|
||||
const { response } = await electron.dialog.showMessageBox(focusedWindow, options);
|
||||
const { response } = await electron.dialog.showMessageBox(
|
||||
focusedWindow,
|
||||
options,
|
||||
);
|
||||
if (response === 0) {
|
||||
logger.error(`test`, isNativeStyle);
|
||||
await config.updateUserConfig({ isCustomTitleBar: isNativeStyle });
|
||||
@@ -181,11 +214,16 @@ export const gpuRestartDialog = async (disableGpu: boolean) => {
|
||||
const options = {
|
||||
type: 'question',
|
||||
title: i18n.t('Relaunch Application')(),
|
||||
message: i18n.t('Would you like to restart and apply these new settings now?')(),
|
||||
message: i18n.t(
|
||||
'Would you like to restart and apply these new settings now?',
|
||||
)(),
|
||||
buttons: [i18n.t('Restart')(), i18n.t('Later')()],
|
||||
cancelId: 1,
|
||||
};
|
||||
const { response } = await electron.dialog.showMessageBox(focusedWindow, options);
|
||||
const { response } = await electron.dialog.showMessageBox(
|
||||
focusedWindow,
|
||||
options,
|
||||
);
|
||||
await config.updateUserConfig({ disableGpu });
|
||||
if (response === 0) {
|
||||
app.relaunch();
|
||||
|
||||
@@ -19,7 +19,10 @@ class DownloadHandler {
|
||||
*/
|
||||
private static async showDialog(): Promise<void> {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
const message = i18n.t('The file you are trying to open cannot be found in the specified path.', DOWNLOAD_MANAGER_NAMESPACE)();
|
||||
const message = i18n.t(
|
||||
'The file you are trying to open cannot be found in the specified path.',
|
||||
DOWNLOAD_MANAGER_NAMESPACE,
|
||||
)();
|
||||
const title = i18n.t('File not Found', DOWNLOAD_MANAGER_NAMESPACE)();
|
||||
|
||||
if (!focusedWindow || !windowExists(focusedWindow)) {
|
||||
@@ -107,9 +110,13 @@ class DownloadHandler {
|
||||
*/
|
||||
private sendDownloadCompleted(item: IDownloadItem): void {
|
||||
if (this.window && !this.window.isDestroyed()) {
|
||||
logger.info(`download-handler: Download completed! Informing the client!`);
|
||||
logger.info(
|
||||
`download-handler: Download completed! Informing the client!`,
|
||||
);
|
||||
this.window.send('download-completed', {
|
||||
id: item._id, fileDisplayName: item.fileName, fileSize: item.total,
|
||||
id: item._id,
|
||||
fileDisplayName: item.fileName,
|
||||
fileSize: item.total,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -135,7 +142,6 @@ class DownloadHandler {
|
||||
|
||||
return this.items[fileIndex].savedPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const downloadHandler = new DownloadHandler();
|
||||
|
||||
@@ -7,8 +7,14 @@ import { getCommandLineArgs } from '../common/utils';
|
||||
import { appStats } from './stats';
|
||||
|
||||
// Handle custom user data path from process.argv
|
||||
const userDataPathArg: string | null = getCommandLineArgs(process.argv, '--userDataPath=', false);
|
||||
const userDataPath = userDataPathArg && userDataPathArg.substring(userDataPathArg.indexOf('=') + 1);
|
||||
const userDataPathArg: string | null = getCommandLineArgs(
|
||||
process.argv,
|
||||
'--userDataPath=',
|
||||
false,
|
||||
);
|
||||
const userDataPath =
|
||||
userDataPathArg &&
|
||||
userDataPathArg.substring(userDataPathArg.indexOf('=') + 1);
|
||||
|
||||
// force sandbox: true for all BrowserWindow instances.
|
||||
if (!isNodeEnv) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
|
||||
import { apiCmds, apiName, IApiArgs, INotificationData } from '../common/api-interface';
|
||||
import {
|
||||
apiCmds,
|
||||
apiName,
|
||||
IApiArgs,
|
||||
INotificationData,
|
||||
} from '../common/api-interface';
|
||||
import { LocaleType } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
import { activityDetection } from './activity-detection';
|
||||
@@ -31,9 +36,14 @@ import {
|
||||
* Handle API related ipc messages from renderers. Only messages from windows
|
||||
* we have created are allowed.
|
||||
*/
|
||||
ipcMain.on(apiName.symphonyApi, async (event: Electron.IpcMainEvent, arg: IApiArgs) => {
|
||||
ipcMain.on(
|
||||
apiName.symphonyApi,
|
||||
async (event: Electron.IpcMainEvent, arg: IApiArgs) => {
|
||||
if (!isValidWindow(BrowserWindow.fromWebContents(event.sender))) {
|
||||
logger.error(`main-api-handler: invalid window try to perform action, ignoring action`, arg.cmd);
|
||||
logger.error(
|
||||
`main-api-handler: invalid window try to perform action, ignoring action`,
|
||||
arg.cmd,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,12 +125,22 @@ ipcMain.on(apiName.symphonyApi, async (event: Electron.IpcMainEvent, arg: IApiAr
|
||||
break;
|
||||
case apiCmds.openScreenPickerWindow:
|
||||
if (Array.isArray(arg.sources) && typeof arg.id === 'number') {
|
||||
windowHandler.createScreenPickerWindow(event.sender, arg.sources, arg.id);
|
||||
windowHandler.createScreenPickerWindow(
|
||||
event.sender,
|
||||
arg.sources,
|
||||
arg.id,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case apiCmds.popupMenu: {
|
||||
const browserWin = BrowserWindow.fromWebContents(event.sender) as ICustomBrowserWindow;
|
||||
if (browserWin && windowExists(browserWin) && browserWin.winName === apiName.mainWindowName) {
|
||||
const browserWin = BrowserWindow.fromWebContents(
|
||||
event.sender,
|
||||
) as ICustomBrowserWindow;
|
||||
if (
|
||||
browserWin &&
|
||||
windowExists(browserWin) &&
|
||||
browserWin.winName === apiName.mainWindowName
|
||||
) {
|
||||
showPopupMenu({ window: browserWin });
|
||||
}
|
||||
break;
|
||||
@@ -147,7 +167,12 @@ ipcMain.on(apiName.symphonyApi, async (event: Electron.IpcMainEvent, arg: IApiAr
|
||||
case apiCmds.openScreenSharingIndicator:
|
||||
const { displayId, id, streamId } = arg;
|
||||
if (typeof displayId === 'string' && typeof id === 'number') {
|
||||
windowHandler.createScreenSharingIndicatorWindow(event.sender, displayId, id, streamId);
|
||||
windowHandler.createScreenSharingIndicatorWindow(
|
||||
event.sender,
|
||||
displayId,
|
||||
id,
|
||||
streamId,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case apiCmds.downloadManagerAction:
|
||||
@@ -173,7 +198,9 @@ ipcMain.on(apiName.symphonyApi, async (event: Electron.IpcMainEvent, arg: IApiAr
|
||||
break;
|
||||
case apiCmds.isMisspelled:
|
||||
if (typeof arg.word === 'string') {
|
||||
event.returnValue = windowHandler.spellchecker ? windowHandler.spellchecker.isMisspelled(arg.word) : false;
|
||||
event.returnValue = windowHandler.spellchecker
|
||||
? windowHandler.spellchecker.isMisspelled(arg.word)
|
||||
: false;
|
||||
}
|
||||
break;
|
||||
case apiCmds.setIsInMeeting:
|
||||
@@ -194,12 +221,28 @@ ipcMain.on(apiName.symphonyApi, async (event: Electron.IpcMainEvent, arg: IApiAr
|
||||
analytics.registerPreloadWindow(event.sender);
|
||||
break;
|
||||
case apiCmds.setCloudConfig:
|
||||
const { podLevelEntitlements, acpFeatureLevelEntitlements, pmpEntitlements, ...rest } = arg.cloudConfig as ICloudConfig;
|
||||
if (podLevelEntitlements && podLevelEntitlements.autoLaunchPath && podLevelEntitlements.autoLaunchPath.match(/\\\\/g)) {
|
||||
podLevelEntitlements.autoLaunchPath = podLevelEntitlements.autoLaunchPath.replace(/\\+/g, '\\');
|
||||
const {
|
||||
podLevelEntitlements,
|
||||
acpFeatureLevelEntitlements,
|
||||
pmpEntitlements,
|
||||
...rest
|
||||
} = arg.cloudConfig as ICloudConfig;
|
||||
if (
|
||||
podLevelEntitlements &&
|
||||
podLevelEntitlements.autoLaunchPath &&
|
||||
podLevelEntitlements.autoLaunchPath.match(/\\\\/g)
|
||||
) {
|
||||
podLevelEntitlements.autoLaunchPath = podLevelEntitlements.autoLaunchPath.replace(
|
||||
/\\+/g,
|
||||
'\\',
|
||||
);
|
||||
}
|
||||
logger.info('main-api-handler: ignored other values from SFE', rest);
|
||||
await config.updateCloudConfig({ podLevelEntitlements, acpFeatureLevelEntitlements, pmpEntitlements });
|
||||
await config.updateCloudConfig({
|
||||
podLevelEntitlements,
|
||||
acpFeatureLevelEntitlements,
|
||||
pmpEntitlements,
|
||||
});
|
||||
await updateFeaturesForCloudConfig();
|
||||
if (windowHandler.appMenu) {
|
||||
windowHandler.appMenu.buildMenu();
|
||||
@@ -225,5 +268,5 @@ ipcMain.on(apiName.symphonyApi, async (event: Electron.IpcMainEvent, arg: IApiAr
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -18,12 +18,17 @@ import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||
// Set automatic period substitution to false because of a bug in draft js on the client app
|
||||
// See https://perzoinc.atlassian.net/browse/SDA-2215 for more details
|
||||
if (isMac) {
|
||||
systemPreferences.setUserDefault('NSAutomaticPeriodSubstitutionEnabled', 'string', 'false');
|
||||
systemPreferences.setUserDefault(
|
||||
'NSAutomaticPeriodSubstitutionEnabled',
|
||||
'string',
|
||||
'false',
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`App started with the args ${JSON.stringify(process.argv)}`);
|
||||
|
||||
const allowMultiInstance: string | boolean = getCommandLineArgs(process.argv, '--multiInstance', true) || isDevEnv;
|
||||
const allowMultiInstance: string | boolean =
|
||||
getCommandLineArgs(process.argv, '--multiInstance', true) || isDevEnv;
|
||||
let isAppAlreadyOpen: boolean = false;
|
||||
|
||||
// Setting the env path child_process issue https://github.com/electron/electron/issues/7688
|
||||
@@ -31,7 +36,7 @@ let isAppAlreadyOpen: boolean = false;
|
||||
try {
|
||||
const paths = await shellPath();
|
||||
if (paths) {
|
||||
return process.env.PATH = paths;
|
||||
return (process.env.PATH = paths);
|
||||
}
|
||||
if (isMac) {
|
||||
process.env.PATH = [
|
||||
@@ -72,7 +77,9 @@ if (!isDevEnv) {
|
||||
let oneStart = false;
|
||||
const startApplication = async () => {
|
||||
if (config.isFirstTimeLaunch()) {
|
||||
logger.info(`main: This is a first time launch! will update config and handle auto launch`);
|
||||
logger.info(
|
||||
`main: This is a first time launch! will update config and handle auto launch`,
|
||||
);
|
||||
await config.setUpFirstTimeLaunch();
|
||||
if (!isLinux) {
|
||||
await autoLaunchInstance.handleAutoLaunch();
|
||||
@@ -83,7 +90,9 @@ const startApplication = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('main: app is ready, performing initial checks oneStart: ' + oneStart);
|
||||
logger.info(
|
||||
'main: app is ready, performing initial checks oneStart: ' + oneStart,
|
||||
);
|
||||
oneStart = true;
|
||||
createAppCacheFile();
|
||||
// Picks global config values and updates them in the user config
|
||||
@@ -95,18 +104,24 @@ const startApplication = async () => {
|
||||
|
||||
// Handle multiple/single instances
|
||||
if (!allowMultiInstance) {
|
||||
logger.info('main: Multiple instances are not allowed, requesting lock', { allowMultiInstance });
|
||||
logger.info('main: Multiple instances are not allowed, requesting lock', {
|
||||
allowMultiInstance,
|
||||
});
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
// quit if another instance is already running, ignore for dev env or if app was started with multiInstance flag
|
||||
if (!gotTheLock) {
|
||||
logger.info(`main: got the lock hence closing the new instance`, { gotTheLock });
|
||||
logger.info(`main: got the lock hence closing the new instance`, {
|
||||
gotTheLock,
|
||||
});
|
||||
app.exit();
|
||||
} else {
|
||||
logger.info(`main: Creating the first instance of the application`);
|
||||
app.on('second-instance', (_event, argv) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
logger.info(`main: We've got a second instance of the app, will check if it's allowed and exit if not`);
|
||||
logger.info(
|
||||
`main: We've got a second instance of the app, will check if it's allowed and exit if not`,
|
||||
);
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
if (isMac) {
|
||||
@@ -125,7 +140,9 @@ if (!allowMultiInstance) {
|
||||
startApplication();
|
||||
}
|
||||
} else {
|
||||
logger.info(`main: multi instance allowed, creating second instance`, { allowMultiInstance });
|
||||
logger.info(`main: multi instance allowed, creating second instance`, {
|
||||
allowMultiInstance,
|
||||
});
|
||||
startApplication();
|
||||
}
|
||||
|
||||
@@ -149,7 +166,7 @@ app.on('quit', () => {
|
||||
/**
|
||||
* Cleans up reference before quiting
|
||||
*/
|
||||
app.on('before-quit', () => windowHandler.willQuitApp = true);
|
||||
app.on('before-quit', () => (windowHandler.willQuitApp = true));
|
||||
|
||||
/**
|
||||
* Is triggered when the application is launched
|
||||
@@ -160,7 +177,9 @@ app.on('before-quit', () => windowHandler.willQuitApp = true);
|
||||
app.on('activate', () => {
|
||||
const mainWindow: ICustomBrowserWindow | null = windowHandler.getMainWindow();
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
logger.info(`main: main window not existing or destroyed, creating a new instance of the main window!`);
|
||||
logger.info(
|
||||
`main: main window not existing or destroyed, creating a new instance of the main window!`,
|
||||
);
|
||||
startApplication();
|
||||
return;
|
||||
}
|
||||
@@ -174,6 +193,8 @@ app.on('activate', () => {
|
||||
* This event is emitted only on macOS at this moment
|
||||
*/
|
||||
app.on('open-url', (_event, url) => {
|
||||
logger.info(`main: we got a protocol request with url ${url}! processing the request!`);
|
||||
logger.info(
|
||||
`main: we got a protocol request with url ${url}! processing the request!`,
|
||||
);
|
||||
protocolHandler.sendProtocol(url);
|
||||
});
|
||||
|
||||
@@ -32,7 +32,9 @@ class MemoryMonitor {
|
||||
*/
|
||||
public setMemoryInfo(memoryInfo: Electron.ProcessMemoryInfo): void {
|
||||
this.memoryInfo = memoryInfo;
|
||||
logger.info(`memory-monitor: setting memory info to ${JSON.stringify(memoryInfo)}`);
|
||||
logger.info(
|
||||
`memory-monitor: setting memory info to ${JSON.stringify(memoryInfo)}`,
|
||||
);
|
||||
this.validateMemory();
|
||||
}
|
||||
|
||||
@@ -60,17 +62,23 @@ class MemoryMonitor {
|
||||
*/
|
||||
private validateMemory(): void {
|
||||
logger.info(`memory-monitor: validating memory refresh conditions`);
|
||||
const { memoryRefresh } = config.getConfigFields([ 'memoryRefresh' ]);
|
||||
const { memoryRefresh } = config.getConfigFields(['memoryRefresh']);
|
||||
if (memoryRefresh !== CloudConfigDataTypes.ENABLED) {
|
||||
logger.info(`memory-monitor: memory reload is disabled in the config, not going to refresh!`);
|
||||
logger.info(
|
||||
`memory-monitor: memory reload is disabled in the config, not going to refresh!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const time = electron.powerMonitor.getSystemIdleTime();
|
||||
const idleTime = time * 1000;
|
||||
// for MacOS use private else use residentSet
|
||||
const memoryConsumption = isMac ? (this.memoryInfo && this.memoryInfo.private) : (this.memoryInfo && this.memoryInfo.residentSet);
|
||||
logger.info(`memory-monitor: Checking different conditions to see if we should auto reload the app`);
|
||||
const memoryConsumption = isMac
|
||||
? this.memoryInfo && this.memoryInfo.private
|
||||
: this.memoryInfo && this.memoryInfo.residentSet;
|
||||
logger.info(
|
||||
`memory-monitor: Checking different conditions to see if we should auto reload the app`,
|
||||
);
|
||||
|
||||
logger.info(`memory-monitor: Is in meeting: `, this.isInMeeting);
|
||||
logger.info(`memory-monitor: Is Network online: `, windowHandler.isOnline);
|
||||
@@ -79,37 +87,55 @@ class MemoryMonitor {
|
||||
logger.info(`memory-monitor: Last Reload time: `, this.lastReloadTime);
|
||||
|
||||
if (this.isInMeeting) {
|
||||
logger.info(`memory-monitor: NOT RELOADING -> User is currently in a meeting. Meeting status from client: `, this.isInMeeting);
|
||||
logger.info(
|
||||
`memory-monitor: NOT RELOADING -> User is currently in a meeting. Meeting status from client: `,
|
||||
this.isInMeeting,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!windowHandler.isOnline) {
|
||||
logger.info(`memory-monitor: NOT RELOADING -> Not connected to network. Network status: `, windowHandler.isOnline);
|
||||
logger.info(
|
||||
`memory-monitor: NOT RELOADING -> Not connected to network. Network status: `,
|
||||
windowHandler.isOnline,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(memoryConsumption && memoryConsumption > this.memoryThreshold)) {
|
||||
logger.info(`memory-monitor: NOT RELOADING -> Memory consumption ${memoryConsumption} is lesser than the threshold ${this.memoryThreshold}`);
|
||||
logger.info(
|
||||
`memory-monitor: NOT RELOADING -> Memory consumption ${memoryConsumption} is lesser than the threshold ${this.memoryThreshold}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(idleTime > this.maxIdleTime)) {
|
||||
logger.info(`memory-monitor: NOT RELOADING -> User is not idle for: `, idleTime);
|
||||
logger.info(
|
||||
`memory-monitor: NOT RELOADING -> User is not idle for: `,
|
||||
idleTime,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.canReload) {
|
||||
logger.info(`memory-monitor: NOT RELOADING -> Already refreshed at: `, this.lastReloadTime);
|
||||
logger.info(
|
||||
`memory-monitor: NOT RELOADING -> Already refreshed at: `,
|
||||
this.lastReloadTime,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (!(mainWindow && windowExists(mainWindow))) {
|
||||
logger.info(`memory-monitor: NOT RELOADING -> Main window doesn't exist!`);
|
||||
logger.info(
|
||||
`memory-monitor: NOT RELOADING -> Main window doesn't exist!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`memory-monitor: RELOADING -> auto reloading the app as all the conditions are satisfied`);
|
||||
logger.info(
|
||||
`memory-monitor: RELOADING -> auto reloading the app as all the conditions are satisfied`,
|
||||
);
|
||||
|
||||
windowHandler.setIsAutoReload(true);
|
||||
mainWindow.reload();
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Notification, NotificationConstructorOptions } from 'electron';
|
||||
|
||||
import { ElectronNotificationData, INotificationData, NotificationActions } from '../../common/api-interface';
|
||||
import {
|
||||
ElectronNotificationData,
|
||||
INotificationData,
|
||||
NotificationActions,
|
||||
} from '../../common/api-interface';
|
||||
|
||||
export class ElectronNotification extends Notification {
|
||||
private callback: (
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ElectronNotificationData, INotificationData, NotificationActions } from '../../common/api-interface';
|
||||
import {
|
||||
ElectronNotificationData,
|
||||
INotificationData,
|
||||
NotificationActions,
|
||||
} from '../../common/api-interface';
|
||||
import { isWindowsOS } from '../../common/env';
|
||||
import { notification } from '../../renderer/notification';
|
||||
import { windowHandler } from '../window-handler';
|
||||
@@ -27,14 +31,19 @@ class NotificationHelper {
|
||||
|
||||
// This is replace notification with same tag
|
||||
if (this.activeElectronNotification.has(options.tag)) {
|
||||
const electronNotification = this.activeElectronNotification.get(options.tag);
|
||||
const electronNotification = this.activeElectronNotification.get(
|
||||
options.tag,
|
||||
);
|
||||
if (electronNotification) {
|
||||
electronNotification.close();
|
||||
}
|
||||
this.activeElectronNotification.delete(options.tag);
|
||||
}
|
||||
|
||||
const electronToast = new ElectronNotification(options, this.notificationCallback);
|
||||
const electronToast = new ElectronNotification(
|
||||
options,
|
||||
this.notificationCallback,
|
||||
);
|
||||
this.electronNotification.set(options.id, electronToast);
|
||||
this.activeElectronNotification.set(options.tag, electronToast);
|
||||
electronToast.show();
|
||||
@@ -73,7 +82,11 @@ class NotificationHelper {
|
||||
) {
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (mainWindow && windowExists(mainWindow) && mainWindow.webContents) {
|
||||
mainWindow.webContents.send('notification-actions', { event, data, notificationData });
|
||||
mainWindow.webContents.send('notification-actions', {
|
||||
event,
|
||||
data,
|
||||
notificationData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,24 @@ import { logger } from '../common/logger';
|
||||
import { CloudConfigDataTypes, config, IConfig } from './config-handler';
|
||||
|
||||
export const handlePerformanceSettings = () => {
|
||||
const { customFlags } = config.getCloudConfigFields([ 'customFlags' ]) as IConfig;
|
||||
const { disableThrottling } = config.getCloudConfigFields([ 'disableThrottling' ]) as any;
|
||||
const { customFlags } = config.getCloudConfigFields([
|
||||
'customFlags',
|
||||
]) as IConfig;
|
||||
const { disableThrottling } = config.getCloudConfigFields([
|
||||
'disableThrottling',
|
||||
]) as any;
|
||||
|
||||
if ((customFlags && customFlags.disableThrottling === CloudConfigDataTypes.ENABLED) || disableThrottling === CloudConfigDataTypes.ENABLED) {
|
||||
if (
|
||||
(customFlags &&
|
||||
customFlags.disableThrottling === CloudConfigDataTypes.ENABLED) ||
|
||||
disableThrottling === CloudConfigDataTypes.ENABLED
|
||||
) {
|
||||
logger.info(`perf-handler: Disabling power throttling!`);
|
||||
powerSaveBlocker.start('prevent-display-sleep');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`perf-handler: Power throttling enabled as config is not set to override power throttling!`);
|
||||
logger.info(
|
||||
`perf-handler: Power throttling enabled as config is not set to override power throttling!`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ enum protocol {
|
||||
}
|
||||
|
||||
class ProtocolHandler {
|
||||
|
||||
private static isValidProtocolUri = (uri: string): boolean => !!(uri && uri.startsWith(protocol.SymphonyProtocol));
|
||||
private static isValidProtocolUri = (uri: string): boolean =>
|
||||
!!(uri && uri.startsWith(protocol.SymphonyProtocol));
|
||||
|
||||
private preloadWebContents: Electron.WebContents | null = null;
|
||||
private protocolUri: string | null = null;
|
||||
@@ -26,10 +26,14 @@ class ProtocolHandler {
|
||||
*/
|
||||
public setPreloadWebContents(webContents: Electron.WebContents): void {
|
||||
this.preloadWebContents = webContents;
|
||||
logger.info(`protocol handler: SFE is active and we have a valid protocol window with web contents!`);
|
||||
logger.info(
|
||||
`protocol handler: SFE is active and we have a valid protocol window with web contents!`,
|
||||
);
|
||||
if (this.protocolUri) {
|
||||
this.sendProtocol(this.protocolUri);
|
||||
logger.info(`protocol handler: we have a cached url ${this.protocolUri}, so, processed the request to SFE!`);
|
||||
logger.info(
|
||||
`protocol handler: we have a cached url ${this.protocolUri}, so, processed the request to SFE!`,
|
||||
);
|
||||
this.protocolUri = null;
|
||||
}
|
||||
}
|
||||
@@ -42,12 +46,18 @@ class ProtocolHandler {
|
||||
*/
|
||||
public sendProtocol(url: string, isAppRunning: boolean = true): void {
|
||||
if (url && url.length > 2083) {
|
||||
logger.info(`protocol-handler: protocol handler url length is greater than 2083, not performing any action!`);
|
||||
logger.info(
|
||||
`protocol-handler: protocol handler url length is greater than 2083, not performing any action!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
logger.info(`protocol handler: processing protocol request for the url ${url}!`);
|
||||
logger.info(
|
||||
`protocol handler: processing protocol request for the url ${url}!`,
|
||||
);
|
||||
if (!this.preloadWebContents || !isAppRunning) {
|
||||
logger.info(`protocol handler: app was started from the protocol request. Caching the URL ${url}!`);
|
||||
logger.info(
|
||||
`protocol handler: app was started from the protocol request. Caching the URL ${url}!`,
|
||||
);
|
||||
this.protocolUri = url;
|
||||
return;
|
||||
}
|
||||
@@ -58,7 +68,9 @@ class ProtocolHandler {
|
||||
}
|
||||
|
||||
if (ProtocolHandler.isValidProtocolUri(url)) {
|
||||
logger.info(`protocol handler: our protocol request is a valid url ${url}! sending request to SFE for further action!`);
|
||||
logger.info(
|
||||
`protocol handler: our protocol request is a valid url ${url}! sending request to SFE for further action!`,
|
||||
);
|
||||
this.preloadWebContents.send('protocol-action', url);
|
||||
}
|
||||
}
|
||||
@@ -70,9 +82,15 @@ class ProtocolHandler {
|
||||
*/
|
||||
public processArgv(argv?: string[], isAppAlreadyOpen: boolean = false): void {
|
||||
logger.info(`protocol handler: processing protocol args!`);
|
||||
const protocolUriFromArgv = getCommandLineArgs(argv || process.argv, protocol.SymphonyProtocol, false);
|
||||
const protocolUriFromArgv = getCommandLineArgs(
|
||||
argv || process.argv,
|
||||
protocol.SymphonyProtocol,
|
||||
false,
|
||||
);
|
||||
if (protocolUriFromArgv) {
|
||||
logger.info(`protocol handler: we have a protocol request for the url ${protocolUriFromArgv}!`);
|
||||
logger.info(
|
||||
`protocol handler: we have a protocol request for the url ${protocolUriFromArgv}!`,
|
||||
);
|
||||
this.sendProtocol(protocolUriFromArgv, isAppAlreadyOpen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,12 @@ import { logger } from '../common/logger';
|
||||
* @param fileExtensions {Array} array of file ext
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
const generateArchiveForDirectory = (source: string, destination: string, fileExtensions: string[], retrievedLogs: ILogs[]): Promise<void> => {
|
||||
|
||||
const generateArchiveForDirectory = (
|
||||
source: string,
|
||||
destination: string,
|
||||
fileExtensions: string[],
|
||||
retrievedLogs: ILogs[],
|
||||
): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info(`reports-handler: generating archive for directory ${source}`);
|
||||
const output = fs.createWriteStream(destination);
|
||||
@@ -42,7 +46,9 @@ const generateArchiveForDirectory = (source: string, destination: string, fileEx
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
}
|
||||
logger.error(`reports-handler: error archiving directory for ${source} with error ${err}`);
|
||||
logger.error(
|
||||
`reports-handler: error archiving directory for ${source} with error ${err}`,
|
||||
);
|
||||
return reject(err);
|
||||
});
|
||||
|
||||
@@ -67,8 +73,8 @@ const generateArchiveForDirectory = (source: string, destination: string, fileEx
|
||||
|
||||
for (const logs of retrievedLogs) {
|
||||
for (const logFile of logs.logFiles) {
|
||||
const file = path.join( source, logFile.filename );
|
||||
fs.writeFileSync(file, logFile.contents );
|
||||
const file = path.join(source, logFile.filename);
|
||||
fs.writeFileSync(file, logFile.contents);
|
||||
archive.file(file, { name: 'logs/' + logFile.filename });
|
||||
filesForCleanup.push(file);
|
||||
}
|
||||
@@ -82,14 +88,17 @@ let logWebContents: Electron.WebContents;
|
||||
const logTypes: string[] = [];
|
||||
const receivedLogs: ILogs[] = [];
|
||||
|
||||
export const registerLogRetriever = (sender: Electron.WebContents, logName: string): void => {
|
||||
export const registerLogRetriever = (
|
||||
sender: Electron.WebContents,
|
||||
logName: string,
|
||||
): void => {
|
||||
logWebContents = sender;
|
||||
logTypes.push( logName );
|
||||
logTypes.push(logName);
|
||||
};
|
||||
|
||||
export const collectLogs = (): void => {
|
||||
receivedLogs.length = 0;
|
||||
logWebContents.send('collect-logs' );
|
||||
logWebContents.send('collect-logs');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -99,12 +108,16 @@ export const collectLogs = (): void => {
|
||||
* Windows - AppData\Roaming\Symphony\logs
|
||||
*/
|
||||
export const packageLogs = (retrievedLogs: ILogs[]): void => {
|
||||
const FILE_EXTENSIONS = [ '.log' ];
|
||||
const FILE_EXTENSIONS = ['.log'];
|
||||
const MAC_LOGS_PATH = '/Library/Logs/Symphony/';
|
||||
const LINUX_LOGS_PATH = '/.config/Symphony/';
|
||||
const WINDOWS_LOGS_PATH = '\\AppData\\Local\\Symphony\\Symphony\\logs';
|
||||
|
||||
const logsPath = isMac ? MAC_LOGS_PATH : isLinux ? LINUX_LOGS_PATH : WINDOWS_LOGS_PATH;
|
||||
const logsPath = isMac
|
||||
? MAC_LOGS_PATH
|
||||
: isLinux
|
||||
? LINUX_LOGS_PATH
|
||||
: WINDOWS_LOGS_PATH;
|
||||
const source = app.getPath('home') + logsPath;
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
|
||||
@@ -117,11 +130,16 @@ export const packageLogs = (retrievedLogs: ILogs[]): void => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const destPath = (isMac || isLinux) ? '/logs_symphony_' : '\\logs_symphony_';
|
||||
const destPath = isMac || isLinux ? '/logs_symphony_' : '\\logs_symphony_';
|
||||
const timestamp = new Date().getTime();
|
||||
const destination = app.getPath('downloads') + destPath + timestamp + '.zip';
|
||||
|
||||
generateArchiveForDirectory(source, destination, FILE_EXTENSIONS, retrievedLogs)
|
||||
generateArchiveForDirectory(
|
||||
source,
|
||||
destination,
|
||||
FILE_EXTENSIONS,
|
||||
retrievedLogs,
|
||||
)
|
||||
.then(() => {
|
||||
shell.showItemInFolder(destination);
|
||||
})
|
||||
@@ -166,12 +184,17 @@ export const exportLogs = (): void => {
|
||||
* Compress and export crash dump stored under system crashes directory
|
||||
*/
|
||||
export const exportCrashDumps = (): void => {
|
||||
const FILE_EXTENSIONS = isMac ? [ '.dmp' ] : [ '.dmp', '.txt' ];
|
||||
const FILE_EXTENSIONS = isMac ? ['.dmp'] : ['.dmp', '.txt'];
|
||||
const crashesDirectory = (electron.crashReporter as any).getCrashesDirectory();
|
||||
const source = isMac ? crashesDirectory + '/completed' : crashesDirectory;
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
|
||||
if (!fs.existsSync(source) || fs.readdirSync(source).length === 0 && focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
if (
|
||||
!fs.existsSync(source) ||
|
||||
(fs.readdirSync(source).length === 0 &&
|
||||
focusedWindow &&
|
||||
!focusedWindow.isDestroyed())
|
||||
) {
|
||||
electron.dialog.showMessageBox(focusedWindow as BrowserWindow, {
|
||||
message: i18n.t('No crashes available to share')(),
|
||||
title: i18n.t('Failed!')(),
|
||||
@@ -180,10 +203,12 @@ export const exportCrashDumps = (): void => {
|
||||
return;
|
||||
}
|
||||
|
||||
const destPath = (isMac || isLinux) ? '/crashes_symphony_' : '\\crashes_symphony_';
|
||||
const destPath =
|
||||
isMac || isLinux ? '/crashes_symphony_' : '\\crashes_symphony_';
|
||||
const timestamp = new Date().getTime();
|
||||
|
||||
const destination = electron.app.getPath('downloads') + destPath + timestamp + '.zip';
|
||||
const destination =
|
||||
electron.app.getPath('downloads') + destPath + timestamp + '.zip';
|
||||
|
||||
generateArchiveForDirectory(source, destination, FILE_EXTENSIONS, [])
|
||||
.then(() => {
|
||||
@@ -192,7 +217,9 @@ export const exportCrashDumps = (): void => {
|
||||
.catch((err) => {
|
||||
if (focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
electron.dialog.showMessageBox(focusedWindow, {
|
||||
message: `${i18n.t('Unable to generate crash reports due to ')()} ${err}`,
|
||||
message: `${i18n.t(
|
||||
'Unable to generate crash reports due to ',
|
||||
)()} ${err}`,
|
||||
title: i18n.t('Failed!')(),
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
@@ -15,7 +15,11 @@ import {
|
||||
} from '../common/env';
|
||||
import { i18n } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
import { analytics, AnalyticsElements, ScreenSnippetActionTypes } from './analytics-handler';
|
||||
import {
|
||||
analytics,
|
||||
AnalyticsElements,
|
||||
ScreenSnippetActionTypes,
|
||||
} from './analytics-handler';
|
||||
import { updateAlwaysOnTop } from './window-actions';
|
||||
import { windowHandler } from './window-handler';
|
||||
import { windowExists } from './window-utils';
|
||||
@@ -53,9 +57,21 @@ class ScreenSnippet {
|
||||
this.captureUtil = '/usr/bin/gnome-screenshot';
|
||||
}
|
||||
|
||||
ipcMain.on('snippet-analytics-data', async (_event, eventData: { element: AnalyticsElements, type: ScreenSnippetActionTypes }) => {
|
||||
analytics.track({ element: eventData.element, action_type: eventData.type });
|
||||
ipcMain.on(
|
||||
'snippet-analytics-data',
|
||||
async (
|
||||
_event,
|
||||
eventData: {
|
||||
element: AnalyticsElements;
|
||||
type: ScreenSnippetActionTypes;
|
||||
},
|
||||
) => {
|
||||
analytics.track({
|
||||
element: eventData.element,
|
||||
action_type: eventData.type,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,9 +126,15 @@ class ScreenSnippet {
|
||||
try {
|
||||
await this.execCmd(this.captureUtil, this.captureUtilArgs);
|
||||
if (windowHandler.isMana) {
|
||||
logger.info('screen-snippet-handler: Attempting to extract image dimensions from: ' + this.outputFilePath);
|
||||
logger.info(
|
||||
'screen-snippet-handler: Attempting to extract image dimensions from: ' +
|
||||
this.outputFilePath,
|
||||
);
|
||||
const dimensions = this.getImageDimensions(this.outputFilePath);
|
||||
logger.info('screen-snippet-handler: Extracted dimensions from image: ' + JSON.stringify(dimensions));
|
||||
logger.info(
|
||||
'screen-snippet-handler: Extracted dimensions from image: ' +
|
||||
JSON.stringify(dimensions),
|
||||
);
|
||||
if (!dimensions) {
|
||||
logger.error('screen-snippet-handler: Could not get image size');
|
||||
return;
|
||||
@@ -261,7 +283,9 @@ class ScreenSnippet {
|
||||
* Gets the dimensions of an image
|
||||
* @param filePath path to file to get image dimensions of
|
||||
*/
|
||||
private getImageDimensions(filePath: string): {
|
||||
private getImageDimensions(
|
||||
filePath: string,
|
||||
): {
|
||||
height: number;
|
||||
width: number;
|
||||
} {
|
||||
@@ -276,7 +300,12 @@ class ScreenSnippet {
|
||||
* @param webContents A browser window's web contents object
|
||||
*/
|
||||
private uploadSnippet(webContents: Electron.webContents) {
|
||||
ipcMain.once('upload-snippet', async (_event, snippetData: { screenSnippetPath: string, mergedImageData: string }) => {
|
||||
ipcMain.once(
|
||||
'upload-snippet',
|
||||
async (
|
||||
_event,
|
||||
snippetData: { screenSnippetPath: string; mergedImageData: string },
|
||||
) => {
|
||||
try {
|
||||
windowHandler.closeSnippingToolWindow();
|
||||
const [type, data] = snippetData.mergedImageData.split(',');
|
||||
@@ -285,7 +314,9 @@ class ScreenSnippet {
|
||||
data,
|
||||
type,
|
||||
};
|
||||
logger.info('screen-snippet-handler: Snippet uploaded correctly, sending payload to SFE');
|
||||
logger.info(
|
||||
'screen-snippet-handler: Snippet uploaded correctly, sending payload to SFE',
|
||||
);
|
||||
webContents.send('screen-snippet-data', payload);
|
||||
await this.verifyAndUpdateAlwaysOnTop();
|
||||
} catch (error) {
|
||||
@@ -294,7 +325,8 @@ class ScreenSnippet {
|
||||
`screen-snippet-handler: upload of screen capture failed with error: ${error}!`,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { app, MenuItem } from 'electron';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ContextMenuBuilder, DictionarySync, SpellCheckHandler } from 'electron-spellchecker';
|
||||
import {
|
||||
ContextMenuBuilder,
|
||||
DictionarySync,
|
||||
SpellCheckHandler,
|
||||
} from 'electron-spellchecker';
|
||||
import { isDevEnv, isMac } from '../common/env';
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
@@ -20,7 +24,11 @@ export class SpellChecker {
|
||||
this.dictionaryPath = path.join(app.getAppPath(), dictionariesDirName);
|
||||
} else {
|
||||
const execPath = path.dirname(app.getPath('exe'));
|
||||
this.dictionaryPath = path.join(execPath, (isMac) ? '..' : '', dictionariesDirName);
|
||||
this.dictionaryPath = path.join(
|
||||
execPath,
|
||||
isMac ? '..' : '',
|
||||
dictionariesDirName,
|
||||
);
|
||||
}
|
||||
this.dictionarySync = new DictionarySync(this.dictionaryPath);
|
||||
this.spellCheckHandler = new SpellCheckHandler(this.dictionarySync);
|
||||
@@ -42,10 +50,17 @@ export class SpellChecker {
|
||||
* @param webContents {Electron.WebContents}
|
||||
*/
|
||||
public attachToWebContents(webContents: Electron.WebContents): void {
|
||||
const contextMenuBuilder = new ContextMenuBuilder(this.spellCheckHandler, webContents, false, this.processMenu);
|
||||
const contextMenuBuilder = new ContextMenuBuilder(
|
||||
this.spellCheckHandler,
|
||||
webContents,
|
||||
false,
|
||||
this.processMenu,
|
||||
);
|
||||
contextMenuBuilder.setAlternateStringFormatter(this.getStringTable());
|
||||
this.locale = i18n.getLocale();
|
||||
logger.info(`spell-check-handler: Building context menu with locale ${this.locale}!`);
|
||||
logger.info(
|
||||
`spell-check-handler: Building context menu with locale ${this.locale}!`,
|
||||
);
|
||||
const contextMenuListener = (_event, info) => {
|
||||
if (this.locale !== i18n.getLocale()) {
|
||||
contextMenuBuilder.setAlternateStringFormatter(this.getStringTable());
|
||||
@@ -86,7 +101,10 @@ export class SpellChecker {
|
||||
copyImage: () => i18n.t('Copy Image', namespace)(),
|
||||
addToDictionary: () => i18n.t('Add to Dictionary', namespace)(),
|
||||
lookUpDefinition: (lookup) => {
|
||||
const formattedString = i18n.t('Look Up {searchText}', namespace)( { searchText: lookup.word });
|
||||
const formattedString = i18n.t(
|
||||
'Look Up {searchText}',
|
||||
namespace,
|
||||
)({ searchText: lookup.word });
|
||||
return formattedString || `Look Up ${lookup.word}`;
|
||||
},
|
||||
searchGoogle: () => i18n.t('Search with Google', namespace)(),
|
||||
@@ -118,13 +136,15 @@ export class SpellChecker {
|
||||
|
||||
if (!isLink) {
|
||||
menu.append(new MenuItem({ type: 'separator' }));
|
||||
menu.append(new MenuItem({
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
label: i18n.t('Reload')(),
|
||||
click: (_menuItem, browserWindow , _event) => {
|
||||
click: (_menuItem, browserWindow, _event) => {
|
||||
reloadWindow(browserWindow as ICustomBrowserWindow);
|
||||
},
|
||||
}));
|
||||
}),
|
||||
);
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as os from 'os';
|
||||
import { logger } from '../common/logger';
|
||||
|
||||
export class AppStats {
|
||||
|
||||
private MB_IN_BYTES = 1048576;
|
||||
|
||||
/**
|
||||
@@ -22,35 +21,39 @@ export class AppStats {
|
||||
* Logs system related statistics
|
||||
*/
|
||||
private logSystemStats() {
|
||||
logger.info(`-----------------Gathering system information-----------------`);
|
||||
logger.info( `Network Info -> `, os.networkInterfaces());
|
||||
logger.info( `CPU Info -> `, os.cpus());
|
||||
logger.info( `Operating System -> `, os.type());
|
||||
logger.info( `Platform -> `, os.platform());
|
||||
logger.info( `Architecture -> `, os.arch());
|
||||
logger.info( `Hostname -> `, os.hostname());
|
||||
logger.info( `Temp Directory -> `, os.tmpdir());
|
||||
logger.info( `Home Directory -> `, os.homedir());
|
||||
logger.info( `Total Memory (MB) -> `, os.totalmem() / this.MB_IN_BYTES);
|
||||
logger.info( `Free Memory (MB) -> `, os.freemem() / this.MB_IN_BYTES);
|
||||
logger.info( `Load Average -> `, os.loadavg());
|
||||
logger.info( `Uptime -> `, os.uptime());
|
||||
logger.info( `User Info (OS Returned) -> `, os.userInfo());
|
||||
logger.info(
|
||||
`-----------------Gathering system information-----------------`,
|
||||
);
|
||||
logger.info(`Network Info -> `, os.networkInterfaces());
|
||||
logger.info(`CPU Info -> `, os.cpus());
|
||||
logger.info(`Operating System -> `, os.type());
|
||||
logger.info(`Platform -> `, os.platform());
|
||||
logger.info(`Architecture -> `, os.arch());
|
||||
logger.info(`Hostname -> `, os.hostname());
|
||||
logger.info(`Temp Directory -> `, os.tmpdir());
|
||||
logger.info(`Home Directory -> `, os.homedir());
|
||||
logger.info(`Total Memory (MB) -> `, os.totalmem() / this.MB_IN_BYTES);
|
||||
logger.info(`Free Memory (MB) -> `, os.freemem() / this.MB_IN_BYTES);
|
||||
logger.info(`Load Average -> `, os.loadavg());
|
||||
logger.info(`Uptime -> `, os.uptime());
|
||||
logger.info(`User Info (OS Returned) -> `, os.userInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs GPU Statistics
|
||||
*/
|
||||
private logGPUStats() {
|
||||
logger.info( `-----------------Gathering GPU information-----------------`);
|
||||
logger.info( `GPU Feature Status -> `, app.getGPUFeatureStatus());
|
||||
logger.info(`-----------------Gathering GPU information-----------------`);
|
||||
logger.info(`GPU Feature Status -> `, app.getGPUFeatureStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs Configuration Data
|
||||
*/
|
||||
private logConfigurationData() {
|
||||
logger.info(`-----------------App Configuration Information-----------------`);
|
||||
logger.info(
|
||||
`-----------------App Configuration Information-----------------`,
|
||||
);
|
||||
logger.info(`stats: Is app packaged? ${app.isPackaged}`);
|
||||
}
|
||||
|
||||
@@ -61,7 +64,10 @@ export class AppStats {
|
||||
logger.info(`-----------------Gathering App Metrics-----------------`);
|
||||
const metrics = app.getAppMetrics();
|
||||
metrics.forEach((metric) => {
|
||||
logger.info(`stats: PID -> ${metric.pid}, Type -> ${metric.type}, CPU Usage -> `, metric.cpu);
|
||||
logger.info(
|
||||
`stats: PID -> ${metric.pid}, Type -> ${metric.type}, CPU Usage -> `,
|
||||
metric.cpu,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,10 +76,23 @@ export class AppStats {
|
||||
*/
|
||||
private logAppEvents() {
|
||||
const events = [
|
||||
'will-finish-launching', 'ready', 'window-all-closed', 'before-quit', 'will-quit', 'quit',
|
||||
'open-file', 'open-url', 'activate',
|
||||
'browser-window-created', 'web-contents-created', 'certificate-error', 'login', 'gpu-process-crashed',
|
||||
'accessibility-support-changed', 'session-created', 'second-instance',
|
||||
'will-finish-launching',
|
||||
'ready',
|
||||
'window-all-closed',
|
||||
'before-quit',
|
||||
'will-quit',
|
||||
'quit',
|
||||
'open-file',
|
||||
'open-url',
|
||||
'activate',
|
||||
'browser-window-created',
|
||||
'web-contents-created',
|
||||
'certificate-error',
|
||||
'login',
|
||||
'gpu-process-crashed',
|
||||
'accessibility-support-changed',
|
||||
'session-created',
|
||||
'second-instance',
|
||||
];
|
||||
|
||||
events.forEach((appEvent: any) => {
|
||||
@@ -99,6 +118,4 @@ export class AppStats {
|
||||
|
||||
const appStats = new AppStats();
|
||||
|
||||
export {
|
||||
appStats,
|
||||
};
|
||||
export { appStats };
|
||||
|
||||
@@ -21,7 +21,9 @@ export const checkIfBuildExpired = (): boolean => {
|
||||
|
||||
const currentDate: Date = new Date();
|
||||
const expiryDate: Date = new Date(expiryTime);
|
||||
logger.info(`ttl-handler: Current Time: ${currentDate.getTime()}, Expiry Time: ${expiryDate.getTime()}`);
|
||||
logger.info(
|
||||
`ttl-handler: Current Time: ${currentDate.getTime()}, Expiry Time: ${expiryDate.getTime()}`,
|
||||
);
|
||||
|
||||
const buildExpired = currentDate.getTime() > expiryDate.getTime();
|
||||
return buildExpired;
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { net } from 'electron';
|
||||
import * as nodeURL from 'url';
|
||||
import { buildNumber, clientVersion, optionalDependencies, searchAPIVersion, sfeClientType, sfeVersion, version } from '../../package.json';
|
||||
import {
|
||||
buildNumber,
|
||||
clientVersion,
|
||||
optionalDependencies,
|
||||
searchAPIVersion,
|
||||
sfeClientType,
|
||||
sfeVersion,
|
||||
version,
|
||||
} from '../../package.json';
|
||||
import { logger } from '../common/logger';
|
||||
import { config, IGlobalConfig } from './config-handler';
|
||||
|
||||
@@ -25,7 +33,6 @@ interface IVersionInfo {
|
||||
}
|
||||
|
||||
class VersionHandler {
|
||||
|
||||
public versionInfo: IVersionInfo;
|
||||
private serverVersionInfo: any;
|
||||
private mainUrl;
|
||||
@@ -57,12 +64,18 @@ class VersionHandler {
|
||||
/**
|
||||
* Get Symphony version from the pod
|
||||
*/
|
||||
public getClientVersion(fetchFromServer: boolean = false, mainUrl?: string): Promise<IVersionInfo> {
|
||||
public getClientVersion(
|
||||
fetchFromServer: boolean = false,
|
||||
mainUrl?: string,
|
||||
): Promise<IVersionInfo> {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
if (this.serverVersionInfo && !fetchFromServer) {
|
||||
this.versionInfo.clientVersion = this.serverVersionInfo['Implementation-Version'] || this.versionInfo.clientVersion;
|
||||
this.versionInfo.buildNumber = this.serverVersionInfo['Implementation-Build'] || this.versionInfo.buildNumber;
|
||||
this.versionInfo.clientVersion =
|
||||
this.serverVersionInfo['Implementation-Version'] ||
|
||||
this.versionInfo.clientVersion;
|
||||
this.versionInfo.buildNumber =
|
||||
this.serverVersionInfo['Implementation-Build'] ||
|
||||
this.versionInfo.buildNumber;
|
||||
resolve(this.versionInfo);
|
||||
return;
|
||||
}
|
||||
@@ -71,15 +84,23 @@ class VersionHandler {
|
||||
this.mainUrl = mainUrl;
|
||||
}
|
||||
|
||||
const { url: podUrl }: IGlobalConfig = config.getGlobalConfigFields(['url']);
|
||||
const { url: podUrl }: IGlobalConfig = config.getGlobalConfigFields([
|
||||
'url',
|
||||
]);
|
||||
|
||||
if (!this.mainUrl || !nodeURL.parse(this.mainUrl)) {
|
||||
this.mainUrl = podUrl;
|
||||
}
|
||||
|
||||
if (!this.mainUrl) {
|
||||
logger.error(`version-handler: Unable to get pod url for getting version data from server! Setting defaults!`);
|
||||
logger.info(`version-handler: Setting defaults -> ${JSON.stringify(this.versionInfo)}`);
|
||||
logger.error(
|
||||
`version-handler: Unable to get pod url for getting version data from server! Setting defaults!`,
|
||||
);
|
||||
logger.info(
|
||||
`version-handler: Setting defaults -> ${JSON.stringify(
|
||||
this.versionInfo,
|
||||
)}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
return;
|
||||
}
|
||||
@@ -94,11 +115,12 @@ class VersionHandler {
|
||||
const versionApiPath = '/webcontroller/HealthCheck/version/advanced';
|
||||
|
||||
const url = `${protocol}//${hostname}${versionApiPath}`;
|
||||
logger.info(`version-handler: Trying to get version info for the URL: ${url}`);
|
||||
logger.info(
|
||||
`version-handler: Trying to get version info for the URL: ${url}`,
|
||||
);
|
||||
|
||||
const request = net.request(url);
|
||||
request.on('response', (res) => {
|
||||
|
||||
let body: string = '';
|
||||
res.on('data', (d: Buffer) => {
|
||||
body += d;
|
||||
@@ -107,27 +129,40 @@ class VersionHandler {
|
||||
res.on('end', () => {
|
||||
try {
|
||||
this.serverVersionInfo = JSON.parse(body)[0];
|
||||
this.versionInfo.clientVersion = this.serverVersionInfo['Implementation-Version'] || this.versionInfo.clientVersion;
|
||||
this.versionInfo.buildNumber = this.serverVersionInfo['Implementation-Build'] || this.versionInfo.buildNumber;
|
||||
logger.info(`version-handler: Updated version info from server! ${JSON.stringify(this.versionInfo)}`);
|
||||
this.versionInfo.clientVersion =
|
||||
this.serverVersionInfo['Implementation-Version'] ||
|
||||
this.versionInfo.clientVersion;
|
||||
this.versionInfo.buildNumber =
|
||||
this.serverVersionInfo['Implementation-Build'] ||
|
||||
this.versionInfo.buildNumber;
|
||||
logger.info(
|
||||
`version-handler: Updated version info from server! ${JSON.stringify(
|
||||
this.versionInfo,
|
||||
)}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
} catch (error) {
|
||||
logger.error(`version-handler: Error getting version data from the server! ${error}`);
|
||||
logger.error(
|
||||
`version-handler: Error getting version data from the server! ${error}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
res.on('error', (error: Error) => {
|
||||
logger.error(`version-handler: Error getting version data from the server! ${error}`);
|
||||
logger.error(
|
||||
`version-handler: Error getting version data from the server! ${error}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
return;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
request.on('error', (error: Error) => {
|
||||
logger.error(`version-handler: Error getting version data from the server! ${error}`);
|
||||
logger.error(
|
||||
`version-handler: Error getting version data from the server! ${error}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
return;
|
||||
});
|
||||
@@ -158,11 +193,12 @@ class VersionHandler {
|
||||
urlSfeVersion = `${protocol}//${hostname}/client/version.json`;
|
||||
this.versionInfo.sfeClientType = '1.5';
|
||||
}
|
||||
logger.info(`version-handler: Trying to get SFE version info for the URL: ${urlSfeVersion}`);
|
||||
logger.info(
|
||||
`version-handler: Trying to get SFE version info for the URL: ${urlSfeVersion}`,
|
||||
);
|
||||
|
||||
const requestSfeVersion = net.request(urlSfeVersion);
|
||||
requestSfeVersion.on('response', (res) => {
|
||||
|
||||
let body: string = '';
|
||||
res.on('data', (d: Buffer) => {
|
||||
body += d;
|
||||
@@ -175,27 +211,40 @@ class VersionHandler {
|
||||
|
||||
this.versionInfo.sfeVersion = this.sfeVersionInfo[key];
|
||||
|
||||
logger.info('version-handler: SFE-version: ' + this.sfeVersionInfo[key]);
|
||||
logger.info(
|
||||
'version-handler: SFE-version: ' + this.sfeVersionInfo[key],
|
||||
);
|
||||
|
||||
logger.info(`version-handler: Updated SFE version info from server! ${JSON.stringify(this.versionInfo, null, 3)}`);
|
||||
logger.info(
|
||||
`version-handler: Updated SFE version info from server! ${JSON.stringify(
|
||||
this.versionInfo,
|
||||
null,
|
||||
3,
|
||||
)}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
} catch (error) {
|
||||
logger.error(`version-handler: Error getting SFE version data from the server! ${error}`);
|
||||
logger.error(
|
||||
`version-handler: Error getting SFE version data from the server! ${error}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
res.on('error', (error: Error) => {
|
||||
logger.error(`version-handler: Error getting SFE version data from the server! ${error}`);
|
||||
logger.error(
|
||||
`version-handler: Error getting SFE version data from the server! ${error}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
return;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
requestSfeVersion.on('error', (error: Error) => {
|
||||
logger.error(`version-handler: Error getting SFE version data from the server! ${error}`);
|
||||
logger.error(
|
||||
`version-handler: Error getting SFE version data from the server! ${error}`,
|
||||
);
|
||||
resolve(this.versionInfo);
|
||||
return;
|
||||
});
|
||||
@@ -211,7 +260,6 @@ class VersionHandler {
|
||||
requestSfeVersion.end();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const versionHandler = new VersionHandler();
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { BrowserWindow, dialog, PermissionRequestHandlerHandlerDetails, systemPreferences } from 'electron';
|
||||
import {
|
||||
BrowserWindow,
|
||||
dialog,
|
||||
PermissionRequestHandlerHandlerDetails,
|
||||
systemPreferences,
|
||||
} from 'electron';
|
||||
|
||||
import { apiName, IBoundsChange, KeyCodes } from '../common/api-interface';
|
||||
import { isLinux, isMac, isWindowsOS } from '../common/env';
|
||||
@@ -26,23 +31,39 @@ const saveWindowSettings = async (): Promise<void> => {
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
|
||||
if (browserWindow && windowExists(browserWindow)) {
|
||||
let [ x, y ] = browserWindow.getPosition();
|
||||
let [ width, height ] = browserWindow.getSize();
|
||||
let [x, y] = browserWindow.getPosition();
|
||||
let [width, height] = browserWindow.getSize();
|
||||
if (x && y && width && height) {
|
||||
// Only send bound changes over to client for pop-out windows
|
||||
if (browserWindow.winName !== apiName.mainWindowName && mainWindow && windowExists(mainWindow)) {
|
||||
mainWindow.webContents.send('boundsChange', { x, y, width, height, windowName: browserWindow.winName } as IBoundsChange);
|
||||
if (
|
||||
browserWindow.winName !== apiName.mainWindowName &&
|
||||
mainWindow &&
|
||||
windowExists(mainWindow)
|
||||
) {
|
||||
mainWindow.webContents.send('boundsChange', {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
windowName: browserWindow.winName,
|
||||
} as IBoundsChange);
|
||||
}
|
||||
|
||||
// Update the config file
|
||||
if (browserWindow.winName === apiName.mainWindowName) {
|
||||
const isMaximized = browserWindow.isMaximized();
|
||||
const isFullScreen = browserWindow.isFullScreen();
|
||||
const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]);
|
||||
const { mainWinPos } = config.getUserConfigFields(['mainWinPos']);
|
||||
|
||||
if (isMaximized || isFullScreen) {
|
||||
// Keep the original size and position when window is maximized or full screen
|
||||
if (mainWinPos !== undefined && mainWinPos.x !== undefined && mainWinPos.y !== undefined && mainWinPos.width !== undefined && mainWinPos.height !== undefined) {
|
||||
if (
|
||||
mainWinPos !== undefined &&
|
||||
mainWinPos.x !== undefined &&
|
||||
mainWinPos.y !== undefined &&
|
||||
mainWinPos.width !== undefined &&
|
||||
mainWinPos.height !== undefined
|
||||
) {
|
||||
x = mainWinPos.x;
|
||||
y = mainWinPos.y;
|
||||
width = mainWinPos.width;
|
||||
@@ -50,11 +71,15 @@ const saveWindowSettings = async (): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
await config.updateUserConfig({ mainWinPos: { ...mainWinPos, ...{ height, width, x, y, isMaximized, isFullScreen } } });
|
||||
await config.updateUserConfig({
|
||||
mainWinPos: {
|
||||
...mainWinPos,
|
||||
...{ height, width, x, y, isMaximized, isFullScreen },
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const windowMaximized = async (): Promise<void> => {
|
||||
@@ -63,8 +88,10 @@ const windowMaximized = async (): Promise<void> => {
|
||||
const isMaximized = browserWindow.isMaximized();
|
||||
const isFullScreen = browserWindow.isFullScreen();
|
||||
if (browserWindow.winName === apiName.mainWindowName) {
|
||||
const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]);
|
||||
await config.updateUserConfig({ mainWinPos: { ...mainWinPos, ...{ isMaximized, isFullScreen } } });
|
||||
const { mainWinPos } = config.getUserConfigFields(['mainWinPos']);
|
||||
await config.updateUserConfig({
|
||||
mainWinPos: { ...mainWinPos, ...{ isMaximized, isFullScreen } },
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -92,13 +119,26 @@ export const sendInitialBoundChanges = (childWindow: BrowserWindow): void => {
|
||||
}
|
||||
|
||||
if (!childWindow || !windowExists(childWindow)) {
|
||||
logger.error(`window-actions: child window has already been destroyed - not sending bound change`);
|
||||
logger.error(
|
||||
`window-actions: child window has already been destroyed - not sending bound change`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { x, y, width, height } = childWindow.getBounds();
|
||||
const windowName = (childWindow as ICustomBrowserWindow).winName;
|
||||
mainWindow.webContents.send('boundsChange', { x, y, width, height, windowName } as IBoundsChange);
|
||||
logger.info(`window-actions: Initial bounds sent for ${(childWindow as ICustomBrowserWindow).winName}`, { x, y, width, height });
|
||||
mainWindow.webContents.send('boundsChange', {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
windowName,
|
||||
} as IBoundsChange);
|
||||
logger.info(
|
||||
`window-actions: Initial bounds sent for ${
|
||||
(childWindow as ICustomBrowserWindow).winName
|
||||
}`,
|
||||
{ x, y, width, height },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -110,8 +150,10 @@ export const sendInitialBoundChanges = (childWindow: BrowserWindow): void => {
|
||||
* @param {Boolean} shouldFocus whether to get window to focus or just show
|
||||
* without giving focus
|
||||
*/
|
||||
export const activate = (windowName: string, shouldFocus: boolean = true): void => {
|
||||
|
||||
export const activate = (
|
||||
windowName: string,
|
||||
shouldFocus: boolean = true,
|
||||
): void => {
|
||||
// Electron-136: don't activate when the app is reloaded programmatically
|
||||
if (windowHandler.isAutoReload) {
|
||||
return;
|
||||
@@ -120,13 +162,14 @@ export const activate = (windowName: string, shouldFocus: boolean = true): void
|
||||
const windows = windowHandler.getAllWindows();
|
||||
for (const key in windows) {
|
||||
if (Object.prototype.hasOwnProperty.call(windows, key)) {
|
||||
const window = windows[ key ];
|
||||
const window = windows[key];
|
||||
if (window && !window.isDestroyed() && window.winName === windowName) {
|
||||
|
||||
// Bring the window to the top without focusing
|
||||
// Flash task bar icon in Windows for windows
|
||||
if (!shouldFocus) {
|
||||
return (isMac || isLinux) ? window.showInactive() : window.flashFrame(true);
|
||||
return isMac || isLinux
|
||||
? window.showInactive()
|
||||
: window.flashFrame(true);
|
||||
}
|
||||
|
||||
// Note: On window just focusing will preserve window snapped state
|
||||
@@ -153,10 +196,16 @@ export const updateAlwaysOnTop = async (
|
||||
shouldActivateMainWindow: boolean = true,
|
||||
shouldUpdateUserConfig: boolean = true,
|
||||
): Promise<void> => {
|
||||
logger.info(`window-actions: Should we set always on top? ${shouldSetAlwaysOnTop}!`);
|
||||
logger.info(
|
||||
`window-actions: Should we set always on top? ${shouldSetAlwaysOnTop}!`,
|
||||
);
|
||||
const browserWins: ICustomBrowserWindow[] = BrowserWindow.getAllWindows() as ICustomBrowserWindow[];
|
||||
if (shouldUpdateUserConfig) {
|
||||
await config.updateUserConfig({ alwaysOnTop: shouldSetAlwaysOnTop ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET });
|
||||
await config.updateUserConfig({
|
||||
alwaysOnTop: shouldSetAlwaysOnTop
|
||||
? CloudConfigDataTypes.ENABLED
|
||||
: CloudConfigDataTypes.NOT_SET,
|
||||
});
|
||||
}
|
||||
if (browserWins.length > 0) {
|
||||
browserWins
|
||||
@@ -184,7 +233,11 @@ export const handleKeyPress = (key: number): void => {
|
||||
case KeyCodes.Esc: {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
|
||||
if (focusedWindow && !focusedWindow.isDestroyed() && focusedWindow.isFullScreen()) {
|
||||
if (
|
||||
focusedWindow &&
|
||||
!focusedWindow.isDestroyed() &&
|
||||
focusedWindow.isFullScreen()
|
||||
) {
|
||||
logger.info(`window-actions: exiting fullscreen by esc key action`);
|
||||
focusedWindow.setFullScreen(false);
|
||||
}
|
||||
@@ -195,7 +248,11 @@ export const handleKeyPress = (key: number): void => {
|
||||
return;
|
||||
}
|
||||
const browserWin = BrowserWindow.getFocusedWindow() as ICustomBrowserWindow;
|
||||
if (browserWin && windowExists(browserWin) && browserWin.winName === apiName.mainWindowName) {
|
||||
if (
|
||||
browserWin &&
|
||||
windowExists(browserWin) &&
|
||||
browserWin.winName === apiName.mainWindowName
|
||||
) {
|
||||
logger.info(`window-actions: popping up menu by alt key action`);
|
||||
showPopupMenu({ window: browserWin });
|
||||
}
|
||||
@@ -211,9 +268,17 @@ export const handleKeyPress = (key: number): void => {
|
||||
*/
|
||||
const setSpecificAlwaysOnTop = () => {
|
||||
const browserWindow = BrowserWindow.getFocusedWindow();
|
||||
if (isMac && browserWindow && windowExists(browserWindow) && browserWindow.isAlwaysOnTop()) {
|
||||
if (
|
||||
isMac &&
|
||||
browserWindow &&
|
||||
windowExists(browserWindow) &&
|
||||
browserWindow.isAlwaysOnTop()
|
||||
) {
|
||||
// Set the focused window's always on top level based on fullscreen state
|
||||
browserWindow.setAlwaysOnTop(true, browserWindow.isFullScreen() ? 'modal-panel' : 'floating');
|
||||
browserWindow.setAlwaysOnTop(
|
||||
true,
|
||||
browserWindow.isFullScreen() ? 'modal-panel' : 'floating',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -224,13 +289,15 @@ const setSpecificAlwaysOnTop = () => {
|
||||
*/
|
||||
export const monitorWindowActions = (window: BrowserWindow): void => {
|
||||
if (windowHandler.shouldShowWelcomeScreen) {
|
||||
logger.info(`Not saving window position as we are showing the welcome window!`);
|
||||
logger.info(
|
||||
`Not saving window position as we are showing the welcome window!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!window || window.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
const eventNames = [ 'move', 'resize' ];
|
||||
const eventNames = ['move', 'resize'];
|
||||
eventNames.forEach((event: string) => {
|
||||
if (window) {
|
||||
// @ts-ignore
|
||||
@@ -264,7 +331,7 @@ export const removeWindowEventListener = (window: BrowserWindow): void => {
|
||||
if (!window || window.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
const eventNames = [ 'move', 'resize' ];
|
||||
const eventNames = ['move', 'resize'];
|
||||
eventNames.forEach((event: string) => {
|
||||
if (window) {
|
||||
// @ts-ignore
|
||||
@@ -293,14 +360,25 @@ export const removeWindowEventListener = (window: BrowserWindow): void => {
|
||||
* @param message {string} - custom message displayed to the user
|
||||
* @param callback {function}
|
||||
*/
|
||||
export const handleSessionPermissions = async (permission: boolean, message: string, callback: (permission: boolean) => void): Promise<void> => {
|
||||
export const handleSessionPermissions = async (
|
||||
permission: boolean,
|
||||
message: string,
|
||||
callback: (permission: boolean) => void,
|
||||
): Promise<void> => {
|
||||
logger.info(`window-action: permission is ->`, { type: message, permission });
|
||||
|
||||
if (!permission) {
|
||||
const browserWindow = BrowserWindow.getFocusedWindow();
|
||||
if (browserWindow && !browserWindow.isDestroyed()) {
|
||||
const response = await dialog.showMessageBox(browserWindow, { type: 'error', title: `${i18n.t('Permission Denied')()}!`, message });
|
||||
logger.error(`window-actions: permissions message box closed with response`, response);
|
||||
const response = await dialog.showMessageBox(browserWindow, {
|
||||
type: 'error',
|
||||
title: `${i18n.t('Permission Denied')()}!`,
|
||||
message,
|
||||
});
|
||||
logger.error(
|
||||
`window-actions: permissions message box closed with response`,
|
||||
response,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,12 +396,19 @@ export const handleSessionPermissions = async (permission: boolean, message: str
|
||||
* @param callback {function}
|
||||
* @param details {PermissionRequestHandlerHandlerDetails} - object passed along with certain permission types. see {@link https://www.electronjs.org/docs/api/session#sessetpermissionrequesthandlerhandler}
|
||||
*/
|
||||
const handleMediaPermissions = async (permission: boolean, message: string, callback: (permission: boolean) => void, details: PermissionRequestHandlerHandlerDetails): Promise<void> => {
|
||||
const handleMediaPermissions = async (
|
||||
permission: boolean,
|
||||
message: string,
|
||||
callback: (permission: boolean) => void,
|
||||
details: PermissionRequestHandlerHandlerDetails,
|
||||
): Promise<void> => {
|
||||
logger.info(`window-action: permission is ->`, { type: message, permission });
|
||||
let systemAudioPermission;
|
||||
let systemVideoPermission;
|
||||
if (isMac) {
|
||||
systemAudioPermission = await systemPreferences.askForMediaAccess('microphone');
|
||||
systemAudioPermission = await systemPreferences.askForMediaAccess(
|
||||
'microphone',
|
||||
);
|
||||
systemVideoPermission = await systemPreferences.askForMediaAccess('camera');
|
||||
} else {
|
||||
systemAudioPermission = true;
|
||||
@@ -333,8 +418,15 @@ const handleMediaPermissions = async (permission: boolean, message: string, call
|
||||
if (!permission) {
|
||||
const browserWindow = BrowserWindow.getFocusedWindow();
|
||||
if (browserWindow && !browserWindow.isDestroyed()) {
|
||||
const response = await dialog.showMessageBox(browserWindow, { type: 'error', title: `${i18n.t('Permission Denied')()}!`, message });
|
||||
logger.error(`window-actions: permissions message box closed with response`, response);
|
||||
const response = await dialog.showMessageBox(browserWindow, {
|
||||
type: 'error',
|
||||
title: `${i18n.t('Permission Denied')()}!`,
|
||||
message,
|
||||
});
|
||||
logger.error(
|
||||
`window-actions: permissions message box closed with response`,
|
||||
response,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,39 +447,94 @@ const handleMediaPermissions = async (permission: boolean, message: string, call
|
||||
*
|
||||
* @param webContents {Electron.webContents}
|
||||
*/
|
||||
export const handlePermissionRequests = (webContents: Electron.webContents): void => {
|
||||
|
||||
export const handlePermissionRequests = (
|
||||
webContents: Electron.webContents,
|
||||
): void => {
|
||||
if (!webContents || !webContents.session) {
|
||||
return;
|
||||
}
|
||||
const { session } = webContents;
|
||||
|
||||
const { permissions } = config.getConfigFields([ 'permissions' ]);
|
||||
const { permissions } = config.getConfigFields(['permissions']);
|
||||
if (!permissions) {
|
||||
logger.error('permissions configuration is invalid, so, everything will be true by default!');
|
||||
logger.error(
|
||||
'permissions configuration is invalid, so, everything will be true by default!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
session.setPermissionRequestHandler((_webContents, permission, callback, details) => {
|
||||
session.setPermissionRequestHandler(
|
||||
(_webContents, permission, callback, details) => {
|
||||
switch (permission) {
|
||||
case Permissions.MEDIA:
|
||||
return handleMediaPermissions(permissions.media, i18n.t('Your administrator has disabled sharing your camera, microphone, and speakers. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback, details);
|
||||
return handleMediaPermissions(
|
||||
permissions.media,
|
||||
i18n.t(
|
||||
'Your administrator has disabled sharing your camera, microphone, and speakers. Please contact your admin for help',
|
||||
PERMISSIONS_NAMESPACE,
|
||||
)(),
|
||||
callback,
|
||||
details,
|
||||
);
|
||||
case Permissions.LOCATION:
|
||||
return handleSessionPermissions(permissions.geolocation, i18n.t('Your administrator has disabled sharing your location. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
||||
return handleSessionPermissions(
|
||||
permissions.geolocation,
|
||||
i18n.t(
|
||||
'Your administrator has disabled sharing your location. Please contact your admin for help',
|
||||
PERMISSIONS_NAMESPACE,
|
||||
)(),
|
||||
callback,
|
||||
);
|
||||
case Permissions.NOTIFICATIONS:
|
||||
return handleSessionPermissions(permissions.notifications, i18n.t('Your administrator has disabled notifications. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
||||
return handleSessionPermissions(
|
||||
permissions.notifications,
|
||||
i18n.t(
|
||||
'Your administrator has disabled notifications. Please contact your admin for help',
|
||||
PERMISSIONS_NAMESPACE,
|
||||
)(),
|
||||
callback,
|
||||
);
|
||||
case Permissions.MIDI_SYSEX:
|
||||
return handleSessionPermissions(permissions.midiSysex, i18n.t('Your administrator has disabled MIDI Sysex. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
||||
return handleSessionPermissions(
|
||||
permissions.midiSysex,
|
||||
i18n.t(
|
||||
'Your administrator has disabled MIDI Sysex. Please contact your admin for help',
|
||||
PERMISSIONS_NAMESPACE,
|
||||
)(),
|
||||
callback,
|
||||
);
|
||||
case Permissions.POINTER_LOCK:
|
||||
return handleSessionPermissions(permissions.pointerLock, i18n.t('Your administrator has disabled Pointer Lock. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
||||
return handleSessionPermissions(
|
||||
permissions.pointerLock,
|
||||
i18n.t(
|
||||
'Your administrator has disabled Pointer Lock. Please contact your admin for help',
|
||||
PERMISSIONS_NAMESPACE,
|
||||
)(),
|
||||
callback,
|
||||
);
|
||||
case Permissions.FULL_SCREEN:
|
||||
return handleSessionPermissions(permissions.fullscreen, i18n.t('Your administrator has disabled Full Screen. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
||||
return handleSessionPermissions(
|
||||
permissions.fullscreen,
|
||||
i18n.t(
|
||||
'Your administrator has disabled Full Screen. Please contact your admin for help',
|
||||
PERMISSIONS_NAMESPACE,
|
||||
)(),
|
||||
callback,
|
||||
);
|
||||
case Permissions.OPEN_EXTERNAL:
|
||||
return handleSessionPermissions(permissions.openExternal, i18n.t('Your administrator has disabled Opening External App. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
||||
return handleSessionPermissions(
|
||||
permissions.openExternal,
|
||||
i18n.t(
|
||||
'Your administrator has disabled Opening External App. Please contact your admin for help',
|
||||
PERMISSIONS_NAMESPACE,
|
||||
)(),
|
||||
callback,
|
||||
);
|
||||
default:
|
||||
return callback(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -421,7 +568,10 @@ export const unregisterConsoleMessages = () => {
|
||||
if (!browserWindow || !windowExists(browserWindow)) {
|
||||
return;
|
||||
}
|
||||
browserWindow.webContents.removeListener('console-message', onConsoleMessages);
|
||||
browserWindow.webContents.removeListener(
|
||||
'console-message',
|
||||
onConsoleMessages,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -14,19 +14,38 @@ import * as path from 'path';
|
||||
import { format, parse } from 'url';
|
||||
|
||||
import { apiName, Themes, WindowTypes } from '../common/api-interface';
|
||||
import { isDevEnv, isLinux, isMac, isNodeEnv, isWindowsOS } from '../common/env';
|
||||
import {
|
||||
isDevEnv,
|
||||
isLinux,
|
||||
isMac,
|
||||
isNodeEnv,
|
||||
isWindowsOS,
|
||||
} from '../common/env';
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
import { calculatePercentage, getCommandLineArgs, getGuid } from '../common/utils';
|
||||
import {
|
||||
calculatePercentage,
|
||||
getCommandLineArgs,
|
||||
getGuid,
|
||||
} from '../common/utils';
|
||||
import { notification } from '../renderer/notification';
|
||||
import { cleanAppCacheOnCrash } from './app-cache-handler';
|
||||
import { AppMenu } from './app-menu';
|
||||
import { handleChildWindow } from './child-window-handler';
|
||||
import { CloudConfigDataTypes, config, IConfig, IGlobalConfig } from './config-handler';
|
||||
import {
|
||||
CloudConfigDataTypes,
|
||||
config,
|
||||
IConfig,
|
||||
IGlobalConfig,
|
||||
} from './config-handler';
|
||||
import { SpellChecker } from './spell-check-handler';
|
||||
import { checkIfBuildExpired } from './ttl-handler';
|
||||
import { versionHandler } from './version-handler';
|
||||
import { handlePermissionRequests, monitorWindowActions, onConsoleMessages } from './window-actions';
|
||||
import {
|
||||
handlePermissionRequests,
|
||||
monitorWindowActions,
|
||||
onConsoleMessages,
|
||||
} from './window-actions';
|
||||
import {
|
||||
createComponentWindow,
|
||||
didVerifyAndRestoreWindow,
|
||||
@@ -73,7 +92,6 @@ let DEFAULT_HEIGHT: number = 900;
|
||||
const LISTEN_TIMEOUT: number = 25 * 1000;
|
||||
|
||||
export class WindowHandler {
|
||||
|
||||
/**
|
||||
* Verifies if the url is valid and
|
||||
* forcefully appends https if not present
|
||||
@@ -147,8 +165,8 @@ export class WindowHandler {
|
||||
]);
|
||||
this.userConfig = config.getUserConfigFields(['url']);
|
||||
|
||||
const {customFlags} = this.config;
|
||||
const {disableThrottling} = config.getCloudConfigFields([
|
||||
const { customFlags } = this.config;
|
||||
const { disableThrottling } = config.getCloudConfigFields([
|
||||
'disableThrottling',
|
||||
]) as any;
|
||||
|
||||
@@ -224,7 +242,7 @@ export class WindowHandler {
|
||||
companyName: 'Symphony',
|
||||
submitURL: '',
|
||||
};
|
||||
crashReporter.start({...defaultOpts, extra});
|
||||
crashReporter.start({ ...defaultOpts, extra });
|
||||
} catch (e) {
|
||||
throw new Error('failed to init crash report');
|
||||
}
|
||||
@@ -246,9 +264,9 @@ export class WindowHandler {
|
||||
JSON.stringify(this.config.mainWinPos),
|
||||
);
|
||||
|
||||
let {isFullScreen, isMaximized} = this.config.mainWinPos
|
||||
let { isFullScreen, isMaximized } = this.config.mainWinPos
|
||||
? this.config.mainWinPos
|
||||
: {isFullScreen: false, isMaximized: false};
|
||||
: { isFullScreen: false, isMaximized: false };
|
||||
|
||||
this.url = WindowHandler.getValidUrl(
|
||||
this.userConfig.url ? this.userConfig.url : this.globalConfig.url,
|
||||
@@ -322,7 +340,7 @@ export class WindowHandler {
|
||||
logger.info(
|
||||
`window-handler: trying to set url ${commandLineUrl} from command line.`,
|
||||
);
|
||||
const {podWhitelist} = config.getConfigFields(['podWhitelist']);
|
||||
const { podWhitelist } = config.getConfigFields(['podWhitelist']);
|
||||
logger.info(`window-handler: checking pod whitelist.`);
|
||||
if (podWhitelist.length > 0) {
|
||||
logger.info(
|
||||
@@ -389,7 +407,9 @@ export class WindowHandler {
|
||||
|
||||
// Event needed to hide native menu bar on Windows 10 as we use custom menu bar
|
||||
this.mainWindow.webContents.once('did-start-loading', () => {
|
||||
logger.info(`window-handler: main window web contents started loading for url ${this.mainWindow?.webContents.getURL()}!`);
|
||||
logger.info(
|
||||
`window-handler: main window web contents started loading for url ${this.mainWindow?.webContents.getURL()}!`,
|
||||
);
|
||||
this.finishedLoading = false;
|
||||
this.listenForLoad();
|
||||
if (
|
||||
@@ -408,14 +428,21 @@ export class WindowHandler {
|
||||
});
|
||||
|
||||
const logEvents = [
|
||||
'did-fail-provisional-load', 'did-frame-finish-load',
|
||||
'did-start-loading', 'did-stop-loading', 'will-redirect',
|
||||
'did-navigate', 'did-navigate-in-page', 'preload-error',
|
||||
'did-fail-provisional-load',
|
||||
'did-frame-finish-load',
|
||||
'did-start-loading',
|
||||
'did-stop-loading',
|
||||
'will-redirect',
|
||||
'did-navigate',
|
||||
'did-navigate-in-page',
|
||||
'preload-error',
|
||||
];
|
||||
|
||||
logEvents.forEach((windowEvent: any) => {
|
||||
this.mainWindow?.webContents.on(windowEvent, () => {
|
||||
logger.info(`window-handler: Main Window Event Occurred: ${windowEvent}`);
|
||||
logger.info(
|
||||
`window-handler: Main Window Event Occurred: ${windowEvent}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -437,9 +464,13 @@ export class WindowHandler {
|
||||
this.finishedLoading = true;
|
||||
this.url = this.mainWindow.webContents.getURL();
|
||||
if (this.url.indexOf('about:blank') === 0) {
|
||||
logger.info(`Looks like about:blank got loaded which may lead to blank screen`);
|
||||
logger.info(
|
||||
`Looks like about:blank got loaded which may lead to blank screen`,
|
||||
);
|
||||
logger.info(`Reloading the app to check if it resolves the issue`);
|
||||
await this.mainWindow.loadURL(this.userConfig.url || this.globalConfig.url);
|
||||
await this.mainWindow.loadURL(
|
||||
this.userConfig.url || this.globalConfig.url,
|
||||
);
|
||||
return;
|
||||
}
|
||||
logger.info('window-handler: did-finish-load, url: ' + this.url);
|
||||
@@ -455,7 +486,7 @@ export class WindowHandler {
|
||||
isMainWindow: true,
|
||||
});
|
||||
this.appMenu = new AppMenu();
|
||||
const {permissions} = config.getConfigFields(['permissions']);
|
||||
const { permissions } = config.getConfigFields(['permissions']);
|
||||
this.mainWindow.webContents.send(
|
||||
'is-screen-share-enabled',
|
||||
permissions.media,
|
||||
@@ -525,7 +556,7 @@ export class WindowHandler {
|
||||
return;
|
||||
}
|
||||
logger.info(`window-handler: main window crashed!`);
|
||||
const {response} = await electron.dialog.showMessageBox({
|
||||
const { response } = await electron.dialog.showMessageBox({
|
||||
type: 'error',
|
||||
title: i18n.t('Renderer Process Crashed')(),
|
||||
message: i18n.t(
|
||||
@@ -547,9 +578,7 @@ export class WindowHandler {
|
||||
// application should terminate. The installer sends the 'session-end' message
|
||||
// instead of the 'close' message, and when we receive it, we quit the app.
|
||||
this.mainWindow.on('session-end', () => {
|
||||
logger.info(
|
||||
`window-handler: session-end received`,
|
||||
);
|
||||
logger.info(`window-handler: session-end received`);
|
||||
app.quit();
|
||||
});
|
||||
|
||||
@@ -567,7 +596,7 @@ export class WindowHandler {
|
||||
return this.destroyAllWindows();
|
||||
}
|
||||
|
||||
const {minimizeOnClose} = config.getConfigFields(['minimizeOnClose']);
|
||||
const { minimizeOnClose } = config.getConfigFields(['minimizeOnClose']);
|
||||
if (minimizeOnClose === CloudConfigDataTypes.ENABLED) {
|
||||
event.preventDefault();
|
||||
this.mainWindow.minimize();
|
||||
@@ -687,7 +716,7 @@ export class WindowHandler {
|
||||
});
|
||||
|
||||
ipcMain.on('set-pod-url', async (_event, newPodUrl: string) => {
|
||||
await config.updateUserConfig({url: newPodUrl});
|
||||
await config.updateUserConfig({ url: newPodUrl });
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
});
|
||||
@@ -934,7 +963,7 @@ export class WindowHandler {
|
||||
}
|
||||
const ABOUT_SYMPHONY_NAMESPACE = 'AboutSymphony';
|
||||
const versionLocalised = i18n.t('Version', ABOUT_SYMPHONY_NAMESPACE)();
|
||||
const {hostname} = parse(this.url || this.globalConfig.url);
|
||||
const { hostname } = parse(this.url || this.globalConfig.url);
|
||||
const userConfig = config.userConfig;
|
||||
const globalConfig = config.globalConfig;
|
||||
const cloudConfig = config.cloudConfig;
|
||||
@@ -976,13 +1005,19 @@ export class WindowHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('window-handler, createSnippingToolWindow: Receiving snippet props: ' + JSON.stringify({
|
||||
logger.info(
|
||||
'window-handler, createSnippingToolWindow: Receiving snippet props: ' +
|
||||
JSON.stringify({
|
||||
snipImage,
|
||||
snipDimensions,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
const allDisplays = electron.screen.getAllDisplays();
|
||||
logger.info('window-handler, createSnippingToolWindow: User has these displays: ' + JSON.stringify(allDisplays));
|
||||
logger.info(
|
||||
'window-handler, createSnippingToolWindow: User has these displays: ' +
|
||||
JSON.stringify(allDisplays),
|
||||
);
|
||||
|
||||
if (!this.mainWindow) {
|
||||
logger.error('window-handler: Could not get main window');
|
||||
@@ -996,10 +1031,16 @@ export class WindowHandler {
|
||||
const BUTTON_BAR_BOTTOM_HEIGHT = 72;
|
||||
const BUTTON_BARS_HEIGHT = BUTTON_BAR_TOP_HEIGHT + BUTTON_BAR_BOTTOM_HEIGHT;
|
||||
|
||||
const display = electron.screen.getDisplayMatching(this.mainWindow.getBounds());
|
||||
const display = electron.screen.getDisplayMatching(
|
||||
this.mainWindow.getBounds(),
|
||||
);
|
||||
const workAreaSize = display.workAreaSize;
|
||||
const maxToolHeight = Math.floor(calculatePercentage(workAreaSize.height, 90));
|
||||
const maxToolWidth = Math.floor(calculatePercentage(workAreaSize.width, 90));
|
||||
const maxToolHeight = Math.floor(
|
||||
calculatePercentage(workAreaSize.height, 90),
|
||||
);
|
||||
const maxToolWidth = Math.floor(
|
||||
calculatePercentage(workAreaSize.width, 90),
|
||||
);
|
||||
const availableAnnotateAreaHeight = maxToolHeight - BUTTON_BARS_HEIGHT;
|
||||
const availableAnnotateAreaWidth = maxToolWidth;
|
||||
const scaleFactor = display.scaleFactor;
|
||||
@@ -1007,24 +1048,33 @@ export class WindowHandler {
|
||||
height: Math.floor(snipDimensions.height / scaleFactor),
|
||||
width: Math.floor(snipDimensions.width / scaleFactor),
|
||||
};
|
||||
logger.info('window-handler, createSnippingToolWindow: Image will open with scaled dimensions: ' + JSON.stringify(scaledImageDimensions));
|
||||
logger.info(
|
||||
'window-handler, createSnippingToolWindow: Image will open with scaled dimensions: ' +
|
||||
JSON.stringify(scaledImageDimensions),
|
||||
);
|
||||
|
||||
const annotateAreaHeight = scaledImageDimensions.height > availableAnnotateAreaHeight ?
|
||||
availableAnnotateAreaHeight :
|
||||
scaledImageDimensions.height;
|
||||
const annotateAreaWidth = scaledImageDimensions.width > availableAnnotateAreaWidth ?
|
||||
availableAnnotateAreaWidth :
|
||||
scaledImageDimensions.width;
|
||||
const annotateAreaHeight =
|
||||
scaledImageDimensions.height > availableAnnotateAreaHeight
|
||||
? availableAnnotateAreaHeight
|
||||
: scaledImageDimensions.height;
|
||||
const annotateAreaWidth =
|
||||
scaledImageDimensions.width > availableAnnotateAreaWidth
|
||||
? availableAnnotateAreaWidth
|
||||
: scaledImageDimensions.width;
|
||||
|
||||
let toolHeight: number;
|
||||
let toolWidth: number;
|
||||
|
||||
if (scaledImageDimensions.height + BUTTON_BARS_HEIGHT >= maxToolHeight) {
|
||||
toolHeight = maxToolHeight + OS_PADDING;
|
||||
} else if (scaledImageDimensions.height + BUTTON_BARS_HEIGHT <= MIN_TOOL_HEIGHT) {
|
||||
} else if (
|
||||
scaledImageDimensions.height + BUTTON_BARS_HEIGHT <=
|
||||
MIN_TOOL_HEIGHT
|
||||
) {
|
||||
toolHeight = MIN_TOOL_HEIGHT + OS_PADDING;
|
||||
} else {
|
||||
toolHeight = scaledImageDimensions.height + BUTTON_BARS_HEIGHT + OS_PADDING;
|
||||
toolHeight =
|
||||
scaledImageDimensions.height + BUTTON_BARS_HEIGHT + OS_PADDING;
|
||||
}
|
||||
|
||||
if (scaledImageDimensions.width >= maxToolWidth) {
|
||||
@@ -1076,17 +1126,39 @@ export class WindowHandler {
|
||||
if (this.snippingToolWindow && windowExists(this.snippingToolWindow)) {
|
||||
this.snippingToolWindow.webContents.setZoomFactor(1);
|
||||
const windowBounds = this.snippingToolWindow.getBounds();
|
||||
logger.info('window-handler: Opening snipping tool window on display: ' + JSON.stringify(display));
|
||||
logger.info('window-handler: Opening snipping tool window with size: ' + JSON.stringify({
|
||||
logger.info(
|
||||
'window-handler: Opening snipping tool window on display: ' +
|
||||
JSON.stringify(display),
|
||||
);
|
||||
logger.info(
|
||||
'window-handler: Opening snipping tool window with size: ' +
|
||||
JSON.stringify({
|
||||
toolHeight,
|
||||
toolWidth,
|
||||
}));
|
||||
logger.info('window-handler: Opening snipping tool content with metadata: ' + JSON.stringify(snippingToolInfo));
|
||||
logger.info('window-handler: Actual window size: ' + JSON.stringify(windowBounds));
|
||||
if (windowBounds.height !== toolHeight || windowBounds.width !== toolWidth) {
|
||||
logger.info('window-handler: Could not create window with correct size, resizing with setBounds');
|
||||
this.snippingToolWindow.setBounds({height: toolHeight, width: toolWidth});
|
||||
logger.info('window-handler: window bounds after resizing: ' + JSON.stringify(this.snippingToolWindow.getBounds()));
|
||||
}),
|
||||
);
|
||||
logger.info(
|
||||
'window-handler: Opening snipping tool content with metadata: ' +
|
||||
JSON.stringify(snippingToolInfo),
|
||||
);
|
||||
logger.info(
|
||||
'window-handler: Actual window size: ' + JSON.stringify(windowBounds),
|
||||
);
|
||||
if (
|
||||
windowBounds.height !== toolHeight ||
|
||||
windowBounds.width !== toolWidth
|
||||
) {
|
||||
logger.info(
|
||||
'window-handler: Could not create window with correct size, resizing with setBounds',
|
||||
);
|
||||
this.snippingToolWindow.setBounds({
|
||||
height: toolHeight,
|
||||
width: toolWidth,
|
||||
});
|
||||
logger.info(
|
||||
'window-handler: window bounds after resizing: ' +
|
||||
JSON.stringify(this.snippingToolWindow.getBounds()),
|
||||
);
|
||||
}
|
||||
this.snippingToolWindow.webContents.send(
|
||||
'snipping-tool-data',
|
||||
@@ -1096,7 +1168,9 @@ export class WindowHandler {
|
||||
});
|
||||
|
||||
this.snippingToolWindow.once('closed', () => {
|
||||
logger.info('window-handler, createSnippingToolWindow: Closing snipping window, attempting to delete temp snip image');
|
||||
logger.info(
|
||||
'window-handler, createSnippingToolWindow: Closing snipping window, attempting to delete temp snip image',
|
||||
);
|
||||
this.deleteFile(snipImage);
|
||||
this.removeWindow(opts.winKey);
|
||||
this.screenPickerWindow = null;
|
||||
@@ -1307,7 +1381,7 @@ export class WindowHandler {
|
||||
};
|
||||
|
||||
const login = (_event, arg) => {
|
||||
const {username, password} = arg;
|
||||
const { username, password } = arg;
|
||||
callback(username, password);
|
||||
closeBasicAuth(null, false);
|
||||
};
|
||||
@@ -1326,7 +1400,10 @@ export class WindowHandler {
|
||||
*
|
||||
* @param windowName
|
||||
*/
|
||||
public createNotificationSettingsWindow(windowName: string, theme: Themes): void {
|
||||
public createNotificationSettingsWindow(
|
||||
windowName: string,
|
||||
theme: Themes,
|
||||
): void {
|
||||
const opts = this.getWindowOpts(
|
||||
{
|
||||
width: 540,
|
||||
@@ -1368,12 +1445,12 @@ export class WindowHandler {
|
||||
if (app.isReady()) {
|
||||
screens = electron.screen.getAllDisplays();
|
||||
}
|
||||
const {position, display} = config.getConfigFields([
|
||||
const { position, display } = config.getConfigFields([
|
||||
'notificationSettings',
|
||||
]).notificationSettings;
|
||||
this.notificationSettingsWindow.webContents.send(
|
||||
'notification-settings-data',
|
||||
{screens, position, display, theme},
|
||||
{ screens, position, display, theme },
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1381,10 +1458,10 @@ export class WindowHandler {
|
||||
this.addWindow(opts.winKey, this.notificationSettingsWindow);
|
||||
|
||||
ipcMain.once('notification-settings-update', async (_event, args) => {
|
||||
const {display, position} = args;
|
||||
const { display, position } = args;
|
||||
try {
|
||||
await config.updateUserConfig({
|
||||
notificationSettings: {display, position},
|
||||
notificationSettings: { display, position },
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
@@ -1458,7 +1535,7 @@ export class WindowHandler {
|
||||
devTools: isDevEnv,
|
||||
},
|
||||
),
|
||||
...{winKey: streamId},
|
||||
...{ winKey: streamId },
|
||||
};
|
||||
if (opts.width && opts.height) {
|
||||
opts = Object.assign({}, opts, {
|
||||
@@ -1519,7 +1596,7 @@ export class WindowHandler {
|
||||
);
|
||||
this.screenSharingIndicatorWindow.webContents.send(
|
||||
'screen-sharing-indicator-data',
|
||||
{id, streamId},
|
||||
{ id, streamId },
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -1686,12 +1763,18 @@ export class WindowHandler {
|
||||
if (this.mainWindow && windowExists(this.mainWindow)) {
|
||||
// If the client is fully loaded, upon network interruption, load that
|
||||
if (this.isLoggedIn) {
|
||||
logger.info(`window-utils: user has logged in, getting back to Symphony app`);
|
||||
this.mainWindow.loadURL(this.url || this.userConfig.url || this.globalConfig.url);
|
||||
logger.info(
|
||||
`window-utils: user has logged in, getting back to Symphony app`,
|
||||
);
|
||||
this.mainWindow.loadURL(
|
||||
this.url || this.userConfig.url || this.globalConfig.url,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// If not, revert to loading the starting pod url
|
||||
logger.info(`window-utils: user hasn't logged in yet, loading login page again`);
|
||||
logger.info(
|
||||
`window-utils: user hasn't logged in yet, loading login page again`,
|
||||
);
|
||||
this.mainWindow.loadURL(this.userConfig.url || this.globalConfig.url);
|
||||
}
|
||||
}
|
||||
@@ -1705,13 +1788,18 @@ export class WindowHandler {
|
||||
logger.info(`window-handler: Pod load failed on launch`);
|
||||
if (this.mainWindow && windowExists(this.mainWindow)) {
|
||||
const webContentsUrl = this.mainWindow.webContents.getURL();
|
||||
logger.info(`window-handler: Current main window url is ${webContentsUrl}.`);
|
||||
const reloadUrl = webContentsUrl || this.userConfig.url || this.globalConfig.url;
|
||||
logger.info(
|
||||
`window-handler: Current main window url is ${webContentsUrl}.`,
|
||||
);
|
||||
const reloadUrl =
|
||||
webContentsUrl || this.userConfig.url || this.globalConfig.url;
|
||||
logger.info(`window-handler: Trying to reload ${reloadUrl}.`);
|
||||
await this.mainWindow.loadURL(reloadUrl);
|
||||
return;
|
||||
}
|
||||
logger.error(`window-handler: Cannot reload as main window does not exist`);
|
||||
logger.error(
|
||||
`window-handler: Cannot reload as main window does not exist`,
|
||||
);
|
||||
}
|
||||
}, LISTEN_TIMEOUT);
|
||||
}
|
||||
@@ -1814,7 +1902,7 @@ export class WindowHandler {
|
||||
if (!focusedWindow || !windowExists(focusedWindow)) {
|
||||
return;
|
||||
}
|
||||
const {devToolsEnabled} = config.getConfigFields(['devToolsEnabled']);
|
||||
const { devToolsEnabled } = config.getConfigFields(['devToolsEnabled']);
|
||||
if (devToolsEnabled) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
return;
|
||||
@@ -1941,7 +2029,7 @@ export class WindowHandler {
|
||||
cancelId: 0,
|
||||
};
|
||||
|
||||
const {response} = await electron.dialog.showMessageBox(
|
||||
const { response } = await electron.dialog.showMessageBox(
|
||||
browserWindow,
|
||||
options,
|
||||
);
|
||||
@@ -1975,11 +2063,10 @@ export class WindowHandler {
|
||||
winKey: getGuid(),
|
||||
};
|
||||
|
||||
return {...defaultWindowOpts, ...windowOpts};
|
||||
return { ...defaultWindowOpts, ...windowOpts };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const windowHandler = new WindowHandler();
|
||||
|
||||
export {windowHandler};
|
||||
export { windowHandler };
|
||||
|
||||
@@ -13,7 +13,12 @@ import { logger } from '../common/logger';
|
||||
import { getGuid } from '../common/utils';
|
||||
import { whitelistHandler } from '../common/whitelist-handler';
|
||||
import { autoLaunchInstance } from './auto-launch-controller';
|
||||
import { CloudConfigDataTypes, config, IConfig, ICustomRectangle } from './config-handler';
|
||||
import {
|
||||
CloudConfigDataTypes,
|
||||
config,
|
||||
IConfig,
|
||||
ICustomRectangle,
|
||||
} from './config-handler';
|
||||
import { downloadHandler, IDownloadItem } from './download-handler';
|
||||
import { memoryMonitor } from './memory-monitor';
|
||||
import { screenSnippet } from './screen-snippet-handler';
|
||||
@@ -48,7 +53,8 @@ const DOWNLOAD_MANAGER_NAMESPACE = 'DownloadManager';
|
||||
* @param window {BrowserWindow}
|
||||
* @return boolean
|
||||
*/
|
||||
export const windowExists = (window: BrowserWindow): boolean => !!window && typeof window.isDestroyed === 'function' && !window.isDestroyed();
|
||||
export const windowExists = (window: BrowserWindow): boolean =>
|
||||
!!window && typeof window.isDestroyed === 'function' && !window.isDestroyed();
|
||||
|
||||
/**
|
||||
* Prevents window from navigating
|
||||
@@ -56,15 +62,23 @@ export const windowExists = (window: BrowserWindow): boolean => !!window && type
|
||||
* @param browserWindow
|
||||
* @param isPopOutWindow
|
||||
*/
|
||||
export const preventWindowNavigation = (browserWindow: BrowserWindow, isPopOutWindow: boolean = false): void => {
|
||||
export const preventWindowNavigation = (
|
||||
browserWindow: BrowserWindow,
|
||||
isPopOutWindow: boolean = false,
|
||||
): void => {
|
||||
if (!browserWindow || !windowExists(browserWindow)) {
|
||||
return;
|
||||
}
|
||||
logger.info(`window-utils: preventing window from navigating!`, isPopOutWindow);
|
||||
logger.info(
|
||||
`window-utils: preventing window from navigating!`,
|
||||
isPopOutWindow,
|
||||
);
|
||||
|
||||
const listener = async (e: Electron.Event, winUrl: string) => {
|
||||
if (!winUrl.startsWith('http' || 'https')) {
|
||||
logger.error(`window-utils: ${winUrl} doesn't start with http or https, so, not navigating!`);
|
||||
logger.error(
|
||||
`window-utils: ${winUrl} doesn't start with http or https, so, not navigating!`,
|
||||
);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
@@ -78,18 +92,26 @@ export const preventWindowNavigation = (browserWindow: BrowserWindow, isPopOutWi
|
||||
type: 'warning',
|
||||
buttons: ['OK'],
|
||||
title: i18n.t('Not Allowed')(),
|
||||
message: `${i18n.t(`Sorry, you are not allowed to access this website`)()} (${winUrl}), ${i18n.t('please contact your administrator for more details')()}`,
|
||||
message: `${i18n.t(
|
||||
`Sorry, you are not allowed to access this website`,
|
||||
)()} (${winUrl}), ${i18n.t(
|
||||
'please contact your administrator for more details',
|
||||
)()}`,
|
||||
});
|
||||
logger.info(`window-utils: received ${response} response from dialog`);
|
||||
logger.info(
|
||||
`window-utils: received ${response} response from dialog`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
windowHandler.execCmd(windowHandler.screenShareIndicatorFrameUtil, []);
|
||||
}
|
||||
|
||||
if (browserWindow.isDestroyed()
|
||||
|| browserWindow.webContents.isDestroyed()
|
||||
|| winUrl === browserWindow.webContents.getURL()) {
|
||||
if (
|
||||
browserWindow.isDestroyed() ||
|
||||
browserWindow.webContents.isDestroyed() ||
|
||||
winUrl === browserWindow.webContents.getURL()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -113,7 +135,6 @@ export const createComponentWindow = (
|
||||
opts?: Electron.BrowserWindowConstructorOptions,
|
||||
shouldFocus: boolean = true,
|
||||
): BrowserWindow => {
|
||||
|
||||
const options: Electron.BrowserWindowConstructorOptions = {
|
||||
center: true,
|
||||
height: 300,
|
||||
@@ -131,7 +152,9 @@ export const createComponentWindow = (
|
||||
},
|
||||
};
|
||||
|
||||
const browserWindow: ICustomBrowserWindow = new BrowserWindow(options) as ICustomBrowserWindow;
|
||||
const browserWindow: ICustomBrowserWindow = new BrowserWindow(
|
||||
options,
|
||||
) as ICustomBrowserWindow;
|
||||
if (shouldFocus) {
|
||||
browserWindow.once('ready-to-show', () => {
|
||||
if (!browserWindow || !windowExists(browserWindow)) {
|
||||
@@ -144,7 +167,10 @@ export const createComponentWindow = (
|
||||
if (!browserWindow || !windowExists(browserWindow)) {
|
||||
return;
|
||||
}
|
||||
browserWindow.webContents.send('page-load', { locale: i18n.getLocale(), resource: i18n.loadedResources });
|
||||
browserWindow.webContents.send('page-load', {
|
||||
locale: i18n.getLocale(),
|
||||
resource: i18n.loadedResources,
|
||||
});
|
||||
});
|
||||
browserWindow.setMenu(null as any);
|
||||
|
||||
@@ -170,7 +196,9 @@ export const createComponentWindow = (
|
||||
*/
|
||||
export const showBadgeCount = (count: number): void => {
|
||||
if (typeof count !== 'number') {
|
||||
logger.warn(`window-utils: badgeCount: invalid func arg, must be a number: ${count}`);
|
||||
logger.warn(
|
||||
`window-utils: badgeCount: invalid func arg, must be a number: ${count}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,19 +249,23 @@ export const setDataUrl = (dataUrl: string, count: number): void => {
|
||||
* @param {BrowserWindow} browserWin node emitter event to be tested
|
||||
* @return {Boolean} returns true if exists otherwise false
|
||||
*/
|
||||
export const isValidWindow = (browserWin: Electron.BrowserWindow | null): boolean => {
|
||||
export const isValidWindow = (
|
||||
browserWin: Electron.BrowserWindow | null,
|
||||
): boolean => {
|
||||
if (!checkValidWindow) {
|
||||
return true;
|
||||
}
|
||||
let result: boolean = false;
|
||||
if (browserWin && !browserWin.isDestroyed()) {
|
||||
// @ts-ignore
|
||||
const winKey = browserWin.webContents.browserWindowOptions && browserWin.webContents.browserWindowOptions.winKey;
|
||||
const winKey = browserWin.webContents.browserWindowOptions.winKey;
|
||||
result = windowHandler.hasWindow(winKey, browserWin);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
logger.warn(`window-utils: invalid window try to perform action, ignoring action`);
|
||||
logger.warn(
|
||||
`window-utils: invalid window try to perform action, ignoring action`,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -266,7 +298,9 @@ export const updateLocale = async (locale: LocaleType): Promise<void> => {
|
||||
export const showPopupMenu = (opts: Electron.PopupOptions): void => {
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (mainWindow && windowExists(mainWindow) && isValidWindow(mainWindow)) {
|
||||
const coordinates = windowHandler.isCustomTitleBar ? { x: 20, y: 15 } : { x: 10, y: -20 };
|
||||
const coordinates = windowHandler.isCustomTitleBar
|
||||
? { x: 20, y: 15 }
|
||||
: { x: 10, y: -20 };
|
||||
const { x, y } = mainWindow.isFullScreen() ? { x: 0, y: 0 } : coordinates;
|
||||
const popupOpts = { window: mainWindow, x, y };
|
||||
const appMenu = windowHandler.appMenu;
|
||||
@@ -308,7 +342,11 @@ export const sanitize = async (windowName: string): Promise<void> => {
|
||||
* @param defaultHeight
|
||||
* @return {x?: Number, y?: Number, width: Number, height: Number}
|
||||
*/
|
||||
export const getBounds = (winPos: ICustomRectangle | Electron.Rectangle | undefined, defaultWidth: number, defaultHeight: number): Partial<Electron.Rectangle> => {
|
||||
export const getBounds = (
|
||||
winPos: ICustomRectangle | Electron.Rectangle | undefined,
|
||||
defaultWidth: number,
|
||||
defaultHeight: number,
|
||||
): Partial<Electron.Rectangle> => {
|
||||
logger.info('window-utils: getBounds, winPos: ' + JSON.stringify(winPos));
|
||||
|
||||
if (!winPos || !winPos.x || !winPos.y || !winPos.width || !winPos.height) {
|
||||
@@ -319,15 +357,20 @@ export const getBounds = (winPos: ICustomRectangle | Electron.Rectangle | undefi
|
||||
for (let i = 0, len = displays.length; i < len; i++) {
|
||||
const bounds = displays[i].bounds;
|
||||
logger.info('window-utils: getBounds, bounds: ' + JSON.stringify(bounds));
|
||||
if (winPos.x >= bounds.x && winPos.y >= bounds.y &&
|
||||
((winPos.x + winPos.width) <= (bounds.x + bounds.width)) &&
|
||||
((winPos.y + winPos.height) <= (bounds.y + bounds.height))) {
|
||||
if (
|
||||
winPos.x >= bounds.x &&
|
||||
winPos.y >= bounds.y &&
|
||||
winPos.x + winPos.width <= bounds.x + bounds.width &&
|
||||
winPos.y + winPos.height <= bounds.y + bounds.height
|
||||
) {
|
||||
return winPos;
|
||||
}
|
||||
}
|
||||
|
||||
// Fit in the middle of immediate display
|
||||
const display = electron.screen.getDisplayMatching(winPos as electron.Rectangle);
|
||||
const display = electron.screen.getDisplayMatching(
|
||||
winPos as electron.Rectangle,
|
||||
);
|
||||
|
||||
if (display) {
|
||||
// Check that defaultWidth fits
|
||||
@@ -342,8 +385,10 @@ export const getBounds = (winPos: ICustomRectangle | Electron.Rectangle | undefi
|
||||
windowHeight = display.workArea.height;
|
||||
}
|
||||
|
||||
const windowX = display.workArea.x + display.workArea.width / 2 - windowWidth / 2;
|
||||
const windowY = display.workArea.y + display.workArea.height / 2 - windowHeight / 2;
|
||||
const windowX =
|
||||
display.workArea.x + display.workArea.width / 2 - windowWidth / 2;
|
||||
const windowY =
|
||||
display.workArea.y + display.workArea.height / 2 - windowHeight / 2;
|
||||
|
||||
return { x: windowX, y: windowY, width: windowWidth, height: windowHeight };
|
||||
}
|
||||
@@ -358,7 +403,10 @@ export const getBounds = (winPos: ICustomRectangle | Electron.Rectangle | undefi
|
||||
*/
|
||||
export const downloadManagerAction = async (type, filePath): Promise<void> => {
|
||||
const focusedWindow = electron.BrowserWindow.getFocusedWindow();
|
||||
const message = i18n.t('The file you are trying to open cannot be found in the specified path.', DOWNLOAD_MANAGER_NAMESPACE)();
|
||||
const message = i18n.t(
|
||||
'The file you are trying to open cannot be found in the specified path.',
|
||||
DOWNLOAD_MANAGER_NAMESPACE,
|
||||
)();
|
||||
const title = i18n.t('File not Found', DOWNLOAD_MANAGER_NAMESPACE)();
|
||||
|
||||
if (!focusedWindow || !windowExists(focusedWindow)) {
|
||||
@@ -371,7 +419,11 @@ export const downloadManagerAction = async (type, filePath): Promise<void> => {
|
||||
if (fileExists) {
|
||||
openFileResponse = await electron.shell.openPath(filePath);
|
||||
}
|
||||
if ((openFileResponse !== '') && focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
if (
|
||||
openFileResponse !== '' &&
|
||||
focusedWindow &&
|
||||
!focusedWindow.isDestroyed()
|
||||
) {
|
||||
electron.dialog.showMessageBox(focusedWindow, {
|
||||
message,
|
||||
title,
|
||||
@@ -399,7 +451,11 @@ export const downloadManagerAction = async (type, filePath): Promise<void> => {
|
||||
* @param item {Electron.DownloadItem}
|
||||
* @param webContents {Electron.WebContents}
|
||||
*/
|
||||
export const handleDownloadManager = (_event, item: Electron.DownloadItem, webContents: Electron.WebContents) => {
|
||||
export const handleDownloadManager = (
|
||||
_event,
|
||||
item: Electron.DownloadItem,
|
||||
webContents: Electron.WebContents,
|
||||
) => {
|
||||
// Send file path when download is complete
|
||||
item.once('done', (_e, state) => {
|
||||
if (state === 'completed') {
|
||||
@@ -410,7 +466,9 @@ export const handleDownloadManager = (_event, item: Electron.DownloadItem, webCo
|
||||
total: filesize(item.getTotalBytes() || 0),
|
||||
fileName: savePathSplit[savePathSplit.length - 1] || 'No name',
|
||||
};
|
||||
logger.info('window-utils: Download completed, informing download manager');
|
||||
logger.info(
|
||||
'window-utils: Download completed, informing download manager',
|
||||
);
|
||||
webContents.send('downloadCompleted', data);
|
||||
downloadHandler.onDownloadSuccess(data);
|
||||
} else {
|
||||
@@ -449,7 +507,10 @@ const readAndInsertCSS = async (window): Promise<IStyles[] | void> => {
|
||||
* @param mainWindow {BrowserWindow}
|
||||
* @param isCustomTitleBar {boolean} - whether custom title bar enabled
|
||||
*/
|
||||
export const injectStyles = async (mainWindow: BrowserWindow, isCustomTitleBar: boolean): Promise<IStyles[] | void> => {
|
||||
export const injectStyles = async (
|
||||
mainWindow: BrowserWindow,
|
||||
isCustomTitleBar: boolean,
|
||||
): Promise<IStyles[] | void> => {
|
||||
if (isCustomTitleBar) {
|
||||
const index = styles.findIndex(({ name }) => name === styleNames.titleBar);
|
||||
if (index === -1) {
|
||||
@@ -463,10 +524,20 @@ export const injectStyles = async (mainWindow: BrowserWindow, isCustomTitleBar:
|
||||
}
|
||||
// Window custom title bar styles
|
||||
if (fs.existsSync(titleBarStylesPath)) {
|
||||
styles.push({ name: styleNames.titleBar, content: fs.readFileSync(titleBarStylesPath, 'utf8').toString() });
|
||||
styles.push({
|
||||
name: styleNames.titleBar,
|
||||
content: fs.readFileSync(titleBarStylesPath, 'utf8').toString(),
|
||||
});
|
||||
} else {
|
||||
const stylePath = path.join(__dirname, '..', '/renderer/styles/title-bar.css');
|
||||
styles.push({ name: styleNames.titleBar, content: fs.readFileSync(stylePath, 'utf8').toString() });
|
||||
const stylePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'/renderer/styles/title-bar.css',
|
||||
);
|
||||
styles.push({
|
||||
name: styleNames.titleBar,
|
||||
content: fs.readFileSync(stylePath, 'utf8').toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,15 +545,27 @@ export const injectStyles = async (mainWindow: BrowserWindow, isCustomTitleBar:
|
||||
if (styles.findIndex(({ name }) => name === styleNames.snackBar) === -1) {
|
||||
styles.push({
|
||||
name: styleNames.snackBar,
|
||||
content: fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'), 'utf8').toString(),
|
||||
content: fs
|
||||
.readFileSync(
|
||||
path.join(__dirname, '..', '/renderer/styles/snack-bar.css'),
|
||||
'utf8',
|
||||
)
|
||||
.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
// Banner styles
|
||||
if (styles.findIndex(({ name }) => name === styleNames.messageBanner) === -1) {
|
||||
if (
|
||||
styles.findIndex(({ name }) => name === styleNames.messageBanner) === -1
|
||||
) {
|
||||
styles.push({
|
||||
name: styleNames.messageBanner,
|
||||
content: fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/message-banner.css'), 'utf8').toString(),
|
||||
content: fs
|
||||
.readFileSync(
|
||||
path.join(__dirname, '..', '/renderer/styles/message-banner.css'),
|
||||
'utf8',
|
||||
)
|
||||
.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -508,7 +591,12 @@ export const handleCertificateProxyVerification = (
|
||||
const { tld, domain } = whitelistHandler.parseDomain(hostUrl);
|
||||
const host = domain + tld;
|
||||
|
||||
if (ctWhitelist && Array.isArray(ctWhitelist) && ctWhitelist.length > 0 && ctWhitelist.indexOf(host) > -1) {
|
||||
if (
|
||||
ctWhitelist &&
|
||||
Array.isArray(ctWhitelist) &&
|
||||
ctWhitelist.length > 0 &&
|
||||
ctWhitelist.indexOf(host) > -1
|
||||
) {
|
||||
return callback(0);
|
||||
}
|
||||
|
||||
@@ -522,7 +610,10 @@ export const handleCertificateProxyVerification = (
|
||||
* @param window {ICustomBrowserWindow}
|
||||
* @param url Pod URL to load
|
||||
*/
|
||||
export const isSymphonyReachable = (window: ICustomBrowserWindow | null, url: string) => {
|
||||
export const isSymphonyReachable = (
|
||||
window: ICustomBrowserWindow | null,
|
||||
url: string,
|
||||
) => {
|
||||
if (networkStatusCheckIntervalId) {
|
||||
return;
|
||||
}
|
||||
@@ -536,9 +627,12 @@ export const isSymphonyReachable = (window: ICustomBrowserWindow | null, url: st
|
||||
}
|
||||
const podUrl = `${protocol}//${hostname}`;
|
||||
logger.info(`window-utils: checking to see if pod ${podUrl} is reachable!`);
|
||||
fetch(podUrl, { method: 'GET' }).then((rsp) => {
|
||||
fetch(podUrl, { method: 'GET' })
|
||||
.then((rsp) => {
|
||||
if (rsp.status === 200 && windowHandler.isOnline) {
|
||||
logger.info(`window-utils: pod ${podUrl} is reachable, loading main window!`);
|
||||
logger.info(
|
||||
`window-utils: pod ${podUrl} is reachable, loading main window!`,
|
||||
);
|
||||
windowHandler.reloadSymphony();
|
||||
if (networkStatusCheckIntervalId) {
|
||||
clearInterval(networkStatusCheckIntervalId);
|
||||
@@ -546,9 +640,14 @@ export const isSymphonyReachable = (window: ICustomBrowserWindow | null, url: st
|
||||
}
|
||||
return;
|
||||
}
|
||||
logger.warn(`window-utils: POD is down! statusCode: ${rsp.status}, is online: ${windowHandler.isOnline}`);
|
||||
}).catch((error) => {
|
||||
logger.error(`window-utils: Network status check: No active network connection ${error}`);
|
||||
logger.warn(
|
||||
`window-utils: POD is down! statusCode: ${rsp.status}, is online: ${windowHandler.isOnline}`,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(
|
||||
`window-utils: Network status check: No active network connection ${error}`,
|
||||
);
|
||||
});
|
||||
}, networkStatusCheckInterval);
|
||||
};
|
||||
@@ -589,7 +688,9 @@ export const reloadWindow = (browserWindow: ICustomBrowserWindow) => {
|
||||
*
|
||||
* @param browserWindow {ICustomBrowserWindow}
|
||||
*/
|
||||
export const didVerifyAndRestoreWindow = (browserWindow: BrowserWindow | null): boolean => {
|
||||
export const didVerifyAndRestoreWindow = (
|
||||
browserWindow: BrowserWindow | null,
|
||||
): boolean => {
|
||||
if (!browserWindow || !windowExists(browserWindow)) {
|
||||
return false;
|
||||
}
|
||||
@@ -605,7 +706,9 @@ export const didVerifyAndRestoreWindow = (browserWindow: BrowserWindow | null):
|
||||
*
|
||||
* @param windowName {String}
|
||||
*/
|
||||
export const getWindowByName = (windowName: string): BrowserWindow | undefined => {
|
||||
export const getWindowByName = (
|
||||
windowName: string,
|
||||
): BrowserWindow | undefined => {
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
return allWindows.find((window) => {
|
||||
return (window as ICustomBrowserWindow).winName === windowName;
|
||||
@@ -628,14 +731,23 @@ export const updateFeaturesForCloudConfig = async (): Promise<void> => {
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
|
||||
// Update Always on top feature
|
||||
await updateAlwaysOnTop(isAlwaysOnTop === CloudConfigDataTypes.ENABLED, false, false);
|
||||
await updateAlwaysOnTop(
|
||||
isAlwaysOnTop === CloudConfigDataTypes.ENABLED,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
// Update launch on start up
|
||||
launchOnStartup === CloudConfigDataTypes.ENABLED ? autoLaunchInstance.enableAutoLaunch() : autoLaunchInstance.disableAutoLaunch();
|
||||
launchOnStartup === CloudConfigDataTypes.ENABLED
|
||||
? autoLaunchInstance.enableAutoLaunch()
|
||||
: autoLaunchInstance.disableAutoLaunch();
|
||||
|
||||
if (mainWindow && windowExists(mainWindow)) {
|
||||
if (memoryRefresh) {
|
||||
logger.info(`window-utils: updating the memory threshold`, memoryThreshold);
|
||||
logger.info(
|
||||
`window-utils: updating the memory threshold`,
|
||||
memoryThreshold,
|
||||
);
|
||||
memoryMonitor.setMemoryThreshold(parseInt(memoryThreshold, 10));
|
||||
mainWindow.webContents.send('initialize-memory-refresh');
|
||||
}
|
||||
@@ -665,18 +777,27 @@ export const monitorNetworkInterception = (url: string) => {
|
||||
|
||||
if (mainWindow && windowExists(mainWindow)) {
|
||||
isNetworkMonitorInitialized = true;
|
||||
mainWindow.webContents.session.webRequest.onErrorOccurred(filter, async (details) => {
|
||||
mainWindow.webContents.session.webRequest.onErrorOccurred(
|
||||
filter,
|
||||
async (details) => {
|
||||
if (!mainWindow || !windowExists(mainWindow)) {
|
||||
return;
|
||||
}
|
||||
if (!windowHandler.isMana && windowHandler.isWebPageLoading
|
||||
&& (details.error === 'net::ERR_INTERNET_DISCONNECTED'
|
||||
|| details.error === 'net::ERR_NETWORK_CHANGED'
|
||||
|| details.error === 'net::ERR_NAME_NOT_RESOLVED')) {
|
||||
|
||||
if (
|
||||
!windowHandler.isMana &&
|
||||
windowHandler.isWebPageLoading &&
|
||||
(details.error === 'net::ERR_INTERNET_DISCONNECTED' ||
|
||||
details.error === 'net::ERR_NETWORK_CHANGED' ||
|
||||
details.error === 'net::ERR_NAME_NOT_RESOLVED')
|
||||
) {
|
||||
logger.error(`window-utils: URL failed to load`, details);
|
||||
mainWindow.webContents.send('show-banner', { show: true, bannerType: 'error', url: podUrl });
|
||||
}
|
||||
mainWindow.webContents.send('show-banner', {
|
||||
show: true,
|
||||
bannerType: 'error',
|
||||
url: podUrl,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,7 +30,9 @@ export class AnimationQueue {
|
||||
try {
|
||||
await object.func.apply(null, object.args);
|
||||
} catch (err) {
|
||||
logger.error(`animation-queue: encountered an error: ${err} with stack trace: ${err.stack}`);
|
||||
logger.error(
|
||||
`animation-queue: encountered an error: ${err} with stack trace: ${err.stack}`,
|
||||
);
|
||||
} finally {
|
||||
if (this.queue.length > 0) {
|
||||
// Run next animation
|
||||
|
||||
@@ -88,7 +88,10 @@ export interface IApiArgs {
|
||||
|
||||
export type Themes = 'light' | 'dark';
|
||||
|
||||
export type WindowTypes = 'screen-picker' | 'screen-sharing-indicator' | 'notification-settings';
|
||||
export type WindowTypes =
|
||||
| 'screen-picker'
|
||||
| 'screen-sharing-indicator'
|
||||
| 'notification-settings';
|
||||
|
||||
export interface IBadgeCount {
|
||||
count: number;
|
||||
@@ -198,7 +201,13 @@ export interface ILogMsg {
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
export type LogLevel = 'error' | 'warn' | 'info' | 'verbose' | 'debug' | 'silly';
|
||||
export type LogLevel =
|
||||
| 'error'
|
||||
| 'warn'
|
||||
| 'info'
|
||||
| 'verbose'
|
||||
| 'debug'
|
||||
| 'silly';
|
||||
|
||||
export interface ILogFile {
|
||||
filename: string;
|
||||
@@ -217,4 +226,7 @@ export interface IRestartFloaterData {
|
||||
|
||||
export type Reply = string;
|
||||
export type ElectronNotificationData = Reply | object;
|
||||
export type NotificationActionCallback = (event: NotificationActions, data: INotificationData) => void;
|
||||
export type NotificationActionCallback = (
|
||||
event: NotificationActions,
|
||||
data: INotificationData,
|
||||
) => void;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
export const isDevEnv = process.env.ELECTRON_DEV ?
|
||||
process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false;
|
||||
export const isDevEnv = process.env.ELECTRON_DEV
|
||||
? process.env.ELECTRON_DEV.trim().toLowerCase() === 'true'
|
||||
: false;
|
||||
export const isElectronQA = !!process.env.ELECTRON_QA;
|
||||
|
||||
export const isMac = (process.platform === 'darwin');
|
||||
export const isWindowsOS = (process.platform === 'win32');
|
||||
export const isLinux = (process.platform === 'linux');
|
||||
export const isMac = process.platform === 'darwin';
|
||||
export const isWindowsOS = process.platform === 'win32';
|
||||
export const isLinux = process.platform === 'linux';
|
||||
|
||||
export const isNodeEnv = !!process.env.NODE_ENV;
|
||||
|
||||
@@ -7,7 +7,6 @@ export type LocaleType = 'en-US' | 'ja-JP' | 'fr-FR';
|
||||
type formatterFunction = (args?: object) => string;
|
||||
|
||||
class Translation {
|
||||
|
||||
/**
|
||||
* Returns translated string with respect to value, resource & name space
|
||||
*
|
||||
@@ -15,10 +14,19 @@ class Translation {
|
||||
* @param resource {string} current locale resource
|
||||
* @param namespace {string} name space in the resource
|
||||
*/
|
||||
private static translate(value: string, resource: JSON | null, namespace: string | undefined): string {
|
||||
return resource ? (Translation.getResource(resource, namespace)[value] || value) : value;
|
||||
private static translate(
|
||||
value: string,
|
||||
resource: JSON | null,
|
||||
namespace: string | undefined,
|
||||
): string {
|
||||
return resource
|
||||
? Translation.getResource(resource, namespace)[value] || value
|
||||
: value;
|
||||
}
|
||||
private static getResource = (resource: JSON, namespace: string | undefined): JSON => namespace ? resource[namespace] : resource;
|
||||
private static getResource = (
|
||||
resource: JSON,
|
||||
namespace: string | undefined,
|
||||
): JSON => (namespace ? resource[namespace] : resource);
|
||||
private locale: LocaleType = 'en-US';
|
||||
private loadedResources: object = {};
|
||||
|
||||
@@ -56,10 +64,20 @@ class Translation {
|
||||
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);
|
||||
return formatString(
|
||||
Translation.translate(
|
||||
value,
|
||||
this.loadedResources[this.locale],
|
||||
namespace,
|
||||
),
|
||||
args,
|
||||
);
|
||||
}
|
||||
const resource = this.loadResource(this.locale);
|
||||
return formatString(Translation.translate(value, resource, namespace) || value, args);
|
||||
return formatString(
|
||||
Translation.translate(value, resource, namespace) || value,
|
||||
args,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,7 +100,6 @@ class Translation {
|
||||
private loadResource(locale: LocaleType): JSON | null {
|
||||
return this.loadedResources[locale];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const i18n = new Translation();
|
||||
|
||||
@@ -17,10 +17,19 @@ class Translation {
|
||||
* @param resource {string} current locale resource
|
||||
* @param namespace {string} name space in the resource
|
||||
*/
|
||||
private static translate(value: string, resource: JSON | null, namespace: string | undefined): string {
|
||||
return resource ? (Translation.getResource(resource, namespace)[value] || value) : value;
|
||||
private static translate(
|
||||
value: string,
|
||||
resource: JSON | null,
|
||||
namespace: string | undefined,
|
||||
): string {
|
||||
return resource
|
||||
? Translation.getResource(resource, namespace)[value] || value
|
||||
: value;
|
||||
}
|
||||
private static getResource = (resource: JSON, namespace: string | undefined): JSON => namespace ? resource[namespace] : resource;
|
||||
private static getResource = (
|
||||
resource: JSON,
|
||||
namespace: string | undefined,
|
||||
): JSON => (namespace ? resource[namespace] : resource);
|
||||
public loadedResources: object = {};
|
||||
private locale: LocaleType = 'en-US';
|
||||
|
||||
@@ -73,10 +82,20 @@ class Translation {
|
||||
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);
|
||||
return formatString(
|
||||
Translation.translate(
|
||||
value,
|
||||
this.loadedResources[this.locale],
|
||||
namespace,
|
||||
),
|
||||
args,
|
||||
);
|
||||
}
|
||||
const resource = this.loadResource(this.locale);
|
||||
return formatString(Translation.translate(value, resource, namespace) || value, args);
|
||||
return formatString(
|
||||
Translation.translate(value, resource, namespace) || value,
|
||||
args,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,15 +105,19 @@ class Translation {
|
||||
* @param locale
|
||||
*/
|
||||
private loadResource(locale: LocaleType): JSON | null {
|
||||
const resourcePath = path.resolve(__dirname, '..', 'locale', `${locale}.json`);
|
||||
const resourcePath = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'locale',
|
||||
`${locale}.json`,
|
||||
);
|
||||
|
||||
if (!fs.existsSync(resourcePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.loadedResources[this.locale] = require(resourcePath);
|
||||
return (this.loadedResources[this.locale] = require(resourcePath));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const i18n = new Translation();
|
||||
|
||||
@@ -40,12 +40,17 @@ class Logger {
|
||||
private loggerWindow: Electron.WebContents | null;
|
||||
|
||||
constructor() {
|
||||
|
||||
this.loggerWindow = null;
|
||||
this.logQueue = [];
|
||||
// If the user has specified a custom log path use it.
|
||||
const customLogPathArg = getCommandLineArgs(process.argv, '--logPath=', false);
|
||||
const customLogsFolder = customLogPathArg && customLogPathArg.substring(customLogPathArg.indexOf('=') + 1);
|
||||
const customLogPathArg = getCommandLineArgs(
|
||||
process.argv,
|
||||
'--logPath=',
|
||||
false,
|
||||
);
|
||||
const customLogsFolder =
|
||||
customLogPathArg &&
|
||||
customLogPathArg.substring(customLogPathArg.indexOf('=') + 1);
|
||||
if (customLogsFolder) {
|
||||
if (!fs.existsSync(customLogsFolder)) {
|
||||
fs.mkdirSync(customLogsFolder, { recursive: true });
|
||||
@@ -58,7 +63,8 @@ class Logger {
|
||||
if (app.isPackaged) {
|
||||
transports.file.file = path.join(this.logPath, `app_${Date.now()}.log`);
|
||||
transports.file.level = 'debug';
|
||||
transports.file.format = '{y}-{m}-{d} {h}:{i}:{s}:{ms} {z} | {level} | {text}';
|
||||
transports.file.format =
|
||||
'{y}-{m}-{d} {h}:{i}:{s}:{ms} {z} | {level} | {text}';
|
||||
transports.file.appName = 'Symphony';
|
||||
}
|
||||
|
||||
@@ -177,7 +183,12 @@ class Logger {
|
||||
* @param data {array} - extra data to be logged
|
||||
* @param sendToCloud {boolean} - wehether to send the logs on to cloud
|
||||
*/
|
||||
public log(logLevel: LogLevel, message: string, data: any[] = [], sendToCloud: boolean = true): void {
|
||||
public log(
|
||||
logLevel: LogLevel,
|
||||
message: string,
|
||||
data: any[] = [],
|
||||
sendToCloud: boolean = true,
|
||||
): void {
|
||||
if (data && data.length > 0) {
|
||||
data.forEach((param) => {
|
||||
message += `, '${param && typeof param}': ${JSON.stringify(param)}`;
|
||||
@@ -185,13 +196,26 @@ class Logger {
|
||||
}
|
||||
if (!isElectronQA) {
|
||||
switch (logLevel) {
|
||||
case 'error': electronLog.error(message); break;
|
||||
case 'warn': electronLog.warn(message); break;
|
||||
case 'info': electronLog.info(message); break;
|
||||
case 'verbose': electronLog.verbose(message); break;
|
||||
case 'debug': electronLog.debug(message); break;
|
||||
case 'silly': electronLog.silly(message); break;
|
||||
default: electronLog.info(message);
|
||||
case 'error':
|
||||
electronLog.error(message);
|
||||
break;
|
||||
case 'warn':
|
||||
electronLog.warn(message);
|
||||
break;
|
||||
case 'info':
|
||||
electronLog.info(message);
|
||||
break;
|
||||
case 'verbose':
|
||||
electronLog.verbose(message);
|
||||
break;
|
||||
case 'debug':
|
||||
electronLog.debug(message);
|
||||
break;
|
||||
case 'silly':
|
||||
electronLog.silly(message);
|
||||
break;
|
||||
default:
|
||||
electronLog.info(message);
|
||||
}
|
||||
}
|
||||
if (sendToCloud) {
|
||||
@@ -229,10 +253,20 @@ class Logger {
|
||||
|
||||
if (this.loggerWindow) {
|
||||
const browserWindow = BrowserWindow.fromWebContents(this.loggerWindow);
|
||||
if (!(!!browserWindow && typeof browserWindow.isDestroyed === 'function' && !browserWindow.isDestroyed())) {
|
||||
if (
|
||||
!(
|
||||
!!browserWindow &&
|
||||
typeof browserWindow.isDestroyed === 'function' &&
|
||||
!browserWindow.isDestroyed()
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.loggerWindow.send('log', { msgs: [ logMsg ], logLevel: this.desiredLogLevel, showInConsole: this.showInConsole });
|
||||
this.loggerWindow.send('log', {
|
||||
msgs: [logMsg],
|
||||
logLevel: this.desiredLogLevel,
|
||||
showInConsole: this.showInConsole,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,13 +282,13 @@ class Logger {
|
||||
*/
|
||||
private cleanupOldLogs(): void {
|
||||
const files = fs.readdirSync(this.logPath);
|
||||
const deleteTimeStamp = new Date().getTime() - (5 * 24 * 60 * 60 * 1000);
|
||||
const deleteTimeStamp = new Date().getTime() - 5 * 24 * 60 * 60 * 1000;
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(this.logPath, file);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const stat = fs.statSync(filePath);
|
||||
const fileTimestamp = new Date(util.inspect(stat.mtime)).getTime();
|
||||
if ((fileTimestamp < deleteTimeStamp)) {
|
||||
if (fileTimestamp < deleteTimeStamp) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +75,16 @@ export const compareVersions = (v1: string, v2: string): number => {
|
||||
const p2 = patch.exec(s2[2])[1].split('.').map(tryParse);
|
||||
|
||||
for (let k = 0; k < Math.max(p1.length, p2.length); k++) {
|
||||
if (p1[k] === undefined || typeof p2[k] === 'string' && typeof p1[k] === 'number') {
|
||||
if (
|
||||
p1[k] === undefined ||
|
||||
(typeof p2[k] === 'string' && typeof p1[k] === 'number')
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
if (p2[k] === undefined || typeof p1[k] === 'string' && typeof p2[k] === 'number') {
|
||||
if (
|
||||
p2[k] === undefined ||
|
||||
(typeof p1[k] === 'string' && typeof p2[k] === 'number')
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -104,17 +110,25 @@ export const compareVersions = (v1: string, v2: string): number => {
|
||||
* try finding arg that starts with argName.
|
||||
* @return {String} If found, returns the arg, otherwise null.
|
||||
*/
|
||||
export const getCommandLineArgs = (argv: string[], argName: string, exactMatch: boolean): string | null => {
|
||||
export const getCommandLineArgs = (
|
||||
argv: string[],
|
||||
argName: string,
|
||||
exactMatch: boolean,
|
||||
): string | null => {
|
||||
if (!Array.isArray(argv)) {
|
||||
throw new Error(`get-command-line-args: TypeError invalid func arg, must be an array: ${argv}`);
|
||||
throw new Error(
|
||||
`get-command-line-args: TypeError invalid func arg, must be an array: ${argv}`,
|
||||
);
|
||||
}
|
||||
|
||||
const argNameToFind = argName.toLocaleLowerCase();
|
||||
|
||||
for (let i = 0, len = argv.length; i < len; i++) {
|
||||
const arg = argv[i].toLocaleLowerCase();
|
||||
if ((exactMatch && arg === argNameToFind) ||
|
||||
(!exactMatch && arg.startsWith(argNameToFind))) {
|
||||
if (
|
||||
(exactMatch && arg === argNameToFind) ||
|
||||
(!exactMatch && arg.startsWith(argNameToFind))
|
||||
) {
|
||||
return argv[i];
|
||||
}
|
||||
}
|
||||
@@ -129,10 +143,9 @@ export const getCommandLineArgs = (argv: string[], argName: string, exactMatch:
|
||||
* @return {String} guid value in string
|
||||
*/
|
||||
export const getGuid = (): string => {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
|
||||
(c) => {
|
||||
const r = Math.random() * 16 | 0; // tslint:disable-line:no-bitwise
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8); // tslint:disable-line:no-bitwise
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0; // tslint:disable-line:no-bitwise
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8; // tslint:disable-line:no-bitwise
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
@@ -182,9 +195,14 @@ export const filterOutSelectedValues = (data: object, values): object => {
|
||||
* @param wait
|
||||
* @example const throttled = throttle(anyFunc, 500);
|
||||
*/
|
||||
export const throttle = (func: (...args) => void, wait: number): (...args) => void => {
|
||||
export const throttle = (
|
||||
func: (...args) => void,
|
||||
wait: number,
|
||||
): ((...args) => void) => {
|
||||
if (wait <= 0) {
|
||||
throw Error('throttle: invalid throttleTime arg, must be a number: ' + wait);
|
||||
throw Error(
|
||||
'throttle: invalid throttleTime arg, must be a number: ' + wait,
|
||||
);
|
||||
}
|
||||
|
||||
let timer: NodeJS.Timer;
|
||||
@@ -197,7 +215,7 @@ export const throttle = (func: (...args) => void, wait: number): (...args) => vo
|
||||
} else {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
if ((Date.now() - lastRan) >= wait) {
|
||||
if (Date.now() - lastRan >= wait) {
|
||||
func.apply(null, args);
|
||||
lastRan = Date.now();
|
||||
}
|
||||
@@ -220,7 +238,6 @@ export const throttle = (func: (...args) => void, wait: number): (...args) => vo
|
||||
* @return {*}
|
||||
*/
|
||||
export const formatString = (str: string, data?: object): string => {
|
||||
|
||||
if (!str || !data) {
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ interface IURLObject {
|
||||
}
|
||||
|
||||
export class WhitelistHandler {
|
||||
|
||||
/**
|
||||
* Loops through the list of whitelist urls
|
||||
* @param url {String} - url the electron is navigated to
|
||||
@@ -18,7 +17,7 @@ export class WhitelistHandler {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isWhitelisted(url: string): boolean {
|
||||
const { whitelistUrl } = config.getConfigFields([ 'whitelistUrl' ]);
|
||||
const { whitelistUrl } = config.getConfigFields(['whitelistUrl']);
|
||||
|
||||
return this.checkWhitelist(url, whitelistUrl);
|
||||
}
|
||||
@@ -96,14 +95,17 @@ export class WhitelistHandler {
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private matchDomains(parsedUrl: IURLObject, parsedWhitelist: IURLObject): boolean {
|
||||
private matchDomains(
|
||||
parsedUrl: IURLObject,
|
||||
parsedWhitelist: IURLObject,
|
||||
): boolean {
|
||||
if (!parsedUrl || !parsedWhitelist) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
parsedUrl.subdomain === parsedWhitelist.subdomain
|
||||
&& parsedUrl.domain === parsedWhitelist.domain
|
||||
&& parsedUrl.tld === parsedWhitelist.tld
|
||||
parsedUrl.subdomain === parsedWhitelist.subdomain &&
|
||||
parsedUrl.domain === parsedWhitelist.domain &&
|
||||
parsedUrl.tld === parsedWhitelist.tld
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -115,7 +117,10 @@ export class WhitelistHandler {
|
||||
return hostNameFromUrl === hostNameFromWhitelist;
|
||||
}
|
||||
|
||||
return hostNameFromUrl === hostNameFromWhitelist && this.matchSubDomains(parsedUrl.subdomain, parsedWhitelist.subdomain);
|
||||
return (
|
||||
hostNameFromUrl === hostNameFromWhitelist &&
|
||||
this.matchSubDomains(parsedUrl.subdomain, parsedWhitelist.subdomain)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,20 +132,24 @@ export class WhitelistHandler {
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private matchSubDomains(subDomainUrl: string, subDomainWhitelist: string): boolean {
|
||||
private matchSubDomains(
|
||||
subDomainUrl: string,
|
||||
subDomainWhitelist: string,
|
||||
): boolean {
|
||||
if (subDomainUrl === subDomainWhitelist) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const subDomainUrlArray = subDomainUrl.split('.');
|
||||
const lastCharSubDomainUrl = subDomainUrlArray[subDomainUrlArray.length - 1];
|
||||
const lastCharSubDomainUrl =
|
||||
subDomainUrlArray[subDomainUrlArray.length - 1];
|
||||
|
||||
const subDomainWhitelistArray = subDomainWhitelist.split('.');
|
||||
const lastCharWhitelist = subDomainWhitelistArray[subDomainWhitelistArray.length - 1];
|
||||
const lastCharWhitelist =
|
||||
subDomainWhitelistArray[subDomainWhitelistArray.length - 1];
|
||||
|
||||
return lastCharSubDomainUrl === lastCharWhitelist;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const whitelistHandler = new WhitelistHandler();
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
IScreenSnippet,
|
||||
LogLevel,
|
||||
} from '../common/api-interface';
|
||||
import { ICustomDesktopCapturerSource, ICustomSourcesOptions, IScreenSourceError } from './desktop-capturer';
|
||||
import {
|
||||
ICustomDesktopCapturerSource,
|
||||
ICustomSourcesOptions,
|
||||
IScreenSourceError,
|
||||
} from './desktop-capturer';
|
||||
import { SSFApi } from './ssf-api';
|
||||
|
||||
const ssf = new SSFApi();
|
||||
@@ -24,11 +28,12 @@ try {
|
||||
ssInstance = new SSAPIBridge();
|
||||
} catch (e) {
|
||||
ssInstance = null;
|
||||
console.warn("Failed to initialize swift search. You'll need to include the search dependency. Contact the developers for more details");
|
||||
console.warn(
|
||||
"Failed to initialize swift search. You'll need to include the search dependency. Contact the developers for more details",
|
||||
);
|
||||
}
|
||||
|
||||
export class AppBridge {
|
||||
|
||||
/**
|
||||
* Validates the incoming postMessage
|
||||
* events based on the host name
|
||||
@@ -47,18 +52,26 @@ export class AppBridge {
|
||||
private readonly callbackHandlers = {
|
||||
onMessage: (event) => this.handleMessage(event),
|
||||
onActivityCallback: (idleTime: number) => this.activityCallback(idleTime),
|
||||
onScreenSnippetCallback: (arg: IScreenSnippet) => this.screenSnippetCallback(arg),
|
||||
onRegisterBoundsChangeCallback: (arg: IBoundsChange) => this.registerBoundsChangeCallback(arg),
|
||||
onRegisterLoggerCallback: (msg: ILogMsg, logLevel: LogLevel, showInConsole: boolean) =>
|
||||
this.registerLoggerCallback(msg, logLevel, showInConsole),
|
||||
onRegisterProtocolHandlerCallback: (uri: string) => this.protocolHandlerCallback(uri),
|
||||
onScreenSnippetCallback: (arg: IScreenSnippet) =>
|
||||
this.screenSnippetCallback(arg),
|
||||
onRegisterBoundsChangeCallback: (arg: IBoundsChange) =>
|
||||
this.registerBoundsChangeCallback(arg),
|
||||
onRegisterLoggerCallback: (
|
||||
msg: ILogMsg,
|
||||
logLevel: LogLevel,
|
||||
showInConsole: boolean,
|
||||
) => this.registerLoggerCallback(msg, logLevel, showInConsole),
|
||||
onRegisterProtocolHandlerCallback: (uri: string) =>
|
||||
this.protocolHandlerCallback(uri),
|
||||
onCollectLogsCallback: () => this.collectLogsCallback(),
|
||||
onScreenSharingIndicatorCallback: (arg: IScreenSharingIndicator) => this.screenSharingIndicatorCallback(arg),
|
||||
onScreenSharingIndicatorCallback: (arg: IScreenSharingIndicator) =>
|
||||
this.screenSharingIndicatorCallback(arg),
|
||||
onMediaSourceCallback: (
|
||||
error: IScreenSourceError | null,
|
||||
source: ICustomDesktopCapturerSource | undefined,
|
||||
): void => this.gotMediaSource(error, source),
|
||||
onNotificationCallback: (event, data) => this.notificationCallback(event, data),
|
||||
onNotificationCallback: (event, data) =>
|
||||
this.notificationCallback(event, data),
|
||||
onAnalyticsEventCallback: (data) => this.analyticsEventCallback(data),
|
||||
restartFloater: (data) => this.restartFloater(data),
|
||||
onDownloadItemCallback: (data) => this.onDownloadItemCallback(data),
|
||||
@@ -131,10 +144,15 @@ export class AppBridge {
|
||||
}
|
||||
break;
|
||||
case apiCmds.registerActivityDetection:
|
||||
ssf.registerActivityDetection(data as number, this.callbackHandlers.onActivityCallback);
|
||||
ssf.registerActivityDetection(
|
||||
data as number,
|
||||
this.callbackHandlers.onActivityCallback,
|
||||
);
|
||||
break;
|
||||
case apiCmds.registerDownloadHandler:
|
||||
ssf.registerDownloadHandler(this.callbackHandlers.onDownloadItemCallback);
|
||||
ssf.registerDownloadHandler(
|
||||
this.callbackHandlers.onDownloadItemCallback,
|
||||
);
|
||||
break;
|
||||
case apiCmds.openScreenSnippet:
|
||||
ssf.openScreenSnippet(this.callbackHandlers.onScreenSnippetCallback);
|
||||
@@ -143,31 +161,47 @@ export class AppBridge {
|
||||
ssf.closeScreenSnippet();
|
||||
break;
|
||||
case apiCmds.registerBoundsChange:
|
||||
ssf.registerBoundsChange(this.callbackHandlers.onRegisterBoundsChangeCallback);
|
||||
ssf.registerBoundsChange(
|
||||
this.callbackHandlers.onRegisterBoundsChangeCallback,
|
||||
);
|
||||
break;
|
||||
case apiCmds.registerLogger:
|
||||
ssf.registerLogger(this.callbackHandlers.onRegisterLoggerCallback);
|
||||
break;
|
||||
case apiCmds.registerProtocolHandler:
|
||||
ssf.registerProtocolHandler(this.callbackHandlers.onRegisterProtocolHandlerCallback);
|
||||
ssf.registerProtocolHandler(
|
||||
this.callbackHandlers.onRegisterProtocolHandlerCallback,
|
||||
);
|
||||
break;
|
||||
case apiCmds.registerLogRetriever:
|
||||
ssf.registerLogRetriever(this.callbackHandlers.onCollectLogsCallback, data);
|
||||
ssf.registerLogRetriever(
|
||||
this.callbackHandlers.onCollectLogsCallback,
|
||||
data,
|
||||
);
|
||||
break;
|
||||
case apiCmds.sendLogs:
|
||||
ssf.sendLogs(data.logName, data.logFiles);
|
||||
break;
|
||||
case apiCmds.openScreenSharingIndicator:
|
||||
ssf.openScreenSharingIndicator(data as IScreenSharingIndicatorOptions, this.callbackHandlers.onScreenSharingIndicatorCallback);
|
||||
ssf.openScreenSharingIndicator(
|
||||
data as IScreenSharingIndicatorOptions,
|
||||
this.callbackHandlers.onScreenSharingIndicatorCallback,
|
||||
);
|
||||
break;
|
||||
case apiCmds.closeScreenSharingIndicator:
|
||||
ssf.closeScreenSharingIndicator(data.streamId as string);
|
||||
break;
|
||||
case apiCmds.getMediaSource:
|
||||
await ssf.getMediaSource(data as ICustomSourcesOptions, this.callbackHandlers.onMediaSourceCallback);
|
||||
await ssf.getMediaSource(
|
||||
data as ICustomSourcesOptions,
|
||||
this.callbackHandlers.onMediaSourceCallback,
|
||||
);
|
||||
break;
|
||||
case apiCmds.notification:
|
||||
notification.showNotification(data as INotificationData, this.callbackHandlers.onNotificationCallback);
|
||||
notification.showNotification(
|
||||
data as INotificationData,
|
||||
this.callbackHandlers.onNotificationCallback,
|
||||
);
|
||||
break;
|
||||
case apiCmds.closeNotification:
|
||||
await notification.hideNotification(data as number);
|
||||
@@ -181,7 +215,9 @@ export class AppBridge {
|
||||
}
|
||||
break;
|
||||
case apiCmds.registerAnalyticsHandler:
|
||||
ssf.registerAnalyticsEvent(this.callbackHandlers.onAnalyticsEventCallback);
|
||||
ssf.registerAnalyticsEvent(
|
||||
this.callbackHandlers.onAnalyticsEventCallback,
|
||||
);
|
||||
break;
|
||||
case apiCmds.registerRestartFloater:
|
||||
ssf.registerRestartFloater(this.callbackHandlers.restartFloater);
|
||||
@@ -215,19 +251,22 @@ export class AppBridge {
|
||||
* Broadcast user activity
|
||||
* @param idleTime {number} - system idle tick
|
||||
*/
|
||||
private activityCallback = (idleTime: number): void => this.broadcastMessage('activity-callback', idleTime);
|
||||
private activityCallback = (idleTime: number): void =>
|
||||
this.broadcastMessage('activity-callback', idleTime);
|
||||
|
||||
/**
|
||||
* Broadcast snippet data
|
||||
* @param arg {IScreenSnippet}
|
||||
*/
|
||||
private screenSnippetCallback = (arg: IScreenSnippet): void => this.broadcastMessage('screen-snippet-callback', arg);
|
||||
private screenSnippetCallback = (arg: IScreenSnippet): void =>
|
||||
this.broadcastMessage('screen-snippet-callback', arg);
|
||||
|
||||
/**
|
||||
* Broadcast bound changes
|
||||
* @param arg {IBoundsChange}
|
||||
*/
|
||||
private registerBoundsChangeCallback = (arg: IBoundsChange): void => this.broadcastMessage('bound-changes-callback', arg);
|
||||
private registerBoundsChangeCallback = (arg: IBoundsChange): void =>
|
||||
this.broadcastMessage('bound-changes-callback', arg);
|
||||
|
||||
/**
|
||||
* Broadcast logs
|
||||
@@ -235,7 +274,11 @@ export class AppBridge {
|
||||
* @param logLevel {LogLevel}
|
||||
* @param showInConsole {boolean}
|
||||
*/
|
||||
private registerLoggerCallback(msg: ILogMsg, logLevel: LogLevel, showInConsole: boolean): void {
|
||||
private registerLoggerCallback(
|
||||
msg: ILogMsg,
|
||||
logLevel: LogLevel,
|
||||
showInConsole: boolean,
|
||||
): void {
|
||||
this.broadcastMessage('logger-callback', { msg, logLevel, showInConsole });
|
||||
}
|
||||
|
||||
@@ -243,9 +286,11 @@ export class AppBridge {
|
||||
* Broadcast protocol uri
|
||||
* @param uri {string}
|
||||
*/
|
||||
private protocolHandlerCallback = (uri: string): void => this.broadcastMessage('protocol-callback', uri);
|
||||
private protocolHandlerCallback = (uri: string): void =>
|
||||
this.broadcastMessage('protocol-callback', uri);
|
||||
|
||||
private collectLogsCallback = (): void => this.broadcastMessage('collect-logs', undefined);
|
||||
private collectLogsCallback = (): void =>
|
||||
this.broadcastMessage('collect-logs', undefined);
|
||||
|
||||
/**
|
||||
* Broadcast event that stops screen sharing
|
||||
@@ -284,7 +329,10 @@ export class AppBridge {
|
||||
* @param sourceError {IScreenSourceError}
|
||||
* @param selectedSource {ICustomDesktopCapturerSource}
|
||||
*/
|
||||
private gotMediaSource(sourceError: IScreenSourceError | null, selectedSource: ICustomDesktopCapturerSource | undefined): void {
|
||||
private gotMediaSource(
|
||||
sourceError: IScreenSourceError | null,
|
||||
selectedSource: ICustomDesktopCapturerSource | undefined,
|
||||
): void {
|
||||
if (sourceError) {
|
||||
const { requestId, ...error } = sourceError;
|
||||
this.broadcastMessage('media-source-callback', { requestId, error });
|
||||
@@ -294,8 +342,15 @@ export class AppBridge {
|
||||
|
||||
if (selectedSource && selectedSource.requestId) {
|
||||
const { requestId, ...source } = selectedSource;
|
||||
this.broadcastMessage('media-source-callback', { requestId, source, error: sourceError });
|
||||
this.broadcastMessage('media-source-callback-v1', { requestId, response: { source, error: sourceError } });
|
||||
this.broadcastMessage('media-source-callback', {
|
||||
requestId,
|
||||
source,
|
||||
error: sourceError,
|
||||
});
|
||||
this.broadcastMessage('media-source-callback-v1', {
|
||||
requestId,
|
||||
response: { source, error: sourceError },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +373,6 @@ export class AppBridge {
|
||||
private broadcastMessage(method: string, data: any): void {
|
||||
window.postMessage({ method, data }, this.origin);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const appBridge = new AppBridge();
|
||||
|
||||
@@ -36,7 +36,6 @@ const ABOUT_SYMPHONY_NAMESPACE = 'AboutSymphony';
|
||||
* Window that display app version and copyright info
|
||||
*/
|
||||
export default class AboutApp extends React.Component<{}, IState> {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onCopy: () => this.copy(),
|
||||
};
|
||||
@@ -76,8 +75,15 @@ export default class AboutApp extends React.Component<{}, IState> {
|
||||
* main render function
|
||||
*/
|
||||
public render(): JSX.Element {
|
||||
const { clientVersion, buildNumber, hostname, sfeVersion,
|
||||
sfeClientType, sdaVersion, sdaBuildNumber, client,
|
||||
const {
|
||||
clientVersion,
|
||||
buildNumber,
|
||||
hostname,
|
||||
sfeVersion,
|
||||
sfeClientType,
|
||||
sdaVersion,
|
||||
sdaBuildNumber,
|
||||
client,
|
||||
} = this.state;
|
||||
|
||||
const appName = remote.app.getName() || 'Symphony';
|
||||
@@ -107,10 +113,18 @@ export default class AboutApp extends React.Component<{}, IState> {
|
||||
<div className='AboutApp-main-container'>
|
||||
<section>
|
||||
<ul className='AboutApp-symphony-section'>
|
||||
<li><b>POD:</b> {hostname || 'N/A'}</li>
|
||||
<li><b>SBE:</b> {podVersion}</li>
|
||||
<li><b>SDA:</b> {sdaVersionBuild}</li>
|
||||
<li><b>{sfeClientTypeName}:</b> {sfeVersion} {client}</li>
|
||||
<li>
|
||||
<b>POD:</b> {hostname || 'N/A'}
|
||||
</li>
|
||||
<li>
|
||||
<b>SBE:</b> {podVersion}
|
||||
</li>
|
||||
<li>
|
||||
<b>SDA:</b> {sdaVersionBuild}
|
||||
</li>
|
||||
<li>
|
||||
<b>{sfeClientTypeName}:</b> {sfeVersion} {client}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
@@ -118,8 +132,13 @@ export default class AboutApp extends React.Component<{}, IState> {
|
||||
<button
|
||||
className='AboutApp-copy-button'
|
||||
onClick={this.eventHandlers.onCopy}
|
||||
title={i18n.t('Copy all the version information in this page', ABOUT_SYMPHONY_NAMESPACE)()}
|
||||
>{i18n.t('Copy', ABOUT_SYMPHONY_NAMESPACE)()}</button>
|
||||
title={i18n.t(
|
||||
'Copy all the version information in this page',
|
||||
ABOUT_SYMPHONY_NAMESPACE,
|
||||
)()}
|
||||
>
|
||||
{i18n.t('Copy', ABOUT_SYMPHONY_NAMESPACE)()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -140,7 +159,10 @@ export default class AboutApp extends React.Component<{}, IState> {
|
||||
const { clientVersion, ...rest } = this.state;
|
||||
const data = { ...{ sbeVersion: clientVersion }, ...rest };
|
||||
if (data) {
|
||||
remote.clipboard.write({ text: JSON.stringify(data, null, 4) }, 'clipboard');
|
||||
remote.clipboard.write(
|
||||
{ text: JSON.stringify(data, null, 4) },
|
||||
'clipboard',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { LazyBrush } from 'lazy-brush';
|
||||
import * as React from 'react';
|
||||
import { AnalyticsElements, ScreenSnippetActionTypes } from './../../app/analytics-handler';
|
||||
import { IDimensions, IPath, IPoint, sendAnalyticsToMain, Tool } from './snipping-tool';
|
||||
import {
|
||||
AnalyticsElements,
|
||||
ScreenSnippetActionTypes,
|
||||
} from './../../app/analytics-handler';
|
||||
import {
|
||||
IDimensions,
|
||||
IPath,
|
||||
IPoint,
|
||||
sendAnalyticsToMain,
|
||||
Tool,
|
||||
} from './snipping-tool';
|
||||
|
||||
const { useState } = React;
|
||||
|
||||
@@ -50,7 +59,10 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
updPaths.map((p) => {
|
||||
if (p && p.key === key) {
|
||||
p.shouldShow = false;
|
||||
sendAnalyticsToMain(AnalyticsElements.SCREEN_CAPTURE_ANNOTATE, ScreenSnippetActionTypes.ANNOTATE_ERASED);
|
||||
sendAnalyticsToMain(
|
||||
AnalyticsElements.SCREEN_CAPTURE_ANNOTATE,
|
||||
ScreenSnippetActionTypes.ANNOTATE_ERASED,
|
||||
);
|
||||
}
|
||||
return p;
|
||||
});
|
||||
@@ -229,10 +241,16 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
const handleMouseUp = () => {
|
||||
stopDrawing();
|
||||
if (chosenTool === Tool.pen) {
|
||||
sendAnalyticsToMain(AnalyticsElements.SCREEN_CAPTURE_ANNOTATE, ScreenSnippetActionTypes.ANNOTATE_ADDED_PEN);
|
||||
sendAnalyticsToMain(
|
||||
AnalyticsElements.SCREEN_CAPTURE_ANNOTATE,
|
||||
ScreenSnippetActionTypes.ANNOTATE_ADDED_PEN,
|
||||
);
|
||||
}
|
||||
if (chosenTool === Tool.highlight) {
|
||||
sendAnalyticsToMain(AnalyticsElements.SCREEN_CAPTURE_ANNOTATE, ScreenSnippetActionTypes.ANNOTATE_ADDED_HIGHLIGHT);
|
||||
sendAnalyticsToMain(
|
||||
AnalyticsElements.SCREEN_CAPTURE_ANNOTATE,
|
||||
ScreenSnippetActionTypes.ANNOTATE_ADDED_HIGHLIGHT,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -248,9 +266,7 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
id='annotate-wrapper'
|
||||
style={getAnnotateWrapperStyle()}>
|
||||
<div id='annotate-wrapper' style={getAnnotateWrapperStyle()}>
|
||||
<svg
|
||||
data-testid='annotate-area'
|
||||
style={{ cursor: 'crosshair' }}
|
||||
@@ -262,8 +278,7 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={stopDrawing}
|
||||
>
|
||||
{
|
||||
backgroundImagePath &&
|
||||
{backgroundImagePath && (
|
||||
<image
|
||||
x={0}
|
||||
y={0}
|
||||
@@ -271,11 +286,11 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
xlinkHref={backgroundImagePath}
|
||||
width={imageDimensions.width}
|
||||
height={imageDimensions.height}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
{renderPaths(getSvgPathsData(paths))}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ const BASIC_AUTH_NAMESPACE = 'BasicAuth';
|
||||
* Window that display app version and copyright info
|
||||
*/
|
||||
export default class BasicAuth extends React.Component<{}, IState> {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onChange: (event) => this.change(event),
|
||||
onSubmit: () => this.submit(),
|
||||
@@ -46,35 +45,74 @@ export default class BasicAuth extends React.Component<{}, IState> {
|
||||
*/
|
||||
public render(): JSX.Element {
|
||||
const { hostname, isValidCredentials } = this.state;
|
||||
const shouldShowError = classNames('credentials-error', { 'display-error': !isValidCredentials });
|
||||
const shouldShowError = classNames('credentials-error', {
|
||||
'display-error': !isValidCredentials,
|
||||
});
|
||||
return (
|
||||
<div className='container' lang={i18n.getLocale()}>
|
||||
<span>{i18n.t('Please provide your login credentials for:', BASIC_AUTH_NAMESPACE)()}</span>
|
||||
<span>
|
||||
{i18n.t(
|
||||
'Please provide your login credentials for:',
|
||||
BASIC_AUTH_NAMESPACE,
|
||||
)()}
|
||||
</span>
|
||||
<span className='hostname'>{hostname}</span>
|
||||
<span id='credentialsError' className={shouldShowError}>{i18n.t('Invalid user name/password', BASIC_AUTH_NAMESPACE)()}</span>
|
||||
<form id='basicAuth' name='Basic Auth' action='Login' onSubmit={this.eventHandlers.onSubmit}>
|
||||
<span id='credentialsError' className={shouldShowError}>
|
||||
{i18n.t('Invalid user name/password', BASIC_AUTH_NAMESPACE)()}
|
||||
</span>
|
||||
<form
|
||||
id='basicAuth'
|
||||
name='Basic Auth'
|
||||
action='Login'
|
||||
onSubmit={this.eventHandlers.onSubmit}
|
||||
>
|
||||
<table className='form'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td id='username-text'>{i18n.t('User name:', BASIC_AUTH_NAMESPACE)()}</td>
|
||||
<td id='username-text'>
|
||||
{i18n.t('User name:', BASIC_AUTH_NAMESPACE)()}
|
||||
</td>
|
||||
<td>
|
||||
<input id='username' name='username' title='Username' onChange={this.eventHandlers.onChange} required />
|
||||
<input
|
||||
id='username'
|
||||
name='username'
|
||||
title='Username'
|
||||
onChange={this.eventHandlers.onChange}
|
||||
required
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id='password-text'>{i18n.t('Password:', BASIC_AUTH_NAMESPACE)()}</td>
|
||||
<td id='password-text'>
|
||||
{i18n.t('Password:', BASIC_AUTH_NAMESPACE)()}
|
||||
</td>
|
||||
<td>
|
||||
<input name='password' id='password' type='password' title='Password' onChange={this.eventHandlers.onChange} required />
|
||||
<input
|
||||
name='password'
|
||||
id='password'
|
||||
type='password'
|
||||
title='Password'
|
||||
onChange={this.eventHandlers.onChange}
|
||||
required
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className='footer'>
|
||||
<div className='button-container'>
|
||||
<button type='submit' id='login'>{i18n.t('Log In', BASIC_AUTH_NAMESPACE)()}</button>
|
||||
<button type='submit' id='login'>
|
||||
{i18n.t('Log In', BASIC_AUTH_NAMESPACE)()}
|
||||
</button>
|
||||
</div>
|
||||
<div className='button-container'>
|
||||
<button type='button' id='cancel' onClick={this.eventHandlers.onClose}>{i18n.t('Cancel', BASIC_AUTH_NAMESPACE)()}</button>
|
||||
<button
|
||||
type='button'
|
||||
id='cancel'
|
||||
onClick={this.eventHandlers.onClose}
|
||||
>
|
||||
{i18n.t('Cancel', BASIC_AUTH_NAMESPACE)()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -19,7 +19,6 @@ interface IManagerState {
|
||||
}
|
||||
|
||||
export default class DownloadManager {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onInjectItem: (_event, item: IDownloadItem) => this.injectItem(item),
|
||||
};
|
||||
@@ -35,7 +34,10 @@ export default class DownloadManager {
|
||||
showMainComponent: false,
|
||||
};
|
||||
this.domParser = new DOMParser();
|
||||
const parsedDownloadBar = this.domParser.parseFromString(this.render(), 'text/html');
|
||||
const parsedDownloadBar = this.domParser.parseFromString(
|
||||
this.render(),
|
||||
'text/html',
|
||||
);
|
||||
this.itemsContainer = parsedDownloadBar.getElementById('download-main');
|
||||
this.closeButton = parsedDownloadBar.getElementById('close-download-bar');
|
||||
|
||||
@@ -57,14 +59,14 @@ export default class DownloadManager {
|
||||
* Main react render component
|
||||
*/
|
||||
public render(): string {
|
||||
return (`
|
||||
return `
|
||||
<div id='download-manager' class='download-bar'>
|
||||
<ul id='download-main' />
|
||||
<span
|
||||
id='close-download-bar'
|
||||
class='close-download-bar tempo-icon tempo-icon--close' />
|
||||
</div>
|
||||
`);
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +76,9 @@ export default class DownloadManager {
|
||||
const mainFooter = document.getElementById('footer');
|
||||
const { items } = this.state;
|
||||
if (mainFooter) {
|
||||
items && items.length ? mainFooter.classList.remove('hidden') : mainFooter.classList.add('hidden');
|
||||
items && items.length
|
||||
? mainFooter.classList.remove('hidden')
|
||||
: mainFooter.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +91,8 @@ export default class DownloadManager {
|
||||
const { _id, total, fileName }: IDownloadItem = item;
|
||||
const fileDisplayName = this.getFileDisplayName(fileName, item);
|
||||
const itemContainer = document.getElementById('download-main');
|
||||
const parsedItem = this.domParser.parseFromString(`
|
||||
const parsedItem = this.domParser.parseFromString(
|
||||
`
|
||||
<li id=${_id} class='download-element' title="${fileDisplayName}">
|
||||
<div class='download-item' id='dl-item'>
|
||||
<div class='file'>
|
||||
@@ -99,25 +104,41 @@ export default class DownloadManager {
|
||||
<h1 class='text-cutoff'>
|
||||
${fileDisplayName}
|
||||
</h1>
|
||||
<span id='per' title="${total} ${i18n.t('downloaded', DOWNLOAD_MANAGER_NAMESPACE)()}">
|
||||
${total} ${i18n.t('downloaded', DOWNLOAD_MANAGER_NAMESPACE)()}
|
||||
<span id='per' title="${total} ${i18n.t(
|
||||
'downloaded',
|
||||
DOWNLOAD_MANAGER_NAMESPACE,
|
||||
)()}">
|
||||
${total} ${i18n.t(
|
||||
'downloaded',
|
||||
DOWNLOAD_MANAGER_NAMESPACE,
|
||||
)()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id='menu' class='caret tempo-icon tempo-icon--dropdown'>
|
||||
<div id='download-action-menu' class='download-action-menu' style="width: 200px">
|
||||
<ul id={_id}>
|
||||
<li id='download-open' title="${i18n.t('Open', DOWNLOAD_MANAGER_NAMESPACE)()}">
|
||||
<li id='download-open' title="${i18n.t(
|
||||
'Open',
|
||||
DOWNLOAD_MANAGER_NAMESPACE,
|
||||
)()}">
|
||||
${i18n.t('Open', DOWNLOAD_MANAGER_NAMESPACE)()}
|
||||
</li>
|
||||
<li id='download-show-in-folder' title="${i18n.t('Show in Folder', DOWNLOAD_MANAGER_NAMESPACE)()}">
|
||||
${i18n.t('Show in Folder', DOWNLOAD_MANAGER_NAMESPACE)()}
|
||||
<li id='download-show-in-folder' title="${i18n.t(
|
||||
'Show in Folder',
|
||||
DOWNLOAD_MANAGER_NAMESPACE,
|
||||
)()}">
|
||||
${i18n.t(
|
||||
'Show in Folder',
|
||||
DOWNLOAD_MANAGER_NAMESPACE,
|
||||
)()}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>`,
|
||||
'text/html');
|
||||
'text/html',
|
||||
);
|
||||
const progress = parsedItem.getElementById('download-progress');
|
||||
const domItem = parsedItem.getElementById(_id);
|
||||
|
||||
@@ -151,7 +172,7 @@ export default class DownloadManager {
|
||||
}
|
||||
args.count = itemCount;
|
||||
const newItem = { ...args, ...{ flashing: true } };
|
||||
const allItems = [ ...items, ...[ newItem ] ];
|
||||
const allItems = [...items, ...[newItem]];
|
||||
this.state = { items: allItems, showMainComponent: true };
|
||||
|
||||
// inserts download bar once
|
||||
@@ -175,7 +196,11 @@ export default class DownloadManager {
|
||||
* @param item {Document}
|
||||
* @param itemId {String}
|
||||
*/
|
||||
private attachEventListener(id: string, item: Document, itemId: string): void {
|
||||
private attachEventListener(
|
||||
id: string,
|
||||
item: Document,
|
||||
itemId: string,
|
||||
): void {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@@ -221,7 +246,7 @@ export default class DownloadManager {
|
||||
if (fileIndex !== -1) {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.downloadManagerAction,
|
||||
path: items[ fileIndex ].savedPath,
|
||||
path: items[fileIndex].savedPath,
|
||||
type: 'open',
|
||||
});
|
||||
}
|
||||
@@ -241,7 +266,7 @@ export default class DownloadManager {
|
||||
if (fileIndex !== -1) {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.downloadManagerAction,
|
||||
path: items[ fileIndex ].savedPath,
|
||||
path: items[fileIndex].savedPath,
|
||||
type: 'show',
|
||||
});
|
||||
}
|
||||
@@ -259,7 +284,10 @@ export default class DownloadManager {
|
||||
const extLastIndex = fileName.lastIndexOf('.');
|
||||
const fileCount = ' (' + item.count + ')';
|
||||
|
||||
fileName = fileName.slice(0, extLastIndex) + fileCount + fileName.slice(extLastIndex);
|
||||
fileName =
|
||||
fileName.slice(0, extLastIndex) +
|
||||
fileCount +
|
||||
fileName.slice(extLastIndex);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ enum ERROR {
|
||||
* Window that display app version and copyright info
|
||||
*/
|
||||
export default class LoadingScreen extends React.Component<{}, IState> {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onRetry: () => this.retry(),
|
||||
onQuit: () => this.quit(),
|
||||
@@ -52,9 +51,34 @@ export default class LoadingScreen extends React.Component<{}, IState> {
|
||||
<div className='LoadingScreen'>
|
||||
<img className='LoadingScreen-logo' src={this.getImage(error)} />
|
||||
<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}}' stroke='#ffffff' strokeWidth='10' r='35' strokeDasharray='164.93361431346415 56.97787143782138' transform='rotate(59.6808 50 50)'>
|
||||
<animateTransform attributeName='transform' type='rotate' calcMode='linear' values='0 50 50;360 50 50' keyTimes='0;1' dur='1s' begin='0s' repeatCount='indefinite' />
|
||||
<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}}'
|
||||
stroke='#ffffff'
|
||||
strokeWidth='10'
|
||||
r='35'
|
||||
strokeDasharray='164.93361431346415 56.97787143782138'
|
||||
transform='rotate(59.6808 50 50)'
|
||||
>
|
||||
<animateTransform
|
||||
attributeName='transform'
|
||||
type='rotate'
|
||||
calcMode='linear'
|
||||
values='0 50 50;360 50 50'
|
||||
keyTimes='0;1'
|
||||
dur='1s'
|
||||
begin='0s'
|
||||
repeatCount='indefinite'
|
||||
/>
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -79,11 +103,25 @@ export default class LoadingScreen extends React.Component<{}, IState> {
|
||||
return (
|
||||
<div className='LoadingScreen'>
|
||||
<img className='LoadingScreen-logo' src={this.getImage(error)} />
|
||||
<span className='LoadingScreen-name'>{i18n.t('Problem connecting to Symphony')()}</span>
|
||||
<div id='error-code' className='LoadingScreen-error-code'>{error}</div>
|
||||
<span className='LoadingScreen-name'>
|
||||
{i18n.t('Problem connecting to Symphony')()}
|
||||
</span>
|
||||
<div id='error-code' className='LoadingScreen-error-code'>
|
||||
{error}
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={this.eventHandlers.onRetry} className='LoadingScreen-button'>{i18n.t('Try now')()}</button>
|
||||
<button onClick={this.eventHandlers.onQuit} className='LoadingScreen-button'>{i18n.t('Quit Symphony')()}</button>
|
||||
<button
|
||||
onClick={this.eventHandlers.onRetry}
|
||||
className='LoadingScreen-button'
|
||||
>
|
||||
{i18n.t('Try now')()}
|
||||
</button>
|
||||
<button
|
||||
onClick={this.eventHandlers.onQuit}
|
||||
className='LoadingScreen-button'
|
||||
>
|
||||
{i18n.t('Quit Symphony')()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ const onlineStateInterval = 5 * 1000;
|
||||
let onlineStateIntervalId;
|
||||
|
||||
export default class MessageBanner {
|
||||
|
||||
private readonly body: HTMLCollectionOf<Element> | undefined;
|
||||
private banner: HTMLElement | null = null;
|
||||
private closeButton: HTMLElement | null = null;
|
||||
@@ -19,12 +18,16 @@ export default class MessageBanner {
|
||||
constructor() {
|
||||
this.body = document.getElementsByTagName('body');
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
window.addEventListener(
|
||||
'beforeunload',
|
||||
() => {
|
||||
if (onlineStateIntervalId) {
|
||||
clearInterval(onlineStateIntervalId);
|
||||
onlineStateIntervalId = null;
|
||||
}
|
||||
}, false);
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +60,7 @@ export default class MessageBanner {
|
||||
public showBanner(show: boolean, type: bannerTypes, url?: string): void {
|
||||
this.url = url;
|
||||
if (this.body && this.body.length > 0 && this.banner) {
|
||||
this.body[ 0 ].appendChild(this.banner);
|
||||
this.body[0].appendChild(this.banner);
|
||||
if (show) {
|
||||
this.banner.classList.add('sda-banner-show');
|
||||
this.monitorOnlineState();
|
||||
@@ -100,8 +103,14 @@ export default class MessageBanner {
|
||||
|
||||
onlineStateIntervalId = setInterval(async () => {
|
||||
try {
|
||||
const response = await window.fetch(this.url || window.location.href, { cache: 'no-cache', keepalive: false });
|
||||
if (window.navigator.onLine && (response.status === 200 || response.ok)) {
|
||||
const response = await window.fetch(this.url || window.location.href, {
|
||||
cache: 'no-cache',
|
||||
keepalive: false,
|
||||
});
|
||||
if (
|
||||
window.navigator.onLine &&
|
||||
(response.status === 200 || response.ok)
|
||||
) {
|
||||
if (this.banner) {
|
||||
this.banner.classList.remove('sda-banner-show');
|
||||
}
|
||||
@@ -133,12 +142,20 @@ export default class MessageBanner {
|
||||
<div id='sda-banner' class='sda-banner'>
|
||||
<span class='sda-banner-icon'></span>
|
||||
<span class='sda-banner-message'>
|
||||
${i18n.t('Connection lost. This message will disappear once the connection is restored.', BANNER_NAME_SPACE)()}
|
||||
${i18n.t(
|
||||
'Connection lost. This message will disappear once the connection is restored.',
|
||||
BANNER_NAME_SPACE,
|
||||
)()}
|
||||
</span>
|
||||
<span id='banner-retry' class='sda-banner-retry-button' title='${i18n.t('Retry Now', BANNER_NAME_SPACE)()}'>
|
||||
<span id='banner-retry' class='sda-banner-retry-button' title='${i18n.t(
|
||||
'Retry Now',
|
||||
BANNER_NAME_SPACE,
|
||||
)()}'>
|
||||
${i18n.t('Retry Now', BANNER_NAME_SPACE)()}
|
||||
</span>
|
||||
<span id='banner-close' class='sda-banner-close-icon' title='${i18n.t('Close')()}'></span>
|
||||
<span id='banner-close' class='sda-banner-close-icon' title='${i18n.t(
|
||||
'Close',
|
||||
)()}'></span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ const NETWORK_ERROR_NAMESPACE = 'NetworkError';
|
||||
* Window that display app version and copyright info
|
||||
*/
|
||||
export default class NetworkError extends React.Component<IProps, {}> {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onRetry: () => this.retry(),
|
||||
};
|
||||
@@ -50,7 +49,8 @@ export default class NetworkError extends React.Component<IProps, {}> {
|
||||
cy={0}
|
||||
r={1}
|
||||
gradientUnits='userSpaceOnUse'
|
||||
gradientTransform='matrix(0 40.259 -50.3704 0 20.07 0)'>
|
||||
gradientTransform='matrix(0 40.259 -50.3704 0 20.07 0)'
|
||||
>
|
||||
<stop stopColor='#fff' stopOpacity={0.4} />
|
||||
<stop offset={1} stopColor='#fff' stopOpacity={0} />
|
||||
</radialGradient>
|
||||
@@ -59,15 +59,25 @@ export default class NetworkError extends React.Component<IProps, {}> {
|
||||
</div>
|
||||
<div className='main-message' lang={i18n.getLocale()}>
|
||||
<p className='NetworkError-header'>
|
||||
{i18n.t('Problem connecting to Symphony', NETWORK_ERROR_NAMESPACE)()}
|
||||
{i18n.t(
|
||||
'Problem connecting to Symphony',
|
||||
NETWORK_ERROR_NAMESPACE,
|
||||
)()}
|
||||
</p>
|
||||
<p id='NetworkError-reason'>
|
||||
{i18n.t(`Looks like you are not connected to the Internet. We'll try to reconnect automatically.`, NETWORK_ERROR_NAMESPACE)()}
|
||||
{i18n.t(
|
||||
`Looks like you are not connected to the Internet. We'll try to reconnect automatically.`,
|
||||
NETWORK_ERROR_NAMESPACE,
|
||||
)()}
|
||||
</p>
|
||||
<div id='error-code' className='NetworkError-error-code'>
|
||||
{error || ERROR.NETWORK_OFFLINE}
|
||||
</div>
|
||||
<button id='retry-button' onClick={this.eventHandlers.onRetry} className='NetworkError-button'>
|
||||
<button
|
||||
id='retry-button'
|
||||
onClick={this.eventHandlers.onRetry}
|
||||
className='NetworkError-button'
|
||||
>
|
||||
{i18n.t('Retry', NETWORK_ERROR_NAMESPACE)()}
|
||||
</button>
|
||||
</div>
|
||||
@@ -81,5 +91,4 @@ export default class NetworkError extends React.Component<IProps, {}> {
|
||||
private retry(): void {
|
||||
ipcRenderer.send('reload-symphony');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,8 +4,18 @@ import * as React from 'react';
|
||||
|
||||
import { i18n } from '../../common/i18n-preload';
|
||||
|
||||
const whiteColorRegExp = new RegExp(/^(?:white|#fff(?:fff)?|rgba?\(\s*255\s*,\s*255\s*,\s*255\s*(?:,\s*1\s*)?\))$/i);
|
||||
const darkTheme = [ '#e23030', '#b5616a', '#ab8ead', '#ebc875', '#a3be77', '#58c6ff', '#ebab58' ];
|
||||
const whiteColorRegExp = new RegExp(
|
||||
/^(?:white|#fff(?:fff)?|rgba?\(\s*255\s*,\s*255\s*,\s*255\s*(?:,\s*1\s*)?\))$/i,
|
||||
);
|
||||
const darkTheme = [
|
||||
'#e23030',
|
||||
'#b5616a',
|
||||
'#ab8ead',
|
||||
'#ebc875',
|
||||
'#a3be77',
|
||||
'#58c6ff',
|
||||
'#ebab58',
|
||||
];
|
||||
type Theme = '' | 'light' | 'dark';
|
||||
|
||||
interface IState {
|
||||
@@ -25,7 +35,9 @@ interface IState {
|
||||
canSendMessage: boolean;
|
||||
}
|
||||
|
||||
type mouseEventButton = React.MouseEvent<HTMLDivElement> | React.MouseEvent<HTMLButtonElement>;
|
||||
type mouseEventButton =
|
||||
| React.MouseEvent<HTMLDivElement>
|
||||
| React.MouseEvent<HTMLButtonElement>;
|
||||
type keyboardEvent = React.KeyboardEvent<HTMLInputElement>;
|
||||
|
||||
// Notification container height
|
||||
@@ -33,14 +45,16 @@ const CONTAINER_HEIGHT = 100;
|
||||
const CONTAINER_HEIGHT_WITH_INPUT = 132;
|
||||
|
||||
export default class NotificationComp extends React.Component<{}, IState> {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onClose: (winKey) => (_event: mouseEventButton) => this.close(winKey),
|
||||
onClick: (data) => (_event: mouseEventButton) => this.click(data),
|
||||
onContextMenu: (event) => this.contextMenu(event),
|
||||
onMouseEnter: (winKey) => (_event: mouseEventButton) => this.onMouseEnter(winKey),
|
||||
onMouseLeave: (winKey) => (_event: mouseEventButton) => this.onMouseLeave(winKey),
|
||||
onOpenReply: (winKey) => (event: mouseEventButton) => this.onOpenReply(event, winKey),
|
||||
onMouseEnter: (winKey) => (_event: mouseEventButton) =>
|
||||
this.onMouseEnter(winKey),
|
||||
onMouseLeave: (winKey) => (_event: mouseEventButton) =>
|
||||
this.onMouseLeave(winKey),
|
||||
onOpenReply: (winKey) => (event: mouseEventButton) =>
|
||||
this.onOpenReply(event, winKey),
|
||||
onThumbsUp: () => (_event: mouseEventButton) => this.onThumbsUp(),
|
||||
onReply: (winKey) => (_event: mouseEventButton) => this.onReply(winKey),
|
||||
onKeyUp: (winKey) => (event: keyboardEvent) => this.onKeyUp(event, winKey),
|
||||
@@ -91,21 +105,39 @@ export default class NotificationComp extends React.Component<{}, IState> {
|
||||
* Renders the custom title bar
|
||||
*/
|
||||
public render(): JSX.Element {
|
||||
const { title, body, id, color, isExternal, theme, isInputHidden, containerHeight, hasReply, canSendMessage } = this.state;
|
||||
const {
|
||||
title,
|
||||
body,
|
||||
id,
|
||||
color,
|
||||
isExternal,
|
||||
theme,
|
||||
isInputHidden,
|
||||
containerHeight,
|
||||
hasReply,
|
||||
canSendMessage,
|
||||
} = this.state;
|
||||
let themeClassName;
|
||||
if (theme) {
|
||||
themeClassName = theme;
|
||||
} else if (darkTheme.includes(color.toLowerCase())) {
|
||||
themeClassName = 'blackText';
|
||||
} else {
|
||||
themeClassName = color && color.match(whiteColorRegExp) ? 'light' : 'dark';
|
||||
themeClassName =
|
||||
color && color.match(whiteColorRegExp) ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
const bgColor = { backgroundColor: color || '#ffffff' };
|
||||
const containerClass = classNames('container', { 'external-border': isExternal });
|
||||
const containerClass = classNames('container', {
|
||||
'external-border': isExternal,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={containerClass} style={{ height: containerHeight }} lang={i18n.getLocale()}>
|
||||
<div
|
||||
className={containerClass}
|
||||
style={{ height: containerHeight }}
|
||||
lang={i18n.getLocale()}
|
||||
>
|
||||
<div
|
||||
className='main-container'
|
||||
role='alert'
|
||||
@@ -117,7 +149,10 @@ export default class NotificationComp extends React.Component<{}, IState> {
|
||||
>
|
||||
<div className='logo-container'>
|
||||
<div className='logo'>
|
||||
<img src='../renderer/assets/notification-symphony-logo.svg' alt='Symphony logo'/>
|
||||
<img
|
||||
src='../renderer/assets/notification-symphony-logo.svg'
|
||||
alt='Symphony logo'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='header'>
|
||||
@@ -145,16 +180,19 @@ export default class NotificationComp extends React.Component<{}, IState> {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
<div
|
||||
style={{
|
||||
...{ display: isInputHidden ? 'none' : 'flex' },
|
||||
...bgColor,
|
||||
}} className='rte-container'>
|
||||
}}
|
||||
className='rte-container'
|
||||
>
|
||||
<div className='input-container'>
|
||||
<div className='input-border'/>
|
||||
<div className='input-border' />
|
||||
<div className='input-caret-container'>
|
||||
<span ref={this.customInput} className='custom-input'/>
|
||||
<span ref={this.customInput} className='custom-input' />
|
||||
</div>
|
||||
<div ref={this.inputCaret} className='input-caret'/>
|
||||
<div ref={this.inputCaret} className='input-caret' />
|
||||
<input
|
||||
style={bgColor}
|
||||
className={themeClassName}
|
||||
@@ -171,13 +209,16 @@ export default class NotificationComp extends React.Component<{}, IState> {
|
||||
onMouseUp={this.setInputCaretPosition}
|
||||
onFocus={() => this.animateCaret(true)}
|
||||
onBlur={() => this.animateCaret(false)}
|
||||
ref={this.input}/>
|
||||
ref={this.input}
|
||||
/>
|
||||
</div>
|
||||
<div className='rte-button-container'>
|
||||
<button
|
||||
className={`rte-thumbsup-button ${themeClassName}`}
|
||||
onClick={this.eventHandlers.onThumbsUp()}
|
||||
>👍</button>
|
||||
>
|
||||
👍
|
||||
</button>
|
||||
<button
|
||||
className={`rte-send-button ${themeClassName}`}
|
||||
onClick={this.eventHandlers.onReply(id)}
|
||||
@@ -200,7 +241,10 @@ export default class NotificationComp extends React.Component<{}, IState> {
|
||||
}
|
||||
return (
|
||||
<div className='ext-badge-container'>
|
||||
<img src='../renderer/assets/notification-ext-badge.svg' alt='ext-badge'/>
|
||||
<img
|
||||
src='../renderer/assets/notification-ext-badge.svg'
|
||||
alt='ext-badge'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -297,13 +341,16 @@ export default class NotificationComp extends React.Component<{}, IState> {
|
||||
private onOpenReply(event, id) {
|
||||
event.stopPropagation();
|
||||
ipcRenderer.send('show-reply', id);
|
||||
this.setState({
|
||||
this.setState(
|
||||
{
|
||||
isInputHidden: false,
|
||||
hasReply: false,
|
||||
containerHeight: CONTAINER_HEIGHT_WITH_INPUT,
|
||||
}, () => {
|
||||
},
|
||||
() => {
|
||||
this.input.current?.focus();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,16 +384,18 @@ export default class NotificationComp extends React.Component<{}, IState> {
|
||||
if (this.input.current) {
|
||||
const inputText = this.input.current.value || '';
|
||||
const selectionStart = this.input.current.selectionStart || 0;
|
||||
this.customInput.current.innerText = inputText.substring(0, selectionStart).replace(/\n$/, '\n\u0001');
|
||||
this.customInput.current.innerText = inputText
|
||||
.substring(0, selectionStart)
|
||||
.replace(/\n$/, '\n\u0001');
|
||||
this.setState({
|
||||
canSendMessage: inputText.trim().length > 0,
|
||||
});
|
||||
}
|
||||
|
||||
const rects = this.customInput.current.getClientRects();
|
||||
const lastRect = rects && rects[ rects.length - 1 ];
|
||||
const lastRect = rects && rects[rects.length - 1];
|
||||
|
||||
const x = lastRect && lastRect.width || 0;
|
||||
const x = (lastRect && lastRect.width) || 0;
|
||||
if (this.inputCaret.current) {
|
||||
this.inputCaret.current.style.left = x + 'px';
|
||||
}
|
||||
@@ -374,7 +423,7 @@ export default class NotificationComp extends React.Component<{}, IState> {
|
||||
*/
|
||||
private updateState(_event, data): void {
|
||||
const { color, flash } = data;
|
||||
data.color = (color && !color.startsWith('#')) ? '#' + color : color;
|
||||
data.color = color && !color.startsWith('#') ? '#' + color : color;
|
||||
data.isInputHidden = true;
|
||||
data.containerHeight = CONTAINER_HEIGHT;
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ export enum Themes {
|
||||
* Notification Window component
|
||||
*/
|
||||
export default class NotificationSettings extends React.Component<{}, IState> {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -45,22 +44,50 @@ export default class NotificationSettings extends React.Component<{}, IState> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='content' style={this.state.theme === Themes.DARK ? { backgroundColor: '#25272B' } : undefined} >
|
||||
<div
|
||||
className='content'
|
||||
style={
|
||||
this.state.theme === Themes.DARK
|
||||
? { backgroundColor: '#25272B' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<header
|
||||
className='header'
|
||||
style={this.state.theme === Themes.DARK ? { color: 'white', borderBottom: '1px solid #525760' } : undefined}>
|
||||
style={
|
||||
this.state.theme === Themes.DARK
|
||||
? { color: 'white', borderBottom: '1px solid #525760' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<span className='header-title'>
|
||||
{i18n.t('Set Notification Position', NOTIFICATION_SETTINGS_NAMESPACE)()}
|
||||
{i18n.t(
|
||||
'Set Notification Position',
|
||||
NOTIFICATION_SETTINGS_NAMESPACE,
|
||||
)()}
|
||||
</span>
|
||||
</header>
|
||||
<div className='form'>
|
||||
<label className='display-label' style={this.state.theme === Themes.DARK ? { color: 'white' } : undefined}>
|
||||
<label
|
||||
className='display-label'
|
||||
style={
|
||||
this.state.theme === Themes.DARK ? { color: 'white' } : undefined
|
||||
}
|
||||
>
|
||||
{i18n.t('Show on display', NOTIFICATION_SETTINGS_NAMESPACE)()}
|
||||
</label>
|
||||
<div id='screens' className='display-container'>
|
||||
<select
|
||||
className='display-selector'
|
||||
style={this.state.theme === Themes.DARK ? { border: '2px solid #767A81', backgroundColor: '#25272B', color: 'white' } : undefined}
|
||||
style={
|
||||
this.state.theme === Themes.DARK
|
||||
? {
|
||||
border: '2px solid #767A81',
|
||||
backgroundColor: '#25272B',
|
||||
color: 'white',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
id='screen-selector'
|
||||
title='position'
|
||||
value={this.state.display}
|
||||
@@ -69,10 +96,22 @@ export default class NotificationSettings extends React.Component<{}, IState> {
|
||||
{this.renderScreens()}
|
||||
</select>
|
||||
</div>
|
||||
<label className='position-label' style={this.state.theme === Themes.DARK ? { color: 'white' } : undefined}>
|
||||
<label
|
||||
className='position-label'
|
||||
style={
|
||||
this.state.theme === Themes.DARK ? { color: 'white' } : undefined
|
||||
}
|
||||
>
|
||||
{i18n.t('Position', NOTIFICATION_SETTINGS_NAMESPACE)()}
|
||||
</label>
|
||||
<div className='position-container' style={this.state.theme === Themes.DARK ? { background: '#2E3136' } : undefined}>
|
||||
<div
|
||||
className='position-container'
|
||||
style={
|
||||
this.state.theme === Themes.DARK
|
||||
? { background: '#2E3136' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className='button-set-left'>
|
||||
{this.renderPositionButton('upper-left', 'Top Left')}
|
||||
{this.renderPositionButton('lower-left', 'Bottom Left')}
|
||||
@@ -83,20 +122,37 @@ export default class NotificationSettings extends React.Component<{}, IState> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer className='footer' style={this.state.theme === Themes.DARK ? { borderTop: '1px solid #525760' } : undefined}>
|
||||
<footer
|
||||
className='footer'
|
||||
style={
|
||||
this.state.theme === Themes.DARK
|
||||
? { borderTop: '1px solid #525760' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className='footer-button-container'>
|
||||
<button
|
||||
id='cancel'
|
||||
className='footer-button footer-button-dismiss'
|
||||
onClick={this.close.bind(this)}
|
||||
style={this.state.theme === Themes.DARK ? { backgroundColor: '#25272B', color: 'white' } : undefined}>
|
||||
style={
|
||||
this.state.theme === Themes.DARK
|
||||
? { backgroundColor: '#25272B', color: 'white' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{i18n.t('CANCEL', NOTIFICATION_SETTINGS_NAMESPACE)()}
|
||||
</button>
|
||||
<button
|
||||
id='ok-button'
|
||||
className='footer-button footer-button-ok'
|
||||
onClick={this.submit.bind(this)}
|
||||
style={this.state.theme === Themes.DARK ? { backgroundColor: '#25272B', color: 'white' } : undefined}>
|
||||
style={
|
||||
this.state.theme === Themes.DARK
|
||||
? { backgroundColor: '#25272B', color: 'white' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{i18n.t('OK', NOTIFICATION_SETTINGS_NAMESPACE)()}
|
||||
</button>
|
||||
</div>
|
||||
@@ -208,7 +264,9 @@ export default class NotificationSettings extends React.Component<{}, IState> {
|
||||
return screens.map((screen, index) => {
|
||||
const screenId = screen.id;
|
||||
return (
|
||||
<option id={String(screenId)} key={screenId} value={screenId}>{index + 1}/{screens.length}</option>
|
||||
<option id={String(screenId)} key={screenId} value={screenId}>
|
||||
{index + 1}/{screens.length}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { apiCmds, apiName } from '../../common/api-interface';
|
||||
import { isLinux, isMac, isWindowsOS } from '../../common/env';
|
||||
import { i18n } from '../../common/i18n-preload';
|
||||
|
||||
const screenRegExp = new RegExp(/^screen \d+$/gmi);
|
||||
const screenRegExp = new RegExp(/^screen \d+$/gim);
|
||||
const SCREEN_PICKER_NAMESPACE = 'ScreenPicker';
|
||||
const ENTIRE_SCREEN = 'entire screen';
|
||||
|
||||
@@ -38,7 +38,6 @@ const enum keyCode {
|
||||
type inputChangeEvent = React.ChangeEvent<HTMLInputElement>;
|
||||
|
||||
export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
|
||||
private isScreensAvailable: boolean;
|
||||
private isApplicationsAvailable: boolean;
|
||||
private readonly eventHandlers = {
|
||||
@@ -85,14 +84,20 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
return (
|
||||
<div className='ScreenPicker ScreenPicker-content'>
|
||||
<div className='ScreenPicker-title'>
|
||||
<span>{i18n.t(`Choose what you'd like to share`, SCREEN_PICKER_NAMESPACE)()}</span>
|
||||
<div className='ScreenPicker-x-button' onClick={this.eventHandlers.onClose}>
|
||||
<span>
|
||||
{i18n.t(
|
||||
`Choose what you'd like to share`,
|
||||
SCREEN_PICKER_NAMESPACE,
|
||||
)()}
|
||||
</span>
|
||||
<div
|
||||
className='ScreenPicker-x-button'
|
||||
onClick={this.eventHandlers.onClose}
|
||||
>
|
||||
<div className='content-button'>
|
||||
<i>
|
||||
<svg viewBox='0 0 48 48' fill='grey'>
|
||||
<path
|
||||
d='M39.4,33.8L31,25.4c-0.4-0.4-0.9-0.9-1.4-1.4c0.5-0.5,1-1,1.4-1.4l8.4-8.4c0.8-0.8,0.8-2,0-2.8l-2.8-2.8 c-0.8-0.8-2-0.8-2.8,0L25.4,17c-0.4,0.4-0.9,0.9-1.4,1.4c-0.5-0.5-1-1-1.4-1.4l-8.4-8.4c-0.8-0.8-2-0.8-2.8,0l-2.8,2.8 c-0.8,0.8-0.8,2,0,2.8l8.4,8.4c0.4,0.4,0.9,0.9,1.4,1.4c-0.5,0.5-1,1-1.4,1.4l-8.4,8.4c-0.8,0.8-0.8,2,0,2.8l2.8,2.8 c0.8,0.8,2,0.8,2.8,0l8.4-8.4c0.4-0.4,0.9-0.9,1.4-1.4c0.5,0.5,1,1,1.4,1.4l8.4,8.4c0.8,0.8,2,0.8,2.8,0l2.8-2.8 C40.2,35.8,40.2,34.6,39.4,33.8z'
|
||||
/>
|
||||
<path d='M39.4,33.8L31,25.4c-0.4-0.4-0.9-0.9-1.4-1.4c0.5-0.5,1-1,1.4-1.4l8.4-8.4c0.8-0.8,0.8-2,0-2.8l-2.8-2.8 c-0.8-0.8-2-0.8-2.8,0L25.4,17c-0.4,0.4-0.9,0.9-1.4,1.4c-0.5-0.5-1-1-1.4-1.4l-8.4-8.4c-0.8-0.8-2-0.8-2.8,0l-2.8,2.8 c-0.8,0.8-0.8,2,0,2.8l8.4,8.4c0.4,0.4,0.9,0.9,1.4,1.4c-0.5,0.5-1,1-1.4,1.4l-8.4,8.4c-0.8,0.8-0.8,2,0,2.8l2.8,2.8 c0.8,0.8,2,0.8,2.8,0l8.4-8.4c0.4-0.4,0.9-0.9,1.4-1.4c0.5,0.5,1,1,1.4,1.4l8.4,8.4c0.8,0.8,2,0.8,2.8,0l2.8-2.8 C40.2,35.8,40.2,34.6,39.4,33.8z' />
|
||||
</svg>
|
||||
</i>
|
||||
</div>
|
||||
@@ -107,11 +112,14 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
{i18n.t('Cancel', SCREEN_PICKER_NAMESPACE)()}
|
||||
</button>
|
||||
<button
|
||||
className={classNames('ScreenPicker-share-button',
|
||||
{ 'ScreenPicker-share-button-disable': !selectedSource })}
|
||||
className={classNames('ScreenPicker-share-button', {
|
||||
'ScreenPicker-share-button-disable': !selectedSource,
|
||||
})}
|
||||
onClick={this.eventHandlers.onSubmit}
|
||||
>
|
||||
{selectedSource ? i18n.t('Share', SCREEN_PICKER_NAMESPACE)() : i18n.t('Select Screen', SCREEN_PICKER_NAMESPACE)()}
|
||||
{selectedSource
|
||||
? i18n.t('Share', SCREEN_PICKER_NAMESPACE)()
|
||||
: i18n.t('Select Screen', SCREEN_PICKER_NAMESPACE)()}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -133,22 +141,34 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
{ 'ScreenPicker-selected': this.shouldHighlight(source.id) },
|
||||
);
|
||||
const sourceName = source.name.toLocaleLowerCase();
|
||||
if (((isMac || isLinux) && source.display_id !== '') || (isWindowsOS && (sourceName === ENTIRE_SCREEN || screenRegExp.exec(sourceName)))) {
|
||||
if (
|
||||
((isMac || isLinux) && source.display_id !== '') ||
|
||||
(isWindowsOS &&
|
||||
(sourceName === ENTIRE_SCREEN || screenRegExp.exec(sourceName)))
|
||||
) {
|
||||
source.fileName = 'fullscreen';
|
||||
let screenName;
|
||||
if (sourceName === ENTIRE_SCREEN) {
|
||||
screenName = i18n.t('Entire screen', SCREEN_PICKER_NAMESPACE)();
|
||||
} else {
|
||||
const screenNumber = source.name.substr(7, source.name.length);
|
||||
screenName = i18n.t('Screen {number}', SCREEN_PICKER_NAMESPACE)({ number: screenNumber });
|
||||
screenName = i18n.t(
|
||||
'Screen {number}',
|
||||
SCREEN_PICKER_NAMESPACE,
|
||||
)({ number: screenNumber });
|
||||
}
|
||||
screens.push(
|
||||
<div
|
||||
className={shouldHighlight}
|
||||
id={source.id}
|
||||
onClick={() => this.eventHandlers.onSelect(source)}>
|
||||
onClick={() => this.eventHandlers.onSelect(source)}
|
||||
>
|
||||
<div className='ScreenPicker-screen-section-box'>
|
||||
<img className='ScreenPicker-img-wrapper' src={source.thumbnail as any} alt='thumbnail image' />
|
||||
<img
|
||||
className='ScreenPicker-img-wrapper'
|
||||
src={source.thumbnail as any}
|
||||
alt='thumbnail image'
|
||||
/>
|
||||
</div>
|
||||
<div className='ScreenPicker-screen-source-title'>{screenName}</div>
|
||||
</div>,
|
||||
@@ -159,11 +179,18 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
<div
|
||||
className={shouldHighlight}
|
||||
id={source.id}
|
||||
onClick={() => this.eventHandlers.onSelect(source)}>
|
||||
onClick={() => this.eventHandlers.onSelect(source)}
|
||||
>
|
||||
<div className='ScreenPicker-screen-section-box'>
|
||||
<img className='ScreenPicker-img-wrapper' src={source.thumbnail as any} alt='thumbnail image' />
|
||||
<img
|
||||
className='ScreenPicker-img-wrapper'
|
||||
src={source.thumbnail as any}
|
||||
alt='thumbnail image'
|
||||
/>
|
||||
</div>
|
||||
<div className='ScreenPicker-screen-source-title'>
|
||||
{source.name}
|
||||
</div>
|
||||
<div className='ScreenPicker-screen-source-title'>{source.name}</div>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
@@ -174,7 +201,10 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
return (
|
||||
<div className='ScreenPicker-error-content'>
|
||||
<span className='error-message'>
|
||||
{i18n.t('No screens or applications are currently available.', SCREEN_PICKER_NAMESPACE)()}
|
||||
{i18n.t(
|
||||
'No screens or applications are currently available.',
|
||||
SCREEN_PICKER_NAMESPACE,
|
||||
)()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@@ -204,7 +234,10 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
checked={selectedTab === 'screens'}
|
||||
onChange={this.eventHandlers.onToggle('screens')}
|
||||
/>,
|
||||
<label className={classNames('screens', { hidden: !this.isScreensAvailable })}
|
||||
<label
|
||||
className={classNames('screens', {
|
||||
hidden: !this.isScreensAvailable,
|
||||
})}
|
||||
htmlFor='screen-tab'
|
||||
>
|
||||
{i18n.t('Screens', SCREEN_PICKER_NAMESPACE)()}
|
||||
@@ -217,7 +250,10 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
checked={selectedTab === 'applications'}
|
||||
onChange={this.eventHandlers.onToggle('applications')}
|
||||
/>,
|
||||
<label className={classNames('applications', { hidden: !this.isApplicationsAvailable })}
|
||||
<label
|
||||
className={classNames('applications', {
|
||||
hidden: !this.isApplicationsAvailable,
|
||||
})}
|
||||
htmlFor='application-tab'
|
||||
>
|
||||
{i18n.t('Applications', SCREEN_PICKER_NAMESPACE)()}
|
||||
@@ -234,7 +270,10 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
checked={true}
|
||||
onChange={this.eventHandlers.onToggle('screens')}
|
||||
/>,
|
||||
<label className={classNames('screens', { hidden: !this.isScreensAvailable })}
|
||||
<label
|
||||
className={classNames('screens', {
|
||||
hidden: !this.isScreensAvailable,
|
||||
})}
|
||||
htmlFor='screen-tab'
|
||||
>
|
||||
{i18n.t('Screens', SCREEN_PICKER_NAMESPACE)()}
|
||||
@@ -251,7 +290,10 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
checked={true}
|
||||
onChange={this.eventHandlers.onToggle('applications')}
|
||||
/>,
|
||||
<label className={classNames('applications', { hidden: !this.isApplicationsAvailable })}
|
||||
<label
|
||||
className={classNames('applications', {
|
||||
hidden: !this.isApplicationsAvailable,
|
||||
})}
|
||||
htmlFor='application-tab'
|
||||
>
|
||||
{i18n.t('Applications', SCREEN_PICKER_NAMESPACE)()}
|
||||
@@ -375,7 +417,8 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
}
|
||||
|
||||
// Find the next item to be selected
|
||||
const nextIndex = (this.currentIndex + index + sources.length) % sources.length;
|
||||
const nextIndex =
|
||||
(this.currentIndex + index + sources.length) % sources.length;
|
||||
if (sources[nextIndex] && sources[nextIndex].id) {
|
||||
// Updates the selected source
|
||||
this.setState({ selectedSource: sources[nextIndex] });
|
||||
|
||||
@@ -5,15 +5,10 @@ import * as React from 'react';
|
||||
* Window that display app version and copyright info
|
||||
*/
|
||||
export default class ScreenSharingFrame extends React.Component<{}> {
|
||||
|
||||
/**
|
||||
* main render function
|
||||
*/
|
||||
public render(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className={classNames('ScreenSharingFrame')}>
|
||||
</div>
|
||||
);
|
||||
return <div className={classNames('ScreenSharingFrame')}></div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,13 @@ type mouseEventButton = React.MouseEvent<HTMLButtonElement>;
|
||||
/**
|
||||
* Window that display a banner when the users starting sharing screen
|
||||
*/
|
||||
export default class ScreenSharingIndicator extends React.Component<{}, IState> {
|
||||
|
||||
export default class ScreenSharingIndicator extends React.Component<
|
||||
{},
|
||||
IState
|
||||
> {
|
||||
private readonly eventHandlers = {
|
||||
onStopScreenSharing: (id: number) => (_event: mouseEventButton) => this.stopScreenShare(id),
|
||||
onStopScreenSharing: (id: number) => (_event: mouseEventButton) =>
|
||||
this.stopScreenShare(id),
|
||||
onClose: () => this.close(),
|
||||
};
|
||||
|
||||
@@ -40,11 +43,20 @@ export default class ScreenSharingIndicator extends React.Component<{}, IState>
|
||||
|
||||
return (
|
||||
<div className={classNames('ScreenSharingIndicator', { mac: isMac })}>
|
||||
<span className='text-label'>{(i18n.t(`You are sharing your screen on {appName}`, namespace)({ appName: remote.app.getName() })).replace(remote.app.getName(), '')}
|
||||
<span className='text-label'>
|
||||
{i18n
|
||||
.t(
|
||||
`You are sharing your screen on {appName}`,
|
||||
namespace,
|
||||
)({ appName: remote.app.getName() })
|
||||
.replace(remote.app.getName(), '')}
|
||||
<span className='text-label2'> {remote.app.getName()}</span>
|
||||
</span>
|
||||
<span className='buttons'>
|
||||
<button className='stop-sharing-button' onClick={this.eventHandlers.onStopScreenSharing(id)}>
|
||||
<button
|
||||
className='stop-sharing-button'
|
||||
onClick={this.eventHandlers.onStopScreenSharing(id)}
|
||||
>
|
||||
{i18n.t('Stop sharing', namespace)()}
|
||||
</button>
|
||||
</span>
|
||||
@@ -57,7 +69,10 @@ export default class ScreenSharingIndicator extends React.Component<{}, IState>
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
ipcRenderer.removeListener('screen-sharing-indicator-data', this.updateState);
|
||||
ipcRenderer.removeListener(
|
||||
'screen-sharing-indicator-data',
|
||||
this.updateState,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@ import { i18n } from '../../common/i18n-preload';
|
||||
const SNACKBAR_NAMESPACE = 'SnackBar';
|
||||
|
||||
export default class SnackBar {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onShowSnackBar: () => this.showSnackBar(),
|
||||
onRemoveSnackBar: () => this.removeSnackBar(),
|
||||
@@ -19,9 +18,16 @@ export default class SnackBar {
|
||||
|
||||
constructor() {
|
||||
const browserWindow = remote.getCurrentWindow();
|
||||
if (browserWindow && typeof browserWindow.isDestroyed === 'function' && !browserWindow.isDestroyed()) {
|
||||
if (
|
||||
browserWindow &&
|
||||
typeof browserWindow.isDestroyed === 'function' &&
|
||||
!browserWindow.isDestroyed()
|
||||
) {
|
||||
browserWindow.on('enter-full-screen', this.eventHandlers.onShowSnackBar);
|
||||
browserWindow.on('leave-full-screen', this.eventHandlers.onRemoveSnackBar);
|
||||
browserWindow.on(
|
||||
'leave-full-screen',
|
||||
this.eventHandlers.onRemoveSnackBar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,12 +48,12 @@ export default class SnackBar {
|
||||
public showSnackBar(): void {
|
||||
setTimeout(() => {
|
||||
if (this.body && this.body.length > 0 && this.snackBar) {
|
||||
this.body[ 0 ].appendChild(this.snackBar);
|
||||
this.body[0].appendChild(this.snackBar);
|
||||
this.snackBar.classList.add('SnackBar-show');
|
||||
this.snackBarTimer = setTimeout(() => {
|
||||
if (this.snackBar && this.body) {
|
||||
if (document.getElementById('snack-bar')) {
|
||||
this.body[ 0 ].removeChild(this.snackBar);
|
||||
this.body[0].removeChild(this.snackBar);
|
||||
}
|
||||
}
|
||||
}, 3000);
|
||||
@@ -61,7 +67,7 @@ export default class SnackBar {
|
||||
public removeSnackBar(): void {
|
||||
if (this.body && this.body.length > 0 && this.snackBar) {
|
||||
if (document.getElementById('snack-bar')) {
|
||||
this.body[ 0 ].removeChild(this.snackBar);
|
||||
this.body[0].removeChild(this.snackBar);
|
||||
if (this.snackBarTimer) {
|
||||
clearTimeout(this.snackBarTimer);
|
||||
}
|
||||
@@ -73,12 +79,16 @@ export default class SnackBar {
|
||||
* Renders the custom title bar
|
||||
*/
|
||||
public render(): string {
|
||||
return (
|
||||
`<div id='snack-bar' class='SnackBar'>
|
||||
return `<div id='snack-bar' class='SnackBar'>
|
||||
<span >${i18n.t('Press ', SNACKBAR_NAMESPACE)()}</span>
|
||||
<span class='SnackBar-esc'>${i18n.t('esc', SNACKBAR_NAMESPACE)()}</span>
|
||||
<span >${i18n.t(' to exit full screen', SNACKBAR_NAMESPACE)()}</span>
|
||||
</div>`
|
||||
);
|
||||
<span class='SnackBar-esc'>${i18n.t(
|
||||
'esc',
|
||||
SNACKBAR_NAMESPACE,
|
||||
)()}</span>
|
||||
<span >${i18n.t(
|
||||
' to exit full screen',
|
||||
SNACKBAR_NAMESPACE,
|
||||
)()}</span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { ipcRenderer } from 'electron';
|
||||
import * as React from 'react';
|
||||
import { svgAsPngUri } from 'save-svg-as-png';
|
||||
import { i18n } from '../../common/i18n-preload';
|
||||
import { AnalyticsElements, ScreenSnippetActionTypes } from './../../app/analytics-handler';
|
||||
import {
|
||||
AnalyticsElements,
|
||||
ScreenSnippetActionTypes,
|
||||
} from './../../app/analytics-handler';
|
||||
import AnnotateArea from './annotate-area';
|
||||
import ColorPickerPill, { IColor } from './color-picker-pill';
|
||||
|
||||
@@ -51,11 +54,16 @@ const availableHighlightColors: IColor[] = [
|
||||
];
|
||||
const SNIPPING_TOOL_NAMESPACE = 'ScreenSnippet';
|
||||
|
||||
export const sendAnalyticsToMain = (element: AnalyticsElements, type: ScreenSnippetActionTypes): void => {
|
||||
export const sendAnalyticsToMain = (
|
||||
element: AnalyticsElements,
|
||||
type: ScreenSnippetActionTypes,
|
||||
): void => {
|
||||
ipcRenderer.send('snippet-analytics-data', { element, type });
|
||||
};
|
||||
|
||||
const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPaths }) => {
|
||||
const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({
|
||||
existingPaths,
|
||||
}) => {
|
||||
// State preparation functions
|
||||
|
||||
const [screenSnippetPath, setScreenSnippetPath] = useState('');
|
||||
@@ -69,10 +77,12 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
});
|
||||
const [paths, setPaths] = useState<IPath[]>(existingPaths || []);
|
||||
const [chosenTool, setChosenTool] = useState(Tool.pen);
|
||||
const [penColor, setPenColor] = useState<IColor>({ rgbaColor: 'rgba(0, 142, 255, 1)' });
|
||||
const [highlightColor, setHighlightColor] = useState<IColor>(
|
||||
{ rgbaColor: 'rgba(0, 142, 255, 0.64)' },
|
||||
);
|
||||
const [penColor, setPenColor] = useState<IColor>({
|
||||
rgbaColor: 'rgba(0, 142, 255, 1)',
|
||||
});
|
||||
const [highlightColor, setHighlightColor] = useState<IColor>({
|
||||
rgbaColor: 'rgba(0, 142, 255, 0.64)',
|
||||
});
|
||||
const [
|
||||
shouldRenderHighlightColorPicker,
|
||||
setShouldRenderHighlightColorPicker,
|
||||
@@ -81,21 +91,33 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
false,
|
||||
);
|
||||
|
||||
const getSnipImageData = ({ }, {
|
||||
const getSnipImageData = (
|
||||
{},
|
||||
{
|
||||
snipImage,
|
||||
annotateAreaHeight,
|
||||
annotateAreaWidth,
|
||||
snippetImageHeight,
|
||||
snippetImageWidth,
|
||||
}) => {
|
||||
},
|
||||
) => {
|
||||
setScreenSnippetPath(snipImage);
|
||||
setImageDimensions({ height: snippetImageHeight, width: snippetImageWidth });
|
||||
setAnnotateAreaDimensions({ height: annotateAreaHeight, width: annotateAreaWidth });
|
||||
setImageDimensions({
|
||||
height: snippetImageHeight,
|
||||
width: snippetImageWidth,
|
||||
});
|
||||
setAnnotateAreaDimensions({
|
||||
height: annotateAreaHeight,
|
||||
width: annotateAreaWidth,
|
||||
});
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
ipcRenderer.once('snipping-tool-data', getSnipImageData);
|
||||
sendAnalyticsToMain(AnalyticsElements.SCREEN_CAPTURE_ANNOTATE, ScreenSnippetActionTypes.SCREENSHOT_TAKEN);
|
||||
sendAnalyticsToMain(
|
||||
AnalyticsElements.SCREEN_CAPTURE_ANNOTATE,
|
||||
ScreenSnippetActionTypes.SCREENSHOT_TAKEN,
|
||||
);
|
||||
return () => {
|
||||
ipcRenderer.removeListener('snipping-tool-data', getSnipImageData);
|
||||
};
|
||||
@@ -166,7 +188,10 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
return p;
|
||||
});
|
||||
setPaths(updPaths);
|
||||
sendAnalyticsToMain(AnalyticsElements.SCREEN_CAPTURE_ANNOTATE, ScreenSnippetActionTypes.ANNOTATE_CLEARED);
|
||||
sendAnalyticsToMain(
|
||||
AnalyticsElements.SCREEN_CAPTURE_ANNOTATE,
|
||||
ScreenSnippetActionTypes.ANNOTATE_CLEARED,
|
||||
);
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
@@ -189,7 +214,9 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
const color = penColor.outline ? penColor.outline : penColor.rgbaColor;
|
||||
return { border: '2px solid ' + color };
|
||||
} else if (chosenTool === Tool.highlight) {
|
||||
const color = highlightColor.outline ? highlightColor.outline : highlightColor.rgbaColor;
|
||||
const color = highlightColor.outline
|
||||
? highlightColor.outline
|
||||
: highlightColor.rgbaColor;
|
||||
return { border: '2px solid ' + color };
|
||||
} else if (chosenTool === Tool.eraser) {
|
||||
return { border: '2px solid #008EFF' };
|
||||
@@ -199,8 +226,13 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
|
||||
const done = async () => {
|
||||
const svg = document.getElementById('annotate-area');
|
||||
const mergedImageData = svg ? await svgAsPngUri(document.getElementById('annotate-area'), {}) : 'MERGE_FAIL';
|
||||
sendAnalyticsToMain(AnalyticsElements.SCREEN_CAPTURE_ANNOTATE, ScreenSnippetActionTypes.ANNOTATE_DONE );
|
||||
const mergedImageData = svg
|
||||
? await svgAsPngUri(document.getElementById('annotate-area'), {})
|
||||
: 'MERGE_FAIL';
|
||||
sendAnalyticsToMain(
|
||||
AnalyticsElements.SCREEN_CAPTURE_ANNOTATE,
|
||||
ScreenSnippetActionTypes.ANNOTATE_DONE,
|
||||
);
|
||||
ipcRenderer.send('upload-snippet', { screenSnippetPath, mergedImageData });
|
||||
};
|
||||
|
||||
@@ -247,22 +279,28 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{
|
||||
shouldRenderPenColorPicker && (
|
||||
<div style={{ marginTop: '64px', position: 'absolute', left: '50%' }} ref={colorPickerRef}>
|
||||
{shouldRenderPenColorPicker && (
|
||||
<div
|
||||
style={{ marginTop: '64px', position: 'absolute', left: '50%' }}
|
||||
ref={colorPickerRef}
|
||||
>
|
||||
<div style={{ position: 'relative', left: '-50%' }}>
|
||||
<ColorPickerPill
|
||||
data-testid='pen-colorpicker'
|
||||
availableColors={markChosenColor(availablePenColors, penColor.rgbaColor)}
|
||||
availableColors={markChosenColor(
|
||||
availablePenColors,
|
||||
penColor.rgbaColor,
|
||||
)}
|
||||
onChange={penColorChosen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
shouldRenderHighlightColorPicker && (
|
||||
<div style={{ marginTop: '64px', position: 'absolute', left: '50%' }} ref={colorPickerRef}>
|
||||
)}
|
||||
{shouldRenderHighlightColorPicker && (
|
||||
<div
|
||||
style={{ marginTop: '64px', position: 'absolute', left: '50%' }}
|
||||
ref={colorPickerRef}
|
||||
>
|
||||
<div style={{ position: 'relative', left: '-50%' }}>
|
||||
<ColorPickerPill
|
||||
data-testid='highlight-colorpicker'
|
||||
@@ -274,8 +312,7 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
<main>
|
||||
<div className='imageContainer'>
|
||||
@@ -293,14 +330,11 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<button
|
||||
data-testid='done-button'
|
||||
className='DoneButton'
|
||||
onClick={done}>
|
||||
<button data-testid='done-button' className='DoneButton' onClick={done}>
|
||||
{i18n.t('Done', SNIPPING_TOOL_NAMESPACE)()}
|
||||
</button>
|
||||
</footer>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ interface IState {
|
||||
const WELCOME_NAMESPACE = 'Welcome';
|
||||
|
||||
export default class Welcome extends React.Component<{}, IState> {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onSetPodUrl: () => this.setPodUrl(),
|
||||
};
|
||||
@@ -42,26 +41,42 @@ export default class Welcome extends React.Component<{}, IState> {
|
||||
/>
|
||||
</div>
|
||||
<div className='Welcome-main-container'>
|
||||
<h3 className='Welcome-name'>{i18n.t('Pod URL', WELCOME_NAMESPACE)()}</h3>
|
||||
<h3 className='Welcome-name'>
|
||||
{i18n.t('Pod URL', WELCOME_NAMESPACE)()}
|
||||
</h3>
|
||||
<div className='Welcome-main-container-input-div'>
|
||||
<div className='Welcome-main-container-input-selection'>
|
||||
<input className='Welcome-main-container-podurl-box'
|
||||
type='url' value={url}
|
||||
onChange={this.updatePodUrl.bind(this)}>
|
||||
</input>
|
||||
<input
|
||||
className='Welcome-main-container-podurl-box'
|
||||
type='url'
|
||||
value={url}
|
||||
onChange={this.updatePodUrl.bind(this)}
|
||||
></input>
|
||||
</div>
|
||||
<div className='Welcome-main-container-sso-box'
|
||||
title={i18n.t('Enable Single Sign On', WELCOME_NAMESPACE)()}>
|
||||
<div
|
||||
className='Welcome-main-container-sso-box'
|
||||
title={i18n.t('Enable Single Sign On', WELCOME_NAMESPACE)()}
|
||||
>
|
||||
<label>
|
||||
<input type='checkbox' checked={sso} onChange={this.updateSsoCheckbox.bind(this)}/>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={sso}
|
||||
onChange={this.updateSsoCheckbox.bind(this)}
|
||||
/>
|
||||
{i18n.t('SSO', WELCOME_NAMESPACE)()}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label className='Welcome-message-label'>{message}</label>
|
||||
<button className={!urlValid ? 'Welcome-continue-button-disabled' : 'Welcome-continue-button'}
|
||||
<button
|
||||
className={
|
||||
!urlValid
|
||||
? 'Welcome-continue-button-disabled'
|
||||
: 'Welcome-continue-button'
|
||||
}
|
||||
disabled={!urlValid}
|
||||
onClick={this.eventHandlers.onSetPodUrl}>
|
||||
onClick={this.eventHandlers.onSetPodUrl}
|
||||
>
|
||||
{i18n.t('Continue', WELCOME_NAMESPACE)()}
|
||||
</button>
|
||||
</div>
|
||||
@@ -101,7 +116,10 @@ export default class Welcome extends React.Component<{}, IState> {
|
||||
*/
|
||||
public updatePodUrl(_event): void {
|
||||
const url = _event.target.value.trim();
|
||||
const match = url.match(/(https?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/g) != null;
|
||||
const match =
|
||||
url.match(
|
||||
/(https?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/g,
|
||||
) != null;
|
||||
if (url === 'https://[POD].symphony.com' || !match) {
|
||||
this.updateState(_event, {
|
||||
url,
|
||||
@@ -111,7 +129,12 @@ export default class Welcome extends React.Component<{}, IState> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.updateState(_event, { url, message: '', urlValid: true, sso: this.state.sso });
|
||||
this.updateState(_event, {
|
||||
url,
|
||||
message: '',
|
||||
urlValid: true,
|
||||
sso: this.state.sso,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,5 +160,4 @@ export default class Welcome extends React.Component<{}, IState> {
|
||||
private updateState(_event, data): void {
|
||||
this.setState(data as IState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,19 +39,32 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
this.renderMaximizeButtons = this.renderMaximizeButtons.bind(this);
|
||||
// Event to capture and update icons
|
||||
this.window.on('maximize', () => this.updateState({ isMaximized: true }));
|
||||
this.window.on('unmaximize', () => this.updateState({ isMaximized: false }));
|
||||
this.window.on('enter-full-screen', () => this.updateState({ isFullScreen: true }));
|
||||
this.window.on('leave-full-screen', () => this.updateState({ isFullScreen: false }));
|
||||
this.window.on('unmaximize', () =>
|
||||
this.updateState({ isMaximized: false }),
|
||||
);
|
||||
this.window.on('enter-full-screen', () =>
|
||||
this.updateState({ isFullScreen: true }),
|
||||
);
|
||||
this.window.on('leave-full-screen', () =>
|
||||
this.updateState({ isFullScreen: false }),
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
const target = document.querySelector('title');
|
||||
this.observer = new MutationObserver((mutations) => {
|
||||
const title: string = mutations[0].target.textContent ? mutations[0].target.textContent : 'Symphony';
|
||||
this.setState({ title } );
|
||||
const title: string = mutations[0].target.textContent
|
||||
? mutations[0].target.textContent
|
||||
: 'Symphony';
|
||||
this.setState({ title });
|
||||
});
|
||||
if (target) {
|
||||
this.observer.observe(target, { attributes: true, childList: true, subtree: true, characterData: true });
|
||||
this.observer.observe(target, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -69,14 +82,18 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
* Renders the custom title bar
|
||||
*/
|
||||
public render(): JSX.Element | null {
|
||||
|
||||
const { title, isFullScreen } = this.state;
|
||||
const style = { display: isFullScreen ? 'none' : 'flex' };
|
||||
this.updateTitleBar();
|
||||
|
||||
return (
|
||||
<div id='title-bar'
|
||||
onDoubleClick={this.state.isMaximized ? this.eventHandlers.onUnmaximize : this.eventHandlers.onMaximize}
|
||||
<div
|
||||
id='title-bar'
|
||||
onDoubleClick={
|
||||
this.state.isMaximized
|
||||
? this.eventHandlers.onUnmaximize
|
||||
: this.eventHandlers.onMaximize
|
||||
}
|
||||
style={style}
|
||||
>
|
||||
<div className='title-bar-button-container'>
|
||||
@@ -88,9 +105,19 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
onMouseDown={this.handleMouseDown}
|
||||
>
|
||||
<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'/>
|
||||
<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>
|
||||
@@ -107,7 +134,7 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
onMouseDown={this.handleMouseDown}
|
||||
>
|
||||
<svg x='0px' y='0px' viewBox='0 0 14 1'>
|
||||
<rect fill='rgba(255, 255, 255, 0.9)' width='14' height='0.6'/>
|
||||
<rect fill='rgba(255, 255, 255, 0.9)' width='14' height='0.6' />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -210,7 +237,9 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
*/
|
||||
public unmaximize(): void {
|
||||
if (this.isValidWindow()) {
|
||||
this.window.isFullScreen() ? this.window.setFullScreen(false) : this.window.unmaximize();
|
||||
this.window.isFullScreen()
|
||||
? this.window.setFullScreen(false)
|
||||
: this.window.unmaximize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +258,7 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
* verifies if the this.window is valid and is not destroyed
|
||||
*/
|
||||
public isValidWindow(): boolean {
|
||||
return (this.window && !this.window.isDestroyed());
|
||||
return this.window && !this.window.isDestroyed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,12 +288,20 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
const contentWrapper = document.getElementById('content-wrapper');
|
||||
const appView = document.getElementsByClassName('jss1')[0] as HTMLElement;
|
||||
const root = document.getElementById('root');
|
||||
const railContainer = document.getElementsByClassName('ReactRail-container-2')[0] as HTMLElement;
|
||||
const railList = document.getElementsByClassName('railList')[0] as HTMLElement;
|
||||
const railContainer = document.getElementsByClassName(
|
||||
'ReactRail-container-2',
|
||||
)[0] as HTMLElement;
|
||||
const railList = document.getElementsByClassName(
|
||||
'railList',
|
||||
)[0] as HTMLElement;
|
||||
if (railContainer) {
|
||||
railContainer.style.height = isFullScreen ? '100vh' : `calc(100vh - ${titleBarHeight})`;
|
||||
railContainer.style.height = isFullScreen
|
||||
? '100vh'
|
||||
: `calc(100vh - ${titleBarHeight})`;
|
||||
} else if (railList) {
|
||||
railList.style.height = isFullScreen ? '100vh' : `calc(100vh - ${titleBarHeight})`;
|
||||
railList.style.height = isFullScreen
|
||||
? '100vh'
|
||||
: `calc(100vh - ${titleBarHeight})`;
|
||||
}
|
||||
if (!contentWrapper && !root) {
|
||||
document.body.style.marginTop = isFullScreen ? '0px' : titleBarHeight;
|
||||
@@ -274,16 +311,22 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
if (root) {
|
||||
const rootChild = root.firstElementChild as HTMLElement;
|
||||
if (rootChild && rootChild.style && rootChild.style.height === '100vh') {
|
||||
rootChild.style.height = isFullScreen ? '100vh' : `calc(100vh - ${titleBarHeight})`;
|
||||
rootChild.style.height = isFullScreen
|
||||
? '100vh'
|
||||
: `calc(100vh - ${titleBarHeight})`;
|
||||
}
|
||||
root.style.height = isFullScreen ? '100vh' : `calc(100vh - ${titleBarHeight})`;
|
||||
root.style.height = isFullScreen
|
||||
? '100vh'
|
||||
: `calc(100vh - ${titleBarHeight})`;
|
||||
root.style.marginTop = isFullScreen ? '0px' : titleBarHeight;
|
||||
} else if (contentWrapper) {
|
||||
contentWrapper.style.marginTop = isFullScreen ? '0px' : titleBarHeight;
|
||||
}
|
||||
|
||||
if (appView) {
|
||||
appView.style.height = isFullScreen ? '100vh' : `calc(100vh - ${titleBarHeight})`;
|
||||
appView.style.height = isFullScreen
|
||||
? '100vh'
|
||||
: `calc(100vh - ${titleBarHeight})`;
|
||||
}
|
||||
if (isFullScreen) {
|
||||
document.body.style.removeProperty('margin-top');
|
||||
@@ -313,7 +356,8 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
cy={0}
|
||||
r={1}
|
||||
gradientUnits='userSpaceOnUse'
|
||||
gradientTransform='matrix(0 40.259 -50.3704 0 20.07 0)'>
|
||||
gradientTransform='matrix(0 40.259 -50.3704 0 20.07 0)'
|
||||
>
|
||||
<stop stopColor='#fff' stopOpacity={0.4} />
|
||||
<stop offset={1} stopColor='#fff' stopOpacity={0} />
|
||||
</radialGradient>
|
||||
|
||||
@@ -6,7 +6,11 @@ import {
|
||||
SourcesOptions,
|
||||
} from 'electron';
|
||||
|
||||
import { apiCmds, apiName, NOTIFICATION_WINDOW_TITLE } from '../common/api-interface';
|
||||
import {
|
||||
apiCmds,
|
||||
apiName,
|
||||
NOTIFICATION_WINDOW_TITLE,
|
||||
} from '../common/api-interface';
|
||||
import { isWindowsOS } from '../common/env';
|
||||
import { i18n } from '../common/i18n-preload';
|
||||
|
||||
@@ -30,7 +34,10 @@ export interface IScreenSourceError {
|
||||
requestId: number | undefined;
|
||||
}
|
||||
|
||||
export type CallbackType = (error: IScreenSourceError | null, source?: ICustomDesktopCapturerSource) => void;
|
||||
export type CallbackType = (
|
||||
error: IScreenSourceError | null,
|
||||
source?: ICustomDesktopCapturerSource,
|
||||
) => void;
|
||||
const getNextId = () => ++nextId;
|
||||
|
||||
/**
|
||||
@@ -39,7 +46,10 @@ const getNextId = () => ++nextId;
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isValid = (options: ICustomSourcesOptions) => {
|
||||
return ((options !== null ? options.types : undefined) !== null) && Array.isArray(options.types);
|
||||
return (
|
||||
(options !== null ? options.types : undefined) !== null &&
|
||||
Array.isArray(options.types)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -49,14 +59,21 @@ const isValid = (options: ICustomSourcesOptions) => {
|
||||
* @param callback {CallbackType}
|
||||
* @returns {*}
|
||||
*/
|
||||
export const getSource = async (options: ICustomSourcesOptions, callback: CallbackType) => {
|
||||
export const getSource = async (
|
||||
options: ICustomSourcesOptions,
|
||||
callback: CallbackType,
|
||||
) => {
|
||||
let captureWindow;
|
||||
let captureScreen;
|
||||
let id;
|
||||
const sourcesOpts: string[] = [];
|
||||
const { requestId, ...updatedOptions } = options;
|
||||
if (!isValid(options)) {
|
||||
callback({ name: 'Invalid options', message: 'Invalid options', requestId });
|
||||
callback({
|
||||
name: 'Invalid options',
|
||||
message: 'Invalid options',
|
||||
requestId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
captureWindow = includes.call(options.types, 'window');
|
||||
@@ -92,32 +109,43 @@ export const getSource = async (options: ICustomSourcesOptions, callback: Callba
|
||||
const focusedWindow = remote.BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
remote.dialog.showMessageBox(focusedWindow, {
|
||||
message: `${i18n.t('Your administrator has disabled sharing your screen. Please contact your admin for help', 'Permissions')()}`,
|
||||
message: `${i18n.t(
|
||||
'Your administrator has disabled sharing your screen. Please contact your admin for help',
|
||||
'Permissions',
|
||||
)()}`,
|
||||
title: `${i18n.t('Permission Denied')()}!`,
|
||||
type: 'error',
|
||||
});
|
||||
callback({ name: 'Permission Denied', message: 'Permission Denied', requestId });
|
||||
callback({
|
||||
name: 'Permission Denied',
|
||||
message: 'Permission Denied',
|
||||
requestId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
id = getNextId();
|
||||
const sources: DesktopCapturerSource[] = await desktopCapturer.getSources({ types: sourcesOpts, thumbnailSize: updatedOptions.thumbnailSize });
|
||||
const sources: DesktopCapturerSource[] = await desktopCapturer.getSources({
|
||||
types: sourcesOpts,
|
||||
thumbnailSize: updatedOptions.thumbnailSize,
|
||||
});
|
||||
// Auto select screen source based on args for testing only
|
||||
if (screenShareArgv) {
|
||||
const title = screenShareArgv.substr(screenShareArgv.indexOf('=') + 1);
|
||||
const filteredSource: DesktopCapturerSource[] = sources.filter((source) => source.name === title);
|
||||
const filteredSource: DesktopCapturerSource[] = sources.filter(
|
||||
(source) => source.name === title,
|
||||
);
|
||||
|
||||
if (Array.isArray(filteredSource) && filteredSource.length > 0) {
|
||||
const source = { ...filteredSource[ 0 ], requestId };
|
||||
const source = { ...filteredSource[0], requestId };
|
||||
return callback(null, source);
|
||||
}
|
||||
|
||||
if (sources.length > 0) {
|
||||
const firstSource = { ...sources[ 0 ], requestId };
|
||||
const firstSource = { ...sources[0], requestId };
|
||||
return callback(null, firstSource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const updatedSources = sources
|
||||
@@ -138,7 +166,11 @@ export const getSource = async (options: ICustomSourcesOptions, callback: Callba
|
||||
// Cleaning up the event listener to prevent memory leaks
|
||||
if (!source) {
|
||||
ipcRenderer.removeListener('start-share' + id, successCallback);
|
||||
return callback({ name: 'User Cancelled', message: 'User Cancelled', requestId });
|
||||
return callback({
|
||||
name: 'User Cancelled',
|
||||
message: 'User Cancelled',
|
||||
requestId,
|
||||
});
|
||||
}
|
||||
return callback(null, { ...source, ...{ requestId } });
|
||||
};
|
||||
|
||||
@@ -58,7 +58,11 @@ export default class NotificationHandler {
|
||||
* @param x {number}
|
||||
* @param y {number}
|
||||
*/
|
||||
public setWindowPosition(window: Electron.BrowserWindow, x: number = 0, y: number = 0) {
|
||||
public setWindowPosition(
|
||||
window: Electron.BrowserWindow,
|
||||
x: number = 0,
|
||||
y: number = 0,
|
||||
) {
|
||||
if (window && !window.isDestroyed()) {
|
||||
window.setPosition(parseInt(String(x), 10), parseInt(String(y), 10));
|
||||
}
|
||||
@@ -88,7 +92,7 @@ export default class NotificationHandler {
|
||||
// update corner x/y based on corner of screen where notification should appear
|
||||
const workAreaWidth = display.workAreaSize.width;
|
||||
const workAreaHeight = display.workAreaSize.height;
|
||||
const offSet = (isMac || isLinux ? 20 : 10);
|
||||
const offSet = isMac || isLinux ? 20 : 10;
|
||||
switch (this.settings.startCorner) {
|
||||
case 'upper-right':
|
||||
this.settings.corner.x += workAreaWidth - offSet;
|
||||
@@ -112,7 +116,9 @@ export default class NotificationHandler {
|
||||
}
|
||||
this.calculateDimensions();
|
||||
// Maximum amount of Notifications we can show:
|
||||
this.settings.maxVisibleNotifications = Math.floor(display.workAreaSize.height / this.settings.totalHeight);
|
||||
this.settings.maxVisibleNotifications = Math.floor(
|
||||
display.workAreaSize.height / this.settings.totalHeight,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +129,10 @@ export default class NotificationHandler {
|
||||
activeNotifications.forEach((notification) => {
|
||||
if (notification && windowExists(notification)) {
|
||||
const [, height] = notification.getSize();
|
||||
nextNotificationY += height > this.settings.height ? NEXT_INSERT_POSITION_WITH_INPUT : NEXT_INSERT_POSITION;
|
||||
nextNotificationY +=
|
||||
height > this.settings.height
|
||||
? NEXT_INSERT_POSITION_WITH_INPUT
|
||||
: NEXT_INSERT_POSITION;
|
||||
}
|
||||
});
|
||||
if (activeNotifications.length < this.settings.maxVisibleNotifications) {
|
||||
@@ -136,7 +145,8 @@ export default class NotificationHandler {
|
||||
default:
|
||||
case 'lower-right':
|
||||
case 'lower-left':
|
||||
this.nextInsertPos.y = this.settings.corner.y - (nextNotificationY + NEXT_INSERT_POSITION);
|
||||
this.nextInsertPos.y =
|
||||
this.settings.corner.y - (nextNotificationY + NEXT_INSERT_POSITION);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -150,7 +160,12 @@ export default class NotificationHandler {
|
||||
* @param height {number} height of the closed notification
|
||||
* @param isReset {boolean} whether to reset all notification position
|
||||
*/
|
||||
public moveNotificationDown(startPos, activeNotifications, height: number = 0, isReset: boolean = false) {
|
||||
public moveNotificationDown(
|
||||
startPos,
|
||||
activeNotifications,
|
||||
height: number = 0,
|
||||
isReset: boolean = false,
|
||||
) {
|
||||
if (startPos >= activeNotifications || startPos === -1) {
|
||||
return;
|
||||
}
|
||||
@@ -173,15 +188,15 @@ export default class NotificationHandler {
|
||||
case 'upper-right':
|
||||
case 'upper-left':
|
||||
newY = isReset
|
||||
? this.settings.corner.y + (this.settings.totalHeight * i)
|
||||
: (y - height - this.settings.spacing);
|
||||
? this.settings.corner.y + this.settings.totalHeight * i
|
||||
: y - height - this.settings.spacing;
|
||||
break;
|
||||
default:
|
||||
case 'lower-right':
|
||||
case 'lower-left':
|
||||
newY = isReset
|
||||
? this.settings.corner.y - (this.settings.totalHeight * (i + 1))
|
||||
: (y + height + this.settings.spacing);
|
||||
? this.settings.corner.y - this.settings.totalHeight * (i + 1)
|
||||
: y + height + this.settings.spacing;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -199,7 +214,10 @@ export default class NotificationHandler {
|
||||
if (startPos >= activeNotifications || startPos === -1) {
|
||||
return;
|
||||
}
|
||||
if (this.settings.startCorner === 'lower-right' || this.settings.startCorner === 'lower-left') {
|
||||
if (
|
||||
this.settings.startCorner === 'lower-right' ||
|
||||
this.settings.startCorner === 'lower-left'
|
||||
) {
|
||||
startPos -= 1;
|
||||
}
|
||||
// Build array with index of affected notifications
|
||||
@@ -247,13 +265,21 @@ export default class NotificationHandler {
|
||||
const animationInterval = setInterval(() => {
|
||||
// Abort condition
|
||||
if (curStep === this.settings.animationSteps) {
|
||||
this.setWindowPosition(notificationWindow, this.settings.firstPos.x, newY);
|
||||
this.setWindowPosition(
|
||||
notificationWindow,
|
||||
this.settings.firstPos.x,
|
||||
newY,
|
||||
);
|
||||
clearInterval(animationInterval);
|
||||
done(null, 'done');
|
||||
return;
|
||||
}
|
||||
// Move one step down
|
||||
this.setWindowPosition(notificationWindow, this.settings.firstPos.x, startY + curStep * step);
|
||||
this.setWindowPosition(
|
||||
notificationWindow,
|
||||
this.settings.firstPos.x,
|
||||
startY + curStep * step,
|
||||
);
|
||||
curStep++;
|
||||
}, this.settings.animationStepMs);
|
||||
}
|
||||
@@ -300,5 +326,4 @@ export default class NotificationHandler {
|
||||
this.nextInsertPos.x = this.settings.firstPos.x;
|
||||
this.nextInsertPos.y = this.settings.firstPos.y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { remote } from 'electron';
|
||||
|
||||
import { INotificationData, NotificationActions } from '../common/api-interface';
|
||||
import {
|
||||
INotificationData,
|
||||
NotificationActions,
|
||||
} from '../common/api-interface';
|
||||
const notification = remote.require('../renderer/notification').notification;
|
||||
|
||||
let latestID = 0;
|
||||
@@ -10,12 +13,12 @@ let latestID = 0;
|
||||
* this class is to mock the Window.Notification interface
|
||||
*/
|
||||
export default class SSFNotificationHandler {
|
||||
|
||||
public _data: INotificationData;
|
||||
|
||||
private readonly id: number;
|
||||
private readonly eventHandlers = {
|
||||
onClick: (event: NotificationActions, _data: INotificationData) => this.notificationClicked(event),
|
||||
onClick: (event: NotificationActions, _data: INotificationData) =>
|
||||
this.notificationClicked(event),
|
||||
};
|
||||
private notificationClickCallback: (({ target }) => {}) | undefined;
|
||||
private notificationCloseCallback: (({ target }) => {}) | undefined;
|
||||
@@ -23,7 +26,10 @@ export default class SSFNotificationHandler {
|
||||
constructor(title, options) {
|
||||
this.id = latestID;
|
||||
latestID++;
|
||||
notification.showNotification({ ...options, title, id: this.id }, this.eventHandlers.onClick);
|
||||
notification.showNotification(
|
||||
{ ...options, title, id: this.id },
|
||||
this.eventHandlers.onClick,
|
||||
);
|
||||
this._data = options.data;
|
||||
}
|
||||
|
||||
@@ -76,12 +82,18 @@ export default class SSFNotificationHandler {
|
||||
private notificationClicked(event: NotificationActions): void {
|
||||
switch (event) {
|
||||
case NotificationActions.notificationClicked:
|
||||
if (this.notificationClickCallback && typeof this.notificationClickCallback === 'function') {
|
||||
if (
|
||||
this.notificationClickCallback &&
|
||||
typeof this.notificationClickCallback === 'function'
|
||||
) {
|
||||
this.notificationClickCallback({ target: this });
|
||||
}
|
||||
break;
|
||||
case NotificationActions.notificationClosed:
|
||||
if (this.notificationCloseCallback && typeof this.notificationCloseCallback === 'function') {
|
||||
if (
|
||||
this.notificationCloseCallback &&
|
||||
typeof this.notificationCloseCallback === 'function'
|
||||
) {
|
||||
this.notificationCloseCallback({ target: this });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@ import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
import { config } from '../app/config-handler';
|
||||
import { createComponentWindow, windowExists } from '../app/window-utils';
|
||||
import { AnimationQueue } from '../common/animation-queue';
|
||||
import { apiName, INotificationData, NOTIFICATION_WINDOW_TITLE, NotificationActions } from '../common/api-interface';
|
||||
import {
|
||||
apiName,
|
||||
INotificationData,
|
||||
NOTIFICATION_WINDOW_TITLE,
|
||||
NotificationActions,
|
||||
} from '../common/api-interface';
|
||||
import { isNodeEnv, isWindowsOS } from '../common/env';
|
||||
import { logger } from '../common/logger';
|
||||
import NotificationHandler from './notification-handler';
|
||||
@@ -49,12 +54,13 @@ const notificationSettings = {
|
||||
};
|
||||
|
||||
class Notification extends NotificationHandler {
|
||||
|
||||
private readonly funcHandlers = {
|
||||
onCleanUpInactiveNotification: () => this.cleanUpInactiveNotification(),
|
||||
onCreateNotificationWindow: (data: INotificationData) => this.createNotificationWindow(data),
|
||||
onCreateNotificationWindow: (data: INotificationData) =>
|
||||
this.createNotificationWindow(data),
|
||||
onMouseOver: (_event, windowId) => this.onMouseOver(windowId),
|
||||
onMouseLeave: (_event, windowId, isInputHidden) => this.onMouseLeave(windowId, isInputHidden),
|
||||
onMouseLeave: (_event, windowId, isInputHidden) =>
|
||||
this.onMouseLeave(windowId, isInputHidden),
|
||||
onShowReply: (_event, windowId) => this.onShowReply(windowId),
|
||||
};
|
||||
private activeNotifications: Electron.BrowserWindow[] = [];
|
||||
@@ -83,7 +89,10 @@ class Notification extends NotificationHandler {
|
||||
ipcMain.on('show-reply', this.funcHandlers.onShowReply);
|
||||
// Update latest notification settings from config
|
||||
app.on('ready', () => this.updateNotificationSettings());
|
||||
this.cleanUpTimer = setInterval(this.funcHandlers.onCleanUpInactiveNotification, CLEAN_UP_INTERVAL);
|
||||
this.cleanUpTimer = setInterval(
|
||||
this.funcHandlers.onCleanUpInactiveNotification,
|
||||
CLEAN_UP_INTERVAL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,10 +105,13 @@ class Notification extends NotificationHandler {
|
||||
clearInterval(this.cleanUpTimer);
|
||||
animationQueue.push({
|
||||
func: this.funcHandlers.onCreateNotificationWindow,
|
||||
args: [ data ],
|
||||
args: [data],
|
||||
});
|
||||
this.notificationCallbacks[ data.id ] = callback;
|
||||
this.cleanUpTimer = setInterval(this.funcHandlers.onCleanUpInactiveNotification, CLEAN_UP_INTERVAL);
|
||||
this.notificationCallbacks[data.id] = callback;
|
||||
this.cleanUpTimer = setInterval(
|
||||
this.funcHandlers.onCleanUpInactiveNotification,
|
||||
CLEAN_UP_INTERVAL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,21 +119,27 @@ class Notification extends NotificationHandler {
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
public async createNotificationWindow(data): Promise<ICustomBrowserWindow | undefined> {
|
||||
|
||||
public async createNotificationWindow(
|
||||
data,
|
||||
): Promise<ICustomBrowserWindow | undefined> {
|
||||
// TODO: Handle MAX_QUEUE_SIZE
|
||||
if (data.tag) {
|
||||
for (let i = 0; i < this.notificationQueue.length; i++) {
|
||||
if (this.notificationQueue[ i ].tag === data.tag) {
|
||||
this.notificationQueue[ i ] = data;
|
||||
if (this.notificationQueue[i].tag === data.tag) {
|
||||
this.notificationQueue[i] = data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const window of this.activeNotifications) {
|
||||
const notificationWin = window as ICustomBrowserWindow;
|
||||
const winHeight = windowExists(notificationWin) && notificationWin.getBounds().height;
|
||||
if (window && notificationWin.notificationData.tag === data.tag && winHeight < CONTAINER_HEIGHT_WITH_INPUT) {
|
||||
const winHeight =
|
||||
windowExists(notificationWin) && notificationWin.getBounds().height;
|
||||
if (
|
||||
window &&
|
||||
notificationWin.notificationData.tag === data.tag &&
|
||||
winHeight < CONTAINER_HEIGHT_WITH_INPUT
|
||||
) {
|
||||
this.setNotificationContent(notificationWin, data);
|
||||
return;
|
||||
}
|
||||
@@ -130,7 +148,9 @@ class Notification extends NotificationHandler {
|
||||
|
||||
// Checks if number of active notification displayed is greater than or equal to the
|
||||
// max displayable notification and queues them
|
||||
if (this.activeNotifications.length >= this.settings.maxVisibleNotifications) {
|
||||
if (
|
||||
this.activeNotifications.length >= this.settings.maxVisibleNotifications
|
||||
) {
|
||||
this.notificationQueue.push(data);
|
||||
return;
|
||||
}
|
||||
@@ -154,8 +174,12 @@ class Notification extends NotificationHandler {
|
||||
notificationWindow.notificationData = data;
|
||||
notificationWindow.winName = apiName.notificationWindowName;
|
||||
notificationWindow.once('closed', () => {
|
||||
const activeWindowIndex = this.activeNotifications.indexOf(notificationWindow);
|
||||
const inactiveWindowIndex = this.inactiveWindows.indexOf(notificationWindow);
|
||||
const activeWindowIndex = this.activeNotifications.indexOf(
|
||||
notificationWindow,
|
||||
);
|
||||
const inactiveWindowIndex = this.inactiveWindows.indexOf(
|
||||
notificationWindow,
|
||||
);
|
||||
|
||||
if (activeWindowIndex !== -1) {
|
||||
this.activeNotifications.splice(activeWindowIndex, 1);
|
||||
@@ -181,10 +205,15 @@ class Notification extends NotificationHandler {
|
||||
* @param notificationWindow
|
||||
* @param data {INotificationData}
|
||||
*/
|
||||
public setNotificationContent(notificationWindow: ICustomBrowserWindow, data: INotificationData): void {
|
||||
public setNotificationContent(
|
||||
notificationWindow: ICustomBrowserWindow,
|
||||
data: INotificationData,
|
||||
): void {
|
||||
notificationWindow.clientId = data.id;
|
||||
notificationWindow.notificationData = data;
|
||||
const displayTime = data.displayTime ? data.displayTime : notificationSettings.displayTime;
|
||||
const displayTime = data.displayTime
|
||||
? data.displayTime
|
||||
: notificationSettings.displayTime;
|
||||
let timeoutId;
|
||||
|
||||
// Reset the display timer
|
||||
@@ -192,7 +221,11 @@ class Notification extends NotificationHandler {
|
||||
clearTimeout(notificationWindow.displayTimer);
|
||||
}
|
||||
// Reset notification window size to default
|
||||
notificationWindow.setSize(notificationSettings.width, notificationSettings.height, true);
|
||||
notificationWindow.setSize(
|
||||
notificationSettings.width,
|
||||
notificationSettings.height,
|
||||
true,
|
||||
);
|
||||
// Move notification to top
|
||||
notificationWindow.moveTop();
|
||||
|
||||
@@ -246,7 +279,10 @@ class Notification extends NotificationHandler {
|
||||
const pos = this.activeNotifications.indexOf(browserWindow);
|
||||
this.activeNotifications.splice(pos, 1);
|
||||
|
||||
if (this.inactiveWindows.length < this.settings.maxVisibleNotifications || 5) {
|
||||
if (
|
||||
this.inactiveWindows.length < this.settings.maxVisibleNotifications ||
|
||||
5
|
||||
) {
|
||||
this.inactiveWindows.push(browserWindow);
|
||||
browserWindow.hide();
|
||||
} else {
|
||||
@@ -255,12 +291,15 @@ class Notification extends NotificationHandler {
|
||||
|
||||
this.moveNotificationDown(pos, this.activeNotifications, height);
|
||||
|
||||
if (this.notificationQueue.length > 0 && this.activeNotifications.length < this.settings.maxVisibleNotifications) {
|
||||
if (
|
||||
this.notificationQueue.length > 0 &&
|
||||
this.activeNotifications.length < this.settings.maxVisibleNotifications
|
||||
) {
|
||||
const notificationData = this.notificationQueue[0];
|
||||
this.notificationQueue.splice(0, 1);
|
||||
animationQueue.push({
|
||||
func: this.funcHandlers.onCreateNotificationWindow,
|
||||
args: [ notificationData ],
|
||||
args: [notificationData],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -274,9 +313,13 @@ class Notification extends NotificationHandler {
|
||||
*/
|
||||
public notificationClicked(clientId): void {
|
||||
const browserWindow = this.getNotificationWindow(clientId);
|
||||
if (browserWindow && windowExists(browserWindow) && browserWindow.notificationData) {
|
||||
if (
|
||||
browserWindow &&
|
||||
windowExists(browserWindow) &&
|
||||
browserWindow.notificationData
|
||||
) {
|
||||
const data = browserWindow.notificationData;
|
||||
const callback = this.notificationCallbacks[ clientId ];
|
||||
const callback = this.notificationCallbacks[clientId];
|
||||
if (typeof callback === 'function') {
|
||||
callback(NotificationActions.notificationClicked, data);
|
||||
}
|
||||
@@ -293,9 +336,13 @@ class Notification extends NotificationHandler {
|
||||
*/
|
||||
public notificationClosed(clientId): void {
|
||||
const browserWindow = this.getNotificationWindow(clientId);
|
||||
if (browserWindow && windowExists(browserWindow) && browserWindow.notificationData) {
|
||||
if (
|
||||
browserWindow &&
|
||||
windowExists(browserWindow) &&
|
||||
browserWindow.notificationData
|
||||
) {
|
||||
const data = browserWindow.notificationData;
|
||||
const callback = this.notificationCallbacks[ clientId ];
|
||||
const callback = this.notificationCallbacks[clientId];
|
||||
if (typeof callback === 'function') {
|
||||
callback(NotificationActions.notificationClosed, data);
|
||||
}
|
||||
@@ -309,9 +356,13 @@ class Notification extends NotificationHandler {
|
||||
*/
|
||||
public onNotificationReply(clientId: number, replyText: string): void {
|
||||
const browserWindow = this.getNotificationWindow(clientId);
|
||||
if (browserWindow && windowExists(browserWindow) && browserWindow.notificationData) {
|
||||
if (
|
||||
browserWindow &&
|
||||
windowExists(browserWindow) &&
|
||||
browserWindow.notificationData
|
||||
) {
|
||||
const data = browserWindow.notificationData;
|
||||
const callback = this.notificationCallbacks[ clientId ];
|
||||
const callback = this.notificationCallbacks[clientId];
|
||||
if (typeof callback === 'function') {
|
||||
callback(NotificationActions.notificationReply, data, replyText);
|
||||
}
|
||||
@@ -325,7 +376,9 @@ class Notification extends NotificationHandler {
|
||||
*
|
||||
* @param clientId {number}
|
||||
*/
|
||||
public getNotificationWindow(clientId: number): ICustomBrowserWindow | undefined {
|
||||
public getNotificationWindow(
|
||||
clientId: number,
|
||||
): ICustomBrowserWindow | undefined {
|
||||
const index: number = this.activeNotifications.findIndex((win) => {
|
||||
const notificationWindow = win as ICustomBrowserWindow;
|
||||
return notificationWindow.clientId === clientId;
|
||||
@@ -333,14 +386,16 @@ class Notification extends NotificationHandler {
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
return this.activeNotifications[ index ] as ICustomBrowserWindow;
|
||||
return this.activeNotifications[index] as ICustomBrowserWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update latest notification settings from config
|
||||
*/
|
||||
public updateNotificationSettings(): void {
|
||||
const { display, position } = config.getConfigFields([ 'notificationSettings' ]).notificationSettings;
|
||||
const { display, position } = config.getConfigFields([
|
||||
'notificationSettings',
|
||||
]).notificationSettings;
|
||||
this.settings.displayId = display;
|
||||
this.settings.startCorner = position as startCorner;
|
||||
|
||||
@@ -355,11 +410,16 @@ class Notification extends NotificationHandler {
|
||||
public async cleanUp(): Promise<void> {
|
||||
animationQueue.clear();
|
||||
this.notificationQueue = [];
|
||||
const activeNotificationWindows = Object.assign([], this.activeNotifications);
|
||||
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);
|
||||
await this.hideNotification(
|
||||
(activeWindow as ICustomBrowserWindow).clientId,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const inactiveWindow of inactiveNotificationWindows) {
|
||||
@@ -377,11 +437,20 @@ class Notification extends NotificationHandler {
|
||||
* issue: ELECTRON-1382
|
||||
*/
|
||||
public moveNotificationToTop(): void {
|
||||
const notificationWindows = this.activeNotifications as ICustomBrowserWindow[];
|
||||
const notificationWindows = this
|
||||
.activeNotifications as ICustomBrowserWindow[];
|
||||
notificationWindows
|
||||
.filter((browserWindow) => typeof browserWindow.notificationData === 'object' && browserWindow.isVisible())
|
||||
.filter(
|
||||
(browserWindow) =>
|
||||
typeof browserWindow.notificationData === 'object' &&
|
||||
browserWindow.isVisible(),
|
||||
)
|
||||
.forEach((browserWindow) => {
|
||||
if (browserWindow && windowExists(browserWindow) && browserWindow.isVisible()) {
|
||||
if (
|
||||
browserWindow &&
|
||||
windowExists(browserWindow) &&
|
||||
browserWindow.isVisible()
|
||||
) {
|
||||
browserWindow.moveTop();
|
||||
}
|
||||
});
|
||||
@@ -395,8 +464,13 @@ class Notification extends NotificationHandler {
|
||||
const browserWindows: ICustomBrowserWindow[] = BrowserWindow.getAllWindows() as ICustomBrowserWindow[];
|
||||
for (const win in browserWindows) {
|
||||
if (Object.prototype.hasOwnProperty.call(browserWindows, win)) {
|
||||
const browserWin = browserWindows[ win ];
|
||||
if (browserWin && windowExists(browserWin) && browserWin.winName === apiName.mainWindowName && browserWin.isFullScreen()) {
|
||||
const browserWin = browserWindows[win];
|
||||
if (
|
||||
browserWin &&
|
||||
windowExists(browserWin) &&
|
||||
browserWin.winName === apiName.mainWindowName &&
|
||||
browserWin.isFullScreen()
|
||||
) {
|
||||
browserWin.webContents.send('exit-html-fullscreen');
|
||||
return;
|
||||
}
|
||||
@@ -429,8 +503,15 @@ class Notification extends NotificationHandler {
|
||||
*/
|
||||
private renderNotification(notificationWindow, data): void {
|
||||
this.calcNextInsertPos(this.activeNotifications);
|
||||
this.setWindowPosition(notificationWindow, this.nextInsertPos.x, this.nextInsertPos.y);
|
||||
this.setNotificationContent(notificationWindow, { ...data, windowId: notificationWindow.id });
|
||||
this.setWindowPosition(
|
||||
notificationWindow,
|
||||
this.nextInsertPos.x,
|
||||
this.nextInsertPos.y,
|
||||
);
|
||||
this.setNotificationContent(notificationWindow, {
|
||||
...data,
|
||||
windowId: notificationWindow.id,
|
||||
});
|
||||
this.activeNotifications.push(notificationWindow);
|
||||
}
|
||||
|
||||
@@ -439,13 +520,17 @@ class Notification extends NotificationHandler {
|
||||
*/
|
||||
private cleanUpInactiveNotification() {
|
||||
if (this.inactiveWindows.length > 0) {
|
||||
logger.info('notification: cleaning up inactive notification windows', {inactiveNotification: this.inactiveWindows.length});
|
||||
logger.info('notification: cleaning up inactive notification windows', {
|
||||
inactiveNotification: this.inactiveWindows.length,
|
||||
});
|
||||
this.inactiveWindows.forEach((window) => {
|
||||
if (windowExists(window)) {
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
logger.info(`notification: cleaned up inactive notification windows`, {inactiveNotification: this.inactiveWindows.length});
|
||||
logger.info(`notification: cleaned up inactive notification windows`, {
|
||||
inactiveNotification: this.inactiveWindows.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +559,10 @@ class Notification extends NotificationHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (notificationWindow.notificationData && notificationWindow.notificationData.sticky) {
|
||||
if (
|
||||
notificationWindow.notificationData &&
|
||||
notificationWindow.notificationData.sticky
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -482,7 +570,9 @@ class Notification extends NotificationHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
const displayTime = (notificationWindow.notificationData && notificationWindow.notificationData.displayTime)
|
||||
const displayTime =
|
||||
notificationWindow.notificationData &&
|
||||
notificationWindow.notificationData.displayTime
|
||||
? notificationWindow.notificationData.displayTime
|
||||
: notificationSettings.displayTime;
|
||||
if (notificationWindow && windowExists(notificationWindow)) {
|
||||
@@ -537,6 +627,4 @@ class Notification extends NotificationHandler {
|
||||
|
||||
const notification = new Notification(notificationSettings);
|
||||
|
||||
export {
|
||||
notification,
|
||||
};
|
||||
export { notification };
|
||||
|
||||
@@ -77,7 +77,10 @@ const load = () => {
|
||||
component = NotificationComp;
|
||||
break;
|
||||
case components.notificationSettings:
|
||||
document.title = i18n.t('Notification Settings - Symphony', 'NotificationSettings')();
|
||||
document.title = i18n.t(
|
||||
'Notification Settings - Symphony',
|
||||
'NotificationSettings',
|
||||
)();
|
||||
loadStyle(components.notificationSettings);
|
||||
component = NotificationSettings;
|
||||
break;
|
||||
|
||||
@@ -114,19 +114,22 @@ const monitorMemory = (time) => {
|
||||
cmd: apiCmds.memoryInfo,
|
||||
memoryInfo,
|
||||
});
|
||||
monitorMemory(getRandomTime(minMemoryFetchInterval, maxMemoryFetchInterval));
|
||||
monitorMemory(
|
||||
getRandomTime(minMemoryFetchInterval, maxMemoryFetchInterval),
|
||||
);
|
||||
}, time);
|
||||
};
|
||||
|
||||
// When the window is completely loaded
|
||||
ipcRenderer.on('page-load', (_event, { locale, resources, enableCustomTitleBar }) => {
|
||||
|
||||
ipcRenderer.on(
|
||||
'page-load',
|
||||
(_event, { locale, resources, enableCustomTitleBar }) => {
|
||||
i18n.setResource(locale, resources);
|
||||
|
||||
if (enableCustomTitleBar) {
|
||||
// injects custom title bar
|
||||
const element = React.createElement(WindowsTitleBar);
|
||||
const div = document.createElement( 'div' );
|
||||
const div = document.createElement('div');
|
||||
document.body.appendChild(div);
|
||||
ReactDOM.render(element, div);
|
||||
|
||||
@@ -155,7 +158,8 @@ ipcRenderer.on('page-load', (_event, { locale, resources, enableCustomTitleBar }
|
||||
// initialize red banner
|
||||
banner.initBanner();
|
||||
banner.showBanner(false, 'error');
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
ipcRenderer.on('page-load-welcome', (_event, data) => {
|
||||
const { locale, resource } = data;
|
||||
@@ -178,7 +182,7 @@ ipcRenderer.on('page-load-failed', (_event, { locale, resources }) => {
|
||||
|
||||
// Injects network error content into the DOM
|
||||
ipcRenderer.on('network-error', (_event, { error }) => {
|
||||
const networkErrorContainer = document.createElement( 'div' );
|
||||
const networkErrorContainer = document.createElement('div');
|
||||
networkErrorContainer.id = 'main-frame';
|
||||
networkErrorContainer.classList.add('content-wrapper');
|
||||
document.body.append(networkErrorContainer);
|
||||
|
||||
@@ -15,13 +15,16 @@ export class ScreenSnippetBcHandler {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.openScreenSnippet,
|
||||
});
|
||||
ipcRenderer.on('screen-snippet-data', (_event: Event, arg: IScreenSnippet) => {
|
||||
ipcRenderer.on(
|
||||
'screen-snippet-data',
|
||||
(_event: Event, arg: IScreenSnippet) => {
|
||||
if (arg.type === 'ERROR') {
|
||||
reject(arg);
|
||||
return;
|
||||
}
|
||||
resolve(arg);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -31,8 +31,19 @@ let isAltKey: boolean = false;
|
||||
let isMenuOpen: boolean = false;
|
||||
|
||||
interface ICryptoLib {
|
||||
AESGCMEncrypt: (name: string, base64IV: string, base64AAD: string, base64Key: string, base64In: string) => string | null;
|
||||
AESGCMDecrypt: (base64IV: string, base64AAD: string, base64Key: string, base64In: string) => string | null;
|
||||
AESGCMEncrypt: (
|
||||
name: string,
|
||||
base64IV: string,
|
||||
base64AAD: string,
|
||||
base64Key: string,
|
||||
base64In: string,
|
||||
) => string | null;
|
||||
AESGCMDecrypt: (
|
||||
base64IV: string,
|
||||
base64AAD: string,
|
||||
base64Key: string,
|
||||
base64In: string,
|
||||
) => string | null;
|
||||
}
|
||||
|
||||
export interface ILocalObject {
|
||||
@@ -44,7 +55,7 @@ export interface ILocalObject {
|
||||
boundsChangeCallback?: (arg: IBoundsChange) => void;
|
||||
screenSharingIndicatorCallback?: (arg: IScreenSharingIndicator) => void;
|
||||
protocolActionCallback?: (arg: string) => void;
|
||||
collectLogsCallback?: Array<( () => void )>;
|
||||
collectLogsCallback?: Array<() => void>;
|
||||
analyticsEventHandler?: (arg: any) => void;
|
||||
restartFloater?: (arg: IRestartFloaterData) => void;
|
||||
}
|
||||
@@ -53,7 +64,10 @@ const local: ILocalObject = {
|
||||
ipcRenderer,
|
||||
};
|
||||
|
||||
const notificationActionCallbacks = new Map<number, NotificationActionCallback>();
|
||||
const notificationActionCallbacks = new Map<
|
||||
number,
|
||||
NotificationActionCallback
|
||||
>();
|
||||
|
||||
// Throttle func
|
||||
const throttledSetBadgeCount = throttle((count) => {
|
||||
@@ -139,7 +153,9 @@ try {
|
||||
} catch (e) {
|
||||
cryptoLib = null;
|
||||
// tslint:disable-next-line
|
||||
console.warn('Failed to initialize Crypto Lib. You\'ll need to include the Crypto library. Contact the developers for more details');
|
||||
console.warn(
|
||||
"Failed to initialize Crypto Lib. You'll need to include the Crypto library. Contact the developers for more details",
|
||||
);
|
||||
}
|
||||
|
||||
let swiftSearch: any;
|
||||
@@ -148,7 +164,9 @@ try {
|
||||
} catch (e) {
|
||||
swiftSearch = null;
|
||||
// tslint:disable-next-line
|
||||
console.warn("Failed to initialize swift search. You'll need to include the search dependency. Contact the developers for more details");
|
||||
console.warn(
|
||||
"Failed to initialize swift search. You'll need to include the search dependency. Contact the developers for more details",
|
||||
);
|
||||
}
|
||||
|
||||
let swiftSearchUtils: any;
|
||||
@@ -157,13 +175,14 @@ try {
|
||||
} catch (e) {
|
||||
swiftSearchUtils = null;
|
||||
// tslint:disable-next-line
|
||||
console.warn("Failed to initialize swift search utils. You'll need to include the search dependency. Contact the developers for more details");
|
||||
console.warn(
|
||||
"Failed to initialize swift search utils. You'll need to include the search dependency. Contact the developers for more details",
|
||||
);
|
||||
}
|
||||
|
||||
let nextIndicatorId = 0;
|
||||
|
||||
export class SSFApi {
|
||||
|
||||
/**
|
||||
* Native encryption and decryption.
|
||||
*/
|
||||
@@ -235,7 +254,10 @@ export class SSFApi {
|
||||
* @param {Object} activityDetectionCallback - function that can be called accepting
|
||||
* @example registerActivityDetection(40000, func)
|
||||
*/
|
||||
public registerActivityDetection(period: number, activityDetectionCallback: (arg: number) => void): void {
|
||||
public registerActivityDetection(
|
||||
period: number,
|
||||
activityDetectionCallback: (arg: number) => void,
|
||||
): void {
|
||||
if (typeof activityDetectionCallback === 'function') {
|
||||
local.activityDetectionCallback = activityDetectionCallback;
|
||||
|
||||
@@ -251,7 +273,9 @@ export class SSFApi {
|
||||
* Registers the download handler
|
||||
* @param downloadManagerCallback Callback to be triggered by the download handler
|
||||
*/
|
||||
public registerDownloadHandler(downloadManagerCallback: (arg: any) => void): void {
|
||||
public registerDownloadHandler(
|
||||
downloadManagerCallback: (arg: any) => void,
|
||||
): void {
|
||||
if (typeof downloadManagerCallback === 'function') {
|
||||
local.downloadManagerCallback = downloadManagerCallback;
|
||||
}
|
||||
@@ -282,7 +306,9 @@ export class SSFApi {
|
||||
* logDetails: String
|
||||
* }
|
||||
*/
|
||||
public registerLogger(logger: (msg: ILogMsg, logLevel: LogLevel, showInConsole: boolean) => void): void {
|
||||
public registerLogger(
|
||||
logger: (msg: ILogMsg, logLevel: LogLevel, showInConsole: boolean) => void,
|
||||
): void {
|
||||
if (typeof logger === 'function') {
|
||||
local.logger = logger;
|
||||
|
||||
@@ -310,13 +336,11 @@ export class SSFApi {
|
||||
*/
|
||||
public registerProtocolHandler(protocolHandler): void {
|
||||
if (typeof protocolHandler === 'function') {
|
||||
|
||||
local.protocolActionCallback = protocolHandler;
|
||||
|
||||
local.ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.registerProtocolHandler,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +351,7 @@ export class SSFApi {
|
||||
public registerLogRetriever(collectLogs: () => void, logName: string): void {
|
||||
if (typeof collectLogs === 'function') {
|
||||
if (!local.collectLogsCallback) {
|
||||
local.collectLogsCallback = new Array<( () => void )>();
|
||||
local.collectLogsCallback = new Array<() => void>();
|
||||
}
|
||||
local.collectLogsCallback.push(collectLogs);
|
||||
|
||||
@@ -335,7 +359,6 @@ export class SSFApi {
|
||||
cmd: apiCmds.registerLogRetriever,
|
||||
logName,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +401,9 @@ export class SSFApi {
|
||||
*
|
||||
* @param screenSnippetCallback {function}
|
||||
*/
|
||||
public openScreenSnippet(screenSnippetCallback: (arg: IScreenSnippet) => void): void {
|
||||
public openScreenSnippet(
|
||||
screenSnippetCallback: (arg: IScreenSnippet) => void,
|
||||
): void {
|
||||
if (typeof screenSnippetCallback === 'function') {
|
||||
local.screenSnippetCallback = screenSnippetCallback;
|
||||
|
||||
@@ -434,7 +459,8 @@ export class SSFApi {
|
||||
* Opens a modal window to configure notification preference.
|
||||
*/
|
||||
public showNotificationSettings(data: string): void {
|
||||
const windowName = (remote.getCurrentWindow() as ICustomBrowserWindow).winName;
|
||||
const windowName = (remote.getCurrentWindow() as ICustomBrowserWindow)
|
||||
.winName;
|
||||
local.ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.showNotificationSettings,
|
||||
windowName,
|
||||
@@ -454,15 +480,18 @@ export class SSFApi {
|
||||
* - 'error' - error occured. Event object contains 'reason' field.
|
||||
* - 'stopRequested' - user clicked "Stop Sharing" button.
|
||||
*/
|
||||
public showScreenSharingIndicator(options: IScreenSharingIndicatorOptions, callback): void {
|
||||
public showScreenSharingIndicator(
|
||||
options: IScreenSharingIndicatorOptions,
|
||||
callback,
|
||||
): void {
|
||||
const { displayId, stream } = options;
|
||||
|
||||
if (!stream || !stream.active || stream.getVideoTracks().length !== 1) {
|
||||
callback({type: 'error', reason: 'bad stream'});
|
||||
callback({ type: 'error', reason: 'bad stream' });
|
||||
return;
|
||||
}
|
||||
if (displayId && typeof(displayId) !== 'string') {
|
||||
callback({type: 'error', reason: 'bad displayId'});
|
||||
if (displayId && typeof displayId !== 'string') {
|
||||
callback({ type: 'error', reason: 'bad displayId' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -496,7 +525,10 @@ export class SSFApi {
|
||||
* - 'error' - error occured. Event object contains 'reason' field.
|
||||
* - 'stopRequested' - user clicked "Stop Sharing" button.
|
||||
*/
|
||||
public openScreenSharingIndicator(options: IScreenSharingIndicatorOptions, callback): void {
|
||||
public openScreenSharingIndicator(
|
||||
options: IScreenSharingIndicatorOptions,
|
||||
callback,
|
||||
): void {
|
||||
const { displayId, requestId, streamId } = options;
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
@@ -521,7 +553,9 @@ export class SSFApi {
|
||||
* Allows JS to register a function to restart floater
|
||||
* @param callback
|
||||
*/
|
||||
public registerRestartFloater(callback: (args: IRestartFloaterData) => void): void {
|
||||
public registerRestartFloater(
|
||||
callback: (args: IRestartFloaterData) => void,
|
||||
): void {
|
||||
local.restartFloater = callback;
|
||||
}
|
||||
|
||||
@@ -568,9 +602,7 @@ export class SSFApi {
|
||||
* get CPU usage
|
||||
*/
|
||||
public async getCPUUsage(): Promise<ICPUUsage> {
|
||||
return Promise.resolve(
|
||||
await process.getCPUUsage(),
|
||||
);
|
||||
return Promise.resolve(await process.getCPUUsage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -600,11 +632,17 @@ export class SSFApi {
|
||||
* @param notificationOpts {INotificationData}
|
||||
* @param notificationCallback {NotificationActionCallback}
|
||||
*/
|
||||
public showNotification(notificationOpts: INotificationData, notificationCallback: NotificationActionCallback): void {
|
||||
public showNotification(
|
||||
notificationOpts: INotificationData,
|
||||
notificationCallback: NotificationActionCallback,
|
||||
): void {
|
||||
// Store callbacks based on notification id so,
|
||||
// we can use this to trigger on notification action
|
||||
if (typeof notificationOpts.id === 'number') {
|
||||
notificationActionCallbacks.set(notificationOpts.id, notificationCallback);
|
||||
notificationActionCallbacks.set(
|
||||
notificationOpts.id,
|
||||
notificationCallback,
|
||||
);
|
||||
}
|
||||
// ipc does not support sending Functions, Promises, Symbols, WeakMaps,
|
||||
// or WeakSets will throw an exception
|
||||
@@ -627,7 +665,6 @@ export class SSFApi {
|
||||
notificationId,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -642,8 +679,10 @@ export class SSFApi {
|
||||
* count: number
|
||||
* }
|
||||
*/
|
||||
local.ipcRenderer.on('create-badge-data-url', (_event: Event, arg: IBadgeCount) => {
|
||||
const count = arg && arg.count || 0;
|
||||
local.ipcRenderer.on(
|
||||
'create-badge-data-url',
|
||||
(_event: Event, arg: IBadgeCount) => {
|
||||
const count = (arg && arg.count) || 0;
|
||||
|
||||
// create 32 x 32 img
|
||||
const radius = 16;
|
||||
@@ -679,7 +718,8 @@ local.ipcRenderer.on('create-badge-data-url', (_event: Event, arg: IBadgeCount)
|
||||
dataUrl,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* An event triggered by the main process
|
||||
@@ -691,13 +731,19 @@ local.ipcRenderer.on('create-badge-data-url', (_event: Event, arg: IBadgeCount)
|
||||
* type: 'ERROR' | 'image/jpg;base64',
|
||||
* }
|
||||
*/
|
||||
local.ipcRenderer.on('screen-snippet-data', (_event: Event, arg: IScreenSnippet) => {
|
||||
if (typeof arg === 'object' && typeof local.screenSnippetCallback === 'function') {
|
||||
local.ipcRenderer.on(
|
||||
'screen-snippet-data',
|
||||
(_event: Event, arg: IScreenSnippet) => {
|
||||
if (
|
||||
typeof arg === 'object' &&
|
||||
typeof local.screenSnippetCallback === 'function'
|
||||
) {
|
||||
local.screenSnippetCallback(arg);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
local.ipcRenderer.on('collect-logs', ( _event: Event ) => {
|
||||
local.ipcRenderer.on('collect-logs', (_event: Event) => {
|
||||
if (local.collectLogsCallback) {
|
||||
for (const callback of local.collectLogsCallback) {
|
||||
callback();
|
||||
@@ -712,20 +758,32 @@ local.ipcRenderer.on('collect-logs', ( _event: Event ) => {
|
||||
* @param {number} idleTime - current system idle tick
|
||||
*/
|
||||
local.ipcRenderer.on('activity', (_event: Event, idleTime: number) => {
|
||||
if (typeof idleTime === 'number' && typeof local.activityDetectionCallback === 'function') {
|
||||
if (
|
||||
typeof idleTime === 'number' &&
|
||||
typeof local.activityDetectionCallback === 'function'
|
||||
) {
|
||||
local.activityDetectionCallback(idleTime);
|
||||
}
|
||||
});
|
||||
|
||||
local.ipcRenderer.on('download-completed', (_event: Event, downloadItem: IDownloadItem) => {
|
||||
if (typeof downloadItem === 'object' && typeof local.downloadManagerCallback === 'function') {
|
||||
local.downloadManagerCallback({status: 'download-completed', item: downloadItem});
|
||||
local.ipcRenderer.on(
|
||||
'download-completed',
|
||||
(_event: Event, downloadItem: IDownloadItem) => {
|
||||
if (
|
||||
typeof downloadItem === 'object' &&
|
||||
typeof local.downloadManagerCallback === 'function'
|
||||
) {
|
||||
local.downloadManagerCallback({
|
||||
status: 'download-completed',
|
||||
item: downloadItem,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
local.ipcRenderer.on('download-failed', (_event: Event) => {
|
||||
if (typeof local.downloadManagerCallback === 'function') {
|
||||
local.downloadManagerCallback({status: 'download-failed'});
|
||||
local.downloadManagerCallback({ status: 'download-failed' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -744,7 +802,14 @@ local.ipcRenderer.on('download-failed', (_event: Event) => {
|
||||
*/
|
||||
local.ipcRenderer.on('boundsChange', (_event, arg: IBoundsChange): void => {
|
||||
const { x, y, height, width, windowName } = arg;
|
||||
if (x && y && height && width && windowName && typeof local.boundsChangeCallback === 'function') {
|
||||
if (
|
||||
x &&
|
||||
y &&
|
||||
height &&
|
||||
width &&
|
||||
windowName &&
|
||||
typeof local.boundsChangeCallback === 'function'
|
||||
) {
|
||||
local.boundsChangeCallback({
|
||||
x,
|
||||
y,
|
||||
@@ -761,7 +826,10 @@ local.ipcRenderer.on('boundsChange', (_event, arg: IBoundsChange): void => {
|
||||
*/
|
||||
local.ipcRenderer.on('screen-sharing-stopped', (_event, id) => {
|
||||
if (typeof local.screenSharingIndicatorCallback === 'function') {
|
||||
local.screenSharingIndicatorCallback({ type: 'stopRequested', requestId: id });
|
||||
local.screenSharingIndicatorCallback({
|
||||
type: 'stopRequested',
|
||||
requestId: id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -787,7 +855,10 @@ local.ipcRenderer.on('log', (_event, arg) => {
|
||||
* @param {String} arg - the protocol url
|
||||
*/
|
||||
local.ipcRenderer.on('protocol-action', (_event, arg: string) => {
|
||||
if (typeof local.protocolActionCallback === 'function' && typeof arg === 'string') {
|
||||
if (
|
||||
typeof local.protocolActionCallback === 'function' &&
|
||||
typeof arg === 'string'
|
||||
) {
|
||||
local.protocolActionCallback(arg);
|
||||
}
|
||||
});
|
||||
@@ -802,11 +873,14 @@ local.ipcRenderer.on('analytics-callback', (_event, arg: object) => {
|
||||
* An event triggered by the main process to restart the child window
|
||||
* @param {IRestartFloaterData}
|
||||
*/
|
||||
local.ipcRenderer.on('restart-floater', (_event, { windowName, bounds }: IRestartFloaterData) => {
|
||||
local.ipcRenderer.on(
|
||||
'restart-floater',
|
||||
(_event, { windowName, bounds }: IRestartFloaterData) => {
|
||||
if (typeof local.restartFloater === 'function' && windowName) {
|
||||
local.restartFloater({ windowName, bounds });
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* An event triggered by the main process on notification actions
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": ["tslint:latest", "tslint-config-prettier"],
|
||||
"extends": ["tslint:recommended", "tslint-config-prettier"],
|
||||
"linterOptions": {
|
||||
"exclude": ["**/*.json"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user