adding actions to notifications
Summary: This diff adds action buttons to the notifications. Notifications with actions can only be sent from the main process. This is why we need to send a message to the main process which then shows the notification. The action callbacks are sent back to the renderer process to handle the action and log the event. Reviewed By: passy Differential Revision: D12999886 fbshipit-source-id: b415fded3172582fad11d88cabf0cfc5b3b8d4f9
This commit is contained in:
committed by
Facebook Github Bot
parent
4954d018d0
commit
8cb715bb3a
@@ -15,7 +15,10 @@
|
|||||||
"productName": "Flipper",
|
"productName": "Flipper",
|
||||||
"artifactName": "Flipper-${os}.${ext}",
|
"artifactName": "Flipper-${os}.${ext}",
|
||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.developer-tools"
|
"category": "public.app-category.developer-tools",
|
||||||
|
"extendInfo": {
|
||||||
|
"NSUserNotificationAlertStyle": "alert"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"publisherName": "Facebook, Inc."
|
"publisherName": "Facebook, Inc."
|
||||||
|
|||||||
@@ -10,12 +10,18 @@ import type Logger from '../fb-stubs/Logger.js';
|
|||||||
import type {PluginNotification} from '../reducers/notifications';
|
import type {PluginNotification} from '../reducers/notifications';
|
||||||
import type {FlipperPlugin} from '../plugin.js';
|
import type {FlipperPlugin} from '../plugin.js';
|
||||||
|
|
||||||
|
import {ipcRenderer} from 'electron';
|
||||||
import {selectPlugin} from '../reducers/connections';
|
import {selectPlugin} from '../reducers/connections';
|
||||||
import {setActiveNotifications} from '../reducers/notifications';
|
import {
|
||||||
|
setActiveNotifications,
|
||||||
|
updatePluginBlacklist,
|
||||||
|
} from '../reducers/notifications';
|
||||||
import {textContent} from '../utils/index';
|
import {textContent} from '../utils/index';
|
||||||
import {clientPlugins} from '../plugins/index.js';
|
import {clientPlugins} from '../plugins/index.js';
|
||||||
import GK from '../fb-stubs/GK';
|
import GK from '../fb-stubs/GK';
|
||||||
|
|
||||||
|
type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action';
|
||||||
|
|
||||||
export default (store: Store, logger: Logger) => {
|
export default (store: Store, logger: Logger) => {
|
||||||
if (GK.get('flipper_disable_notifications')) {
|
if (GK.get('flipper_disable_notifications')) {
|
||||||
return;
|
return;
|
||||||
@@ -24,22 +30,67 @@ export default (store: Store, logger: Logger) => {
|
|||||||
const knownNotifications: Set<string> = new Set();
|
const knownNotifications: Set<string> = new Set();
|
||||||
const knownPluginStates: Map<string, Object> = new Map();
|
const knownPluginStates: Map<string, Object> = new Map();
|
||||||
|
|
||||||
|
ipcRenderer.on(
|
||||||
|
'notificationEvent',
|
||||||
|
(
|
||||||
|
e,
|
||||||
|
eventName: NotificationEvents,
|
||||||
|
pluginNotification: PluginNotification,
|
||||||
|
arg: null | string | number,
|
||||||
|
) => {
|
||||||
|
if (eventName === 'click' || (eventName === 'action' && arg === 0)) {
|
||||||
|
store.dispatch(
|
||||||
|
selectPlugin({
|
||||||
|
selectedPlugin: 'notifications',
|
||||||
|
selectedApp: null,
|
||||||
|
deepLinkPayload: pluginNotification.notification.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (eventName === 'action') {
|
||||||
|
if (arg === 1 && pluginNotification.notification.category) {
|
||||||
|
// Hide similar (category)
|
||||||
|
logger.track(
|
||||||
|
'usage',
|
||||||
|
'notification-hide-category',
|
||||||
|
pluginNotification,
|
||||||
|
);
|
||||||
|
} else if (arg === 2) {
|
||||||
|
// Hide plugin
|
||||||
|
logger.track('usage', 'notification-hide-plugin', pluginNotification);
|
||||||
|
|
||||||
|
const {blacklistedPlugins} = store.getState().notifications;
|
||||||
|
if (blacklistedPlugins.indexOf(pluginNotification.pluginId) === -1) {
|
||||||
|
store.dispatch(
|
||||||
|
updatePluginBlacklist([
|
||||||
|
...blacklistedPlugins,
|
||||||
|
pluginNotification.pluginId,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
store.subscribe(() => {
|
store.subscribe(() => {
|
||||||
const {notifications, pluginStates} = store.getState();
|
const {notifications, pluginStates} = store.getState();
|
||||||
|
|
||||||
|
const pluginMap: Map<string, Class<FlipperPlugin<>>> = clientPlugins.reduce(
|
||||||
|
(acc, cv) => acc.set(cv.id, cv),
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
|
||||||
Object.keys(pluginStates).forEach(key => {
|
Object.keys(pluginStates).forEach(key => {
|
||||||
if (knownPluginStates.get(key) !== pluginStates[key]) {
|
if (knownPluginStates.get(key) !== pluginStates[key]) {
|
||||||
knownPluginStates.set(key, pluginStates[key]);
|
knownPluginStates.set(key, pluginStates[key]);
|
||||||
const [client, pluginId] = key.split('#');
|
const [client, pluginId] = key.split('#');
|
||||||
const persistingPlugin: ?Class<FlipperPlugin<>> = clientPlugins.find(
|
const persistingPlugin: ?Class<FlipperPlugin<>> = pluginMap.get(
|
||||||
(p: Class<FlipperPlugin<>>) =>
|
pluginId,
|
||||||
p.id === pluginId && p.getActiveNotifications,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (persistingPlugin) {
|
if (persistingPlugin && persistingPlugin.getActiveNotifications) {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
setActiveNotifications({
|
setActiveNotifications({
|
||||||
// $FlowFixMe: Ensured getActiveNotifications is implemented in filter
|
|
||||||
notifications: persistingPlugin.getActiveNotifications(
|
notifications: persistingPlugin.getActiveNotifications(
|
||||||
pluginStates[key],
|
pluginStates[key],
|
||||||
),
|
),
|
||||||
@@ -55,21 +106,34 @@ export default (store: Store, logger: Logger) => {
|
|||||||
|
|
||||||
activeNotifications.forEach((n: PluginNotification) => {
|
activeNotifications.forEach((n: PluginNotification) => {
|
||||||
if (
|
if (
|
||||||
|
store.getState().connections.selectedPlugin !== 'notifications' &&
|
||||||
!knownNotifications.has(n.notification.id) &&
|
!knownNotifications.has(n.notification.id) &&
|
||||||
blacklistedPlugins.indexOf(n.pluginId) === -1
|
blacklistedPlugins.indexOf(n.pluginId) === -1
|
||||||
) {
|
) {
|
||||||
const notification = new window.Notification(n.notification.title, {
|
ipcRenderer.send('sendNotification', {
|
||||||
body: textContent(n.notification.message),
|
payload: {
|
||||||
|
title: n.notification.title,
|
||||||
|
body: textContent(n.notification.message),
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
text: 'Show',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
text: 'Hide similar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
text: `Hide all ${pluginMap.get(n.pluginId)?.title || ''}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
closeButtonText: 'Hide',
|
||||||
|
},
|
||||||
|
closeAfter: 10000,
|
||||||
|
pluginNotification: n,
|
||||||
});
|
});
|
||||||
logger.track('usage', 'native-notification', n.notification);
|
logger.track('usage', 'native-notification', n.notification);
|
||||||
notification.onclick = () =>
|
|
||||||
store.dispatch(
|
|
||||||
selectPlugin({
|
|
||||||
selectedPlugin: 'notifications',
|
|
||||||
selectedApp: null,
|
|
||||||
deepLinkPayload: n.notification.id,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
knownNotifications.add(n.notification.id);
|
knownNotifications.add(n.notification.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,13 +8,14 @@
|
|||||||
const [s, ns] = process.hrtime();
|
const [s, ns] = process.hrtime();
|
||||||
let launchStartTime = s * 1e3 + ns / 1e6;
|
let launchStartTime = s * 1e3 + ns / 1e6;
|
||||||
|
|
||||||
const {app, BrowserWindow, ipcMain} = require('electron');
|
const {app, BrowserWindow, ipcMain, Notification} = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const {exec} = require('child_process');
|
const {exec} = require('child_process');
|
||||||
const compilePlugins = require('./compilePlugins.js');
|
const compilePlugins = require('./compilePlugins.js');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|
||||||
// disable electron security warnings: https://github.com/electron/electron/blob/master/docs/tutorial/security.md#security-native-capabilities-and-your-responsibility
|
// disable electron security warnings: https://github.com/electron/electron/blob/master/docs/tutorial/security.md#security-native-capabilities-and-your-responsibility
|
||||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
||||||
|
|
||||||
@@ -168,6 +169,37 @@ ipcMain.on('getLaunchTime', event => {
|
|||||||
launchStartTime = null;
|
launchStartTime = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.on(
|
||||||
|
'sendNotification',
|
||||||
|
(e, {payload, pluginNotification, closeAfter}) => {
|
||||||
|
// notifications can only be sent when app is ready
|
||||||
|
if (appReady) {
|
||||||
|
const n = new Notification(payload);
|
||||||
|
|
||||||
|
// Forwarding notification events to renderer process
|
||||||
|
// https://electronjs.org/docs/api/notification#instance-events
|
||||||
|
['show', 'click', 'close', 'reply', 'action'].forEach(eventName => {
|
||||||
|
n.on(eventName, (event, ...args) => {
|
||||||
|
e.sender.send(
|
||||||
|
'notificationEvent',
|
||||||
|
eventName,
|
||||||
|
pluginNotification,
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
n.show();
|
||||||
|
|
||||||
|
if (closeAfter) {
|
||||||
|
setTimeout(() => {
|
||||||
|
n.close();
|
||||||
|
}, closeAfter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Define custom protocol handler. Deep linking works on packaged versions of the application!
|
// Define custom protocol handler. Deep linking works on packaged versions of the application!
|
||||||
app.setAsDefaultProtocolClient('flipper');
|
app.setAsDefaultProtocolClient('flipper');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user