mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Typescript - Add set badge count api
This commit is contained in:
parent
8c44c04540
commit
a4858f336b
@ -1,27 +1,33 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
|
||||
import { apiCmds, apiName, IApiArgs } from '../common/api-interface';
|
||||
import { logger } from '../common/logger';
|
||||
import { windowHandler } from './window-handler';
|
||||
import { setDataUrl, showBadgeCount } from './window-utils';
|
||||
|
||||
const checkValidWindow = true;
|
||||
|
||||
/**
|
||||
* Ensure events comes from a window that we have created.
|
||||
* @param {EventEmitter} event node emitter event to be tested
|
||||
* @return {Boolean} returns true if exists otherwise false
|
||||
*/
|
||||
function isValidWindow(event: Electron.Event) {
|
||||
/*if (!checkValidWindow) {
|
||||
function isValidWindow(event: Electron.Event): boolean {
|
||||
if (!checkValidWindow) {
|
||||
return true;
|
||||
}*/
|
||||
const result = false;
|
||||
}
|
||||
let result = false;
|
||||
if (event && event.sender) {
|
||||
// validate that event sender is from window we created
|
||||
// const browserWin = BrowserWindow.fromWebContents(event.sender);
|
||||
const browserWin = BrowserWindow.fromWebContents(event.sender);
|
||||
// @ts-ignore
|
||||
const winKey = event.sender.browserWindowOptions && event.sender.browserWindowOptions.winKey;
|
||||
|
||||
// result = windowMgr.hasWindow(browserWin, event.sender.id);
|
||||
result = windowHandler.hasWindow(winKey, browserWin);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
// log.send(logLevels.WARN, 'invalid window try to perform action, ignoring action');
|
||||
logger.warn('invalid window try to perform action, ignoring action', event.sender);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -45,22 +51,23 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.Event, arg: IApiArgs) => {
|
||||
if (typeof arg.isOnline === 'boolean') {
|
||||
windowMgr.setIsOnline(arg.isOnline);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.setBadgeCount:
|
||||
break;*/
|
||||
case apiCmds.setBadgeCount:
|
||||
if (typeof arg.count === 'number') {
|
||||
badgeCount.show(arg.count);
|
||||
console.log(arg.count);
|
||||
showBadgeCount(arg.count);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.registerProtocolHandler:
|
||||
/*case ApiCmds.registerProtocolHandler:
|
||||
protocolHandler.setProtocolWindow(event.sender);
|
||||
protocolHandler.checkProtocolAction();
|
||||
break;
|
||||
case ApiCmds.badgeDataUrl:
|
||||
break;*/
|
||||
case apiCmds.badgeDataUrl:
|
||||
if (typeof arg.dataUrl === 'string' && typeof arg.count === 'number') {
|
||||
badgeCount.setDataUrl(arg.dataUrl, arg.count);
|
||||
setDataUrl(arg.dataUrl, arg.count);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.activate:
|
||||
/*case ApiCmds.activate:
|
||||
if (typeof arg.windowName === 'string') {
|
||||
windowMgr.activate(arg.windowName);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { cleanUpAppCache, createAppCacheFile } from './app-cache-handler';
|
||||
import { autoLaunchInstance } from './auto-launch-controller';
|
||||
import setChromeFlags from './chrome-flags';
|
||||
import { config } from './config-handler';
|
||||
import './main-api-handler';
|
||||
import { windowHandler } from './window-handler';
|
||||
|
||||
const allowMultiInstance: string | boolean = getCommandLineArgs(process.argv, '--multiInstance', true) || isDevEnv;
|
||||
|
@ -3,11 +3,15 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
import { getCommandLineArgs } from '../common/utils';
|
||||
import { getCommandLineArgs, getGuid } from '../common/utils';
|
||||
import { config, IConfig } from './config-handler';
|
||||
import { createComponentWindow } from './window-utils';
|
||||
|
||||
const { buildNumber, clientVersion, version } = require('../../package.json');// tslint:disable-line:no-var-requires
|
||||
const { buildNumber, clientVersion, version } = require('../../package.json'); // tslint:disable-line:no-var-requires
|
||||
|
||||
interface ICustomBrowserWindowConstructorOpts extends Electron.BrowserWindowConstructorOptions {
|
||||
winKey: string;
|
||||
}
|
||||
|
||||
export class WindowHandler {
|
||||
|
||||
@ -28,6 +32,7 @@ export class WindowHandler {
|
||||
preload: path.join(__dirname, '../renderer/preload-main'),
|
||||
sandbox: false,
|
||||
},
|
||||
winKey: getGuid(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -65,15 +70,17 @@ export class WindowHandler {
|
||||
return url.format(parsedUrl);
|
||||
}
|
||||
|
||||
private readonly windowOpts: Electron.BrowserWindowConstructorOptions;
|
||||
private readonly windowOpts: ICustomBrowserWindowConstructorOpts;
|
||||
private readonly globalConfig: IConfig;
|
||||
// Window reference
|
||||
private readonly windows: object;
|
||||
private mainWindow: Electron.BrowserWindow | null;
|
||||
private loadingWindow: Electron.BrowserWindow | null;
|
||||
private aboutAppWindow: Electron.BrowserWindow | null;
|
||||
|
||||
constructor(opts?: Electron.BrowserViewConstructorOptions) {
|
||||
this.windowOpts = { ... WindowHandler.getMainWindowOpts(), ...opts };
|
||||
this.windows = {};
|
||||
this.windowOpts = { ...WindowHandler.getMainWindowOpts(), ...opts };
|
||||
this.mainWindow = null;
|
||||
this.loadingWindow = null;
|
||||
this.aboutAppWindow = null;
|
||||
@ -108,6 +115,7 @@ export class WindowHandler {
|
||||
}
|
||||
this.createAboutAppWindow();
|
||||
});
|
||||
this.addWindow(this.windowOpts.winKey, this.mainWindow);
|
||||
return this.mainWindow;
|
||||
}
|
||||
|
||||
@ -118,6 +126,16 @@ export class WindowHandler {
|
||||
return this.mainWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the window and a key has a window
|
||||
* @param key {string}
|
||||
* @param window {Electron.BrowserWindow}
|
||||
*/
|
||||
public hasWindow(key: string, window: Electron.BrowserWindow): boolean {
|
||||
const browserWindow = this.windows[key];
|
||||
return browserWindow && window === browserWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a loading window until the main
|
||||
* application is loaded
|
||||
@ -138,11 +156,20 @@ export class WindowHandler {
|
||||
*/
|
||||
public createAboutAppWindow() {
|
||||
this.aboutAppWindow = createComponentWindow('about-app');
|
||||
this.aboutAppWindow.webContents.once('did-finish-load', () => {
|
||||
if (this.aboutAppWindow) {
|
||||
this.aboutAppWindow.webContents.send('about-app-data', { buildNumber, clientVersion, version });
|
||||
}
|
||||
});
|
||||
this.aboutAppWindow.webContents.once('did-finish-load', () => {
|
||||
if (this.aboutAppWindow) {
|
||||
this.aboutAppWindow.webContents.send('about-app-data', { buildNumber, clientVersion, version });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores information of all the window we have created
|
||||
* @param key {string}
|
||||
* @param browserWindow {Electron.BrowserWindow}
|
||||
*/
|
||||
private addWindow(key: string, browserWindow: Electron.BrowserWindow): void {
|
||||
this.windows[key] = browserWindow;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
import { isMac } from '../common/env';
|
||||
import { logger } from '../common/logger';
|
||||
import { windowHandler } from './window-handler';
|
||||
|
||||
/**
|
||||
@ -58,4 +61,49 @@ export function preventWindowNavigation(browserWindow: Electron.BrowserWindow) {
|
||||
};
|
||||
|
||||
browserWindow.webContents.on('will-navigate', listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the badge count
|
||||
* @param count {number}
|
||||
*/
|
||||
export function showBadgeCount(count: number): void {
|
||||
if (typeof count !== 'number') {
|
||||
logger.warn(`badgeCount: invalid func arg, must be a number: ${count}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMac) {
|
||||
// too big of a number here and setBadgeCount crashes
|
||||
app.setBadgeCount(Math.min(1e8, count));
|
||||
return;
|
||||
}
|
||||
|
||||
// handle ms windows...
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (mainWindow) {
|
||||
if (count > 0) {
|
||||
// get badge img from renderer process, will return
|
||||
// img dataUrl in setDataUrl func.
|
||||
mainWindow.webContents.send('create-badge-data-url', { count });
|
||||
} else {
|
||||
// clear badge count icon
|
||||
mainWindow.setOverlayIcon(null, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data url
|
||||
* @param dataUrl
|
||||
* @param count
|
||||
*/
|
||||
export function setDataUrl(dataUrl: string, count: number): void {
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (mainWindow && dataUrl && count) {
|
||||
const img = nativeImage.createFromDataURL(dataUrl);
|
||||
// for accessibility screen readers
|
||||
const desc = 'Symphony has ' + count + ' unread messages';
|
||||
mainWindow.setOverlayIcon(img, desc);
|
||||
}
|
||||
}
|
@ -1,22 +1,22 @@
|
||||
export enum apiCmds {
|
||||
isOnline,
|
||||
registerLogger,
|
||||
setBadgeCount,
|
||||
badgeDataUrl,
|
||||
activate,
|
||||
registerBoundsChange,
|
||||
registerProtocolHandler,
|
||||
registerActivityDetection,
|
||||
showNotificationSettings,
|
||||
sanitize,
|
||||
bringToFront,
|
||||
openScreenPickerWindow,
|
||||
popupMenu,
|
||||
optimizeMemoryConsumption,
|
||||
optimizeMemoryRegister,
|
||||
setIsInMeeting,
|
||||
setLocale,
|
||||
keyPress,
|
||||
isOnline = 'is-online',
|
||||
registerLogger = 'register-logger',
|
||||
setBadgeCount = 'set-badge-count',
|
||||
badgeDataUrl = 'badge-data-url',
|
||||
activate = 'activate',
|
||||
registerBoundsChange = 'register-bounds-change',
|
||||
registerProtocolHandler = 'register-protocol-handler',
|
||||
registerActivityDetection = 'register-activity-detection',
|
||||
showNotificationSettings = 'show-notification-settings',
|
||||
sanitize = 'sanitize',
|
||||
bringToFront = 'bring-to-front',
|
||||
openScreenPickerWindow = 'open-screen-picker-window',
|
||||
popupMenu = 'popup-menu',
|
||||
optimizeMemoryConsumption = 'optimize-memory-consumption',
|
||||
optimizeMemoryRegister = 'optimize-memory-register',
|
||||
setIsInMeeting = 'set-is-in-meeting',
|
||||
setLocale = 'set-locale',
|
||||
keyPress = 'key-press',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
@ -33,7 +33,6 @@ export interface IApiArgs {
|
||||
reason: string;
|
||||
sources: Electron.DesktopCapturerSource[];
|
||||
id: number;
|
||||
memory: Electron.ProcessMemoryInfo;
|
||||
cpuUsage: Electron.CPUUsage;
|
||||
isInMeeting: boolean;
|
||||
locale: string;
|
||||
|
@ -179,7 +179,7 @@ const formatString = (str: string, data?: object): string => {
|
||||
* @param wait
|
||||
* @example const throttled = throttle(anyFunc, 500);
|
||||
*/
|
||||
const throttle = (func: () => void, wait: number): () => void => {
|
||||
const throttle = (func: (...args) => void, wait: number): (...args) => void => {
|
||||
if (wait <= 0) {
|
||||
throw Error('throttle: invalid throttleTime arg, must be a number: ' + wait);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export default class AboutApp extends React.Component<{}, IState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
ipcRenderer.removeListener('open-file-reply', this.updateState);
|
||||
ipcRenderer.removeListener('about-app-data', this.updateState);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import WindowsTitleBar from '../renderer/windows-title-bar';
|
||||
import { SSFApi } from './ssf-api';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', load);
|
||||
|
||||
@ -11,4 +12,27 @@ document.addEventListener('DOMContentLoaded', load);
|
||||
function load() {
|
||||
const element = React.createElement(WindowsTitleBar);
|
||||
ReactDOM.render(element, document.body);
|
||||
}
|
||||
|
||||
createAPI();
|
||||
|
||||
/**
|
||||
* creates API exposed from electron.
|
||||
*/
|
||||
function createAPI() {
|
||||
// iframes (and any other non-top level frames) get no api access
|
||||
// http://stackoverflow.com/questions/326069/how-to-identify-if-a-webpage-is-being-loaded-inside-an-iframe-or-directly-into-t/326076
|
||||
if (window.self !== window.top) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note: window.open from main window (if in the same domain) will get
|
||||
// api access. window.open in another domain will be opened in the default
|
||||
// browser (see: handler for event 'new-window' in windowMgr.js)
|
||||
|
||||
//
|
||||
// API exposed to renderer process.
|
||||
//
|
||||
// @ts-ignore
|
||||
window.ssf = new SSFApi();
|
||||
}
|
77
src/renderer/ssf-api.ts
Normal file
77
src/renderer/ssf-api.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import { apiCmds, apiName } from '../common/api-interface';
|
||||
import { throttle } from '../common/utils';
|
||||
|
||||
const local = {
|
||||
ipcRenderer,
|
||||
};
|
||||
|
||||
// Throttle func
|
||||
const throttleSetBadgeCount = throttle((count) => {
|
||||
console.log(count);
|
||||
console.log(apiCmds.setBadgeCount);
|
||||
local.ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.setBadgeCount,
|
||||
count,
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
export class SSFApi {
|
||||
/**
|
||||
* sets the count on the tray icon to the given number.
|
||||
* @param {number} count count to be displayed
|
||||
* note: count of 0 will remove the displayed count.
|
||||
* note: for mac the number displayed will be 1 to infinity
|
||||
* note: for windws the number displayed will be 1 to 99 and 99+
|
||||
*/
|
||||
public setBadgeCount(count: number): void {
|
||||
console.log(count);
|
||||
throttleSetBadgeCount(count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ipc events
|
||||
*/
|
||||
|
||||
// Creates a data url
|
||||
ipcRenderer.on('create-badge-data-url', (arg) => {
|
||||
const count = arg && arg.count || 0;
|
||||
|
||||
// create 32 x 32 img
|
||||
const radius = 16;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.height = radius * 2;
|
||||
canvas.width = radius * 2;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.beginPath();
|
||||
ctx.arc(radius, radius, radius, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = 'white';
|
||||
|
||||
const text = count > 99 ? '99+' : count.toString();
|
||||
if (text.length > 2) {
|
||||
ctx.font = 'bold 18px sans-serif';
|
||||
ctx.fillText(text, radius, 22);
|
||||
} else if (text.length > 1) {
|
||||
ctx.font = 'bold 24px sans-serif';
|
||||
ctx.fillText(text, radius, 24);
|
||||
} else {
|
||||
ctx.font = 'bold 26px sans-serif';
|
||||
ctx.fillText(text, radius, 26);
|
||||
}
|
||||
const dataUrl = canvas.toDataURL('image/png', 1.0);
|
||||
|
||||
local.ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.badgeDataUrl,
|
||||
count,
|
||||
dataUrl,
|
||||
});
|
||||
}
|
||||
});
|
@ -104,4 +104,8 @@
|
||||
width: 100%;
|
||||
z-index: 3000;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.symphony-logo {
|
||||
content: url("../src/renderer/assets/symphony-title-bar-logo.png");
|
||||
}
|
@ -86,7 +86,7 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
</button>
|
||||
</div>
|
||||
<div className='title-container'>
|
||||
<img src='./assets/symphony-title-bar-logo.png'/>
|
||||
<img className='symphony-logo'/>
|
||||
<p className='title-bar-title'>{document.title || 'Symphony'}</p>
|
||||
</div>
|
||||
<div className='title-bar-button-container'>
|
||||
|
Loading…
Reference in New Issue
Block a user