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
@ -14,6 +14,7 @@ import { protocolHandler } from './protocol-handler';
|
||||
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||
|
||||
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
|
||||
// See https://perzoinc.atlassian.net/browse/SDA-2215 for more details
|
||||
@ -171,6 +172,7 @@ app.on('window-all-closed', () => {
|
||||
*/
|
||||
app.on('quit', () => {
|
||||
logger.info(`main: quitting the app!`);
|
||||
presenceStatusStore.destroyCurrentTray();
|
||||
cleanUpAppCache();
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { nativeImage, WebContents } from 'electron';
|
||||
import { app, Menu, nativeImage, WebContents } from 'electron';
|
||||
import {
|
||||
EPresenceStatus,
|
||||
IPresenceStatus,
|
||||
@ -7,7 +7,8 @@ import {
|
||||
import { i18n } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
import { presenceStatusStore } from './stores';
|
||||
import { showBadgeCount, showSystemTrayPresence } from './window-utils';
|
||||
import { windowHandler } from './window-handler';
|
||||
import { initSysTray, showBadgeCount } from './window-utils';
|
||||
|
||||
export interface IListItem {
|
||||
name: string;
|
||||
@ -79,11 +80,98 @@ class PresenceStatus {
|
||||
};
|
||||
|
||||
public setMyPresence = (myPresence: IPresenceStatus) => {
|
||||
const currentPresenceStatus = presenceStatusStore.getStatus();
|
||||
const count = presenceStatusStore.getNotificationCount();
|
||||
|
||||
presenceStatusStore.setStatus(myPresence.category);
|
||||
if (currentPresenceStatus !== myPresence.category) {
|
||||
presenceStatusStore.setStatus(myPresence.category);
|
||||
this.updateSystemTrayPresence();
|
||||
}
|
||||
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 = (
|
||||
@ -92,6 +180,7 @@ class PresenceStatus {
|
||||
) => {
|
||||
webContents.send('send-presence-status-data', 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 {
|
||||
EPresenceStatus,
|
||||
IStatusBadge,
|
||||
ITray,
|
||||
} 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
|
||||
|
||||
export class PresenceStatus {
|
||||
private presenceStatus: IStatusBadge = {
|
||||
status: EPresenceStatus.AVAILABLE,
|
||||
status: EPresenceStatus.NO_PRESENCE,
|
||||
count: 0,
|
||||
};
|
||||
private tray: ITray = {
|
||||
@ -37,9 +38,19 @@ export class PresenceStatus {
|
||||
|
||||
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) => {
|
||||
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) {
|
||||
case EPresenceStatus.AVAILABLE:
|
||||
@ -71,7 +82,15 @@ export class PresenceStatus {
|
||||
place === 'tray' ? 'out-of-office-tray.png' : 'out-of-office.png'
|
||||
}`;
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
dialog,
|
||||
Event,
|
||||
ipcMain,
|
||||
nativeTheme,
|
||||
RenderProcessGoneDetails,
|
||||
screen,
|
||||
shell,
|
||||
@ -43,6 +44,7 @@ import {
|
||||
import crashHandler from './crash-handler';
|
||||
import LocalMenuShortcuts from './local-menu-shortcuts';
|
||||
import { mainEvents } from './main-event-handler';
|
||||
import { presenceStatus } from './presence-status-handler';
|
||||
import { exportLogs } from './reports-handler';
|
||||
import { SpellChecker } from './spell-check-handler';
|
||||
import { winStore } from './stores';
|
||||
@ -60,6 +62,7 @@ import {
|
||||
getWindowByName,
|
||||
handleCertificateProxyVerification,
|
||||
handleDownloadManager,
|
||||
initSysTray,
|
||||
injectStyles,
|
||||
isSymphonyReachable,
|
||||
loadBrowserViews,
|
||||
@ -455,6 +458,12 @@ export class WindowHandler {
|
||||
mainEvents.publish('maximize');
|
||||
}
|
||||
this.mainWindow.show();
|
||||
initSysTray();
|
||||
if (isMac) {
|
||||
nativeTheme.on('updated', () => {
|
||||
presenceStatus.updateSystemTrayPresence();
|
||||
});
|
||||
}
|
||||
|
||||
// check for build expiry in case of test builds
|
||||
this.checkExpiry(this.mainWindow);
|
||||
|
@ -3,7 +3,9 @@ import {
|
||||
BrowserView,
|
||||
BrowserWindow,
|
||||
dialog,
|
||||
Menu,
|
||||
nativeImage,
|
||||
nativeTheme,
|
||||
Rectangle,
|
||||
screen,
|
||||
shell,
|
||||
@ -299,25 +301,30 @@ export const showBadgeCount = (count: number): void => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the badge count
|
||||
*
|
||||
* @param count {number}
|
||||
* Creates sys tray
|
||||
*/
|
||||
export const showSystemTrayPresence = (status: EPresenceStatus): void => {
|
||||
const tray = presenceStatusStore.getCurrentTray();
|
||||
const backgroundImage = presenceStatusStore.generateImagePath(status, 'tray');
|
||||
if (!backgroundImage) {
|
||||
return;
|
||||
}
|
||||
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');
|
||||
}
|
||||
export const initSysTray = () => {
|
||||
const defaultSysTrayIcon = 'no-status-tray.png';
|
||||
const os = isWindowsOS ? 'windows' : isMac ? 'macOS' : 'linux';
|
||||
const theme = nativeTheme.shouldUseDarkColors ? 'light' : 'dark';
|
||||
logger.info('theme: ', theme, nativeTheme.themeSource);
|
||||
const assetsPath = `renderer/assets/presence-status/${os}/${theme}`;
|
||||
const defaultSysTrayIconPath = path.join(
|
||||
__dirname,
|
||||
`../${assetsPath}/${defaultSysTrayIcon}`,
|
||||
);
|
||||
const backgroundImage = nativeImage.createFromPath(defaultSysTrayIconPath);
|
||||
const tray = new Tray(backgroundImage);
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
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',
|
||||
'BE_RIGHT_BACK' = 'BE_RIGHT_BACK',
|
||||
'OFF_WORK' = 'OFF_WORK',
|
||||
'NO_PRESENCE' = 'NO_PRESENCE',
|
||||
}
|
||||
|
||||
export interface IPresenceStatus {
|
||||
|
@ -250,5 +250,6 @@
|
||||
"AVAILABLE": "Available",
|
||||
"OUT_OF_OFFICE": "Out of office",
|
||||
"BE_RIGHT_BACK": "Be right back"
|
||||
}
|
||||
},
|
||||
"My presence": "My presence"
|
||||
}
|
||||
|
@ -250,5 +250,6 @@
|
||||
"AVAILABLE": "Available",
|
||||
"OUT_OF_OFFICE": "Out of office",
|
||||
"BE_RIGHT_BACK": "Be right back"
|
||||
}
|
||||
},
|
||||
"My presence": "My presence"
|
||||
}
|
||||
|
BIN
src/renderer/assets/presence-status/macOS/dark/brb-tray.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/renderer/assets/presence-status/macOS/dark/busy-tray.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 990 B |
BIN
src/renderer/assets/presence-status/macOS/dark/offline-tray.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/renderer/assets/presence-status/macOS/dark/online-tray.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
BIN
src/renderer/assets/presence-status/macOS/light/brb-tray.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/renderer/assets/presence-status/macOS/light/busy-tray.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 288 B |
BIN
src/renderer/assets/presence-status/macOS/light/offline-tray.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/renderer/assets/presence-status/macOS/light/online-tray.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 412 B |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 395 B After Width: | Height: | Size: 395 B |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 515 B After Width: | Height: | Size: 515 B |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 432 B After Width: | Height: | Size: 432 B |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 527 B After Width: | Height: | Size: 527 B |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 515 B After Width: | Height: | Size: 515 B |
BIN
src/renderer/assets/presence-status/windows/light/brb-tray.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
src/renderer/assets/presence-status/windows/light/brb.png
Normal file
After Width: | Height: | Size: 412 B |
BIN
src/renderer/assets/presence-status/windows/light/busy-tray.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
src/renderer/assets/presence-status/windows/light/busy.png
Normal file
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 |
BIN
src/renderer/assets/presence-status/windows/light/offline.png
Normal file
After Width: | Height: | Size: 432 B |
After Width: | Height: | Size: 3.9 KiB |
BIN
src/renderer/assets/presence-status/windows/light/online.png
Normal file
After Width: | Height: | Size: 527 B |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 515 B |