|
|
|
@ -51,7 +51,7 @@ namespace StabilityMatrix.Avalonia.ViewModels;
|
|
|
|
|
public partial class SettingsViewModel : PageViewModelBase |
|
|
|
|
{ |
|
|
|
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private readonly INotificationService notificationService; |
|
|
|
|
private readonly ISettingsManager settingsManager; |
|
|
|
|
private readonly IPrerequisiteHelper prerequisiteHelper; |
|
|
|
@ -60,82 +60,91 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
private readonly ICompletionProvider completionProvider; |
|
|
|
|
private readonly ITrackedDownloadService trackedDownloadService; |
|
|
|
|
private readonly IModelIndexService modelIndexService; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public SharedState SharedState { get; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override string Title => "Settings"; |
|
|
|
|
public override IconSource IconSource => new SymbolIconSource {Symbol = Symbol.Settings, IsFilled = true}; |
|
|
|
|
|
|
|
|
|
public override IconSource IconSource => |
|
|
|
|
new SymbolIconSource { Symbol = Symbol.Settings, IsFilled = true }; |
|
|
|
|
|
|
|
|
|
// ReSharper disable once MemberCanBeMadeStatic.Global |
|
|
|
|
public string AppVersion => $"Version {Compat.AppVersion}" + |
|
|
|
|
(Program.IsDebugBuild ? " (Debug)" : ""); |
|
|
|
|
|
|
|
|
|
public string AppVersion => |
|
|
|
|
$"Version {Compat.AppVersion}" + (Program.IsDebugBuild ? " (Debug)" : ""); |
|
|
|
|
|
|
|
|
|
// Theme section |
|
|
|
|
[ObservableProperty] private string? selectedTheme; |
|
|
|
|
|
|
|
|
|
public IReadOnlyList<string> AvailableThemes { get; } = new[] |
|
|
|
|
{ |
|
|
|
|
"Light", |
|
|
|
|
"Dark", |
|
|
|
|
"System", |
|
|
|
|
}; |
|
|
|
|
[ObservableProperty] |
|
|
|
|
private string? selectedTheme; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] private CultureInfo selectedLanguage; |
|
|
|
|
public IReadOnlyList<string> AvailableThemes { get; } = new[] { "Light", "Dark", "System", }; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private CultureInfo selectedLanguage; |
|
|
|
|
|
|
|
|
|
// ReSharper disable once MemberCanBeMadeStatic.Global |
|
|
|
|
public IReadOnlyList<CultureInfo> AvailableLanguages => Cultures.SupportedCultures; |
|
|
|
|
|
|
|
|
|
public IReadOnlyList<float> AnimationScaleOptions { get; } = new[] |
|
|
|
|
{ |
|
|
|
|
0f, |
|
|
|
|
0.25f, |
|
|
|
|
0.5f, |
|
|
|
|
0.75f, |
|
|
|
|
1f, |
|
|
|
|
1.25f, |
|
|
|
|
1.5f, |
|
|
|
|
1.75f, |
|
|
|
|
2f, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] private float selectedAnimationScale; |
|
|
|
|
|
|
|
|
|
public IReadOnlyList<float> AnimationScaleOptions { get; } = |
|
|
|
|
new[] { 0f, 0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f, }; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private float selectedAnimationScale; |
|
|
|
|
|
|
|
|
|
// Shared folder options |
|
|
|
|
[ObservableProperty] private bool removeSymlinksOnShutdown; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool removeSymlinksOnShutdown; |
|
|
|
|
|
|
|
|
|
// Inference UI section |
|
|
|
|
[ObservableProperty] private bool isPromptCompletionEnabled; |
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool isPromptCompletionEnabled; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private IReadOnlyList<string> availableTagCompletionCsvs = Array.Empty<string>(); |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private string? selectedTagCompletionCsv; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool isCompletionRemoveUnderscoresEnabled; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool isImageViewerPixelGridEnabled; |
|
|
|
|
|
|
|
|
|
// Integrations section |
|
|
|
|
[ObservableProperty] private bool isDiscordRichPresenceEnabled; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool isDiscordRichPresenceEnabled; |
|
|
|
|
|
|
|
|
|
// Debug section |
|
|
|
|
[ObservableProperty] private string? debugPaths; |
|
|
|
|
[ObservableProperty] private string? debugCompatInfo; |
|
|
|
|
[ObservableProperty] private string? debugGpuInfo; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private string? debugPaths; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private string? debugCompatInfo; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private string? debugGpuInfo; |
|
|
|
|
|
|
|
|
|
// Info section |
|
|
|
|
private const int VersionTapCountThreshold = 7; |
|
|
|
|
[ObservableProperty, NotifyPropertyChangedFor(nameof(VersionFlyoutText))] private int versionTapCount; |
|
|
|
|
[ObservableProperty] private bool isVersionTapTeachingTipOpen; |
|
|
|
|
public string VersionFlyoutText => $"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options."; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty, NotifyPropertyChangedFor(nameof(VersionFlyoutText))] |
|
|
|
|
private int versionTapCount; |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool isVersionTapTeachingTipOpen; |
|
|
|
|
public string VersionFlyoutText => |
|
|
|
|
$"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options."; |
|
|
|
|
|
|
|
|
|
public SettingsViewModel( |
|
|
|
|
INotificationService notificationService, |
|
|
|
|
INotificationService notificationService, |
|
|
|
|
ISettingsManager settingsManager, |
|
|
|
|
IPrerequisiteHelper prerequisiteHelper, |
|
|
|
|
IPyRunner pyRunner, |
|
|
|
|
ServiceManager<ViewModelBase> dialogFactory, |
|
|
|
|
ITrackedDownloadService trackedDownloadService, |
|
|
|
|
SharedState sharedState, |
|
|
|
|
SharedState sharedState, |
|
|
|
|
ICompletionProvider completionProvider, |
|
|
|
|
IModelIndexService modelIndexService) |
|
|
|
|
IModelIndexService modelIndexService |
|
|
|
|
) |
|
|
|
|
{ |
|
|
|
|
this.notificationService = notificationService; |
|
|
|
|
this.settingsManager = settingsManager; |
|
|
|
@ -147,40 +156,58 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
this.modelIndexService = modelIndexService; |
|
|
|
|
|
|
|
|
|
SharedState = sharedState; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SelectedTheme = settingsManager.Settings.Theme ?? AvailableThemes[1]; |
|
|
|
|
SelectedLanguage = Cultures.GetSupportedCultureOrDefault(settingsManager.Settings.Language); |
|
|
|
|
RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown; |
|
|
|
|
SelectedAnimationScale = settingsManager.Settings.AnimationScale; |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor(this, |
|
|
|
|
vm => vm.SelectedTheme, |
|
|
|
|
settings => settings.Theme); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor(this, |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor(this, vm => vm.SelectedTheme, settings => settings.Theme); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor( |
|
|
|
|
this, |
|
|
|
|
vm => vm.IsDiscordRichPresenceEnabled, |
|
|
|
|
settings => settings.IsDiscordRichPresenceEnabled, |
|
|
|
|
true); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor(this, |
|
|
|
|
true |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor( |
|
|
|
|
this, |
|
|
|
|
vm => vm.SelectedAnimationScale, |
|
|
|
|
settings => settings.AnimationScale); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor(this, |
|
|
|
|
settings => settings.AnimationScale |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor( |
|
|
|
|
this, |
|
|
|
|
vm => vm.SelectedTagCompletionCsv, |
|
|
|
|
settings => settings.TagCompletionCsv); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor(this, |
|
|
|
|
settings => settings.TagCompletionCsv |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor( |
|
|
|
|
this, |
|
|
|
|
vm => vm.IsPromptCompletionEnabled, |
|
|
|
|
settings => settings.IsPromptCompletionEnabled, |
|
|
|
|
true); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor(this, |
|
|
|
|
true |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor( |
|
|
|
|
this, |
|
|
|
|
vm => vm.IsCompletionRemoveUnderscoresEnabled, |
|
|
|
|
settings => settings.IsCompletionRemoveUnderscoresEnabled, |
|
|
|
|
true); |
|
|
|
|
|
|
|
|
|
DebugThrowAsyncExceptionCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn); |
|
|
|
|
true |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
settingsManager.RelayPropertyFor( |
|
|
|
|
this, |
|
|
|
|
vm => vm.IsImageViewerPixelGridEnabled, |
|
|
|
|
settings => settings.IsImageViewerPixelGridEnabled, |
|
|
|
|
true |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
DebugThrowAsyncExceptionCommand.WithNotificationErrorHandler( |
|
|
|
|
notificationService, |
|
|
|
|
LogLevel.Warn |
|
|
|
|
); |
|
|
|
|
ImportTagCsvCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -195,7 +222,8 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
partial void OnSelectedThemeChanged(string? value) |
|
|
|
|
{ |
|
|
|
|
// In case design / tests |
|
|
|
|
if (Application.Current is null) return; |
|
|
|
|
if (Application.Current is null) |
|
|
|
|
return; |
|
|
|
|
// Change theme |
|
|
|
|
Application.Current.RequestedThemeVariant = value switch |
|
|
|
|
{ |
|
|
|
@ -207,16 +235,16 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
|
|
|
|
|
partial void OnSelectedLanguageChanged(CultureInfo? oldValue, CultureInfo newValue) |
|
|
|
|
{ |
|
|
|
|
if (oldValue is null || newValue.Name == Cultures.Current.Name) return; |
|
|
|
|
if (oldValue is null || newValue.Name == Cultures.Current.Name) |
|
|
|
|
return; |
|
|
|
|
// Set locale |
|
|
|
|
if (AvailableLanguages.Contains(newValue)) |
|
|
|
|
{ |
|
|
|
|
Logger.Info("Changing language from {Old} to {New}", |
|
|
|
|
oldValue, newValue); |
|
|
|
|
Logger.Info("Changing language from {Old} to {New}", oldValue, newValue); |
|
|
|
|
|
|
|
|
|
Cultures.TrySetSupportedCulture(newValue); |
|
|
|
|
settingsManager.Transaction(s => s.Language = newValue.Name); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var dialog = new BetterContentDialog |
|
|
|
|
{ |
|
|
|
|
Title = Resources.Label_RelaunchRequired, |
|
|
|
@ -237,11 +265,14 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
Logger.Info("Requested invalid language change from {Old} to {New}", |
|
|
|
|
oldValue, newValue); |
|
|
|
|
Logger.Info( |
|
|
|
|
"Requested invalid language change from {Old} to {New}", |
|
|
|
|
oldValue, |
|
|
|
|
newValue |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
partial void OnRemoveSymlinksOnShutdownChanged(bool value) |
|
|
|
|
{ |
|
|
|
|
settingsManager.Transaction(s => s.RemoveFolderLinksOnShutdown = value); |
|
|
|
@ -251,29 +282,30 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
{ |
|
|
|
|
settingsManager.Transaction(s => s.InstalledModelHashes = new HashSet<string>()); |
|
|
|
|
await Task.Run(() => settingsManager.IndexCheckpoints()); |
|
|
|
|
notificationService.Show("Checkpoint cache reset", "The checkpoint cache has been reset.", |
|
|
|
|
NotificationType.Success); |
|
|
|
|
notificationService.Show( |
|
|
|
|
"Checkpoint cache reset", |
|
|
|
|
"The checkpoint cache has been reset.", |
|
|
|
|
NotificationType.Success |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#region Package Environment |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task OpenEnvVarsDialog() |
|
|
|
|
{ |
|
|
|
|
var viewModel = dialogFactory.Get<EnvVarsViewModel>(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Load current settings |
|
|
|
|
var current = settingsManager.Settings.EnvironmentVariables |
|
|
|
|
?? new Dictionary<string, string>(); |
|
|
|
|
var current = |
|
|
|
|
settingsManager.Settings.EnvironmentVariables ?? new Dictionary<string, string>(); |
|
|
|
|
viewModel.EnvVars = new ObservableCollection<EnvVarKeyPair>( |
|
|
|
|
current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value))); |
|
|
|
|
|
|
|
|
|
current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value)) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
var dialog = new BetterContentDialog |
|
|
|
|
{ |
|
|
|
|
Content = new EnvVarsDialog |
|
|
|
|
{ |
|
|
|
|
DataContext = viewModel |
|
|
|
|
}, |
|
|
|
|
Content = new EnvVarsDialog { DataContext = viewModel }, |
|
|
|
|
PrimaryButtonText = "Save", |
|
|
|
|
IsPrimaryButtonEnabled = true, |
|
|
|
|
CloseButtonText = "Cancel", |
|
|
|
@ -321,21 +353,23 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
dialog.PrimaryButtonText = "Ok"; |
|
|
|
|
await dialog.ShowAsync(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion |
|
|
|
|
|
|
|
|
|
#region Inference UI |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateAvailableTagCompletionCsvs() |
|
|
|
|
{ |
|
|
|
|
if (!settingsManager.IsLibraryDirSet) return; |
|
|
|
|
|
|
|
|
|
if (!settingsManager.IsLibraryDirSet) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
var tagsDir = settingsManager.TagsDirectory; |
|
|
|
|
if (!tagsDir.Exists) return; |
|
|
|
|
|
|
|
|
|
if (!tagsDir.Exists) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
var csvFiles = tagsDir.Info.EnumerateFiles("*.csv"); |
|
|
|
|
AvailableTagCompletionCsvs = csvFiles.Select(f => f.Name).ToImmutableArray(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Set selected to current if exists |
|
|
|
|
var settingsCsv = settingsManager.Settings.TagCompletionCsv; |
|
|
|
|
if (settingsCsv is not null && AvailableTagCompletionCsvs.Contains(settingsCsv)) |
|
|
|
@ -343,45 +377,48 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
SelectedTagCompletionCsv = settingsCsv; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand(FlowExceptionsToTaskScheduler = true)] |
|
|
|
|
private async Task ImportTagCsv() |
|
|
|
|
{ |
|
|
|
|
var storage = App.StorageProvider; |
|
|
|
|
var files = await storage.OpenFilePickerAsync(new FilePickerOpenOptions |
|
|
|
|
{ |
|
|
|
|
FileTypeFilter = new List<FilePickerFileType> |
|
|
|
|
var files = await storage.OpenFilePickerAsync( |
|
|
|
|
new FilePickerOpenOptions |
|
|
|
|
{ |
|
|
|
|
new("CSV") |
|
|
|
|
FileTypeFilter = new List<FilePickerFileType> |
|
|
|
|
{ |
|
|
|
|
Patterns = new[] {"*.csv"}, |
|
|
|
|
new("CSV") { Patterns = new[] { "*.csv" }, } |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if (files.Count == 0) return; |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (files.Count == 0) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
var sourceFile = new FilePath(files[0].TryGetLocalPath()!); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var tagsDir = settingsManager.TagsDirectory; |
|
|
|
|
tagsDir.Create(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Copy to tags directory |
|
|
|
|
var targetFile = tagsDir.JoinFile(sourceFile.Name); |
|
|
|
|
await sourceFile.CopyToAsync(targetFile); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update index |
|
|
|
|
UpdateAvailableTagCompletionCsvs(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Trigger load |
|
|
|
|
completionProvider.BackgroundLoadFromFile(targetFile, true); |
|
|
|
|
|
|
|
|
|
notificationService.Show($"Imported {sourceFile.Name}", |
|
|
|
|
$"The {sourceFile.Name} file has been imported.", NotificationType.Success); |
|
|
|
|
|
|
|
|
|
notificationService.Show( |
|
|
|
|
$"Imported {sourceFile.Name}", |
|
|
|
|
$"The {sourceFile.Name} file has been imported.", |
|
|
|
|
NotificationType.Success |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#endregion |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region System |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
@ -392,27 +429,29 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
{ |
|
|
|
|
if (!Compat.IsWindows) |
|
|
|
|
{ |
|
|
|
|
notificationService.Show( |
|
|
|
|
"Not supported", "This feature is only supported on Windows."); |
|
|
|
|
notificationService.Show("Not supported", "This feature is only supported on Windows."); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await using var _ = new MinimumDelay(200, 300); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var shortcutDir = new DirectoryPath( |
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), |
|
|
|
|
"Programs"); |
|
|
|
|
"Programs" |
|
|
|
|
); |
|
|
|
|
var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk"); |
|
|
|
|
|
|
|
|
|
var appPath = Compat.AppCurrentPath; |
|
|
|
|
var iconPath = shortcutDir.JoinFile("Stability Matrix.ico"); |
|
|
|
|
await Assets.AppIcon.ExtractTo(iconPath); |
|
|
|
|
|
|
|
|
|
WindowsShortcuts.CreateShortcut( |
|
|
|
|
shortcutLink, appPath, iconPath, "Stability Matrix"); |
|
|
|
|
|
|
|
|
|
notificationService.Show("Added to Start Menu", |
|
|
|
|
"Stability Matrix has been added to the Start Menu.", NotificationType.Success); |
|
|
|
|
|
|
|
|
|
WindowsShortcuts.CreateShortcut(shortcutLink, appPath, iconPath, "Stability Matrix"); |
|
|
|
|
|
|
|
|
|
notificationService.Show( |
|
|
|
|
"Added to Start Menu", |
|
|
|
|
"Stability Matrix has been added to the Start Menu.", |
|
|
|
|
NotificationType.Success |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
@ -424,54 +463,62 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
{ |
|
|
|
|
if (!Compat.IsWindows) |
|
|
|
|
{ |
|
|
|
|
notificationService.Show( |
|
|
|
|
"Not supported", "This feature is only supported on Windows."); |
|
|
|
|
notificationService.Show("Not supported", "This feature is only supported on Windows."); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Confirmation dialog |
|
|
|
|
var dialog = new BetterContentDialog |
|
|
|
|
{ |
|
|
|
|
Title = "This will create a shortcut for Stability Matrix in the Start Menu for all users", |
|
|
|
|
Title = |
|
|
|
|
"This will create a shortcut for Stability Matrix in the Start Menu for all users", |
|
|
|
|
Content = "You will be prompted for administrator privileges. Continue?", |
|
|
|
|
PrimaryButtonText = "Yes", |
|
|
|
|
CloseButtonText = "Cancel", |
|
|
|
|
DefaultButton = ContentDialogButton.Primary |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (await dialog.ShowAsync() != ContentDialogResult.Primary) |
|
|
|
|
{ |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await using var _ = new MinimumDelay(200, 300); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var shortcutDir = new DirectoryPath( |
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu), |
|
|
|
|
"Programs"); |
|
|
|
|
"Programs" |
|
|
|
|
); |
|
|
|
|
var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var appPath = Compat.AppCurrentPath; |
|
|
|
|
var iconPath = shortcutDir.JoinFile("Stability Matrix.ico"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We can't directly write to the targets, so extract to temporary directory first |
|
|
|
|
using var tempDir = new TempDirectoryPath(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await Assets.AppIcon.ExtractTo(tempDir.JoinFile("Stability Matrix.ico")); |
|
|
|
|
WindowsShortcuts.CreateShortcut( |
|
|
|
|
tempDir.JoinFile("Stability Matrix.lnk"), appPath, iconPath, |
|
|
|
|
"Stability Matrix"); |
|
|
|
|
|
|
|
|
|
tempDir.JoinFile("Stability Matrix.lnk"), |
|
|
|
|
appPath, |
|
|
|
|
iconPath, |
|
|
|
|
"Stability Matrix" |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Move to target |
|
|
|
|
try |
|
|
|
|
{ |
|
|
|
|
var moveLinkResult = await WindowsElevated.MoveFiles( |
|
|
|
|
(tempDir.JoinFile("Stability Matrix.lnk"), shortcutLink), |
|
|
|
|
(tempDir.JoinFile("Stability Matrix.ico"), iconPath)); |
|
|
|
|
(tempDir.JoinFile("Stability Matrix.ico"), iconPath) |
|
|
|
|
); |
|
|
|
|
if (moveLinkResult != 0) |
|
|
|
|
{ |
|
|
|
|
notificationService.ShowPersistent("Failed to create shortcut", $"Could not copy shortcut", |
|
|
|
|
NotificationType.Error); |
|
|
|
|
notificationService.ShowPersistent( |
|
|
|
|
"Failed to create shortcut", |
|
|
|
|
$"Could not copy shortcut", |
|
|
|
|
NotificationType.Error |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
catch (Win32Exception e) |
|
|
|
@ -481,9 +528,12 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
notificationService.Show("Could not create shortcut", "", NotificationType.Warning); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
notificationService.Show("Added to Start Menu", |
|
|
|
|
"Stability Matrix has been added to the Start Menu for all users.", NotificationType.Success); |
|
|
|
|
|
|
|
|
|
notificationService.Show( |
|
|
|
|
"Added to Start Menu", |
|
|
|
|
"Stability Matrix has been added to the Start Menu for all users.", |
|
|
|
|
NotificationType.Success |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public async Task PickNewDataDirectory() |
|
|
|
@ -494,10 +544,7 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
IsPrimaryButtonEnabled = false, |
|
|
|
|
IsSecondaryButtonEnabled = false, |
|
|
|
|
IsFooterVisible = false, |
|
|
|
|
Content = new SelectDataDirectoryDialog |
|
|
|
|
{ |
|
|
|
|
DataContext = viewModel |
|
|
|
|
} |
|
|
|
|
Content = new SelectDataDirectoryDialog { DataContext = viewModel } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var result = await dialog.ShowAsync(); |
|
|
|
@ -513,7 +560,7 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
{ |
|
|
|
|
settingsManager.SetLibraryPath(viewModel.DataDirectory); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Restart |
|
|
|
|
var restartDialog = new BetterContentDialog |
|
|
|
|
{ |
|
|
|
@ -524,14 +571,14 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
IsSecondaryButtonEnabled = false, |
|
|
|
|
}; |
|
|
|
|
await restartDialog.ShowAsync(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Process.Start(Compat.AppCurrentPath); |
|
|
|
|
App.Shutdown(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#endregion |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Debug Section |
|
|
|
|
public void LoadDebugInfo() |
|
|
|
|
{ |
|
|
|
@ -547,12 +594,12 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
AppData Directory [SpecialFolder.ApplicationData] |
|
|
|
|
"{appData}" |
|
|
|
|
""";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Check portable mode |
|
|
|
|
var appDir = Compat.AppCurrentDir; |
|
|
|
|
var expectedPortableFile = Path.Combine(appDir, "Data", ".sm-portable"); |
|
|
|
|
var isPortableMode = File.Exists(expectedPortableFile); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DebugCompatInfo = $"""
|
|
|
|
|
Platform: {Compat.Platform} |
|
|
|
|
AppData: {Compat.AppData} |
|
|
|
@ -565,24 +612,27 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
IsLibraryDirSet = {settingsManager.IsLibraryDirSet} |
|
|
|
|
IsPortableMode = {settingsManager.IsPortableMode} |
|
|
|
|
""";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get Gpu info |
|
|
|
|
var gpuInfo = ""; |
|
|
|
|
foreach (var (i, gpu) in HardwareHelper.IterGpuInfo().Enumerate()) |
|
|
|
|
{ |
|
|
|
|
gpuInfo += $"[{i+1}] {gpu}\n"; |
|
|
|
|
gpuInfo += $"[{i + 1}] {gpu}\n"; |
|
|
|
|
} |
|
|
|
|
DebugGpuInfo = gpuInfo; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Debug buttons |
|
|
|
|
[RelayCommand] |
|
|
|
|
private void DebugNotification() |
|
|
|
|
{ |
|
|
|
|
notificationService.Show(new Notification( |
|
|
|
|
title: "Test Notification", |
|
|
|
|
message: "Here is some message", |
|
|
|
|
type: NotificationType.Information)); |
|
|
|
|
notificationService.Show( |
|
|
|
|
new Notification( |
|
|
|
|
title: "Test Notification", |
|
|
|
|
message: "Here is some message", |
|
|
|
|
type: NotificationType.Information |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
@ -597,8 +647,7 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var result = await dialog.ShowAsync(); |
|
|
|
|
notificationService.Show(new Notification("Content dialog closed", |
|
|
|
|
$"Result: {result}")); |
|
|
|
|
notificationService.Show(new Notification("Content dialog closed", $"Result: {result}")); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
@ -606,7 +655,7 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
{ |
|
|
|
|
throw new OperationCanceledException("Example Message"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand(FlowExceptionsToTaskScheduler = true)] |
|
|
|
|
private async Task DebugThrowAsyncException() |
|
|
|
|
{ |
|
|
|
@ -619,18 +668,19 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
private async Task DebugMakeImageGrid() |
|
|
|
|
{ |
|
|
|
|
var provider = App.StorageProvider; |
|
|
|
|
var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions() |
|
|
|
|
{ |
|
|
|
|
AllowMultiple = true |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if (files.Count == 0) return; |
|
|
|
|
|
|
|
|
|
var images = await files.SelectAsync(async f => |
|
|
|
|
SKImage.FromEncodedData(await f.OpenReadAsync())); |
|
|
|
|
var files = await provider.OpenFilePickerAsync( |
|
|
|
|
new FilePickerOpenOptions() { AllowMultiple = true } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (files.Count == 0) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
var images = await files.SelectAsync( |
|
|
|
|
async f => SKImage.FromEncodedData(await f.OpenReadAsync()) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
var grid = ImageProcessor.CreateImageGrid(images.ToImmutableArray()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Show preview |
|
|
|
|
|
|
|
|
|
using var peekPixels = grid.PeekPixels(); |
|
|
|
@ -639,10 +689,7 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
|
|
|
|
|
var bitmap = WriteableBitmap.Decode(stream); |
|
|
|
|
|
|
|
|
|
var galleryImages = new List<ImageSource> |
|
|
|
|
{ |
|
|
|
|
new(bitmap), |
|
|
|
|
}; |
|
|
|
|
var galleryImages = new List<ImageSource> { new(bitmap), }; |
|
|
|
|
galleryImages.AddRange(files.Select(f => new ImageSource(f.Path.ToString()))); |
|
|
|
|
|
|
|
|
|
var imageBox = new ImageGalleryCard |
|
|
|
@ -667,39 +714,34 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
|
|
|
|
|
await dialog.ShowAsync(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task DebugLoadCompletionCsv() |
|
|
|
|
{ |
|
|
|
|
var provider = App.StorageProvider; |
|
|
|
|
var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions()); |
|
|
|
|
|
|
|
|
|
if (files.Count == 0) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (files.Count == 0) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
await completionProvider.LoadFromFile(files[0].TryGetLocalPath()!, true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
notificationService.Show("Loaded completion file", ""); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task DebugRefreshModelsIndex() |
|
|
|
|
{ |
|
|
|
|
await modelIndexService.RefreshIndex(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand] |
|
|
|
|
private async Task DebugTrackedDownload() |
|
|
|
|
{ |
|
|
|
|
var textFields = new TextBoxField[] |
|
|
|
|
{ |
|
|
|
|
new() |
|
|
|
|
{ |
|
|
|
|
Label = "Url", |
|
|
|
|
}, |
|
|
|
|
new() |
|
|
|
|
{ |
|
|
|
|
Label = "File path" |
|
|
|
|
} |
|
|
|
|
new() { Label = "Url", }, |
|
|
|
|
new() { Label = "File path" } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var dialog = DialogHelper.CreateTextEntryDialog("Add download", "", textFields); |
|
|
|
@ -719,10 +761,11 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
public void OnVersionClick() |
|
|
|
|
{ |
|
|
|
|
// Ignore if already enabled |
|
|
|
|
if (SharedState.IsDebugMode) return; |
|
|
|
|
|
|
|
|
|
if (SharedState.IsDebugMode) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
VersionTapCount++; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch (VersionTapCount) |
|
|
|
|
{ |
|
|
|
|
// Reached required threshold |
|
|
|
@ -732,7 +775,9 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
// Enable debug options |
|
|
|
|
SharedState.IsDebugMode = true; |
|
|
|
|
notificationService.Show( |
|
|
|
|
"Debug options enabled", "Warning: Improper use may corrupt application state or cause loss of data."); |
|
|
|
|
"Debug options enabled", |
|
|
|
|
"Warning: Improper use may corrupt application state or cause loss of data." |
|
|
|
|
); |
|
|
|
|
VersionTapCount = 0; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
@ -756,8 +801,11 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
} |
|
|
|
|
catch (Exception e) |
|
|
|
|
{ |
|
|
|
|
notificationService.Show("Failed to read licenses information", |
|
|
|
|
$"{e}", NotificationType.Error); |
|
|
|
|
notificationService.Show( |
|
|
|
|
"Failed to read licenses information", |
|
|
|
|
$"{e}", |
|
|
|
|
NotificationType.Error |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -765,15 +813,17 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
{ |
|
|
|
|
// Read licenses.json |
|
|
|
|
using var reader = new StreamReader(Assets.LicensesJson.Open()); |
|
|
|
|
var licenses = JsonSerializer |
|
|
|
|
.Deserialize<IReadOnlyList<LicenseInfo>>(reader.ReadToEnd()) ?? |
|
|
|
|
throw new InvalidOperationException("Failed to read licenses.json"); |
|
|
|
|
|
|
|
|
|
var licenses = |
|
|
|
|
JsonSerializer.Deserialize<IReadOnlyList<LicenseInfo>>(reader.ReadToEnd()) |
|
|
|
|
?? throw new InvalidOperationException("Failed to read licenses.json"); |
|
|
|
|
|
|
|
|
|
// Generate markdown |
|
|
|
|
var builder = new StringBuilder(); |
|
|
|
|
foreach (var license in licenses) |
|
|
|
|
{ |
|
|
|
|
builder.AppendLine($"## [{license.PackageName}]({license.PackageUrl}) by {string.Join(", ", license.Authors)}"); |
|
|
|
|
builder.AppendLine( |
|
|
|
|
$"## [{license.PackageName}]({license.PackageUrl}) by {string.Join(", ", license.Authors)}" |
|
|
|
|
); |
|
|
|
|
builder.AppendLine(); |
|
|
|
|
builder.AppendLine(license.Description); |
|
|
|
|
builder.AppendLine(); |
|
|
|
@ -785,5 +835,4 @@ public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#endregion |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|