Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa62e53013 | ||
|
|
3c7cfd23ac | ||
|
|
4c9e22faf8 |
No files matched your search
@@ -0,0 +1,10 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
using PantherByte.ViewModels;
|
||||||
|
|
||||||
|
namespace PantherByte.Messages;
|
||||||
|
|
||||||
|
public class OpenNotificationDialogMessage(string title, string message)
|
||||||
|
: AsyncRequestMessage<NotificationDialogViewModel?> {
|
||||||
|
public readonly string Title = title;
|
||||||
|
public readonly string Message = message;
|
||||||
|
}
|
||||||
@@ -4,6 +4,6 @@ using PantherByte.ViewModels;
|
|||||||
|
|
||||||
namespace PantherByte.Messages;
|
namespace PantherByte.Messages;
|
||||||
|
|
||||||
public class OpenProgressDialogMessage(ProcessStartInfo info) : AsyncRequestMessage<ProgressWindowViewModel?> {
|
public class OpenProgressDialogMessage(ProgressWindowViewModel vm) : AsyncRequestMessage<ProgressWindowViewModel?> {
|
||||||
public readonly ProcessStartInfo Info = info;
|
public readonly ProgressWindowViewModel ViewModel = vm;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
using PantherByte.ViewModels;
|
||||||
|
|
||||||
|
namespace PantherByte.Messages;
|
||||||
|
|
||||||
|
public class OpenSimpleDialogMessage(SimpleDialogViewModel vm) : AsyncRequestMessage<SimpleDialogViewModel.SimpleDialogResult?> {
|
||||||
|
public SimpleDialogViewModel ViewModel = vm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace PantherByte.ViewModels;
|
||||||
|
|
||||||
|
public partial class DependencyProgressViewModel : ViewModelBase, IProgressWindowViewModel {
|
||||||
|
private readonly ProcessStartInfo _processStartInfo;
|
||||||
|
private string _processStdOut = "";
|
||||||
|
private bool _error = false;
|
||||||
|
private bool _inProgress = false;
|
||||||
|
private string _statusMessage = "Downloading ffmpeg...";
|
||||||
|
private string _barColor = "CornflowerBlue";
|
||||||
|
|
||||||
|
public string ProcessStdOut {
|
||||||
|
get => _processStdOut;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _processStdOut, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StatusMessage {
|
||||||
|
get => _statusMessage;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Error {
|
||||||
|
get => _error;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _error, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InProgress {
|
||||||
|
get => _inProgress;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _inProgress, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BarColor {
|
||||||
|
get => _barColor;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _barColor, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DependencyProgressViewModel() {
|
||||||
|
_processStartInfo = new ProcessStartInfo() {
|
||||||
|
UseShellExecute = false
|
||||||
|
};
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||||
|
// Windows specific command.
|
||||||
|
_processStartInfo.FileName = "winget";
|
||||||
|
_processStartInfo.Arguments = "install ffmpeg";
|
||||||
|
_processStartInfo.Verb = "runas";
|
||||||
|
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
|
||||||
|
// Linux specific command.
|
||||||
|
_processStartInfo.FileName = "sudo";
|
||||||
|
_processStartInfo.Arguments = "apt install ffmpeg";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task RunProcessAsync() {
|
||||||
|
Process installProcess = new();
|
||||||
|
installProcess.StartInfo = _processStartInfo;
|
||||||
|
|
||||||
|
installProcess.EnableRaisingEvents = true;
|
||||||
|
installProcess.OutputDataReceived += (s, e) => {
|
||||||
|
if (string.IsNullOrWhiteSpace(e.Data)) return;
|
||||||
|
|
||||||
|
Console.WriteLine(e.Data);
|
||||||
|
ProcessStdOut += e.Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
installProcess.ErrorDataReceived += (s, e) => {
|
||||||
|
if (string.IsNullOrWhiteSpace(e.Data)) return;
|
||||||
|
Console.WriteLine(e.Data);
|
||||||
|
ProcessStdOut += e.Data + "\n";
|
||||||
|
Error = true;
|
||||||
|
StatusMessage = "Error occurred while installing ffmpeg.";
|
||||||
|
BarColor = "Red";
|
||||||
|
};
|
||||||
|
|
||||||
|
InProgress = true;
|
||||||
|
try {
|
||||||
|
installProcess.Start();
|
||||||
|
Console.WriteLine("Installation process started.");
|
||||||
|
installProcess.BeginOutputReadLine();
|
||||||
|
installProcess.BeginErrorReadLine();
|
||||||
|
await installProcess.WaitForExitAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
Console.WriteLine(ex.Message);
|
||||||
|
Error = true;
|
||||||
|
StatusMessage = "Error occurred while installing ffmpeg.";
|
||||||
|
BarColor = "Red";
|
||||||
|
ProcessStdOut += ex.Message;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
InProgress = false;
|
||||||
|
|
||||||
|
if (!Error) {
|
||||||
|
BarColor = "Green";
|
||||||
|
StatusMessage = "Download finished!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PantherByte.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a series of properties and methods that are used by the <see cref="Views.ProgressWindow"/>.
|
||||||
|
/// It requires the implementation of <see cref="INotifyPropertyChanged"/> as well, so that it can
|
||||||
|
/// preserve the reactivity of the interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface IProgressWindowViewModel : INotifyPropertyChanged {
|
||||||
|
public string ProcessStdOut { get; set; }
|
||||||
|
public string StatusMessage { get; set; }
|
||||||
|
public bool Error { get; set; }
|
||||||
|
public bool InProgress { get; set; }
|
||||||
|
public string BarColor { get; set; }
|
||||||
|
public Task RunProcessAsync();
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
@@ -13,6 +14,7 @@ using PantherByte.Messages;
|
|||||||
using PantherByte.Services;
|
using PantherByte.Services;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using PantherByte.Services.Interfaces;
|
using PantherByte.Services.Interfaces;
|
||||||
|
using PantherByte.Views;
|
||||||
|
|
||||||
namespace PantherByte.ViewModels;
|
namespace PantherByte.ViewModels;
|
||||||
|
|
||||||
@@ -100,16 +102,20 @@ public partial class MainWindowViewModel : ViewModelBase {
|
|||||||
if (Directory.Exists(tempPath)) {
|
if (Directory.Exists(tempPath)) {
|
||||||
SaveLocation = tempPath;
|
SaveLocation = tempPath;
|
||||||
Console.WriteLine("Successfully set the file path.");
|
Console.WriteLine("Successfully set the file path.");
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw new DirectoryNotFoundException($"Directory does not exist: {tempPath}");
|
throw new DirectoryNotFoundException($"Directory does not exist: {tempPath}");
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw new NullReferenceException("The resulting storage folder was null.");
|
throw new NullReferenceException("The resulting storage folder was null.");
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw new NullReferenceException("Missing a FileSaveService instance");
|
throw new NullReferenceException("Missing a FileSaveService instance");
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
// Exceptions are caught locally for now, but will eventually require a modal error window.
|
// Exceptions are caught locally for now, but will eventually require a modal error window.
|
||||||
Console.WriteLine(ex.ToString());
|
Console.WriteLine(ex.ToString());
|
||||||
}
|
}
|
||||||
@@ -118,7 +124,6 @@ public partial class MainWindowViewModel : ViewModelBase {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates the arguments used for the main command string.
|
/// Generates the arguments used for the main command string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RelayCommand]
|
|
||||||
private async Task<List<string>> GenerateCmdArgsAsync() {
|
private async Task<List<string>> GenerateCmdArgsAsync() {
|
||||||
// The command string format to use: yt-dlp <link> -t <mp3|mp4|wav> -P <path> --no-playlist
|
// The command string format to use: yt-dlp <link> -t <mp3|mp4|wav> -P <path> --no-playlist
|
||||||
// yt-dlp requires usage of ffmpeg as well.
|
// yt-dlp requires usage of ffmpeg as well.
|
||||||
@@ -129,7 +134,8 @@ public partial class MainWindowViewModel : ViewModelBase {
|
|||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||||
Console.WriteLine("Using Windows program.");
|
Console.WriteLine("Using Windows program.");
|
||||||
fileName = "Programs/yt-dlp-win64.exe";
|
fileName = "Programs/yt-dlp-win64.exe";
|
||||||
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
|
||||||
Console.WriteLine("Using Linux program. May require additional run permissions.");
|
Console.WriteLine("Using Linux program. May require additional run permissions.");
|
||||||
fileName = "Programs/yt-dlp-linux";
|
fileName = "Programs/yt-dlp-linux";
|
||||||
}
|
}
|
||||||
@@ -139,7 +145,8 @@ public partial class MainWindowViewModel : ViewModelBase {
|
|||||||
// WAV files require special command arguments
|
// WAV files require special command arguments
|
||||||
if (Format.Equals("wav")) {
|
if (Format.Equals("wav")) {
|
||||||
args.AddRange(["--extract-audio", "--audio-format", Format]);
|
args.AddRange(["--extract-audio", "--audio-format", Format]);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
args.AddRange(["-t", Format]);
|
args.AddRange(["-t", Format]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +155,39 @@ public partial class MainWindowViewModel : ViewModelBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the download process of the file. Generates the command and runs it.
|
/// Checks if ffmpeg is installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if ffmpeg is installed, or false if not.</returns>
|
||||||
|
private async Task<bool> CheckFfmpegInstalled() {
|
||||||
|
return await Task.Run(async () => {
|
||||||
|
ProcessStartInfo ffmpegInfo = new("ffmpeg") {
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Process process = new() {
|
||||||
|
StartInfo = ffmpegInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
process.Start();
|
||||||
|
await process.WaitForExitAsync();
|
||||||
|
}
|
||||||
|
catch (Exception) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the download process of the file.
|
||||||
|
///
|
||||||
|
/// This command is also where the dependency check/installation is run from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task DownloadFileAsync() {
|
private async Task DownloadFileAsync() {
|
||||||
@@ -157,29 +196,88 @@ public partial class MainWindowViewModel : ViewModelBase {
|
|||||||
var args = await GenerateCmdArgsAsync();
|
var args = await GenerateCmdArgsAsync();
|
||||||
// Run the command.
|
// Run the command.
|
||||||
try {
|
try {
|
||||||
|
// ffmpeg installation check
|
||||||
|
var installed = await CheckFfmpegInstalled();
|
||||||
|
Console.WriteLine($"Is ffmpeg installed: {installed}");
|
||||||
|
if (!installed) {
|
||||||
|
SimpleDialogViewModel vm = new("Dependency ffmpeg was not found. Would you like to install it?",
|
||||||
|
"Dependency Install");
|
||||||
|
var result = await WeakReferenceMessenger.Default.Send(new OpenSimpleDialogMessage(vm));
|
||||||
|
if (result is SimpleDialogViewModel.SimpleDialogResult.Yes) {
|
||||||
|
// Install the program
|
||||||
|
installed = await InstallFfmpegAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This means installation has failed or the user canceled it.
|
||||||
|
if (!installed) return;
|
||||||
|
|
||||||
// The command that's run must work on at least Linux and Windows
|
// The command that's run must work on at least Linux and Windows
|
||||||
ProcessStartInfo info = new() {
|
ProcessStartInfo info = new() {
|
||||||
FileName = args[0],
|
FileName = args[0],
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true
|
CreateNoWindow = true,
|
||||||
};
|
};
|
||||||
info.ArgumentList.AddRange(args[1..]);
|
info.ArgumentList.AddRange(args[1..]);
|
||||||
|
|
||||||
// This window isn't linked to the download process yet, but that is the plan.
|
// This window isn't linked to the download process yet, but that is the plan.
|
||||||
await OpenProgressWindowAsync(info);
|
await OpenProgressWindowAsync(info);
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
Console.WriteLine(ex.ToString());
|
Console.WriteLine(ex.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins the installation process for ffmpeg and displays a window.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the installation was successful, or false if not.</returns>
|
||||||
|
private async Task<bool> InstallFfmpegAsync() {
|
||||||
|
ProcessStartInfo info = new() {
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||||
|
// Run: winget install "FFmpeg (Essentials Build)"
|
||||||
|
info.Verb = "runas";
|
||||||
|
info.FileName = "winget";
|
||||||
|
info.Arguments = "install \"FFmpeg (Essentials Build)\"";
|
||||||
|
|
||||||
|
ProgressWindowViewModel vm = new(info) {
|
||||||
|
StatusMessage = "Installing ffmpeg..."
|
||||||
|
};
|
||||||
|
await WeakReferenceMessenger.Default.Send(new OpenProgressDialogMessage(vm));
|
||||||
|
|
||||||
|
// If there was an error, return false. Otherwise, true.
|
||||||
|
result = !vm.Error;
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
|
||||||
|
await WeakReferenceMessenger.Default.Send(new OpenNotificationDialogMessage(
|
||||||
|
"Linux Install Error",
|
||||||
|
"Linux users must install ffmpeg manually.\nThis may be developed further in the future."
|
||||||
|
));
|
||||||
|
|
||||||
|
// Since this requires a manual install (we can't run sudo nicely) we'll just return false,
|
||||||
|
// as we didn't actually install anything.
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens a ProgressWindow as a modal dialog. I haven't decided if this window should run the command or if it
|
/// Opens a ProgressWindow as a modal dialog. I haven't decided if this window should run the command or if it
|
||||||
/// should simply display the progress of a command that's running here.
|
/// should simply display the progress of a command that's running here.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RelayCommand]
|
|
||||||
private async Task OpenProgressWindowAsync(ProcessStartInfo info) {
|
private async Task OpenProgressWindowAsync(ProcessStartInfo info) {
|
||||||
await WeakReferenceMessenger.Default.Send(new OpenProgressDialogMessage(info));
|
ProgressWindowViewModel vm = new(info);
|
||||||
|
await WeakReferenceMessenger.Default.Send(new OpenProgressDialogMessage(vm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace PantherByte.ViewModels;
|
||||||
|
|
||||||
|
public class NotificationDialogViewModel : ViewModelBase {
|
||||||
|
public string Title { get; set; } = "Notification";
|
||||||
|
public string Message { get; set; } = "Testing message";
|
||||||
|
|
||||||
|
public NotificationDialogViewModel() { }
|
||||||
|
|
||||||
|
public NotificationDialogViewModel(string title, string message) {
|
||||||
|
(Title, Message) = (title, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using ReactiveUI;
|
|||||||
|
|
||||||
namespace PantherByte.ViewModels;
|
namespace PantherByte.ViewModels;
|
||||||
|
|
||||||
public class ProgressWindowViewModel : ViewModelBase {
|
public partial class ProgressWindowViewModel : ViewModelBase, IProgressWindowViewModel {
|
||||||
// Pass variables between the MainWindow and this window, specifically what point the application
|
// Pass variables between the MainWindow and this window, specifically what point the application
|
||||||
// is at in terms of downloading. We'll likely do this as a simple percentage of total progress.
|
// is at in terms of downloading. We'll likely do this as a simple percentage of total progress.
|
||||||
private readonly ProcessStartInfo _processStartInfo;
|
private readonly ProcessStartInfo _processStartInfo;
|
||||||
@@ -49,11 +49,12 @@ public class ProgressWindowViewModel : ViewModelBase {
|
|||||||
Process downloadProcess = new();
|
Process downloadProcess = new();
|
||||||
downloadProcess.StartInfo = _processStartInfo;
|
downloadProcess.StartInfo = _processStartInfo;
|
||||||
|
|
||||||
|
downloadProcess.EnableRaisingEvents = true;
|
||||||
downloadProcess.OutputDataReceived += (_, e) => {
|
downloadProcess.OutputDataReceived += (_, e) => {
|
||||||
if (!string.IsNullOrWhiteSpace(e.Data)) {
|
if (string.IsNullOrWhiteSpace(e.Data)) return;
|
||||||
Console.WriteLine(e.Data);
|
|
||||||
ProcessStdOut += e.Data + "\n";
|
Console.WriteLine(e.Data);
|
||||||
}
|
ProcessStdOut += e.Data + "\n";
|
||||||
};
|
};
|
||||||
|
|
||||||
downloadProcess.ErrorDataReceived += (_, e) => {
|
downloadProcess.ErrorDataReceived += (_, e) => {
|
||||||
@@ -76,7 +77,7 @@ public class ProgressWindowViewModel : ViewModelBase {
|
|||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Console.WriteLine(e.StackTrace);
|
Console.WriteLine(e.StackTrace);
|
||||||
ProcessStdOut += "Error: " + e.StackTrace + "\n";
|
ProcessStdOut += "Error: " + e.StackTrace + "\n";
|
||||||
StatusMessage = "Error while downloading video.";
|
StatusMessage = "Error while performing download.";
|
||||||
BarColor = "Red";
|
BarColor = "Red";
|
||||||
Error = true;
|
Error = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
namespace PantherByte.ViewModels;
|
||||||
|
|
||||||
|
public partial class SimpleDialogViewModel(string prompt, string title) : ViewModelBase {
|
||||||
|
public enum SimpleDialogResult {
|
||||||
|
Yes,
|
||||||
|
No
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PromptMessage { get; set; } = prompt;
|
||||||
|
public string Title { get; set; } = title;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using PantherByte.Messages;
|
using PantherByte.Messages;
|
||||||
using PantherByte.ViewModels;
|
using PantherByte.ViewModels;
|
||||||
@@ -12,9 +13,26 @@ public partial class MainWindow : Window {
|
|||||||
WeakReferenceMessenger.Default.Register<MainWindow, OpenProgressDialogMessage>(this,
|
WeakReferenceMessenger.Default.Register<MainWindow, OpenProgressDialogMessage>(this,
|
||||||
static (w, m) => {
|
static (w, m) => {
|
||||||
var dialog = new ProgressWindow {
|
var dialog = new ProgressWindow {
|
||||||
DataContext = new ProgressWindowViewModel(m.Info)
|
DataContext = m.ViewModel
|
||||||
};
|
};
|
||||||
m.Reply(dialog.ShowDialog<ProgressWindowViewModel?>(w));
|
m.Reply(dialog.ShowDialog<ProgressWindowViewModel?>(w));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Register<MainWindow, OpenSimpleDialogMessage>(this,
|
||||||
|
static async (w, m) => {
|
||||||
|
var dialog = new SimpleDialog {
|
||||||
|
DataContext = m.ViewModel
|
||||||
|
};
|
||||||
|
var response = dialog.ShowDialog<SimpleDialogViewModel.SimpleDialogResult?>(w);
|
||||||
|
m.Reply(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Register<MainWindow, OpenNotificationDialogMessage>(this,
|
||||||
|
static (w, m) => {
|
||||||
|
var dialog = new NotificationDialog {
|
||||||
|
DataContext = new NotificationDialogViewModel(m.Title, m.Message)
|
||||||
|
};
|
||||||
|
m.Reply(dialog.ShowDialog<NotificationDialogViewModel?>(w));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:PantherByte.ViewModels"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="PantherByte.Views.NotificationDialog"
|
||||||
|
x:DataType="vm:NotificationDialogViewModel"
|
||||||
|
Title="{Binding Title}"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
SizeToContent="WidthAndHeight"
|
||||||
|
Background="Gray"
|
||||||
|
CanMaximize="False">
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:NotificationDialogViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<StackPanel Spacing="20" Margin="10" HorizontalAlignment="Stretch">
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Text="{Binding Message}" />
|
||||||
|
<Button HorizontalAlignment="Center"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Content="OK"
|
||||||
|
Padding="30 10"
|
||||||
|
Click="OkButton_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace PantherByte.Views;
|
||||||
|
|
||||||
|
public partial class NotificationDialog : Window {
|
||||||
|
public NotificationDialog() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OkButton_Click(object? sender, RoutedEventArgs e) =>
|
||||||
|
Close(DataContext);
|
||||||
|
}
|
||||||
@@ -19,18 +19,19 @@
|
|||||||
<StackPanel Spacing="10" Margin="10">
|
<StackPanel Spacing="10" Margin="10">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Text="{Binding StatusMessage}"/>
|
Text="{Binding StatusMessage}" />
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
IsIndeterminate="{Binding InProgress}"
|
IsIndeterminate="{Binding InProgress}"
|
||||||
Foreground="{Binding BarColor}"
|
Foreground="{Binding BarColor}"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
Value="100"/>
|
Value="100" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Expander VerticalAlignment="Top" HorizontalAlignment="Stretch"
|
<Expander VerticalAlignment="Top" HorizontalAlignment="Stretch"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
Background="Transparent">
|
Background="Transparent"
|
||||||
|
x:Name="InfoExpander">
|
||||||
<Expander.Header>More information...</Expander.Header>
|
<Expander.Header>More information...</Expander.Header>
|
||||||
<Grid RowDefinitions="*" ColumnDefinitions="*">
|
<Grid RowDefinitions="*" ColumnDefinitions="*">
|
||||||
<ScrollViewer Grid.Row="0"
|
<ScrollViewer Grid.Row="0"
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
VerticalScrollBarVisibility="Auto">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<TextBlock x:Name="StdOutText"
|
<TextBlock x:Name="StdOutText"
|
||||||
Text="{Binding ProcessStdOut}"
|
Text="{Binding ProcessStdOut}"
|
||||||
TextWrapping="Wrap"/>
|
TextWrapping="Wrap" />
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Expander>
|
</Expander>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using PantherByte.ViewModels;
|
using PantherByte.ViewModels;
|
||||||
|
|
||||||
@@ -7,14 +8,23 @@ public partial class ProgressWindow : Window {
|
|||||||
public ProgressWindow() {
|
public ProgressWindow() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
// This feels a little fragile, but we'll leave it for now.
|
// Runs the specified process in the view model when the window is opened.
|
||||||
Opened += async (_, _) => {
|
Opened += async (_, _) => {
|
||||||
if (DataContext is not ProgressWindowViewModel vm)
|
if (DataContext is IProgressWindowViewModel vm)
|
||||||
return;
|
await vm.RunProcessAsync();
|
||||||
|
|
||||||
await vm.RunProcessAsync();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Automatically scrolls to the end of the logs when more information is added.
|
||||||
StdOutText.SizeChanged += (_, _) => ScrollView.ScrollToEnd();
|
StdOutText.SizeChanged += (_, _) => ScrollView.ScrollToEnd();
|
||||||
|
|
||||||
|
// Automatically resizes the window when the user collapses the expander.
|
||||||
|
InfoExpander.Collapsing += (_, _) => {
|
||||||
|
if (Content is not Control content) return;
|
||||||
|
|
||||||
|
content.Measure(Size.Infinity);
|
||||||
|
var desired = content.DesiredSize;
|
||||||
|
Width = desired.Width;
|
||||||
|
Height = desired.Height;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:PantherByte.ViewModels"
|
||||||
|
x:DataType="vm:SimpleDialogViewModel"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:Class="PantherByte.Views.SimpleDialog"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
SizeToContent="WidthAndHeight"
|
||||||
|
Background="Gray"
|
||||||
|
Title="{Binding Title}"
|
||||||
|
CanMaximize="False">
|
||||||
|
<Design.DataContext>
|
||||||
|
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||||
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
|
<vm:SimpleDialogViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<StackPanel Spacing="20" Margin="10" HorizontalAlignment="Stretch">
|
||||||
|
<TextBlock Text="{Binding PromptMessage}" HorizontalAlignment="Center"/>
|
||||||
|
<Grid ColumnDefinitions="*, *" ColumnSpacing="10" Width="200" MaxWidth="300">
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
Content="Yes"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Click="YesButtonClicked"/>
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Content="No"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Click="NoButtonClicked"/>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using PantherByte.ViewModels;
|
||||||
|
|
||||||
|
namespace PantherByte.Views;
|
||||||
|
|
||||||
|
public partial class SimpleDialog : Window {
|
||||||
|
public SimpleDialog() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void YesButtonClicked(object sender, RoutedEventArgs e) =>
|
||||||
|
Close(SimpleDialogViewModel.SimpleDialogResult.Yes);
|
||||||
|
|
||||||
|
public void NoButtonClicked(object sender, RoutedEventArgs e) =>
|
||||||
|
Close(SimpleDialogViewModel.SimpleDialogResult.No);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user