├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── Directory.Build.props
├── Directory.Build.targets
├── Icon.png
├── LICENSE.md
├── NuGet.config
├── README.md
├── appveyor.yml
├── build
└── Targets
│ ├── AvaloniaDiagnostics.props
│ ├── Import.props
│ ├── Import.targets
│ ├── PackageProperties.props
│ ├── PackageProperties.targets
│ ├── PackageVersions.targets
│ └── RepoLayout.props
├── dotnet-properties.sln
├── install.bat
├── install.sh
└── src
└── dotnet-properties
├── App.xaml
├── App.xaml.cs
├── DataAnnotations
└── NuGetPackageLicenseExpressionAttribute.cs
├── Dialogs
├── Models
│ └── UnsavedChangesDialogResult.cs
├── ViewModels
│ └── UnsavedChangesDialogViewModel.cs
└── Views
│ ├── UnsavedChangesDialog.xaml
│ └── UnsavedChangesDialog.xaml.cs
├── Pages
├── Models
│ ├── DotNetFrameworkTfm.cs
│ ├── OutputType.cs
│ ├── PlatformTarget.cs
│ ├── RunPostBuildEvent.cs
│ └── TargetFramework.cs
├── ViewModels
│ ├── ApplicationPageViewModel.cs
│ ├── BuildEventsPageViewModel.cs
│ ├── BuildPageViewModel.cs
│ ├── PackagePageViewModel.cs
│ ├── PropertyPageViewModel.cs
│ └── SigningPageViewModel.cs
└── Views
│ ├── ApplicationPage.xaml
│ ├── ApplicationPage.xaml.cs
│ ├── BuildEventsPage.xaml
│ ├── BuildEventsPage.xaml.cs
│ ├── BuildPage.xaml
│ ├── BuildPage.xaml.cs
│ ├── PackagePage.xaml
│ ├── PackagePage.xaml.cs
│ ├── SigningPage.xaml
│ └── SigningPage.xaml.cs
├── PagesViewLocator.cs
├── Project
├── BuildEnvironment.cs
├── DotNetSdkPaths.cs
├── MSBuildProject.cs
└── MSBuildProperties.cs
├── Properties
└── launchSettings.json
├── Resources
├── Fonts
│ └── OpenSans
│ │ ├── OpenSans-Bold.ttf
│ │ ├── OpenSans-BoldItalic.ttf
│ │ ├── OpenSans-ExtraBold.ttf
│ │ ├── OpenSans-ExtraBoldItalic.ttf
│ │ ├── OpenSans-Italic.ttf
│ │ ├── OpenSans-Light.ttf
│ │ ├── OpenSans-LightItalic.ttf
│ │ ├── OpenSans-Regular.ttf
│ │ ├── OpenSans-SemiBold.ttf
│ │ └── OpenSans-SemiBoldItalic.ttf
├── Icon.ico
└── Icon.png
├── Services
├── DialogService.cs
├── DotNetInfo.cs
├── DotNetSdkResolver.cs
├── IDialogService.cs
├── IDotNetSdkResolver.cs
├── IMSBuildLoader.cs
├── IOpenFileDialogService.cs
├── IPropertyManager.cs
├── ITheme.cs
├── IThemeService.cs
├── MSBuildLoader.cs
├── OpenFileDialogService.cs
├── PropertyManager.cs
├── Theme.cs
└── ThemeService.cs
├── Styles
├── Accents
│ ├── BaseLightBlue.xaml
│ ├── CitrusThemeAccents.xaml
│ ├── DefaultThemeAccents.xaml
│ └── FluentThemeAccents.xaml
├── SideBar.xaml
└── Styles.xaml
├── ViewModels
└── MainWindowViewModel.cs
├── Views
├── MainWindow.xaml
└── MainWindow.xaml.cs
└── dotnet-properties.csproj
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 4
5 | indent_style = space
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | end_of_line = crlf
9 |
10 | [*.cs]
11 | csharp_style_conditional_delegate_call = true:warning
12 | csharp_style_expression_bodied_accessors = true:suggestion
13 | csharp_style_expression_bodied_constructors = false:warning
14 | csharp_style_expression_bodied_indexers = true:suggestion
15 | csharp_style_expression_bodied_methods = true:suggestion
16 | csharp_style_expression_bodied_operators = true:suggestion
17 | csharp_style_expression_bodied_properties = true:suggestion
18 | csharp_style_inlined_variable_declaration = true:warning
19 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
20 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
21 | csharp_style_throw_expression = true:warning
22 | csharp_style_var_elsewhere = true:suggestion
23 | csharp_style_var_for_built_in_types = false:none
24 | csharp_style_var_when_type_is_apparent = true:suggestion
25 |
26 | csharp_new_line_before_catch = true:warning
27 | csharp_new_line_before_else = true:warning
28 | csharp_new_line_before_finally = true:warning
29 | csharp_new_line_before_members_in_anonymous_types = true:warning
30 | csharp_new_line_before_members_in_object_initializers = true:warning
31 | #csharp_new_line_before_open_brace = all:warning
32 | csharp_new_line_between_query_expression_clauses = true:warning
33 |
34 | csharp_indent_case_contents = true:warning
35 | csharp_indent_labels = one_less_than_current:suggestion
36 | csharp_indent_switch_labels = true:warning
37 |
38 | csharp_preserve_single_line_blocks = true:suggestion
39 | csharp_preserve_single_line_statements = false:warning
40 |
41 | csharp_space_after_cast = false:warning
42 | csharp_space_after_keywords_in_control_flow_statements = true:warning
43 | csharp_space_between_method_call_parameter_list_parentheses = false:warning
44 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning
45 | csharp_space_between_parentheses = false:warning
46 |
47 | csharp_prefer_braces = true:warning
48 | csharp_prefer_simple_default_expression = false:none
49 |
50 | dotnet_sort_system_directives_first = true:suggestion
51 |
52 | dotnet_style_coalesce_expression = true:warning
53 | dotnet_style_collection_initializer = true:suggestion
54 | dotnet_style_explicit_tuple_names = true:suggestion
55 | dotnet_style_null_propagation = true:warning
56 | dotnet_style_object_initializer = true:suggestion
57 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
58 | dotnet_style_predefined_type_for_member_access = false:warning
59 | dotnet_style_qualification_for_event = false:warning
60 | dotnet_style_qualification_for_field = false:warning
61 | dotnet_style_qualification_for_method = false:warning
62 | dotnet_style_qualification_for_property = false:warning
63 |
64 | dotnet_naming_rule.camel_case_for_private_fields.severity = suggestion
65 | dotnet_naming_rule.camel_case_for_private_fields.symbols = private_fields
66 | dotnet_naming_rule.camel_case_for_private_fields.style = camel_case_underscore_style
67 |
68 | dotnet_naming_symbols.private_fields.applicable_kinds = field
69 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private
70 |
71 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
72 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
73 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: jp2masa
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # User-specific files
2 |
3 | .vs/
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Build results
10 |
11 | [Aa]rtifacts/
12 | [Bb]in/
13 | [Oo]bj/
14 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildThisFileDirectory)
5 |
6 |
7 |
8 | 8.0
9 | Enable
10 | CA1303;CA1812;CA1822;$(NoWarn)
11 |
12 |
13 |
14 |
15 | False
16 |
17 |
18 |
19 | 0.3.0
20 | -build.$(APPVEYOR_BUILD_NUMBER)+$(APPVEYOR_REPO_COMMIT.Substring(0, 7))
21 | -localbuild$([System.DateTime]::Now.ToString("yyyyMMddHHmmss"))
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/Icon.png
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright © jp2masa
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the “Software”), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://ci.appveyor.com/project/jp2masa/dotnet-properties/branch/master)
2 | [](https://www.nuget.org/packages/dotnet-properties/)
3 | [](https://www.myget.org/feed/jp2masa/package/nuget/dotnet-properties)
4 |
5 | # dotnet-properties
6 |
7 | 
8 |
9 | dotnet-properties is a .NET Core CLI extension which allows to edit project properties using a cross platform UI.
10 |
11 | ## Special Thanks
12 |
13 | ### Avalonia
14 |
15 | It's the UI framework used by this project and part of the UI is inspired on the control catalog sample.
16 |
17 | [
](https://github.com/AvaloniaUI/Avalonia)
18 |
19 | ### Buildalyzer
20 |
21 | A big part of the project loading code comes from Buildalyzer.
22 |
23 | [
](https://github.com/daveaglick/Buildalyzer)
24 |
25 | ### Citrus.Avalonia
26 |
27 | [Avalonia themes.](https://github.com/worldbeater/Citrus.Avalonia)
28 |
29 | ### linea
30 |
31 | The project logo is from [linea.io](http://linea.io).
32 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 0.3.0-build{build}
2 | image: Visual Studio 2019
3 |
4 | shallow_clone: true
5 | clone_folder: c:\dotnet-properties
6 |
7 | configuration:
8 | - Debug
9 | - Release
10 | platform: Any CPU
11 |
12 | matrix:
13 | fast_finish: true
14 |
15 | nuget:
16 | account_feed: false
17 | project_feed: true
18 | disable_publish_on_pr: true
19 |
20 | build_script:
21 | - cmd: dotnet build dotnet-properties.sln
22 |
23 | test: off
24 |
25 | artifacts:
26 | - path: 'artifacts\Debug\nupkg\*.nupkg'
27 | name: DebugNupkg
28 | - path: 'artifacts\Release\nupkg\*.nupkg'
29 | name: ReleaseNupkg
30 |
31 | deploy:
32 | #- provider: GitHub
33 | # auth_token:
34 | # secure: +kq8LAyp9cUJjEdoBhOCsldBLElEjcl8M+yF4Ml4bMNqaJLqvwG7nqODNBAn+kxR
35 | # artifact: ReleaseNupkg
36 | # on:
37 | # branch: master
38 | # configuration: Release
39 | # appveyor_repo_tag: true
40 | - provider: NuGet
41 | api_key:
42 | secure: 5yR2VWgBBYQvQu+F6p367NXT2SYI+vzy29YEI+Rs+T9Zq2www5kMgMV/vSiQQfgP
43 | artifact: ReleaseNupkg
44 | on:
45 | branch: master
46 | configuration: Release
47 | appveyor_repo_tag: true
48 | - provider: NuGet
49 | server: https://www.myget.org/F/jp2masa/api/v2/package
50 | artifact: DebugNupkg
51 | api_key:
52 | secure: puOcEbngEmaVMEnUL20u4mzATgvoyaTPRWGGwE98as1+8KGY3ypOKzt5OV63duwI
53 | on:
54 | branch: master
55 | configuration: Debug
56 |
--------------------------------------------------------------------------------
/build/Targets/AvaloniaDiagnostics.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | AVALONIA_DIAGNOSTICS;$(DefineConstants)
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build/Targets/Import.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/build/Targets/Import.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/build/Targets/PackageProperties.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(PackageVersion)$(PackageVersionSuffix)
5 |
6 |
7 |
8 | dotnet-properties is a .NET Core global tool which allows to edit project properties using a cross-platform UI.
9 |
10 |
11 |
12 | jp2masa
13 | Copyright © $([System.DateTime]::Now.Year) jp2masa
14 | Icon.png
15 | http://github.com/jp2masa/dotnet-properties
16 | MIT
17 | True
18 | dotnet properties core csharp vb visual basic fsharp csproj vbproj fsproj proj
19 | git
20 | https://github.com/jp2masa/dotnet-properties
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/build/Targets/PackageProperties.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(PackageDescription)
5 |
6 | $(BaseDescription)
7 | $(BaseDescription)
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/build/Targets/PackageVersions.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 0.10.0
5 | 1.68.0.2
6 | 1.4.3
7 | 16.9.0
8 | 5.0.3
9 | 5.9.0
10 | 13.2.2
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/build/Targets/RepoLayout.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | $(Platform)
7 |
8 |
9 |
10 | $(RepoRoot)artifacts\
11 | $(ArtifactsDir)$(Configuration)\nupkg\
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/dotnet-properties.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28407.52
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-properties", "src\dotnet-properties\dotnet-properties.csproj", "{4B0E3D01-F0C9-42EF-9556-32C96F59E353}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{89EF6D8C-C8F7-4374-B424-C88013F4791C}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | Directory.Build.props = Directory.Build.props
12 | Directory.Build.targets = Directory.Build.targets
13 | NuGet.config = NuGet.config
14 | EndProjectSection
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{233FDDCD-5706-4E83-AF15-41A0CA9F0E42}"
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{9C9397C0-677A-481E-9A3B-5C0152ECF6E1}"
19 | ProjectSection(SolutionItems) = preProject
20 | build\Targets\AvaloniaDiagnostics.props = build\Targets\AvaloniaDiagnostics.props
21 | build\Targets\Import.props = build\Targets\Import.props
22 | build\Targets\Import.targets = build\Targets\Import.targets
23 | build\Targets\PackageProperties.props = build\Targets\PackageProperties.props
24 | build\Targets\PackageProperties.targets = build\Targets\PackageProperties.targets
25 | build\Targets\PackageVersions.targets = build\Targets\PackageVersions.targets
26 | build\Targets\RepoLayout.props = build\Targets\RepoLayout.props
27 | EndProjectSection
28 | EndProject
29 | Global
30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
31 | Debug|Any CPU = Debug|Any CPU
32 | Release|Any CPU = Release|Any CPU
33 | EndGlobalSection
34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
35 | {4B0E3D01-F0C9-42EF-9556-32C96F59E353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {4B0E3D01-F0C9-42EF-9556-32C96F59E353}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {4B0E3D01-F0C9-42EF-9556-32C96F59E353}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {4B0E3D01-F0C9-42EF-9556-32C96F59E353}.Release|Any CPU.Build.0 = Release|Any CPU
39 | EndGlobalSection
40 | GlobalSection(SolutionProperties) = preSolution
41 | HideSolutionNode = FALSE
42 | EndGlobalSection
43 | GlobalSection(NestedProjects) = preSolution
44 | {233FDDCD-5706-4E83-AF15-41A0CA9F0E42} = {89EF6D8C-C8F7-4374-B424-C88013F4791C}
45 | {9C9397C0-677A-481E-9A3B-5C0152ECF6E1} = {233FDDCD-5706-4E83-AF15-41A0CA9F0E42}
46 | EndGlobalSection
47 | GlobalSection(ExtensibilityGlobals) = postSolution
48 | SolutionGuid = {32FBE198-F721-4B81-B08E-A834940EC363}
49 | EndGlobalSection
50 | EndGlobal
51 |
--------------------------------------------------------------------------------
/install.bat:
--------------------------------------------------------------------------------
1 | dotnet tool uninstall --global dotnet-properties
2 |
3 | dotnet build dotnet-properties.sln -c Release
4 | dotnet tool install --global --add-source artifacts\Release\nupkg\ --version 0.3.0-* dotnet-properties
5 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | dotnet tool uninstall --global dotnet-properties
4 |
5 | dotnet build dotnet-properties.sln -c Release
6 | dotnet tool install --global --add-source artifacts/Release/nupkg/ --version 0.3.0-* dotnet-properties
7 |
--------------------------------------------------------------------------------
/src/dotnet-properties/App.xaml:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/dotnet-properties/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.IO;
5 | using System.Runtime.Loader;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | using Avalonia;
10 | using Avalonia.Controls;
11 | using Avalonia.Controls.ApplicationLifetimes;
12 | using Avalonia.Controls.Platform;
13 | using Avalonia.Markup.Xaml;
14 | using Avalonia.ReactiveUI;
15 | using Avalonia.Threading;
16 |
17 | using DotNet.Properties.Dialogs.ViewModels;
18 | using DotNet.Properties.Dialogs.Views;
19 | using DotNet.Properties.Services;
20 | using DotNet.Properties.ViewModels;
21 | using DotNet.Properties.Views;
22 |
23 | namespace DotNet.Properties
24 | {
25 | internal sealed class App : Application
26 | {
27 | private static readonly List OpenProjectFileDialogFilters = new List()
28 | {
29 | new FileDialogFilter() { Name = "All Project Files", Extensions = new List() { "*proj" } },
30 | new FileDialogFilter() { Name = "C# Project Files", Extensions = new List() { "csproj" } },
31 | new FileDialogFilter() { Name = "Visual Basic Project Files", Extensions = new List() { "vbproj" } },
32 | new FileDialogFilter() { Name = "F# Project Files", Extensions = new List() { "fsproj" } },
33 | };
34 |
35 | public override void Initialize() => AvaloniaXamlLoader.Load(this);
36 |
37 | public override void OnFrameworkInitializationCompleted()
38 | {
39 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
40 | {
41 | var mainWindow = new MainWindow();
42 | var mainWindowViewModel = BuildMainWindowDataContext(mainWindow);
43 |
44 | if (mainWindowViewModel != null)
45 | {
46 | mainWindow.DataContext = mainWindowViewModel;
47 | desktop.MainWindow = mainWindow;
48 | }
49 | }
50 |
51 | base.OnFrameworkInitializationCompleted();
52 | }
53 |
54 | private static int Main(string[] args) =>
55 | BuildAvaloniaApp().StartWithClassicDesktopLifetime(args, ShutdownMode.OnMainWindowClose);
56 |
57 | private MainWindowViewModel? BuildMainWindowDataContext(MainWindow mainWindow)
58 | {
59 | var projFiles = Directory.GetFiles(Environment.CurrentDirectory, "*.*proj");
60 |
61 | var projectPath = projFiles.Length == 1 ? projFiles[0] : OpenProjectFile();
62 | var projectDirectory = Path.GetDirectoryName(projectPath);
63 |
64 | // TODO: replace with File.Exists and Directory.Exists when nullable annotations are correct
65 | if (!FileExists(projectPath) || !DirectoryExists(projectDirectory))
66 | {
67 | return null;
68 | }
69 |
70 | IDotNetSdkResolver dotnetSdkResolver = new DotNetSdkResolver();
71 |
72 | if (!dotnetSdkResolver.TryResolveSdkPath(projectDirectory, out var dotnetSdkPath))
73 | {
74 | return null;
75 | }
76 |
77 | var dotnetSdkPaths = new DotNetSdkPaths(dotnetSdkPath);
78 |
79 | IMSBuildLoader msBuildLoader = new MSBuildLoader(dotnetSdkPaths);
80 |
81 | AssemblyLoadContext.Default.Resolving += (context, name) =>
82 | {
83 | if (name.Name != null && msBuildLoader.TryResolveMSBuildAssembly(context, name.Name, out var assembly))
84 | {
85 | return assembly;
86 | }
87 |
88 | return null;
89 | };
90 |
91 | var msBuildProject = new MSBuildProject(dotnetSdkPaths, projectPath);
92 |
93 | return new MainWindowViewModel(
94 | msBuildProject,
95 | new DialogService(NewUnsavedChangesDialog, mainWindow),
96 | new OpenFileDialogService(mainWindow),
97 | new ThemeService(this));
98 | }
99 |
100 | private static string? OpenProjectFile()
101 | {
102 | Task task;
103 |
104 | using (var source = new CancellationTokenSource())
105 | {
106 | task = OpenProjectFileAsync();
107 | task.ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext());
108 |
109 | Dispatcher.UIThread.MainLoop(source.Token);
110 | }
111 |
112 | return task.Result;
113 | }
114 |
115 | private static async Task OpenProjectFileAsync()
116 | {
117 | var openFileDialog = new OpenFileDialog
118 | {
119 | AllowMultiple = false,
120 | Filters = OpenProjectFileDialogFilters
121 | };
122 |
123 | var result = await ShowOpenFileDialogAsync(openFileDialog).ConfigureAwait(false);
124 |
125 | if (result != null && result.Length > 0)
126 | {
127 | return result[0];
128 | }
129 |
130 | return null;
131 | }
132 |
133 | private static AppBuilder BuildAvaloniaApp() =>
134 | AppBuilder.Configure()
135 | .UsePlatformDetect()
136 | #if DEBUG
137 | .LogToTrace()
138 | #endif
139 | .UseReactiveUI();
140 |
141 | private static UnsavedChangesDialog NewUnsavedChangesDialog() => new UnsavedChangesDialog();
142 |
143 | private static bool DirectoryExists([NotNullWhen(true)] string? path) => Directory.Exists(path);
144 |
145 | private static bool FileExists([NotNullWhen(true)] string? path) => File.Exists(path);
146 |
147 | private static Task ShowOpenFileDialogAsync(FileDialog dialog, Window? parent = null)
148 | {
149 | var systemDialogImpl = AvaloniaLocator.Current.GetService();
150 | return systemDialogImpl.ShowFileDialogAsync(dialog, parent);
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/dotnet-properties/DataAnnotations/NuGetPackageLicenseExpressionAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | using NuGet.Packaging.Licenses;
5 |
6 | namespace DotNet.Properties.DataAnnotations
7 | {
8 | internal sealed class NuGetPackageLicenseExpressionAttribute : ValidationAttribute
9 | {
10 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
11 | {
12 | if (!(value is string str))
13 | {
14 | throw new InvalidOperationException();
15 | }
16 |
17 | try
18 | {
19 | var expression = NuGetLicenseExpression.Parse(str);
20 |
21 | if (expression.HasOnlyStandardIdentifiers())
22 | {
23 | return ValidationResult.Success;
24 | }
25 |
26 | return new ValidationResult("Unknown license identifier(s)!");
27 | }
28 | catch (NuGetLicenseExpressionParsingException e)
29 | {
30 | return new ValidationResult(e.Message);
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Dialogs/Models/UnsavedChangesDialogResult.cs:
--------------------------------------------------------------------------------
1 | namespace DotNet.Properties.Dialogs.Models
2 | {
3 | internal enum UnsavedChangesDialogResult
4 | {
5 | Yes,
6 | No,
7 | Cancel
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Dialogs/ViewModels/UnsavedChangesDialogViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Input;
2 |
3 | using Avalonia.Controls;
4 |
5 | using ReactiveUI;
6 |
7 | using DotNet.Properties.Dialogs.Models;
8 |
9 | namespace DotNet.Properties.Dialogs.ViewModels
10 | {
11 | internal sealed class UnsavedChangesDialogViewModel : ReactiveObject
12 | {
13 | private UnsavedChangesDialogResult _dialogResult = UnsavedChangesDialogResult.Cancel;
14 |
15 | public UnsavedChangesDialogViewModel()
16 | {
17 | YesCommand = ReactiveCommand.Create(window => Close(UnsavedChangesDialogResult.Yes, window));
18 | NoCommand = ReactiveCommand.Create(window => Close(UnsavedChangesDialogResult.No, window));
19 | CancelCommand = ReactiveCommand.Create(window => Close(UnsavedChangesDialogResult.Cancel, window));
20 | }
21 |
22 | public ICommand YesCommand { get; }
23 |
24 | public ICommand NoCommand { get; }
25 |
26 | public ICommand CancelCommand { get; }
27 |
28 | public UnsavedChangesDialogResult DialogResult
29 | {
30 | get => _dialogResult;
31 | set => this.RaiseAndSetIfChanged(ref _dialogResult, value);
32 | }
33 |
34 | private void Close(UnsavedChangesDialogResult dialogResult, Window window)
35 | {
36 | DialogResult = dialogResult;
37 | window.Close();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Dialogs/Views/UnsavedChangesDialog.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
14 |
15 |
16 |
17 |
20 |
21 |
24 |
25 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Dialogs/Views/UnsavedChangesDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Markup.Xaml;
3 | using Avalonia.ReactiveUI;
4 |
5 | using DotNet.Properties.Dialogs.ViewModels;
6 |
7 | namespace DotNet.Properties.Dialogs.Views
8 | {
9 | internal sealed class UnsavedChangesDialog : ReactiveWindow
10 | {
11 | public UnsavedChangesDialog()
12 | {
13 | InitializeComponent();
14 | #if AVALONIA_DIAGNOSTICS
15 | this.AttachDevTools();
16 | #endif
17 | }
18 |
19 | private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Models/DotNetFrameworkTfm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DotNet.Properties.Pages.Models
4 | {
5 | internal sealed class DotNetFrameworkTfm
6 | {
7 | public DotNetFrameworkTfm(string shortName, Version version)
8 | {
9 | ShortName = shortName;
10 | Version = version;
11 | }
12 |
13 | public string ShortName { get; }
14 |
15 | public Version Version { get; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Models/OutputType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DotNet.Properties.Pages.Models
4 | {
5 | internal sealed class OutputType : IEquatable
6 | {
7 | public OutputType(string value, string displayName)
8 | {
9 | Value = value;
10 | DisplayName = displayName;
11 | }
12 |
13 | public string Value { get; }
14 |
15 | public string DisplayName { get; }
16 |
17 | public bool Equals(OutputType? other) => !(other is null) && DisplayName.Equals(other.DisplayName, StringComparison.Ordinal);
18 |
19 | public override bool Equals(object? obj) => obj is OutputType other && Equals(other);
20 |
21 | public override int GetHashCode() => DisplayName.GetHashCode(StringComparison.Ordinal);
22 |
23 | public override string ToString() => DisplayName;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Models/PlatformTarget.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DotNet.Properties.Pages.Models
4 | {
5 | internal sealed class PlatformTarget : IEquatable
6 | {
7 | public PlatformTarget(string value, string displayName)
8 | {
9 | Value = value;
10 | DisplayName = displayName;
11 | }
12 |
13 | public string Value { get; }
14 |
15 | public string DisplayName { get; }
16 |
17 | public bool Equals(PlatformTarget? other) => !(other is null) && DisplayName.Equals(other.DisplayName, StringComparison.Ordinal);
18 |
19 | public override bool Equals(object? obj) => obj is PlatformTarget other && Equals(other);
20 |
21 | public override int GetHashCode() => DisplayName.GetHashCode(StringComparison.Ordinal);
22 |
23 | public override string ToString() => DisplayName;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Models/RunPostBuildEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DotNet.Properties.Pages.Models
4 | {
5 | internal sealed class RunPostBuildEvent : IEquatable
6 | {
7 | public RunPostBuildEvent(string value, string displayName)
8 | {
9 | Value = value;
10 | DisplayName = displayName;
11 | }
12 |
13 | public string Value { get; }
14 |
15 | public string DisplayName { get; }
16 |
17 | public bool Equals(RunPostBuildEvent? other) => !(other is null) && DisplayName.Equals(other.DisplayName, StringComparison.Ordinal);
18 |
19 | public override bool Equals(object? obj) => obj is RunPostBuildEvent other && Equals(other);
20 |
21 | public override int GetHashCode() => DisplayName.GetHashCode(StringComparison.Ordinal);
22 |
23 | public override string ToString() => DisplayName;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Models/TargetFramework.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using NuGet.Frameworks;
4 |
5 | namespace DotNet.Properties.Pages.Models
6 | {
7 | internal sealed class TargetFramework : IEquatable
8 | {
9 | public TargetFramework(NuGetFramework framework, string displayName)
10 | {
11 | NuGetFramework = framework;
12 | DisplayName = displayName;
13 | ShortName = framework.GetShortFolderName();
14 | }
15 |
16 | public TargetFramework(string framework, string displayName)
17 | : this(NuGetFramework.Parse(framework), displayName)
18 | {
19 | }
20 |
21 | public NuGetFramework NuGetFramework { get; }
22 |
23 | public string DisplayName { get; }
24 |
25 | public string ShortName { get; }
26 |
27 | public override string ToString() => DisplayName;
28 |
29 | public bool Equals(TargetFramework? other) => !(other is null) && NuGetFramework.Equals(other.NuGetFramework);
30 |
31 | public override bool Equals(object? obj) => obj is TargetFramework other && Equals(other);
32 |
33 | public override int GetHashCode() => NuGetFramework.GetHashCode();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/ViewModels/ApplicationPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using System.Linq;
5 |
6 | using DotNet.Properties.Pages.Models;
7 | using DotNet.Properties.Services;
8 |
9 | namespace DotNet.Properties.Pages.ViewModels
10 | {
11 | internal sealed class ApplicationPageViewModel : PropertyPageViewModel
12 | {
13 | private static class Property
14 | {
15 | public const string AssemblyName = nameof(AssemblyName);
16 | public const string RootNamespace = nameof(RootNamespace);
17 | public const string TargetFramework = nameof(TargetFramework);
18 | public const string OutputType = nameof(OutputType);
19 | }
20 |
21 | private readonly Lazy> _supportedTargetFrameworks;
22 |
23 | private static readonly ImmutableArray _outputTypes =
24 | ImmutableArray.Create(
25 | new OutputType("Exe", "Console Application"),
26 | new OutputType("WinExe", "Windows Application"),
27 | new OutputType("Library", "Class Library"));
28 |
29 | public ApplicationPageViewModel(IPropertyManager propertyManager)
30 | : base(propertyManager)
31 | {
32 | _supportedTargetFrameworks = new Lazy>(GetAvailableFrameworks);
33 | }
34 |
35 | public string? AssemblyName
36 | {
37 | get => GetStringProperty(Property.AssemblyName);
38 | set => SetStringProperty(Property.AssemblyName, value);
39 | }
40 |
41 | public string? DefaultNamespace
42 | {
43 | get => GetStringProperty(Property.RootNamespace);
44 | set => SetStringProperty(Property.RootNamespace, value);
45 | }
46 |
47 | public IEnumerable SupportedTargetFrameworks => _supportedTargetFrameworks.Value;
48 |
49 | public TargetFramework TargetFramework
50 | {
51 | get => _supportedTargetFrameworks.Value.Where(f => f.ShortName == GetStringProperty(Property.TargetFramework)).Single();
52 | set => SetStringProperty(Property.TargetFramework, value.ShortName);
53 | }
54 |
55 | public IEnumerable SupportedOutputTypes => _outputTypes;
56 |
57 | public OutputType OutputType
58 | {
59 | get => _outputTypes.Where(t => t.Value == GetStringProperty(Property.OutputType)).Single();
60 | set => SetStringProperty(Property.OutputType, value.Value);
61 | }
62 |
63 | private ImmutableArray GetAvailableFrameworks()
64 | {
65 | var supportedTargetFrameworkItems = GetItems("SupportedTargetFramework");
66 |
67 | var builder = ImmutableArray.CreateBuilder(supportedTargetFrameworkItems.Count);
68 |
69 | builder.AddRange(
70 | from item in supportedTargetFrameworkItems
71 | select new TargetFramework(item.EvaluatedInclude, item.GetMetadata("DisplayName").EvaluatedValue));
72 |
73 | return builder.ToImmutable();
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/ViewModels/BuildEventsPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | using DotNet.Properties.Pages.Models;
5 | using DotNet.Properties.Services;
6 |
7 | namespace DotNet.Properties.Pages.ViewModels
8 | {
9 | internal sealed class BuildEventsPageViewModel : PropertyPageViewModel
10 | {
11 | private static class Property
12 | {
13 | public const string PreBuildEvent = nameof(PreBuildEvent);
14 | public const string PostBuildEvent = nameof(PostBuildEvent);
15 | public const string RunPostBuildEvent = nameof(RunPostBuildEvent);
16 | }
17 |
18 | private static readonly IEnumerable _runPostBuildEvent = new List()
19 | {
20 | new RunPostBuildEvent("OnBuildSuccess", "On Build Success"),
21 | new RunPostBuildEvent("OnOutputUpdated", "On Output Updated"),
22 | new RunPostBuildEvent("Always", "Always")
23 | };
24 |
25 | public BuildEventsPageViewModel(IPropertyManager propertyManager)
26 | : base(propertyManager)
27 | {
28 | }
29 |
30 | public string? PreBuildEvent
31 | {
32 | get => GetStringProperty(Property.PreBuildEvent);
33 | set => SetStringProperty(Property.PreBuildEvent, value);
34 | }
35 |
36 | public string? PostBuildEvent
37 | {
38 | get => GetStringProperty(Property.PostBuildEvent);
39 | set => SetStringProperty(Property.PostBuildEvent, value);
40 | }
41 |
42 | public IEnumerable SupportedRunPostBuildEvent => _runPostBuildEvent.Distinct();
43 |
44 | public RunPostBuildEvent RunPostBuildEvent
45 | {
46 | get => _runPostBuildEvent.Where(
47 | r => r.Value == GetStringProperty(Property.RunPostBuildEvent)).SingleOrDefault() ?? _runPostBuildEvent.ElementAt(0);
48 | set => SetStringProperty(Property.RunPostBuildEvent, value.Value);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/ViewModels/BuildPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.Immutable;
3 | using System.Linq;
4 |
5 | using ReactiveUI;
6 |
7 | using DotNet.Properties.Pages.Models;
8 | using DotNet.Properties.Services;
9 |
10 | namespace DotNet.Properties.Pages.ViewModels
11 | {
12 | internal sealed class BuildPageViewModel : PropertyPageViewModel
13 | {
14 | private static class Property
15 | {
16 | // General
17 | public const string DefineConstants = nameof(DefineConstants);
18 | public const string PlatformTarget = nameof(PlatformTarget);
19 | public const string Prefer32Bit = nameof(Prefer32Bit);
20 | public const string AllowUnsafeBlocks = nameof(AllowUnsafeBlocks);
21 | public const string Optimize = nameof(Optimize);
22 | // Errors and warnings
23 | public const string WarningLevel = nameof(WarningLevel);
24 | public const string NoWarn = nameof(NoWarn);
25 | public const string TreatWarningsAsErrors = nameof(TreatWarningsAsErrors);
26 | public const string WarningsAsErrors = nameof(WarningsAsErrors);
27 | // Output
28 | public const string OutputPath = nameof(OutputPath);
29 | public const string AppendTargetFrameworkToOutputPath = nameof(AppendTargetFrameworkToOutputPath);
30 | public const string AppendRuntimeIdentifierToOutputPath = nameof(AppendRuntimeIdentifierToOutputPath);
31 | public const string GenerateDocumentationFile = nameof(GenerateDocumentationFile);
32 | public const string DocumentationFile = nameof(DocumentationFile);
33 | }
34 |
35 | private static readonly IEnumerable _platformTargets =
36 | ImmutableArray.Create(
37 | new PlatformTarget("AnyCPU", "Any CPU"),
38 | new PlatformTarget("x64", "x64"),
39 | new PlatformTarget("x86", "x86"),
40 | new PlatformTarget("ARM", "ARM"));
41 |
42 | private static readonly IEnumerable _warningLevels =
43 | ImmutableArray.Create("0", "1", "2", "3", "4");
44 |
45 | public BuildPageViewModel(IPropertyManager propertyManager)
46 | : base(propertyManager)
47 | {
48 | }
49 |
50 | public string? ConditionalCompilationSymbols
51 | {
52 | get => GetStringProperty(Property.DefineConstants);
53 | set => SetStringProperty(Property.DefineConstants, value);
54 | }
55 |
56 | public IEnumerable AvailablePlatformTargets => _platformTargets;
57 |
58 | public bool PlatformTargetEnabled { get; } = true;
59 |
60 | public PlatformTarget PlatformTarget
61 | {
62 | get => _platformTargets.Single(t => t.Value == (GetStringProperty(Property.PlatformTarget) ?? "AnyCPU"));
63 | set => SetStringProperty(Property.PlatformTarget, value.Value);
64 | }
65 |
66 | public bool Prefer32Bit
67 | {
68 | get => GetBooleanProperty(Property.Prefer32Bit);
69 | set => SetBooleanProperty(Property.Prefer32Bit, value);
70 | }
71 |
72 | public bool AllowUnsafeCode
73 | {
74 | get => GetBooleanProperty(Property.AllowUnsafeBlocks);
75 | set => SetBooleanProperty(Property.AllowUnsafeBlocks, value);
76 | }
77 |
78 | public bool OptimizeCode
79 | {
80 | get => GetBooleanProperty(Property.Optimize);
81 | set => SetBooleanProperty(Property.Optimize, value);
82 | }
83 |
84 | public IEnumerable AvailableWarningLevels => _warningLevels;
85 |
86 | public string? WarningLevel
87 | {
88 | get => AvailableWarningLevels.SingleOrDefault(x => x == GetStringProperty(Property.WarningLevel));
89 | set => SetStringProperty(Property.WarningLevel, value);
90 | }
91 |
92 | public string? NoWarn
93 | {
94 | get => GetStringProperty(Property.NoWarn);
95 | set => SetStringProperty(Property.NoWarn, value);
96 | }
97 |
98 | public bool TreatWarningsAsErrors
99 | {
100 | get => GetBooleanProperty(Property.TreatWarningsAsErrors);
101 | set => SetBooleanProperty(Property.TreatWarningsAsErrors, value);
102 | }
103 |
104 | public string? WarningsAsErrors
105 | {
106 | get => GetStringProperty(Property.WarningsAsErrors);
107 | set => SetStringProperty(Property.WarningsAsErrors, value);
108 | }
109 |
110 | public string? OutputPath
111 | {
112 | get => GetStringProperty(Property.OutputPath);
113 | set => SetStringProperty(Property.OutputPath, value);
114 | }
115 |
116 | public bool AppendTargetFrameworkToOutputPath
117 | {
118 | get => GetBooleanProperty(Property.AppendTargetFrameworkToOutputPath);
119 | set
120 | {
121 | SetBooleanProperty(Property.AppendTargetFrameworkToOutputPath, value);
122 | this.RaisePropertyChanged(nameof(OutputPath));
123 | }
124 | }
125 |
126 | public bool AppendRuntimeIdentifierToOutputPath
127 | {
128 | get => GetBooleanProperty(Property.AppendRuntimeIdentifierToOutputPath);
129 | set
130 | {
131 | SetBooleanProperty(Property.AppendRuntimeIdentifierToOutputPath, value);
132 | this.RaisePropertyChanged(nameof(OutputPath));
133 | }
134 | }
135 |
136 | public bool GenerateDocumentationFile
137 | {
138 | get => GetBooleanProperty(Property.GenerateDocumentationFile);
139 | set => SetBooleanProperty(Property.GenerateDocumentationFile, value);
140 | }
141 |
142 | public string? DocumentationFile
143 | {
144 | get => GetStringProperty(Property.DocumentationFile);
145 | set => SetStringProperty(Property.DocumentationFile, value);
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/ViewModels/PackagePageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 |
4 | using ReactiveUI;
5 |
6 | using DotNet.Properties.DataAnnotations;
7 | using DotNet.Properties.Services;
8 |
9 | namespace DotNet.Properties.Pages.ViewModels
10 | {
11 | internal sealed class PackagePageViewModel : PropertyPageViewModel
12 | {
13 | private static class Property
14 | {
15 | public const string GeneratePackageOnBuild = nameof(GeneratePackageOnBuild);
16 | public const string PackageId = nameof(PackageId);
17 | public const string Title = nameof(Title);
18 | public const string PackageVersion = nameof(PackageVersion);
19 | public const string Authors = nameof(Authors);
20 | public const string Company = nameof(Company);
21 | public const string PackageDescription = nameof(PackageDescription);
22 | public const string Copyright = nameof(Copyright);
23 | public const string PackageLicenseUrl = nameof(PackageLicenseUrl);
24 | public const string PackageLicenseFile = nameof(PackageLicenseFile);
25 | public const string PackageLicenseExpression = nameof(PackageLicenseExpression);
26 | public const string PackageRequireLicenseAcceptance = nameof(PackageRequireLicenseAcceptance);
27 | public const string PackageProjectUrl = nameof(PackageProjectUrl);
28 | public const string PackageIconUrl = nameof(PackageIconUrl);
29 | public const string PackageIcon = nameof(PackageIcon);
30 | public const string RepositoryUrl = nameof(RepositoryUrl);
31 | public const string RepositoryType = nameof(RepositoryType);
32 | public const string PackageTags = nameof(PackageTags);
33 | public const string PackageReleaseNotes = nameof(PackageReleaseNotes);
34 | }
35 |
36 | private bool? _isLicenseURL;
37 | private bool? _isLicenseFile;
38 | private bool? _isLicenseExpression;
39 |
40 | private string? _licenseURL;
41 | private string? _licenseFile;
42 | private string? _licenseExpression;
43 |
44 | private bool? _isIconUrl;
45 | private bool? _isIconFile;
46 |
47 | private string? _iconUrl;
48 | private string? _iconFile;
49 |
50 | public PackagePageViewModel(IPropertyManager propertyManager)
51 | : base(propertyManager)
52 | {
53 | }
54 |
55 | public bool GeneratePackageOnBuild
56 | {
57 | get => GetBooleanProperty(Property.GeneratePackageOnBuild);
58 | set => SetBooleanProperty(Property.GeneratePackageOnBuild, value);
59 | }
60 |
61 | public string? PackageId
62 | {
63 | get => GetStringProperty(Property.PackageId);
64 | set => SetStringProperty(Property.PackageId, value);
65 | }
66 |
67 | public string? PackageTitle
68 | {
69 | get => GetStringProperty(Property.Title);
70 | set => SetStringProperty(Property.Title, value);
71 | }
72 |
73 | public string? PackageVersion
74 | {
75 | get => GetStringProperty(Property.PackageVersion);
76 | set => SetStringProperty(Property.PackageVersion, value);
77 | }
78 |
79 | public string? Authors
80 | {
81 | get => GetStringProperty(Property.Authors);
82 | set => SetStringProperty(Property.Authors, value);
83 | }
84 |
85 | public string? Company
86 | {
87 | get => GetStringProperty(Property.Company);
88 | set => SetStringProperty(Property.Company, value);
89 | }
90 |
91 | public string? PackageDescription
92 | {
93 | get => GetStringProperty(Property.PackageDescription);
94 | set => SetStringProperty(Property.PackageDescription, value);
95 | }
96 |
97 | public string? Copyright
98 | {
99 | get => GetStringProperty(Property.Copyright);
100 | set => SetStringProperty(Property.Copyright, value);
101 | }
102 |
103 | public string? LicenseURL
104 | {
105 | get => GetStringProperty(Property.PackageLicenseUrl);
106 | set => UpdateProperty(ref _licenseURL, value, Property.PackageLicenseUrl);
107 | }
108 |
109 | public string? LicenseFile
110 | {
111 | get => GetStringProperty(Property.PackageLicenseFile);
112 | set => UpdateProperty(ref _licenseFile, value, Property.PackageLicenseFile);
113 | }
114 |
115 | [NuGetPackageLicenseExpression]
116 | public string? LicenseExpression
117 | {
118 | get => _licenseExpression ?? (_licenseExpression = GetStringProperty(Property.PackageLicenseExpression));
119 | set => UpdateProperty(ref _licenseExpression, value, Property.PackageLicenseExpression);
120 | }
121 |
122 | public bool IsLicenseURL
123 | {
124 | get => _isLicenseURL ?? (_isLicenseURL = !String.IsNullOrEmpty(LicenseURL)).Value;
125 | set => ChangeLicenseKind(_licenseURL, null, null, ref _isLicenseURL, value);
126 | }
127 |
128 | public bool IsLicenseFile
129 | {
130 | get => _isLicenseFile ?? (_isLicenseFile = !String.IsNullOrEmpty(LicenseFile)).Value;
131 | set => ChangeLicenseKind(null, _licenseFile, null, ref _isLicenseFile, value);
132 | }
133 |
134 | public bool IsLicenseExpression
135 | {
136 | get => _isLicenseExpression ?? (_isLicenseExpression = !String.IsNullOrEmpty(LicenseExpression)).Value;
137 | set => ChangeLicenseKind(null, null, _licenseExpression, ref _isLicenseExpression, value);
138 | }
139 |
140 | public bool RequireLicenseAcceptance
141 | {
142 | get => GetBooleanProperty(Property.PackageRequireLicenseAcceptance);
143 | set => SetBooleanProperty(Property.PackageRequireLicenseAcceptance, value);
144 | }
145 |
146 | public string? ProjectURL
147 | {
148 | get => GetStringProperty(Property.PackageProjectUrl);
149 | set => SetStringProperty(Property.PackageProjectUrl, value);
150 | }
151 |
152 | public string? IconUrl
153 | {
154 | get => GetStringProperty(Property.PackageIconUrl);
155 | set => UpdateProperty(ref _iconUrl, value, Property.PackageIconUrl);
156 | }
157 |
158 | public string? IconFile
159 | {
160 | get => GetStringProperty(Property.PackageIcon);
161 | set => UpdateProperty(ref _iconFile, value, Property.PackageIcon);
162 | }
163 |
164 | public bool IsIconUrl
165 | {
166 | get => _isIconUrl ?? (_isIconUrl = !String.IsNullOrEmpty(IconUrl)).Value;
167 | set => ChangeIconKind(_iconUrl, null, ref _isIconUrl, value);
168 | }
169 |
170 | public bool IsIconFile
171 | {
172 | get => _isIconFile ?? (_isIconFile = !String.IsNullOrEmpty(IconFile)).Value;
173 | set => ChangeIconKind(null, _iconFile, ref _isIconFile, value);
174 | }
175 |
176 | public string? RepositoryURL
177 | {
178 | get => GetStringProperty(Property.RepositoryUrl);
179 | set => SetStringProperty(Property.RepositoryUrl, value);
180 | }
181 |
182 | public string? RepositoryType
183 | {
184 | get => GetStringProperty(Property.RepositoryType);
185 | set => SetStringProperty(Property.RepositoryType, value);
186 | }
187 |
188 | public string? Tags
189 | {
190 | get => GetStringProperty(Property.PackageTags);
191 | set => SetStringProperty(Property.PackageTags, value);
192 | }
193 |
194 | public string? ReleaseNotes
195 | {
196 | get => GetStringProperty(Property.PackageReleaseNotes);
197 | set => SetStringProperty(Property.PackageReleaseNotes, value);
198 | }
199 |
200 | private void ChangeLicenseKind(
201 | string? licenseURL,
202 | string? licenseFile,
203 | string? licenseExpression,
204 | ref bool? isLicenseField,
205 | bool isLicense,
206 | [CallerMemberName] string? propertyName = null)
207 | {
208 | if (isLicense)
209 | {
210 | LicenseURL = licenseURL;
211 | LicenseFile = licenseFile;
212 | LicenseExpression = licenseExpression;
213 | }
214 |
215 | this.RaiseAndSetIfChanged(ref isLicenseField, isLicense, propertyName);
216 | }
217 |
218 | private void ChangeIconKind(
219 | string? iconUrl,
220 | string? icon,
221 | ref bool? isIconField,
222 | bool isIcon,
223 | [CallerMemberName] string? propertyName = null)
224 | {
225 | if (isIcon)
226 | {
227 | IconUrl = iconUrl;
228 | IconFile = icon;
229 | }
230 |
231 | this.RaiseAndSetIfChanged(ref isIconField, isIcon, propertyName);
232 | }
233 |
234 | private void UpdateProperty(
235 | ref string? field,
236 | string? value,
237 | string propertyName)
238 | {
239 | if (value != null)
240 | {
241 | field = value;
242 | }
243 |
244 | SetStringProperty(propertyName, value);
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/ViewModels/PropertyPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Runtime.CompilerServices;
5 | using Microsoft.Build.Evaluation;
6 |
7 | using ReactiveUI;
8 |
9 | using DotNet.Properties.Services;
10 |
11 | namespace DotNet.Properties.Pages.ViewModels
12 | {
13 | internal abstract class PropertyPageViewModel : ReactiveObject
14 | {
15 | protected PropertyPageViewModel(IPropertyManager propertyManager)
16 | {
17 | PropertyManager = propertyManager;
18 | }
19 |
20 | protected IPropertyManager PropertyManager { get; }
21 |
22 | protected bool GetBooleanProperty(string propertyName)
23 | {
24 | var value = PropertyManager.GetProperty(propertyName);
25 | return String.Equals(value, Boolean.TrueString, StringComparison.InvariantCultureIgnoreCase);
26 | }
27 |
28 | protected string? GetStringProperty(string propertyName) => PropertyManager.GetProperty(propertyName);
29 |
30 | protected IReadOnlyCollection GetItems(string itemType) =>
31 | PropertyManager.GetItems(itemType);
32 |
33 | protected void SetBooleanProperty(string propertyName, bool value, [CallerMemberName] string? changedProperty = null)
34 | {
35 | PropertyManager.SetProperty(propertyName, value.ToString(CultureInfo.InvariantCulture));
36 | this.RaisePropertyChanged(changedProperty);
37 | }
38 |
39 | protected void SetStringProperty(string propertyName, string? value, [CallerMemberName] string? changedProperty = null)
40 | {
41 | PropertyManager.SetProperty(propertyName, value ?? String.Empty);
42 | this.RaisePropertyChanged(changedProperty);
43 | }
44 |
45 | protected void Save() => PropertyManager.Save();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/ViewModels/SigningPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using System.Windows.Input;
5 |
6 | using Avalonia.Controls;
7 |
8 | using ReactiveUI;
9 |
10 | using DotNet.Properties.Services;
11 |
12 | namespace DotNet.Properties.Pages.ViewModels
13 | {
14 | internal sealed class SigningPageViewModel : PropertyPageViewModel
15 | {
16 | private static class Property
17 | {
18 | public const string SignAssembly = nameof(SignAssembly);
19 | public const string AssemblyOriginatorKeyFile = nameof(AssemblyOriginatorKeyFile);
20 | public const string DelaySign = nameof(DelaySign);
21 | }
22 |
23 | private readonly IOpenFileDialogService _openFileDialogService;
24 |
25 | public SigningPageViewModel(
26 | IPropertyManager propertyManager,
27 | IOpenFileDialogService openFileDialogService)
28 | : base(propertyManager)
29 | {
30 | _openFileDialogService = openFileDialogService;
31 |
32 | OpenKeyFileCommand = ReactiveCommand.Create(OpenKeyFile);
33 | }
34 |
35 | public bool SignAssembly
36 | {
37 | get => GetBooleanProperty(Property.SignAssembly);
38 | set => SetBooleanProperty(Property.SignAssembly, value);
39 | }
40 |
41 | public string? AssemblyOriginatorKeyFile
42 | {
43 | get => GetStringProperty(Property.AssemblyOriginatorKeyFile);
44 | set => SetStringProperty(Property.AssemblyOriginatorKeyFile, PropertyManager.MakeRelativePath(value));
45 | }
46 |
47 | public ICommand OpenKeyFileCommand { get; }
48 |
49 | public bool DelaySign
50 | {
51 | get => GetBooleanProperty(Property.DelaySign);
52 | set => SetBooleanProperty(Property.DelaySign, value);
53 | }
54 |
55 | private async Task OpenKeyFile()
56 | {
57 | var openFileDialog = new OpenFileDialog()
58 | {
59 | AllowMultiple = false,
60 | Filters = new List()
61 | {
62 | new FileDialogFilter() { Name = "Key Files", Extensions = new List() { "snk", "pfx" } }
63 | },
64 | Title = String.Empty
65 | };
66 |
67 | var paths = await _openFileDialogService.ShowDialogAsync(openFileDialog).ConfigureAwait(false);
68 |
69 | if (paths != null && paths.Count > 0)
70 | {
71 | AssemblyOriginatorKeyFile = paths[0];
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/ApplicationPage.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
41 |
42 |
43 |
44 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
57 |
60 |
61 |
62 |
63 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/ApplicationPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using Avalonia.ReactiveUI;
3 |
4 | using DotNet.Properties.Pages.ViewModels;
5 |
6 | namespace DotNet.Properties.Pages.Views
7 | {
8 | internal sealed class ApplicationPage : ReactiveUserControl
9 | {
10 | public ApplicationPage()
11 | {
12 | InitializeComponent();
13 | }
14 |
15 | private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/BuildEventsPage.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
38 |
39 |
40 |
41 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/BuildEventsPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using Avalonia.ReactiveUI;
3 |
4 | using DotNet.Properties.Pages.ViewModels;
5 |
6 | namespace DotNet.Properties.Pages.Views
7 | {
8 | internal sealed class BuildEventsPage : ReactiveUserControl
9 | {
10 | public BuildEventsPage()
11 | {
12 | InitializeComponent();
13 | }
14 |
15 | private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/BuildPage.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
45 |
46 |
48 |
49 |
51 |
52 |
53 |
54 |
55 |
56 |
58 |
59 |
61 |
62 |
64 |
65 |
66 |
67 |
70 |
71 |
72 |
73 |
75 |
76 |
77 |
78 |
80 |
81 |
82 |
83 |
85 |
86 |
88 |
89 |
90 |
91 |
94 |
95 |
96 |
97 |
98 |
99 |
101 |
102 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
112 |
113 |
115 |
116 |
118 |
119 |
120 |
121 |
123 |
124 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/BuildPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using Avalonia.ReactiveUI;
3 |
4 | using DotNet.Properties.Pages.ViewModels;
5 |
6 | namespace DotNet.Properties.Pages.Views
7 | {
8 | internal sealed class BuildPage : ReactiveUserControl
9 | {
10 | public BuildPage()
11 | {
12 | InitializeComponent();
13 | }
14 |
15 | private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/PackagePage.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
9 |
10 |
12 |
13 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
44 |
45 |
46 |
47 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
81 |
82 |
84 |
85 |
87 |
88 |
90 |
91 |
92 |
93 |
98 |
99 |
100 |
101 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
141 |
142 |
144 |
145 |
147 |
148 |
149 |
150 |
151 |
152 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
174 |
175 |
177 |
178 |
179 |
180 |
181 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/PackagePage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using Avalonia.ReactiveUI;
3 |
4 | using DotNet.Properties.Pages.ViewModels;
5 |
6 | namespace DotNet.Properties.Pages.Views
7 | {
8 | internal sealed class PackagePage : ReactiveUserControl
9 | {
10 | public PackagePage()
11 | {
12 | InitializeComponent();
13 | }
14 |
15 | private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/SigningPage.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
9 |
10 |
12 |
13 |
18 |
19 |
21 |
22 |
25 |
26 |
31 |
32 |
33 |
34 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Pages/Views/SigningPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using Avalonia.ReactiveUI;
3 |
4 | using DotNet.Properties.Pages.ViewModels;
5 |
6 | namespace DotNet.Properties.Pages.Views
7 | {
8 | internal sealed class SigningPage : ReactiveUserControl
9 | {
10 | public SigningPage()
11 | {
12 | InitializeComponent();
13 | }
14 |
15 | private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/dotnet-properties/PagesViewLocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using Avalonia.Controls;
5 | using Avalonia.Controls.Templates;
6 |
7 | using DotNet.Properties.Pages.ViewModels;
8 | using DotNet.Properties.Pages.Views;
9 |
10 | namespace DotNet.Properties
11 | {
12 | internal sealed class PagesViewLocator : IDataTemplate
13 | {
14 | private static readonly Dictionary> Templates = new Dictionary>()
15 | {
16 | { typeof(ApplicationPageViewModel), New },
17 | { typeof(BuildEventsPageViewModel), New },
18 | { typeof(BuildPageViewModel), New },
19 | { typeof(PackagePageViewModel), New },
20 | { typeof(SigningPageViewModel), New }
21 | };
22 |
23 | public bool SupportsRecycling => false;
24 |
25 | public IControl Build(object param) => Templates[param.GetType()]();
26 |
27 | public bool Match(object data) => Templates.ContainsKey(data.GetType());
28 |
29 | private static T New() where T : new() => new T();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Project/BuildEnvironment.cs:
--------------------------------------------------------------------------------
1 | // Code copied from Buildalyzer (https://github.com/daveaglick/Buildalyzer)
2 |
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace DotNet.Properties
7 | {
8 | internal sealed class BuildEnvironment : IDisposable
9 | {
10 | private readonly string? _oldMsBuildExtensionsPath;
11 | private readonly string? _oldMsBuildSdksPath;
12 |
13 | public BuildEnvironment(IReadOnlyDictionary globalProperties)
14 | {
15 | if (globalProperties.TryGetValue(MSBuildProperties.MSBuildExtensionsPath, out var msBuildExtensionsPath))
16 | {
17 | _oldMsBuildExtensionsPath = Environment.GetEnvironmentVariable(MSBuildProperties.MSBuildExtensionsPath);
18 | Environment.SetEnvironmentVariable(MSBuildProperties.MSBuildExtensionsPath, msBuildExtensionsPath);
19 | }
20 | if (globalProperties.TryGetValue(MSBuildProperties.MSBuildSDKsPath, out var msBuildSDKsPath))
21 | {
22 | _oldMsBuildSdksPath = Environment.GetEnvironmentVariable(MSBuildProperties.MSBuildSDKsPath);
23 | Environment.SetEnvironmentVariable(MSBuildProperties.MSBuildSDKsPath, msBuildSDKsPath);
24 | }
25 | }
26 |
27 | public void Dispose()
28 | {
29 | if (_oldMsBuildExtensionsPath != null)
30 | {
31 | Environment.SetEnvironmentVariable(MSBuildProperties.MSBuildExtensionsPath, _oldMsBuildExtensionsPath);
32 | }
33 | if (_oldMsBuildSdksPath != null)
34 | {
35 | Environment.SetEnvironmentVariable(MSBuildProperties.MSBuildSDKsPath, _oldMsBuildSdksPath);
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Project/DotNetSdkPaths.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace DotNet.Properties
4 | {
5 | internal sealed class DotNetSdkPaths
6 | {
7 | public DotNetSdkPaths(string dotnetSdkPath)
8 | {
9 | ToolsPath = dotnetSdkPath;
10 | ExtensionsPath = dotnetSdkPath;
11 | SdksPath = Path.Combine(dotnetSdkPath, "Sdks");
12 | RoslynTargetsPath = Path.Combine(dotnetSdkPath, "Roslyn");
13 | }
14 |
15 | public string ToolsPath { get; }
16 |
17 | public string ExtensionsPath { get; }
18 |
19 | public string SdksPath { get; }
20 |
21 | public string RoslynTargetsPath { get; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Project/MSBuildProject.cs:
--------------------------------------------------------------------------------
1 | // Most of the code in this file is copied from Buildalyzer (https://github.com/daveaglick/Buildalyzer)
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using Microsoft.Build.Construction;
7 | using Microsoft.Build.Evaluation;
8 | using Microsoft.Build.Utilities;
9 |
10 | namespace DotNet.Properties
11 | {
12 | internal sealed class MSBuildProject
13 | {
14 | private readonly DotNetSdkPaths _dotnetSdkPaths;
15 |
16 | private readonly Dictionary _globalProperties;
17 | private readonly string _projectPath;
18 |
19 | private Project? _project;
20 |
21 | public Project Project
22 | {
23 | get
24 | {
25 | if (_project == null)
26 | {
27 | _project = Load();
28 | }
29 |
30 | return _project;
31 | }
32 | }
33 |
34 | public MSBuildProject(DotNetSdkPaths dotnetSdkPaths, string projectPath)
35 | {
36 | _dotnetSdkPaths = dotnetSdkPaths;
37 |
38 | _projectPath = projectPath;
39 |
40 | var solutionDir = Path.GetDirectoryName(projectPath) ?? throw new InvalidOperationException();
41 |
42 | // Set global properties
43 | _globalProperties = new Dictionary
44 | {
45 | { MSBuildProperties.SolutionDir, solutionDir },
46 | { MSBuildProperties.MSBuildExtensionsPath, _dotnetSdkPaths.ExtensionsPath },
47 | { MSBuildProperties.MSBuildSDKsPath, _dotnetSdkPaths.SdksPath },
48 | { MSBuildProperties.RoslynTargetsPath, _dotnetSdkPaths.RoslynTargetsPath },
49 | //{ MSBuildProperties.DesignTimeBuild, "true" },
50 | { MSBuildProperties.BuildProjectReferences, "false" },
51 | { MSBuildProperties.SkipCompilerExecution, "true" },
52 | { MSBuildProperties.ProvideCommandLineArgs, "true" },
53 | // Workaround for a problem with resource files, see https://github.com/dotnet/sdk/issues/346#issuecomment-257654120
54 | { MSBuildProperties.GenerateResourceMSBuildArchitecture, "CurrentArchitecture" }
55 | };
56 | }
57 |
58 | public Project Load()
59 | {
60 | // Create a project collection for each project since the toolset might change depending on the type of project
61 | var projectCollection = CreateProjectCollection();
62 |
63 | // Load the project
64 | using (new BuildEnvironment(_globalProperties))
65 | {
66 | return new Project(ProjectRootElement.Open(_projectPath, projectCollection, true),
67 | _globalProperties, ToolLocationHelper.CurrentToolsVersion, projectCollection);
68 | }
69 | }
70 |
71 | private ProjectCollection CreateProjectCollection()
72 | {
73 | var projectCollection = new ProjectCollection(_globalProperties);
74 |
75 | projectCollection.RemoveAllToolsets(); // Make sure we're only using the latest tools
76 | projectCollection.AddToolset(new Toolset(ToolLocationHelper.CurrentToolsVersion, _dotnetSdkPaths.ToolsPath, projectCollection, String.Empty));
77 | projectCollection.DefaultToolsVersion = ToolLocationHelper.CurrentToolsVersion;
78 |
79 | return projectCollection;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Project/MSBuildProperties.cs:
--------------------------------------------------------------------------------
1 | // Code copied from Buildalyzer (https://github.com/daveaglick/Buildalyzer)
2 |
3 | namespace DotNet.Properties
4 | {
5 | internal static class MSBuildProperties
6 | {
7 | // MSBuild Project Loading
8 | public const string MSBuildExtensionsPath = nameof(MSBuildExtensionsPath);
9 | public const string MSBuildSDKsPath = nameof(MSBuildSDKsPath);
10 | public const string RoslynTargetsPath = nameof(RoslynTargetsPath);
11 | public const string SolutionDir = nameof(SolutionDir);
12 |
13 | // Design-time Build
14 | public const string DesignTimeBuild = nameof(DesignTimeBuild);
15 | public const string BuildProjectReferences = nameof(BuildProjectReferences);
16 | public const string SkipCompilerExecution = nameof(SkipCompilerExecution);
17 | public const string ProvideCommandLineArgs = nameof(ProvideCommandLineArgs);
18 |
19 | // Others
20 | public const string GenerateResourceMSBuildArchitecture = nameof(GenerateResourceMSBuildArchitecture);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "dotnet-properties": {
4 | "commandName": "Project",
5 | "workingDirectory": ".\\"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-Bold.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-ExtraBold.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-Italic.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-Light.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-LightItalic.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-SemiBold.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Fonts/OpenSans/OpenSans-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Icon.ico
--------------------------------------------------------------------------------
/src/dotnet-properties/Resources/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/dotnet-properties/73755a414f0e4a191719e69f9644e0427e61cd76/src/dotnet-properties/Resources/Icon.png
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/DialogService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | using Avalonia.Controls;
6 | using Avalonia.Threading;
7 |
8 | namespace DotNet.Properties.Services
9 | {
10 | internal sealed class DialogService : IDialogService where TView : Window
11 | {
12 | private readonly Func _viewFactory;
13 | private readonly Window _owner;
14 |
15 | public DialogService(
16 | Func viewFactory,
17 | Window owner)
18 | {
19 | _viewFactory = viewFactory;
20 | _owner = owner;
21 | }
22 |
23 | public void Show(TViewModel viewModel)
24 | {
25 | var view = _viewFactory();
26 | view.DataContext = viewModel;
27 |
28 | using (var source = new CancellationTokenSource())
29 | {
30 | view.ShowDialog(_owner).ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext());
31 | Dispatcher.UIThread.MainLoop(source.Token);
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/DotNetInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using NuGet.Versioning;
5 |
6 | namespace DotNet.Properties.Services
7 | {
8 | // Code from OmniSharp: https://github.com/OmniSharp/omnisharp-roslyn/blob/1cc5321d320020f07c89969ef06eb52fe8c77379/src/OmniSharp.Abstractions/Services/DotNetInfo.cs
9 | internal sealed class DotNetInfo
10 | {
11 | public static DotNetInfo Empty { get; } = new DotNetInfo();
12 |
13 | public bool IsEmpty { get; }
14 |
15 | public SemanticVersion? Version { get; }
16 | public string? OSName { get; }
17 | public string? OSVersion { get; }
18 | public string? OSPlatform { get; }
19 | public string? RID { get; }
20 | public string? BasePath { get; }
21 |
22 | private DotNetInfo()
23 | {
24 | IsEmpty = true;
25 | }
26 |
27 | private DotNetInfo(string version, string osName, string osVersion, string osPlatform, string rid, string basePath)
28 | {
29 | IsEmpty = false;
30 |
31 | Version = SemanticVersion.TryParse(version, out var value) ? value : null;
32 |
33 | OSName = osName;
34 | OSVersion = osVersion;
35 | OSPlatform = osPlatform;
36 | RID = rid;
37 | BasePath = basePath;
38 | }
39 |
40 | public static DotNetInfo Parse(List lines)
41 | {
42 | if (lines == null || lines.Count == 0)
43 | {
44 | return Empty;
45 | }
46 |
47 | var version = String.Empty;
48 | var osName = String.Empty;
49 | var osVersion = String.Empty;
50 | var osPlatform = String.Empty;
51 | var rid = String.Empty;
52 | var basePath = String.Empty;
53 |
54 | foreach (var line in lines)
55 | {
56 | var colonIndex = line.IndexOf(':', StringComparison.OrdinalIgnoreCase);
57 |
58 | if (colonIndex >= 0)
59 | {
60 | var name = line.Substring(0, colonIndex).Trim();
61 | var value = line.Substring(colonIndex + 1).Trim();
62 |
63 | if (String.IsNullOrEmpty(version) && name.Equals("Version", StringComparison.OrdinalIgnoreCase))
64 | {
65 | version = value;
66 | }
67 | else if (String.IsNullOrEmpty(osName) && name.Equals("OS Name", StringComparison.OrdinalIgnoreCase))
68 | {
69 | osName = value;
70 | }
71 | else if (String.IsNullOrEmpty(osVersion) && name.Equals("OS Version", StringComparison.OrdinalIgnoreCase))
72 | {
73 | osVersion = value;
74 | }
75 | else if (String.IsNullOrEmpty(osPlatform) && name.Equals("OS Platform", StringComparison.OrdinalIgnoreCase))
76 | {
77 | osPlatform = value;
78 | }
79 | else if (String.IsNullOrEmpty(rid) && name.Equals("RID", StringComparison.OrdinalIgnoreCase))
80 | {
81 | rid = value;
82 | }
83 | else if (String.IsNullOrEmpty(basePath) && name.Equals("Base Path", StringComparison.OrdinalIgnoreCase))
84 | {
85 | basePath = value;
86 | }
87 | }
88 | }
89 |
90 | if (String.IsNullOrWhiteSpace(version) &&
91 | String.IsNullOrWhiteSpace(osName) &&
92 | String.IsNullOrWhiteSpace(osVersion) &&
93 | String.IsNullOrWhiteSpace(osPlatform) &&
94 | String.IsNullOrWhiteSpace(rid) &&
95 | String.IsNullOrWhiteSpace(basePath))
96 | {
97 | return Empty;
98 | }
99 |
100 | return new DotNetInfo(version, osName, osVersion, osPlatform, rid, basePath);
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/DotNetSdkResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.Diagnostics;
6 | using System.Diagnostics.CodeAnalysis;
7 | using System.IO;
8 |
9 | namespace DotNet.Properties.Services
10 | {
11 | // Based on code from OmniSharp
12 | // https://github.com/OmniSharp/omnisharp-roslyn/blob/78ccc8b4376c73da282a600ac6fb10fce8620b52/src/OmniSharp.Abstractions/Services/DotNetCliService.cs
13 | internal sealed class DotNetSdkResolver : IDotNetSdkResolver
14 | {
15 | private readonly ConcurrentDictionary _dotnetInfos = new ConcurrentDictionary();
16 |
17 | public bool TryResolveSdkPath(string workingDirectory, [NotNullWhen(true)] out string? path)
18 | {
19 | if (!_dotnetInfos.TryGetValue(workingDirectory, out var info))
20 | {
21 | info = GetInfo(workingDirectory);
22 | _dotnetInfos.TryAdd(workingDirectory, info);
23 | }
24 |
25 | path = info.BasePath;
26 | return path != null;
27 | }
28 |
29 | private static DotNetInfo GetInfo(string workingDirectory)
30 | {
31 | const string DOTNET_CLI_UI_LANGUAGE = nameof(DOTNET_CLI_UI_LANGUAGE);
32 |
33 | // Ensure that we set the DOTNET_CLI_UI_LANGUAGE environment variable to "en-US" before
34 | // running 'dotnet --info'. Otherwise, we may get localized results.
35 | var originalValue = Environment.GetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE);
36 | Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, "en-US");
37 |
38 | try
39 | {
40 | Process process;
41 |
42 | try
43 | {
44 | var startInfo = new ProcessStartInfo("dotnet", "--info")
45 | {
46 | WorkingDirectory = workingDirectory,
47 | CreateNoWindow = true,
48 | UseShellExecute = false,
49 | RedirectStandardOutput = true,
50 | RedirectStandardError = true
51 | };
52 |
53 | process = Process.Start(startInfo) ?? throw new FileNotFoundException();
54 | }
55 | catch (FileNotFoundException)
56 | {
57 | return DotNetInfo.Empty;
58 | }
59 | catch (Win32Exception)
60 | {
61 | return DotNetInfo.Empty;
62 | }
63 |
64 | if (process.HasExited)
65 | {
66 | return DotNetInfo.Empty;
67 | }
68 |
69 | var lines = new List();
70 | process.OutputDataReceived += (_, e) =>
71 | {
72 | if (!String.IsNullOrWhiteSpace(e.Data))
73 | {
74 | lines.Add(e.Data);
75 | }
76 | };
77 |
78 | process.BeginOutputReadLine();
79 |
80 | process.WaitForExit();
81 |
82 | return DotNetInfo.Parse(lines);
83 | }
84 | finally
85 | {
86 | Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, originalValue);
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/IDialogService.cs:
--------------------------------------------------------------------------------
1 | namespace DotNet.Properties.Services
2 | {
3 | internal interface IDialogService
4 | {
5 | void Show(TViewModel viewModel);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/IDotNetSdkResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace DotNet.Properties.Services
4 | {
5 | internal interface IDotNetSdkResolver
6 | {
7 | bool TryResolveSdkPath(string workingDirectory, [NotNullWhen(true)] out string? path);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/IMSBuildLoader.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.Loader;
3 |
4 | namespace DotNet.Properties.Services
5 | {
6 | internal interface IMSBuildLoader
7 | {
8 | bool TryResolveMSBuildAssembly(AssemblyLoadContext context, string assemblyName, out Assembly? assembly);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/IOpenFileDialogService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 |
4 | using Avalonia.Controls;
5 |
6 | namespace DotNet.Properties.Services
7 | {
8 | internal interface IOpenFileDialogService
9 | {
10 | Task> ShowDialogAsync(OpenFileDialog openFileDialog);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/IPropertyManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.Build.Evaluation;
4 |
5 | namespace DotNet.Properties.Services
6 | {
7 | internal interface IPropertyManager
8 | {
9 | event EventHandler IsDirtyChanged;
10 |
11 | bool IsDirty { get; }
12 |
13 | IEnumerable AvailableConfigurations { get; }
14 | IEnumerable AvailablePlatforms { get; }
15 |
16 | string? Configuration { get; set; }
17 | string? Platform { get; set; }
18 |
19 | string? GetProperty(string propertyName, bool evaluatedValue = true);
20 | IReadOnlyCollection GetItems(string itemType);
21 |
22 | void SetProperty(string propertyName, string value);
23 |
24 | void Save();
25 |
26 | string MakeRelativePath(string? path);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/ITheme.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Styling;
2 |
3 | namespace DotNet.Properties.Services
4 | {
5 | internal interface ITheme
6 | {
7 | string Name { get; }
8 | IStyle Style { get; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/IThemeService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace DotNet.Properties.Services
4 | {
5 | internal interface IThemeService
6 | {
7 | ITheme CurrentTheme { get; set; }
8 | IReadOnlyCollection AvailableThemes { get; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/MSBuildLoader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Runtime.Loader;
6 |
7 | namespace DotNet.Properties.Services
8 | {
9 | internal sealed class MSBuildLoader : IMSBuildLoader
10 | {
11 | private static readonly ImmutableHashSet MSBuildAssemblies =
12 | ImmutableHashSet.Create(
13 | StringComparer.OrdinalIgnoreCase,
14 | "Microsoft.Build",
15 | "Microsoft.Build.Framework",
16 | "Microsoft.Build.Tasks.Core",
17 | "Microsoft.Build.Utilities.Core");
18 |
19 | private readonly DotNetSdkPaths _dotnetSdkPaths;
20 |
21 | public MSBuildLoader(DotNetSdkPaths dotnetSdkPaths)
22 | {
23 | _dotnetSdkPaths = dotnetSdkPaths;
24 | }
25 |
26 | public bool TryResolveMSBuildAssembly(AssemblyLoadContext context, string assemblyName, out Assembly? assembly)
27 | {
28 | if (MSBuildAssemblies.Contains(assemblyName))
29 | {
30 | var assemblyPath = Path.Combine(_dotnetSdkPaths.ToolsPath, $"{assemblyName}.dll");
31 |
32 | if (File.Exists(assemblyPath))
33 | {
34 | try
35 | {
36 | assembly = context.LoadFromAssemblyPath(assemblyPath);
37 | return true;
38 | }
39 | catch (FileLoadException)
40 | {
41 | }
42 | catch (BadImageFormatException)
43 | {
44 | }
45 | }
46 | }
47 |
48 | assembly = null;
49 | return false;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/OpenFileDialogService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 |
4 | using Avalonia.Controls;
5 |
6 | namespace DotNet.Properties.Services
7 | {
8 | internal sealed class OpenFileDialogService : IOpenFileDialogService
9 | {
10 | private readonly Window _window;
11 |
12 | public OpenFileDialogService(Window window)
13 | {
14 | _window = window;
15 | }
16 |
17 | public async Task> ShowDialogAsync(OpenFileDialog openFileDialog) =>
18 | await openFileDialog.ShowAsync(_window).ConfigureAwait(false);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/PropertyManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using System.Globalization;
5 | using System.IO;
6 | using System.Linq;
7 | using Microsoft.Build.Construction;
8 | using Microsoft.Build.Evaluation;
9 |
10 | namespace DotNet.Properties.Services
11 | {
12 | internal sealed class PropertyManager : IPropertyManager
13 | {
14 | private const string ConfigurationCondition = "'$(Configuration)' == '{0}'";
15 | private const string PlatformCondition = "'$(Platform)' == '{0}'";
16 | private const string ConfigurationAndPlatformCondition = "'$(Configuration)|$(Platform)' == '{0}|{1}'";
17 |
18 | private readonly Project _project;
19 |
20 | private string? _configuration;
21 | private string? _platform;
22 |
23 | public PropertyManager(Project project)
24 | {
25 | _project = project ?? throw new ArgumentNullException(nameof(project));
26 |
27 | SetConfiguration(String.Empty, false);
28 | SetPlatform(String.Empty, false);
29 |
30 | _project.ReevaluateIfNecessary();
31 | }
32 |
33 | public event EventHandler? IsDirtyChanged;
34 |
35 | public bool IsDirty => _project.Xml.HasUnsavedChanges;
36 |
37 | public IEnumerable AvailableConfigurations =>
38 | _project.GetPropertyValue("Configurations").Split(';').Select(c => c.Trim());
39 |
40 | public IEnumerable AvailablePlatforms =>
41 | _project.GetPropertyValue("Platforms").Split(';').Select(c => c.Trim());
42 |
43 | public string? Configuration
44 | {
45 | get => _configuration;
46 | set => SetConfiguration(value);
47 | }
48 |
49 | public string? Platform
50 | {
51 | get => _platform;
52 | set => SetPlatform(value);
53 | }
54 |
55 | public string? GetProperty(
56 | string propertyName,
57 | bool evaluatedValue = true)
58 | {
59 | var property = _project.GetProperty(propertyName);
60 | return evaluatedValue ? property?.EvaluatedValue : property?.UnevaluatedValue;
61 | }
62 |
63 | public IReadOnlyCollection GetItems(string itemType) =>
64 | ImmutableArray.CreateRange(_project.GetItems(itemType));
65 |
66 | public void SetProperty(string propertyName, string value)
67 | {
68 | var isAnyConfiguration = IsAny(_configuration);
69 | var isAnyPlatform = IsAny(_platform);
70 |
71 | if (!isAnyConfiguration || !isAnyPlatform)
72 | {
73 | ProjectPropertyGroupElement propertyGroup;
74 |
75 | // IsAny checks for null, so _platform and _configuration can't be null
76 | #nullable disable
77 |
78 | if (isAnyConfiguration)
79 | {
80 | propertyGroup = GetPropertyGroupForPlatform(_platform);
81 | }
82 | else if (isAnyPlatform)
83 | {
84 | propertyGroup = GetPropertyGroupForConfiguration(_configuration);
85 | }
86 | else
87 | {
88 | propertyGroup = GetPropertyGroupForConfigurationAndPlatform(_configuration, _platform);
89 | }
90 |
91 | #nullable enable
92 |
93 | propertyGroup.SetProperty(propertyName, value);
94 |
95 | _project.ReevaluateIfNecessary();
96 | }
97 | else
98 | {
99 | _project.SetProperty(propertyName, value);
100 | }
101 |
102 | OnIsDirtyChanged();
103 | }
104 |
105 | public void Save()
106 | {
107 | _project.Save();
108 | _project.ReevaluateIfNecessary();
109 |
110 | OnIsDirtyChanged();
111 | }
112 |
113 | public string MakeRelativePath(string? path)
114 | {
115 | if (String.IsNullOrEmpty(path))
116 | {
117 | return _project.DirectoryPath;
118 | }
119 |
120 | return Path.GetRelativePath(_project.DirectoryPath, path);
121 | }
122 |
123 | private void SetConfiguration(string? configuration, bool reevaluateIfNecessary = true)
124 | {
125 | if (_configuration != configuration)
126 | {
127 | _project.SetGlobalProperty("Configuration", configuration);
128 | _configuration = configuration;
129 |
130 | if (reevaluateIfNecessary)
131 | {
132 | _project.ReevaluateIfNecessary();
133 | }
134 | }
135 | }
136 |
137 | private void SetPlatform(string? platform, bool reevaluateIfNecessary = true)
138 | {
139 | if (_platform != platform)
140 | {
141 | _project.SetGlobalProperty("Platform", platform);
142 | _platform = platform;
143 |
144 | if (reevaluateIfNecessary)
145 | {
146 | _project.ReevaluateIfNecessary();
147 | }
148 | }
149 | }
150 |
151 | private ProjectPropertyGroupElement CreatePropertyGroup(string condition)
152 | {
153 | var propertyGroupElement = _project.Xml.AddPropertyGroup();
154 | propertyGroupElement.Condition = condition;
155 |
156 | return propertyGroupElement;
157 | }
158 |
159 | private ProjectPropertyGroupElement GetPropertyGroup(string condition) =>
160 | _project.Xml.PropertyGroups.Where(p => p.Condition.Trim() == condition).LastOrDefault()
161 | ?? CreatePropertyGroup(condition);
162 |
163 | private ProjectPropertyGroupElement GetPropertyGroupForConfiguration(string configuration) =>
164 | GetPropertyGroup(String.Format(CultureInfo.InvariantCulture, ConfigurationCondition, configuration));
165 |
166 | private ProjectPropertyGroupElement GetPropertyGroupForPlatform(string platform) =>
167 | GetPropertyGroup(Format(PlatformCondition, platform));
168 |
169 | private ProjectPropertyGroupElement GetPropertyGroupForConfigurationAndPlatform(
170 | string configuration, string platform) =>
171 | GetPropertyGroup(Format(ConfigurationAndPlatformCondition, configuration, platform));
172 |
173 | private void OnIsDirtyChanged() => IsDirtyChanged?.Invoke(this, EventArgs.Empty);
174 |
175 | private static bool IsAny(string? value) => String.IsNullOrEmpty(value);
176 |
177 | private static string Format(string format, object arg0) =>
178 | String.Format(CultureInfo.InvariantCulture, format, arg0);
179 |
180 | private static string Format(string format, object arg0, object arg1) =>
181 | String.Format(CultureInfo.InvariantCulture, format, arg0, arg1);
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/Theme.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Styling;
2 |
3 | namespace DotNet.Properties.Services
4 | {
5 | internal sealed class Theme : ITheme
6 | {
7 | public Theme(string name, IStyle style)
8 | {
9 | Name = name;
10 | Style = style;
11 | }
12 |
13 | public string Name { get; }
14 |
15 | public IStyle Style { get; }
16 |
17 | public override string ToString() => Name;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Services/ThemeService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using Avalonia;
5 | using Avalonia.Markup.Xaml.Styling;
6 | using Avalonia.Styling;
7 | using Avalonia.Themes.Fluent;
8 |
9 | namespace DotNet.Properties.Services
10 | {
11 | internal sealed class ThemeService : IThemeService
12 | {
13 | private static readonly Uri StylesBase = new Uri("avares://dotnet-properties/Styles/");
14 | private static readonly Uri Accents = new Uri(StylesBase, "Accents/");
15 |
16 | private static readonly Uri AvaloniaDefaultAccents = new Uri("avares://Avalonia.Themes.Default/Accents/");
17 | private static readonly Uri AvaloniaFluentAccents = new Uri("avares://Avalonia.Themes.Fluent/Accents/");
18 |
19 | private static readonly Uri CitrusBase = new Uri("avares://Citrus.Avalonia/");
20 |
21 | private static readonly StyleInclude Styles = Style(StylesBase, "Styles.xaml");
22 |
23 | private static readonly StyleInclude AvaloniaDefault = Style("avares://Avalonia.Themes.Default/DefaultTheme.xaml");
24 |
25 | private static readonly StyleInclude AvaloniaDefaultBaseLight = Style(AvaloniaDefaultAccents, "BaseLight.xaml");
26 | private static readonly StyleInclude AvaloniaDefaultBaseDark = Style(AvaloniaDefaultAccents, "BaseDark.xaml");
27 |
28 | private static readonly StyleInclude AvaloniaFluentBase = Style(AvaloniaFluentAccents, "Base.xaml");
29 | private static readonly StyleInclude AvaloniaFluentBaseLight = Style(AvaloniaFluentAccents, "BaseLight.xaml");
30 | private static readonly StyleInclude AvaloniaFluentBaseDark = Style(AvaloniaFluentAccents, "BaseDark.xaml");
31 |
32 | private static readonly IStyle AvaloniaFluentLight = new FluentTheme(default(Uri)!) { Mode = FluentThemeMode.Light };
33 | private static readonly IStyle AvaloniaFluentDark = new FluentTheme(default(Uri)!) { Mode = FluentThemeMode.Dark };
34 |
35 | private static readonly StyleInclude BaseLightBlue = Style(Accents, "BaseLightBlue.xaml");
36 |
37 | private static readonly StyleInclude Citrus = Style(CitrusBase, "Citrus.xaml");
38 | private static readonly StyleInclude CitrusSea = Style(CitrusBase, "Sea.xaml");
39 | private static readonly StyleInclude CitrusRust = Style(CitrusBase, "Rust.xaml");
40 | private static readonly StyleInclude CitrusCandy = Style(CitrusBase, "Candy.xaml");
41 | private static readonly StyleInclude CitrusMagma = Style(CitrusBase, "Magma.xaml");
42 |
43 | private static readonly StyleInclude DefaultThemeAccents = Style(Accents, "DefaultThemeAccents.xaml");
44 | private static readonly StyleInclude FluentThemeAccents = Style(Accents, "FluentThemeAccents.xaml");
45 | private static readonly StyleInclude CitrusThemeAccents = Style(Accents, "CitrusThemeAccents.xaml");
46 |
47 | private static readonly Theme[] Themes = new Theme[]
48 | {
49 | new Theme("Light", new Styles() { AvaloniaFluentBase, AvaloniaFluentBaseLight, AvaloniaDefault, AvaloniaDefaultBaseLight, DefaultThemeAccents, Styles }),
50 | new Theme("Light Blue", new Styles() { AvaloniaFluentBase, AvaloniaFluentBaseLight, AvaloniaDefault, BaseLightBlue, DefaultThemeAccents, Styles }),
51 | new Theme("Dark", new Styles() { AvaloniaFluentBase, AvaloniaFluentBaseDark, AvaloniaDefault, AvaloniaDefaultBaseDark, DefaultThemeAccents, Styles }),
52 | new Theme("Fluent Light", new Styles() { AvaloniaFluentLight, FluentThemeAccents, Styles }),
53 | new Theme("Fluent Dark", new Styles() { AvaloniaFluentDark, FluentThemeAccents, Styles }),
54 | new Theme("Citrus", new Styles() { Citrus, CitrusThemeAccents, Styles }),
55 | new Theme("Sea (Citrus)", new Styles() { CitrusSea, CitrusThemeAccents, Styles }),
56 | new Theme("Rust (Citrus)", new Styles() { CitrusRust, CitrusThemeAccents, Styles }),
57 | new Theme("Candy (Citrus)", new Styles() { CitrusCandy, CitrusThemeAccents, Styles }),
58 | new Theme("Magma (Citrus)", new Styles() { CitrusMagma, CitrusThemeAccents, Styles }),
59 | };
60 |
61 | private readonly Application _app;
62 |
63 | private ITheme _currentTheme;
64 |
65 | public ThemeService(Application app)
66 | {
67 | _app = app;
68 |
69 | AvailableThemes = Themes;
70 |
71 | _currentTheme = Themes[0];
72 | _app.Styles.Insert(0, _currentTheme.Style);
73 | }
74 |
75 | public ITheme CurrentTheme
76 | {
77 | get => _currentTheme;
78 | set
79 | {
80 | _app.Styles.Remove(_currentTheme.Style);
81 | _currentTheme = value;
82 | _app.Styles.Insert(0, _currentTheme.Style);
83 | }
84 | }
85 |
86 | public IReadOnlyCollection AvailableThemes { get; }
87 |
88 | private static StyleInclude Style(string source) => Style(new Uri(source));
89 |
90 | private static StyleInclude Style(Uri source) => new StyleInclude(source) { Source = source };
91 |
92 | private static StyleInclude Style(Uri baseUri, string source) => new StyleInclude(baseUri) { Source = new Uri(source, UriKind.Relative) };
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Styles/Accents/BaseLightBlue.xaml:
--------------------------------------------------------------------------------
1 |
78 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Styles/Accents/CitrusThemeAccents.xaml:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Styles/Accents/DefaultThemeAccents.xaml:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Styles/Accents/FluentThemeAccents.xaml:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Styles/SideBar.xaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
48 |
49 |
62 |
63 |
67 |
68 |
72 |
73 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Styles/Styles.xaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | /Resources/Fonts/OpenSans#Open Sans
6 | /Resources/Icon.ico
7 |
8 |
9 |
10 |
11 |
14 |
15 |
18 |
19 |
22 |
23 |
27 |
28 |
32 |
33 |
36 |
37 |
40 |
41 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/dotnet-properties/ViewModels/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Reactive.Linq;
6 | using System.Windows.Input;
7 | using Microsoft.Build.Evaluation;
8 |
9 | using ReactiveUI;
10 |
11 | using DotNet.Properties.Dialogs.Models;
12 | using DotNet.Properties.Dialogs.ViewModels;
13 | using DotNet.Properties.Pages.ViewModels;
14 | using DotNet.Properties.Services;
15 |
16 | namespace DotNet.Properties.ViewModels
17 | {
18 | internal sealed class MainWindowViewModel : ReactiveObject
19 | {
20 | private const string AnyConfiguration = "Any Configuration";
21 | private const string AnyPlatform = "Any Platform";
22 |
23 | private readonly IDialogService _unsavedChangesDialogService;
24 |
25 | private readonly Project _project;
26 | private readonly IPropertyManager _propertyManager;
27 |
28 | private readonly IThemeService _themeService;
29 |
30 | public MainWindowViewModel(
31 | MSBuildProject project,
32 | IDialogService unsavedChangesDialogService,
33 | IOpenFileDialogService openFileDialogService,
34 | IThemeService themeService)
35 | {
36 | _project = project.Project;
37 | _propertyManager = new PropertyManager(_project);
38 |
39 | _unsavedChangesDialogService = unsavedChangesDialogService;
40 | _themeService = themeService;
41 |
42 | ClosingCommand = ReactiveCommand.Create(OnClosing);
43 |
44 | SaveCommand = ReactiveCommand.Create(
45 | _propertyManager.Save,
46 | Observable.FromEventPattern(
47 | handler => _propertyManager.IsDirtyChanged += handler,
48 | handler => _propertyManager.IsDirtyChanged -= handler)
49 | .Select(_ => _propertyManager.IsDirty));
50 |
51 | ApplicationPage = new ApplicationPageViewModel(_propertyManager);
52 | BuildPage = new BuildPageViewModel(_propertyManager);
53 | BuildEventsPage = new BuildEventsPageViewModel(_propertyManager);
54 | PackagePage = new PackagePageViewModel(_propertyManager);
55 | SigningPage = new SigningPageViewModel(_propertyManager, openFileDialogService);
56 | }
57 |
58 | public ICommand ClosingCommand { get; }
59 |
60 | public string? ProjectPath => _project?.FullPath;
61 |
62 | public ICommand SaveCommand { get; }
63 |
64 | public IEnumerable AvailableConfigurations =>
65 | _propertyManager.AvailableConfigurations.Append(AnyConfiguration);
66 |
67 | public IEnumerable AvailablePlatforms =>
68 | _propertyManager.AvailablePlatforms.Append(AnyPlatform);
69 |
70 | public string Configuration
71 | {
72 | get => GetConfigurationDisplayName(_propertyManager.Configuration);
73 | set
74 | {
75 | _propertyManager.Configuration = value == AnyConfiguration ? String.Empty : value;
76 | this.RaisePropertyChanged();
77 | }
78 | }
79 |
80 | public string Platform
81 | {
82 | get => GetPlatformDisplayName(_propertyManager.Platform);
83 | set
84 | {
85 | _propertyManager.Platform = value == AnyPlatform ? String.Empty : value;
86 | this.RaisePropertyChanged();
87 | }
88 | }
89 |
90 | public ApplicationPageViewModel ApplicationPage { get; }
91 | public BuildPageViewModel BuildPage { get; }
92 | public BuildEventsPageViewModel BuildEventsPage { get; }
93 | public PackagePageViewModel PackagePage { get; }
94 | public SigningPageViewModel SigningPage { get; }
95 |
96 | public IEnumerable AvailableThemes => _themeService.AvailableThemes;
97 |
98 | public ITheme CurrentTheme
99 | {
100 | get => _themeService.CurrentTheme;
101 | set
102 | {
103 | _themeService.CurrentTheme = value;
104 | this.RaisePropertyChanged();
105 | }
106 | }
107 |
108 | private string GetConfigurationDisplayName(string? configuration) =>
109 | String.IsNullOrEmpty(configuration) ? AnyConfiguration : configuration;
110 |
111 | private string GetPlatformDisplayName(string? platform) =>
112 | String.IsNullOrEmpty(platform) ? AnyPlatform : platform;
113 |
114 | private void OnClosing(CancelEventArgs e)
115 | {
116 | if (_propertyManager.IsDirty)
117 | {
118 | var unsavedChangesDialogViewModel = new UnsavedChangesDialogViewModel();
119 | _unsavedChangesDialogService.Show(unsavedChangesDialogViewModel);
120 |
121 | switch (unsavedChangesDialogViewModel.DialogResult)
122 | {
123 | case UnsavedChangesDialogResult.Yes:
124 | _propertyManager.Save();
125 | e.Cancel = false;
126 | break;
127 | case UnsavedChangesDialogResult.No:
128 | e.Cancel = false;
129 | break;
130 | case UnsavedChangesDialogResult.Cancel:
131 | e.Cancel = true;
132 | break;
133 | default:
134 | throw new InvalidOperationException("Internal error!");
135 | }
136 | }
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Views/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
14 |
15 |
18 |
19 |
20 |
21 |
24 |
25 |
29 |
30 |
31 |
32 |
36 |
37 |
39 |
40 |
41 |
42 |
44 |
45 |
46 |
47 |
49 |
50 |
51 |
52 |
54 |
55 |
56 |
57 |
58 |
59 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
72 |
73 |
74 |
75 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/dotnet-properties/Views/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Windows.Input;
3 |
4 | using Avalonia;
5 | using Avalonia.Controls;
6 | using Avalonia.Markup.Xaml;
7 | using Avalonia.ReactiveUI;
8 |
9 | using DotNet.Properties.ViewModels;
10 |
11 | namespace DotNet.Properties.Views
12 | {
13 | internal sealed class MainWindow : ReactiveWindow
14 | {
15 | public static readonly StyledProperty ClosingCommandProperty =
16 | AvaloniaProperty.Register(nameof(ClosingCommand));
17 |
18 | public ICommand ClosingCommand
19 | {
20 | get => GetValue(ClosingCommandProperty);
21 | set => SetValue(ClosingCommandProperty, value);
22 | }
23 |
24 | public MainWindow()
25 | {
26 | InitializeComponent();
27 | #if AVALONIA_DIAGNOSTICS
28 | this.AttachDevTools();
29 | #endif
30 |
31 | Closing += HandleClosingCommand;
32 | }
33 |
34 | private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
35 |
36 | private void HandleClosingCommand(object? sender, CancelEventArgs e)
37 | {
38 | if (ClosingCommand != null)
39 | {
40 | if (!ClosingCommand.CanExecute(e))
41 | {
42 | e.Cancel = false;
43 | }
44 | else
45 | {
46 | ClosingCommand.Execute(e);
47 | }
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/dotnet-properties/dotnet-properties.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | WinExe
6 | Resources\Icon.ico
7 | DotNet.Properties
8 | True
9 |
10 |
11 |
12 | True
13 | dotnet-properties
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------