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
This commit is contained in:
Salah Benmoussati authored and GitHub committed 2023-03-01 10:53:56 +01:00
1 parent a3e2192d4f
commit 722a47769e
48 files changed
+157 -28

No files matched your search

+2
View File
@@ -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();
}); });
+93 -4
View File
@@ -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();
}; };
} }
+23 -4
View File
@@ -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
View File
@@ -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);
+25 -18
View File
@@ -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;
}; };
/** /**
+1
View File
@@ -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 {
+2 -1
View File
@@ -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"
} }
+2 -1
View File
@@ -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"
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

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