diff --git a/ElectronNET.API/Entities/DisplayBalloonOptions.cs b/ElectronNET.API/Entities/DisplayBalloonOptions.cs new file mode 100644 index 0000000..da59480 --- /dev/null +++ b/ElectronNET.API/Entities/DisplayBalloonOptions.cs @@ -0,0 +1,9 @@ +namespace ElectronNET.API +{ + public class DisplayBalloonOptions + { + public string Icon { get; set; } + public string Title { get; set; } + public string Content { get; set; } + } +} \ No newline at end of file diff --git a/ElectronNET.API/Entities/HighlightMode.cs b/ElectronNET.API/Entities/HighlightMode.cs new file mode 100644 index 0000000..19dc276 --- /dev/null +++ b/ElectronNET.API/Entities/HighlightMode.cs @@ -0,0 +1,20 @@ +namespace ElectronNET.API.Entities +{ + public enum HighlightMode + { + /// + /// Highlight the tray icon when it is clicked and also when its context menu is open. This is the default. + /// + selection, + + /// + /// Always highlight the tray icon. + /// + always, + + /// + /// Never highlight the tray icon. + /// + never + } +} \ No newline at end of file diff --git a/ElectronNET.API/Entities/NotificationAction.cs b/ElectronNET.API/Entities/NotificationAction.cs new file mode 100644 index 0000000..25bee11 --- /dev/null +++ b/ElectronNET.API/Entities/NotificationAction.cs @@ -0,0 +1,15 @@ +namespace ElectronNET.API.Entities +{ + public class NotificationAction + { + /// + /// The label for the given action. + /// + public string Text { get; set; } + + /// + /// The type of action, can be button. + /// + public string Type { get; set; } + } +} \ No newline at end of file diff --git a/ElectronNET.API/Entities/NotificationOptions.cs b/ElectronNET.API/Entities/NotificationOptions.cs index efa127f..f2fc91c 100644 --- a/ElectronNET.API/Entities/NotificationOptions.cs +++ b/ElectronNET.API/Entities/NotificationOptions.cs @@ -1,4 +1,7 @@ -namespace ElectronNET.API.Entities +using Newtonsoft.Json; +using System; + +namespace ElectronNET.API.Entities { public class NotificationOptions { @@ -44,6 +47,63 @@ /// public string Sound { get; set; } + /// + /// Actions to add to the notification. Please read the available actions and + /// limitations in the NotificationAction documentation + /// + public NotificationAction Actions { get; set; } + + /// + /// Emitted when the notification is shown to the user, + /// note this could be fired multiple times as a notification + /// can be shown multiple times through the Show() method. + /// + [JsonIgnore] + public Action OnShow { get; set; } + + [JsonProperty] + internal string ShowID { get; set; } + + /// + /// Emitted when the notification is clicked by the user. + /// + [JsonIgnore] + public Action OnClick { get; set; } + + [JsonProperty] + internal string ClickID { get; set; } + + /// + /// Emitted when the notification is closed by manual intervention from the user. + /// + /// This event is not guarunteed to be emitted in all cases where the notification is closed. + /// + [JsonIgnore] + public Action OnClose { get; set; } + + [JsonProperty] + internal string CloseID { get; set; } + + /// + /// macOS only: Emitted when the user clicks the “Reply” button on a notification with hasReply: true. + /// + /// The string the user entered into the inline reply field + /// + [JsonIgnore] + public Action OnReply { get; set; } + + [JsonProperty] + internal string ReplyID { get; set; } + + /// + /// macOS only - The index of the action that was activated + /// + [JsonIgnore] + public Action OnAction { get; set; } + + [JsonProperty] + internal string ActionID { get; set; } + public NotificationOptions(string title, string body) { Title = title; diff --git a/ElectronNET.API/Entities/TrayClickEventArgs.cs b/ElectronNET.API/Entities/TrayClickEventArgs.cs new file mode 100644 index 0000000..5e7b09b --- /dev/null +++ b/ElectronNET.API/Entities/TrayClickEventArgs.cs @@ -0,0 +1,10 @@ +namespace ElectronNET.API +{ + public class TrayClickEventArgs + { + public bool AltKey { get; set; } + public bool ShiftKey { get; set; } + public bool CtrlKey { get; set; } + public bool MetaKey { get; set; } + } +} \ No newline at end of file diff --git a/ElectronNET.API/Notification.cs b/ElectronNET.API/Notification.cs index ad396d0..b7fd081 100644 --- a/ElectronNET.API/Notification.cs +++ b/ElectronNET.API/Notification.cs @@ -2,6 +2,10 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace ElectronNET.API { @@ -24,11 +28,105 @@ namespace ElectronNET.API } } + private static List _notificationOptions = new List(); + + /// + /// Create OS desktop notifications + /// + /// public void Show(NotificationOptions notificationOptions) { + GenerateIDsForDefinedActions(notificationOptions); + BridgeConnector.Socket.Emit("createNotification", JObject.FromObject(notificationOptions, _jsonSerializer)); } + private static void GenerateIDsForDefinedActions(NotificationOptions notificationOptions) + { + bool isActionDefined = false; + + if (notificationOptions.OnShow != null) + { + notificationOptions.ShowID = Guid.NewGuid().ToString(); + isActionDefined = true; + + BridgeConnector.Socket.Off("NotificationEventShow"); + BridgeConnector.Socket.On("NotificationEventShow", (id) => { + _notificationOptions.Single(x => x.ShowID == id.ToString()).OnShow(); + }); + } + + if (notificationOptions.OnClick != null) + { + notificationOptions.ClickID = Guid.NewGuid().ToString(); + isActionDefined = true; + + BridgeConnector.Socket.Off("NotificationEventClick"); + BridgeConnector.Socket.On("NotificationEventClick", (id) => { + _notificationOptions.Single(x => x.ClickID == id.ToString()).OnClick(); + }); + } + + if (notificationOptions.OnClose != null) + { + notificationOptions.CloseID = Guid.NewGuid().ToString(); + isActionDefined = true; + + BridgeConnector.Socket.Off("NotificationEventClose"); + BridgeConnector.Socket.On("NotificationEventClose", (id) => { + _notificationOptions.Single(x => x.CloseID == id.ToString()).OnClose(); + }); + } + + if (notificationOptions.OnReply != null) + { + notificationOptions.ReplyID = Guid.NewGuid().ToString(); + isActionDefined = true; + + BridgeConnector.Socket.Off("NotificationEventReply"); + BridgeConnector.Socket.On("NotificationEventReply", (args) => { + var arguments = ((JArray)args).ToObject(); + _notificationOptions.Single(x => x.ReplyID == arguments[0].ToString()).OnReply(arguments[1].ToString()); + }); + } + + if (notificationOptions.OnAction != null) + { + notificationOptions.ActionID = Guid.NewGuid().ToString(); + isActionDefined = true; + + BridgeConnector.Socket.Off("NotificationEventAction"); + BridgeConnector.Socket.On("NotificationEventAction", (args) => { + var arguments = ((JArray)args).ToObject(); + _notificationOptions.Single(x => x.ReplyID == arguments[0].ToString()).OnAction(arguments[1].ToString()); + }); + } + + if (isActionDefined) + { + _notificationOptions.Add(notificationOptions); + } + } + + /// + /// Whether or not desktop notifications are supported on the current system. + /// + /// + public Task IsSupportedAsync() + { + var taskCompletionSource = new TaskCompletionSource(); + + BridgeConnector.Socket.On("notificationIsSupportedComplete", (isSupported) => + { + BridgeConnector.Socket.Off("notificationIsSupportedComplete"); + taskCompletionSource.SetResult((bool)isSupported); + }); + + BridgeConnector.Socket.Emit("notificationIsSupported"); + + return taskCompletionSource.Task; + } + private JsonSerializer _jsonSerializer = new JsonSerializer() { ContractResolver = new CamelCasePropertyNamesContractResolver(), diff --git a/ElectronNET.API/Tray.cs b/ElectronNET.API/Tray.cs index bbb23dc..aa42268 100644 --- a/ElectronNET.API/Tray.cs +++ b/ElectronNET.API/Tray.cs @@ -3,12 +3,182 @@ using ElectronNET.API.Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; +using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace ElectronNET.API { public sealed class Tray { + /// + /// Emitted when the tray icon is clicked. + /// + public event Action OnClick + { + add + { + if (_click == null) + { + BridgeConnector.Socket.On("tray-click-event", (result) => + { + var args = ((JArray)result).ToObject(); + var trayClickEventArgs = ((JObject)args[0]).ToObject(); + var bounds = ((JObject)args[1]).ToObject(); + _click(trayClickEventArgs, bounds); + }); + + BridgeConnector.Socket.Emit("register-tray-click"); + } + _click += value; + } + remove + { + _click -= value; + } + } + + private event Action _click; + + /// + /// macOS, Windows: Emitted when the tray icon is right clicked. + /// + public event Action OnRightClick + { + add + { + if (_rightClick == null) + { + BridgeConnector.Socket.On("tray-right-click-event", (result) => + { + var args = ((JArray)result).ToObject(); + var trayClickEventArgs = ((JObject)args[0]).ToObject(); + var bounds = ((JObject)args[1]).ToObject(); + _rightClick(trayClickEventArgs, bounds); + }); + + BridgeConnector.Socket.Emit("register-tray-right-click"); + } + _rightClick += value; + } + remove + { + _rightClick -= value; + } + } + + private event Action _rightClick; + + /// + /// macOS, Windows: Emitted when the tray icon is double clicked. + /// + public event Action OnDoubleClick + { + add + { + if (_doubleClick == null) + { + BridgeConnector.Socket.On("tray-double-click-event", (result) => + { + var args = ((JArray)result).ToObject(); + var trayClickEventArgs = ((JObject)args[0]).ToObject(); + var bounds = ((JObject)args[1]).ToObject(); + _doubleClick(trayClickEventArgs, bounds); + }); + + BridgeConnector.Socket.Emit("register-tray-double-click"); + } + _doubleClick += value; + } + remove + { + _doubleClick -= value; + } + } + + private event Action _doubleClick; + + /// + /// Windows: Emitted when the tray balloon shows. + /// + public event Action OnBalloonShow + { + add + { + if (_balloonShow == null) + { + BridgeConnector.Socket.On("tray-balloon-show-event", () => + { + _balloonShow(); + }); + + BridgeConnector.Socket.Emit("register-tray-balloon-show"); + } + _balloonShow += value; + } + remove + { + _balloonShow -= value; + } + } + + private event Action _balloonShow; + + /// + /// Windows: Emitted when the tray balloon is clicked. + /// + public event Action OnBalloonClick + { + add + { + if (_balloonClick == null) + { + BridgeConnector.Socket.On("tray-balloon-click-event", () => + { + _balloonClick(); + }); + + BridgeConnector.Socket.Emit("register-tray-balloon-click"); + } + _balloonClick += value; + } + remove + { + _balloonClick -= value; + } + } + + private event Action _balloonClick; + + /// + /// Windows: Emitted when the tray balloon is closed + /// because of timeout or user manually closes it. + /// + public event Action OnBalloonClosed + { + add + { + if (_balloonClosed == null) + { + BridgeConnector.Socket.On("tray-balloon-closed-event", () => + { + _balloonClosed(); + }); + + BridgeConnector.Socket.Emit("register-tray-balloon-closed"); + } + _balloonClosed += value; + } + remove + { + _balloonClosed -= value; + } + } + + private event Action _balloonClosed; + + // TODO: Implement macOS Events + private static Tray _tray; internal Tray() { } @@ -42,6 +212,91 @@ namespace ElectronNET.API }); } + /// + /// Destroys the tray icon immediately. + /// + public void Destroy() + { + BridgeConnector.Socket.Emit("tray-destroy"); + } + + /// + /// Sets the image associated with this tray icon. + /// + /// + public void SetImage(string image) + { + BridgeConnector.Socket.Emit("tray-setImage", image); + } + + /// + /// Sets the image associated with this tray icon when pressed on macOS. + /// + /// + public void SetPressedImage(string image) + { + BridgeConnector.Socket.Emit("tray-setPressedImage", image); + } + + /// + /// Sets the hover text for this tray icon. + /// + /// + public void SetToolTip(string toolTip) + { + BridgeConnector.Socket.Emit("tray-setToolTip", toolTip); + } + + /// + /// macOS: Sets the title displayed aside of the tray icon in the status bar. + /// + /// + public void SetTitle(string title) + { + BridgeConnector.Socket.Emit("tray-setTitle", title); + } + + /// + /// macOS: Sets when the tray’s icon background becomes highlighted (in blue). + /// + /// Note: You can use highlightMode with a BrowserWindow by toggling between + /// 'never' and 'always' modes when the window visibility changes. + /// + /// + public void SetHighlightMode(HighlightMode highlightMode) + { + BridgeConnector.Socket.Emit("tray-setHighlightMode", highlightMode.ToString()); + } + + /// + /// Windows: Displays a tray balloon. + /// + /// + public void DisplayBalloon(DisplayBalloonOptions options) + { + BridgeConnector.Socket.Emit("tray-displayBalloon", JObject.FromObject(options, _jsonSerializer)); + } + + /// + /// Whether the tray icon is destroyed. + /// + /// + public Task IsDestroyedAsync() + { + var taskCompletionSource = new TaskCompletionSource(); + + BridgeConnector.Socket.On("tray-isDestroyedCompleted", (isDestroyed) => + { + BridgeConnector.Socket.Off("tray-isDestroyedCompleted"); + + taskCompletionSource.SetResult((bool)isDestroyed); + }); + + BridgeConnector.Socket.Emit("tray-isDestroyed"); + + return taskCompletionSource.Task; + } + private JsonSerializer _jsonSerializer = new JsonSerializer() { ContractResolver = new CamelCasePropertyNamesContractResolver(), diff --git a/ElectronNET.Host/api/notification.js b/ElectronNET.Host/api/notification.js index 8ce83a7..020a153 100644 --- a/ElectronNET.Host/api/notification.js +++ b/ElectronNET.Host/api/notification.js @@ -1,10 +1,49 @@ "use strict"; exports.__esModule = true; var electron_1 = require("electron"); +var notifications = []; module.exports = function (socket) { socket.on('createNotification', function (options) { var notification = new electron_1.Notification(options); + var haveEvent = false; + if (options.showID) { + haveEvent = true; + notification.on('show', function () { + socket.emit('NotificationEventShow', options.showID); + }); + } + if (options.clickID) { + haveEvent = true; + notification.on('click', function () { + socket.emit('NotificationEventClick', options.clickID); + }); + } + if (options.closeID) { + haveEvent = true; + notification.on('close', function () { + socket.emit('NotificationEventClose', options.closeID); + }); + } + if (options.replyID) { + haveEvent = true; + notification.on('reply', function (event, value) { + socket.emit('NotificationEventReply', [options.replyID, value]); + }); + } + if (options.actionID) { + haveEvent = true; + notification.on('action', function (event, value) { + socket.emit('NotificationEventAction', [options.actionID, value]); + }); + } + if (haveEvent) { + notifications.push(notification); + } notification.show(); }); + socket.on('notificationIsSupported', function (options) { + var isSupported = electron_1.Notification.isSupported; + socket.emit('notificationIsSupportedComplete', isSupported); + }); }; //# sourceMappingURL=notification.js.map \ No newline at end of file diff --git a/ElectronNET.Host/api/notification.js.map b/ElectronNET.Host/api/notification.js.map index b7d3ab1..85c75b9 100644 --- a/ElectronNET.Host/api/notification.js.map +++ b/ElectronNET.Host/api/notification.js.map @@ -1 +1 @@ -{"version":3,"file":"notification.js","sourceRoot":"","sources":["notification.ts"],"names":[],"mappings":";;AAAA,qCAAwC;AAExC,MAAM,CAAC,OAAO,GAAG,UAAC,MAAuB;IACrC,MAAM,CAAC,EAAE,CAAC,oBAAoB,EAAE,UAAC,OAAO;QACpC,IAAM,YAAY,GAAG,IAAI,uBAAY,CAAC,OAAO,CAAC,CAAC;QAC/C,YAAY,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;AACP,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"notification.js","sourceRoot":"","sources":["notification.ts"],"names":[],"mappings":";;AAAA,qCAAwC;AACxC,IAAM,aAAa,GAA4B,EAAE,CAAC;AAElD,MAAM,CAAC,OAAO,GAAG,UAAC,MAAuB;IACrC,MAAM,CAAC,EAAE,CAAC,oBAAoB,EAAE,UAAC,OAAO;QACpC,IAAM,YAAY,GAAG,IAAI,uBAAY,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,SAAS,GAAG,KAAK,CAAC;QAEtB,EAAE,CAAA,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAChB,SAAS,GAAG,IAAI,CAAC;YACjB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE;gBACpB,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YACzD,CAAC,CAAC,CAAC;QACP,CAAC;QAED,EAAE,CAAA,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;YACjB,SAAS,GAAG,IAAI,CAAC;YACjB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;gBACrB,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAC3D,CAAC,CAAC,CAAC;QACP,CAAC;QAED,EAAE,CAAA,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;YACjB,SAAS,GAAG,IAAI,CAAC;YACjB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;gBACrB,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAC3D,CAAC,CAAC,CAAC;QACP,CAAC;QAED,EAAE,CAAA,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;YACjB,SAAS,GAAG,IAAI,CAAC;YACjB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,UAAC,KAAK,EAAE,KAAK;gBAClC,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;YACpE,CAAC,CAAC,CAAC;QACP,CAAC;QAED,EAAE,CAAA,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;YAClB,SAAS,GAAG,IAAI,CAAC;YACjB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAC,KAAK,EAAE,KAAK;gBACnC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;YACtE,CAAC,CAAC,CAAC;QACP,CAAC;QAED,EAAE,CAAA,CAAC,SAAS,CAAC,CAAC,CAAC;YACX,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACrC,CAAC;QAED,YAAY,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,yBAAyB,EAAE,UAAC,OAAO;QACzC,IAAM,WAAW,GAAG,uBAAY,CAAC,WAAW,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE,WAAW,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACP,CAAC,CAAA"} \ No newline at end of file diff --git a/ElectronNET.Host/api/notification.ts b/ElectronNET.Host/api/notification.ts index 2264373..b388a4e 100644 --- a/ElectronNET.Host/api/notification.ts +++ b/ElectronNET.Host/api/notification.ts @@ -1,8 +1,55 @@ import { Notification } from "electron"; +const notifications: Electron.Notification[] = []; module.exports = (socket: SocketIO.Server) => { socket.on('createNotification', (options) => { const notification = new Notification(options); + let haveEvent = false; + + if(options.showID) { + haveEvent = true; + notification.on('show', () => { + socket.emit('NotificationEventShow', options.showID); + }); + } + + if(options.clickID) { + haveEvent = true; + notification.on('click', () => { + socket.emit('NotificationEventClick', options.clickID); + }); + } + + if(options.closeID) { + haveEvent = true; + notification.on('close', () => { + socket.emit('NotificationEventClose', options.closeID); + }); + } + + if(options.replyID) { + haveEvent = true; + notification.on('reply', (event, value) => { + socket.emit('NotificationEventReply', [options.replyID, value]); + }); + } + + if(options.actionID) { + haveEvent = true; + notification.on('action', (event, value) => { + socket.emit('NotificationEventAction', [options.actionID, value]); + }); + } + + if(haveEvent) { + notifications.push(notification); + } + notification.show(); }); + + socket.on('notificationIsSupported', (options) => { + const isSupported = Notification.isSupported; + socket.emit('notificationIsSupportedComplete', isSupported); + }); } \ No newline at end of file diff --git a/ElectronNET.Host/api/tray.js b/ElectronNET.Host/api/tray.js index 1a9a53f..c7ffe28 100644 --- a/ElectronNET.Host/api/tray.js +++ b/ElectronNET.Host/api/tray.js @@ -4,6 +4,48 @@ var electron_1 = require("electron"); var path = require('path'); var tray; module.exports = function (socket) { + socket.on('register-tray-click', function () { + if (tray) { + tray.on('click', function (event, bounds) { + socket.emit('tray-click-event', [event.__proto__, bounds]); + }); + } + }); + socket.on('register-tray-right-click', function () { + if (tray) { + tray.on('right-click', function (event, bounds) { + socket.emit('tray-right-click-event', [event.__proto__, bounds]); + }); + } + }); + socket.on('register-tray-double-click', function () { + if (tray) { + tray.on('double-click', function (event, bounds) { + socket.emit('tray-double-click-event', [event.__proto__, bounds]); + }); + } + }); + socket.on('register-tray-balloon-show', function () { + if (tray) { + tray.on('balloon-show', function () { + socket.emit('tray-balloon-show-event'); + }); + } + }); + socket.on('register-tray-balloon-click', function () { + if (tray) { + tray.on('balloon-click', function () { + socket.emit('tray-balloon-click-event'); + }); + } + }); + socket.on('register-tray-balloon-closed', function () { + if (tray) { + tray.on('balloon-closed', function () { + socket.emit('tray-balloon-closed-event'); + }); + } + }); socket.on('create-tray', function (image, menuItems) { var menu = electron_1.Menu.buildFromTemplate(menuItems); addMenuItemClickConnector(menu.items, function (id) { @@ -13,6 +55,48 @@ module.exports = function (socket) { tray = new electron_1.Tray(imagePath); tray.setContextMenu(menu); }); + socket.on('tray-destroy', function () { + if (tray) { + tray.destroy(); + } + }); + socket.on('tray-setImage', function (image) { + if (tray) { + tray.setImage(image); + } + }); + socket.on('tray-setPressedImage', function (image) { + if (tray) { + var img = electron_1.nativeImage.createFromPath(image); + tray.setPressedImage(img); + } + }); + socket.on('tray-setToolTip', function (toolTip) { + if (tray) { + tray.setToolTip(toolTip); + } + }); + socket.on('tray-setTitle', function (title) { + if (tray) { + tray.setTitle(title); + } + }); + socket.on('tray-setHighlightMode', function (mode) { + if (tray) { + tray.setHighlightMode(mode); + } + }); + socket.on('tray-displayBalloon', function (options) { + if (tray) { + tray.displayBalloon(options); + } + }); + socket.on('tray-isDestroyed', function () { + if (tray) { + var isDestroyed = tray.isDestroyed(); + socket.emit('tray-isDestroyedCompleted', isDestroyed); + } + }); function addMenuItemClickConnector(menuItems, callback) { menuItems.forEach(function (item) { if (item.submenu && item.submenu.items.length > 0) { diff --git a/ElectronNET.Host/api/tray.js.map b/ElectronNET.Host/api/tray.js.map index e28487a..571c594 100644 --- a/ElectronNET.Host/api/tray.js.map +++ b/ElectronNET.Host/api/tray.js.map @@ -1 +1 @@ -{"version":3,"file":"tray.js","sourceRoot":"","sources":["tray.ts"],"names":[],"mappings":";;AAAA,qCAAsC;AACtC,IAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAC7B,IAAI,IAAI,CAAC;AAET,MAAM,CAAC,OAAO,GAAG,UAAC,MAAuB;IACrC,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,UAAC,KAAK,EAAE,SAAS;QACtC,IAAM,IAAI,GAAG,eAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAE/C,yBAAyB,CAAC,IAAI,CAAC,KAAK,EAAE,UAAC,EAAE;YACrC,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,IAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAExE,IAAI,GAAG,IAAI,eAAI,CAAC,SAAS,CAAC,CAAC;QAC3B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,mCAAmC,SAAS,EAAE,QAAQ;QAClD,SAAS,CAAC,OAAO,CAAC,UAAC,IAAI;YACnB,EAAE,CAAA,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC/C,yBAAyB,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YAC5D,CAAC;YAED,EAAE,CAAA,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACzB,IAAI,CAAC,KAAK,GAAG,cAAQ,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;AACL,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"tray.js","sourceRoot":"","sources":["tray.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/ElectronNET.Host/api/tray.ts b/ElectronNET.Host/api/tray.ts index 01b8150..a6511ab 100644 --- a/ElectronNET.Host/api/tray.ts +++ b/ElectronNET.Host/api/tray.ts @@ -1,8 +1,56 @@ -import { Menu, Tray } from "electron"; +import { Menu, Tray, nativeImage } from "electron"; const path = require('path'); -let tray; +let tray: Electron.Tray; module.exports = (socket: SocketIO.Server) => { + socket.on('register-tray-click', () => { + if (tray) { + tray.on('click', (event, bounds) => { + socket.emit('tray-click-event', [(event).__proto__, bounds]); + }); + } + }); + + socket.on('register-tray-right-click', () => { + if (tray) { + tray.on('right-click', (event, bounds) => { + socket.emit('tray-right-click-event', [(event).__proto__, bounds]); + }); + } + }); + + socket.on('register-tray-double-click', () => { + if (tray) { + tray.on('double-click', (event, bounds) => { + socket.emit('tray-double-click-event', [(event).__proto__, bounds]); + }); + } + }); + + socket.on('register-tray-balloon-show', () => { + if (tray) { + tray.on('balloon-show', () => { + socket.emit('tray-balloon-show-event'); + }); + } + }); + + socket.on('register-tray-balloon-click', () => { + if (tray) { + tray.on('balloon-click', () => { + socket.emit('tray-balloon-click-event'); + }); + } + }); + + socket.on('register-tray-balloon-closed', () => { + if (tray) { + tray.on('balloon-closed', () => { + socket.emit('tray-balloon-closed-event'); + }); + } + }); + socket.on('create-tray', (image, menuItems) => { const menu = Menu.buildFromTemplate(menuItems); @@ -16,13 +64,63 @@ module.exports = (socket: SocketIO.Server) => { tray.setContextMenu(menu); }); + socket.on('tray-destroy', () => { + if (tray) { + tray.destroy(); + } + }); + + socket.on('tray-setImage', (image) => { + if (tray) { + tray.setImage(image); + } + }); + + socket.on('tray-setPressedImage', (image) => { + if (tray) { + let img = nativeImage.createFromPath(image); + tray.setPressedImage(img); + } + }); + + socket.on('tray-setToolTip', (toolTip) => { + if (tray) { + tray.setToolTip(toolTip); + } + }); + + socket.on('tray-setTitle', (title) => { + if (tray) { + tray.setTitle(title); + } + }); + + socket.on('tray-setHighlightMode', (mode) => { + if (tray) { + tray.setHighlightMode(mode); + } + }); + + socket.on('tray-displayBalloon', (options) => { + if (tray) { + tray.displayBalloon(options); + } + }); + + socket.on('tray-isDestroyed', () => { + if (tray) { + let isDestroyed = tray.isDestroyed(); + socket.emit('tray-isDestroyedCompleted', isDestroyed); + } + }); + function addMenuItemClickConnector(menuItems, callback) { menuItems.forEach((item) => { - if(item.submenu && item.submenu.items.length > 0) { + if (item.submenu && item.submenu.items.length > 0) { addMenuItemClickConnector(item.submenu.items, callback); } - - if("id" in item && item.id) { + + if ("id" in item && item.id) { item.click = () => { callback(item.id); }; } }); diff --git a/ElectronNET.WebApp/Controllers/HomeController.cs b/ElectronNET.WebApp/Controllers/HomeController.cs index 12792a1..e939029 100644 --- a/ElectronNET.WebApp/Controllers/HomeController.cs +++ b/ElectronNET.WebApp/Controllers/HomeController.cs @@ -11,7 +11,13 @@ namespace ElectronNET.WebApp.Controllers { Electron.IpcMain.On("SayHello", async (args) => { - Electron.Notification.Show(new NotificationOptions("Hallo Robert", "Nachricht von ASP.NET Core App")); + Electron.Notification.Show(new NotificationOptions("Hallo Robert", "Nachricht von ASP.NET Core App") { + OnClick = async () => { await Electron.Dialog.ShowMessageBoxAsync(new MessageBoxOptions("Notification clicked")); }, + OnShow = async () => { await Electron.Dialog.ShowMessageBoxAsync(new MessageBoxOptions("Notification show")); }, + OnClose = async () => { await Electron.Dialog.ShowMessageBoxAsync(new MessageBoxOptions("Notification closed")); }, + OnAction = async (value) => { await Electron.Dialog.ShowMessageBoxAsync(new MessageBoxOptions("Notification Action")); }, + OnReply = async (value) => { await Electron.Dialog.ShowMessageBoxAsync(new MessageBoxOptions("Notification Reply")); } + }); Electron.IpcMain.Send(Electron.WindowManager.BrowserWindows.First(), "Goodbye", "Elephant!"); var currentBrowserWindow = Electron.WindowManager.BrowserWindows.First(); @@ -28,6 +34,8 @@ namespace ElectronNET.WebApp.Controllers Electron.IpcMain.On("GetPath", async (args) => { + Electron.Notification.Show(new NotificationOptions("test", "test2")); + var currentBrowserWindow = Electron.WindowManager.BrowserWindows.First(); string pathName = await Electron.App.GetPathAsync(PathName.pictures); diff --git a/ElectronNET.WebApp/Startup.cs b/ElectronNET.WebApp/Startup.cs index 2df074f..d87adc2 100644 --- a/ElectronNET.WebApp/Startup.cs +++ b/ElectronNET.WebApp/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System; using System.Linq; using System.Threading; diff --git a/README.md b/README.md index 2ba8976..a2729b7 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,42 @@ Master: [![Build status](https://ci.appveyor.com/api/projects/status/u710d6hman5 Build cross platform desktop apps with .NET Core and ASP.NET NET Core. +# Usage: + +* To communicate with the "native" (sort of native...) Electron API include the [ElectronNET.API NuGet package](https://www.nuget.org/packages/ElectronNET.API/) in your ASP.NET Core app. + +* For the tooling you will need your dotnet-electronize package [ElectronNET.CLI NuGet package](https://www.nuget.org/packages/ElectronNET.CLI/). This package __must__ be referenced in the .csproj like this: + +``` + + + +``` + +* Make sure you have node.js and on OSX/Ubuntu the electron-packager installed (via "sudo npm install electron-packager --global") +* In your ASP.NET Core folder run: + +``` + dotnet electronize init +``` + +* Now a electronnet.manifest.json should appear in your ASP.NET Core project +* Now run the following: + +``` + dotnet electronize build +``` + +* In your default setting we just build the application for the OS you are running (Windows builds Windows, OSX builds OSX etc.), but this can be changed with: + +``` + dotnet electronize build win + dotnet electronize build osx + dotnet electronize build linux +``` + +* The end result should be a electron app under your /bin/desktop folder + ## Dev Notes: Currently there are two main projects and a "WebApp" that serves as a demo playground. @@ -13,12 +49,7 @@ Currently there are two main projects and a "WebApp" that serves as a demo playg Both projects create their own nuget package. The resulting nuget packages are saved under "/artifacts". -__Currently__ the packages are just build with version 1.0.0. -If you change something you need to clear the nuget cache under this directory (because the version number doesn't change): - - %userprofile%\.nuget\packages - -The solution contains a NuGet.config which points to the artifacts directory, so we can just use the produced NuGet packages without uploading to NuGet.org. +__Currently__ the packages are just build with version 1.0.0 on your machine. NuGet might pick the wrong version from its own cache, so please read the dev notes if you want to develop! __ElectronNET.WebApp:__ @@ -28,11 +59,7 @@ The WebApp now has referenced the API NuGet package and via the .csproj referenc -## Dev Notes: Usage -Navigate to the WebApp folder and type this in a command prompt: - - dotnet electronize # Dev Notes: Dev Workflow @@ -41,5 +68,5 @@ Navigate to the WebApp folder and type this in a command prompt: * Change something in the CLI project * rebuild the project (a nuget package should now appear in the /artifacts directory) * make sure there is no ElectronNET.CLI package in your %userprofile%\.nuget\packages cache with the same version -* execute dotnet restore in the WebApp +* execute dotnet restore in the WebApp * execute dotnet electronize