SDA-4070 Presence status on macOS (#1738)
* SDA-4070 Presence status on macOS * bugfix * SDA-4070 Presence status on macOS * SDA-4070 Presence status on macOS
No files matched your search
@@ -14,6 +14,7 @@ import { protocolHandler } from './protocol-handler';
|
|||||||
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||||
|
|
||||||
import { autoLaunchInstance } from './auto-launch-controller';
|
import { autoLaunchInstance } from './auto-launch-controller';
|
||||||
|
import { presenceStatusStore } from './stores';
|
||||||
|
|
||||||
// Set automatic period substitution to false because of a bug in draft js on the client app
|
// 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
|
// See https://perzoinc.atlassian.net/browse/SDA-2215 for more details
|
||||||
@@ -171,6 +172,7 @@ app.on('window-all-closed', () => {
|
|||||||
*/
|
*/
|
||||||
app.on('quit', () => {
|
app.on('quit', () => {
|
||||||
logger.info(`main: quitting the app!`);
|
logger.info(`main: quitting the app!`);
|
||||||
|
presenceStatusStore.destroyCurrentTray();
|
||||||
cleanUpAppCache();
|
cleanUpAppCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { nativeImage, WebContents } from 'electron';
|
import { app, Menu, nativeImage, WebContents } from 'electron';
|
||||||
import {
|
import {
|
||||||
EPresenceStatus,
|
EPresenceStatus,
|
||||||
IPresenceStatus,
|
IPresenceStatus,
|
||||||
@@ -7,7 +7,8 @@ import {
|
|||||||
import { i18n } from '../common/i18n';
|
import { i18n } from '../common/i18n';
|
||||||
import { logger } from '../common/logger';
|
import { logger } from '../common/logger';
|
||||||
import { presenceStatusStore } from './stores';
|
import { presenceStatusStore } from './stores';
|
||||||
import { showBadgeCount, showSystemTrayPresence } from './window-utils';
|
import { windowHandler } from './window-handler';
|
||||||
|
import { initSysTray, showBadgeCount } from './window-utils';
|
||||||
|
|
||||||
export interface IListItem {
|
export interface IListItem {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -79,11 +80,98 @@ class PresenceStatus {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public setMyPresence = (myPresence: IPresenceStatus) => {
|
public setMyPresence = (myPresence: IPresenceStatus) => {
|
||||||
|
const currentPresenceStatus = presenceStatusStore.getStatus();
|
||||||
const count = presenceStatusStore.getNotificationCount();
|
const count = presenceStatusStore.getNotificationCount();
|
||||||
|
if (currentPresenceStatus !== myPresence.category) {
|
||||||
presenceStatusStore.setStatus(myPresence.category);
|
presenceStatusStore.setStatus(myPresence.category);
|
||||||
|
this.updateSystemTrayPresence();
|
||||||
|
}
|
||||||
showBadgeCount(count);
|
showBadgeCount(count);
|
||||||
showSystemTrayPresence(myPresence.category);
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the badge count
|
||||||
|
*
|
||||||
|
* @param count {number}
|
||||||
|
*/
|
||||||
|
public updateSystemTrayPresence = (): void => {
|
||||||
|
const status = presenceStatusStore.getStatus();
|
||||||
|
let tray = presenceStatusStore.getCurrentTray();
|
||||||
|
const backgroundImage = presenceStatusStore.generateImagePath(
|
||||||
|
status,
|
||||||
|
'tray',
|
||||||
|
);
|
||||||
|
if (!backgroundImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tray) {
|
||||||
|
tray = initSysTray();
|
||||||
|
logger.info('presence-status-handler: create and save Symphony tray');
|
||||||
|
} else {
|
||||||
|
tray.setImage(backgroundImage);
|
||||||
|
logger.info('presence-status-handler: new Symphony status updated');
|
||||||
|
}
|
||||||
|
const currentStatus = presenceStatusStore.getStatus();
|
||||||
|
const presenceNamespace = 'PresenceStatus';
|
||||||
|
const isMana = !!windowHandler.isMana;
|
||||||
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: i18n.t('My presence')(),
|
||||||
|
visible: isMana,
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: i18n.t(EPresenceStatus.AVAILABLE, presenceNamespace)(),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: currentStatus === EPresenceStatus.AVAILABLE,
|
||||||
|
click: () => {
|
||||||
|
this.handlePresenceChange(EPresenceStatus.AVAILABLE);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.t(EPresenceStatus.BUSY, presenceNamespace)(),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: currentStatus === EPresenceStatus.BUSY,
|
||||||
|
click: () => {
|
||||||
|
this.handlePresenceChange(EPresenceStatus.BUSY);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.t(EPresenceStatus.BE_RIGHT_BACK, presenceNamespace)(),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: currentStatus === EPresenceStatus.BE_RIGHT_BACK,
|
||||||
|
click: () => {
|
||||||
|
this.handlePresenceChange(EPresenceStatus.BE_RIGHT_BACK);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.t(EPresenceStatus.OUT_OF_OFFICE, presenceNamespace)(),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: currentStatus === EPresenceStatus.OUT_OF_OFFICE,
|
||||||
|
click: () => {
|
||||||
|
this.handlePresenceChange(EPresenceStatus.OUT_OF_OFFICE);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.t('Quit Symphony')(),
|
||||||
|
click: () => app.quit(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
tray?.setContextMenu(contextMenu);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handlePresenceChange = (currentStatus: EPresenceStatus) => {
|
||||||
|
const status = {
|
||||||
|
category: currentStatus,
|
||||||
|
statusGroup: '',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
presenceStatus.setMyPresence(status);
|
||||||
|
const mainWebContents = windowHandler.getMainWebContents();
|
||||||
|
if (mainWebContents) {
|
||||||
|
mainWebContents.send('send-presence-status-data', currentStatus);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private setPresenceStatus = (
|
private setPresenceStatus = (
|
||||||
@@ -92,6 +180,7 @@ class PresenceStatus {
|
|||||||
) => {
|
) => {
|
||||||
webContents.send('send-presence-status-data', status);
|
webContents.send('send-presence-status-data', status);
|
||||||
presenceStatusStore.setStatus(status);
|
presenceStatusStore.setStatus(status);
|
||||||
|
this.updateSystemTrayPresence();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Tray } from 'electron';
|
import { nativeTheme, Tray } from 'electron';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {
|
import {
|
||||||
EPresenceStatus,
|
EPresenceStatus,
|
||||||
IStatusBadge,
|
IStatusBadge,
|
||||||
ITray,
|
ITray,
|
||||||
} from '../../common/api-interface';
|
} from '../../common/api-interface';
|
||||||
|
import { isMac, isWindowsOS } from '../../common/env';
|
||||||
|
|
||||||
// Flags can be read more here https://www.electronjs.org/docs/latest/api/browser-window#winsetthumbarbuttonsbuttons-windows
|
// Flags can be read more here https://www.electronjs.org/docs/latest/api/browser-window#winsetthumbarbuttonsbuttons-windows
|
||||||
|
|
||||||
export class PresenceStatus {
|
export class PresenceStatus {
|
||||||
private presenceStatus: IStatusBadge = {
|
private presenceStatus: IStatusBadge = {
|
||||||
status: EPresenceStatus.AVAILABLE,
|
status: EPresenceStatus.NO_PRESENCE,
|
||||||
count: 0,
|
count: 0,
|
||||||
};
|
};
|
||||||
private tray: ITray = {
|
private tray: ITray = {
|
||||||
@@ -37,9 +38,19 @@ export class PresenceStatus {
|
|||||||
|
|
||||||
public getCurrentTray = () => this.tray.current;
|
public getCurrentTray = () => this.tray.current;
|
||||||
|
|
||||||
|
public destroyCurrentTray = () => {
|
||||||
|
if (this.tray.current) {
|
||||||
|
this.tray.current.removeAllListeners();
|
||||||
|
this.tray.current.destroy();
|
||||||
|
this.tray.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public generateImagePath = (status: EPresenceStatus, place: string) => {
|
public generateImagePath = (status: EPresenceStatus, place: string) => {
|
||||||
let backgroundImage: string = '';
|
let backgroundImage: string = '';
|
||||||
const assetsPath = 'src/renderer/assets/presence-status';
|
const os = isWindowsOS ? 'windows' : isMac ? 'macOS' : 'linux';
|
||||||
|
const theme = nativeTheme.shouldUseDarkColors ? 'light' : 'dark';
|
||||||
|
const assetsPath = `src/renderer/assets/presence-status/${os}/${theme}`;
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case EPresenceStatus.AVAILABLE:
|
case EPresenceStatus.AVAILABLE:
|
||||||
@@ -71,7 +82,15 @@ export class PresenceStatus {
|
|||||||
place === 'tray' ? 'out-of-office-tray.png' : 'out-of-office.png'
|
place === 'tray' ? 'out-of-office-tray.png' : 'out-of-office.png'
|
||||||
}`;
|
}`;
|
||||||
break;
|
break;
|
||||||
|
case EPresenceStatus.IN_A_MEETING:
|
||||||
|
backgroundImage = `../../../${assetsPath}/${
|
||||||
|
place === 'tray' ? 'in-a-meeting-tray.png' : 'in-a-meeting.png'
|
||||||
|
}`;
|
||||||
|
break;
|
||||||
|
case EPresenceStatus.NO_PRESENCE:
|
||||||
|
backgroundImage =
|
||||||
|
place === 'tray' ? `../../../${assetsPath}/no-status-tray.png` : '';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
dialog,
|
dialog,
|
||||||
Event,
|
Event,
|
||||||
ipcMain,
|
ipcMain,
|
||||||
|
nativeTheme,
|
||||||
RenderProcessGoneDetails,
|
RenderProcessGoneDetails,
|
||||||
screen,
|
screen,
|
||||||
shell,
|
shell,
|
||||||
@@ -43,6 +44,7 @@ import {
|
|||||||
import crashHandler from './crash-handler';
|
import crashHandler from './crash-handler';
|
||||||
import LocalMenuShortcuts from './local-menu-shortcuts';
|
import LocalMenuShortcuts from './local-menu-shortcuts';
|
||||||
import { mainEvents } from './main-event-handler';
|
import { mainEvents } from './main-event-handler';
|
||||||
|
import { presenceStatus } from './presence-status-handler';
|
||||||
import { exportLogs } from './reports-handler';
|
import { exportLogs } from './reports-handler';
|
||||||
import { SpellChecker } from './spell-check-handler';
|
import { SpellChecker } from './spell-check-handler';
|
||||||
import { winStore } from './stores';
|
import { winStore } from './stores';
|
||||||
@@ -60,6 +62,7 @@ import {
|
|||||||
getWindowByName,
|
getWindowByName,
|
||||||
handleCertificateProxyVerification,
|
handleCertificateProxyVerification,
|
||||||
handleDownloadManager,
|
handleDownloadManager,
|
||||||
|
initSysTray,
|
||||||
injectStyles,
|
injectStyles,
|
||||||
isSymphonyReachable,
|
isSymphonyReachable,
|
||||||
loadBrowserViews,
|
loadBrowserViews,
|
||||||
@@ -455,6 +458,12 @@ export class WindowHandler {
|
|||||||
mainEvents.publish('maximize');
|
mainEvents.publish('maximize');
|
||||||
}
|
}
|
||||||
this.mainWindow.show();
|
this.mainWindow.show();
|
||||||
|
initSysTray();
|
||||||
|
if (isMac) {
|
||||||
|
nativeTheme.on('updated', () => {
|
||||||
|
presenceStatus.updateSystemTrayPresence();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// check for build expiry in case of test builds
|
// check for build expiry in case of test builds
|
||||||
this.checkExpiry(this.mainWindow);
|
this.checkExpiry(this.mainWindow);
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import {
|
|||||||
BrowserView,
|
BrowserView,
|
||||||
BrowserWindow,
|
BrowserWindow,
|
||||||
dialog,
|
dialog,
|
||||||
|
Menu,
|
||||||
nativeImage,
|
nativeImage,
|
||||||
|
nativeTheme,
|
||||||
Rectangle,
|
Rectangle,
|
||||||
screen,
|
screen,
|
||||||
shell,
|
shell,
|
||||||
@@ -299,25 +301,30 @@ export const showBadgeCount = (count: number): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the badge count
|
* Creates sys tray
|
||||||
*
|
|
||||||
* @param count {number}
|
|
||||||
*/
|
*/
|
||||||
export const showSystemTrayPresence = (status: EPresenceStatus): void => {
|
export const initSysTray = () => {
|
||||||
const tray = presenceStatusStore.getCurrentTray();
|
const defaultSysTrayIcon = 'no-status-tray.png';
|
||||||
const backgroundImage = presenceStatusStore.generateImagePath(status, 'tray');
|
const os = isWindowsOS ? 'windows' : isMac ? 'macOS' : 'linux';
|
||||||
if (!backgroundImage) {
|
const theme = nativeTheme.shouldUseDarkColors ? 'light' : 'dark';
|
||||||
return;
|
logger.info('theme: ', theme, nativeTheme.themeSource);
|
||||||
}
|
const assetsPath = `renderer/assets/presence-status/${os}/${theme}`;
|
||||||
if (!tray) {
|
const defaultSysTrayIconPath = path.join(
|
||||||
const symphonyTray = new Tray(backgroundImage);
|
__dirname,
|
||||||
presenceStatusStore.setCurrentTray(symphonyTray);
|
`../${assetsPath}/${defaultSysTrayIcon}`,
|
||||||
symphonyTray.setToolTip('Symphony');
|
);
|
||||||
logger.info('main-api-handler: create and save Symphony tray');
|
const backgroundImage = nativeImage.createFromPath(defaultSysTrayIconPath);
|
||||||
} else {
|
const tray = new Tray(backgroundImage);
|
||||||
tray.setImage(backgroundImage);
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
logger.info('main-api-handler: new Symphony status updated');
|
{
|
||||||
}
|
label: i18n.t('Quit Symphony')(),
|
||||||
|
click: () => app.quit(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
tray.setContextMenu(contextMenu);
|
||||||
|
tray.setToolTip('Symphony');
|
||||||
|
presenceStatusStore.setCurrentTray(tray);
|
||||||
|
return tray;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ export enum EPresenceStatus {
|
|||||||
'IN_A_MEETING' = 'IN_A_MEETING',
|
'IN_A_MEETING' = 'IN_A_MEETING',
|
||||||
'BE_RIGHT_BACK' = 'BE_RIGHT_BACK',
|
'BE_RIGHT_BACK' = 'BE_RIGHT_BACK',
|
||||||
'OFF_WORK' = 'OFF_WORK',
|
'OFF_WORK' = 'OFF_WORK',
|
||||||
|
'NO_PRESENCE' = 'NO_PRESENCE',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPresenceStatus {
|
export interface IPresenceStatus {
|
||||||
|
|||||||
@@ -250,5 +250,6 @@
|
|||||||
"AVAILABLE": "Available",
|
"AVAILABLE": "Available",
|
||||||
"OUT_OF_OFFICE": "Out of office",
|
"OUT_OF_OFFICE": "Out of office",
|
||||||
"BE_RIGHT_BACK": "Be right back"
|
"BE_RIGHT_BACK": "Be right back"
|
||||||
}
|
},
|
||||||
|
"My presence": "My presence"
|
||||||
}
|
}
|
||||||
@@ -250,5 +250,6 @@
|
|||||||
"AVAILABLE": "Available",
|
"AVAILABLE": "Available",
|
||||||
"OUT_OF_OFFICE": "Out of office",
|
"OUT_OF_OFFICE": "Out of office",
|
||||||
"BE_RIGHT_BACK": "Be right back"
|
"BE_RIGHT_BACK": "Be right back"
|
||||||
}
|
},
|
||||||
|
"My presence": "My presence"
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 990 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 288 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 395 B |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 515 B |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 527 B |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 515 B |