diff --git a/.github/WikiLinks.exe b/.github/WikiLinks.exe
new file mode 100644
index 0000000..19fdab4
Binary files /dev/null and b/.github/WikiLinks.exe differ
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0901bd2..a0e7cfb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,7 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
+ 6.0.x
8.0.x
- name: Build
@@ -32,6 +33,7 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
+ 6.0.x
8.0.x
- name: Build
diff --git a/.github/workflows/publish-wiki.yml b/.github/workflows/publish-wiki.yml
new file mode 100644
index 0000000..de97577
--- /dev/null
+++ b/.github/workflows/publish-wiki.yml
@@ -0,0 +1,59 @@
+name: Publish wiki
+on:
+ push:
+ branches: [electronnet_core, main]
+ workflow_dispatch:
+concurrency:
+ group: publish-wiki
+ cancel-in-progress: true
+permissions:
+ contents: write
+jobs:
+ publish-wiki:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Remove level 1 headings from Markdown files
+ shell: bash
+ run: |
+ find docs/ -name '*.md' -exec sed -i '1d' {} \;
+ - name: Move all files to root folder
+ shell: bash
+ run: |
+ mv docs/*/* docs/
+ - name: Delete unwanted files
+ shell: bash
+ run: |
+ # rm docs/*.xlsm
+ # rm docs/*.pptx
+ rm docs/*.shproj
+ - name: Stripping file extensions....
+ uses: softworkz/strip-markdown-extensions-from-links-action@main
+ with:
+ path: ./docs/
+ - name: Copy Changelog
+ shell: bash
+ run: |
+ cp Changelog.md docs/RelInfo/ 2>/dev/null || true
+ - name: Copy images to wiki/wiki folder
+ shell: bash
+ run: |
+ mkdir docs/wiki
+ cp docs/*.svg docs/wiki/ 2>/dev/null || true
+ cp docs/*.png docs/wiki/ 2>/dev/null || true
+ cp docs/*.jpg docs/wiki/ 2>/dev/null || true
+ cp docs/*.gif docs/wiki/ 2>/dev/null || true
+ cp docs/*.mp4 docs/wiki/ 2>/dev/null || true
+ - name: Commit and push changes
+ run: |
+ git config --global user.name "GitHub Action"
+ git config --global user.email "action@github.com"
+ git add -A
+ git commit -m "Automatically update Markdown files" || echo "No changes to commit"
+ - uses: Andrew-Chen-Wang/github-wiki-action@v4.4.0
+ with:
+ path: docs/
+ ignore: |
+ '**/*.xlsm'
+ '**/*.pptx'
+ '**/*.shproj'
diff --git a/.gitignore b/.gitignore
index 705f03c..e75ee86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -266,3 +266,4 @@ __pycache__/
# Nuke build tool
.nuke/temp
+/publish.cmd
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index daa29e2..466bf0f 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -1,11 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
- "title": "Build Schema",
"$ref": "#/definitions/build",
+ "title": "Build Schema",
"definitions": {
"build": {
"type": "object",
"properties": {
+ "CommonPropsFilePath": {
+ "type": "string",
+ "description": "common.props file path - to determine the configured version"
+ },
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
@@ -64,7 +68,7 @@
},
"ReleaseNotesFilePath": {
"type": "string",
- "description": "ReleaseNotesFilePath - To determine the SemanticVersion"
+ "description": "ReleaseNotesFilePath - To determine the lates changelog version"
},
"Root": {
"type": "string",
@@ -78,14 +82,8 @@
"enum": [
"Clean",
"Compile",
- "CompileSample",
"CreatePackages",
"Default",
- "ElectronizeCustomWin7TargetSample",
- "ElectronizeGenericTargetSample",
- "ElectronizeLinuxTargetSample",
- "ElectronizeMacOsTargetSample",
- "ElectronizeWindowsTargetSample",
"Package",
"PrePublish",
"Publish",
@@ -109,14 +107,8 @@
"enum": [
"Clean",
"Compile",
- "CompileSample",
"CreatePackages",
"Default",
- "ElectronizeCustomWin7TargetSample",
- "ElectronizeGenericTargetSample",
- "ElectronizeLinuxTargetSample",
- "ElectronizeMacOsTargetSample",
- "ElectronizeWindowsTargetSample",
"Package",
"PrePublish",
"Publish",
@@ -141,4 +133,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/.nuke/parameters.json b/.nuke/parameters.json
index abd31df..39ee7a3 100644
--- a/.nuke/parameters.json
+++ b/.nuke/parameters.json
@@ -1,4 +1,4 @@
{
"$schema": "./build.schema.json",
- "Solution": "src/ElectronNET.sln"
+ "Solution": "src/ElectronNET.Lean.sln"
}
\ No newline at end of file
diff --git a/Changelog.md b/Changelog.md
index 4998dda..bd13258 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,389 +1,57 @@
-# 23.6.2
+# 0.0.18
+
+## ElectronNET.Core
+
+### Highlights
+- **Complete MSBuild Integration**: Eliminated CLI tool dependency, moved all build processes to MSBuild with deep Visual Studio integration
+- **Modernized Architecture**: Restructured process lifecycle with .NET launching first and running Electron as child process for better control and reliability
+- **Cross-Platform Development**: Build and debug Linux applications directly from Windows Visual Studio via WSL integration
+- **Flexible Electron Versioning**: Removed version lock-in, users can now select any Electron version with build-time validation
+- **Enhanced Debugging Experience**: ASP.NET-first debugging with Hot Reload support and improved process termination handling
+- **Console App Support**: No longer requires ASP.NET - now works with simple console applications for file system or remote server HTML/JS
+
+### Build System & Project Structure
+- Eliminated electron.manifest.json configuration file, replaced with MSBuild project properties
+- Introduced new package structure: ElectronNET.Core (main package), ElectronNET.Core.Api (API definitions), ElectronNET.Core.AspNet (ASP.NET integration)
+- Added Runtime Identifier (RID) selection as part of project configuration
+- Restructured build folder layout to use standard .NET format (bin\net8.0\win-x64) instead of bin\Desktop
+- Implemented incremental build support for Electron assets to improve build performance
+- Added custom MSBuild tasks for Electron-specific build operations
+
+### Development Experience
+- Implemented unpackaged run-mode for development using regular .NET builds with unpackaged Electron configuration
+- Added support for building Linux packages on Windows via WSL integration
+- Enabled running and debugging Linux application outputs on Windows through WSL
+- Integrated TypeScript compilation with ASP.NET tooling for consistent builds
+- Added process orchestration supporting 8 different launch scenarios (packaged/unpackaged Χ console/ASP.NET Χ dotnet-first/electron-first)
+
+### Debugging & Runtime
+- Dramatically improved debugging experience with ASP.NET-first launch mode
+- Added Hot Reload support for ASP.NET code during development
+- Implemented proper process termination handling for all exit scenarios
+- Minimized startup times through optimized build and launch procedures
+
+### Technical Improvements
+- Enhanced splash screen handling with automatic path resolution
+- Improved ElectronHostHook integration as proper npm package dependency
+- Updated to latest TypeScript version with ESLint configuration
+- Added support for custom main.js files in projects
+- Implemented version management through common.props file
+- Added build-time Electron version compatibility validation
+
+### Package & Distribution
+- Integrated MSBuild publishing mechanisms for creating Electron packages
+- Added folder publishing support with improved parameter handling
+- Implemented automated package.json generation from MSBuild properties
+- Added GitHub release automation with proper versioning
+- Reduced package sizes by eliminating unnecessary TypeScript dependencies
+
+### Migration & Compatibility
+- Maintained backward compatibility for existing ElectronHostHook implementations
+- Removed ASP.NET requirement: Now works with simple console applications for file system or remote server HTML/JS scenarios
+- Added support for both console and ASP.NET Core application types
+- Preserved all existing Electron API functionality while modernizing architecture
+- Added migration path for existing projects through updated package structure
+
+This represents a comprehensive modernization of Electron.NET, addressing the major pain points around debugging, build complexity, and platform limitations while maintaining full API compatibility.
-## ElectronNET.API
-
-* Fixed escaping of URL (#735) @cosmo0
-* Fixed huge memory waste in IpcMain.Once (#833) @Yuvix25
-* Fixed enabling of accessibility support (#798) @dlanorok
-* Updated `Display` to current spec (#828) @Yuvix25
-* Changed `ZoomFactor` type from `int` to `double` (#754) @Yuvix25
-* Added splash screen size config (#822) @NickRimmer
-* Added OSX ARM architecture detection (#821) @sajmonr
-* Added support for `did-navigate` event (#819) @NickRimmer
-* Added support for `will-redirect` event (#819) @NickRimmer
-* Added support for `did-fail-load` event (#819) @NickRimmer
-* Added support for `did-start-navigation` event (#819) @NickRimmer
-* Added support for `did-redirect-navigation` event (#819) @NickRimmer
-* Added support for `dom-ready` event (#813) @softworkz
-
-## ElectronNET.CLI
-
-* (none)
-
-## Infrastructure
-
-* Changed build system to NUKE (#757) @FlorianRappl
-* Updated target framework for host project (#753) @r-pankevicius
-* Fixed typo in README (#800) @franpersanchez
-
-# 23.6.1
-
-## ElectronNET.CLI
-
-* New Feature: Upgrade to .NET 6 support
-
-## ElectronNET.API
-
-* New Feature: Native Electron 23.2.0 support, but not all new API features included (we search contributors)
-* New Feature: Upgrade to .NET 6 support
-* New Feature: Changed Web-Socket .NET Library to [SocketIOClient](https://github.com/doghappy/socket.io-client-csharp)
-* Breaking Changes: We removed deprecated API events/methods from ElectronNET.API [(More Details)](https://www.electronjs.org/docs/latest/breaking-changes)
-
-# 13.5.1
-
-ElectronNET.CLI:
-
-* New Feature: Using exit code instead of seek for the term 'error' (thanks [TSrgy](https://github.com/TSrgy)) [\#562](https://github.com/ElectronNET/Electron.NET/pull/562)
-* Fixed bug: Allow for property overrides to be passed in (thanks [danatcofo](https://github.com/danatcofo)) [\#531](https://github.com/ElectronNET/Electron.NET/pull/531)
-Use `/p:propertyName=value` or `/property:propertyName=value` to pass in property overrides. This is equivalent to the `-p:` option documented here: [https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish)
-* Fixed bug: Add ability to pass an argument for "Version" for both the "dotnet publish" and "electron-builder" commands (thanks [tub5](https://github.com/tub5)) [\#546](https://github.com/ElectronNET/Electron.NET/pull/546)
-* Fixed bug: Version flag not persisting with the referenced executable (thanks [tub5](https://github.com/tub5)) [\#585](https://github.com/ElectronNET/Electron.NET/pull/585)
-* Fixed bug: Changes PublishSingleFile default to false for NET5 compatibility (thanks [cristiangiagante](https://github.com/cristiangiagante)) [\#570](https://github.com/ElectronNET/Electron.NET/pull/570)
-
-ElectronNET.API:
-
-* New Feature: Native Electron 13.1.5 support, but not all new features (we search contributors)
-* Breaking API Changes (from native Electron 13.1.5):
- - `Shell.MoveItemToTrashAsync` renamed with `Shell.TrashItemAsync`
- - The deprecated extension APIs have been removed: `BrowserWindow.GetAllExtensionsAsync()`, `BrowserWindow.RemoveExtension()`, `BrowserWindow.AddExtensionAsync()`. Use the session APIs instead: `Session.GetAllExtensionsAsync()`, `Session.RemoveExtension()`, `Session.LoadExtensionAsync()`.
-* New Feature: singleInstance handle command line arguments [\#520](https://github.com/ElectronNET/Electron.NET/issues/520)
-* New Feature: Add WebContents [insertCSS](https://www.electronjs.org/docs/api/web-contents#contentsinsertcsscss-options) functionality (thanks [nfichter](https://github.com/nfichter)) [\#559](https://github.com/ElectronNET/Electron.NET/pull/559)
-* New Feature: Allow IpcMain to send IPC messages to BrowserViews (thanks [nfichter](https://github.com/nfichter)) [\#560](https://github.com/ElectronNET/Electron.NET/pull/560)
-* New Feature: Add support for proxies that require basic username/password authentication (thanks [nfichter](https://github.com/nfichter)) [\#561](https://github.com/ElectronNET/Electron.NET/pull/561)
-* New Feature: Add PostData to LoadURLOptions to allow http-posts in LoadURL calls (thanks [Funkrusha](https://github.com/Funkrusha)) [\#547](https://github.com/ElectronNET/Electron.NET/pull/547)
-* Fixed bug: Fix splash screen interaction causing crashes, ghost dragging, and resizable behavior #540 (thanks [MiniguyBrendan](https://github.com/MiniguyBrendan)) [\#540](https://github.com/ElectronNET/Electron.NET/pull/540)
-* Fixed bug: Vibrancy serialization fix (thanks [tantumalice](https://github.com/tantumalice)) [\#573](https://github.com/ElectronNET/Electron.NET/pull/573)
-
-# 11.5.1
-
-ElectronNET.CLI:
-
-* New Feature: Added new build and start commandline options for single exe (thanks [nathanwienand](https://github.com/nathanwienand)) [\#506](https://github.com/ElectronNET/Electron.NET/pull/506)
-* New Feature: Set a description of the app in `electron.manifest.json` (thanks [BurtsevC](https://github.com/BurtsevC)) [\#433](https://github.com/ElectronNET/Electron.NET/pull/433)
-* New Feature: Set a target for the start command (thanks [gabecook](https://github.com/gabecook)) [\#463](https://github.com/ElectronNET/Electron.NET/pull/463)
-* New Feature: `electronize init` support for F# projects (thanks [kojo12228](https://github.com/kojo12228)) [\#457](https://github.com/ElectronNET/Electron.NET/pull/457)
-* New Feature: Linux support for the buildAll.sh (thanks [duncanawoods](https://github.com/duncanawoods)) [\#465](https://github.com/ElectronNET/Electron.NET/pull/465)
-* Fixed bug: ERR_UNKNOWN_URL_SCHEME by intercepting file:// protocol (thanks [duncanawoods](https://github.com/duncanawoods)) [\#467](https://github.com/ElectronNET/Electron.NET/pull/467)
-
-ElectronNET.API:
-
-* New Feature: Native Electron 11.1.1 support, but not all new features (we search contributors)
-* Breaking API Changes (from native Electron 11.0): - Removed: BrowserView.{destroy, fromId, fromWebContents, getAllViews} and id property of BrowserView
-* New Feature: Upgrade to .NET 5 (thanks [scottkuhl](https://github.com/scottkuhl)) [\#509](https://github.com/ElectronNET/Electron.NET/pull/509)
-* New Feature: Extension Method for adding the Electron static class members to the standard MS DI Containers, this is a QOL issue only. `services.AddElectron()` (thanks [danatcofo](https://github.com/danatcofo )) [\#528](https://github.com/ElectronNET/Electron.NET/pull/528)
-* New Feature: SetMenu completed for the Dock (MacOS) (thanks [danatcofo](https://github.com/danatcofo )) [\#528](https://github.com/ElectronNET/Electron.NET/pull/528)
-
-Example for the Dock Menu
-
-`Electron.Dock.SetMenu(new [] {
- new MenuItem {
- Label = "Dock Menu Item",
- Click = () => {
- // do something
- }
- },
-});`
-
-Example for consuming the activate event (MacOs only)
-
-`Electron.App.On("activate", obj => {
- var hasWindows = (bool)obj;
- // do something
-});`
-
-* New Feature: On and Once implementations for the App and Tray to cover the plethora of events that are not mapped explicitly in those two modules. (thanks [danatcofo](https://github.com/danatcofo )) [\#528](https://github.com/ElectronNET/Electron.NET/pull/528)
-* New Feature: Adding the `EnableRemoteModule` property to the WebPreferences object. As of Electron 10, this property defaulted to false and without it exposed you can't use the remote module within a window. (thanks [danatcofo](https://github.com/danatcofo )) [\#528](https://github.com/ElectronNET/Electron.NET/pull/528)
-* New Feature: Adding a configurable default electron port. (thanks [aarong-av](https://github.com/aarong-av)) [\#505](https://github.com/ElectronNET/Electron.NET/pull/505)
-* New Feature: Added support for launching the application with a file on MacOS (thanks [dlitty](https://github.com/dlitty)) [\#478](https://github.com/ElectronNET/Electron.NET/pull/478)
-* Improved: Avoid Blocking Calls in App and AutoUpdater (thanks [freosc](https://github.com/freosc)) [\#474](https://github.com/ElectronNET/Electron.NET/pull/474)
-* Fixed bug: Maintain references between socket.io connection events (thanks [danatcofo](https://github.com/danatcofo )) [\#468](https://github.com/ElectronNET/Electron.NET/pull/486)
-* Fixed bug: Set default WebPreferences.DefaultFontSize (thanks [duncanawoods](https://github.com/duncanawoods)) [\#468](https://github.com/ElectronNET/Electron.NET/pull/468)
-
-# 9.31.2
-
-* Electron-Builder fixed for Windows builds.
-
-# 9.31.1
-
-ElectronNET.CLI:
-
-* New Feature: Added config parameter (thanks [konstantingross](https://github.com/konstantingross)) [\#409](https://github.com/ElectronNET/Electron.NET/pull/409)
-* New Feature: Set the configuration environment with the electron.manifest.json file.
-* Fixed bug: Custom user path removed and replaced by the correct directory with VS macro (When ElectronNET.CLI is the Startup Project, press F5 (Debug) and the ElectronNET.WebApp starts correctly without error!) (thanks [konstantingross](https://github.com/konstantingross)) [\#409](https://github.com/ElectronNET/Electron.NET/pull/409)
-
-ElectronNET.API:
-
-* New Feature: Native Electron 9.0.3 support, but not all new features (we search contributors)
-* New Feature: PowerMonitor API Support (thanks [gustavo-lara-molina](https://github.com/gustavo-lara-molina)) [\#399](https://github.com/ElectronNET/Electron.NET/pull/399) [\#423](https://github.com/ElectronNET/Electron.NET/pull/423)
-* New Feature: NativeTheme API Support (thanks [konstantingross](https://github.com/konstantingross)) [\#402](https://github.com/ElectronNET/Electron.NET/pull/402)
-* New Feature: Cookie API Support (thanks [freosc](https://github.com/freosc)) [\#413](https://github.com/ElectronNET/Electron.NET/pull/413)
-* Changed Feature: Removed dock methods from App API and moved to Dock API (thanks [konstantingross](https://github.com/konstantingross)) [\#422](https://github.com/ElectronNET/Electron.NET/pull/422)
-* App-Api Enhancement: MenuItems with Submenus need an submenu type workaround [\#412](https://github.com/ElectronNET/Electron.NET/issues/412)
-* App-Api Enhancement: Added UserAgentFallback (thanks [Mandrakia](https://github.com/Mandrakia)) [\#406](https://github.com/ElectronNET/Electron.NET/pull/406)
-* App-Api Enhancement: Summaries rewritten, new App.IsReady / App.HasSingleInstanceLock property, App.Ready event, App.Focus with force parameter method, many parameters changes (thanks [konstantingross](https://github.com/konstantingross)) [\#415](https://github.com/ElectronNET/Electron.NET/pull/415) [\#422](https://github.com/ElectronNET/Electron.NET/pull/422)
-* App-Api Enhancement: New App.IsReady property and App.Ready event (thanks [konstantingross](https://github.com/konstantingross)) [\#415](https://github.com/ElectronNET/Electron.NET/pull/415)
-* Shell-Api Enhancement: API fixes for Electron 9.0.0 / Added missing parameters / Summaries rewritten (thanks [konstantingross](https://github.com/konstantingross)) [\#417](https://github.com/ElectronNET/Electron.NET/pull/417) [\#418](https://github.com/ElectronNET/Electron.NET/pull/418)
-* Notification-Api Enhancement: Added missing properties in Notifications (thanks [konstantingross](https://github.com/konstantingross)) [\#410](https://github.com/ElectronNET/Electron.NET/pull/410)
-* BrowserWindows-Api Enhancement: Add missing API call for SetProgressBar options (thanks [konstantingross](https://github.com/konstantingross)) [\#416](https://github.com/ElectronNET/Electron.NET/pull/416)
-* BrowserWindow Enhancement: Add BrowserWindow.GetNativeWindowHandle() (thanks [kdlslyv](https://github.com/kdlslyv)) [\#429](https://github.com/ElectronNET/Electron.NET/pull/429)
-* HostHook-Api Enhancement: HostHook.CallAsync should use TaskCompletionSource.SetException instead of throwing exception (thanks [Fre V](https://github.com/freosc)) [\#430](https://github.com/ElectronNET/Electron.NET/pull/430)
-* MacOS Enhancement: Application exit logic (thanks [dafergu2](https://github.com/dafergu2)) [\#405](https://github.com/ElectronNET/Electron.NET/pull/405)
-* Fixed bug: ElectronNET.API.Entities.WebPreferences.ContextIsolation [DefaultValue(true)] [\#411](https://github.com/ElectronNET/Electron.NET/issues/411)
-
-ElectronNET.WebApp (internal use):
-* Improvement debugging and testing new API calls (without install ElectronNET.CLI) (thanks [konstantingross](https://github.com/konstantingross)) [\#425](https://github.com/ElectronNET/Electron.NET/pull/425)
-* Fixed bug: Cannot find modules in ElectronHostHook (thanks [konstantingross](https://github.com/konstantingross)) [\#425](https://github.com/ElectronNET/Electron.NET/pull/425)
-
-Thank you for donation [Phil Seeman](https://github.com/mpnow) β€
-
-# 8.31.2
-
-ElectronNET.CLI:
-* New Feature: Deactivate PublishReadyToRun for build or start [\#395](https://github.com/ElectronNET/Electron.NET/issues/395)
-
- `electronize build /target win /PublishReadyToRun false`
- `electronize start /PublishReadyToRun false`
-* Fixed bug: Application window doesn't open after packaging [\#387](https://github.com/ElectronNET/Electron.NET/issues/387)
-
-ElectronNET.API:
-
-* New Feature: NativeImage Support (thanks [ThrDev](https://github.com/ThrDev)) [\#394](https://github.com/ElectronNET/Electron.NET/pull/394)
-* New Feature: Update menu items for context menu and system tray on-the-fly. [\#270](https://github.com/ElectronNET/Electron.NET/pull/270)
-
-
-# 8.31.1
-
-ElectronNET.CLI:
-* New Feature: Set a name and author of the app in `electron.manifest.json` [\#348](https://github.com/ElectronNET/Electron.NET/issues/348#issuecomment-615977950) [\#310](https://github.com/ElectronNET/Electron.NET/issues/310#issuecomment-617361086)
-* New Feature: Live reload (thanks [syedadeel2](https://github.com/syedadeel2)) [\#390](https://github.com/ElectronNET/Electron.NET/pull/390)
-`electronize start /watch`
-* New Feature: Every new window will created with an clear cache [\#273](https://github.com/ElectronNET/Electron.NET/issues/273)
-`electronize start /clear-cache`
-
-ElectronNET.API:
-
-* New Feature: Native Electron 8.2.3 support, but not all new features (we search contributors)
-* New Feature: We incease the startup time for ~25-36% [\#356](https://github.com/ElectronNET/Electron.NET/issues/356)
-* New Feature: Added print capability (thanks [x-xx-o](https://github.com/x-xx-o)) [\#355](https://github.com/ElectronNET/Electron.NET/pull/355)
-* New Feature: BrowserView API [\#371](https://github.com/ElectronNET/Electron.NET/issues/371)
-* Changed App.GetNameAsync and App.SetNameAsync to the App.Name Property [\#350](https://github.com/ElectronNET/Electron.NET/issues/350)
-* Fixed bug: Splash Screen disappearing on click [\#357](https://github.com/ElectronNET/Electron.NET/issues/357)
-* Fixed bug: Start MenuRole enum at 1 (thanks [jjuback](https://github.com/jjuback)) [\#369](https://github.com/ElectronNET/Electron.NET/pull/369)
-* Fixed bug: BridgeConnector not connected (spam console) [\#347](https://github.com/ElectronNET/Electron.NET/issues/347)
-* Fixed bug: BrowserWindowOptions is not setting Width and Height properly [\#373](https://github.com/ElectronNET/Electron.NET/issues/373)
-* Fixed bug: IpcMain.Once(string) is not one time use, is not removing listener [\#366](https://github.com/ElectronNET/Electron.NET/issues/366)
-* Fixed bug: IpcMain.RemoveAllListeners(string) is not removing the listeners [\#365](https://github.com/ElectronNET/Electron.NET/issues/365)
-* Fixed bug: GetLoginItemSettingsAsync does not work [\#352](https://github.com/ElectronNET/Electron.NET/issues/352)
-* Fixed bug: Using OnReadyToShow to display the main window in Blazor does not seem to work with Show set to false [\#361](https://github.com/ElectronNET/Electron.NET/issues/361)
-* Fixed bug: Unable to disable WebSecurity along with NodeIntegration enabled [\#389](https://github.com/ElectronNET/Electron.NET/issues/389)
-
-# 7.30.2
-
-ElectronNET.CLI:
-
-* New Feature: Different manifest file support [\#340](https://github.com/ElectronNET/Electron.NET/issues/340)
- * Create a additional manifest file: `electronize init /manifest test`
- * Start the app with your additional manifest file: `electronize start /manifest electron.manifest.test.json`
- * Build the app with your additional manifest file: `electronize build /target win /manifest electron.manifest.test.json`.
-
-* New Feature: Command Line support [\#337](https://github.com/ElectronNET/Electron.NET/issues/337)
- * You can start the app with: `electronize start /args --dog=woof --test=true`
- * Or as binary: `myapp.exe /args --dog=woof --test=true`
-* Fixed bug: Start process with listen port 8000 error. [\#308](https://github.com/ElectronNET/Electron.NET/issues/308) (thanks [thecodejedi](https://github.com/thecodejedi))
-* Fixed bug: `electronize build` with no arguments would throw a `KeyNotFoundException`. (thanks [jamiebrynes7](https://github.com/jamiebrynes7))
-
-ElectronNET.API:
-
-* New Feature: Electron 7.1.2 support, but not all new features (we search contributors) [\#341](https://github.com/ElectronNET/Electron.NET/issues/341)
-* New Feature: Electron.App.CommandLine API [\#337](https://github.com/ElectronNET/Electron.NET/issues/337)
-* New Feature: Support of BrowserWindow.AddExtension, BrowserWindow.RemoveExtension, BrowserWindow.GetExtensions (thanks [Daddoon](https://github.com/Daddoon))
-
-Thank you for donation [robertmclaws](https://github.com/robertmclaws) β€
-
-# 5.30.1
-
-ElectronNET.CLI:
-
-* Move to .NET Core 3.0
-* Use npm npx instead of global installations (thanks [jimbuck](https://github.com/jimbuck))
-
-ElectronNET.API:
-
-* Move to .NET Core 3.0
-* New Feature: Add BrowserWindow.RemoveMenu() (thanks [hack2root](https://github.com/hack2root))
-
-Thanks to [MaherJendoubi](https://github.com/MaherJendoubi), [kant2002](https://github.com/kant2002), [raz-canva](https://github.com/raz-canva) and [Daddoon](https://github.com/Daddoon) to give .NET Core 3.0 feedback!
-# 5.22.14
-
-ElectronNET.CLI:
-
-* Fixed bug: Build fails with latest electron-builder version [\#288](https://github.com/ElectronNET/Electron.NET/issues/288)
-
-ElectronNET.API:
-
-* New Feature: Full support for Auto Updater [(Based on electron-updater - Version 4.0.6)](https://www.electron.build/auto-update)
-* New Feature: Support for set a custom static Port to ASP.NET Backend [\#155](https://github.com/ElectronNET/Electron.NET/issues/155)
-* Fixed bug: Electron tray icon TypeError ([Electron original issue](https://github.com/electron/electron/issues/7657)) (thanks [Tum4ik](https://github.com/Tum4ik))
-* Fixed bug: Wrong tray icon path in the application built via `electronize build` command (thanks [Tum4ik](https://github.com/Tum4ik))
-* Fixed bug: fix async issue where same port is considered open [\#261](https://github.com/ElectronNET/Electron.NET/issues/261) (thanks [netpoetica](https://github.com/netpoetica))
-
-ElectronNET.WebApp:
-
-* Fixed usage of the `Electron.Tray.Show` according fixed bugs in the ElectronNET.CLI (thanks [Tum4ik](https://github.com/Tum4ik))
-
-# 5.22.13
-
-ElectronNET.API:
-
-* Fixed bug: Menu Item visibility [\#257](https://github.com/ElectronNET/Electron.NET/issues/257)
-* Fixed bug: electron.manifest.json - singleInstance not working [\#258](https://github.com/ElectronNET/Electron.NET/issues/258)
-* Fixed security issue: ASP.NET Core process is now bound to 127.0.0.1 instead of the broader localhost [\#258](https://github.com/ElectronNET/Electron.NET/pull/266)
-
-# 5.22.12
-
-ElectronNET.CLI:
-
-* New Feature: Changed from **electron packager** to [**electron builder**](https://www.electron.build/)
-* New Feature: 'add hosthook' command for add a ElectronHostHook-Directory
-* Fixed bug: 'Unexpected firewall warnings' [\#181](https://github.com/ElectronNET/Electron.NET/issues/181)
-* Fixed bug: 'found 8 vulnerabilities (1 low, 5 moderate, 2 high)' [\#199](https://github.com/ElectronNET/Electron.NET/pull/199)
-* Merged pull request: Call electronize from the Path instead of via dotnet in launchSettings.json [\#243](https://github.com/ElectronNET/Electron.NET/pull/243) (thanks [grbd](https://github.com/grbd))
-
-ElectronNET.API:
-
-* New Feature: Electron 5.0.1 support, but not all new features
-* New Feature: Auto Updater [(Based on electron-updater)](https://www.electron.build/auto-update)
-* New Feature: Splashscreen-Support
-* New Feature: HostHook-API for execute own TypeScript/JavaScript code on native Electron Main-Process
-* New Feature: Session-API functions
-* Fixed bug: Node process running after stopping app [\#96](https://github.com/ElectronNET/Electron.NET/issues/96)
-* Fixed bug: 'X and Y options to not work on Windows 10' [\#193](https://github.com/ElectronNET/Electron.NET/issues/193)
-* Fixed bug: Unable to clear cache [\#66](https://github.com/ElectronNET/Electron.NET/issues/66)
-* Merged pull request: Fix BrowserWindow::SetMenu [\#231](https://github.com/ElectronNET/Electron.NET/pull/231) thanks (thanks [CodeKenpachi](https://github.com/CodeKenpachi))
-* Merged pull request: FIX application hangs after socket reconnect [\#233](https://github.com/ElectronNET/Electron.NET/pull/233) (thanks [pedromrpinto](https://github.com/pedromrpinto))
-* Merged pull request: Reduce chance of detecting false positives when scanning subprocesses for errors. [\#236](https://github.com/ElectronNET/Electron.NET/pull/236) (thanks [BorisTheBrave](https://github.com/BorisTheBrave))
-* Merged pull request: Updates the C# API to accept floating point as in JS. [\#240](https://github.com/ElectronNET/Electron.NET/pull/240) (thanks [BorisTheBrave](https://github.com/BorisTheBrave))
-* Merged pull request: buildReleaseNuGetPackages should leave you in the same directory you β¦ [\#241](https://github.com/ElectronNET/Electron.NET/pull/241) (thanks [BorisTheBrave](https://github.com/BorisTheBrave))
-
-ElectronNET.WebApp:
-
-* Implemented a sample for the new HostHook-API
-* Fixed bug: 'Electron.NET API Demo: unable to copy code?' [\#247](https://github.com/ElectronNET/Electron.NET/issues/247)
-
-# 0.0.11
-
-ElectronNET.CLI:
-
-* Invoke 'npm install' without --prod flag to install needed devDependencies as well.
-* Enable SourceLink
-* NuGet Package License Information updated (deprecation of licenseUrl)
-
-ElectronNET.API:
-
-* Documentation added for WebContents.GetUrl()
-* Enable SourceLink
-* NuGet Package License Information updated (deprecation of licenseUrl)
-
-# 0.0.10
-
-ElectronNET.API:
-
-* manifestJsonFilePath fixed (thanks @smack0007)
-* Use Electron release 3.0.0 and updated packages (thanks @deinok)
-* fixes for Socket interaction (thanks @mojinxun)
-* Fixing SingleInstances (thanks @yaofeng)
-* Enhance WebContent.GetUrl (thanks @ru-sh)
-
-ElectronNET.CLI:
-
-* Show Resultcode for better debugging when using Build/Start Command
-* ElectronNET.CLI is now a global dotnet tool
-
-# 0.0.9
-
-ElectronNET.API:
-
-* Better Async handling - thanks @danielmarbach
-
-ElectronNET.CLI:
-
-* More options on the 'build' command, e.g. for a 32bit debug build with electron prune: build /target custom win7-x86;win32 /dotnet-configuration Debug /electron-arch ia32 /electron-params "--prune=true "
-* .NET Core project is now built with Release configuration but can be adjusted with the new params.
-* Be aware: "Breaking" (but because of the alpha status of this project, we won't use SemVer)
-
-# 0.0.8
-
-This version was skipped because we unfortunately released a pre-version of this on NuGet.
-
-# 0.0.7
-
-ElectronNET.CLI:
-
-* Fixed electronize start for macos/linux - thanks @yamachu
-* Skip NPM install on start when node_modules directory already exists
-
-# 0.0.6
-
-ElectronNET.CLI:
-
-* nuget packages are now release bits and have the correct assembly version
-* Version command
-* better devCleanup.cmd
-* Better Platform Support Issue - thanks to @Petermarcu
-* Start Command should now work on OSX/Linux - thanks to @r105m
-
-ElectronNET.API:
-
-* Thread-Safe methods - thanks to @yeskunall
-
-# 0.0.5
-
-ElectronNET.API:
-
-* The last nuget package didn't contain the actual webpreferences settings with defaults - hopefully now.
-
-# 0.0.4
-
-ElectronNET.CLI:
-
-* dotnet electronize start fixed
-
-ElectronNET.API:
-
-* WebPreferences settings with default values
-
-# 0.0.3
-
-ElectronNET.CLI:
-* Init with Debug profile
-* Build for all platforms (well... for newest OSX/Linux/Windows)
-
-ElectronNET.API:
-* Moar XML documentation
-* Hybrid support (e.g. running as normal website and electron app)
-* Event bugfixing
-
-# 0.0.2
-
-ElectronNET.CLI:
-* Added Init to Help page
-* Added XML documentation to NuGet output
-* Maybe fixed for https://github.com/GregorBiswanger/Electron.NET/issues/2
-
-ElectronNET.API:
-* Add XML documentation to NuGet output
-* Implemented Notification-, Dialog- & Tray-API
-
-# 0.0.1
-
-* init everything and basic functionality
diff --git a/NuGet.config b/NuGet.config
index c1e1ed5..53d8875 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -1,8 +1,11 @@
-
+
+
+
+
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c145a0d..812cddb 100644
--- a/README.md
+++ b/README.md
@@ -2,47 +2,56 @@
[](https://donorbox.org/electron-net) [](https://gitter.im/ElectronNET/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://github.com/ElectronNET/Electron.NET/actions/workflows/ci.yml)
-Build cross platform desktop apps with .NET 8 and Blazor, ASP.NET Core (Razor Pages, MVC).
+# Electron.Net Core is here!
-Electron.NET is a __wrapper__ around a native Electron application with an embedded ASP.NET Core application. Via our Electron.NET IPC bridge we can invoke Electron APIs from .NET.
+## A Complete Transformation
-The CLI extensions hosts our toolset to build and start Electron.NET applications.
+ElectronNET.Core represents a fundamental modernization of Electron.NET, addressing years of accumulated pain points while preserving full API compatibility. This isn't just an updateβit's a complete rethinking of how .NET developers build and debug cross-platform desktop applications with Electron.
-## Wait - you host a .NET Core app inside Electron? Why?
+Read more: [**What's New in ElectronNET.Core**](wiki/What's-New)
-Well... there are lots of different approaches how to get a X-plat desktop app running. We thought it would be nice for .NET devs to use the ASP.NET Core environment and just embed it inside a pretty robust X-plat enviroment called Electron. Porting Electron to .NET is not a goal of this project, at least we don't have any clue how to do it. We just combine ASP.NET Core & Electron.
+
+Build cross platform desktop applications with .NET 6/8 - from console apps to ASP.Net Core (Razor Pages, MVC) to Blazor
+
+
+## Wait - how does that work exactly?
+
+Well... there are lots of different approaches how to get a X-plat desktop app running. Electron.NET provides a range of ways to build .NET based solutions using Electron at the side of presentation. While the classic Electron.Net setup, using an ASP.Net host ran by the Electron side is still the primary way, there's more flexibility now: both, dotnet and Electron are now able to launch the other for better lifetime management, and when you don't need a local web server - like when running content from files or remote servers, you can drop the ASP.Net stack altogether and got with a lightweight console app instead.
## π¦ NuGet
-[ ElectronNET.API ](https://www.nuget.org/packages/ElectronNET.API/) | [ ElectronNET.CLI](https://www.nuget.org/packages/ElectronNET.CLI/)
+[ 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/)
+
## π Requirements to Run
-The current Electron.NET CLI builds Windows/macOS/Linux binaries. Our API uses .NET 8, so our minimum base OS is the same as [.NET 8](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md).
+ Our API uses .NET 6/8, so our
Also you should have installed:
-* npm [contained in nodejs (at least Version 16.17.1)](https://nodejs.org)
+* .NET 6/8 or later
+* OS
+ 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).
+* NodeJS (at least [Version 22.x](https://nodejs.org))
-## π¬ Community
-[](https://gitter.im/ElectronNET/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+## π©βπ« Usage with ASP.Net
-Besides the chat on Gitter and the issues [discussed here](https://github.com/ElectronNET/Electron.NET/issues) you can also use [StackOverflow](https://stackoverflow.com/questions/tagged/electron.net) with the tag `electron.net`.
-
-If you want to sponsor the further maintenance and development of this project [see the donate section](#π-donate).
-
-## π©βπ« Usage
-
-To activate and communicate with the "native" (sort of native...) Electron API include the [ElectronNET.API NuGet package](https://www.nuget.org/packages/ElectronNET.API/) in your ASP.NET Core app.
+- Create a new ASP.Net Core project
+- Install the following two nuget packages:
```ps1
-PM> Install-Package ElectronNET.API
+dotnet add package ElectronNET.Core
+
+dotnet add package ElectronNET.Core.AspNet
```
-## Setup Using Minimal-API
+### Enable Electron.NET on Startup
-You start Electron.NET up with an `UseElectron` WebHostBuilder-Extension and open the Electron Window:
+To do so, use the `UseElectron` extension method on a `WebApplicationBuilder`, an `IWebHostBuilder` or any descendants.
+
+> [!NOTE]
+> New in Electron.NET Core is that you provide a callback method as an argument to `UseElectron()`, which ensures that you get to know the right moment to set up your application UI.
### Program.cs
@@ -50,143 +59,46 @@ You start Electron.NET up with an `UseElectron` WebHostBuilder-Extension and ope
using ElectronNET.API;
using ElectronNET.API.Entities;
-var builder = WebApplication.CreateBuilder(args);
-builder.WebHost.UseElectron(args);
+ public static void Main(string[] args)
+ {
+ WebHost.CreateDefaultBuilder(args)
+ .UseElectron(args, ElectronAppReady)
+ .UseStartup()
+ .Build()
+ .Run();
+ }
-// Is optional, but you can use the Electron.NET API-Classes directly with DI (relevant if you want more encoupled code)
-builder.Services.AddElectron();
+ public static async Task ElectronAppReady()
+ {
+ var browserWindow = await Electron.WindowManager.CreateWindowAsync(
+ new BrowserWindowOptions { Show = false });
-var app = builder.Build();
-
-...
-
-await app.StartAsync();
-
-// Open the Electron-Window here
-await Electron.WindowManager.CreateWindowAsync();
-
-app.WaitForShutdown();
+ browserWindow.OnReadyToShow += () => browserWindow.Show();
+ }
```
-## Setup using Normal-API
-### Program.cs
+## π Starting and Debugging the Application
-You start Electron.NET up with an `UseElectron` WebHostBuilder-Extension.
+Just press F5 in Visual Studio or use dotnet for debugging.
-```csharp
-public static IHostBuilder CreateHostBuilder(string[] args) =>
- Host.CreateDefaultBuilder(args)
- .ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.UseElectron(args);
- webBuilder.UseStartup();
- });
-```
-
-### Startup.cs
-
-Open the Electron Window in the *Startup.cs* file:
-
-```csharp
-public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
-{
- //...
-
- Electron.WindowManager.CreateWindowAsync();
-}
-```
-
-## π Starting the Application
-
-To start the application make sure you have installed the "[ElectronNET.CLI](https://www.nuget.org/packages/ElectronNET.CLI/)" packages as global tool:
-
-```sh
-dotnet tool install ElectronNET.CLI -g
-```
-
-At the first time, you need an Electron.NET project initialization. Type the following command in your ASP.NET Core folder:
-
-```sh
-electronize init
-```
-
-* Now a electronnet.manifest.json should appear in your ASP.NET Core project
-* Now run the following:
-
-```sh
-electronize start
-```
-
-### Note
-
-> Only the first `electronize start` is slow. The next will go on faster.
-
-## π Develop Electron.NET apps using a file watcher
-
-The file watcher is included with version 8.31.1 of Electron.NET. For example, a file change can trigger compilation, test execution, or deployment. The Electron.NET window will automatically refresh and new code changes will be visible more quickly. The following Electron.NET CLI command is required:
-
-```sh
-electronize start /watch
-```
-
-### Note
-
-> Only the first `electronize start` is slow. The next will go on faster.
-
-## π Debugging the Application
-
-Start your Electron.NET application with the Electron.NET CLI command. In Visual Studio attach to your running application instance. Go in the __Debug__ Menu and click on __Attach to Process...__. Sort by your projectname on the right and select it on the list.
## π Usage of the Electron API
-A complete documentation will follow. Until then take a look in the source code of the sample application:
-[Electron.NET API Demos](https://github.com/ElectronNET/electron.net-api-demos)
+A complete documentation is available on the Wiki.
In this YouTube video, we show you how you can create a new project, use the Electron.NET API, debug a application and build an executable desktop app for Windows: [Electron.NET - Getting Started](https://www.youtube.com/watch?v=nuM6AojRFHk)
-## β Building Release Artifacts
+ > [!NOTE]
+ > The video hasn't been updated for the changes in ElectronNET.Core, so it is partially outdated.
-Here you need the Electron.NET CLI as well. Type the following command in your ASP.NET Core folder:
-```sh
-electronize build /target win
-```
-
-There are additional platforms available:
-
-```sh
-electronize build /target win
-electronize build /target osx
-electronize build /target linux
-```
-
-Those three "default" targets will produce x64 packages for those platforms.
-
-For certain NuGet packages or certain scenarios you may want to build a pure x86 application. To support those things you can define the desired [.NET Core runtime](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog), the [electron platform](https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#platform) and [electron architecture](https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#arch) like this:
-
-```sh
-electronize build /target custom "win7-x86;win32" /electron-arch ia32
-```
-
-The end result should be an electron app under your __/bin/desktop__ folder.
-
-### Note
-
-> 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.
-
-## π Update
-
-After an update to the latest Electron.API package, an update to the latest Electron.CLI is always required. In addition, always update the CLI via NuGet:
-
-```sh
-dotnet tool update ElectronNET.CLI -g
-```
## π¨βπ» Authors
* **[Gregor Biswanger](https://github.com/GregorBiswanger)** - (Microsoft MVP, Intel Black Belt and Intel Software Innovator) is a freelance lecturer, consultant, trainer, author and speaker. He is a consultant for large and medium-sized companies, organizations and agencies for software architecture, web- and cross-platform development. You can find Gregor often on the road attending or speaking at international conferences. - [Cross-Platform-Blog](http://www.cross-platform-blog.com) - Twitter [@BFreakout](https://www.twitter.com/BFreakout)
* **[Dr. Florian Rappl](https://github.com/FlorianRappl)** - Software Developer - from Munich, Germany. Microsoft MVP & Web Geek. - [The Art of Micro Frontends](https://microfrontends.art) - [Homepage](https://florian-rappl.de) - Twitter [@florianrappl](https://twitter.com/florianrappl)
+* [**softworkz**](https://github.com/softworkz) - full range developer - likes to start where others gave up - MS MVP alumni and Munich citizen as well
* **[Robert Muehsig](https://github.com/robertmuehsig)** - Software Developer - from Dresden, Germany, now living & working in Switzerland. Microsoft MVP & Web Geek. - [codeinside Blog](https://blog.codeinside.eu) - Twitter [@robert0muehsig](https://twitter.com/robert0muehsig)
See also the list of [contributors](https://github.com/ElectronNET/Electron.NET/graphs/contributors) who participated in this project.
@@ -196,15 +108,6 @@ See also the list of [contributors](https://github.com/ElectronNET/Electron.NET/
Feel free to submit a pull request if you find any bugs (to see a list of active issues, visit the [Issues section](https://github.com/ElectronNET/Electron.NET/issues).
Please make sure all commits are properly documented.
-## π§ͺ Working with this Repo
-
-This video provides an introduction to development for Electron.NET: [Electron.NET - Contributing Getting Started](https://youtu.be/Po-saU_Z6Ws)
-
-This repository consists of the main parts (API & CLI) and it's own "playground" ASP.NET Core application. Both main parts produce local NuGet packages, that are versioned with 99.0.0. The first thing you will need is to run one of the build scripts (.cmd or .ps1 for Windows, the .sh for macOS/Linux).
-
-If you look for pure __[demo projects](https://github.com/ElectronNET)__ checkout the other repositories.
-
-The problem working with this repository is, that NuGet has a pretty aggressive cache, see [here for further information](https://github.com/ElectronNET/Electron.NET/wiki).
## π Donate
@@ -225,102 +128,5 @@ MIT-licensed. See [LICENSE](./LICENSE) for details.
**Enjoy!**
-## π Important notes
-### ElectronNET.API & ElectronNET.CLI Version 9.31.2
-Make sure you also have the new Electron.NET API & CLI 9.31.2 version.
-
-```sh
-dotnet tool update ElectronNET.CLI -g
-```
-
-This now uses [electron-builder](https://www.electron.build/configuration/configuration) and the necessary configuration to build is made in the **electron.manifest.json** file (on the build part). In addition, own Electron.NET configurations are stored (on the root).
-
-Please make sure that your **electron.manifest.json** file has the following new structure:
-
-```json
-{
- "executable": "{{executable}}",
- "splashscreen": {
- "imageFile": ""
- },
- "name": "{{executable}}",
- "author": "",
- "singleInstance": false,
- "build": {
- "appId": "com.{{executable}}.app",
- "productName": "{{executable}}",
- "copyright": "Copyright Β© 2020",
- "buildVersion": "1.0.0",
- "compression": "maximum",
- "directories": {
- "output": "../../../bin/Desktop"
- },
- "extraResources": [
- {
- "from": "./bin",
- "to": "bin",
- "filter": ["**/*"]
- }
- ],
- "files": [
- {
- "from": "./ElectronHostHook/node_modules",
- "to": "ElectronHostHook/node_modules",
- "filter": ["**/*"]
- },
- "**/*"
- ]
- }
-}
-```
-
-### ElectronNET.CLI Version 0.0.9
-
-In the Version 0.0.9 the CLI was not a global tool and needed to be registered like this in the *.csproj*:
-
-```xml
-
-
-
-```
-
-After you edited the *.csproj* file, you need to restore your NuGet packages within your Project. Run the following command in your ASP.NET Core folder:
-
-```sh
-dotnet restore
-```
-
-If you still use this version you will need to invoke it like this:
-
-```sh
-electronize ...
-```
-
-### Node.js Integration
-
-Electron.NET requires Node.js integration to be enabled for IPC to function. If you are not using the IPC functionality you can disable Node.js integration like so:
-
-```csharp
-WebPreferences wp = new WebPreferences();
-wp.NodeIntegration = false;
-BrowserWindowOptions browserWindowOptions = new BrowserWindowOptions
-{
- WebPreferences = wp
-};
-
-```
-
-### Dependency Injection
-
-ElectronNET.API can be added to your DI container within the `Startup` class. All of the modules available in Electron will be added as Singletons.
-
-```csharp
-using ElectronNET.API;
-
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddElectron();
-}
-```
diff --git a/artifacts/ElectronNET.Core.0.0.18.nupkg b/artifacts/ElectronNET.Core.0.0.18.nupkg
new file mode 100644
index 0000000..3c863a8
Binary files /dev/null and b/artifacts/ElectronNET.Core.0.0.18.nupkg differ
diff --git a/artifacts/ElectronNET.Core.API.0.0.18.nupkg b/artifacts/ElectronNET.Core.API.0.0.18.nupkg
new file mode 100644
index 0000000..9aa86ab
Binary files /dev/null and b/artifacts/ElectronNET.Core.API.0.0.18.nupkg differ
diff --git a/artifacts/ElectronNET.Core.API.0.0.18.snupkg b/artifacts/ElectronNET.Core.API.0.0.18.snupkg
new file mode 100644
index 0000000..4477241
Binary files /dev/null and b/artifacts/ElectronNET.Core.API.0.0.18.snupkg differ
diff --git a/artifacts/ElectronNET.Core.AspNet.0.0.18.nupkg b/artifacts/ElectronNET.Core.AspNet.0.0.18.nupkg
new file mode 100644
index 0000000..f6cde1d
Binary files /dev/null and b/artifacts/ElectronNET.Core.AspNet.0.0.18.nupkg differ
diff --git a/artifacts/ElectronNET.Core.AspNet.0.0.18.snupkg b/artifacts/ElectronNET.Core.AspNet.0.0.18.snupkg
new file mode 100644
index 0000000..e6517aa
Binary files /dev/null and b/artifacts/ElectronNET.Core.AspNet.0.0.18.snupkg differ
diff --git a/docs/.docproj/DocProj.props b/docs/.docproj/DocProj.props
new file mode 100644
index 0000000..88e67e5
--- /dev/null
+++ b/docs/.docproj/DocProj.props
@@ -0,0 +1,27 @@
+
+
+
+
+ 8.1
+ true
+ true
+
+
+
+ **/*.md;**/*.markdown
+ **/*.png;**/*.bmp;**/*.jpg;**/*.gif;**/*.mp4
+ **/*.css;**/*.js;**/*.json
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.docproj/DocProj.targets b/docs/.docproj/DocProj.targets
new file mode 100644
index 0000000..6d7ee06
--- /dev/null
+++ b/docs/.docproj/DocProj.targets
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/API/App.md b/docs/API/App.md
new file mode 100644
index 0000000..3ea5d3b
--- /dev/null
+++ b/docs/API/App.md
@@ -0,0 +1,489 @@
+# Electron.App
+
+Control your application's event lifecycle.
+
+## Overview
+
+The `Electron.App` API provides comprehensive control over your application's lifecycle, including startup, shutdown, window management, and system integration. It handles application-level events and provides methods for managing the overall application state.
+
+## Properties
+
+#### π `CommandLine`
+A `CommandLine` object that allows you to read and manipulate the command line arguments that Chromium uses.
+
+#### π `IsReady`
+Application host fully started.
+
+#### π `Name`
+A string property that indicates the current application's name, which is the name in the application's package.json file.
+
+Usually the name field of package.json is a short lowercase name, according to the npm modules spec. You should usually also specify a productName field, which is your application's full capitalized name, and which will be preferred over name by Electron.
+
+#### π `NameAsync`
+A `Task` property that indicates the current application's name, which is the name in the application's package.json file.
+
+Usually the name field of package.json is a short lowercase name, according to the npm modules spec. You should usually also specify a productName field, which is your application's full capitalized name, and which will be preferred over name by Electron.
+
+#### π `UserAgentFallback`
+A string which is the user agent string Electron will use as a global fallback.
+
+This is the user agent that will be used when no user agent is set at the webContents or session level. It is useful for ensuring that your entire app has the same user agent. Set to a custom value as early as possible in your app's initialization to ensure that your overridden value is used.
+
+#### π `UserAgentFallbackAsync`
+A `Task` which is the user agent string Electron will use as a global fallback.
+
+This is the user agent that will be used when no user agent is set at the webContents or session level. It is useful for ensuring that your entire app has the same user agent. Set to a custom value as early as possible in your app's initialization to ensure that your overridden value is used.
+
+## Methods
+
+#### π§ `void AddRecentDocument(string path)`
+Adds path to the recent documents list. This list is managed by the OS. On Windows you can visit the list from the task bar, and on macOS you can visit it from dock menu.
+
+#### π§ `void ClearRecentDocuments()`
+Clears the recent documents list.
+
+#### π§ `void Exit(int exitCode = 0)`
+All windows will be closed immediately without asking user and the BeforeQuit and WillQuit events will not be emitted.
+
+**Parameters:**
+- `exitCode` - Exits immediately with exitCode. exitCode defaults to 0.
+
+#### π§ `void Focus()`
+On Linux, focuses on the first visible window. On macOS, makes the application the active app. On Windows, focuses on the application's first window.
+
+#### π§ `void Focus(FocusOptions focusOptions)`
+On Linux, focuses on the first visible window. On macOS, makes the application the active app. On Windows, focuses on the application's first window.
+
+You should seek to use the `FocusOptions.Steal` option as sparingly as possible.
+
+**Parameters:**
+- `focusOptions` - Focus options
+
+#### π§ `Task GetAppMetricsAsync(CancellationToken cancellationToken = default)`
+Memory and cpu usage statistics of all the processes associated with the app.
+
+**Returns:**
+
+Array of ProcessMetric objects that correspond to memory and cpu usage statistics of all the processes associated with the app.
+
+#### π§ `Task GetAppPathAsync(CancellationToken cancellationToken = default)`
+The current application directory.
+
+**Returns:**
+
+The current application directory.
+
+#### π§ `Task GetBadgeCountAsync(CancellationToken cancellationToken = default)`
+The current value displayed in the counter badge.
+
+**Returns:**
+
+The current value displayed in the counter badge.
+
+#### π§ `Task GetCurrentActivityTypeAsync(CancellationToken cancellationToken = default)`
+The type of the currently running activity.
+
+**Returns:**
+
+The type of the currently running activity.
+
+#### π§ `Task GetGpuFeatureStatusAsync(CancellationToken cancellationToken = default)`
+The Graphics Feature Status from chrome://gpu/.
+
+Note: This information is only usable after the gpu-info-update event is emitted.
+
+**Returns:**
+
+The Graphics Feature Status from chrome://gpu/.
+
+#### π§ `Task GetJumpListSettingsAsync(CancellationToken cancellationToken = default)`
+Jump List settings for the application.
+
+**Returns:**
+
+Jump List settings.
+
+#### π§ `Task GetLocaleAsync(CancellationToken cancellationToken = default)`
+The current application locale. Possible return values are documented [here](https://www.electronjs.org/docs/api/locales).
+
+Note: When distributing your packaged app, you have to also ship the locales folder.
+
+Note: On Windows, you have to call it after the Ready events gets emitted.
+
+**Returns:**
+
+The current application locale.
+
+#### π§ `Task GetLoginItemSettingsAsync(CancellationToken cancellationToken = default)`
+If you provided path and args options to SetLoginItemSettings then you need to pass the same arguments here for LoginItemSettings.OpenAtLogin to be set correctly.
+
+**Returns:**
+
+Login item settings.
+
+#### π§ `Task GetPathAsync(PathName pathName, CancellationToken cancellationToken = default)`
+The path to a special directory. If GetPathAsync is called without called SetAppLogsPath being called first, a default directory will be created equivalent to calling SetAppLogsPath without a path parameter.
+
+**Parameters:**
+- `pathName` - Special directory.
+
+**Returns:**
+
+A path to a special directory or file associated with name.
+
+#### π§ `Task GetVersionAsync(CancellationToken cancellationToken = default)`
+The version of the loaded application. If no version is found in the application's package.json file, the version of the current bundle or executable is returned.
+
+**Returns:**
+
+The version of the loaded application.
+
+#### π§ `Task HasSingleInstanceLockAsync(CancellationToken cancellationToken = default)`
+This method returns whether or not this instance of your app is currently holding the single instance lock. You can request the lock with RequestSingleInstanceLockAsync and release with ReleaseSingleInstanceLock.
+
+**Returns:**
+
+Whether this instance of your app is currently holding the single instance lock.
+
+#### π§ `void Hide()`
+Hides all application windows without minimizing them.
+
+#### π§ `Task ImportCertificateAsync(ImportCertificateOptions options, CancellationToken cancellationToken = default)`
+Imports the certificate in pkcs12 format into the platform certificate store. callback is called with the result of import operation, a value of 0 indicates success while any other value indicates failure according to chromium net_error_list.
+
+**Parameters:**
+- `options` - Import certificate options
+- `cancellationToken` - The cancellation token
+
+**Returns:**
+
+Result of import. Value of 0 indicates success.
+
+#### π§ `void InvalidateCurrentActivity()`
+Invalidates the current Handoff user activity.
+
+#### π§ `Task IsAccessibilitySupportEnabledAsync(CancellationToken cancellationToken = default)`
+true if Chrome's accessibility support is enabled, false otherwise. This API will return true if the use of assistive technologies, such as screen readers, has been detected. See Chromium's accessibility docs for more details.
+
+**Returns:**
+
+true if Chrome's accessibility support is enabled, false otherwise.
+
+#### π§ `Task IsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)`
+This method checks if the current executable is the default handler for a protocol (aka URI scheme).
+
+Note: On macOS, you can use this method to check if the app has been registered as the default protocol handler for a protocol. You can also verify this by checking ~/Library/Preferences/com.apple.LaunchServices.plist on the macOS machine. Please refer to Apple's documentation for details.
+
+The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
+
+**Parameters:**
+- `protocol` - The name of your protocol, without ://
+- `cancellationToken` - The cancellation token
+
+**Returns:**
+
+Whether the current executable is the default handler for a protocol (aka URI scheme).
+
+#### π§ `Task IsUnityRunningAsync(CancellationToken cancellationToken = default)`
+Whether the current desktop environment is Unity launcher.
+
+**Returns:**
+
+Whether the current desktop environment is Unity launcher.
+
+#### π§ `void Quit()`
+Try to close all windows. The BeforeQuit event will be emitted first. If all windows are successfully closed, the WillQuit event will be emitted and by default the application will terminate. This method guarantees that all beforeunload and unload event handlers are correctly executed. It is possible that a window cancels the quitting by returning false in the beforeunload event handler.
+
+#### π§ `void ReleaseSingleInstanceLock()`
+Releases all locks that were created by makeSingleInstance. This will allow multiple instances of the application to once again run side by side.
+
+#### π§ `void Relaunch()`
+Relaunches the app when current instance exits. By default the new instance will use the same working directory and command line arguments with current instance.
+
+Note that this method does not quit the app when executed, you have to call Quit or Exit after calling Relaunch() to make the app restart.
+
+When Relaunch() is called for multiple times, multiple instances will be started after current instance exited.
+
+#### π§ `void Relaunch(RelaunchOptions relaunchOptions)`
+Relaunches the app when current instance exits. By default the new instance will use the same working directory and command line arguments with current instance. When RelaunchOptions.Args is specified, the RelaunchOptions.Args will be passed as command line arguments instead. When RelaunchOptions.ExecPath is specified, the RelaunchOptions.ExecPath will be executed for relaunch instead of current app.
+
+Note that this method does not quit the app when executed, you have to call Quit or Exit after calling Relaunch() to make the app restart.
+
+When Relaunch() is called for multiple times, multiple instances will be started after current instance exited.
+
+**Parameters:**
+- `relaunchOptions` - Options for the relaunch
+
+#### π§ `Task RemoveAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)`
+This method checks if the current executable as the default handler for a protocol (aka URI scheme). If so, it will remove the app as the default handler.
+
+**Parameters:**
+- `protocol` - The name of your protocol, without ://
+- `cancellationToken` - The cancellation token
+
+**Returns:**
+
+Whether the call succeeded.
+
+#### π§ `Task RequestSingleInstanceLockAsync(Action newInstanceOpened, CancellationToken cancellationToken = default)`
+The return value of this method indicates whether or not this instance of your application successfully obtained the lock. If it failed to obtain the lock, you can assume that another instance of your application is already running with the lock and exit immediately.
+
+I.e.This method returns true if your process is the primary instance of your application and your app should continue loading. It returns false if your process should immediately quit as it has sent its parameters to another instance that has already acquired the lock.
+
+On macOS, the system enforces single instance automatically when users try to open a second instance of your app in Finder, and the open-file and open-url events will be emitted for that.However when users start your app in command line, the system's single instance mechanism will be bypassed, and you have to use this method to ensure single instance.
+
+**Parameters:**
+- `newInstanceOpened` - Lambda with an array of the second instance's command line arguments. The second parameter is the working directory path.
+- `cancellationToken` - The cancellation token
+
+**Returns:**
+
+This method returns false if your process is the primary instance of the application and your app should continue loading. And returns true if your process has sent its parameters to another instance, and you should immediately quit.
+
+#### π§ `void ResignCurrentActivity()`
+Marks the current Handoff user activity as inactive without invalidating it.
+
+#### π§ `void SetAccessibilitySupportEnabled(bool enabled)`
+Manually enables Chrome's accessibility support, allowing to expose accessibility switch to users in application settings. See Chromium's accessibility docs for more details. Disabled (false) by default.
+
+This API must be called after the Ready event is emitted.
+
+Note: Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default.
+
+**Parameters:**
+- `enabled` - Enable or disable accessibility tree rendering
+
+#### π§ `void SetAppLogsPath(string path)`
+Sets or creates a directory your app's logs which can then be manipulated with GetPathAsync or SetPath.
+
+Calling SetAppLogsPath without a path parameter will result in this directory being set to ~/Library/Logs/YourAppName on macOS, and inside the userData directory on Linux and Windows.
+
+**Parameters:**
+- `path` - A custom path for your logs. Must be absolute
+
+#### π§ `void SetAppUserModelId(string id)`
+Changes the Application User Model ID to id.
+
+**Parameters:**
+- `id` - Model Id
+
+#### π§ `Task SetAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)`
+Sets the current executable as the default handler for a protocol (aka URI scheme). It allows you to integrate your app deeper into the operating system. Once registered, all links with your-protocol:// will be opened with the current executable. The whole link, including protocol, will be passed to your application as a parameter.
+
+Note: On macOS, you can only register protocols that have been added to your app's info.plist, which cannot be modified at runtime. However, you can change the file during build time via Electron Forge, Electron Packager, or by editing info.plist with a text editor. Please refer to Apple's documentation for details.
+
+Note: In a Windows Store environment (when packaged as an appx) this API will return true for all calls but the registry key it sets won't be accessible by other applications. In order to register your Windows Store application as a default protocol handler you must declare the protocol in your manifest.
+
+The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
+
+**Parameters:**
+- `protocol` - The name of your protocol, without ://. For example, if you want your app to handle electron:// links, call this method with electron as the parameter.
+- `cancellationToken` - The cancellation token
+
+**Returns:**
+
+Whether the call succeeded.
+
+#### π§ `Task SetBadgeCountAsync(int count, CancellationToken cancellationToken = default)`
+Sets the counter badge for current app. Setting the count to 0 will hide the badge. On macOS it shows on the dock icon. On Linux it only works for Unity launcher.
+
+Note: Unity launcher requires the existence of a .desktop file to work, for more information please read Desktop Environment Integration.
+
+**Parameters:**
+- `count` - Counter badge
+- `cancellationToken` - The cancellation token
+
+**Returns:**
+
+Whether the call succeeded.
+
+#### π§ `void SetJumpList(JumpListCategory[] categories)`
+Sets or removes a custom Jump List for the application. If categories is null the previously set custom Jump List (if any) will be replaced by the standard Jump List for the app (managed by Windows).
+
+Note: If a JumpListCategory object has neither the Type nor the Name property set then its Type is assumed to be tasks. If the Name property is set but the Type property is omitted then the Type is assumed to be custom.
+
+Note: Users can remove items from custom categories, and Windows will not allow a removed item to be added back into a custom category until after the next successful call to SetJumpList. Any attempt to re-add a removed item to a custom category earlier than that will result in the entire custom category being omitted from the Jump List. The list of removed items can be obtained using GetJumpListSettingsAsync.
+
+**Parameters:**
+- `categories` - Array of JumpListCategory objects
+
+#### π§ `void SetLoginItemSettings(LoginSettings loginSettings)`
+Set the app's login item settings.
+
+To work with Electron's autoUpdater on Windows, which uses Squirrel, you'll want to set the launch path to Update.exe, and pass arguments that specify your application name.
+
+**Parameters:**
+- `loginSettings` - Login settings
+
+#### π§ `void SetPath(PathName name, string path)`
+Overrides the path to a special directory or file associated with name. If the path specifies a directory that does not exist, an Error is thrown. In that case, the directory should be created with fs.mkdirSync or similar.
+
+You can only override paths of a name defined in GetPathAsync.
+
+By default, web pages' cookies and caches will be stored under the PathName.UserData directory. If you want to change this location, you have to override the PathName.UserData path before the Ready event of the App module is emitted.
+
+**Parameters:**
+- `name` - Special directory name
+- `path` - New path to a special directory
+
+#### π§ `void SetUserActivity(string type, object userInfo)`
+Creates an NSUserActivity and sets it as the current activity. The activity is eligible for Handoff to another device afterward.
+
+**Parameters:**
+- `type` - Uniquely identifies the activity. Maps to NSUserActivity.activityType.
+- `userInfo` - App-specific state to store for use by another device
+
+#### π§ `Task SetUserTasksAsync(UserTask[] userTasks, CancellationToken cancellationToken = default)`
+Adds tasks to the UserTask category of the JumpList on Windows.
+
+Note: If you'd like to customize the Jump List even more use SetJumpList instead.
+
+**Parameters:**
+- `userTasks` - Array of UserTask objects
+- `cancellationToken` - The cancellation token
+
+**Returns:**
+
+Whether the call succeeded.
+
+#### π§ `void Show()`
+Shows application windows after they were hidden. Does not automatically focus them.
+
+#### π§ `void ShowAboutPanel()`
+Show the app's about panel options. These options can be overridden with SetAboutPanelOptions.
+
+## Events
+
+#### β‘ `AccessibilitySupportChanged`
+Emitted when Chrome's accessibility support changes. This event fires when assistive technologies, such as screen readers, are enabled or disabled. See https://www.chromium.org/developers/design-documents/accessibility for more details.
+
+#### β‘ `BrowserWindowBlur`
+Emitted when a BrowserWindow blurred.
+
+#### β‘ `BrowserWindowCreated`
+Emitted when a new BrowserWindow is created.
+
+#### β‘ `BrowserWindowFocus`
+Emitted when a BrowserWindow gets focused.
+
+#### β‘ `OpenFile`
+Emitted when a macOS user wants to open a file with the application. The open-file event is usually emitted when the application is already open and the OS wants to reuse the application to open the file. open-file is also emitted when a file is dropped onto the dock and the application is not yet running.
+
+On Windows, you have to parse the arguments using App.CommandLine to get the filepath.
+
+#### β‘ `OpenUrl`
+Emitted when a macOS user wants to open a URL with the application. Your application's Info.plist file must define the URL scheme within the CFBundleURLTypes key, and set NSPrincipalClass to AtomApplication.
+
+#### β‘ `Quitting`
+Emitted when the application is quitting.
+
+Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
+
+#### β‘ `Ready`
+Emitted when the application has finished basic startup.
+
+#### β‘ `WebContentsCreated`
+Emitted when a new WebContents is created.
+
+#### β‘ `WillQuit`
+Emitted when all windows have been closed and the application will quit.
+
+See the description of the WindowAllClosed event for the differences between the WillQuit and WindowAllClosed events.
+
+Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
+
+#### β‘ `WindowAllClosed`
+Emitted when all windows have been closed.
+
+If you do not subscribe to this event and all windows are closed, the default behavior is to quit the app; however, if you subscribe, you control whether the app quits or not.If the user pressed Cmd + Q, or the developer called Quit, Electron will first try to close all the windows and then emit the WillQuit event, and in this case the WindowAllClosed event would not be emitted.
+
+## Usage Examples
+
+### Application Lifecycle
+
+```csharp
+// Handle app startup
+Electron.App.Ready += () =>
+{
+ Console.WriteLine("App is ready!");
+};
+
+// Handle window management
+Electron.App.WindowAllClosed += () =>
+{
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ Electron.App.Quit();
+ }
+};
+
+// Prevent quit
+Electron.App.BeforeQuit += async (args) =>
+{
+ var result = await Electron.Dialog.ShowMessageBoxAsync("Do you want to quit?");
+ if (result.Response == 1) // Cancel
+ {
+ args.PreventDefault = true;
+ }
+};
+```
+
+### Protocol Handling
+
+```csharp
+// Register custom protocol
+var success = await Electron.App.SetAsDefaultProtocolClientAsync("myapp");
+
+// Check if registered
+var isDefault = await Electron.App.IsDefaultProtocolClientAsync("myapp");
+```
+
+### Jump List (Windows)
+
+```csharp
+// Set user tasks
+await Electron.App.SetUserTasksAsync(new[]
+{
+ new UserTask
+ {
+ Program = "myapp.exe",
+ Arguments = "--new-document",
+ Title = "New Document",
+ Description = "Create a new document"
+ }
+});
+```
+
+### Application Information
+
+```csharp
+// Get app information
+var appPath = await Electron.App.GetAppPathAsync();
+var version = await Electron.App.GetVersionAsync();
+var locale = await Electron.App.GetLocaleAsync();
+
+// Set app name
+await Electron.App.NameAsync; // Get current name
+Electron.App.Name = "My Custom App Name";
+```
+
+### Badge Count (macOS/Linux)
+
+```csharp
+// Set badge count
+await Electron.App.SetBadgeCountAsync(5);
+
+// Get current badge count
+var count = await Electron.App.GetBadgeCountAsync();
+```
+
+## Related APIs
+
+- [Electron.WindowManager](WindowManager.md) - Window creation and management
+- [Electron.Dialog](Dialog.md) - User interaction dialogs
+- [Electron.Menu](Menu.md) - Application menus
+
+## Additional Resources
+
+- [Electron App Documentation](https://electronjs.org/docs/api/app) - Official Electron app API
+- [Startup Methods](../Using/Startup-Methods.md) - Different application startup modes
diff --git a/docs/API/AutoUpdater.md b/docs/API/AutoUpdater.md
new file mode 100644
index 0000000..98414b1
--- /dev/null
+++ b/docs/API/AutoUpdater.md
@@ -0,0 +1,243 @@
+# Electron.AutoUpdater
+
+Handle application updates and installation processes.
+
+## Overview
+
+The `Electron.AutoUpdater` API provides comprehensive functionality for handling application updates, including checking for updates, downloading, and installation. It supports multiple update providers and platforms with automatic update capabilities.
+
+## Properties
+
+#### π `bool AllowDowngrade`
+Whether to allow version downgrade. Default is false.
+
+#### π `bool AllowPrerelease`
+GitHub provider only. Whether to allow update to pre-release versions. Defaults to true if application version contains prerelease components.
+
+#### π `bool AutoDownload`
+Whether to automatically download an update when it is found. Default is true.
+
+#### π `bool AutoInstallOnAppQuit`
+Whether to automatically install a downloaded update on app quit. Applicable only on Windows and Linux.
+
+#### π `string Channel`
+Get the update channel. Not applicable for GitHub. Doesn't return channel from the update configuration, only if was previously set.
+
+#### π `Task ChannelAsync`
+Get the update channel. Not applicable for GitHub. Doesn't return channel from the update configuration, only if was previously set.
+
+#### π `Task CurrentVersionAsync`
+Get the current application version.
+
+#### π `bool FullChangelog`
+GitHub provider only. Get all release notes (from current version to latest), not just the latest. Default is false.
+
+#### π `Dictionary RequestHeaders`
+The request headers.
+
+#### π `Task> RequestHeadersAsync`
+Get the current request headers.
+
+#### π `string UpdateConfigPath`
+For test only. Configuration path for updates.
+
+## Methods
+
+#### π§ `Task CheckForUpdatesAndNotifyAsync()`
+Asks the server whether there is an update and notifies the user if an update is available.
+
+#### π§ `Task CheckForUpdatesAsync()`
+Asks the server whether there is an update.
+
+#### π§ `Task DownloadUpdateAsync()`
+Start downloading update manually. Use this method if AutoDownload option is set to false.
+
+**Returns:**
+
+Path to downloaded file.
+
+#### π§ `Task GetFeedURLAsync()`
+Get the current feed URL.
+
+**Returns:**
+
+Feed URL.
+
+#### π§ `void QuitAndInstall(bool isSilent = false, bool isForceRunAfter = false)`
+Restarts the app and installs the update after it has been downloaded. Should only be called after update-downloaded has been emitted.
+
+Note: QuitAndInstall() will close all application windows first and only emit before-quit event on app after that. This is different from the normal quit event sequence.
+
+**Parameters:**
+- `isSilent` - Windows-only: Runs the installer in silent mode. Defaults to false
+- `isForceRunAfter` - Run the app after finish even on silent install. Not applicable for macOS
+
+## Events
+
+#### β‘ `OnCheckingForUpdate`
+Emitted when checking if an update has started.
+
+#### β‘ `OnDownloadProgress`
+Emitted on download progress.
+
+#### β‘ `OnError`
+Emitted when there is an error while updating.
+
+#### β‘ `OnUpdateAvailable`
+Emitted when there is an available update. The update is downloaded automatically if AutoDownload is true.
+
+#### β‘ `OnUpdateDownloaded`
+Emitted on download complete.
+
+#### β‘ `OnUpdateNotAvailable`
+Emitted when there is no available update.
+
+## Usage Examples
+
+### Basic Auto-Update Setup
+
+```csharp
+// Configure auto-updater
+Electron.AutoUpdater.AutoDownload = true;
+Electron.AutoUpdater.AutoInstallOnAppQuit = true;
+
+// Check for updates
+var updateCheck = await Electron.AutoUpdater.CheckForUpdatesAsync();
+if (updateCheck.UpdateInfo != null)
+{
+ Console.WriteLine($"Update available: {updateCheck.UpdateInfo.Version}");
+}
+```
+
+### Manual Update Management
+
+```csharp
+// Disable auto-download for manual control
+Electron.AutoUpdater.AutoDownload = false;
+
+// Check for updates
+var result = await Electron.AutoUpdater.CheckForUpdatesAsync();
+if (result.UpdateInfo != null)
+{
+ Console.WriteLine($"Update found: {result.UpdateInfo.Version}");
+
+ // Ask user confirmation
+ var confirmResult = await Electron.Dialog.ShowMessageBoxAsync(
+ "Update Available",
+ $"Version {result.UpdateInfo.Version} is available. Download now?");
+
+ if (confirmResult.Response == 1) // Yes
+ {
+ // Download update manually
+ var downloadPath = await Electron.AutoUpdater.DownloadUpdateAsync();
+ Console.WriteLine($"Downloaded to: {downloadPath}");
+
+ // Install update
+ Electron.AutoUpdater.QuitAndInstall();
+ }
+}
+```
+
+### Update Event Handling
+
+```csharp
+// Handle update events
+Electron.AutoUpdater.OnCheckingForUpdate += () =>
+{
+ Console.WriteLine("Checking for updates...");
+};
+
+Electron.AutoUpdater.OnUpdateAvailable += (updateInfo) =>
+{
+ Console.WriteLine($"Update available: {updateInfo.Version}");
+};
+
+Electron.AutoUpdater.OnUpdateNotAvailable += (updateInfo) =>
+{
+ Console.WriteLine("No updates available");
+};
+
+Electron.AutoUpdater.OnDownloadProgress += (progressInfo) =>
+{
+ Console.WriteLine($"Download progress: {progressInfo.Percent}%");
+};
+
+Electron.AutoUpdater.OnUpdateDownloaded += (updateInfo) =>
+{
+ Console.WriteLine($"Update downloaded: {updateInfo.Version}");
+
+ // Show notification to user
+ Electron.Notification.Show(new NotificationOptions
+ {
+ Title = "Update Downloaded",
+ Body = $"Version {updateInfo.Version} is ready to install.",
+ Actions = new[]
+ {
+ new NotificationAction { Text = "Install Now", Type = NotificationActionType.Button },
+ new NotificationAction { Text = "Later", Type = NotificationActionType.Button }
+ }
+ });
+};
+
+Electron.AutoUpdater.OnError += (error) =>
+{
+ Console.WriteLine($"Update error: {error}");
+ Electron.Dialog.ShowErrorBox("Update Error", $"Failed to update: {error}");
+};
+```
+
+### GitHub Provider Configuration
+
+```csharp
+// Configure for GitHub releases
+Electron.AutoUpdater.AllowPrerelease = true; // Allow pre-release versions
+Electron.AutoUpdater.FullChangelog = true; // Get full changelog
+Electron.AutoUpdater.AllowDowngrade = false; // Prevent downgrades
+
+// Set request headers if needed
+Electron.AutoUpdater.RequestHeaders = new Dictionary
+{
+ ["Authorization"] = "token your-github-token",
+ ["User-Agent"] = "MyApp-Updater"
+};
+```
+
+### Update Installation
+
+```csharp
+// Install update immediately
+if (updateDownloaded)
+{
+ Electron.AutoUpdater.QuitAndInstall();
+}
+
+// Silent install (Windows only)
+Electron.AutoUpdater.QuitAndInstall(isSilent: true, isForceRunAfter: true);
+```
+
+### Version Management
+
+```csharp
+// Get current version
+var currentVersion = await Electron.AutoUpdater.CurrentVersionAsync;
+Console.WriteLine($"Current version: {currentVersion}");
+
+// Get update channel
+var channel = await Electron.AutoUpdater.ChannelAsync;
+Console.WriteLine($"Update channel: {channel}");
+
+// Set custom feed URL
+// Note: This would typically be configured in electron-builder.json
+var feedUrl = await Electron.AutoUpdater.GetFeedURLAsync();
+Console.WriteLine($"Feed URL: {feedUrl}");
+```
+
+## Related APIs
+
+- [Electron.App](App.md) - Application lifecycle events during updates
+- [Electron.Notification](Notification.md) - Notify users about update status
+- [Electron.Dialog](Dialog.md) - Show update confirmation dialogs
+
+## Additional Resources
+
+- [Electron AutoUpdater Documentation](https://electronjs.org/docs/api/auto-updater) - Official Electron auto-updater API
diff --git a/docs/API/Clipboard.md b/docs/API/Clipboard.md
new file mode 100644
index 0000000..a835453
--- /dev/null
+++ b/docs/API/Clipboard.md
@@ -0,0 +1,231 @@
+# Electron.Clipboard
+
+Perform copy and paste operations on the system clipboard.
+
+## Overview
+
+The `Electron.Clipboard` API provides comprehensive access to the system clipboard, supporting multiple data formats including text, HTML, RTF, images, and custom data. It enables reading from and writing to the clipboard with platform-specific behavior.
+
+## Methods
+
+#### π§ `Task AvailableFormatsAsync(string type = "")`
+Get an array of supported formats for the clipboard type.
+
+**Parameters:**
+- `type` - Clipboard type
+
+**Returns:**
+
+An array of supported formats for the clipboard type.
+
+#### π§ `void Clear(string type = "")`
+Clears the clipboard content.
+
+**Parameters:**
+- `type` - Clipboard type
+
+#### π§ `Task ReadBookmarkAsync()`
+Returns an Object containing title and url keys representing the bookmark in the clipboard. The title and url values will be empty strings when the bookmark is unavailable.
+
+**Returns:**
+
+Object containing title and url keys representing the bookmark in the clipboard.
+
+#### π§ `Task ReadFindTextAsync()`
+macOS: The text on the find pasteboard. This method uses synchronous IPC when called from the renderer process. The cached value is reread from the find pasteboard whenever the application is activated.
+
+**Returns:**
+
+The text on the find pasteboard.
+
+#### π§ `Task ReadHTMLAsync(string type = "")`
+Read the content in the clipboard as HTML markup.
+
+**Parameters:**
+- `type` - Clipboard type
+
+**Returns:**
+
+The content in the clipboard as markup.
+
+#### π§ `Task ReadImageAsync(string type = "")`
+Read an image from the clipboard.
+
+**Parameters:**
+- `type` - Clipboard type
+
+**Returns:**
+
+An image from the clipboard.
+
+#### π§ `Task ReadRTFAsync(string type = "")`
+Read the content in the clipboard as RTF.
+
+**Parameters:**
+- `type` - Clipboard type
+
+**Returns:**
+
+The content in the clipboard as RTF.
+
+#### π§ `Task ReadTextAsync(string type = "")`
+Read the content in the clipboard as plain text.
+
+**Parameters:**
+- `type` - Clipboard type
+
+**Returns:**
+
+The content in the clipboard as plain text.
+
+#### π§ `void Write(Data data, string type = "")`
+Writes data to the clipboard.
+
+**Parameters:**
+- `data` - Data object to write
+- `type` - Clipboard type
+
+#### π§ `void WriteBookmark(string title, string url, string type = "")`
+Writes the title and url into the clipboard as a bookmark.
+
+Note: Most apps on Windows don't support pasting bookmarks into them so you can use clipboard.write to write both a bookmark and fallback text to the clipboard.
+
+**Parameters:**
+- `title` - Bookmark title
+- `url` - Bookmark URL
+- `type` - Clipboard type
+
+#### π§ `void WriteFindText(string text)`
+macOS: Writes the text into the find pasteboard as plain text. This method uses synchronous IPC when called from the renderer process.
+
+**Parameters:**
+- `text` - Text to write to find pasteboard
+
+#### π§ `void WriteHTML(string markup, string type = "")`
+Writes markup to the clipboard.
+
+**Parameters:**
+- `markup` - HTML markup to write
+- `type` - Clipboard type
+
+#### π§ `void WriteImage(NativeImage image, string type = "")`
+Writes an image to the clipboard.
+
+**Parameters:**
+- `image` - Image to write to clipboard
+- `type` - Clipboard type
+
+#### π§ `void WriteRTF(string text, string type = "")`
+Writes the text into the clipboard in RTF.
+
+**Parameters:**
+- `text` - RTF content to write
+- `type` - Clipboard type
+
+#### π§ `void WriteText(string text, string type = "")`
+Writes the text into the clipboard as plain text.
+
+**Parameters:**
+- `text` - Text content to write
+- `type` - Clipboard type
+
+## Usage Examples
+
+### Basic Text Operations
+
+```csharp
+// Read text from clipboard
+var text = await Electron.Clipboard.ReadTextAsync();
+Console.WriteLine($"Clipboard text: {text}");
+
+// Write text to clipboard
+Electron.Clipboard.WriteText("Hello, Electron.NET!");
+
+// Read with specific type
+var html = await Electron.Clipboard.ReadHTMLAsync("public.main");
+```
+
+### Rich Content Handling
+
+```csharp
+// Copy formatted text
+var htmlContent = "Title Some bold text
";
+Electron.Clipboard.WriteHTML(htmlContent);
+
+// Read RTF content
+var rtf = await Electron.Clipboard.ReadRTFAsync();
+Console.WriteLine($"RTF content: {rtf}");
+```
+
+### Image Operations
+
+```csharp
+// Read image from clipboard
+var image = await Electron.Clipboard.ReadImageAsync();
+if (image != null)
+{
+ Console.WriteLine($"Image size: {image.Size.Width}x{image.Size.Height}");
+}
+
+// Write image to clipboard
+var nativeImage = NativeImage.CreateFromPath("screenshot.png");
+Electron.Clipboard.WriteImage(nativeImage);
+```
+
+### Bookmark Management
+
+```csharp
+// Read bookmark from clipboard
+var bookmark = await Electron.Clipboard.ReadBookmarkAsync();
+if (!string.IsNullOrEmpty(bookmark.Title))
+{
+ Console.WriteLine($"Bookmark: {bookmark.Title} -> {bookmark.Url}");
+}
+
+// Write bookmark to clipboard
+Electron.Clipboard.WriteBookmark("Electron.NET", "https://github.com/ElectronNET/Electron.NET");
+```
+
+### Advanced Clipboard Operations
+
+```csharp
+// Check available formats
+var formats = await Electron.Clipboard.AvailableFormatsAsync();
+Console.WriteLine($"Available formats: {string.Join(", ", formats)}");
+
+// Clear clipboard
+Electron.Clipboard.Clear();
+
+// Write custom data
+var data = new Data
+{
+ Text = "Custom data",
+ Html = "Custom HTML
",
+ Image = nativeImage
+};
+Electron.Clipboard.Write(data);
+```
+
+### macOS Find Pasteboard
+
+```csharp
+// macOS specific find pasteboard operations
+if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+{
+ // Read find text
+ var findText = await Electron.Clipboard.ReadFindTextAsync();
+ Console.WriteLine($"Find text: {findText}");
+
+ // Write find text
+ Electron.Clipboard.WriteFindText("search term");
+}
+```
+
+## Related APIs
+
+- [Electron.Shell](Shell.md) - Work with file paths from clipboard
+- [Electron.Notification](Notification.md) - Show clipboard operation results
+
+## Additional Resources
+
+- [Electron Clipboard Documentation](https://electronjs.org/docs/api/clipboard) - Official Electron clipboard API
diff --git a/docs/API/Dialog.md b/docs/API/Dialog.md
new file mode 100644
index 0000000..f24898f
--- /dev/null
+++ b/docs/API/Dialog.md
@@ -0,0 +1,160 @@
+# Electron.Dialog
+
+Display native system dialogs for opening and saving files, alerting, etc.
+
+## Overview
+
+The `Electron.Dialog` API provides access to native system dialogs for file operations, message boxes, and certificate trust dialogs. These dialogs are modal and provide a consistent user experience across different platforms.
+
+## Methods
+
+#### π§ `Task ShowMessageBoxAsync(BrowserWindow browserWindow, MessageBoxOptions messageBoxOptions)`
+Shows a message box, it will block the process until the message box is closed. It returns the index of the clicked button. If a callback is passed, the dialog will not block the process.
+
+**Parameters:**
+- `browserWindow` - The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.
+- `messageBoxOptions` - Message content and configuration
+
+**Returns:**
+
+The API call will be asynchronous and the result will be passed via MessageBoxResult.
+
+#### π§ `Task ShowMessageBoxAsync(BrowserWindow browserWindow, string message)`
+Shows a message box, it will block the process until the message box is closed. It returns the index of the clicked button. If a callback is passed, the dialog will not block the process.
+
+**Parameters:**
+- `browserWindow` - The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.
+- `message` - Message content
+
+**Returns:**
+
+The API call will be asynchronous and the result will be passed via MessageBoxResult.
+
+#### π§ `Task ShowMessageBoxAsync(MessageBoxOptions messageBoxOptions)`
+Shows a message box, it will block the process until the message box is closed. It returns the index of the clicked button. If a callback is passed, the dialog will not block the process.
+
+**Parameters:**
+- `messageBoxOptions` - Message content and configuration
+
+**Returns:**
+
+The API call will be asynchronous and the result will be passed via MessageBoxResult.
+
+#### π§ `Task ShowMessageBoxAsync(string message)`
+Shows a message box, it will block the process until the message box is closed. It returns the index of the clicked button. If a callback is passed, the dialog will not block the process.
+
+**Parameters:**
+- `message` - Message content
+
+**Returns:**
+
+The API call will be asynchronous and the result will be passed via MessageBoxResult.
+
+#### π§ `Task ShowOpenDialogAsync(BrowserWindow browserWindow, OpenDialogOptions options)`
+Note: On Windows and Linux an open dialog can not be both a file selector and a directory selector, so if you set properties to ['openFile', 'openDirectory'] on these platforms, a directory selector will be shown.
+
+**Parameters:**
+- `browserWindow` - The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.
+- `options` - Dialog configuration options
+
+**Returns:**
+
+An array of file paths chosen by the user
+
+#### π§ `Task ShowSaveDialogAsync(BrowserWindow browserWindow, SaveDialogOptions options)`
+Dialog for save files.
+
+**Parameters:**
+- `browserWindow` - The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.
+- `options` - Dialog configuration options
+
+**Returns:**
+
+Returns String, the path of the file chosen by the user, if a callback is provided it returns an empty string.
+
+#### π§ `void ShowErrorBox(string title, string content)`
+Displays a modal dialog that shows an error message.
+
+This API can be called safely before the ready event the app module emits, it is usually used to report errors in early stage of startup.If called before the app readyevent on Linux, the message will be emitted to stderr, and no GUI dialog will appear.
+
+**Parameters:**
+- `title` - The title to display in the error box.
+- `content` - The text content to display in the error box.
+
+#### π§ `Task ShowCertificateTrustDialogAsync(BrowserWindow browserWindow, CertificateTrustDialogOptions options)`
+On macOS, this displays a modal dialog that shows a message and certificate information, and gives the user the option of trusting/importing the certificate. If you provide a browserWindow argument the dialog will be attached to the parent window, making it modal.
+
+**Parameters:**
+- `browserWindow` - Parent window for modal behavior
+- `options` - Certificate trust dialog options
+
+#### π§ `Task ShowCertificateTrustDialogAsync(CertificateTrustDialogOptions options)`
+On macOS, this displays a modal dialog that shows a message and certificate information, and gives the user the option of trusting/importing the certificate. If you provide a browserWindow argument the dialog will be attached to the parent window, making it modal.
+
+**Parameters:**
+- `options` - Certificate trust dialog options
+
+## Usage Examples
+
+### File Operations
+
+```csharp
+// Open multiple files
+var files = await Electron.Dialog.ShowOpenDialogAsync(window, new OpenDialogOptions
+{
+ Properties = new[] { OpenDialogProperty.OpenFile, OpenDialogProperty.MultiSelections }
+});
+
+// Save with custom extension
+var path = await Electron.Dialog.ShowSaveDialogAsync(window, new SaveDialogOptions
+{
+ DefaultPath = "backup.json",
+ Filters = new[] { new FileFilter { Name = "JSON", Extensions = new[] { "json" } } }
+});
+```
+
+### User Confirmation
+
+```csharp
+// Confirmation dialog
+var result = await Electron.Dialog.ShowMessageBoxAsync(window, new MessageBoxOptions
+{
+ Type = MessageBoxType.Question,
+ Title = "Confirm Delete",
+ Message = $"Delete {filename}?",
+ Buttons = new[] { "Cancel", "Delete" },
+ DefaultId = 0,
+ CancelId = 0
+});
+
+if (result.Response == 1)
+{
+ // Delete file
+}
+```
+
+### Error Handling
+
+```csharp
+// Error dialog
+Electron.Dialog.ShowErrorBox("Save Failed", "Could not save file. Please check permissions and try again.");
+
+// Warning dialog
+await Electron.Dialog.ShowMessageBoxAsync(new MessageBoxOptions
+{
+ Type = MessageBoxType.Warning,
+ Title = "Warning",
+ Message = "This operation may take several minutes.",
+ Buttons = new[] { "Continue", "Cancel" }
+});
+```
+
+## Related APIs
+
+- [Electron.WindowManager](WindowManager.md) - Parent windows for modal dialogs
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.Shell](Shell.md) - File operations with selected paths
+
+## Additional Resources
+
+- [Electron Dialog Documentation](https://electronjs.org/docs/api/dialog) - Official Electron dialog API
diff --git a/docs/API/Dock.md b/docs/API/Dock.md
new file mode 100644
index 0000000..8231cff
--- /dev/null
+++ b/docs/API/Dock.md
@@ -0,0 +1,209 @@
+# Electron.Dock
+
+Control your app in the macOS dock.
+
+## Overview
+
+The `Electron.Dock` API provides control over your application's appearance and behavior in the macOS dock. This includes bouncing the dock icon, setting badges, managing menus, and controlling visibility.
+
+## Properties
+
+#### π `IReadOnlyCollection MenuItems`
+Gets a read-only collection of all current dock menu items.
+
+## Methods
+
+#### π§ `Task BounceAsync(DockBounceType type, CancellationToken cancellationToken = default)`
+When `DockBounceType.Critical` is passed, the dock icon will bounce until either the application becomes active or the request is canceled. When `DockBounceType.Informational` is passed, the dock icon will bounce for one second. However, the request remains active until either the application becomes active or the request is canceled.
+
+Note: This method can only be used while the app is not focused; when the app is focused it will return -1.
+
+**Parameters:**
+- `type` - Can be critical or informational. The default is informational.
+- `cancellationToken` - The cancellation token
+
+**Returns:**
+
+An ID representing the request.
+
+#### π§ `void CancelBounce(int id)`
+Cancel the bounce of id.
+
+**Parameters:**
+- `id` - Id of the request
+
+#### π§ `void DownloadFinished(string filePath)`
+Bounces the Downloads stack if the filePath is inside the Downloads folder.
+
+**Parameters:**
+- `filePath` - Path to the downloaded file
+
+#### π§ `Task GetBadgeAsync(CancellationToken cancellationToken = default)`
+Gets the string to be displayed in the dock's badging area.
+
+**Returns:**
+
+The badge string of the dock.
+
+#### π§ `Task GetMenu(CancellationToken cancellationToken = default)`
+Gets the application's dock menu.
+
+**Returns:**
+
+The application's dock menu.
+
+#### π§ `void Hide()`
+Hides the dock icon.
+
+#### π§ `Task IsVisibleAsync(CancellationToken cancellationToken = default)`
+Whether the dock icon is visible. The app.dock.show() call is asynchronous so this method might not return true immediately after that call.
+
+**Returns:**
+
+Whether the dock icon is visible.
+
+#### π§ `void SetBadge(string text)`
+Sets the string to be displayed in the dock's badging area.
+
+**Parameters:**
+- `text` - Badge text to display
+
+#### π§ `void SetIcon(string image)`
+Sets the image associated with this dock icon.
+
+**Parameters:**
+- `image` - Icon image path
+
+#### π§ `void SetMenu(MenuItem[] menuItems)`
+Sets the application's dock menu.
+
+**Parameters:**
+- `menuItems` - Array of menu items for the dock menu
+
+#### π§ `void Show()`
+Shows the dock icon.
+
+## Usage Examples
+
+### Basic Dock Operations
+
+```csharp
+// Hide/Show dock icon
+Electron.Dock.Hide();
+await Task.Delay(2000);
+Electron.Dock.Show();
+
+// Check visibility
+var isVisible = await Electron.Dock.IsVisibleAsync();
+Console.WriteLine($"Dock visible: {isVisible}");
+```
+
+### Badge Notifications
+
+```csharp
+// Set badge count
+Electron.Dock.SetBadge("5");
+
+// Get current badge
+var badge = await Electron.Dock.GetBadgeAsync();
+Console.WriteLine($"Current badge: {badge}");
+
+// Clear badge
+Electron.Dock.SetBadge("");
+```
+
+### Dock Icon Animation
+
+```csharp
+// Bounce for attention
+var bounceId = await Electron.Dock.BounceAsync(DockBounceType.Critical);
+Console.WriteLine($"Bounce ID: {bounceId}");
+
+// Cancel bounce after 3 seconds
+await Task.Delay(3000);
+Electron.Dock.CancelBounce(bounceId);
+
+// Informational bounce
+await Electron.Dock.BounceAsync(DockBounceType.Informational);
+```
+
+### Dock Menu
+
+```csharp
+// Create dock menu
+var dockMenuItems = new[]
+{
+ new MenuItem { Label = "Show Window", Click = () => ShowMainWindow() },
+ new MenuItem { Label = "Settings", Click = () => OpenSettings() },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Label = "Exit", Click = () => Electron.App.Quit() }
+};
+
+// Set dock menu
+Electron.Dock.SetMenu(dockMenuItems);
+
+// Get current menu
+var currentMenu = await Electron.Dock.GetMenu();
+Console.WriteLine($"Menu items: {Electron.Dock.MenuItems.Count}");
+```
+
+### Download Notifications
+
+```csharp
+// Notify about completed download
+var downloadPath = "/Users/username/Downloads/document.pdf";
+Electron.Dock.DownloadFinished(downloadPath);
+```
+
+### Custom Dock Icon
+
+```csharp
+// Set custom dock icon
+Electron.Dock.SetIcon("assets/custom-dock-icon.png");
+
+// Set icon based on status
+if (isConnected)
+{
+ Electron.Dock.SetIcon("assets/connected-icon.png");
+}
+else
+{
+ Electron.Dock.SetIcon("assets/disconnected-icon.png");
+}
+```
+
+### Application Integration
+
+```csharp
+// Update dock badge based on unread count
+UpdateDockBadge(unreadMessageCount);
+
+void UpdateDockBadge(int count)
+{
+ if (count > 0)
+ {
+ Electron.Dock.SetBadge(count.ToString());
+ }
+ else
+ {
+ Electron.Dock.SetBadge("");
+ }
+}
+
+// Animate dock when receiving message
+private async void OnMessageReceived()
+{
+ await Electron.Dock.BounceAsync(DockBounceType.Informational);
+ Electron.Dock.SetBadge((unreadCount + 1).ToString());
+}
+```
+
+## Related APIs
+
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.Notification](Notification.md) - Desktop notifications
+- [Electron.Menu](Menu.md) - Menu items for dock menu
+
+## Additional Resources
+
+- [Electron Dock Documentation](https://electronjs.org/docs/api/dock) - Official Electron dock API
diff --git a/docs/API/GlobalShortcut.md b/docs/API/GlobalShortcut.md
new file mode 100644
index 0000000..34ca008
--- /dev/null
+++ b/docs/API/GlobalShortcut.md
@@ -0,0 +1,189 @@
+# Electron.GlobalShortcut
+
+Register global keyboard shortcuts that work even when the application is not focused.
+
+## Overview
+
+The `Electron.GlobalShortcut` API provides the ability to register global keyboard shortcuts that can be triggered even when the application does not have keyboard focus. This is useful for creating system-wide hotkeys and shortcuts.
+
+## Methods
+
+#### π§ `Task IsRegisteredAsync(string accelerator)`
+Check if the accelerator is registered.
+
+**Parameters:**
+- `accelerator` - Keyboard shortcut to check
+
+**Returns:**
+
+Whether this application has registered the accelerator.
+
+#### π§ `void Register(string accelerator, Action function)`
+Registers a global shortcut of accelerator. The callback is called when the registered shortcut is pressed by the user.
+
+**Parameters:**
+- `accelerator` - Keyboard shortcut combination
+- `function` - Callback function to execute when shortcut is pressed
+
+#### π§ `void Unregister(string accelerator)`
+Unregisters the global shortcut of accelerator.
+
+**Parameters:**
+- `accelerator` - Keyboard shortcut to unregister
+
+#### π§ `void UnregisterAll()`
+Unregisters all of the global shortcuts.
+
+## Usage Examples
+
+### Basic Global Shortcuts
+
+```csharp
+// Register global shortcuts
+Electron.GlobalShortcut.Register("CommandOrControl+N", () =>
+{
+ CreateNewDocument();
+});
+
+Electron.GlobalShortcut.Register("CommandOrControl+O", () =>
+{
+ OpenDocument();
+});
+
+Electron.GlobalShortcut.Register("CommandOrControl+S", () =>
+{
+ SaveDocument();
+});
+```
+
+### Media Control Shortcuts
+
+```csharp
+// Media playback shortcuts
+Electron.GlobalShortcut.Register("MediaPlayPause", () =>
+{
+ TogglePlayback();
+});
+
+Electron.GlobalShortcut.Register("MediaNextTrack", () =>
+{
+ NextTrack();
+});
+
+Electron.GlobalShortcut.Register("MediaPreviousTrack", () =>
+{
+ PreviousTrack();
+});
+```
+
+### Application Control Shortcuts
+
+```csharp
+// Application control shortcuts
+Electron.GlobalShortcut.Register("CommandOrControl+Shift+Q", async () =>
+{
+ var result = await Electron.Dialog.ShowMessageBoxAsync("Quit Application?", "Are you sure you want to quit?");
+ if (result.Response == 1) // Yes
+ {
+ Electron.App.Quit();
+ }
+});
+
+Electron.GlobalShortcut.Register("CommandOrControl+Shift+H", () =>
+{
+ ToggleMainWindow();
+});
+```
+
+### Dynamic Shortcut Management
+
+```csharp
+// Register shortcuts based on user preferences
+public void RegisterUserShortcuts(Dictionary shortcuts)
+{
+ foreach (var shortcut in shortcuts)
+ {
+ Electron.GlobalShortcut.Register(shortcut.Key, shortcut.Value);
+ }
+}
+
+// Check if shortcut is available
+public async Task IsShortcutAvailable(string accelerator)
+{
+ return await Electron.GlobalShortcut.IsRegisteredAsync(accelerator);
+}
+
+// Unregister specific shortcut
+public void UnregisterShortcut(string accelerator)
+{
+ Electron.GlobalShortcut.Unregister(accelerator);
+}
+```
+
+### Platform-Specific Shortcuts
+
+```csharp
+// macOS specific shortcuts
+if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+{
+ Electron.GlobalShortcut.Register("Command+Comma", () =>
+ {
+ OpenPreferences();
+ });
+
+ Electron.GlobalShortcut.Register("Command+H", () =>
+ {
+ Electron.App.Hide();
+ });
+}
+
+// Windows/Linux shortcuts
+else
+{
+ Electron.GlobalShortcut.Register("Ctrl+Shift+P", () =>
+ {
+ OpenPreferences();
+ });
+
+ Electron.GlobalShortcut.Register("Alt+F4", () =>
+ {
+ Electron.App.Quit();
+ });
+}
+```
+
+### Shortcut Validation
+
+```csharp
+// Validate shortcuts before registration
+public async Task TryRegisterShortcut(string accelerator, Action callback)
+{
+ if (await Electron.GlobalShortcut.IsRegisteredAsync(accelerator))
+ {
+ Console.WriteLine($"Shortcut {accelerator} is already registered");
+ return false;
+ }
+
+ try
+ {
+ Electron.GlobalShortcut.Register(accelerator, callback);
+ Console.WriteLine($"Successfully registered shortcut: {accelerator}");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to register shortcut {accelerator}: {ex.Message}");
+ return false;
+ }
+}
+```
+
+## Related APIs
+
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.Menu](Menu.md) - Menu-based shortcuts
+- [Electron.WindowManager](WindowManager.md) - Window focus management
+
+## Additional Resources
+
+- [Electron GlobalShortcut Documentation](https://electronjs.org/docs/api/global-shortcut) - Official Electron global shortcut API
diff --git a/docs/API/HostHook.md b/docs/API/HostHook.md
new file mode 100644
index 0000000..e0d5807
--- /dev/null
+++ b/docs/API/HostHook.md
@@ -0,0 +1,152 @@
+# Electron.HostHook
+
+Execute native JavaScript/TypeScript code from the host process.
+
+## Overview
+
+The `Electron.HostHook` API allows you to execute native JavaScript/TypeScript code from the host process. This enables advanced integration scenarios where you need to run custom JavaScript code or access Node.js APIs directly.
+
+## Methods
+
+#### π§ `void Call(string socketEventName, params dynamic[] arguments)`
+Execute native JavaScript/TypeScript code synchronously.
+
+**Parameters:**
+- `socketEventName` - Socket name registered on the host
+- `arguments` - Optional parameters
+
+#### π§ `Task CallAsync(string socketEventName, params dynamic[] arguments)`
+Execute native JavaScript/TypeScript code asynchronously with type-safe return values.
+
+**Parameters:**
+- `T` - Expected return type
+- `socketEventName` - Socket name registered on the host
+- `arguments` - Optional parameters
+
+**Returns:**
+
+Task with the result from the executed host code.
+
+## Usage Examples
+
+### Basic Host Hook Execution
+
+```csharp
+// Execute simple JavaScript function
+Electron.HostHook.Call("myFunction", "parameter1", 42);
+
+// Execute with callback-style result
+var result = await Electron.HostHook.CallAsync("getUserName", userId);
+Console.WriteLine($"User name: {result}");
+```
+
+### Advanced Integration
+
+```csharp
+// Call custom Electron API
+var fileContent = await Electron.HostHook.CallAsync("readFile", "config.json");
+Console.WriteLine($"Config: {fileContent}");
+
+// Execute with multiple parameters
+var processedData = await Electron.HostHook.CallAsync("processData", rawData, options);
+
+// Call with complex objects
+var settings = new { theme = "dark", language = "en" };
+var updatedSettings = await Electron.HostHook.CallAsync("updateSettings", settings);
+```
+
+### Error Handling
+
+```csharp
+try
+{
+ // Execute host function with error handling
+ var result = await Electron.HostHook.CallAsync("riskyOperation", inputData);
+ Console.WriteLine($"Success: {result}");
+}
+catch (Exception ex)
+{
+ // Handle execution errors
+ Console.WriteLine($"Host hook error: {ex.Message}");
+ Electron.Dialog.ShowErrorBox("Operation Failed", "Could not execute host function.");
+}
+```
+
+### Type-Safe Operations
+
+```csharp
+// Strongly typed return values
+var userInfo = await Electron.HostHook.CallAsync("getUserInfo", userId);
+Console.WriteLine($"User: {userInfo.Name}, Email: {userInfo.Email}");
+
+// Array results
+var fileList = await Electron.HostHook.CallAsync("listFiles", directoryPath);
+foreach (var file in fileList)
+{
+ Console.WriteLine($"File: {file}");
+}
+
+// Complex object results
+var systemStats = await Electron.HostHook.CallAsync("getSystemStats");
+Console.WriteLine($"CPU: {systemStats.CpuUsage}%, Memory: {systemStats.MemoryUsage}%");
+```
+
+### Custom ElectronHostHook Setup
+
+```csharp
+// In your ElectronHostHook/index.ts
+import { app } from 'electron';
+
+export function getAppVersion(): string {
+ return app.getVersion();
+}
+
+export async function readConfigFile(): Promise {
+ const fs = require('fs').promises;
+ return await fs.readFile('config.json', 'utf8');
+}
+
+export function customNotification(message: string): void {
+ // Custom notification logic
+ console.log(`Custom notification: ${message}`);
+}
+```
+
+### Integration with .NET Code
+
+```csharp
+// Use host hook in your application logic
+public async Task GetApplicationVersion()
+{
+ return await Electron.HostHook.CallAsync("getAppVersion");
+}
+
+public async Task LoadConfiguration()
+{
+ try
+ {
+ var config = await Electron.HostHook.CallAsync("readConfigFile");
+ ApplyConfiguration(config);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to load config: {ex.Message}");
+ UseDefaultConfiguration();
+ }
+}
+
+public void ShowCustomNotification(string message)
+{
+ Electron.HostHook.Call("customNotification", message);
+}
+```
+
+## Related APIs
+
+- [Electron.IpcMain](IpcMain.md) - Inter-process communication
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.WebContents](WebContents.md) - Web content integration
+
+## Additional Resources
+
+- [Host Hook Documentation](../Core/Advanced-Migration-Topics.md) - Setting up custom host hooks
diff --git a/docs/API/IpcMain.md b/docs/API/IpcMain.md
new file mode 100644
index 0000000..af1cc56
--- /dev/null
+++ b/docs/API/IpcMain.md
@@ -0,0 +1,167 @@
+# Electron.IpcMain
+
+Communicate asynchronously from the main process to renderer processes.
+
+## Overview
+
+The `Electron.IpcMain` API provides inter-process communication between the main process and renderer processes. It allows you to send messages, listen for events, and handle communication between different parts of your Electron application.
+
+## Methods
+
+#### π§ `Task On(string channel, Action listener)`
+Listens to channel, when a new message arrives listener would be called with listener(event, args...).
+
+**Parameters:**
+- `channel` - Channel name to listen on
+- `listener` - Callback method to handle incoming messages
+
+#### π§ `void OnSync(string channel, Func listener)`
+Send a message to the renderer process synchronously via channel. Note: Sending a synchronous message will block the whole renderer process.
+
+**Parameters:**
+- `channel` - Channel name to listen on
+- `listener` - Synchronous callback method
+
+#### π§ `void Once(string channel, Action listener)`
+Adds a one time listener method for the event. This listener is invoked only the next time a message is sent to channel, after which it is removed.
+
+**Parameters:**
+- `channel` - Channel name to listen on
+- `listener` - Callback method to handle the message once
+
+#### π§ `void RemoveAllListeners(string channel)`
+Removes all listeners of the specified channel.
+
+**Parameters:**
+- `channel` - Channel name to remove listeners from
+
+#### π§ `void Send(BrowserView browserView, string channel, params object[] data)`
+Send a message to the BrowserView renderer process asynchronously via channel.
+
+**Parameters:**
+- `browserView` - Target browser view
+- `channel` - Channel name to send on
+- `data` - Arguments to send
+
+#### π§ `void Send(BrowserWindow browserWindow, string channel, params object[] data)`
+Send a message to the renderer process asynchronously via channel.
+
+**Parameters:**
+- `browserWindow` - Target browser window
+- `channel` - Channel name to send on
+- `data` - Arguments to send
+
+## Usage Examples
+
+### Basic Message Handling
+
+```csharp
+// Listen for messages from renderer
+await Electron.IpcMain.On("request-data", (args) =>
+{
+ Console.WriteLine($"Received request: {args}");
+ // Process the request and send response
+});
+
+// Send response back to renderer
+Electron.IpcMain.Send(mainWindow, "data-response", processedData);
+```
+
+### Synchronous Communication
+
+```csharp
+// Handle synchronous requests
+Electron.IpcMain.OnSync("get-user-info", (request) =>
+{
+ var userId = request.ToString();
+ var userInfo = GetUserInfo(userId);
+ return userInfo;
+});
+```
+
+### One-time Event Handling
+
+```csharp
+// Handle initialization request once
+Electron.IpcMain.Once("app-initialized", (args) =>
+{
+ Console.WriteLine("App initialized, setting up...");
+ InitializeApplication();
+});
+```
+
+### Complex Data Exchange
+
+```csharp
+// Send complex data to renderer
+var appData = new
+{
+ Version = "1.0.0",
+ Features = new[] { "feature1", "feature2" },
+ Settings = new { Theme = "dark", Language = "en" }
+};
+
+Electron.IpcMain.Send(mainWindow, "app-config", appData);
+
+// Listen for settings updates
+await Electron.IpcMain.On("update-settings", (settings) =>
+{
+ var config = JsonConvert.DeserializeObject(settings.ToString());
+ ApplySettings(config);
+});
+```
+
+### Multi-Window Communication
+
+```csharp
+// Send message to specific window
+var settingsWindow = await Electron.WindowManager.CreateWindowAsync();
+Electron.IpcMain.Send(settingsWindow, "show-settings", currentSettings);
+
+// Broadcast to all windows
+foreach (var window in Electron.WindowManager.BrowserWindows)
+{
+ Electron.IpcMain.Send(window, "notification", message);
+}
+```
+
+### Error Handling
+
+```csharp
+// Handle IPC errors gracefully
+await Electron.IpcMain.On("risky-operation", async (args) =>
+{
+ try
+ {
+ var result = await PerformRiskyOperation(args);
+ Electron.IpcMain.Send(mainWindow, "operation-success", result);
+ }
+ catch (Exception ex)
+ {
+ Electron.IpcMain.Send(mainWindow, "operation-error", ex.Message);
+ }
+});
+```
+
+### Integration with Host Hooks
+
+```csharp
+// Use with custom host functionality
+await Electron.IpcMain.On("execute-host-function", async (args) =>
+{
+ var functionName = args.ToString();
+ var result = await Electron.HostHook.CallAsync(functionName);
+
+ Electron.IpcMain.Send(mainWindow, "function-result", result);
+});
+```
+
+## Related APIs
+
+- [Electron.HostHook](HostHook.md) - Execute custom JavaScript functions
+- [Electron.WindowManager](WindowManager.md) - Target specific windows for communication
+- [Electron.WebContents](WebContents.md) - Send messages to web content
+
+## Additional Resources
+
+- [Electron IPC Documentation](https://electronjs.org/docs/api/ipc-main) - Official Electron IPC API
diff --git a/docs/API/Menu.md b/docs/API/Menu.md
new file mode 100644
index 0000000..525922c
--- /dev/null
+++ b/docs/API/Menu.md
@@ -0,0 +1,210 @@
+# Electron.Menu
+
+Create application menus, context menus, and menu items with full keyboard shortcut support.
+
+## Overview
+
+The `Electron.Menu` API provides comprehensive control over application menus and context menus. It supports native platform menus with custom menu items, submenus, keyboard shortcuts, and role-based menu items.
+
+## Properties
+
+#### π `IReadOnlyDictionary> ContextMenuItems`
+Gets a read-only dictionary of all current context menu items, keyed by browser window ID.
+
+#### π `IReadOnlyCollection MenuItems`
+Gets a read-only collection of all current application menu items.
+
+## Methods
+
+#### π§ `void ContextMenuPopup(BrowserWindow browserWindow)`
+Shows the context menu for the specified browser window.
+
+**Parameters:**
+- `browserWindow` - The browser window to show the context menu for
+
+#### π§ `void SetApplicationMenu(MenuItem[] menuItems)`
+Sets the application menu for the entire application.
+
+**Parameters:**
+- `menuItems` - Array of MenuItem objects defining the application menu
+
+#### π§ `void SetContextMenu(BrowserWindow browserWindow, MenuItem[] menuItems)`
+Sets a context menu for a specific browser window.
+
+**Parameters:**
+- `browserWindow` - The browser window to attach the context menu to
+- `menuItems` - Array of MenuItem objects defining the context menu
+
+## Usage Examples
+
+### Application Menu
+
+```csharp
+// Create application menu
+var menuItems = new[]
+{
+ new MenuItem
+ {
+ Label = "File",
+ Submenu = new[]
+ {
+ new MenuItem { Label = "New", Click = () => CreateNewDocument() },
+ new MenuItem { Label = "Open", Click = () => OpenDocument() },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Label = "Exit", Click = () => Electron.App.Quit() }
+ }
+ },
+ new MenuItem
+ {
+ Label = "Edit",
+ Submenu = new[]
+ {
+ new MenuItem { Role = MenuRole.Undo },
+ new MenuItem { Role = MenuRole.Redo },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Role = MenuRole.Cut },
+ new MenuItem { Role = MenuRole.Copy },
+ new MenuItem { Role = MenuRole.Paste }
+ }
+ },
+ new MenuItem
+ {
+ Label = "View",
+ Submenu = new[]
+ {
+ new MenuItem { Role = MenuRole.Reload },
+ new MenuItem { Role = MenuRole.ForceReload },
+ new MenuItem { Role = MenuRole.ToggleDevTools },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Role = MenuRole.ResetZoom },
+ new MenuItem { Role = MenuRole.ZoomIn },
+ new MenuItem { Role = MenuRole.ZoomOut }
+ }
+ },
+ new MenuItem
+ {
+ Label = "Window",
+ Submenu = new[]
+ {
+ new MenuItem { Role = MenuRole.Minimize },
+ new MenuItem { Role = MenuRole.Close }
+ }
+ }
+};
+
+// Set application menu
+Electron.Menu.SetApplicationMenu(menuItems);
+```
+
+### Context Menu
+
+```csharp
+// Create context menu for specific window
+var contextMenuItems = new[]
+{
+ new MenuItem { Label = "Copy", Click = () => CopySelectedText() },
+ new MenuItem { Label = "Paste", Click = () => PasteText() },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Label = "Inspect Element", Click = () => InspectElement() }
+};
+
+// Set context menu for window
+Electron.Menu.SetContextMenu(mainWindow, contextMenuItems);
+
+// Show context menu programmatically
+Electron.Menu.ContextMenuPopup(mainWindow);
+```
+
+### Menu with Keyboard Shortcuts
+
+```csharp
+// Create menu with keyboard shortcuts
+var menuItems = new[]
+{
+ new MenuItem
+ {
+ Label = "File",
+ Submenu = new[]
+ {
+ new MenuItem
+ {
+ Label = "New",
+ Accelerator = "CmdOrCtrl+N",
+ Click = () => CreateNewDocument()
+ },
+ new MenuItem
+ {
+ Label = "Open",
+ Accelerator = "CmdOrCtrl+O",
+ Click = () => OpenDocument()
+ },
+ new MenuItem
+ {
+ Label = "Save",
+ Accelerator = "CmdOrCtrl+S",
+ Click = () => SaveDocument()
+ }
+ }
+ }
+};
+
+Electron.Menu.SetApplicationMenu(menuItems);
+```
+
+### Dynamic Menu Updates
+
+```csharp
+// Update menu items dynamically
+var fileMenu = Electron.Menu.MenuItems.FirstOrDefault(m => m.Label == "File");
+if (fileMenu?.Submenu != null)
+{
+ var saveItem = fileMenu.Submenu.FirstOrDefault(m => m.Label == "Save");
+ if (saveItem != null)
+ {
+ saveItem.Enabled = HasUnsavedChanges;
+ }
+}
+```
+
+### Platform-Specific Menus
+
+```csharp
+// macOS specific menu items
+if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+{
+ var macMenuItems = new[]
+ {
+ new MenuItem
+ {
+ Label = "MyApp",
+ Submenu = new[]
+ {
+ new MenuItem { Role = MenuRole.About },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Role = MenuRole.Services },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Role = MenuRole.Hide },
+ new MenuItem { Role = MenuRole.HideOthers },
+ new MenuItem { Role = MenuRole.Unhide },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Role = MenuRole.Quit }
+ }
+ }
+ };
+
+ // Insert before File menu
+ var allMenus = new List(macMenuItems);
+ allMenus.AddRange(menuItems);
+ Electron.Menu.SetApplicationMenu(allMenus.ToArray());
+}
+```
+
+## Related APIs
+
+- [Electron.WindowManager](WindowManager.md) - Windows for context menus
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.GlobalShortcut](GlobalShortcut.md) - Global keyboard shortcuts
+
+## Additional Resources
+
+- [Electron Menu Documentation](https://electronjs.org/docs/api/menu) - Official Electron menu API
diff --git a/docs/API/NativeTheme.md b/docs/API/NativeTheme.md
new file mode 100644
index 0000000..e00f704
--- /dev/null
+++ b/docs/API/NativeTheme.md
@@ -0,0 +1,189 @@
+# Electron.NativeTheme
+
+Detect and respond to changes in Chromium's native color theme.
+
+## Overview
+
+The `Electron.NativeTheme` API provides access to Chromium's native color theme information and allows you to detect and respond to changes in the system's dark/light mode settings. This enables your application to automatically adapt to the user's theme preferences.
+
+## Methods
+
+#### π§ `Task GetThemeSourceAsync()`
+Get the current theme source setting.
+
+**Returns:**
+
+A `ThemeSourceMode` property that can be `ThemeSourceMode.System`, `ThemeSourceMode.Light` or `ThemeSourceMode.Dark`.
+
+#### π§ `void SetThemeSource(ThemeSourceMode themeSourceMode)`
+Setting this property to `ThemeSourceMode.System` will remove the override and everything will be reset to the OS default. By default 'ThemeSource' is `ThemeSourceMode.System`.
+
+**Parameters:**
+- `themeSourceMode` - The new ThemeSource
+
+#### π§ `Task ShouldUseDarkColorsAsync()`
+Check if the system is currently using dark colors.
+
+**Returns:**
+
+A bool for if the OS / Chromium currently has a dark mode enabled or is being instructed to show a dark-style UI.
+
+#### π§ `Task ShouldUseHighContrastColorsAsync()`
+Check if the system is currently using high contrast colors.
+
+**Returns:**
+
+A bool for if the OS / Chromium currently has high-contrast mode enabled or is being instructed to show a high-contrast UI.
+
+#### π§ `Task ShouldUseInvertedColorSchemeAsync()`
+Check if the system is currently using an inverted color scheme.
+
+**Returns:**
+
+A bool for if the OS / Chromium currently has an inverted color scheme or is being instructed to use an inverted color scheme.
+
+## Events
+
+#### β‘ `Updated`
+Emitted when something in the underlying NativeTheme has changed. This normally means that either the value of ShouldUseDarkColorsAsync, ShouldUseHighContrastColorsAsync or ShouldUseInvertedColorSchemeAsync has changed.
+
+## Usage Examples
+
+### Basic Theme Detection
+
+```csharp
+// Check current theme
+var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
+Console.WriteLine($"Dark mode: {isDarkMode}");
+
+// Get current theme source
+var themeSource = await Electron.NativeTheme.GetThemeSourceAsync();
+Console.WriteLine($"Theme source: {themeSource}");
+```
+
+### Theme Change Monitoring
+
+```csharp
+// Monitor theme changes
+Electron.NativeTheme.Updated += () =>
+{
+ Console.WriteLine("Theme updated");
+ UpdateApplicationTheme();
+};
+
+async void UpdateApplicationTheme()
+{
+ var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
+ var isHighContrast = await Electron.NativeTheme.ShouldUseHighContrastColorsAsync();
+
+ // Update application appearance
+ ApplyTheme(isDarkMode, isHighContrast);
+}
+```
+
+### Manual Theme Control
+
+```csharp
+// Force dark theme
+Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Dark);
+
+// Force light theme
+Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light);
+
+// Follow system theme
+Electron.NativeTheme.SetThemeSource(ThemeSourceMode.System);
+```
+
+### Application Theme Integration
+
+```csharp
+public async Task InitializeThemeSupport()
+{
+ // Set initial theme based on system preference
+ var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
+ ApplyTheme(isDarkMode);
+
+ // Monitor theme changes
+ Electron.NativeTheme.Updated += async () =>
+ {
+ var darkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
+ ApplyTheme(darkMode);
+ };
+}
+
+private void ApplyTheme(bool isDarkMode)
+{
+ if (isDarkMode)
+ {
+ // Apply dark theme
+ SetDarkThemeColors();
+ UpdateWindowTheme("dark");
+ }
+ else
+ {
+ // Apply light theme
+ SetLightThemeColors();
+ UpdateWindowTheme("light");
+ }
+}
+```
+
+### Advanced Theme Management
+
+```csharp
+// Check all theme properties
+var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
+var isHighContrast = await Electron.NativeTheme.ShouldUseHighContrastColorsAsync();
+var isInverted = await Electron.NativeTheme.ShouldUseInvertedColorSchemeAsync();
+
+Console.WriteLine($"Dark mode: {isDarkMode}");
+Console.WriteLine($"High contrast: {isHighContrast}");
+Console.WriteLine($"Inverted: {isInverted}");
+
+// Apply appropriate theme
+if (isHighContrast)
+{
+ ApplyHighContrastTheme();
+}
+else if (isDarkMode)
+{
+ ApplyDarkTheme();
+}
+else
+{
+ ApplyLightTheme();
+}
+```
+
+### Theme-Aware Window Creation
+
+```csharp
+// Create window with theme-appropriate settings
+var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
+
+var windowOptions = new BrowserWindowOptions
+{
+ Width = 1200,
+ Height = 800,
+ Title = "My Application",
+ BackgroundColor = isDarkMode ? "#1a1a1a" : "#ffffff",
+ WebPreferences = new WebPreferences
+ {
+ // Additional web preferences based on theme
+ }
+};
+
+var window = await Electron.WindowManager.CreateWindowAsync(windowOptions);
+```
+
+## Related APIs
+
+- [Electron.WindowManager](WindowManager.md) - Apply theme to windows
+- [Electron.Screen](Screen.md) - Screen-related theme considerations
+- [Electron.App](App.md) - Application-level theme events
+
+## Additional Resources
+
+- [Electron NativeTheme Documentation](https://electronjs.org/docs/api/native-theme) - Official Electron native theme API
+- [Theme Support](../Core/What's-New.md) - Understanding theme functionality
+- [User Experience](../Using/Configuration.md) - Design theme-aware applications
diff --git a/docs/API/Notification.md b/docs/API/Notification.md
new file mode 100644
index 0000000..4d4ead7
--- /dev/null
+++ b/docs/API/Notification.md
@@ -0,0 +1,164 @@
+# Electron.Notification
+
+Show native desktop notifications with custom content and actions.
+
+## Overview
+
+The `Electron.Notification` API provides the ability to show native desktop notifications with custom titles, bodies, icons, and actions. Notifications work across Windows, macOS, and Linux with platform-specific behavior.
+
+## Methods
+
+#### π§ `Task IsSupportedAsync()`
+Check if desktop notifications are supported on the current platform.
+
+**Returns:**
+
+Whether or not desktop notifications are supported on the current system.
+
+#### π§ `void Show(NotificationOptions notificationOptions)`
+Create OS desktop notifications with the specified options.
+
+**Parameters:**
+- `notificationOptions` - Notification configuration options
+
+## Usage Examples
+
+### Basic Notification
+
+```csharp
+// Simple notification
+Electron.Notification.Show(new NotificationOptions
+{
+ Title = "My Application",
+ Body = "This is a notification message",
+ Icon = "assets/notification-icon.png"
+});
+```
+
+### Notification with Actions
+
+```csharp
+// Notification with reply action
+Electron.Notification.Show(new NotificationOptions
+{
+ Title = "New Message",
+ Body = "You have a new message from John",
+ Icon = "assets/message-icon.png",
+ Actions = new[]
+ {
+ new NotificationAction { Text = "Reply", Type = NotificationActionType.Button },
+ new NotificationAction { Text = "View", Type = NotificationActionType.Button }
+ },
+ OnClick = () => OpenMessageWindow(),
+ OnAction = (action) =>
+ {
+ if (action == "Reply")
+ {
+ ShowReplyDialog();
+ }
+ else if (action == "View")
+ {
+ OpenMessageWindow();
+ }
+ }
+});
+```
+
+### Rich Notifications
+
+```csharp
+// Rich notification with all options
+Electron.Notification.Show(new NotificationOptions
+{
+ Title = "Download Complete",
+ Subtitle = "Your file has finished downloading",
+ Body = "document.pdf has been downloaded to your Downloads folder.",
+ Icon = "assets/download-icon.png",
+ ImageUrl = "file://" + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "assets/preview.png"),
+ Sound = NotificationSound.Default,
+ Urgency = NotificationUrgency.Normal,
+ Category = "transfer.complete",
+ Tag = "download-123",
+ Actions = new[]
+ {
+ new NotificationAction { Text = "Open", Type = NotificationActionType.Button },
+ new NotificationAction { Text = "Show in Folder", Type = NotificationActionType.Button }
+ },
+ OnShow = () => Console.WriteLine("Notification shown"),
+ OnClick = () => OpenDownloadedFile(),
+ OnClose = () => Console.WriteLine("Notification closed"),
+ OnAction = (action) =>
+ {
+ if (action == "Open")
+ {
+ OpenDownloadedFile();
+ }
+ else if (action == "Show in Folder")
+ {
+ ShowInFolder();
+ }
+ }
+});
+```
+
+### Platform-Specific Notifications
+
+```csharp
+// Windows toast notification
+if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+{
+ Electron.Notification.Show(new NotificationOptions
+ {
+ Title = "Background Task",
+ Body = "Your backup is complete",
+ Icon = "assets/app-icon.ico",
+ Tag = "backup-complete",
+ RequireInteraction = true
+ });
+}
+
+// macOS notification with sound
+else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+{
+ Electron.Notification.Show(new NotificationOptions
+ {
+ Title = "Alert",
+ Body = "Something needs your attention",
+ Sound = NotificationSound.Default,
+ Actions = new[]
+ {
+ new NotificationAction { Text = "View", Type = NotificationActionType.Button }
+ }
+ });
+}
+```
+
+### Notification Management
+
+```csharp
+// Check notification support
+var isSupported = await Electron.Notification.IsSupportedAsync();
+Console.WriteLine($"Notifications supported: {isSupported}");
+
+// Create notification with events
+var notification = new NotificationOptions
+{
+ Title = "Task Complete",
+ Body = "Your long-running task has finished",
+ OnShow = () => Console.WriteLine("Notification displayed"),
+ OnClick = () => OpenTaskResults(),
+ OnClose = () => Console.WriteLine("Notification dismissed")
+};
+
+Electron.Notification.Show(notification);
+```
+
+## Related APIs
+
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.Tray](Tray.md) - System tray integration with notifications
+- [Electron.Screen](Screen.md) - Position notifications based on screen layout
+
+## Additional Resources
+
+- [Electron Notification Documentation](https://electronjs.org/docs/api/notification) - Official Electron notification API
diff --git a/docs/API/Overview.md b/docs/API/Overview.md
new file mode 100644
index 0000000..01ce106
--- /dev/null
+++ b/docs/API/Overview.md
@@ -0,0 +1,62 @@
+# API Reference Overview
+
+The ElectronNET.Core API provides comprehensive access to Electron's native desktop functionality through a .NET interface. This section documents all the available API classes and their methods, events, and usage patterns.
+
+## API Classes
+
+### Core Application Management
+- **[Electron.App](App.md)** - Control your application's event lifecycle, manage app metadata, and handle system-level operations
+- **[Electron.WindowManager](WindowManager.md)** - Create and manage browser windows, control window behavior and appearance
+- **[Electron.Menu](Menu.md)** - Create application menus, context menus, and menu items with full keyboard shortcut support
+
+### User Interface & Interaction
+- **[Electron.Dialog](Dialog.md)** - Display native system dialogs for opening/saving files, showing messages and alerts
+- **[Electron.Notification](Notification.md)** - Show native desktop notifications with custom content and actions
+- **[Electron.Tray](Tray.md)** - Create system tray icons with context menus and tooltip support
+- **[Electron.Dock](Dock.md)** - macOS dock integration for bounce effects and badge counts
+
+### System Integration
+- **[Electron.Shell](Shell.md)** - Desktop integration for opening files, URLs, and accessing system paths
+- **[Electron.Clipboard](Clipboard.md)** - Read from and write to the system clipboard with multiple data formats
+- **[Electron.Screen](Screen.md)** - Access display and screen information for responsive layouts
+- **[Electron.NativeTheme](NativeTheme.md)** - Detect and respond to system theme changes (light/dark mode)
+
+### Communication & Automation
+- **[Electron.IpcMain](IpcMain.md)** - Inter-process communication between main process and renderer processes
+- **[Electron.HostHook](HostHook.md)** - Custom host hook functionality for advanced integration scenarios
+- **[Electron.GlobalShortcut](GlobalShortcut.md)** - Register global keyboard shortcuts that work even when app is not focused
+- **[Electron.AutoUpdater](AutoUpdater.md)** - Handle application updates and installation processes
+
+### System Monitoring
+- **[Electron.PowerMonitor](PowerMonitor.md)** - Monitor system power events like sleep, wake, and battery status
+
+
+## API Relationships
+
+### Window and Dialog Integration
+- Use `BrowserWindow` instances as parent windows for dialogs
+- Dialogs automatically become modal when parent window is provided
+- Window events coordinate with application lifecycle events
+
+### IPC Communication
+- `IpcMain` handles communication from renderer processes
+- Use with `Electron.WindowManager` for window-specific messaging
+- Coordinate with `Electron.App` events for application-wide communication
+
+### System Integration
+- `Shell` operations work with file paths from `Dialog` operations
+- `Screen` information helps create properly sized windows
+- `Notification` and `Tray` provide complementary user interaction methods
+
+## π Next Steps
+
+- **[Electron.App](App.md)** - Start with application lifecycle management
+- **[Electron.WindowManager](WindowManager.md)** - Learn window creation and management
+- **[Electron.Dialog](Dialog.md)** - Add file operations and user interactions
+- **[Electron.Menu](Menu.md)** - Implement application menus and shortcuts
+
+## π Additional Resources
+
+- **[Electron Documentation](https://electronjs.org/docs)** - Official Electron API reference
+- **[Getting Started](../GettingStarted/ASP.Net.md)** - Development setup guides
+- **[Migration Guide](../Core/Migration-Guide.md)** - Moving from previous versions
diff --git a/docs/API/PowerMonitor.md b/docs/API/PowerMonitor.md
new file mode 100644
index 0000000..14a0dd7
--- /dev/null
+++ b/docs/API/PowerMonitor.md
@@ -0,0 +1,188 @@
+# Electron.PowerMonitor
+
+Monitor system power events like sleep, wake, and battery status.
+
+## Overview
+
+The `Electron.PowerMonitor` API provides access to system power events and state changes. This includes monitoring when the system is going to sleep, waking up, or changing power sources.
+
+## Events
+
+#### β‘ `OnAC`
+Emitted when the system changes to AC power.
+
+#### β‘ `OnBattery`
+Emitted when system changes to battery power.
+
+#### β‘ `OnLockScreen`
+Emitted when the system is about to lock the screen.
+
+#### β‘ `OnResume`
+Emitted when system is resuming.
+
+#### β‘ `OnShutdown`
+Emitted when the system is about to reboot or shut down.
+
+#### β‘ `OnSuspend`
+Emitted when the system is suspending.
+
+#### β‘ `OnUnLockScreen`
+Emitted when the system is about to unlock the screen.
+
+## Usage Examples
+
+### Basic Power Event Monitoring
+
+```csharp
+// Monitor system sleep/wake
+Electron.PowerMonitor.OnSuspend += () =>
+{
+ Console.WriteLine("System going to sleep");
+ // Save application state
+ SaveApplicationState();
+};
+
+Electron.PowerMonitor.OnResume += () =>
+{
+ Console.WriteLine("System waking up");
+ // Restore application state
+ RestoreApplicationState();
+};
+```
+
+### Screen Lock/Unlock Monitoring
+
+```csharp
+// Handle screen lock events
+Electron.PowerMonitor.OnLockScreen += () =>
+{
+ Console.WriteLine("Screen locking");
+ // Pause real-time operations
+ PauseRealTimeOperations();
+};
+
+Electron.PowerMonitor.OnUnLockScreen += () =>
+{
+ Console.WriteLine("Screen unlocking");
+ // Resume real-time operations
+ ResumeRealTimeOperations();
+};
+```
+
+### Power Source Changes
+
+```csharp
+// Monitor power source changes
+Electron.PowerMonitor.OnAC += () =>
+{
+ Console.WriteLine("Switched to AC power");
+ // Adjust power-intensive operations
+ EnablePowerIntensiveFeatures();
+};
+
+Electron.PowerMonitor.OnBattery += () =>
+{
+ Console.WriteLine("Switched to battery power");
+ // Reduce power consumption
+ EnablePowerSavingMode();
+};
+```
+
+### System Shutdown Handling
+
+```csharp
+// Handle system shutdown
+Electron.PowerMonitor.OnShutdown += () =>
+{
+ Console.WriteLine("System shutting down");
+ // Save critical data and exit gracefully
+ SaveAndExit();
+};
+```
+
+### Application State Management
+
+```csharp
+private bool isSuspended = false;
+
+public void InitializePowerMonitoring()
+{
+ // Track suspension state
+ Electron.PowerMonitor.OnSuspend += () =>
+ {
+ isSuspended = true;
+ OnSystemSleep();
+ };
+
+ Electron.PowerMonitor.OnResume += () =>
+ {
+ isSuspended = false;
+ OnSystemWake();
+ };
+
+ // Handle screen lock for security
+ Electron.PowerMonitor.OnLockScreen += () =>
+ {
+ OnScreenLocked();
+ };
+}
+
+private void OnSystemSleep()
+{
+ // Pause network operations
+ PauseNetworkOperations();
+
+ // Save unsaved work
+ AutoSaveDocuments();
+
+ // Reduce resource usage
+ MinimizeResourceUsage();
+}
+
+private void OnSystemWake()
+{
+ // Resume network operations
+ ResumeNetworkOperations();
+
+ // Check for updates
+ CheckForUpdates();
+
+ // Restore full functionality
+ RestoreFullFunctionality();
+}
+
+private void OnScreenLocked()
+{
+ // Hide sensitive information
+ HideSensitiveData();
+
+ // Pause real-time features
+ PauseRealTimeFeatures();
+}
+```
+
+### Battery Status Monitoring
+
+```csharp
+// Monitor battery status changes
+Electron.PowerMonitor.OnAC += () =>
+{
+ Console.WriteLine("Plugged in - full performance mode");
+ EnableFullPerformanceMode();
+};
+
+Electron.PowerMonitor.OnBattery += () =>
+{
+ Console.WriteLine("On battery - power saving mode");
+ EnablePowerSavingMode();
+};
+```
+
+## Related APIs
+
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.Notification](Notification.md) - Notify users about power events
+
+## Additional Resources
+
+- [Electron PowerMonitor Documentation](https://electronjs.org/docs/api/power-monitor) - Official Electron power monitor API
diff --git a/docs/API/Screen.md b/docs/API/Screen.md
new file mode 100644
index 0000000..767050e
--- /dev/null
+++ b/docs/API/Screen.md
@@ -0,0 +1,174 @@
+# Electron.Screen
+
+Access display and screen information for responsive layouts.
+
+## Overview
+
+The `Electron.Screen` API provides access to screen and display information, including screen size, display metrics, cursor position, and multi-monitor configurations. This is essential for creating responsive applications that adapt to different screen configurations.
+
+## Methods
+
+#### π§ `Task GetAllDisplaysAsync()`
+Gets information about all available displays.
+
+**Returns:**
+
+An array of displays that are currently available.
+
+#### π§ `Task GetCursorScreenPointAsync()`
+Gets the current position of the mouse cursor on screen.
+
+**Returns:**
+
+The current absolute position of the mouse pointer.
+
+#### π§ `Task GetDisplayMatchingAsync(Rectangle rectangle)`
+Gets the display that most closely intersects the provided bounds.
+
+**Parameters:**
+- `rectangle` - The rectangle to find the matching display for
+
+**Returns:**
+
+The display that most closely intersects the provided bounds.
+
+#### π§ `Task GetDisplayNearestPointAsync(Point point)`
+Gets the display that is closest to the specified point.
+
+**Parameters:**
+- `point` - The point to find the nearest display for
+
+**Returns:**
+
+The display nearest the specified point.
+
+#### π§ `Task GetMenuBarHeightAsync()`
+macOS: The height of the menu bar in pixels.
+
+**Returns:**
+
+The height of the menu bar in pixels.
+
+#### π§ `Task GetPrimaryDisplayAsync()`
+Gets information about the primary display (main screen).
+
+**Returns:**
+
+The primary display.
+
+## Events
+
+#### β‘ `OnDisplayAdded`
+Emitted when a new Display has been added.
+
+#### β‘ `OnDisplayMetricsChanged`
+Emitted when one or more metrics change in a display. The changedMetrics is an array of strings that describe the changes. Possible changes are bounds, workArea, scaleFactor and rotation.
+
+#### β‘ `OnDisplayRemoved`
+Emitted when oldDisplay has been removed.
+
+## Usage Examples
+
+### Display Information
+
+```csharp
+// Get primary display
+var primaryDisplay = await Electron.Screen.GetPrimaryDisplayAsync();
+Console.WriteLine($"Primary display: {primaryDisplay.Size.Width}x{primaryDisplay.Size.Height}");
+
+// Get all displays
+var displays = await Electron.Screen.GetAllDisplaysAsync();
+Console.WriteLine($"Available displays: {displays.Length}");
+
+// Get display near cursor
+var cursorPoint = await Electron.Screen.GetCursorScreenPointAsync();
+var nearestDisplay = await Electron.Screen.GetDisplayNearestPointAsync(cursorPoint);
+Console.WriteLine($"Nearest display scale factor: {nearestDisplay.ScaleFactor}");
+```
+
+### Multi-Monitor Setup
+
+```csharp
+// Get all displays for multi-monitor setup
+var displays = await Electron.Screen.GetAllDisplaysAsync();
+
+foreach (var display in displays)
+{
+ Console.WriteLine($"Display {display.Id}:");
+ Console.WriteLine($" Size: {display.Size.Width}x{display.Size.Height}");
+ Console.WriteLine($" Position: {display.Bounds.X},{display.Bounds.Y}");
+ Console.WriteLine($" Scale Factor: {display.ScaleFactor}");
+ Console.WriteLine($" Work Area: {display.WorkArea.Width}x{display.WorkArea.Height}");
+}
+```
+
+### Responsive Window Placement
+
+```csharp
+// Create window on appropriate display
+var displays = await Electron.Screen.GetAllDisplaysAsync();
+var targetDisplay = displays.FirstOrDefault(d => d.Bounds.X > 0) ?? displays.First();
+
+var windowOptions = new BrowserWindowOptions
+{
+ Width = Math.Min(1200, targetDisplay.WorkArea.Width),
+ Height = Math.Min(800, targetDisplay.WorkArea.Height),
+ X = targetDisplay.WorkArea.X + (targetDisplay.WorkArea.Width - 1200) / 2,
+ Y = targetDisplay.WorkArea.Y + (targetDisplay.WorkArea.Height - 800) / 2
+};
+
+var window = await Electron.WindowManager.CreateWindowAsync(windowOptions);
+```
+
+### Display Change Monitoring
+
+```csharp
+// Monitor display changes
+Electron.Screen.OnDisplayAdded += (display) =>
+{
+ Console.WriteLine($"Display added: {display.Id}");
+ UpdateWindowPositions();
+};
+
+Electron.Screen.OnDisplayRemoved += (display) =>
+{
+ Console.WriteLine($"Display removed: {display.Id}");
+ UpdateWindowPositions();
+};
+
+Electron.Screen.OnDisplayMetricsChanged += (display, metrics) =>
+{
+ Console.WriteLine($"Display {display.Id} metrics changed: {string.Join(", ", metrics)}");
+ UpdateWindowPositions();
+};
+
+void UpdateWindowPositions()
+{
+ // Recalculate window positions based on current displays
+}
+```
+
+### macOS Menu Bar Height
+
+```csharp
+// Account for menu bar height on macOS
+if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+{
+ var menuBarHeight = await Electron.Screen.GetMenuBarHeightAsync();
+
+ var windowOptions = new BrowserWindowOptions
+ {
+ Y = menuBarHeight, // Position below menu bar
+ TitleBarStyle = TitleBarStyle.Hidden // Hide title bar for custom look
+ };
+}
+```
+
+## Related APIs
+
+- [Electron.WindowManager](WindowManager.md) - Position windows based on screen information
+- [Electron.App](App.md) - Handle display-related application events
+
+## Additional Resources
+
+- [Electron Screen Documentation](https://electronjs.org/docs/api/screen) - Official Electron screen API
diff --git a/docs/API/Shell.md b/docs/API/Shell.md
new file mode 100644
index 0000000..1f2b2e3
--- /dev/null
+++ b/docs/API/Shell.md
@@ -0,0 +1,175 @@
+# Electron.Shell
+
+Desktop integration for opening files, URLs, and accessing system paths.
+
+## Overview
+
+The `Electron.Shell` API provides system integration functionality for opening files and URLs with their default applications, managing trash/recycle bin, and creating/reading shortcut links.
+
+## Methods
+
+#### π§ `void Beep()`
+Play the beep sound.
+
+#### π§ `Task OpenExternalAsync(string url)`
+Open the given external protocol URL in the desktop's default manner (e.g., mailto: URLs in the user's default mail agent).
+
+**Parameters:**
+- `url` - Max 2081 characters on windows
+
+**Returns:**
+
+The error message corresponding to the failure if a failure occurred, otherwise empty string.
+
+#### π§ `Task OpenExternalAsync(string url, OpenExternalOptions options)`
+Open the given external protocol URL with additional options.
+
+**Parameters:**
+- `url` - Max 2081 characters on windows
+- `options` - Controls the behavior of OpenExternal
+
+**Returns:**
+
+The error message corresponding to the failure if a failure occurred, otherwise empty string.
+
+#### π§ `Task OpenPathAsync(string path)`
+Open the given file in the desktop's default manner.
+
+**Parameters:**
+- `path` - The path to the directory or file
+
+**Returns:**
+
+The error message corresponding to the failure if a failure occurred, otherwise empty string.
+
+#### π§ `Task ReadShortcutLinkAsync(string shortcutPath)`
+Resolves the shortcut link at shortcutPath. An exception will be thrown when any error happens.
+
+**Parameters:**
+- `shortcutPath` - The path to the shortcut
+
+**Returns:**
+
+ShortcutDetails of the shortcut.
+
+#### π§ `Task ShowItemInFolderAsync(string fullPath)`
+Show the given file in a file manager. If possible, select the file.
+
+**Parameters:**
+- `fullPath` - The full path to the directory or file
+
+#### π§ `Task TrashItemAsync(string fullPath)`
+Move the given file to trash and returns a bool status for the operation.
+
+**Parameters:**
+- `fullPath` - The full path to the directory or file
+
+**Returns:**
+
+Whether the item was successfully moved to the trash.
+
+#### π§ `Task WriteShortcutLinkAsync(string shortcutPath, ShortcutLinkOperation operation, ShortcutDetails options)`
+Creates or updates a shortcut link at shortcutPath.
+
+**Parameters:**
+- `shortcutPath` - The path to the shortcut
+- `operation` - Default is ShortcutLinkOperation.Create
+- `options` - Structure of a shortcut
+
+**Returns:**
+
+Whether the shortcut was created successfully.
+
+## Usage Examples
+
+### File Operations
+
+```csharp
+// Open file with default application
+var error = await Electron.Shell.OpenPathAsync(filePath);
+if (string.IsNullOrEmpty(error))
+{
+ Console.WriteLine("File opened successfully");
+}
+else
+{
+ Console.WriteLine($"Failed to open file: {error}");
+}
+
+// Show file in file manager
+await Electron.Shell.ShowItemInFolderAsync(filePath);
+
+// Move file to trash
+var trashed = await Electron.Shell.TrashItemAsync(filePath);
+Console.WriteLine($"File trashed: {trashed}");
+```
+
+### URL Operations
+
+```csharp
+// Open URL in default browser
+var error = await Electron.Shell.OpenExternalAsync("https://electron.net");
+if (!string.IsNullOrEmpty(error))
+{
+ Console.WriteLine($"Failed to open URL: {error}");
+}
+
+// Open email client
+await Electron.Shell.OpenExternalAsync("mailto:user@example.com");
+
+// Open with options
+var error = await Electron.Shell.OpenExternalAsync("https://example.com", new OpenExternalOptions
+{
+ Activate = true
+});
+```
+
+### System Integration
+
+```csharp
+// Play system beep
+Electron.Shell.Beep();
+
+// Create desktop shortcut
+var success = await Electron.Shell.WriteShortcutLinkAsync(
+ @"C:\Users\Public\Desktop\MyApp.lnk",
+ ShortcutLinkOperation.Create,
+ new ShortcutDetails
+ {
+ Target = "C:\\Program Files\\MyApp\\MyApp.exe",
+ Description = "My Application",
+ WorkingDirectory = "C:\\Program Files\\MyApp"
+ }
+);
+
+// Read shortcut information
+var details = await Electron.Shell.ReadShortcutLinkAsync(@"C:\Users\Public\Desktop\MyApp.lnk");
+Console.WriteLine($"Target: {details.Target}");
+```
+
+### Integration with Dialog API
+
+```csharp
+// Use with file dialog results
+var files = await Electron.Dialog.ShowOpenDialogAsync(window, options);
+if (files.Length > 0)
+{
+ var selectedFile = files[0];
+
+ // Open the selected file
+ await Electron.Shell.OpenPathAsync(selectedFile);
+
+ // Show in file manager
+ await Electron.Shell.ShowItemInFolderAsync(selectedFile);
+}
+```
+
+## Related APIs
+
+- [Electron.Dialog](Dialog.md) - Select files to open with Shell
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.Clipboard](Clipboard.md) - Copy file paths for Shell operations
+
+## Additional Resources
+
+- [Electron Shell Documentation](https://electronjs.org/docs/api/shell) - Official Electron shell API
diff --git a/docs/API/Tray.md b/docs/API/Tray.md
new file mode 100644
index 0000000..5aaad88
--- /dev/null
+++ b/docs/API/Tray.md
@@ -0,0 +1,232 @@
+# Electron.Tray
+
+Add icons and context menus to the system's notification area.
+
+## Overview
+
+The `Electron.Tray` API provides the ability to add icons and context menus to the system's notification area (system tray). This allows applications to provide quick access to common functions and maintain a presence in the system even when windows are closed.
+
+## Properties
+
+#### π `IReadOnlyCollection MenuItems`
+Gets a read-only collection of all current tray menu items.
+
+## Methods
+
+#### π§ `void Destroy()`
+Destroys the tray icon immediately.
+
+#### π§ `void DisplayBalloon(DisplayBalloonOptions options)`
+Windows: Displays a tray balloon notification.
+
+**Parameters:**
+- `options` - Balloon notification options
+
+#### π§ `Task IsDestroyedAsync()`
+Check if the tray icon has been destroyed.
+
+**Returns:**
+
+Whether the tray icon is destroyed.
+
+#### π§ `void SetImage(string image)`
+Sets the image associated with this tray icon.
+
+**Parameters:**
+- `image` - New image for the tray icon
+
+#### π§ `void SetPressedImage(string image)`
+Sets the image associated with this tray icon when pressed on macOS.
+
+**Parameters:**
+- `image` - Image for pressed state
+
+#### π§ `void SetTitle(string title)`
+macOS: Sets the title displayed aside of the tray icon in the status bar.
+
+**Parameters:**
+- `title` - Title text
+
+#### π§ `void SetToolTip(string toolTip)`
+Sets the hover text for this tray icon.
+
+**Parameters:**
+- `toolTip` - Tooltip text
+
+#### π§ `void Show(string image)`
+Shows the tray icon without a context menu.
+
+**Parameters:**
+- `image` - The image to use for the tray icon
+
+#### π§ `void Show(string image, MenuItem menuItem)`
+Shows the tray icon with a single menu item.
+
+**Parameters:**
+- `image` - The image to use for the tray icon
+- `menuItem` - Single menu item for the tray context menu
+
+#### π§ `void Show(string image, MenuItem[] menuItems)`
+Shows the tray icon with multiple menu items.
+
+**Parameters:**
+- `image` - The image to use for the tray icon
+- `menuItems` - Array of menu items for the tray context menu
+
+## Events
+
+#### β‘ `OnBalloonClick`
+Windows: Emitted when the tray balloon is clicked.
+
+#### β‘ `OnBalloonClosed`
+Windows: Emitted when the tray balloon is closed because of timeout or user manually closes it.
+
+#### β‘ `OnBalloonShow`
+Windows: Emitted when the tray balloon shows.
+
+#### β‘ `OnClick`
+Emitted when the tray icon is clicked.
+
+#### β‘ `OnDoubleClick`
+macOS, Windows: Emitted when the tray icon is double clicked.
+
+#### β‘ `OnRightClick`
+macOS, Windows: Emitted when the tray icon is right clicked.
+
+## Usage Examples
+
+### Basic Tray Icon
+
+```csharp
+// Simple tray icon
+await Electron.Tray.Show("assets/tray-icon.png");
+
+// Tray icon with single menu item
+await Electron.Tray.Show("assets/tray-icon.png", new MenuItem
+{
+ Label = "Show Window",
+ Click = () => ShowMainWindow()
+});
+```
+
+### Tray with Context Menu
+
+```csharp
+// Tray with multiple menu items
+var trayMenuItems = new[]
+{
+ new MenuItem { Label = "Show Window", Click = () => ShowMainWindow() },
+ new MenuItem { Label = "Settings", Click = () => OpenSettings() },
+ new MenuItem { Type = MenuType.Separator },
+ new MenuItem { Label = "Exit", Click = () => Electron.App.Quit() }
+};
+
+await Electron.Tray.Show("assets/tray-icon.png", trayMenuItems);
+```
+
+### Dynamic Tray Updates
+
+```csharp
+// Update tray tooltip based on status
+await Electron.Tray.SetToolTip("MyApp - Connected");
+
+// Change tray icon based on state
+if (isConnected)
+{
+ await Electron.Tray.SetImage("assets/connected.png");
+}
+else
+{
+ await Electron.Tray.SetImage("assets/disconnected.png");
+}
+```
+
+### Tray Event Handling
+
+```csharp
+// Handle tray clicks
+Electron.Tray.OnClick += (clickArgs, bounds) =>
+{
+ if (clickArgs.AltKey || clickArgs.ShiftKey)
+ {
+ // Alt+Click or Shift+Click - show context menu
+ Electron.Menu.ContextMenuPopup(Electron.WindowManager.BrowserWindows.First());
+ }
+ else
+ {
+ // Regular click - toggle main window
+ ToggleMainWindow();
+ }
+};
+
+Electron.Tray.OnRightClick += (clickArgs, bounds) =>
+{
+ // Show context menu on right click
+ Electron.Menu.ContextMenuPopup(Electron.WindowManager.BrowserWindows.First());
+};
+```
+
+### Windows Balloon Notifications
+
+```csharp
+// Show Windows balloon notification
+await Electron.Tray.DisplayBalloon(new DisplayBalloonOptions
+{
+ Title = "Background Task Complete",
+ Content = "Your file has been processed successfully.",
+ Icon = "assets/notification-icon.ico"
+});
+
+// Handle balloon events
+Electron.Tray.OnBalloonClick += () =>
+{
+ ShowMainWindow();
+ Electron.WindowManager.BrowserWindows.First().Focus();
+};
+```
+
+### macOS Tray Features
+
+```csharp
+// macOS specific tray features
+if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+{
+ await Electron.Tray.SetTitle("MyApp");
+
+ // Use template image for dark mode support
+ await Electron.Tray.SetImage("assets/tray-template.png");
+ await Electron.Tray.SetPressedImage("assets/tray-pressed-template.png");
+}
+```
+
+### Application Integration
+
+```csharp
+// Integrate with application lifecycle
+Electron.App.WindowAllClosed += () =>
+{
+ // Keep app running in tray when windows are closed
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ Electron.App.Hide();
+ }
+};
+
+// Handle tray double-click
+Electron.Tray.OnDoubleClick += (clickArgs, bounds) =>
+{
+ ShowMainWindow();
+ Electron.WindowManager.BrowserWindows.First().Focus();
+};
+```
+
+## Related APIs
+
+- [Electron.Menu](Menu.md) - Context menus for tray icons
+- [Electron.Notification](Notification.md) - Desktop notifications
+- [Electron.App](App.md) - Application lifecycle events
+- [Electron.WindowManager](WindowManager.md) - Windows to show/hide from tray
+
+## Additional Resources
+
+- [Electron Tray Documentation](https://electronjs.org/docs/api/tray) - Official Electron tray API
diff --git a/docs/API/WebContents.md b/docs/API/WebContents.md
new file mode 100644
index 0000000..e878e58
--- /dev/null
+++ b/docs/API/WebContents.md
@@ -0,0 +1,292 @@
+# Electron.WebContents
+
+Render and control web pages.
+
+## Overview
+
+The `Electron.WebContents` API provides control over web page content within Electron windows. It handles page loading, navigation, JavaScript execution, and web page lifecycle events.
+
+## Properties
+
+#### π `int Id`
+Gets the unique identifier for this web contents.
+
+#### π `Session Session`
+Manage browser sessions, cookies, cache, proxy settings, etc.
+
+## Methods
+
+#### π§ `void ExecuteJavaScriptAsync(string code, bool userGesture = false)`
+Evaluates script code in page.
+
+In the browser window some HTML APIs like `requestFullScreen` can only be invoked by a gesture from the user. Setting `userGesture` to `true` will remove this limitation.
+
+Code execution will be suspended until web page stop loading.
+
+**Parameters:**
+- `code` - The code to execute
+- `userGesture` - if set to `true` simulate a user gesture
+
+**Returns:**
+
+The result of the executed code.
+
+#### π§ `Task GetPrintersAsync()`
+Get system printers.
+
+**Returns:**
+
+Array of available printers.
+
+#### π§ `Task GetUrl()`
+Get the URL of the loaded page.
+
+It's useful if a web-server redirects you and you need to know where it redirects. For instance, It's useful in case of Implicit Authorization.
+
+**Returns:**
+
+URL of the loaded page.
+
+#### π§ `void InsertCSS(bool isBrowserWindow, string path)`
+Inserts CSS into the web page.
+
+See: https://www.electronjs.org/docs/api/web-contents#contentsinsertcsscss-options
+
+Works for both BrowserWindows and BrowserViews.
+
+**Parameters:**
+- `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 LoadURLAsync(string url)`
+Loads the url in the window. The url must contain the protocol prefix.
+
+The async method will resolve when the page has finished loading, and rejects if the page fails to load.
+
+A noop rejection handler is already attached, which avoids unhandled rejection errors.
+
+Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then use the `pragma` header to achieve it.
+
+**Parameters:**
+- `url` - URL to load
+
+#### π§ `Task LoadURLAsync(string url, LoadURLOptions options)`
+Loads the url with additional options.
+
+The async method will resolve when the page has finished loading, and rejects if the page fails to load.
+
+A noop rejection handler is already attached, which avoids unhandled rejection errors.
+
+Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then use the `pragma` header to achieve it.
+
+**Parameters:**
+- `url` - URL to load
+- `options` - Loading options
+
+#### π§ `void OpenDevTools()`
+Opens the devtools.
+
+#### π§ `void OpenDevTools(OpenDevToolsOptions openDevToolsOptions)`
+Opens the devtools with options.
+
+**Parameters:**
+- `openDevToolsOptions` - Developer tools options
+
+#### π§ `Task PrintAsync(PrintOptions options = null)`
+Prints window's web page.
+
+**Parameters:**
+- `options` - Print options
+
+**Returns:**
+
+Whether the print operation succeeded.
+
+#### π§ `Task PrintToPDFAsync(string path, PrintToPDFOptions options = null)`
+Prints window's web page as PDF with Chromium's preview printing custom settings.The landscape will be ignored if @page CSS at-rule is used in the web page. By default, an empty options will be regarded as: Use page-break-before: always; CSS style to force to print to a new page.
+
+**Parameters:**
+- `path` - Output file path
+- `options` - PDF generation options
+
+**Returns:**
+
+Whether the PDF generation succeeded.
+
+## Events
+
+#### β‘ `InputEvent`
+Emitted when an input event is sent to the WebContents.
+
+#### β‘ `OnCrashed`
+Emitted when the renderer process crashes or is killed.
+
+#### β‘ `OnDidFailLoad`
+Emitted when the load failed.
+
+#### β‘ `OnDidFinishLoad`
+Emitted when the navigation is done, i.e. the spinner of the tab has stopped spinning, and the onload event was dispatched.
+
+#### β‘ `OnDidNavigate`
+Emitted when a main frame navigation is done.
+
+#### β‘ `OnDidRedirectNavigation`
+Emitted after a server side redirect occurs during navigation.
+
+#### β‘ `OnDidStartNavigation`
+Emitted when any frame (including main) starts navigating.
+
+#### β‘ `OnDomReady`
+Emitted when the document in the top-level frame is loaded.
+
+#### β‘ `OnWillRedirect`
+Emitted when a server side redirect occurs during navigation.
+
+## Usage Examples
+
+### Page Loading
+
+```csharp
+// Load URL with options
+await webContents.LoadURLAsync("https://example.com", new LoadURLOptions
+{
+ UserAgent = "MyApp/1.0",
+ ExtraHeaders = "Authorization: Bearer token123"
+});
+
+// Load local file
+await webContents.LoadURLAsync("file://" + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app/index.html"));
+
+// Get current URL
+var currentUrl = await webContents.GetUrl();
+Console.WriteLine($"Current URL: {currentUrl}");
+```
+
+### JavaScript Execution
+
+```csharp
+// Execute simple JavaScript
+var result = await webContents.ExecuteJavaScriptAsync("document.title");
+Console.WriteLine($"Page title: {result}");
+
+// Execute with user gesture simulation
+await webContents.ExecuteJavaScriptAsync("document.requestFullscreen()", true);
+
+// Execute complex code
+var userAgent = await webContents.ExecuteJavaScriptAsync("navigator.userAgent");
+Console.WriteLine($"User agent: {userAgent}");
+```
+
+### Developer Tools
+
+```csharp
+// Open dev tools
+webContents.OpenDevTools();
+
+// Open with specific options
+webContents.OpenDevTools(new OpenDevToolsOptions
+{
+ Mode = DevToolsMode.Detached,
+ Activate = true
+});
+```
+
+### CSS Injection
+
+```csharp
+// Inject CSS file
+webContents.InsertCSS(true, "styles/custom-theme.css");
+
+// Inject CSS for BrowserView
+webContents.InsertCSS(false, "styles/browser-view.css");
+```
+
+### Printing Operations
+
+```csharp
+// Print web page
+var printSuccess = await webContents.PrintAsync(new PrintOptions
+{
+ Silent = false,
+ PrintBackground = true,
+ DeviceName = "My Printer"
+});
+
+if (printSuccess)
+{
+ Console.WriteLine("Print job sent successfully");
+}
+```
+
+### PDF Generation
+
+```csharp
+// Generate PDF
+var pdfSuccess = await webContents.PrintToPDFAsync("document.pdf", new PrintToPDFOptions
+{
+ MarginsType = PrintToPDFMarginsType.None,
+ PageSize = PrintToPDFPageSize.A4,
+ PrintBackground = true,
+ Landscape = false
+});
+
+if (pdfSuccess)
+{
+ Console.WriteLine("PDF generated successfully");
+}
+```
+
+### Navigation Monitoring
+
+```csharp
+// Monitor navigation events
+webContents.OnDidStartNavigation += (url) =>
+{
+ Console.WriteLine($"Starting navigation to: {url}");
+};
+
+webContents.OnDidNavigate += (navInfo) =>
+{
+ Console.WriteLine($"Navigated to: {navInfo.Url}");
+};
+
+webContents.OnDidFinishLoad += () =>
+{
+ Console.WriteLine("Page finished loading");
+};
+
+webContents.OnDidFailLoad += (failInfo) =>
+{
+ Console.WriteLine($"Page failed to load: {failInfo.ErrorCode} - {failInfo.ErrorDescription}");
+};
+```
+
+### Content Interaction
+
+```csharp
+// Wait for DOM ready
+webContents.OnDomReady += () =>
+{
+ Console.WriteLine("DOM is ready");
+ // Safe to execute DOM-related JavaScript now
+};
+
+// Handle page crashes
+webContents.OnCrashed += (killed) =>
+{
+ Console.WriteLine($"Renderer crashed, killed: {killed}");
+ // Optionally reload the page
+};
+```
+
+## Related APIs
+
+- [Electron.WindowManager](WindowManager.md) - Windows containing web contents
+- [Electron.Session](Session.md) - Session management for web contents
+- [Electron.IpcMain](IpcMain.md) - Communication with web contents
+
+## Additional Resources
+
+- [Electron WebContents Documentation](https://electronjs.org/docs/api/web-contents) - Official Electron web contents API
+- [Web Content Management](../Core/What's-New.md) - Understanding web content handling
+- [Security Considerations](../Using/Configuration.md) - Secure web content integration
diff --git a/docs/API/WindowManager.md b/docs/API/WindowManager.md
new file mode 100644
index 0000000..134e452
--- /dev/null
+++ b/docs/API/WindowManager.md
@@ -0,0 +1,208 @@
+# Electron.WindowManager
+
+Create and manage browser windows, control window behavior and appearance.
+
+## Overview
+
+The `Electron.WindowManager` API provides comprehensive control over browser windows in your Electron application. It handles window creation, management, and coordination with the application lifecycle.
+
+## Properties
+
+#### π `IReadOnlyCollection BrowserViews`
+Gets a read-only collection of all currently open browser views.
+
+#### π `IReadOnlyCollection BrowserWindows`
+Gets a read-only collection of all currently open browser windows.
+
+#### π `bool IsQuitOnWindowAllClosed`
+Controls whether the application quits when all windows are closed. Default is true.
+
+## Methods
+
+#### π§ `Task CreateBrowserViewAsync()`
+Creates a new browser view with default options.
+
+**Returns:**
+
+The created BrowserView instance.
+
+#### π§ `Task CreateBrowserViewAsync(BrowserViewConstructorOptions options)`
+Creates a new browser view with custom options.
+
+**Parameters:**
+- `options` - Browser view configuration options
+
+**Returns:**
+
+The created BrowserView instance.
+
+#### π§ `Task CreateWindowAsync(string loadUrl = "http://localhost")`
+Creates a new browser window with default options.
+
+**Parameters:**
+- `loadUrl` - URL to load in the window. Defaults to "http://localhost"
+
+**Returns:**
+
+The created BrowserWindow instance.
+
+#### π§ `Task CreateWindowAsync(BrowserWindowOptions options, string loadUrl = "http://localhost")`
+Creates a new browser window with custom options.
+
+**Parameters:**
+- `options` - Window configuration options
+- `loadUrl` - URL to load in the window. Defaults to "http://localhost"
+
+**Returns:**
+
+The created BrowserWindow instance.
+
+## Usage Examples
+
+### Basic Window Creation
+
+```csharp
+// Create window with default options
+var mainWindow = await Electron.WindowManager.CreateWindowAsync();
+
+// Create window with custom options
+var settingsWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
+{
+ Width = 800,
+ Height = 600,
+ Show = false,
+ Title = "Settings",
+ WebPreferences = new WebPreferences
+ {
+ NodeIntegration = false,
+ ContextIsolation = true
+ }
+}, "https://localhost:5001/settings");
+```
+
+### Window Management
+
+```csharp
+// Get all windows
+var windows = Electron.WindowManager.BrowserWindows;
+Console.WriteLine($"Open windows: {windows.Count}");
+
+// Configure quit behavior
+Electron.WindowManager.IsQuitOnWindowAllClosed = false; // Keep app running when windows close
+
+// Handle window lifecycle
+Electron.App.WindowAllClosed += () =>
+{
+ Console.WriteLine("All windows closed");
+ if (Electron.WindowManager.IsQuitOnWindowAllClosed)
+ {
+ Electron.App.Quit();
+ }
+};
+```
+
+### Browser View Integration
+
+```csharp
+// Create browser view
+var browserView = await Electron.WindowManager.CreateBrowserViewAsync(new BrowserViewConstructorOptions
+{
+ WebPreferences = new WebPreferences
+ {
+ NodeIntegration = false,
+ ContextIsolation = true
+ }
+});
+
+// Add to window
+await mainWindow.SetBrowserViewAsync(browserView);
+await browserView.WebContents.LoadURLAsync("https://example.com");
+
+// Set view bounds
+await mainWindow.SetBoundsAsync(browserView, new Rectangle
+{
+ X = 0,
+ Y = 100,
+ Width = 800,
+ Height = 400
+});
+```
+
+### Window Options Configuration
+
+```csharp
+// Comprehensive window options
+var options = new BrowserWindowOptions
+{
+ Width = 1200,
+ Height = 800,
+ MinWidth = 600,
+ MinHeight = 400,
+ MaxWidth = 1920,
+ MaxHeight = 1080,
+ X = 100,
+ Y = 100,
+ Center = true,
+ Frame = true,
+ Title = "My Application",
+ Icon = "assets/app-icon.png",
+ Show = false,
+ AlwaysOnTop = false,
+ SkipTaskbar = false,
+ Kiosk = false,
+ TitleBarStyle = TitleBarStyle.Default,
+ BackgroundColor = "#FFFFFF",
+ DarkTheme = false,
+ Transparent = false,
+ WebPreferences = new WebPreferences
+ {
+ NodeIntegration = false,
+ ContextIsolation = true,
+ EnableWebSQL = false,
+ Partition = "persist:electron",
+ ZoomFactor = 1.0f,
+ DevTools = true
+ }
+};
+```
+
+### Multi-Window Applications
+
+```csharp
+// Create main window
+var mainWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
+{
+ Width = 1200,
+ Height = 800,
+ Show = false
+});
+
+// Create secondary window
+var secondaryWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
+{
+ Width = 600,
+ Height = 400,
+ Parent = mainWindow,
+ Modal = true,
+ Show = false
+});
+
+// Load different content
+await mainWindow.WebContents.LoadURLAsync("https://localhost:5001");
+await secondaryWindow.WebContents.LoadURLAsync("https://localhost:5001/settings");
+
+// Show windows when ready
+mainWindow.OnReadyToShow += () => mainWindow.Show();
+secondaryWindow.OnReadyToShow += () => secondaryWindow.Show();
+```
+
+## Related APIs
+
+- [Electron.App](App.md) - Application lifecycle and window events
+- [Electron.Dialog](Dialog.md) - Parent windows for modal dialogs
+- [Electron.Menu](Menu.md) - Window-specific menus
+- [Electron.WebContents](WebContents.md) - Window content management
+
+## Additional Resources
+
+- [Electron Window Management Documentation](https://electronjs.org/docs/api/browser-window) - Official Electron window API
diff --git a/docs/About.md b/docs/About.md
new file mode 100644
index 0000000..6c18fb2
--- /dev/null
+++ b/docs/About.md
@@ -0,0 +1,51 @@
+
+
+# About this Project
+
+Electron.NET has been developed by a small number of people in the hope that it may be useful for others.
+
+Support for this project in all forms is very welcome, no matter whether in form of code contributions or donations.
+
+## π¬ Community
+
+[](https://gitter.im/ElectronNET/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+
+Besides the chat on Gitter and the issues [discussed here](https://github.com/ElectronNET/Electron.NET/issues) you can also use [StackOverflow](https://stackoverflow.com/questions/tagged/electron.net) with the tag `electron.net`.
+
+
+## πββοΈπββ Contributing
+
+Feel free to submit a pull request if you find any bugs (to see a list of active issues, visit the [Issues section](https://github.com/ElectronNET/Electron.NET/issues).
+Please make sure all commits are properly documented.
+
+
+## π Donate
+
+We do this open source work in our free time. If you'd like us to invest more time on it, please [donate](https://donorbox.org/electron-net). Donation can be used to increase some issue priority. Thank you!
+
+[](https://donorbox.org/electron-net)
+
+Alternatively, consider using a GitHub sponsorship for the core maintainers:
+
+- [Gregor Biswanger](https://github.com/sponsors/GregorBiswanger)
+- [Florian Rappl](https://github.com/sponsors/FlorianRappl)
+
+Any support appreciated! π»
+
+
+## π¨βπ» Authors
+
+* **[Gregor Biswanger](https://github.com/GregorBiswanger)** - (Microsoft MVP, Intel Black Belt and Intel Software Innovator) is a freelance lecturer, consultant, trainer, author and speaker. He is a consultant for large and medium-sized companies, organizations and agencies for software architecture, web- and cross-platform development. You can find Gregor often on the road attending or speaking at international conferences. - [Cross-Platform-Blog](http://www.cross-platform-blog.com) - Twitter [@BFreakout](https://www.twitter.com/BFreakout)
+
+* **[Dr. Florian Rappl](https://github.com/FlorianRappl)** - Software Developer - from Munich, Germany. Microsoft MVP & Web Geek. - [The Art of Micro Frontends](https://microfrontends.art) - [Homepage](https://florian-rappl.de) - Twitter [@florianrappl](https://twitter.com/florianrappl)
+
+* [**softworkz**](https://github.com/softworkz) - full range developer - likes to start where others gave up - MS MVP alumni and Munich citizen as well. Has not been involved in the evolution of Electron.NET but rather dropped off the update and overhaul for ElectronNET.Core in a kind-of drive-by action.
+
+* **[Robert Muehsig](https://github.com/robertmuehsig)** - Software Developer - from Dresden, Germany, now living & working in Switzerland. Microsoft MVP & Web Geek. - [codeinside Blog](https://blog.codeinside.eu) - Twitter [@robert0muehsig](https://twitter.com/robert0muehsig)
+
+See also the list of [contributors](https://github.com/ElectronNET/Electron.NET/graphs/contributors) who participated in this project.
+
+
+## π License
+
+MIT-licensed. See [LICENSE](https://github.com/ElectronNET/Electron.NET?tab=MIT-1-ov-file#readme) for details.
diff --git a/docs/Core/Advanced-Migration-Topics.md b/docs/Core/Advanced-Migration-Topics.md
new file mode 100644
index 0000000..2881030
--- /dev/null
+++ b/docs/Core/Advanced-Migration-Topics.md
@@ -0,0 +1,90 @@
+# Advanced Migration Topics
+
+This guide covers advanced scenarios and edge cases that may require additional configuration when migrating to ElectronNET.Core.
+
+## Custom ASP.NET Port Configuration
+
+### Port Configuration Changes
+
+**Previous Approach:**
+Specifying the WebPort in `electron.manifest.json` is no longer supported because the ASP.NET-first launch mode makes this timing-dependent.
+
+**New Approach:**
+Configure custom ASP.NET ports through MSBuild metadata:
+
+```xml
+
+
+
+```
+
+## Custom ElectronHostHook Configuration
+
+> [!NOTE]
+> These changes are only required in case you are using a custom ElectronHostHook implementation!
+> If you have an ElectronHostHook folder in your project but you did not customize that code and aren't using its demo features (Excel and ZIP), you can also just remove that folder from your project.
+
+
+### TypeScript and Node.js Updates
+
+**Update package.json:**
+
+This shows the relevant changes only: All shown versions are the new required minimum versions.
+
+```json
+{
+ "devDependencies": {
+ "@types/node": "^22.18",
+ "typescript": "^5.9.3"
+ },
+ "dependencies": {
+ "socket.io": "^4.8.1",
+ }
+}
+```
+
+**Update Project File:**
+
+The below modifications will allow you to use the latest TypeScript compiler in your ASP.Net project.
+
+```xml
+
+
+
+ commonjs
+ true
+ ElectronHostHook/tsconfig.json
+
+
+
+
+
+
+
+
+
+```
+
+### Integration Benefits
+
+- **Modern TypeScript** - Latest language features and better type checking
+- **Updated Node.js Types** - Compatibility with Node.js 22.x APIs
+- **ESLint Integration** - Better code quality and consistency
+- **MSBuild Compilation** - Integrated with Visual Studio build process
+
+## Troubleshooting Advanced Scenarios
+
+### Multi-Project Solutions
+
+When using ElectronNET.Core in multi-project solutions:
+
+1. **Install ElectronNET.Core.Api** in class library projects
+2. **Install ElectronNET.Core and ElectronNET.Core.AspNet** only in the startup project
+3. **Share configuration** through project references or shared files
+
+
+## Next Steps
+
+- **[Migration Guide](Migration-Guide.md)** - Complete migration process
+- **[What's New?](What's-New.md)** - Overview of all ElectronNET.Core features
+- **[Getting Started](../GettingStarted/ASP.Net.md)** - Development workflows
diff --git a/docs/Core/Migration-Guide.md b/docs/Core/Migration-Guide.md
new file mode 100644
index 0000000..39ca173
--- /dev/null
+++ b/docs/Core/Migration-Guide.md
@@ -0,0 +1,170 @@
+# Migration Guide
+
+Migrating from previous versions of Electron.NET to ElectronNET.Core is straightforward but requires several important changes. This guide walks you through the process step by step.
+
+## π Prerequisites
+
+Before starting the migration:
+
+- **Backup your project** - Ensure you have a working backup
+- **Update development tools** - Install Node.js 22.x and .NET 8.0+
+- **Review current setup** - Note your current Electron and ASP.NET versions
+
+## π Migration Steps
+
+### Step 1: Update NuGet Packages
+
+**Uninstall old packages:**
+```powershell
+dotnet remove package ElectronNET.API
+```
+
+**Install new packages:**
+```powershell
+dotnet add package ElectronNET.Core
+dotnet add package ElectronNET.Core.AspNet # For ASP.NET projects
+```
+
+> **Note**: The API package is automatically included as a dependency of `ElectronNET.Core`. See [Package Description](../RelInfo/Package-Description.md) for details about the package structure.
+
+
+### Step 2: Configure Project Settings
+
+**Auto-generated Configuration:**
+ElectronNET.Core automatically creates `electron-builder.json` during the first build or NuGet restore. No manual configuration is needed for basic setups.
+
+**Migrate Existing Configuration:**
+If you have an existing `electron.manifest.json` file:
+
+1. **Open the generated `electron-builder.json`** file in your project
+2. **Locate the 'build' section** in your old `electron.manifest.json`
+3. **Copy the contents** of the build section (not the "build" key itself) into the new `electron-builder.json`
+4. **Use Visual Studio Project Designer** to configure Electron settings through the UI
+5. **Delete the old `electron.manifest.json`** file
+
+**Alternative: Manual Configuration**
+You can also manually edit `electron-builder.json`:
+
+```json
+{
+ "linux": {
+ "target": [
+ "tar.xz"
+ ]
+ },
+ "win": {
+ "target": [
+ {
+ "target": "portable",
+ "arch": "x64"
+ }
+ ]
+ }
+}
+```
+
+## π― Testing Migration
+
+After completing the migration steps:
+
+1. **Build your project** to ensure no compilation errors
+2. **Test debugging** using the new ASP.NET-first approach
+3. **Verify packaging** works with the new configuration
+4. **Check cross-platform builds** if targeting multiple platforms
+
+## π¨ Common Migration Issues
+
+### Build Errors
+- **Node.js version**: Verify Node.js 22.x is installed and in PATH
+- **Package conflicts**: Clean NuGet cache if needed
+
+### Runtime Errors
+- **Missing electron-builder.json**: Trigger rebuild or manual NuGet restore
+- **Process termination**: Use .NET-first startup mode for better cleanup
+
+## π Next Steps
+
+- **[What's New?](What's-New.md)** - Complete overview of ElectronNET.Core features
+- **[Advanced Migration Topics](Advanced-Migration-Topics.md)** - Handle complex scenarios
+- **[Getting Started](../GettingStarted/ASP.Net.md)** - Learn about new development workflows
+
+## π‘ Migration Benefits
+
+β
**Simplified Configuration** - No more CLI tools or JSON files
+β
**Better Debugging** - Native Visual Studio experience with Hot Reload
+β
**Modern Architecture** - .NET-first process lifecycle
+β
**Cross-Platform Ready** - Build Linux apps from Windows
+β
**Future-Proof** - Flexible Electron version selection
+
+### Step 3: Update Startup Code
+
+**Update UseElectron() calls** to include the new callback parameter. This callback executes at the right moment to initialize your Electron UI.
+
+#### Modern ASP.NET Core (WebApplication)
+
+```csharp
+using ElectronNET.API;
+using ElectronNET.API.Entities;
+
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ builder.UseElectron(args, ElectronAppReady);
+
+ var app = builder.Build();
+ app.Run();
+ }
+
+ public static async Task ElectronAppReady()
+ {
+ var browserWindow = await Electron.WindowManager.CreateWindowAsync(
+ new BrowserWindowOptions { Show = false });
+
+ browserWindow.OnReadyToShow += () => browserWindow.Show();
+ }
+```
+
+#### Traditional ASP.NET Core (IWebHostBuilder)
+
+```csharp
+using ElectronNET.API;
+using ElectronNET.API.Entities;
+
+ public static void Main(string[] args)
+ {
+ WebHost.CreateDefaultBuilder(args)
+ .UseElectron(args, ElectronAppReady)
+ .UseStartup()
+ .Build()
+ .Run();
+ }
+
+ public static async Task ElectronAppReady()
+ {
+ var browserWindow = await Electron.WindowManager.CreateWindowAsync(
+ new BrowserWindowOptions { Show = false });
+
+ browserWindow.OnReadyToShow += () => browserWindow.Show();
+ }
+```
+
+
+### Step 4: Update Development Tools
+
+See [System Requirements](../GettingStarted/System-Requirements.md).
+
+### Step 5: Update Debugging Setup
+
+**Watch Feature Removal:**
+
+The old 'watch' feature is no longer supported. Instead, use the new ASP.NET-first debugging with Hot Reload:
+
+- **Old approach**: Manual process attachment and slow refresh
+- **New approach**: Native Visual Studio debugging with Hot Reload
+- **Benefits**: Faster development cycle, better debugging experience
+
+**Update Launch Settings:**
+
+Replace old watch configurations with new debugging profiles. See [Debugging](../GettingStarted/Debugging.md) for detailed setup instructions.
+
diff --git a/docs/Core/What's-New.md b/docs/Core/What's-New.md
new file mode 100644
index 0000000..7b06e1d
--- /dev/null
+++ b/docs/Core/What's-New.md
@@ -0,0 +1,131 @@
+# What's New in ElectronNET.Core
+
+## A Complete Transformation
+
+ElectronNET.Core represents a fundamental modernization of Electron.NET, addressing years of accumulated pain points while preserving full API compatibility. This isn't just an updateβit's a complete rethinking of how .NET developers build and debug cross-platform desktop applications with Electron.
+
+## Complete Build System Overhaul
+
+### From CLI Complexity to MSBuild Simplicity
+
+The most visible change is the complete elimination of the CLI tool dependency. Where developers once needed to manage complex command-line operations and JSON configuration files, everything now flows through Visual Studio's native project system.
+
+The old `electron.manifest.json` file is gone, replaced by clean MSBuild project properties that integrate seamlessly with Visual Studio's project designer. This provides not just a better development experience, but also eliminates entire categories of configuration errors that plagued earlier versions.
+
+### Intelligent Package Structure
+
+The new package architecture reflects a clearer separation of concerns:
+
+- **ElectronNET.Core** - The main package containing build logic and project system integration
+- **ElectronNET.Core.Api** - Pure API definitions for Electron integration
+- **ElectronNET.Core.AspNet** - ASP.NET-specific runtime components
+
+This modular approach allows projects to include only what they need while maintaining the flexibility to scale from simple console applications to complex web applications.
+More about the available nuget packages: [Package Description](../RelInfo/Package-Description.md).
+
+## Beyond ASP.NET: Console Application Support
+
+### A Shift in Accessibility
+
+A major new opportunity in ElectronNET.Core is the removal of the ASP.NET requirement. Developers can now build Electron solutions using simple DotNet console applications, expanding the use cases and removing a major barrier to adoption for a number of use cases.
+
+### Flexible Content Sources
+
+Console applications with ElectronNET.Core support multiple content scenarios:
+
+- **File System HTML/JS**: Serve static web content directly from the file system
+- **Remote Server Integration**: Connect to existing web servers or APIs
+- **Lightweight Architecture**: Avoid the overhead of ASP.NET when it's not needed
+- **Simplified Deployment**: Package and distribute with minimal dependencies
+
+This capability transforms ElectronNET from a web-focused framework into a versatile platform that can integrate with any HTML/JS content source, making it accessible to a much broader range of development scenarios and team structures.
+
+## Revolutionary Development Experience
+
+### Debugging Reimagined
+
+The debugging experience has been completely transformed. The new DotNet-first launch mode means developers can now debug their .NET code directly, with full access to familiar debugging tools and Hot Reload capabilities. No more attaching to processes or working around limited debugging scenarios β the development workflow now matches standard .NET development patterns.
+
+### Cross-Platform Development Without Compromises
+
+One of the most significant breakthroughs is the ability to build and debug Linux applications directly from Windows Visual Studio through WSL integration. Developers can now:
+
+- Build Linux packages while working on Windows
+- Debug Linux application behavior in real-time
+- Test cross-platform functionality without context switching
+- Deploy to Linux targets with confidence
+
+This capability eliminates the traditional barriers between Windows development environments and Linux deployment targets.
+
+### Flexible Runtime Identifier Support
+
+Runtime Identifier (RID) selection is now a first-class part of the project configuration, allowing developers to explicitly target specific platforms and architectures. The build system automatically structures output folders using standard .NET conventions (`bin\net8.0\win-x64`) instead of the ambiguous `bin\Desktop` layout, making multi-target builds clean and predictable.
+
+## Modernized Architecture
+
+### Process Lifecycle Revolution
+
+The underlying process architecture has been fundamentally redesigned. Instead of Electron launching first and managing the .NET process, ElectronNET.Core puts .NET in control. The .NET application launches first and runs Electron as a child process, providing:
+
+- Better process lifecycle management
+- More reliable application termination
+- Enhanced error handling and recovery
+- Cleaner separation between web and native concerns
+
+This architecture supports eight different launch scenarios, covering every combination of packaged/unpackaged deployment, console/ASP.NET hosting, and dotnet-first/electron-first initialization. The Electron-first launch method is still available or course.
+For more details, see: [Startup Methods](../GettingStarted/Startup-Methods.md).
+
+### Unpackaged Development Mode
+
+The new unpackaged run-mode transforms development workflows by using regular .NET builds with unpackaged Electron configurations. This approach leverages .NET's incremental build capabilities for both managed and native code, dramatically reducing rebuild times and improving the development feedback loop.
+
+## Enhanced Technical Foundation
+
+### TypeScript Integration
+
+TypeScript compilation is now fully integrated with ASP.NET tooling, providing consistent builds across different development environments. The updated toolchain uses modern TypeScript versions with ESLint configuration, eliminating the compatibility issues that previously affected custom ElectronHostHook implementations.
+
+### API Enhancements
+
+The improved splash screen handling with automatic path resolution eliminates common configuration pitfalls, while maintaining full backward compatibility with existing ElectronHostHook code.
+
+### Performance Optimizations
+
+Package sizes have been reduced by eliminating unnecessary dependencies, while build performance has improved through intelligent incremental compilation. The new architecture also minimizes startup times through optimized build and launch procedures.
+
+## Seamless Migration Path
+
+### Backward Compatibility Focus
+
+Despite the extensive changes, ElectronNET.Core maintains complete API compatibility with existing applications. The modular package structure allows for incremental adoption, and existing ElectronHostHook implementations continue to work without modification.
+
+### Clear Upgrade Journey
+
+The migration path is designed to be straightforward:
+1. Update package references to the new structure
+2. Remove the old manifest file
+3. Configure project properties through Visual Studio
+4. Adopt new debugging workflows at your own pace
+
+Further reading: [Migration Guide](Migration-Guide.md).
+
+## Future Horizons
+
+### Unlocked Possibilities
+
+This modernization removes the technical debt that was limiting Electron.NET's evolution. The flexible Electron versioning, integrated build system, and cross-platform capabilities create a foundation for:
+
+- More frequent updates and feature additions
+- Enhanced community contributions
+- Better tooling and IDE integration
+- Expanded platform support
+
+### Version Independence
+
+The removal of rigid Electron version coupling means developers can now choose the Electron version that best fits their needs, with build-time validation ensuring compatibility. This approach encourages community feedback and enables faster adoption of new Electron features.
+
+## Conclusion
+
+ElectronNET.Core represents more than just new featuresβit's a complete reimagining of what .NET + Electron development can be. By eliminating friction points, removing the ASP.NET requirement to support console applications, improving debugging experiences, and enabling true cross-platform development, it transforms Electron.NET from a challenging framework to work with into a modern, efficient platform for building cross-platform desktop applications.
+
+The changes address the core issues that were driving developers away from Electron.NET while opening new possibilities for the future. This foundation will enable more rapid innovation and better support for the growing demands of cross-platform .NET development.
diff --git a/docs/Docs.shproj b/docs/Docs.shproj
new file mode 100644
index 0000000..0da6e25
--- /dev/null
+++ b/docs/Docs.shproj
@@ -0,0 +1,25 @@
+
+
+ 06caadc7-de5b-47b4-ab2a-e9501459a2d1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/GettingStarted/ASP.Net.md b/docs/GettingStarted/ASP.Net.md
new file mode 100644
index 0000000..953a641
--- /dev/null
+++ b/docs/GettingStarted/ASP.Net.md
@@ -0,0 +1,129 @@
+
+# ASP.NET Core Setup
+
+ASP.NET Core remains the recommended approach for complex web applications with ElectronNET.Core, providing all the benefits of the ASP.NET ecosystem along with enhanced Electron integration.
+
+## π System Requirements
+
+See [System Requirements](../GettingStarted/System-Requirements.md).
+
+
+## π Quick Start
+
+### 1. Create ASP.NET Core Project
+
+#### Visual Studio
+
+Create a new ASP.NET Core Web App in Visual Studio by selecting **New Project** and choosing one of the ASP.NET Core project templates.
+
+#### From the command line
+
+```bash
+dotnet new webapp -n MyElectronWebApp
+cd MyElectronWebApp
+```
+
+### 2. Install NuGet Packages
+
+#### Visual Studio
+
+Right-click the solution and select **Manage Nuget Packages**.
+Finr and install `ElectronNET.Core` as well as `ElectronNET.Core.AspNet`.
+
+#### From the command line
+
+```powershell
+dotnet add package ElectronNET.Core
+dotnet add package ElectronNET.Core.AspNet
+```
+
+> [!Note]
+> The API package is automatically included as a dependency of `ElectronNET.Core`.
+
+
+### 3. Configure Program.cs
+
+Update your `Program.cs` to enable Electron.NET:
+
+```csharp
+using ElectronNET.API;
+using ElectronNET.API.Entities;
+
+public class Program
+{
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ builder.Services.AddRazorPages();
+
+ // Add this line to enable Electron.NET:
+ builder.UseElectron(args, ElectronAppReady);
+
+ var app = builder.Build();
+
+ if (!app.Environment.IsDevelopment())
+ {
+ app.UseExceptionHandler("/Error");
+ }
+ app.UseStaticFiles();
+
+ app.UseRouting();
+
+ app.UseAuthorization();
+
+ app.MapRazorPages();
+
+ app.Run();
+ }
+
+ public static async Task ElectronAppReady()
+ {
+ var browserWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false });
+
+ browserWindow.OnReadyToShow += () => browserWindow.Show();
+ }
+}
+```
+
+#### ASP.NET Port
+
+If you want to launch a specific URL, you can retrieve the actual ASP.NET port from the new `ElectronNetRuntime` static class, for example:
+
+```csharp
+ await browserWindow.WebContents
+ .LoadURLAsync($"http://localhost:{ElectronNetRuntime.AspNetWebPort}/mypage.html");
+```
+
+### 4. Alternative: IWebHostBuilder Setup
+
+For projects using the traditional `Startup.cs` pattern, please see "Traditional ASP.NET Core" in the [Migration Guide](../Core/Migration-Guide.md).
+
+
+### 5. Dependency Injection
+
+ElectronNET.API can be added to your DI container within the `Startup` class. All of the modules available in Electron will be added as Singletons.
+
+```csharp
+using ElectronNET.API;
+
+public void ConfigureServices(IServiceCollection services)
+{
+ services.AddElectron();
+}
+```
+
+
+## π Next Steps
+
+- **[Debugging](../Using/Debugging.md)** - Learn about ASP.NET debugging features
+- **[Package Building](../Using/Package-Building.md)** - Create distributable packages
+- **[Startup Methods](../Using/Startup-Methods.md)** - Understanding launch scenarios
+
+## π‘ Benefits of ASP.NET + Electron
+
+β
**Full Web Stack** - Use MVC, Razor Pages, Blazor, and all ASP.NET features
+β
**Hot Reload** - Edit ASP.NET code and see changes instantly
+β
**Rich Ecosystem** - Access to thousands of ASP.NET packages
+β
**Modern Development** - Latest C# features and ASP.NET patterns
+β
**Scalable Architecture** - Build complex, maintainable applications
diff --git a/docs/GettingStarted/Console-App.md b/docs/GettingStarted/Console-App.md
new file mode 100644
index 0000000..f9efe16
--- /dev/null
+++ b/docs/GettingStarted/Console-App.md
@@ -0,0 +1,166 @@
+
+
+# Console Application Setup
+
+A major benefit in ElectronNET.Core is the ability to build Electron applications using simple console applications instead of requiring ASP.NET Core. This removes a significant barrier and enables many more use cases.
+
+## π― What You Can Build
+
+Console applications with ElectronNET.Core support multiple content scenarios:
+
+- **File System HTML/JS** - Serve static web content directly from the file system
+- **Remote Server Integration** - Connect to existing web servers or APIs
+- **Lightweight Architecture** - Avoid ASP.NET overhead when not needed
+- **Simplified Deployment** - Package and distribute with minimal dependencies
+
+## π Prerequisites
+
+See [System Requirements](../GettingStarted/System-Requirements.md).
+
+
+## π Quick Start
+
+### 1. Create Console Application
+
+#### Visual Studio
+
+Create a new console application in Visual Studio by selecting **New Project** and choosing one of the project templates for console apps.
+
+#### From the command line
+
+```bash
+dotnet new console -n MyElectronApp
+cd MyElectronApp
+```
+
+### 2. Install NuGet Packages
+
+```powershell
+dotnet add package ElectronNET.Core
+```
+
+> [!Note]
+> The API package is automatically included as a dependency of `ElectronNET.Core`.
+
+### 3. Configure Project File
+
+Add the Electron.NET configuration to your `.csproj` file:
+
+```xml
+
+ Exe
+ net8.0
+ win-x64
+
+
+
+
+
+```
+
+> [!WARNING]
+> Specifying `OutputType` property is crucial in order to get the ability of WSL debugging. Especially it is not included in ASP.NET projects.
+> When you migrate from ASP.NET to a console application, be sure to add this to the project file.
+
+
+### 4. Implement Basic Structure
+
+Here's a complete console application example:
+
+```csharp
+using System;
+using System.Threading.Tasks;
+using ElectronNET.API.Entities;
+
+namespace MyElectronApp
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ var runtimeController = ElectronNetRuntime.RuntimeController;
+
+ try
+ {
+ // Start Electron runtime
+ await runtimeController.Start();
+ await runtimeController.WaitReadyTask;
+
+ // Initialize your Electron app
+ await InitializeApp();
+
+ // Wait for shutdown
+ await runtimeController.WaitStoppedTask.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ await runtimeController.Stop().ConfigureAwait(false);
+ await runtimeController.WaitStoppedTask.WaitAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
+ }
+ }
+
+ private static async Task InitializeApp()
+ {
+ // Create main window
+ var browserWindow = await Electron.WindowManager.CreateWindowAsync(
+ new BrowserWindowOptions
+ {
+ Show = false,
+ WebPreferences = new WebPreferences
+ {
+ // Add these two when using file:// URLs
+ WebSecurity = false,
+ AllowRunningInsecureContent = true,
+
+ NodeIntegration = false,
+ ContextIsolation = true
+ }
+ });
+
+ // Load your content (file system, remote URL, etc.)
+ await browserWindow.WebContents.LoadURLAsync("https://example.com");
+
+ // Show window when ready
+ browserWindow.OnReadyToShow += () => browserWindow.Show();
+ }
+}
+```
+
+## π Content Sources
+
+### File System Content
+
+Serve HTML/JS files from your project:
+
+```csharp
+// In your project root, create wwwroot/index.html
+var fileInfo = new FileInfo(Environment.ProcessPath);
+var exeFolder = fileInfo.DirectoryName;
+var htmlPath = Path.Combine(exeFolder, "wwwroot/index.html");
+var url = new Uri(htmlPath, UriKind.Absolute);
+await browserWindow.WebContents.LoadFileAsync(url.ToString());
+```
+
+### Remote Content
+
+Load content from any web server:
+
+```csharp
+await browserWindow.WebContents.LoadURLAsync("https://your-server.com/app");
+```
+
+
+## π Next Steps
+
+- **[Debugging](../Using/Debugging.md)** - Learn about debugging console applications
+- **[Package Building](../Using/Package-Building.md)** - Create distributable packages
+- **[Migration Guide](../Core/Migration-Guide.md)** - Moving from ASP.NET projects
+
+## π‘ Benefits of Console Apps
+
+β
**Simpler Architecture** - No ASP.NET complexity when not needed
+β
**Flexible Content** - Use any HTML/JS source
+β
**Faster Development** - Less overhead for simple applications
+β
**Easy Deployment** - Minimal dependencies
+β
**Better Performance** - Lighter weight than full web applications
diff --git a/docs/GettingStarted/System-Requirements.md b/docs/GettingStarted/System-Requirements.md
new file mode 100644
index 0000000..000b9b4
--- /dev/null
+++ b/docs/GettingStarted/System-Requirements.md
@@ -0,0 +1,53 @@
+
+## π System Requirements
+
+### Required Software
+
+- **.NET 8.0** or later
+- **Node.js 22.x** or later (see below)
+- **Visual Studio 2022** (recommended) or other .NET IDE
+
+### Supported Operating Systems
+
+- **Windows 10/11** (x64, ARM64)
+- **macOS 11+** (Intel, Apple Silicon)
+- **Linux** (most distributions with glibc 2.31+)
+
+> [!Note]
+> For Linux development on Windows, install [WSL2](https://docs.microsoft.com/windows/wsl/install) to build and debug Linux packages.
+> Do not forget to install NodeJS 22.x (LTS) on WSL.
+> Visual Studio will automatically install .NET when debugging on WSL. In all other cases you will need to install a matching .NET SDK on WSL as well.
+
+
+### NodeJS Installation
+
+
+ElectronNET.Core requires Node.js 22.x. Update your installation:
+
+**Windows:**
+
+1. Download from [nodejs.org](https://nodejs.org)
+2. Run the installer
+3. Verify: `node --version` should show v22.x.x
+
+**Linux:**
+
+```bash
+# Using Node Version Manager (recommended)
+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
+source ~/.bashrc
+nvm install 22
+nvm use 22
+
+# Or using package manager
+sudo apt update
+sudo apt install nodejs=22.*
+```
+
+
+## π Next Steps
+
+- **[Debugging](../Using/Debugging.md)** - Learn about ASP.NET debugging features
+- **[Package Building](../Using/Package-Building.md)** - Create distributable packages
+- **[Startup Methods](../Using/Startup-Methods.md)** - Understanding launch scenarios
+
diff --git a/docs/Home.md b/docs/Home.md
new file mode 100644
index 0000000..a338d81
--- /dev/null
+++ b/docs/Home.md
@@ -0,0 +1,59 @@
+
+
+# Electron.NET Wiki Home
+
+Welcome to the **Electron.NET Core** documentation! This wiki covers everything you need to know about building cross-platform desktop applications with ASP.NET Core and Electron.NET.
+
+## π What is Electron.NET Core?
+
+Electron.NET Core is a complete modernization of Electron.NET that eliminates the CLI tool dependency and integrates deeply with Visual Studio's MSBuild system. It transforms the development experience by providing:
+
+- **Native Visual Studio Integration** - No more CLI tools or JSON configuration files
+- **Console Application Support** - Build Electron apps with simple console applications, not just ASP.NET
+- **Cross-Platform Development** - Build and debug Linux applications from Windows via WSL
+- **Enhanced Debugging** - ASP.NET-first debugging with Hot Reload support
+- **Flexible Architecture** - Choose any Electron version and target multiple platforms
+
+## π Documentation Sections
+
+### [Core Documentation](Core/What's-New.md)
+- **[What's New?](Core/What's-New.md)** - Complete overview of ElectronNET.Core features and improvements
+- **[Migration Guide](Core/Migration-Guide.md)** - Step-by-step migration from previous versions
+- **[Advanced Migration Topics](Core/Advanced-Migration-Topics.md)** - Technical details for complex scenarios
+
+### [Getting Started](GettingStarted/ASP.Net.md)
+- **[ASP.NET Applications](GettingStarted/ASP.Net.md)** - Build Electron apps with ASP.NET Core
+- **[Console Applications](GettingStarted/Console-App.md)** - Use console apps for file system or remote content
+- **[Startup Methods](GettingStarted/Startup-Methods.md)** - Understanding different launch scenarios
+- **[Debugging](GettingStarted/Debugging.md)** - Debug Electron apps effectively in Visual Studio
+- **[Package Building](GettingStarted/Package-Building.md)** - Create distributable packages
+
+### [Release Information](RelInfo/Package-Description.md)
+- **[Package Description](RelInfo/Package-Description.md)** - Understanding the three NuGet packages
+- **[Changelog](../Changelog.md)** - Complete list of changes and improvements
+
+## π System Requirements
+
+See [System Requirements](GettingStarted/System-Requirements.md).
+
+## π‘ Key Benefits
+
+β
**No CLI Tools Required** - Everything works through Visual Studio
+β
**Console App Support** - Use any HTML/JS source, not just ASP.NET
+β
**True Cross-Platform** - Build Linux apps from Windows
+β
**Modern Debugging** - Hot Reload and ASP.NET-first debugging
+β
**Flexible Packaging** - Choose any Electron version
+β
**MSBuild Integration** - Leverages .NET's build system
+
+## π¦ Getting Started
+
+New to ElectronNET.Core? Start here:
+
+1. **[ASP.NET Setup](GettingStarted/ASP.Net.md)** - Traditional web application approach
+2. **[Console App Setup](GettingStarted/Console-App.md)** - Lightweight console application approach
+3. **[Migration Guide](Core/Migration-Guide.md)** - Moving from previous versions
+
+## π€ Contributing
+
+Found an issue or want to improve the documentation? Contributions are welcome!
+The wiki is auto-generated from the `/docs` folder in the [GitHub repository](https://github.com/ElectronNET/Electron.NET).
diff --git a/docs/RelInfo/Package-Description.md b/docs/RelInfo/Package-Description.md
new file mode 100644
index 0000000..eaf049e
--- /dev/null
+++ b/docs/RelInfo/Package-Description.md
@@ -0,0 +1,116 @@
+# Package Description
+
+ElectronNET.Core consists of three specialized NuGet packages designed for different development scenarios and project architectures.
+
+## π¦ Package Overview
+
+### ElectronNET.Core (Main Package)
+**Primary package for Electron.NET applications**
+
+- **Build system integration** - MSBuild targets and tasks for Electron
+- **Project system extensions** - Visual Studio designer integration
+- **Runtime orchestration** - Process lifecycle management
+- **Automatic configuration** - Generates electron-builder.json and package.json
+- **Includes ElectronNET.Core.Api** as a dependency
+
+**When to use:**
+- Main application projects (startup projects)
+- Projects that need full Electron.NET functionality
+- Applications requiring build-time Electron configuration
+
+### ElectronNET.Core.Api (API Package)
+**Pure API definitions without build integration**
+
+- **Electron API wrappers** - Complete Electron API surface
+- **Type definitions** - Full TypeScript-style IntelliSense
+- **No build dependencies** - Clean API-only package
+- **Multi-platform support** - Windows, macOS, Linux
+
+**When to use:**
+- Class library projects that interact with Electron APIs
+- Projects that only need API access without build integration
+- Multi-project solutions where only the startup project needs build integration
+
+### ElectronNET.Core.AspNet (ASP.NET Integration)
+**ASP.NET-specific runtime components**
+
+- **ASP.NET middleware** - UseElectron() extension methods
+- **WebHost integration** - Seamless ASP.NET + Electron hosting
+- **Hot Reload support** - Enhanced debugging experience
+- **Web-specific optimizations** - ASP.NET tailored performance
+
+**When to use:**
+- ASP.NET Core projects building Electron applications
+- Applications requiring web server functionality
+- Projects using MVC, Razor Pages, or Blazor
+
+## π Architecture Benefits
+
+### Separation of Concerns
+- **Build logic separate from API** - Clean dependency management
+- **Optional ASP.NET integration** - Use only what you need
+- **Multi-project friendly** - Share APIs across projects without build conflicts
+
+### Project Scenarios
+
+**Single Project (ASP.NET):**
+```xml
+
+
+
+
+```
+
+**Single Project (Console):**
+```xml
+
+
+
+```
+
+**Multi-Project Solution (ASP.NET):**
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+**Multi-Project Solution (Console):**
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+## π Dependency Chain
+
+```
+ElectronNET.Core.AspNet β ElectronNET.Core.Api
+
+ElectronNET.Core β ElectronNET.Core.Api
+```
+
+- **ElectronNET.Core.AspNet** depends on ElectronNET.Core.Api
+- **ElectronNET.Core** depends on ElectronNET.Core.Api
+- **ElectronNET.Core.Api** has no dependencies
+
+## π‘ Recommendations
+
+β
**Start with ElectronNET.Core** for new projects
+β
**Add ElectronNET.Core.AspNet** only for ASP.NET applications
+β
**Use ElectronNET.Core.Api** for class libraries and API-only scenarios
+β
**Multi-project solutions** benefit from the modular architecture
+
diff --git a/docs/Using/Configuration.md b/docs/Using/Configuration.md
new file mode 100644
index 0000000..8685a15
--- /dev/null
+++ b/docs/Using/Configuration.md
@@ -0,0 +1,94 @@
+
+# Project Configuration
+
+
+## π§ Visual Studio App Designer
+
+Electron.NET provides close integration via the Visual Studio Project System and MSBuild. After adding the ElectronNET.Core package, you will see this in the project configuration page after double-click on the 'Properties' folder or right-click on the project and choosing 'Properties':
+
+
+
+
+
+
+
+## Project File Settings
+
+The same settings can be configured manually by editing the MSBuild properties in your `.csproj` file.
+These are the current default values when you don't make any changes:
+
+```xml
+
+ 30.4.0
+ 26.0
+ win-x64
+ true
+
+
+ $(MSBuildProjectName.Replace(".", "-").ToLower())
+ electron-builder.json
+ $(MSBuildProjectName)
+
+```
+
+### Relation to package.json
+
+ElectronNET.Core does not work with an `electron-manifest.json` file anymore.
+Since electron builder still expects a `package.json` file to exist, ElectronNET.Core is creating this under the hood automatically during build. For reference, here's the package.json template file that is being used, so you can see how the MSBuild properties are being mapped to `package.json` data:
+
+```json
+{
+ "name": "$(PackageId)",
+ "productName": "$(ElectronTitle)",
+ "build": {
+ "appId": "$(PackageId)",
+ "linux": {
+ "desktop": {
+ "entry": { "Name": "$(Title)" }
+ },
+ "executableName": "$(PackageId)"
+ },
+ "deb": {
+ "desktop": {
+ "entry": { "Name": "$(Title)" }
+ }
+ }
+ },
+ "description": "$(Description)",
+ "version": "$(Version)",
+ "main": "main.js",
+ "author": {
+ "name": "$(Company)"
+ },
+ "license": "$(License)",
+ "executable": "$(TargetName)",
+ "singleInstance": "$(ElectronSingleInstance)",
+ "homepage": "$(ProjectUrl)",
+ "splashscreen": {
+ "imageFile": "$(ElectronSplashScreen)"
+ }
+}
+```
+
+### Node.js Integration
+
+Electron.NET requires Node.js integration to be enabled for IPC to function. If you are not using the IPC functionality you can disable Node.js integration like so:
+
+```csharp
+WebPreferences wp = new WebPreferences();
+wp.NodeIntegration = false;
+BrowserWindowOptions browserWindowOptions = new BrowserWindowOptions
+{
+ WebPreferences = wp
+};
+
+```
+
+
+
+## π Next Steps
+
+- **[Startup Methods](Startup-Methods.md)** - Understanding launch scenarios
+- **[Debugging](../Using/Debugging.md)** - Learn about ASP.NET debugging features
+- **[Package Building](Package-Building.md)** - Create distributable packages
+
diff --git a/docs/Using/Debugging.md b/docs/Using/Debugging.md
new file mode 100644
index 0000000..3c7f5c4
--- /dev/null
+++ b/docs/Using/Debugging.md
@@ -0,0 +1,242 @@
+# Debugging
+
+ElectronNET.Core transforms the debugging experience by providing native Visual Studio integration with multiple debugging modes. No more complex setup or manual process attachmentβdebugging now works as expected for .NET developers.
+
+## π― Debugging Modes
+
+ElectronNET.Core supports three main debugging approaches, all configured through Visual Studio's launch profiles:
+
+### 1. ASP.NET-First Debugging (Recommended)
+
+Debug your .NET code directly with full Hot Reload support:
+
+```json
+{
+ "profiles": {
+ "ASP.Net (unpackaged)": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:8001/"
+ }
+ }
+}
+```
+
+**Benefits:**
+- β
Full C# debugging with breakpoints
+- β
Hot Reload for ASP.NET code
+- β
Edit-and-continue functionality
+- β
Native Visual Studio debugging experience
+
+### 2. Electron-First Debugging
+
+Debug the Electron process when you need to inspect native Electron APIs:
+
+```json
+{
+ "profiles": {
+ "Electron (unpackaged)": {
+ "commandName": "Executable",
+ "executablePath": "node",
+ "commandLineArgs": "node_modules/electron/cli.js main.js -unpackedelectron",
+ "workingDirectory": "$(TargetDir).electron",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
+```
+
+**Benefits:**
+- β
Debug Electron main process
+- β
Inspect native Electron APIs
+- β
Node.js debugging capabilities
+
+### 3. Cross-Platform WSL Debugging
+
+Debug Linux builds directly from Windows Visual Studio:
+
+```json
+{
+ "profiles": {
+ "WSL": {
+ "commandName": "WSL2",
+ "launchUrl": "http://localhost:8001/",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "ASPNETCORE_URLS": "http://localhost:8001/"
+ },
+ "distributionName": ""
+ }
+ }
+}
+```
+
+**Benefits:**
+- β
Debug Linux applications from Windows
+- β
Test Linux-specific behavior
+- β
Validate cross-platform compatibility
+
+## π§ Setup Instructions
+
+### 1. Configure Launch Settings
+
+Add the debugging profiles to `Properties/launchSettings.json`:
+
+```json
+{
+ "profiles": {
+ "ASP.Net (unpackaged)": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:8001/"
+ },
+ "Electron (unpackaged)": {
+ "commandName": "Executable",
+ "executablePath": "node",
+ "commandLineArgs": "node_modules/electron/cli.js main.js -unpackedelectron",
+ "workingDirectory": "$(TargetDir).electron",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "WSL": {
+ "commandName": "WSL2",
+ "launchUrl": "http://localhost:8001/",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "ASPNETCORE_URLS": "http://localhost:8001/"
+ },
+ "distributionName": ""
+ }
+ }
+}
+```
+
+### 2. Switch Runtime Identifiers
+
+When switching between Windows and WSL debugging:
+
+1. **Right-click your project** in Solution Explorer
+2. Select **Properties**
+3. Adjust the Runtime Identifier
+
+
+Without Visual Studio:
+
+1. **Edit** the .csproj file
+2. **Update the RuntimeIdentifier**:
+
+```xml
+
+win-x64
+
+
+linux-x64
+```
+
+### 3. Enable WSL Debugging
+
+For WSL debugging, ensure:
+
+- **WSL2 is installed** and configured
+- **Linux distribution** is set in the launch profile
+- **Project targets Linux RID** for WSL debugging
+
+## π Debugging Workflow
+
+### ASP.NET-First Debugging (Default)
+
+1. **Select "ASP.Net (unpackaged)"** profile in Visual Studio
+2. **Press F5** to start debugging
+3. **Set breakpoints** in your C# code
+4. **Use Hot Reload** to edit ASP.NET code during runtime
+5. **Stop debugging** when finished
+
+### Electron Process Debugging
+
+1. **Select "Electron (unpackaged)"** profile
+2. **Press F5** to start debugging
+3. **Attach to Electron process** if needed
+4. **Debug Node.js and Electron APIs**
+
+### Cross-Platform Debugging
+
+1. **Set RuntimeIdentifier** to `linux-x64`
+2. **Select "WSL"** profile
+3. **Press F5** to debug in WSL
+4. **Test Linux-specific behavior**
+
+## π Debugging Tips
+
+### Hot Reload
+
+- **Works with ASP.NET-first debugging**
+- **Edit Razor views, controllers, and pages**
+- **See changes instantly** without restart
+- **Preserves application state**
+
+### Breakpoint Debugging
+
+```csharp
+// Set breakpoints here
+public async Task Index()
+{
+ var data = await GetData(); // β Breakpoint
+ return View(data);
+}
+```
+
+### Process Management
+
+- **ASP.NET-first mode** automatically manages Electron process lifecycle
+- **Proper cleanup** on debugging session end
+- **No manual process killing** required
+
+## π Troubleshooting
+
+### Common Issues
+
+**"Electron process not found"**
+- Ensure Node.js 22.x is installed
+- Check that packages are restored (`dotnet restore`)
+- Verify RuntimeIdentifier matches your target platform
+
+**"WSL debugging fails"**
+- Install and configure WSL2
+- Ensure Linux distribution is properly set up
+- Check that project targets correct RID
+
+**"Hot Reload not working"**
+- Use ASP.NET-first debugging profile
+- Ensure ASPNETCORE_ENVIRONMENT=Development
+- Check for compilation errors
+
+## π¨ Visual Debugging
+
+*Placeholder for image showing Visual Studio debugging interface with Electron.NET*
+
+The debugging interface provides familiar Visual Studio tools:
+- **Locals and Watch windows** for variable inspection
+- **Call Stack** for method call tracing
+- **Immediate Window** for runtime evaluation
+- **Hot Reload** indicator for edit-and-continue
+
+## π Next Steps
+
+- **[Startup Methods](Startup-Methods.md)** - Understanding different launch scenarios
+- **[Package Building](Package-Building.md)** - Debug packaged applications
+- **[Migration Guide](../Core/Migration-Guide.md)** - Moving from old debugging workflows
+
+## π‘ Benefits
+
+β
**Native Visual Studio Experience** - No complex setup or manual attachment
+β
**Hot Reload Support** - Edit ASP.NET code during debugging
+β
**Cross-Platform Debugging** - Debug Linux apps from Windows
+β
**Multiple Debugging Modes** - Choose the right approach for your needs
+β
**Process Lifecycle Management** - Automatic cleanup and proper termination
diff --git a/docs/Using/Package-Building.md b/docs/Using/Package-Building.md
new file mode 100644
index 0000000..110c850
--- /dev/null
+++ b/docs/Using/Package-Building.md
@@ -0,0 +1,145 @@
+# Package Building
+
+ElectronNET.Core integrates with Visual Studio's publishing system to create distributable Electron packages using electron-builder. The process leverages .NET's build system while automatically generating the necessary Electron configuration files.
+
+## π― Publishing Overview
+
+The publishing process differs slightly between ASP.NET and console applications:
+
+- **ASP.NET Apps** - Use folder publishing with SelfContained=true
+- **Console Apps** - Use folder publishing with SelfContained=false
+
+
+## π Publishing Process
+
+### Step 1: Create Publish Profiles
+
+Add publish profiles to `Properties/PublishProfiles/`:
+
+#### ASP.NET Application Profile (Windows)
+
+**win-x64.pubxml:**
+
+```xml
+
+
+
+ Release
+ Any CPU
+ publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
+ FileSystem
+ net8.0
+ win-x64
+ true
+ false
+
+
+```
+
+#### ASP.NET Application Profile (Linux)
+
+**linux-x64.pubxml:**
+
+```xml
+
+
+
+ Release
+ Any CPU
+ publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
+ FileSystem
+ net8.0
+ linux-x64
+ true
+ false
+
+
+```
+
+#### Console Application Profile (Windows)
+
+**win-x64.pubxml:**
+
+```xml
+
+
+
+ Release
+ Any CPU
+ publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
+ FileSystem
+ net8.0
+ win-x64
+ false
+ false
+ false
+
+
+```
+
+#### Console Application Profile (Linux)
+
+**linux-x64.pubxml:**
+
+```xml
+
+
+
+ Release
+ Any CPU
+ publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
+ FileSystem
+ net8.0
+ linux-x64
+ false
+ false
+
+
+```
+
+### Step 2: Configure Electron Builder
+
+ElectronNET.Core automatically adds a default `electron-builder.json` file under `Properties\electron-builder.json`.
+Please see here for details of the available configuration options: https://www.electron.build/.
+
+
+### Step 3: Publish from Visual Studio
+
+1. **Right-click your project** in Solution Explorer
+2. **Select "Publish"**
+4. **Select your publish profile** (Windows/Linux)
+5. **Click "Publish"**
+
+The publish process will:
+- Build your .NET application
+- Copy all files as needed
+- Install npm dependencies
+- Run electron-builder
+
+> [!NOTE]
+> When running publish for a Linux configuration on Windows, Electron.NET will automatically use WSL for the platform-specific steps.
+
+**After publishing**, the final results will be in
+
+`publish\Release\netx.0\xxx-xxx\`
+
+
+## MacOS
+
+> [!NOTE]
+> 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.
+
+
+## π Next Steps
+
+- **[Startup Methods](Startup-Methods.md)** - Understanding different launch modes for packaged apps
+- **[Debugging](Debugging.md)** - Debug packaged applications
+- **[Migration Guide](../Core/Migration-Guide.md)** - Update existing projects for new publishing
+
+## π‘ Benefits
+
+β
**Native VS Integration** - Use familiar publish workflows
+β
**Cross-Platform Building** - Build Linux packages from Windows
+β
**Automatic Configuration** - No manual electron-builder setup
+β
**Multiple Package Types** - NSIS, AppImage, DMG, etc.
+β
**CI/CD Ready** - Easy integration with build pipelines
diff --git a/docs/Using/Startup-Methods.md b/docs/Using/Startup-Methods.md
new file mode 100644
index 0000000..1c68483
--- /dev/null
+++ b/docs/Using/Startup-Methods.md
@@ -0,0 +1,246 @@
+# Startup Methods
+
+ElectronNET.Core supports multiple startup methods to handle different development and deployment scenarios. The framework automatically detects the appropriate mode based on command-line flags and environment.
+
+## π― Startup Scenarios
+
+The framework supports **8 different launch scenarios** covering every combination of:
+
+- **Packaged vs Unpackaged** deployment
+- **Console vs ASP.NET** application types
+- **Dotnet-first vs Electron-first** initialization
+
+## π Command-Line Flags
+
+### Unpackaged Debugging Modes
+
+#### **`-unpackedelectron`** - Electron-first debugging
+```bash
+# Launch Electron first, which then starts .NET
+node node_modules/electron/cli.js main.js -unpackedelectron
+```
+
+#### **`-unpackeddotnet`** - .NET-first debugging
+```bash
+# Launch .NET first, which then starts Electron
+dotnet run -unpackeddotnet
+```
+
+### Packaged Deployment Modes
+
+#### **`-dotnetpacked`** - .NET-first packaged execution
+```bash
+# Run packaged app with .NET starting first
+MyApp.exe -dotnetpacked
+```
+
+#### **No flags** - Electron-first packaged execution (default)
+```bash
+# Run packaged app with Electron starting first
+MyApp.exe
+```
+
+## π Startup Method Details
+
+### 1. Unpackaged + Electron-First (Development)
+- **Use Case**: Debug Electron main process and Node.js code
+- **Command**: `-unpackedelectron` flag
+- **Process Flow**:
+ 1. Electron starts first
+ 2. Electron launches .NET process
+ 3. .NET connects back to Electron
+ 4. Application runs with Electron in control
+
+### 2. Unpackaged + .NET-First (Development)
+- **Use Case**: Debug ASP.NET/C# code with Hot Reload
+- **Command**: `-unpackeddotnet` flag
+- **Process Flow**:
+ 1. .NET application starts first
+ 2. .NET launches Electron process
+ 3. Electron connects back to .NET
+ 4. Application runs with .NET in control
+
+### 3. Packaged + .NET-First (Production)
+- **Use Case**: Deployed application with .NET controlling lifecycle
+- **Command**: `-dotnetpacked` flag
+- **Process Flow**:
+ 1. .NET executable starts first
+ 2. .NET launches Electron from packaged files
+ 3. Electron loads from app.asar or extracted files
+ 4. .NET maintains process control
+
+### 4. Packaged + Electron-First (Production)
+- **Use Case**: Traditional Electron app behavior
+- **Command**: No special flags
+- **Process Flow**:
+ 1. Electron executable starts first
+ 2. Electron launches .NET from packaged files
+ 3. .NET runs from Electron's process context
+ 4. Electron maintains UI control
+
+## π§ Configuration Examples
+
+### ASP.NET Application Startup
+
+```csharp
+// Program.cs
+var builder = WebApplication.CreateBuilder(args);
+
+// Configure for different startup modes
+builder.WebHost.UseElectron(args, async () =>
+{
+ var browserWindow = await Electron.WindowManager.CreateWindowAsync(
+ new BrowserWindowOptions { Show = false });
+
+ await browserWindow.WebContents.LoadURLAsync("http://localhost:8001");
+ browserWindow.OnReadyToShow += () => browserWindow.Show();
+});
+
+var app = builder.Build();
+app.Run();
+```
+
+### Console Application Startup
+
+```csharp
+// Program.cs
+public static async Task Main(string[] args)
+{
+ var runtimeController = ElectronNetRuntime.RuntimeController;
+
+ await runtimeController.Start();
+ await runtimeController.WaitReadyTask;
+
+ await InitializeApplication();
+
+ await runtimeController.WaitStoppedTask;
+}
+```
+
+## π¨ Visual Process Flow
+
+
+
+
+The image above illustrates how each combination of deployment type, application type, and initialization order affects the process lifecycle.
+
+## π Development Workflows
+
+### Debugging Workflow
+
+**ASP.NET-First Debugging** (Recommended)
+```json
+// launchSettings.json
+{
+ "ASP.Net (unpackaged)": {
+ "commandName": "Project",
+ "commandLineArgs": "-unpackeddotnet"
+ }
+}
+```
+
+**Electron-First Debugging**
+```json
+// launchSettings.json
+{
+ "Electron (unpackaged)": {
+ "commandName": "Executable",
+ "executablePath": "node",
+ "commandLineArgs": "node_modules/electron/cli.js main.js -unpackedelectron"
+ }
+}
+```
+
+### Production Deployment
+
+**Dotnet-First Deployment**
+
+```bash
+# Build and package
+dotnet publish -c Release -r win-x64
+cd publish\Release\net8.0\win-x64
+npm install
+npx electron-builder
+
+# Run with dotnet-first
+MyApp.exe -dotnetpacked
+```
+
+**Electron-First Deployment** (Default)
+
+```bash
+# Run packaged application (no special flags needed)
+MyApp.exe
+```
+
+## π Process Lifecycle Management
+
+### Automatic Cleanup
+
+ElectronNET.Core automatically manages process lifecycle:
+
+- **Graceful shutdown** when main window is closed
+- **Proper cleanup** of child processes
+- **Error handling** for process failures
+- **Cross-platform compatibility** for process management
+
+### Manual Control
+
+Access runtime controller for advanced scenarios:
+
+```csharp
+var runtime = ElectronNetRuntime.RuntimeController;
+
+// Wait for Electron to be ready
+await runtime.WaitReadyTask;
+
+// Stop Electron runtime
+await runtime.Stop();
+await runtime.WaitStoppedTask;
+```
+
+## π Troubleshooting
+
+### Common Startup Issues
+
+**"Electron process not found"**
+- Ensure Node.js 22.x is installed
+- Check that .NET build succeeded
+- Verify RuntimeIdentifier is set correctly
+
+**"Port conflicts"**
+- Use different ports for different startup modes
+- Check that no other instances are running
+- Verify firewall settings
+
+**"Process won't terminate"**
+- Use dotnet-first mode for better cleanup
+- Check for unhandled exceptions
+- Verify all windows are properly closed
+
+## π‘ Best Practices
+
+### Choose the Right Mode
+
+- **Development**: Use .NET-first for C# debugging, Electron-first for Node.js debugging
+- **Production**: Use .NET-first for better process control, Electron-first for traditional behavior
+- **Cross-platform**: Use .NET-first for consistent behavior across platforms
+
+### Environment Configuration
+
+```xml
+
+
+ Production
+
+```
+
+## π Next Steps
+
+- **[Debugging](Debugging.md)** - Debug different startup modes
+- **[Package Building](Package-Building.md)** - Package for different deployment scenarios
+- **[Migration Guide](../Core/Migration-Guide.md)** - Update existing apps for new startup methods
+
+## π― Summary
+
+The flexible startup system ensures ElectronNET.Core works optimally in every scenario while providing the control and debugging experience .NET developers expect. Choose the appropriate mode based on your development workflow and deployment requirements.
diff --git a/docs/_Footer.md b/docs/_Footer.md
new file mode 100644
index 0000000..1cde9a4
--- /dev/null
+++ b/docs/_Footer.md
@@ -0,0 +1,2 @@
+
+Want to contribute to this documentation? Please fork and create a PR! The Wiki is autogenerated from the /docs content in the repository.
\ No newline at end of file
diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md
new file mode 100644
index 0000000..4f1db54
--- /dev/null
+++ b/docs/_Sidebar.md
@@ -0,0 +1,50 @@
+
+
+# Wiki
+
+- [Home](Home.md)
+- [About this Project](About.md)
+
+# Electron.NET Core
+
+- [What's new?](Core/What's-New.md)
+- [Migration Guide](Core/Migration-Guide.md)
+- [Advanced Migration](Core/Advanced-Migration-Topics.md)
+
+# Getting Started
+
+- [System Requirements](GettingStarted/System-Requirements.md)
+- [With ASP.Net](GettingStarted/ASP.Net.md)
+- [With a Console App](GettingStarted/Console-App.md)
+
+# Using Electron.NET
+
+- [Configuration](Using/Configuration.md)
+- [Startup-Methods](Using/Startup-Methods.md)
+- [Debugging](Using/Debugging.md)
+- [Package Building](Using/Package-Building.md)
+
+# API Reference
+
+- [API Overview](API/Overview.md)
+- [Electron.App](API/App.md)
+- [Electron.Dialog](API/Dialog.md)
+- [Electron.Menu](API/Menu.md)
+- [Electron.WindowManager](API/WindowManager.md)
+- [Electron.Shell](API/Shell.md)
+- [Electron.Screen](API/Screen.md)
+- [Electron.Notification](API/Notification.md)
+- [Electron.Tray](API/Tray.md)
+- [Electron.Clipboard](API/Clipboard.md)
+- [Electron.Dock](API/Dock.md)
+- [Electron.HostHook](API/HostHook.md)
+- [Electron.IpcMain](API/IpcMain.md)
+- [Electron.GlobalShortcut](API/GlobalShortcut.md)
+- [Electron.AutoUpdater](API/AutoUpdater.md)
+- [Electron.NativeTheme](API/NativeTheme.md)
+- [Electron.PowerMonitor](API/PowerMonitor.md)
+
+# Release Information
+
+- [Package Description](RelInfo/Package-Description.md)
+- [Changelog](RelInfo/Changelog.md)
diff --git a/docs/images/app_designer1.png b/docs/images/app_designer1.png
new file mode 100644
index 0000000..1df2c1d
Binary files /dev/null and b/docs/images/app_designer1.png differ
diff --git a/docs/images/app_designer2.png b/docs/images/app_designer2.png
new file mode 100644
index 0000000..ed93945
Binary files /dev/null and b/docs/images/app_designer2.png differ
diff --git a/docs/images/startup_modes.png b/docs/images/startup_modes.png
new file mode 100644
index 0000000..e05fdf9
Binary files /dev/null and b/docs/images/startup_modes.png differ
diff --git a/docs/md-styles.css b/docs/md-styles.css
new file mode 100644
index 0000000..a819f2f
--- /dev/null
+++ b/docs/md-styles.css
@@ -0,0 +1,7857 @@
+/*!
+ * Bootstrap v3.4.1 (https://getbootstrap.com/)
+ * Copyright 2011-2019 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+html {
+ font-family: sans-serif;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%
+}
+
+article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary {
+ display: block
+}
+
+audio, canvas, progress, video {
+ display: inline-block;
+ vertical-align: baseline
+}
+
+ audio:not([controls]) {
+ display: none;
+ height: 0
+ }
+
+[hidden], template {
+ display: none
+}
+
+a:active, a:hover {
+ outline: 0
+}
+
+abbr[title] {
+ border-bottom: none;
+ text-decoration: underline;
+ -webkit-text-decoration: underline dotted;
+ -moz-text-decoration: underline dotted;
+ text-decoration: underline dotted
+}
+
+b, optgroup, strong {
+ font-weight: 700
+}
+
+dfn {
+ font-style: italic
+}
+
+h1 {
+ margin: .67em 0
+}
+
+mark {
+ background: #ff0;
+ color: #000
+}
+
+sub, sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline
+}
+
+sup {
+ top: -.5em
+}
+
+sub {
+ bottom: -.25em
+}
+
+img {
+ border: 0;
+ vertical-align: middle
+}
+
+svg:not(:root) {
+ overflow: hidden
+}
+
+hr {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0
+}
+
+code, kbd, pre, samp {
+ font-size: 1em
+}
+
+button, input, optgroup, select, textarea {
+ color: inherit;
+ font: inherit;
+ margin: 0
+}
+
+button {
+ overflow: visible
+}
+
+button, select {
+ text-transform: none
+}
+
+button, html input[type=button], input[type=reset], input[type=submit] {
+ -webkit-appearance: button;
+ cursor: pointer
+}
+
+ button[disabled], html input[disabled] {
+ cursor: default
+ }
+
+ button::-moz-focus-inner, input::-moz-focus-inner {
+ border: 0;
+ padding: 0
+ }
+
+input {
+ line-height: normal
+}
+
+ input[type=checkbox], input[type=radio] {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ padding: 0
+ }
+
+ input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button {
+ height: auto
+ }
+
+ input[type=search] {
+ -webkit-appearance: textfield;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box
+ }
+
+ input[type=search]::-webkit-search-cancel-button, input[type=search]::-webkit-search-decoration {
+ -webkit-appearance: none
+ }
+
+textarea {
+ overflow: auto
+}
+
+td, th {
+ padding: 0
+}
+/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
+@media print {
+ *, :after, :before {
+ color: #000 !important;
+ text-shadow: none !important;
+ background: 0 0 !important;
+ -webkit-box-shadow: none !important;
+ box-shadow: none !important
+ }
+
+ a, a:visited {
+ text-decoration: underline
+ }
+
+ a[href]:after {
+ content: " (" attr(href) ")"
+ }
+
+ abbr[title]:after {
+ content: " (" attr(title) ")"
+ }
+
+ a[href^="#"]:after, a[href^="javascript:"]:after {
+ content: ""
+ }
+
+ blockquote, pre {
+ border: 1px solid #999;
+ page-break-inside: avoid
+ }
+
+ thead {
+ display: table-header-group
+ }
+
+ img, tr {
+ page-break-inside: avoid
+ }
+
+ img {
+ max-width: 100% !important
+ }
+
+ h2, h3, p {
+ orphans: 3;
+ widows: 3
+ }
+
+ h2, h3 {
+ page-break-after: avoid
+ }
+
+ .navbar {
+ display: none
+ }
+
+ .btn > .caret, .dropup > .btn > .caret {
+ border-top-color: #000 !important
+ }
+
+ .label {
+ border: 1px solid #000
+ }
+
+ .table {
+ border-collapse: collapse !important
+ }
+
+ .table td, .table th {
+ background-color: #fff !important
+ }
+
+ .table-bordered td, .table-bordered th {
+ border: 1px solid #ddd !important
+ }
+}
+
+@font-face {
+ font-family: "Glyphicons Halflings";
+ src: url("../fonts/glyphicons-halflings-regular.eot");
+ src: url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("../fonts/glyphicons-halflings-regular.woff") format("woff"),url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")
+}
+
+.glyphicon {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ font-family: "Glyphicons Halflings";
+ font-style: normal;
+ font-weight: 400;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale
+}
+
+.glyphicon-asterisk:before {
+ content: "\002a"
+}
+
+.glyphicon-plus:before {
+ content: "\002b"
+}
+
+.glyphicon-eur:before, .glyphicon-euro:before {
+ content: "\20ac"
+}
+
+.glyphicon-minus:before {
+ content: "\2212"
+}
+
+.glyphicon-cloud:before {
+ content: "\2601"
+}
+
+.glyphicon-envelope:before {
+ content: "\2709"
+}
+
+.glyphicon-pencil:before {
+ content: "\270f"
+}
+
+.glyphicon-glass:before {
+ content: "\e001"
+}
+
+.glyphicon-music:before {
+ content: "\e002"
+}
+
+.glyphicon-search:before {
+ content: "\e003"
+}
+
+.glyphicon-heart:before {
+ content: "\e005"
+}
+
+.glyphicon-star:before {
+ content: "\e006"
+}
+
+.glyphicon-star-empty:before {
+ content: "\e007"
+}
+
+.glyphicon-user:before {
+ content: "\e008"
+}
+
+.glyphicon-film:before {
+ content: "\e009"
+}
+
+.glyphicon-th-large:before {
+ content: "\e010"
+}
+
+.glyphicon-th:before {
+ content: "\e011"
+}
+
+.glyphicon-th-list:before {
+ content: "\e012"
+}
+
+.glyphicon-ok:before {
+ content: "\e013"
+}
+
+.glyphicon-remove:before {
+ content: "\e014"
+}
+
+.glyphicon-zoom-in:before {
+ content: "\e015"
+}
+
+.glyphicon-zoom-out:before {
+ content: "\e016"
+}
+
+.glyphicon-off:before {
+ content: "\e017"
+}
+
+.glyphicon-signal:before {
+ content: "\e018"
+}
+
+.glyphicon-cog:before {
+ content: "\e019"
+}
+
+.glyphicon-trash:before {
+ content: "\e020"
+}
+
+.glyphicon-home:before {
+ content: "\e021"
+}
+
+.glyphicon-file:before {
+ content: "\e022"
+}
+
+.glyphicon-time:before {
+ content: "\e023"
+}
+
+.glyphicon-road:before {
+ content: "\e024"
+}
+
+.glyphicon-download-alt:before {
+ content: "\e025"
+}
+
+.glyphicon-download:before {
+ content: "\e026"
+}
+
+.glyphicon-upload:before {
+ content: "\e027"
+}
+
+.glyphicon-inbox:before {
+ content: "\e028"
+}
+
+.glyphicon-play-circle:before {
+ content: "\e029"
+}
+
+.glyphicon-repeat:before {
+ content: "\e030"
+}
+
+.glyphicon-refresh:before {
+ content: "\e031"
+}
+
+.glyphicon-list-alt:before {
+ content: "\e032"
+}
+
+.glyphicon-lock:before {
+ content: "\e033"
+}
+
+.glyphicon-flag:before {
+ content: "\e034"
+}
+
+.glyphicon-headphones:before {
+ content: "\e035"
+}
+
+.glyphicon-volume-off:before {
+ content: "\e036"
+}
+
+.glyphicon-volume-down:before {
+ content: "\e037"
+}
+
+.glyphicon-volume-up:before {
+ content: "\e038"
+}
+
+.glyphicon-qrcode:before {
+ content: "\e039"
+}
+
+.glyphicon-barcode:before {
+ content: "\e040"
+}
+
+.glyphicon-tag:before {
+ content: "\e041"
+}
+
+.glyphicon-tags:before {
+ content: "\e042"
+}
+
+.glyphicon-book:before {
+ content: "\e043"
+}
+
+.glyphicon-bookmark:before {
+ content: "\e044"
+}
+
+.glyphicon-print:before {
+ content: "\e045"
+}
+
+.glyphicon-camera:before {
+ content: "\e046"
+}
+
+.glyphicon-font:before {
+ content: "\e047"
+}
+
+.glyphicon-bold:before {
+ content: "\e048"
+}
+
+.glyphicon-italic:before {
+ content: "\e049"
+}
+
+.glyphicon-text-height:before {
+ content: "\e050"
+}
+
+.glyphicon-text-width:before {
+ content: "\e051"
+}
+
+.glyphicon-align-left:before {
+ content: "\e052"
+}
+
+.glyphicon-align-center:before {
+ content: "\e053"
+}
+
+.glyphicon-align-right:before {
+ content: "\e054"
+}
+
+.glyphicon-align-justify:before {
+ content: "\e055"
+}
+
+.glyphicon-list:before {
+ content: "\e056"
+}
+
+.glyphicon-indent-left:before {
+ content: "\e057"
+}
+
+.glyphicon-indent-right:before {
+ content: "\e058"
+}
+
+.glyphicon-facetime-video:before {
+ content: "\e059"
+}
+
+.glyphicon-picture:before {
+ content: "\e060"
+}
+
+.glyphicon-map-marker:before {
+ content: "\e062"
+}
+
+.glyphicon-adjust:before {
+ content: "\e063"
+}
+
+.glyphicon-tint:before {
+ content: "\e064"
+}
+
+.glyphicon-edit:before {
+ content: "\e065"
+}
+
+.glyphicon-share:before {
+ content: "\e066"
+}
+
+.glyphicon-check:before {
+ content: "\e067"
+}
+
+.glyphicon-move:before {
+ content: "\e068"
+}
+
+.glyphicon-step-backward:before {
+ content: "\e069"
+}
+
+.glyphicon-fast-backward:before {
+ content: "\e070"
+}
+
+.glyphicon-backward:before {
+ content: "\e071"
+}
+
+.glyphicon-play:before {
+ content: "\e072"
+}
+
+.glyphicon-pause:before {
+ content: "\e073"
+}
+
+.glyphicon-stop:before {
+ content: "\e074"
+}
+
+.glyphicon-forward:before {
+ content: "\e075"
+}
+
+.glyphicon-fast-forward:before {
+ content: "\e076"
+}
+
+.glyphicon-step-forward:before {
+ content: "\e077"
+}
+
+.glyphicon-eject:before {
+ content: "\e078"
+}
+
+.glyphicon-chevron-left:before {
+ content: "\e079"
+}
+
+.glyphicon-chevron-right:before {
+ content: "\e080"
+}
+
+.glyphicon-plus-sign:before {
+ content: "\e081"
+}
+
+.glyphicon-minus-sign:before {
+ content: "\e082"
+}
+
+.glyphicon-remove-sign:before {
+ content: "\e083"
+}
+
+.glyphicon-ok-sign:before {
+ content: "\e084"
+}
+
+.glyphicon-question-sign:before {
+ content: "\e085"
+}
+
+.glyphicon-info-sign:before {
+ content: "\e086"
+}
+
+.glyphicon-screenshot:before {
+ content: "\e087"
+}
+
+.glyphicon-remove-circle:before {
+ content: "\e088"
+}
+
+.glyphicon-ok-circle:before {
+ content: "\e089"
+}
+
+.glyphicon-ban-circle:before {
+ content: "\e090"
+}
+
+.glyphicon-arrow-left:before {
+ content: "\e091"
+}
+
+.glyphicon-arrow-right:before {
+ content: "\e092"
+}
+
+.glyphicon-arrow-up:before {
+ content: "\e093"
+}
+
+.glyphicon-arrow-down:before {
+ content: "\e094"
+}
+
+.glyphicon-share-alt:before {
+ content: "\e095"
+}
+
+.glyphicon-resize-full:before {
+ content: "\e096"
+}
+
+.glyphicon-resize-small:before {
+ content: "\e097"
+}
+
+.glyphicon-exclamation-sign:before {
+ content: "\e101"
+}
+
+.glyphicon-gift:before {
+ content: "\e102"
+}
+
+.glyphicon-leaf:before {
+ content: "\e103"
+}
+
+.glyphicon-fire:before {
+ content: "\e104"
+}
+
+.glyphicon-eye-open:before {
+ content: "\e105"
+}
+
+.glyphicon-eye-close:before {
+ content: "\e106"
+}
+
+.glyphicon-warning-sign:before {
+ content: "\e107"
+}
+
+.glyphicon-plane:before {
+ content: "\e108"
+}
+
+.glyphicon-calendar:before {
+ content: "\e109"
+}
+
+.glyphicon-random:before {
+ content: "\e110"
+}
+
+.glyphicon-comment:before {
+ content: "\e111"
+}
+
+.glyphicon-magnet:before {
+ content: "\e112"
+}
+
+.glyphicon-chevron-up:before {
+ content: "\e113"
+}
+
+.glyphicon-chevron-down:before {
+ content: "\e114"
+}
+
+.glyphicon-retweet:before {
+ content: "\e115"
+}
+
+.glyphicon-shopping-cart:before {
+ content: "\e116"
+}
+
+.glyphicon-folder-close:before {
+ content: "\e117"
+}
+
+.glyphicon-folder-open:before {
+ content: "\e118"
+}
+
+.glyphicon-resize-vertical:before {
+ content: "\e119"
+}
+
+.glyphicon-resize-horizontal:before {
+ content: "\e120"
+}
+
+.glyphicon-hdd:before {
+ content: "\e121"
+}
+
+.glyphicon-bullhorn:before {
+ content: "\e122"
+}
+
+.glyphicon-bell:before {
+ content: "\e123"
+}
+
+.glyphicon-certificate:before {
+ content: "\e124"
+}
+
+.glyphicon-thumbs-up:before {
+ content: "\e125"
+}
+
+.glyphicon-thumbs-down:before {
+ content: "\e126"
+}
+
+.glyphicon-hand-right:before {
+ content: "\e127"
+}
+
+.glyphicon-hand-left:before {
+ content: "\e128"
+}
+
+.glyphicon-hand-up:before {
+ content: "\e129"
+}
+
+.glyphicon-hand-down:before {
+ content: "\e130"
+}
+
+.glyphicon-circle-arrow-right:before {
+ content: "\e131"
+}
+
+.glyphicon-circle-arrow-left:before {
+ content: "\e132"
+}
+
+.glyphicon-circle-arrow-up:before {
+ content: "\e133"
+}
+
+.glyphicon-circle-arrow-down:before {
+ content: "\e134"
+}
+
+.glyphicon-globe:before {
+ content: "\e135"
+}
+
+.glyphicon-wrench:before {
+ content: "\e136"
+}
+
+.glyphicon-tasks:before {
+ content: "\e137"
+}
+
+.glyphicon-filter:before {
+ content: "\e138"
+}
+
+.glyphicon-briefcase:before {
+ content: "\e139"
+}
+
+.glyphicon-fullscreen:before {
+ content: "\e140"
+}
+
+.glyphicon-dashboard:before {
+ content: "\e141"
+}
+
+.glyphicon-paperclip:before {
+ content: "\e142"
+}
+
+.glyphicon-heart-empty:before {
+ content: "\e143"
+}
+
+.glyphicon-link:before {
+ content: "\e144"
+}
+
+.glyphicon-phone:before {
+ content: "\e145"
+}
+
+.glyphicon-pushpin:before {
+ content: "\e146"
+}
+
+.glyphicon-usd:before {
+ content: "\e148"
+}
+
+.glyphicon-gbp:before {
+ content: "\e149"
+}
+
+.glyphicon-sort:before {
+ content: "\e150"
+}
+
+.glyphicon-sort-by-alphabet:before {
+ content: "\e151"
+}
+
+.glyphicon-sort-by-alphabet-alt:before {
+ content: "\e152"
+}
+
+.glyphicon-sort-by-order:before {
+ content: "\e153"
+}
+
+.glyphicon-sort-by-order-alt:before {
+ content: "\e154"
+}
+
+.glyphicon-sort-by-attributes:before {
+ content: "\e155"
+}
+
+.glyphicon-sort-by-attributes-alt:before {
+ content: "\e156"
+}
+
+.glyphicon-unchecked:before {
+ content: "\e157"
+}
+
+.glyphicon-expand:before {
+ content: "\e158"
+}
+
+.glyphicon-collapse-down:before {
+ content: "\e159"
+}
+
+.glyphicon-collapse-up:before {
+ content: "\e160"
+}
+
+.glyphicon-log-in:before {
+ content: "\e161"
+}
+
+.glyphicon-flash:before {
+ content: "\e162"
+}
+
+.glyphicon-log-out:before {
+ content: "\e163"
+}
+
+.glyphicon-new-window:before {
+ content: "\e164"
+}
+
+.glyphicon-record:before {
+ content: "\e165"
+}
+
+.glyphicon-save:before {
+ content: "\e166"
+}
+
+.glyphicon-open:before {
+ content: "\e167"
+}
+
+.glyphicon-saved:before {
+ content: "\e168"
+}
+
+.glyphicon-import:before {
+ content: "\e169"
+}
+
+.glyphicon-export:before {
+ content: "\e170"
+}
+
+.glyphicon-send:before {
+ content: "\e171"
+}
+
+.glyphicon-floppy-disk:before {
+ content: "\e172"
+}
+
+.glyphicon-floppy-saved:before {
+ content: "\e173"
+}
+
+.glyphicon-floppy-remove:before {
+ content: "\e174"
+}
+
+.glyphicon-floppy-save:before {
+ content: "\e175"
+}
+
+.glyphicon-floppy-open:before {
+ content: "\e176"
+}
+
+.glyphicon-credit-card:before {
+ content: "\e177"
+}
+
+.glyphicon-transfer:before {
+ content: "\e178"
+}
+
+.glyphicon-cutlery:before {
+ content: "\e179"
+}
+
+.glyphicon-header:before {
+ content: "\e180"
+}
+
+.glyphicon-compressed:before {
+ content: "\e181"
+}
+
+.glyphicon-earphone:before {
+ content: "\e182"
+}
+
+.glyphicon-phone-alt:before {
+ content: "\e183"
+}
+
+.glyphicon-tower:before {
+ content: "\e184"
+}
+
+.glyphicon-stats:before {
+ content: "\e185"
+}
+
+.glyphicon-sd-video:before {
+ content: "\e186"
+}
+
+.glyphicon-hd-video:before {
+ content: "\e187"
+}
+
+.glyphicon-subtitles:before {
+ content: "\e188"
+}
+
+.glyphicon-sound-stereo:before {
+ content: "\e189"
+}
+
+.glyphicon-sound-dolby:before {
+ content: "\e190"
+}
+
+.glyphicon-sound-5-1:before {
+ content: "\e191"
+}
+
+.glyphicon-sound-6-1:before {
+ content: "\e192"
+}
+
+.glyphicon-sound-7-1:before {
+ content: "\e193"
+}
+
+.glyphicon-copyright-mark:before {
+ content: "\e194"
+}
+
+.glyphicon-registration-mark:before {
+ content: "\e195"
+}
+
+.glyphicon-cloud-download:before {
+ content: "\e197"
+}
+
+.glyphicon-cloud-upload:before {
+ content: "\e198"
+}
+
+.glyphicon-tree-conifer:before {
+ content: "\e199"
+}
+
+.glyphicon-tree-deciduous:before {
+ content: "\e200"
+}
+
+.glyphicon-cd:before {
+ content: "\e201"
+}
+
+.glyphicon-save-file:before {
+ content: "\e202"
+}
+
+.glyphicon-open-file:before {
+ content: "\e203"
+}
+
+.glyphicon-level-up:before {
+ content: "\e204"
+}
+
+.glyphicon-copy:before {
+ content: "\e205"
+}
+
+.glyphicon-paste:before {
+ content: "\e206"
+}
+
+.glyphicon-alert:before {
+ content: "\e209"
+}
+
+.glyphicon-equalizer:before {
+ content: "\e210"
+}
+
+.glyphicon-king:before {
+ content: "\e211"
+}
+
+.glyphicon-queen:before {
+ content: "\e212"
+}
+
+.glyphicon-pawn:before {
+ content: "\e213"
+}
+
+.glyphicon-bishop:before {
+ content: "\e214"
+}
+
+.glyphicon-knight:before {
+ content: "\e215"
+}
+
+.glyphicon-baby-formula:before {
+ content: "\e216"
+}
+
+.glyphicon-tent:before {
+ content: "\26fa"
+}
+
+.glyphicon-blackboard:before {
+ content: "\e218"
+}
+
+.glyphicon-bed:before {
+ content: "\e219"
+}
+
+.glyphicon-apple:before {
+ content: "\f8ff"
+}
+
+.glyphicon-erase:before {
+ content: "\e221"
+}
+
+.glyphicon-hourglass:before {
+ content: "\231b"
+}
+
+.glyphicon-lamp:before {
+ content: "\e223"
+}
+
+.glyphicon-duplicate:before {
+ content: "\e224"
+}
+
+.glyphicon-piggy-bank:before {
+ content: "\e225"
+}
+
+.glyphicon-scissors:before {
+ content: "\e226"
+}
+
+.glyphicon-bitcoin:before, .glyphicon-btc:before, .glyphicon-xbt:before {
+ content: "\e227"
+}
+
+.glyphicon-jpy:before, .glyphicon-yen:before {
+ content: "\00a5"
+}
+
+.glyphicon-rub:before, .glyphicon-ruble:before {
+ content: "\20bd"
+}
+
+.glyphicon-scale:before {
+ content: "\e230"
+}
+
+.glyphicon-ice-lolly:before {
+ content: "\e231"
+}
+
+.glyphicon-ice-lolly-tasted:before {
+ content: "\e232"
+}
+
+.glyphicon-education:before {
+ content: "\e233"
+}
+
+.glyphicon-option-horizontal:before {
+ content: "\e234"
+}
+
+.glyphicon-option-vertical:before {
+ content: "\e235"
+}
+
+.glyphicon-menu-hamburger:before {
+ content: "\e236"
+}
+
+.glyphicon-modal-window:before {
+ content: "\e237"
+}
+
+.glyphicon-oil:before {
+ content: "\e238"
+}
+
+.glyphicon-grain:before {
+ content: "\e239"
+}
+
+.glyphicon-sunglasses:before {
+ content: "\e240"
+}
+
+.glyphicon-text-size:before {
+ content: "\e241"
+}
+
+.glyphicon-text-color:before {
+ content: "\e242"
+}
+
+.glyphicon-text-background:before {
+ content: "\e243"
+}
+
+.glyphicon-object-align-top:before {
+ content: "\e244"
+}
+
+.glyphicon-object-align-bottom:before {
+ content: "\e245"
+}
+
+.glyphicon-object-align-horizontal:before {
+ content: "\e246"
+}
+
+.glyphicon-object-align-left:before {
+ content: "\e247"
+}
+
+.glyphicon-object-align-vertical:before {
+ content: "\e248"
+}
+
+.glyphicon-object-align-right:before {
+ content: "\e249"
+}
+
+.glyphicon-triangle-right:before {
+ content: "\e250"
+}
+
+.glyphicon-triangle-left:before {
+ content: "\e251"
+}
+
+.glyphicon-triangle-bottom:before {
+ content: "\e252"
+}
+
+.glyphicon-triangle-top:before {
+ content: "\e253"
+}
+
+.glyphicon-console:before {
+ content: "\e254"
+}
+
+.glyphicon-superscript:before {
+ content: "\e255"
+}
+
+.glyphicon-subscript:before {
+ content: "\e256"
+}
+
+.glyphicon-menu-left:before {
+ content: "\e257"
+}
+
+.glyphicon-menu-right:before {
+ content: "\e258"
+}
+
+.glyphicon-menu-down:before {
+ content: "\e259"
+}
+
+.glyphicon-menu-up:before {
+ content: "\e260"
+}
+
+*, :after, :before {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box
+}
+
+html {
+ font-size: 10px;
+ -webkit-tap-highlight-color: transparent
+}
+
+
+button, input, select, textarea {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit
+}
+
+a {
+ background-color: transparent;
+ color: #337ab7;
+ text-decoration: none
+}
+
+ a:focus, a:hover {
+ color: #23527c;
+ text-decoration: underline
+ }
+
+ a:focus {
+ outline: -webkit-focus-ring-color auto 5px;
+ outline-offset: -2px
+ }
+
+figure {
+ margin: 0
+}
+
+.carousel-inner > .item > a > img, .carousel-inner > .item > img, .img-responsive, .thumbnail a > img, .thumbnail > img {
+ display: block;
+ max-width: 100%;
+ height: auto
+}
+
+.img-rounded {
+ border-radius: 6px
+}
+
+.img-thumbnail {
+ padding: 4px;
+ line-height: 1.42857143;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -webkit-transition: .2s ease-in-out;
+ -o-transition: .2s ease-in-out;
+ transition: .2s ease-in-out;
+ display: inline-block;
+ max-width: 100%;
+ height: auto
+}
+
+.img-circle {
+ border-radius: 50%
+}
+
+hr {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border: 0;
+ border-top: 1px solid #eee
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0,0,0,0);
+ border: 0
+}
+
+.sr-only-focusable:active, .sr-only-focusable:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ clip: auto
+}
+
+[role=button] {
+ cursor: pointer
+}
+
+.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
+ font-family: inherit;
+ font-weight: 500;
+ line-height: 1.1;
+ color: inherit
+}
+
+ .h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, .h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small {
+ font-weight: 400;
+ line-height: 1;
+ color: #777
+ }
+
+.h1, .h2, .h3, h1, h2, h3 {
+ margin-top: 20px;
+ margin-bottom: 10px
+}
+
+ .h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small {
+ font-size: 65%
+ }
+
+.h4, .h5, .h6, h4, h5, h6 {
+ margin-top: 10px;
+ margin-bottom: 10px
+}
+
+ .h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small {
+ font-size: 75%
+ }
+
+.h1, h1 {
+ font-size: 36px
+}
+
+.h2, h2 {
+ font-size: 30px
+}
+
+.h3, h3 {
+ font-size: 24px
+}
+
+.h4, h4 {
+ font-size: 18px
+}
+
+.h5, h5 {
+ font-size: 14px
+}
+
+.h6, h6 {
+ font-size: 12px
+}
+
+p {
+ margin: 0 0 10px
+}
+
+.lead {
+ margin-bottom: 20px;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 1.4
+}
+
+@media (min-width:768px) {
+ .lead {
+ font-size: 21px
+ }
+
+ .dl-horizontal dt {
+ float: left;
+ width: 160px;
+ clear: left;
+ text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap
+ }
+
+ .dl-horizontal dd {
+ margin-left: 180px
+ }
+}
+
+.small, small {
+ font-size: 85%
+}
+
+.mark, mark {
+ padding: .2em;
+ background-color: #fcf8e3
+}
+
+.text-left {
+ text-align: left
+}
+
+.text-right {
+ text-align: right
+}
+
+.text-center {
+ text-align: center
+}
+
+.text-justify {
+ text-align: justify
+}
+
+.text-nowrap {
+ white-space: nowrap
+}
+
+.text-lowercase {
+ text-transform: lowercase
+}
+
+.text-uppercase {
+ text-transform: uppercase
+}
+
+.text-capitalize {
+ text-transform: capitalize
+}
+
+.text-muted {
+ color: #777
+}
+
+.text-primary {
+ color: #337ab7
+}
+
+a.text-primary:focus, a.text-primary:hover {
+ color: #286090
+}
+
+.text-success {
+ color: #3c763d
+}
+
+a.text-success:focus, a.text-success:hover {
+ color: #2b542c
+}
+
+.text-info {
+ color: #31708f
+}
+
+a.text-info:focus, a.text-info:hover {
+ color: #245269
+}
+
+.text-warning {
+ color: #8a6d3b
+}
+
+a.text-warning:focus, a.text-warning:hover {
+ color: #66512c
+}
+
+.text-danger {
+ color: #a94442
+}
+
+a.text-danger:focus, a.text-danger:hover {
+ color: #843534
+}
+
+.bg-primary {
+ color: #fff;
+ background-color: #337ab7
+}
+
+a.bg-primary:focus, a.bg-primary:hover {
+ background-color: #286090
+}
+
+.bg-success {
+ background-color: #dff0d8
+}
+
+a.bg-success:focus, a.bg-success:hover {
+ background-color: #c1e2b3
+}
+
+.bg-info {
+ background-color: #d9edf7
+}
+
+a.bg-info:focus, a.bg-info:hover {
+ background-color: #afd9ee
+}
+
+.bg-warning {
+ background-color: #fcf8e3
+}
+
+a.bg-warning:focus, a.bg-warning:hover {
+ background-color: #f7ecb5
+}
+
+.bg-danger {
+ background-color: #f2dede
+}
+
+a.bg-danger:focus, a.bg-danger:hover {
+ background-color: #e4b9b9
+}
+
+.page-header {
+ padding-bottom: 9px;
+ margin: 40px 0 20px;
+ border-bottom: 1px solid #eee
+}
+
+ol, ul {
+ margin-top: 0;
+ margin-bottom: 10px
+}
+
+ ol ol, ol ul, ul ol, ul ul {
+ margin-bottom: 0
+ }
+
+.list-unstyled {
+ padding-left: 0;
+ list-style: none
+}
+
+.list-inline {
+ padding-left: 0;
+ list-style: none;
+ margin-left: -5px
+}
+
+ .list-inline > li {
+ display: inline-block;
+ padding-right: 5px;
+ padding-left: 5px
+ }
+
+dl {
+ margin-top: 0;
+ margin-bottom: 20px
+}
+
+dd, dt {
+ line-height: 1.42857143
+}
+
+dt {
+ font-weight: 700
+}
+
+dd {
+ margin-left: 0
+}
+
+abbr[data-original-title], abbr[title] {
+ cursor: help
+}
+
+.initialism {
+ font-size: 90%;
+ text-transform: uppercase
+}
+
+blockquote {
+ padding: 10px 20px;
+ margin: 0 0 20px;
+ font-size: 17.5px;
+ border-left: 5px solid #eee
+}
+
+ blockquote ol:last-child, blockquote p:last-child, blockquote ul:last-child {
+ margin-bottom: 0
+ }
+
+ blockquote .small, blockquote footer, blockquote small {
+ display: block;
+ font-size: 80%;
+ line-height: 1.42857143;
+ color: #777
+ }
+
+ blockquote .small:before, blockquote footer:before, blockquote small:before {
+ content: "\2014 \00A0"
+ }
+
+ .blockquote-reverse, blockquote.pull-right {
+ padding-right: 15px;
+ padding-left: 0;
+ text-align: right;
+ border-right: 5px solid #eee;
+ border-left: 0
+ }
+
+ .blockquote-reverse .small:before, .blockquote-reverse footer:before, .blockquote-reverse small:before, blockquote.pull-right .small:before, blockquote.pull-right footer:before, blockquote.pull-right small:before {
+ content: ""
+ }
+
+ .blockquote-reverse .small:after, .blockquote-reverse footer:after, .blockquote-reverse small:after, blockquote.pull-right .small:after, blockquote.pull-right footer:after, blockquote.pull-right small:after {
+ content: "\00A0 \2014"
+ }
+
+address {
+ margin-bottom: 20px;
+ font-style: normal;
+ line-height: 1.42857143
+}
+
+code, kbd, pre, samp {
+ font-family: Menlo,Monaco,Consolas,"Courier New",monospace
+}
+
+code {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #c7254e;
+ background-color: #f9f2f4;
+ border-radius: 4px
+}
+
+kbd {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #fff;
+ background-color: #333;
+ border-radius: 3px;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);
+ box-shadow: inset 0 -1px 0 rgba(0,0,0,.25)
+}
+
+ kbd kbd {
+ padding: 0;
+ font-size: 100%;
+ font-weight: 700;
+ -webkit-box-shadow: none;
+ box-shadow: none
+ }
+
+pre {
+ overflow: auto;
+ display: block;
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ line-height: 1.42857143;
+ color: #333;
+ word-break: break-all;
+ word-wrap: break-word;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border-radius: 4px
+}
+
+ pre code {
+ padding: 0;
+ font-size: inherit;
+ color: inherit;
+ white-space: pre-wrap;
+ background-color: transparent;
+ border-radius: 0
+ }
+
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll
+}
+
+.container {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto
+}
+
+@media (min-width:768px) {
+ .container {
+ width: 750px
+ }
+}
+
+@media (min-width:992px) {
+ .container {
+ width: 970px
+ }
+}
+
+@media (min-width:1200px) {
+ .container {
+ width: 1170px
+ }
+}
+
+.container-fluid {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto
+}
+
+.row {
+ margin-right: -15px;
+ margin-left: -15px
+}
+
+.row-no-gutters {
+ margin-right: 0;
+ margin-left: 0
+}
+
+ .row-no-gutters [class*=col-] {
+ padding-right: 0;
+ padding-left: 0
+ }
+
+.col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9 {
+ position: relative;
+ min-height: 1px;
+ padding-right: 15px;
+ padding-left: 15px
+}
+
+.col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9 {
+ float: left
+}
+
+.col-xs-12 {
+ width: 100%
+}
+
+.col-xs-11 {
+ width: 91.66666667%
+}
+
+.col-xs-10 {
+ width: 83.33333333%
+}
+
+.col-xs-9 {
+ width: 75%
+}
+
+.col-xs-8 {
+ width: 66.66666667%
+}
+
+.col-xs-7 {
+ width: 58.33333333%
+}
+
+.col-xs-6 {
+ width: 50%
+}
+
+.col-xs-5 {
+ width: 41.66666667%
+}
+
+.col-xs-4 {
+ width: 33.33333333%
+}
+
+.col-xs-3 {
+ width: 25%
+}
+
+.col-xs-2 {
+ width: 16.66666667%
+}
+
+.col-xs-1 {
+ width: 8.33333333%
+}
+
+.col-xs-pull-12 {
+ right: 100%
+}
+
+.col-xs-pull-11 {
+ right: 91.66666667%
+}
+
+.col-xs-pull-10 {
+ right: 83.33333333%
+}
+
+.col-xs-pull-9 {
+ right: 75%
+}
+
+.col-xs-pull-8 {
+ right: 66.66666667%
+}
+
+.col-xs-pull-7 {
+ right: 58.33333333%
+}
+
+.col-xs-pull-6 {
+ right: 50%
+}
+
+.col-xs-pull-5 {
+ right: 41.66666667%
+}
+
+.col-xs-pull-4 {
+ right: 33.33333333%
+}
+
+.col-xs-pull-3 {
+ right: 25%
+}
+
+.col-xs-pull-2 {
+ right: 16.66666667%
+}
+
+.col-xs-pull-1 {
+ right: 8.33333333%
+}
+
+.col-xs-pull-0 {
+ right: auto
+}
+
+.col-xs-push-12 {
+ left: 100%
+}
+
+.col-xs-push-11 {
+ left: 91.66666667%
+}
+
+.col-xs-push-10 {
+ left: 83.33333333%
+}
+
+.col-xs-push-9 {
+ left: 75%
+}
+
+.col-xs-push-8 {
+ left: 66.66666667%
+}
+
+.col-xs-push-7 {
+ left: 58.33333333%
+}
+
+.col-xs-push-6 {
+ left: 50%
+}
+
+.col-xs-push-5 {
+ left: 41.66666667%
+}
+
+.col-xs-push-4 {
+ left: 33.33333333%
+}
+
+.col-xs-push-3 {
+ left: 25%
+}
+
+.col-xs-push-2 {
+ left: 16.66666667%
+}
+
+.col-xs-push-1 {
+ left: 8.33333333%
+}
+
+.col-xs-push-0 {
+ left: auto
+}
+
+.col-xs-offset-12 {
+ margin-left: 100%
+}
+
+.col-xs-offset-11 {
+ margin-left: 91.66666667%
+}
+
+.col-xs-offset-10 {
+ margin-left: 83.33333333%
+}
+
+.col-xs-offset-9 {
+ margin-left: 75%
+}
+
+.col-xs-offset-8 {
+ margin-left: 66.66666667%
+}
+
+.col-xs-offset-7 {
+ margin-left: 58.33333333%
+}
+
+.col-xs-offset-6 {
+ margin-left: 50%
+}
+
+.col-xs-offset-5 {
+ margin-left: 41.66666667%
+}
+
+.col-xs-offset-4 {
+ margin-left: 33.33333333%
+}
+
+.col-xs-offset-3 {
+ margin-left: 25%
+}
+
+.col-xs-offset-2 {
+ margin-left: 16.66666667%
+}
+
+.col-xs-offset-1 {
+ margin-left: 8.33333333%
+}
+
+.col-xs-offset-0 {
+ margin-left: 0
+}
+
+@media (min-width:768px) {
+ .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9 {
+ float: left
+ }
+
+ .col-sm-12 {
+ width: 100%
+ }
+
+ .col-sm-11 {
+ width: 91.66666667%
+ }
+
+ .col-sm-10 {
+ width: 83.33333333%
+ }
+
+ .col-sm-9 {
+ width: 75%
+ }
+
+ .col-sm-8 {
+ width: 66.66666667%
+ }
+
+ .col-sm-7 {
+ width: 58.33333333%
+ }
+
+ .col-sm-6 {
+ width: 50%
+ }
+
+ .col-sm-5 {
+ width: 41.66666667%
+ }
+
+ .col-sm-4 {
+ width: 33.33333333%
+ }
+
+ .col-sm-3 {
+ width: 25%
+ }
+
+ .col-sm-2 {
+ width: 16.66666667%
+ }
+
+ .col-sm-1 {
+ width: 8.33333333%
+ }
+
+ .col-sm-pull-12 {
+ right: 100%
+ }
+
+ .col-sm-pull-11 {
+ right: 91.66666667%
+ }
+
+ .col-sm-pull-10 {
+ right: 83.33333333%
+ }
+
+ .col-sm-pull-9 {
+ right: 75%
+ }
+
+ .col-sm-pull-8 {
+ right: 66.66666667%
+ }
+
+ .col-sm-pull-7 {
+ right: 58.33333333%
+ }
+
+ .col-sm-pull-6 {
+ right: 50%
+ }
+
+ .col-sm-pull-5 {
+ right: 41.66666667%
+ }
+
+ .col-sm-pull-4 {
+ right: 33.33333333%
+ }
+
+ .col-sm-pull-3 {
+ right: 25%
+ }
+
+ .col-sm-pull-2 {
+ right: 16.66666667%
+ }
+
+ .col-sm-pull-1 {
+ right: 8.33333333%
+ }
+
+ .col-sm-pull-0 {
+ right: auto
+ }
+
+ .col-sm-push-12 {
+ left: 100%
+ }
+
+ .col-sm-push-11 {
+ left: 91.66666667%
+ }
+
+ .col-sm-push-10 {
+ left: 83.33333333%
+ }
+
+ .col-sm-push-9 {
+ left: 75%
+ }
+
+ .col-sm-push-8 {
+ left: 66.66666667%
+ }
+
+ .col-sm-push-7 {
+ left: 58.33333333%
+ }
+
+ .col-sm-push-6 {
+ left: 50%
+ }
+
+ .col-sm-push-5 {
+ left: 41.66666667%
+ }
+
+ .col-sm-push-4 {
+ left: 33.33333333%
+ }
+
+ .col-sm-push-3 {
+ left: 25%
+ }
+
+ .col-sm-push-2 {
+ left: 16.66666667%
+ }
+
+ .col-sm-push-1 {
+ left: 8.33333333%
+ }
+
+ .col-sm-push-0 {
+ left: auto
+ }
+
+ .col-sm-offset-12 {
+ margin-left: 100%
+ }
+
+ .col-sm-offset-11 {
+ margin-left: 91.66666667%
+ }
+
+ .col-sm-offset-10 {
+ margin-left: 83.33333333%
+ }
+
+ .col-sm-offset-9 {
+ margin-left: 75%
+ }
+
+ .col-sm-offset-8 {
+ margin-left: 66.66666667%
+ }
+
+ .col-sm-offset-7 {
+ margin-left: 58.33333333%
+ }
+
+ .col-sm-offset-6 {
+ margin-left: 50%
+ }
+
+ .col-sm-offset-5 {
+ margin-left: 41.66666667%
+ }
+
+ .col-sm-offset-4 {
+ margin-left: 33.33333333%
+ }
+
+ .col-sm-offset-3 {
+ margin-left: 25%
+ }
+
+ .col-sm-offset-2 {
+ margin-left: 16.66666667%
+ }
+
+ .col-sm-offset-1 {
+ margin-left: 8.33333333%
+ }
+
+ .col-sm-offset-0 {
+ margin-left: 0
+ }
+}
+
+@media (min-width:992px) {
+ .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9 {
+ float: left
+ }
+
+ .col-md-12 {
+ width: 100%
+ }
+
+ .col-md-11 {
+ width: 91.66666667%
+ }
+
+ .col-md-10 {
+ width: 83.33333333%
+ }
+
+ .col-md-9 {
+ width: 75%
+ }
+
+ .col-md-8 {
+ width: 66.66666667%
+ }
+
+ .col-md-7 {
+ width: 58.33333333%
+ }
+
+ .col-md-6 {
+ width: 50%
+ }
+
+ .col-md-5 {
+ width: 41.66666667%
+ }
+
+ .col-md-4 {
+ width: 33.33333333%
+ }
+
+ .col-md-3 {
+ width: 25%
+ }
+
+ .col-md-2 {
+ width: 16.66666667%
+ }
+
+ .col-md-1 {
+ width: 8.33333333%
+ }
+
+ .col-md-pull-12 {
+ right: 100%
+ }
+
+ .col-md-pull-11 {
+ right: 91.66666667%
+ }
+
+ .col-md-pull-10 {
+ right: 83.33333333%
+ }
+
+ .col-md-pull-9 {
+ right: 75%
+ }
+
+ .col-md-pull-8 {
+ right: 66.66666667%
+ }
+
+ .col-md-pull-7 {
+ right: 58.33333333%
+ }
+
+ .col-md-pull-6 {
+ right: 50%
+ }
+
+ .col-md-pull-5 {
+ right: 41.66666667%
+ }
+
+ .col-md-pull-4 {
+ right: 33.33333333%
+ }
+
+ .col-md-pull-3 {
+ right: 25%
+ }
+
+ .col-md-pull-2 {
+ right: 16.66666667%
+ }
+
+ .col-md-pull-1 {
+ right: 8.33333333%
+ }
+
+ .col-md-pull-0 {
+ right: auto
+ }
+
+ .col-md-push-12 {
+ left: 100%
+ }
+
+ .col-md-push-11 {
+ left: 91.66666667%
+ }
+
+ .col-md-push-10 {
+ left: 83.33333333%
+ }
+
+ .col-md-push-9 {
+ left: 75%
+ }
+
+ .col-md-push-8 {
+ left: 66.66666667%
+ }
+
+ .col-md-push-7 {
+ left: 58.33333333%
+ }
+
+ .col-md-push-6 {
+ left: 50%
+ }
+
+ .col-md-push-5 {
+ left: 41.66666667%
+ }
+
+ .col-md-push-4 {
+ left: 33.33333333%
+ }
+
+ .col-md-push-3 {
+ left: 25%
+ }
+
+ .col-md-push-2 {
+ left: 16.66666667%
+ }
+
+ .col-md-push-1 {
+ left: 8.33333333%
+ }
+
+ .col-md-push-0 {
+ left: auto
+ }
+
+ .col-md-offset-12 {
+ margin-left: 100%
+ }
+
+ .col-md-offset-11 {
+ margin-left: 91.66666667%
+ }
+
+ .col-md-offset-10 {
+ margin-left: 83.33333333%
+ }
+
+ .col-md-offset-9 {
+ margin-left: 75%
+ }
+
+ .col-md-offset-8 {
+ margin-left: 66.66666667%
+ }
+
+ .col-md-offset-7 {
+ margin-left: 58.33333333%
+ }
+
+ .col-md-offset-6 {
+ margin-left: 50%
+ }
+
+ .col-md-offset-5 {
+ margin-left: 41.66666667%
+ }
+
+ .col-md-offset-4 {
+ margin-left: 33.33333333%
+ }
+
+ .col-md-offset-3 {
+ margin-left: 25%
+ }
+
+ .col-md-offset-2 {
+ margin-left: 16.66666667%
+ }
+
+ .col-md-offset-1 {
+ margin-left: 8.33333333%
+ }
+
+ .col-md-offset-0 {
+ margin-left: 0
+ }
+}
+
+@media (min-width:1200px) {
+ .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9 {
+ float: left
+ }
+
+ .col-lg-12 {
+ width: 100%
+ }
+
+ .col-lg-11 {
+ width: 91.66666667%
+ }
+
+ .col-lg-10 {
+ width: 83.33333333%
+ }
+
+ .col-lg-9 {
+ width: 75%
+ }
+
+ .col-lg-8 {
+ width: 66.66666667%
+ }
+
+ .col-lg-7 {
+ width: 58.33333333%
+ }
+
+ .col-lg-6 {
+ width: 50%
+ }
+
+ .col-lg-5 {
+ width: 41.66666667%
+ }
+
+ .col-lg-4 {
+ width: 33.33333333%
+ }
+
+ .col-lg-3 {
+ width: 25%
+ }
+
+ .col-lg-2 {
+ width: 16.66666667%
+ }
+
+ .col-lg-1 {
+ width: 8.33333333%
+ }
+
+ .col-lg-pull-12 {
+ right: 100%
+ }
+
+ .col-lg-pull-11 {
+ right: 91.66666667%
+ }
+
+ .col-lg-pull-10 {
+ right: 83.33333333%
+ }
+
+ .col-lg-pull-9 {
+ right: 75%
+ }
+
+ .col-lg-pull-8 {
+ right: 66.66666667%
+ }
+
+ .col-lg-pull-7 {
+ right: 58.33333333%
+ }
+
+ .col-lg-pull-6 {
+ right: 50%
+ }
+
+ .col-lg-pull-5 {
+ right: 41.66666667%
+ }
+
+ .col-lg-pull-4 {
+ right: 33.33333333%
+ }
+
+ .col-lg-pull-3 {
+ right: 25%
+ }
+
+ .col-lg-pull-2 {
+ right: 16.66666667%
+ }
+
+ .col-lg-pull-1 {
+ right: 8.33333333%
+ }
+
+ .col-lg-pull-0 {
+ right: auto
+ }
+
+ .col-lg-push-12 {
+ left: 100%
+ }
+
+ .col-lg-push-11 {
+ left: 91.66666667%
+ }
+
+ .col-lg-push-10 {
+ left: 83.33333333%
+ }
+
+ .col-lg-push-9 {
+ left: 75%
+ }
+
+ .col-lg-push-8 {
+ left: 66.66666667%
+ }
+
+ .col-lg-push-7 {
+ left: 58.33333333%
+ }
+
+ .col-lg-push-6 {
+ left: 50%
+ }
+
+ .col-lg-push-5 {
+ left: 41.66666667%
+ }
+
+ .col-lg-push-4 {
+ left: 33.33333333%
+ }
+
+ .col-lg-push-3 {
+ left: 25%
+ }
+
+ .col-lg-push-2 {
+ left: 16.66666667%
+ }
+
+ .col-lg-push-1 {
+ left: 8.33333333%
+ }
+
+ .col-lg-push-0 {
+ left: auto
+ }
+
+ .col-lg-offset-12 {
+ margin-left: 100%
+ }
+
+ .col-lg-offset-11 {
+ margin-left: 91.66666667%
+ }
+
+ .col-lg-offset-10 {
+ margin-left: 83.33333333%
+ }
+
+ .col-lg-offset-9 {
+ margin-left: 75%
+ }
+
+ .col-lg-offset-8 {
+ margin-left: 66.66666667%
+ }
+
+ .col-lg-offset-7 {
+ margin-left: 58.33333333%
+ }
+
+ .col-lg-offset-6 {
+ margin-left: 50%
+ }
+
+ .col-lg-offset-5 {
+ margin-left: 41.66666667%
+ }
+
+ .col-lg-offset-4 {
+ margin-left: 33.33333333%
+ }
+
+ .col-lg-offset-3 {
+ margin-left: 25%
+ }
+
+ .col-lg-offset-2 {
+ margin-left: 16.66666667%
+ }
+
+ .col-lg-offset-1 {
+ margin-left: 8.33333333%
+ }
+
+ .col-lg-offset-0 {
+ margin-left: 0
+ }
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+ background-color: transparent
+}
+
+ table col[class*=col-] {
+ position: static;
+ display: table-column;
+ float: none
+ }
+
+ table td[class*=col-], table th[class*=col-] {
+ position: static;
+ display: table-cell;
+ float: none
+ }
+
+caption {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ color: #777;
+ text-align: left
+}
+
+th {
+ text-align: left
+}
+
+.table {
+ width: 100%;
+ max-width: 100%;
+ margin-bottom: 20px
+}
+
+ .table > tbody > tr > td, .table > tbody > tr > th, .table > tfoot > tr > td, .table > tfoot > tr > th, .table > thead > tr > td, .table > thead > tr > th {
+ padding: 8px;
+ line-height: 1.42857143;
+ vertical-align: top;
+ border-top: 1px solid #ddd
+ }
+
+ .table > thead > tr > th {
+ vertical-align: bottom;
+ border-bottom: 2px solid #ddd
+ }
+
+ .table > caption + thead > tr:first-child > td, .table > caption + thead > tr:first-child > th, .table > colgroup + thead > tr:first-child > td, .table > colgroup + thead > tr:first-child > th, .table > thead:first-child > tr:first-child > td, .table > thead:first-child > tr:first-child > th {
+ border-top: 0
+ }
+
+ .table > tbody + tbody {
+ border-top: 2px solid #ddd
+ }
+
+ .table .table {
+ background-color: #fff
+ }
+
+.table-condensed > tbody > tr > td, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > td, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > thead > tr > th {
+ padding: 5px
+}
+
+.table-bordered, .table-bordered > tbody > tr > td, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > td, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > thead > tr > th {
+ border: 1px solid #ddd
+}
+
+ .table-bordered > thead > tr > td, .table-bordered > thead > tr > th {
+ border-bottom-width: 2px
+ }
+
+.table-striped > tbody > tr:nth-of-type(odd) {
+ background-color: #f9f9f9
+}
+
+.table-hover > tbody > tr:hover, .table > tbody > tr.active > td, .table > tbody > tr.active > th, .table > tbody > tr > td.active, .table > tbody > tr > th.active, .table > tfoot > tr.active > td, .table > tfoot > tr.active > th, .table > tfoot > tr > td.active, .table > tfoot > tr > th.active, .table > thead > tr.active > td, .table > thead > tr.active > th, .table > thead > tr > td.active, .table > thead > tr > th.active {
+ background-color: #f5f5f5
+}
+
+ .table-hover > tbody > tr.active:hover > td, .table-hover > tbody > tr.active:hover > th, .table-hover > tbody > tr:hover > .active, .table-hover > tbody > tr > td.active:hover, .table-hover > tbody > tr > th.active:hover {
+ background-color: #e8e8e8
+ }
+
+.table > tbody > tr.success > td, .table > tbody > tr.success > th, .table > tbody > tr > td.success, .table > tbody > tr > th.success, .table > tfoot > tr.success > td, .table > tfoot > tr.success > th, .table > tfoot > tr > td.success, .table > tfoot > tr > th.success, .table > thead > tr.success > td, .table > thead > tr.success > th, .table > thead > tr > td.success, .table > thead > tr > th.success {
+ background-color: #dff0d8
+}
+
+.table-hover > tbody > tr.success:hover > td, .table-hover > tbody > tr.success:hover > th, .table-hover > tbody > tr:hover > .success, .table-hover > tbody > tr > td.success:hover, .table-hover > tbody > tr > th.success:hover {
+ background-color: #d0e9c6
+}
+
+.table > tbody > tr.info > td, .table > tbody > tr.info > th, .table > tbody > tr > td.info, .table > tbody > tr > th.info, .table > tfoot > tr.info > td, .table > tfoot > tr.info > th, .table > tfoot > tr > td.info, .table > tfoot > tr > th.info, .table > thead > tr.info > td, .table > thead > tr.info > th, .table > thead > tr > td.info, .table > thead > tr > th.info {
+ background-color: #d9edf7
+}
+
+.table-hover > tbody > tr.info:hover > td, .table-hover > tbody > tr.info:hover > th, .table-hover > tbody > tr:hover > .info, .table-hover > tbody > tr > td.info:hover, .table-hover > tbody > tr > th.info:hover {
+ background-color: #c4e3f3
+}
+
+.table > tbody > tr.warning > td, .table > tbody > tr.warning > th, .table > tbody > tr > td.warning, .table > tbody > tr > th.warning, .table > tfoot > tr.warning > td, .table > tfoot > tr.warning > th, .table > tfoot > tr > td.warning, .table > tfoot > tr > th.warning, .table > thead > tr.warning > td, .table > thead > tr.warning > th, .table > thead > tr > td.warning, .table > thead > tr > th.warning {
+ background-color: #fcf8e3
+}
+
+.table-hover > tbody > tr.warning:hover > td, .table-hover > tbody > tr.warning:hover > th, .table-hover > tbody > tr:hover > .warning, .table-hover > tbody > tr > td.warning:hover, .table-hover > tbody > tr > th.warning:hover {
+ background-color: #faf2cc
+}
+
+.table > tbody > tr.danger > td, .table > tbody > tr.danger > th, .table > tbody > tr > td.danger, .table > tbody > tr > th.danger, .table > tfoot > tr.danger > td, .table > tfoot > tr.danger > th, .table > tfoot > tr > td.danger, .table > tfoot > tr > th.danger, .table > thead > tr.danger > td, .table > thead > tr.danger > th, .table > thead > tr > td.danger, .table > thead > tr > th.danger {
+ background-color: #f2dede
+}
+
+.table-hover > tbody > tr.danger:hover > td, .table-hover > tbody > tr.danger:hover > th, .table-hover > tbody > tr:hover > .danger, .table-hover > tbody > tr > td.danger:hover, .table-hover > tbody > tr > th.danger:hover {
+ background-color: #ebcccc
+}
+
+.table-responsive {
+ min-height: .01%;
+ overflow-x: auto
+}
+
+@media screen and (max-width:767px) {
+ .table-responsive {
+ width: 100%;
+ margin-bottom: 15px;
+ overflow-y: hidden;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+ border: 1px solid #ddd
+ }
+
+ .table-responsive > .table {
+ margin-bottom: 0
+ }
+
+ .table-responsive > .table > tbody > tr > td, .table-responsive > .table > tbody > tr > th, .table-responsive > .table > tfoot > tr > td, .table-responsive > .table > tfoot > tr > th, .table-responsive > .table > thead > tr > td, .table-responsive > .table > thead > tr > th {
+ white-space: nowrap
+ }
+
+ .table-responsive > .table-bordered {
+ border: 0
+ }
+
+ .table-responsive > .table-bordered > tbody > tr > td:first-child, .table-responsive > .table-bordered > tbody > tr > th:first-child, .table-responsive > .table-bordered > tfoot > tr > td:first-child, .table-responsive > .table-bordered > tfoot > tr > th:first-child, .table-responsive > .table-bordered > thead > tr > td:first-child, .table-responsive > .table-bordered > thead > tr > th:first-child {
+ border-left: 0
+ }
+
+ .table-responsive > .table-bordered > tbody > tr > td:last-child, .table-responsive > .table-bordered > tbody > tr > th:last-child, .table-responsive > .table-bordered > tfoot > tr > td:last-child, .table-responsive > .table-bordered > tfoot > tr > th:last-child, .table-responsive > .table-bordered > thead > tr > td:last-child, .table-responsive > .table-bordered > thead > tr > th:last-child {
+ border-right: 0
+ }
+
+ .table-responsive > .table-bordered > tbody > tr:last-child > td, .table-responsive > .table-bordered > tbody > tr:last-child > th, .table-responsive > .table-bordered > tfoot > tr:last-child > td, .table-responsive > .table-bordered > tfoot > tr:last-child > th {
+ border-bottom: 0
+ }
+}
+
+fieldset {
+ min-width: 0;
+ padding: 0;
+ margin: 0;
+ border: 0
+}
+
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: inherit;
+ color: #333;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5
+}
+
+label {
+ display: inline-block;
+ max-width: 100%;
+ margin-bottom: 5px;
+ font-weight: 700
+}
+
+input[type=search] {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none
+}
+
+input[type=checkbox], input[type=radio] {
+ margin: 4px 0 0;
+ line-height: normal
+}
+
+ fieldset[disabled] input[type=checkbox], fieldset[disabled] input[type=radio], input[type=checkbox].disabled, input[type=checkbox][disabled], input[type=radio].disabled, input[type=radio][disabled] {
+ cursor: not-allowed
+ }
+
+input[type=file] {
+ display: block
+}
+
+input[type=range] {
+ display: block;
+ width: 100%
+}
+
+select[multiple], select[size] {
+ height: auto
+}
+
+input[type=checkbox]:focus, input[type=file]:focus, input[type=radio]:focus {
+ outline: -webkit-focus-ring-color auto 5px;
+ outline-offset: -2px
+}
+
+output {
+ display: block;
+ padding-top: 7px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555
+}
+
+.form-control {
+ display: block;
+ width: 100%;
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ -webkit-transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
+ -o-transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
+ transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out
+}
+
+ .form-control:focus {
+ border-color: #66afe9;
+ outline: 0;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)
+ }
+
+ .form-control::-moz-placeholder {
+ color: #999;
+ opacity: 1
+ }
+
+ .form-control:-ms-input-placeholder {
+ color: #999
+ }
+
+ .form-control::-webkit-input-placeholder {
+ color: #999
+ }
+
+ .form-control::-ms-expand {
+ background-color: transparent;
+ border: 0
+ }
+
+ .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control {
+ background-color: #eee;
+ opacity: 1
+ }
+
+ .form-control[disabled], fieldset[disabled] .form-control {
+ cursor: not-allowed
+ }
+
+textarea.form-control {
+ height: auto
+}
+
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ input[type=date].form-control, input[type=datetime-local].form-control, input[type=month].form-control, input[type=time].form-control {
+ line-height: 34px
+ }
+
+ .input-group-sm input[type=date], .input-group-sm input[type=datetime-local], .input-group-sm input[type=month], .input-group-sm input[type=time], input[type=date].input-sm, input[type=datetime-local].input-sm, input[type=month].input-sm, input[type=time].input-sm {
+ line-height: 30px
+ }
+
+ .input-group-lg input[type=date], .input-group-lg input[type=datetime-local], .input-group-lg input[type=month], .input-group-lg input[type=time], input[type=date].input-lg, input[type=datetime-local].input-lg, input[type=month].input-lg, input[type=time].input-lg {
+ line-height: 46px
+ }
+}
+
+.form-group {
+ margin-bottom: 15px
+}
+
+.checkbox, .radio {
+ position: relative;
+ display: block;
+ margin-top: 10px;
+ margin-bottom: 10px
+}
+
+ .checkbox.disabled label, .radio.disabled label, fieldset[disabled] .checkbox label, fieldset[disabled] .radio label {
+ cursor: not-allowed
+ }
+
+ .checkbox label, .radio label {
+ min-height: 20px;
+ padding-left: 20px;
+ margin-bottom: 0;
+ font-weight: 400;
+ cursor: pointer
+ }
+
+ .checkbox input[type=checkbox], .checkbox-inline input[type=checkbox], .radio input[type=radio], .radio-inline input[type=radio] {
+ position: absolute;
+ margin-left: -20px
+ }
+
+ .checkbox + .checkbox, .radio + .radio {
+ margin-top: -5px
+ }
+
+.checkbox-inline, .radio-inline {
+ position: relative;
+ display: inline-block;
+ padding-left: 20px;
+ margin-bottom: 0;
+ font-weight: 400;
+ vertical-align: middle;
+ cursor: pointer
+}
+
+ .checkbox-inline.disabled, .radio-inline.disabled, fieldset[disabled] .checkbox-inline, fieldset[disabled] .radio-inline {
+ cursor: not-allowed
+ }
+
+ .checkbox-inline + .checkbox-inline, .radio-inline + .radio-inline {
+ margin-top: 0;
+ margin-left: 10px
+ }
+
+.form-control-static {
+ min-height: 34px;
+ padding-top: 7px;
+ padding-bottom: 7px;
+ margin-bottom: 0
+}
+
+ .form-control-static.input-lg, .form-control-static.input-sm {
+ padding-right: 0;
+ padding-left: 0
+ }
+
+.input-sm {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px
+}
+
+select.input-sm {
+ height: 30px;
+ line-height: 30px
+}
+
+select[multiple].input-sm, textarea.input-sm {
+ height: auto
+}
+
+.form-group-sm .form-control {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px
+}
+
+.form-group-sm select.form-control {
+ height: 30px;
+ line-height: 30px
+}
+
+.form-group-sm select[multiple].form-control, .form-group-sm textarea.form-control {
+ height: auto
+}
+
+.form-group-sm .form-control-static {
+ height: 30px;
+ min-height: 32px;
+ padding: 6px 10px;
+ font-size: 12px;
+ line-height: 1.5
+}
+
+.input-lg {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px
+}
+
+select.input-lg {
+ height: 46px;
+ line-height: 46px
+}
+
+select[multiple].input-lg, textarea.input-lg {
+ height: auto
+}
+
+.form-group-lg .form-control {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px
+}
+
+.form-group-lg select.form-control {
+ height: 46px;
+ line-height: 46px
+}
+
+.form-group-lg select[multiple].form-control, .form-group-lg textarea.form-control {
+ height: auto
+}
+
+.form-group-lg .form-control-static {
+ height: 46px;
+ min-height: 38px;
+ padding: 11px 16px;
+ font-size: 18px;
+ line-height: 1.3333333
+}
+
+.has-feedback {
+ position: relative
+}
+
+ .has-feedback .form-control {
+ padding-right: 42.5px
+ }
+
+.form-control-feedback {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 2;
+ display: block;
+ width: 34px;
+ height: 34px;
+ line-height: 34px;
+ text-align: center;
+ pointer-events: none
+}
+
+.form-group-lg .form-control + .form-control-feedback, .input-group-lg + .form-control-feedback, .input-lg + .form-control-feedback {
+ width: 46px;
+ height: 46px;
+ line-height: 46px
+}
+
+.form-group-sm .form-control + .form-control-feedback, .input-group-sm + .form-control-feedback, .input-sm + .form-control-feedback {
+ width: 30px;
+ height: 30px;
+ line-height: 30px
+}
+
+.has-success .checkbox, .has-success .checkbox-inline, .has-success .control-label, .has-success .help-block, .has-success .radio, .has-success .radio-inline, .has-success.checkbox label, .has-success.checkbox-inline label, .has-success.radio label, .has-success.radio-inline label {
+ color: #3c763d
+}
+
+.has-success .form-control {
+ border-color: #3c763d;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075)
+}
+
+ .has-success .form-control:focus {
+ border-color: #2b542c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168
+ }
+
+.has-success .input-group-addon {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #3c763d
+}
+
+.has-success .form-control-feedback {
+ color: #3c763d
+}
+
+.has-warning .checkbox, .has-warning .checkbox-inline, .has-warning .control-label, .has-warning .help-block, .has-warning .radio, .has-warning .radio-inline, .has-warning.checkbox label, .has-warning.checkbox-inline label, .has-warning.radio label, .has-warning.radio-inline label {
+ color: #8a6d3b
+}
+
+.has-warning .form-control {
+ border-color: #8a6d3b;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075)
+}
+
+ .has-warning .form-control:focus {
+ border-color: #66512c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b
+ }
+
+.has-warning .input-group-addon {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #8a6d3b
+}
+
+.has-warning .form-control-feedback {
+ color: #8a6d3b
+}
+
+.has-error .checkbox, .has-error .checkbox-inline, .has-error .control-label, .has-error .help-block, .has-error .radio, .has-error .radio-inline, .has-error.checkbox label, .has-error.checkbox-inline label, .has-error.radio label, .has-error.radio-inline label {
+ color: #a94442
+}
+
+.has-error .form-control {
+ border-color: #a94442;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075)
+}
+
+ .has-error .form-control:focus {
+ border-color: #843534;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483
+ }
+
+.has-error .input-group-addon {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #a94442
+}
+
+.has-error .form-control-feedback {
+ color: #a94442
+}
+
+.has-feedback label ~ .form-control-feedback {
+ top: 25px
+}
+
+.has-feedback label.sr-only ~ .form-control-feedback {
+ top: 0
+}
+
+.help-block {
+ display: block;
+ margin-top: 5px;
+ margin-bottom: 10px;
+ color: #737373
+}
+
+.form-horizontal .checkbox, .form-horizontal .checkbox-inline, .form-horizontal .radio, .form-horizontal .radio-inline {
+ padding-top: 7px;
+ margin-top: 0;
+ margin-bottom: 0
+}
+
+.form-horizontal .checkbox, .form-horizontal .radio {
+ min-height: 27px
+}
+
+.form-horizontal .form-group {
+ margin-right: -15px;
+ margin-left: -15px
+}
+
+.form-horizontal .has-feedback .form-control-feedback {
+ right: 15px
+}
+
+@media (min-width:768px) {
+ .form-inline .form-group {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+
+ .form-inline .form-control {
+ display: inline-block;
+ width: auto;
+ vertical-align: middle
+ }
+
+ .form-inline .form-control-static {
+ display: inline-block
+ }
+
+ .form-inline .input-group {
+ display: inline-table;
+ vertical-align: middle
+ }
+
+ .form-inline .input-group .form-control, .form-inline .input-group .input-group-addon, .form-inline .input-group .input-group-btn {
+ width: auto
+ }
+
+ .form-inline .input-group > .form-control {
+ width: 100%
+ }
+
+ .form-inline .control-label {
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+
+ .form-inline .checkbox, .form-inline .radio {
+ display: inline-block;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+
+ .form-inline .checkbox label, .form-inline .radio label {
+ padding-left: 0
+ }
+
+ .form-inline .checkbox input[type=checkbox], .form-inline .radio input[type=radio] {
+ position: relative;
+ margin-left: 0
+ }
+
+ .form-inline .has-feedback .form-control-feedback {
+ top: 0
+ }
+
+ .form-horizontal .control-label {
+ padding-top: 7px;
+ margin-bottom: 0;
+ text-align: right
+ }
+
+ .form-horizontal .form-group-lg .control-label {
+ padding-top: 11px;
+ font-size: 18px
+ }
+
+ .form-horizontal .form-group-sm .control-label {
+ padding-top: 6px;
+ font-size: 12px
+ }
+}
+
+.btn {
+ display: inline-block;
+ margin-bottom: 0;
+ font-weight: 400;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation;
+ cursor: pointer;
+ background-image: none;
+ border: 1px solid transparent;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ border-radius: 4px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none
+}
+
+ .btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus {
+ outline: -webkit-focus-ring-color auto 5px;
+ outline-offset: -2px
+ }
+
+ .btn.focus, .btn:focus, .btn:hover {
+ color: #333;
+ text-decoration: none
+ }
+
+ .btn.active, .btn:active {
+ background-image: none;
+ outline: 0;
+ -webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
+ box-shadow: inset 0 3px 5px rgba(0,0,0,.125)
+ }
+
+ .btn.disabled, .btn[disabled], fieldset[disabled] .btn {
+ cursor: not-allowed;
+ opacity: .65;
+ -webkit-box-shadow: none;
+ box-shadow: none
+ }
+
+a.btn.disabled, fieldset[disabled] a.btn {
+ pointer-events: none
+}
+
+.btn-default {
+ color: #333;
+ background-color: #fff;
+ border-color: #ccc
+}
+
+ .btn-default.focus, .btn-default:focus {
+ color: #333;
+ background-color: #e6e6e6;
+ border-color: #8c8c8c
+ }
+
+ .btn-default:hover {
+ color: #333;
+ background-color: #e6e6e6;
+ border-color: #adadad
+ }
+
+ .btn-default.active, .btn-default:active, .open > .dropdown-toggle.btn-default {
+ color: #333;
+ background-color: #e6e6e6;
+ background-image: none;
+ border-color: #adadad
+ }
+
+ .btn-default.active.focus, .btn-default.active:focus, .btn-default.active:hover, .btn-default:active.focus, .btn-default:active:focus, .btn-default:active:hover, .open > .dropdown-toggle.btn-default.focus, .open > .dropdown-toggle.btn-default:focus, .open > .dropdown-toggle.btn-default:hover {
+ color: #333;
+ background-color: #d4d4d4;
+ border-color: #8c8c8c
+ }
+
+ .btn-default.disabled.focus, .btn-default.disabled:focus, .btn-default.disabled:hover, .btn-default[disabled].focus, .btn-default[disabled]:focus, .btn-default[disabled]:hover, fieldset[disabled] .btn-default.focus, fieldset[disabled] .btn-default:focus, fieldset[disabled] .btn-default:hover {
+ background-color: #fff;
+ border-color: #ccc
+ }
+
+ .btn-default .badge {
+ color: #fff;
+ background-color: #333
+ }
+
+.btn-primary {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #2e6da4
+}
+
+ .btn-primary.focus, .btn-primary:focus {
+ color: #fff;
+ background-color: #286090;
+ border-color: #122b40
+ }
+
+ .btn-primary:hover {
+ color: #fff;
+ background-color: #286090;
+ border-color: #204d74
+ }
+
+ .btn-primary.active, .btn-primary:active, .open > .dropdown-toggle.btn-primary {
+ color: #fff;
+ background-color: #286090;
+ background-image: none;
+ border-color: #204d74
+ }
+
+ .btn-primary.active.focus, .btn-primary.active:focus, .btn-primary.active:hover, .btn-primary:active.focus, .btn-primary:active:focus, .btn-primary:active:hover, .open > .dropdown-toggle.btn-primary.focus, .open > .dropdown-toggle.btn-primary:focus, .open > .dropdown-toggle.btn-primary:hover {
+ color: #fff;
+ background-color: #204d74;
+ border-color: #122b40
+ }
+
+ .btn-primary.disabled.focus, .btn-primary.disabled:focus, .btn-primary.disabled:hover, .btn-primary[disabled].focus, .btn-primary[disabled]:focus, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary.focus, fieldset[disabled] .btn-primary:focus, fieldset[disabled] .btn-primary:hover {
+ background-color: #337ab7;
+ border-color: #2e6da4
+ }
+
+ .btn-primary .badge {
+ color: #337ab7;
+ background-color: #fff
+ }
+
+.btn-success {
+ color: #fff;
+ background-color: #5cb85c;
+ border-color: #4cae4c
+}
+
+ .btn-success.focus, .btn-success:focus {
+ color: #fff;
+ background-color: #449d44;
+ border-color: #255625
+ }
+
+ .btn-success:hover {
+ color: #fff;
+ background-color: #449d44;
+ border-color: #398439
+ }
+
+ .btn-success.active, .btn-success:active, .open > .dropdown-toggle.btn-success {
+ color: #fff;
+ background-color: #449d44;
+ background-image: none;
+ border-color: #398439
+ }
+
+ .btn-success.active.focus, .btn-success.active:focus, .btn-success.active:hover, .btn-success:active.focus, .btn-success:active:focus, .btn-success:active:hover, .open > .dropdown-toggle.btn-success.focus, .open > .dropdown-toggle.btn-success:focus, .open > .dropdown-toggle.btn-success:hover {
+ color: #fff;
+ background-color: #398439;
+ border-color: #255625
+ }
+
+ .btn-success.disabled.focus, .btn-success.disabled:focus, .btn-success.disabled:hover, .btn-success[disabled].focus, .btn-success[disabled]:focus, .btn-success[disabled]:hover, fieldset[disabled] .btn-success.focus, fieldset[disabled] .btn-success:focus, fieldset[disabled] .btn-success:hover {
+ background-color: #5cb85c;
+ border-color: #4cae4c
+ }
+
+ .btn-success .badge {
+ color: #5cb85c;
+ background-color: #fff
+ }
+
+.btn-info {
+ color: #fff;
+ background-color: #5bc0de;
+ border-color: #46b8da
+}
+
+ .btn-info.focus, .btn-info:focus {
+ color: #fff;
+ background-color: #31b0d5;
+ border-color: #1b6d85
+ }
+
+ .btn-info:hover {
+ color: #fff;
+ background-color: #31b0d5;
+ border-color: #269abc
+ }
+
+ .btn-info.active, .btn-info:active, .open > .dropdown-toggle.btn-info {
+ color: #fff;
+ background-color: #31b0d5;
+ background-image: none;
+ border-color: #269abc
+ }
+
+ .btn-info.active.focus, .btn-info.active:focus, .btn-info.active:hover, .btn-info:active.focus, .btn-info:active:focus, .btn-info:active:hover, .open > .dropdown-toggle.btn-info.focus, .open > .dropdown-toggle.btn-info:focus, .open > .dropdown-toggle.btn-info:hover {
+ color: #fff;
+ background-color: #269abc;
+ border-color: #1b6d85
+ }
+
+ .btn-info.disabled.focus, .btn-info.disabled:focus, .btn-info.disabled:hover, .btn-info[disabled].focus, .btn-info[disabled]:focus, .btn-info[disabled]:hover, fieldset[disabled] .btn-info.focus, fieldset[disabled] .btn-info:focus, fieldset[disabled] .btn-info:hover {
+ background-color: #5bc0de;
+ border-color: #46b8da
+ }
+
+ .btn-info .badge {
+ color: #5bc0de;
+ background-color: #fff
+ }
+
+.btn-warning {
+ color: #fff;
+ background-color: #f0ad4e;
+ border-color: #eea236
+}
+
+ .btn-warning.focus, .btn-warning:focus {
+ color: #fff;
+ background-color: #ec971f;
+ border-color: #985f0d
+ }
+
+ .btn-warning:hover {
+ color: #fff;
+ background-color: #ec971f;
+ border-color: #d58512
+ }
+
+ .btn-warning.active, .btn-warning:active, .open > .dropdown-toggle.btn-warning {
+ color: #fff;
+ background-color: #ec971f;
+ background-image: none;
+ border-color: #d58512
+ }
+
+ .btn-warning.active.focus, .btn-warning.active:focus, .btn-warning.active:hover, .btn-warning:active.focus, .btn-warning:active:focus, .btn-warning:active:hover, .open > .dropdown-toggle.btn-warning.focus, .open > .dropdown-toggle.btn-warning:focus, .open > .dropdown-toggle.btn-warning:hover {
+ color: #fff;
+ background-color: #d58512;
+ border-color: #985f0d
+ }
+
+ .btn-warning.disabled.focus, .btn-warning.disabled:focus, .btn-warning.disabled:hover, .btn-warning[disabled].focus, .btn-warning[disabled]:focus, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning.focus, fieldset[disabled] .btn-warning:focus, fieldset[disabled] .btn-warning:hover {
+ background-color: #f0ad4e;
+ border-color: #eea236
+ }
+
+ .btn-warning .badge {
+ color: #f0ad4e;
+ background-color: #fff
+ }
+
+.btn-danger {
+ color: #fff;
+ background-color: #d9534f;
+ border-color: #d43f3a
+}
+
+ .btn-danger.focus, .btn-danger:focus {
+ color: #fff;
+ background-color: #c9302c;
+ border-color: #761c19
+ }
+
+ .btn-danger:hover {
+ color: #fff;
+ background-color: #c9302c;
+ border-color: #ac2925
+ }
+
+ .btn-danger.active, .btn-danger:active, .open > .dropdown-toggle.btn-danger {
+ color: #fff;
+ background-color: #c9302c;
+ background-image: none;
+ border-color: #ac2925
+ }
+
+ .btn-danger.active.focus, .btn-danger.active:focus, .btn-danger.active:hover, .btn-danger:active.focus, .btn-danger:active:focus, .btn-danger:active:hover, .open > .dropdown-toggle.btn-danger.focus, .open > .dropdown-toggle.btn-danger:focus, .open > .dropdown-toggle.btn-danger:hover {
+ color: #fff;
+ background-color: #ac2925;
+ border-color: #761c19
+ }
+
+ .btn-danger.disabled.focus, .btn-danger.disabled:focus, .btn-danger.disabled:hover, .btn-danger[disabled].focus, .btn-danger[disabled]:focus, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger.focus, fieldset[disabled] .btn-danger:focus, fieldset[disabled] .btn-danger:hover {
+ background-color: #d9534f;
+ border-color: #d43f3a
+ }
+
+ .btn-danger .badge {
+ color: #d9534f;
+ background-color: #fff
+ }
+
+.btn-link {
+ font-weight: 400;
+ color: #337ab7;
+ border-radius: 0
+}
+
+ .btn-link, .btn-link.active, .btn-link:active, .btn-link[disabled], fieldset[disabled] .btn-link {
+ background-color: transparent;
+ -webkit-box-shadow: none;
+ box-shadow: none
+ }
+
+ .btn-link, .btn-link:active, .btn-link:focus, .btn-link:hover {
+ border-color: transparent
+ }
+
+ .btn-link:focus, .btn-link:hover {
+ color: #23527c;
+ text-decoration: underline;
+ background-color: transparent
+ }
+
+ .btn-link[disabled]:focus, .btn-link[disabled]:hover, fieldset[disabled] .btn-link:focus, fieldset[disabled] .btn-link:hover {
+ color: #777;
+ text-decoration: none
+ }
+
+.btn-group-lg > .btn, .btn-lg {
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px
+}
+
+.btn-group-sm > .btn, .btn-sm {
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px
+}
+
+.btn-group-xs > .btn, .btn-xs {
+ padding: 1px 5px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px
+}
+
+.btn-block {
+ display: block;
+ width: 100%
+}
+
+ .btn-block + .btn-block {
+ margin-top: 5px
+ }
+
+input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].btn-block {
+ width: 100%
+}
+
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity .15s linear;
+ -o-transition: opacity .15s linear;
+ transition: opacity .15s linear
+}
+
+ .fade.in {
+ opacity: 1
+ }
+
+.collapse {
+ display: none
+}
+
+ .collapse.in {
+ display: block
+ }
+
+tr.collapse.in {
+ display: table-row
+}
+
+tbody.collapse.in {
+ display: table-row-group
+}
+
+.collapsing {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ -webkit-transition-property: height,visibility;
+ -o-transition-property: height,visibility;
+ transition-property: height,visibility;
+ -webkit-transition-duration: .35s;
+ -o-transition-duration: .35s;
+ transition-duration: .35s;
+ -webkit-transition-timing-function: ease;
+ -o-transition-timing-function: ease;
+ transition-timing-function: ease
+}
+
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ margin-left: 2px;
+ vertical-align: middle;
+ border-top: 4px dashed;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent
+}
+
+.dropdown, .dropup {
+ position: relative
+}
+
+.dropdown-toggle:focus {
+ outline: 0
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ font-size: 14px;
+ text-align: left;
+ list-style: none;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid rgba(0,0,0,.15);
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
+ box-shadow: 0 6px 12px rgba(0,0,0,.175)
+}
+
+ .dropdown-menu.pull-right {
+ right: 0;
+ left: auto
+ }
+
+ .dropdown-menu .divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5
+ }
+
+ .dropdown-menu > li > a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: 400;
+ line-height: 1.42857143;
+ color: #333;
+ white-space: nowrap
+ }
+
+ .dropdown-menu > li > a:focus, .dropdown-menu > li > a:hover {
+ color: #262626;
+ text-decoration: none;
+ background-color: #f5f5f5
+ }
+
+ .dropdown-menu > .active > a, .dropdown-menu > .active > a:focus, .dropdown-menu > .active > a:hover {
+ color: #fff;
+ text-decoration: none;
+ background-color: #337ab7;
+ outline: 0
+ }
+
+ .dropdown-menu > .disabled > a, .dropdown-menu > .disabled > a:focus, .dropdown-menu > .disabled > a:hover {
+ color: #777
+ }
+
+ .dropdown-menu > .disabled > a:focus, .dropdown-menu > .disabled > a:hover {
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent;
+ background-image: none
+ }
+
+.open > .dropdown-menu {
+ display: block
+}
+
+.open > a {
+ outline: 0
+}
+
+.dropdown-menu-right {
+ right: 0;
+ left: auto
+}
+
+.dropdown-menu-left {
+ right: auto;
+ left: 0
+}
+
+.dropdown-header {
+ display: block;
+ padding: 3px 20px;
+ font-size: 12px;
+ line-height: 1.42857143;
+ color: #777;
+ white-space: nowrap
+}
+
+.dropdown-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 990
+}
+
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto
+}
+
+.dropup .caret, .navbar-fixed-bottom .dropdown .caret {
+ content: "";
+ border-top: 0;
+ border-bottom: 4px dashed
+}
+
+.dropup .dropdown-menu, .navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 2px
+}
+
+.btn-group, .btn-group-vertical {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle
+}
+
+ .btn-group-vertical > .btn, .btn-group > .btn {
+ position: relative;
+ float: left
+ }
+
+ .btn-group-vertical > .btn.active, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn:focus, .btn-group-vertical > .btn:hover, .btn-group > .btn.active, .btn-group > .btn:active, .btn-group > .btn:focus, .btn-group > .btn:hover {
+ z-index: 2
+ }
+
+ .btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group {
+ margin-left: -1px
+ }
+
+.btn-toolbar {
+ margin-left: -5px
+}
+
+ .btn-toolbar .btn, .btn-toolbar .btn-group, .btn-toolbar .input-group {
+ float: left
+ }
+
+ .btn-toolbar > .btn, .btn-toolbar > .btn-group, .btn-toolbar > .input-group {
+ margin-left: 5px
+ }
+
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+ border-radius: 0
+}
+
+.btn-group > .btn:first-child {
+ margin-left: 0
+}
+
+ .btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0
+ }
+
+.btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0
+}
+
+.btn-group > .btn-group {
+ float: left
+}
+
+ .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-radius: 0
+ }
+
+ .btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0
+ }
+
+ .btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0
+ }
+
+.btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle {
+ outline: 0
+}
+
+.btn-group > .btn + .dropdown-toggle {
+ padding-right: 8px;
+ padding-left: 8px
+}
+
+.btn-group > .btn-lg + .dropdown-toggle {
+ padding-right: 12px;
+ padding-left: 12px
+}
+
+.btn-group.open .dropdown-toggle {
+ -webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
+ box-shadow: inset 0 3px 5px rgba(0,0,0,.125)
+}
+
+ .btn-group.open .dropdown-toggle.btn-link {
+ -webkit-box-shadow: none;
+ box-shadow: none
+ }
+
+.btn .caret {
+ margin-left: 0
+}
+
+.btn-lg .caret {
+ border-width: 5px 5px 0
+}
+
+.dropup .btn-lg .caret {
+ border-width: 0 5px 5px
+}
+
+.btn-group-vertical > .btn, .btn-group-vertical > .btn-group, .btn-group-vertical > .btn-group > .btn {
+ display: block;
+ float: none;
+ width: 100%;
+ max-width: 100%
+}
+
+ .btn-group-vertical > .btn-group > .btn {
+ float: none
+ }
+
+ .btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group {
+ margin-top: -1px;
+ margin-left: 0
+ }
+
+ .btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+ border-radius: 0
+ }
+
+ .btn-group-vertical > .btn:first-child:not(:last-child) {
+ border-radius: 4px 4px 0 0
+ }
+
+ .btn-group-vertical > .btn:last-child:not(:first-child) {
+ border-radius: 0 0 4px 4px
+ }
+
+ .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-radius: 0
+ }
+
+ .btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0
+ }
+
+ .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0
+ }
+
+.btn-group-justified {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: separate
+}
+
+ .btn-group-justified > .btn, .btn-group-justified > .btn-group {
+ display: table-cell;
+ float: none;
+ width: 1%
+ }
+
+ .btn-group-justified > .btn-group .btn {
+ width: 100%
+ }
+
+ .btn-group-justified > .btn-group .dropdown-menu {
+ left: auto
+ }
+
+[data-toggle=buttons] > .btn input[type=checkbox], [data-toggle=buttons] > .btn input[type=radio], [data-toggle=buttons] > .btn-group > .btn input[type=checkbox], [data-toggle=buttons] > .btn-group > .btn input[type=radio] {
+ position: absolute;
+ clip: rect(0,0,0,0);
+ pointer-events: none
+}
+
+.input-group {
+ position: relative;
+ display: table;
+ border-collapse: separate
+}
+
+ .input-group[class*=col-] {
+ float: none;
+ padding-right: 0;
+ padding-left: 0
+ }
+
+ .input-group .form-control {
+ position: relative;
+ z-index: 2;
+ float: left;
+ width: 100%;
+ margin-bottom: 0
+ }
+
+ .input-group .form-control:focus {
+ z-index: 3
+ }
+
+.input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px
+}
+
+select.input-group-lg > .form-control, select.input-group-lg > .input-group-addon, select.input-group-lg > .input-group-btn > .btn {
+ height: 46px;
+ line-height: 46px
+}
+
+select[multiple].input-group-lg > .form-control, select[multiple].input-group-lg > .input-group-addon, select[multiple].input-group-lg > .input-group-btn > .btn, textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-addon, textarea.input-group-lg > .input-group-btn > .btn {
+ height: auto
+}
+
+.input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px
+}
+
+select.input-group-sm > .form-control, select.input-group-sm > .input-group-addon, select.input-group-sm > .input-group-btn > .btn {
+ height: 30px;
+ line-height: 30px
+}
+
+select[multiple].input-group-sm > .form-control, select[multiple].input-group-sm > .input-group-addon, select[multiple].input-group-sm > .input-group-btn > .btn, textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-addon, textarea.input-group-sm > .input-group-btn > .btn {
+ height: auto
+}
+
+.input-group .form-control, .input-group-addon, .input-group-btn {
+ display: table-cell
+}
+
+ .input-group .form-control:not(:first-child):not(:last-child), .input-group-addon:not(:first-child):not(:last-child), .input-group-btn:not(:first-child):not(:last-child) {
+ border-radius: 0
+ }
+
+.input-group-addon, .input-group-btn {
+ width: 1%;
+ white-space: nowrap;
+ vertical-align: middle
+}
+
+.input-group-addon {
+ padding: 6px 12px;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1;
+ color: #555;
+ text-align: center;
+ background-color: #eee;
+ border: 1px solid #ccc;
+ border-radius: 4px
+}
+
+ .input-group-addon.input-sm {
+ padding: 5px 10px;
+ font-size: 12px;
+ border-radius: 3px
+ }
+
+ .input-group-addon.input-lg {
+ padding: 10px 16px;
+ font-size: 18px;
+ border-radius: 6px
+ }
+
+ .input-group-addon input[type=checkbox], .input-group-addon input[type=radio] {
+ margin-top: 0
+ }
+
+ .input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn-group:not(:last-child) > .btn, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0
+ }
+
+ .input-group-addon:first-child {
+ border-right: 0
+ }
+
+ .input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:first-child > .btn-group:not(:first-child) > .btn, .input-group-btn:first-child > .btn:not(:first-child), .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group > .btn, .input-group-btn:last-child > .dropdown-toggle {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0
+ }
+
+ .input-group-addon:last-child {
+ border-left: 0
+ }
+
+.input-group-btn {
+ position: relative;
+ font-size: 0;
+ white-space: nowrap
+}
+
+ .input-group-btn > .btn {
+ position: relative
+ }
+
+ .input-group-btn > .btn + .btn {
+ margin-left: -1px
+ }
+
+ .input-group-btn > .btn:active, .input-group-btn > .btn:focus, .input-group-btn > .btn:hover {
+ z-index: 2
+ }
+
+ .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group {
+ margin-right: -1px
+ }
+
+ .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group {
+ z-index: 2;
+ margin-left: -1px
+ }
+
+.nav {
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none
+}
+
+ .nav > li {
+ position: relative;
+ display: block
+ }
+
+ .nav > li > a {
+ position: relative;
+ display: block;
+ padding: 10px 15px
+ }
+
+ .nav > li > a:focus, .nav > li > a:hover {
+ text-decoration: none;
+ background-color: #eee
+ }
+
+ .nav > li.disabled > a {
+ color: #777
+ }
+
+ .nav > li.disabled > a:focus, .nav > li.disabled > a:hover {
+ color: #777;
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent
+ }
+
+ .nav .open > a, .nav .open > a:focus, .nav .open > a:hover {
+ background-color: #eee;
+ border-color: #337ab7
+ }
+
+ .nav .nav-divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5
+ }
+
+ .nav > li > a > img {
+ max-width: none
+ }
+
+.nav-tabs {
+ border-bottom: 1px solid #ddd
+}
+
+ .nav-tabs > li {
+ float: left;
+ margin-bottom: -1px
+ }
+
+ .nav-tabs > li > a {
+ margin-right: 2px;
+ line-height: 1.42857143;
+ border: 1px solid transparent;
+ border-radius: 4px 4px 0 0
+ }
+
+ .nav-tabs > li > a:hover {
+ border-color: #eee #eee #ddd
+ }
+
+ .nav-tabs > li.active > a, .nav-tabs > li.active > a:focus, .nav-tabs > li.active > a:hover {
+ color: #555;
+ cursor: default;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent
+ }
+
+ .nav-tabs.nav-justified {
+ width: 100%;
+ border-bottom: 0
+ }
+
+ .nav-tabs.nav-justified > li {
+ float: none
+ }
+
+ .nav-tabs.nav-justified > li > a {
+ margin-bottom: 5px;
+ text-align: center;
+ margin-right: 0;
+ border-radius: 4px
+ }
+
+ .nav-tabs.nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto
+ }
+
+ .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:focus, .nav-tabs.nav-justified > .active > a:hover {
+ border: 1px solid #ddd
+ }
+
+.nav-pills > li {
+ float: left
+}
+
+ .nav-pills > li > a {
+ border-radius: 4px
+ }
+
+ .nav-pills > li + li {
+ margin-left: 2px
+ }
+
+ .nav-pills > li.active > a, .nav-pills > li.active > a:focus, .nav-pills > li.active > a:hover {
+ color: #fff;
+ background-color: #337ab7
+ }
+
+.nav-stacked > li {
+ float: none
+}
+
+ .nav-stacked > li + li {
+ margin-top: 2px;
+ margin-left: 0
+ }
+
+.nav-justified {
+ width: 100%
+}
+
+ .nav-justified > li {
+ float: none
+ }
+
+ .nav-justified > li > a {
+ margin-bottom: 5px;
+ text-align: center
+ }
+
+ .nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto
+ }
+
+@media (min-width:768px) {
+ .navbar-right .dropdown-menu {
+ right: 0;
+ left: auto
+ }
+
+ .navbar-right .dropdown-menu-left {
+ right: auto;
+ left: 0
+ }
+
+ .nav-tabs.nav-justified > li {
+ display: table-cell;
+ width: 1%
+ }
+
+ .nav-tabs.nav-justified > li > a {
+ margin-bottom: 0;
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0
+ }
+
+ .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:focus, .nav-tabs.nav-justified > .active > a:hover {
+ border-bottom-color: #fff
+ }
+
+ .nav-justified > li {
+ display: table-cell;
+ width: 1%
+ }
+
+ .nav-justified > li > a {
+ margin-bottom: 0
+ }
+}
+
+.nav-tabs-justified {
+ border-bottom: 0
+}
+
+ .nav-tabs-justified > li > a {
+ margin-right: 0;
+ border-radius: 4px
+ }
+
+ .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:focus, .nav-tabs-justified > .active > a:hover {
+ border: 1px solid #ddd
+ }
+
+@media (min-width:768px) {
+ .nav-tabs-justified > li > a {
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0
+ }
+
+ .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:focus, .nav-tabs-justified > .active > a:hover {
+ border-bottom-color: #fff
+ }
+}
+
+.tab-content > .tab-pane {
+ display: none
+}
+
+.tab-content > .active {
+ display: block
+}
+
+.nav-tabs .dropdown-menu {
+ margin-top: -1px;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0
+}
+
+.navbar {
+ position: relative;
+ min-height: 50px;
+ margin-bottom: 20px;
+ border: 1px solid transparent
+}
+
+.navbar-collapse {
+ padding-right: 15px;
+ padding-left: 15px;
+ overflow-x: visible;
+ border-top: 1px solid transparent;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
+ -webkit-overflow-scrolling: touch
+}
+
+ .navbar-collapse.in {
+ overflow-y: auto
+ }
+
+.navbar-fixed-bottom, .navbar-fixed-top {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030
+}
+
+ .navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse {
+ max-height: 340px
+ }
+
+@media (max-device-width:480px) and (orientation:landscape) {
+ .navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse {
+ max-height: 200px
+ }
+}
+
+@media (min-width:768px) {
+ .navbar {
+ border-radius: 4px
+ }
+
+ .navbar-header {
+ float: left
+ }
+
+ .navbar-collapse {
+ width: auto;
+ border-top: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none
+ }
+
+ .navbar-collapse.collapse {
+ display: block !important;
+ height: auto !important;
+ padding-bottom: 0;
+ overflow: visible !important
+ }
+
+ .navbar-collapse.in {
+ overflow-y: visible
+ }
+
+ .navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse, .navbar-static-top .navbar-collapse {
+ padding-right: 0;
+ padding-left: 0
+ }
+
+ .navbar-fixed-bottom, .navbar-fixed-top {
+ border-radius: 0
+ }
+}
+
+.navbar-fixed-top {
+ top: 0;
+ border-width: 0 0 1px
+}
+
+.navbar-fixed-bottom {
+ bottom: 0;
+ margin-bottom: 0;
+ border-width: 1px 0 0
+}
+
+.container-fluid > .navbar-collapse, .container-fluid > .navbar-header, .container > .navbar-collapse, .container > .navbar-header {
+ margin-right: -15px;
+ margin-left: -15px
+}
+
+.navbar-static-top {
+ z-index: 1000;
+ border-width: 0 0 1px
+}
+
+.navbar-brand {
+ float: left;
+ height: 50px;
+ padding: 15px;
+ font-size: 18px;
+ line-height: 20px
+}
+
+ .navbar-brand:focus, .navbar-brand:hover {
+ text-decoration: none
+ }
+
+ .navbar-brand > img {
+ display: block
+ }
+
+@media (min-width:768px) {
+ .container-fluid > .navbar-collapse, .container-fluid > .navbar-header, .container > .navbar-collapse, .container > .navbar-header {
+ margin-right: 0;
+ margin-left: 0
+ }
+
+ .navbar-static-top {
+ border-radius: 0
+ }
+
+ .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand {
+ margin-left: -15px
+ }
+
+ .navbar-toggle {
+ display: none
+ }
+}
+
+.navbar-toggle {
+ position: relative;
+ float: right;
+ padding: 9px 10px;
+ margin-right: 15px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ background-color: transparent;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 4px
+}
+
+ .navbar-toggle:focus {
+ outline: 0
+ }
+
+ .navbar-toggle .icon-bar {
+ display: block;
+ width: 22px;
+ height: 2px;
+ border-radius: 1px
+ }
+
+ .navbar-toggle .icon-bar + .icon-bar {
+ margin-top: 4px
+ }
+
+.navbar-nav {
+ margin: 7.5px -15px
+}
+
+ .navbar-nav > li > a {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ line-height: 20px
+ }
+
+@media (max-width:767px) {
+ .navbar-nav .open .dropdown-menu {
+ position: static;
+ float: none;
+ width: auto;
+ margin-top: 0;
+ background-color: transparent;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none
+ }
+
+ .navbar-nav .open .dropdown-menu .dropdown-header, .navbar-nav .open .dropdown-menu > li > a {
+ padding: 5px 15px 5px 25px
+ }
+
+ .navbar-nav .open .dropdown-menu > li > a {
+ line-height: 20px
+ }
+
+ .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-nav .open .dropdown-menu > li > a:hover {
+ background-image: none
+ }
+}
+
+@media (min-width:768px) {
+ .navbar-nav {
+ float: left;
+ margin: 0
+ }
+
+ .navbar-nav > li {
+ float: left
+ }
+
+ .navbar-nav > li > a {
+ padding-top: 15px;
+ padding-bottom: 15px
+ }
+
+ .navbar-form .form-group {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+
+ .navbar-form .form-control {
+ display: inline-block;
+ width: auto;
+ vertical-align: middle
+ }
+
+ .navbar-form .form-control-static {
+ display: inline-block
+ }
+
+ .navbar-form .input-group {
+ display: inline-table;
+ vertical-align: middle
+ }
+
+ .navbar-form .input-group .form-control, .navbar-form .input-group .input-group-addon, .navbar-form .input-group .input-group-btn {
+ width: auto
+ }
+
+ .navbar-form .input-group > .form-control {
+ width: 100%
+ }
+
+ .navbar-form .control-label {
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+
+ .navbar-form .checkbox, .navbar-form .radio {
+ display: inline-block;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+
+ .navbar-form .checkbox label, .navbar-form .radio label {
+ padding-left: 0
+ }
+
+ .navbar-form .checkbox input[type=checkbox], .navbar-form .radio input[type=radio] {
+ position: relative;
+ margin-left: 0
+ }
+
+ .navbar-form .has-feedback .form-control-feedback {
+ top: 0
+ }
+}
+
+.navbar-form {
+ padding: 10px 15px;
+ border-top: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);
+ margin: 8px -15px
+}
+
+@media (max-width:767px) {
+ .navbar-form .form-group {
+ margin-bottom: 5px
+ }
+
+ .navbar-form .form-group:last-child {
+ margin-bottom: 0
+ }
+
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+ color: #777
+ }
+
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover {
+ color: #333;
+ background-color: transparent
+ }
+
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover {
+ color: #555;
+ background-color: #e7e7e7
+ }
+
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover {
+ color: #ccc;
+ background-color: transparent
+ }
+}
+
+@media (min-width:768px) {
+ .navbar-form {
+ width: auto;
+ padding-top: 0;
+ padding-bottom: 0;
+ margin-right: 0;
+ margin-left: 0;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none
+ }
+
+ .navbar-text {
+ float: left;
+ margin-right: 15px;
+ margin-left: 15px
+ }
+}
+
+.navbar-nav > li > .dropdown-menu {
+ margin-top: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0
+}
+
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+ margin-bottom: 0;
+ border-radius: 4px 4px 0 0
+}
+
+.navbar-btn {
+ margin-top: 8px;
+ margin-bottom: 8px
+}
+
+ .navbar-btn.btn-sm {
+ margin-top: 10px;
+ margin-bottom: 10px
+ }
+
+ .navbar-btn.btn-xs {
+ margin-top: 14px;
+ margin-bottom: 14px
+ }
+
+.navbar-text {
+ margin-top: 15px;
+ margin-bottom: 15px
+}
+
+@media (min-width:768px) {
+ .navbar-left {
+ float: left !important
+ }
+
+ .navbar-right {
+ float: right !important;
+ margin-right: -15px
+ }
+
+ .navbar-right ~ .navbar-right {
+ margin-right: 0
+ }
+}
+
+.navbar-default {
+ background-color: #f8f8f8;
+ border-color: #e7e7e7
+}
+
+ .navbar-default .navbar-brand {
+ color: #777
+ }
+
+ .navbar-default .navbar-brand:focus, .navbar-default .navbar-brand:hover {
+ color: #5e5e5e;
+ background-color: transparent
+ }
+
+ .navbar-default .navbar-nav > li > a, .navbar-default .navbar-text {
+ color: #777
+ }
+
+ .navbar-default .navbar-nav > li > a:focus, .navbar-default .navbar-nav > li > a:hover {
+ color: #333;
+ background-color: transparent
+ }
+
+ .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:focus, .navbar-default .navbar-nav > .active > a:hover {
+ color: #555;
+ background-color: #e7e7e7
+ }
+
+ .navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:focus, .navbar-default .navbar-nav > .disabled > a:hover {
+ color: #ccc;
+ background-color: transparent
+ }
+
+ .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:focus, .navbar-default .navbar-nav > .open > a:hover {
+ color: #555;
+ background-color: #e7e7e7
+ }
+
+ .navbar-default .navbar-toggle {
+ border-color: #ddd
+ }
+
+ .navbar-default .navbar-toggle:focus, .navbar-default .navbar-toggle:hover {
+ background-color: #ddd
+ }
+
+ .navbar-default .navbar-toggle .icon-bar {
+ background-color: #888
+ }
+
+ .navbar-default .navbar-collapse, .navbar-default .navbar-form {
+ border-color: #e7e7e7
+ }
+
+ .navbar-default .navbar-link {
+ color: #777
+ }
+
+ .navbar-default .navbar-link:hover {
+ color: #333
+ }
+
+ .navbar-default .btn-link {
+ color: #777
+ }
+
+ .navbar-default .btn-link:focus, .navbar-default .btn-link:hover {
+ color: #333
+ }
+
+ .navbar-default .btn-link[disabled]:focus, .navbar-default .btn-link[disabled]:hover, fieldset[disabled] .navbar-default .btn-link:focus, fieldset[disabled] .navbar-default .btn-link:hover {
+ color: #ccc
+ }
+
+.navbar-inverse {
+ background-color: #222;
+ border-color: #080808
+}
+
+ .navbar-inverse .navbar-brand {
+ color: #9d9d9d
+ }
+
+ .navbar-inverse .navbar-brand:focus, .navbar-inverse .navbar-brand:hover {
+ color: #fff;
+ background-color: transparent
+ }
+
+ .navbar-inverse .navbar-nav > li > a, .navbar-inverse .navbar-text {
+ color: #9d9d9d
+ }
+
+ .navbar-inverse .navbar-nav > li > a:focus, .navbar-inverse .navbar-nav > li > a:hover {
+ color: #fff;
+ background-color: transparent
+ }
+
+ .navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:focus, .navbar-inverse .navbar-nav > .active > a:hover {
+ color: #fff;
+ background-color: #080808
+ }
+
+ .navbar-inverse .navbar-nav > .disabled > a, .navbar-inverse .navbar-nav > .disabled > a:focus, .navbar-inverse .navbar-nav > .disabled > a:hover {
+ color: #444;
+ background-color: transparent
+ }
+
+ .navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:focus, .navbar-inverse .navbar-nav > .open > a:hover {
+ color: #fff;
+ background-color: #080808
+ }
+
+@media (max-width:767px) {
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+ border-color: #080808
+ }
+
+ .navbar-inverse .navbar-nav .open .dropdown-menu .divider {
+ background-color: #080808
+ }
+
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+ color: #9d9d9d
+ }
+
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover {
+ color: #fff;
+ background-color: transparent
+ }
+
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover {
+ color: #fff;
+ background-color: #080808
+ }
+
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover {
+ color: #444;
+ background-color: transparent
+ }
+}
+
+.navbar-inverse .navbar-toggle {
+ border-color: #333
+}
+
+ .navbar-inverse .navbar-toggle:focus, .navbar-inverse .navbar-toggle:hover {
+ background-color: #333
+ }
+
+ .navbar-inverse .navbar-toggle .icon-bar {
+ background-color: #fff
+ }
+
+.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form {
+ border-color: #101010
+}
+
+.navbar-inverse .navbar-link {
+ color: #9d9d9d
+}
+
+ .navbar-inverse .navbar-link:hover {
+ color: #fff
+ }
+
+.navbar-inverse .btn-link {
+ color: #9d9d9d
+}
+
+ .navbar-inverse .btn-link:focus, .navbar-inverse .btn-link:hover {
+ color: #fff
+ }
+
+ .navbar-inverse .btn-link[disabled]:focus, .navbar-inverse .btn-link[disabled]:hover, fieldset[disabled] .navbar-inverse .btn-link:focus, fieldset[disabled] .navbar-inverse .btn-link:hover {
+ color: #444
+ }
+
+.breadcrumb {
+ padding: 8px 15px;
+ margin-bottom: 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ border-radius: 4px
+}
+
+ .breadcrumb > li {
+ display: inline-block
+ }
+
+ .breadcrumb > li + li:before {
+ padding: 0 5px;
+ color: #ccc;
+ content: "/\00a0"
+ }
+
+ .breadcrumb > .active {
+ color: #777
+ }
+
+.pagination {
+ display: inline-block;
+ padding-left: 0;
+ margin: 20px 0;
+ border-radius: 4px
+}
+
+ .pagination > li {
+ display: inline
+ }
+
+ .pagination > li > a, .pagination > li > span {
+ position: relative;
+ float: left;
+ padding: 6px 12px;
+ margin-left: -1px;
+ line-height: 1.42857143;
+ color: #337ab7;
+ text-decoration: none;
+ background-color: #fff;
+ border: 1px solid #ddd
+ }
+
+ .pagination > li > a:focus, .pagination > li > a:hover, .pagination > li > span:focus, .pagination > li > span:hover {
+ z-index: 2;
+ color: #23527c;
+ background-color: #eee;
+ border-color: #ddd
+ }
+
+ .pagination > li:first-child > a, .pagination > li:first-child > span {
+ margin-left: 0;
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px
+ }
+
+ .pagination > li:last-child > a, .pagination > li:last-child > span {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px
+ }
+
+ .pagination > .active > a, .pagination > .active > a:focus, .pagination > .active > a:hover, .pagination > .active > span, .pagination > .active > span:focus, .pagination > .active > span:hover {
+ z-index: 3;
+ color: #fff;
+ cursor: default;
+ background-color: #337ab7;
+ border-color: #337ab7
+ }
+
+ .pagination > .disabled > a, .pagination > .disabled > a:focus, .pagination > .disabled > a:hover, .pagination > .disabled > span, .pagination > .disabled > span:focus, .pagination > .disabled > span:hover {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #fff;
+ border-color: #ddd
+ }
+
+.pagination-lg > li > a, .pagination-lg > li > span {
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333
+}
+
+.pagination-lg > li:first-child > a, .pagination-lg > li:first-child > span {
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px
+}
+
+.pagination-lg > li:last-child > a, .pagination-lg > li:last-child > span {
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px
+}
+
+.pagination-sm > li > a, .pagination-sm > li > span {
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5
+}
+
+.pagination-sm > li:first-child > a, .pagination-sm > li:first-child > span {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px
+}
+
+.pagination-sm > li:last-child > a, .pagination-sm > li:last-child > span {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px
+}
+
+.pager {
+ padding-left: 0;
+ margin: 20px 0;
+ text-align: center;
+ list-style: none
+}
+
+ .pager li {
+ display: inline
+ }
+
+ .pager li > a, .pager li > span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 15px
+ }
+
+ .pager li > a:focus, .pager li > a:hover {
+ text-decoration: none;
+ background-color: #eee
+ }
+
+ .pager .next > a, .pager .next > span {
+ float: right
+ }
+
+ .pager .previous > a, .pager .previous > span {
+ float: left
+ }
+
+ .pager .disabled > a, .pager .disabled > a:focus, .pager .disabled > a:hover, .pager .disabled > span {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #fff
+ }
+
+.label {
+ display: inline;
+ padding: .2em .6em .3em;
+ font-size: 75%;
+ font-weight: 700;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border-radius: .25em
+}
+
+a.label:focus, a.label:hover {
+ color: #fff;
+ text-decoration: none;
+ cursor: pointer
+}
+
+.label:empty {
+ display: none
+}
+
+.btn .label {
+ position: relative;
+ top: -1px
+}
+
+.label-default {
+ background-color: #777
+}
+
+ .label-default[href]:focus, .label-default[href]:hover {
+ background-color: #5e5e5e
+ }
+
+.label-primary {
+ background-color: #337ab7
+}
+
+ .label-primary[href]:focus, .label-primary[href]:hover {
+ background-color: #286090
+ }
+
+.label-success {
+ background-color: #5cb85c
+}
+
+ .label-success[href]:focus, .label-success[href]:hover {
+ background-color: #449d44
+ }
+
+.label-info {
+ background-color: #5bc0de
+}
+
+ .label-info[href]:focus, .label-info[href]:hover {
+ background-color: #31b0d5
+ }
+
+.label-warning {
+ background-color: #f0ad4e
+}
+
+ .label-warning[href]:focus, .label-warning[href]:hover {
+ background-color: #ec971f
+ }
+
+.label-danger {
+ background-color: #d9534f
+}
+
+ .label-danger[href]:focus, .label-danger[href]:hover {
+ background-color: #c9302c
+ }
+
+.badge {
+ display: inline-block;
+ min-width: 10px;
+ padding: 3px 7px;
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ background-color: #777;
+ border-radius: 10px
+}
+
+ .badge:empty {
+ display: none
+ }
+
+.btn .badge {
+ position: relative;
+ top: -1px
+}
+
+.btn-group-xs > .btn .badge, .btn-xs .badge {
+ top: 0;
+ padding: 1px 5px
+}
+
+a.badge:focus, a.badge:hover {
+ color: #fff;
+ text-decoration: none;
+ cursor: pointer
+}
+
+.list-group-item.active > .badge, .nav-pills > .active > a > .badge {
+ color: #337ab7;
+ background-color: #fff
+}
+
+.list-group-item > .badge {
+ float: right
+}
+
+ .list-group-item > .badge + .badge {
+ margin-right: 5px
+ }
+
+.nav-pills > li > a > .badge {
+ margin-left: 3px
+}
+
+.jumbotron {
+ padding-top: 30px;
+ padding-bottom: 30px;
+ margin-bottom: 30px;
+ color: inherit;
+ background-color: #eee
+}
+
+ .jumbotron .h1, .jumbotron h1 {
+ color: inherit
+ }
+
+ .jumbotron p {
+ margin-bottom: 15px;
+ font-size: 21px;
+ font-weight: 200
+ }
+
+ .jumbotron > hr {
+ border-top-color: #d5d5d5
+ }
+
+.container .jumbotron, .container-fluid .jumbotron {
+ padding-right: 15px;
+ padding-left: 15px;
+ border-radius: 6px
+}
+
+.jumbotron .container {
+ max-width: 100%
+}
+
+@media screen and (min-width:768px) {
+ .jumbotron {
+ padding-top: 48px;
+ padding-bottom: 48px
+ }
+
+ .container .jumbotron, .container-fluid .jumbotron {
+ padding-right: 60px;
+ padding-left: 60px
+ }
+
+ .jumbotron .h1, .jumbotron h1 {
+ font-size: 63px
+ }
+}
+
+.thumbnail {
+ display: block;
+ padding: 4px;
+ margin-bottom: 20px;
+ line-height: 1.42857143;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -webkit-transition: border .2s ease-in-out;
+ -o-transition: border .2s ease-in-out;
+ transition: border .2s ease-in-out
+}
+
+ .thumbnail a > img, .thumbnail > img {
+ margin-right: auto;
+ margin-left: auto
+ }
+
+a.thumbnail.active, a.thumbnail:focus, a.thumbnail:hover {
+ border-color: #337ab7
+}
+
+.thumbnail .caption {
+ padding: 9px;
+ color: #333
+}
+
+.alert {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+ border-radius: 4px
+}
+
+ .alert h4 {
+ margin-top: 0;
+ color: inherit
+ }
+
+ .alert .alert-link {
+ font-weight: 700
+ }
+
+ .alert > p, .alert > ul {
+ margin-bottom: 0
+ }
+
+ .alert > p + p {
+ margin-top: 5px
+ }
+
+.alert-dismissable, .alert-dismissible {
+ padding-right: 35px
+}
+
+ .alert-dismissable .close, .alert-dismissible .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ color: inherit
+ }
+
+.alert-success {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6
+}
+
+ .alert-success hr {
+ border-top-color: #c9e2b3
+ }
+
+ .alert-success .alert-link {
+ color: #2b542c
+ }
+
+.alert-info {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1
+}
+
+ .alert-info hr {
+ border-top-color: #a6e1ec
+ }
+
+ .alert-info .alert-link {
+ color: #245269
+ }
+
+.alert-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc
+}
+
+ .alert-warning hr {
+ border-top-color: #f7e1b5
+ }
+
+ .alert-warning .alert-link {
+ color: #66512c
+ }
+
+.alert-danger {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1
+}
+
+ .alert-danger hr {
+ border-top-color: #e4b9c0
+ }
+
+ .alert-danger .alert-link {
+ color: #843534
+ }
+
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0
+ }
+
+ to {
+ background-position: 0 0
+ }
+}
+
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0
+ }
+
+ to {
+ background-position: 0 0
+ }
+}
+
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0
+ }
+
+ to {
+ background-position: 0 0
+ }
+}
+
+.progress {
+ height: 20px;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
+ box-shadow: inset 0 1px 2px rgba(0,0,0,.1)
+}
+
+.progress-bar {
+ float: left;
+ width: 0%;
+ height: 100%;
+ font-size: 12px;
+ line-height: 20px;
+ color: #fff;
+ text-align: center;
+ background-color: #337ab7;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
+ box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
+ -webkit-transition: width .6s;
+ -o-transition: width .6s;
+ transition: width .6s
+}
+
+.progress-bar-striped, .progress-striped .progress-bar {
+ background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ -webkit-background-size: 40px 40px;
+ background-size: 40px 40px
+}
+
+.progress-bar.active, .progress.active .progress-bar {
+ -webkit-animation: 2s linear infinite progress-bar-stripes;
+ -o-animation: 2s linear infinite progress-bar-stripes;
+ animation: 2s linear infinite progress-bar-stripes
+}
+
+.progress-bar-success {
+ background-color: #5cb85c
+}
+
+.progress-striped .progress-bar-success {
+ background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+
+.progress-bar-info {
+ background-color: #5bc0de
+}
+
+.progress-striped .progress-bar-info {
+ background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+
+.progress-bar-warning {
+ background-color: #f0ad4e
+}
+
+.progress-striped .progress-bar-warning {
+ background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+
+.progress-bar-danger {
+ background-color: #d9534f
+}
+
+.progress-striped .progress-bar-danger {
+ background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+ background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+
+.media {
+ margin-top: 15px
+}
+
+ .media:first-child {
+ margin-top: 0
+ }
+
+.media, .media-body {
+ overflow: hidden;
+ zoom: 1
+}
+
+.media-body {
+ width: 10000px
+}
+
+.media-object {
+ display: block
+}
+
+ .media-object.img-thumbnail {
+ max-width: none
+ }
+
+.media-right, .media > .pull-right {
+ padding-left: 10px
+}
+
+.media-left, .media > .pull-left {
+ padding-right: 10px
+}
+
+.media-body, .media-left, .media-right {
+ display: table-cell;
+ vertical-align: top
+}
+
+.media-middle {
+ vertical-align: middle
+}
+
+.media-bottom {
+ vertical-align: bottom
+}
+
+.media-heading {
+ margin-top: 0;
+ margin-bottom: 5px
+}
+
+.media-list {
+ padding-left: 0;
+ list-style: none
+}
+
+.list-group {
+ padding-left: 0;
+ margin-bottom: 20px
+}
+
+.list-group-item {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+ margin-bottom: -1px;
+ background-color: #fff;
+ border: 1px solid #ddd
+}
+
+ .list-group-item:first-child {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px
+ }
+
+ .list-group-item:last-child {
+ margin-bottom: 0;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px
+ }
+
+ .list-group-item.disabled, .list-group-item.disabled:focus, .list-group-item.disabled:hover {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #eee
+ }
+
+ .list-group-item.disabled .list-group-item-heading, .list-group-item.disabled:focus .list-group-item-heading, .list-group-item.disabled:hover .list-group-item-heading {
+ color: inherit
+ }
+
+ .list-group-item.disabled .list-group-item-text, .list-group-item.disabled:focus .list-group-item-text, .list-group-item.disabled:hover .list-group-item-text {
+ color: #777
+ }
+
+ .list-group-item.active, .list-group-item.active:focus, .list-group-item.active:hover {
+ z-index: 2;
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #337ab7
+ }
+
+ .list-group-item.active .list-group-item-heading, .list-group-item.active .list-group-item-heading > .small, .list-group-item.active .list-group-item-heading > small, .list-group-item.active:focus .list-group-item-heading, .list-group-item.active:focus .list-group-item-heading > .small, .list-group-item.active:focus .list-group-item-heading > small, .list-group-item.active:hover .list-group-item-heading, .list-group-item.active:hover .list-group-item-heading > .small, .list-group-item.active:hover .list-group-item-heading > small {
+ color: inherit
+ }
+
+ .list-group-item.active .list-group-item-text, .list-group-item.active:focus .list-group-item-text, .list-group-item.active:hover .list-group-item-text {
+ color: #c7ddef
+ }
+
+a.list-group-item, button.list-group-item {
+ color: #555
+}
+
+ a.list-group-item .list-group-item-heading, button.list-group-item .list-group-item-heading {
+ color: #333
+ }
+
+ a.list-group-item:focus, a.list-group-item:hover, button.list-group-item:focus, button.list-group-item:hover {
+ color: #555;
+ text-decoration: none;
+ background-color: #f5f5f5
+ }
+
+button.list-group-item {
+ width: 100%;
+ text-align: left
+}
+
+.list-group-item-success {
+ color: #3c763d;
+ background-color: #dff0d8
+}
+
+a.list-group-item-success, button.list-group-item-success {
+ color: #3c763d
+}
+
+ a.list-group-item-success .list-group-item-heading, button.list-group-item-success .list-group-item-heading {
+ color: inherit
+ }
+
+ a.list-group-item-success:focus, a.list-group-item-success:hover, button.list-group-item-success:focus, button.list-group-item-success:hover {
+ color: #3c763d;
+ background-color: #d0e9c6
+ }
+
+ a.list-group-item-success.active, a.list-group-item-success.active:focus, a.list-group-item-success.active:hover, button.list-group-item-success.active, button.list-group-item-success.active:focus, button.list-group-item-success.active:hover {
+ color: #fff;
+ background-color: #3c763d;
+ border-color: #3c763d
+ }
+
+.list-group-item-info {
+ color: #31708f;
+ background-color: #d9edf7
+}
+
+a.list-group-item-info, button.list-group-item-info {
+ color: #31708f
+}
+
+ a.list-group-item-info .list-group-item-heading, button.list-group-item-info .list-group-item-heading {
+ color: inherit
+ }
+
+ a.list-group-item-info:focus, a.list-group-item-info:hover, button.list-group-item-info:focus, button.list-group-item-info:hover {
+ color: #31708f;
+ background-color: #c4e3f3
+ }
+
+ a.list-group-item-info.active, a.list-group-item-info.active:focus, a.list-group-item-info.active:hover, button.list-group-item-info.active, button.list-group-item-info.active:focus, button.list-group-item-info.active:hover {
+ color: #fff;
+ background-color: #31708f;
+ border-color: #31708f
+ }
+
+.list-group-item-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3
+}
+
+a.list-group-item-warning, button.list-group-item-warning {
+ color: #8a6d3b
+}
+
+ a.list-group-item-warning .list-group-item-heading, button.list-group-item-warning .list-group-item-heading {
+ color: inherit
+ }
+
+ a.list-group-item-warning:focus, a.list-group-item-warning:hover, button.list-group-item-warning:focus, button.list-group-item-warning:hover {
+ color: #8a6d3b;
+ background-color: #faf2cc
+ }
+
+ a.list-group-item-warning.active, a.list-group-item-warning.active:focus, a.list-group-item-warning.active:hover, button.list-group-item-warning.active, button.list-group-item-warning.active:focus, button.list-group-item-warning.active:hover {
+ color: #fff;
+ background-color: #8a6d3b;
+ border-color: #8a6d3b
+ }
+
+.list-group-item-danger {
+ color: #a94442;
+ background-color: #f2dede
+}
+
+a.list-group-item-danger, button.list-group-item-danger {
+ color: #a94442
+}
+
+ a.list-group-item-danger .list-group-item-heading, button.list-group-item-danger .list-group-item-heading {
+ color: inherit
+ }
+
+ a.list-group-item-danger:focus, a.list-group-item-danger:hover, button.list-group-item-danger:focus, button.list-group-item-danger:hover {
+ color: #a94442;
+ background-color: #ebcccc
+ }
+
+ a.list-group-item-danger.active, a.list-group-item-danger.active:focus, a.list-group-item-danger.active:hover, button.list-group-item-danger.active, button.list-group-item-danger.active:focus, button.list-group-item-danger.active:hover {
+ color: #fff;
+ background-color: #a94442;
+ border-color: #a94442
+ }
+
+.list-group-item-heading {
+ margin-top: 0;
+ margin-bottom: 5px
+}
+
+.list-group-item-text {
+ margin-bottom: 0;
+ line-height: 1.3
+}
+
+.panel {
+ margin-bottom: 20px;
+ background-color: #fff;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.05);
+ box-shadow: 0 1px 1px rgba(0,0,0,.05)
+}
+
+.panel-body {
+ padding: 15px
+}
+
+.panel-heading {
+ padding: 10px 15px;
+ border-bottom: 1px solid transparent;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px
+}
+
+ .panel-heading > .dropdown .dropdown-toggle {
+ color: inherit
+ }
+
+.panel-title {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-size: 16px;
+ color: inherit
+}
+
+ .panel-title > .small, .panel-title > .small > a, .panel-title > a, .panel-title > small, .panel-title > small > a {
+ color: inherit
+ }
+
+.panel-footer {
+ padding: 10px 15px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px
+}
+
+.panel > .list-group, .panel > .panel-collapse > .list-group {
+ margin-bottom: 0
+}
+
+ .panel > .list-group .list-group-item, .panel > .panel-collapse > .list-group .list-group-item {
+ border-width: 1px 0;
+ border-radius: 0
+ }
+
+ .panel > .list-group:first-child .list-group-item:first-child, .panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
+ border-top: 0;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px
+ }
+
+ .panel > .list-group:last-child .list-group-item:last-child, .panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {
+ border-bottom: 0;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px
+ }
+
+.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0
+}
+
+.list-group + .panel-footer, .panel-heading + .list-group .list-group-item:first-child {
+ border-top-width: 0
+}
+
+.panel > .panel-collapse > .table, .panel > .table, .panel > .table-responsive > .table {
+ margin-bottom: 0
+}
+
+ .panel > .panel-collapse > .table caption, .panel > .table caption, .panel > .table-responsive > .table caption {
+ padding-right: 15px;
+ padding-left: 15px
+ }
+
+ .panel > .table-responsive:first-child > .table:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, .panel > .table:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child, .panel > .table:first-child > thead:first-child > tr:first-child {
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px
+ }
+
+ .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table:first-child > thead:first-child > tr:first-child th:first-child {
+ border-top-left-radius: 3px
+ }
+
+ .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table:first-child > thead:first-child > tr:first-child th:last-child {
+ border-top-right-radius: 3px
+ }
+
+ .panel > .table-responsive:last-child > .table:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child, .panel > .table:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child {
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px
+ }
+
+ .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
+ border-bottom-left-radius: 3px
+ }
+
+ .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
+ border-bottom-right-radius: 3px
+ }
+
+ .panel > .panel-body + .table, .panel > .panel-body + .table-responsive, .panel > .table + .panel-body, .panel > .table-responsive + .panel-body {
+ border-top: 1px solid #ddd
+ }
+
+ .panel > .table > tbody:first-child > tr:first-child td, .panel > .table > tbody:first-child > tr:first-child th {
+ border-top: 0
+ }
+
+.panel > .table-bordered, .panel > .table-responsive > .table-bordered {
+ border: 0
+}
+
+ .panel > .table-bordered > tbody > tr > td:first-child, .panel > .table-bordered > tbody > tr > th:first-child, .panel > .table-bordered > tfoot > tr > td:first-child, .panel > .table-bordered > tfoot > tr > th:first-child, .panel > .table-bordered > thead > tr > td:first-child, .panel > .table-bordered > thead > tr > th:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, .panel > .table-responsive > .table-bordered > thead > tr > td:first-child, .panel > .table-responsive > .table-bordered > thead > tr > th:first-child {
+ border-left: 0
+ }
+
+ .panel > .table-bordered > tbody > tr > td:last-child, .panel > .table-bordered > tbody > tr > th:last-child, .panel > .table-bordered > tfoot > tr > td:last-child, .panel > .table-bordered > tfoot > tr > th:last-child, .panel > .table-bordered > thead > tr > td:last-child, .panel > .table-bordered > thead > tr > th:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, .panel > .table-responsive > .table-bordered > thead > tr > td:last-child, .panel > .table-responsive > .table-bordered > thead > tr > th:last-child {
+ border-right: 0
+ }
+
+ .panel > .table-bordered > tbody > tr:first-child > td, .panel > .table-bordered > tbody > tr:first-child > th, .panel > .table-bordered > tbody > tr:last-child > td, .panel > .table-bordered > tbody > tr:last-child > th, .panel > .table-bordered > tfoot > tr:last-child > td, .panel > .table-bordered > tfoot > tr:last-child > th, .panel > .table-bordered > thead > tr:first-child > td, .panel > .table-bordered > thead > tr:first-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th, .panel > .table-responsive > .table-bordered > thead > tr:first-child > td, .panel > .table-responsive > .table-bordered > thead > tr:first-child > th {
+ border-bottom: 0
+ }
+
+.panel > .table-responsive {
+ margin-bottom: 0;
+ border: 0
+}
+
+.panel-group {
+ margin-bottom: 20px
+}
+
+ .panel-group .panel {
+ margin-bottom: 0;
+ border-radius: 4px
+ }
+
+ .panel-group .panel + .panel {
+ margin-top: 5px
+ }
+
+ .panel-group .panel-heading {
+ border-bottom: 0
+ }
+
+ .panel-group .panel-heading + .panel-collapse > .list-group, .panel-group .panel-heading + .panel-collapse > .panel-body {
+ border-top: 1px solid #ddd
+ }
+
+ .panel-group .panel-footer {
+ border-top: 0
+ }
+
+ .panel-group .panel-footer + .panel-collapse .panel-body {
+ border-bottom: 1px solid #ddd
+ }
+
+.panel-default {
+ border-color: #ddd
+}
+
+ .panel-default > .panel-heading {
+ color: #333;
+ background-color: #f5f5f5;
+ border-color: #ddd
+ }
+
+ .panel-default > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #ddd
+ }
+
+ .panel-default > .panel-heading .badge {
+ color: #f5f5f5;
+ background-color: #333
+ }
+
+ .panel-default > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #ddd
+ }
+
+.panel-primary {
+ border-color: #337ab7
+}
+
+ .panel-primary > .panel-heading {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #337ab7
+ }
+
+ .panel-primary > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #337ab7
+ }
+
+ .panel-primary > .panel-heading .badge {
+ color: #337ab7;
+ background-color: #fff
+ }
+
+ .panel-primary > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #337ab7
+ }
+
+.panel-success {
+ border-color: #d6e9c6
+}
+
+ .panel-success > .panel-heading {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6
+ }
+
+ .panel-success > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #d6e9c6
+ }
+
+ .panel-success > .panel-heading .badge {
+ color: #dff0d8;
+ background-color: #3c763d
+ }
+
+ .panel-success > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #d6e9c6
+ }
+
+.panel-info {
+ border-color: #bce8f1
+}
+
+ .panel-info > .panel-heading {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1
+ }
+
+ .panel-info > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #bce8f1
+ }
+
+ .panel-info > .panel-heading .badge {
+ color: #d9edf7;
+ background-color: #31708f
+ }
+
+ .panel-info > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #bce8f1
+ }
+
+.panel-warning {
+ border-color: #faebcc
+}
+
+ .panel-warning > .panel-heading {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc
+ }
+
+ .panel-warning > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #faebcc
+ }
+
+ .panel-warning > .panel-heading .badge {
+ color: #fcf8e3;
+ background-color: #8a6d3b
+ }
+
+ .panel-warning > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #faebcc
+ }
+
+.panel-danger {
+ border-color: #ebccd1
+}
+
+ .panel-danger > .panel-heading {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1
+ }
+
+ .panel-danger > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #ebccd1
+ }
+
+ .panel-danger > .panel-heading .badge {
+ color: #f2dede;
+ background-color: #a94442
+ }
+
+ .panel-danger > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #ebccd1
+ }
+
+.embed-responsive {
+ position: relative;
+ display: block;
+ height: 0;
+ padding: 0;
+ overflow: hidden
+}
+
+ .embed-responsive .embed-responsive-item, .embed-responsive embed, .embed-responsive iframe, .embed-responsive object, .embed-responsive video {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 0
+ }
+
+.embed-responsive-16by9 {
+ padding-bottom: 56.25%
+}
+
+.embed-responsive-4by3 {
+ padding-bottom: 75%
+}
+
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.05);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.05)
+}
+
+ .well blockquote {
+ border-color: rgba(0,0,0,.15)
+ }
+
+.well-lg {
+ padding: 24px;
+ border-radius: 6px
+}
+
+.well-sm {
+ padding: 9px;
+ border-radius: 3px
+}
+
+.close {
+ float: right;
+ font-size: 21px;
+ font-weight: 700;
+ line-height: 1;
+ color: #000;
+ text-shadow: 0 1px 0 #fff;
+ opacity: .2
+}
+
+ .close:focus, .close:hover {
+ color: #000;
+ text-decoration: none;
+ cursor: pointer;
+ opacity: .5
+ }
+
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: 0 0;
+ border: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none
+}
+
+.modal-open {
+ overflow: hidden
+}
+
+.modal {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1050;
+ display: none;
+ overflow: hidden;
+ -webkit-overflow-scrolling: touch;
+ outline: 0
+}
+
+ .modal.fade .modal-dialog {
+ -webkit-transform: translate(0,-25%);
+ -ms-transform: translate(0,-25%);
+ -o-transform: translate(0,-25%);
+ transform: translate(0,-25%);
+ -webkit-transition: -webkit-transform .3s ease-out;
+ -o-transition: -o-transform .3s ease-out;
+ transition: transform .3s ease-out;
+ transition: transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out
+ }
+
+ .modal.in .modal-dialog {
+ -webkit-transform: translate(0,0);
+ -ms-transform: translate(0,0);
+ -o-transform: translate(0,0);
+ transform: translate(0,0)
+ }
+
+.modal-open .modal {
+ overflow-x: hidden;
+ overflow-y: auto
+}
+
+.modal-dialog {
+ position: relative;
+ width: auto;
+ margin: 10px
+}
+
+.modal-content {
+ position: relative;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid rgba(0,0,0,.2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 3px 9px rgba(0,0,0,.5);
+ box-shadow: 0 3px 9px rgba(0,0,0,.5);
+ outline: 0
+}
+
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ background-color: #000
+}
+
+ .modal-backdrop.fade {
+ opacity: 0
+ }
+
+ .modal-backdrop.in {
+ opacity: .5
+ }
+
+.modal-header {
+ padding: 15px;
+ border-bottom: 1px solid #e5e5e5
+}
+
+ .modal-header .close {
+ margin-top: -2px
+ }
+
+.modal-title {
+ margin: 0;
+ line-height: 1.42857143
+}
+
+.modal-body {
+ position: relative;
+ padding: 15px
+}
+
+.modal-footer {
+ padding: 15px;
+ text-align: right;
+ border-top: 1px solid #e5e5e5
+}
+
+ .modal-footer .btn + .btn {
+ margin-bottom: 0;
+ margin-left: 5px
+ }
+
+ .modal-footer .btn-group .btn + .btn {
+ margin-left: -1px
+ }
+
+ .modal-footer .btn-block + .btn-block {
+ margin-left: 0
+ }
+
+.modal-scrollbar-measure {
+ position: absolute;
+ top: -9999px;
+ width: 50px;
+ height: 50px;
+ overflow: scroll
+}
+
+@media (min-width:768px) {
+ .modal-dialog {
+ width: 600px;
+ margin: 30px auto
+ }
+
+ .modal-content {
+ -webkit-box-shadow: 0 5px 15px rgba(0,0,0,.5);
+ box-shadow: 0 5px 15px rgba(0,0,0,.5)
+ }
+
+ .modal-sm {
+ width: 300px
+ }
+}
+
+@media (min-width:992px) {
+ .modal-lg {
+ width: 900px
+ }
+}
+
+.tooltip {
+ position: absolute;
+ z-index: 1070;
+ display: block;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 1.42857143;
+ line-break: auto;
+ text-align: left;
+ text-align: start;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: normal;
+ word-break: normal;
+ word-spacing: normal;
+ word-wrap: normal;
+ white-space: normal;
+ font-size: 12px;
+ opacity: 0
+}
+
+ .tooltip.in {
+ opacity: .9
+ }
+
+ .tooltip.top {
+ padding: 5px 0;
+ margin-top: -3px
+ }
+
+ .tooltip.right {
+ padding: 0 5px;
+ margin-left: 3px
+ }
+
+ .tooltip.bottom {
+ padding: 5px 0;
+ margin-top: 3px
+ }
+
+ .tooltip.left {
+ padding: 0 5px;
+ margin-left: -3px
+ }
+
+ .tooltip.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000
+ }
+
+ .tooltip.top-left .tooltip-arrow {
+ right: 5px;
+ bottom: 0;
+ margin-bottom: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000
+ }
+
+ .tooltip.top-right .tooltip-arrow {
+ bottom: 0;
+ left: 5px;
+ margin-bottom: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000
+ }
+
+ .tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-width: 5px 5px 5px 0;
+ border-right-color: #000
+ }
+
+ .tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-width: 5px 0 5px 5px;
+ border-left-color: #000
+ }
+
+ .tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000
+ }
+
+ .tooltip.bottom-left .tooltip-arrow {
+ top: 0;
+ right: 5px;
+ margin-top: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000
+ }
+
+ .tooltip.bottom-right .tooltip-arrow {
+ top: 0;
+ left: 5px;
+ margin-top: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000
+ }
+
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #fff;
+ text-align: center;
+ background-color: #000;
+ border-radius: 4px
+}
+
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid
+}
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1060;
+ display: none;
+ max-width: 276px;
+ padding: 1px;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 1.42857143;
+ line-break: auto;
+ text-align: left;
+ text-align: start;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: normal;
+ word-break: normal;
+ word-spacing: normal;
+ word-wrap: normal;
+ white-space: normal;
+ font-size: 14px;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid rgba(0,0,0,.2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ box-shadow: 0 5px 10px rgba(0,0,0,.2)
+}
+
+ .popover.top {
+ margin-top: -10px
+ }
+
+ .popover.right {
+ margin-left: 10px
+ }
+
+ .popover.bottom {
+ margin-top: 10px
+ }
+
+ .popover.left {
+ margin-left: -10px
+ }
+
+ .popover > .arrow {
+ border-width: 11px
+ }
+
+ .popover > .arrow, .popover > .arrow:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid
+ }
+
+ .popover > .arrow:after {
+ content: "";
+ border-width: 10px
+ }
+
+ .popover.top > .arrow {
+ bottom: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-color: rgba(0,0,0,.25);
+ border-bottom-width: 0
+ }
+
+ .popover.top > .arrow:after {
+ bottom: 1px;
+ margin-left: -10px;
+ content: " ";
+ border-top-color: #fff;
+ border-bottom-width: 0
+ }
+
+ .popover.right > .arrow {
+ top: 50%;
+ left: -11px;
+ margin-top: -11px;
+ border-right-color: rgba(0,0,0,.25);
+ border-left-width: 0
+ }
+
+ .popover.right > .arrow:after {
+ bottom: -10px;
+ left: 1px;
+ content: " ";
+ border-right-color: #fff;
+ border-left-width: 0
+ }
+
+ .popover.bottom > .arrow {
+ top: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-width: 0;
+ border-bottom-color: rgba(0,0,0,.25)
+ }
+
+ .popover.bottom > .arrow:after {
+ top: 1px;
+ margin-left: -10px;
+ content: " ";
+ border-top-width: 0;
+ border-bottom-color: #fff
+ }
+
+ .popover.left > .arrow {
+ top: 50%;
+ right: -11px;
+ margin-top: -11px;
+ border-right-width: 0;
+ border-left-color: rgba(0,0,0,.25)
+ }
+
+ .popover.left > .arrow:after {
+ right: 1px;
+ bottom: -10px;
+ content: " ";
+ border-right-width: 0;
+ border-left-color: #fff
+ }
+
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ border-radius: 5px 5px 0 0
+}
+
+.popover-content {
+ padding: 9px 14px
+}
+
+.carousel {
+ position: relative
+}
+
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden
+}
+
+ .carousel-inner > .item {
+ position: relative;
+ display: none;
+ -webkit-transition: left .6s ease-in-out;
+ -o-transition: left .6s ease-in-out;
+ transition: left .6s ease-in-out
+ }
+
+ .carousel-inner > .item > a > img, .carousel-inner > .item > img {
+ line-height: 1
+ }
+
+@media all and (transform-3d),(-webkit-transform-3d) {
+ .carousel-inner > .item {
+ -webkit-transition: -webkit-transform .6s ease-in-out;
+ -o-transition: -o-transform .6s ease-in-out;
+ transition: transform .6s ease-in-out;
+ transition: transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-perspective: 1000px;
+ perspective: 1000px
+ }
+
+ .carousel-inner > .item.active.right, .carousel-inner > .item.next {
+ -webkit-transform: translate3d(100%,0,0);
+ transform: translate3d(100%,0,0);
+ left: 0
+ }
+
+ .carousel-inner > .item.active.left, .carousel-inner > .item.prev {
+ -webkit-transform: translate3d(-100%,0,0);
+ transform: translate3d(-100%,0,0);
+ left: 0
+ }
+
+ .carousel-inner > .item.active, .carousel-inner > .item.next.left, .carousel-inner > .item.prev.right {
+ -webkit-transform: translate3d(0,0,0);
+ transform: translate3d(0,0,0);
+ left: 0
+ }
+}
+
+.carousel-inner > .active, .carousel-inner > .next, .carousel-inner > .prev {
+ display: block
+}
+
+.carousel-inner > .active {
+ left: 0
+}
+
+.carousel-inner > .next, .carousel-inner > .prev {
+ position: absolute;
+ top: 0;
+ width: 100%
+}
+
+.carousel-inner > .next {
+ left: 100%
+}
+
+.carousel-inner > .prev {
+ left: -100%
+}
+
+ .carousel-inner > .next.left, .carousel-inner > .prev.right {
+ left: 0
+ }
+
+.carousel-inner > .active.left {
+ left: -100%
+}
+
+.carousel-inner > .active.right {
+ left: 100%
+}
+
+.carousel-control {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 15%;
+ font-size: 20px;
+ color: #fff;
+ text-align: center;
+ text-shadow: 0 1px 2px rgba(0,0,0,.6);
+ background-color: rgba(0,0,0,0);
+ opacity: .5
+}
+
+ .carousel-control.left {
+ background-image: -webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+ background-image: -o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+ background-image: -webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));
+ background-image: linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+ background-repeat: repeat-x
+ }
+
+ .carousel-control.right {
+ right: 0;
+ left: auto;
+ background-image: -webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+ background-image: -o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+ background-image: -webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));
+ background-image: linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+ background-repeat: repeat-x
+ }
+
+ .carousel-control:focus, .carousel-control:hover {
+ color: #fff;
+ text-decoration: none;
+ outline: 0;
+ opacity: .9
+ }
+
+ .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next, .carousel-control .icon-prev {
+ position: absolute;
+ top: 50%;
+ z-index: 5;
+ display: inline-block;
+ margin-top: -10px
+ }
+
+ .carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev {
+ left: 50%;
+ margin-left: -10px
+ }
+
+ .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next {
+ right: 50%;
+ margin-right: -10px
+ }
+
+ .carousel-control .icon-next, .carousel-control .icon-prev {
+ width: 20px;
+ height: 20px;
+ font-family: serif;
+ line-height: 1
+ }
+
+ .carousel-control .icon-prev:before {
+ content: "\2039"
+ }
+
+ .carousel-control .icon-next:before {
+ content: "\203a"
+ }
+
+.carousel-indicators {
+ position: absolute;
+ bottom: 10px;
+ left: 50%;
+ z-index: 15;
+ width: 60%;
+ padding-left: 0;
+ margin-left: -30%;
+ text-align: center;
+ list-style: none
+}
+
+ .carousel-indicators li {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ margin: 1px;
+ text-indent: -999px;
+ cursor: pointer;
+ background-color: rgba(0,0,0,0);
+ border: 1px solid #fff;
+ border-radius: 10px
+ }
+
+ .carousel-indicators .active {
+ width: 12px;
+ height: 12px;
+ margin: 0;
+ background-color: #fff
+ }
+
+.carousel-caption {
+ position: absolute;
+ right: 15%;
+ bottom: 20px;
+ left: 15%;
+ z-index: 10;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ color: #fff;
+ text-align: center;
+ text-shadow: 0 1px 2px rgba(0,0,0,.6)
+}
+
+ .carousel-caption .btn {
+ text-shadow: none
+ }
+
+@media screen and (min-width:768px) {
+ .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next, .carousel-control .icon-prev {
+ width: 30px;
+ height: 30px;
+ margin-top: -10px;
+ font-size: 30px
+ }
+
+ .carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev {
+ margin-left: -10px
+ }
+
+ .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next {
+ margin-right: -10px
+ }
+
+ .carousel-caption {
+ right: 20%;
+ left: 20%;
+ padding-bottom: 30px
+ }
+
+ .carousel-indicators {
+ bottom: 20px
+ }
+}
+
+.btn-group-vertical > .btn-group:after, .btn-group-vertical > .btn-group:before, .btn-toolbar:after, .btn-toolbar:before, .clearfix:after, .clearfix:before, .container-fluid:after, .container-fluid:before, .container:after, .container:before, .dl-horizontal dd:after, .dl-horizontal dd:before, .form-horizontal .form-group:after, .form-horizontal .form-group:before, .modal-footer:after, .modal-footer:before, .modal-header:after, .modal-header:before, .nav:after, .nav:before, .navbar-collapse:after, .navbar-collapse:before, .navbar-header:after, .navbar-header:before, .navbar:after, .navbar:before, .pager:after, .pager:before, .panel-body:after, .panel-body:before, .row:after, .row:before {
+ display: table;
+ content: " "
+}
+
+.btn-group-vertical > .btn-group:after, .btn-toolbar:after, .clearfix:after, .container-fluid:after, .container:after, .dl-horizontal dd:after, .form-horizontal .form-group:after, .modal-footer:after, .modal-header:after, .nav:after, .navbar-collapse:after, .navbar-header:after, .navbar:after, .pager:after, .panel-body:after, .row:after {
+ clear: both
+}
+
+.center-block {
+ display: block;
+ margin-right: auto;
+ margin-left: auto
+}
+
+.pull-right {
+ float: right !important
+}
+
+.pull-left {
+ float: left !important
+}
+
+.hide {
+ display: none !important
+}
+
+.show {
+ display: block !important
+}
+
+.invisible {
+ visibility: hidden
+}
+
+.text-hide {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0
+}
+
+.hidden {
+ display: none !important
+}
+
+.affix {
+ position: fixed
+}
+
+@-ms-viewport {
+ width: device-width
+}
+
+.visible-lg, .visible-lg-block, .visible-lg-inline, .visible-lg-inline-block, .visible-md, .visible-md-block, .visible-md-inline, .visible-md-inline-block, .visible-sm, .visible-sm-block, .visible-sm-inline, .visible-sm-inline-block, .visible-xs, .visible-xs-block, .visible-xs-inline, .visible-xs-inline-block {
+ display: none !important
+}
+
+@media (max-width:767px) {
+ .visible-xs {
+ display: block !important
+ }
+
+ table.visible-xs {
+ display: table !important
+ }
+
+ tr.visible-xs {
+ display: table-row !important
+ }
+
+ td.visible-xs, th.visible-xs {
+ display: table-cell !important
+ }
+
+ .visible-xs-block {
+ display: block !important
+ }
+
+ .visible-xs-inline {
+ display: inline !important
+ }
+
+ .visible-xs-inline-block {
+ display: inline-block !important
+ }
+}
+
+@media (min-width:768px) and (max-width:991px) {
+ .visible-sm {
+ display: block !important
+ }
+
+ table.visible-sm {
+ display: table !important
+ }
+
+ tr.visible-sm {
+ display: table-row !important
+ }
+
+ td.visible-sm, th.visible-sm {
+ display: table-cell !important
+ }
+
+ .visible-sm-block {
+ display: block !important
+ }
+
+ .visible-sm-inline {
+ display: inline !important
+ }
+
+ .visible-sm-inline-block {
+ display: inline-block !important
+ }
+}
+
+@media (min-width:992px) and (max-width:1199px) {
+ .visible-md {
+ display: block !important
+ }
+
+ table.visible-md {
+ display: table !important
+ }
+
+ tr.visible-md {
+ display: table-row !important
+ }
+
+ td.visible-md, th.visible-md {
+ display: table-cell !important
+ }
+
+ .visible-md-block {
+ display: block !important
+ }
+
+ .visible-md-inline {
+ display: inline !important
+ }
+
+ .visible-md-inline-block {
+ display: inline-block !important
+ }
+}
+
+@media (min-width:1200px) {
+ .visible-lg {
+ display: block !important
+ }
+
+ table.visible-lg {
+ display: table !important
+ }
+
+ tr.visible-lg {
+ display: table-row !important
+ }
+
+ td.visible-lg, th.visible-lg {
+ display: table-cell !important
+ }
+
+ .visible-lg-block {
+ display: block !important
+ }
+
+ .visible-lg-inline {
+ display: inline !important
+ }
+
+ .visible-lg-inline-block {
+ display: inline-block !important
+ }
+
+ .hidden-lg {
+ display: none !important
+ }
+}
+
+@media (max-width:767px) {
+ .hidden-xs {
+ display: none !important
+ }
+}
+
+@media (min-width:768px) and (max-width:991px) {
+ .hidden-sm {
+ display: none !important
+ }
+}
+
+@media (min-width:992px) and (max-width:1199px) {
+ .hidden-md {
+ display: none !important
+ }
+}
+
+.visible-print {
+ display: none !important
+}
+
+@media print {
+ .visible-print {
+ display: block !important
+ }
+
+ table.visible-print {
+ display: table !important
+ }
+
+ tr.visible-print {
+ display: table-row !important
+ }
+
+ td.visible-print, th.visible-print {
+ display: table-cell !important
+ }
+}
+
+.visible-print-block {
+ display: none !important
+}
+
+@media print {
+ .visible-print-block {
+ display: block !important
+ }
+}
+
+.visible-print-inline {
+ display: none !important
+}
+
+@media print {
+ .visible-print-inline {
+ display: inline !important
+ }
+}
+
+.visible-print-inline-block {
+ display: none !important
+}
+
+@media print {
+ .visible-print-inline-block {
+ display: inline-block !important
+ }
+
+ .hidden-print {
+ display: none !important
+ }
+}
+
+.hljs {
+ display: block;
+ background: #fff;
+ padding: .5em;
+ color: #333;
+ overflow-x: auto
+}
+
+.hljs-comment, .hljs-meta {
+ color: #969896
+}
+
+.hljs-emphasis, .hljs-quote, .hljs-string, .hljs-strong, .hljs-template-variable, .hljs-variable {
+ color: #df5000
+}
+
+.hljs-keyword, .hljs-selector-tag, .hljs-type {
+ color: #a71d5d
+}
+
+.hljs-attribute, .hljs-bullet, .hljs-literal, .hljs-symbol {
+ color: #0086b3
+}
+
+.hljs-name, .hljs-section {
+ color: #63a35c
+}
+
+.hljs-tag {
+ color: #333
+}
+
+.hljs-attr, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id, .hljs-selector-pseudo, .hljs-title {
+ color: #795da3
+}
+
+.hljs-addition {
+ color: #55a532;
+ background-color: #eaffea
+}
+
+.hljs-deletion {
+ color: #bd2c00;
+ background-color: #ffecec
+}
+
+.hljs-link {
+ text-decoration: underline
+}
+
+/* COLOR VARIABLES*/
+:root {
+ --header-bg-color: #212121;
+ --header-ft-color: #fefefe;
+ --highlight-light: rgb(9, 105, 218);
+ --highlight-dark: rgb(9, 105, 218);
+ --accent-dim: #e0e0e0;
+ --accent-super-dim: #f3f3f3;
+ --font-color: rgb(31, 35, 40);
+ --toc-font-color: #222222;
+ --tab-hover-color: #444444;
+ --card-box-shadow: 0 1px 2px 0 rgba(61, 65, 68, 0.06), 0 1px 3px 1px rgba(61, 65, 68, 0.16);
+ --search-box-shadow: 0 1px 2px 0 rgba(41, 45, 48, 0.36), 0 1px 3px 1px rgba(41, 45, 48, 0.46);
+ --transition: 350ms;
+}
+
+
+
+body {
+ background: white;
+ color: var(--font-color);
+ font-family: "Roboto", sans-serif;
+ line-height: 1.5;
+ font-size: 15px !important;
+ /* -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;*/
+ word-wrap: break-word;
+}
+
+ body div, body p, body ul {
+ color: var(--font-color);
+ font-family: "Roboto", sans-serif !important;
+ line-height: 1.5 !important;
+ font-size: 15px !important;
+ /* -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;*/
+ word-wrap: break-word;
+ }
+
+body, p, blockquote, ul, ol, dl, table, pre, code, tr {
+ color: var(--font-color);
+}
+
+/* HIGHLIGHT COLOR */
+p {
+ margin: 0 0 12px;
+ /*text-align: justify;*/
+}
+
+button,
+a {
+ color: var(--highlight-dark);
+ cursor: pointer;
+}
+
+ button:hover,
+ button:focus,
+ a:hover,
+ a:focus {
+ color: var(--highlight-light);
+ text-decoration: none;
+ }
+
+.toc .nav > li.active > a {
+ color: var(--highlight-dark);
+}
+
+ .toc .nav > li.active > a:hover,
+ .toc .nav > li.active > a:focus {
+ color: var(--highlight-light);
+ }
+
+/*.pagination > .active > a {
+ background-color: var(--header-bg-color);
+ border-color: var(--header-bg-color);
+}
+
+.pagination > .active > a,
+.pagination > .active > a:focus,
+.pagination > .active > a:hover,
+.pagination > .active > span,
+.pagination > .active > span:focus,
+.pagination > .active > span:hover {
+ background-color: var(--highlight-light);
+ border-color: var(--highlight-light);
+}
+*/
+/* HEADINGS */
+
+h1 {
+ font-weight: 300;
+ font-size: 34px;
+ color: #000000b0;
+}
+
+h2 {
+ font-weight: 500;
+ font-size: 22px;
+ line-height: 1.8;
+}
+
+h3 {
+ font-weight: 400;
+ font-size: 18px;
+ line-height: 1.8;
+}
+
+h4 {
+ font-weight: 500;
+}
+
+h5 {
+ font-size: 14px;
+ font-weight: 500;
+ padding: 10px 0px;
+}
+
+.h6, h6 {
+ font-size: 13px;
+}
+
+article h1 {
+ margin-top: 45px;
+ margin-bottom: 20px;
+}
+
+article > h1:first-child {
+ margin-top: 10px;
+}
+
+article h2,
+article h3,
+article h4 {
+ margin-top: 25px;
+ margin-bottom: 10px;
+}
+
+article h2 {
+ margin-top: 35px;
+}
+
+article h3 {
+ margin-top: 30px;
+}
+
+article h4 {
+ padding-bottom: 6px;
+ border-bottom: 1px solid #ddd;
+ font-weight: 500;
+ font-size: 17px;
+}
+
+th {
+ font-weight: 500;
+}
+
+h1 + h2, h2 + h3, h3 + h4 {
+ margin-top: 0;
+}
+
+article ul {
+ margin-bottom: 12px;
+}
+
+article li {
+ margin-bottom: 4px;
+}
+
+#fields, #properties, #methods, #events {
+ font-weight: 500;
+ margin-top: 2em;
+}
+
+
+
+/* SIDEBAR */
+
+.toc .nav > li > a {
+ color: var(--toc-font-color);
+}
+
+.sidefilter {
+ background-color: #fff;
+ border-left: none;
+ border-right: none;
+}
+
+.sidefilter {
+ background-color: #fff;
+ border-left: none;
+ border-right: none;
+}
+
+.toc-filter {
+ padding: 5px;
+ margin: 0;
+ box-shadow: var(--card-box-shadow);
+ transition: var(--transition);
+ opacity: 0.5;
+}
+
+ .toc-filter:hover, .toc-filter:focus, .toc-filter:focus-within {
+ opacity: 1;
+ }
+
+ .toc-filter > input {
+ border: none;
+ background-color: inherit;
+ transition: inherit;
+ }
+
+ .toc-filter > .filter-icon {
+ display: none;
+ }
+
+.sidetoc > .toc {
+ background-color: #fff;
+ overflow-x: unset;
+}
+
+.sidetoc {
+ background-color: #fff;
+ border: none;
+}
+
+.toc {
+ margin: 0;
+ padding: 0;
+ font-size: 14px;
+}
+
+ .toc .nav > li {
+ position: relative;
+ display: block;
+ margin-bottom: 8px;
+ text-transform: capitalize;
+ }
+
+ .toc ul {
+ margin-top: 8px;
+ margin-left: 10px;
+ font-size: 14px;
+ }
+
+ .toc:first-child > ul {
+ margin-top: 0;
+ }
+
+ .toc:first-child > ul:first-child > li {
+ margin-top: 0;
+ }
+
+.sidetoc {
+ overflow-x: auto;
+ overflow-wrap: normal;
+ width: 260px;
+}
+
+.toc .level1 > li {
+ font-weight: 400;
+ margin-top: 8px;
+ position: relative;
+ font-size: 14px;
+}
+
+.toc .level2 {
+ font-weight: normal;
+ font-size: 14px;
+}
+
+.toc UL.level2 {
+ margin: 8px 0 0 15px;
+ font-size: 14px;
+}
+
+.expand-stub {
+ left: -10px;
+ font-family: "WebComponentsIcons";
+}
+
+.toc .nav > li.active > .expand-stub::before,
+.toc .nav > li.in > .expand-stub::before,
+.toc .nav > li.in.active > .expand-stub::before,
+.toc .nav > li.filtered > .expand-stub::before {
+ content: "\e015";
+}
+
+.toc .nav > li > .expand-stub::before,
+.toc .nav > li.active > .expand-stub::before {
+ content: "\e014";
+}
+
+
+/* ALERTS */
+
+.alert {
+ padding: 0px 0px 5px 0px;
+ margin-top: 20px;
+ color: inherit;
+ background-color: inherit;
+ border: none;
+ box-shadow: var(--card-box-shadow);
+}
+
+ .alert > p {
+ margin-bottom: 0;
+ padding: 5px 10px;
+ }
+
+ .alert > ul {
+ margin-bottom: 0;
+ padding: 5px 40px;
+ }
+
+ .alert > h5 {
+ padding: 10px 15px;
+ margin-top: 0;
+ text-transform: uppercase;
+ font-weight: 500;
+ border-radius: 4px 4px 0 0;
+ }
+
+.alert-info > h5 {
+ color: #1976d2;
+ border-bottom: 4px solid #1976d2;
+ background-color: #e3f2fd;
+}
+
+.alert-warning > h5 {
+ color: #f57f17;
+ border-bottom: 4px solid #f57f17;
+ background-color: #fff3e0;
+}
+
+.alert-danger > h5 {
+ color: #d32f2f;
+ border-bottom: 4px solid #d32f2f;
+ background-color: #ffebee;
+}
+
+/* CODE HIGHLIGHT */
+pre {
+ padding: 9.5px;
+ margin: 20px 0;
+ font-size: 15px;
+ word-break: break-all;
+ word-wrap: break-word;
+ background-color: #f8f8f7;
+ border-radius: 4px;
+ border: none;
+ box-shadow: var(--card-box-shadow);
+}
+
+/* STYLE FOR IMAGES */
+
+.article .small-image {
+ margin-top: 15px;
+ box-shadow: var(--card-box-shadow);
+ max-width: 350px;
+}
+
+.article .medium-image {
+ margin-top: 15px;
+ box-shadow: var(--card-box-shadow);
+ max-width: 550px;
+}
+
+.article .large-image {
+ margin-top: 15px;
+ box-shadow: var(--card-box-shadow);
+ max-width: 700px;
+}
+
+*, :after, :before {
+ box-sizing: border-box;
+}
+
+header {
+ background-color: var(--header-bg-color);
+}
+
+.article {
+ margin-top: 200px;
+ margin-bottom: 115px;
+}
+
+.sidefilter {
+ top: 200px;
+ margin: 0;
+ padding: 0;
+ width: 260px;
+}
+
+.toc-filter {
+ border-radius: 5px;
+ background: unset;
+ color: unset;
+ padding: 0;
+ position: relative;
+ margin: 0;
+}
+
+#toc_filter_input {
+ width: 100%;
+ height: 26px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ border: 1px solid #ccc;
+}
+
+
+.sidetoc {
+ top: 240px;
+}
+
+.navbar-brand {
+ height: unset;
+}
+
+.hidden-sm.col-md-2 {
+ padding-left: 0;
+}
+
+.sideaffix {
+ top: 150px;
+ font-size: 13px;
+ max-width: calc(12% - 5px);
+ margin-left: 5px;
+}
+
+.affix {
+ text-transform: capitalize;
+}
+
+ .affix ul > li > a {
+ text-transform: capitalize;
+ }
+
+ .affix ul > li > a {
+ padding-top: 3px;
+ padding-right: 12px;
+ padding-bottom: 3px;
+ padding-left: 14px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ .affix h5 {
+ font-weight: 500;
+ }
+
+ .affix ul ul > li > a:before {
+ top: unset;
+ }
+
+
+
+
+.article.grid-right {
+ margin-left: 280px;
+}
+
+@media only screen and (max-width: 768px) {
+ .article.grid-right {
+ margin-left: 0;
+ }
+}
+
+
+
+.sdkversion {
+ color: var(--highlight-dark);
+ font-weight: 500;
+}
+
+
+#search .form-control {
+ width: 100%;
+ height: 26px;
+ /* padding: 6px 12px; */
+ font-size: 14px;
+ line-height: 1.42857143;
+ border: 1px solid #ccc;
+}
+
+.container #breadcrumb {
+ float: left;
+}
+
+.breadcrumb li {
+ font-size: 14px;
+ text-transform: capitalize;
+}
+
+.opspan {
+ text-transform: uppercase;
+ padding: 3px 10px;
+ color: white;
+ font-weight: bold;
+ border-radius: 5px;
+}
+
+ .opspan.opspan-post {
+ background: #49cc90;
+ }
+
+ .opspan.opspan-put {
+ background: #fca130;
+ }
+
+ .opspan.opspan-delete {
+ background: #f93e3e;
+ }
+
+ .opspan.opspan-get {
+ background: #61affe;
+ }
+
+ .opspan.opspan-patch {
+ background: #50e3c2;
+ }
+
+ .opspan.opspan-head {
+ background: #9012fe;
+ }
+
+ .opspan.opspan-options {
+ background: #0d5aa7;
+ }
+
+.grad-bottom {
+ background: linear-gradient(rgb(0 0 0 / 0%), rgb(0 0 0 / 8%));
+ height: 5px;
+}
+
+.footer {
+ border-top: 1px solid #ededed;
+ background-color: #111;
+ padding: 15px 0;
+ color: #757575;
+}
+
+ .footer a.lnk {
+ color: #9e9e9e;
+ }
+
+.refdoc pre {
+ margin: 0;
+}
+
+.refdoc h4 {
+ margin-top: 40px;
+}
+
+.refdoc h4 {
+ margin-top: 50px;
+ padding-bottom: 3px;
+}
+
+.refdoc h5 {
+ padding-bottom: 3px;
+ margin-top: 16px;
+}
+
+.hljs-keyword {
+ color: rgb(86,156,214);
+}
+
+.hljs-string, .hljs-meta-string {
+ color: rgb(214, 157, 133);
+}
+
+.card.fw-horizontal {
+ text-align: left;
+ padding: 1.4rem;
+ flex-direction: row;
+ border-radius: 0.75rem;
+ box-shadow: 3px 3px 13px 0px rgb(255 255 255 / 42%);
+ /* height: 100%; */
+ align-items: flex-start;
+ gap: 1rem;
+ margin: 1rem 1rem;
+ max-height: 12rem;
+}
+
+@media (min-width: 768px) {
+ .card.fw-horizontal:first-child {
+ width: calc(66.66666667% + 2rem);
+ }
+}
+
+@media (min-width: 990px) {
+ .card.fw-horizontal:first-child {
+ margin: 1rem;
+ width: 25%;
+ }
+}
+
+.card {
+ padding: 1rem 2rem;
+ margin-bottom: 1rem;
+}
+
+.card {
+ position: relative;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ word-wrap: break-word;
+ /* background-color: var(--fw-card-bg); */
+ /* background-clip: border-box; */
+ border: 1px solid var(--fw-card-border-color);
+ border-radius: 0.5rem;
+ background: linear-gradient(12deg, #e3e3e3, white);
+ align-items: center;
+ margin: 1rem;
+ box-shadow: 3px 3px 10px 0px rgb(99 113 119 / 17%);
+}
+
+ .card .category-icon {
+ height: 4.5rem;
+ margin: 0 0.2em 0.6rem 0;
+ }
+
+ .card.fw-horizontal .fw-category-icon {
+ width: 4.5rem;
+ margin: 0.2rem 0 0 0;
+ }
+
+.card-body {
+ color: var(--fw-card-fg);
+}
+
+.card.fw-horizontal .card-title {
+ margin-bottom: 0.3rem;
+ padding-bottom: 0;
+}
+
+.cardRow {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-content: inherit;
+ align-items: stretch;
+ /* gap: 2rem; */
+ margin-bottom: 70px;
+}
+
+.card.fw-horizontal .card-body {
+ width: 75%;
+ overflow: hidden;
+ max-height: 100%;
+}
+
+.card-text {
+ color: var(--font-color);
+ font-size: 15px;
+ overflow: hidden;
+}
+
+H4.card-title {
+ margin-top: 0;
+ line-height: 1.3;
+ border: none;
+}
+
+H2.card-title {
+ margin-top: 0;
+ line-height: 1.2;
+ margin-bottom: 0.4rem;
+}
+
+.landingBackground {
+ background-image: url(../images/books_background.jpg);
+ position: absolute;
+ top: -50px;
+ left: 0;
+ right: 0;
+ height: 350px;
+ background-position: center center;
+ -webkit-background-size: cover;
+ background-size: cover;
+ z-index: -1;
+}
+
+
+ .landingBackground.searchResults {
+ height: 220px;
+ }
+
+.landing-search {
+ margin-top: 180px;
+ margin-bottom: 20px;
+}
+
+@media only screen and (max-width: 990px) {
+ .landingBackground {
+ top: 0px;
+ height: 410px;
+ }
+
+ .landingBackground.searchResults {
+ height: 180px;
+ }
+
+ .landing-search {
+ margin-top: 140px;
+ }
+}
+
+@media only screen and (max-width: 950px) {
+ .landing-search {
+ margin-top: 120px;
+ margin-bottom: 0;
+ }
+
+ .landingBackground {
+ top: -30px;
+ height: 420px;
+ }
+}
+
+@media only screen and (max-width: 900px) {
+ .landing-search {
+ margin-top: 80px;
+ margin-bottom: 0;
+ }
+
+ .landingBackground {
+ height: 440px;
+ }
+}
+
+
+@media only screen and (max-width: 768px) {
+ .landing-search {
+ margin-top: 20px;
+ margin-bottom: 40px;
+ }
+
+ .landingBackground.searchResults {
+ height: 250px;
+ }
+
+ .landingBackground {
+ height: 250px;
+ }
+}
+
+.search-form {
+ margin: auto;
+ width: 32rem;
+ display: flex !important;
+ flex-direction: column;
+ align-items: center;
+}
+
+#search.search-form .form-control {
+ width: unset;
+ font-size: 24px;
+ height: 42px;
+ width: 36rem;
+}
+
+.search-heading h2 {
+ color: white;
+ font-size: 29px;
+ white-space: nowrap;
+ font-weight: 300;
+ margin-bottom: 0.1rem;
+}
+
+.landingPage H1 {
+ text-align: center;
+}
+
+.landingPage #search-results {
+ margin-top: 60px;
+}
+
+.card.fw-horizontal .card-text {
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ display: -webkit-inline-box;
+ -webkit-box-orient: vertical;
+}
+
+.landingPage ARTICLE H1:first-child {
+ display: none;
+}
+
+body {
+ padding: 20px !important;
+}
+
+ body > div:first-child > hr + p {
+ display: none;
+ }
+
+ body > div:first-child hr {
+ display: none;
+ }
+
+ body > div:first-child {
+ margin-top: 20px;
+ }
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..ceda25e
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "8.0.305",
+ "rollForward": "feature",
+ "allowPrerelease": false
+ }
+}
diff --git a/nuke/Build.cs b/nuke/Build.cs
index 0b39025..f7fdb39 100644
--- a/nuke/Build.cs
+++ b/nuke/Build.cs
@@ -18,6 +18,7 @@ using System.Linq;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
+// ReSharper disable ArrangeThisQualifier
class Build : NukeBuild
{
@@ -27,25 +28,20 @@ class Build : NukeBuild
/// - Microsoft VisualStudio https://nuke.build/visualstudio
/// - Microsoft VSCode https://nuke.build/vscode
- public static int Main () => Execute(x => x.RunUnitTests);
+ public static int Main() => Execute(x => x.RunUnitTests);
[Nuke.Common.Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
- [Nuke.Common.Parameter("ReleaseNotesFilePath - To determine the SemanticVersion")]
+ [Nuke.Common.Parameter("ReleaseNotesFilePath - To determine the lates changelog version")]
readonly AbsolutePath ReleaseNotesFilePath = RootDirectory / "Changelog.md";
+ [Nuke.Common.Parameter("common.props file path - to determine the configured version")]
+ readonly AbsolutePath CommonPropsFilePath = RootDirectory / "src" / "common.props";
+
[Solution]
readonly Solution Solution;
- string TargetProjectName => "ElectronNET";
-
- string ApiTargetLibName => $"{TargetProjectName}.API";
-
- string CliTargetLibName => $"{TargetProjectName}.CLI";
-
- string DemoTargetLibName => $"{TargetProjectName}.WebApp";
-
AbsolutePath SourceDirectory => RootDirectory / "src";
AbsolutePath ResultDirectory => RootDirectory / "artifacts";
@@ -60,16 +56,7 @@ class Build : NukeBuild
string Version { get; set; }
- AbsolutePath[] Projects
- {
- get
- {
- var api = SourceDirectory / ApiTargetLibName / $"{ApiTargetLibName}.csproj";
- var cli = SourceDirectory / CliTargetLibName / $"{CliTargetLibName}.csproj";
- var projects = new[] { api, cli };
- return projects;
- }
- }
+ string VersionPostFix { get; set; }
protected override void OnBuildInitialized()
{
@@ -82,9 +69,19 @@ class Build : NukeBuild
LatestReleaseNotes = ChangeLog.First();
LatestReleaseNotes.NotNull("LatestVersion could not be read!");
- Log.Debug("Using LastestVersion from ChangeLog: {LatestVersion}", LatestReleaseNotes.Version);
+ var propsParser = new CommonPropsParser();
+
+ var propsVersion = propsParser.Parse(CommonPropsFilePath);
+
+ propsVersion.NotNull("Version from common.props could not be read!");
+
+ Assert.True(propsVersion == LatestReleaseNotes.Version,
+ $"The version in common.props ({propsVersion}) does not " +
+ $"equal the latest version in the changelog ({LatestReleaseNotes.Version})");
+
+ Log.Debug("Using version: {LatestVersion}", propsVersion);
SemVersion = LatestReleaseNotes.SemVersion;
- Version = LatestReleaseNotes.Version.ToString();
+ Version = propsVersion.ToString();
if (GitHubActions != null)
{
@@ -94,13 +91,17 @@ class Build : NukeBuild
if (ScheduledTargets.Contains(Default))
{
- Version = $"{Version}-ci.{buildNumber}";
+ VersionPostFix = $"-ci.{buildNumber}";
}
else if (ScheduledTargets.Contains(PrePublish))
{
- Version = $"{Version}-alpha.{buildNumber}";
+ VersionPostFix = $"-pre.{buildNumber}";
}
}
+ else if (ScheduledTargets.Contains(PrePublish))
+ {
+ VersionPostFix = $"-pre";
+ }
Log.Information("Building version: {Version}", Version);
}
@@ -115,120 +116,32 @@ class Build : NukeBuild
Target Restore => _ => _
.Executes(() =>
{
- Projects.ForEach(project =>
- {
- DotNetRestore(s => s
- .SetProjectFile(project));
- });
+ DotNetRestore(s => s.SetProjectFile(Solution.Path));
});
Target Compile => _ => _
.DependsOn(Restore)
.Executes(() =>
{
- Projects.ForEach(project =>
- {
- DotNetBuild(s => s
- .SetProjectFile(project)
- .SetVersion(Version)
- .SetConfiguration(Configuration)
- .EnableNoRestore());
- });
+ DotNetBuild(s => s
+ .SetProjectFile(Solution.Path)
+ .SetConfiguration(Configuration)
+ .SetProperty("GeneratePackageOnBuild", "True")
+ .SetProperty("VersionPostFix", VersionPostFix ?? string.Empty));
});
Target RunUnitTests => _ => _
.DependsOn(Compile)
.Executes(() =>
{
- Projects.ForEach(project =>
- {
- DotNetTest(s => s
- .SetProjectFile(project)
- .SetConfiguration(Configuration)
- .EnableNoRestore()
- .EnableNoBuild());
- });
+ // There aren't any yet
});
Target CreatePackages => _ => _
.DependsOn(Compile)
.Executes(() =>
{
- Projects.ForEach(project =>
- {
- DotNetPack(s => s
- .SetProject(project)
- .SetVersion(Version)
- .SetConfiguration(Configuration)
- .SetOutputDirectory(ResultDirectory)
- .SetIncludeSymbols(true)
- .SetSymbolPackageFormat("snupkg")
- .EnableNoRestore()
- );
- });
- });
-
- Target CompileSample => _ => _
- .DependsOn(Compile)
- .Executes(() =>
- {
- var sample = SourceDirectory / DemoTargetLibName / $"{DemoTargetLibName}.csproj";
- DotNetBuild(s => s.SetProjectFile(sample).SetConfiguration(Configuration));
- });
-
- Target ElectronizeGenericTargetSample => _ => _
- .DependsOn(CompileSample)
- .Executes(() =>
- {
- var sample = SourceDirectory / DemoTargetLibName;
- var cli = SourceDirectory / CliTargetLibName / $"{CliTargetLibName}.csproj";
- var args = "build /target custom win7-x86;win /dotnet-configuration Debug /electron-arch ia32 /electron-params \"--publish never\"";
-
- DotNet($"run --project {cli} -- {args}", sample);
- });
-
- Target ElectronizeWindowsTargetSample => _ => _
- .DependsOn(CompileSample)
- .Executes(() =>
- {
- var sample = SourceDirectory / DemoTargetLibName;
- var cli = SourceDirectory / CliTargetLibName / $"{CliTargetLibName}.csproj";
- var args = "build /target win /electron-params \"--publish never\"";
-
- DotNet($"run --project {cli} -- {args}", sample);
- });
-
- Target ElectronizeCustomWin7TargetSample => _ => _
- .DependsOn(CompileSample)
- .Executes(() =>
- {
- var sample = SourceDirectory / DemoTargetLibName;
- var cli = SourceDirectory / CliTargetLibName / $"{CliTargetLibName}.csproj";
- var args = "build /target custom win7-x86;win /electron-params \"--publish never\"";
-
- DotNet($"run --project {cli} -- {args}", sample);
- });
-
- Target ElectronizeMacOsTargetSample => _ => _
- .DependsOn(CompileSample)
- .Executes(() =>
- {
- var sample = SourceDirectory / DemoTargetLibName;
- var cli = SourceDirectory / CliTargetLibName / $"{CliTargetLibName}.csproj";
- var args = "build /target osx /electron-params \"--publish never\"";
-
- DotNet($"run --project {cli} -- {args}", sample);
- });
-
- Target ElectronizeLinuxTargetSample => _ => _
- .DependsOn(CompileSample)
- .Executes(() =>
- {
- var sample = SourceDirectory / DemoTargetLibName;
- var cli = SourceDirectory / CliTargetLibName / $"{CliTargetLibName}.csproj";
- var args = "build /target linux /electron-params \"--publish never\"";
-
- DotNet($"run --project {cli} -- {args}", sample);
+ // Packages are created on build
});
Target PublishPackages => _ => _
@@ -280,9 +193,9 @@ class Build : NukeBuild
new InMemoryCredentialStore(credentials));
GitHubTasks.GitHubClient.Repository.Release
- .Create("ElectronNET", "Electron.NET", new NewRelease(Version)
+ .Create("ElectronNET", "Electron.NET", new NewRelease(Version + VersionPostFix)
{
- Name = Version,
+ Name = "ElectronNET.Core " + Version + VersionPostFix,
Body = String.Join(Environment.NewLine, LatestReleaseNotes.Notes),
Prerelease = true,
TargetCommitish = "develop",
@@ -318,7 +231,7 @@ class Build : NukeBuild
GitHubTasks.GitHubClient.Repository.Release
.Create("ElectronNET", "Electron.NET", new NewRelease(Version)
{
- Name = Version,
+ Name = "ElectronNET.Core " + Version,
Body = String.Join(Environment.NewLine, LatestReleaseNotes.Notes),
Prerelease = false,
TargetCommitish = "main",
diff --git a/nuke/CommonPropsParser.cs b/nuke/CommonPropsParser.cs
new file mode 100644
index 0000000..ac780bd
--- /dev/null
+++ b/nuke/CommonPropsParser.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Linq;
+using System.Xml.Linq;
+
+///
+/// Parses a version from an MSBuild .props file (XML).
+///
+public sealed class CommonPropsParser
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CommonPropsParser()
+ {
+ }
+
+ public Version Parse(string propsPath)
+ {
+ var doc = XDocument.Load(propsPath);
+
+ var versionElement = doc
+ .Descendants()
+ .FirstOrDefault(e => e.Name.LocalName == "Version");
+
+ if (Version.TryParse(versionElement?.Value.Trim(), out var version))
+ {
+ version = new Version(version.Major, version.Minor, version.Build);
+ return version;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/App.cs b/src/ElectronNET.API/API/App.cs
similarity index 97%
rename from src/ElectronNET.API/App.cs
rename to src/ElectronNET.API/API/App.cs
index 20e1541..088ed84 100644
--- a/src/ElectronNET.API/App.cs
+++ b/src/ElectronNET.API/API/App.cs
@@ -70,7 +70,7 @@ namespace ElectronNET.API
{
BridgeConnector.Socket.On("app-before-quit" + GetHashCode(), async () =>
{
- await _beforeQuit(new QuitEventArgs());
+ await this._beforeQuit(new QuitEventArgs()).ConfigureAwait(false);
if (_preventQuit)
{
@@ -84,7 +84,7 @@ namespace ElectronNET.API
}
else if (_willQuit != null)
{
- await _willQuit(new QuitEventArgs());
+ await this._willQuit(new QuitEventArgs()).ConfigureAwait(false);
if (_preventQuit)
{
@@ -98,14 +98,14 @@ namespace ElectronNET.API
}
else
{
- await _quitting();
+ await this._quitting().ConfigureAwait(false);
Exit();
}
}
}
else if (_quitting != null)
{
- await _quitting();
+ await this._quitting().ConfigureAwait(false);
Exit();
}
}
@@ -142,7 +142,7 @@ namespace ElectronNET.API
{
BridgeConnector.Socket.On("app-will-quit" + GetHashCode(), async () =>
{
- await _willQuit(new QuitEventArgs());
+ await this._willQuit(new QuitEventArgs()).ConfigureAwait(false);
if (_preventQuit)
{
@@ -156,7 +156,7 @@ namespace ElectronNET.API
}
else
{
- await _quitting();
+ await this._quitting().ConfigureAwait(false);
Exit();
}
}
@@ -192,7 +192,7 @@ namespace ElectronNET.API
{
if(_willQuit == null)
{
- await _quitting();
+ await this._quitting().ConfigureAwait(false);
Exit();
}
});
@@ -567,6 +567,11 @@ namespace ElectronNET.API
BridgeConnector.Socket.Emit("appExit", exitCode);
}
+ public void DisposeSocket()
+ {
+ BridgeConnector.Socket.DisposeSocket();
+ }
+
///
/// Relaunches the app when current instance exits. By default the new instance will use the same working directory
/// and command line arguments with current instance.
@@ -816,7 +821,7 @@ namespace ElectronNET.API
/// Whether the call succeeded.
public async Task SetAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)
{
- return await SetAsDefaultProtocolClientAsync(protocol, null, null, cancellationToken);
+ return await this.SetAsDefaultProtocolClientAsync(protocol, null, null, cancellationToken).ConfigureAwait(false);
}
///
@@ -847,7 +852,7 @@ namespace ElectronNET.API
/// Whether the call succeeded.
public async Task SetAsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default)
{
- return await SetAsDefaultProtocolClientAsync(protocol, path, null, cancellationToken);
+ return await this.SetAsDefaultProtocolClientAsync(protocol, path, null, cancellationToken).ConfigureAwait(false);
}
///
@@ -906,7 +911,7 @@ namespace ElectronNET.API
/// Whether the call succeeded.
public async Task RemoveAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)
{
- return await RemoveAsDefaultProtocolClientAsync(protocol, null, null, cancellationToken);
+ return await this.RemoveAsDefaultProtocolClientAsync(protocol, null, null, cancellationToken).ConfigureAwait(false);
}
///
@@ -919,7 +924,7 @@ namespace ElectronNET.API
/// Whether the call succeeded.
public async Task RemoveAsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default)
{
- return await RemoveAsDefaultProtocolClientAsync(protocol, path, null, cancellationToken);
+ return await this.RemoveAsDefaultProtocolClientAsync(protocol, path, null, cancellationToken).ConfigureAwait(false);
}
///
@@ -966,7 +971,7 @@ namespace ElectronNET.API
/// Whether the current executable is the default handler for a protocol (aka URI scheme).
public async Task IsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)
{
- return await IsDefaultProtocolClientAsync(protocol, null, null, cancellationToken);
+ return await this.IsDefaultProtocolClientAsync(protocol, null, null, cancellationToken).ConfigureAwait(false);
}
///
@@ -985,7 +990,7 @@ namespace ElectronNET.API
/// Whether the current executable is the default handler for a protocol (aka URI scheme).
public async Task IsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default)
{
- return await IsDefaultProtocolClientAsync(protocol, path, null, cancellationToken);
+ return await this.IsDefaultProtocolClientAsync(protocol, path, null, cancellationToken).ConfigureAwait(false);
}
///
@@ -1437,7 +1442,7 @@ namespace ElectronNET.API
///
public async Task GetLoginItemSettingsAsync(CancellationToken cancellationToken = default)
{
- return await GetLoginItemSettingsAsync(null, cancellationToken);
+ return await this.GetLoginItemSettingsAsync(null, cancellationToken).ConfigureAwait(false);
}
///
@@ -1624,7 +1629,7 @@ namespace ElectronNET.API
/// The event name
/// The handler
public async Task On(string eventName, Action action)
- => await Events.Instance.On(ModuleName, eventName, action);
+ => await Events.Instance.On(ModuleName, eventName, action).ConfigureAwait(false);
///
/// Subscribe to an unmapped event on the module once.
///
@@ -1638,6 +1643,6 @@ namespace ElectronNET.API
/// The event name
/// The handler
public async Task Once(string eventName, Action action)
- => await Events.Instance.Once(ModuleName, eventName, action);
+ => await Events.Instance.Once(ModuleName, eventName, action).ConfigureAwait(false);
}
}
diff --git a/src/ElectronNET.API/AutoUpdater.cs b/src/ElectronNET.API/API/AutoUpdater.cs
similarity index 100%
rename from src/ElectronNET.API/AutoUpdater.cs
rename to src/ElectronNET.API/API/AutoUpdater.cs
diff --git a/src/ElectronNET.API/BrowserView.cs b/src/ElectronNET.API/API/BrowserView.cs
similarity index 100%
rename from src/ElectronNET.API/BrowserView.cs
rename to src/ElectronNET.API/API/BrowserView.cs
diff --git a/src/ElectronNET.API/BrowserWindow.cs b/src/ElectronNET.API/API/BrowserWindow.cs
similarity index 100%
rename from src/ElectronNET.API/BrowserWindow.cs
rename to src/ElectronNET.API/API/BrowserWindow.cs
diff --git a/src/ElectronNET.API/Clipboard.cs b/src/ElectronNET.API/API/Clipboard.cs
similarity index 100%
rename from src/ElectronNET.API/Clipboard.cs
rename to src/ElectronNET.API/API/Clipboard.cs
diff --git a/src/ElectronNET.API/CommandLine.cs b/src/ElectronNET.API/API/CommandLine.cs
similarity index 100%
rename from src/ElectronNET.API/CommandLine.cs
rename to src/ElectronNET.API/API/CommandLine.cs
diff --git a/src/ElectronNET.API/API/Cookies.cs b/src/ElectronNET.API/API/Cookies.cs
new file mode 100644
index 0000000..4959cd9
--- /dev/null
+++ b/src/ElectronNET.API/API/Cookies.cs
@@ -0,0 +1,67 @@
+ο»Ώusing System;
+using System.Threading.Tasks;
+using ElectronNET.API.Entities;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace ElectronNET.API
+{
+ ///
+ /// Query and modify a session's cookies.
+ ///
+ public class Cookies
+ {
+ ///
+ /// Gets the identifier.
+ ///
+ ///
+ /// The identifier.
+ ///
+ public int Id { get; private set; }
+
+ internal Cookies(int id)
+ {
+ Id = id;
+ }
+
+ ///
+ /// Emitted when a cookie is changed because it was added, edited, removed, or expired.
+ ///
+ public event Action OnChanged
+ {
+ add
+ {
+ if (_changed == null)
+ {
+ BridgeConnector.Socket.On("webContents-session-cookies-changed" + Id, (args) =>
+ {
+ Cookie cookie = ((JArray)args)[0].ToObject();
+ CookieChangedCause cause = ((JArray)args)[1].ToObject();
+ bool removed = ((JArray)args)[2].ToObject();
+ _changed(cookie, cause, removed);
+ });
+
+ BridgeConnector.Socket.Emit("register-webContents-session-cookies-changed", Id);
+ }
+ _changed += value;
+ }
+ remove
+ {
+ _changed -= value;
+
+ if (_changed == null)
+ BridgeConnector.Socket.Off("webContents-session-cookies-changed" + Id);
+ }
+ }
+
+ private event Action _changed;
+
+ private JsonSerializer _jsonSerializer = new JsonSerializer()
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver(),
+ NullValueHandling = NullValueHandling.Ignore,
+ DefaultValueHandling = DefaultValueHandling.Ignore
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Dialog.cs b/src/ElectronNET.API/API/Dialog.cs
similarity index 96%
rename from src/ElectronNET.API/Dialog.cs
rename to src/ElectronNET.API/API/Dialog.cs
index ddc7be5..f8cc413 100644
--- a/src/ElectronNET.API/Dialog.cs
+++ b/src/ElectronNET.API/API/Dialog.cs
@@ -103,7 +103,7 @@ namespace ElectronNET.API
/// The API call will be asynchronous and the result will be passed via MessageBoxResult.
public async Task ShowMessageBoxAsync(string message)
{
- return await ShowMessageBoxAsync(null, new MessageBoxOptions(message));
+ return await this.ShowMessageBoxAsync(null, new MessageBoxOptions(message)).ConfigureAwait(false);
}
///
@@ -117,7 +117,7 @@ namespace ElectronNET.API
/// The API call will be asynchronous and the result will be passed via MessageBoxResult.
public async Task ShowMessageBoxAsync(MessageBoxOptions messageBoxOptions)
{
- return await ShowMessageBoxAsync(null, messageBoxOptions);
+ return await this.ShowMessageBoxAsync(null, messageBoxOptions).ConfigureAwait(false);
}
///
@@ -130,7 +130,7 @@ namespace ElectronNET.API
/// The API call will be asynchronous and the result will be passed via MessageBoxResult.
public async Task ShowMessageBoxAsync(BrowserWindow browserWindow, string message)
{
- return await ShowMessageBoxAsync(browserWindow, new MessageBoxOptions(message));
+ return await this.ShowMessageBoxAsync(browserWindow, new MessageBoxOptions(message)).ConfigureAwait(false);
}
///
diff --git a/src/ElectronNET.API/Dock.cs b/src/ElectronNET.API/API/Dock.cs
similarity index 100%
rename from src/ElectronNET.API/Dock.cs
rename to src/ElectronNET.API/API/Dock.cs
diff --git a/src/ElectronNET.API/Electron.cs b/src/ElectronNET.API/API/Electron.cs
similarity index 100%
rename from src/ElectronNET.API/Electron.cs
rename to src/ElectronNET.API/API/Electron.cs
diff --git a/src/ElectronNET.API/Entities/AboutPanelOptions.cs b/src/ElectronNET.API/API/Entities/AboutPanelOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/AboutPanelOptions.cs
rename to src/ElectronNET.API/API/Entities/AboutPanelOptions.cs
diff --git a/src/ElectronNET.API/Entities/AddRepresentationOptions.cs b/src/ElectronNET.API/API/Entities/AddRepresentationOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/AddRepresentationOptions.cs
rename to src/ElectronNET.API/API/Entities/AddRepresentationOptions.cs
diff --git a/src/ElectronNET.API/Entities/AppDetailsOptions.cs b/src/ElectronNET.API/API/Entities/AppDetailsOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/AppDetailsOptions.cs
rename to src/ElectronNET.API/API/Entities/AppDetailsOptions.cs
diff --git a/src/ElectronNET.API/Entities/AutoResizeOptions.cs b/src/ElectronNET.API/API/Entities/AutoResizeOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/AutoResizeOptions.cs
rename to src/ElectronNET.API/API/Entities/AutoResizeOptions.cs
diff --git a/src/ElectronNET.API/Entities/BitmapOptions.cs b/src/ElectronNET.API/API/Entities/BitmapOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/BitmapOptions.cs
rename to src/ElectronNET.API/API/Entities/BitmapOptions.cs
diff --git a/src/ElectronNET.API/Entities/Blob.cs b/src/ElectronNET.API/API/Entities/Blob.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Blob.cs
rename to src/ElectronNET.API/API/Entities/Blob.cs
diff --git a/src/ElectronNET.API/Entities/BlockMapDataHolder.cs b/src/ElectronNET.API/API/Entities/BlockMapDataHolder.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/BlockMapDataHolder.cs
rename to src/ElectronNET.API/API/Entities/BlockMapDataHolder.cs
diff --git a/src/ElectronNET.API/Entities/BrowserViewConstructorOptions.cs b/src/ElectronNET.API/API/Entities/BrowserViewConstructorOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/BrowserViewConstructorOptions.cs
rename to src/ElectronNET.API/API/Entities/BrowserViewConstructorOptions.cs
diff --git a/src/ElectronNET.API/Entities/BrowserWindowOptions.cs b/src/ElectronNET.API/API/Entities/BrowserWindowOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/BrowserWindowOptions.cs
rename to src/ElectronNET.API/API/Entities/BrowserWindowOptions.cs
diff --git a/src/ElectronNET.API/Entities/CPUUsage.cs b/src/ElectronNET.API/API/Entities/CPUUsage.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CPUUsage.cs
rename to src/ElectronNET.API/API/Entities/CPUUsage.cs
diff --git a/src/ElectronNET.API/Entities/Certificate.cs b/src/ElectronNET.API/API/Entities/Certificate.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Certificate.cs
rename to src/ElectronNET.API/API/Entities/Certificate.cs
diff --git a/src/ElectronNET.API/Entities/CertificatePrincipal.cs b/src/ElectronNET.API/API/Entities/CertificatePrincipal.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CertificatePrincipal.cs
rename to src/ElectronNET.API/API/Entities/CertificatePrincipal.cs
diff --git a/src/ElectronNET.API/Entities/CertificateTrustDialogOptions.cs b/src/ElectronNET.API/API/Entities/CertificateTrustDialogOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CertificateTrustDialogOptions.cs
rename to src/ElectronNET.API/API/Entities/CertificateTrustDialogOptions.cs
diff --git a/src/ElectronNET.API/Entities/ChromeExtensionInfo.cs b/src/ElectronNET.API/API/Entities/ChromeExtensionInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ChromeExtensionInfo.cs
rename to src/ElectronNET.API/API/Entities/ChromeExtensionInfo.cs
diff --git a/src/ElectronNET.API/Entities/ClearStorageDataOptions.cs b/src/ElectronNET.API/API/Entities/ClearStorageDataOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ClearStorageDataOptions.cs
rename to src/ElectronNET.API/API/Entities/ClearStorageDataOptions.cs
diff --git a/src/ElectronNET.API/Entities/Cookie.cs b/src/ElectronNET.API/API/Entities/Cookie.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Cookie.cs
rename to src/ElectronNET.API/API/Entities/Cookie.cs
diff --git a/src/ElectronNET.API/Entities/CookieChangedCause.cs b/src/ElectronNET.API/API/Entities/CookieChangedCause.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CookieChangedCause.cs
rename to src/ElectronNET.API/API/Entities/CookieChangedCause.cs
diff --git a/src/ElectronNET.API/Entities/CookieDetails.cs b/src/ElectronNET.API/API/Entities/CookieDetails.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CookieDetails.cs
rename to src/ElectronNET.API/API/Entities/CookieDetails.cs
diff --git a/src/ElectronNET.API/Entities/CookieFilter.cs b/src/ElectronNET.API/API/Entities/CookieFilter.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CookieFilter.cs
rename to src/ElectronNET.API/API/Entities/CookieFilter.cs
diff --git a/src/ElectronNET.API/Entities/CreateFromBitmapOptions.cs b/src/ElectronNET.API/API/Entities/CreateFromBitmapOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CreateFromBitmapOptions.cs
rename to src/ElectronNET.API/API/Entities/CreateFromBitmapOptions.cs
diff --git a/src/ElectronNET.API/Entities/CreateFromBufferOptions.cs b/src/ElectronNET.API/API/Entities/CreateFromBufferOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CreateFromBufferOptions.cs
rename to src/ElectronNET.API/API/Entities/CreateFromBufferOptions.cs
diff --git a/src/ElectronNET.API/Entities/CreateInterruptedDownloadOptions.cs b/src/ElectronNET.API/API/Entities/CreateInterruptedDownloadOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/CreateInterruptedDownloadOptions.cs
rename to src/ElectronNET.API/API/Entities/CreateInterruptedDownloadOptions.cs
diff --git a/src/ElectronNET.API/Entities/Data.cs b/src/ElectronNET.API/API/Entities/Data.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Data.cs
rename to src/ElectronNET.API/API/Entities/Data.cs
diff --git a/src/ElectronNET.API/Entities/DefaultFontFamily.cs b/src/ElectronNET.API/API/Entities/DefaultFontFamily.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/DefaultFontFamily.cs
rename to src/ElectronNET.API/API/Entities/DefaultFontFamily.cs
diff --git a/src/ElectronNET.API/Entities/DevToolsMode.cs b/src/ElectronNET.API/API/Entities/DevToolsMode.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/DevToolsMode.cs
rename to src/ElectronNET.API/API/Entities/DevToolsMode.cs
diff --git a/src/ElectronNET.API/Entities/Display.cs b/src/ElectronNET.API/API/Entities/Display.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Display.cs
rename to src/ElectronNET.API/API/Entities/Display.cs
diff --git a/src/ElectronNET.API/Entities/DisplayBalloonOptions.cs b/src/ElectronNET.API/API/Entities/DisplayBalloonOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/DisplayBalloonOptions.cs
rename to src/ElectronNET.API/API/Entities/DisplayBalloonOptions.cs
diff --git a/src/ElectronNET.API/Entities/DockBounceType.cs b/src/ElectronNET.API/API/Entities/DockBounceType.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/DockBounceType.cs
rename to src/ElectronNET.API/API/Entities/DockBounceType.cs
diff --git a/src/ElectronNET.API/Entities/EnableNetworkEmulationOptions.cs b/src/ElectronNET.API/API/Entities/EnableNetworkEmulationOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/EnableNetworkEmulationOptions.cs
rename to src/ElectronNET.API/API/Entities/EnableNetworkEmulationOptions.cs
diff --git a/src/ElectronNET.API/Entities/Extension.cs b/src/ElectronNET.API/API/Entities/Extension.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Extension.cs
rename to src/ElectronNET.API/API/Entities/Extension.cs
diff --git a/src/ElectronNET.API/Entities/FileFilter.cs b/src/ElectronNET.API/API/Entities/FileFilter.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/FileFilter.cs
rename to src/ElectronNET.API/API/Entities/FileFilter.cs
diff --git a/src/ElectronNET.API/Entities/FileIconOptions.cs b/src/ElectronNET.API/API/Entities/FileIconOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/FileIconOptions.cs
rename to src/ElectronNET.API/API/Entities/FileIconOptions.cs
diff --git a/src/ElectronNET.API/Entities/FileIconSize.cs b/src/ElectronNET.API/API/Entities/FileIconSize.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/FileIconSize.cs
rename to src/ElectronNET.API/API/Entities/FileIconSize.cs
diff --git a/src/ElectronNET.API/Entities/FocusOptions.cs b/src/ElectronNET.API/API/Entities/FocusOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/FocusOptions.cs
rename to src/ElectronNET.API/API/Entities/FocusOptions.cs
diff --git a/src/ElectronNET.API/Entities/GPUFeatureStatus.cs b/src/ElectronNET.API/API/Entities/GPUFeatureStatus.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/GPUFeatureStatus.cs
rename to src/ElectronNET.API/API/Entities/GPUFeatureStatus.cs
diff --git a/src/ElectronNET.API/Entities/IPostData.cs b/src/ElectronNET.API/API/Entities/IPostData.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/IPostData.cs
rename to src/ElectronNET.API/API/Entities/IPostData.cs
diff --git a/src/ElectronNET.API/Entities/ImportCertificateOptions.cs b/src/ElectronNET.API/API/Entities/ImportCertificateOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ImportCertificateOptions.cs
rename to src/ElectronNET.API/API/Entities/ImportCertificateOptions.cs
diff --git a/src/ElectronNET.API/Entities/InputEvent.cs b/src/ElectronNET.API/API/Entities/InputEvent.cs
similarity index 98%
rename from src/ElectronNET.API/Entities/InputEvent.cs
rename to src/ElectronNET.API/API/Entities/InputEvent.cs
index 95f3d57..aa38542 100644
--- a/src/ElectronNET.API/Entities/InputEvent.cs
+++ b/src/ElectronNET.API/API/Entities/InputEvent.cs
@@ -1,10 +1,11 @@
ο»Ώusing Newtonsoft.Json.Converters;
using System.Collections.Generic;
-using ElectronNET.API.Converter;
using Newtonsoft.Json;
namespace ElectronNET.API.Entities
{
+ using ElectronNET.Converter;
+
///
///
///
diff --git a/src/ElectronNET.API/Entities/InputEventType.cs b/src/ElectronNET.API/API/Entities/InputEventType.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/InputEventType.cs
rename to src/ElectronNET.API/API/Entities/InputEventType.cs
diff --git a/src/ElectronNET.API/Entities/JumpListCategory.cs b/src/ElectronNET.API/API/Entities/JumpListCategory.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/JumpListCategory.cs
rename to src/ElectronNET.API/API/Entities/JumpListCategory.cs
diff --git a/src/ElectronNET.API/Entities/JumpListCategoryType.cs b/src/ElectronNET.API/API/Entities/JumpListCategoryType.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/JumpListCategoryType.cs
rename to src/ElectronNET.API/API/Entities/JumpListCategoryType.cs
diff --git a/src/ElectronNET.API/Entities/JumpListItem.cs b/src/ElectronNET.API/API/Entities/JumpListItem.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/JumpListItem.cs
rename to src/ElectronNET.API/API/Entities/JumpListItem.cs
diff --git a/src/ElectronNET.API/Entities/JumpListItemType.cs b/src/ElectronNET.API/API/Entities/JumpListItemType.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/JumpListItemType.cs
rename to src/ElectronNET.API/API/Entities/JumpListItemType.cs
diff --git a/src/ElectronNET.API/Entities/JumpListSettings.cs b/src/ElectronNET.API/API/Entities/JumpListSettings.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/JumpListSettings.cs
rename to src/ElectronNET.API/API/Entities/JumpListSettings.cs
diff --git a/src/ElectronNET.API/Entities/LoadURLOptions.cs b/src/ElectronNET.API/API/Entities/LoadURLOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/LoadURLOptions.cs
rename to src/ElectronNET.API/API/Entities/LoadURLOptions.cs
diff --git a/src/ElectronNET.API/Entities/LoginItemSettings.cs b/src/ElectronNET.API/API/Entities/LoginItemSettings.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/LoginItemSettings.cs
rename to src/ElectronNET.API/API/Entities/LoginItemSettings.cs
diff --git a/src/ElectronNET.API/Entities/LoginItemSettingsOptions.cs b/src/ElectronNET.API/API/Entities/LoginItemSettingsOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/LoginItemSettingsOptions.cs
rename to src/ElectronNET.API/API/Entities/LoginItemSettingsOptions.cs
diff --git a/src/ElectronNET.API/Entities/LoginSettings.cs b/src/ElectronNET.API/API/Entities/LoginSettings.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/LoginSettings.cs
rename to src/ElectronNET.API/API/Entities/LoginSettings.cs
diff --git a/src/ElectronNET.API/Entities/Margins.cs b/src/ElectronNET.API/API/Entities/Margins.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Margins.cs
rename to src/ElectronNET.API/API/Entities/Margins.cs
diff --git a/src/ElectronNET.API/Entities/MemoryInfo.cs b/src/ElectronNET.API/API/Entities/MemoryInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/MemoryInfo.cs
rename to src/ElectronNET.API/API/Entities/MemoryInfo.cs
diff --git a/src/ElectronNET.API/Entities/MenuItem.cs b/src/ElectronNET.API/API/Entities/MenuItem.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/MenuItem.cs
rename to src/ElectronNET.API/API/Entities/MenuItem.cs
diff --git a/src/ElectronNET.API/Entities/MenuRole.cs b/src/ElectronNET.API/API/Entities/MenuRole.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/MenuRole.cs
rename to src/ElectronNET.API/API/Entities/MenuRole.cs
diff --git a/src/ElectronNET.API/Entities/MenuType.cs b/src/ElectronNET.API/API/Entities/MenuType.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/MenuType.cs
rename to src/ElectronNET.API/API/Entities/MenuType.cs
diff --git a/src/ElectronNET.API/Entities/MessageBoxOptions.cs b/src/ElectronNET.API/API/Entities/MessageBoxOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/MessageBoxOptions.cs
rename to src/ElectronNET.API/API/Entities/MessageBoxOptions.cs
diff --git a/src/ElectronNET.API/Entities/MessageBoxResult.cs b/src/ElectronNET.API/API/Entities/MessageBoxResult.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/MessageBoxResult.cs
rename to src/ElectronNET.API/API/Entities/MessageBoxResult.cs
diff --git a/src/ElectronNET.API/Entities/MessageBoxType.cs b/src/ElectronNET.API/API/Entities/MessageBoxType.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/MessageBoxType.cs
rename to src/ElectronNET.API/API/Entities/MessageBoxType.cs
diff --git a/src/ElectronNET.API/Entities/ModifierType.cs b/src/ElectronNET.API/API/Entities/ModifierType.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ModifierType.cs
rename to src/ElectronNET.API/API/Entities/ModifierType.cs
diff --git a/src/ElectronNET.API/Entities/NativeImage.cs b/src/ElectronNET.API/API/Entities/NativeImage.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/NativeImage.cs
rename to src/ElectronNET.API/API/Entities/NativeImage.cs
diff --git a/src/ElectronNET.API/Entities/NativeImageJsonConverter.cs b/src/ElectronNET.API/API/Entities/NativeImageJsonConverter.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/NativeImageJsonConverter.cs
rename to src/ElectronNET.API/API/Entities/NativeImageJsonConverter.cs
diff --git a/src/ElectronNET.API/Entities/NotificationAction.cs b/src/ElectronNET.API/API/Entities/NotificationAction.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/NotificationAction.cs
rename to src/ElectronNET.API/API/Entities/NotificationAction.cs
diff --git a/src/ElectronNET.API/Entities/NotificationOptions.cs b/src/ElectronNET.API/API/Entities/NotificationOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/NotificationOptions.cs
rename to src/ElectronNET.API/API/Entities/NotificationOptions.cs
diff --git a/src/ElectronNET.API/Entities/OnDidFailLoadInfo.cs b/src/ElectronNET.API/API/Entities/OnDidFailLoadInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/OnDidFailLoadInfo.cs
rename to src/ElectronNET.API/API/Entities/OnDidFailLoadInfo.cs
diff --git a/src/ElectronNET.API/Entities/OnDidNavigateInfo.cs b/src/ElectronNET.API/API/Entities/OnDidNavigateInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/OnDidNavigateInfo.cs
rename to src/ElectronNET.API/API/Entities/OnDidNavigateInfo.cs
diff --git a/src/ElectronNET.API/Entities/OnTopLevel.cs b/src/ElectronNET.API/API/Entities/OnTopLevel.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/OnTopLevel.cs
rename to src/ElectronNET.API/API/Entities/OnTopLevel.cs
diff --git a/src/ElectronNET.API/Entities/OpenDevToolsOptions.cs b/src/ElectronNET.API/API/Entities/OpenDevToolsOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/OpenDevToolsOptions.cs
rename to src/ElectronNET.API/API/Entities/OpenDevToolsOptions.cs
diff --git a/src/ElectronNET.API/Entities/OpenDialogOptions.cs b/src/ElectronNET.API/API/Entities/OpenDialogOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/OpenDialogOptions.cs
rename to src/ElectronNET.API/API/Entities/OpenDialogOptions.cs
diff --git a/src/ElectronNET.API/Entities/OpenDialogProperty.cs b/src/ElectronNET.API/API/Entities/OpenDialogProperty.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/OpenDialogProperty.cs
rename to src/ElectronNET.API/API/Entities/OpenDialogProperty.cs
diff --git a/src/ElectronNET.API/Entities/OpenExternalOptions.cs b/src/ElectronNET.API/API/Entities/OpenExternalOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/OpenExternalOptions.cs
rename to src/ElectronNET.API/API/Entities/OpenExternalOptions.cs
diff --git a/src/ElectronNET.API/Entities/PathName.cs b/src/ElectronNET.API/API/Entities/PathName.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/PathName.cs
rename to src/ElectronNET.API/API/Entities/PathName.cs
diff --git a/src/ElectronNET.API/Entities/Point.cs b/src/ElectronNET.API/API/Entities/Point.cs
similarity index 58%
rename from src/ElectronNET.API/Entities/Point.cs
rename to src/ElectronNET.API/API/Entities/Point.cs
index a464bed..9b3bed8 100644
--- a/src/ElectronNET.API/Entities/Point.cs
+++ b/src/ElectronNET.API/API/Entities/Point.cs
@@ -20,5 +20,14 @@
/// The y.
///
public int Y { get; set; }
+
+ ///
+ /// Convert this to .
+ ///
+ /// The point.
+ public static implicit operator System.Drawing.Point(Point point)
+ {
+ return new System.Drawing.Point(point.X, point.Y);
+ }
}
}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Entities/PrintOptions.cs b/src/ElectronNET.API/API/Entities/PrintOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/PrintOptions.cs
rename to src/ElectronNET.API/API/Entities/PrintOptions.cs
diff --git a/src/ElectronNET.API/Entities/PrintToPDFOptions.cs b/src/ElectronNET.API/API/Entities/PrintToPDFOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/PrintToPDFOptions.cs
rename to src/ElectronNET.API/API/Entities/PrintToPDFOptions.cs
diff --git a/src/ElectronNET.API/Entities/PrinterInfo.cs b/src/ElectronNET.API/API/Entities/PrinterInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/PrinterInfo.cs
rename to src/ElectronNET.API/API/Entities/PrinterInfo.cs
diff --git a/src/ElectronNET.API/Entities/ProcessMetric.cs b/src/ElectronNET.API/API/Entities/ProcessMetric.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ProcessMetric.cs
rename to src/ElectronNET.API/API/Entities/ProcessMetric.cs
diff --git a/src/ElectronNET.API/Entities/ProgressBarMode.cs b/src/ElectronNET.API/API/Entities/ProgressBarMode.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ProgressBarMode.cs
rename to src/ElectronNET.API/API/Entities/ProgressBarMode.cs
diff --git a/src/ElectronNET.API/Entities/ProgressBarOptions.cs b/src/ElectronNET.API/API/Entities/ProgressBarOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ProgressBarOptions.cs
rename to src/ElectronNET.API/API/Entities/ProgressBarOptions.cs
diff --git a/src/ElectronNET.API/Entities/ProgressInfo.cs b/src/ElectronNET.API/API/Entities/ProgressInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ProgressInfo.cs
rename to src/ElectronNET.API/API/Entities/ProgressInfo.cs
diff --git a/src/ElectronNET.API/Entities/ProxyConfig.cs b/src/ElectronNET.API/API/Entities/ProxyConfig.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ProxyConfig.cs
rename to src/ElectronNET.API/API/Entities/ProxyConfig.cs
diff --git a/src/ElectronNET.API/Entities/ReadBookmark.cs b/src/ElectronNET.API/API/Entities/ReadBookmark.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ReadBookmark.cs
rename to src/ElectronNET.API/API/Entities/ReadBookmark.cs
diff --git a/src/ElectronNET.API/Entities/Rectangle.cs b/src/ElectronNET.API/API/Entities/Rectangle.cs
similarity index 67%
rename from src/ElectronNET.API/Entities/Rectangle.cs
rename to src/ElectronNET.API/API/Entities/Rectangle.cs
index 5d171e7..b062272 100644
--- a/src/ElectronNET.API/Entities/Rectangle.cs
+++ b/src/ElectronNET.API/API/Entities/Rectangle.cs
@@ -36,5 +36,14 @@
/// The height.
///
public int Height { get; set; }
+
+ ///
+ /// Convert this to .
+ ///
+ /// The rectangle.
+ public static implicit operator System.Drawing.Rectangle(Rectangle rectangle)
+ {
+ return new System.Drawing.Rectangle(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
+ }
}
}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Entities/RelaunchOptions.cs b/src/ElectronNET.API/API/Entities/RelaunchOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/RelaunchOptions.cs
rename to src/ElectronNET.API/API/Entities/RelaunchOptions.cs
diff --git a/src/ElectronNET.API/Entities/ReleaseNoteInfo.cs b/src/ElectronNET.API/API/Entities/ReleaseNoteInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ReleaseNoteInfo.cs
rename to src/ElectronNET.API/API/Entities/ReleaseNoteInfo.cs
diff --git a/src/ElectronNET.API/Entities/RemovePassword.cs b/src/ElectronNET.API/API/Entities/RemovePassword.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/RemovePassword.cs
rename to src/ElectronNET.API/API/Entities/RemovePassword.cs
diff --git a/src/ElectronNET.API/Entities/ResizeOptions.cs b/src/ElectronNET.API/API/Entities/ResizeOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ResizeOptions.cs
rename to src/ElectronNET.API/API/Entities/ResizeOptions.cs
diff --git a/src/ElectronNET.API/Entities/SaveDialogOptions.cs b/src/ElectronNET.API/API/Entities/SaveDialogOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/SaveDialogOptions.cs
rename to src/ElectronNET.API/API/Entities/SaveDialogOptions.cs
diff --git a/src/ElectronNET.API/Entities/Scheme.cs b/src/ElectronNET.API/API/Entities/Scheme.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Scheme.cs
rename to src/ElectronNET.API/API/Entities/Scheme.cs
diff --git a/src/ElectronNET.API/Entities/SemVer.cs b/src/ElectronNET.API/API/Entities/SemVer.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/SemVer.cs
rename to src/ElectronNET.API/API/Entities/SemVer.cs
diff --git a/src/ElectronNET.API/Entities/ShortcutDetails.cs b/src/ElectronNET.API/API/Entities/ShortcutDetails.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ShortcutDetails.cs
rename to src/ElectronNET.API/API/Entities/ShortcutDetails.cs
diff --git a/src/ElectronNET.API/Entities/ShortcutLinkOperation.cs b/src/ElectronNET.API/API/Entities/ShortcutLinkOperation.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ShortcutLinkOperation.cs
rename to src/ElectronNET.API/API/Entities/ShortcutLinkOperation.cs
diff --git a/src/ElectronNET.API/Entities/Size.cs b/src/ElectronNET.API/API/Entities/Size.cs
similarity index 59%
rename from src/ElectronNET.API/Entities/Size.cs
rename to src/ElectronNET.API/API/Entities/Size.cs
index d63d180..61bbd12 100644
--- a/src/ElectronNET.API/Entities/Size.cs
+++ b/src/ElectronNET.API/API/Entities/Size.cs
@@ -20,5 +20,14 @@
/// The height.
///
public int Height { get; set; }
+
+ ///
+ /// Convert this to .
+ ///
+ /// The size.
+ public static implicit operator System.Drawing.Size(Size size)
+ {
+ return new System.Drawing.Size(size.Width, size.Height);
+ }
}
}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Entities/ThemeSourceMode.cs b/src/ElectronNET.API/API/Entities/ThemeSourceMode.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ThemeSourceMode.cs
rename to src/ElectronNET.API/API/Entities/ThemeSourceMode.cs
diff --git a/src/ElectronNET.API/Entities/ThumbarButton.cs b/src/ElectronNET.API/API/Entities/ThumbarButton.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ThumbarButton.cs
rename to src/ElectronNET.API/API/Entities/ThumbarButton.cs
diff --git a/src/ElectronNET.API/Entities/ThumbarButtonFlag.cs b/src/ElectronNET.API/API/Entities/ThumbarButtonFlag.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ThumbarButtonFlag.cs
rename to src/ElectronNET.API/API/Entities/ThumbarButtonFlag.cs
diff --git a/src/ElectronNET.API/Entities/TitleBarStyle.cs b/src/ElectronNET.API/API/Entities/TitleBarStyle.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/TitleBarStyle.cs
rename to src/ElectronNET.API/API/Entities/TitleBarStyle.cs
diff --git a/src/ElectronNET.API/Entities/ToBitmapOptions.cs b/src/ElectronNET.API/API/Entities/ToBitmapOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ToBitmapOptions.cs
rename to src/ElectronNET.API/API/Entities/ToBitmapOptions.cs
diff --git a/src/ElectronNET.API/Entities/ToDataUrlOptions.cs b/src/ElectronNET.API/API/Entities/ToDataUrlOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ToDataUrlOptions.cs
rename to src/ElectronNET.API/API/Entities/ToDataUrlOptions.cs
diff --git a/src/ElectronNET.API/Entities/ToPNGOptions.cs b/src/ElectronNET.API/API/Entities/ToPNGOptions.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/ToPNGOptions.cs
rename to src/ElectronNET.API/API/Entities/ToPNGOptions.cs
diff --git a/src/ElectronNET.API/Entities/TrayClickEventArgs.cs b/src/ElectronNET.API/API/Entities/TrayClickEventArgs.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/TrayClickEventArgs.cs
rename to src/ElectronNET.API/API/Entities/TrayClickEventArgs.cs
diff --git a/src/ElectronNET.API/Entities/UpdateCancellationToken.cs b/src/ElectronNET.API/API/Entities/UpdateCancellationToken.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/UpdateCancellationToken.cs
rename to src/ElectronNET.API/API/Entities/UpdateCancellationToken.cs
diff --git a/src/ElectronNET.API/Entities/UpdateCheckResult.cs b/src/ElectronNET.API/API/Entities/UpdateCheckResult.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/UpdateCheckResult.cs
rename to src/ElectronNET.API/API/Entities/UpdateCheckResult.cs
diff --git a/src/ElectronNET.API/Entities/UpdateFileInfo.cs b/src/ElectronNET.API/API/Entities/UpdateFileInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/UpdateFileInfo.cs
rename to src/ElectronNET.API/API/Entities/UpdateFileInfo.cs
diff --git a/src/ElectronNET.API/Entities/UpdateInfo.cs b/src/ElectronNET.API/API/Entities/UpdateInfo.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/UpdateInfo.cs
rename to src/ElectronNET.API/API/Entities/UpdateInfo.cs
diff --git a/src/ElectronNET.API/Entities/UploadFile.cs b/src/ElectronNET.API/API/Entities/UploadFile.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/UploadFile.cs
rename to src/ElectronNET.API/API/Entities/UploadFile.cs
diff --git a/src/ElectronNET.API/Entities/UploadRawData.cs b/src/ElectronNET.API/API/Entities/UploadRawData.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/UploadRawData.cs
rename to src/ElectronNET.API/API/Entities/UploadRawData.cs
diff --git a/src/ElectronNET.API/Entities/UserTask.cs b/src/ElectronNET.API/API/Entities/UserTask.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/UserTask.cs
rename to src/ElectronNET.API/API/Entities/UserTask.cs
diff --git a/src/ElectronNET.API/Entities/Vibrancy.cs b/src/ElectronNET.API/API/Entities/Vibrancy.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/Vibrancy.cs
rename to src/ElectronNET.API/API/Entities/Vibrancy.cs
diff --git a/src/ElectronNET.API/Entities/WebPreferences.cs b/src/ElectronNET.API/API/Entities/WebPreferences.cs
similarity index 100%
rename from src/ElectronNET.API/Entities/WebPreferences.cs
rename to src/ElectronNET.API/API/Entities/WebPreferences.cs
diff --git a/src/ElectronNET.API/Extensions/EnumExtensions.cs b/src/ElectronNET.API/API/Extensions/EnumExtensions.cs
similarity index 100%
rename from src/ElectronNET.API/Extensions/EnumExtensions.cs
rename to src/ElectronNET.API/API/Extensions/EnumExtensions.cs
diff --git a/src/ElectronNET.API/Extensions/MenuItemExtensions.cs b/src/ElectronNET.API/API/Extensions/MenuItemExtensions.cs
similarity index 100%
rename from src/ElectronNET.API/Extensions/MenuItemExtensions.cs
rename to src/ElectronNET.API/API/Extensions/MenuItemExtensions.cs
diff --git a/src/ElectronNET.API/Extensions/ThumbarButtonExtensions.cs b/src/ElectronNET.API/API/Extensions/ThumbarButtonExtensions.cs
similarity index 100%
rename from src/ElectronNET.API/Extensions/ThumbarButtonExtensions.cs
rename to src/ElectronNET.API/API/Extensions/ThumbarButtonExtensions.cs
diff --git a/src/ElectronNET.API/GlobalShortcut.cs b/src/ElectronNET.API/API/GlobalShortcut.cs
similarity index 100%
rename from src/ElectronNET.API/GlobalShortcut.cs
rename to src/ElectronNET.API/API/GlobalShortcut.cs
diff --git a/src/ElectronNET.API/HostHook.cs b/src/ElectronNET.API/API/HostHook.cs
similarity index 100%
rename from src/ElectronNET.API/HostHook.cs
rename to src/ElectronNET.API/API/HostHook.cs
diff --git a/src/ElectronNET.API/HybridSupport.cs b/src/ElectronNET.API/API/HybridSupport.cs
similarity index 86%
rename from src/ElectronNET.API/HybridSupport.cs
rename to src/ElectronNET.API/API/HybridSupport.cs
index 935ea51..a84641e 100644
--- a/src/ElectronNET.API/HybridSupport.cs
+++ b/src/ElectronNET.API/API/HybridSupport.cs
@@ -15,7 +15,7 @@
{
get
{
- return !string.IsNullOrEmpty(BridgeSettings.SocketPort);
+ return ElectronNetRuntime.RuntimeController != null;
}
}
}
diff --git a/src/ElectronNET.API/IpcMain.cs b/src/ElectronNET.API/API/IpcMain.cs
similarity index 99%
rename from src/ElectronNET.API/IpcMain.cs
rename to src/ElectronNET.API/API/IpcMain.cs
index 3239083..31122d1 100644
--- a/src/ElectronNET.API/IpcMain.cs
+++ b/src/ElectronNET.API/API/IpcMain.cs
@@ -46,7 +46,7 @@ namespace ElectronNET.API
/// Callback Method.
public async Task On(string channel, Action listener)
{
- await BridgeConnector.Socket.Emit("registerIpcMainChannel", channel);
+ await BridgeConnector.Socket.Emit("registerIpcMainChannel", channel).ConfigureAwait(false);
BridgeConnector.Socket.Off(channel);
BridgeConnector.Socket.On(channel, (args) =>
{
diff --git a/src/ElectronNET.API/Menu.cs b/src/ElectronNET.API/API/Menu.cs
similarity index 100%
rename from src/ElectronNET.API/Menu.cs
rename to src/ElectronNET.API/API/Menu.cs
diff --git a/src/ElectronNET.API/NativeTheme.cs b/src/ElectronNET.API/API/NativeTheme.cs
similarity index 100%
rename from src/ElectronNET.API/NativeTheme.cs
rename to src/ElectronNET.API/API/NativeTheme.cs
diff --git a/src/ElectronNET.API/Notification.cs b/src/ElectronNET.API/API/Notification.cs
similarity index 100%
rename from src/ElectronNET.API/Notification.cs
rename to src/ElectronNET.API/API/Notification.cs
diff --git a/src/ElectronNET.API/PowerMonitor.cs b/src/ElectronNET.API/API/PowerMonitor.cs
similarity index 100%
rename from src/ElectronNET.API/PowerMonitor.cs
rename to src/ElectronNET.API/API/PowerMonitor.cs
diff --git a/src/ElectronNET.API/QuitEventArgs.cs b/src/ElectronNET.API/API/QuitEventArgs.cs
similarity index 100%
rename from src/ElectronNET.API/QuitEventArgs.cs
rename to src/ElectronNET.API/API/QuitEventArgs.cs
diff --git a/src/ElectronNET.API/Screen.cs b/src/ElectronNET.API/API/Screen.cs
similarity index 100%
rename from src/ElectronNET.API/Screen.cs
rename to src/ElectronNET.API/API/Screen.cs
diff --git a/src/ElectronNET.API/Session.cs b/src/ElectronNET.API/API/Session.cs
similarity index 99%
rename from src/ElectronNET.API/Session.cs
rename to src/ElectronNET.API/API/Session.cs
index a4cb6bb..a6acbc9 100644
--- a/src/ElectronNET.API/Session.cs
+++ b/src/ElectronNET.API/API/Session.cs
@@ -25,10 +25,13 @@ namespace ElectronNET.API
///
public Cookies Cookies { get; }
+ public WebRequest WebRequest { get; private set; }
+
internal Session(int id)
{
Id = id;
Cookies = new Cookies(id);
+ WebRequest = new WebRequest(id);
}
///
diff --git a/src/ElectronNET.API/Shell.cs b/src/ElectronNET.API/API/Shell.cs
similarity index 100%
rename from src/ElectronNET.API/Shell.cs
rename to src/ElectronNET.API/API/Shell.cs
diff --git a/src/ElectronNET.API/Tray.cs b/src/ElectronNET.API/API/Tray.cs
similarity index 95%
rename from src/ElectronNET.API/Tray.cs
rename to src/ElectronNET.API/API/Tray.cs
index e8947fa..906a58c 100644
--- a/src/ElectronNET.API/Tray.cs
+++ b/src/ElectronNET.API/API/Tray.cs
@@ -241,7 +241,7 @@ namespace ElectronNET.API
/// The menu item.
public async Task Show(string image, MenuItem menuItem)
{
- await Show(image, new MenuItem[] { menuItem });
+ await this.Show(image, new MenuItem[] { menuItem }).ConfigureAwait(false);
}
///
@@ -252,7 +252,7 @@ namespace ElectronNET.API
public async Task Show(string image, MenuItem[] menuItems)
{
menuItems.AddMenuItemsId();
- await BridgeConnector.Socket.Emit("create-tray", image, JArray.FromObject(menuItems, _jsonSerializer));
+ await BridgeConnector.Socket.Emit("create-tray", image, JArray.FromObject(menuItems, this._jsonSerializer)).ConfigureAwait(false);
_items.Clear();
_items.AddRange(menuItems);
@@ -270,7 +270,7 @@ namespace ElectronNET.API
/// The image.
public async Task Show(string image)
{
- await BridgeConnector.Socket.Emit("create-tray", image);
+ await BridgeConnector.Socket.Emit("create-tray", image).ConfigureAwait(false);
}
///
@@ -278,7 +278,7 @@ namespace ElectronNET.API
///
public async Task Destroy()
{
- await BridgeConnector.Socket.Emit("tray-destroy");
+ await BridgeConnector.Socket.Emit("tray-destroy").ConfigureAwait(false);
_items.Clear();
}
@@ -288,7 +288,7 @@ namespace ElectronNET.API
///
public async Task SetImage(string image)
{
- await BridgeConnector.Socket.Emit("tray-setImage", image);
+ await BridgeConnector.Socket.Emit("tray-setImage", image).ConfigureAwait(false);
}
///
@@ -297,7 +297,7 @@ namespace ElectronNET.API
///
public async Task SetPressedImage(string image)
{
- await BridgeConnector.Socket.Emit("tray-setPressedImage", image);
+ await BridgeConnector.Socket.Emit("tray-setPressedImage", image).ConfigureAwait(false);
}
///
@@ -306,7 +306,7 @@ namespace ElectronNET.API
///
public async Task SetToolTip(string toolTip)
{
- await BridgeConnector.Socket.Emit("tray-setToolTip", toolTip);
+ await BridgeConnector.Socket.Emit("tray-setToolTip", toolTip).ConfigureAwait(false);
}
///
@@ -315,7 +315,7 @@ namespace ElectronNET.API
///
public async Task SetTitle(string title)
{
- await BridgeConnector.Socket.Emit("tray-setTitle", title);
+ await BridgeConnector.Socket.Emit("tray-setTitle", title).ConfigureAwait(false);
}
///
@@ -324,7 +324,7 @@ namespace ElectronNET.API
///
public async Task DisplayBalloon(DisplayBalloonOptions options)
{
- await BridgeConnector.Socket.Emit("tray-displayBalloon", JObject.FromObject(options, _jsonSerializer));
+ await BridgeConnector.Socket.Emit("tray-displayBalloon", JObject.FromObject(options, this._jsonSerializer)).ConfigureAwait(false);
}
///
@@ -342,9 +342,9 @@ namespace ElectronNET.API
taskCompletionSource.SetResult(isDestroyed);
});
- await BridgeConnector.Socket.Emit("tray-isDestroyed");
+ await BridgeConnector.Socket.Emit("tray-isDestroyed").ConfigureAwait(false);
- return await taskCompletionSource.Task;
+ return await taskCompletionSource.Task.ConfigureAwait(false);
}
private readonly JsonSerializer _jsonSerializer = new()
@@ -367,7 +367,7 @@ namespace ElectronNET.API
/// The event name
/// The handler
public async Task On(string eventName, Action action)
- => await Events.Instance.On(ModuleName, eventName, action);
+ => await Events.Instance.On(ModuleName, eventName, action).ConfigureAwait(false);
///
/// Subscribe to an unmapped event on the module once.
///
@@ -381,6 +381,6 @@ namespace ElectronNET.API
/// The event name
/// The handler
public async Task Once(string eventName, Action action)
- => await Events.Instance.Once(ModuleName, eventName, action);
+ => await Events.Instance.Once(ModuleName, eventName, action).ConfigureAwait(false);
}
}
diff --git a/src/ElectronNET.API/WebContents.cs b/src/ElectronNET.API/API/WebContents.cs
similarity index 100%
rename from src/ElectronNET.API/WebContents.cs
rename to src/ElectronNET.API/API/WebContents.cs
diff --git a/src/ElectronNET.API/API/WebRequest.cs b/src/ElectronNET.API/API/WebRequest.cs
new file mode 100644
index 0000000..3b55106
--- /dev/null
+++ b/src/ElectronNET.API/API/WebRequest.cs
@@ -0,0 +1,61 @@
+ο»Ώusing Newtonsoft.Json.Linq;
+using System;
+
+namespace ElectronNET.API.Entities
+{
+ public class OnBeforeRequestDetails
+ {
+ public int Id { get; set; }
+ public string Url { get; set; }
+ public string Method { get; set; }
+ public int? WebContentsId { get; set; }
+ // Ensure all necessary properties are included as per Electron documentation
+ }
+
+ public class WebRequestFilter
+ {
+ public string[] Urls { get; set; }
+ }
+
+ public class WebRequest
+ {
+ public int Id { get; private set; }
+
+ internal WebRequest(int id)
+ {
+ Id = id;
+ }
+
+ private event Action> _onBeforeRequest;
+
+ public void OnBeforeRequest(WebRequestFilter filter, Action> listener)
+ {
+ if (_onBeforeRequest == null)
+ {
+ BridgeConnector.Socket.On($"webContents-session-webRequest-onBeforeRequest{Id}",
+ (args) =>
+ {
+ ////var details = ((JObject)args[0]).ToObject();
+ ////var callback = args.Length > 1 ? (Action)((response) => { BridgeConnector.Socket.Emit($"webContents-session-webRequest-onBeforeRequest-response{Id}", response); }) : null;
+ var details = ((JObject)args).ToObject();
+ var callback = (Action)((response) => { BridgeConnector.Socket.Emit($"webContents-session-webRequest-onBeforeRequest-response{Id}", response); });
+
+ _onBeforeRequest?.Invoke(details, callback);
+ });
+
+ BridgeConnector.Socket.Emit("register-webContents-session-webRequest-onBeforeRequest", Id, JObject.FromObject(filter));
+ }
+
+ _onBeforeRequest += listener;
+ }
+
+ public void RemoveListener(Action> listener)
+ {
+ _onBeforeRequest -= listener;
+ if (_onBeforeRequest == null)
+ {
+ BridgeConnector.Socket.Off($"webContents-session-webRequest-onBeforeRequest{Id}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/WindowManager.cs b/src/ElectronNET.API/API/WindowManager.cs
similarity index 92%
rename from src/ElectronNET.API/WindowManager.cs
rename to src/ElectronNET.API/API/WindowManager.cs
index 0f9b5b5..f5e714d 100644
--- a/src/ElectronNET.API/WindowManager.cs
+++ b/src/ElectronNET.API/API/WindowManager.cs
@@ -84,7 +84,7 @@ namespace ElectronNET.API
///
public async Task CreateWindowAsync(string loadUrl = "http://localhost")
{
- return await CreateWindowAsync(new BrowserWindowOptions(), loadUrl);
+ return await this.CreateWindowAsync(new BrowserWindowOptions(), loadUrl).ConfigureAwait(false);
}
///
@@ -124,9 +124,9 @@ namespace ElectronNET.API
}
});
- if (loadUrl.ToUpper() == "HTTP://LOCALHOST")
+ if (loadUrl.ToUpper() == "HTTP://LOCALHOST" && ElectronNetRuntime.AspNetWebPort.HasValue)
{
- loadUrl = $"{loadUrl}:{BridgeSettings.WebPort}";
+ loadUrl = $"{loadUrl}:{ElectronNetRuntime.AspNetWebPort}";
}
// Workaround Windows 10 / Electron Bug
@@ -142,7 +142,7 @@ namespace ElectronNET.API
options.X = 0;
options.Y = 0;
- await BridgeConnector.Socket.Emit("createBrowserWindow", JObject.FromObject(options, _jsonSerializer), loadUrl);
+ await BridgeConnector.Socket.Emit("createBrowserWindow", JObject.FromObject(options, this._jsonSerializer), loadUrl).ConfigureAwait(false);
}
else
{
@@ -158,10 +158,10 @@ namespace ElectronNET.API
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
- await BridgeConnector.Socket.Emit("createBrowserWindow", JObject.FromObject(options, ownjsonSerializer), loadUrl);
+ await BridgeConnector.Socket.Emit("createBrowserWindow", JObject.FromObject(options, ownjsonSerializer), loadUrl).ConfigureAwait(false);
}
- return await taskCompletionSource.Task;
+ return await taskCompletionSource.Task.ConfigureAwait(false);
}
private bool IsWindows10()
@@ -208,9 +208,9 @@ namespace ElectronNET.API
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
- await BridgeConnector.Socket.Emit("createBrowserView", JObject.FromObject(options, ownjsonSerializer));
+ await BridgeConnector.Socket.Emit("createBrowserView", JObject.FromObject(options, ownjsonSerializer)).ConfigureAwait(false);
- return await taskCompletionSource.Task;
+ return await taskCompletionSource.Task.ConfigureAwait(false);
}
private readonly JsonSerializer _jsonSerializer = new()
diff --git a/src/ElectronNET.API/API/web-request.md b/src/ElectronNET.API/API/web-request.md
new file mode 100644
index 0000000..a337836
--- /dev/null
+++ b/src/ElectronNET.API/API/web-request.md
@@ -0,0 +1,255 @@
+ο»Ώ## Class: WebRequest
+
+> Intercept and modify the contents of a request at various stages of its lifetime.
+
+Process: [Main](../glossary.md#main-process)
+_This class is not exported from the `'electron'` module. It is only available as a return value of other methods in the Electron API._
+
+Instances of the `WebRequest` class are accessed by using the `webRequest`
+property of a `Session`.
+
+The methods of `WebRequest` accept an optional `filter` and a `listener`. The
+`listener` will be called with `listener(details)` when the API's event has
+happened. The `details` object describes the request.
+
+β οΈ Only the last attached `listener` will be used. Passing `null` as `listener` will unsubscribe from the event.
+
+The `filter` object has a `urls` property which is an Array of URL
+patterns that will be used to filter out the requests that do not match the URL
+patterns. If the `filter` is omitted then all requests will be matched.
+
+For certain events the `listener` is passed with a `callback`, which should be
+called with a `response` object when `listener` has done its work.
+
+An example of adding `User-Agent` header for requests:
+
+```js
+const { session } = require('electron')
+
+// Modify the user agent for all requests to the following urls.
+const filter = {
+ urls: ['https://*.github.com/*', '*://electron.github.io/*']
+}
+
+session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
+ details.requestHeaders['User-Agent'] = 'MyAgent'
+ callback({ requestHeaders: details.requestHeaders })
+})
+```
+
+### Instance Methods
+
+The following methods are available on instances of `WebRequest`:
+
+#### `webRequest.onBeforeRequest([filter, ]listener)`
+
+* `filter` [WebRequestFilter](structures/web-request-filter.md) (optional)
+* `listener` Function | null
+ * `details` Object
+ * `id` Integer
+ * `url` string
+ * `method` string
+ * `webContentsId` Integer (optional)
+ * `webContents` WebContents (optional)
+ * `frame` WebFrameMain (optional)
+ * `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
+ * `referrer` string
+ * `timestamp` Double
+ * `uploadData` [UploadData[]](structures/upload-data.md)
+ * `callback` Function
+ * `response` Object
+ * `cancel` boolean (optional)
+ * `redirectURL` string (optional) - The original request is prevented from
+ being sent or completed and is instead redirected to the given URL.
+
+The `listener` will be called with `listener(details, callback)` when a request
+is about to occur.
+
+The `uploadData` is an array of `UploadData` objects.
+
+The `callback` has to be called with an `response` object.
+
+Some examples of valid `urls`:
+
+```js
+'http://foo:1234/'
+'http://foo.com/'
+'http://foo:1234/bar'
+'*://*/*'
+'*://example.com/*'
+'*://example.com/foo/*'
+'http://*.foo:1234/'
+'file://foo:1234/bar'
+'http://foo:*/'
+'*://www.foo.com/'
+```
+
+#### `webRequest.onBeforeSendHeaders([filter, ]listener)`
+
+* `filter` [WebRequestFilter](structures/web-request-filter.md) (optional)
+* `listener` Function | null
+ * `details` Object
+ * `id` Integer
+ * `url` string
+ * `method` string
+ * `webContentsId` Integer (optional)
+ * `webContents` WebContents (optional)
+ * `frame` WebFrameMain (optional)
+ * `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
+ * `referrer` string
+ * `timestamp` Double
+ * `uploadData` [UploadData[]](structures/upload-data.md) (optional)
+ * `requestHeaders` Record
+ * `callback` Function
+ * `beforeSendResponse` Object
+ * `cancel` boolean (optional)
+ * `requestHeaders` Record (optional) - When provided, request will be made
+ with these headers.
+
+The `listener` will be called with `listener(details, callback)` before sending
+an HTTP request, once the request headers are available. This may occur after a
+TCP connection is made to the server, but before any http data is sent.
+
+The `callback` has to be called with a `response` object.
+
+#### `webRequest.onSendHeaders([filter, ]listener)`
+
+* `filter` [WebRequestFilter](structures/web-request-filter.md) (optional)
+* `listener` Function | null
+ * `details` Object
+ * `id` Integer
+ * `url` string
+ * `method` string
+ * `webContentsId` Integer (optional)
+ * `webContents` WebContents (optional)
+ * `frame` WebFrameMain (optional)
+ * `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
+ * `referrer` string
+ * `timestamp` Double
+ * `requestHeaders` Record
+
+The `listener` will be called with `listener(details)` just before a request is
+going to be sent to the server, modifications of previous `onBeforeSendHeaders`
+response are visible by the time this listener is fired.
+
+#### `webRequest.onHeadersReceived([filter, ]listener)`
+
+* `filter` [WebRequestFilter](structures/web-request-filter.md) (optional)
+* `listener` Function | null
+ * `details` Object
+ * `id` Integer
+ * `url` string
+ * `method` string
+ * `webContentsId` Integer (optional)
+ * `webContents` WebContents (optional)
+ * `frame` WebFrameMain (optional)
+ * `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
+ * `referrer` string
+ * `timestamp` Double
+ * `statusLine` string
+ * `statusCode` Integer
+ * `responseHeaders` Record (optional)
+ * `callback` Function
+ * `headersReceivedResponse` Object
+ * `cancel` boolean (optional)
+ * `responseHeaders` Record (optional) - When provided, the server is assumed
+ to have responded with these headers.
+ * `statusLine` string (optional) - Should be provided when overriding
+ `responseHeaders` to change header status otherwise original response
+ header's status will be used.
+
+The `listener` will be called with `listener(details, callback)` when HTTP
+response headers of a request have been received.
+
+The `callback` has to be called with a `response` object.
+
+#### `webRequest.onResponseStarted([filter, ]listener)`
+
+* `filter` [WebRequestFilter](structures/web-request-filter.md) (optional)
+* `listener` Function | null
+ * `details` Object
+ * `id` Integer
+ * `url` string
+ * `method` string
+ * `webContentsId` Integer (optional)
+ * `webContents` WebContents (optional)
+ * `frame` WebFrameMain (optional)
+ * `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
+ * `referrer` string
+ * `timestamp` Double
+ * `responseHeaders` Record (optional)
+ * `fromCache` boolean - Indicates whether the response was fetched from disk
+ cache.
+ * `statusCode` Integer
+ * `statusLine` string
+
+The `listener` will be called with `listener(details)` when first byte of the
+response body is received. For HTTP requests, this means that the status line
+and response headers are available.
+
+#### `webRequest.onBeforeRedirect([filter, ]listener)`
+
+* `filter` [WebRequestFilter](structures/web-request-filter.md) (optional)
+* `listener` Function | null
+ * `details` Object
+ * `id` Integer
+ * `url` string
+ * `method` string
+ * `webContentsId` Integer (optional)
+ * `webContents` WebContents (optional)
+ * `frame` WebFrameMain (optional)
+ * `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
+ * `referrer` string
+ * `timestamp` Double
+ * `redirectURL` string
+ * `statusCode` Integer
+ * `statusLine` string
+ * `ip` string (optional) - The server IP address that the request was
+ actually sent to.
+ * `fromCache` boolean
+ * `responseHeaders` Record (optional)
+
+The `listener` will be called with `listener(details)` when a server initiated
+redirect is about to occur.
+
+#### `webRequest.onCompleted([filter, ]listener)`
+
+* `filter` [WebRequestFilter](structures/web-request-filter.md) (optional)
+* `listener` Function | null
+ * `details` Object
+ * `id` Integer
+ * `url` string
+ * `method` string
+ * `webContentsId` Integer (optional)
+ * `webContents` WebContents (optional)
+ * `frame` WebFrameMain (optional)
+ * `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
+ * `referrer` string
+ * `timestamp` Double
+ * `responseHeaders` Record (optional)
+ * `fromCache` boolean
+ * `statusCode` Integer
+ * `statusLine` string
+ * `error` string
+
+The `listener` will be called with `listener(details)` when a request is
+completed.
+
+#### `webRequest.onErrorOccurred([filter, ]listener)`
+
+* `filter` [WebRequestFilter](structures/web-request-filter.md) (optional)
+* `listener` Function | null
+ * `details` Object
+ * `id` Integer
+ * `url` string
+ * `method` string
+ * `webContentsId` Integer (optional)
+ * `webContents` WebContents (optional)
+ * `frame` WebFrameMain (optional)
+ * `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
+ * `referrer` string
+ * `timestamp` Double
+ * `fromCache` boolean
+ * `error` string - The error description.
+
+The `listener` will be called with `listener(details)` when an error occurs.
\ No newline at end of file
diff --git a/src/ElectronNET.API/Bridge/BridgeConnector.cs b/src/ElectronNET.API/Bridge/BridgeConnector.cs
new file mode 100644
index 0000000..3c06b43
--- /dev/null
+++ b/src/ElectronNET.API/Bridge/BridgeConnector.cs
@@ -0,0 +1,15 @@
+ο»Ώ#pragma warning disable IDE0130 // Namespace does not match folder structure
+// ReSharper disable once CheckNamespace
+namespace ElectronNET.API
+{
+ internal static class BridgeConnector
+ {
+ public static SocketIoFacade Socket
+ {
+ get
+ {
+ return ElectronNetRuntime.GetSocket();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Events.cs b/src/ElectronNET.API/Bridge/Events.cs
similarity index 68%
rename from src/ElectronNET.API/Events.cs
rename to src/ElectronNET.API/Bridge/Events.cs
index 4c3c366..8bcff02 100644
--- a/src/ElectronNET.API/Events.cs
+++ b/src/ElectronNET.API/Bridge/Events.cs
@@ -1,9 +1,11 @@
-ο»Ώusing System;
-using System.Globalization;
-using System.Threading.Tasks;
-
+ο»Ώ#pragma warning disable IDE0130 // Namespace does not match folder structure
+// ReSharper disable once CheckNamespace
namespace ElectronNET.API
{
+ using System;
+ using System.Globalization;
+ using System.Threading.Tasks;
+
///
/// Generic Event Consumers for Electron Modules
///
@@ -12,9 +14,9 @@ namespace ElectronNET.API
private static Events _events;
private static readonly object SyncRoot = new();
private readonly TextInfo _textInfo = new CultureInfo("en-US", false).TextInfo;
-
+
private Events()
- {}
+ { }
public static Events Instance
{
@@ -41,8 +43,14 @@ namespace ElectronNET.API
/// The name of the module, e.g. app, dock, etc...
/// The name of the event
/// The event handler
- public void On(string moduleName, string eventName, Action action)
- => On(moduleName, eventName, action);
+ public async Task On(string moduleName, string eventName, Action action)
+ {
+ var listener = $"{moduleName}{_textInfo.ToTitleCase(eventName)}Completed";
+ var subscriber = $"register-{moduleName}-on-event";
+
+ BridgeConnector.Socket.On(listener, action);
+ await BridgeConnector.Socket.Emit(subscriber, eventName, listener).ConfigureAwait(false);
+ }
///
@@ -57,17 +65,20 @@ namespace ElectronNET.API
var subscriber = $"register-{moduleName}-on-event";
BridgeConnector.Socket.On(listener, action);
- await BridgeConnector.Socket.Emit(subscriber, eventName, listener);
+ await BridgeConnector.Socket.Emit(subscriber, eventName, listener).ConfigureAwait(false);
}
- ///
- /// Subscribe to an unmapped electron event.
- ///
+ /// Subscribe to an unmapped electron event.
/// The name of the module, e.g. app, dock, etc...
/// The name of the event
- /// The event handler
- public void Once(string moduleName, string eventName, Action action)
- => Once(moduleName, eventName, action);
+ /// The action.
+ public async Task Once(string moduleName, string eventName, Action action)
+ {
+ var listener = $"{moduleName}{_textInfo.ToTitleCase(eventName)}Completed";
+ var subscriber = $"register-{moduleName}-once-event";
+ BridgeConnector.Socket.Once(listener, action);
+ await BridgeConnector.Socket.Emit(subscriber, eventName, listener).ConfigureAwait(false);
+ }
///
@@ -81,7 +92,7 @@ namespace ElectronNET.API
var listener = $"{moduleName}{_textInfo.ToTitleCase(eventName)}Completed";
var subscriber = $"register-{moduleName}-once-event";
BridgeConnector.Socket.Once(listener, action);
- await BridgeConnector.Socket.Emit(subscriber, eventName, listener);
+ await BridgeConnector.Socket.Emit(subscriber, eventName, listener).ConfigureAwait(false);
}
}
diff --git a/src/ElectronNET.API/SocketIOFacade.cs b/src/ElectronNET.API/Bridge/SocketIOFacade.cs
similarity index 69%
rename from src/ElectronNET.API/SocketIOFacade.cs
rename to src/ElectronNET.API/Bridge/SocketIOFacade.cs
index ec5eb46..c3815d6 100644
--- a/src/ElectronNET.API/SocketIOFacade.cs
+++ b/src/ElectronNET.API/Bridge/SocketIOFacade.cs
@@ -1,11 +1,13 @@
-ο»Ώusing System;
+ο»Ώ#pragma warning disable IDE0130 // Namespace does not match folder structure
+// ReSharper disable once CheckNamespace
+namespace ElectronNET.API;
+
+using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
-using SocketIOClient;
-using SocketIOClient.Newtonsoft.Json;
-
-namespace ElectronNET.API;
+using SocketIO.Serializer.NewtonsoftJson;
+using SocketIO = SocketIOClient.SocketIO;
internal class SocketIoFacade
{
@@ -21,9 +23,13 @@ internal class SocketIoFacade
DefaultValueHandling = DefaultValueHandling.Ignore
});
- _socket.JsonSerializer = jsonSerializer;
+ _socket.Serializer = jsonSerializer;
}
+ public event EventHandler BridgeDisconnected;
+
+ public event EventHandler BridgeConnected;
+
public void Connect()
{
_socket.OnError += (sender, e) =>
@@ -34,11 +40,13 @@ internal class SocketIoFacade
_socket.OnConnected += (_, _) =>
{
Console.WriteLine("BridgeConnector connected!");
+ this.BridgeConnected?.Invoke(this, EventArgs.Empty);
};
_socket.OnDisconnected += (_, _) =>
{
Console.WriteLine("BridgeConnector disconnected!");
+ this.BridgeDisconnected?.Invoke(this, EventArgs.Empty);
};
_socket.ConnectAsync().GetAwaiter().GetResult();
@@ -46,7 +54,7 @@ internal class SocketIoFacade
public void On(string eventName, Action action)
{
- _socket.On(eventName, response =>
+ _socket.On(eventName, _ =>
{
Task.Run(action);
});
@@ -59,19 +67,28 @@ internal class SocketIoFacade
var value = response.GetValue();
Task.Run(() => action(value));
});
- }
-
+ }
+
// TODO: Remove this method when SocketIoClient supports object deserialization
public void On(string eventName, Action action)
{
_socket.On(eventName, response =>
{
var value = response.GetValue();
- Console.WriteLine($"Called Event {eventName} - data {value}");
+ ////Console.WriteLine($"Called Event {eventName} - data {value}");
Task.Run(() => action(value));
});
}
+ public void Once(string eventName, Action action)
+ {
+ _socket.On(eventName, _ =>
+ {
+ _socket.Off(eventName);
+ Task.Run(action);
+ });
+ }
+
public void Once(string eventName, Action action)
{
_socket.On(eventName, (socketIoResponse) =>
@@ -88,6 +105,11 @@ internal class SocketIoFacade
public async Task Emit(string eventName, params object[] args)
{
- await _socket.EmitAsync(eventName, args);
+ await _socket.EmitAsync(eventName, args).ConfigureAwait(false);
+ }
+
+ public void DisposeSocket()
+ {
+ _socket.Dispose();
}
}
\ No newline at end of file
diff --git a/src/ElectronNET.API/BridgeConnector.cs b/src/ElectronNET.API/BridgeConnector.cs
deleted file mode 100644
index a12b130..0000000
--- a/src/ElectronNET.API/BridgeConnector.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-ο»Ώusing System;
-using Newtonsoft.Json.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace ElectronNET.API
-{
- internal static class BridgeConnector
- {
- private static SocketIoFacade _socket;
- private static readonly object SyncRoot = new();
-
- public static SocketIoFacade Socket
- {
- get
- {
- if (_socket == null)
- {
- lock (SyncRoot)
- {
- if (_socket == null)
- {
-
- string socketUrl = HybridSupport.IsElectronActive
- ? $"http://localhost:{BridgeSettings.SocketPort}"
- : "http://localhost";
-
- _socket = new SocketIoFacade(socketUrl);
- _socket.Connect();
- }
- }
- }
-
- return _socket;
- }
- }
-
- public static async Task GetValueOverSocketAsync(string eventString, string eventCompletedString)
- {
- CancellationToken cancellationToken = new();
- cancellationToken.ThrowIfCancellationRequested();
-
- var taskCompletionSource = new TaskCompletionSource();
- using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
- {
- BridgeConnector.Socket.On(eventCompletedString, (value) =>
- {
- BridgeConnector.Socket.Off(eventCompletedString);
-
- if (value == null)
- {
- Console.WriteLine($"ERROR: BridgeConnector (event: '{eventString}') returned null. Socket loop hang.");
- taskCompletionSource.SetCanceled();
- return;
- }
-
- try
- {
- taskCompletionSource.SetResult( new JValue(value).ToObject() );
- }
- catch (Exception e)
- {
- Console.WriteLine($"ERROR: BridgeConnector (event: '{eventString}') exception: {e.Message}. Socket loop hung.");
- }
- });
-
- BridgeConnector.Socket.Emit(eventString);
-
- return await taskCompletionSource.Task.ConfigureAwait(false);
- }
- }
-
- public static async Task GetObjectOverSocketAsync(string eventString, string eventCompletedString)
- {
- CancellationToken cancellationToken = new();
- cancellationToken.ThrowIfCancellationRequested();
-
- var taskCompletionSource = new TaskCompletionSource();
- using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
- {
- BridgeConnector.Socket.On(eventCompletedString, (value) =>
- {
- BridgeConnector.Socket.Off(eventCompletedString);
- taskCompletionSource.SetResult( ((JObject)value).ToObject() );
- });
-
- BridgeConnector.Socket.Emit(eventString);
-
- return await taskCompletionSource.Task.ConfigureAwait(false);
- }
- }
-
- public static async Task GetArrayOverSocketAsync(string eventString, string eventCompletedString)
- {
- CancellationToken cancellationToken = new();
- cancellationToken.ThrowIfCancellationRequested();
-
- var taskCompletionSource = new TaskCompletionSource();
- using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
- {
- BridgeConnector.Socket.On(eventCompletedString, (value) =>
- {
- BridgeConnector.Socket.Off(eventCompletedString);
- taskCompletionSource.SetResult( ((JArray)value).ToObject() );
- });
-
- BridgeConnector.Socket.Emit(eventString);
-
- return await taskCompletionSource.Task.ConfigureAwait(false);
- }
- }
-
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.API/BridgeSettings.cs b/src/ElectronNET.API/BridgeSettings.cs
deleted file mode 100644
index 96a2653..0000000
--- a/src/ElectronNET.API/BridgeSettings.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-ο»Ώnamespace ElectronNET.API
-{
- ///
- ///
- ///
- public static class BridgeSettings
- {
- ///
- /// Gets the socket port.
- ///
- ///
- /// The socket port.
- ///
- public static string SocketPort { get; internal set; }
-
- ///
- /// Gets the web port.
- ///
- ///
- /// The web port.
- ///
- public static string WebPort { get; internal set; }
- }
-}
diff --git a/src/ElectronNET.API/Common/Extensions.cs b/src/ElectronNET.API/Common/Extensions.cs
new file mode 100644
index 0000000..8d9a90a
--- /dev/null
+++ b/src/ElectronNET.API/Common/Extensions.cs
@@ -0,0 +1,37 @@
+ο»Ώnamespace ElectronNET.Common
+{
+ using System;
+ using System.Collections.Immutable;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Services;
+
+ public static class Extensions
+ {
+ public static bool IsUnpackaged(this StartupMethod method)
+ {
+ switch (method)
+ {
+ case StartupMethod.UnpackedElectronFirst:
+ case StartupMethod.UnpackedDotnetFirst:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static bool IsReady(this LifetimeServiceBase service)
+ {
+ return service != null && service.State == LifetimeState.Ready;
+ }
+
+ public static bool IsNotStopped(this LifetimeServiceBase service)
+ {
+ return service != null && service.State != LifetimeState.Stopped;
+ }
+
+ public static bool IsNullOrStopped(this LifetimeServiceBase service)
+ {
+ return service == null || service.State == LifetimeState.Stopped;
+ }
+ }
+}
diff --git a/src/ElectronNET.API/Common/ProcessRunner.cs b/src/ElectronNET.API/Common/ProcessRunner.cs
new file mode 100644
index 0000000..f3c3529
--- /dev/null
+++ b/src/ElectronNET.API/Common/ProcessRunner.cs
@@ -0,0 +1,595 @@
+ο»Ώnamespace ElectronNET.Common
+{
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ ///
+ /// Class encapsulating out-of-process execution of console applications.
+ ///
+ ///
+ /// Why this class?
+ /// Probably everybody who has tried to use System.Diagnotics.Process cross-platform and with reading
+ /// stderr and stdout will know that it is a pretty quirky API.
+ /// The code below may look weird, even non-sensical, but it works 100% reliable with all .net frameworks
+ /// and .net versions and on every platform where .net runs. This is just the innermost core, that's why
+ /// there are many dead ends, but it has all the crucial parts.
+ ///
+ ///
+ [SuppressMessage("ReSharper", "SuspiciousLockOverSynchronizationPrimitive")]
+ public class ProcessRunner : IDisposable
+ {
+ private volatile Process process;
+ private readonly StringBuilder stdOut = new StringBuilder(4 * 1024);
+ private readonly StringBuilder stdErr = new StringBuilder(4 * 1024);
+
+ private volatile ManualResetEvent stdOutEvent;
+ private volatile ManualResetEvent stdErrEvent;
+ private volatile Stopwatch stopwatch;
+
+ /// Initializes a new instance of the class.
+ /// A name identifying the process to execute.
+ public ProcessRunner(string name)
+ {
+ this.Name = name;
+ }
+
+ public event EventHandler ProcessExited;
+
+ public bool IsDisposed { get; private set; }
+
+ private Process Process
+ {
+ get
+ {
+ return this.process;
+ }
+ }
+
+ public bool IsRunning
+ {
+ get
+ {
+ var proc = this.process;
+ if (proc != null)
+ {
+ try
+ {
+ return !proc.HasExited;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ /// Gets the name identifying the process.
+ /// The name.
+ public string Name { get; }
+
+ public string CommandLine { get; private set; }
+
+ public string ExecutableFileName { get; private set; }
+
+ public string WorkingFolder { get; private set; }
+
+ public bool RecordStandardOutput { get; set; }
+
+ public bool RecordStandardError { get; set; }
+
+ public string StandardOutput
+ {
+ get
+ {
+ lock (this.stdOut)
+ {
+ return this.stdOut.ToString();
+ }
+ }
+ }
+
+ public string StandardError
+ {
+ get
+ {
+ lock (this.stdErr)
+ {
+ return this.stdErr.ToString();
+ }
+ }
+ }
+
+ public int? LastExitCode { get; private set; }
+
+ public bool Run(string exeFileName, string commandLineArgs, string workingDirectory)
+ {
+ this.CommandLine = commandLineArgs;
+ this.WorkingFolder = workingDirectory;
+ this.ExecutableFileName = exeFileName;
+
+ var startInfo = new RunnerParams(exeFileName)
+ {
+ Arguments = commandLineArgs,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = true,
+ ErrorDialog = false,
+ CreateNoWindow = true,
+ WorkingDirectory = workingDirectory
+ };
+
+ return this.Run(startInfo);
+ }
+
+ protected bool Run(RunnerParams runnerParams)
+ {
+ if (this.IsDisposed)
+ {
+ throw new ObjectDisposedException(this.GetType().ToString());
+ }
+
+ this.Close();
+
+ this.LastExitCode = null;
+
+ lock (this.stdOut)
+ {
+ this.stdOut.Clear();
+ }
+
+ lock (this.stdErr)
+ {
+ this.stdErr.Clear();
+ }
+
+ this.stdOutEvent = new ManualResetEvent(false);
+ this.stdErrEvent = new ManualResetEvent(false);
+
+ if (!this.OnBeforeStartProcessCore(runnerParams))
+ {
+ return false;
+ }
+
+ var startInfo = new ProcessStartInfo(runnerParams.FileName)
+ {
+ Arguments = runnerParams.Arguments,
+ UseShellExecute = runnerParams.UseShellExecute,
+ RedirectStandardOutput = runnerParams.RedirectStandardOutput,
+ RedirectStandardError = runnerParams.RedirectStandardError,
+ RedirectStandardInput = runnerParams.RedirectStandardInput,
+ ErrorDialog = runnerParams.ErrorDialog,
+ CreateNoWindow = runnerParams.CreateNoWindow,
+ WorkingDirectory = runnerParams.WorkingDirectory
+ };
+
+ foreach (var variableSetting in runnerParams.EnvironmentVariables)
+ {
+ startInfo.EnvironmentVariables[variableSetting.Key] = variableSetting.Value;
+ }
+
+ var proc = new Process { StartInfo = startInfo };
+
+ proc.EnableRaisingEvents = true;
+
+ this.RegisterProcessEvents(proc);
+
+ this.process = proc;
+
+ try
+ {
+ this.process.Start();
+ this.stopwatch = Stopwatch.StartNew();
+ this.process.BeginOutputReadLine();
+ this.process.BeginErrorReadLine();
+ this.process.Refresh();
+ this.OnProcessStartedCore();
+ }
+ catch (Exception ex)
+ {
+ this.OnProcessErrorCore(ex);
+ this.Close();
+ throw;
+ }
+
+ return true;
+ }
+
+ public async Task WriteAsync(string data)
+ {
+ var proc = this.Process;
+ if (proc != null && !proc.HasExited)
+ {
+ try
+ {
+ await proc.StandardInput.WriteAsync(data).ConfigureAwait(false);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("{0}.{1}: {2}", ex, nameof(ProcessRunner), nameof(this.WriteAsync));
+ }
+ }
+
+ return false;
+ }
+
+ public bool WaitForExit()
+ {
+ var proc = this.process;
+
+ if (proc == null)
+ {
+ return true;
+ }
+
+ try
+ {
+ // Wait for process and all I/O to finish.
+ proc.WaitForExit();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ this.OnProcessErrorCore(ex);
+ return false;
+ }
+ }
+
+ /// Sychronously waits for the specified amount and ends the process afterwards.
+ /// The timeout ms.
+ /// This method allows for a clean exit, since it also waits until the StandardOutput and
+ /// StandardError pipes are processed to the end.
+ /// true, if the process has exited gracefully; false otherwise.
+ public bool WaitAndKill(int timeoutMs)
+ {
+ var proc = this.process;
+
+ if (proc == null)
+ {
+ return true;
+ }
+
+ try
+ {
+ if (timeoutMs <= 0)
+ {
+ throw new ArgumentException("Argument must be greater then 0", nameof(timeoutMs));
+ }
+
+ // Timed waiting. We need to wait for I/O ourselves.
+ if (!proc.WaitForExit(timeoutMs))
+ {
+ this.Cancel();
+ }
+
+ // Wait for the I/O to finish.
+ var waitMs = (int)(timeoutMs - this.stopwatch.ElapsedMilliseconds);
+ waitMs = Math.Max(waitMs, 10);
+ this.stdOutEvent?.WaitOne(waitMs);
+
+ waitMs = (int)(timeoutMs - this.stopwatch.ElapsedMilliseconds);
+ waitMs = Math.Max(waitMs, 10);
+ return this.stdErrEvent?.WaitOne(waitMs) ?? false;
+ }
+ finally
+ {
+ // Cleanup.
+ this.Cancel();
+ }
+ }
+
+ /// Asynchronously waits for the specified amount and ends the process afterwards.
+ /// The timeout ms.
+ /// Tjhis method performs the wait operation on a threadpool thread.
+ /// Only recommended for short timeouts and situations where a synchronous call is undesired.
+ /// true, if the process has exited gracefully; false otherwise.
+ public Task WaitAndKillAsync(int timeoutMs)
+ {
+ var task = Task.Run(() => this.WaitAndKill(timeoutMs));
+ return task;
+ }
+
+ /// Waits asynchronously for the process to exit.
+ /// The timeout ms.
+ /// The cancellation token.
+ /// true, if the process has exited, false if the process is still running.
+ ///
+ /// This methods waits until the process has existed or the
+ /// has elapsed.
+ /// This method does not end the process itself.
+ ///
+ public Task WaitForExitAsync(int timeoutMs, CancellationToken cancellationToken = default)
+ {
+ timeoutMs = Math.Max(0, timeoutMs);
+
+ var timeoutSource = new CancellationTokenSource(timeoutMs);
+ var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, cancellationToken);
+
+ return this.WaitForExitAsync(linkedSource.Token);
+ }
+
+ /// Waits asynchronously for the process to exit.
+ /// The cancellation token.
+ /// This methods waits until the process has existed or the
+ /// has been triggered.
+ /// This method does not end the process itself.
+ /// true, if the process has exited, false if the process is still running.
+ public async Task WaitForExitAsync(CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var proc = this.process;
+
+ if (proc == null)
+ {
+ return true;
+ }
+
+ var tcs = new TaskCompletionSource();
+
+ // Use local function instead of a lambda to allow proper deregistration of the event
+ void ProcessExited(object sender, EventArgs e)
+ {
+ tcs.TrySetResult(true);
+ }
+
+ try
+ {
+ proc.EnableRaisingEvents = true;
+ proc.Exited += ProcessExited;
+
+ if (proc.HasExited)
+ {
+ return true;
+ }
+
+ using (cancellationToken.Register(() => tcs.TrySetResult(false)))
+ {
+ return await tcs.Task.ConfigureAwait(false);
+ }
+ }
+ finally
+ {
+ proc.Exited -= ProcessExited;
+ }
+ }
+
+ public void Cancel()
+ {
+ var proc = this.process;
+
+ if (proc != null)
+ {
+ try
+ {
+ // Invalidate cached data to requery.
+ proc.Refresh();
+
+ // We need to do this in case of a non-UI proc
+ // or one to be forced to cancel.
+ if (!proc.HasExited)
+ {
+ // Cancel all pending IO ops.
+ proc.CancelErrorRead();
+ proc.CancelOutputRead();
+ }
+
+ if (!proc.HasExited)
+ {
+ proc.Kill();
+ }
+ }
+ catch
+ {
+ // Kill will throw when/if the process has already exited.
+ }
+ }
+
+ var outEvent = this.stdOutEvent;
+ this.stdOutEvent = null;
+ if (outEvent != null)
+ {
+ lock (outEvent)
+ {
+ outEvent.Close();
+ outEvent.Dispose();
+ }
+ }
+
+ var errEvent = this.stdErrEvent;
+ this.stdErrEvent = null;
+ if (errEvent != null)
+ {
+ lock (errEvent)
+ {
+ errEvent.Close();
+ errEvent.Dispose();
+ }
+ }
+ }
+
+ private void Close()
+ {
+ this.Cancel();
+
+ var proc = this.process;
+ this.process = null;
+ if (proc != null)
+ {
+ try
+ {
+ this.UnRegisterProcessEvents(proc);
+
+ // Dispose in all cases.
+ proc.Close();
+ proc.Dispose();
+ }
+ catch (Exception ex)
+ {
+ this.OnProcessErrorCore(ex);
+ }
+ }
+ }
+
+ protected virtual void OnDispose()
+ {
+ }
+
+ void IDisposable.Dispose()
+ {
+ this.IsDisposed = true;
+ this.Close();
+ this.OnDispose();
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0}: {1} {2}", this.GetType().Name, this.Name, this.process);
+ }
+
+ protected virtual bool OnBeforeStartProcessCore(RunnerParams processRunnerInfo)
+ {
+ return true;
+ }
+
+ protected virtual void OnProcessStartedCore()
+ {
+ }
+
+ protected virtual void OnProcessErrorCore(Exception processException)
+ {
+ }
+
+ protected virtual void OnProcessExitCore(int exitCode)
+ {
+ }
+
+ private void RegisterProcessEvents(Process proc)
+ {
+ proc.ErrorDataReceived += this.Process_ErrorDataReceived;
+ proc.OutputDataReceived += this.Process_OutputDataReceived;
+ proc.Exited += this.Process_Exited;
+ }
+
+ private void UnRegisterProcessEvents(Process proc)
+ {
+ proc.ErrorDataReceived -= this.Process_ErrorDataReceived;
+ proc.OutputDataReceived -= this.Process_OutputDataReceived;
+ proc.Exited -= this.Process_Exited;
+ }
+
+ private void Process_Exited(object sender, EventArgs e)
+ {
+ this.WaitForExitAfterExited();
+ this.SetExitCode();
+ this.OnProcessExitCore(this.LastExitCode ?? -9998);
+ this.ProcessExited?.Invoke(this, new EventArgs());
+ }
+
+ private void WaitForExitAfterExited()
+ {
+ try
+ {
+ // This shouldn't throw here, but the mono process implementation doesn't always behave as it should.
+ this.process.WaitForExit();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Error when calling WaitForExit after exited event has fired: {0}.{1}: {2}", ex, nameof(ProcessRunner), nameof(this.WaitForExitAfterExited));
+ }
+ }
+
+ private void SetExitCode()
+ {
+ int exitCode = -9999;
+
+ try
+ {
+ if (this.Process != null)
+ {
+ exitCode = this.Process.ExitCode;
+ }
+ }
+ catch
+ {
+ // Ignore error.
+ }
+
+ this.LastExitCode = exitCode;
+ }
+
+ private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (this.RecordStandardError)
+ {
+ lock (this.stdErr)
+ {
+ this.stdErr.AppendLine(e.Data);
+ }
+ }
+
+ if (e.Data != null)
+ {
+ Console.WriteLine("|| " + e.Data);
+ }
+ else
+ {
+ var evt = this.stdErrEvent;
+ if (evt != null)
+ {
+ lock (evt)
+ {
+ try
+ {
+ evt.Set();
+ }
+ catch
+ {
+ // Ignore error.
+ }
+ }
+ }
+ }
+ }
+
+ private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (this.RecordStandardOutput)
+ {
+ lock (this.stdOut)
+ {
+ this.stdOut.AppendLine(e.Data);
+ }
+ }
+
+ if (e.Data != null)
+ {
+ Console.WriteLine("|| " + e.Data);
+ }
+ else
+ {
+ var evt = this.stdOutEvent;
+ if (evt != null)
+ {
+ lock (evt)
+ {
+ try
+ {
+ evt.Set();
+ }
+ catch
+ {
+ // Ignore error.
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Common/RunnerParams.cs b/src/ElectronNET.API/Common/RunnerParams.cs
new file mode 100644
index 0000000..001b881
--- /dev/null
+++ b/src/ElectronNET.API/Common/RunnerParams.cs
@@ -0,0 +1,163 @@
+ο»Ώnamespace ElectronNET.Common
+{
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Text;
+
+ public sealed class RunnerParams
+ {
+ private string fileName;
+ private string arguments;
+ private string directory;
+ private string userName;
+ private string verb;
+ private ProcessWindowStyle windowStyle;
+
+ ///
+ /// Default constructor. At least the
+ /// property must be set before starting the process.
+ ///
+ public RunnerParams()
+ {
+ }
+
+ ///
+ /// Specifies the name of the application or document that is to be started.
+ ///
+ public RunnerParams(string fileName)
+ {
+ this.fileName = fileName;
+ }
+
+ ///
+ /// Specifies the name of the application that is to be started, as well as a set
+ /// of command line arguments to pass to the application.
+ ///
+ public RunnerParams(string fileName, string arguments)
+ {
+ this.fileName = fileName;
+ this.arguments = arguments;
+ }
+
+ ///
+ /// Specifies the set of command line arguments to use when starting the application.
+ ///
+ public string Arguments
+ {
+ get
+ {
+ return this.arguments ?? string.Empty;
+ }
+
+ set
+ {
+ this.arguments = value;
+ }
+ }
+
+ public bool CreateNoWindow { get; set; }
+
+ public Dictionary EnvironmentVariables { get; set; } = new Dictionary();
+
+ public bool RedirectStandardInput { get; set; }
+
+ public bool RedirectStandardOutput { get; set; }
+
+ public bool RedirectStandardError { get; set; }
+
+ public Encoding StandardInputEncoding { get; set; }
+
+ public Encoding StandardErrorEncoding { get; set; }
+
+ public Encoding StandardOutputEncoding { get; set; }
+
+ ///
+ ///
+ /// Returns or sets the application, document, or URL that is to be launched.
+ ///
+ ///
+ public string FileName
+ {
+ get
+ {
+ return this.fileName ?? string.Empty;
+ }
+
+ set
+ {
+ this.fileName = value;
+ }
+ }
+
+ ///
+ /// Returns or sets the initial directory for the process that is started.
+ /// Specify "" to if the default is desired.
+ ///
+ public string WorkingDirectory
+ {
+ get
+ {
+ return this.directory ?? string.Empty;
+ }
+
+ set
+ {
+ this.directory = value;
+ }
+ }
+
+ public bool ErrorDialog { get; set; }
+
+ public IntPtr ErrorDialogParentHandle { get; set; }
+
+ public string UserName
+ {
+ get
+ {
+ return this.userName ?? string.Empty;
+ }
+
+ set
+ {
+ this.userName = value;
+ }
+ }
+
+ [DefaultValue("")]
+ public string Verb
+ {
+ get
+ {
+ return this.verb ?? string.Empty;
+ }
+
+ set
+ {
+ this.verb = value;
+ }
+ }
+
+ [DefaultValue(ProcessWindowStyle.Normal)]
+ public ProcessWindowStyle WindowStyle
+ {
+ get
+ {
+ return this.windowStyle;
+ }
+
+ set
+ {
+ if (!Enum.IsDefined(typeof(ProcessWindowStyle), value))
+ {
+ throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(ProcessWindowStyle));
+ }
+
+ this.windowStyle = value;
+ }
+ }
+
+ public bool UseShellExecute { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Converter/ModifierTypeListConverter.cs b/src/ElectronNET.API/Converter/ModifierTypeListConverter.cs
index e4b4446..5d1daed 100644
--- a/src/ElectronNET.API/Converter/ModifierTypeListConverter.cs
+++ b/src/ElectronNET.API/Converter/ModifierTypeListConverter.cs
@@ -1,12 +1,12 @@
-ο»Ώusing System;
+ο»Ώnamespace ElectronNET.Converter;
+
+using System;
using System.Collections.Generic;
using System.Linq;
using ElectronNET.API.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-namespace ElectronNET.API.Converter;
-
///
///
///
diff --git a/src/ElectronNET.API/Cookies.cs b/src/ElectronNET.API/Cookies.cs
deleted file mode 100644
index 98be3d4..0000000
--- a/src/ElectronNET.API/Cookies.cs
+++ /dev/null
@@ -1,154 +0,0 @@
-ο»Ώusing System;
-using System.Threading.Tasks;
-using ElectronNET.API.Entities;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Newtonsoft.Json.Serialization;
-
-namespace ElectronNET.API
-{
- ///
- /// Query and modify a session's cookies.
- ///
- public class Cookies
- {
- ///
- /// Gets the identifier.
- ///
- ///
- /// The identifier.
- ///
- public int Id { get; private set; }
-
- internal Cookies(int id)
- {
- Id = id;
- }
-
- ///
- /// Emitted when a cookie is changed because it was added, edited, removed, or expired.
- ///
- public event Action OnChanged
- {
- add
- {
- if (_changed == null)
- {
- BridgeConnector.Socket.On("webContents-session-cookies-changed" + Id, (args) =>
- {
- Cookie cookie = ((JArray)args)[0].ToObject();
- CookieChangedCause cause = ((JArray)args)[1].ToObject();
- bool removed = ((JArray)args)[2].ToObject();
- _changed(cookie, cause, removed);
- });
-
- BridgeConnector.Socket.Emit("register-webContents-session-cookies-changed", Id);
- }
- _changed += value;
- }
- remove
- {
- _changed -= value;
-
- if (_changed == null)
- BridgeConnector.Socket.Off("webContents-session-cookies-changed" + Id);
- }
- }
-
- private event Action _changed;
-
- ///
- /// Sends a request to get all cookies matching filter, and resolves a callack with the response.
- ///
- ///
- ///
- /// A task which resolves an array of cookie objects.
- public Task GetAsync(CookieFilter filter)
- {
- var taskCompletionSource = new TaskCompletionSource();
- string guid = Guid.NewGuid().ToString();
-
- BridgeConnector.Socket.On("webContents-session-cookies-get-completed" + guid, (cookies) =>
- {
- Cookie[] result = ((JArray)cookies).ToObject();
-
- BridgeConnector.Socket.Off("webContents-session-cookies-get-completed" + guid);
- taskCompletionSource.SetResult(result);
- });
-
- BridgeConnector.Socket.Emit("webContents-session-cookies-get", Id, JObject.FromObject(filter, _jsonSerializer), guid);
-
- return taskCompletionSource.Task;
- }
-
- ///
- ///
- ///
- ///
- ///
- public Task SetAsync(CookieDetails details)
- {
- var taskCompletionSource = new TaskCompletionSource();
- string guid = Guid.NewGuid().ToString();
-
- BridgeConnector.Socket.On("webContents-session-cookies-set-completed" + guid, () =>
- {
- BridgeConnector.Socket.Off("webContents-session-cookies-set-completed" + guid);
- taskCompletionSource.SetResult(null);
- });
-
- BridgeConnector.Socket.Emit("webContents-session-cookies-set", Id, JObject.FromObject(details, _jsonSerializer), guid);
-
- return taskCompletionSource.Task;
- }
-
- ///
- /// Removes the cookies matching url and name
- ///
- /// The URL associated with the cookie.
- /// The name of cookie to remove.
- /// A task which resolves when the cookie has been removed
- public Task RemoveAsync(string url, string name)
- {
- var taskCompletionSource = new TaskCompletionSource();
- string guid = Guid.NewGuid().ToString();
-
- BridgeConnector.Socket.On("webContents-session-cookies-remove-completed" + guid, () =>
- {
- BridgeConnector.Socket.Off("webContents-session-cookies-remove-completed" + guid);
- taskCompletionSource.SetResult(null);
- });
-
- BridgeConnector.Socket.Emit("webContents-session-cookies-remove", Id, url, name, guid);
-
- return taskCompletionSource.Task;
- }
-
- ///
- /// Writes any unwritten cookies data to disk.
- ///
- /// A task which resolves when the cookie store has been flushed
- public Task FlushStoreAsync()
- {
- var taskCompletionSource = new TaskCompletionSource();
- string guid = Guid.NewGuid().ToString();
-
- BridgeConnector.Socket.On("webContents-session-cookies-flushStore-completed" + guid, () =>
- {
- BridgeConnector.Socket.Off("webContents-session-cookies-flushStore-completed" + guid);
- taskCompletionSource.SetResult(null);
- });
-
- BridgeConnector.Socket.Emit("webContents-session-cookies-flushStore", Id, guid);
-
- return taskCompletionSource.Task;
- }
-
- private JsonSerializer _jsonSerializer = new JsonSerializer()
- {
- ContractResolver = new CamelCasePropertyNamesContractResolver(),
- NullValueHandling = NullValueHandling.Ignore,
- DefaultValueHandling = DefaultValueHandling.Ignore
- };
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.API/ElectronNET.API.csproj b/src/ElectronNET.API/ElectronNET.API.csproj
index 1f44ff7..15d7663 100644
--- a/src/ElectronNET.API/ElectronNET.API.csproj
+++ b/src/ElectronNET.API/ElectronNET.API.csproj
@@ -1,47 +1,40 @@
ο»Ώ
+
+
+
- net8.0
+ net6.0;net8.0
..\..\artifacts
- ElectronNET.API
- Gregor Biswanger, Florian Rappl
-
- Electron.NET
- MIT
- https://github.com/ElectronNET/Electron.NET/
- Building cross platform electron based desktop apps with .NET Core and ASP.NET Core.
-This package contains the API to access the "native" electron API.
- https://github.com/ElectronNET/Electron.NET/
- git
- true
- electron aspnetcore
- Changelog: https://github.com/ElectronNET/Electron.NET/blob/main/Changelog.md
- PackageIcon.png
- 99.0.0.0
+ $(PackageNamePrefix).API
+ $(PackageId)
true
+ True
+ snupkg
+ disable
+ $(DescriptionFirstPart) This package contains the API to access the "native" electron API.
+ ElectronNET
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+
-
-
-
-
-
-
-
-
-
-
+
all
runtime; build; native; contentfiles; analyzers
-
-
-
-
- all
-
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ElectronNET.API/ElectronNET.API.csproj.DotSettings b/src/ElectronNET.API/ElectronNET.API.csproj.DotSettings
new file mode 100644
index 0000000..8d8335c
--- /dev/null
+++ b/src/ElectronNET.API/ElectronNET.API.csproj.DotSettings
@@ -0,0 +1,3 @@
+ο»Ώ
+ Library
+ True
\ No newline at end of file
diff --git a/src/ElectronNET.API/ElectronNetRuntime.cs b/src/ElectronNET.API/ElectronNetRuntime.cs
new file mode 100644
index 0000000..bf3ce4e
--- /dev/null
+++ b/src/ElectronNET.API/ElectronNetRuntime.cs
@@ -0,0 +1,55 @@
+ο»Ώnamespace ElectronNET
+{
+ using System;
+ using System.Collections.Immutable;
+ using System.Threading.Tasks;
+ using ElectronNET.API;
+ using ElectronNET.Runtime;
+ using ElectronNET.Runtime.Controllers;
+ using ElectronNET.Runtime.Data;
+
+ public static class ElectronNetRuntime
+ {
+ internal static StartupManager StartupManager;
+
+ internal const int DefaultSocketPort = 8000;
+ internal const int DefaultWebPort = 8001;
+ internal const string ElectronPortArgumentName = "electronPort";
+ internal const string ElectronPidArgumentName = "electronPID";
+
+ /// Initializes the class.
+ static ElectronNetRuntime()
+ {
+ StartupManager = new StartupManager();
+ StartupManager.Initialize();
+ }
+
+ public static int? ElectronSocketPort { get; internal set; }
+
+ public static int? AspNetWebPort { get; internal set; }
+
+ public static StartupMethod StartupMethod { get; internal set; }
+
+ public static DotnetAppType DotnetAppType { get; internal set; }
+
+ public static string ElectronExecutable { get; internal set; }
+
+ public static ImmutableList ProcessArguments { get; internal set; }
+
+ public static BuildInfo BuildInfo { get; internal set; }
+
+ public static IElectronNetRuntimeController RuntimeController => RuntimeControllerCore;
+
+ // The below properties are non-public
+ internal static RuntimeControllerBase RuntimeControllerCore { get; set; }
+
+ internal static int? ElectronProcessId { get; set; }
+
+ internal static Func OnAppReadyCallback { get; set; }
+
+ internal static SocketIoFacade GetSocket()
+ {
+ return RuntimeControllerCore?.Socket;
+ }
+ }
+}
diff --git a/src/ElectronNET.API/Entities/ProcessVersions.cs b/src/ElectronNET.API/Entities/ProcessVersions.cs
deleted file mode 100644
index df41db6..0000000
--- a/src/ElectronNET.API/Entities/ProcessVersions.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace ElectronNET.API
-{
- ///
- /// An object listing the version strings specific to Electron
- ///
- /// Value representing Chrome's version string
- /// Value representing Electron's version string
- ///
- public record ProcessVersions(string Chrome, string Electron);
-}
\ No newline at end of file
diff --git a/src/ElectronNET.API/GlobalUsings.cs b/src/ElectronNET.API/GlobalUsings.cs
deleted file mode 100644
index 7b02f48..0000000
--- a/src/ElectronNET.API/GlobalUsings.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-// Global using directives
-
-global using System.Web;
\ No newline at end of file
diff --git a/src/ElectronNET.API/LifetimeServiceHost.cs b/src/ElectronNET.API/LifetimeServiceHost.cs
deleted file mode 100644
index cb58eb1..0000000
--- a/src/ElectronNET.API/LifetimeServiceHost.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-ο»Ώusing System;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Hosting;
-
-namespace ElectronNET.API
-{
- ///
- /// Base class that reports if ASP.NET Core has fully started.
- ///
- internal class LifetimeServiceHost : IHostedService
- {
- public LifetimeServiceHost(IHostApplicationLifetime lifetime)
- {
- lifetime.ApplicationStarted.Register(() =>
- {
- App.Instance.IsReady = true;
-
- Console.WriteLine("ASP.NET Core host has fully started.");
- });
- }
-
- ///
- /// Triggered when the application host is ready to start the service.
- ///
- /// Indicates that the start process has been aborted.
- public Task StartAsync(CancellationToken cancellationToken)
- {
- return Task.CompletedTask;
- }
-
- ///
- /// Triggered when the application host is performing a graceful shutdown.
- ///
- /// Indicates that the shutdown process should no longer be graceful.
- public Task StopAsync(CancellationToken cancellationToken)
- {
- return Task.CompletedTask;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerBase.cs b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerBase.cs
new file mode 100644
index 0000000..c80c0d6
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerBase.cs
@@ -0,0 +1,33 @@
+ο»Ώnamespace ElectronNET.Runtime.Controllers
+{
+ using System.Threading.Tasks;
+ using ElectronNET.API;
+ using ElectronNET.Runtime.Services;
+ using ElectronNET.Runtime.Services.ElectronProcess;
+ using ElectronNET.Runtime.Services.SocketBridge;
+
+ internal abstract class RuntimeControllerBase : LifetimeServiceBase, IElectronNetRuntimeController
+ {
+ protected RuntimeControllerBase()
+ {
+ }
+
+ internal abstract SocketIoFacade Socket { get; }
+
+ internal abstract ElectronProcessBase ElectronProcess { get; }
+
+ internal abstract SocketBridgeService SocketBridge { get; }
+
+ protected override Task StartCore()
+ {
+
+ return Task.CompletedTask;
+ }
+
+ protected override Task StopCore()
+ {
+ return Task.CompletedTask;
+ }
+
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs
new file mode 100644
index 0000000..7b8e888
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs
@@ -0,0 +1,107 @@
+ο»Ώnamespace ElectronNET.Runtime.Controllers
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.API;
+ using ElectronNET.Common;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Helpers;
+ using ElectronNET.Runtime.Services.ElectronProcess;
+ using ElectronNET.Runtime.Services.SocketBridge;
+
+ internal class RuntimeControllerDotNetFirst : RuntimeControllerBase
+ {
+ private ElectronProcessBase electronProcess;
+ private SocketBridgeService socketBridge;
+ private int? port;
+
+ public RuntimeControllerDotNetFirst()
+ {
+ }
+
+ internal override SocketIoFacade Socket
+ {
+ get
+ {
+ if (this.State == LifetimeState.Ready)
+ {
+ return this.socketBridge.Socket;
+ }
+
+ throw new Exception("Cannot access socket bridge. Runtime is not in 'Ready' state");
+ }
+ }
+
+ internal override ElectronProcessBase ElectronProcess => this.electronProcess;
+
+ internal override SocketBridgeService SocketBridge => this.socketBridge;
+
+ protected override Task StartCore()
+ {
+ var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();
+ var electronBinaryName = ElectronNetRuntime.ElectronExecutable;
+ var args = Environment.CommandLine;
+ this.port = ElectronNetRuntime.ElectronSocketPort;
+
+ if (!this.port.HasValue)
+ {
+ this.port = PortHelper.GetFreePort(ElectronNetRuntime.DefaultSocketPort);
+ ElectronNetRuntime.ElectronSocketPort = this.port;
+ }
+
+ this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, this.port.Value);
+ this.electronProcess.Ready += this.ElectronProcess_Ready;
+ this.electronProcess.Stopped += this.ElectronProcess_Stopped;
+
+ return this.electronProcess.Start();
+ }
+
+ private void ElectronProcess_Ready(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Started);
+ this.socketBridge = new SocketBridgeService(this.port!.Value);
+ this.socketBridge.Ready += this.SocketBridge_Ready;
+ this.socketBridge.Stopped += this.SocketBridge_Stopped;
+ this.socketBridge.Start();
+ }
+
+ private void SocketBridge_Ready(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Ready);
+
+ }
+
+ private void SocketBridge_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+
+ private void ElectronProcess_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+
+ private void HandleStopped()
+ {
+ if (this.socketBridge.State != LifetimeState.Stopped)
+ {
+ this.socketBridge.Stop();
+ }
+ else if (this.electronProcess.State != LifetimeState.Stopped)
+ {
+ this.electronProcess.Stop();
+ }
+ else
+ {
+ this.TransitionState(LifetimeState.Stopped);
+ }
+ }
+
+ protected override Task StopCore()
+ {
+ this.electronProcess.Stop();
+ return Task.CompletedTask;
+ }
+
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs
new file mode 100644
index 0000000..c8b9031
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs
@@ -0,0 +1,108 @@
+ο»Ώnamespace ElectronNET.Runtime.Controllers
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.API;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Services.ElectronProcess;
+ using ElectronNET.Runtime.Services.SocketBridge;
+
+ internal class RuntimeControllerElectronFirst : RuntimeControllerBase
+ {
+ private ElectronProcessBase electronProcess;
+ private SocketBridgeService socketBridge;
+ private int? port;
+
+ public RuntimeControllerElectronFirst()
+ {
+ }
+
+ internal override SocketIoFacade Socket
+ {
+ get
+ {
+ if (this.State == LifetimeState.Ready)
+ {
+ return this.socketBridge.Socket;
+ }
+
+ throw new Exception("Cannot access socket bridge. Runtime is not in 'Ready' state");
+ }
+ }
+
+ internal override ElectronProcessBase ElectronProcess => this.electronProcess;
+
+ internal override SocketBridgeService SocketBridge => this.socketBridge;
+
+ protected override Task StartCore()
+ {
+ this.port = ElectronNetRuntime.ElectronSocketPort;
+
+ if (!this.port.HasValue)
+ {
+ throw new Exception("No port has been specified by Electron!");
+ }
+
+ if (!ElectronNetRuntime.ElectronProcessId.HasValue)
+ {
+ throw new Exception("No electronPID has been specified by Electron!");
+ }
+
+ this.TransitionState(LifetimeState.Starting);
+ this.socketBridge = new SocketBridgeService(this.port!.Value);
+ this.socketBridge.Ready += this.SocketBridge_Ready;
+ this.socketBridge.Stopped += this.SocketBridge_Stopped;
+ this.socketBridge.Start();
+
+ this.electronProcess = new ElectronProcessPassive(ElectronNetRuntime.ElectronProcessId.Value);
+ this.electronProcess.Ready += this.ElectronProcess_Ready;
+ this.electronProcess.Stopped += this.ElectronProcess_Stopped;
+
+ this.electronProcess.Start();
+
+ return Task.CompletedTask;
+ }
+
+ private void ElectronProcess_Ready(object sender, EventArgs e)
+ {
+ }
+
+ private void SocketBridge_Ready(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Ready);
+ }
+
+ private void SocketBridge_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+
+ private void ElectronProcess_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+
+ private void HandleStopped()
+ {
+ if (this.socketBridge.State != LifetimeState.Stopped)
+ {
+ this.socketBridge.Stop();
+ }
+ else if (this.electronProcess.State != LifetimeState.Stopped)
+ {
+ this.electronProcess.Stop();
+ }
+ else
+ {
+ this.TransitionState(LifetimeState.Stopped);
+ }
+ }
+
+ protected override Task StopCore()
+ {
+ this.socketBridge.Stop();
+ return Task.CompletedTask;
+ }
+
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Data/BuildInfo.cs b/src/ElectronNET.API/Runtime/Data/BuildInfo.cs
new file mode 100644
index 0000000..cb71aa0
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Data/BuildInfo.cs
@@ -0,0 +1,19 @@
+ο»Ώnamespace ElectronNET.Runtime.Data
+{
+ public class BuildInfo
+ {
+ public string ElectronExecutable { get; internal set; }
+
+ public string ElectronVersion { get; internal set; }
+
+ public string RuntimeIdentifier { get; internal set; }
+
+ public string ElectronSingleInstance { get; internal set; }
+
+ public string Title { get; internal set; }
+
+ public string Version { get; internal set; }
+
+ public string BuildConfiguration { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Runtime/Data/DotnetAppType.cs b/src/ElectronNET.API/Runtime/Data/DotnetAppType.cs
new file mode 100644
index 0000000..d52328a
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Data/DotnetAppType.cs
@@ -0,0 +1,11 @@
+ο»Ώnamespace ElectronNET.Runtime.Data
+{
+ public enum DotnetAppType
+ {
+ /// A plain DotNet cross-platform console app.
+ ConsoleApp,
+
+ /// ASP.NET Core cross-platform app.
+ AspNetCoreApp,
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Data/LifetimeState.cs b/src/ElectronNET.API/Runtime/Data/LifetimeState.cs
new file mode 100644
index 0000000..1785887
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Data/LifetimeState.cs
@@ -0,0 +1,12 @@
+ο»Ώnamespace ElectronNET.Runtime.Data
+{
+ public enum LifetimeState
+ {
+ Uninitialized,
+ Starting,
+ Started,
+ Ready,
+ Stopping,
+ Stopped,
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Data/StartupMethod.cs b/src/ElectronNET.API/Runtime/Data/StartupMethod.cs
new file mode 100644
index 0000000..9d3e380
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Data/StartupMethod.cs
@@ -0,0 +1,37 @@
+ο»Ώnamespace ElectronNET.Runtime.Data
+{
+ public enum StartupMethod
+ {
+ /// Packaged Electron app where Electron launches the DotNet app.
+ ///
+ /// This is the classic way of ElectrronNET startup.
+ ///
+ PackagedElectronFirst,
+
+ /// Packaged Electron app where DotNet launches the Electron prozess.
+ ///
+ /// Provides better ways for managing the overall app lifecycle.
+ /// On the command lines, this is "dotnetpacked"
+ ///
+ PackagedDotnetFirst,
+
+ /// Unpackacked execution for debugging the Electron process and NodeJS.
+ ///
+ /// Similar to the legacy ElectronNET debugging but without packaging (=fast) and allows selection of
+ /// the debug adapter. It's rarely useful, unless it's about debugging NodeJS.
+ /// Note: 'Unpackaged' means that it's run directly from the compilation output folders (./bin/*).
+ /// On the command lines, this is "unpackedelectron"
+ ///
+ UnpackedElectronFirst,
+
+
+ /// Unpackacked execution for debugging the DotNet process.
+ ///
+ /// This is the new way of super-fast startup for debugging in-place with Hot Reload
+ /// (edit and continue), even on WSL - all from within Visual Studio.
+ /// Note: 'Unpackaged' means that it's run directly from the compilation output folders (./bin/*).
+ /// On the command lines, this is "unpackeddotnet"
+ ///
+ UnpackedDotnetFirst,
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Helpers/LaunchOrderDetector.cs b/src/ElectronNET.API/Runtime/Helpers/LaunchOrderDetector.cs
new file mode 100644
index 0000000..8320cfe
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Helpers/LaunchOrderDetector.cs
@@ -0,0 +1,72 @@
+ο»Ώnamespace ElectronNET.Runtime.Helpers
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+
+ internal class LaunchOrderDetector
+ {
+ public static bool CheckIsLaunchedByDotNet()
+ {
+ var tests = new List>();
+
+ tests.Add(CheckIsDotNetStartup1);
+ tests.Add(CheckIsDotNetStartup2);
+ tests.Add(CheckIsDotNetStartup3);
+
+ int scoreDotNet = 0, scoreElectron = 0;
+
+ foreach (var test in tests)
+ {
+ var res = test();
+
+ if (res == true)
+ {
+ scoreDotNet++;
+ }
+
+ if (res == false)
+ {
+ scoreElectron++;
+ }
+ }
+
+ Console.WriteLine("Probe scored for launch origin: DotNet {0} vs. {1} Electron", scoreDotNet, scoreElectron);
+ return scoreDotNet > scoreElectron;
+ }
+
+ private static bool? CheckIsDotNetStartup1()
+ {
+ var hasPortArg = ElectronNetRuntime.ProcessArguments.Any(e => e.Contains(ElectronNetRuntime.ElectronPortArgumentName, StringComparison.OrdinalIgnoreCase));
+ if (hasPortArg)
+ {
+ return false;
+ }
+
+
+ return true;
+ }
+
+ private static bool? CheckIsDotNetStartup2()
+ {
+ var hasPidArg = ElectronNetRuntime.ProcessArguments.Any(e => e.Contains(ElectronNetRuntime.ElectronPidArgumentName, StringComparison.OrdinalIgnoreCase));
+ if (hasPidArg)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool? CheckIsDotNetStartup3()
+ {
+ if (Debugger.IsAttached)
+ {
+ return true;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Helpers/PortHelper.cs b/src/ElectronNET.API/Runtime/Helpers/PortHelper.cs
new file mode 100644
index 0000000..3a14108
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Helpers/PortHelper.cs
@@ -0,0 +1,26 @@
+ο»Ώnamespace ElectronNET.Runtime.Helpers
+{
+ using System.Linq;
+ using System.Net.NetworkInformation;
+
+ internal static class PortHelper
+ {
+ public static int GetFreePort(int? defaultPost)
+ {
+ var listeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners().Select(e => e.Port).ToList();
+
+ int port = defaultPost ?? 8000;
+
+ while (true)
+ {
+ if (!listeners.Contains(port))
+ {
+ return port;
+ }
+
+ port += 2;
+ }
+ }
+
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs b/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs
new file mode 100644
index 0000000..d4fbd9d
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs
@@ -0,0 +1,109 @@
+ο»Ώnamespace ElectronNET.Runtime.Helpers
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Linq;
+
+ internal class UnpackagedDetector
+ {
+ public static bool CheckIsUnpackaged()
+ {
+ var tests = new List>();
+
+ tests.Add(CheckUnpackaged1);
+
+ // We let this one account twice
+ tests.Add(CheckUnpackaged2);
+ tests.Add(CheckUnpackaged2);
+
+ tests.Add(CheckUnpackaged3);
+ tests.Add(CheckUnpackaged4);
+
+ int scoreUnpackaged = 0, scorePackaged = 0;
+
+ foreach (var test in tests)
+ {
+ var res = test();
+
+ if (res == true)
+ {
+ scoreUnpackaged++;
+ }
+
+ if (res == false)
+ {
+ scorePackaged++;
+ }
+ }
+
+ Console.WriteLine("Probe scored for package mode: Unpackaged {0} vs. {1} Packaged", scoreUnpackaged, scorePackaged);
+ return scoreUnpackaged > scorePackaged;
+ }
+
+ private static bool? CheckUnpackaged1()
+ {
+ var cfg = ElectronNetRuntime.BuildInfo.BuildConfiguration;
+ if (cfg != null)
+ {
+ if (cfg.Equals("Debug", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ if (cfg.Equals("Release", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
+
+ return null;
+ }
+
+ private static bool? CheckUnpackaged2()
+ {
+ var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
+ if (dir.Name == "bin" && dir.Parent?.Name == "resources")
+ {
+ return false;
+ }
+
+ if (dir.GetDirectories().Any(e => e.Name == ".electron"))
+ {
+ return true;
+ }
+
+ return null;
+ }
+
+ private static bool? CheckUnpackaged3()
+ {
+ if (Debugger.IsAttached)
+ {
+ return true;
+ }
+
+
+ return null;
+ }
+
+ private static bool? CheckUnpackaged4()
+ {
+ var isUnpackaged = ElectronNetRuntime.ProcessArguments.Any(e => e.Contains("unpacked", StringComparison.OrdinalIgnoreCase));
+
+ if (isUnpackaged)
+ {
+ return true;
+ }
+
+ var isPackaged = ElectronNetRuntime.ProcessArguments.Any(e => e.Contains("dotnetpacked", StringComparison.OrdinalIgnoreCase));
+ if (isPackaged)
+ {
+ return false;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/IElectronNetRuntimeController.cs b/src/ElectronNET.API/Runtime/IElectronNetRuntimeController.cs
new file mode 100644
index 0000000..4b69c3c
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/IElectronNetRuntimeController.cs
@@ -0,0 +1,33 @@
+ο»Ώnamespace ElectronNET.Runtime
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.Runtime.Data;
+
+ public interface IElectronNetRuntimeController
+ {
+ LifetimeState State { get; }
+
+ Task WaitStartedTask { get; }
+
+ Task WaitReadyTask { get; }
+
+ Task WaitStoppingTask { get; }
+
+ Task WaitStoppedTask { get; }
+
+ event EventHandler Starting;
+
+ event EventHandler Started;
+
+ event EventHandler Ready;
+
+ event EventHandler Stopping;
+
+ event EventHandler Stopped;
+
+ Task Start();
+
+ Task Stop();
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs
new file mode 100644
index 0000000..57056fe
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs
@@ -0,0 +1,94 @@
+ο»Ώnamespace ElectronNET.Runtime.Services.ElectronProcess
+{
+ using System;
+ using System.ComponentModel;
+ using System.IO;
+ using System.Threading.Tasks;
+ using ElectronNET.Common;
+ using ElectronNET.Runtime.Data;
+
+ ///
+ /// Launches and manages the Electron app process.
+ ///
+ [Localizable(false)]
+ internal class ElectronProcessActive : ElectronProcessBase
+ {
+ private readonly bool isUnpackaged;
+ private readonly string electronBinaryName;
+ private readonly string extraArguments;
+ private readonly int socketPort;
+ private ProcessRunner process;
+
+ /// Initializes a new instance of the class.
+ /// The is debug.
+ /// Name of the electron.
+ /// The extraArguments.
+ /// The socket port.
+ public ElectronProcessActive(bool isUnpackaged, string electronBinaryName, string extraArguments, int socketPort)
+ {
+ this.isUnpackaged = isUnpackaged;
+ this.electronBinaryName = electronBinaryName;
+ this.extraArguments = extraArguments;
+ this.socketPort = socketPort;
+ }
+
+ protected override Task StartCore()
+ {
+ var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
+ string startCmd, args, workingDir;
+
+ if (this.isUnpackaged)
+ {
+ var electrondir = Path.Combine(dir.FullName, ".electron");
+ startCmd = Path.Combine(electrondir, "node_modules", "electron", "dist", "electron");
+
+ args = $"main.js -unpackeddotnet --trace-warnings -electronforcedport={this.socketPort:D} " + this.extraArguments;
+ workingDir = electrondir;
+ }
+ else
+ {
+ dir = dir.Parent?.Parent;
+ startCmd = Path.Combine(dir.FullName, this.electronBinaryName);
+ args = $"-dotnetpacked -electronforcedport={this.socketPort:D} " + this.extraArguments;
+ workingDir = dir.FullName;
+ }
+
+
+ // We don't await this in order to let the state transition to "Starting"
+ Task.Run(async () => await this.StartInternal(startCmd, args, workingDir).ConfigureAwait(false));
+
+ return Task.CompletedTask;
+ }
+
+ protected override Task StopCore()
+ {
+ this.process.Cancel();
+ return Task.CompletedTask;
+ }
+
+ private async Task StartInternal(string startCmd, string args, string directoriy)
+ {
+ await Task.Delay(10).ConfigureAwait(false);
+
+ this.process = new ProcessRunner("ElectronRunner");
+ this.process.ProcessExited += this.Process_Exited;
+ this.process.Run(startCmd, args, directoriy);
+
+ await Task.Delay(500).ConfigureAwait(false);
+
+ if (!this.process.IsRunning)
+ {
+ Task.Run(() => this.TransitionState(LifetimeState.Stopped));
+
+ throw new Exception("Failed to launch the Electron process.");
+ }
+
+ this.TransitionState(LifetimeState.Ready);
+ }
+
+ private void Process_Exited(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Stopped);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessBase.cs b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessBase.cs
new file mode 100644
index 0000000..969c807
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessBase.cs
@@ -0,0 +1,15 @@
+ο»Ώnamespace ElectronNET.Runtime.Services.ElectronProcess
+{
+ using System.ComponentModel;
+
+ ///
+ /// Manages the Electron app process.
+ ///
+ [Localizable(false)]
+ internal abstract class ElectronProcessBase : LifetimeServiceBase
+ {
+ protected ElectronProcessBase()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessPassive.cs b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessPassive.cs
new file mode 100644
index 0000000..7d13b3f
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessPassive.cs
@@ -0,0 +1,53 @@
+ο»Ώnamespace ElectronNET.Runtime.Services.ElectronProcess
+{
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Threading.Tasks;
+ using ElectronNET.Runtime.Data;
+
+ ///
+ /// Launches and manages the Electron app process.
+ ///
+ [Localizable(false)]
+ internal class ElectronProcessPassive : ElectronProcessBase
+ {
+ private readonly int pid;
+ private Process process;
+
+ /// Initializes a new instance of the class.
+ ///
+ public ElectronProcessPassive(int pid)
+ {
+ this.pid = pid;
+ }
+
+ protected override Task StartCore()
+ {
+ this.process = Process.GetProcessById(this.pid);
+
+ if (this.process == null)
+ {
+ throw new ArgumentException($"Unable to find process with ID {this.pid}");
+ }
+
+ this.process.Exited += this.Process_Exited1;
+
+ Task.Run(() => this.TransitionState(LifetimeState.Ready));
+
+ return Task.CompletedTask;
+ }
+
+ private void Process_Exited1(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Stopped);
+ }
+
+ protected override Task StopCore()
+ {
+ // Not sure about this:
+ ////this.process.Kill(true);
+ return Task.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Runtime/Services/LifetimeServiceBase.cs b/src/ElectronNET.API/Runtime/Services/LifetimeServiceBase.cs
new file mode 100644
index 0000000..15c5823
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Services/LifetimeServiceBase.cs
@@ -0,0 +1,135 @@
+ο»Ώnamespace ElectronNET.Runtime.Services
+{
+ using System;
+ using System.Runtime.CompilerServices;
+ using System.Threading.Tasks;
+ using ElectronNET.Runtime.Data;
+
+ public abstract class LifetimeServiceBase
+ {
+ private readonly TaskCompletionSource tcsStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly TaskCompletionSource tcsReady = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly TaskCompletionSource tcsStopping = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly TaskCompletionSource tcsStopped = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ private LifetimeState state = LifetimeState.Uninitialized;
+
+ protected LifetimeServiceBase() { }
+
+ public event EventHandler Starting;
+
+ public event EventHandler Started;
+
+ public event EventHandler Ready;
+
+ public event EventHandler Stopping;
+
+ public event EventHandler Stopped;
+
+
+ public LifetimeState State => this.state;
+
+ public Task WaitStartedTask => this.tcsStarted.Task;
+
+ public Task WaitReadyTask => this.tcsReady.Task;
+
+ public Task WaitStoppingTask => this.tcsStopping.Task;
+
+ public Task WaitStoppedTask => this.tcsStopped.Task;
+
+
+ public virtual async Task Start()
+ {
+ this.ValidateMaxState(LifetimeState.Uninitialized);
+
+ await this.StartCore().ConfigureAwait(false);
+
+ this.TransitionState(LifetimeState.Starting);
+ }
+
+ public virtual async Task Stop()
+ {
+ this.ValidateMaxState(LifetimeState.Ready);
+
+ await this.StopCore().ConfigureAwait(false);
+
+ this.TransitionState(LifetimeState.Stopping);
+ }
+
+ protected virtual Task StopCore()
+ {
+ return Task.CompletedTask;
+ }
+
+ protected virtual Task StartCore()
+ {
+ return Task.CompletedTask;
+ }
+
+ private void ValidateMaxState(LifetimeState evalState, [CallerMemberName] string callerMemberName = null)
+ {
+ if (this.state > evalState)
+ {
+ throw new Exception($"Invalid state! Cannot execute {callerMemberName} in state {this.state}");
+ }
+ }
+
+ protected void TransitionState(LifetimeState newState)
+ {
+ if (newState == this.state)
+ {
+ return;
+ }
+
+ if (newState < this.state)
+ {
+ throw new Exception($"Invalid state transition from {this.state} to {newState}: " + this.GetType().Name);
+ }
+
+ var oldState = this.state;
+
+ this.state = newState;
+
+ switch (this.state)
+ {
+ case LifetimeState.Starting:
+ this.Starting?.Invoke(this, EventArgs.Empty);
+ break;
+ case LifetimeState.Started:
+ this.Started?.Invoke(this, EventArgs.Empty);
+ break;
+ case LifetimeState.Ready:
+ this.Ready?.Invoke(this, EventArgs.Empty);
+ break;
+ case LifetimeState.Stopping:
+ this.Stopping?.Invoke(this, EventArgs.Empty);
+ break;
+ case LifetimeState.Stopped:
+ this.Stopped?.Invoke(this, EventArgs.Empty);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ if (oldState < LifetimeState.Started && newState >= LifetimeState.Started)
+ {
+ this.tcsStarted.TrySetResult();
+ }
+
+ if (oldState < LifetimeState.Ready && newState >= LifetimeState.Ready)
+ {
+ this.tcsReady.TrySetResult();
+ }
+
+ if (oldState < LifetimeState.Stopping && newState >= LifetimeState.Stopping)
+ {
+ this.tcsStopping.TrySetResult();
+ }
+
+ if (oldState < LifetimeState.Stopped && newState >= LifetimeState.Stopped)
+ {
+ this.tcsStopped.TrySetResult();
+ }
+ }
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Services/SocketBridge/SocketBridgeService.cs b/src/ElectronNET.API/Runtime/Services/SocketBridge/SocketBridgeService.cs
new file mode 100644
index 0000000..1253051
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Services/SocketBridge/SocketBridgeService.cs
@@ -0,0 +1,56 @@
+ο»Ώnamespace ElectronNET.Runtime.Services.SocketBridge
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.API;
+ using ElectronNET.Runtime.Data;
+
+ internal class SocketBridgeService : LifetimeServiceBase
+ {
+ private readonly int socketPort;
+ private readonly string socketUrl;
+ private SocketIoFacade socket;
+
+ public SocketBridgeService(int socketPort)
+ {
+ this.socketPort = socketPort;
+ this.socketUrl = $"http://localhost:{this.socketPort}";
+ }
+
+ public int SocketPort => this.socketPort;
+
+ internal SocketIoFacade Socket => this.socket;
+
+ protected override Task StartCore()
+ {
+ this.socket = new SocketIoFacade(this.socketUrl);
+ this.socket.BridgeConnected += this.Socket_BridgeConnected;
+ this.socket.BridgeDisconnected += this.Socket_BridgeDisconnected;
+ Task.Run(this.Connect);
+
+ return Task.CompletedTask;
+ }
+
+ protected override Task StopCore()
+ {
+ this.socket.DisposeSocket();
+ return Task.CompletedTask;
+ }
+
+ private void Connect()
+ {
+ this.socket.Connect();
+ this.TransitionState(LifetimeState.Started);
+ }
+
+ private void Socket_BridgeDisconnected(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Stopped);
+ }
+
+ private void Socket_BridgeConnected(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Ready);
+ }
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/StartupManager.cs b/src/ElectronNET.API/Runtime/StartupManager.cs
new file mode 100644
index 0000000..147c452
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/StartupManager.cs
@@ -0,0 +1,161 @@
+ο»Ώnamespace ElectronNET.Runtime
+{
+ using System;
+ using System.Collections.Immutable;
+ using System.Globalization;
+ using System.Linq;
+ using System.Reflection;
+ using ElectronNET.Runtime.Controllers;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Helpers;
+
+ internal class StartupManager
+ {
+ public void Initialize()
+ {
+ try
+ {
+ ElectronNetRuntime.BuildInfo = this.GatherBuildInfo();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ }
+
+ this.CollectProcessData();
+ this.SetElectronExecutable();
+
+
+ ElectronNetRuntime.StartupMethod = this.DetectAppTypeAndStartup();
+ Console.WriteLine((string)("Evaluated StartupMethod: " + ElectronNetRuntime.StartupMethod));
+
+ if (ElectronNetRuntime.DotnetAppType != DotnetAppType.AspNetCoreApp)
+ {
+ ElectronNetRuntime.RuntimeControllerCore = this.CreateRuntimeController();
+ }
+ }
+
+ private RuntimeControllerBase CreateRuntimeController()
+ {
+ switch (ElectronNetRuntime.StartupMethod)
+ {
+ case StartupMethod.PackagedDotnetFirst:
+ case StartupMethod.UnpackedDotnetFirst:
+ return new RuntimeControllerDotNetFirst();
+ case StartupMethod.PackagedElectronFirst:
+ case StartupMethod.UnpackedElectronFirst:
+ return new RuntimeControllerElectronFirst();
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private StartupMethod DetectAppTypeAndStartup()
+ {
+ var isLaunchedByDotNet = LaunchOrderDetector.CheckIsLaunchedByDotNet();
+ var isUnPackaged = UnpackagedDetector.CheckIsUnpackaged();
+
+ if (isLaunchedByDotNet)
+ {
+ if (isUnPackaged)
+ {
+ return StartupMethod.UnpackedDotnetFirst;
+ }
+
+ return StartupMethod.PackagedDotnetFirst;
+ }
+ else
+ {
+ if (isUnPackaged)
+ {
+ return StartupMethod.UnpackedElectronFirst;
+ }
+
+ return StartupMethod.PackagedElectronFirst;
+ }
+ }
+
+ private void CollectProcessData()
+ {
+ var argsList = Environment.GetCommandLineArgs().ToImmutableList();
+
+ ElectronNetRuntime.ProcessArguments = argsList;
+
+ var portArg = argsList.FirstOrDefault(e => e.Contains(ElectronNetRuntime.ElectronPortArgumentName, StringComparison.OrdinalIgnoreCase));
+
+ if (portArg != null)
+ {
+ var parts = portArg.Split('=', StringSplitOptions.TrimEntries);
+ if (parts.Length > 1 && int.TryParse(parts[1], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result))
+ {
+ ElectronNetRuntime.ElectronSocketPort = result;
+
+ Console.WriteLine("Use Electron Port: " + result);
+ }
+ }
+
+ var pidArg = argsList.FirstOrDefault(e => e.Contains(ElectronNetRuntime.ElectronPidArgumentName, StringComparison.OrdinalIgnoreCase));
+
+ if (pidArg != null)
+ {
+ var parts = pidArg.Split('=', StringSplitOptions.TrimEntries);
+ if (parts.Length > 1 && int.TryParse(parts[1], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result))
+ {
+ ElectronNetRuntime.ElectronProcessId = result;
+
+ Console.WriteLine("Electron Process ID: " + result);
+ }
+ }
+ }
+
+ private void SetElectronExecutable()
+ {
+ string executable = ElectronNetRuntime.BuildInfo.ElectronExecutable;
+ if (string.IsNullOrEmpty(executable))
+ {
+ throw new Exception("AssemblyMetadataAttribute 'ElectronExecutable' could not be found!");
+ }
+
+ if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ {
+ executable += ".exe";
+ }
+
+ ElectronNetRuntime.ElectronExecutable = executable;
+
+ }
+
+ private BuildInfo GatherBuildInfo()
+ {
+ var buildInfo = new BuildInfo();
+
+ var attributes = Assembly.GetEntryAssembly()?.GetCustomAttributes().ToList();
+
+ if (attributes?.Count > 0)
+ {
+ buildInfo.ElectronExecutable = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronExecutable))?.Value;
+ buildInfo.ElectronVersion = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronVersion))?.Value;
+ buildInfo.RuntimeIdentifier = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.RuntimeIdentifier))?.Value;
+ buildInfo.ElectronSingleInstance = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronSingleInstance))?.Value;
+ buildInfo.Title = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.Title))?.Value;
+ buildInfo.Version = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.Version))?.Value;
+ buildInfo.BuildConfiguration = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.BuildConfiguration))?.Value;
+ var isAspNet = attributes.FirstOrDefault(e => e.Key == "IsAspNet")?.Value;
+
+ if (isAspNet?.Length > 0 && bool.TryParse(isAspNet, out var res) && res)
+ {
+ ElectronNetRuntime.DotnetAppType = DotnetAppType.AspNetCoreApp;
+ }
+
+ var httpPort = attributes.FirstOrDefault(e => e.Key == "AspNetHttpPort")?.Value;
+
+ if (httpPort?.Length > 0 && int.TryParse(httpPort, out var port))
+ {
+ ElectronNetRuntime.AspNetWebPort = port;
+ }
+ }
+
+ return buildInfo;
+ }
+ }
+}
diff --git a/src/ElectronNET.API/ServiceCollectionExtensions.cs b/src/ElectronNET.API/ServiceCollectionExtensions.cs
deleted file mode 100644
index a63aacf..0000000
--- a/src/ElectronNET.API/ServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-ο»Ώusing Microsoft.Extensions.DependencyInjection;
-
-namespace ElectronNET.API
-{
- ///
- ///
- ///
- public static class ServiceCollectionExtensions
- {
- ///
- /// Adds the Members to the Service Collection
- ///
- public static IServiceCollection AddElectron(this IServiceCollection services)
- => services
- // adding in this manner to ensure late binding.
- .AddSingleton(provider => IpcMain.Instance)
- .AddSingleton(provider => App.Instance)
- .AddSingleton(provider => AutoUpdater.Instance)
- .AddSingleton(provider => WindowManager.Instance)
- .AddSingleton(provider => Menu.Instance)
- .AddSingleton(provider => Dialog.Instance)
- .AddSingleton(provider => Notification.Instance)
- .AddSingleton(provider => Tray.Instance)
- .AddSingleton(provider => GlobalShortcut.Instance)
- .AddSingleton(provider => Shell.Instance)
- .AddSingleton(provider => Screen.Instance)
- .AddSingleton(provider => Clipboard.Instance)
- .AddSingleton(provider => HostHook.Instance)
- .AddSingleton(provider => PowerMonitor.Instance)
- .AddSingleton(provider => NativeTheme.Instance)
- .AddSingleton(provider => Dock.Instance)
- .AddSingleton(provider => Process.Instance);
- }
-}
diff --git a/src/ElectronNET.API/WebHostBuilderExtensions.cs b/src/ElectronNET.API/WebHostBuilderExtensions.cs
deleted file mode 100644
index af7cf91..0000000
--- a/src/ElectronNET.API/WebHostBuilderExtensions.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-ο»Ώusing Microsoft.AspNetCore.Hosting;
-using System;
-using System.IO;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace ElectronNET.API
-{
- ///
- ///
- ///
- public static class WebHostBuilderExtensions
- {
- ///
- /// Use a Electron support for this .NET Core Project.
- ///
- /// The builder.
- /// The arguments.
- ///
- public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args)
- {
- foreach (string argument in args)
- {
- if (argument.ToUpper().Contains("ELECTRONPORT"))
- {
- BridgeSettings.SocketPort = argument.ToUpper().Replace("/ELECTRONPORT=", "");
- Console.WriteLine("Use Electron Port: " + BridgeSettings.SocketPort);
- }
- else if (argument.ToUpper().Contains("ELECTRONWEBPORT"))
- {
- BridgeSettings.WebPort = argument.ToUpper().Replace("/ELECTRONWEBPORT=", "");
- }
- }
-
- if (HybridSupport.IsElectronActive)
- {
- builder.ConfigureServices(services =>
- {
- services.AddHostedService();
- });
-
- // check for the content folder if its exists in base director otherwise no need to include
- // It was used before because we are publishing the project which copies everything to bin folder and contentroot wwwroot was folder there.
- // now we have implemented the live reload if app is run using /watch then we need to use the default project path.
- if (Directory.Exists($"{AppDomain.CurrentDomain.BaseDirectory}\\wwwroot"))
- {
- builder.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
- .UseUrls("http://localhost:" + BridgeSettings.WebPort);
- }
- else
- {
- builder.UseUrls("http://localhost:" + BridgeSettings.WebPort);
- }
- }
-
- return builder;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.API/devCleanup.cmd b/src/ElectronNET.API/devCleanup.cmd
deleted file mode 100644
index 1ea3754..0000000
--- a/src/ElectronNET.API/devCleanup.cmd
+++ /dev/null
@@ -1 +0,0 @@
-rd /s /q %userprofile%\.nuget\packages\electronnet.api 2>nul
diff --git a/src/ElectronNET.API/devCleanup.sh b/src/ElectronNET.API/devCleanup.sh
deleted file mode 100644
index f3b1b44..0000000
--- a/src/ElectronNET.API/devCleanup.sh
+++ /dev/null
@@ -1 +0,0 @@
-rm -rf ~/.nuget/packages/electronnet.api
\ No newline at end of file
diff --git a/src/ElectronNET.AspNet/API/ServiceCollectionExtensions.cs b/src/ElectronNET.AspNet/API/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..b12f636
--- /dev/null
+++ b/src/ElectronNET.AspNet/API/ServiceCollectionExtensions.cs
@@ -0,0 +1,33 @@
+ο»Ώnamespace ElectronNET.API
+{
+ using Microsoft.Extensions.DependencyInjection;
+
+ ///
+ ///
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// Adds the Members to the Service Collection
+ ///
+ public static IServiceCollection AddElectron(this IServiceCollection services)
+ => services
+ // adding in this manner to ensure late binding.
+ .AddSingleton(_ => IpcMain.Instance)
+ .AddSingleton(_ => App.Instance)
+ .AddSingleton(_ => AutoUpdater.Instance)
+ .AddSingleton(_ => WindowManager.Instance)
+ .AddSingleton(_ => Menu.Instance)
+ .AddSingleton(_ => Dialog.Instance)
+ .AddSingleton(_ => Notification.Instance)
+ .AddSingleton(_ => Tray.Instance)
+ .AddSingleton(_ => GlobalShortcut.Instance)
+ .AddSingleton(_ => Shell.Instance)
+ .AddSingleton(_ => Screen.Instance)
+ .AddSingleton(_ => Clipboard.Instance)
+ .AddSingleton(_ => HostHook.Instance)
+ .AddSingleton(_ => PowerMonitor.Instance)
+ .AddSingleton(_ => NativeTheme.Instance)
+ .AddSingleton(_ => Dock.Instance);
+ }
+}
diff --git a/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs b/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs
new file mode 100644
index 0000000..0283bea
--- /dev/null
+++ b/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs
@@ -0,0 +1,48 @@
+ο»Ώnamespace ElectronNET.API
+{
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.AspNetCore.Builder;
+
+ ///
+ /// Provides extension methods for to enable Electron.NET
+ /// integration in ASP.NET Core applications (including Razor Pages) using the minimal hosting model.
+ ///
+ ///
+ /// Call this extension during host configuration (for example, in Program.cs) to wire up Electron
+ /// with any command-line arguments and an optional application-ready callback.
+ ///
+ public static class WebApplicationBuilderExtensions
+ {
+ ///
+ /// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback.
+ ///
+ /// The to extend.
+ /// The command-line arguments passed to the process, forwarded to Electron.
+ ///
+ /// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
+ ///
+ ///
+ /// The same instance to enable fluent configuration.
+ ///
+ ///
+ ///
+ /// var builder = WebApplication.CreateBuilder(args)
+ /// .UseElectron(args, async () =>
+ /// {
+ /// // Create the main browser window or perform other startup tasks.
+ /// });
+ ///
+ /// var app = builder.Build();
+ /// app.MapRazorPages();
+ /// app.Run();
+ ///
+ ///
+ public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func onAppReadyCallback)
+ {
+ builder.WebHost.UseElectron(args, onAppReadyCallback);
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs
new file mode 100644
index 0000000..c607836
--- /dev/null
+++ b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs
@@ -0,0 +1,103 @@
+ο»Ώnamespace ElectronNET.API
+{
+ using System;
+ using System.IO;
+ using System.Threading.Tasks;
+ using ElectronNET.AspNet;
+ using ElectronNET.AspNet.Runtime;
+ using ElectronNET.Runtime;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Helpers;
+ using Microsoft.AspNetCore.Hosting;
+ using Microsoft.Extensions.DependencyInjection;
+
+ ///
+ /// Provides extension methods for to enable Electron.NET
+ /// integration in ASP.NET Core applications (including Razor Pages) using the WebHost-based hosting model.
+ ///
+ ///
+ /// Call this extension during web host configuration (for example, inside ConfigureWebHostDefaults in Program.cs)
+ /// to wire up Electron with any command-line arguments and an optional application-ready callback.
+ ///
+ public static class WebHostBuilderExtensions
+ {
+ ///
+ /// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback.
+ ///
+ /// The to extend.
+ /// The command-line arguments passed to the process.
+ ///
+ /// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
+ ///
+ ///
+ /// The same instance to enable fluent configuration.
+ ///
+ ///
+ ///
+ /// using Microsoft.AspNetCore.Hosting;
+ /// using Microsoft.Extensions.Hosting;
+ /// using ElectronNET.API;
+ ///
+ /// public class Program
+ /// {
+ /// public static void Main(string[] args)
+ /// {
+ /// Host.CreateDefaultBuilder(args)
+ /// .ConfigureWebHostDefaults(webBuilder =>
+ /// {
+ /// webBuilder.UseStartup<Startup>();
+ /// webBuilder.UseElectron(args, async () =>
+ /// {
+ /// // Create the main browser window or perform other startup tasks.
+ /// });
+ /// })
+ /// .Build()
+ /// .Run();
+ /// }
+ /// }
+ ///
+ ///
+ public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func onAppReadyCallback)
+ {
+ ElectronNetRuntime.OnAppReadyCallback = onAppReadyCallback;
+
+ var webPort = PortHelper.GetFreePort(ElectronNetRuntime.AspNetWebPort ?? ElectronNetRuntime.DefaultWebPort);
+ ElectronNetRuntime.AspNetWebPort = webPort;
+
+ // check for the content folder if its exists in base director otherwise no need to include
+ // It was used before because we are publishing the project which copies everything to bin folder and contentroot wwwroot was folder there.
+ // now we have implemented the live reload if app is run using /watch then we need to use the default project path.
+ if (Directory.Exists($"{AppDomain.CurrentDomain.BaseDirectory}\\wwwroot"))
+ {
+ builder = builder.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
+ .UseUrls("http://localhost:" + webPort);
+ }
+ else
+ {
+ builder = builder.UseUrls("http://localhost:" + webPort);
+ }
+
+ builder = builder.ConfigureServices(services =>
+ {
+ services.AddTransient();
+ services.AddSingleton();
+
+ switch (ElectronNetRuntime.StartupMethod)
+ {
+ case StartupMethod.PackagedElectronFirst:
+ case StartupMethod.UnpackedElectronFirst:
+ services.AddSingleton();
+ break;
+ case StartupMethod.PackagedDotnetFirst:
+ case StartupMethod.UnpackedDotnetFirst:
+ services.AddSingleton();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ });
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.AspNet/ElectronNET.AspNet.csproj b/src/ElectronNET.AspNet/ElectronNET.AspNet.csproj
new file mode 100644
index 0000000..d3a95fe
--- /dev/null
+++ b/src/ElectronNET.AspNet/ElectronNET.AspNet.csproj
@@ -0,0 +1,44 @@
+ο»Ώ
+
+
+
+
+ net6.0;net8.0
+ ..\..\artifacts
+ $(PackageNamePrefix).AspNet
+ $(PackageId)
+ $(DescriptionFirstPart) This package contains the ASP.Net Core integration.
+ true
+ True
+ snupkg
+ disable
+ ElectronNET
+
+
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/PackageIcon.png b/src/ElectronNET.AspNet/PackageIcon.png
similarity index 100%
rename from src/ElectronNET.CLI/PackageIcon.png
rename to src/ElectronNET.AspNet/PackageIcon.png
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs
new file mode 100644
index 0000000..65487df
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs
@@ -0,0 +1,132 @@
+ο»Ώnamespace ElectronNET.AspNet.Runtime
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.API;
+ using ElectronNET.Common;
+ using ElectronNET.Runtime.Controllers;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Services.SocketBridge;
+
+ internal abstract class RuntimeControllerAspNetBase : RuntimeControllerBase
+ {
+ private readonly AspNetLifetimeAdapter aspNetLifetimeAdapter;
+ private SocketBridgeService socketBridge;
+
+ protected RuntimeControllerAspNetBase(AspNetLifetimeAdapter aspNetLifetimeAdapter)
+ {
+ this.aspNetLifetimeAdapter = aspNetLifetimeAdapter;
+ this.aspNetLifetimeAdapter.Ready += this.AspNetLifetimeAdapter_Ready;
+ this.aspNetLifetimeAdapter.Stopping += this.AspNetLifetimeAdapter_Stopping;
+ this.aspNetLifetimeAdapter.Stopped += this.AspNetLifetimeAdapter_Stopped;
+
+ ElectronNetRuntime.RuntimeControllerCore = this;
+ }
+
+ internal override SocketBridgeService SocketBridge => this.socketBridge;
+
+ internal override SocketIoFacade Socket
+ {
+ get
+ {
+ if (this.State == LifetimeState.Ready)
+ {
+ return this.socketBridge.Socket;
+ }
+
+ throw new Exception("Cannot access socket bridge. Runtime is not in 'Ready' state");
+ }
+ }
+
+ protected void CreateSocketBridge(int port)
+ {
+ this.socketBridge = new SocketBridgeService(port);
+ this.socketBridge.Ready += this.SocketBridge_Ready;
+ this.socketBridge.Stopped += this.SocketBridge_Stopped;
+ this.socketBridge.Start();
+ }
+
+ protected void HandleReady()
+ {
+ if (this.SocketBridge.IsReady() &&
+ this.ElectronProcess.IsReady() &&
+ this.aspNetLifetimeAdapter.IsReady())
+ {
+ this.TransitionState(LifetimeState.Ready);
+ Task.Run(this.RunReadyCallback);
+ }
+ }
+
+ protected void HandleStopped()
+ {
+ this.TransitionState(LifetimeState.Stopping);
+
+ if (this.SocketBridge.IsNotStopped())
+ {
+ this.SocketBridge.Stop();
+ }
+
+ if (this.ElectronProcess.IsNotStopped())
+ {
+ this.ElectronProcess.Stop();
+ }
+
+ if (this.aspNetLifetimeAdapter.IsNotStopped())
+ {
+ this.aspNetLifetimeAdapter.Stop();
+ }
+
+ if ((this.SocketBridge.IsNullOrStopped()) &&
+ (this.ElectronProcess.IsNullOrStopped()) &&
+ (this.aspNetLifetimeAdapter.IsNullOrStopped()))
+ {
+ this.TransitionState(LifetimeState.Stopped);
+ }
+ }
+
+ protected abstract override Task StopCore();
+
+ private void SocketBridge_Ready(object sender, EventArgs e)
+ {
+ this.HandleReady();
+ }
+
+ private void AspNetLifetimeAdapter_Ready(object sender, EventArgs e)
+ {
+ this.HandleReady();
+ }
+
+ private void SocketBridge_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+
+ private void AspNetLifetimeAdapter_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+
+ private void AspNetLifetimeAdapter_Stopping(object sender, EventArgs e)
+ {
+ }
+
+ private async Task RunReadyCallback()
+ {
+ if (ElectronNetRuntime.OnAppReadyCallback == null)
+ {
+ Console.WriteLine("Warning: Non OnReadyCallback provided in UseElectron() setup.");
+ return;
+ }
+
+ try
+ {
+ await ElectronNetRuntime.OnAppReadyCallback().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Exception while executing OnAppReadyCallback. Stopping...\n" + ex);
+ this.Stop();
+ }
+ }
+ }
+}
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs
new file mode 100644
index 0000000..4c76291
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs
@@ -0,0 +1,58 @@
+ο»Ώnamespace ElectronNET.AspNet.Runtime
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.Common;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Helpers;
+ using ElectronNET.Runtime.Services.ElectronProcess;
+
+ internal class RuntimeControllerAspNetDotnetFirst : RuntimeControllerAspNetBase
+ {
+ private ElectronProcessBase electronProcess;
+ private int? port;
+
+ public RuntimeControllerAspNetDotnetFirst(AspNetLifetimeAdapter aspNetLifetimeAdapter) : base(aspNetLifetimeAdapter)
+ {
+ }
+
+ internal override ElectronProcessBase ElectronProcess => this.electronProcess;
+
+ protected override Task StartCore()
+ {
+ var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();
+ var electronBinaryName = ElectronNetRuntime.ElectronExecutable;
+ var args = Environment.CommandLine;
+ this.port = ElectronNetRuntime.ElectronSocketPort;
+
+ if (!this.port.HasValue)
+ {
+ this.port = PortHelper.GetFreePort(ElectronNetRuntime.DefaultSocketPort);
+ ElectronNetRuntime.ElectronSocketPort = this.port;
+ }
+
+ this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, this.port.Value);
+ this.electronProcess.Ready += this.ElectronProcess_Ready;
+ this.electronProcess.Stopped += this.ElectronProcess_Stopped;
+
+ return this.electronProcess.Start();
+ }
+
+ protected override Task StopCore()
+ {
+ this.electronProcess.Stop();
+ return Task.CompletedTask;
+ }
+
+ private void ElectronProcess_Ready(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Started);
+ this.CreateSocketBridge(this.port!.Value);
+ }
+
+ private void ElectronProcess_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+ }
+}
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs
new file mode 100644
index 0000000..c9eb069
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs
@@ -0,0 +1,56 @@
+ο»Ώnamespace ElectronNET.AspNet.Runtime
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Services.ElectronProcess;
+
+ internal class RuntimeControllerAspNetElectronFirst : RuntimeControllerAspNetBase
+ {
+ private ElectronProcessBase electronProcess;
+ private int? port;
+
+ public RuntimeControllerAspNetElectronFirst(AspNetLifetimeAdapter aspNetLifetimeAdapter) : base(aspNetLifetimeAdapter)
+ {
+ }
+
+ internal override ElectronProcessBase ElectronProcess => this.electronProcess;
+
+ protected override Task StartCore()
+ {
+ this.port = ElectronNetRuntime.ElectronSocketPort;
+
+ if (!this.port.HasValue)
+ {
+ throw new Exception("No port has been specified by Electron!");
+ }
+
+ if (!ElectronNetRuntime.ElectronProcessId.HasValue)
+ {
+ throw new Exception("No electronPID has been specified by Electron!");
+ }
+
+ this.CreateSocketBridge(this.port!.Value);
+
+ this.electronProcess = new ElectronProcessPassive(ElectronNetRuntime.ElectronProcessId.Value);
+ this.electronProcess.Stopped += this.ElectronProcess_Stopped;
+
+ this.electronProcess.Start();
+
+ Task.Run(() => this.TransitionState(LifetimeState.Started));
+
+ return Task.CompletedTask;
+ }
+
+ protected override Task StopCore()
+ {
+ this.electronProcess.Stop();
+ return Task.CompletedTask;
+ }
+
+ private void ElectronProcess_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+ }
+}
diff --git a/src/ElectronNET.AspNet/Runtime/Helpers/ServerReadyStartupFilter.cs b/src/ElectronNET.AspNet/Runtime/Helpers/ServerReadyStartupFilter.cs
new file mode 100644
index 0000000..115a96f
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Helpers/ServerReadyStartupFilter.cs
@@ -0,0 +1,30 @@
+ο»Ώnamespace ElectronNET.AspNet
+{
+ using System;
+ using ElectronNET.AspNet.Runtime;
+ using ElectronNET.Runtime;
+ using Microsoft.AspNetCore.Builder;
+ using Microsoft.AspNetCore.Hosting;
+ using Microsoft.Extensions.DependencyInjection;
+
+ internal sealed class ServerReadyStartupFilter : IStartupFilter
+ {
+ ///
+ /// Extends the provided and returns an of the same type.
+ ///
+ /// The Configure method to extend.
+ /// A modified .
+ public Action Configure(Action next)
+ {
+ return app =>
+ {
+ _ = app.ApplicationServices.GetService();
+ var runtimeController = app.ApplicationServices.GetService();
+
+ runtimeController.Start();
+
+ next(app);
+ };
+ }
+ }
+}
diff --git a/src/ElectronNET.AspNet/Runtime/Services/AspNetLifetimeAdapter.cs b/src/ElectronNET.AspNet/Runtime/Services/AspNetLifetimeAdapter.cs
new file mode 100644
index 0000000..9971ed5
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Services/AspNetLifetimeAdapter.cs
@@ -0,0 +1,27 @@
+ο»Ώnamespace ElectronNET.AspNet.Runtime
+{
+ using System.Threading.Tasks;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Services;
+ using Microsoft.Extensions.Hosting;
+
+ internal class AspNetLifetimeAdapter : LifetimeServiceBase
+ {
+ private readonly IHostApplicationLifetime lifetimeService;
+
+ public AspNetLifetimeAdapter(IHostApplicationLifetime lifetimeService)
+ {
+ this.lifetimeService = lifetimeService;
+
+ this.lifetimeService.ApplicationStarted.Register(() => this.TransitionState(LifetimeState.Ready));
+ this.lifetimeService.ApplicationStopping.Register(() => this.TransitionState(LifetimeState.Stopping));
+ this.lifetimeService.ApplicationStopped.Register(() => this.TransitionState(LifetimeState.Stopped));
+ }
+
+ protected override Task StopCore()
+ {
+ this.lifetimeService.StopApplication();
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/ElectronNET.Build/ElectronNET.Build.csproj b/src/ElectronNET.Build/ElectronNET.Build.csproj
new file mode 100644
index 0000000..1bcb426
--- /dev/null
+++ b/src/ElectronNET.Build/ElectronNET.Build.csproj
@@ -0,0 +1,36 @@
+ο»Ώ
+
+
+
+
+ netstandard2.0
+ False
+
+
+
+
+
+
+
+
+
+
+ <_DllTargetPath>$(MSBuildThisFileDirectory)\..\ElectronNET\build
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ElectronNET.Build/ElectronNET.Build.csproj.DotSettings b/src/ElectronNET.Build/ElectronNET.Build.csproj.DotSettings
new file mode 100644
index 0000000..89316e4
--- /dev/null
+++ b/src/ElectronNET.Build/ElectronNET.Build.csproj.DotSettings
@@ -0,0 +1,2 @@
+ο»Ώ
+ Library
\ No newline at end of file
diff --git a/src/ElectronNET.Build/PrintItemMetadata.cs b/src/ElectronNET.Build/PrintItemMetadata.cs
new file mode 100644
index 0000000..2cce179
--- /dev/null
+++ b/src/ElectronNET.Build/PrintItemMetadata.cs
@@ -0,0 +1,39 @@
+ο»Ώnamespace ElectronNET.Build
+{
+ using System;
+ using Microsoft.Build.Framework;
+ using Microsoft.Build.Utilities;
+
+ public class DumpItemMetadataTask : Task
+ {
+ // The item group whose metadata will be dumped.
+ [Required]
+ public ITaskItem[] Items { get; set; }
+
+ public override bool Execute()
+ {
+ try
+ {
+ foreach (var item in this.Items)
+ {
+ // Log the item's identity (the Include attribute)
+ this.Log.LogMessage(MessageImportance.High, $"Item: {item.ItemSpec}");
+
+ // Iterate through each metadata field of the item.
+ foreach (string metadataName in item.MetadataNames)
+ {
+ string metadataValue = item.GetMetadata(metadataName);
+ this.Log.LogMessage(MessageImportance.High, $" {metadataName}: {metadataValue}");
+ }
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ this.Log.LogErrorFromException(ex);
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.Build/RemoveEnvironmentVariables.cs b/src/ElectronNET.Build/RemoveEnvironmentVariables.cs
new file mode 100644
index 0000000..2840e9b
--- /dev/null
+++ b/src/ElectronNET.Build/RemoveEnvironmentVariables.cs
@@ -0,0 +1,42 @@
+ο»Ώnamespace ElectronNET.Build
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+ using Microsoft.Build.Framework;
+ using Microsoft.Build.Utilities;
+
+ public class RemoveEnvironmentVariables : Task
+ {
+ [Required]
+ public string Variables { get; set; }
+
+ public override bool Execute()
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(this.Variables))
+ {
+ this.Log.LogError("The Variables property is not set");
+ return false;
+ }
+
+ var items = this.Variables.Split(new[] { ':', ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var item in items)
+ {
+ Environment.SetEnvironmentVariable(item.Trim(), null);
+ this.Log.LogMessage("Unset environment variable: {0}", item);
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ this.Log.LogErrorFromException(ex);
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.Build/ReplaceMsBuildPropertiesTask.cs b/src/ElectronNET.Build/ReplaceMsBuildPropertiesTask.cs
new file mode 100644
index 0000000..30e7d65
--- /dev/null
+++ b/src/ElectronNET.Build/ReplaceMsBuildPropertiesTask.cs
@@ -0,0 +1,83 @@
+ο»Ώnamespace ElectronNET.Build
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Text.RegularExpressions;
+ using Microsoft.Build.Framework;
+ using Microsoft.Build.Utilities;
+
+ public class ReplaceTemplateTask : Task
+ {
+ [Required]
+ public string TemplateFile { get; set; }
+
+ [Required]
+ public string OutputFile { get; set; }
+
+ [Required]
+ public ITaskItem[] TemplateProperties { get; set; }
+
+ public override bool Execute()
+ {
+ try
+ {
+ ////var props = this.BuildEngine9.GetGlobalProperties();
+
+ ////var globalProperties = props
+ //// .Select(e => string.Format("{0}: {1}", e.Key, e.Value));
+
+ ////this.Log.LogMessage(MessageImportance.High, "Global Properties: \r\n" + string.Join(Environment.NewLine, globalProperties));
+
+ ////var envVariables = Environment.GetEnvironmentVariables();
+ ////var envList = new List();
+ ////foreach (var v in envVariables.Keys)
+ ////{
+ //// envList.Add(string.Format("{0}: {1}", v, envVariables[v]));
+ ////}
+
+ ////this.Log.LogMessage(MessageImportance.High, "Environment Variables: \r\n" + string.Join(Environment.NewLine, envList));
+
+ string content = File.ReadAllText(this.TemplateFile);
+
+ // Build a dictionary of property names and values.
+ var dict = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var item in this.TemplateProperties)
+ {
+ dict[item.ItemSpec] = item.GetMetadata("Value").Replace("\\", "\\\\");
+ }
+
+ // Regex pattern to match placeholders like $(PropertyName)
+ string pattern = @"\$\((?\w+)\)";
+ content = Regex.Replace(content, pattern, match =>
+ {
+ string propName = match.Groups["prop"].Value;
+ return dict.TryGetValue(propName, out var value) ? value : match.Value;
+ });
+
+ // Check if the output file exists and read its content
+ if (File.Exists(this.OutputFile))
+ {
+ string existingContent = File.ReadAllText(this.OutputFile);
+ // Only write the file if the content has changed
+ if (existingContent != content)
+ {
+ File.WriteAllText(this.OutputFile, content);
+ }
+ }
+ else
+ {
+ // Write the transformed content to the output file if it doesn't exist
+ File.WriteAllText(this.OutputFile, content);
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ this.Log.LogErrorFromException(ex);
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/Commands/Actions/DeployEmbeddedElectronFiles.cs b/src/ElectronNET.CLI/Commands/Actions/DeployEmbeddedElectronFiles.cs
deleted file mode 100644
index c0205b6..0000000
--- a/src/ElectronNET.CLI/Commands/Actions/DeployEmbeddedElectronFiles.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-ο»Ώusing System.IO;
-
-namespace ElectronNET.CLI.Commands.Actions
-{
- public static class DeployEmbeddedElectronFiles
- {
- public static void Do(string tempPath)
- {
- EmbeddedFileHelper.DeployEmbeddedFile(tempPath, "main.js");
- EmbeddedFileHelper.DeployEmbeddedFile(tempPath, "package.json");
- EmbeddedFileHelper.DeployEmbeddedFile(tempPath, "build-helper.js");
-
- string vscodeFolder = Path.Combine(tempPath, ".vscode");
- if (Directory.Exists(vscodeFolder) == false)
- {
- Directory.CreateDirectory(vscodeFolder);
- }
- EmbeddedFileHelper.DeployEmbeddedFile(vscodeFolder, "launch.json", ".vscode.");
- EmbeddedFileHelper.DeployEmbeddedFile(vscodeFolder, "tasks.json", ".vscode.");
-
- string hostApiFolder = Path.Combine(tempPath, "api");
- if (Directory.Exists(hostApiFolder) == false)
- {
- Directory.CreateDirectory(hostApiFolder);
- }
-
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "ipc.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "app.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "browserWindows.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "commandLine.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "dialog.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "dock.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "menu.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "notification.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "tray.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "webContents.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "globalShortcut.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "shell.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "screen.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "clipboard.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "autoUpdater.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "browserView.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "powerMonitor.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "nativeTheme.js", "api.");
- EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "process.js", "api.");
-
- string splashscreenFolder = Path.Combine(tempPath, "splashscreen");
- if (Directory.Exists(splashscreenFolder) == false)
- {
- Directory.CreateDirectory(splashscreenFolder);
- }
- EmbeddedFileHelper.DeployEmbeddedFile(splashscreenFolder, "index.html", "splashscreen.");
- }
- }
-}
diff --git a/src/ElectronNET.CLI/Commands/Actions/DirectoryCopy.cs b/src/ElectronNET.CLI/Commands/Actions/DirectoryCopy.cs
deleted file mode 100644
index 39ded69..0000000
--- a/src/ElectronNET.CLI/Commands/Actions/DirectoryCopy.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-ο»Ώusing System.Collections.Generic;
-using System.IO;
-
-namespace ElectronNET.CLI.Commands.Actions
-{
- public static class DirectoryCopy
- {
- public static void Do(string sourceDirName, string destDirName, bool copySubDirs, List ignoredSubDirs)
- {
- // Get the subdirectories for the specified directory.
- DirectoryInfo dir = new DirectoryInfo(sourceDirName);
-
- if (!dir.Exists)
- {
- throw new DirectoryNotFoundException(
- "Source directory does not exist or could not be found: "
- + sourceDirName);
- }
-
- DirectoryInfo[] dirs = dir.GetDirectories();
- // If the destination directory doesn't exist, create it.
- if (!Directory.Exists(destDirName))
- {
- Directory.CreateDirectory(destDirName);
- }
- else
- {
- DirectoryInfo targetDir = new DirectoryInfo(destDirName);
-
- foreach (FileInfo fileDel in targetDir.EnumerateFiles())
- {
- fileDel.Delete();
- }
- foreach (DirectoryInfo dirDel in targetDir.EnumerateDirectories())
- {
- dirDel.Delete(true);
- }
- }
-
-
-
-
- // Get the files in the directory and copy them to the new location.
- FileInfo[] files = dir.GetFiles();
- foreach (FileInfo file in files)
- {
- string temppath = Path.Combine(destDirName, file.Name);
- file.CopyTo(temppath, false);
- }
-
- // If copying subdirectories, copy them and their contents to new location.
- if (copySubDirs)
- {
- foreach (DirectoryInfo subdir in dirs)
- {
- if (ignoredSubDirs.Contains(subdir.Name))
- {
- continue;
- }
-
- string temppath = Path.Combine(destDirName, subdir.Name);
- Do(subdir.FullName, temppath, copySubDirs, ignoredSubDirs);
- }
- }
- }
- }
-}
diff --git a/src/ElectronNET.CLI/Commands/Actions/GetTargetPlatformInformation.cs b/src/ElectronNET.CLI/Commands/Actions/GetTargetPlatformInformation.cs
deleted file mode 100644
index 4918414..0000000
--- a/src/ElectronNET.CLI/Commands/Actions/GetTargetPlatformInformation.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-ο»Ώusing System;
-using System.Runtime.InteropServices;
-
-namespace ElectronNET.CLI.Commands.Actions
-{
- public static class GetTargetPlatformInformation
- {
- public struct GetTargetPlatformInformationResult
- {
- public string NetCorePublishRid { get; set; }
- public string ElectronPackerPlatform { get; set; }
-
- }
-
- public static GetTargetPlatformInformationResult Do(string desiredPlatform, string specifiedPlatfromFromCustom)
- {
- string netCorePublishRid = string.Empty;
- string electronPackerPlatform = string.Empty;
-
- switch (desiredPlatform)
- {
- case "win":
- netCorePublishRid = "win-x64";
- electronPackerPlatform = "win";
- break;
- case "osx":
- netCorePublishRid = "osx-x64";
- electronPackerPlatform = "mac";
- break;
- case "linux":
- netCorePublishRid = "linux-x64";
- electronPackerPlatform = "linux";
- break;
- case "linux-arm":
- netCorePublishRid = "linux-arm";
- electronPackerPlatform = "linux";
- break;
- case "custom":
- var splittedSpecified = specifiedPlatfromFromCustom.Split(';');
- netCorePublishRid = splittedSpecified[0];
- electronPackerPlatform = splittedSpecified[1];
- break;
- default:
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- netCorePublishRid = $"win-x{(Environment.Is64BitOperatingSystem ? "64" : "86")}";
- electronPackerPlatform = "win";
- }
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- netCorePublishRid = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "osx-arm64" : "osx-x64";
- electronPackerPlatform = "mac";
- }
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- netCorePublishRid = "linux-x64";
- electronPackerPlatform = "linux";
- }
-
- break;
- }
-
- return new GetTargetPlatformInformationResult()
- {
- ElectronPackerPlatform = electronPackerPlatform,
- NetCorePublishRid = netCorePublishRid
- };
- }
- }
-}
diff --git a/src/ElectronNET.CLI/Commands/AddCommand.cs b/src/ElectronNET.CLI/Commands/AddCommand.cs
deleted file mode 100644
index cbfb0dd..0000000
--- a/src/ElectronNET.CLI/Commands/AddCommand.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-ο»Ώusing System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Linq;
-
-namespace ElectronNET.CLI.Commands
-{
- public class AddCommand : ICommand
- {
- public const string COMMAND_NAME = "add";
- public const string COMMAND_DESCRIPTION = "The add command needs to be invoked via 'add hosthook'. This creates a special folder for your custom npm package installation.";
- public const string COMMAND_ARGUMENTS = "hosthook";
- public static IList CommandOptions { get; set; } = new List();
-
-
- private string[] _args;
-
- public AddCommand(string[] args)
- {
- _args = args;
- }
-
- private static string ElectronHostHookFolderName = "ElectronHostHook";
-
- public Task ExecuteAsync()
- {
- return Task.Run(() =>
- {
- if(_args.Length == 0)
- {
- Console.WriteLine("Specify 'hosthook' to add custom npm packages.");
- return false;
- }
-
- if(_args[0].ToLowerInvariant() != "hosthook")
- {
- Console.WriteLine("Specify 'hosthook' to add custom npm packages.");
- return false;
- }
-
- string aspCoreProjectPath = "";
-
- // Maybe ToDo: Adding the possiblity to specify a path (like we did in the InitCommand, but this would require a better command args parser)
- aspCoreProjectPath = Directory.GetCurrentDirectory();
-
- var currentDirectory = aspCoreProjectPath;
-
- var targetFilePath = Path.Combine(currentDirectory, ElectronHostHookFolderName);
-
- if(Directory.Exists(targetFilePath))
- {
- Console.WriteLine("ElectronHostHook directory already in place. If you want to start over, delete the folder and invoke this command again.");
- return false;
- }
-
- Console.WriteLine("Adding the ElectronHostHook folder to your project...");
-
- Directory.CreateDirectory(targetFilePath);
-
- // Deploy related files
- EmbeddedFileHelper.DeployEmbeddedFile(targetFilePath, "index.ts", "ElectronHostHook.");
- EmbeddedFileHelper.DeployEmbeddedFile(targetFilePath, "connector.ts", "ElectronHostHook.");
- EmbeddedFileHelper.DeployEmbeddedFile(targetFilePath, "package.json", "ElectronHostHook.");
- EmbeddedFileHelper.DeployEmbeddedFile(targetFilePath, "tsconfig.json", "ElectronHostHook.");
- EmbeddedFileHelper.DeployEmbeddedFile(targetFilePath, ".gitignore", "ElectronHostHook.");
-
- // npm for typescript compiler etc.
- Console.WriteLine("Start npm install...");
- ProcessHelper.CmdExecute("npm install", targetFilePath);
-
- // run typescript compiler
- // ToDo: Not sure if this runs under linux/macos
- ProcessHelper.CmdExecute(@"npx tsc -p ../../", targetFilePath);
-
- // search .csproj or .fsproj (.csproj has higher precedence)
- Console.WriteLine($"Search your .csproj/.fsproj to add configure CopyToPublishDirectory to 'Never'");
- var projectFile = Directory.EnumerateFiles(currentDirectory, "*.csproj", SearchOption.TopDirectoryOnly)
- .Union(Directory.EnumerateFiles(currentDirectory, "*.fsproj", SearchOption.TopDirectoryOnly))
- .FirstOrDefault();
-
- var extension = Path.GetExtension(projectFile);
- Console.WriteLine($"Found your {extension}: {projectFile} - check for existing CopyToPublishDirectory setting or update it.");
-
- if (!EditProjectFile(projectFile)) return false;
-
- Console.WriteLine($"Everything done - happy electronizing with your custom npm packages!");
-
- return true;
- });
- }
-
- // ToDo: Cleanup this copy/past code.
- private static bool EditProjectFile(string projectFile)
- {
- using (var stream = File.Open(projectFile, FileMode.OpenOrCreate, FileAccess.ReadWrite))
- {
- var xmlDocument = XDocument.Load(stream);
-
- var projectElement = xmlDocument.Descendants("Project").FirstOrDefault();
- if (projectElement == null || projectElement.Attribute("Sdk")?.Value != "Microsoft.NET.Sdk.Web")
- {
- Console.WriteLine(
- $"Project file is not a compatible type of 'Microsoft.NET.Sdk.Web'. Your project: {projectElement?.Attribute("Sdk")?.Value}");
- return false;
- }
-
- string itemGroupXmlString = "" +
- "" +
- "Never " +
- " " +
- " ";
-
- var newItemGroupForConfig = XElement.Parse(itemGroupXmlString);
- xmlDocument.Root.Add(newItemGroupForConfig);
-
- stream.SetLength(0);
- stream.Position = 0;
-
- var xws = new XmlWriterSettings
- {
- OmitXmlDeclaration = true,
- Indent = true
- };
- using (XmlWriter xw = XmlWriter.Create(stream, xws))
- {
- xmlDocument.Save(xw);
- }
-
- }
-
- Console.WriteLine($"Publish setting added in csproj/fsproj!");
- return true;
- }
-
- }
-}
diff --git a/src/ElectronNET.CLI/Commands/BuildCommand.cs b/src/ElectronNET.CLI/Commands/BuildCommand.cs
deleted file mode 100644
index 2f4f52c..0000000
--- a/src/ElectronNET.CLI/Commands/BuildCommand.cs
+++ /dev/null
@@ -1,255 +0,0 @@
-ο»Ώusing System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using ElectronNET.CLI.Commands.Actions;
-
-namespace ElectronNET.CLI.Commands
-{
- public class BuildCommand : ICommand
- {
- public const string COMMAND_NAME = "build";
- public const string COMMAND_DESCRIPTION = "Build your Electron Application.";
- public static string COMMAND_ARGUMENTS = "Needed: '/target' with params 'win/osx/linux' to build for a typical app or use 'custom' and specify .NET Core build config & electron build config" + Environment.NewLine +
- " for custom target, check .NET Core RID Catalog and Electron build target/" + Environment.NewLine +
- " e.g. '/target win' or '/target custom \"win7-x86;win\"'" + Environment.NewLine +
- "Optional: '/dotnet-configuration' with the desired .NET Core build config e.g. release or debug. Default = Release" + Environment.NewLine +
- "Optional: '/electron-arch' to specify the resulting electron processor architecture (e.g. ia86 for x86 builds). Be aware to use the '/target custom' param as well!" + Environment.NewLine +
- "Optional: '/electron-params' specify any other valid parameter, which will be routed to the electron-packager." + Environment.NewLine +
- "Optional: '/relative-path' to specify output a subdirectory for output." + Environment.NewLine +
- "Optional: '/absolute-path to specify and absolute path for output." + Environment.NewLine +
- "Optional: '/package-json' to specify a custom package.json file." + Environment.NewLine +
- "Optional: '/install-modules' to force node module install. Implied by '/package-json'" + Environment.NewLine +
- "Optional: '/Version' to specify the version that should be applied to both the `dotnet publish` and `electron-builder` commands. Implied by '/Version'" + Environment.NewLine +
- "Optional: '/p:[property]' or '/property:[property]' to pass in dotnet publish properties. Example: '/property:Version=1.0.0' to override the FileVersion" + Environment.NewLine +
- "Full example for a 32bit debug build with electron prune: build /target custom win7-x86;win32 /dotnet-configuration Debug /electron-arch ia32 /electron-params \"--prune=true \"";
-
- public static IList CommandOptions { get; set; } = new List();
-
- private string[] _args;
-
- public BuildCommand(string[] args)
- {
- _args = args;
- }
-
- private string _paramTarget = "target";
- private string _paramDotNetConfig = "dotnet-configuration";
- private string _paramElectronArch = "electron-arch";
- private string _paramElectronParams = "electron-params";
- private string _paramOutputDirectory = "relative-path";
- private string _paramAbsoluteOutput = "absolute-path";
- private string _paramPackageJson = "package-json";
- private string _paramForceNodeInstall = "install-modules";
- private string _manifest = "manifest";
- private string _paramPublishReadyToRun = "PublishReadyToRun";
- private string _paramPublishSingleFile = "PublishSingleFile";
- private string _paramVersion = "Version";
-
- public Task ExecuteAsync()
- {
- return Task.Run(() =>
- {
- Console.WriteLine("Build Electron Application...");
-
- SimpleCommandLineParser parser = new SimpleCommandLineParser();
- parser.Parse(_args);
-
- //This version will be shared between the dotnet publish and electron-builder commands
- string version = null;
- if (parser.Arguments.ContainsKey(_paramVersion))
- version = parser.Arguments[_paramVersion][0];
-
- if (!parser.Arguments.ContainsKey(_paramTarget))
- {
- Console.WriteLine($"Error: missing '{_paramTarget}' argument.");
- Console.WriteLine(COMMAND_ARGUMENTS);
- return false;
- }
-
- var desiredPlatform = parser.Arguments[_paramTarget][0];
- string specifiedFromCustom = string.Empty;
- if (desiredPlatform == "custom" && parser.Arguments[_paramTarget].Length > 1)
- {
- specifiedFromCustom = parser.Arguments[_paramTarget][1];
- }
-
- string configuration = "Release";
- if (parser.Arguments.ContainsKey(_paramDotNetConfig))
- {
- configuration = parser.Arguments[_paramDotNetConfig][0];
- }
-
- var platformInfo = GetTargetPlatformInformation.Do(desiredPlatform, specifiedFromCustom);
-
- Console.WriteLine($"Build ASP.NET Core App for {platformInfo.NetCorePublishRid}...");
-
- string tempPath = Path.Combine(Directory.GetCurrentDirectory(), "obj", "desktop", desiredPlatform);
-
- if (Directory.Exists(tempPath) == false)
- {
- Directory.CreateDirectory(tempPath);
- }
- else
- {
- Directory.Delete(tempPath, true);
- Directory.CreateDirectory(tempPath);
- }
-
-
- Console.WriteLine("Executing dotnet publish in this directory: " + tempPath);
-
- string tempBinPath = Path.Combine(tempPath, "bin");
-
- Console.WriteLine($"Build ASP.NET Core App for {platformInfo.NetCorePublishRid} under {configuration}-Configuration...");
-
- var dotNetPublishFlags = GetDotNetPublishFlags(parser);
-
- var command =
- $"dotnet publish -r {platformInfo.NetCorePublishRid} -c \"{configuration}\" --output \"{tempBinPath}\" {string.Join(' ', dotNetPublishFlags.Select(kvp => $"{kvp.Key}={kvp.Value}"))} --self-contained";
-
- // output the command
- Console.ForegroundColor = ConsoleColor.Green;
- Console.WriteLine(command);
- Console.ResetColor();
-
- var resultCode = ProcessHelper.CmdExecute(command, Directory.GetCurrentDirectory());
-
- if (resultCode != 0)
- {
- Console.WriteLine("Error occurred during dotnet publish: " + resultCode);
- return false;
- }
-
- DeployEmbeddedElectronFiles.Do(tempPath);
- var nodeModulesDirPath = Path.Combine(tempPath, "node_modules");
-
- if (parser.Arguments.ContainsKey(_paramPackageJson))
- {
- Console.WriteLine("Copying custom package.json.");
-
- File.Copy(parser.Arguments[_paramPackageJson][0], Path.Combine(tempPath, "package.json"), true);
- }
-
- var checkForNodeModulesDirPath = Path.Combine(tempPath, "node_modules");
-
- if (Directory.Exists(checkForNodeModulesDirPath) == false || parser.Contains(_paramForceNodeInstall) || parser.Contains(_paramPackageJson))
-
- Console.WriteLine("Start npm install...");
- ProcessHelper.CmdExecute("npm install --production", tempPath);
-
- Console.WriteLine("ElectronHostHook handling started...");
-
- string electronhosthookDir = Path.Combine(Directory.GetCurrentDirectory(), "ElectronHostHook");
-
- if (Directory.Exists(electronhosthookDir))
- {
- string hosthookDir = Path.Combine(tempPath, "ElectronHostHook");
- DirectoryCopy.Do(electronhosthookDir, hosthookDir, true, new List() { "node_modules" });
-
- Console.WriteLine("Start npm install for hosthooks...");
- ProcessHelper.CmdExecute("npm install", hosthookDir);
-
- // ToDo: Not sure if this runs under linux/macos
- ProcessHelper.CmdExecute(@"npx tsc -p . --sourceMap false", hosthookDir);
- }
-
- Console.WriteLine("Build Electron Desktop Application...");
-
- // Specifying an absolute path supercedes a relative path
- string buildPath = Path.Combine(Directory.GetCurrentDirectory(), "bin", "desktop");
- if (parser.Arguments.ContainsKey(_paramAbsoluteOutput))
- {
- buildPath = parser.Arguments[_paramAbsoluteOutput][0];
- }
- else if (parser.Arguments.ContainsKey(_paramOutputDirectory))
- {
- buildPath = Path.Combine(Directory.GetCurrentDirectory(), parser.Arguments[_paramOutputDirectory][0]);
- }
-
- Console.WriteLine("Executing electron magic in this directory: " + buildPath);
-
- string electronArch = "x64";
- if (parser.Arguments.ContainsKey(_paramElectronArch))
- {
- electronArch = parser.Arguments[_paramElectronArch][0];
- }
-
- string electronParams = "";
- if (parser.Arguments.ContainsKey(_paramElectronParams))
- {
- electronParams = parser.Arguments[_paramElectronParams][0];
- }
-
- // ToDo: Make the same thing easer with native c# - we can save a tmp file in production code :)
- Console.WriteLine("Create electron-builder configuration file...");
-
- string manifestFileName = "electron.manifest.json";
-
- if (parser.Arguments.ContainsKey(_manifest))
- {
- manifestFileName = parser.Arguments[_manifest].First();
- }
-
- ProcessHelper.CmdExecute(
- string.IsNullOrWhiteSpace(version)
- ? $"node build-helper.js {manifestFileName}"
- : $"node build-helper.js {manifestFileName} {version}", tempPath);
-
- Console.WriteLine($"Package Electron App for Platform {platformInfo.ElectronPackerPlatform}...");
- ProcessHelper.CmdExecute($"npx electron-builder --config=./bin/electron-builder.json --{platformInfo.ElectronPackerPlatform} --{electronArch} -c.electronVersion=23.2.0 {electronParams}", tempPath);
-
- Console.WriteLine("... done");
-
- return true;
- });
- }
-
- private Dictionary GetDotNetPublishFlags(SimpleCommandLineParser parser)
- {
- var dotNetPublishFlags = new Dictionary
- {
- {"/p:PublishReadyToRun", parser.TryGet(_paramPublishReadyToRun, out var rtr) ? rtr[0] : "true"},
- {"/p:PublishSingleFile", parser.TryGet(_paramPublishSingleFile, out var psf) ? psf[0] : "true"},
- };
-
- if (parser.Arguments.ContainsKey(_paramVersion))
- {
- if(parser.Arguments.Keys.All(key => !key.StartsWith("p:Version=") && !key.StartsWith("property:Version=")))
- dotNetPublishFlags.Add("/p:Version", parser.Arguments[_paramVersion][0]);
- if(parser.Arguments.Keys.All(key => !key.StartsWith("p:ProductVersion=") && !key.StartsWith("property:ProductVersion=")))
- dotNetPublishFlags.Add("/p:ProductVersion", parser.Arguments[_paramVersion][0]);
- }
-
- foreach (var parm in parser.Arguments.Keys.Where(key => key.StartsWith("p:") || key.StartsWith("property:")))
- {
- var split = parm.IndexOf('=');
- if (split < 0)
- {
- continue;
- }
-
- var key = $"/{parm.Substring(0, split)}";
- // normalize the key
- if (key.StartsWith("/property:"))
- {
- key = key.Replace("/property:", "/p:");
- }
-
- var value = parm.Substring(split + 1);
-
- if (dotNetPublishFlags.ContainsKey(key))
- {
- dotNetPublishFlags[key] = value;
- }
- else
- {
- dotNetPublishFlags.Add(key, value);
- }
- }
-
- return dotNetPublishFlags;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/Commands/CommandOption.cs b/src/ElectronNET.CLI/Commands/CommandOption.cs
deleted file mode 100644
index 675f571..0000000
--- a/src/ElectronNET.CLI/Commands/CommandOption.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-ο»Ώnamespace ElectronNET.CLI.Commands
-{
- ///
- /// The definitionn of an option for a command.
- ///
- public class CommandOption
- {
- ///
- /// An enum for the possible values for an option
- ///
- public enum CommandOptionValueType { NoValue, StringValue, BoolValue, IntValue, CommaDelimitedList, KeyValuePairs }
-
- ///
- /// The name of the option.
- ///
- public string Name { get; set; }
-
- ///
- /// The short form of the command line switch. This will start with just one dash e.g. -f for framework
- ///
- public string ShortSwitch { get; set; }
-
- ///
- /// The full form of the command line switch. This will start with two dashes e.g. --framework
- ///
- public string Switch { get; set; }
-
- ///
- /// The description of the option
- ///
- public string Description { get; set; }
-
- ///
- /// The type of value that is expected with this command option
- ///
- public CommandOptionValueType ValueType { get; set; }
-
- ///
- /// The JSON key used in configuration file.`
- ///
- public string ConfigFileKey
- {
- get
- {
- var key = this.Switch;
- if (key.StartsWith("--"))
- key = key.Substring(2);
-
- return key;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/Commands/ICommand.cs b/src/ElectronNET.CLI/Commands/ICommand.cs
deleted file mode 100644
index e247a2a..0000000
--- a/src/ElectronNET.CLI/Commands/ICommand.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-ο»Ώusing System.Threading.Tasks;
-
-namespace ElectronNET.CLI.Commands
-{
- ///
- /// Interface for commands to implement.
- ///
- public interface ICommand
- {
- Task ExecuteAsync();
- }
-}
diff --git a/src/ElectronNET.CLI/Commands/InitCommand.cs b/src/ElectronNET.CLI/Commands/InitCommand.cs
deleted file mode 100644
index c253920..0000000
--- a/src/ElectronNET.CLI/Commands/InitCommand.cs
+++ /dev/null
@@ -1,214 +0,0 @@
-ο»Ώusing System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Linq;
-
-namespace ElectronNET.CLI.Commands
-{
- public class InitCommand : ICommand
- {
- public const string COMMAND_NAME = "init";
- public const string COMMAND_DESCRIPTION = "Creates the needed Electron.NET config for your Electron Application.";
- public const string COMMAND_ARGUMENTS = " from ASP.NET Core Project.";
- public static IList CommandOptions { get; set; } = new List();
-
- private static SimpleCommandLineParser _parser = new SimpleCommandLineParser();
- private static string ConfigName = "electron.manifest.json";
- private const string DefaultConfigFileName = "electron.manifest.json";
-
- public InitCommand(string[] args)
- {
- _parser.Parse(args);
- }
-
- private static string _aspCoreProjectPath = "project-path";
- private static string _manifest = "manifest";
-
- public Task ExecuteAsync()
- {
- return Task.Run(() =>
- {
- string aspCoreProjectPath = "";
-
- if (_parser.Arguments.ContainsKey(_aspCoreProjectPath))
- {
- string projectPath = _parser.Arguments[_aspCoreProjectPath].First();
- if (Directory.Exists(projectPath))
- {
- aspCoreProjectPath = projectPath;
- }
- }
- else
- {
- aspCoreProjectPath = Directory.GetCurrentDirectory();
- }
-
- var currentDirectory = aspCoreProjectPath;
-
- if(_parser.Arguments.ContainsKey(_manifest))
- {
- ConfigName = "electron.manifest." + _parser.Arguments[_manifest].First() + ".json";
- Console.WriteLine($"Adding your custom {ConfigName} config file to your project...");
- }
- else
- {
- Console.WriteLine("Adding our config file to your project...");
- }
-
- var targetFilePath = Path.Combine(currentDirectory, ConfigName);
-
- if (File.Exists(targetFilePath))
- {
- Console.WriteLine("Config file already in your project.");
- return false;
- }
-
- // Deploy config file
- EmbeddedFileHelper.DeployEmbeddedFileToTargetFile(currentDirectory, DefaultConfigFileName, ConfigName);
-
- // search .csproj/.fsproj (.csproj has higher precedence)
- Console.WriteLine($"Search your .csproj/fsproj to add the needed {ConfigName}...");
- var projectFile = Directory.EnumerateFiles(currentDirectory, "*.csproj", SearchOption.TopDirectoryOnly)
- .Union(Directory.EnumerateFiles(currentDirectory, "*.fsproj", SearchOption.TopDirectoryOnly))
- .FirstOrDefault();
-
- // update config file with the name of the csproj/fsproj
- // ToDo: If the csproj/fsproj name != application name, this will fail
- string text = File.ReadAllText(targetFilePath);
- text = text.Replace("{{executable}}", Path.GetFileNameWithoutExtension(projectFile));
- File.WriteAllText(targetFilePath, text);
-
- var extension = Path.GetExtension(projectFile);
- Console.WriteLine($"Found your {extension}: {projectFile} - check for existing config or update it.");
-
- if (!EditProjectFile(projectFile)) return false;
-
- // search launchSettings.json
- Console.WriteLine($"Search your .launchSettings to add our electron debug profile...");
-
- EditLaunchSettings(currentDirectory);
-
- Console.WriteLine($"Everything done - happy electronizing!");
-
- return true;
- });
- }
-
- private static void EditLaunchSettings(string currentDirectory)
- {
- // super stupid implementation, but because there is no nativ way to parse json
- // and cli extensions and other nuget packages are buggy
- // this is should solve the problem for 80% of the users
- // for the other 20% we might fail...
- var launchSettingFile = Path.Combine(currentDirectory, "Properties", "launchSettings.json");
-
- if (File.Exists(launchSettingFile) == false)
- {
- Console.WriteLine("launchSettings.json not found - do nothing.");
- return;
- }
-
- string launchSettingText = File.ReadAllText(launchSettingFile);
-
- if(_parser.Arguments.ContainsKey(_manifest))
- {
- string manifestName = _parser.Arguments[_manifest].First();
-
- if(launchSettingText.Contains("start /manifest " + ConfigName) == false)
- {
- StringBuilder debugProfileBuilder = new StringBuilder();
- debugProfileBuilder.AppendLine("profiles\": {");
- debugProfileBuilder.AppendLine(" \"Electron.NET App - " + manifestName + "\": {");
- debugProfileBuilder.AppendLine(" \"commandName\": \"Executable\",");
- debugProfileBuilder.AppendLine(" \"executablePath\": \"electronize\",");
- debugProfileBuilder.AppendLine(" \"commandLineArgs\": \"start /manifest " + ConfigName + "\",");
- debugProfileBuilder.AppendLine(" \"workingDirectory\": \".\"");
- debugProfileBuilder.AppendLine(" },");
-
- launchSettingText = launchSettingText.Replace("profiles\": {", debugProfileBuilder.ToString());
- File.WriteAllText(launchSettingFile, launchSettingText);
-
- Console.WriteLine($"Debug profile added!");
- }
- else
- {
- Console.WriteLine($"Debug profile already existing");
- }
- }
- else if (launchSettingText.Contains("\"executablePath\": \"electronize\"") == false)
- {
- StringBuilder debugProfileBuilder = new StringBuilder();
- debugProfileBuilder.AppendLine("profiles\": {");
- debugProfileBuilder.AppendLine(" \"Electron.NET App\": {");
- debugProfileBuilder.AppendLine(" \"commandName\": \"Executable\",");
- debugProfileBuilder.AppendLine(" \"executablePath\": \"electronize\",");
- debugProfileBuilder.AppendLine(" \"commandLineArgs\": \"start\",");
- debugProfileBuilder.AppendLine(" \"workingDirectory\": \".\"");
- debugProfileBuilder.AppendLine(" },");
-
- launchSettingText = launchSettingText.Replace("profiles\": {", debugProfileBuilder.ToString());
- File.WriteAllText(launchSettingFile, launchSettingText);
-
- Console.WriteLine($"Debug profile added!");
- }
- else
- {
- Console.WriteLine($"Debug profile already existing");
- }
- }
-
- private static bool EditProjectFile(string projectFile)
- {
- using (var stream = File.Open(projectFile, FileMode.OpenOrCreate, FileAccess.ReadWrite))
- {
- var xmlDocument = XDocument.Load(stream);
-
- var projectElement = xmlDocument.Descendants("Project").FirstOrDefault();
- if (projectElement == null || projectElement.Attribute("Sdk")?.Value != "Microsoft.NET.Sdk.Web")
- {
- Console.WriteLine(
- $"Project file is not a compatible type of 'Microsoft.NET.Sdk.Web'. Your project: {projectElement?.Attribute("Sdk")?.Value}");
- return false;
- }
-
- if (xmlDocument.ToString().Contains($"Content Update=\"{ConfigName}\""))
- {
- Console.WriteLine($"{ConfigName} already in csproj/fsproj.");
- return false;
- }
-
- Console.WriteLine($"{ConfigName} will be added to csproj/fsproj.");
-
- string itemGroupXmlString = "" +
- "" +
- "PreserveNewest " +
- " " +
- " ";
-
- var newItemGroupForConfig = XElement.Parse(itemGroupXmlString);
- xmlDocument.Root.Add(newItemGroupForConfig);
-
- stream.SetLength(0);
- stream.Position = 0;
-
- var xws = new XmlWriterSettings
- {
- OmitXmlDeclaration = true,
- Indent = true
- };
- using (XmlWriter xw = XmlWriter.Create(stream, xws))
- {
- xmlDocument.Save(xw);
- }
-
- }
-
- Console.WriteLine($"{ConfigName} added in csproj/fsproj!");
- return true;
- }
- }
-}
diff --git a/src/ElectronNET.CLI/Commands/StartElectronCommand.cs b/src/ElectronNET.CLI/Commands/StartElectronCommand.cs
deleted file mode 100644
index 03382ec..0000000
--- a/src/ElectronNET.CLI/Commands/StartElectronCommand.cs
+++ /dev/null
@@ -1,185 +0,0 @@
-ο»Ώusing System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using ElectronNET.CLI.Commands.Actions;
-
-namespace ElectronNET.CLI.Commands
-{
- public class StartElectronCommand : ICommand
- {
- public const string COMMAND_NAME = "start";
- public const string COMMAND_DESCRIPTION = "Start your ASP.NET Core Application with Electron, without package it as a single exe. Faster for development.";
- public const string COMMAND_ARGUMENTS = " from ASP.NET Core Project.";
- public static IList CommandOptions { get; set; } = new List();
-
- private string[] _args;
-
- public StartElectronCommand(string[] args)
- {
- _args = args;
- }
-
- private string _aspCoreProjectPath = "project-path";
- private string _arguments = "args";
- private string _manifest = "manifest";
- private string _clearCache = "clear-cache";
- private string _paramPublishReadyToRun = "PublishReadyToRun";
- private string _paramPublishSingleFile = "PublishSingleFile";
- private string _paramDotNetConfig = "dotnet-configuration";
- private string _paramTarget = "target";
-
- public Task ExecuteAsync()
- {
- return Task.Run(() =>
- {
- Console.WriteLine("Start Electron Desktop Application...");
-
- SimpleCommandLineParser parser = new SimpleCommandLineParser();
- parser.Parse(_args);
-
- string aspCoreProjectPath = "";
-
- if (parser.Arguments.ContainsKey(_aspCoreProjectPath))
- {
- string projectPath = parser.Arguments[_aspCoreProjectPath].First();
- if (Directory.Exists(projectPath))
- {
- aspCoreProjectPath = projectPath;
- }
- }
- else
- {
- aspCoreProjectPath = Directory.GetCurrentDirectory();
- }
-
- string tempPath = Path.Combine(aspCoreProjectPath, "obj", "Host");
- if (Directory.Exists(tempPath) == false)
- {
- Directory.CreateDirectory(tempPath);
- }
-
- string tempBinPath = Path.Combine(tempPath, "bin");
- var resultCode = 0;
-
- string publishReadyToRun = "/p:PublishReadyToRun=";
- if (parser.Arguments.ContainsKey(_paramPublishReadyToRun))
- {
- publishReadyToRun += parser.Arguments[_paramPublishReadyToRun][0];
- }
- else
- {
- publishReadyToRun += "true";
- }
-
- string publishSingleFile = "/p:PublishSingleFile=";
- if (parser.Arguments.ContainsKey(_paramPublishSingleFile))
- {
- publishSingleFile += parser.Arguments[_paramPublishSingleFile][0];
- }
- else
- {
- publishSingleFile += "true";
- }
-
- // If target is specified as a command line argument, use it.
- // Format is the same as the build command.
- // If target is not specified, autodetect it.
- var platformInfo = GetTargetPlatformInformation.Do(string.Empty, string.Empty);
- if (parser.Arguments.ContainsKey(_paramTarget))
- {
- var desiredPlatform = parser.Arguments[_paramTarget][0];
- string specifiedFromCustom = string.Empty;
- if (desiredPlatform == "custom" && parser.Arguments[_paramTarget].Length > 1)
- {
- specifiedFromCustom = parser.Arguments[_paramTarget][1];
- }
- platformInfo = GetTargetPlatformInformation.Do(desiredPlatform, specifiedFromCustom);
- }
-
- string configuration = "Debug";
- if (parser.Arguments.ContainsKey(_paramDotNetConfig))
- {
- configuration = parser.Arguments[_paramDotNetConfig][0];
- }
-
- if (parser != null && !parser.Arguments.ContainsKey("watch"))
- {
- resultCode = ProcessHelper.CmdExecute($"dotnet publish -r {platformInfo.NetCorePublishRid} -c \"{configuration}\" --output \"{tempBinPath}\" {publishReadyToRun} {publishSingleFile} --no-self-contained", aspCoreProjectPath);
- }
-
- if (resultCode != 0)
- {
- Console.WriteLine("Error occurred during dotnet publish: " + resultCode);
- return false;
- }
-
- DeployEmbeddedElectronFiles.Do(tempPath);
-
- var nodeModulesDirPath = Path.Combine(tempPath, "node_modules");
-
- Console.WriteLine("node_modules missing in: " + nodeModulesDirPath);
-
- Console.WriteLine("Start npm install...");
- ProcessHelper.CmdExecute("npm install", tempPath);
-
- Console.WriteLine("ElectronHostHook handling started...");
-
- string electronhosthookDir = Path.Combine(Directory.GetCurrentDirectory(), "ElectronHostHook");
-
- if (Directory.Exists(electronhosthookDir))
- {
- string hosthookDir = Path.Combine(tempPath, "ElectronHostHook");
- DirectoryCopy.Do(electronhosthookDir, hosthookDir, true, new List() { "node_modules" });
-
- Console.WriteLine("Start npm install for typescript & hosthooks...");
- ProcessHelper.CmdExecute("npm install", hosthookDir);
-
- // ToDo: Not sure if this runs under linux/macos
- ProcessHelper.CmdExecute(@"npx tsc -p ../../ElectronHostHook", tempPath);
- }
-
- string arguments = "";
-
- if (parser.Arguments.ContainsKey(_arguments))
- {
- arguments = string.Join(' ', parser.Arguments[_arguments]);
- }
-
- if (parser.Arguments.ContainsKey(_manifest))
- {
- arguments += " --manifest=" + parser.Arguments[_manifest].First();
- }
-
- if (parser.Arguments.ContainsKey(_clearCache))
- {
- arguments += " --clear-cache=true";
- }
-
- if (parser.Arguments.ContainsKey("watch"))
- {
- arguments += " --watch=true";
- }
-
- string path = Path.Combine(tempPath, "node_modules", ".bin");
- bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-
- if (isWindows)
- {
- Console.WriteLine("Invoke electron.cmd - in dir: " + path);
- ProcessHelper.CmdExecute(@"electron.cmd ""..\..\main.js"" " + arguments, path);
-
- }
- else
- {
- Console.WriteLine("Invoke electron - in dir: " + path);
- ProcessHelper.CmdExecute(@"./electron ""../../main.js"" " + arguments, path);
- }
-
- return true;
- });
- }
- }
-}
diff --git a/src/ElectronNET.CLI/Commands/VersionCommand.cs b/src/ElectronNET.CLI/Commands/VersionCommand.cs
deleted file mode 100644
index c8f60f8..0000000
--- a/src/ElectronNET.CLI/Commands/VersionCommand.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-ο»Ώusing System;
-using System.Collections.Generic;
-using System.Reflection;
-using System.Threading.Tasks;
-
-namespace ElectronNET.CLI.Commands
-{
- public class VersionCommand : ICommand
- {
- public const string COMMAND_NAME = "version";
- public const string COMMAND_DESCRIPTION = "Displays the ElectronNET.CLI version";
- public const string COMMAND_ARGUMENTS = "";
- public static IList CommandOptions { get; set; } = new List();
-
- public VersionCommand(string[] args)
- {
- }
-
- public Task ExecuteAsync()
- {
- return Task.Run(() =>
- {
- var runtimeVersion = typeof(VersionCommand)
- .GetTypeInfo()
- .Assembly
- .GetCustomAttribute();
-
- Console.WriteLine($"ElectronNET.CLI Version: " + runtimeVersion.Version);
-
- return true;
- });
- }
-
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/ElectronNET.CLI.csproj b/src/ElectronNET.CLI/ElectronNET.CLI.csproj
deleted file mode 100644
index 074ffd8..0000000
--- a/src/ElectronNET.CLI/ElectronNET.CLI.csproj
+++ /dev/null
@@ -1,90 +0,0 @@
-ο»Ώ
-
- Exe
- net8.0
- dotnet-electronize
- electronize
- DotnetCliTool
- ..\..\artifacts
- ElectronNET.CLI
-
- 99.0.0.0
- Gregor Biswanger, Florian Rappl
- Electron.NET
-
-
- Building cross platform electron based desktop apps with .NET Core and ASP.NET Core.
- This package contains the dotnet tooling to electronize your application.
-
- MIT
- https://github.com/ElectronNET/Electron.NET/
- https://github.com/ElectronNET/Electron.NET/
- git
- true
- electron aspnetcore
- Changelog: https://github.com/ElectronNET/Electron.NET/blob/main/Changelog.md
- PackageIcon.png
- true
- true
-
-
-
-
- AnyCPU
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
- all
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/EmbeddedFileHelper.cs b/src/ElectronNET.CLI/EmbeddedFileHelper.cs
deleted file mode 100644
index d41e657..0000000
--- a/src/ElectronNET.CLI/EmbeddedFileHelper.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-ο»Ώusing System;
-using System.IO;
-using System.Reflection;
-
-namespace ElectronNET.CLI
-{
- public static class EmbeddedFileHelper
- {
- private const string ResourcePath = "ElectronNET.CLI.{0}";
-
- private static Stream GetTestResourceFileStream(string folderAndFileInProjectPath)
- {
- var asm = Assembly.GetExecutingAssembly();
- var resource = string.Format(ResourcePath, folderAndFileInProjectPath);
-
- return asm.GetManifestResourceStream(resource);
- }
-
- public static void DeployEmbeddedFile(string targetPath, string file, string namespacePath = "")
- {
- using (var fileStream = File.Create(Path.Combine(targetPath, file)))
- {
- var streamFromEmbeddedFile = GetTestResourceFileStream("ElectronHost." + namespacePath + file);
- if (streamFromEmbeddedFile == null)
- {
- Console.WriteLine("Error: Couldn't find embedded file: " + file);
- }
-
- streamFromEmbeddedFile.CopyTo(fileStream);
- }
- }
-
- public static void DeployEmbeddedFileToTargetFile(string targetPath, string embeddedFile, string targetFile, string namespacePath = "")
- {
- using (var fileStream = File.Create(Path.Combine(targetPath, targetFile)))
- {
- var streamFromEmbeddedFile = GetTestResourceFileStream("ElectronHost." + namespacePath + embeddedFile);
- if (streamFromEmbeddedFile == null)
- {
- Console.WriteLine("Error: Couldn't find embedded file: " + embeddedFile);
- }
-
- streamFromEmbeddedFile.CopyTo(fileStream);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/ProcessHelper.cs b/src/ElectronNET.CLI/ProcessHelper.cs
deleted file mode 100644
index 9e0048f..0000000
--- a/src/ElectronNET.CLI/ProcessHelper.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-ο»Ώusing System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace ElectronNET.CLI
-{
- public class ProcessHelper
- {
- public static int CmdExecute(string command, string workingDirectoryPath, bool output = true, bool waitForExit = true)
- {
- using (Process cmd = new Process())
- {
- bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-
- if (isWindows)
- {
- cmd.StartInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
- }
- else
- {
- // works for OSX and Linux (at least on Ubuntu)
- var escapedArgs = command.Replace("\"", "\\\"");
- cmd.StartInfo = new ProcessStartInfo("bash", $"-c \"{escapedArgs}\"");
- }
-
- cmd.StartInfo.RedirectStandardInput = true;
- cmd.StartInfo.RedirectStandardOutput = true;
- cmd.StartInfo.RedirectStandardError = true;
- cmd.StartInfo.CreateNoWindow = true;
- cmd.StartInfo.UseShellExecute = false;
- cmd.StartInfo.WorkingDirectory = workingDirectoryPath;
-
- if (output)
- {
- cmd.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
- cmd.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
- }
-
- Console.WriteLine(command);
- cmd.Start();
- cmd.BeginOutputReadLine();
- cmd.BeginErrorReadLine();
-
- if (waitForExit)
- {
- cmd.WaitForExit();
- }
-
- return cmd.ExitCode;
- }
- }
- }
-}
diff --git a/src/ElectronNET.CLI/Program.cs b/src/ElectronNET.CLI/Program.cs
deleted file mode 100644
index 2030030..0000000
--- a/src/ElectronNET.CLI/Program.cs
+++ /dev/null
@@ -1,201 +0,0 @@
-ο»Ώusing ElectronNET.CLI.Commands;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-
-namespace ElectronNET.CLI
-{
- class Program
- {
- static void Main(string[] args)
- {
- if (args.Length == 0)
- {
- PrintUsageHeader();
- PrintUsage();
- Environment.Exit(-1);
- }
-
- ICommand command = null;
-
- switch (args[0])
- {
- case StartElectronCommand.COMMAND_NAME:
- command = new StartElectronCommand(args.Skip(1).ToArray());
- break;
- case BuildCommand.COMMAND_NAME:
- command = new BuildCommand(args.Skip(1).ToArray());
- break;
- case InitCommand.COMMAND_NAME:
- command = new InitCommand(args.Skip(1).ToArray());
- break;
- case AddCommand.COMMAND_NAME:
- command = new AddCommand(args.Skip(1).ToArray());
- break;
- case VersionCommand.COMMAND_NAME:
- command = new VersionCommand(args.Skip(1).ToArray());
- break;
- case "--help":
- case "--h":
- case "help":
- PrintUsageHeader();
-
- if (args.Length > 1)
- PrintUsage(args[1]);
- else
- PrintUsage();
- break;
- default:
- Console.Error.WriteLine($"Unknown command {args[0]}");
- PrintUsage();
- Environment.Exit(-1);
- break;
- }
-
- if (command != null)
- {
- var success = command.ExecuteAsync().Result;
- if (!success)
- {
- Environment.Exit(-1);
- }
- }
- }
-
- private static void PrintUsageHeader()
- {
- var sb = new StringBuilder("Electron.NET Tools");
- var version = Version;
- if (!string.IsNullOrEmpty(version))
- {
- sb.Append($" ({version})");
- }
- Console.WriteLine(sb.ToString());
- Console.WriteLine("Project Home: https://github.com/ElectronNET/Electron.NET");
- Console.WriteLine("\t");
- }
-
- private static void PrintUsage()
- {
- const int NAME_WIDTH = 23;
- Console.WriteLine("\t");
- Console.WriteLine("Commands to start the Electron Application:");
- Console.WriteLine("\t");
- Console.WriteLine($"\t{StartElectronCommand.COMMAND_NAME.PadRight(NAME_WIDTH)} {StartElectronCommand.COMMAND_DESCRIPTION}");
-
- Console.WriteLine("\t");
- Console.WriteLine("Command to build the Electron Application:");
- Console.WriteLine("\t");
- Console.WriteLine($"\t{BuildCommand.COMMAND_NAME.PadRight(NAME_WIDTH)} {BuildCommand.COMMAND_DESCRIPTION}");
-
- Console.WriteLine("\t");
- Console.WriteLine("Command to init the Electron Application:");
- Console.WriteLine("\t");
- Console.WriteLine($"\t{InitCommand.COMMAND_NAME.PadRight(NAME_WIDTH)} {InitCommand.COMMAND_DESCRIPTION}");
-
- Console.WriteLine("\t");
- Console.WriteLine("Command to add a custom npm packages to the Electron Application:");
- Console.WriteLine("\t");
- Console.WriteLine($"\t{AddCommand.COMMAND_NAME.PadRight(NAME_WIDTH)} {AddCommand.COMMAND_DESCRIPTION}");
-
- Console.WriteLine("\t");
- Console.WriteLine("Commands to see the current ElectronNET version number:");
- Console.WriteLine("\t");
- Console.WriteLine($"\t{VersionCommand.COMMAND_NAME.PadRight(NAME_WIDTH)} {VersionCommand.COMMAND_DESCRIPTION}");
-
- Console.WriteLine("\t");
- Console.WriteLine("\t");
- Console.WriteLine("To get help on individual commands execute:");
- Console.WriteLine("\tdotnet electronize help ");
- }
-
- private static void PrintUsage(string command)
- {
- switch (command)
- {
- case StartElectronCommand.COMMAND_NAME:
- PrintUsage(StartElectronCommand.COMMAND_NAME, StartElectronCommand.COMMAND_DESCRIPTION, StartElectronCommand.CommandOptions, StartElectronCommand.COMMAND_ARGUMENTS);
- break;
- case BuildCommand.COMMAND_NAME:
- PrintUsage(BuildCommand.COMMAND_NAME, BuildCommand.COMMAND_DESCRIPTION, BuildCommand.CommandOptions, BuildCommand.COMMAND_ARGUMENTS);
- break;
- case InitCommand.COMMAND_NAME:
- PrintUsage(InitCommand.COMMAND_NAME, InitCommand.COMMAND_DESCRIPTION, InitCommand.CommandOptions, InitCommand.COMMAND_ARGUMENTS);
- break;
- case AddCommand.COMMAND_NAME:
- PrintUsage(AddCommand.COMMAND_NAME, AddCommand.COMMAND_DESCRIPTION, AddCommand.CommandOptions, AddCommand.COMMAND_ARGUMENTS);
- break;
- case VersionCommand.COMMAND_NAME:
- PrintUsage(VersionCommand.COMMAND_NAME, VersionCommand.COMMAND_DESCRIPTION, VersionCommand.CommandOptions, VersionCommand.COMMAND_ARGUMENTS);
- break;
- default:
- Console.Error.WriteLine($"Unknown command {command}");
- PrintUsage();
- break;
- }
- }
-
- private static void PrintUsage(string command, string description, IList options, string arguments)
- {
- const int INDENT = 3;
-
- Console.WriteLine($"{command}: ");
- Console.WriteLine($"{new string(' ', INDENT)}{description}");
- Console.WriteLine("\t");
-
-
- if (!string.IsNullOrEmpty(arguments))
- {
- Console.WriteLine($"{new string(' ', INDENT)}dotnet electronize {command} [arguments] [options]");
- Console.WriteLine($"{new string(' ', INDENT)}Arguments:");
- Console.WriteLine($"{new string(' ', INDENT * 2)}{arguments}");
- }
- else
- {
- Console.WriteLine($"{new string(' ', INDENT)}dotnet electronize {command} [options]");
- }
-
- const int SWITCH_COLUMN_WIDTH = 40;
-
- Console.WriteLine($"{new string(' ', INDENT)}Options:");
- foreach (var option in options)
- {
- StringBuilder stringBuilder = new StringBuilder();
- if (option.ShortSwitch != null)
- {
- stringBuilder.Append($"{option.ShortSwitch.PadRight(6)} | ");
- }
-
- stringBuilder.Append($"{option.Switch}");
- if (stringBuilder.Length < SWITCH_COLUMN_WIDTH)
- {
- stringBuilder.Append(new string(' ', SWITCH_COLUMN_WIDTH - stringBuilder.Length));
- }
-
- stringBuilder.Append(option.Description);
-
-
- Console.WriteLine($"{new string(' ', INDENT * 2)}{stringBuilder.ToString()}");
- }
- }
-
- private static string Version
- {
- get
- {
- AssemblyInformationalVersionAttribute attribute = null;
- try
- {
- attribute = Assembly.GetEntryAssembly().GetCustomAttribute();
- }
- catch (AmbiguousMatchException)
- {
- // Catch exception and continue if multiple attributes are found.
- }
- return attribute?.InformationalVersion;
- }
- }
- }
-}
diff --git a/src/ElectronNET.CLI/Properties/launchSettings.json b/src/ElectronNET.CLI/Properties/launchSettings.json
deleted file mode 100644
index 5adf9cd..0000000
--- a/src/ElectronNET.CLI/Properties/launchSettings.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "profiles": {
- "ElectronNET.CLI": {
- "commandName": "Project",
- "commandLineArgs": "start /project-path \"$(SolutionDir)ElectronNET.WebApp\" /watch"
- }
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/SimpleCommandLineParser.cs b/src/ElectronNET.CLI/SimpleCommandLineParser.cs
deleted file mode 100644
index 9da5d73..0000000
--- a/src/ElectronNET.CLI/SimpleCommandLineParser.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-ο»Ώusing System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace ElectronNET.CLI
-{
- public class SimpleCommandLineParser
- {
- public SimpleCommandLineParser()
- {
- Arguments = new Dictionary();
- }
- public IDictionary Arguments { get; private set; }
- public void Parse(string[] args)
- {
- var currentName = "";
- var values = new List();
- foreach (var arg in args)
- {
- if (arg.StartsWith("/"))
- {
- if (currentName != "")
- Arguments[currentName] = values.ToArray();
- values.Clear();
- currentName = arg.Substring(1);
- }
- else if (currentName == "")
- Arguments[arg] = new string[0];
- else
- values.Add(arg);
- }
- if (currentName != "")
- Arguments[currentName] = values.ToArray();
-
- Console.ForegroundColor = ConsoleColor.Green;
- Console.WriteLine($"Arguments: \n\t{string.Join("\n\t",Arguments.Keys.Select(i => $"{i} = {string.Join(" ", Arguments[i])}"))}");
- Console.ResetColor();
- }
- public bool Contains(string name)
- {
- return Arguments.ContainsKey(name);
- }
-
- internal bool TryGet(string key, out string[] value)
- {
- value = null;
- if (!Contains(key)) {
- return false;
- }
- value = Arguments[key];
- return true;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/devCleanup.cmd b/src/ElectronNET.CLI/devCleanup.cmd
deleted file mode 100644
index 669e3af..0000000
--- a/src/ElectronNET.CLI/devCleanup.cmd
+++ /dev/null
@@ -1,3 +0,0 @@
-rd /s /q %userprofile%\.nuget\packages\.tools\electronnet.cli
-rd /s /q %userprofile%\.nuget\packages\electronnet.cli
-del ..\artifacts\ElectronNET.CLI.1.0.0.nupkg
\ No newline at end of file
diff --git a/src/ElectronNET.CLI/devCleanup.sh b/src/ElectronNET.CLI/devCleanup.sh
deleted file mode 100644
index b8b7e1f..0000000
--- a/src/ElectronNET.CLI/devCleanup.sh
+++ /dev/null
@@ -1 +0,0 @@
-rm -rf ~/.nuget/packages/electronnet.cli
\ No newline at end of file
diff --git a/src/ElectronNET.ConsoleApp/Assets/electron.ico b/src/ElectronNET.ConsoleApp/Assets/electron.ico
new file mode 100644
index 0000000..3a10449
Binary files /dev/null and b/src/ElectronNET.ConsoleApp/Assets/electron.ico differ
diff --git a/src/ElectronNET.ConsoleApp/Assets/electron_32x32.png b/src/ElectronNET.ConsoleApp/Assets/electron_32x32.png
new file mode 100644
index 0000000..125dde6
Binary files /dev/null and b/src/ElectronNET.ConsoleApp/Assets/electron_32x32.png differ
diff --git a/src/ElectronNET.ConsoleApp/ElectronNET.ConsoleApp.csproj b/src/ElectronNET.ConsoleApp/ElectronNET.ConsoleApp.csproj
new file mode 100644
index 0000000..5c58a31
--- /dev/null
+++ b/src/ElectronNET.ConsoleApp/ElectronNET.ConsoleApp.csproj
@@ -0,0 +1,77 @@
+
+
+