mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Typescript - Add support for child window
This commit is contained in:
parent
81f0a1460e
commit
cbb920ef1d
@ -1,4 +1,4 @@
|
||||
import { app, Menu, session, shell } from 'electron';
|
||||
import { app, dialog, Menu, session, shell } from 'electron';
|
||||
|
||||
import { isMac, isWindowsOS } from '../common/env';
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
@ -305,9 +305,25 @@ export class AppMenu {
|
||||
click: () => exportCrashDumps(),
|
||||
label: isMac ? i18n.t('Show crash dump in Finder')() : i18n.t('Show crash dump in Explorer')(),
|
||||
}, {
|
||||
click: () => windowHandler.createMoreInfoWindow(),
|
||||
label: i18n.t('More Information')(),
|
||||
} ],
|
||||
label: i18n.t('Toggle Developer Tools')(),
|
||||
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click(_item, focusedWindow) {
|
||||
const devToolsEnabled = config.getGlobalConfigFields([ 'devToolsEnabled' ]);
|
||||
if (focusedWindow && devToolsEnabled) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
} else {
|
||||
dialog.showMessageBox(focusedWindow, {
|
||||
type: 'warning',
|
||||
buttons: [ 'Ok' ],
|
||||
title: i18n.t('Dev Tools disabled')(),
|
||||
message: i18n.t('Dev Tools has been disabled! Please contact your system administrator to enable it!')(),
|
||||
});
|
||||
}
|
||||
},
|
||||
},{
|
||||
click: () => windowHandler.createMoreInfoWindow(),
|
||||
label: i18n.t('More Information')(),
|
||||
}],
|
||||
} ],
|
||||
};
|
||||
}
|
||||
|
147
src/browser/pop-out-window-handler.ts
Normal file
147
src/browser/pop-out-window-handler.ts
Normal file
@ -0,0 +1,147 @@
|
||||
import { BrowserWindow, WebContents } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { parse as parseQuerystring } from 'querystring';
|
||||
import { format, parse, Url } from 'url';
|
||||
import { isWindowsOS } from '../common/env';
|
||||
import { getGuid } from '../common/utils';
|
||||
import { enterFullScreen, leaveFullScreen, throttledWindowChanges } from './window-actions';
|
||||
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||
import { getBounds, preventWindowNavigation } from './window-utils';
|
||||
|
||||
const DEFAULT_POP_OUT_WIDTH = 300;
|
||||
const DEFAULT_POP_OUT_HEIGHT = 600;
|
||||
|
||||
const MIN_WIDTH = 300;
|
||||
const MIN_HEIGHT = 300;
|
||||
|
||||
/**
|
||||
* Verifies if the url is valid and
|
||||
* forcefully appends https if not present
|
||||
*
|
||||
* @param configURL {string}
|
||||
*/
|
||||
const getParsedUrl = (configURL: string): Url => {
|
||||
const parsedUrl = parse(configURL);
|
||||
|
||||
if (!parsedUrl.protocol || parsedUrl.protocol !== 'https') {
|
||||
parsedUrl.protocol = 'https:';
|
||||
parsedUrl.slashes = true;
|
||||
}
|
||||
return parse(format(parsedUrl));
|
||||
};
|
||||
|
||||
export const handleChildWindow = (webContents: WebContents): void => {
|
||||
const childWindow = (event, newWinUrl, frameName, disposition, newWinOptions): void => {
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (!mainWindow || mainWindow.isDestroyed()) return;
|
||||
if (!windowHandler.url) return;
|
||||
|
||||
if (!newWinOptions.webPreferences) {
|
||||
newWinOptions.webPreferences = {};
|
||||
}
|
||||
Object.assign(newWinOptions.webPreferences, webContents);
|
||||
const newWinParsedUrl = getParsedUrl(newWinUrl);
|
||||
const mainWinParsedUrl = getParsedUrl(windowHandler.url);
|
||||
|
||||
const newWinHost = newWinParsedUrl && newWinParsedUrl.host;
|
||||
const mainWinHost = mainWinParsedUrl && mainWinParsedUrl.host;
|
||||
|
||||
const emptyUrlString = 'about:blank';
|
||||
const dispositionWhitelist = ['new-window', 'foreground-tab'];
|
||||
|
||||
// only allow window.open to succeed is if coming from same hsot,
|
||||
// otherwise open in default browser.
|
||||
if ((newWinHost === mainWinHost || newWinUrl === emptyUrlString) && dispositionWhitelist.includes(disposition)) {
|
||||
|
||||
const newWinKey = getGuid();
|
||||
if (!frameName) {
|
||||
// abort - no frame name provided.
|
||||
return;
|
||||
}
|
||||
|
||||
const width = newWinOptions.width || DEFAULT_POP_OUT_WIDTH;
|
||||
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);
|
||||
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);
|
||||
|
||||
newWinOptions.x = x;
|
||||
newWinOptions.y = y;
|
||||
} else {
|
||||
newWinOptions.x = 0;
|
||||
newWinOptions.y = 0;
|
||||
}
|
||||
} else {
|
||||
// create new window at slight offset from main window.
|
||||
const { x, y } = mainWindow.getBounds();
|
||||
newWinOptions.x = x + 50;
|
||||
newWinOptions.y = y + 50;
|
||||
}
|
||||
|
||||
newWinOptions.width = Math.max(width, DEFAULT_POP_OUT_WIDTH);
|
||||
newWinOptions.height = Math.max(height, DEFAULT_POP_OUT_HEIGHT);
|
||||
newWinOptions.minWidth = MIN_WIDTH;
|
||||
newWinOptions.minHeight = MIN_HEIGHT;
|
||||
newWinOptions.alwaysOnTop = mainWindow.isAlwaysOnTop();
|
||||
newWinOptions.frame = true;
|
||||
newWinOptions.winKey = newWinKey;
|
||||
|
||||
const childWebContents = newWinOptions.webContents;
|
||||
// Event needed to hide native menu bar
|
||||
childWebContents.once('did-start-loading', () => {
|
||||
const browserWin = BrowserWindow.fromWebContents(childWebContents);
|
||||
if (isWindowsOS && browserWin && !browserWin.isDestroyed()) {
|
||||
browserWin.setMenuBarVisibility(false);
|
||||
}
|
||||
});
|
||||
|
||||
childWebContents.once('did-finish-load', () => {
|
||||
const browserWin = BrowserWindow.fromWebContents(childWebContents) as ICustomBrowserWindow;
|
||||
if (!browserWin) return;
|
||||
windowHandler.addWindow(newWinKey, browserWin);
|
||||
browserWin.webContents.send('page-load', { isWindowsOS });
|
||||
browserWin.webContents.insertCSS(
|
||||
fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'), 'utf8').toString(),
|
||||
);
|
||||
browserWin.winName = frameName;
|
||||
browserWin.setAlwaysOnTop(mainWindow.isAlwaysOnTop());
|
||||
|
||||
// prevents window from navigating
|
||||
preventWindowNavigation(browserWin, true);
|
||||
// Monitor window for events
|
||||
const eventNames = [ 'move', 'resize', 'maximize', 'unmaximize' ];
|
||||
eventNames.forEach((e: string) => {
|
||||
// @ts-ignore
|
||||
if (this.mainWindow) this.mainWindow.on(e, throttledWindowChanges);
|
||||
});
|
||||
browserWin.on('enter-full-screen', enterFullScreen);
|
||||
browserWin.on('leave-full-screen', leaveFullScreen);
|
||||
|
||||
// Remove the attached event listeners when the window is about to close
|
||||
browserWin.once('close', () => {
|
||||
browserWin.removeListener('close', throttledWindowChanges);
|
||||
browserWin.removeListener('resize', throttledWindowChanges);
|
||||
browserWin.removeListener('maximize', throttledWindowChanges);
|
||||
browserWin.removeListener('unmaximize', throttledWindowChanges);
|
||||
browserWin.removeListener('enter-full-screen', leaveFullScreen);
|
||||
browserWin.removeListener('leave-full-screen', leaveFullScreen);
|
||||
});
|
||||
|
||||
// TODO: handle Permission Requests & setCertificateVerifyProc
|
||||
});
|
||||
} else {
|
||||
event.preventDefault();
|
||||
windowHandler.openUrlInDefaultBrowser(newWinUrl);
|
||||
}
|
||||
};
|
||||
webContents.on('new-window', childWindow);
|
||||
};
|
@ -2,7 +2,7 @@ import { BrowserWindow } from 'electron';
|
||||
|
||||
import { IBoundsChange, KeyCodes } from '../common/api-interface';
|
||||
import { isWindowsOS } from '../common/env';
|
||||
import { throttle } from '../common/utils';
|
||||
import { throttle } from '../common/throttle';
|
||||
import { config } from './config-handler';
|
||||
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||
import { showPopupMenu } from './window-utils';
|
||||
|
@ -2,7 +2,7 @@ import * as electron from 'electron';
|
||||
import { BrowserWindow, crashReporter, ipcMain, webContents } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import { format, parse } from 'url';
|
||||
|
||||
import { buildNumber, clientVersion, version } from '../../package.json';
|
||||
import DesktopCapturerSource = Electron.DesktopCapturerSource;
|
||||
@ -11,8 +11,9 @@ import { isMac, isWindowsOS } from '../common/env';
|
||||
import { getCommandLineArgs, getGuid } from '../common/utils';
|
||||
import { AppMenu } from './app-menu';
|
||||
import { config, IConfig } from './config-handler';
|
||||
import { handleChildWindow } from './pop-out-window-handler';
|
||||
import { enterFullScreen, leaveFullScreen, throttledWindowChanges } from './window-actions';
|
||||
import { createComponentWindow } from './window-utils';
|
||||
import { createComponentWindow, getBounds } from './window-utils';
|
||||
|
||||
interface ICustomBrowserWindowConstructorOpts extends Electron.BrowserWindowConstructorOptions {
|
||||
winKey: string;
|
||||
@ -74,19 +75,20 @@ export class WindowHandler {
|
||||
*
|
||||
* @param configURL {string}
|
||||
*/
|
||||
private static validateURL(configURL: string): string {
|
||||
const parsedUrl = url.parse(configURL);
|
||||
private static getValidUrl(configURL: string): string {
|
||||
const parsedUrl = parse(configURL);
|
||||
|
||||
if (!parsedUrl.protocol || parsedUrl.protocol !== 'https') {
|
||||
parsedUrl.protocol = 'https:';
|
||||
parsedUrl.slashes = true;
|
||||
}
|
||||
return url.format(parsedUrl);
|
||||
return format(parsedUrl);
|
||||
}
|
||||
|
||||
public appMenu: AppMenu | null;
|
||||
public isAutoReload: boolean;
|
||||
public isOnline: boolean;
|
||||
public url: string | undefined;
|
||||
|
||||
private readonly windowOpts: ICustomBrowserWindowConstructorOpts;
|
||||
private readonly globalConfig: IConfig;
|
||||
@ -134,7 +136,7 @@ export class WindowHandler {
|
||||
public createApplication() {
|
||||
// set window opts with additional config
|
||||
this.mainWindow = new BrowserWindow({
|
||||
...this.windowOpts, ...this.getBounds(this.config.mainWinPos),
|
||||
...this.windowOpts, ...getBounds(this.config.mainWinPos, DEFAULT_WIDTH, DEFAULT_HEIGHT),
|
||||
}) as ICustomBrowserWindow;
|
||||
this.mainWindow.winName = 'main';
|
||||
|
||||
@ -146,7 +148,8 @@ export class WindowHandler {
|
||||
});
|
||||
|
||||
const urlFromCmd = getCommandLineArgs(process.argv, '--url=', false);
|
||||
this.mainWindow.loadURL(urlFromCmd && urlFromCmd.substr(6) || WindowHandler.validateURL(this.globalConfig.url));
|
||||
this.url = urlFromCmd && urlFromCmd.substr(6) || WindowHandler.getValidUrl(this.globalConfig.url);
|
||||
this.mainWindow.loadURL(this.url);
|
||||
this.mainWindow.webContents.on('did-finish-load', () => {
|
||||
// close the loading window when
|
||||
// the main windows finished loading
|
||||
@ -175,6 +178,9 @@ export class WindowHandler {
|
||||
});
|
||||
this.mainWindow.webContents.toggleDevTools();
|
||||
this.addWindow(this.windowOpts.winKey, this.mainWindow);
|
||||
|
||||
// Handle pop-outs window
|
||||
handleChildWindow(this.mainWindow.webContents);
|
||||
return this.mainWindow;
|
||||
}
|
||||
|
||||
@ -289,13 +295,24 @@ export class WindowHandler {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an external url in the system's default browser
|
||||
*
|
||||
* @param urlToOpen
|
||||
*/
|
||||
public openUrlInDefaultBrowser(urlToOpen) {
|
||||
if (urlToOpen) {
|
||||
electron.shell.openExternal(urlToOpen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores information of all the window we have created
|
||||
*
|
||||
* @param key {string}
|
||||
* @param browserWindow {Electron.BrowserWindow}
|
||||
*/
|
||||
private addWindow(key: string, browserWindow: Electron.BrowserWindow): void {
|
||||
public addWindow(key: string, browserWindow: Electron.BrowserWindow): void {
|
||||
this.windows[ key ] = browserWindow;
|
||||
}
|
||||
|
||||
@ -304,7 +321,7 @@ export class WindowHandler {
|
||||
*
|
||||
* @param key {string}
|
||||
*/
|
||||
private removeWindow(key): void {
|
||||
public removeWindow(key): void {
|
||||
delete this.windows[ key ];
|
||||
}
|
||||
|
||||
@ -323,29 +340,6 @@ export class WindowHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config stored rectangle if it is contained within the workArea of at
|
||||
* least one of the screens else returns the default rectangle value with out x, y
|
||||
* as the default is to center the window
|
||||
*
|
||||
* @param mainWinPos {Electron.Rectangle}
|
||||
* @return {x?: Number, y?: Number, width: Number, height: Number}
|
||||
*/
|
||||
private getBounds(mainWinPos): Partial<Electron.Rectangle> {
|
||||
if (!mainWinPos) return { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT };
|
||||
const displays = electron.screen.getAllDisplays();
|
||||
|
||||
for (let i = 0, len = displays.length; i < len; i++) {
|
||||
const workArea = displays[ i ].workArea;
|
||||
if (mainWinPos.x >= workArea.x && mainWinPos.y >= workArea.y &&
|
||||
((mainWinPos.x + mainWinPos.width) <= (workArea.x + workArea.width)) &&
|
||||
((mainWinPos.y + mainWinPos.height) <= (workArea.y + workArea.height))) {
|
||||
return mainWinPos;
|
||||
}
|
||||
}
|
||||
return { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT };
|
||||
}
|
||||
|
||||
/**
|
||||
* Main window opts
|
||||
*/
|
||||
@ -358,10 +352,9 @@ export class WindowHandler {
|
||||
show: false,
|
||||
title: 'Symphony',
|
||||
webPreferences: {
|
||||
nativeWindowOpen: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, '../renderer/preload-main'),
|
||||
sandbox: false,
|
||||
preload: path.join(__dirname, '../renderer/_preload-main.js'),
|
||||
sandbox: true,
|
||||
},
|
||||
winKey: getGuid(),
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import * as electron from 'electron';
|
||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
@ -14,16 +15,27 @@ const checkValidWindow = true;
|
||||
* Prevents window from navigating
|
||||
*
|
||||
* @param browserWindow
|
||||
* @param isPopOutWindow
|
||||
*/
|
||||
export const preventWindowNavigation = (browserWindow: Electron.BrowserWindow): void => {
|
||||
export const preventWindowNavigation = (browserWindow: Electron.BrowserWindow, isPopOutWindow: boolean = false): void => {
|
||||
const listener = (e: Electron.Event, winUrl: string) => {
|
||||
if (isPopOutWindow && !winUrl.startsWith('http' || 'https')) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (browserWindow.isDestroyed()
|
||||
|| browserWindow.webContents.isDestroyed()
|
||||
|| winUrl === browserWindow.webContents.getURL()) return;
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
browserWindow.webContents.on('will-navigate', listener);
|
||||
|
||||
browserWindow.once('close', () => {
|
||||
browserWindow.webContents.removeListener('will-navigate', listener);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -186,4 +198,29 @@ export const sanitize = (windowName: string): void => {
|
||||
// Closes all the child windows
|
||||
// windowMgr.cleanUpChildWindows();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the config stored rectangle if it is contained within the workArea of at
|
||||
* least one of the screens else returns the default rectangle value with out x, y
|
||||
* as the default is to center the window
|
||||
*
|
||||
* @param winPos {Electron.Rectangle}
|
||||
* @param defaultWidth
|
||||
* @param defaultHeight
|
||||
* @return {x?: Number, y?: Number, width: Number, height: Number}
|
||||
*/
|
||||
export const getBounds = (winPos: Electron.Rectangle, defaultWidth: number, defaultHeight: number): Partial<Electron.Rectangle> => {
|
||||
if (!winPos) return { width: defaultWidth, height: defaultHeight };
|
||||
const displays = electron.screen.getAllDisplays();
|
||||
|
||||
for (let i = 0, len = displays.length; i < len; i++) {
|
||||
const workArea = displays[ i ].workArea;
|
||||
if (winPos.x >= workArea.x && winPos.y >= workArea.y &&
|
||||
((winPos.x + winPos.width) <= (workArea.x + workArea.width)) &&
|
||||
((winPos.y + winPos.height) <= (workArea.y + workArea.height))) {
|
||||
return winPos;
|
||||
}
|
||||
}
|
||||
return { width: defaultWidth, height: defaultHeight };
|
||||
};
|
30
src/common/format-string.ts
Normal file
30
src/common/format-string.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Formats a string with dynamic values
|
||||
* @param str {String} String to be formatted
|
||||
* @param data {Object} - Data to be added
|
||||
*
|
||||
* @example
|
||||
* StringFormat(this will log {time}`, { time: '1234' })
|
||||
*
|
||||
* result:
|
||||
* this will log 1234
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
export const formatString = (str: string, data?: object): string => {
|
||||
|
||||
if (!str || !data) return str;
|
||||
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
return str.replace(/({([^}]+)})/g, (i) => {
|
||||
const replacedKey = i.replace(/{/, '').replace(/}/, '');
|
||||
if (!data[replacedKey]) {
|
||||
return i;
|
||||
}
|
||||
return data[replacedKey];
|
||||
});
|
||||
}
|
||||
}
|
||||
return str;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { formatString } from './utils';
|
||||
import { formatString } from './format-string';
|
||||
|
||||
const localeCodeRegex = /^([a-z]{2})-([A-Z]{2})$/;
|
||||
|
||||
|
22
src/common/throttle.ts
Normal file
22
src/common/throttle.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Limits your function to be called at most every milliseconds
|
||||
*
|
||||
* @param func
|
||||
* @param wait
|
||||
* @example const throttled = throttle(anyFunc, 500);
|
||||
*/
|
||||
export const throttle = (func: (...args) => void, wait: number): (...args) => void => {
|
||||
if (wait <= 0) {
|
||||
throw Error('throttle: invalid throttleTime arg, must be a number: ' + wait);
|
||||
}
|
||||
|
||||
let isCalled: boolean = false;
|
||||
|
||||
return (...args) => {
|
||||
if (!isCalled) {
|
||||
func(...args);
|
||||
isCalled = true;
|
||||
setTimeout(() => isCalled = false, wait);
|
||||
}
|
||||
};
|
||||
};
|
@ -1,9 +1,9 @@
|
||||
// regex match the semver (semantic version) this checks for the pattern X.Y.Z
|
||||
// ex-valid v1.2.0, 1.2.0, 2.3.4-r51
|
||||
import archiver from 'archiver';
|
||||
import * as archiver from 'archiver';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// regex match the semver (semantic version) this checks for the pattern X.Y.Z
|
||||
// ex-valid v1.2.0, 1.2.0, 2.3.4-r51
|
||||
const semver = /^v?(?:\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+)(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)?)?$/i;
|
||||
const patch = /-([0-9A-Za-z-.]+)/;
|
||||
|
||||
@ -145,60 +145,6 @@ const pick = (object: object, fields: string[]) => {
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a string with dynamic values
|
||||
* @param str {String} String to be formatted
|
||||
* @param data {Object} - Data to be added
|
||||
*
|
||||
* @example
|
||||
* StringFormat(this will log {time}`, { time: '1234' })
|
||||
*
|
||||
* result:
|
||||
* this will log 1234
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
const formatString = (str: string, data?: object): string => {
|
||||
|
||||
if (!str || !data) return str;
|
||||
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
return str.replace(/({([^}]+)})/g, (i) => {
|
||||
const replacedKey = i.replace(/{/, '').replace(/}/, '');
|
||||
if (!data[replacedKey]) {
|
||||
return i;
|
||||
}
|
||||
return data[replacedKey];
|
||||
});
|
||||
}
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Limits your function to be called at most every milliseconds
|
||||
*
|
||||
* @param func
|
||||
* @param wait
|
||||
* @example const throttled = throttle(anyFunc, 500);
|
||||
*/
|
||||
const throttle = (func: (...args) => void, wait: number): (...args) => void => {
|
||||
if (wait <= 0) {
|
||||
throw Error('throttle: invalid throttleTime arg, must be a number: ' + wait);
|
||||
}
|
||||
|
||||
let isCalled: boolean = false;
|
||||
|
||||
return (...args) => {
|
||||
if (!isCalled) {
|
||||
func(...args);
|
||||
isCalled = true;
|
||||
setTimeout(() => isCalled = false, wait);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Archives files in the source directory
|
||||
* that matches the given file extension
|
||||
@ -245,4 +191,4 @@ const generateArchiveForDirectory = (source: string, destination: string, fileEx
|
||||
});
|
||||
};
|
||||
|
||||
export { compareVersions, getCommandLineArgs, getGuid, pick, formatString, throttle, generateArchiveForDirectory };
|
||||
export { compareVersions, getCommandLineArgs, getGuid, pick, generateArchiveForDirectory };
|
@ -10,7 +10,7 @@ import {
|
||||
IScreenSnippet, KeyCodes,
|
||||
} from '../common/api-interface';
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
import { throttle } from '../common/utils';
|
||||
import { throttle } from '../common/throttle';
|
||||
import { getSource } from './desktop-capturer';
|
||||
|
||||
let isAltKey = false;
|
||||
|
Loading…
Reference in New Issue
Block a user