diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b65071e..f65b9ef 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -3,7 +3,7 @@ name: "PipManager Continuous Integration"
on:
push:
branches:
- - main
+ - development
jobs:
build:
diff --git a/README.md b/README.md
index 2a312fb..5e46c93 100644
--- a/README.md
+++ b/README.md
@@ -74,36 +74,8 @@ Double click `PipManager.exe` or `PipManager_withRuntime.exe` *If you have not i
(back to top)
-## Roadmap
-
-### Features
-
-- [x] Install Package
- - [x] Default *pip install*
- - [x] requirements.txt Import
- - [x] Download Distributions
- - [ ] Install via distributions
- - [ ] Install via VCS
-- [x] Update Package
-- [x] Delete Package
-- [x] Show Packages in list
-- [x] Multi-environment Management
-- [x] Search Package
-- [ ] Dependency Completion Check
-- [ ] Scenario Recommendation
-- [ ] Cache Management
-- [ ] Tools
- - [ ] Embedded Lite Python Script Editor
-
-### Long-term
-
-- Logging
-- Localization
-- Rules of action exceptions
-
See the [Open Issues](https://github.com/AuroraZiling/PipManager/issues) for a full list of proposed features (and known issues).
-(back to top)
## Contributing
@@ -113,8 +85,6 @@ See the [Open Issues](https://github.com/AuroraZiling/PipManager/issues) for a f
4. Push to the Branch `development`
5. Open a Pull Request
-(back to top)
-
## License
Distributed under the MIT License. See `LICENSE` for more information.
diff --git a/src/PipManager.PackageSearch/PackageSearchService.cs b/src/PipManager.PackageSearch/PackageSearchService.cs
index 8f787a3..4fffcaf 100644
--- a/src/PipManager.PackageSearch/PackageSearchService.cs
+++ b/src/PipManager.PackageSearch/PackageSearchService.cs
@@ -1,12 +1,11 @@
using HtmlAgilityPack;
using PipManager.PackageSearch.Wrappers.Query;
-using Serilog;
namespace PipManager.PackageSearch;
public class PackageSearchService(HttpClient httpClient) : IPackageSearchService
{
- public Dictionary<(string, int), QueryWrapper> QueryCaches { get; set; } = [];
+ private Dictionary<(string, int), QueryWrapper> QueryCaches { get; } = [];
public async Task Query(string name, int page = 1)
{
@@ -14,12 +13,12 @@ public async Task Query(string name, int page = 1)
{
return QueryCaches[(name, page)];
}
- var htmlContent = "";
+ string htmlContent;
try
{
htmlContent = await httpClient.GetStringAsync($"https://pypi.org/search/?q={name}&page={page}");
}
- catch (Exception exception) when (exception is TaskCanceledException || exception is HttpRequestException)
+ catch (Exception exception) when (exception is TaskCanceledException or HttpRequestException)
{
return new QueryWrapper
{
@@ -53,6 +52,7 @@ public async Task Query(string name, int page = 1)
Version = resultItem.ChildNodes[1].ChildNodes[3].InnerText,
Description = resultItem.ChildNodes[3].InnerText,
Url = $"https://pypi.org{resultItem.Attributes["href"].Value}",
+ // ReSharper disable once StringLiteralTypo
UpdateTime = DateTime.ParseExact(resultItem.ChildNodes[1].ChildNodes[5].ChildNodes[0].Attributes["datetime"].Value, "yyyy-MM-ddTHH:mm:sszzz", null, System.Globalization.DateTimeStyles.RoundtripKind)
});
}
diff --git a/src/PipManager/App.xaml.cs b/src/PipManager/App.xaml.cs
index 2811883..56f99d6 100644
--- a/src/PipManager/App.xaml.cs
+++ b/src/PipManager/App.xaml.cs
@@ -129,7 +129,7 @@ public static T GetService()
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
- private static partial bool FreeConsole();
+ private static partial void FreeConsole();
private bool _showConsoleWindow;
private bool _experimentMode;
diff --git a/src/PipManager/AppInfo.cs b/src/PipManager/AppInfo.cs
index 8db2990..3e9da3c 100644
--- a/src/PipManager/AppInfo.cs
+++ b/src/PipManager/AppInfo.cs
@@ -5,7 +5,7 @@ namespace PipManager;
public static class AppInfo
{
- public static readonly string AppVersion = Assembly.GetExecutingAssembly().GetName().Version!.ToString(3) ?? string.Empty;
+ public static readonly string AppVersion = Assembly.GetExecutingAssembly().GetName().Version!.ToString(3);
public static readonly string ConfigPath = Path.Combine(Directory.GetCurrentDirectory(), "config.json");
public static readonly string CrushesDir = Path.Combine(Directory.GetCurrentDirectory(), "crashes");
diff --git a/src/PipManager/AppStarting.cs b/src/PipManager/AppStarting.cs
index b1abef8..a9aa8e8 100644
--- a/src/PipManager/AppStarting.cs
+++ b/src/PipManager/AppStarting.cs
@@ -4,6 +4,7 @@
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
+using PipManager.Helpers;
namespace PipManager;
@@ -11,14 +12,14 @@ public partial class AppStarting
{
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
- private static partial bool AllocConsole();
+ private static partial void AllocConsole();
- public readonly AppConfig Config;
+ private readonly AppConfig _config;
public bool ShowConsoleWindow = false;
public AppStarting()
{
- Config = ConfigurationService.LoadConfiguration();
+ _config = ConfigurationService.LoadConfiguration();
Directory.CreateDirectory(AppInfo.CrushesDir);
Directory.CreateDirectory(AppInfo.LogDir);
Directory.CreateDirectory(AppInfo.CachesDir);
@@ -26,7 +27,7 @@ public AppStarting()
public void LoadLanguage()
{
- var language = Config.Personalization.Language;
+ var language = _config.Personalization.Language;
if (language != "Auto")
{
I18NExtension.Culture = new CultureInfo(language);
@@ -61,10 +62,10 @@ public void StartLogging()
public void LogDeletion()
{
- if (!Config.Personalization.LogAutoDeletion || !Directory.Exists(AppInfo.LogDir)) return;
+ if (!_config.Personalization.LogAutoDeletion || !Directory.Exists(AppInfo.LogDir)) return;
var fileList = Directory.GetFileSystemEntries(AppInfo.LogDir);
var logFileAmount = fileList.Count(file => File.Exists(file) && file.EndsWith(".txt"));
- if (logFileAmount >= Config.Personalization.LogAutoDeletionTimes)
+ if (logFileAmount >= _config.Personalization.LogAutoDeletionTimes)
{
var directoryInfo = new DirectoryInfo(AppInfo.LogDir);
var filesInfo = directoryInfo.GetFileSystemInfos();
@@ -77,7 +78,7 @@ public void LogDeletion()
}
catch
{
- continue;
+ // ignored
}
}
Log.Information($"{logFileAmount} log file(s) deleted");
@@ -86,27 +87,29 @@ public void LogDeletion()
public void CrushesDeletion()
{
- if (!Config.Personalization.CrushesAutoDeletion || !Directory.Exists(AppInfo.CrushesDir)) return;
+ if (!_config.Personalization.CrushesAutoDeletion || !Directory.Exists(AppInfo.CrushesDir)) return;
var fileList = Directory.GetFileSystemEntries(AppInfo.CrushesDir);
var crushFileAmount = fileList.Count(file => File.Exists(file) && file.EndsWith(".txt"));
- if (crushFileAmount >= Config.Personalization.CrushesAutoDeletionTimes)
+ if (crushFileAmount < _config.Personalization.CrushesAutoDeletionTimes)
{
- var directoryInfo = new DirectoryInfo(AppInfo.CrushesDir);
- var filesInfo = directoryInfo.GetFileSystemInfos();
- foreach (var file in filesInfo)
+ return;
+ }
+
+ var directoryInfo = new DirectoryInfo(AppInfo.CrushesDir);
+ var filesInfo = directoryInfo.GetFileSystemInfos();
+ foreach (var file in filesInfo)
+ {
+ if (file.Extension != ".txt") continue;
+ try
{
- if (file.Extension != ".txt") continue;
- try
- {
- File.Delete(file.FullName);
- }
- catch
- {
- continue;
- }
+ File.Delete(file.FullName);
+ }
+ catch
+ {
+ // ignored
}
- Log.Information($"{crushFileAmount} crush file(s) deleted");
}
+ Log.Information($"{crushFileAmount} crush file(s) deleted");
}
public void CachesDeletion()
@@ -115,19 +118,32 @@ public void CachesDeletion()
var directoryInfo = new DirectoryInfo(AppInfo.CachesDir);
var filesInfo = directoryInfo.GetFileSystemInfos();
var cacheFileAmount = 0;
+ foreach (var subDir in directoryInfo.GetDirectories("tempTarGz-*", SearchOption.AllDirectories))
+ {
+ try
+ {
+ subDir.Delete(true);
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+ }
foreach (var file in filesInfo)
{
- if (file.Name.StartsWith("temp_"))
+ if (!file.Name.StartsWith("temp_"))
{
- try
- {
- File.Delete(file.FullName);
- cacheFileAmount++;
- }
- catch
- {
- continue;
- }
+ continue;
+ }
+
+ try
+ {
+ File.Delete(file.FullName);
+ cacheFileAmount++;
+ }
+ catch
+ {
+ // ignored
}
}
Log.Information($"{cacheFileAmount} cache file(s) deleted");
diff --git a/src/PipManager/Helpers/ThreadIdEnricher.cs b/src/PipManager/Helpers/ThreadIdEnricher.cs
index c232723..1e3de84 100644
--- a/src/PipManager/Helpers/ThreadIdEnricher.cs
+++ b/src/PipManager/Helpers/ThreadIdEnricher.cs
@@ -1,11 +1,13 @@
using Serilog.Core;
using Serilog.Events;
+namespace PipManager.Helpers;
+
internal class ThreadIdEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
- "ThreadId", Environment.CurrentManagedThreadId));
+ "ThreadId", Environment.CurrentManagedThreadId));
}
}
\ No newline at end of file
diff --git a/src/PipManager/Languages/Lang.Designer.cs b/src/PipManager/Languages/Lang.Designer.cs
index fe36478..6eaa83e 100644
--- a/src/PipManager/Languages/Lang.Designer.cs
+++ b/src/PipManager/Languages/Lang.Designer.cs
@@ -1,7 +1,6 @@
//------------------------------------------------------------------------------
//
// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -258,6 +257,15 @@ public static string Action_NoCurrentOperations {
}
}
+ ///
+ /// Looks up a localized string similar to Cancel.
+ ///
+ public static string Action_Operation_Cancel {
+ get {
+ return ResourceManager.GetString("Action_Operation_Cancel", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Download.
///
@@ -303,6 +311,24 @@ public static string Action_Operation_Update {
}
}
+ ///
+ /// Looks up a localized string similar to Action Already Running.
+ ///
+ public static string Action_OperationCanceled_AlreadyRunning {
+ get {
+ return ResourceManager.GetString("Action_OperationCanceled_AlreadyRunning", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Action Cancelled.
+ ///
+ public static string Action_OperationCanceled_Success {
+ get {
+ return ResourceManager.GetString("Action_OperationCanceled_Success", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Time.
///
@@ -429,6 +455,24 @@ public static string ContentDialog_Message_ActionStillRunning {
}
}
+ ///
+ /// Looks up a localized string similar to {0} Caches Cleared.
+ ///
+ public static string ContentDialog_Message_CacheCleared {
+ get {
+ return ResourceManager.GetString("ContentDialog_Message_CacheCleared", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to clear caches.
+ ///
+ public static string ContentDialog_Message_CacheClearFailed {
+ get {
+ return ResourceManager.GetString("ContentDialog_Message_CacheClearFailed", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Environment already exists.
///
@@ -619,7 +663,16 @@ public static string Environment_Operation_CheckEnvironmentUpdate {
}
///
- /// Looks up a localized string similar to Remove Environment (from list).
+ /// Looks up a localized string similar to Clear Cache.
+ ///
+ public static string Environment_Operation_ClearEnvironmentCache {
+ get {
+ return ResourceManager.GetString("Environment_Operation_ClearEnvironmentCache", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove from list.
///
public static string Environment_Operation_RemoveEnvironment {
get {
@@ -628,7 +681,7 @@ public static string Environment_Operation_RemoveEnvironment {
}
///
- /// Looks up a localized string similar to Verify Environment.
+ /// Looks up a localized string similar to Verify Availability.
///
public static string Environment_Operation_VerifyEnvironment {
get {
@@ -1212,6 +1265,51 @@ public static string LibraryInstall_Header {
}
}
+ ///
+ /// Looks up a localized string similar to Distribution File already added to the list.
+ ///
+ public static string LibraryInstall_InstallDistributions_AlreadyExists {
+ get {
+ return ResourceManager.GetString("LibraryInstall_InstallDistributions_AlreadyExists", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Install via distributions.
+ ///
+ public static string LibraryInstall_InstallDistributions_Header {
+ get {
+ return ResourceManager.GetString("LibraryInstall_InstallDistributions_Header", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Install dependencies.
+ ///
+ public static string LibraryInstall_InstallDistributions_InstallDependencies {
+ get {
+ return ResourceManager.GetString("LibraryInstall_InstallDistributions_InstallDependencies", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Invalid Distribution File.
+ ///
+ public static string LibraryInstall_InstallDistributions_InvalidFile {
+ get {
+ return ResourceManager.GetString("LibraryInstall_InstallDistributions_InvalidFile", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Error while reading a file, check if the file is occupied.
+ ///
+ public static string LibraryInstall_InstallDistributions_IOError {
+ get {
+ return ResourceManager.GetString("LibraryInstall_InstallDistributions_IOError", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The newest and compatible version will be installed.
///
diff --git a/src/PipManager/Languages/Lang.resx b/src/PipManager/Languages/Lang.resx
index 6fc5f0a..ed1af42 100644
--- a/src/PipManager/Languages/Lang.resx
+++ b/src/PipManager/Languages/Lang.resx
@@ -208,10 +208,10 @@
Add Environment
- Remove Environment (from list)
+ Remove from list
- Verify Environment
+ Verify Availability
Package Source
@@ -699,4 +699,37 @@
Copied to clipboard
+
+ Action Cancelled
+
+
+ Action Already Running
+
+
+ Cancel
+
+
+ Error while reading a file, check if the file is occupied
+
+
+ Invalid Distribution File
+
+
+ Distribution File already added to the list
+
+
+ Install via distributions
+
+
+ Install dependencies
+
+
+ Clear Cache
+
+
+ {0} Caches Cleared
+
+
+ Failed to clear caches
+
\ No newline at end of file
diff --git a/src/PipManager/Languages/Lang.zh-cn.resx b/src/PipManager/Languages/Lang.zh-cn.resx
index 7723af0..0e72601 100644
--- a/src/PipManager/Languages/Lang.zh-cn.resx
+++ b/src/PipManager/Languages/Lang.zh-cn.resx
@@ -208,10 +208,10 @@
添加环境
- 从列表中移除环境
+ 从列表中移除
- 验证环境可用性
+ 验证可用性
包源
@@ -699,4 +699,37 @@
已复制
+
+ 任务已取消
+
+
+ 任务已运行
+
+
+ 取消
+
+
+ 在读取文件时出现错误,检查文件是否被占用
+
+
+ 该发行文件不可用
+
+
+ 该发行文件已在列表中
+
+
+ 通过发行文件安装
+
+
+ 安装依赖包
+
+
+ 清除缓存
+
+
+ 已清除 {0} 个缓存文件
+
+
+ 清除缓存失败
+
\ No newline at end of file
diff --git a/src/PipManager/Models/Action/ActionListItem.cs b/src/PipManager/Models/Action/ActionListItem.cs
index feb1295..01b56ce 100644
--- a/src/PipManager/Models/Action/ActionListItem.cs
+++ b/src/PipManager/Models/Action/ActionListItem.cs
@@ -5,7 +5,7 @@ namespace PipManager.Models.Action;
public partial class ActionListItem : ObservableObject
{
- public ActionListItem(ActionType operationType, string operationCommand, string displayCommand = "", string path = "", string[]? extraParameters = null, bool progressIntermediate = false, int totalSubTaskNumber = 1)
+ public ActionListItem(ActionType operationType, string[] operationCommand, string displayCommand = "", string path = "", string[]? extraParameters = null, bool progressIntermediate = false, int totalSubTaskNumber = 1)
{
OperationType = operationType;
OperationCommand = operationCommand;
@@ -15,7 +15,7 @@ public ActionListItem(ActionType operationType, string operationCommand, string
ExtraParameters = extraParameters;
DisplayCommand = displayCommand switch
{
- "" => operationCommand,
+ "" => string.Join(' ', operationCommand),
_ => displayCommand,
};
OperationDescription = operationType switch
@@ -55,7 +55,7 @@ public ActionListItem(ActionType operationType, string operationCommand, string
public ActionType OperationType { get; set; }
public string OperationDescription { get; set; }
public string OperationTimestamp { get; set; } = DateTime.Now.ToLocalTime().ToString("yyyy-M-d HH:mm:ss");
- public string OperationCommand { get; set; }
+ public string[] OperationCommand { get; set; }
public string DisplayCommand { get; set; }
public string Path { get; set; }
public string[]? ExtraParameters { get; set; }
@@ -72,7 +72,7 @@ public ActionListItem(ActionType operationType, string operationCommand, string
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ProgressBarValue))]
- private int _completedSubTaskNumber = 0;
+ private int _completedSubTaskNumber;
public double ProgressBarValue
{
diff --git a/src/PipManager/Models/Pages/LibraryInstallPackageItem.cs b/src/PipManager/Models/Pages/LibraryInstallPackageItem.cs
index 828348c..e1b0ea2 100644
--- a/src/PipManager/Models/Pages/LibraryInstallPackageItem.cs
+++ b/src/PipManager/Models/Pages/LibraryInstallPackageItem.cs
@@ -3,8 +3,8 @@
public class LibraryInstallPackageItem
{
public string? PackageName { get; set; }
- public bool VersionSpecified { get; set; } = false;
- public string VersionSpecifiedType { get; set; } = "~=";
+ public bool VersionSpecified { get; set; }
+ public string? DistributionFilePath { get; set; } // Distribution Install Only
public string TargetVersion { get; set; } = string.Empty;
public List? AvailableVersions { get; set; }
}
\ No newline at end of file
diff --git a/src/PipManager/PipManager.csproj b/src/PipManager/PipManager.csproj
index cb198a3..c8ebb3d 100644
--- a/src/PipManager/PipManager.csproj
+++ b/src/PipManager/PipManager.csproj
@@ -29,6 +29,7 @@
+
@@ -36,11 +37,12 @@
+
-
+
-
+
@@ -98,10 +100,6 @@
-
-
-
-
diff --git a/src/PipManager/Services/Action/ActionService.cs b/src/PipManager/Services/Action/ActionService.cs
index 318bc41..7cfd836 100644
--- a/src/PipManager/Services/Action/ActionService.cs
+++ b/src/PipManager/Services/Action/ActionService.cs
@@ -6,13 +6,14 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
+using Meziantou.Framework.WPF.Collections;
namespace PipManager.Services.Action;
public class ActionService(IEnvironmentService environmentService, IToastService toastService)
: IActionService
{
- public ObservableCollection ActionList { get; set; } = [];
+ public ConcurrentObservableCollection ActionList { get; set; } = [];
public ObservableCollection ExceptionList { get; set; } = [];
public void AddOperation(ActionListItem actionListItem)
@@ -21,9 +22,19 @@ public void AddOperation(ActionListItem actionListItem)
ActionList.Add(actionListItem);
}
+ public string TryCancelOperation(string operationId)
+ {
+ var targetAction = ActionList.ToList().FindIndex(action => action.OperationId == operationId);
+ if (ActionList[targetAction].OperationStatus != Lang.Action_CurrentStatus_WaitingInQueue)
+ {
+ return Lang.Action_OperationCanceled_AlreadyRunning;
+ }
+ ActionList.Remove(ActionList[targetAction]);
+ return Lang.Action_OperationCanceled_Success;
+ }
+
public void Runner()
{
- // TODO: Refactor (FUCKING MESSY COMMAND and output)
while (true)
{
if (ActionList.Count > 0)
@@ -31,18 +42,27 @@ public void Runner()
var errorDetection = false;
var consoleError = new StringBuilder(512);
var currentAction = ActionList[0];
- currentAction.ConsoleOutput = Lang.Action_CurrentStatus_WaitingInQueue + '\n';
+ var currentActionRunning = false;
+ currentAction.ConsoleOutput = Lang.Action_CurrentStatus_WaitingInQueue;
switch (currentAction.OperationType)
{
case ActionType.Uninstall:
{
- var queue = currentAction.OperationCommand.Split(' ');
+ var queue = currentAction.OperationCommand;
foreach (var item in queue)
{
currentAction.OperationStatus = $"Uninstalling {item}";
- var result = environmentService.Uninstall(item, (sender, eventArgs) =>
+ var result = environmentService.Uninstall(item, (_, eventArgs) =>
{
- currentAction.ConsoleOutput += string.IsNullOrEmpty(eventArgs.Data) ? Lang.Action_CurrentStatus_WaitingInQueue : eventArgs.Data.Trim() + '\n';
+ if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput = eventArgs.Data.Trim();
+ currentActionRunning = true;
+ }
+ else if (!string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim();
+ }
});
currentAction.CompletedSubTaskNumber++;
Log.Information(result.Success
@@ -55,14 +75,22 @@ public void Runner()
case ActionType.Install:
{
- var queue = currentAction.OperationCommand.Split(' ');
+ var queue = currentAction.OperationCommand;
foreach (var item in queue)
{
currentAction.OperationStatus = $"Installing {item}";
- var result = environmentService.Install(item, (sender, eventArgs) =>
+ var result = environmentService.Install(item, (_, eventArgs) =>
{
- currentAction.ConsoleOutput += string.IsNullOrEmpty(eventArgs.Data) ? Lang.Action_CurrentStatus_WaitingInQueue : eventArgs.Data.Trim() + '\n';
- });
+ if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput = eventArgs.Data.Trim();
+ currentActionRunning = true;
+ }
+ else if (!string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim();
+ }
+ }, extraParameters: currentAction.ExtraParameters);
currentAction.CompletedSubTaskNumber++;
if (!result.Success)
{
@@ -80,11 +108,19 @@ public void Runner()
case ActionType.InstallByRequirements:
{
var requirementsTempFilePath = Path.Combine(AppInfo.CachesDir, $"temp_install_requirements_{currentAction.OperationId}.txt");
- File.WriteAllText(requirementsTempFilePath, currentAction.OperationCommand);
- currentAction.OperationStatus = $"Installing from requirements.txt";
- var result = environmentService.InstallByRequirements(requirementsTempFilePath, (sender, eventArgs) =>
+ File.WriteAllText(requirementsTempFilePath, currentAction.OperationCommand[0]);
+ currentAction.OperationStatus = "Installing from requirements.txt";
+ var result = environmentService.InstallByRequirements(requirementsTempFilePath, (_, eventArgs) =>
{
- currentAction.ConsoleOutput += string.IsNullOrEmpty(eventArgs.Data) ? Lang.Action_CurrentStatus_WaitingInQueue : eventArgs.Data.Trim() + '\n';
+ if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput = eventArgs.Data.Trim();
+ currentActionRunning = true;
+ }
+ else if (!string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim();
+ }
});
if (!result.Success)
{
@@ -97,13 +133,21 @@ public void Runner()
}
case ActionType.Download:
{
- var queue = currentAction.OperationCommand.Split(' ');
+ var queue = currentAction.OperationCommand;
foreach (var item in queue)
{
currentAction.OperationStatus = $"Downloading {item}";
- var result = environmentService.Download(item, currentAction.Path, (sender, eventArgs) =>
+ var result = environmentService.Download(item, currentAction.Path, (_, eventArgs) =>
{
- currentAction.ConsoleOutput += string.IsNullOrEmpty(eventArgs.Data) ? Lang.Action_CurrentStatus_WaitingInQueue : eventArgs.Data.Trim() + '\n';
+ if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput = eventArgs.Data.Trim();
+ currentActionRunning = true;
+ }
+ else if (!string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim();
+ }
}, extraParameters: currentAction.ExtraParameters);
currentAction.CompletedSubTaskNumber++;
if (!result.Success)
@@ -121,13 +165,21 @@ public void Runner()
}
case ActionType.Update:
{
- var queue = currentAction.OperationCommand.Split(' ');
+ var queue = currentAction.OperationCommand;
foreach (var item in queue)
{
currentAction.OperationStatus = $"Updating {item}";
- var result = environmentService.Update(item, (sender, eventArgs) =>
+ var result = environmentService.Update(item, (_, eventArgs) =>
{
- currentAction.ConsoleOutput += string.IsNullOrEmpty(eventArgs.Data) ? Lang.Action_CurrentStatus_WaitingInQueue : eventArgs.Data.Trim() + '\n';
+ if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput = eventArgs.Data.Trim();
+ currentActionRunning = true;
+ }
+ else if (!string.IsNullOrEmpty(eventArgs.Data))
+ {
+ currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim();
+ }
});
currentAction.CompletedSubTaskNumber++;
if (!result.Success)
@@ -154,7 +206,7 @@ public void Runner()
{
toastService.Error(Lang.Action_IssueDetectedToast);
});
- currentAction.ConsoleError = consoleError.ToString();
+ currentAction.ConsoleError = consoleError.ToString().TrimEnd();
ExceptionList.Add(currentAction);
}
Thread.Sleep(100);
diff --git a/src/PipManager/Services/Action/IActionService.cs b/src/PipManager/Services/Action/IActionService.cs
index 8b2592d..fc43d29 100644
--- a/src/PipManager/Services/Action/IActionService.cs
+++ b/src/PipManager/Services/Action/IActionService.cs
@@ -1,14 +1,17 @@
-using PipManager.Models.Action;
+using Meziantou.Framework.WPF.Collections;
+using PipManager.Models.Action;
using System.Collections.ObjectModel;
namespace PipManager.Services.Action;
public interface IActionService
{
- public ObservableCollection ActionList { get; set; }
+ public ConcurrentObservableCollection ActionList { get; set; }
public ObservableCollection ExceptionList { get; set; }
public void AddOperation(ActionListItem actionListItem);
+ public string? TryCancelOperation(string operationId);
+
public void Runner();
}
\ No newline at end of file
diff --git a/src/PipManager/Services/Environment/EnvironmentService.cs b/src/PipManager/Services/Environment/EnvironmentService.cs
index 8b4b545..573cb8c 100644
--- a/src/PipManager/Services/Environment/EnvironmentService.cs
+++ b/src/PipManager/Services/Environment/EnvironmentService.cs
@@ -36,6 +36,30 @@ public ActionResponse CheckEnvironmentAvailable(EnvironmentItem environmentItem)
: new ActionResponse { Success = false, Exception = ExceptionType.Environment_Broken };
}
+ public ActionResponse PurgeEnvironmentCache(EnvironmentItem environmentItem)
+ {
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath,
+ Arguments = "-m pip cache purge",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ }
+ };
+ process.Start();
+ var output = process.StandardOutput.ReadToEnd();
+ var error = process.StandardError.ReadToEnd();
+ process.WaitForExit();
+ process.Close();
+ process.Dispose();
+ error = error.Replace("WARNING: No matching packages", "").Trim();
+ return !string.IsNullOrEmpty(error) ? new ActionResponse { Success = false, Exception = ExceptionType.Process_Error, Message = error } : new ActionResponse { Success = true, Message = output[15..].TrimEnd()};
+ }
+
public async Task?> GetLibraries()
{
if (configurationService.AppConfig.CurrentEnvironment is null)
@@ -205,7 +229,6 @@ public async Task GetVersions(string packageName)
packageName = PackageNameNormalizerRegex().Replace(packageName, "-").ToLower();
if (!PackageNameVerificationRegex().IsMatch(packageName))
return new GetVersionsResponse { Status = 2, Versions = [] };
- var sth = $"{configurationService.GetUrlFromPackageSourceType("pypi")}{packageName}/json";
var responseMessage =
await _httpClient.GetAsync($"{configurationService.GetUrlFromPackageSourceType("pypi")}{packageName}/json");
var response = await responseMessage.Content.ReadAsStringAsync();
@@ -214,13 +237,13 @@ public async Task GetVersions(string packageName)
?.Releases?
.Where(item => item.Value.Count != 0).OrderBy(e => e.Value[0].UploadTime)
.ThenBy(e => e.Value[0].UploadTime).ToDictionary(pair => pair.Key, pair => pair.Value);
- if (pypiPackageInfo == null || pypiPackageInfo?.Count == 0)
+ if (pypiPackageInfo == null || pypiPackageInfo.Count == 0)
{
Log.Warning($"[EnvironmentService] {packageName} package not found");
return new GetVersionsResponse { Status = 1, Versions = [] };
}
Log.Information($"[EnvironmentService] Found {packageName}");
- return new GetVersionsResponse { Status = 0, Versions = pypiPackageInfo?.Keys.ToArray() };
+ return new GetVersionsResponse { Status = 0, Versions = pypiPackageInfo.Keys.ToArray() };
}
catch (Exception)
{
@@ -229,15 +252,16 @@ public async Task GetVersions(string packageName)
}
}
- public ActionResponse Install(string packageName, DataReceivedEventHandler consoleOutputCallback)
+ public ActionResponse Install(string packageName, DataReceivedEventHandler consoleOutputCallback, string[]? extraParameters = null)
{
+ string? extra = extraParameters != null ? string.Join(" ", extraParameters) : null;
var process = new Process
{
StartInfo = new ProcessStartInfo
{
- FileName = configurationService.AppConfig!.CurrentEnvironment!.PythonPath,
+ FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath,
Arguments =
- $"-m pip install \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6",
+ $"-m pip install \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6 {extra}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
@@ -260,7 +284,7 @@ public ActionResponse InstallByRequirements(string requirementsFilePath, DataRec
{
StartInfo = new ProcessStartInfo
{
- FileName = configurationService.AppConfig!.CurrentEnvironment!.PythonPath,
+ FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath,
Arguments =
$"-m pip install -r \"{requirementsFilePath}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6",
UseShellExecute = false,
@@ -286,7 +310,7 @@ public ActionResponse Download(string packageName, string downloadPath, DataRece
{
StartInfo = new ProcessStartInfo
{
- FileName = configurationService.AppConfig!.CurrentEnvironment!.PythonPath,
+ FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath,
Arguments =
$"-m pip download -d \"{downloadPath}\" \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6 {extra}",
UseShellExecute = false,
@@ -311,7 +335,7 @@ public ActionResponse Update(string packageName, DataReceivedEventHandler consol
{
StartInfo = new ProcessStartInfo
{
- FileName = configurationService.AppConfig!.CurrentEnvironment!.PythonPath,
+ FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath,
Arguments =
$"-m pip install --upgrade \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6",
UseShellExecute = false,
@@ -336,7 +360,7 @@ public ActionResponse Uninstall(string packageName, DataReceivedEventHandler con
{
StartInfo = new ProcessStartInfo
{
- FileName = configurationService.AppConfig!.CurrentEnvironment!.PythonPath,
+ FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath,
Arguments = $"-m pip uninstall -y \"{packageName}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
diff --git a/src/PipManager/Services/Environment/IEnvironmentService.cs b/src/PipManager/Services/Environment/IEnvironmentService.cs
index ba5f5c0..9267e32 100644
--- a/src/PipManager/Services/Environment/IEnvironmentService.cs
+++ b/src/PipManager/Services/Environment/IEnvironmentService.cs
@@ -11,11 +11,13 @@ public interface IEnvironmentService
public ActionResponse CheckEnvironmentAvailable(EnvironmentItem environmentItem);
+ public ActionResponse PurgeEnvironmentCache(EnvironmentItem environmentItem);
+
public Task?> GetLibraries();
public Task GetVersions(string packageName);
- public ActionResponse Install(string packageName, DataReceivedEventHandler consoleOutputCallback);
+ public ActionResponse Install(string packageName, DataReceivedEventHandler consoleOutputCallback, string[]? extraParameters = null);
public ActionResponse InstallByRequirements(string requirementsFilePath, DataReceivedEventHandler consoleOutputCallback);
diff --git a/src/PipManager/Services/Mask/IMaskService.cs b/src/PipManager/Services/Mask/IMaskService.cs
index 97ff839..e452d74 100644
--- a/src/PipManager/Services/Mask/IMaskService.cs
+++ b/src/PipManager/Services/Mask/IMaskService.cs
@@ -1,5 +1,4 @@
using PipManager.Controls.Mask;
-using PipManager.Models.Action;
namespace PipManager.Services.Mask;
@@ -7,8 +6,6 @@ public interface IMaskService
{
public void SetMaskPresenter(MaskPresenter maskPresenter);
- public MaskPresenter GetMaskPresenter();
-
public void Show(string message = "");
public void Hide();
diff --git a/src/PipManager/Services/Mask/MaskService.cs b/src/PipManager/Services/Mask/MaskService.cs
index 98c317d..b44e18c 100644
--- a/src/PipManager/Services/Mask/MaskService.cs
+++ b/src/PipManager/Services/Mask/MaskService.cs
@@ -1,6 +1,5 @@
using PipManager.Controls.Mask;
using PipManager.Languages;
-using PipManager.Models.Action;
using System.Windows.Controls;
namespace PipManager.Services.Mask;
@@ -16,12 +15,11 @@ public void SetMaskPresenter(MaskPresenter maskPresenter)
_grid = Application.Current.TryFindResource("MaskGrid") as Grid;
}
- public MaskPresenter GetMaskPresenter() => _presenter ?? throw new ArgumentNullException("The MaskPresenter didn't set previously.");
public void Show(string message = "")
{
if (_presenter == null || _grid == null)
- throw new ArgumentNullException("The MaskPresenter didn't set previously.");
+ throw new ArgumentNullException($"The MaskPresenter didn't set previously.");
((_grid.Children[0] as StackPanel)!.Children[1] as TextBlock)!.Text = Lang.Mask_Loading;
((_grid.Children[0] as StackPanel)!.Children[2] as TextBlock)!.Text = message;
_ = _presenter.ShowGrid(_grid);
@@ -30,7 +28,7 @@ public void Show(string message = "")
public void Hide()
{
if (_presenter == null)
- throw new ArgumentNullException("The MaskPresenter didn't set previously.");
+ throw new ArgumentNullException($"The MaskPresenter didn't set previously.");
_ = _presenter.HideGrid();
}
}
\ No newline at end of file
diff --git a/src/PipManager/ViewModels/Pages/About/AboutViewModel.cs b/src/PipManager/ViewModels/Pages/About/AboutViewModel.cs
index 1bcceae..c63e46d 100644
--- a/src/PipManager/ViewModels/Pages/About/AboutViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/About/AboutViewModel.cs
@@ -30,25 +30,46 @@ private void InitializeViewModel()
ExperimentMode = configurationService.ExperimentMode;
AppVersion = AppInfo.AppVersion;
_isInitialized = true;
+ NugetLibraryList =
+ [
+ new AboutNugetLibraryListItem("Antelcat.I18N.WPF", "MIT", "Copyright (c) 2023 Feast",
+ "https://github.com/Antelcat/Antelcat.I18N"),
+ new AboutNugetLibraryListItem("CommunityToolkit.Mvvm", "MIT",
+ "Copyright © .NET Foundation and Contributors", "https://github.com/CommunityToolkit/dotnet"),
+ new AboutNugetLibraryListItem("HtmlAgilityPack", "MIT", "Copyright © ZZZ Projects Inc.",
+ "https://github.com/zzzprojects/html-agility-pack"),
+ new AboutNugetLibraryListItem("Meziantou.Framework.WPF", "MIT", "Copyright (c) 2019 Gérald Barré",
+ "https://github.com/meziantou/Meziantou.Framework"),
+ new AboutNugetLibraryListItem("Microsoft.Extensions.Hosting", "MIT",
+ "Copyright © .NET Foundation and Contributors", "https://github.com/dotnet/runtime"),
+ new AboutNugetLibraryListItem("Microsoft.Web.WebView2", "Custom License",
+ "© Microsoft Corporation. All rights reserved.", "https://github.com/dotnet/runtime"),
+ new AboutNugetLibraryListItem("Microsoft.Xaml.Behaviors.Wpf", "MIT", "Copyright (c) 2015 Microsoft",
+ "https://github.com/microsoft/XamlBehaviorsWpf"),
+ new AboutNugetLibraryListItem("Newtonsoft.Json", "MIT", "Copyright (c) 2007 James Newton-King",
+ "https://github.com/JamesNK/Newtonsoft.Json"),
+ new AboutNugetLibraryListItem("Serilog", "Apache-2.0", "Copyright © 2013-2020 Serilog Contributors",
+ "https://github.com/serilog/serilog"),
+ new AboutNugetLibraryListItem("Serilog.Extensions.Logging", "Apache-2.0",
+ "Copyright © 2013-2020 Serilog Contributors", "https://github.com/serilog/serilog-extensions-logging"),
+ new AboutNugetLibraryListItem("Serilog.Sinks.Console", "Apache-2.0",
+ "Copyright © 2016 Serilog Contributors", "https://github.com/serilog/serilog-sinks-console"),
+ new AboutNugetLibraryListItem("Serilog.Sinks.File", "Apache-2.0", "Copyright © 2016 Serilog Contributors",
+ "https://github.com/serilog/serilog-sinks-file"),
+ new AboutNugetLibraryListItem("SharpZipLib", "MIT", "Copyright © 2000-2018 SharpZipLib Contributors",
+ "https://github.com/icsharpcode/SharpZipLib"),
+ new AboutNugetLibraryListItem("ValueConverters", "MIT", "Copyright (c) 2019 Thomas Galliker",
+ "https://github.com/thomasgalliker/ValueConverters.NET"),
+ new AboutNugetLibraryListItem("WPF-UI", "MIT",
+ "Copyright (c) 2021-2023 Leszek Pomianowski and WPF UI Contributors",
+ "https://github.com/lepoco/wpfui"),
+ new AboutNugetLibraryListItem("WPF-UI.Tray", "MIT",
+ "Copyright (c) 2021-2023 Leszek Pomianowski and WPF UI Contributors",
+ "https://github.com/lepoco/wpfui"),
+ ];
Log.Information("[About] Initialized");
}
[ObservableProperty]
- private ObservableCollection _nugetLibraryList =
- [
- new AboutNugetLibraryListItem("Antelcat.I18N.WPF", "MIT", "Copyright (c) 2023 Feast", "https://github.com/Antelcat/Antelcat.I18N"),
- new AboutNugetLibraryListItem("CommunityToolkit.Mvvm", "MIT", "Copyright © .NET Foundation and Contributors", "https://github.com/CommunityToolkit/dotnet"),
- new AboutNugetLibraryListItem("HtmlAgilityPack", "MIT", "Copyright © ZZZ Projects Inc.", "https://github.com/zzzprojects/html-agility-pack"),
- new AboutNugetLibraryListItem("Microsoft.Extensions.Hosting", "MIT", "Copyright © .NET Foundation and Contributors", "https://github.com/dotnet/runtime"),
- new AboutNugetLibraryListItem("Microsoft.Web.WebView2", "Custom License", "© Microsoft Corporation. All rights reserved.", "https://github.com/dotnet/runtime"),
- new AboutNugetLibraryListItem("Microsoft.Xaml.Behaviors.Wpf", "MIT", "Copyright (c) 2015 Microsoft", "https://github.com/microsoft/XamlBehaviorsWpf"),
- new AboutNugetLibraryListItem("Newtonsoft.Json", "MIT", "Copyright (c) 2007 James Newton-King", "https://github.com/JamesNK/Newtonsoft.Json"),
- new AboutNugetLibraryListItem("Serilog", "Apache-2.0", "Copyright © 2013-2020 Serilog Contributors", "https://github.com/serilog/serilog"),
- new AboutNugetLibraryListItem("Serilog.Extensions.Logging", "Apache-2.0", "Copyright © 2013-2020 Serilog Contributors", "https://github.com/serilog/serilog-extensions-logging"),
- new AboutNugetLibraryListItem("Serilog.Sinks.Console", "Apache-2.0", "Copyright © 2016 Serilog Contributors", "https://github.com/serilog/serilog-sinks-console"),
- new AboutNugetLibraryListItem("Serilog.Sinks.File", "Apache-2.0", "Copyright © 2016 Serilog Contributors", "https://github.com/serilog/serilog-sinks-file"),
- new AboutNugetLibraryListItem("ValueConverters", "MIT", "Copyright (c) 2019 Thomas Galliker", "https://github.com/thomasgalliker/ValueConverters.NET"),
- new AboutNugetLibraryListItem("WPF-UI", "MIT", "Copyright (c) 2021-2023 Leszek Pomianowski and WPF UI Contributors", "https://github.com/lepoco/wpfui"),
- new AboutNugetLibraryListItem("WPF-UI.Tray", "MIT", "Copyright (c) 2021-2023 Leszek Pomianowski and WPF UI Contributors", "https://github.com/lepoco/wpfui"),
- ];
+ private ObservableCollection _nugetLibraryList = [];
}
\ No newline at end of file
diff --git a/src/PipManager/ViewModels/Pages/Action/ActionExceptionViewModel.cs b/src/PipManager/ViewModels/Pages/Action/ActionExceptionViewModel.cs
index 389deff..d8466d7 100644
--- a/src/PipManager/ViewModels/Pages/Action/ActionExceptionViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Action/ActionExceptionViewModel.cs
@@ -5,6 +5,8 @@
using Serilog;
using System.Collections.ObjectModel;
using System.Diagnostics;
+using System.IO;
+using System.Text;
using System.Web;
using Wpf.Ui.Controls;
@@ -44,9 +46,28 @@ private void InitializeViewModel()
Log.Information("[Action][Exceptions] Initialized");
}
- public void UpdateActionExceptionList()
+ private void UpdateActionExceptionList()
{
Exceptions = new ObservableCollection(_actionService.ExceptionList);
+ Log.Information("[Action][Exceptions] Exception List updated ({Count} items)", Exceptions.Count);
+ }
+
+ private static string ExceptionFilter(string parameter)
+ {
+ var exceptionBuilder = new StringBuilder();
+ using (StringReader reader = new (parameter))
+ {
+ while (reader.ReadLine() is { } line)
+ {
+ line = line.Trim();
+ if (line.StartsWith("ERROR"))
+ {
+ exceptionBuilder.Append(line).Append(' ');
+ }
+ }
+ }
+
+ return exceptionBuilder.ToString();
}
[RelayCommand]
@@ -54,7 +75,7 @@ private static void ExceptionBingSearch(string? parameter)
{
if (parameter != null)
{
- Process.Start(new ProcessStartInfo($"https://bing.com/search?q={HttpUtility.UrlEncode(parameter)}") { UseShellExecute = true });
+ Process.Start(new ProcessStartInfo($"https://bing.com/search?q={HttpUtility.UrlEncode(ExceptionFilter(parameter))}") { UseShellExecute = true });
}
}
@@ -63,17 +84,20 @@ private static void ExceptionGoogleSearch(string? parameter)
{
if (parameter != null)
{
- Process.Start(new ProcessStartInfo($"https://www.google.com/search?q={HttpUtility.UrlEncode(parameter)}") { UseShellExecute = true });
+ Process.Start(new ProcessStartInfo($"https://www.google.com/search?q={HttpUtility.UrlEncode(ExceptionFilter(parameter))}") { UseShellExecute = true });
}
}
[RelayCommand]
private void ExceptionCopyToClipboard(string? parameter)
{
- if (parameter != null)
+ if (parameter == null)
{
- Clipboard.SetDataObject(parameter);
- _toastService.Success(Lang.ActionException_CopyToClipboardNotice);
+ return;
}
+
+ Clipboard.SetDataObject(ExceptionFilter(parameter));
+ _toastService.Success(Lang.ActionException_CopyToClipboardNotice);
+ Log.Information("[Action][Exceptions] Copied exception to clipboard");
}
}
\ No newline at end of file
diff --git a/src/PipManager/ViewModels/Pages/Action/ActionViewModel.cs b/src/PipManager/ViewModels/Pages/Action/ActionViewModel.cs
index 7c7367e..bc4300c 100644
--- a/src/PipManager/ViewModels/Pages/Action/ActionViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Action/ActionViewModel.cs
@@ -1,8 +1,10 @@
-using PipManager.Models.Action;
+using Meziantou.Framework.WPF.Collections;
+using PipManager.Languages;
+using PipManager.Models.Action;
using PipManager.Services.Action;
+using PipManager.Services.Toast;
using PipManager.Views.Pages.Action;
using Serilog;
-using System.Collections.ObjectModel;
using Wpf.Ui;
using Wpf.Ui.Controls;
@@ -13,15 +15,17 @@ public partial class ActionViewModel : ObservableObject, INavigationAware
private bool _isInitialized;
[ObservableProperty]
- private ObservableCollection _actions;
+ private ConcurrentObservableCollection _actions;
private readonly IActionService _actionService;
+ private readonly IToastService _toastService;
private readonly INavigationService _navigationService;
- public ActionViewModel(IActionService actionService, INavigationService navigationService)
+ public ActionViewModel(IActionService actionService, INavigationService navigationService, IToastService toastService)
{
_actionService = actionService;
_navigationService = navigationService;
+ _toastService = toastService;
Actions = _actionService.ActionList;
}
@@ -46,4 +50,25 @@ private void ShowExceptions()
{
_navigationService.NavigateWithHierarchy(typeof(ActionExceptionPage));
}
+
+ [RelayCommand]
+ private void CancelAction(string? operationId)
+ {
+ if (string.IsNullOrEmpty(operationId))
+ {
+ return;
+ }
+
+ var result = _actionService.TryCancelOperation(operationId);
+ if(result == Lang.Action_OperationCanceled_AlreadyRunning)
+ {
+ _toastService.Error(Lang.Action_OperationCanceled_AlreadyRunning);
+ Log.Warning("[Action] Operation cancellation failed (already running): {OperationId}", operationId);
+ }
+ else
+ {
+ _toastService.Success(Lang.Action_OperationCanceled_Success);
+ Log.Information("[Action] Operation canceled: {OperationId}", operationId);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/PipManager/ViewModels/Pages/Environment/AddEnvironmentViewModel.cs b/src/PipManager/ViewModels/Pages/Environment/AddEnvironmentViewModel.cs
index d968b98..0aa1738 100644
--- a/src/PipManager/ViewModels/Pages/Environment/AddEnvironmentViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Environment/AddEnvironmentViewModel.cs
@@ -14,10 +14,6 @@ namespace PipManager.ViewModels.Pages.Environment;
public partial class AddEnvironmentViewModel(INavigationService navigationService, IConfigurationService configurationService, IEnvironmentService environmentService, IToastService toastService) : ObservableObject, INavigationAware
{
private bool _isInitialized;
- private readonly INavigationService _navigationService = navigationService;
- private readonly IConfigurationService _configurationService = configurationService;
- private readonly IEnvironmentService _environmentService = environmentService;
- private readonly IToastService _toastService = toastService;
public void OnNavigatedTo()
{
@@ -77,7 +73,7 @@ await Task.Run(() =>
if (!File.Exists(Path.Combine(item, "python.exe")))
continue;
var environmentItem =
- _configurationService.GetEnvironmentItem(Path.Combine(item, "python.exe"));
+ configurationService.GetEnvironmentItem(Path.Combine(item, "python.exe"));
if (environmentItem == null) continue;
EnvironmentItems.Add(environmentItem);
}
@@ -133,83 +129,83 @@ private void AddEnvironment(string parameter)
switch (ByWay)
{
case 0 when EnvironmentItemInList == null:
- _toastService.Error(Lang.ContentDialog_Message_EnvironmentNoSelection);
+ toastService.Error(Lang.ContentDialog_Message_EnvironmentNoSelection);
break;
case 0:
{
- var result = _environmentService.CheckEnvironmentAvailable(EnvironmentItemInList);
- var alreadyExists = _environmentService.CheckEnvironmentExists(EnvironmentItemInList);
+ var result = environmentService.CheckEnvironmentAvailable(EnvironmentItemInList);
+ var alreadyExists = environmentService.CheckEnvironmentExists(EnvironmentItemInList);
if (result.Success)
{
if (alreadyExists)
{
- _toastService.Error(Lang.ContentDialog_Message_EnvironmentAlreadyExists);
+ toastService.Error(Lang.ContentDialog_Message_EnvironmentAlreadyExists);
}
else
{
- _configurationService.AppConfig.CurrentEnvironment = EnvironmentItemInList;
- _configurationService.AppConfig.EnvironmentItems.Add(EnvironmentItemInList);
- _configurationService.Save();
+ configurationService.AppConfig.CurrentEnvironment = EnvironmentItemInList;
+ configurationService.AppConfig.EnvironmentItems.Add(EnvironmentItemInList);
+ configurationService.Save();
Log.Information($"[AddEnvironment] Environment added ({EnvironmentItemInList.PipVersion} for {EnvironmentItemInList.PythonVersion})");
- _navigationService.GoBack();
+ navigationService.GoBack();
}
}
else
{
- _toastService.Error(result.Message);
+ toastService.Error(result.Message);
}
break;
}
case 1:
{
- var result = _configurationService.GetEnvironmentItemFromCommand(PipCommand, "-V");
+ var result = configurationService.GetEnvironmentItemFromCommand(PipCommand, "-V");
if (result != null)
{
- var alreadyExists = _environmentService.CheckEnvironmentExists(result);
+ var alreadyExists = environmentService.CheckEnvironmentExists(result);
if (alreadyExists)
{
- _toastService.Error(Lang.ContentDialog_Message_EnvironmentAlreadyExists);
+ toastService.Error(Lang.ContentDialog_Message_EnvironmentAlreadyExists);
}
else
{
- _configurationService.AppConfig.CurrentEnvironment = result;
- _configurationService.AppConfig.EnvironmentItems.Add(result);
+ configurationService.AppConfig.CurrentEnvironment = result;
+ configurationService.AppConfig.EnvironmentItems.Add(result);
Log.Information($"[AddEnvironment] Environment added ({result.PipVersion} for {result.PythonVersion})");
- _configurationService.Save();
- _navigationService.GoBack();
+ configurationService.Save();
+ navigationService.GoBack();
}
}
else
{
- _toastService.Error(Lang.ContentDialog_Message_EnvironmentInvaild);
+ toastService.Error(Lang.ContentDialog_Message_EnvironmentInvaild);
}
break;
}
case 2:
{
- var result = _configurationService.GetEnvironmentItem(PythonPath);
+ var result = configurationService.GetEnvironmentItem(PythonPath);
if (result != null)
{
- var alreadyExists = _environmentService.CheckEnvironmentExists(result);
+ var alreadyExists = environmentService.CheckEnvironmentExists(result);
if (alreadyExists)
{
- _toastService.Error(Lang.ContentDialog_Message_EnvironmentAlreadyExists);
+ toastService.Error(Lang.ContentDialog_Message_EnvironmentAlreadyExists);
}
else
{
- _configurationService.AppConfig.CurrentEnvironment = result;
- _configurationService.AppConfig.EnvironmentItems.Add(result);
+ configurationService.AppConfig.CurrentEnvironment = result;
+ configurationService.AppConfig.EnvironmentItems.Add(result);
Log.Information($"[AddEnvironment] Environment added ({result.PipVersion} for {result.PythonVersion})");
- _configurationService.Save();
- _navigationService.GoBack();
+ configurationService.Save();
+ navigationService.GoBack();
}
}
else
{
- _toastService.Error(Lang.ContentDialog_Message_EnvironmentInvaild);
+ toastService.Error(Lang.ContentDialog_Message_EnvironmentInvaild);
}
break;
diff --git a/src/PipManager/ViewModels/Pages/Environment/EnvironmentViewModel.cs b/src/PipManager/ViewModels/Pages/Environment/EnvironmentViewModel.cs
index 7eaca89..c87d8a6 100644
--- a/src/PipManager/ViewModels/Pages/Environment/EnvironmentViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Environment/EnvironmentViewModel.cs
@@ -36,13 +36,15 @@ public void OnNavigatedTo()
var currentEnvironment = configurationService.AppConfig.CurrentEnvironment;
foreach (var environmentItem in EnvironmentItems)
{
- if (currentEnvironment is not null && environmentItem.PythonPath == currentEnvironment.PythonPath)
+ if (currentEnvironment is null || environmentItem.PythonPath != currentEnvironment.PythonPath)
{
- CurrentEnvironment = environmentItem;
- var mainWindowViewModel = App.GetService();
- mainWindowViewModel.ApplicationTitle = $"Pip Manager | {CurrentEnvironment.PipVersion} for {CurrentEnvironment.PythonVersion}";
- Log.Information($"[Environment] Current Environment changed: {CurrentEnvironment.PythonPath}");
+ continue;
}
+
+ CurrentEnvironment = environmentItem;
+ var mainWindowViewModel = App.GetService();
+ mainWindowViewModel.ApplicationTitle = $"Pip Manager | {CurrentEnvironment.PipVersion} for {CurrentEnvironment.PythonVersion}";
+ Log.Information($"[Environment] Current Environment changed: {CurrentEnvironment.PythonPath}");
}
}
@@ -64,7 +66,7 @@ private void InitializeViewModel()
private bool _environmentSelected;
[RelayCommand]
- private async Task DeleteEnvironmentAsync()
+ private async Task DeleteEnvironment()
{
var result = await contentDialogService.ShowSimpleDialogAsync(
ContentDialogCreateOptions.Warning(Lang.ContentDialog_Message_EnvironmentDeletion,
@@ -74,10 +76,10 @@ private async Task DeleteEnvironmentAsync()
EnvironmentItems.Remove(CurrentEnvironment!);
CurrentEnvironment = null;
configurationService.AppConfig.CurrentEnvironment = null;
- configurationService.AppConfig.EnvironmentItems = new List(EnvironmentItems);
+ configurationService.AppConfig.EnvironmentItems = [..EnvironmentItems];
configurationService.Save();
var mainWindowViewModel = App.GetService();
- mainWindowViewModel.ApplicationTitle = $"Pip Manager";
+ mainWindowViewModel.ApplicationTitle = "Pip Manager";
EnvironmentSelected = false;
}
@@ -98,7 +100,7 @@ private async Task CheckEnvironment()
Lang.ContentDialog_PrimaryButton_EnvironmentDeletion));
if (result == ContentDialogResult.Primary)
{
- await DeleteEnvironmentAsync();
+ await DeleteEnvironment();
}
}
}
@@ -129,7 +131,7 @@ await Task.Run(async () =>
actionService.AddOperation(new ActionListItem
(
ActionType.Update,
- "pip",
+ ["pip"],
progressIntermediate: false,
totalSubTaskNumber: 1
));
@@ -140,10 +142,28 @@ await Task.Run(async () =>
else if (latest == string.Empty)
{
toastService.Error(Lang.ContentDialog_Message_NetworkError);
+ Log.Error("[Environment] Network error while checking for updates (environment: {environment})", CurrentEnvironment!.PipVersion);
}
else
{
toastService.Info(Lang.ContentDialog_Message_EnvironmentIsLatest);
+ Log.Information("[Environment] Environment is already up to date (environment: {environment})", CurrentEnvironment!.PipVersion);
+ }
+ }
+
+ [RelayCommand]
+ private void ClearCache()
+ {
+ var result = environmentService.PurgeEnvironmentCache(CurrentEnvironment!);
+ if (result.Success)
+ {
+ Log.Information($"[Environment] Cache cleared ({CurrentEnvironment!.PipVersion} for {CurrentEnvironment.PythonVersion})");
+ toastService.Info(string.Format(Lang.ContentDialog_Message_CacheCleared, result.Message));
+ }
+ else
+ {
+ Log.Error($"[Environment] Cache clear failed ({CurrentEnvironment!.PipVersion} for {CurrentEnvironment.PythonVersion})");
+ toastService.Error(Lang.ContentDialog_Message_CacheClearFailed);
}
}
diff --git a/src/PipManager/ViewModels/Pages/Lab/LabViewModel.cs b/src/PipManager/ViewModels/Pages/Lab/LabViewModel.cs
index af3abce..f461853 100644
--- a/src/PipManager/ViewModels/Pages/Lab/LabViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Lab/LabViewModel.cs
@@ -16,7 +16,7 @@ private void ActionTest()
actionService.AddOperation(new ActionListItem
(
ActionType.Install,
- "114514==114",
+ ["pytorch"],
totalSubTaskNumber: 1,
progressIntermediate: false
));
diff --git a/src/PipManager/ViewModels/Pages/Library/LibraryDetailViewModel.cs b/src/PipManager/ViewModels/Pages/Library/LibraryDetailViewModel.cs
index 4bb0ea0..56a9712 100644
--- a/src/PipManager/ViewModels/Pages/Library/LibraryDetailViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Library/LibraryDetailViewModel.cs
@@ -3,7 +3,6 @@
using PipManager.Models.Package;
using PipManager.Models.Pages;
using System.Collections.ObjectModel;
-using Wpf.Ui;
using Wpf.Ui.Controls;
namespace PipManager.ViewModels.Pages.Library;
@@ -12,7 +11,6 @@ public partial class LibraryDetailViewModel : ObservableObject, INavigationAware
{
public record LibraryDetailMessage(PackageItem Package);
private bool _isInitialized;
- private readonly INavigationService _navigationService;
[ObservableProperty]
private PackageItem? _package;
@@ -38,9 +36,8 @@ public record LibraryDetailMessage(PackageItem Package);
#endregion Classifier
- public LibraryDetailViewModel(INavigationService navigationService)
+ public LibraryDetailViewModel()
{
- _navigationService = navigationService;
WeakReferenceMessenger.Default.Register(this, Receive);
}
diff --git a/src/PipManager/ViewModels/Pages/Library/LibraryInstallViewModel.cs b/src/PipManager/ViewModels/Pages/Library/LibraryInstallViewModel.cs
index ac83c7b..919cecc 100644
--- a/src/PipManager/ViewModels/Pages/Library/LibraryInstallViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Library/LibraryInstallViewModel.cs
@@ -11,6 +11,10 @@
using PipManager.Views.Pages.Action;
using System.Collections.ObjectModel;
using System.IO;
+using System.IO.Compression;
+using System.Text;
+using ICSharpCode.SharpZipLib.GZip;
+using ICSharpCode.SharpZipLib.Tar;
using Wpf.Ui;
using Wpf.Ui.Controls;
@@ -36,6 +40,7 @@ public LibraryInstallViewModel(IActionService actionService, IMaskService maskSe
_contentDialogService = contentDialogService;
_environmentService = environmentService;
_toastService = toastService;
+ _installWheelDependencies = false;
_navigationService = navigationService;
WeakReferenceMessenger.Default.Register(this, Receive);
}
@@ -44,6 +49,7 @@ public void OnNavigatedTo()
{
if (!_isInitialized)
InitializeViewModel();
+ InstallWheelDependencies = true;
}
public void OnNavigatedFrom()
@@ -118,7 +124,7 @@ private void AddDefaultToAction()
_actionService.AddOperation(new ActionListItem
(
ActionType.Install,
- string.Join(' ', operationCommand),
+ operationCommand.ToArray(),
totalSubTaskNumber: operationCommand.Count
));
PreInstallPackages.Clear();
@@ -172,7 +178,7 @@ private void AddRequirementsToAction()
_actionService.AddOperation(new ActionListItem
(
ActionType.InstallByRequirements,
- Requirements,
+ [Requirements],
displayCommand: "requirements.txt"
));
Requirements = "";
@@ -185,8 +191,8 @@ private void AddRequirementsToAction()
[ObservableProperty] private ObservableCollection _preDownloadPackages = [];
[ObservableProperty] private string _downloadDistributionsFolderPath = "";
- [ObservableProperty] private bool _downloadDistributionsEnabled = false;
- [ObservableProperty] private bool _downloadDependencies = false;
+ [ObservableProperty] private bool _downloadDistributionsEnabled;
+ [ObservableProperty] private bool _downloadWheelDependencies;
[RelayCommand]
private async Task DownloadDistributionsTask()
@@ -251,9 +257,9 @@ private void DownloadDistributionsToAction()
_actionService.AddOperation(new ActionListItem
(
ActionType.Download,
- string.Join(' ', operationCommand),
+ operationCommand.ToArray(),
path: DownloadDistributionsFolderPath,
- extraParameters: DownloadDependencies ? null : ["--no-deps"],
+ extraParameters: DownloadWheelDependencies ? null : ["--no-deps"],
totalSubTaskNumber: operationCommand.Count
));
PreDownloadPackages.Clear();
@@ -280,4 +286,145 @@ private void DeleteDownloadDistributionsTask(object? parameter)
}
#endregion Download Wheel File
+
+ #region Install via distributions
+ [ObservableProperty] private ObservableCollection _preInstallDistributions = [];
+ [ObservableProperty] private bool _installWheelDependencies;
+
+ [RelayCommand]
+ private async Task SelectDistributions()
+ {
+ var openFileDialog = new OpenFileDialog
+ {
+ Filter = "Wheel Files|*.whl;*.tar.gz",
+ Multiselect = true
+ };
+ if (openFileDialog.ShowDialog() != true)
+ {
+ return;
+ }
+
+ foreach (var fileName in openFileDialog.FileNames)
+ {
+ var packageName = "";
+ var packageVersion = "";
+ try
+ {
+ if (fileName.EndsWith(".whl"))
+ {
+ await using var wheelFileStream = new FileStream(fileName, FileMode.Open);
+ using var wheelFileArchive = new ZipArchive(wheelFileStream, ZipArchiveMode.Read);
+ foreach (ZipArchiveEntry entry in wheelFileArchive.Entries)
+ {
+ if (!entry.FullName.Contains(".dist-info/METADATA") || !entry.FullName.Contains("PKG-INFO"))
+ {
+ continue;
+ }
+
+ using var streamReader = new StreamReader(entry.Open());
+ while (await streamReader.ReadLineAsync() is { } line)
+ {
+ if (line.StartsWith("Name: "))
+ {
+ packageName = line[6..];
+ }
+ else if (line.StartsWith("Version: "))
+ {
+ packageVersion = line[9..];
+ break;
+ }
+ }
+ }
+ }
+ else if (fileName.EndsWith(".tar.gz"))
+ {
+ var inStream = File.OpenRead(fileName);
+ var gzipStream = new GZipInputStream(inStream);
+ var tarArchive = TarArchive.CreateInputTarArchive(gzipStream, Encoding.UTF8);
+ var randomizedDirectory = Path.Combine(AppInfo.CachesDir, $"tempTarGz-{Guid.NewGuid():N}");
+ tarArchive.ExtractContents(randomizedDirectory);
+ tarArchive.Close();
+ gzipStream.Close();
+ inStream.Close();
+ string targetDirectory = Directory.GetDirectories(randomizedDirectory)[0];
+
+ if (File.Exists(Path.Combine(targetDirectory, "PKG-INFO")))
+ {
+ using var streamReader = File.OpenText(Path.Combine(targetDirectory, "PKG-INFO"));
+ while (await streamReader.ReadLineAsync() is { } line)
+ {
+ if (line.StartsWith("Name: "))
+ {
+ packageName = line[6..];
+ }
+ else if (line.StartsWith("Version: "))
+ {
+ packageVersion = line[9..];
+ break;
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e) when (e is IOException or InvalidDataException)
+ {
+ _toastService.Error(Lang.LibraryInstall_InstallDistributions_IOError);
+ return;
+ }
+
+ if (packageName == "" || packageVersion == "")
+ {
+ _toastService.Error(Lang.LibraryInstall_InstallDistributions_InvalidFile);
+ return;
+ }
+
+ if (PreInstallDistributions.Any(item => item.PackageName == packageName))
+ {
+ _toastService.Error(Lang.LibraryInstall_InstallDistributions_AlreadyExists);
+ return;
+ }
+
+ PreInstallDistributions.Add(new LibraryInstallPackageItem
+ {
+ PackageName = packageName,
+ TargetVersion = packageVersion,
+ DistributionFilePath = fileName
+ });
+ }
+ }
+
+ [RelayCommand]
+ private void DeleteInstallDistributions(object? parameter)
+ {
+ var target = -1;
+ for (int index = 0; index < PreInstallDistributions.Count; index++)
+ {
+ if (ReferenceEquals(PreInstallDistributions[index].PackageName, parameter))
+ {
+ target = index;
+ }
+ }
+
+ if (target != -1)
+ {
+ PreInstallDistributions.RemoveAt(target);
+ }
+ }
+
+ [RelayCommand]
+ private void InstallDistributionsToAction()
+ {
+ List operationCommand = [];
+ operationCommand.AddRange(PreInstallDistributions.Select(preInstallPackage => preInstallPackage.DistributionFilePath)!);
+ _actionService.AddOperation(new ActionListItem
+ (
+ ActionType.Install,
+ operationCommand.ToArray(),
+ totalSubTaskNumber: operationCommand.Count,
+ extraParameters: DownloadWheelDependencies ? null : ["--no-deps"]
+ ));
+ PreInstallDistributions.Clear();
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/PipManager/ViewModels/Pages/Library/LibraryViewModel.cs b/src/PipManager/ViewModels/Pages/Library/LibraryViewModel.cs
index 9fe0121..2628ecb 100644
--- a/src/PipManager/ViewModels/Pages/Library/LibraryViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Library/LibraryViewModel.cs
@@ -94,7 +94,7 @@ private async Task DeletePackageAsync()
_actionService.AddOperation(new ActionListItem
(
ActionType.Uninstall,
- command.Trim(),
+ command.Trim().Split(' '),
progressIntermediate: false,
totalSubTaskNumber: selected.Count
));
@@ -141,7 +141,7 @@ await Task.Run(() =>
_actionService.AddOperation(new ActionListItem
(
ActionType.Update,
- operationList.Trim(),
+ operationList.Trim().Split(' '),
progressIntermediate: false,
totalSubTaskNumber: msgList.Count
));
diff --git a/src/PipManager/ViewModels/Pages/Search/SearchDetailViewModel.cs b/src/PipManager/ViewModels/Pages/Search/SearchDetailViewModel.cs
index 1c8603c..a49b5e9 100644
--- a/src/PipManager/ViewModels/Pages/Search/SearchDetailViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Search/SearchDetailViewModel.cs
@@ -4,7 +4,6 @@
using PipManager.Languages;
using PipManager.PackageSearch.Wrappers.Query;
using PipManager.Services.Environment;
-using PipManager.Services.Mask;
using PipManager.Services.Toast;
using PipManager.Views.Pages.Search;
using Serilog;
@@ -24,11 +23,10 @@ public record SearchDetailMessage(QueryListItemModel Package);
private readonly HttpClient _httpClient;
private readonly IThemeService _themeService;
private readonly IToastService _toastService;
- private readonly IMaskService _maskService;
private readonly IEnvironmentService _environmentService;
[ObservableProperty]
- private bool _projectDescriptionVisibility = false;
+ private bool _projectDescriptionVisibility;
private string _themeType = "light";
@@ -56,13 +54,12 @@ public record SearchDetailMessage(QueryListItemModel Package);
[ObservableProperty]
private QueryListItemModel? _package;
- public SearchDetailViewModel(INavigationService navigationService, HttpClient httpClient, IThemeService themeService, IToastService toastService, IMaskService maskService, IEnvironmentService environmentService)
+ public SearchDetailViewModel(INavigationService navigationService, HttpClient httpClient, IThemeService themeService, IToastService toastService, IEnvironmentService environmentService)
{
_navigationService = navigationService;
_httpClient = httpClient;
_themeService = themeService;
_toastService = toastService;
- _maskService = maskService;
_environmentService = environmentService;
WeakReferenceMessenger.Default.Register(this, Receive);
@@ -118,7 +115,6 @@ private async Task InstallPackage()
if (installedPackages.Any(item => item.Name == Package!.Name))
{
_toastService.Error(Lang.LibraryInstall_Add_AlreadyInstalled);
- return;
}
}
@@ -126,7 +122,7 @@ public void Receive(object recipient, SearchDetailMessage message)
{
Package = message.Package;
- SearchDetailPage.ProjectDescriptionWebView!.Loaded += async (sender, e) =>
+ SearchDetailPage.ProjectDescriptionWebView!.Loaded += async (_, _) =>
{
ProjectDescriptionVisibility = false;
var packageVersions = await _environmentService.GetVersions(Package!.Name);
@@ -145,8 +141,8 @@ public void Receive(object recipient, SearchDetailMessage message)
TargetVersion = AvailableVersions.First();
break;
}
- var webView2Environment = await CoreWebView2Environment.CreateAsync(null, AppInfo.CachesDir);
- await SearchDetailPage.ProjectDescriptionWebView!.EnsureCoreWebView2Async().ConfigureAwait(true);
+ await CoreWebView2Environment.CreateAsync(null, AppInfo.CachesDir);
+ await SearchDetailPage.ProjectDescriptionWebView.EnsureCoreWebView2Async().ConfigureAwait(true);
try
{
var projectDescriptionUrl = message.Package.Url;
@@ -155,8 +151,8 @@ public void Receive(object recipient, SearchDetailMessage message)
htmlDocument.LoadHtml(html);
string projectDescriptionHtml = string.Format(_htmlModel, _themeType, ThemeTypeInHex, htmlDocument.DocumentNode.SelectSingleNode("//*[@id=\"description\"]/div").InnerHtml);
- SearchDetailPage.ProjectDescriptionWebView!.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
- SearchDetailPage.ProjectDescriptionWebView!.NavigateToString(projectDescriptionHtml);
+ SearchDetailPage.ProjectDescriptionWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
+ SearchDetailPage.ProjectDescriptionWebView.NavigateToString(projectDescriptionHtml);
}
catch (Exception ex)
{
@@ -164,8 +160,8 @@ public void Receive(object recipient, SearchDetailMessage message)
_toastService.Error(Lang.SearchDetail_ProjectDescription_LoadFailed);
string projectDescriptionHtml = string.Format(_htmlModel, _themeType, ThemeTypeInHex, $"{Lang.SearchDetail_ProjectDescription_LoadFailed}
");
- SearchDetailPage.ProjectDescriptionWebView!.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
- SearchDetailPage.ProjectDescriptionWebView!.NavigateToString(projectDescriptionHtml);
+ SearchDetailPage.ProjectDescriptionWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
+ SearchDetailPage.ProjectDescriptionWebView.NavigateToString(projectDescriptionHtml);
}
finally
{
diff --git a/src/PipManager/ViewModels/Pages/Search/SearchViewModel.cs b/src/PipManager/ViewModels/Pages/Search/SearchViewModel.cs
index d49ada0..d16c340 100644
--- a/src/PipManager/ViewModels/Pages/Search/SearchViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Search/SearchViewModel.cs
@@ -27,16 +27,16 @@ public partial class SearchViewModel(IPackageSearchService packageSearchService,
private string _totalResultNumber = "";
[ObservableProperty]
- private bool _onQuerying = false;
+ private bool _onQuerying;
[ObservableProperty]
- private bool _successQueried = false;
+ private bool _successQueried;
[ObservableProperty]
private bool _reachesFirstPage = true;
[ObservableProperty]
- private bool _reachesLastPage = false;
+ private bool _reachesLastPage;
[ObservableProperty]
private int _currentPage = 1;
@@ -65,7 +65,6 @@ private void InitializeViewModel()
[RelayCommand]
private void ToDetailPage(object parameter)
{
- if (QueryList is null) return;
navigationService.Navigate(typeof(SearchDetailPage));
var current = QueryList.Where(searchListItem => searchListItem.Name == parameter as string).ToList()[0];
WeakReferenceMessenger.Default.Send(new SearchDetailMessage(current));
@@ -157,7 +156,7 @@ public async Task Search(string? parameter)
MaxPage = 1;
CurrentPage = 1;
QueryPackageName = parameter;
- var result = await packageSearchService.Query(parameter, 1);
+ var result = await packageSearchService.Query(parameter);
Process(result);
OnQuerying = false;
}
diff --git a/src/PipManager/ViewModels/Pages/Tools/ToolsViewModel.cs b/src/PipManager/ViewModels/Pages/Tools/ToolsViewModel.cs
index 5daf698..1b8d115 100644
--- a/src/PipManager/ViewModels/Pages/Tools/ToolsViewModel.cs
+++ b/src/PipManager/ViewModels/Pages/Tools/ToolsViewModel.cs
@@ -6,6 +6,9 @@ namespace PipManager.ViewModels.Pages.Tools;
public partial class ToolsViewModel : ObservableObject, INavigationAware
{
private bool _isInitialized;
+
+ [ObservableProperty]
+ private string? _testProperty;
public void OnNavigatedTo()
{
diff --git a/src/PipManager/ViewModels/Windows/MainWindowViewModel.cs b/src/PipManager/ViewModels/Windows/MainWindowViewModel.cs
index 2216cb2..3ccc622 100644
--- a/src/PipManager/ViewModels/Windows/MainWindowViewModel.cs
+++ b/src/PipManager/ViewModels/Windows/MainWindowViewModel.cs
@@ -5,16 +5,14 @@ namespace PipManager.ViewModels.Windows;
public partial class MainWindowViewModel : ObservableObject
{
- private readonly IConfigurationService _configurationService;
[ObservableProperty] private bool _experimentMode;
public MainWindowViewModel(IConfigurationService configurationService)
{
- _configurationService = configurationService;
- if (_configurationService.AppConfig.CurrentEnvironment != null)
+ if (configurationService.AppConfig.CurrentEnvironment != null)
{
- Log.Information($"[MainWindow] Environment loaded ({_configurationService.AppConfig.CurrentEnvironment.PipVersion} for {_configurationService.AppConfig.CurrentEnvironment.PythonVersion})");
- ApplicationTitle = $"Pip Manager | {_configurationService.AppConfig.CurrentEnvironment.PipVersion} for {_configurationService.AppConfig.CurrentEnvironment.PythonVersion}";
+ Log.Information($"[MainWindow] Environment loaded ({configurationService.AppConfig.CurrentEnvironment.PipVersion} for {configurationService.AppConfig.CurrentEnvironment.PythonVersion})");
+ ApplicationTitle = $"Pip Manager | {configurationService.AppConfig.CurrentEnvironment.PipVersion} for {configurationService.AppConfig.CurrentEnvironment.PythonVersion}";
}
else
{
diff --git a/src/PipManager/Views/Pages/Action/ActionExceptionPage.xaml b/src/PipManager/Views/Pages/Action/ActionExceptionPage.xaml
index b1a4a7f..380a2b1 100644
--- a/src/PipManager/Views/Pages/Action/ActionExceptionPage.xaml
+++ b/src/PipManager/Views/Pages/Action/ActionExceptionPage.xaml
@@ -1,25 +1,25 @@
+ x:Class="PipManager.Views.Pages.Action.ActionExceptionPage"
+ x:Name="ActionException"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:action="clr-namespace:PipManager.Views.Pages.Action"
+ xmlns:action1="clr-namespace:PipManager.Models.Action"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:lang="clr-namespace:PipManager.Languages"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -29,39 +29,39 @@
-
+
+ Text="{Binding OperationDescription}"
+ VerticalAlignment="Center" />
+ VerticalAlignment="Center">
-
+
+ TextWrapping="WrapWithOverflow"
+ VerticalAlignment="Center" />
-
+
+ Margin="5,0,0,0"
+ Text="{Binding OperationTimestamp}"
+ VerticalAlignment="Center" />
@@ -76,47 +76,44 @@
+ Grid.Row="1"
+ Orientation="Horizontal"
+ VerticalAlignment="Bottom">
+ Icon="{ui:SymbolIcon Search24}"
+ Margin="3,0,0,0" />
+ Content="{I18N {x:Static lang:LangKeys.ActionException_CopyToClipboard}}"
+ Margin="3,0,0,0" />
diff --git a/src/PipManager/Views/Pages/Action/ActionPage.xaml b/src/PipManager/Views/Pages/Action/ActionPage.xaml
index f659997..171afa9 100644
--- a/src/PipManager/Views/Pages/Action/ActionPage.xaml
+++ b/src/PipManager/Views/Pages/Action/ActionPage.xaml
@@ -1,10 +1,12 @@
+ ItemsSource="{Binding ViewModel.Actions.AsObservable}">
+
+ Text="{Binding ConsoleOutput}">
+
+
+
+
+
+
diff --git a/src/PipManager/Views/Pages/Environment/AddEnvironmentPage.xaml b/src/PipManager/Views/Pages/Environment/AddEnvironmentPage.xaml
index f641eb9..ed65968 100644
--- a/src/PipManager/Views/Pages/Environment/AddEnvironmentPage.xaml
+++ b/src/PipManager/Views/Pages/Environment/AddEnvironmentPage.xaml
@@ -76,7 +76,7 @@
FontSize="22"
Text="{I18N {x:Static lang:LangKeys.EnvironmentAdd_EnvironmentVariable_NotFound}}"
Visibility="{Binding ViewModel.Found, Converter={StaticResource BoolToVisibility}}" />
-
-
+
@@ -114,8 +114,8 @@
-
-
+
+
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -28,44 +29,51 @@
Content="{I18N {x:Static lang:LangKeys.Environment_Operation_AddEnvironment}}"
Icon="{ui:SymbolIcon Add24}" />
+ IsEnabled="{Binding ViewModel.CurrentEnvironment, Converter={StaticResource NullToBool}}"
+ Margin="5,0,0,0" />
+ IsEnabled="{Binding ViewModel.CurrentEnvironment, Converter={StaticResource NullToBool}}"
+ Margin="5,0,0,0" />
+ IsEnabled="{Binding ViewModel.CurrentEnvironment, Converter={StaticResource NullToBool}}"
+ Margin="5,0,0,0" />
+
-
-
+ SelectedItem="{Binding ViewModel.CurrentEnvironment}"
+ VerticalAlignment="Stretch">
+
-
+
+
+ Source="../../../Assets/logo/python-logo-only.png"
+ Width="48" />
@@ -81,7 +89,7 @@
-
-
+
+
\ No newline at end of file
diff --git a/src/PipManager/Views/Pages/Library/LibraryInstallPage.xaml b/src/PipManager/Views/Pages/Library/LibraryInstallPage.xaml
index 510190f..e327794 100644
--- a/src/PipManager/Views/Pages/Library/LibraryInstallPage.xaml
+++ b/src/PipManager/Views/Pages/Library/LibraryInstallPage.xaml
@@ -1,37 +1,37 @@
+ x:Class="PipManager.Views.Pages.Library.LibraryInstallPage"
+ x:Name="LibraryInstall"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:lang="clr-namespace:PipManager.Languages"
+ xmlns:library="clr-namespace:PipManager.Views.Pages.Library"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:pages="clr-namespace:PipManager.Models.Pages"
+ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
+
+
+ IsEnabled="{Binding ElementName=AddDefaultTaskList, Path=Items.Count, Converter={StaticResource IntegerToBool}}"
+ Margin="5,0,0,0" />
-
+
@@ -69,38 +69,37 @@
+ Text="{Binding PackageName}"
+ VerticalAlignment="Center" />
+ IsChecked="{Binding VersionSpecified, Mode=TwoWay}"
+ x:Name="VersionSpecifiedCheckbox" />
-
+
+
+ Text="{Binding ViewModel.Requirements, Mode=TwoWay}"
+ x:Name="AddRequirementsTextBox" />
+ IsEnabled="{Binding ElementName=AddRequirementsTextBox, Path=Text, Converter={StaticResource StringIsNotNullOrEmpty}}"
+ Margin="0,10,0,0" />
+
-
+
-
-
+
+
+ Text="{Binding PackageName}"
+ VerticalAlignment="Center" />
+ IsChecked="{Binding VersionSpecified, Mode=TwoWay}"
+ x:Name="DownloadDistributionsVersionSpecifiedCheckbox" />
-
+
+ Text="{I18N {x:Static lang:LangKeys.LibraryInstall_Requirements_DownloadFolder}}"
+ VerticalAlignment="Center" />
+ Margin="10,0,0,0"
+ Text="{Binding ViewModel.DownloadDistributionsFolderPath, Mode=TwoWay}"
+ x:Name="DownloadDistributionsFolderBrowseTextBox" />
+ IsEnabled="{Binding ElementName=DownloadDistributionsTaskList, Path=Items.Count, Converter={StaticResource IntegerToBool}}"
+ Margin="5,0,0,0" />
+ IsEnabled="{Binding ViewModel.DownloadDistributionsEnabled}"
+ Margin="5,0,0,0" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/PipManager/Views/Pages/Library/LibraryPage.xaml b/src/PipManager/Views/Pages/Library/LibraryPage.xaml
index e83c747..5452a22 100644
--- a/src/PipManager/Views/Pages/Library/LibraryPage.xaml
+++ b/src/PipManager/Views/Pages/Library/LibraryPage.xaml
@@ -1,27 +1,25 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -37,28 +35,28 @@
Icon="{ui:SymbolIcon Add24}"
IsEnabled="{Binding ViewModel.EnvironmentFoundVisible}" />
+ IsEnabled="{Binding ElementName=LibraryList, Path=SelectedItems.Count, Converter={StaticResource IntegerToBool}}"
+ Margin="5,0,0,0" />
+ IsEnabled="{Binding ElementName=LibraryList, Path=SelectedItems.Count, Converter={StaticResource IntegerToBool}}"
+ Margin="5,0,0,0" />
+ Orientation="Horizontal"
+ VerticalAlignment="Center">
@@ -71,14 +69,14 @@
-
+
@@ -94,17 +92,17 @@
-
-
+ Visibility="{Binding ViewModel.LibraryList, Converter={StaticResource NotNullToVisibility}}"
+ x:Name="LibraryList">
+
@@ -116,9 +114,9 @@
+ Text="{Binding PackageName}"
+ VerticalAlignment="Center" />
@@ -143,37 +141,38 @@
TextWrapping="Wrap" />
+ Grid.Column="0"
+ Grid.ColumnSpan="2"
+ HorizontalAlignment="Right"
+ Icon="{ui:SymbolIcon ChevronRight24}"
+ Margin="0,0,10,0" />
-
-
-
-
-
+
+
+ FontSize="16"
+ HorizontalAlignment="Center"
+ Margin="0,5,0,0" />
\ No newline at end of file
diff --git a/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml b/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml
index 310bb14..683135c 100644
--- a/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml
+++ b/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml
@@ -1,25 +1,25 @@
+ x:Class="PipManager.Views.Pages.Search.SearchDetailPage"
+ x:Name="SearchDetail"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:lang="clr-namespace:PipManager.Languages"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:search="clr-namespace:PipManager.Views.Pages.Search"
+ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
+ xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -28,27 +28,27 @@
+ CornerRadius="5"
+ Grid.Row="1"
+ Margin="0,10,0,0"
+ Padding="3">
+ VerticalAlignment="Center"
+ Visibility="{Binding ViewModel.ProjectDescriptionVisibility, Converter={StaticResource InverseBoolToVisibility}}"
+ Width="100" />
+ NavigationStarting="SearchDetailProjectDescriptionWebView_NavigationStarting"
+ Visibility="{Binding ViewModel.ProjectDescriptionVisibility, Converter={StaticResource BoolToVisibility}}"
+ x:Name="SearchDetailProjectDescriptionWebView" />
@@ -57,19 +57,19 @@
+ Text="{Binding ViewModel.Package.Name}"
+ VerticalAlignment="Center" />
+ VerticalAlignment="Center">
@@ -78,35 +78,35 @@
FontTypography="Caption"
Text="{I18N {x:Static lang:LangKeys.SearchDetail_LatestUpdatedTime}}" />
+ Orientation="Horizontal"
+ VerticalAlignment="Center">
+ SelectedItem="{Binding ViewModel.TargetVersion, Mode=TwoWay}"
+ VerticalAlignment="Center"
+ Width="150" />
+ IsEnabled="False"
+ Margin="15,0,0,0" />
+ IsEnabled="False"
+ Margin="15,0,0,0" />
diff --git a/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml.cs b/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml.cs
index ae5e662..5ef7642 100644
--- a/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml.cs
+++ b/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml.cs
@@ -1,4 +1,5 @@
-using Microsoft.Web.WebView2.Wpf;
+using Microsoft.Web.WebView2.Core;
+using Microsoft.Web.WebView2.Wpf;
using Wpf.Ui.Controls;
using SearchDetailViewModel = PipManager.ViewModels.Pages.Search.SearchDetailViewModel;
@@ -6,7 +7,7 @@ namespace PipManager.Views.Pages.Search;
public partial class SearchDetailPage : INavigableView
{
- public static WebView2? ProjectDescriptionWebView { get; set; }
+ public static WebView2? ProjectDescriptionWebView { get; private set; }
public SearchDetailViewModel ViewModel { get; }
@@ -15,10 +16,10 @@ public SearchDetailPage(SearchDetailViewModel viewModel)
ViewModel = viewModel;
DataContext = this;
InitializeComponent();
- ProjectDescriptionWebView = SearchDetailProjectDesciptionWebView;
+ ProjectDescriptionWebView = SearchDetailProjectDescriptionWebView;
}
- private void SearchDetailProjectDesciptionWebView_NavigationStarting(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
+ private void SearchDetailProjectDescriptionWebView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
if (e.Uri.StartsWith("http://") || e.Uri.StartsWith("https://"))
{
@@ -26,9 +27,9 @@ private void SearchDetailProjectDesciptionWebView_NavigationStarting(object send
}
}
- private void SearchDetailProjectDesciptionWebView_CoreWebView2InitializationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
+ private void SearchDetailProjectDescriptionWebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs? e)
{
- if (e != null && e.IsSuccess)
+ if (e is { IsSuccess: true })
{
ProjectDescriptionWebView!.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(
"document.addEventListener('contextmenu', event => event.preventDefault());");
diff --git a/src/PipManager/Views/Pages/Search/SearchPage.xaml b/src/PipManager/Views/Pages/Search/SearchPage.xaml
index f2d6e80..f9cba8d 100644
--- a/src/PipManager/Views/Pages/Search/SearchPage.xaml
+++ b/src/PipManager/Views/Pages/Search/SearchPage.xaml
@@ -1,25 +1,25 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -34,77 +34,76 @@
+ x:Name="SearchTextBox" />
+ IsDefault="True"
+ Margin="5,0,0,0"
+ VerticalAlignment="Stretch" />
+ VerticalAlignment="Center"
+ Visibility="{Binding ViewModel.OnQuerying, Converter={StaticResource BoolToVisibility}}"
+ Width="80" />
-
-
+ x:Name="SearchList">
+
+ Text="{Binding Name}"
+ VerticalAlignment="Center" />
+ Text="{Binding Version}"
+ VerticalAlignment="Center" />
+ TextWrapping="Wrap"
+ Width="800" />
+ Grid.Column="0"
+ HorizontalAlignment="Right"
+ Icon="{ui:SymbolIcon ChevronRight24}"
+ Margin="0,0,10,0" />
-
-
+
+
+ Text="{Binding ViewModel.CurrentPage}"
+ VerticalAlignment="Center" />
+ Text=" / "
+ VerticalAlignment="Center" />
+ Text="{Binding ViewModel.MaxPage}"
+ VerticalAlignment="Center" />
diff --git a/src/PipManager/Views/Pages/Settings/SettingsPage.xaml b/src/PipManager/Views/Pages/Settings/SettingsPage.xaml
index f9e6953..899bba2 100644
--- a/src/PipManager/Views/Pages/Settings/SettingsPage.xaml
+++ b/src/PipManager/Views/Pages/Settings/SettingsPage.xaml
@@ -1,7 +1,16 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
@@ -35,7 +31,7 @@
-
+
@@ -43,23 +39,23 @@
+ Margin="0,8,0,0"
+ Message="{I18N {x:Static lang:LangKeys.Settings_PackageSource_Notice}}"
+ Title="{I18N {x:Static lang:LangKeys.Common_NoticeTitle_Notice}}" />
@@ -72,69 +68,69 @@
+ Icon="{ui:SymbolIcon NetworkCheck20}"
+ Margin="0,10,0,0" />
@@ -147,7 +143,7 @@
-
+
@@ -155,23 +151,23 @@
+ SelectedItem="{Binding ViewModel.Language, Mode=TwoWay}"
+ Width="200"
+ x:Name="LanguageComboBox">
@@ -181,7 +177,7 @@
-
+
@@ -189,13 +185,13 @@
@@ -207,17 +203,17 @@
GroupName="themeSelect"
IsChecked="{Binding ViewModel.CurrentTheme, Converter={StaticResource ThemeEnumToBooleanConverter}, ConverterParameter=Light, Mode=TwoWay}" />
+ IsChecked="{Binding ViewModel.CurrentTheme, Converter={StaticResource ThemeEnumToBooleanConverter}, ConverterParameter=Dark, Mode=TwoWay}"
+ Margin="10,0,0,0" />
-
+
@@ -225,33 +221,33 @@
-
+
+ Width="200"
+ x:Name="LogAutoDeletionSlider">
@@ -259,9 +255,9 @@
@@ -273,7 +269,7 @@
-
+
@@ -281,33 +277,33 @@
-
+
+ Width="200"
+ x:Name="CrushesAutoDeletionSlider">
@@ -315,9 +311,9 @@
@@ -337,9 +333,9 @@
+ Icon="{ui:SymbolIcon Delete24}"
+ Margin="0,5,0,0">
@@ -347,13 +343,13 @@
@@ -361,9 +357,9 @@
+ Icon="{ui:SymbolIcon AppFolder20}"
+ Margin="0,3,0,0">
@@ -371,13 +367,13 @@
@@ -385,9 +381,9 @@
+ Icon="{ui:SymbolIcon Record20}"
+ Margin="0,3,0,0">
@@ -395,13 +391,13 @@
@@ -409,9 +405,9 @@
+ Icon="{ui:SymbolIcon CircleOff20}"
+ Margin="0,3,0,0">
@@ -419,20 +415,20 @@
-
+
@@ -440,13 +436,13 @@
diff --git a/src/PipManager/Views/Windows/ExceptionWindow.xaml.cs b/src/PipManager/Views/Windows/ExceptionWindow.xaml.cs
index 5d8deae..f474603 100644
--- a/src/PipManager/Views/Windows/ExceptionWindow.xaml.cs
+++ b/src/PipManager/Views/Windows/ExceptionWindow.xaml.cs
@@ -16,7 +16,10 @@ public void Initialize(Exception exception)
{
TypeTextBlock.Text = exception.GetType().ToString();
MessageTextBlock.Text = exception.Message;
- StackTraceTextBox.Text = exception.StackTrace;
+ if (exception.StackTrace != null)
+ {
+ StackTraceTextBox.Text = exception.StackTrace;
+ }
}
private void ReportButton_OnClick(object sender, RoutedEventArgs e)
diff --git a/src/PipManager/Views/Windows/MainWindow.xaml b/src/PipManager/Views/Windows/MainWindow.xaml
index 0a561ad..776aa14 100644
--- a/src/PipManager/Views/Windows/MainWindow.xaml
+++ b/src/PipManager/Views/Windows/MainWindow.xaml
@@ -1,7 +1,25 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -43,18 +43,18 @@
+ Padding="42,0,42,0"
+ Transition="FadeInWithSlide"
+ x:Name="NavigationView">
-
+
+ Grid.RowSpan="2"
+ x:Name="MaskPresenter" />
+ Grid.RowSpan="2"
+ x:Name="MaskActionExceptionPresenter" />
+ Grid.RowSpan="2"
+ x:Name="RootContentDialog" />
+
+ Grid.Row="0"
+ ShowMaximize="False"
+ Title="{Binding ViewModel.ApplicationTitle}">
+
diff --git a/src/PipManager/Views/Windows/MainWindow.xaml.cs b/src/PipManager/Views/Windows/MainWindow.xaml.cs
index e26dd1f..04c72fa 100644
--- a/src/PipManager/Views/Windows/MainWindow.xaml.cs
+++ b/src/PipManager/Views/Windows/MainWindow.xaml.cs
@@ -18,7 +18,6 @@ public MainWindow(
MainWindowViewModel viewModel,
INavigationService navigationService,
IServiceProvider serviceProvider,
- ISnackbarService snackbarService,
IContentDialogService contentDialogService,
IMaskService maskPresenter,
IActionService actionService