diff --git a/ElectronNET.API/App.cs b/ElectronNET.API/App.cs index 715207c..4d4c86a 100644 --- a/ElectronNET.API/App.cs +++ b/ElectronNET.API/App.cs @@ -258,9 +258,9 @@ namespace ElectronNET.API private event Action _accessibilitySupportChanged; - private App() { } + internal App() { } - public static App Instance + internal static App Instance { get { @@ -280,19 +280,6 @@ namespace ElectronNET.API ContractResolver = new CamelCasePropertyNamesContractResolver() }; - // TODO: Auslagern in eigenes Window-Management - public void OpenWindow(int width, int height, bool show) - { - var browserWindowOptions = new BrowserWindowOptions() - { - Height = height, - Width = width, - Show = show - }; - - BridgeConnector.Socket.Emit("createBrowserWindow", JObject.FromObject(browserWindowOptions, _jsonSerializer)); - } - // TODO: Auslagern in eigenes Notification-API public void CreateNotification(NotificationOptions notificationOptions) { diff --git a/ElectronNET.API/BrowserWindow.cs b/ElectronNET.API/BrowserWindow.cs new file mode 100644 index 0000000..55fdaa8 --- /dev/null +++ b/ElectronNET.API/BrowserWindow.cs @@ -0,0 +1,16 @@ +namespace ElectronNET.API +{ + public class BrowserWindow + { + public int Id { get; private set; } + + internal BrowserWindow(int id) { + Id = id; + } + + public void Minimize() + { + BridgeConnector.Socket.Emit("browserWindow-minimize", Id); + } + } +} diff --git a/ElectronNET.API/Electron.cs b/ElectronNET.API/Electron.cs index f7b854d..0a551b0 100644 --- a/ElectronNET.API/Electron.cs +++ b/ElectronNET.API/Electron.cs @@ -11,5 +11,15 @@ /// Control your application's event lifecycle. /// public static App App { get { return App.Instance; } } + + /// + /// Control your windows. + /// + public static WindowManager WindowManager { get { return WindowManager.Instance; } } + + /// + /// Create native application menus and context menus. + /// + public static Menu Menu { get { return Menu.Instance; } } } } diff --git a/ElectronNET.API/Entities/BrowserWindowConstructorOptions.cs b/ElectronNET.API/Entities/BrowserWindowConstructorOptions.cs new file mode 100644 index 0000000..2e6f805 --- /dev/null +++ b/ElectronNET.API/Entities/BrowserWindowConstructorOptions.cs @@ -0,0 +1,244 @@ +namespace ElectronNET.API.Entities +{ + public class BrowserWindowConstructorOptions + { + /// + /// Window's width in pixels. Default is 800. + /// + public int Width { get; set; } + + /// + /// Window's height in pixels. Default is 600. + /// + public int Height { get; set; } + + /// + /// ( if y is used) Window's left offset from screen. Default is to center the + /// window. + /// + public int X { get; set; } + + /// + /// ( if x is used) Window's top offset from screen. Default is to center the + /// window. + /// + public int Y { get; set; } + + /// + /// The width and height would be used as web page's size, which means the actual + /// window's size will include window frame's size and be slightly larger.Default + /// is false. + /// + public bool UseContentSize { get; set; } + + /// + /// Show window in the center of the screen. + /// + public bool Center { get; set; } + + /// + /// Window's minimum width. Default is 0. + /// + public int MinWidth { get; set; } + + /// + /// Window's minimum height. Default is 0. + /// + public int MinHeight { get; set; } + + /// + /// Window's maximum width. Default is no limit. + /// + public int MaxWidth { get; set; } + + /// + /// Window's maximum height. Default is no limit. + /// + public int MaxHeight { get; set; } + + /// + /// Whether window is resizable. Default is true. + /// + public bool Resizable { get; set; } + + /// + /// Whether window is movable. This is not implemented on Linux. Default is true. + /// + public bool Movable { get; set; } + + /// + /// Whether window is minimizable. This is not implemented on Linux. Default is true. + /// + public bool Minimizable { get; set; } + + /// + /// Whether window is maximizable. This is not implemented on Linux. Default is true. + /// + public bool Maximizable { get; set; } + + /// + /// Whether window is closable. This is not implemented on Linux. Default is true. + /// + public bool Closable { get; set; } + + /// + /// Whether the window can be focused. Default is true. On Windows setting + /// focusable: false also implies setting skipTaskbar: true. On Linux setting + /// focusable: false makes the window stop interacting with wm, so the window will + /// always stay on top in all workspaces. + /// + public bool Focusable { get; set; } + + /// + /// Whether the window should always stay on top of other windows. Default is false. + /// + public bool AlwaysOnTop { get; set; } + + /// + /// Whether the window should show in fullscreen. When explicitly set to false the + /// fullscreen button will be hidden or disabled on macOS.Default is false. + /// + public bool Fullscreen { get; set; } + + /// + /// Whether the window can be put into fullscreen mode. On macOS, also whether the + /// maximize/zoom button should toggle full screen mode or maximize window.Default + /// is true. + /// + public bool Fullscreenable { get; set; } + + /// + /// Whether to show the window in taskbar. Default is false. + /// + public bool SkipTaskbar { get; set; } + + /// + /// The kiosk mode. Default is false. + /// + public bool Kiosk { get; set; } + + /// + /// Default window title. Default is "Electron.NET". + /// + public string Title { get; set; } = "Electron.NET"; + + /// + /// The window icon. On Windows it is recommended to use ICO icons to get best + /// visual effects, you can also leave it undefined so the executable's icon will be used. + /// + public string Icon { get; set; } + + /// + /// Whether window should be shown when created. Default is true. + /// + public bool Show { get; set; } + + /// + /// Specify false to create a . Default is true. + /// + public bool Frame { get; set; } + + /// + /// Whether this is a modal window. This only works when the window is a child + /// window.Default is false. + /// + public bool Modal { get; set; } + + /// + /// Whether the web view accepts a single mouse-down event that simultaneously + /// activates the window.Default is false. + /// + public bool AcceptFirstMouse { get; set; } + + /// + /// Whether to hide cursor when typing. Default is false. + /// + public bool DisableAutoHideCursor { get; set; } + + /// + /// Auto hide the menu bar unless the Alt key is pressed. Default is false. + /// + public bool AutoHideMenuBar { get; set; } + + /// + /// Enable the window to be resized larger than screen. Default is false. + /// + public bool EnableLargerThanScreen { get; set; } + + /// + /// Window's background color as Hexadecimal value, like #66CD00 or #FFF or + /// #80FFFFFF (alpha is supported). Default is #FFF (white). + /// + public string BackgroundColor { get; set; } + + /// + /// Whether window should have a shadow. This is only implemented on macOS. Default + /// is true. + /// + public bool HasShadow { get; set; } + + /// + /// Forces using dark theme for the window, only works on some GTK+3 desktop + /// environments.Default is false. + /// + public bool DarkTheme { get; set; } + + /// + /// Makes the window . Default is false. + /// + public bool Transparent { get; set; } + + /// + /// The type of window, default is normal window. See more about this below. + /// + public string Type { get; set; } + + /// + /// The style of window title bar. Default is default. Possible values are: + /// 'default' | 'hidden' | 'hidden-inset' | 'hiddenInset' | 'customButtonsOnHover' + /// + public string TitleBarStyle { get; set; } + + /// + /// Shows the title in the tile bar in full screen mode on macOS for all + /// titleBarStyle options.Default is false. + /// + public bool FullscreenWindowTitle { get; set; } + + /// + /// Use WS_THICKFRAME style for frameless windows on Windows, which adds standard + /// window frame.Setting it to false will remove window shadow and window + /// animations.Default is true. + /// + public bool ThickFrame { get; set; } + + /// + /// Add a type of vibrancy effect to the window, only on macOS. Can be + /// appearance-based, light, dark, titlebar, selection, menu, popover, sidebar, + /// medium-light or ultra-dark. + /// + public string Vibrancy { get; set; } + + /// + /// Controls the behavior on macOS when option-clicking the green stoplight button + /// on the toolbar or by clicking the Window > Zoom menu item.If true, the window + /// will grow to the preferred width of the web page when zoomed, false will cause + /// it to zoom to the width of the screen.This will also affect the behavior when + /// calling maximize() directly.Default is false. + /// + public bool ZoomToPageWidth { get; set; } + + /// + /// Tab group name, allows opening the window as a native tab on macOS 10.12+. + /// Windows with the same tabbing identifier will be grouped together.This also + /// adds a native new tab button to your window's tab bar and allows your app and + /// window to receive the new-window-for-tab event. + /// + public string TabbingIdentifier { get; set; } + + /// + /// Settings of web page's features. + /// + public WebPreferences WebPreferences { get; set; } + } +} diff --git a/ElectronNET.API/Entities/MenuItem.cs b/ElectronNET.API/Entities/MenuItem.cs new file mode 100644 index 0000000..667993b --- /dev/null +++ b/ElectronNET.API/Entities/MenuItem.cs @@ -0,0 +1,72 @@ +using Newtonsoft.Json; +using System; + +namespace ElectronNET.API.Entities +{ + public class MenuItem + { + /// + /// Will be called with click(menuItem, browserWindow, event) when the menu item is + /// clicked. + /// + [JsonIgnore] + public Action Click { get; set; } + + /// + /// Define the action of the menu item, when specified the click property will be + /// ignored. + /// + public string Role { get; set; } + + /// + /// Can be normal, separator, submenu, checkbox or radio. + /// + public string Type { get; set; } + + + public string Label { get; set; } + + + public string Sublabel { get; set; } + + + public string Accelerator { get; set; } + + + public string Icon { get; set; } + + /// + /// If false, the menu item will be greyed out and unclickable. + /// + public bool Enabled { get; set; } + + /// + /// If false, the menu item will be entirely hidden. + /// + public bool Visible { get; set; } + + /// + /// Should only be specified for checkbox or radio type menu items. + /// + public bool Checked { get; set; } + + /// + /// Should be specified for submenu type menu items. If submenu is specified, the + /// type: 'submenu' can be omitted.If the value is not a Menu then it will be + /// automatically converted to one using Menu.buildFromTemplate. + /// + public MenuItem[] Submenu { get; set; } + + /// + /// Unique within a single menu. If defined then it can be used as a reference to + /// this item by the position attribute. + /// + public string Id { get; internal set; } + + /// + /// This field allows fine-grained definition of the specific location within a + /// given menu. + /// + public string Position { get; set; } + } +} diff --git a/ElectronNET.API/Entities/WebPreferences.cs b/ElectronNET.API/Entities/WebPreferences.cs new file mode 100644 index 0000000..8f4e738 --- /dev/null +++ b/ElectronNET.API/Entities/WebPreferences.cs @@ -0,0 +1,186 @@ +namespace ElectronNET.API.Entities +{ + public class WebPreferences + { + /// + /// Whether to enable DevTools. If it is set to false, can not use + /// BrowserWindow.webContents.openDevTools() to open DevTools.Default is true. + /// + public bool DevTools { get; set; } + + /// + /// Whether node integration is enabled. Default is true. + /// + public bool NodeIntegration { get; set; } + + /// + /// Whether node integration is enabled in web workers. Default is false. + /// + public bool NodeIntegrationInWorker { get; set; } + + /// + /// Specifies a script that will be loaded before other scripts run in the page. + /// This script will always have access to node APIs no matter whether node + /// integration is turned on or off.The value should be the absolute file path to + /// the script. When node integration is turned off, the preload script can + /// reintroduce Node global symbols back to the global scope. + /// + public string Preload { get; set; } + + /// + /// If set, this will sandbox the renderer associated with the window, making it + /// compatible with the Chromium OS-level sandbox and disabling the Node.js engine. + /// This is not the same as the nodeIntegration option and the APIs available to the + /// preload script are more limited. Read more about the option.This option is + /// currently experimental and may change or be removed in future Electron releases. + /// + public bool Sandbox { get; set; } + + /// + /// Sets the session used by the page according to the session's partition string. + /// If partition starts with persist:, the page will use a persistent session + /// available to all pages in the app with the same partition.If there is no + /// persist: prefix, the page will use an in-memory session. By assigning the same + /// partition, multiple pages can share the same session.Default is the default + /// session. + /// + public string Partition { get; set; } + + /// + /// The default zoom factor of the page, 3.0 represents 300%. Default is 1.0. + /// + public int ZoomFactor { get; set; } + + /// + /// Enables JavaScript support. Default is true. + /// + public bool Javascript { get; set; } + + /// + /// When false, it will disable the same-origin policy (usually using testing + /// websites by people), and set allowRunningInsecureContent to true if this options + /// has not been set by user.Default is true. + /// + public bool WebSecurity { get; set; } + + /// + /// Allow an https page to run JavaScript, CSS or plugins from http URLs. Default is + /// false. + /// + public bool AllowRunningInsecureContent { get; set; } + + /// + /// Enables image support. Default is true. + /// + public bool Images { get; set; } + + /// + /// Make TextArea elements resizable. Default is true. + /// + public bool TextAreasAreResizable { get; set; } + + /// + /// Enables WebGL support. Default is true. + /// + public bool Webgl { get; set; } + + /// + /// Enables WebAudio support. Default is true. + /// + public bool Webaudio { get; set; } + + /// + /// Whether plugins should be enabled. Default is false. + /// + public bool Plugins { get; set; } + + /// + /// Enables Chromium's experimental features. Default is false. + /// + public bool ExperimentalFeatures { get; set; } + + /// + /// Enables Chromium's experimental canvas features. Default is false. + /// + public bool ExperimentalCanvasFeatures { get; set; } + + /// + /// Enables scroll bounce (rubber banding) effect on macOS. Default is false. + /// + public bool ScrollBounce { get; set; } + + /// + /// A list of feature strings separated by ,, like CSSVariables,KeyboardEventKey to + /// enable.The full list of supported feature strings can be found in the file. + /// + public string BlinkFeatures { get; set; } + + /// + /// A list of feature strings separated by ,, like CSSVariables,KeyboardEventKey to + /// disable.The full list of supported feature strings can be found in the file. + /// + public string DisableBlinkFeatures { get; set; } + + /// + /// Defaults to 16. + /// + public int DefaultFontSize { get; set; } + + /// + /// Defaults to 13. + /// + public int DefaultMonospaceFontSize { get; set; } + + /// + /// Defaults to 0. + /// + public int MinimumFontSize { get; set; } + + /// + /// Defaults to ISO-8859-1. + /// + public string DefaultEncoding { get; set; } + + /// + /// Whether to throttle animations and timers when the page becomes background. This + /// also affects the[Page Visibility API][#page-visibility]. Defaults to true. + /// + public bool BackgroundThrottling { get; set; } + + /// + /// Whether to enable offscreen rendering for the browser window. Defaults to false. + /// + public bool Offscreen { get; set; } + + /// + /// Whether to run Electron APIs and the specified preload script in a separate + /// JavaScript context.Defaults to false. The context that the preload script runs + /// in will still have full access to the document and window globals but it will + /// use its own set of JavaScript builtins (Array, Object, JSON, etc.) and will be + /// isolated from any changes made to the global environment by the loaded page.The + /// Electron API will only be available in the preload script and not the loaded + /// page. This option should be used when loading potentially untrusted remote + /// content to ensure the loaded content cannot tamper with the preload script and + /// any Electron APIs being used. This option uses the same technique used by . You + /// can access this context in the dev tools by selecting the 'Electron Isolated + /// Context' entry in the combo box at the top of the Console tab. This option is + /// currently experimental and may change or be removed in future Electron releases. + /// + public bool ContextIsolation { get; set; } + + /// + /// Whether to use native window.open(). Defaults to false. This option is currently experimental. + /// + public bool NativeWindowOpen { get; set; } + + /// + /// Whether to enable the . Defaults to the value of the nodeIntegration option. The + /// preload script configured for the will have node integration enabled + /// when it is executed so you should ensure remote/untrusted content is not able to + /// create a tag with a possibly malicious preload script.You can use the + /// will-attach-webview event on to strip away the preload script and to validate or + /// alter the's initial settings. + /// + public bool WebviewTag { get; set; } + } +} \ No newline at end of file diff --git a/ElectronNET.API/IpcMain.cs b/ElectronNET.API/IpcMain.cs index 7cf2577..5f3dc62 100644 --- a/ElectronNET.API/IpcMain.cs +++ b/ElectronNET.API/IpcMain.cs @@ -9,9 +9,9 @@ namespace ElectronNET.API { private static IpcMain _ipcMain; - private IpcMain() { } + internal IpcMain() { } - public static IpcMain Instance + internal static IpcMain Instance { get { diff --git a/ElectronNET.API/Menu.cs b/ElectronNET.API/Menu.cs new file mode 100644 index 0000000..4a82028 --- /dev/null +++ b/ElectronNET.API/Menu.cs @@ -0,0 +1,92 @@ +using ElectronNET.API.Entities; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace ElectronNET.API +{ + public sealed class Menu + { + private static Menu _menu; + + internal Menu() { } + + internal static Menu Instance + { + get + { + if (_menu == null) + { + _menu = new Menu(); + } + + return _menu; + } + } + + public IReadOnlyCollection Items { get { return _items.AsReadOnly(); } } + private List _items = new List(); + + public void SetApplicationMenu(MenuItem[] menuItems) + { + AddMenuItemsId(menuItems); + BridgeConnector.Socket.Emit("menu-setApplicationMenu", JArray.FromObject(menuItems, _jsonSerializer)); + _items.AddRange(menuItems); + + BridgeConnector.Socket.On("menuItemClicked", (id) => { + MenuItem menuItem = GetMenuItem(_items, id.ToString()); + menuItem?.Click(); + }); + } + + private void AddMenuItemsId(MenuItem[] menuItems) + { + for (int index = 0; index < menuItems.Length; index++) + { + var menuItem = menuItems[index]; + if(menuItem?.Submenu?.Length > 0) + { + AddMenuItemsId(menuItem.Submenu); + } + + if(string.IsNullOrEmpty(menuItem.Role)) + { + menuItem.Id = Guid.NewGuid().ToString(); + } + } + } + + private MenuItem GetMenuItem(List menuItems, string id) + { + MenuItem result = new MenuItem(); + + foreach (var item in menuItems) + { + if(item.Id == id) + { + result = item; + } + else if(item?.Submenu?.Length > 0) + { + var menuItem = GetMenuItem(item.Submenu.ToList(), id); + if(menuItem.Id == id) + { + result = menuItem; + } + } + } + + return result; + } + + private JsonSerializer _jsonSerializer = new JsonSerializer() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore + }; + } +} diff --git a/ElectronNET.API/WindowManager.cs b/ElectronNET.API/WindowManager.cs new file mode 100644 index 0000000..9622ef4 --- /dev/null +++ b/ElectronNET.API/WindowManager.cs @@ -0,0 +1,67 @@ +using ElectronNET.API.Entities; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ElectronNET.API +{ + public sealed class WindowManager + { + private static WindowManager _windowManager; + + internal WindowManager() { } + + internal static WindowManager Instance + { + get + { + if (_windowManager == null) + { + _windowManager = new WindowManager(); + } + + return _windowManager; + } + } + + public IReadOnlyCollection BrowserWindows { get { return _browserWindows.AsReadOnly(); } } + private List _browserWindows = new List(); + + public async Task CreateWindowAsync(string loadUrl = "http://localhost") + { + return await CreateWindowAsync(new BrowserWindowConstructorOptions(), loadUrl); + } + + public Task CreateWindowAsync(BrowserWindowConstructorOptions options, string loadUrl = "http://localhost") + { + var taskCompletionSource = new TaskCompletionSource(); + + BridgeConnector.Socket.On("BrowserWindowCreated", (id) => + { + string windowId = id.ToString(); + BrowserWindow browserWindow = new BrowserWindow(int.Parse(windowId)); + _browserWindows.Add(browserWindow); + + taskCompletionSource.SetResult(browserWindow); + }); + + if (loadUrl.ToUpper() == "HTTP://LOCALHOST") + { + loadUrl = $"{loadUrl}:{BridgeSettings.WebPort}"; + } + + BridgeConnector.Socket.Emit("createBrowserWindow", JObject.FromObject(options, _jsonSerializer), loadUrl); + + return taskCompletionSource.Task; + } + + private JsonSerializer _jsonSerializer = new JsonSerializer() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore + }; + } +} diff --git a/ElectronNET.CLI/Commands/BuildCommand.cs b/ElectronNET.CLI/Commands/BuildCommand.cs index c240da9..7e3b366 100644 --- a/ElectronNET.CLI/Commands/BuildCommand.cs +++ b/ElectronNET.CLI/Commands/BuildCommand.cs @@ -40,6 +40,8 @@ namespace ElectronNET.CLI.Commands Directory.CreateDirectory(hostApiFolder); } EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "ipc.js", "api."); + EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "app.js", "api."); + EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "browserWindows.js", "api."); Console.WriteLine("Start npm install..."); ProcessHelper.CmdExecute("npm install", tempPath); diff --git a/ElectronNET.CLI/Commands/StartElectronCommand.cs b/ElectronNET.CLI/Commands/StartElectronCommand.cs index c0e4995..8dd5159 100644 --- a/ElectronNET.CLI/Commands/StartElectronCommand.cs +++ b/ElectronNET.CLI/Commands/StartElectronCommand.cs @@ -59,6 +59,8 @@ namespace ElectronNET.CLI.Commands Directory.CreateDirectory(hostApiFolder); } EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "ipc.js", "api."); + EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "app.js", "api."); + EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "browserWindows.js", "api."); Console.WriteLine("Start npm install..."); ProcessHelper.CmdExecute("npm install", tempPath); diff --git a/ElectronNET.CLI/ElectronNET.CLI.csproj b/ElectronNET.CLI/ElectronNET.CLI.csproj index 3a7efa6..b530400 100644 --- a/ElectronNET.CLI/ElectronNET.CLI.csproj +++ b/ElectronNET.CLI/ElectronNET.CLI.csproj @@ -34,4 +34,9 @@ + + + + + diff --git a/ElectronNET.Host/api/browserWindows.js b/ElectronNET.Host/api/browserWindows.js new file mode 100644 index 0000000..85b812c --- /dev/null +++ b/ElectronNET.Host/api/browserWindows.js @@ -0,0 +1,43 @@ +"use strict"; +exports.__esModule = true; +var electron_1 = require("electron"); +var windows = []; +var ipc; +module.exports = function (socket) { + socket.on('createBrowserWindow', function (options, loadUrl) { + var window = new electron_1.BrowserWindow(options); + window.on('closed', function (sender) { + for (var index = 0; index < windows.length; index++) { + var windowItem = windows[index]; + try { + windowItem.id; + } + catch (error) { + if (error.message === 'Object has been destroyed') { + windows.splice(index, 1); + } + } + } + }); + if (ipc == undefined) { + ipc = require('./ipc')(socket, window); + } + if (loadUrl) { + window.loadURL(loadUrl); + } + windows.push(window); + socket.emit('BrowserWindowCreated', window.id); + }); + socket.on('browserWindow-minimize', function (id) { + getWindowById(id).minimize(); + }); + function getWindowById(id) { + for (var index = 0; index < windows.length; index++) { + var element = windows[index]; + if (element.id == id) { + return element; + } + } + } +}; +//# sourceMappingURL=browserWindows.js.map \ No newline at end of file diff --git a/ElectronNET.Host/api/browserWindows.js.map b/ElectronNET.Host/api/browserWindows.js.map new file mode 100644 index 0000000..f1c28ff --- /dev/null +++ b/ElectronNET.Host/api/browserWindows.js.map @@ -0,0 +1 @@ +{"version":3,"file":"browserWindows.js","sourceRoot":"","sources":["browserWindows.ts"],"names":[],"mappings":";;AAAA,qCAAyC;AACzC,IAAI,OAAO,GAA6B,EAAE,CAAA;AAC1C,IAAI,GAAG,CAAC;AAER,MAAM,CAAC,OAAO,GAAG,UAAC,MAAuB;IACrC,MAAM,CAAC,EAAE,CAAC,qBAAqB,EAAE,UAAC,OAAO,EAAE,OAAO;QAC9C,IAAI,MAAM,GAAG,IAAI,wBAAa,CAAC,OAAO,CAAC,CAAC;QAExC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAC,MAAM;YACvB,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;gBAClD,IAAI,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;gBAChC,IAAI,CAAC;oBACD,UAAU,CAAC,EAAE,CAAC;gBAClB,CAAC;gBAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;oBACb,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,2BAA2B,CAAC,CAAC,CAAC;wBAChD,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;oBAC7B,CAAC;gBACL,CAAC;YACL,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC,CAAC;YACnB,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC3C,CAAC;QAED,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACV,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,wBAAwB,EAAE,UAAC,EAAE;QACnC,aAAa,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,uBAAuB,EAAU;QAC7B,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YAClD,IAAI,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC7B,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACnB,MAAM,CAAC,OAAO,CAAC;YACnB,CAAC;QACL,CAAC;IACL,CAAC;AACL,CAAC,CAAA"} \ No newline at end of file diff --git a/ElectronNET.Host/api/browserWindows.ts b/ElectronNET.Host/api/browserWindows.ts new file mode 100644 index 0000000..105b9ed --- /dev/null +++ b/ElectronNET.Host/api/browserWindows.ts @@ -0,0 +1,47 @@ +import { BrowserWindow } from "electron"; +let windows: Electron.BrowserWindow[] = [] +let ipc; + +module.exports = (socket: SocketIO.Server) => { + socket.on('createBrowserWindow', (options, loadUrl) => { + let window = new BrowserWindow(options); + + window.on('closed', (sender) => { + for (var index = 0; index < windows.length; index++) { + var windowItem = windows[index]; + try { + windowItem.id; + } catch (error) { + if (error.message === 'Object has been destroyed') { + windows.splice(index, 1); + } + } + } + }); + + // TODO: IPC Lösung für mehrere Fenster finden + if (ipc == undefined) { + ipc = require('./ipc')(socket, window); + } + + if (loadUrl) { + window.loadURL(loadUrl); + } + + windows.push(window); + socket.emit('BrowserWindowCreated', window.id); + }); + + socket.on('browserWindow-minimize', (id) => { + getWindowById(id).minimize(); + }); + + function getWindowById(id: number): Electron.BrowserWindow { + for (var index = 0; index < windows.length; index++) { + var element = windows[index]; + if (element.id == id) { + return element; + } + } + } +} \ No newline at end of file diff --git a/ElectronNET.Host/main.js b/ElectronNET.Host/main.js index e22ac47..0fcca2e 100644 --- a/ElectronNET.Host/main.js +++ b/ElectronNET.Host/main.js @@ -1,9 +1,9 @@ -const { app, BrowserWindow, Notification } = require('electron'); +const { app, Notification, Menu } = require('electron'); const fs = require('fs'); const path = require('path'); const process = require('child_process').spawn; const portfinder = require('detect-port'); -let io, window, apiProcess, loadURL, ipc, appApi; +let io, browserWindows, apiProcess, loadURL, appApi; app.on('ready', () => { portfinder(8000, (error, port) => { @@ -18,17 +18,16 @@ function startSocketApiBridge(port) { io.on('connection', (socket) => { console.log('ASP.NET Core Application connected...'); appApi = require('./api/app')(socket, app); + browserWindows = require('./api/browserWindows')(socket); - socket.on('createBrowserWindow', (options) => { - window = new BrowserWindow(options); - window.loadURL(loadURL); + socket.on('menu-setApplicationMenu', (menuItems) => { + const menu = Menu.buildFromTemplate(menuItems); - window.on('closed', function () { - mainWindow = null; - apiProcess = null; + addMenuItemClickConnector(menu.items, (id) => { + socket.emit("menuItemClicked", id); }); - ipc = require('./api/ipc')(socket, window); + Menu.setApplicationMenu(menu); }); socket.on('createNotification', (options) => { @@ -39,6 +38,19 @@ function startSocketApiBridge(port) { }); } +function addMenuItemClickConnector(menuItems, callback) { + menuItems.forEach((item) => { + if(item.submenu && item.submenu.items.length > 0) { + addMenuItemClickConnector(item.submenu.items, callback); + } + + if("id" in item && item.id) { + item.click = () => { callback(item.id); }; + } + }); +} + + function startAspCoreBackend(electronPort) { portfinder(8000, (error, electronWebPort) => { loadURL = `http://localhost:${electronWebPort}` @@ -68,10 +80,10 @@ app.on('window-all-closed', () => { } }); -app.on('activate', () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (win === null) { - createWindow(); - } -}); \ No newline at end of file +// app.on('activate', () => { +// // On macOS it's common to re-create a window in the app when the +// // dock icon is clicked and there are no other windows open. +// if (window === null) { +// createWindow(); +// } +// }); \ No newline at end of file diff --git a/ElectronNET.WebApp/Controllers/HomeController.cs b/ElectronNET.WebApp/Controllers/HomeController.cs index 1c7bc7c..3dea985 100644 --- a/ElectronNET.WebApp/Controllers/HomeController.cs +++ b/ElectronNET.WebApp/Controllers/HomeController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using ElectronNET.API; using ElectronNET.API.Entities; +using System.Linq; namespace ElectronNET.WebApp.Controllers { @@ -22,7 +23,11 @@ namespace ElectronNET.WebApp.Controllers { string pathName = await Electron.App.GetPathAsync(PathName.pictures); Electron.IpcMain.Send("GetPathComplete", pathName); + + Electron.WindowManager.BrowserWindows.First().Minimize(); + await Electron.WindowManager.CreateWindowAsync("http://www.google.de"); }); + return View(); } diff --git a/ElectronNET.WebApp/Startup.cs b/ElectronNET.WebApp/Startup.cs index fa2b53a..a1648ec 100644 --- a/ElectronNET.WebApp/Startup.cs +++ b/ElectronNET.WebApp/Startup.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System.Linq; +using System.Threading; namespace ElectronNET.WebApp { @@ -36,7 +38,27 @@ namespace ElectronNET.WebApp template: "{controller=Home}/{action=Index}/{id?}"); }); - Electron.App.OpenWindow(800, 600, true); + Bootstrap(); + } + + public async void Bootstrap() + { + Electron.Menu.SetApplicationMenu(new MenuItem[] { + new MenuItem { + Label = "Datei", + Submenu = new MenuItem[] { + new MenuItem { + Label = "Beenden", + Click = () => + { + Electron.App.Exit(); + } + } + } + } + }); + + var browserWindow = await Electron.WindowManager.CreateWindowAsync(); } } }