mirror of
https://github.com/ElectronNET/Electron.NET.git
synced 2026-09-22 15:04:47 +00:00
Compare commits
38 Commits
0.5.2-pre.
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87cc6f98b6 | ||
|
|
a0c0523c7d | ||
|
|
6d94cf9538 | ||
|
|
521e7c935a | ||
|
|
730eacbd52 | ||
|
|
deca8166ec | ||
|
|
a04ca0b9bc | ||
|
|
fffd7dfb79 | ||
|
|
98c72af3d6 | ||
|
|
703cf8f880 | ||
|
|
248d4bd664 | ||
|
|
d5da1dbc9b | ||
|
|
672ff6c40d | ||
|
|
7cd552a78f | ||
|
|
042d8e6c70 | ||
|
|
0140749753 | ||
|
|
7af2e954c7 | ||
|
|
da0e1f17db | ||
|
|
9cb210040d | ||
|
|
7e8f45b16d | ||
|
|
76dc687269 | ||
|
|
dcf4585510 | ||
|
|
a6d7434adf | ||
|
|
76ff44f67e | ||
|
|
20ab82b16d | ||
|
|
b27f705a6a | ||
|
|
8697c4de44 | ||
|
|
376a791e48 | ||
|
|
195a29052e | ||
|
|
f577a53227 | ||
|
|
382d544468 | ||
|
|
18f08f9aff | ||
|
|
def289ab54 | ||
|
|
4011f3d30b | ||
|
|
26b9d86ee4 | ||
|
|
3b57de976a | ||
|
|
d9e9b3d2e6 | ||
|
|
1ec1fde3c6 |
29
Changelog.md
29
Changelog.md
@@ -1,3 +1,32 @@
|
||||
# 0.6.0
|
||||
|
||||
## ElectronNET.Core
|
||||
|
||||
- Updated dependencies
|
||||
- Updated `WebContents` zoom level APIs to use `double` instead of `int` (#956)
|
||||
- Improved migration checks to honor custom output paths and to detect `ProjectGuid` in publish profiles (#946)
|
||||
- Fixed single instance handling on macOS (#1040)
|
||||
- Fixed slow socket bridge startup by binding to an explicit loopback address (#1103)
|
||||
- Fixed socket bridge connection when a system proxy is configured (#1105)
|
||||
- Fixed electron-builder using the host RID instead of the target RID (#1097)
|
||||
- Fixed cross-compilation behavior on same platform (#1098) @epsnm
|
||||
- Fixed resolution of unrelated target (#1099) @epsnm
|
||||
- Fixed false alarm for `ELECTRON001` on a root `package-lock.json` (#946)
|
||||
- Added `ElectronNET.Core.Templates` package with a `dotnet new electron-blazor` template (#414)
|
||||
- Added `WebContents.OnZoomChanged` event (#956)
|
||||
- Added `WebContents` page loading APIs `LoadFileAsync`, `IsLoadingAsync`, `IsLoadingMainFrameAsync`, `IsWaitingForResponseAsync`, `Reload`, `ReloadIgnoringCache` and `Stop` (#956)
|
||||
- Added `WebContents.InsertCSSAsync` and `WebContents.RemoveInsertedCSSAsync` for dynamic CSS (#956)
|
||||
- Added `WebContents` editing and selection APIs (undo, redo, cut, copy, paste, insert text, select, ...) (#956)
|
||||
- Added `WebContents.FindInPageAsync`, `WebContents.StopFindInPage` and the `OnFoundInPage` event (#956)
|
||||
- Added `WebContents` audio events `OnAudioStateChanged`, `OnMediaStartedPlaying` and `OnMediaPaused` (#956)
|
||||
- Added `WebContents.ScrollToTop` and `WebContents.ScrollToBottom` (#956)
|
||||
- Added target framework customization (#1095) @epsnm
|
||||
- Added configurable Electron root directory for custom packaging layouts (#1106) @DYH1319
|
||||
- Added ability for `custom_main.js` to modify command line switches (#1029) @AeonSake
|
||||
- Added migration checks for incomplete `ElectronHostHook` folders (`ELECTRON010`, `ELECTRON011`) (#946)
|
||||
- Added `ElectronExecutableName` to separate the product name from the executable name (#1003) @AeonSake
|
||||
- Added build extensibility properties `ElectronSkipExecCommands` and `ElectronIntermediatePublishDir` (#1106) @DYH1319
|
||||
|
||||
# 0.5.2
|
||||
|
||||
## ElectronNET.Core
|
||||
|
||||
@@ -5,4 +5,11 @@
|
||||
<!-- Uncomment to include parent Directory.Build.props file -->
|
||||
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />-->
|
||||
|
||||
<PropertyGroup>
|
||||
<ElectronNetBuildProps>$([MSBuild]::GetPathOfFileAbove('ElectronNet.Build.props', '$(MSBuildThisFileDirectory)../'))</ElectronNetBuildProps>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Only import specific project related MSBuild file -->
|
||||
<Import Project="$(ElectronNetBuildProps)" Condition=" '$(ElectronNetBuildProps)' != '' " />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -23,13 +23,14 @@ While the classic Electron.NET setup (using an ASP.NET host run by the Electron
|
||||
* ElectronNET.Core: [](https://www.nuget.org/packages/ElectronNET.Core.API/)
|
||||
* ElectronNET.Core.API: [](https://www.nuget.org/packages/ElectronNET.Core.API/)
|
||||
* ElectronNET.Core.AspNet: [](https://www.nuget.org/packages/ElectronNET.Core.AspNet/)
|
||||
* ElectronNET.Core.Templates: [](https://www.nuget.org/packages/ElectronNET.Core.Templates/)
|
||||
|
||||
## 🛠 Requirements to Run
|
||||
|
||||
You should have installed:
|
||||
|
||||
* .NET 6/8 or later.
|
||||
* The minimum base OS is the same as [.NET 6](https://github.com/dotnet/core/blob/main/release-notes/6.0/supported-os.md) / [.NET 8](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md).
|
||||
* .NET 8/10 or later.
|
||||
* The minimum base OS is the same as [.NET 8](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md) / [.NET 10](https://github.com/dotnet/core/blob/main/release-notes/10.0/supported-os.md).
|
||||
* Node.JS using at least [Version 22.x](https://nodejs.org).
|
||||
|
||||
## 👩🏫 Usage with ASP.NET
|
||||
@@ -202,6 +203,8 @@ Alternatively, consider using a GitHub sponsorship for the core maintainers:
|
||||
- [Gregor Biswanger](https://github.com/sponsors/GregorBiswanger)
|
||||
- [Florian Rappl](https://github.com/sponsors/FlorianRappl)
|
||||
|
||||
Or just [buy Florian a coffee](https://buymeacoffee.com/florianrappl) :coffee:.
|
||||
|
||||
Any support appreciated! 🍻
|
||||
|
||||
## 🎉 License
|
||||
|
||||
@@ -58,6 +58,23 @@ Works for both BrowserWindows and BrowserViews.
|
||||
- `isBrowserWindow` - Whether the webContents belong to a BrowserWindow or not (the other option is a BrowserView)
|
||||
- `path` - Absolute path to the CSS file location
|
||||
|
||||
#### 🧊 `Task<string> InsertCSSAsync(string css, string cssOrigin = null)`
|
||||
Injects CSS into the current web page and returns a unique key for the inserted style sheet.
|
||||
|
||||
**Parameters:**
|
||||
- `css` - The style sheet to inject
|
||||
- `cssOrigin` - Can be either `user` or `author`, defaults to `author`
|
||||
|
||||
**Returns:**
|
||||
|
||||
The key of the inserted style sheet, to be used with `RemoveInsertedCSSAsync`.
|
||||
|
||||
#### 🧊 `Task RemoveInsertedCSSAsync(string key)`
|
||||
Removes a previously inserted style sheet from the current web page.
|
||||
|
||||
**Parameters:**
|
||||
- `key` - The key returned by `InsertCSSAsync`
|
||||
|
||||
#### 🧊 `Task LoadURLAsync(string url)`
|
||||
Loads the url in the window. The url must contain the protocol prefix.
|
||||
|
||||
@@ -113,6 +130,106 @@ Prints window's web page as PDF with Chromium's preview printing custom settings
|
||||
|
||||
Whether the PDF generation succeeded.
|
||||
|
||||
#### 🧊 `Task<double> GetZoomFactorAsync()`
|
||||
Returns the current zoom factor. A factor of `1.0` means 100%.
|
||||
|
||||
#### 🧊 `void SetZoomFactor(double factor)`
|
||||
Changes the zoom factor to the specified factor. The zoom factor is the zoom percent divided by 100, so 300% = `3.0`. The factor must be greater than `0.0`.
|
||||
|
||||
**Parameters:**
|
||||
- `factor` - The zoom factor
|
||||
|
||||
#### 🧊 `Task<double> GetZoomLevelAsync()`
|
||||
Returns the current zoom level.
|
||||
|
||||
#### 🧊 `void SetZoomLevel(double level)`
|
||||
Changes the zoom level to the specified level. The original size is `0` and each increment above or below represents zooming 20% larger or smaller to default limits of 300% and 50% of the original size, respectively.
|
||||
|
||||
**Parameters:**
|
||||
- `level` - The zoom level
|
||||
|
||||
#### 🧊 `Task SetVisualZoomLevelLimitsAsync(double minimumLevel, double maximumLevel)`
|
||||
Sets the maximum and minimum pinch-to-zoom level.
|
||||
|
||||
**Parameters:**
|
||||
- `minimumLevel` - The minimum pinch-to-zoom level
|
||||
- `maximumLevel` - The maximum pinch-to-zoom level
|
||||
|
||||
#### 🧊 `Task LoadFileAsync(string filePath, LoadFileOptions options = null)`
|
||||
Loads the given HTML file, relative to the root of the application.
|
||||
|
||||
**Parameters:**
|
||||
- `filePath` - Path to the HTML file
|
||||
- `options` - Optional `Query`, `Search` and `Hash` parts of the resulting URL
|
||||
|
||||
#### 🧊 `void SetAudioMuted(bool muted)`
|
||||
Mutes or unmutes the audio on the current web page.
|
||||
|
||||
#### 🧊 `Task<bool> IsAudioMutedAsync()`
|
||||
Whether this page has been muted.
|
||||
|
||||
#### 🧊 `Task<bool> IsCurrentlyAudibleAsync()`
|
||||
Whether audio is currently playing.
|
||||
|
||||
#### 🧊 `Task<string> GetUserAgentAsync()` / `void SetUserAgent(string userAgent)`
|
||||
Gets or overrides the user agent for this web page.
|
||||
|
||||
#### 🧊 `Task<bool> IsLoadingAsync()`
|
||||
Whether the web page is still loading resources.
|
||||
|
||||
#### 🧊 `Task<bool> IsLoadingMainFrameAsync()`
|
||||
Whether the main frame (and not just iframes or frames within it) is still loading.
|
||||
|
||||
#### 🧊 `Task<bool> IsWaitingForResponseAsync()`
|
||||
Whether the web page is waiting for a first response from the main resource of the page.
|
||||
|
||||
#### 🧊 `void Reload()`
|
||||
Reloads the current web page.
|
||||
|
||||
#### 🧊 `void ReloadIgnoringCache()`
|
||||
Reloads the current web page and ignores the cache.
|
||||
|
||||
#### 🧊 `void Stop()`
|
||||
Stops any pending navigation.
|
||||
|
||||
#### 🧊 `void Undo()` / `void Redo()`
|
||||
Executes the `undo` / `redo` editing command in the web page.
|
||||
|
||||
#### 🧊 `void Cut()` / `void Copy()` / `void Paste()` / `void PasteAndMatchStyle()` / `void Delete()`
|
||||
Executes the corresponding editing command in the web page.
|
||||
|
||||
#### 🧊 `void CopyImageAt(int x, int y)`
|
||||
Copies the image at the given position to the clipboard.
|
||||
|
||||
#### 🧊 `void SelectAll()` / `void Unselect()` / `void CenterSelection()`
|
||||
Selects all content, clears the selection, or scrolls to the current selection.
|
||||
|
||||
#### 🧊 `void ScrollToTop()` / `void ScrollToBottom()`
|
||||
Scrolls to the top or the bottom of the current web page.
|
||||
|
||||
#### 🧊 `void AdjustSelection(AdjustSelectionOptions options)`
|
||||
Adjusts the start and end points of the current text selection by the given amounts. Negative amounts move towards the beginning of the document.
|
||||
|
||||
#### 🧊 `Task InsertTextAsync(string text)`
|
||||
Inserts text into the focused element.
|
||||
|
||||
#### 🧊 `void Replace(string text)` / `void ReplaceMisspelling(string text)`
|
||||
Replaces the current selection, or the currently misspelled word, with the given text.
|
||||
|
||||
#### 🧊 `Task<int> FindInPageAsync(string text, FindInPageOptions options = null)`
|
||||
Starts a request to find all matches for the text in the web page. Results are reported via the `OnFoundInPage` event.
|
||||
|
||||
**Parameters:**
|
||||
- `text` - Content to be searched, must not be empty
|
||||
- `options` - Optional `Forward`, `FindNext` and `MatchCase` flags
|
||||
|
||||
**Returns:**
|
||||
|
||||
The request id of the find request.
|
||||
|
||||
#### 🧊 `void StopFindInPage(StopFindInPageAction action)`
|
||||
Stops any `FindInPageAsync` request with the given action (`ClearSelection`, `KeepSelection` or `ActivateSelection`).
|
||||
|
||||
## Events
|
||||
|
||||
#### ⚡ `InputEvent`
|
||||
@@ -142,6 +259,21 @@ Emitted when the document in the top-level frame is loaded.
|
||||
#### ⚡ `OnWillRedirect`
|
||||
Emitted when a server side redirect occurs during navigation.
|
||||
|
||||
#### ⚡ `OnZoomChanged`
|
||||
Emitted when the user changes the zoom level using the mouse wheel or the keyboard. The handler receives the `ZoomDirection` (`In` or `Out`).
|
||||
|
||||
#### ⚡ `OnFoundInPage`
|
||||
Emitted when a result is available for a `FindInPageAsync` request. The handler receives a `FoundInPageResult`.
|
||||
|
||||
#### ⚡ `OnAudioStateChanged`
|
||||
Emitted when media becomes audible or inaudible. The handler receives `true` if one or more frames or child web contents are emitting audio.
|
||||
|
||||
#### ⚡ `OnMediaStartedPlaying`
|
||||
Emitted when media starts playing.
|
||||
|
||||
#### ⚡ `OnMediaPaused`
|
||||
Emitted when media is paused or done playing.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Page Loading
|
||||
@@ -279,6 +411,82 @@ webContents.OnCrashed += (killed) =>
|
||||
};
|
||||
```
|
||||
|
||||
### Zoom Control
|
||||
|
||||
```csharp
|
||||
// Set the zoom to 150%
|
||||
webContents.SetZoomFactor(1.5);
|
||||
|
||||
var factor = await webContents.GetZoomFactorAsync();
|
||||
Console.WriteLine($"Current zoom: {factor * 100}%");
|
||||
|
||||
// Zoom levels are relative: each step is 20% larger/smaller, 0 is the original size
|
||||
webContents.SetZoomLevel(2);
|
||||
|
||||
// Restrict pinch-to-zoom
|
||||
await webContents.SetVisualZoomLevelLimitsAsync(1, 3);
|
||||
|
||||
// React to zoom changes triggered by the user
|
||||
webContents.OnZoomChanged += (direction) =>
|
||||
{
|
||||
Console.WriteLine($"User zoomed {direction}");
|
||||
};
|
||||
```
|
||||
|
||||
> **Note:** The zoom factor is shared by all windows using the same session partition. Assign a unique `WebPreferences.Partition` per window if each window should keep its own zoom.
|
||||
|
||||
### Dynamic CSS
|
||||
|
||||
```csharp
|
||||
var key = await webContents.InsertCSSAsync("body { background-color: #202020; }");
|
||||
|
||||
// ... later
|
||||
await webContents.RemoveInsertedCSSAsync(key);
|
||||
```
|
||||
|
||||
### Editing and Selection
|
||||
|
||||
```csharp
|
||||
await webContents.InsertTextAsync("Hello from .NET");
|
||||
|
||||
webContents.SelectAll();
|
||||
webContents.Copy();
|
||||
webContents.Unselect();
|
||||
```
|
||||
|
||||
### Find in Page
|
||||
|
||||
```csharp
|
||||
webContents.OnFoundInPage += (result) =>
|
||||
{
|
||||
Console.WriteLine($"{result.ActiveMatchOrdinal}/{result.Matches} matches");
|
||||
|
||||
if (result.FinalUpdate)
|
||||
{
|
||||
webContents.StopFindInPage(StopFindInPageAction.ClearSelection);
|
||||
}
|
||||
};
|
||||
|
||||
var requestId = await webContents.FindInPageAsync("electron", new FindInPageOptions { MatchCase = false });
|
||||
```
|
||||
|
||||
### Audio
|
||||
|
||||
```csharp
|
||||
webContents.SetAudioMuted(true);
|
||||
|
||||
var muted = await webContents.IsAudioMutedAsync();
|
||||
var audible = await webContents.IsCurrentlyAudibleAsync();
|
||||
|
||||
webContents.OnAudioStateChanged += (isAudible) =>
|
||||
{
|
||||
Console.WriteLine(isAudible ? "Page started emitting audio" : "Page went silent");
|
||||
};
|
||||
|
||||
webContents.OnMediaStartedPlaying += () => Console.WriteLine("Media playing");
|
||||
webContents.OnMediaPaused += () => Console.WriteLine("Media paused");
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [Electron.WindowManager](WindowManager.md) - Windows containing web contents
|
||||
|
||||
@@ -17,6 +17,10 @@ When you build an Electron.NET project, the following validation checks are perf
|
||||
| [ELECTRON005](#4-parent-paths-not-allowed-in-electron-builderjson) | Parent paths not allowed | Checks for `..` references in config |
|
||||
| [ELECTRON006](#5-publish-profile-validation) | ASP.NET publish profile mismatch | Warns when ASP.NET projects have console-style profiles |
|
||||
| [ELECTRON007](#5-publish-profile-validation) | Console publish profile mismatch | Warns when console projects have ASP.NET-style profiles |
|
||||
| [ELECTRON010](#6-electronhosthook-folder-validation) | ElectronHostHook without package.json | Warns when hook TypeScript files exist without a `package.json` |
|
||||
| [ELECTRON011](#6-electronhosthook-folder-validation) | ElectronHostHook without tsconfig.json | Warns when hook TypeScript files exist without a `tsconfig.json` |
|
||||
|
||||
All checks skip the build output folders (`bin`, `obj`, `publish`, `.electron`, `node_modules`) as well as any custom output paths configured via `BaseOutputPath` / `BaseIntermediateOutputPath` (for example the .NET `artifacts` output layout).
|
||||
|
||||
---
|
||||
|
||||
@@ -32,7 +36,7 @@ Rules:
|
||||
|
||||
- **ELECTRON001**: `package.json` / `package-lock.json` must not exist in the project directory or subdirectories
|
||||
- Exception: `ElectronHostHook` folder is allowed
|
||||
- Note: a **root** `package.json` is **excluded** from `ELECTRON001` and validated by `ELECTRON008` / `ELECTRON009`
|
||||
- Note: a **root** `package.json` (and its `package-lock.json`) is **excluded** from `ELECTRON001` and validated by `ELECTRON008` / `ELECTRON009`
|
||||
|
||||
- **ELECTRON008**: If a root `package.json` exists, it must **not** contain electron-related dependencies or configuration.
|
||||
|
||||
@@ -113,6 +117,10 @@ The `electron.manifest.json` file format is deprecated. All configuration should
|
||||
"mac": {
|
||||
"icon": "Assets/app.icns",
|
||||
"target": ["dmg", "zip"]
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": true,
|
||||
"perMachine": false
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -208,7 +216,7 @@ The build system examines `.pubxml` files in the `Properties/PublishProfiles` fo
|
||||
|
||||
- **ELECTRON006**: For **ASP.NET projects** (using `Microsoft.NET.Sdk.Web`), checks that publish profiles include `WebPublishMethod`. This property is required for proper ASP.NET publishing.
|
||||
|
||||
- **ELECTRON007**: For **console/other projects** (not using the Web SDK), checks that publish profiles do NOT include the `WebPublishMethod` property. These ASP.NET-specific properties are incorrect for non-web applications.
|
||||
- **ELECTRON007**: For **console/other projects** (not using the Web SDK), checks that publish profiles do NOT include the `WebPublishMethod` or `ProjectGuid` property. These ASP.NET-specific properties are incorrect for non-web applications.
|
||||
|
||||
### Why this matters
|
||||
|
||||
@@ -233,9 +241,54 @@ For correct publish profile examples for both ASP.NET and Console applications,
|
||||
|
||||
---
|
||||
|
||||
## 6. ElectronHostHook Folder Validation
|
||||
|
||||
**Warning Codes:** `ELECTRON010`, `ELECTRON011`
|
||||
|
||||
### What is checked
|
||||
|
||||
If the project contains an `ElectronHostHook` folder with TypeScript files:
|
||||
|
||||
- **ELECTRON010**: The folder must contain a `package.json`
|
||||
- **ELECTRON011**: The folder must contain a `tsconfig.json`
|
||||
|
||||
### Why this matters
|
||||
|
||||
Custom host hook code is only compiled and packaged when the `ElectronHostHook` folder declares its npm dependencies in a `package.json`. If that file is missing, the hook is silently dropped and the corresponding `Electron.HostHook` calls fail at runtime. The `tsconfig.json` is required so the hook sources are compiled with the expected settings.
|
||||
|
||||
### How to fix
|
||||
|
||||
Add the missing files to the `ElectronHostHook` folder:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "electron-host-hook",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"socket.io": "^4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **See also:** [HostHook API](../API/HostHook.md)
|
||||
|
||||
---
|
||||
|
||||
## Disabling Migration Checks
|
||||
|
||||
If you need to disable specific migration checks (not recommended), you can set the following properties in your `.csproj` file:
|
||||
Individual checks can be downgraded to messages by their warning code:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);ELECTRON005</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
If you need to disable all migration checks (not recommended), you can set the following property in your `.csproj` file:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -48,17 +48,19 @@ You can also manually edit `electron-builder.json`:
|
||||
```json
|
||||
{
|
||||
"linux": {
|
||||
"target": [
|
||||
"tar.xz"
|
||||
]
|
||||
"target": ["tar.xz"]
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "portable",
|
||||
"target": "nsis",
|
||||
"arch": "x64"
|
||||
}
|
||||
]
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": true,
|
||||
"perMachine": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -10,6 +10,11 @@ See [System Requirements](../GettingStarted/System-Requirements.md).
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
> [!Tip]
|
||||
> To skip the manual setup below, scaffold a ready-to-run app with the
|
||||
> [project templates](Templates.md): `dotnet new install ElectronNET.Core.Templates` followed by
|
||||
> `dotnet new electron-blazor -n MyDesktopApp`.
|
||||
|
||||
### 1. Create ASP.NET Core Project
|
||||
|
||||
#### Visual Studio
|
||||
|
||||
@@ -54,7 +54,7 @@ Add the Electron.NET configuration to your `.csproj` file:
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.5.2" />
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.6.0" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
|
||||
88
docs/GettingStarted/Templates.md
Normal file
88
docs/GettingStarted/Templates.md
Normal file
@@ -0,0 +1,88 @@
|
||||
|
||||
# Project Templates
|
||||
|
||||
Electron.NET ships a `dotnet new` template package so you can scaffold a ready-to-run desktop app instead of wiring up an ASP.NET project by hand.
|
||||
|
||||
## 🛠 System Requirements
|
||||
|
||||
See [System Requirements](../GettingStarted/System-Requirements.md).
|
||||
|
||||
## 📦 Install the Templates
|
||||
|
||||
```bash
|
||||
dotnet new install ElectronNET.Core.Templates
|
||||
```
|
||||
|
||||
To update to the latest version, run the same command again. To remove them:
|
||||
|
||||
```bash
|
||||
dotnet new uninstall ElectronNET.Core.Templates
|
||||
```
|
||||
|
||||
## 🚀 Available Templates
|
||||
|
||||
| Template | Short name | Description |
|
||||
|----------|------------|-------------|
|
||||
| Electron.NET Blazor App | `electron-blazor` | A Blazor Server app hosted in an Electron shell |
|
||||
|
||||
## 🧱 Create a Blazor App
|
||||
|
||||
```bash
|
||||
dotnet new electron-blazor -n MyDesktopApp
|
||||
cd MyDesktopApp
|
||||
```
|
||||
|
||||
Then start it in the Electron shell:
|
||||
|
||||
```bash
|
||||
dotnet build
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Values | Default | Description |
|
||||
|--------|--------|---------|-------------|
|
||||
| `-f`, `--framework` | `net8.0`, `net10.0` | `net10.0` | The target framework of the generated project |
|
||||
| `-e`, `--electron-version` | any Electron version | `38.2.2` | The Electron version the app is built against |
|
||||
| `-p`, `--port` | port number | `8001` | The port the ASP.NET server uses during development |
|
||||
| `--no-restore` | — | — | Skip the automatic `dotnet restore` |
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
dotnet new electron-blazor -n MyDesktopApp -f net10.0 -e 30.4.0 -p 8123
|
||||
```
|
||||
|
||||
## 📁 What You Get
|
||||
|
||||
```
|
||||
MyDesktopApp/
|
||||
├── Components/
|
||||
│ ├── Layout/ MainLayout.razor, NavMenu.razor
|
||||
│ ├── Pages/ Home.razor, Counter.razor
|
||||
│ ├── App.razor
|
||||
│ ├── Routes.razor
|
||||
│ └── _Imports.razor
|
||||
├── Properties/
|
||||
│ ├── PublishProfiles/ win-x64, linux-x64 and osx-arm64 folder profiles
|
||||
│ ├── electron-builder.json
|
||||
│ └── launchSettings.json
|
||||
├── wwwroot/app.css
|
||||
├── appsettings.json
|
||||
├── Program.cs
|
||||
└── MyDesktopApp.csproj
|
||||
```
|
||||
|
||||
The project already references `ElectronNET.Core` and `ElectronNET.Core.AspNet`, calls `builder.UseElectron(...)` in `Program.cs`, and passes all [Migration Checks](../Core/Migration-Checks.md) out of the box.
|
||||
|
||||
> [!Note]
|
||||
> `ElectronNET.API` also defines a type named `App`, which collides with the Blazor root
|
||||
> component. That is why the template calls `app.MapRazorComponents<MyDesktopApp.Components.App>()`
|
||||
> with a fully qualified type name.
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
- **[Configuration](../Using/Configuration.md)** - Adjust app metadata and Electron settings
|
||||
- **[Debugging](../Using/Debugging.md)** - Debug the .NET and Electron sides
|
||||
- **[Package Building](../Using/Package-Building.md)** - Create distributable packages
|
||||
@@ -28,9 +28,77 @@ These are the current default values when you don't make any changes:
|
||||
<ElectronPackageId>$(MSBuildProjectName.Replace(".", "-").ToLower())</ElectronPackageId>
|
||||
<ElectronBuilderJson>electron-builder.json</ElectronBuilderJson>
|
||||
<Title>$(MSBuildProjectName)</Title>
|
||||
<ElectronExecutableName></ElectronExecutableName>
|
||||
<ElectronRootDir>../..</ElectronRootDir>
|
||||
<ElectronSkipExecCommands>false</ElectronSkipExecCommands>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
### Product Name and Executable Name
|
||||
|
||||
`Title` is the product name of the application. It is used for the window title, the installer, the macOS app bundle and the Linux desktop entry, and it may contain spaces and other characters that are awkward in a file name.
|
||||
|
||||
`ElectronExecutableName` controls the name of the executable inside the package (electron-builder's `executableName`). When it is not set, it defaults to the following behavior:
|
||||
|
||||
| Target | Default |
|
||||
| --- | --- |
|
||||
| Windows | `$(Title)` |
|
||||
| Linux / macOS | `$(ElectronPackageId)` |
|
||||
|
||||
So to ship a product called `My Great App` with a `mygreatapp` binary on every platform:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<Title>My Great App</Title>
|
||||
<ElectronExecutableName>mygreatapp</ElectronExecutableName>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> `ElectronExecutable` is a separate, advanced setting: it is the binary Electron.NET launches when the .NET app starts first. It defaults to `ElectronExecutableName` and only needs to be changed when the package contains a launcher stub (for example added by an electron-builder hook) that should be started instead of the Electron binary.
|
||||
|
||||
### Custom Packaging Layout
|
||||
|
||||
`ElectronRootDir` tells the .NET app where to find the Electron binary when it is started first (DotNet-First startup of a packaged app). The path is resolved relative to the directory of the .NET executable; absolute paths are used as-is.
|
||||
|
||||
The default `../..` matches the standard Electron-First packaging layout produced by `dotnet publish`, where the .NET app is placed in `resources/bin` of the Electron package:
|
||||
|
||||
```
|
||||
<install-root>/
|
||||
MyApp # Electron executable
|
||||
resources/
|
||||
bin/
|
||||
MyApp # .NET executable
|
||||
```
|
||||
|
||||
If your own packaging pipeline places the .NET executable at the package root and Electron in a subdirectory, set `ElectronRootDir` accordingly:
|
||||
|
||||
```xml
|
||||
<ElectronRootDir>electron</ElectronRootDir>
|
||||
```
|
||||
|
||||
```
|
||||
<install-root>/
|
||||
MyApp # .NET executable
|
||||
electron/
|
||||
MyApp # Electron executable
|
||||
resources/
|
||||
locales/
|
||||
```
|
||||
|
||||
The property is also used to detect whether the app runs packaged or unpackaged, so it must point to the directory that actually contains the Electron binary.
|
||||
|
||||
### Build Extensibility
|
||||
|
||||
These properties are useful when Electron.NET is embedded into a custom build or CI pipeline:
|
||||
|
||||
| Property | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `ElectronSkipExecCommands` | `false` | Skips all `npm`/`npx` invocations of the Electron build and publish targets. Use it when the dependencies and the Electron distribution are provided by the pipeline itself (for example from a cache or an offline mirror). |
|
||||
| `ElectronIntermediatePublishDir` | `$(IntermediateOutputPath)PubTmp\` | Overrides the intermediate directory the .NET app is published to before it is assembled into the Electron package. |
|
||||
|
||||
`ElectronPackageId` and `Title` only fall back to the project name when they are not already set, so both can be defined by a `Directory.Build.props` or passed on the command line.
|
||||
|
||||
### Relation to package.json
|
||||
|
||||
ElectronNET.Core does not work with an `electron-manifest.json` file anymore.
|
||||
@@ -42,11 +110,17 @@ Since electron builder still expects a `package.json` file to exist, ElectronNET
|
||||
"productName": "$(ElectronTitle)",
|
||||
"build": {
|
||||
"appId": "$(ElectronPackageId)",
|
||||
"win": {
|
||||
"executableName": "$(ElectronExecutableName)"
|
||||
},
|
||||
"mac": {
|
||||
"executableName": "$(ElectronExecutableName)"
|
||||
},
|
||||
"linux": {
|
||||
"desktop": {
|
||||
"entry": { "Name": "$(Title)" }
|
||||
},
|
||||
"executableName": "$(ElectronPackageId)"
|
||||
"executableName": "$(ElectronExecutableName)"
|
||||
},
|
||||
"deb": {
|
||||
"desktop": {
|
||||
|
||||
@@ -7,6 +7,7 @@ This guide explains how to include and use a `custom_main.js` file in your Elect
|
||||
- Register custom protocol handlers (e.g., `myapp://`) — protocols must be registered before the app is fully initialized
|
||||
- Integrate Node.js modules (e.g., telemetry, OS APIs)
|
||||
- Control startup logic (abort, environment checks)
|
||||
- Modify Chromium / Electron.NET command line switches before they are evaluated
|
||||
- Set up IPC messaging or preload scripts
|
||||
|
||||
## Step-by-Step Process
|
||||
@@ -65,10 +66,29 @@ Use environment variables to control features:
|
||||
if (env === 'Development') { /* enable dev features */ }
|
||||
```
|
||||
|
||||
### Modifying Command Line Switches
|
||||
|
||||
`onStartup` is invoked before Electron.NET reads any of its own switches (`manifest`, `unpackedelectron`, `unpackeddotnet`, `dotnetpacked`, `electronforcedport`, `electronurl`), so the hook can append or override them:
|
||||
|
||||
```javascript
|
||||
module.exports.onStartup = function (host) {
|
||||
const { app } = require('electron');
|
||||
|
||||
// Force a fixed socket bridge port
|
||||
app.commandLine.appendSwitch('electronforcedport', '8000');
|
||||
|
||||
// Chromium switches, e.g. to disable the GPU sandbox
|
||||
app.commandLine.appendSwitch('disable-gpu-sandbox');
|
||||
|
||||
return true;
|
||||
};
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `custom_main.js` must use CommonJS syntax (`module.exports.onStartup = ...`).
|
||||
- Place the file in your source directory and copy it to `.electron` using `.csproj`.
|
||||
- Electron.NET will abort startup if `onStartup` returns `false`.
|
||||
- `onStartup` runs before the Electron.NET command line switches are evaluated, so switches set there are picked up by the host.
|
||||
|
||||
### Complete example is available here [ElectronNetSampleApp](https://github.com/niteshsinghal85/ElectronNetSampleApp)
|
||||
@@ -222,6 +222,15 @@ The publish process will:
|
||||
> macOS builds can't be created on Windows machines because they require symlinks that aren't supported on Windows (per [this Electron issue](https://github.com/electron-userland/electron-packager/issues/71)). macOS builds can be produced on either Linux or macOS machines.
|
||||
|
||||
|
||||
## Custom Packaging Pipelines
|
||||
|
||||
The default layout is Electron-First: electron-builder produces the Electron package and the .NET app is placed in `resources/bin`. If you assemble the package yourself and put the .NET executable at the package root with Electron in a subdirectory, set `ElectronRootDir` so the .NET app can locate the Electron binary at runtime.
|
||||
|
||||
For pipelines that provide the npm dependencies themselves, `ElectronSkipExecCommands` suppresses all `npm`/`npx` invocations, and `ElectronIntermediatePublishDir` redirects the intermediate publish output.
|
||||
|
||||
See [Configuration](Configuration.md#custom-packaging-layout) for details.
|
||||
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
- **[Startup Methods](Startup-Methods.md)** - Understanding different launch modes for packaged apps
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# Getting Started
|
||||
|
||||
- [System Requirements](GettingStarted/System-Requirements.md)
|
||||
- [Project Templates](GettingStarted/Templates.md)
|
||||
- [With ASP.Net](GettingStarted/ASP.Net.md)
|
||||
- [With a Console App](GettingStarted/Console-App.md)
|
||||
|
||||
|
||||
18
src/ElectronNET.API/API/Entities/AdjustSelectionOptions.cs
Normal file
18
src/ElectronNET.API/API/Entities/AdjustSelectionOptions.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace ElectronNET.API.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for <see cref="WebContents.AdjustSelection"/>.
|
||||
/// </summary>
|
||||
public class AdjustSelectionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount to shift the start index of the current selection.
|
||||
/// </summary>
|
||||
public int? Start { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Amount to shift the end index of the current selection.
|
||||
/// </summary>
|
||||
public int? End { get; set; }
|
||||
}
|
||||
}
|
||||
24
src/ElectronNET.API/API/Entities/FindInPageOptions.cs
Normal file
24
src/ElectronNET.API/API/Entities/FindInPageOptions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace ElectronNET.API.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for <see cref="WebContents.FindInPageAsync"/>.
|
||||
/// </summary>
|
||||
public class FindInPageOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to search forward or backward, defaults to true.
|
||||
/// </summary>
|
||||
public bool? Forward { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to begin a new text finding session with this request. Should be true
|
||||
/// for initial requests, and false for subsequent requests. Defaults to false.
|
||||
/// </summary>
|
||||
public bool? FindNext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether search should be case-sensitive, defaults to false.
|
||||
/// </summary>
|
||||
public bool? MatchCase { get; set; }
|
||||
}
|
||||
}
|
||||
33
src/ElectronNET.API/API/Entities/FoundInPageResult.cs
Normal file
33
src/ElectronNET.API/API/Entities/FoundInPageResult.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace ElectronNET.API.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// The result of a text finding session, reported by the found-in-page event.
|
||||
/// </summary>
|
||||
public class FoundInPageResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier of the request returned by FindInPageAsync.
|
||||
/// </summary>
|
||||
public int RequestId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Position of the active match.
|
||||
/// </summary>
|
||||
public int ActiveMatchOrdinal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of matches.
|
||||
/// </summary>
|
||||
public int Matches { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Coordinates of the first match region.
|
||||
/// </summary>
|
||||
public Rectangle SelectionArea { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether more responses are to follow.
|
||||
/// </summary>
|
||||
public bool FinalUpdate { get; set; }
|
||||
}
|
||||
}
|
||||
25
src/ElectronNET.API/API/Entities/LoadFileOptions.cs
Normal file
25
src/ElectronNET.API/API/Entities/LoadFileOptions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ElectronNET.API.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for loading a local file into a web page.
|
||||
/// </summary>
|
||||
public class LoadFileOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Passed to url.format().
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Query { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Passed to url.format().
|
||||
/// </summary>
|
||||
public string Search { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Passed to url.format().
|
||||
/// </summary>
|
||||
public string Hash { get; set; }
|
||||
}
|
||||
}
|
||||
23
src/ElectronNET.API/API/Entities/StopFindInPageAction.cs
Normal file
23
src/ElectronNET.API/API/Entities/StopFindInPageAction.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace ElectronNET.API.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines what happens to the selection when a text finding session is stopped.
|
||||
/// </summary>
|
||||
public enum StopFindInPageAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Clear the selection.
|
||||
/// </summary>
|
||||
ClearSelection,
|
||||
|
||||
/// <summary>
|
||||
/// Translate the selection into a normal selection.
|
||||
/// </summary>
|
||||
KeepSelection,
|
||||
|
||||
/// <summary>
|
||||
/// Focus and click the selection node.
|
||||
/// </summary>
|
||||
ActivateSelection
|
||||
}
|
||||
}
|
||||
18
src/ElectronNET.API/API/Entities/ZoomDirection.cs
Normal file
18
src/ElectronNET.API/API/Entities/ZoomDirection.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace ElectronNET.API.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// The direction of a zoom change triggered by the user.
|
||||
/// </summary>
|
||||
public enum ZoomDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// The user zoomed in.
|
||||
/// </summary>
|
||||
In,
|
||||
|
||||
/// <summary>
|
||||
/// The user zoomed out.
|
||||
/// </summary>
|
||||
Out
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,52 @@ public class WebContents : ApiBase
|
||||
remove => RemoveEvent(value, Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emitted when the user is requesting to change the zoom level using the mouse wheel or the keyboard.
|
||||
/// </summary>
|
||||
public event Action<ZoomDirection> OnZoomChanged
|
||||
{
|
||||
add => AddEvent(value, Id);
|
||||
remove => RemoveEvent(value, Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emitted when a result is available for a <see cref="FindInPageAsync(string, FindInPageOptions)"/> request.
|
||||
/// </summary>
|
||||
public event Action<FoundInPageResult> OnFoundInPage
|
||||
{
|
||||
add => AddEvent(value, Id);
|
||||
remove => RemoveEvent(value, Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emitted when media becomes audible or inaudible. The parameter is true if one or more
|
||||
/// frames or child web contents are emitting audio.
|
||||
/// </summary>
|
||||
public event Action<bool> OnAudioStateChanged
|
||||
{
|
||||
add => AddEvent(value, Id);
|
||||
remove => RemoveEvent(value, Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emitted when media starts playing.
|
||||
/// </summary>
|
||||
public event Action OnMediaStartedPlaying
|
||||
{
|
||||
add => AddEvent(value, Id);
|
||||
remove => RemoveEvent(value, Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emitted when media is paused or done playing.
|
||||
/// </summary>
|
||||
public event Action OnMediaPaused
|
||||
{
|
||||
add => AddEvent(value, Id);
|
||||
remove => RemoveEvent(value, Id);
|
||||
}
|
||||
|
||||
internal WebContents(int id)
|
||||
{
|
||||
Id = id;
|
||||
@@ -304,6 +350,68 @@ public class WebContents : ApiBase
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the given file in the window. The file path must be a path to an HTML file
|
||||
/// relative to the root of your application.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the HTML file.</param>
|
||||
/// <param name="options">Optional query, search and hash parts of the resulting URL.</param>
|
||||
public Task LoadFileAsync(string filePath, LoadFileOptions options = null)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
BridgeConnector.Socket.Once("webContents-loadFile-complete" + Id, () =>
|
||||
{
|
||||
BridgeConnector.Socket.Off("webContents-loadFile-error" + Id);
|
||||
tcs.SetResult();
|
||||
});
|
||||
|
||||
BridgeConnector.Socket.Once<string>("webContents-loadFile-error" + Id, (error) => { tcs.SetException(new InvalidOperationException(error)); });
|
||||
|
||||
BridgeConnector.Socket.Emit("webContents-loadFile", Id, filePath, options);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns boolean - Whether web page is still loading resources.
|
||||
/// </summary>
|
||||
public Task<bool> IsLoadingAsync() => InvokeAsync<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns boolean - Whether the main frame (and not just iframes or frames within it) is still loading.
|
||||
/// </summary>
|
||||
public Task<bool> IsLoadingMainFrameAsync() => InvokeAsync<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns boolean - Whether the web page is waiting for a first-response from the main resource of the page.
|
||||
/// </summary>
|
||||
public Task<bool> IsWaitingForResponseAsync() => InvokeAsync<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the current web page.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-reload", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the current page and ignores cache.
|
||||
/// </summary>
|
||||
public void ReloadIgnoringCache()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-reloadIgnoringCache", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops any pending navigation.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-stop", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts CSS into the web page.
|
||||
/// See: https://www.electronjs.org/docs/api/web-contents#contentsinsertcsscss-options
|
||||
@@ -316,6 +424,37 @@ public class WebContents : ApiBase
|
||||
BridgeConnector.Socket.Emit("webContents-insertCSS", Id, isBrowserWindow, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Injects CSS into the current web page and returns a unique key for the inserted
|
||||
/// stylesheet, which can be used with <see cref="RemoveInsertedCSSAsync"/>.
|
||||
/// </summary>
|
||||
/// <param name="css">The style sheet to inject.</param>
|
||||
/// <param name="cssOrigin">Can be either 'user' or 'author'. Defaults to 'author'.</param>
|
||||
/// <returns>The key of the inserted style sheet.</returns>
|
||||
public Task<string> InsertCSSAsync(string css, string cssOrigin = null)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
|
||||
BridgeConnector.Socket.Once<string>("webContents-insertCSSText-completed" + Id, tcs.SetResult);
|
||||
BridgeConnector.Socket.Emit("webContents-insertCSSText", Id, css, cssOrigin);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the inserted CSS from the current web page.
|
||||
/// </summary>
|
||||
/// <param name="key">The key returned by <see cref="InsertCSSAsync"/>.</param>
|
||||
public Task RemoveInsertedCSSAsync(string key)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
BridgeConnector.Socket.Once("webContents-removeInsertedCSS-completed" + Id, tcs.SetResult);
|
||||
BridgeConnector.Socket.Emit("webContents-removeInsertedCSS", Id, key);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns number - The current zoom factor.
|
||||
/// </summary>
|
||||
@@ -337,14 +476,14 @@ public class WebContents : ApiBase
|
||||
/// Returns number - The current zoom level.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<int> GetZoomLevelAsync() => InvokeAsync<int>();
|
||||
public Task<double> GetZoomLevelAsync() => InvokeAsync<double>();
|
||||
|
||||
/// <summary>
|
||||
/// Changes the zoom level to the specified level.
|
||||
/// The original size is 0 and each increment above or below represents zooming 20% larger or smaller to default limits of 300% and 50% of original size, respectively.
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
public void SetZoomLevel(int level)
|
||||
public void SetZoomLevel(double level)
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-setZoomLevel", Id, level);
|
||||
}
|
||||
@@ -354,7 +493,7 @@ public class WebContents : ApiBase
|
||||
/// </summary>
|
||||
/// <param name="minimumLevel"></param>
|
||||
/// <param name="maximumLevel"></param>
|
||||
public Task SetVisualZoomLevelLimitsAsync(int minimumLevel, int maximumLevel)
|
||||
public Task SetVisualZoomLevelLimitsAsync(double minimumLevel, double maximumLevel)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
@@ -399,4 +538,178 @@ public class WebContents : ApiBase
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-setUserAgent", Id, userAgent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command undo in web page.
|
||||
/// </summary>
|
||||
public void Undo()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-undo", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command redo in web page.
|
||||
/// </summary>
|
||||
public void Redo()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-redo", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command cut in web page.
|
||||
/// </summary>
|
||||
public void Cut()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-cut", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command copy in web page.
|
||||
/// </summary>
|
||||
public void Copy()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-copy", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the image at the given position to the clipboard.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public void CopyImageAt(int x, int y)
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-copyImageAt", Id, x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command paste in web page.
|
||||
/// </summary>
|
||||
public void Paste()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-paste", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command pasteAndMatchStyle in web page.
|
||||
/// </summary>
|
||||
public void PasteAndMatchStyle()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-pasteAndMatchStyle", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command delete in web page.
|
||||
/// </summary>
|
||||
public void Delete()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-delete", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command selectAll in web page.
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-selectAll", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command unselect in web page.
|
||||
/// </summary>
|
||||
public void Unselect()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-unselect", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the current text selection starting and ending points by the given amounts.
|
||||
/// A negative amount moves the selection towards the beginning of the document.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public void AdjustSelection(AdjustSelectionOptions options)
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-adjustSelection", Id, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls to the current text selection.
|
||||
/// </summary>
|
||||
public void CenterSelection()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-centerSelection", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls to the top of the current web page.
|
||||
/// </summary>
|
||||
public void ScrollToTop()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-scrollToTop", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls to the bottom of the current web page.
|
||||
/// </summary>
|
||||
public void ScrollToBottom()
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-scrollToBottom", Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts text to the focused element.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to be inserted.</param>
|
||||
public Task InsertTextAsync(string text)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
BridgeConnector.Socket.Once("webContents-insertText-completed" + Id, tcs.SetResult);
|
||||
BridgeConnector.Socket.Emit("webContents-insertText", Id, text);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command replace in web page.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public void Replace(string text)
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-replace", Id, text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the editing command replaceMisspelling in web page.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public void ReplaceMisspelling(string text)
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-replaceMisspelling", Id, text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a request to find all matches for the text in the web page.
|
||||
/// The result of the request can be obtained by subscribing to the <see cref="OnFoundInPage"/> event.
|
||||
/// </summary>
|
||||
/// <param name="text">Content to be searched, must not be empty.</param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns>The request id used for the request.</returns>
|
||||
public Task<int> FindInPageAsync(string text, FindInPageOptions options = null)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
|
||||
BridgeConnector.Socket.Once<int>("webContents-findInPage-completed" + Id, tcs.SetResult);
|
||||
BridgeConnector.Socket.Emit("webContents-findInPage", Id, text, options);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops any findInPage request for the web contents with the provided action.
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public void StopFindInPage(StopFindInPageAction action)
|
||||
{
|
||||
BridgeConnector.Socket.Emit("webContents-stopFindInPage", Id, action);
|
||||
}
|
||||
}
|
||||
@@ -157,10 +157,19 @@ namespace ElectronNET.API
|
||||
}
|
||||
|
||||
BridgeConnector.Socket.On<int[]>("BrowserWindowClosed", HandleBrowserWindowClosed);
|
||||
BridgeConnector.Socket.On<int>("BrowserWindowRecreated", HandleBrowserWindowRecreated);
|
||||
_browserWindowClosedSubscribed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBrowserWindowRecreated(int id)
|
||||
{
|
||||
if (_browserWindows.All(window => window.Id != id))
|
||||
{
|
||||
_browserWindows.Add(new BrowserWindow(id));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBrowserWindowClosed(int[] ids)
|
||||
{
|
||||
if (ids == null || ids.Length == 0)
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace ElectronNET.API;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using ElectronNET.API.Serialization;
|
||||
using SocketIO.Serializer.SystemTextJson;
|
||||
@@ -18,13 +19,21 @@ internal class SocketIOConnection : ISocketConnection
|
||||
|
||||
public SocketIOConnection(string uri, string authorization)
|
||||
{
|
||||
var opts = string.IsNullOrEmpty(authorization) ? new SocketIOOptions() : new SocketIOOptions
|
||||
var opts = new SocketIOOptions
|
||||
{
|
||||
ExtraHeaders = new Dictionary<string, string>
|
||||
// The bridge is a loopback IPC channel, so it must never go through a
|
||||
// (system) proxy - otherwise the handshake never reaches the socket server.
|
||||
Proxy = DirectProxy.Instance,
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(authorization))
|
||||
{
|
||||
opts.ExtraHeaders = new Dictionary<string, string>
|
||||
{
|
||||
["authorization"] = authorization
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
_socket = new SocketIO(uri, opts);
|
||||
_socket.Serializer = new SystemTextJsonSerializer(ElectronJson.Options);
|
||||
// Use default System.Text.Json serializer from SocketIOClient.
|
||||
@@ -152,4 +161,15 @@ internal class SocketIOConnection : ISocketConnection
|
||||
throw new ObjectDisposedException(nameof(SocketIOConnection));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DirectProxy : IWebProxy
|
||||
{
|
||||
public static readonly DirectProxy Instance = new DirectProxy();
|
||||
|
||||
public ICredentials Credentials { get; set; }
|
||||
|
||||
public Uri GetProxy(Uri destination) => destination;
|
||||
|
||||
public bool IsBypassed(Uri host) => true;
|
||||
}
|
||||
}
|
||||
@@ -37,10 +37,18 @@
|
||||
public ProcessRunner(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
this.ExitWaitTimeout = DefaultExitWaitTimeout;
|
||||
}
|
||||
|
||||
public event EventHandler<EventArgs> ProcessExited;
|
||||
|
||||
/// <summary>Gets or sets the value <see cref="ExitWaitTimeout"/> is initialized with.</summary>
|
||||
public static int DefaultExitWaitTimeout { get; set; } = 5000;
|
||||
|
||||
/// <summary>Gets or sets the time in milliseconds to wait for the process and its I/O to finish
|
||||
/// after the exited event has been raised. Values of zero or less wait indefinitely.</summary>
|
||||
public int ExitWaitTimeout { get; set; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
private Process Process
|
||||
@@ -498,8 +506,17 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeout = this.ExitWaitTimeout;
|
||||
|
||||
// This shouldn't throw here, but the mono process implementation doesn't always behave as it should.
|
||||
this.process.WaitForExit();
|
||||
if (timeout > 0)
|
||||
{
|
||||
this.process.WaitForExit(timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.process.WaitForExit();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Import Project="..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0;net10.0</TargetFrameworks>
|
||||
<TargetFrameworks>$(ElectronNetTargetFrameworks)</TargetFrameworks>
|
||||
<PackageOutputPath>..\..\artifacts</PackageOutputPath>
|
||||
<PackageId>$(PackageNamePrefix).API</PackageId>
|
||||
<Title>$(PackageId)</Title>
|
||||
@@ -29,7 +29,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="SocketIOClient" Version="3.1.2" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.22" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.6" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.6" Condition=" '$(TargetFramework)' == 'net6.0' " />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
internal const int DefaultSocketPort = 8000;
|
||||
internal const int DefaultWebPort = 8001;
|
||||
internal const string ElectronPortArgumentName = "electronPort";
|
||||
internal const string ElectronHostArgumentName = "electronHost";
|
||||
internal const string ElectronPidArgumentName = "electronPID";
|
||||
internal const string ElectronAuthTokenArgumentName = "electronAuthToken";
|
||||
|
||||
@@ -31,6 +32,11 @@
|
||||
|
||||
public static int? ElectronSocketPort { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The loopback address the Electron socket bridge is listening on.
|
||||
/// </summary>
|
||||
public static string ElectronSocketHost { get; internal set; }
|
||||
|
||||
public static int? AspNetWebPort { get; internal set; }
|
||||
|
||||
public static StartupMethod StartupMethod { get; internal set; }
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
public string ElectronVersion { get; internal set; }
|
||||
|
||||
public string ElectronRootDir { get; internal set; }
|
||||
|
||||
public string RuntimeIdentifier { get; internal set; }
|
||||
|
||||
public bool ElectronSingleInstance { get; internal set; }
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace ElectronNET.Runtime.Helpers
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the directory that contains the Electron binary of a packaged application.
|
||||
/// </summary>
|
||||
internal static class ElectronRootDirResolver
|
||||
{
|
||||
/// <summary>The location of the Electron binary relative to the .NET application directory in the
|
||||
/// default (Electron-first) layout, where the .NET application is placed in 'resources/bin'.</summary>
|
||||
internal const string DefaultElectronRootDir = "../..";
|
||||
|
||||
/// <summary>Resolves the Electron root directory relative to the given .NET application directory.</summary>
|
||||
/// <param name="baseDirectory">The directory containing the .NET application.</param>
|
||||
/// <returns>The directory containing the Electron binary.</returns>
|
||||
public static DirectoryInfo Resolve(DirectoryInfo baseDirectory)
|
||||
{
|
||||
var rootDir = ElectronNetRuntime.BuildInfo?.ElectronRootDir;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rootDir))
|
||||
{
|
||||
rootDir = DefaultElectronRootDir;
|
||||
}
|
||||
|
||||
// An absolute ElectronRootDir is used as-is by Path.Combine.
|
||||
return new DirectoryInfo(Path.GetFullPath(Path.Combine(baseDirectory.FullName, rootDir)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,15 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
// Packaged, if the Electron binary is found where ElectronRootDir points to.
|
||||
var electronExecutable = ElectronNetRuntime.ElectronExecutable;
|
||||
|
||||
if (!string.IsNullOrEmpty(electronExecutable) &&
|
||||
File.Exists(Path.Combine(ElectronRootDirResolver.Resolve(dir).FullName, electronExecutable)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dir.GetDirectories().Any(e => e.Name == ".electron"))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using ElectronNET.Common;
|
||||
using ElectronNET.Runtime.Data;
|
||||
using ElectronNET.Runtime.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Launches and manages the Electron app process.
|
||||
@@ -18,7 +19,7 @@
|
||||
[Localizable(false)]
|
||||
internal class ElectronProcessActive : ElectronProcessBase
|
||||
{
|
||||
private readonly Regex extractor = new Regex("^Electron Socket: listening on port (\\d+) at .* using ([a-f0-9]+)$");
|
||||
private readonly Regex extractor = new Regex("^Electron Socket: listening on port (\\d+) at (\\S+) using ([a-f0-9]+)$");
|
||||
|
||||
private readonly bool isUnpackaged;
|
||||
private readonly string electronBinaryName;
|
||||
@@ -87,7 +88,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
dir = dir.Parent!.Parent!;
|
||||
dir = ElectronRootDirResolver.Resolve(dir);
|
||||
startCmd = Path.Combine(dir.FullName, this.electronBinaryName);
|
||||
args = $"-dotnetpacked -electronforcedport={this.socketPort:D} " + this.extraArguments;
|
||||
workingDir = dir.FullName;
|
||||
@@ -179,11 +180,13 @@
|
||||
if (match?.Success ?? false)
|
||||
{
|
||||
var port = int.Parse(match.Groups[1].Value);
|
||||
var token = match.Groups[2].Value;
|
||||
var host = match.Groups[2].Value;
|
||||
var token = match.Groups[3].Value;
|
||||
|
||||
this.process.LineReceived -= Read_SocketIO_Parameters;
|
||||
ElectronNetRuntime.ElectronAuthToken = token;
|
||||
ElectronNetRuntime.ElectronSocketPort = port;
|
||||
ElectronNetRuntime.ElectronSocketHost = host;
|
||||
tcs.SetResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,19 @@
|
||||
{
|
||||
this.socketPort = socketPort;
|
||||
this.authorization = authorization;
|
||||
this.socketUrl = $"http://localhost:{this.socketPort}";
|
||||
this.socketUrl = $"http://{FormatHost(ElectronNetRuntime.ElectronSocketHost)}:{this.socketPort}";
|
||||
}
|
||||
|
||||
// The Electron host reports the loopback address it is actually listening on; only
|
||||
// when it is unknown we have to fall back to the ambiguous hostname.
|
||||
private static string FormatHost(string socketHost)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(socketHost))
|
||||
{
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
return socketHost.Contains(':') ? $"[{socketHost}]" : socketHost;
|
||||
}
|
||||
|
||||
public int SocketPort => this.socketPort;
|
||||
|
||||
@@ -94,8 +94,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
var pidArg = argsList.FirstOrDefault(e => e.Contains(ElectronNetRuntime.ElectronPidArgumentName, StringComparison.OrdinalIgnoreCase));
|
||||
var hostArg = argsList.FirstOrDefault(e => e.Contains(ElectronNetRuntime.ElectronHostArgumentName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (hostArg != null)
|
||||
{
|
||||
var parts = hostArg.Split('=', StringSplitOptions.TrimEntries);
|
||||
|
||||
if (parts.Length > 1 && !string.IsNullOrWhiteSpace(parts[1]))
|
||||
{
|
||||
ElectronNetRuntime.ElectronSocketHost = parts[1];
|
||||
|
||||
Console.WriteLine("Use Electron Host: " + parts[1]);
|
||||
}
|
||||
}
|
||||
var pidArg = argsList.FirstOrDefault(e => e.Contains(ElectronNetRuntime.ElectronPidArgumentName, StringComparison.OrdinalIgnoreCase));
|
||||
if (pidArg != null)
|
||||
{
|
||||
var parts = pidArg.Split('=', StringSplitOptions.TrimEntries);
|
||||
@@ -166,6 +178,7 @@
|
||||
{
|
||||
buildInfo.ElectronExecutable = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronExecutable))?.Value;
|
||||
buildInfo.ElectronVersion = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronVersion))?.Value;
|
||||
buildInfo.ElectronRootDir = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronRootDir))?.Value;
|
||||
buildInfo.RuntimeIdentifier = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.RuntimeIdentifier))?.Value;
|
||||
buildInfo.Title = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.Title))?.Value;
|
||||
buildInfo.Version = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.Version))?.Value;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Import Project="..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0;net10.0</TargetFrameworks>
|
||||
<TargetFrameworks>$(ElectronNetTargetFrameworks)</TargetFrameworks>
|
||||
<PackageOutputPath>..\..\artifacts</PackageOutputPath>
|
||||
<PackageId>$(PackageNamePrefix).AspNet</PackageId>
|
||||
<Title>$(PackageId)</Title>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<ProjectReference Include="..\ElectronNET.API\ElectronNET.API.csproj" Condition="$(ElectronNetDevMode)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.6.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\ElectronNET\build\ElectronNET.Core.targets" Condition="$(ElectronNetDevMode)" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.3864779">
|
||||
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.6537497">
|
||||
<ItemGroup>
|
||||
<None Include=".vscode\tasks.json" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -39,7 +39,6 @@ const windows = (global["browserWindows"] =
|
||||
global["browserWindows"] || []);
|
||||
let readyToShowWindowsIds = [];
|
||||
let window;
|
||||
let lastOptions;
|
||||
let electronSocket;
|
||||
const proxyToCredentialsMap = (global["proxyToCredentialsMap"] = global["proxyToCredentialsMap"] || []);
|
||||
module.exports = (socket, app) => {
|
||||
@@ -203,6 +202,18 @@ module.exports = (socket, app) => {
|
||||
});
|
||||
});
|
||||
socket.on("createBrowserWindow", (options, loadUrl) => {
|
||||
createWindow(options, loadUrl, "BrowserWindowCreated");
|
||||
});
|
||||
// Allows the host (main.js) to bring the main window back when the app is activated
|
||||
// again on macOS after all windows have been closed.
|
||||
global["recreateMainWindow"] = () => {
|
||||
if (electron_1.BrowserWindow.getAllWindows().length > 0 || !app["mainWindowOptions"]) {
|
||||
return false;
|
||||
}
|
||||
createWindow(app["mainWindowOptions"], app["mainWindowURL"], "BrowserWindowRecreated");
|
||||
return true;
|
||||
};
|
||||
function createWindow(options, loadUrl, createdEventName) {
|
||||
if (options.webPreferences &&
|
||||
!("nodeIntegration" in options.webPreferences)) {
|
||||
options = {
|
||||
@@ -226,21 +237,19 @@ module.exports = (socket, app) => {
|
||||
delete options.isRunningBlazor;
|
||||
// we dont want to recreate the window when watch is ready.
|
||||
if (app.commandLine.hasSwitch("watch") &&
|
||||
app["mainWindowURL"] === loadUrl) {
|
||||
app["mainWindowURL"] === loadUrl &&
|
||||
app["mainWindow"] &&
|
||||
!app["mainWindow"].isDestroyed()) {
|
||||
window = app["mainWindow"];
|
||||
if (window) {
|
||||
window.reload();
|
||||
synchronizeWindowRegistry();
|
||||
if (!windows.some((entry) => tryGetWindowId(entry) === window.id)) {
|
||||
windows.push(window);
|
||||
}
|
||||
electronSocket.emit("BrowserWindowCreated", window.id);
|
||||
return;
|
||||
window.reload();
|
||||
synchronizeWindowRegistry();
|
||||
if (!windows.some((entry) => tryGetWindowId(entry) === window.id)) {
|
||||
windows.push(window);
|
||||
}
|
||||
electronSocket.emit(createdEventName, window.id);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
window = new electron_1.BrowserWindow(options);
|
||||
}
|
||||
window = new electron_1.BrowserWindow(options);
|
||||
if (options.proxy) {
|
||||
window.webContents.session.setProxy({ proxyRules: options.proxy });
|
||||
}
|
||||
@@ -255,18 +264,10 @@ module.exports = (socket, app) => {
|
||||
readyToShowWindowsIds.push(window.id);
|
||||
}
|
||||
});
|
||||
lastOptions = options;
|
||||
window.on("closed", () => {
|
||||
synchronizeWindowRegistry();
|
||||
emitBrowserWindowClosed();
|
||||
});
|
||||
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 && lastOptions) {
|
||||
window = new electron_1.BrowserWindow(lastOptions);
|
||||
}
|
||||
});
|
||||
if (loadUrl) {
|
||||
// Append authentication token to initial URL if available
|
||||
const token = global["authToken"];
|
||||
@@ -299,10 +300,14 @@ module.exports = (socket, app) => {
|
||||
if (app["mainWindowURL"] == undefined || app["mainWindowURL"] == "") {
|
||||
app["mainWindowURL"] = loadUrl;
|
||||
app["mainWindow"] = window;
|
||||
app["mainWindowOptions"] = options;
|
||||
}
|
||||
else if (app["mainWindowURL"] === loadUrl) {
|
||||
app["mainWindow"] = window;
|
||||
}
|
||||
windows.push(window);
|
||||
electronSocket.emit("BrowserWindowCreated", window.id);
|
||||
});
|
||||
electronSocket.emit(createdEventName, window.id);
|
||||
}
|
||||
socket.on("browserWindowDestroy", (id) => {
|
||||
getWindowById(id).destroy();
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -10,7 +10,6 @@ const windows: Electron.BrowserWindow[] = (global["browserWindows"] =
|
||||
let readyToShowWindowsIds: number[] = [];
|
||||
|
||||
let window;
|
||||
let lastOptions;
|
||||
let electronSocket;
|
||||
|
||||
const proxyToCredentialsMap: { [proxy: string]: string } = (global[
|
||||
@@ -217,6 +216,26 @@ export = (socket: Socket, app: Electron.App) => {
|
||||
});
|
||||
|
||||
socket.on("createBrowserWindow", (options, loadUrl) => {
|
||||
createWindow(options, loadUrl, "BrowserWindowCreated");
|
||||
});
|
||||
|
||||
// Allows the host (main.js) to bring the main window back when the app is activated
|
||||
// again on macOS after all windows have been closed.
|
||||
global["recreateMainWindow"] = () => {
|
||||
if (BrowserWindow.getAllWindows().length > 0 || !app["mainWindowOptions"]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
createWindow(
|
||||
app["mainWindowOptions"],
|
||||
app["mainWindowURL"],
|
||||
"BrowserWindowRecreated",
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
function createWindow(options, loadUrl, createdEventName: string) {
|
||||
if (
|
||||
options.webPreferences &&
|
||||
!("nodeIntegration" in options.webPreferences)
|
||||
@@ -250,22 +269,24 @@ export = (socket: Socket, app: Electron.App) => {
|
||||
// we dont want to recreate the window when watch is ready.
|
||||
if (
|
||||
app.commandLine.hasSwitch("watch") &&
|
||||
app["mainWindowURL"] === loadUrl
|
||||
app["mainWindowURL"] === loadUrl &&
|
||||
app["mainWindow"] &&
|
||||
!app["mainWindow"].isDestroyed()
|
||||
) {
|
||||
window = app["mainWindow"];
|
||||
if (window) {
|
||||
window.reload();
|
||||
synchronizeWindowRegistry();
|
||||
if (!windows.some((entry) => tryGetWindowId(entry) === window.id)) {
|
||||
windows.push(window);
|
||||
}
|
||||
electronSocket.emit("BrowserWindowCreated", window.id);
|
||||
return;
|
||||
window.reload();
|
||||
synchronizeWindowRegistry();
|
||||
|
||||
if (!windows.some((entry) => tryGetWindowId(entry) === window.id)) {
|
||||
windows.push(window);
|
||||
}
|
||||
} else {
|
||||
window = new BrowserWindow(options);
|
||||
|
||||
electronSocket.emit(createdEventName, window.id);
|
||||
return;
|
||||
}
|
||||
|
||||
window = new BrowserWindow(options);
|
||||
|
||||
if (options.proxy) {
|
||||
window.webContents.session.setProxy({ proxyRules: options.proxy });
|
||||
}
|
||||
@@ -284,21 +305,11 @@ export = (socket: Socket, app: Electron.App) => {
|
||||
}
|
||||
});
|
||||
|
||||
lastOptions = options;
|
||||
|
||||
window.on("closed", () => {
|
||||
synchronizeWindowRegistry();
|
||||
emitBrowserWindowClosed();
|
||||
});
|
||||
|
||||
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 && lastOptions) {
|
||||
window = new BrowserWindow(lastOptions);
|
||||
}
|
||||
});
|
||||
|
||||
if (loadUrl) {
|
||||
// Append authentication token to initial URL if available
|
||||
const token = global["authToken"];
|
||||
@@ -338,11 +349,14 @@ export = (socket: Socket, app: Electron.App) => {
|
||||
if (app["mainWindowURL"] == undefined || app["mainWindowURL"] == "") {
|
||||
app["mainWindowURL"] = loadUrl;
|
||||
app["mainWindow"] = window;
|
||||
app["mainWindowOptions"] = options;
|
||||
} else if (app["mainWindowURL"] === loadUrl) {
|
||||
app["mainWindow"] = window;
|
||||
}
|
||||
|
||||
windows.push(window);
|
||||
electronSocket.emit("BrowserWindowCreated", window.id);
|
||||
});
|
||||
electronSocket.emit(createdEventName, window.id);
|
||||
}
|
||||
|
||||
socket.on("browserWindowDestroy", (id) => {
|
||||
getWindowById(id).destroy();
|
||||
|
||||
@@ -111,6 +111,41 @@ module.exports = (socket) => {
|
||||
electronSocket.emit("webContents-domReady" + id);
|
||||
});
|
||||
});
|
||||
socket.on("register-webContents-zoomChanged", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
browserWindow.webContents.removeAllListeners("zoom-changed");
|
||||
browserWindow.webContents.on("zoom-changed", (_, zoomDirection) => {
|
||||
electronSocket.emit("webContents-zoomChanged" + id, zoomDirection);
|
||||
});
|
||||
});
|
||||
socket.on("register-webContents-foundInPage", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
browserWindow.webContents.removeAllListeners("found-in-page");
|
||||
browserWindow.webContents.on("found-in-page", (_, result) => {
|
||||
electronSocket.emit("webContents-foundInPage" + id, result);
|
||||
});
|
||||
});
|
||||
socket.on("register-webContents-audioStateChanged", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
browserWindow.webContents.removeAllListeners("audio-state-changed");
|
||||
browserWindow.webContents.on("audio-state-changed", (event) => {
|
||||
electronSocket.emit("webContents-audioStateChanged" + id, event.audible);
|
||||
});
|
||||
});
|
||||
socket.on("register-webContents-mediaStartedPlaying", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
browserWindow.webContents.removeAllListeners("media-started-playing");
|
||||
browserWindow.webContents.on("media-started-playing", () => {
|
||||
electronSocket.emit("webContents-mediaStartedPlaying" + id);
|
||||
});
|
||||
});
|
||||
socket.on("register-webContents-mediaPaused", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
browserWindow.webContents.removeAllListeners("media-paused");
|
||||
browserWindow.webContents.on("media-paused", () => {
|
||||
electronSocket.emit("webContents-mediaPaused" + id);
|
||||
});
|
||||
});
|
||||
socket.on("webContents-openDevTools", (id, options) => {
|
||||
if (options) {
|
||||
getWindowById(id).webContents.openDevTools(options);
|
||||
@@ -308,6 +343,36 @@ module.exports = (socket) => {
|
||||
electronSocket.emit("webContents-loadURL-error" + id, error);
|
||||
});
|
||||
});
|
||||
socket.on("webContents-loadFile", (id, filePath, options) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
browserWindow.webContents
|
||||
.loadFile(filePath, options ?? undefined)
|
||||
.then(() => {
|
||||
electronSocket.emit("webContents-loadFile-complete" + id);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
electronSocket.emit("webContents-loadFile-error" + id, error);
|
||||
});
|
||||
});
|
||||
socket.on("webContents-isLoading", (id) => {
|
||||
electronSocket.emit("webContents-isLoading-completed", getWindowById(id).webContents.isLoading());
|
||||
});
|
||||
socket.on("webContents-isLoadingMainFrame", (id) => {
|
||||
electronSocket.emit("webContents-isLoadingMainFrame-completed", getWindowById(id).webContents.isLoadingMainFrame());
|
||||
});
|
||||
socket.on("webContents-isWaitingForResponse", (id) => {
|
||||
electronSocket.emit("webContents-isWaitingForResponse-completed", getWindowById(id).webContents.isWaitingForResponse());
|
||||
});
|
||||
socket.on("webContents-reload", (id) => {
|
||||
getWindowById(id).webContents.reload();
|
||||
});
|
||||
socket.on("webContents-reloadIgnoringCache", (id) => {
|
||||
getWindowById(id).webContents.reloadIgnoringCache();
|
||||
});
|
||||
socket.on("webContents-stop", (id) => {
|
||||
getWindowById(id).webContents.stop();
|
||||
});
|
||||
socket.on("webContents-insertCSS", (id, isBrowserWindow, path) => {
|
||||
if (isBrowserWindow) {
|
||||
const browserWindow = getWindowById(id);
|
||||
@@ -330,6 +395,14 @@ module.exports = (socket) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.on("webContents-insertCSSText", async (id, css, cssOrigin) => {
|
||||
const key = await getWindowById(id).webContents.insertCSS(css, cssOrigin ? { cssOrigin } : undefined);
|
||||
electronSocket.emit("webContents-insertCSSText-completed" + id, key);
|
||||
});
|
||||
socket.on("webContents-removeInsertedCSS", async (id, key) => {
|
||||
await getWindowById(id).webContents.removeInsertedCSS(key);
|
||||
electronSocket.emit("webContents-removeInsertedCSS-completed" + id);
|
||||
});
|
||||
socket.on("webContents-session-getAllExtensions", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
const extensionsList = browserWindow.webContents.session.getAllExtensions();
|
||||
@@ -403,6 +476,65 @@ module.exports = (socket) => {
|
||||
socket.on("webContents-setUserAgent", (id, userAgent) => {
|
||||
getWindowById(id).webContents.setUserAgent(userAgent);
|
||||
});
|
||||
socket.on("webContents-undo", (id) => {
|
||||
getWindowById(id).webContents.undo();
|
||||
});
|
||||
socket.on("webContents-redo", (id) => {
|
||||
getWindowById(id).webContents.redo();
|
||||
});
|
||||
socket.on("webContents-cut", (id) => {
|
||||
getWindowById(id).webContents.cut();
|
||||
});
|
||||
socket.on("webContents-copy", (id) => {
|
||||
getWindowById(id).webContents.copy();
|
||||
});
|
||||
socket.on("webContents-copyImageAt", (id, x, y) => {
|
||||
getWindowById(id).webContents.copyImageAt(x, y);
|
||||
});
|
||||
socket.on("webContents-paste", (id) => {
|
||||
getWindowById(id).webContents.paste();
|
||||
});
|
||||
socket.on("webContents-pasteAndMatchStyle", (id) => {
|
||||
getWindowById(id).webContents.pasteAndMatchStyle();
|
||||
});
|
||||
socket.on("webContents-delete", (id) => {
|
||||
getWindowById(id).webContents.delete();
|
||||
});
|
||||
socket.on("webContents-selectAll", (id) => {
|
||||
getWindowById(id).webContents.selectAll();
|
||||
});
|
||||
socket.on("webContents-unselect", (id) => {
|
||||
getWindowById(id).webContents.unselect();
|
||||
});
|
||||
socket.on("webContents-adjustSelection", (id, options) => {
|
||||
getWindowById(id).webContents.adjustSelection(options ?? {});
|
||||
});
|
||||
socket.on("webContents-centerSelection", (id) => {
|
||||
getWindowById(id).webContents.centerSelection();
|
||||
});
|
||||
socket.on("webContents-scrollToTop", (id) => {
|
||||
getWindowById(id).webContents.scrollToTop();
|
||||
});
|
||||
socket.on("webContents-scrollToBottom", (id) => {
|
||||
getWindowById(id).webContents.scrollToBottom();
|
||||
});
|
||||
socket.on("webContents-insertText", async (id, text) => {
|
||||
await getWindowById(id).webContents.insertText(text);
|
||||
electronSocket.emit("webContents-insertText-completed" + id);
|
||||
});
|
||||
socket.on("webContents-replace", (id, text) => {
|
||||
getWindowById(id).webContents.replace(text);
|
||||
});
|
||||
socket.on("webContents-replaceMisspelling", (id, text) => {
|
||||
getWindowById(id).webContents.replaceMisspelling(text);
|
||||
});
|
||||
socket.on("webContents-findInPage", (id, text, options) => {
|
||||
const requestId = getWindowById(id).webContents.findInPage(text, options ?? undefined);
|
||||
electronSocket.emit("webContents-findInPage-completed" + id, requestId);
|
||||
});
|
||||
socket.on("webContents-stopFindInPage", (id, action) => {
|
||||
getWindowById(id).webContents.stopFindInPage(action);
|
||||
});
|
||||
function getWindowById(id) {
|
||||
if (id >= 1000) {
|
||||
return (0, browserView_1.browserViewMediateService)(id - 1000);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -103,6 +103,51 @@ export = (socket: Socket) => {
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("register-webContents-zoomChanged", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
|
||||
browserWindow.webContents.removeAllListeners("zoom-changed");
|
||||
browserWindow.webContents.on("zoom-changed", (_, zoomDirection) => {
|
||||
electronSocket.emit("webContents-zoomChanged" + id, zoomDirection);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("register-webContents-foundInPage", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
|
||||
browserWindow.webContents.removeAllListeners("found-in-page");
|
||||
browserWindow.webContents.on("found-in-page", (_, result) => {
|
||||
electronSocket.emit("webContents-foundInPage" + id, result);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("register-webContents-audioStateChanged", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
|
||||
browserWindow.webContents.removeAllListeners("audio-state-changed");
|
||||
browserWindow.webContents.on("audio-state-changed", (event) => {
|
||||
electronSocket.emit("webContents-audioStateChanged" + id, event.audible);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("register-webContents-mediaStartedPlaying", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
|
||||
browserWindow.webContents.removeAllListeners("media-started-playing");
|
||||
browserWindow.webContents.on("media-started-playing", () => {
|
||||
electronSocket.emit("webContents-mediaStartedPlaying" + id);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("register-webContents-mediaPaused", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
|
||||
browserWindow.webContents.removeAllListeners("media-paused");
|
||||
browserWindow.webContents.on("media-paused", () => {
|
||||
electronSocket.emit("webContents-mediaPaused" + id);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("webContents-openDevTools", (id, options) => {
|
||||
if (options) {
|
||||
getWindowById(id).webContents.openDevTools(options);
|
||||
@@ -409,6 +454,52 @@ export = (socket: Socket) => {
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("webContents-loadFile", (id, filePath, options) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
browserWindow.webContents
|
||||
.loadFile(filePath, options ?? undefined)
|
||||
.then(() => {
|
||||
electronSocket.emit("webContents-loadFile-complete" + id);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
electronSocket.emit("webContents-loadFile-error" + id, error);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("webContents-isLoading", (id) => {
|
||||
electronSocket.emit(
|
||||
"webContents-isLoading-completed",
|
||||
getWindowById(id).webContents.isLoading(),
|
||||
);
|
||||
});
|
||||
|
||||
socket.on("webContents-isLoadingMainFrame", (id) => {
|
||||
electronSocket.emit(
|
||||
"webContents-isLoadingMainFrame-completed",
|
||||
getWindowById(id).webContents.isLoadingMainFrame(),
|
||||
);
|
||||
});
|
||||
|
||||
socket.on("webContents-isWaitingForResponse", (id) => {
|
||||
electronSocket.emit(
|
||||
"webContents-isWaitingForResponse-completed",
|
||||
getWindowById(id).webContents.isWaitingForResponse(),
|
||||
);
|
||||
});
|
||||
|
||||
socket.on("webContents-reload", (id) => {
|
||||
getWindowById(id).webContents.reload();
|
||||
});
|
||||
|
||||
socket.on("webContents-reloadIgnoringCache", (id) => {
|
||||
getWindowById(id).webContents.reloadIgnoringCache();
|
||||
});
|
||||
|
||||
socket.on("webContents-stop", (id) => {
|
||||
getWindowById(id).webContents.stop();
|
||||
});
|
||||
|
||||
socket.on("webContents-insertCSS", (id, isBrowserWindow, path) => {
|
||||
if (isBrowserWindow) {
|
||||
const browserWindow = getWindowById(id);
|
||||
@@ -431,6 +522,19 @@ export = (socket: Socket) => {
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("webContents-insertCSSText", async (id, css, cssOrigin) => {
|
||||
const key = await getWindowById(id).webContents.insertCSS(
|
||||
css,
|
||||
cssOrigin ? { cssOrigin } : undefined,
|
||||
);
|
||||
electronSocket.emit("webContents-insertCSSText-completed" + id, key);
|
||||
});
|
||||
|
||||
socket.on("webContents-removeInsertedCSS", async (id, key) => {
|
||||
await getWindowById(id).webContents.removeInsertedCSS(key);
|
||||
electronSocket.emit("webContents-removeInsertedCSS-completed" + id);
|
||||
});
|
||||
|
||||
socket.on("webContents-session-getAllExtensions", (id) => {
|
||||
const browserWindow = getWindowById(id);
|
||||
const extensionsList = browserWindow.webContents.session.getAllExtensions();
|
||||
@@ -557,6 +661,87 @@ export = (socket: Socket) => {
|
||||
getWindowById(id).webContents.setUserAgent(userAgent);
|
||||
});
|
||||
|
||||
socket.on("webContents-undo", (id) => {
|
||||
getWindowById(id).webContents.undo();
|
||||
});
|
||||
|
||||
socket.on("webContents-redo", (id) => {
|
||||
getWindowById(id).webContents.redo();
|
||||
});
|
||||
|
||||
socket.on("webContents-cut", (id) => {
|
||||
getWindowById(id).webContents.cut();
|
||||
});
|
||||
|
||||
socket.on("webContents-copy", (id) => {
|
||||
getWindowById(id).webContents.copy();
|
||||
});
|
||||
|
||||
socket.on("webContents-copyImageAt", (id, x, y) => {
|
||||
getWindowById(id).webContents.copyImageAt(x, y);
|
||||
});
|
||||
|
||||
socket.on("webContents-paste", (id) => {
|
||||
getWindowById(id).webContents.paste();
|
||||
});
|
||||
|
||||
socket.on("webContents-pasteAndMatchStyle", (id) => {
|
||||
getWindowById(id).webContents.pasteAndMatchStyle();
|
||||
});
|
||||
|
||||
socket.on("webContents-delete", (id) => {
|
||||
getWindowById(id).webContents.delete();
|
||||
});
|
||||
|
||||
socket.on("webContents-selectAll", (id) => {
|
||||
getWindowById(id).webContents.selectAll();
|
||||
});
|
||||
|
||||
socket.on("webContents-unselect", (id) => {
|
||||
getWindowById(id).webContents.unselect();
|
||||
});
|
||||
|
||||
socket.on("webContents-adjustSelection", (id, options) => {
|
||||
getWindowById(id).webContents.adjustSelection(options ?? {});
|
||||
});
|
||||
|
||||
socket.on("webContents-centerSelection", (id) => {
|
||||
getWindowById(id).webContents.centerSelection();
|
||||
});
|
||||
|
||||
socket.on("webContents-scrollToTop", (id) => {
|
||||
getWindowById(id).webContents.scrollToTop();
|
||||
});
|
||||
|
||||
socket.on("webContents-scrollToBottom", (id) => {
|
||||
getWindowById(id).webContents.scrollToBottom();
|
||||
});
|
||||
|
||||
socket.on("webContents-insertText", async (id, text) => {
|
||||
await getWindowById(id).webContents.insertText(text);
|
||||
electronSocket.emit("webContents-insertText-completed" + id);
|
||||
});
|
||||
|
||||
socket.on("webContents-replace", (id, text) => {
|
||||
getWindowById(id).webContents.replace(text);
|
||||
});
|
||||
|
||||
socket.on("webContents-replaceMisspelling", (id, text) => {
|
||||
getWindowById(id).webContents.replaceMisspelling(text);
|
||||
});
|
||||
|
||||
socket.on("webContents-findInPage", (id, text, options) => {
|
||||
const requestId = getWindowById(id).webContents.findInPage(
|
||||
text,
|
||||
options ?? undefined,
|
||||
);
|
||||
electronSocket.emit("webContents-findInPage-completed" + id, requestId);
|
||||
});
|
||||
|
||||
socket.on("webContents-stopFindInPage", (id, action) => {
|
||||
getWindowById(id).webContents.stopFindInPage(action);
|
||||
});
|
||||
|
||||
function getWindowById(
|
||||
id: number,
|
||||
): Electron.BrowserWindow | Electron.BrowserView {
|
||||
|
||||
@@ -29,32 +29,11 @@ let electronforcedport;
|
||||
let electronUrl;
|
||||
let authToken = randomUUID().split('-').join('');
|
||||
|
||||
if (app.commandLine.hasSwitch('manifest')) {
|
||||
manifestJsonFileName = app.commandLine.getSwitchValue('manifest');
|
||||
}
|
||||
|
||||
if (app.commandLine.hasSwitch('unpackedelectron')) {
|
||||
unpackedelectron = true;
|
||||
}
|
||||
else if (app.commandLine.hasSwitch('unpackeddotnet')) {
|
||||
unpackeddotnet = true;
|
||||
}
|
||||
else if (app.commandLine.hasSwitch('dotnetpacked')) {
|
||||
dotnetpacked = true;
|
||||
}
|
||||
|
||||
if (app.commandLine.hasSwitch('electronforcedport')) {
|
||||
electronforcedport = +app.commandLine.getSwitchValue('electronforcedport');
|
||||
}
|
||||
|
||||
// Store in global for access by browser windows
|
||||
global.authToken = authToken;
|
||||
|
||||
if (app.commandLine.hasSwitch('electronurl')) {
|
||||
electronUrl = app.commandLine.getSwitchValue('electronurl');
|
||||
}
|
||||
|
||||
// Custom startup hook: look for custom_main.js and invoke its onStartup(host) if present.
|
||||
// Runs before any command line switch is evaluated so the hook can still modify them.
|
||||
// If the hook returns false, abort Electron startup.
|
||||
try {
|
||||
const fs = require('fs');
|
||||
@@ -77,6 +56,28 @@ try {
|
||||
console.error('Error while executing custom_main.js:', err);
|
||||
}
|
||||
|
||||
if (app.commandLine.hasSwitch('manifest')) {
|
||||
manifestJsonFileName = app.commandLine.getSwitchValue('manifest');
|
||||
}
|
||||
|
||||
if (app.commandLine.hasSwitch('unpackedelectron')) {
|
||||
unpackedelectron = true;
|
||||
}
|
||||
else if (app.commandLine.hasSwitch('unpackeddotnet')) {
|
||||
unpackeddotnet = true;
|
||||
}
|
||||
else if (app.commandLine.hasSwitch('dotnetpacked')) {
|
||||
dotnetpacked = true;
|
||||
}
|
||||
|
||||
if (app.commandLine.hasSwitch('electronforcedport')) {
|
||||
electronforcedport = +app.commandLine.getSwitchValue('electronforcedport');
|
||||
}
|
||||
|
||||
if (app.commandLine.hasSwitch('electronurl')) {
|
||||
electronUrl = app.commandLine.getSwitchValue('electronurl');
|
||||
}
|
||||
|
||||
const currentPath = __dirname;
|
||||
let currentBinPath = path.join(currentPath.replace('app.asar', ''), 'bin');
|
||||
let manifestJsonFilePath = path.join(currentPath, manifestJsonFileName);
|
||||
@@ -103,9 +104,44 @@ app.on('will-finish-launching', () => {
|
||||
|
||||
const manifestJsonFile = require(manifestJsonFilePath);
|
||||
|
||||
// Brings the app back to the foreground: focuses an already existing window or - if
|
||||
// all windows have been closed (which keeps the app alive on macOS) - recreates the
|
||||
// main window.
|
||||
function activateApp() {
|
||||
const windows = BrowserWindow.getAllWindows().filter((window) => !window.isDestroyed());
|
||||
|
||||
if (!windows.length) {
|
||||
return typeof global.recreateMainWindow === 'function' && global.recreateMainWindow();
|
||||
}
|
||||
|
||||
const target = windows.find((window) => window.isVisible()) || windows[0];
|
||||
|
||||
if (target.isMinimized()) {
|
||||
target.restore();
|
||||
}
|
||||
|
||||
if (!target.isVisible()) {
|
||||
target.show();
|
||||
}
|
||||
|
||||
target.focus();
|
||||
|
||||
if (platform() === 'darwin') {
|
||||
// On macOS focusing a window does not necessarily bring the app itself to the front
|
||||
app.focus({ steal: true });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (manifestJsonFile.singleInstance) {
|
||||
const mainInstance = app.requestSingleInstanceLock();
|
||||
app.on('second-instance', (events, args = []) => {
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
// Another instance already owns the lock. Exit right away so that neither the
|
||||
// socket bridge nor the .NET backend process of this instance is started.
|
||||
app.exit(0);
|
||||
}
|
||||
|
||||
app.on('second-instance', (event, args = []) => {
|
||||
args.forEach((parameter) => {
|
||||
const words = parameter.split('=');
|
||||
|
||||
@@ -116,20 +152,16 @@ if (manifestJsonFile.singleInstance) {
|
||||
}
|
||||
});
|
||||
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
if (windows.length) {
|
||||
if (windows[0].isMinimized()) {
|
||||
windows[0].restore();
|
||||
}
|
||||
windows[0].focus();
|
||||
}
|
||||
activateApp();
|
||||
});
|
||||
|
||||
if (!mainInstance) {
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
|
||||
// On macOS launching an already running app (or clicking its dock icon) does not
|
||||
// start a second instance - the 'activate' event is raised instead.
|
||||
app.on('activate', () => {
|
||||
activateApp();
|
||||
});
|
||||
|
||||
// Collect user supplied command line args (excluding those handled by Electron host itself)
|
||||
function getForwardedArgs() {
|
||||
const skipSwitches = new Set(['unpackedelectron', 'unpackeddotnet', 'dotnetpacked']);
|
||||
@@ -144,6 +176,7 @@ function getForwardedArgs() {
|
||||
if (cleaned.startsWith('remote-debugging-port')) return false;
|
||||
// We add /electronPort ourselves later
|
||||
if (cleaned.startsWith('electronPort=')) return false;
|
||||
if (cleaned.startsWith('electronHost=')) return false;
|
||||
if (cleaned.startsWith('electronWebPort=')) return false;
|
||||
return true;
|
||||
});
|
||||
@@ -264,7 +297,6 @@ function startSocketApiBridge(port) {
|
||||
// otherwise the Windows Firewall will be triggered
|
||||
console.debug('Electron Socket: starting...');
|
||||
server = createServer();
|
||||
const host = !port ? '127.0.0.1' : 'localhost';
|
||||
let hostHook;
|
||||
io = new Server({
|
||||
pingTimeout: 60000, // in ms, default is 5000
|
||||
@@ -272,16 +304,35 @@ function startSocketApiBridge(port) {
|
||||
});
|
||||
io.attach(server);
|
||||
|
||||
server.listen(port, host);
|
||||
// Never bind to the 'localhost' hostname: it may resolve to ::1 and 127.0.0.1 in any
|
||||
// order, so server and client can end up on different stacks - which costs a failed
|
||||
// connection attempt (or a DNS lookup) on every startup.
|
||||
const hostCandidates = ['127.0.0.1', '::1'];
|
||||
let hostIndex = 0;
|
||||
|
||||
server.on('error', (error) => {
|
||||
const isUnavailable = error.code === 'EADDRNOTAVAIL' || error.code === 'EAFNOSUPPORT' || error.code === 'EINVAL';
|
||||
|
||||
if (isUnavailable && hostIndex + 1 < hostCandidates.length) {
|
||||
console.warn(`Electron Socket: cannot bind to ${hostCandidates[hostIndex]} (${error.code}), falling back to ${hostCandidates[hostIndex + 1]}.`);
|
||||
hostIndex++;
|
||||
server.listen(port, hostCandidates[hostIndex]);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Electron Socket: ' + error.message);
|
||||
});
|
||||
|
||||
server.listen(port, hostCandidates[hostIndex]);
|
||||
server.on('listening', function () {
|
||||
const addr = server.address();
|
||||
console.info(`Electron Socket: listening on port ${addr.port} at ${addr.address} using ${authToken}`);
|
||||
|
||||
// Now that socket connection is established, we can guarantee port will not be open for portscanner
|
||||
if (unpackedelectron) {
|
||||
startAspCoreBackendUnpackaged(addr.port);
|
||||
startAspCoreBackendUnpackaged(addr.port, addr.address);
|
||||
} else if (!unpackeddotnet && !dotnetpacked) {
|
||||
startAspCoreBackend(addr.port);
|
||||
startAspCoreBackend(addr.port, addr.address);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -385,7 +436,7 @@ function startSocketApiBridge(port) {
|
||||
});
|
||||
}
|
||||
|
||||
function startAspCoreBackend(electronPort) {
|
||||
function startAspCoreBackend(electronPort, electronHost) {
|
||||
startBackend();
|
||||
|
||||
function startBackend() {
|
||||
@@ -394,6 +445,7 @@ function startAspCoreBackend(electronPort) {
|
||||
const parameters = [
|
||||
envParam,
|
||||
`/electronPort=${electronPort}`,
|
||||
`/electronHost=${electronHost}`,
|
||||
`/electronPID=${process.pid}`,
|
||||
`/electronAuthToken=${authToken}`,
|
||||
// forward user supplied args (avoid duplicate environment)
|
||||
@@ -416,7 +468,7 @@ function startAspCoreBackend(electronPort) {
|
||||
}
|
||||
}
|
||||
|
||||
function startAspCoreBackendUnpackaged(electronPort) {
|
||||
function startAspCoreBackendUnpackaged(electronPort, electronHost) {
|
||||
startBackend();
|
||||
|
||||
function startBackend() {
|
||||
@@ -425,6 +477,7 @@ function startAspCoreBackendUnpackaged(electronPort) {
|
||||
const parameters = [
|
||||
envParam,
|
||||
`/electronPort=${electronPort}`,
|
||||
`/electronHost=${electronHost}`,
|
||||
`/electronPID=${process.pid}`,
|
||||
`/electronAuthToken=${authToken}`,
|
||||
...forwardedArgs.filter(a => !(envParam && a.startsWith('--environment=')))
|
||||
|
||||
20
src/ElectronNET.Host/package-lock.json
generated
20
src/ElectronNET.Host/package-lock.json
generated
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18",
|
||||
"electron": "^30.0.3",
|
||||
"electron": "^39.8.10",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
@@ -762,15 +762,15 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "30.5.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-30.5.1.tgz",
|
||||
"integrity": "sha512-AhL7+mZ8Lg14iaNfoYTkXQ2qee8mmsQyllKdqxlpv/zrKgfxz6jNVtcRRbQtLxtF8yzcImWdfTQROpYiPumdbw==",
|
||||
"version": "39.8.10",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-39.8.10.tgz",
|
||||
"integrity": "sha512-zbYtGPYUI7PzqLAzkk21Rk6j67WN0hxn0Mq/njErZo1d0HSf33is4f8ICI5fMLy5vYe0JtCtM5sYunNOaochSQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/node": "^22.7.7",
|
||||
"extract-zip": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
@@ -847,16 +847,6 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron/node_modules/@types/node": {
|
||||
"version": "20.19.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
|
||||
"integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18",
|
||||
"electron": "^30.0.3",
|
||||
"electron": "^39.8.10",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ElectronNET.IntegrationTests.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the Electron clean targets.
|
||||
/// Covers GitHub issue #1096: cleaning must not require a restore, so the clean targets
|
||||
/// must not depend on targets that need the assets file (NETSDK1004).
|
||||
/// </summary>
|
||||
public class ElectronCleanTargetsTests
|
||||
{
|
||||
private static readonly string CorePropsPath = FindBuildFile("src/ElectronNET/build/ElectronNET.Core.props", "ElectronNET/build/ElectronNET.Core.props");
|
||||
private static readonly string CoreTargetsPath = FindBuildFile("src/ElectronNET/build/ElectronNET.Core.targets", "ElectronNET/build/ElectronNET.Core.targets");
|
||||
|
||||
[Fact]
|
||||
public async Task ElectronCleanTargets_WithoutRestore_ShouldSucceed()
|
||||
{
|
||||
var tempDir = CreateTempProjectDirectory();
|
||||
|
||||
try
|
||||
{
|
||||
await WriteMinimalCsprojAsync(tempDir);
|
||||
|
||||
var (exitCode, output) = await RunDotnetMsBuildAsync(tempDir, "Clean");
|
||||
|
||||
output.Should().NotContain("NETSDK1004",
|
||||
$"cleaning must not require the assets file of a previous restore. Full output:\n{output}");
|
||||
|
||||
exitCode.Should().Be(0,
|
||||
$"cleaning an unrestored project must succeed. Full output:\n{output}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FindBuildFile(string relativeFromRepoRoot, string relativeFromSrc)
|
||||
{
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (dir != null)
|
||||
{
|
||||
var fromRepoRoot = Path.Combine(dir.FullName, relativeFromRepoRoot);
|
||||
if (File.Exists(fromRepoRoot))
|
||||
{
|
||||
return Path.GetFullPath(fromRepoRoot);
|
||||
}
|
||||
|
||||
var fromSrc = Path.Combine(dir.FullName, relativeFromSrc);
|
||||
if (File.Exists(fromSrc))
|
||||
{
|
||||
return Path.GetFullPath(fromSrc);
|
||||
}
|
||||
|
||||
dir = dir.Parent;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException(
|
||||
$"Could not locate '{relativeFromRepoRoot}' by walking up from '{AppContext.BaseDirectory}'.");
|
||||
}
|
||||
|
||||
private static string CreateTempProjectDirectory()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), $"electron-net-clean-test-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempDir);
|
||||
Directory.CreateDirectory(Path.Combine(tempDir, "Properties"));
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
private static Task WriteMinimalCsprojAsync(string tempDir)
|
||||
{
|
||||
var propsPathEscaped = CorePropsPath.Replace("'", "'");
|
||||
var targetsPathEscaped = CoreTargetsPath.Replace("'", "'");
|
||||
|
||||
return File.WriteAllTextAsync(
|
||||
Path.Combine(tempDir, "TestApp.csproj"),
|
||||
$$"""
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="{{propsPathEscaped}}" />
|
||||
|
||||
<Import Project="{{targetsPathEscaped}}" />
|
||||
</Project>
|
||||
""");
|
||||
}
|
||||
|
||||
private static async Task<(int ExitCode, string Output)> RunDotnetMsBuildAsync(string workingDirectory, string target)
|
||||
{
|
||||
// Deliberately without /restore - that is what issue #1096 is about.
|
||||
var psi = new ProcessStartInfo("dotnet", $"msbuild TestApp.csproj --nologo -v:minimal /t:{target}")
|
||||
{
|
||||
WorkingDirectory = workingDirectory,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
};
|
||||
|
||||
using var process = Process.Start(psi)!;
|
||||
var stdOut = await process.StandardOutput.ReadToEndAsync();
|
||||
var stdErr = await process.StandardError.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
return (process.ExitCode, stdOut + stdErr);
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,111 @@ public class MigrationChecksTargetsTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MigrationChecksTargets_BuildWithRootPackageLockJson_ShouldNotEmitELECTRON001Warning()
|
||||
{
|
||||
// A root package.json is validated by ELECTRON008/ELECTRON009 and therefore allowed.
|
||||
// Its accompanying package-lock.json must not be reported by ELECTRON001.
|
||||
|
||||
var tempDir = CreateTempProjectDirectory();
|
||||
try
|
||||
{
|
||||
await File.WriteAllTextAsync(
|
||||
Path.Combine(tempDir, "package.json"),
|
||||
"""{ "devDependencies": { "vite": "^5.0.0" } }""");
|
||||
await File.WriteAllTextAsync(
|
||||
Path.Combine(tempDir, "package-lock.json"),
|
||||
"""{ "lockfileVersion": 3 }""");
|
||||
|
||||
await WriteMinimalCsprojAsync(tempDir);
|
||||
|
||||
var (exitCode, output) = await RunDotnetBuildAsync(tempDir);
|
||||
|
||||
exitCode.Should().Be(0, $"Full build output:\n{output}");
|
||||
|
||||
output.Should().NotContain(
|
||||
"ELECTRON001",
|
||||
$"a root package-lock.json belongs to the allowed root package.json. " +
|
||||
$"Full build output:\n{output}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MigrationChecksTargets_BuildWithIncompleteHostHookFolder_ShouldEmitELECTRON010AndELECTRON011Warnings()
|
||||
{
|
||||
// Host hook TypeScript sources are silently ignored when package.json/tsconfig.json
|
||||
// are missing, so the migration checks must point that out.
|
||||
|
||||
var tempDir = CreateTempProjectDirectory();
|
||||
try
|
||||
{
|
||||
var hookDir = Path.Combine(tempDir, "ElectronHostHook");
|
||||
Directory.CreateDirectory(hookDir);
|
||||
await File.WriteAllTextAsync(Path.Combine(hookDir, "index.ts"), "export class HookService {}");
|
||||
|
||||
await WriteMinimalCsprojAsync(tempDir);
|
||||
|
||||
var (exitCode, output) = await RunDotnetBuildAsync(tempDir);
|
||||
|
||||
exitCode.Should().Be(0, $"Full build output:\n{output}");
|
||||
|
||||
output.Should().Contain(
|
||||
"ELECTRON010",
|
||||
$"the ElectronHostHook folder has TypeScript files but no package.json. " +
|
||||
$"Full build output:\n{output}");
|
||||
output.Should().Contain(
|
||||
"ELECTRON011",
|
||||
$"the ElectronHostHook folder has TypeScript files but no tsconfig.json. " +
|
||||
$"Full build output:\n{output}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MigrationChecksTargets_BuildWithConsoleProfileContainingProjectGuid_ShouldEmitELECTRON007Warning()
|
||||
{
|
||||
// ProjectGuid is an ASP.NET publish profile property and must be reported for
|
||||
// non-web projects, just like WebPublishMethod.
|
||||
|
||||
var tempDir = CreateTempProjectDirectory();
|
||||
try
|
||||
{
|
||||
var profilesDir = Path.Combine(tempDir, "Properties", "PublishProfiles");
|
||||
Directory.CreateDirectory(profilesDir);
|
||||
await File.WriteAllTextAsync(
|
||||
Path.Combine(profilesDir, "FolderProfile.pubxml"),
|
||||
"""
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>00000000-0000-0000-0000-000000000000</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
""");
|
||||
|
||||
await WriteMinimalCsprojAsync(tempDir);
|
||||
|
||||
var (exitCode, output) = await RunDotnetBuildAsync(tempDir);
|
||||
|
||||
exitCode.Should().Be(0, $"Full build output:\n{output}");
|
||||
|
||||
output.Should().Contain(
|
||||
"ELECTRON007",
|
||||
$"a console project must not use an ASP.NET publish profile. " +
|
||||
$"Full build output:\n{output}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@@ -108,13 +108,19 @@ namespace ElectronNET.IntegrationTests.Tests
|
||||
await Task.Delay(500.ms());
|
||||
|
||||
var ok = await window.WebContents.GetZoomLevelAsync();
|
||||
ok.Should().Be(0);
|
||||
ok.Should().Be(0.0);
|
||||
|
||||
window.WebContents.SetZoomLevel(2);
|
||||
await Task.Delay(500.ms());
|
||||
|
||||
ok = await window.WebContents.GetZoomLevelAsync();
|
||||
ok.Should().Be(2);
|
||||
ok.Should().Be(2.0);
|
||||
|
||||
window.WebContents.SetZoomLevel(0.5);
|
||||
await Task.Delay(500.ms());
|
||||
|
||||
ok = await window.WebContents.GetZoomLevelAsync();
|
||||
ok.Should().BeApproximately(0.5, 0.001);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -193,5 +199,94 @@ namespace ElectronNET.IntegrationTests.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task InsertAndRemoveCSS_check()
|
||||
{
|
||||
var wc = this.MainWindow.WebContents;
|
||||
await wc.LoadURLAsync("data:text/html,<html><body><p>CSS Test</p></body></html>");
|
||||
|
||||
var key = await wc.InsertCSSAsync("body { background-color: rgb(255, 0, 0); }");
|
||||
key.Should().NotBeNullOrEmpty();
|
||||
|
||||
var color = await wc.ExecuteJavaScriptAsync<string>("getComputedStyle(document.body).backgroundColor");
|
||||
color.Should().Be("rgb(255, 0, 0)");
|
||||
|
||||
await wc.RemoveInsertedCSSAsync(key);
|
||||
|
||||
color = await wc.ExecuteJavaScriptAsync<string>("getComputedStyle(document.body).backgroundColor");
|
||||
color.Should().NotBe("rgb(255, 0, 0)");
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task EditCommands_check()
|
||||
{
|
||||
var wc = this.MainWindow.WebContents;
|
||||
await wc.LoadURLAsync("data:text/html,<html><body><input id='in' autofocus /></body></html>");
|
||||
await Task.Delay(500.ms());
|
||||
|
||||
await wc.InsertTextAsync("Electron.NET");
|
||||
await Task.Delay(500.ms());
|
||||
|
||||
var value = await wc.ExecuteJavaScriptAsync<string>("document.getElementById('in').value");
|
||||
value.Should().Be("Electron.NET");
|
||||
|
||||
wc.SelectAll();
|
||||
wc.Delete();
|
||||
await Task.Delay(500.ms());
|
||||
|
||||
value = await wc.ExecuteJavaScriptAsync<string>("document.getElementById('in').value");
|
||||
value.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task FindInPage_check()
|
||||
{
|
||||
var wc = this.MainWindow.WebContents;
|
||||
await wc.LoadURLAsync("data:text/html,<html><body><p>needle in a haystack</p></body></html>");
|
||||
await Task.Delay(500.ms());
|
||||
|
||||
var tcs = new TaskCompletionSource<FoundInPageResult>();
|
||||
|
||||
void OnFound(FoundInPageResult result) => tcs.TrySetResult(result);
|
||||
|
||||
wc.OnFoundInPage += OnFound;
|
||||
|
||||
try
|
||||
{
|
||||
var requestId = await wc.FindInPageAsync("needle");
|
||||
requestId.Should().BeGreaterThan(0);
|
||||
|
||||
var completed = await Task.WhenAny(tcs.Task, Task.Delay(5.seconds()));
|
||||
completed.Should().Be(tcs.Task);
|
||||
|
||||
var result = await tcs.Task;
|
||||
result.RequestId.Should().Be(requestId);
|
||||
result.Matches.Should().BeGreaterThan(0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
wc.OnFoundInPage -= OnFound;
|
||||
wc.StopFindInPage(StopFindInPageAction.ClearSelection);
|
||||
}
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task Reload_and_loading_state_check()
|
||||
{
|
||||
var wc = this.MainWindow.WebContents;
|
||||
await wc.LoadURLAsync("data:text/html,<html><body><h1>Reload Test</h1></body></html>");
|
||||
await Task.Delay(500.ms());
|
||||
|
||||
(await wc.IsLoadingAsync()).Should().BeFalse();
|
||||
(await wc.IsLoadingMainFrameAsync()).Should().BeFalse();
|
||||
(await wc.IsWaitingForResponseAsync()).Should().BeFalse();
|
||||
|
||||
wc.Reload();
|
||||
await Task.Delay(1.seconds());
|
||||
|
||||
var title = await wc.ExecuteJavaScriptAsync<string>("document.querySelector('h1').textContent");
|
||||
title.Should().Be("Reload Test");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "!Config", "!Config", "{02EA
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.AspNet", "ElectronNET.AspNet\ElectronNET.AspNet.csproj", "{DD10D21A-D131-1D9C-33F9-406046E0C5B0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.Templates", "ElectronNET.Templates\ElectronNET.Templates.csproj", "{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{1BB6F634-2831-4496-83A6-BC6761DCEC8D}"
|
||||
EndProject
|
||||
Global
|
||||
@@ -45,6 +47,10 @@ Global
|
||||
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -54,6 +60,7 @@ Global
|
||||
{829FC339-4785-4229-ABA5-53ADB544DA00} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
|
||||
{8860606D-6847-F22A-5AED-DF4E0984DD24} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
|
||||
{DD10D21A-D131-1D9C-33F9-406046E0C5B0} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {81A62E71-9E04-4EFE-AD5C-23165375F8EF}
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.6.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.6.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
65
src/ElectronNET.Templates/ElectronNET.Templates.csproj
Normal file
65
src/ElectronNET.Templates/ElectronNET.Templates.csproj
Normal file
@@ -0,0 +1,65 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageOutputPath>..\..\artifacts</PackageOutputPath>
|
||||
<PackageId>$(PackageNamePrefix).Templates</PackageId>
|
||||
<Title>$(PackageId)</Title>
|
||||
<Description>$(DescriptionFirstPart) This package contains the 'dotnet new' project templates.</Description>
|
||||
<PackageTags>electron aspnetcore blazor dotnet-new templates</PackageTags>
|
||||
<PackageType>Template</PackageType>
|
||||
<IncludeContentInPack>true</IncludeContentInPack>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<ContentTargetFolders>content</ContentTargetFolders>
|
||||
<!-- Template content contains dot-prefixed folders (.template.config) that must be packed -->
|
||||
<NoDefaultExcludes>true</NoDefaultExcludes>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
|
||||
<NoWarn>$(NoWarn);NU5128</NoWarn>
|
||||
<Nullable>disable</Nullable>
|
||||
<!-- The version pinned in the template project files on disk. It is replaced by the
|
||||
actual package version while packing, so a template always scaffolds an app that
|
||||
references the Electron.NET packages it was shipped with. -->
|
||||
<TemplatePlaceholderVersion>0.6.0</TemplatePlaceholderVersion>
|
||||
<TemplateProjectFile>templates\blazor\ElectronBlazorApp.csproj</TemplateProjectFile>
|
||||
<TemplateProjectPackagePath>content\templates\blazor\ElectronBlazorApp.csproj</TemplateProjectPackagePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**;$(TemplateProjectFile)" />
|
||||
<Compile Remove="**\*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\ElectronNET\PackageIcon.png" Pack="true" PackagePath="\" />
|
||||
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RewriteTemplateProjectVersion" BeforeTargets="_GetPackageFiles">
|
||||
<PropertyGroup>
|
||||
<_TemplateProjectSource>$(MSBuildProjectDirectory)\$(TemplateProjectFile)</_TemplateProjectSource>
|
||||
<_TemplateProjectTarget>$(IntermediateOutputPath)templates\blazor\ElectronBlazorApp.csproj</_TemplateProjectTarget>
|
||||
<_TemplateProjectContent>$([System.IO.File]::ReadAllText('$(_TemplateProjectSource)'))</_TemplateProjectContent>
|
||||
</PropertyGroup>
|
||||
|
||||
<Error Condition="!$(_TemplateProjectContent.Contains('Version="$(TemplatePlaceholderVersion)"'))"
|
||||
Text="'$(TemplateProjectFile)' contains no PackageReference pinned to $(TemplatePlaceholderVersion), so the package version cannot be injected. Align the template's PackageReference versions with the TemplatePlaceholderVersion property." />
|
||||
|
||||
<PropertyGroup>
|
||||
<_TemplateProjectContent>$(_TemplateProjectContent.Replace('Version="$(TemplatePlaceholderVersion)"', 'Version="$(Version)"'))</_TemplateProjectContent>
|
||||
</PropertyGroup>
|
||||
|
||||
<WriteLinesToFile File="$(_TemplateProjectTarget)"
|
||||
Lines="$([MSBuild]::Escape($(_TemplateProjectContent)))"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="true" />
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(_TemplateProjectTarget)" Pack="true" PackagePath="$(TemplateProjectPackagePath)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/dotnetcli.host",
|
||||
"symbolInfo": {
|
||||
"Framework": {
|
||||
"longName": "framework",
|
||||
"shortName": "f"
|
||||
},
|
||||
"ElectronVersion": {
|
||||
"longName": "electron-version",
|
||||
"shortName": "e"
|
||||
},
|
||||
"Port": {
|
||||
"longName": "port",
|
||||
"shortName": "p"
|
||||
},
|
||||
"skipRestore": {
|
||||
"longName": "no-restore",
|
||||
"shortName": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/template",
|
||||
"author": "Electron.NET",
|
||||
"classifications": [ "Blazor", "Desktop", "Electron", "Web" ],
|
||||
"identity": "ElectronNET.Core.Templates.Blazor.CSharp",
|
||||
"groupIdentity": "ElectronNET.Core.Templates.Blazor",
|
||||
"name": "Electron.NET Blazor App",
|
||||
"description": "A Blazor Server app hosted in an Electron shell, wired up with Electron.NET.",
|
||||
"shortName": "electron-blazor",
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
},
|
||||
"sourceName": "ElectronBlazorApp",
|
||||
"preferNameDirectory": true,
|
||||
"defaultName": "ElectronBlazorApp",
|
||||
"symbols": {
|
||||
"Framework": {
|
||||
"type": "parameter",
|
||||
"description": "The target framework for the project.",
|
||||
"datatype": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"choice": "net10.0",
|
||||
"description": "Target net10.0"
|
||||
},
|
||||
{
|
||||
"choice": "net8.0",
|
||||
"description": "Target net8.0"
|
||||
}
|
||||
],
|
||||
"replaces": "net8.0",
|
||||
"defaultValue": "net10.0"
|
||||
},
|
||||
"ElectronVersion": {
|
||||
"type": "parameter",
|
||||
"description": "The version of Electron to build the app against.",
|
||||
"datatype": "text",
|
||||
"replaces": "ELECTRON_VERSION",
|
||||
"defaultValue": "38.2.2"
|
||||
},
|
||||
"Port": {
|
||||
"type": "parameter",
|
||||
"description": "Port number to use for the ASP.NET server during development.",
|
||||
"datatype": "integer",
|
||||
"replaces": "8001",
|
||||
"defaultValue": "8001"
|
||||
},
|
||||
"skipRestore": {
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"description": "If specified, skips the automatic restore of the project on create.",
|
||||
"defaultValue": "false"
|
||||
}
|
||||
},
|
||||
"primaryOutputs": [
|
||||
{
|
||||
"path": "ElectronBlazorApp.csproj"
|
||||
}
|
||||
],
|
||||
"postActions": [
|
||||
{
|
||||
"id": "restore",
|
||||
"condition": "(!skipRestore)",
|
||||
"description": "Restore NuGet packages required by this project.",
|
||||
"manualInstructions": [
|
||||
{
|
||||
"text": "Run 'dotnet restore'"
|
||||
}
|
||||
],
|
||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
||||
"continueOnError": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<ResourcePreloader />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<ImportMap />
|
||||
<title>ElectronBlazorApp</title>
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="page">
|
||||
<NavMenu />
|
||||
|
||||
<main>
|
||||
@Body
|
||||
</main>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
<nav class="nav-menu">
|
||||
<NavLink href="" Match="NavLinkMatch.All">Home</NavLink>
|
||||
<NavLink href="counter">Counter</NavLink>
|
||||
</nav>
|
||||
@@ -0,0 +1,19 @@
|
||||
@page "/counter"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Counter</PageTitle>
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p role="status">Current count: @currentCount</p>
|
||||
|
||||
<button class="btn" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
@page "/"
|
||||
@rendermode InteractiveServer
|
||||
@using ElectronNET.API
|
||||
@using ElectronNET.API.Entities
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
<h1>Hello from Electron.NET!</h1>
|
||||
|
||||
<p>
|
||||
This Blazor app is served by ASP.NET Core and displayed in an Electron window.
|
||||
Everything in the <code>ElectronNET.API</code> namespace is available from your components.
|
||||
</p>
|
||||
|
||||
<button class="btn" @onclick="ShowNotification">Show a native notification</button>
|
||||
|
||||
<p role="status">@status</p>
|
||||
|
||||
@code {
|
||||
private string? status;
|
||||
|
||||
private void ShowNotification()
|
||||
{
|
||||
if (HybridSupport.IsElectronActive)
|
||||
{
|
||||
Electron.Notification.Show(new NotificationOptions("ElectronBlazorApp", "Hello from Blazor!"));
|
||||
status = "Notification sent.";
|
||||
}
|
||||
else
|
||||
{
|
||||
status = "Not running inside Electron.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
@@ -0,0 +1,9 @@
|
||||
@using System.Net.Http
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.JSInterop
|
||||
@using ElectronBlazorApp
|
||||
@using ElectronBlazorApp.Components
|
||||
@using ElectronBlazorApp.Components.Layout
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!-- Electron hosts the app out of process -->
|
||||
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="ElectronNetCommon">
|
||||
<ElectronPackageId>electron-blazor-app</ElectronPackageId>
|
||||
<Title>ElectronBlazorApp</Title>
|
||||
<Description>A Blazor app running in Electron.</Description>
|
||||
<Version>1.0.0</Version>
|
||||
<Company>My Company</Company>
|
||||
<Copyright>Copyright © $(Company)</Copyright>
|
||||
<ElectronVersion>ELECTRON_VERSION</ElectronVersion>
|
||||
<ElectronSingleInstance>true</ElectronSingleInstance>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.6.0" />
|
||||
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
48
src/ElectronNET.Templates/templates/blazor/Program.cs
Normal file
48
src/ElectronNET.Templates/templates/blazor/Program.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using ElectronNET.API;
|
||||
using ElectronNET.API.Entities;
|
||||
using ElectronNET.AspNet.Middleware;
|
||||
using ElectronNET.AspNet.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
builder.Services.AddSingleton<IElectronAuthenticationService, ElectronAuthenticationService>();
|
||||
|
||||
builder.Services.AddElectron();
|
||||
|
||||
// Starts the Electron shell and invokes ElectronAppReady once it is up.
|
||||
builder.UseElectron(args, ElectronAppReady);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseMiddleware<ElectronAuthenticationMiddleware>();
|
||||
app.UseRouting();
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseAntiforgery();
|
||||
|
||||
// Fully qualified because ElectronNET.API also defines an 'App' type.
|
||||
app.MapRazorComponents<ElectronBlazorApp.Components.App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.Run();
|
||||
|
||||
static async Task ElectronAppReady()
|
||||
{
|
||||
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
|
||||
{
|
||||
IsRunningBlazor = true,
|
||||
Width = 1152,
|
||||
Height = 940,
|
||||
Show = false,
|
||||
});
|
||||
|
||||
window.OnReadyToShow += () => window.Show();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<DeleteExistingFiles>true</DeleteExistingFiles>
|
||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||
<LaunchSiteAfterPublish>false</LaunchSiteAfterPublish>
|
||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||
<PublishProvider>FileSystem</PublishProvider>
|
||||
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
|
||||
<WebPublishMethod>FileSystem</WebPublishMethod>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<DeleteExistingFiles>true</DeleteExistingFiles>
|
||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||
<LaunchSiteAfterPublish>false</LaunchSiteAfterPublish>
|
||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||
<PublishProvider>FileSystem</PublishProvider>
|
||||
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
|
||||
<WebPublishMethod>FileSystem</WebPublishMethod>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<DeleteExistingFiles>true</DeleteExistingFiles>
|
||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||
<LaunchSiteAfterPublish>false</LaunchSiteAfterPublish>
|
||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||
<PublishProvider>FileSystem</PublishProvider>
|
||||
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
|
||||
<WebPublishMethod>FileSystem</WebPublishMethod>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compression": "maximum",
|
||||
"linux": {
|
||||
"target": [ "AppImage" ],
|
||||
"executableArgs": [ "--no-sandbox" ]
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": "x64"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"target": [ "dmg" ]
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": true,
|
||||
"perMachine": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"profiles": {
|
||||
"ElectronBlazorApp": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:8001/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
49
src/ElectronNET.Templates/templates/blazor/wwwroot/app.css
Normal file
49
src/ElectronNET.Templates/templates/blazor/wwwroot/app.css
Normal file
@@ -0,0 +1,49 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background-color: #1b1b1f;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
color: #d7d7d7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-menu a.active {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
background-color: #1b6ec2;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #1861ac;
|
||||
}
|
||||
@@ -29,9 +29,6 @@ namespace ElectronNET.WebApp.Controllers
|
||||
|
||||
Electron.IpcMain.On("auto-update", async (args) =>
|
||||
{
|
||||
// Electron.NET CLI Command for deploy:
|
||||
// electronize build /target win /electron-params --publish=always
|
||||
|
||||
var currentVersion = await Electron.App.GetVersionAsync();
|
||||
var updateCheckResult = await Electron.AutoUpdater.CheckForUpdatesAndNotifyAsync();
|
||||
var availableVersion = updateCheckResult.UpdateInfo.Version;
|
||||
|
||||
@@ -204,29 +204,43 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||
"integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@humanfs/types": "^0.15.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/node": {
|
||||
"version": "0.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
||||
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
|
||||
"integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@humanfs/core": "^0.19.1",
|
||||
"@humanfs/core": "^0.19.2",
|
||||
"@humanfs/types": "^0.15.0",
|
||||
"@humanwhocodes/retry": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/types": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
|
||||
"integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/module-importer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||
@@ -526,9 +540,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
@@ -1337,9 +1351,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
|
||||
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
|
||||
"integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1797,9 +1811,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/readdir-glob/node_modules/brace-expansion": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
|
||||
@@ -75,8 +75,8 @@
|
||||
<ProjectReference Include="..\ElectronNET.AspNet\ElectronNET.AspNet.csproj" Condition="$(ElectronNetDevMode)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="ElectronNET.Core" Version="0.6.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.6.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</h1>
|
||||
<h3>The <code>HostHook</code> API allows you to execute your own JavaScript/TypeScript code on the host process.</h3>
|
||||
|
||||
<p>Create first an ElectronHostHook directory via the Electron.NET CLI, with the following command: <code>electronize add hosthook</code>.</p>
|
||||
<p>Create first an ElectronHostHook directory with a package.json and a JavaScript file.</p>
|
||||
<p>In this directory you can install any NPM packages and embed your own JavaScript/TypeScript code. It is also possible to respond to events from the host process.</p>
|
||||
<p>You find the sample source code in <code>Controllers\HostHookController.cs</code> and in the <code>ElectronHostHook</code> folder.</p>
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "!Config", "!Config", "{02EA
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.AspNet", "ElectronNET.AspNet\ElectronNET.AspNet.csproj", "{DD10D21A-D131-1D9C-33F9-406046E0C5B0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.Templates", "ElectronNET.Templates\ElectronNET.Templates.csproj", "{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.ConsoleApp", "ElectronNET.ConsoleApp\ElectronNET.ConsoleApp.csproj", "{EE38A326-5DE8-AF09-9EB9-DF0878938783}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6} = {1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6}
|
||||
@@ -99,6 +101,10 @@ Global
|
||||
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EE38A326-5DE8-AF09-9EB9-DF0878938783}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EE38A326-5DE8-AF09-9EB9-DF0878938783}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EE38A326-5DE8-AF09-9EB9-DF0878938783}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -131,6 +137,7 @@ Global
|
||||
{829FC339-4785-4229-ABA5-53ADB544DA00} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
|
||||
{8860606D-6847-F22A-5AED-DF4E0984DD24} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
|
||||
{DD10D21A-D131-1D9C-33F9-406046E0C5B0} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
|
||||
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
|
||||
{EE38A326-5DE8-AF09-9EB9-DF0878938783} = {EDCBFC49-2AEE-4BAF-9368-4409298C52FC}
|
||||
{015CB06B-6CAE-209F-E050-21C3ACA5FE9F} = {985D39A7-5216-4945-8167-2FD0CB387BD8}
|
||||
{06CAADC7-DE5B-47B4-AB2A-E9501459A2D1} = {D36CDFFD-3438-42E4-A7FF-88BA19AC4964}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Import Project="..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0;net10.0</TargetFrameworks>
|
||||
<TargetFrameworks>$(ElectronNetTargetFrameworks)</TargetFrameworks>
|
||||
<PackageOutputPath>..\..\artifacts</PackageOutputPath>
|
||||
<PackageId>$(PackageNamePrefix)</PackageId>
|
||||
<Title>$(PackageId)</Title>
|
||||
|
||||
Binary file not shown.
@@ -4,19 +4,21 @@
|
||||
<PropertyGroup Label="ElectronNetCommon">
|
||||
<ElectronVersion>30.4.0</ElectronVersion>
|
||||
<ElectronBuilderVersion>26.0</ElectronBuilderVersion>
|
||||
<!-- Allow ElectronRuntimeIdentifier to be overridden (explicitly or via RuntimeIdentifier) -->
|
||||
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(RuntimeIdentifier)' != ''">$(RuntimeIdentifier)</ElectronRuntimeIdentifier>
|
||||
<!-- When using the dotnet CLI (Core MSBuild), infer the current RID as a default -->
|
||||
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(MSBuildRuntimeType)' == 'Core'">$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier)</ElectronRuntimeIdentifier>
|
||||
<!-- Otherwise, Full MSBuild runs only on Windows, so infer a Windows RID from OSArchitecture -->
|
||||
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(MSBuildRuntimeType)' == 'Full'">win-$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLower())</ElectronRuntimeIdentifier>
|
||||
<!-- ElectronRuntimeIdentifier may be set explicitly here; it is otherwise resolved from
|
||||
RuntimeIdentifier (or the host) in ElectronResolveRuntimeIdentifier, which runs late
|
||||
enough to also see RIDs coming from the project file or a publish profile. -->
|
||||
<ElectronSingleInstance>true</ElectronSingleInstance>
|
||||
<ElectronSplashScreen></ElectronSplashScreen>
|
||||
<ElectronIcon></ElectronIcon>
|
||||
<PackageIcon></PackageIcon>
|
||||
<ElectronPackageId>$(MSBuildProjectName.Replace(".", "-").ToLower())</ElectronPackageId>
|
||||
<ElectronPackageId Condition="'$(ElectronPackageId)' == ''">$(MSBuildProjectName.Replace(".", "-").ToLower())</ElectronPackageId>
|
||||
<ElectronBuilderJson>electron-builder.json</ElectronBuilderJson>
|
||||
<Title>$(MSBuildProjectName)</Title>
|
||||
<Title Condition="'$(Title)' == ''">$(MSBuildProjectName)</Title>
|
||||
<!-- Location of the Electron binary relative to the .NET application directory of a packaged app.
|
||||
The default matches the standard layout, where the .NET app is placed in 'resources/bin'. -->
|
||||
<ElectronRootDir Condition="'$(ElectronRootDir)' == ''">../..</ElectronRootDir>
|
||||
<!-- Set to true to suppress the npm/npx invocations of the Electron build and publish targets. -->
|
||||
<ElectronSkipExecCommands Condition="'$(ElectronSkipExecCommands)' == ''">false</ElectronSkipExecCommands>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<PropertyGroup Condition="'$(ElectronExecutable)' == ''">
|
||||
<PropertyGroup Condition="'$(ElectronExecutableName)' == ''">
|
||||
<WinPrefix>win</WinPrefix>
|
||||
<ElectronExecutable Condition="'$(RuntimeIdentifier.StartsWith($(WinPrefix)))' == 'true'">$(Title)</ElectronExecutable>
|
||||
<ElectronExecutable Condition="'$(RuntimeIdentifier.StartsWith($(WinPrefix)))' != 'true'">$(ElectronPackageId)</ElectronExecutable>
|
||||
<ElectronExecutableName Condition="'$(RuntimeIdentifier.StartsWith($(WinPrefix)))' == 'true'">$(Title)</ElectronExecutableName>
|
||||
<ElectronExecutableName Condition="'$(RuntimeIdentifier.StartsWith($(WinPrefix)))' != 'true'">$(ElectronPackageId)</ElectronExecutableName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- The binary Electron.NET launches. It only differs from the packaged executable in advanced
|
||||
scenarios, e.g. when a build hook adds a launcher stub to the package. -->
|
||||
<ElectronExecutable Condition="'$(ElectronExecutable)' == ''">$(ElectronExecutableName)</ElectronExecutable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -19,6 +25,7 @@
|
||||
<ItemGroup>
|
||||
<AssemblyMetadata Include="ElectronExecutable" Value="$(ElectronExecutable)" />
|
||||
<AssemblyMetadata Include="ElectronVersion" Value="$(ElectronVersion)" />
|
||||
<AssemblyMetadata Include="ElectronRootDir" Value="$(ElectronRootDir)" />
|
||||
<AssemblyMetadata Include="RuntimeIdentifier" Value="$(RuntimeIdentifier)" />
|
||||
<AssemblyMetadata Include="ElectronSingleInstance" Value="$(ElectronSingleInstance)" />
|
||||
<AssemblyMetadata Include="Title" Value="$(Title)" />
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
<!-- Targets -->
|
||||
|
||||
<Target Name="ElectronBeforeClean" AfterTargets="BeforeClean" DependsOnTargets="ElectronResolvePaths">
|
||||
<Target Name="ElectronBeforeClean" AfterTargets="BeforeClean" DependsOnTargets="ElectronSetPaths">
|
||||
<ItemGroup>
|
||||
<Clean Include="$(ElectronOutDir)**" />
|
||||
</ItemGroup>
|
||||
@@ -60,7 +60,7 @@
|
||||
<!--<Message Text="ElectronBeforeClean - Clean Files: @(Clean)" Importance="High" />-->
|
||||
</Target>
|
||||
|
||||
<Target Name="ElectronClean" AfterTargets="CoreClean" DependsOnTargets="ElectronResolvePaths">
|
||||
<Target Name="ElectronClean" AfterTargets="CoreClean" DependsOnTargets="ElectronSetPaths">
|
||||
|
||||
<RemoveDir Directories="$(ElectronOutDir)" />
|
||||
|
||||
@@ -95,8 +95,24 @@
|
||||
</Target>
|
||||
|
||||
|
||||
<!-- Resolved at execution time, so that a RuntimeIdentifier coming from the project file,
|
||||
a publish profile or the CLI wins over the host RID. -->
|
||||
<Target Name="ElectronResolveRuntimeIdentifier">
|
||||
|
||||
<PropertyGroup>
|
||||
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(RuntimeIdentifier)' != ''">$(RuntimeIdentifier)</ElectronRuntimeIdentifier>
|
||||
<!-- When using the dotnet CLI (Core MSBuild), infer the current RID as a default -->
|
||||
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(MSBuildRuntimeType)' == 'Core'">$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier)</ElectronRuntimeIdentifier>
|
||||
<!-- Otherwise, Full MSBuild runs only on Windows, so infer a Windows RID from OSArchitecture -->
|
||||
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(MSBuildRuntimeType)' == 'Full'">win-$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLower())</ElectronRuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<Message Text="ElectronRuntimeIdentifier: $(ElectronRuntimeIdentifier)" Importance="High" />
|
||||
|
||||
</Target>
|
||||
|
||||
<!-- This is also run at design time, each time when a property of the project is changed -->
|
||||
<Target Name="ElectronCreatePackageJson" DependsOnTargets="ResolveProjectReferences;ElectronSetPaths">
|
||||
<Target Name="ElectronCreatePackageJson" DependsOnTargets="ResolveProjectReferences;ElectronSetPaths;ElectronResolveRuntimeIdentifier">
|
||||
|
||||
<Message Text="Creating package.json from template..." Importance="High" />
|
||||
<Message Text="OutDir: $(OutDir)" Importance="High" />
|
||||
@@ -154,7 +170,9 @@
|
||||
<PropertyGroup Condition="'$(UsingMicrosoftNETSdkWeb)' != 'true'">
|
||||
<_NonIntermediatePublishDir>$(PublishDir)</_NonIntermediatePublishDir>
|
||||
<PublishUrl>$(PublishDir)</PublishUrl>
|
||||
<PublishDir>$(IntermediateOutputPath)PubTmp\</PublishDir>
|
||||
<!-- ElectronIntermediatePublishDir allows overriding the intermediate publish directory -->
|
||||
<PublishDir Condition="'$(ElectronIntermediatePublishDir)' != ''">$(ElectronIntermediatePublishDir)</PublishDir>
|
||||
<PublishDir Condition="'$(ElectronIntermediatePublishDir)' == ''">$(IntermediateOutputPath)PubTmp\</PublishDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<RemoveDir Directories="$(PublishDir);$(PublishUrl)" Condition="'$(UsingMicrosoftNETSdkWeb)' != 'true'" />
|
||||
@@ -370,7 +388,7 @@
|
||||
<RemoveDir Directories="$(ElectronHookTargetModuleDir)" Condition="Exists($(ElectronHookTargetModuleDir))" />
|
||||
</Target>
|
||||
|
||||
<Target Name="ElectronCheckVersionMismatch">
|
||||
<Target Name="ElectronCheckVersionMismatch" DependsOnTargets="ElectronResolveRuntimeIdentifier">
|
||||
|
||||
<PropertyGroup>
|
||||
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'win-x64'">x64</ElectronArch>
|
||||
@@ -443,13 +461,13 @@
|
||||
|
||||
<!--<Message Importance="High" Text="ElectronConfigureApp: CopiedFiles - @(_CopiedFiles)" />-->
|
||||
|
||||
<Message Importance="High" Text="Running command: $(_NpmCmd) in folder: $(ElectronOutputPath)" Condition="@(_CopiedFiles->Count()) > 0" />
|
||||
<Message Importance="High" Text="Running command: $(_NpmCmd) in folder: $(ElectronOutputPath)" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(_CopiedFiles->Count()) > 0" />
|
||||
|
||||
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronOutputPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(_CopiedFiles->Count()) > 0">
|
||||
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronOutputPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(_CopiedFiles->Count()) > 0">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
|
||||
</Exec>
|
||||
|
||||
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '0'" />
|
||||
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
|
||||
|
||||
<PropertyGroup>
|
||||
<_NpmCmd>node node_modules/electron/install.js</_NpmCmd>
|
||||
@@ -458,13 +476,13 @@
|
||||
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'</_NpmCmd>
|
||||
</PropertyGroup>
|
||||
|
||||
<Message Importance="High" Text="Running command: $(_NpmCmd) in folder: $(ElectronOutputPath)" Condition="$(ElectronMajor) >= 42 AND @(_CopiedFiles->Count()) > 0" />
|
||||
<Message Importance="High" Text="Running command: $(_NpmCmd) in folder: $(ElectronOutputPath)" Condition="'$(ElectronSkipExecCommands)' != 'true' AND $(ElectronMajor) >= 42 AND @(_CopiedFiles->Count()) > 0" />
|
||||
|
||||
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronOutputPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="$(ElectronMajor) >= 42 AND @(_CopiedFiles->Count()) > 0">
|
||||
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronOutputPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND $(ElectronMajor) >= 42 AND @(_CopiedFiles->Count()) > 0">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
|
||||
</Exec>
|
||||
|
||||
<Message Importance="High" Text="Electron installation failed!" Condition="'$(ExecExitCode)' != '0'" />
|
||||
<Message Importance="High" Text="Electron installation failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
|
||||
|
||||
<!-- Fix up incorrect symlinks created by npm on Windows when targeting macOS -->
|
||||
<PropertyGroup>
|
||||
@@ -599,11 +617,11 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
<_NpmAppDepsCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmAppDepsCmd)'</_NpmAppDepsCmd>
|
||||
</PropertyGroup>
|
||||
|
||||
<Exec Command="$(_NpmAppDepsCmd)" WorkingDirectory="$(ElectronAppDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
|
||||
<Exec Command="$(_NpmAppDepsCmd)" WorkingDirectory="$(ElectronAppDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(CopiedFiles->Count()) > 0">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
|
||||
</Exec>
|
||||
|
||||
<Message Importance="High" Text="Electron app dependency install failed!" Condition="'$(ExecExitCode)' != '0'" />
|
||||
<Message Importance="High" Text="Electron app dependency install failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
|
||||
|
||||
<!-- Install electron-builder (the dev tool) into the publish root, which is the
|
||||
electron-builder projectDir (cwd). app metadata + runtime deps are read from
|
||||
@@ -615,6 +633,7 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
app\package.json instead. -->
|
||||
<ItemGroup>
|
||||
<DevTemplateProperty Include="ElectronPackageId" Value="$(ElectronPackageId)" />
|
||||
<DevTemplateProperty Include="ElectronExecutableName" Value="$(ElectronExecutableName)" />
|
||||
<DevTemplateProperty Include="Title" Value="$(Title)" />
|
||||
<DevTemplateProperty Include="Version" Value="$(Version)" />
|
||||
</ItemGroup>
|
||||
@@ -629,11 +648,11 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'</_NpmCmd>
|
||||
</PropertyGroup>
|
||||
|
||||
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
|
||||
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(CopiedFiles->Count()) > 0">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
|
||||
</Exec>
|
||||
|
||||
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '0'" />
|
||||
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
|
||||
|
||||
<!-- Transform ElectronPublishUrlFullPath to a wslpath if IsLinuxWsl is true -->
|
||||
<Exec Command="wsl wslpath '$(ElectronPublishUrlFullPath)'" ConsoleToMsBuild="true" Condition="'$(IsLinuxWsl)' == 'true'">
|
||||
@@ -653,18 +672,15 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_NpxCmd>npx electron-builder --config=./$(ElectronBuilderJson)</_NpxCmd>
|
||||
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">$(_NpxCmd) --$(ElectronPlatform)</_NpxCmd>
|
||||
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">$(_NpxCmd) --$(ElectronArch)</_NpxCmd>
|
||||
<_NpxCmd>$(_NpxCmd) -c.electronVersion=$(ElectronVersion) -c.directories.output "$(ElectronPublishUrlFullPath)" $(ElectronPaParams)</_NpxCmd>
|
||||
<_NpxCmd>npx electron-builder -c.$(ElectronPlatform).defaultArch=$(ElectronArch) --config=./$(ElectronBuilderJson) -c.electronVersion=$(ElectronVersion) --$(ElectronPlatform) --$(ElectronArch) -c.directories.output "$(ElectronPublishUrlFullPath)" $(ElectronPaParams)</_NpxCmd>
|
||||
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpxCmd)'</_NpxCmd>
|
||||
</PropertyGroup>
|
||||
|
||||
<Exec Command="$(_NpxCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="1800000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
|
||||
<Exec Command="$(_NpxCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="1800000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(CopiedFiles->Count()) > 0">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
|
||||
</Exec>
|
||||
|
||||
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '0'" />
|
||||
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
|
||||
|
||||
</Target>
|
||||
|
||||
|
||||
@@ -9,10 +9,22 @@
|
||||
ElectronCheckNoManifestJson;
|
||||
ElectronCheckElectronBuilderJson;
|
||||
ElectronCheckNoParentPaths;
|
||||
ElectronCheckPubxmlFiles
|
||||
ElectronCheckPubxmlFiles;
|
||||
ElectronCheckHostHookFolder
|
||||
</ElectronMigrationChecksDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
Directories that are never scanned by the migration checks. Besides the well-known
|
||||
bin/obj/publish/node_modules folders this also honors custom output paths (e.g. the
|
||||
.NET 'artifacts' output layout) so that generated files never trigger false alarms.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<_ElectronCheckExcludes>$(MSBuildProjectDirectory)\bin\**\*;$(MSBuildProjectDirectory)\obj\**\*;$(MSBuildProjectDirectory)\publish\**\*;$(MSBuildProjectDirectory)\node_modules\**\*;$(MSBuildProjectDirectory)\.electron\**\*</_ElectronCheckExcludes>
|
||||
<_ElectronCheckExcludes Condition="'$(BaseOutputPath)' != ''">$(_ElectronCheckExcludes);$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(BaseOutputPath)'))**\*</_ElectronCheckExcludes>
|
||||
<_ElectronCheckExcludes Condition="'$(BaseIntermediateOutputPath)' != ''">$(_ElectronCheckExcludes);$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(BaseIntermediateOutputPath)'))**\*</_ElectronCheckExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Main migration checks target that runs before build -->
|
||||
<Target Name="ElectronMigrationChecks"
|
||||
BeforeTargets="BeforeBuild"
|
||||
@@ -32,16 +44,13 @@
|
||||
<_InvalidPackageJson Include="$(MSBuildProjectDirectory)\**\package.json"
|
||||
Exclude="$(MSBuildProjectDirectory)\package.json;
|
||||
$(MSBuildProjectDirectory)\ElectronHostHook\**\package.json;
|
||||
$(MSBuildProjectDirectory)\bin\**\package.json;
|
||||
$(MSBuildProjectDirectory)\obj\**\package.json;
|
||||
$(MSBuildProjectDirectory)\publish\**\package.json;
|
||||
$(MSBuildProjectDirectory)\node_modules\**\package.json" />
|
||||
$(_ElectronCheckExcludes)" />
|
||||
<!-- The root package-lock.json belongs to the root package.json, which is validated
|
||||
by ELECTRON008/ELECTRON009 instead of ELECTRON001. -->
|
||||
<_InvalidPackageLockJson Include="$(MSBuildProjectDirectory)\**\package-lock.json"
|
||||
Exclude="$(MSBuildProjectDirectory)\ElectronHostHook\**\package-lock.json;
|
||||
$(MSBuildProjectDirectory)\bin\**\package-lock.json;
|
||||
$(MSBuildProjectDirectory)\obj\**\package-lock.json;
|
||||
$(MSBuildProjectDirectory)\publish\**\package-lock.json;
|
||||
$(MSBuildProjectDirectory)\node_modules\**\package-lock.json" />
|
||||
Exclude="$(MSBuildProjectDirectory)\package-lock.json;
|
||||
$(MSBuildProjectDirectory)\ElectronHostHook\**\package-lock.json;
|
||||
$(_ElectronCheckExcludes)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -141,10 +150,7 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
|
||||
<ItemGroup>
|
||||
<_InvalidManifestJson Include="$(MSBuildProjectDirectory)\**\electron.manifest.json;$(MSBuildProjectDirectory)\**\electron-manifest.json"
|
||||
Exclude="$(MSBuildProjectDirectory)\bin\**\*;
|
||||
$(MSBuildProjectDirectory)\obj\**\*;
|
||||
$(MSBuildProjectDirectory)\publish\**\*;
|
||||
$(MSBuildProjectDirectory)\node_modules\**\*" />
|
||||
Exclude="$(_ElectronCheckExcludes)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -187,10 +193,7 @@ MIGRATION REQUIRED:
|
||||
<ItemGroup>
|
||||
<_ElectronBuilderJsonWrongLocation Include="$(MSBuildProjectDirectory)\**\electron-builder.json"
|
||||
Exclude="$(MSBuildProjectDirectory)\Properties\electron-builder.json;
|
||||
$(MSBuildProjectDirectory)\bin\**\*;
|
||||
$(MSBuildProjectDirectory)\obj\**\*;
|
||||
$(MSBuildProjectDirectory)\publish\**\*;
|
||||
$(MSBuildProjectDirectory)\node_modules\**\*" />
|
||||
$(_ElectronCheckExcludes)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -296,10 +299,11 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
</_PubxmlFileInfo>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Check each file for WebPublishMethod presence -->
|
||||
<!-- Check each file for the ASP.NET specific properties (WebPublishMethod / ProjectGuid) -->
|
||||
<ItemGroup Condition="'$(_HasPubxmlFiles)' == 'true'">
|
||||
<_PubxmlFileInfoWithFlags Include="@(_PubxmlFileInfo)" Condition="'%(Identity)' != ''">
|
||||
<HasWebPublishMethod>$([System.Text.RegularExpressions.Regex]::IsMatch('%(FileContent)', '<WebPublishMethod>'))</HasWebPublishMethod>
|
||||
<HasProjectGuid>$([System.Text.RegularExpressions.Regex]::IsMatch('%(FileContent)', '<ProjectGuid>'))</HasProjectGuid>
|
||||
</_PubxmlFileInfoWithFlags>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -309,10 +313,10 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
Condition="'$(_IsAspNetProject)' == 'true' AND '%(HasWebPublishMethod)' == 'False'" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- For Console/Non-ASP.NET projects: find pubxml files that HAVE WebPublishMethod -->
|
||||
<!-- For Console/Non-ASP.NET projects: find pubxml files that HAVE ASP.NET specific properties -->
|
||||
<ItemGroup>
|
||||
<_ConsolePubxmlWithAspNetProperties Include="@(_PubxmlFileInfoWithFlags)"
|
||||
Condition="'$(_IsAspNetProject)' != 'true' AND '%(HasWebPublishMethod)' == 'True'" />
|
||||
Condition="'$(_IsAspNetProject)' != 'true' AND ('%(HasWebPublishMethod)' == 'True' OR '%(HasProjectGuid)' == 'True')" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Warning for ASP.NET projects with incorrect pubxml files -->
|
||||
@@ -336,7 +340,7 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
<Warning
|
||||
Condition="'@(_ConsolePubxmlWithAspNetProperties)' != ''"
|
||||
Code="ELECTRON007"
|
||||
Text="The publish profile '%(_ConsolePubxmlWithAspNetProperties.Identity)' appears to be configured for ASP.NET publishing (containing the WebPublishMethod property), but this is a console application project.
|
||||
Text="The publish profile '%(_ConsolePubxmlWithAspNetProperties.Identity)' appears to be configured for ASP.NET publishing (containing the WebPublishMethod and/or ProjectGuid property), but this is a console application project.
|
||||
|
||||
RECOMMENDED ACTION:
|
||||
1. Delete the existing publish profiles
|
||||
@@ -351,4 +355,59 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Check 6: The ElectronHostHook folder must be set up correctly
|
||||
|
||||
Custom host hook TypeScript code is only compiled and packaged when the folder also
|
||||
contains a package.json (and a tsconfig.json for the TypeScript compilation). Without
|
||||
those files the hook code is silently ignored.
|
||||
-->
|
||||
<Target Name="ElectronCheckHostHookFolder"
|
||||
Condition="Exists('$(MSBuildProjectDirectory)\ElectronHostHook')">
|
||||
|
||||
<ItemGroup>
|
||||
<_HostHookTsFiles Include="$(MSBuildProjectDirectory)\ElectronHostHook\*.ts" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_HasHostHookTsFiles>false</_HasHostHookTsFiles>
|
||||
<_HasHostHookTsFiles Condition="@(_HostHookTsFiles->Count()) > 0">true</_HasHostHookTsFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<Warning Condition="'$(_HasHostHookTsFiles)' == 'true' AND !Exists('$(MSBuildProjectDirectory)\ElectronHostHook\package.json')"
|
||||
Code="ELECTRON010"
|
||||
Text="The 'ElectronHostHook' folder contains TypeScript files but no package.json.
|
||||
|
||||
Folder: $(MSBuildProjectDirectory)\ElectronHostHook
|
||||
|
||||
The host hook code is only compiled and packaged when the folder contains a package.json declaring its npm dependencies. Without it the hook is silently ignored at runtime.
|
||||
|
||||
HOW TO FIX:
|
||||
Add a package.json to the 'ElectronHostHook' folder, for example:
|
||||
|
||||
{
|
||||
"name": "electron-host-hook",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": { "socket.io": "^4.8.1" },
|
||||
"devDependencies": { "typescript": "^5.9.3" }
|
||||
}
|
||||
|
||||
For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migration-Checks#6-electronhosthook-folder-validation" />
|
||||
|
||||
<Warning Condition="'$(_HasHostHookTsFiles)' == 'true' AND !Exists('$(MSBuildProjectDirectory)\ElectronHostHook\tsconfig.json')"
|
||||
Code="ELECTRON011"
|
||||
Text="The 'ElectronHostHook' folder contains TypeScript files but no tsconfig.json.
|
||||
|
||||
Folder: $(MSBuildProjectDirectory)\ElectronHostHook
|
||||
|
||||
Without a tsconfig.json the hook TypeScript sources are not compiled with the expected settings.
|
||||
|
||||
HOW TO FIX:
|
||||
Add a tsconfig.json to the 'ElectronHostHook' folder.
|
||||
|
||||
For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migration-Checks#6-electronhosthook-folder-validation" />
|
||||
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -553,6 +553,30 @@
|
||||
Category="General">
|
||||
</StringProperty>
|
||||
|
||||
<StringProperty Name="ElectronExecutableName"
|
||||
DisplayName="Executable Name"
|
||||
Description="Name of the executable inside the package, without extension. Defaults to the title on Windows and to the package id on Linux and macOS"
|
||||
Category="General">
|
||||
</StringProperty>
|
||||
|
||||
<StringProperty Name="ElectronRootDir"
|
||||
DisplayName="Electron Root Directory"
|
||||
Description="Location of the Electron binary relative to the .NET application directory of a packaged app. Default: ../.. (the .NET app lives in resources/bin)"
|
||||
Category="General">
|
||||
</StringProperty>
|
||||
|
||||
<BoolProperty Name="ElectronSkipExecCommands"
|
||||
DisplayName="Skip npm/npx Commands"
|
||||
Description="If enabled, the npm and npx invocations of the Electron build and publish targets are skipped"
|
||||
Category="General">
|
||||
</BoolProperty>
|
||||
|
||||
<StringProperty Name="ElectronIntermediatePublishDir"
|
||||
DisplayName="Intermediate Publish Directory"
|
||||
Description="Overrides the intermediate publish directory used while packaging. Default: $(IntermediateOutputPath)PubTmp\"
|
||||
Category="General">
|
||||
</StringProperty>
|
||||
|
||||
<StringProperty Name="ElectronSplashScreen"
|
||||
DisplayName="Splash Screen Image"
|
||||
Description="Choose a PNG image to be shown as splash screen on startup"
|
||||
|
||||
@@ -4,11 +4,17 @@
|
||||
"private": true,
|
||||
"build": {
|
||||
"appId": "$(ElectronPackageId)",
|
||||
"win": {
|
||||
"executableName": "$(ElectronExecutableName)"
|
||||
},
|
||||
"mac": {
|
||||
"executableName": "$(ElectronExecutableName)"
|
||||
},
|
||||
"linux": {
|
||||
"desktop": {
|
||||
"entry": { "Name": "$(Title)" }
|
||||
},
|
||||
"executableName": "$(ElectronPackageId)"
|
||||
"executableName": "$(ElectronExecutableName)"
|
||||
},
|
||||
"deb": {
|
||||
"desktop": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>0.5.2</Version>
|
||||
<Version>0.6.0</Version>
|
||||
<PackageNamePrefix>ElectronNET.Core</PackageNamePrefix>
|
||||
<Authors>Gregor Biswanger, Florian Rappl, softworkz</Authors>
|
||||
<Product>Electron.NET</Product>
|
||||
@@ -18,6 +18,7 @@
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<Version>$(Version)$(VersionPostFix)</Version>
|
||||
<InformationalVersion>$(Version)</InformationalVersion>
|
||||
<ElectronNetTargetFrameworks Condition="'$(ElectronNetTargetFrameworks)' == ''">net6.0;net8.0;net10.0</ElectronNetTargetFrameworks>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user