SDA-3333: ADD Presence status for SDA Side

This commit is contained in:
NguyenTranHoangSym 2023-02-28 17:02:43 +07:00 committed by Salah Benmoussati
parent fffdc467f1
commit 3970facccb
24 changed files with 373 additions and 18 deletions

View File

@ -88,7 +88,7 @@ gulp.task('copy', function () {
return gulp
.src(
[
'./src/renderer/assets/*',
'./src/renderer/assets/**',
'./src/renderer/*.html',
'./src/locale/*',
'./package.json',

View File

@ -51,6 +51,8 @@ import {
import { getCommandLineArgs } from '../common/utils';
import { autoUpdate, AutoUpdateTrigger } from './auto-update-handler';
import { presenceStatus } from './presence-status-handler';
import { presenceStatusStore } from './stores/index';
// Swift search API
let swiftSearchInstance;
@ -108,6 +110,8 @@ ipcMain.on(
case apiCmds.setBadgeCount:
if (typeof arg.count === 'number') {
showBadgeCount(arg.count);
presenceStatusStore.setNotificationCount(arg.count);
logger.info(`main-api-handler: show and update count: ${arg.count}`);
}
break;
case apiCmds.registerProtocolHandler:
@ -128,8 +132,11 @@ ipcMain.on(
finalizeLogExports(arg.logs);
break;
case apiCmds.badgeDataUrl:
if (typeof arg.dataUrl === 'string' && typeof arg.count === 'number') {
setDataUrl(arg.dataUrl, arg.count);
if (typeof arg.dataUrl === 'string') {
if (typeof arg.count === 'number' && arg.count > 0) {
setDataUrl(arg.dataUrl, arg.count);
logger.info(`main-api-handler: set badge count: ${arg.count}`);
}
}
break;
case apiCmds.activate:
@ -209,6 +216,10 @@ ipcMain.on(
handleKeyPress(arg.keyCode);
}
break;
case apiCmds.getMyPresence:
presenceStatus.setMyPresence(arg.status);
logger.info('main-api-handler: get presence from C2 to set in SDA');
break;
case apiCmds.openScreenSnippet:
screenSnippet.capture(event.sender, arg.hideOnCapture);
break;
@ -283,6 +294,15 @@ ipcMain.on(
windowHandler.isMana = arg.isMana;
// Update App Menu
const appMenu = windowHandler.appMenu;
const mainWindow = windowHandler.getMainWindow();
if (mainWebContents) {
const items = presenceStatus.createThumbarButtons(mainWebContents);
mainWindow?.setThumbarButtons(items);
logger.info('main-api-handler: Add actions preview menu');
}
if (appMenu && windowHandler.isMana) {
appMenu.buildMenu();
}

View File

@ -0,0 +1,100 @@
import { nativeImage, WebContents } from 'electron';
import {
EPresenceStatus,
IPresenceStatus,
IThumbarButton,
} from '../common/api-interface';
import { i18n } from '../common/i18n';
import { logger } from '../common/logger';
import { presenceStatusStore } from './stores';
import { showBadgeCount, showSystemTrayPresence } from './window-utils';
export interface IListItem {
name: string;
event: string;
dataTestId: string;
onClick: (eventName: string) => Promise<void>;
}
class PresenceStatus {
private NAMESPACE = 'PresenceStatus';
public createThumbarButtons = (
webContents: WebContents,
): IThumbarButton[] => {
return [
{
click: () => {
logger.info('presence-status-handler: Available Clicked');
this.setPresenceStatus(webContents, EPresenceStatus.AVAILABLE);
},
icon: nativeImage.createFromPath(
presenceStatusStore.generateImagePath(
EPresenceStatus.AVAILABLE,
'thumbnail',
),
),
tooltip: i18n.t(EPresenceStatus.AVAILABLE, this.NAMESPACE)(),
},
{
click: () => {
logger.info('presence-status-handler: Busy Clicked');
this.setPresenceStatus(webContents, EPresenceStatus.BUSY);
},
icon: nativeImage.createFromPath(
presenceStatusStore.generateImagePath(
EPresenceStatus.BUSY,
'thumbnail',
),
),
tooltip: i18n.t(EPresenceStatus.BUSY, this.NAMESPACE)(),
},
{
click: () => {
logger.info('presence-status-handler: Be Right Back Clicked');
this.setPresenceStatus(webContents, EPresenceStatus.BE_RIGHT_BACK);
},
icon: nativeImage.createFromPath(
presenceStatusStore.generateImagePath(
EPresenceStatus.BE_RIGHT_BACK,
'thumbnail',
),
),
tooltip: i18n.t(EPresenceStatus.BE_RIGHT_BACK, this.NAMESPACE)(),
},
{
click: () => {
logger.info('presence-status-handler: Out of Office Clicked');
this.setPresenceStatus(webContents, EPresenceStatus.OUT_OF_OFFICE);
},
icon: nativeImage.createFromPath(
presenceStatusStore.generateImagePath(
EPresenceStatus.OUT_OF_OFFICE,
'thumbnail',
),
),
tooltip: i18n.t(EPresenceStatus.OUT_OF_OFFICE, this.NAMESPACE)(),
},
];
};
public setMyPresence = (myPresence: IPresenceStatus) => {
const count = presenceStatusStore.getNotificationCount();
presenceStatusStore.setStatus(myPresence.category);
showBadgeCount(count);
showSystemTrayPresence(myPresence.category);
};
private setPresenceStatus = (
webContents: WebContents,
status: EPresenceStatus,
) => {
webContents.send('send-presence-status-data', status);
presenceStatusStore.setStatus(status);
};
}
const presenceStatus = new PresenceStatus();
export { presenceStatus };

View File

@ -1,5 +1,7 @@
import { PresenceStatus } from './presence-status-store';
import { WindowStore } from './window-store';
const winStore = new WindowStore();
const presenceStatusStore = new PresenceStatus();
export { winStore };
export { winStore, presenceStatusStore };

View File

@ -0,0 +1,81 @@
import { Tray } from 'electron';
import * as path from 'path';
import {
EPresenceStatus,
IStatusBadge,
ITray,
} from '../../common/api-interface';
// Flags can be read more here https://www.electronjs.org/docs/latest/api/browser-window#winsetthumbarbuttonsbuttons-windows
export class PresenceStatus {
private presenceStatus: IStatusBadge = {
status: EPresenceStatus.AVAILABLE,
count: 0,
};
private tray: ITray = {
current: null,
};
public setStatus = (status: EPresenceStatus) => {
this.presenceStatus.status = status;
};
public setNotificationCount = (count: number) => {
this.presenceStatus.count = count;
};
public getStatus = () => {
return this.presenceStatus.status;
};
public getNotificationCount = () => {
return this.presenceStatus.count;
};
public setCurrentTray = (tray: Tray) => (this.tray.current = tray);
public getCurrentTray = () => this.tray.current;
public generateImagePath = (status: EPresenceStatus, place: string) => {
let backgroundImage: string = '';
const assetsPath = 'src/renderer/assets/presence-status';
switch (status) {
case EPresenceStatus.AVAILABLE:
backgroundImage = `../../../${assetsPath}/${
place === 'tray' ? 'online-tray.png' : 'online.png'
}`;
break;
case EPresenceStatus.BUSY:
backgroundImage = `../../../${assetsPath}/${
place === 'tray' ? 'busy-tray.png' : 'busy.png'
}`;
break;
case EPresenceStatus.BE_RIGHT_BACK || EPresenceStatus.AWAY:
backgroundImage = `../../../${assetsPath}/${
place === 'tray' ? 'brb-tray.png' : 'brb.png'
}`;
break;
case EPresenceStatus.OFFLINE:
backgroundImage = `../../../${assetsPath}/${
place === 'tray' ? 'offline-tray.png' : 'offline.png'
}`;
break;
case EPresenceStatus.OUT_OF_OFFICE:
backgroundImage = `../../../${assetsPath}/${
place === 'tray' ? 'out-of-office-tray.png' : 'out-of-office.png'
}`;
break;
default:
break;
}
return backgroundImage ? path.join(__dirname, backgroundImage) : '';
};
}

View File

@ -7,6 +7,7 @@ import {
Rectangle,
screen,
shell,
Tray,
WebContents,
} from 'electron';
import electron = require('electron');
@ -15,7 +16,11 @@ import * as filesize from 'filesize';
import * as fs from 'fs';
import * as path from 'path';
import { format, parse } from 'url';
import { apiName } from '../common/api-interface';
import {
apiName,
EPresenceStatus,
// IStatusBadge,
} from '../common/api-interface';
import { isDevEnv, isLinux, isMac, isWindowsOS } from '../common/env';
import { i18n, LocaleType } from '../common/i18n';
@ -50,6 +55,7 @@ import { notification } from '../renderer/notification';
import { autoLaunchInstance } from './auto-launch-controller';
import { autoUpdate, AutoUpdateTrigger } from './auto-update-handler';
import { mainEvents } from './main-event-handler';
import { presenceStatusStore } from './stores';
interface IStyles {
name: styleNames;
@ -269,7 +275,6 @@ export const showBadgeCount = (count: number): void => {
}
// handle ms windows...
const mainWindow = windowHandler.getMainWindow();
const mainWebContents = windowHandler.getMainWebContents();
if (!mainWebContents || mainWebContents.isDestroyed()) {
return;
@ -280,14 +285,39 @@ export const showBadgeCount = (count: number): void => {
if (count > 0) {
mainWebContents.send('create-badge-data-url', { count });
return;
}
} else {
const status = presenceStatusStore.getStatus();
const backgroundImage = presenceStatusStore.generateImagePath(
status,
'taskbar',
);
if (!mainWindow || !windowExists(mainWindow)) {
if (backgroundImage) {
setStatusBadge(backgroundImage, status);
}
}
};
/**
* Shows the badge count
*
* @param count {number}
*/
export const showSystemTrayPresence = (status: EPresenceStatus): void => {
const tray = presenceStatusStore.getCurrentTray();
const backgroundImage = presenceStatusStore.generateImagePath(status, 'tray');
if (!backgroundImage) {
return;
}
// clear badge count icon
mainWindow.setOverlayIcon(null, '');
if (!tray) {
const symphonyTray = new Tray(backgroundImage);
presenceStatusStore.setCurrentTray(symphonyTray);
symphonyTray.setToolTip('Symphony');
logger.info('main-api-handler: create and save Symphony tray');
} else {
tray.setImage(backgroundImage);
logger.info('main-api-handler: new Symphony status updated');
}
};
/**
@ -296,6 +326,20 @@ export const showBadgeCount = (count: number): void => {
* @param dataUrl
* @param count
*/
export const setStatusBadge = (
path: string,
status?: EPresenceStatus,
): void => {
const mainWindow = windowHandler.getMainWindow();
if (mainWindow && path && status) {
const img = nativeImage.createFromPath(path);
// for accessibility screen readers
const desc = `Your current status is ${i18n.t(status, 'PresenceStatus')()}`;
mainWindow.setOverlayIcon(img, desc);
logger.info('window-utils: Taskbar Presence Updated');
}
};
export const setDataUrl = (dataUrl: string, count: number): void => {
const mainWindow = windowHandler.getMainWindow();
if (mainWindow && dataUrl && count) {

View File

@ -1,4 +1,4 @@
import { Size } from 'electron';
import { NativeImage, Size, Tray } from 'electron';
import { AutoUpdateTrigger } from '../app/auto-update-handler';
export enum apiCmds {
@ -73,6 +73,9 @@ export enum apiCmds {
downloadUpdate = 'download-update',
checkForUpdates = 'check-for-updates',
seamlessLogin = 'seamless-login',
updateMyPresence = 'update-my-presence',
getMyPresence = 'get-my-presence',
updateSymphonyTray = 'update-system-tray',
}
export enum apiName {
@ -130,6 +133,7 @@ export interface IApiArgs {
data: Uint8Array;
autoUpdateTrigger: AutoUpdateTrigger;
hideOnCapture: boolean;
status: IPresenceStatus;
}
export type Themes = 'light' | 'dark';
@ -157,6 +161,54 @@ export interface IBoundsChange extends Electron.Rectangle {
windowName: string;
}
// Presence Status
export interface IThumbarButton {
icon: NativeImage;
click: () => void;
tooltip?: string;
flags?: Array<
| 'enabled'
| 'disabled'
| 'dismissonclick'
| 'nobackground'
| 'hidden'
| 'noninteractive'
>;
}
export interface IStatusBadge extends IBadgeCount {
status: EPresenceStatus;
}
export interface ITray {
current: Tray | null;
}
export interface IPresenceStore {
statusBadge: IStatusBadge;
presenceStatus: IPresenceStatus;
}
export enum EPresenceStatus {
'ONLINE' = 'ONLINE',
'OFFLINE' = 'OFFLINE',
'AWAY' = 'AWAY',
'DO_NOT_DISTURB' = 'DO_NOT_DISTURB',
'BUSY' = 'BUSY',
'ON_THE_PHONE' = 'ON_THE_PHONE',
'AVAILABLE' = 'AVAILABLE',
'OUT_OF_OFFICE' = 'OUT_OF_OFFICE',
'IN_A_MEETING' = 'IN_A_MEETING',
'BE_RIGHT_BACK' = 'BE_RIGHT_BACK',
'OFF_WORK' = 'OFF_WORK',
}
export interface IPresenceStatus {
category: EPresenceStatus;
statusGroup: string;
timestamp: number;
}
/**
* Screen sharing indicator
*/

View File

@ -241,5 +241,14 @@
"Allow once (risky)": "Allow once (risky)",
"Deny": "Deny",
"Invalid security certificate": "has an invalid security certificate.",
"IV Dogfooding": "IV Dogfooding"
}
"IV Dogfooding": "IV Dogfooding",
"PresenceStatus": {
"OFFLINE": "Offline",
"AWAY": "Away",
"DO_NOT_DISTURB": "Do not disturb",
"BUSY": "Busy",
"AVAILABLE": "Available",
"OUT_OF_OFFICE": "Out of office",
"BE_RIGHT_BACK": "Be right back"
}
}

View File

@ -241,5 +241,14 @@
"Allow once (risky)": "Allow once (risky)",
"Deny": "Deny",
"Invalid security certificate": "has an invalid security certificate.",
"IV Dogfooding": "IV Dogfooding"
}
"IV Dogfooding": "IV Dogfooding",
"PresenceStatus": {
"OFFLINE": "Offline",
"AWAY": "Away",
"DO_NOT_DISTURB": "Do not disturb",
"BUSY": "Busy",
"AVAILABLE": "Available",
"OUT_OF_OFFICE": "Out of office",
"BE_RIGHT_BACK": "Be right back"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

View File

@ -97,6 +97,8 @@ if (ssfWindow.ssf) {
updateAndRestart: ssfWindow.ssf.updateAndRestart,
downloadUpdate: ssfWindow.ssf.downloadUpdate,
checkForUpdates: ssfWindow.ssf.checkForUpdates,
updateMyPresence: ssfWindow.ssf.updateMyPresence,
getMyPresence: ssfWindow.ssf.getMyPresence,
});
}

View File

@ -13,17 +13,19 @@ import {
apiCmds,
apiName,
ConfigUpdateType,
IBadgeCount,
EPresenceStatus,
IBoundsChange,
ICloud9Pipe,
ICPUUsage,
ILogMsg,
IMediaPermission,
INotificationData,
IPresenceStatus,
IRestartFloaterData,
IScreenSharingIndicator,
IScreenSharingIndicatorOptions,
IScreenSnippet,
IStatusBadge,
IVersionInfo,
KeyCodes,
LogLevel,
@ -58,6 +60,7 @@ export interface ILocalObject {
>;
c9PipeEventCallback?: (event: string, arg?: any) => void;
c9MessageCallback?: (status: IShellStatus) => void;
updateMyPresenceCallback?: (presence: EPresenceStatus) => void;
}
const local: ILocalObject = {
@ -363,6 +366,30 @@ export class SSFApi {
// tslint:disable-next-line
public ScreenSnippet = ScreenSnippetBcHandler;
/**
* Update presence of current user
* @param callback (presence:IPresenceStatus)=>void
* if none is provided then the old logic will be triggered.
* I dont send this event to main-api-handler because this will only act as a callback assignment
* It will only trigger if you hit any button at presence-status-handler
*
*/
public updateMyPresence(callback: (category: EPresenceStatus) => void) {
if (typeof callback === 'function') {
local.updateMyPresenceCallback = callback;
}
}
/**
* Get presence of current user
* @param myPresence IPresenceStatus
*/
public getMyPresence(myPresence: IPresenceStatus) {
local.ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.getMyPresence,
status: myPresence,
});
}
/**
* Allow user to capture portion of screen.
* There is a method overload of this with additional param allows user to hide SDA,
@ -863,7 +890,7 @@ export class SSFApi {
*/
local.ipcRenderer.on(
'create-badge-data-url',
(_event: Event, arg: IBadgeCount) => {
(_event: Event, arg: IStatusBadge) => {
const count = (arg && arg.count) || 0;
// create 32 x 32 img
@ -903,6 +930,15 @@ local.ipcRenderer.on(
},
);
local.ipcRenderer.on(
'send-presence-status-data',
(_event: Event, arg: EPresenceStatus) => {
if (typeof local.updateMyPresenceCallback === 'function') {
local.updateMyPresenceCallback(arg);
}
},
);
/**
* An event triggered by the main process
* when the snippet is complete