This commit is contained in:
Tyrrrz
2021-12-08 23:50:21 +02:00
parent 8e7baee8a5
commit 880f400e2c
148 changed files with 14241 additions and 14396 deletions

View File

@@ -3,47 +3,46 @@ using System.Reflection;
using DiscordChatExporter.Gui.Utils;
using MaterialDesignThemes.Wpf;
namespace DiscordChatExporter.Gui
namespace DiscordChatExporter.Gui;
public partial class App
{
public partial class App
private static Assembly Assembly { get; } = typeof(App).Assembly;
public static string Name { get; } = Assembly.GetName().Name!;
public static Version Version { get; } = Assembly.GetName().Version!;
public static string VersionString { get; } = Version.ToString(3);
public static string GitHubProjectUrl { get; } = "https://github.com/Tyrrrz/DiscordChatExporter";
public static string GitHubProjectWikiUrl { get; } = GitHubProjectUrl + "/wiki";
}
public partial class App
{
private static Theme LightTheme { get; } = Theme.Create(
new MaterialDesignLightTheme(),
MediaColor.FromHex("#343838"),
MediaColor.FromHex("#F9A825")
);
private static Theme DarkTheme { get; } = Theme.Create(
new MaterialDesignDarkTheme(),
MediaColor.FromHex("#E8E8E8"),
MediaColor.FromHex("#F9A825")
);
public static void SetLightTheme()
{
private static Assembly Assembly { get; } = typeof(App).Assembly;
public static string Name { get; } = Assembly.GetName().Name!;
public static Version Version { get; } = Assembly.GetName().Version!;
public static string VersionString { get; } = Version.ToString(3);
public static string GitHubProjectUrl { get; } = "https://github.com/Tyrrrz/DiscordChatExporter";
public static string GitHubProjectWikiUrl { get; } = GitHubProjectUrl + "/wiki";
var paletteHelper = new PaletteHelper();
paletteHelper.SetTheme(LightTheme);
}
public partial class App
public static void SetDarkTheme()
{
private static Theme LightTheme { get; } = Theme.Create(
new MaterialDesignLightTheme(),
MediaColor.FromHex("#343838"),
MediaColor.FromHex("#F9A825")
);
private static Theme DarkTheme { get; } = Theme.Create(
new MaterialDesignDarkTheme(),
MediaColor.FromHex("#E8E8E8"),
MediaColor.FromHex("#F9A825")
);
public static void SetLightTheme()
{
var paletteHelper = new PaletteHelper();
paletteHelper.SetTheme(LightTheme);
}
public static void SetDarkTheme()
{
var paletteHelper = new PaletteHelper();
paletteHelper.SetTheme(DarkTheme);
}
var paletteHelper = new PaletteHelper();
paletteHelper.SetTheme(DarkTheme);
}
}

View File

@@ -1,8 +1,7 @@
using DiscordChatExporter.Core.Discord.Data;
namespace DiscordChatExporter.Gui.Behaviors
namespace DiscordChatExporter.Gui.Behaviors;
public class ChannelMultiSelectionListBoxBehavior : MultiSelectionListBoxBehavior<Channel>
{
public class ChannelMultiSelectionListBoxBehavior : MultiSelectionListBoxBehavior<Channel>
{
}
}

View File

@@ -5,88 +5,87 @@ using System.Windows;
using System.Windows.Controls;
using Microsoft.Xaml.Behaviors;
namespace DiscordChatExporter.Gui.Behaviors
namespace DiscordChatExporter.Gui.Behaviors;
public class MultiSelectionListBoxBehavior<T> : Behavior<ListBox>
{
public class MultiSelectionListBoxBehavior<T> : Behavior<ListBox>
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(nameof(SelectedItems), typeof(IList),
typeof(MultiSelectionListBoxBehavior<T>),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSelectedItemsChanged));
private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(nameof(SelectedItems), typeof(IList),
typeof(MultiSelectionListBoxBehavior<T>),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSelectedItemsChanged));
var behavior = (MultiSelectionListBoxBehavior<T>) sender;
if (behavior._modelHandled) return;
private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
if (behavior.AssociatedObject is null)
return;
behavior._modelHandled = true;
behavior.SelectItems();
behavior._modelHandled = false;
}
private bool _viewHandled;
private bool _modelHandled;
public IList? SelectedItems
{
get => (IList?) GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
// Propagate selected items from model to view
private void SelectItems()
{
_viewHandled = true;
AssociatedObject.SelectedItems.Clear();
if (SelectedItems is not null)
{
var behavior = (MultiSelectionListBoxBehavior<T>) sender;
if (behavior._modelHandled) return;
if (behavior.AssociatedObject is null)
return;
behavior._modelHandled = true;
behavior.SelectItems();
behavior._modelHandled = false;
foreach (var item in SelectedItems)
AssociatedObject.SelectedItems.Add(item);
}
private bool _viewHandled;
private bool _modelHandled;
_viewHandled = false;
}
public IList? SelectedItems
// Propagate selected items from view to model
private void OnListBoxSelectionChanged(object? sender, SelectionChangedEventArgs args)
{
if (_viewHandled) return;
if (AssociatedObject.Items.SourceCollection is null) return;
SelectedItems = AssociatedObject.SelectedItems.Cast<T>().ToArray();
}
// Re-select items when the set of items changes
private void OnListBoxItemsChanged(object? sender, NotifyCollectionChangedEventArgs args)
{
if (_viewHandled) return;
if (AssociatedObject.Items.SourceCollection is null) return;
SelectItems();
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += OnListBoxSelectionChanged;
((INotifyCollectionChanged) AssociatedObject.Items).CollectionChanged += OnListBoxItemsChanged;
}
/// <inheritdoc />
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject is not null)
{
get => (IList?) GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
// Propagate selected items from model to view
private void SelectItems()
{
_viewHandled = true;
AssociatedObject.SelectedItems.Clear();
if (SelectedItems is not null)
{
foreach (var item in SelectedItems)
AssociatedObject.SelectedItems.Add(item);
}
_viewHandled = false;
}
// Propagate selected items from view to model
private void OnListBoxSelectionChanged(object? sender, SelectionChangedEventArgs args)
{
if (_viewHandled) return;
if (AssociatedObject.Items.SourceCollection is null) return;
SelectedItems = AssociatedObject.SelectedItems.Cast<T>().ToArray();
}
// Re-select items when the set of items changes
private void OnListBoxItemsChanged(object? sender, NotifyCollectionChangedEventArgs args)
{
if (_viewHandled) return;
if (AssociatedObject.Items.SourceCollection is null) return;
SelectItems();
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += OnListBoxSelectionChanged;
((INotifyCollectionChanged) AssociatedObject.Items).CollectionChanged += OnListBoxItemsChanged;
}
/// <inheritdoc />
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject is not null)
{
AssociatedObject.SelectionChanged -= OnListBoxSelectionChanged;
((INotifyCollectionChanged) AssociatedObject.Items).CollectionChanged -= OnListBoxItemsChanged;
}
AssociatedObject.SelectionChanged -= OnListBoxSelectionChanged;
((INotifyCollectionChanged) AssociatedObject.Items).CollectionChanged -= OnListBoxItemsChanged;
}
}
}

View File

@@ -2,27 +2,26 @@
using System.Globalization;
using System.Windows.Data;
namespace DiscordChatExporter.Gui.Converters
namespace DiscordChatExporter.Gui.Converters;
[ValueConversion(typeof(DateTimeOffset?), typeof(DateTime?))]
public class DateTimeOffsetToDateTimeConverter : IValueConverter
{
[ValueConversion(typeof(DateTimeOffset?), typeof(DateTime?))]
public class DateTimeOffsetToDateTimeConverter : IValueConverter
public static DateTimeOffsetToDateTimeConverter Instance { get; } = new();
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
public static DateTimeOffsetToDateTimeConverter Instance { get; } = new();
if (value is DateTimeOffset dateTimeOffsetValue)
return dateTimeOffsetValue.DateTime;
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTimeOffset dateTimeOffsetValue)
return dateTimeOffsetValue.DateTime;
return default(DateTime?);
}
return default(DateTime?);
}
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime dateTimeValue)
return new DateTimeOffset(dateTimeValue);
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime dateTimeValue)
return new DateTimeOffset(dateTimeValue);
return default(DateTimeOffset?);
}
return default(DateTimeOffset?);
}
}

View File

@@ -3,22 +3,21 @@ using System.Globalization;
using System.Windows.Data;
using DiscordChatExporter.Core.Exporting;
namespace DiscordChatExporter.Gui.Converters
namespace DiscordChatExporter.Gui.Converters;
[ValueConversion(typeof(ExportFormat), typeof(string))]
public class ExportFormatToStringConverter : IValueConverter
{
[ValueConversion(typeof(ExportFormat), typeof(string))]
public class ExportFormatToStringConverter : IValueConverter
public static ExportFormatToStringConverter Instance { get; } = new();
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
public static ExportFormatToStringConverter Instance { get; } = new();
if (value is ExportFormat exportFormatValue)
return exportFormatValue.GetDisplayName();
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ExportFormat exportFormatValue)
return exportFormatValue.GetDisplayName();
return default(string?);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
return default(string?);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}

View File

@@ -2,27 +2,26 @@
using System.Globalization;
using System.Windows.Data;
namespace DiscordChatExporter.Gui.Converters
namespace DiscordChatExporter.Gui.Converters;
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBoolConverter : IValueConverter
{
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBoolConverter : IValueConverter
public static InverseBoolConverter Instance { get; } = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
public static InverseBoolConverter Instance { get; } = new();
if (value is bool boolValue)
return !boolValue;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
return default(bool);
}
return default(bool);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
return default(bool);
}
return default(bool);
}
}

View File

@@ -2,27 +2,26 @@
using System.Globalization;
using System.Windows.Data;
namespace DiscordChatExporter.Gui.Converters
namespace DiscordChatExporter.Gui.Converters;
[ValueConversion(typeof(TimeSpan?), typeof(DateTime?))]
public class TimeSpanToDateTimeConverter : IValueConverter
{
[ValueConversion(typeof(TimeSpan?), typeof(DateTime?))]
public class TimeSpanToDateTimeConverter : IValueConverter
public static TimeSpanToDateTimeConverter Instance { get; } = new();
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
public static TimeSpanToDateTimeConverter Instance { get; } = new();
if (value is TimeSpan timeSpanValue)
return DateTime.Today.Add(timeSpanValue);
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is TimeSpan timeSpanValue)
return DateTime.Today.Add(timeSpanValue);
return default(DateTime?);
}
return default(DateTime?);
}
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime dateTimeValue)
return dateTimeValue.TimeOfDay;
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime dateTimeValue)
return dateTimeValue.TimeOfDay;
return default(TimeSpan?);
}
return default(TimeSpan?);
}
}

View File

@@ -2,39 +2,38 @@
using DiscordChatExporter.Core.Exporting;
using Tyrrrz.Settings;
namespace DiscordChatExporter.Gui.Services
namespace DiscordChatExporter.Gui.Services;
public class SettingsService : SettingsManager
{
public class SettingsService : SettingsManager
public bool IsAutoUpdateEnabled { get; set; } = true;
public bool IsDarkModeEnabled { get; set; }
public bool IsTokenPersisted { get; set; } = true;
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
public int ParallelLimit { get; set; } = 1;
public bool ShouldReuseMedia { get; set; }
public AuthToken? LastToken { get; set; }
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
public string? LastPartitionLimitValue { get; set; }
public string? LastMessageFilterValue { get; set; }
public bool LastShouldDownloadMedia { get; set; }
public SettingsService()
{
public bool IsAutoUpdateEnabled { get; set; } = true;
public bool IsDarkModeEnabled { get; set; }
public bool IsTokenPersisted { get; set; } = true;
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
public int ParallelLimit { get; set; } = 1;
public bool ShouldReuseMedia { get; set; }
public AuthToken? LastToken { get; set; }
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
public string? LastPartitionLimitValue { get; set; }
public string? LastMessageFilterValue { get; set; }
public bool LastShouldDownloadMedia { get; set; }
public SettingsService()
{
Configuration.StorageSpace = StorageSpace.Instance;
Configuration.SubDirectoryPath = "";
Configuration.FileName = "Settings.dat";
}
public bool ShouldSerializeLastToken() => IsTokenPersisted;
Configuration.StorageSpace = StorageSpace.Instance;
Configuration.SubDirectoryPath = "";
Configuration.FileName = "Settings.dat";
}
public bool ShouldSerializeLastToken() => IsTokenPersisted;
}

View File

@@ -4,78 +4,77 @@ using Onova;
using Onova.Exceptions;
using Onova.Services;
namespace DiscordChatExporter.Gui.Services
namespace DiscordChatExporter.Gui.Services;
public class UpdateService : IDisposable
{
public class UpdateService : IDisposable
private readonly IUpdateManager _updateManager = new UpdateManager(
new GithubPackageResolver("Tyrrrz", "DiscordChatExporter", "DiscordChatExporter.zip"),
new ZipPackageExtractor()
);
private readonly SettingsService _settingsService;
private Version? _updateVersion;
private bool _updatePrepared;
private bool _updaterLaunched;
public UpdateService(SettingsService settingsService)
{
private readonly IUpdateManager _updateManager = new UpdateManager(
new GithubPackageResolver("Tyrrrz", "DiscordChatExporter", "DiscordChatExporter.zip"),
new ZipPackageExtractor()
);
private readonly SettingsService _settingsService;
private Version? _updateVersion;
private bool _updatePrepared;
private bool _updaterLaunched;
public UpdateService(SettingsService settingsService)
{
_settingsService = settingsService;
}
public async ValueTask<Version?> CheckForUpdatesAsync()
{
if (!_settingsService.IsAutoUpdateEnabled)
return null;
var check = await _updateManager.CheckForUpdatesAsync();
return check.CanUpdate ? check.LastVersion : null;
}
public async ValueTask PrepareUpdateAsync(Version version)
{
if (!_settingsService.IsAutoUpdateEnabled)
return;
try
{
await _updateManager.PrepareUpdateAsync(_updateVersion = version);
_updatePrepared = true;
}
catch (UpdaterAlreadyLaunchedException)
{
// Ignore race conditions
}
catch (LockFileNotAcquiredException)
{
// Ignore race conditions
}
}
public void FinalizeUpdate(bool needRestart)
{
if (!_settingsService.IsAutoUpdateEnabled)
return;
if (_updateVersion is null || !_updatePrepared || _updaterLaunched)
return;
try
{
_updateManager.LaunchUpdater(_updateVersion, needRestart);
_updaterLaunched = true;
}
catch (UpdaterAlreadyLaunchedException)
{
// Ignore race conditions
}
catch (LockFileNotAcquiredException)
{
// Ignore race conditions
}
}
public void Dispose() => _updateManager.Dispose();
_settingsService = settingsService;
}
public async ValueTask<Version?> CheckForUpdatesAsync()
{
if (!_settingsService.IsAutoUpdateEnabled)
return null;
var check = await _updateManager.CheckForUpdatesAsync();
return check.CanUpdate ? check.LastVersion : null;
}
public async ValueTask PrepareUpdateAsync(Version version)
{
if (!_settingsService.IsAutoUpdateEnabled)
return;
try
{
await _updateManager.PrepareUpdateAsync(_updateVersion = version);
_updatePrepared = true;
}
catch (UpdaterAlreadyLaunchedException)
{
// Ignore race conditions
}
catch (LockFileNotAcquiredException)
{
// Ignore race conditions
}
}
public void FinalizeUpdate(bool needRestart)
{
if (!_settingsService.IsAutoUpdateEnabled)
return;
if (_updateVersion is null || !_updatePrepared || _updaterLaunched)
return;
try
{
_updateManager.LaunchUpdater(_updateVersion, needRestart);
_updaterLaunched = true;
}
catch (UpdaterAlreadyLaunchedException)
{
// Ignore race conditions
}
catch (LockFileNotAcquiredException)
{
// Ignore race conditions
}
}
public void Dispose() => _updateManager.Dispose();
}

View File

@@ -1,9 +1,8 @@
using System.Windows.Media;
namespace DiscordChatExporter.Gui.Utils
namespace DiscordChatExporter.Gui.Utils;
internal static class MediaColor
{
internal static class MediaColor
{
public static Color FromHex(string hex) => (Color) ColorConverter.ConvertFromString(hex);
}
public static Color FromHex(string hex) => (Color) ColorConverter.ConvertFromString(hex);
}

View File

@@ -1,17 +1,16 @@
using System.Diagnostics;
namespace DiscordChatExporter.Gui.Utils
{
internal static class ProcessEx
{
public static void StartShellExecute(string path)
{
var startInfo = new ProcessStartInfo(path)
{
UseShellExecute = true
};
namespace DiscordChatExporter.Gui.Utils;
using (Process.Start(startInfo)) {}
}
internal static class ProcessEx
{
public static void StartShellExecute(string path)
{
var startInfo = new ProcessStartInfo(path)
{
UseShellExecute = true
};
using (Process.Start(startInfo)) {}
}
}

View File

@@ -10,129 +10,128 @@ using DiscordChatExporter.Core.Utils.Extensions;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.ViewModels.Framework;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
public class ExportSetupViewModel : DialogScreen
{
public class ExportSetupViewModel : DialogScreen
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
public Guild? Guild { get; set; }
public IReadOnlyList<Channel>? Channels { get; set; }
public bool IsSingleChannel => Channels is null || Channels.Count == 1;
public string? OutputPath { get; set; }
public IReadOnlyList<ExportFormat> AvailableFormats =>
Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
public ExportFormat SelectedFormat { get; set; }
// This date/time abomination is required because we use separate controls to set these
public DateTimeOffset? AfterDate { get; set; }
public bool IsAfterDateSet => AfterDate is not null;
public TimeSpan? AfterTime { get; set; }
public DateTimeOffset? After => AfterDate?.Add(AfterTime ?? TimeSpan.Zero);
public DateTimeOffset? BeforeDate { get; set; }
public bool IsBeforeDateSet => BeforeDate is not null;
public TimeSpan? BeforeTime { get; set; }
public DateTimeOffset? Before => BeforeDate?.Add(BeforeTime ?? TimeSpan.Zero);
public string? PartitionLimitValue { get; set; }
public PartitionLimit PartitionLimit => !string.IsNullOrWhiteSpace(PartitionLimitValue)
? PartitionLimit.Parse(PartitionLimitValue)
: PartitionLimit.Null;
public string? MessageFilterValue { get; set; }
public MessageFilter MessageFilter => !string.IsNullOrWhiteSpace(MessageFilterValue)
? MessageFilter.Parse(MessageFilterValue)
: MessageFilter.Null;
public bool ShouldDownloadMedia { get; set; }
// Whether to show the "advanced options" by default when the dialog opens.
// This is active if any of the advanced options are set to non-default values.
public bool IsAdvancedSectionDisplayedByDefault =>
After != default ||
Before != default ||
!string.IsNullOrWhiteSpace(PartitionLimitValue) ||
!string.IsNullOrWhiteSpace(MessageFilterValue) ||
ShouldDownloadMedia != default;
public ExportSetupViewModel(DialogManager dialogManager, SettingsService settingsService)
{
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
_dialogManager = dialogManager;
_settingsService = settingsService;
public Guild? Guild { get; set; }
public IReadOnlyList<Channel>? Channels { get; set; }
public bool IsSingleChannel => Channels is null || Channels.Count == 1;
public string? OutputPath { get; set; }
public IReadOnlyList<ExportFormat> AvailableFormats =>
Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
public ExportFormat SelectedFormat { get; set; }
// This date/time abomination is required because we use separate controls to set these
public DateTimeOffset? AfterDate { get; set; }
public bool IsAfterDateSet => AfterDate is not null;
public TimeSpan? AfterTime { get; set; }
public DateTimeOffset? After => AfterDate?.Add(AfterTime ?? TimeSpan.Zero);
public DateTimeOffset? BeforeDate { get; set; }
public bool IsBeforeDateSet => BeforeDate is not null;
public TimeSpan? BeforeTime { get; set; }
public DateTimeOffset? Before => BeforeDate?.Add(BeforeTime ?? TimeSpan.Zero);
public string? PartitionLimitValue { get; set; }
public PartitionLimit PartitionLimit => !string.IsNullOrWhiteSpace(PartitionLimitValue)
? PartitionLimit.Parse(PartitionLimitValue)
: PartitionLimit.Null;
public string? MessageFilterValue { get; set; }
public MessageFilter MessageFilter => !string.IsNullOrWhiteSpace(MessageFilterValue)
? MessageFilter.Parse(MessageFilterValue)
: MessageFilter.Null;
public bool ShouldDownloadMedia { get; set; }
// Whether to show the "advanced options" by default when the dialog opens.
// This is active if any of the advanced options are set to non-default values.
public bool IsAdvancedSectionDisplayedByDefault =>
After != default ||
Before != default ||
!string.IsNullOrWhiteSpace(PartitionLimitValue) ||
!string.IsNullOrWhiteSpace(MessageFilterValue) ||
ShouldDownloadMedia != default;
public ExportSetupViewModel(DialogManager dialogManager, SettingsService settingsService)
{
_dialogManager = dialogManager;
_settingsService = settingsService;
// Persist preferences
SelectedFormat = _settingsService.LastExportFormat;
PartitionLimitValue = _settingsService.LastPartitionLimitValue;
MessageFilterValue = _settingsService.LastMessageFilterValue;
ShouldDownloadMedia = _settingsService.LastShouldDownloadMedia;
}
public void Confirm()
{
// Persist preferences
_settingsService.LastExportFormat = SelectedFormat;
_settingsService.LastPartitionLimitValue = PartitionLimitValue;
_settingsService.LastMessageFilterValue = MessageFilterValue;
_settingsService.LastShouldDownloadMedia = ShouldDownloadMedia;
// If single channel - prompt file path
if (Channels is not null && IsSingleChannel)
{
var channel = Channels.Single();
var defaultFileName = ExportRequest.GetDefaultOutputFileName(
Guild!,
channel,
SelectedFormat,
After?.Pipe(Snowflake.FromDate),
Before?.Pipe(Snowflake.FromDate)
);
// Filter
var ext = SelectedFormat.GetFileExtension();
var filter = $"{ext.ToUpperInvariant()} files|*.{ext}";
OutputPath = _dialogManager.PromptSaveFilePath(filter, defaultFileName);
}
// If multiple channels - prompt dir path
else
{
OutputPath = _dialogManager.PromptDirectoryPath();
}
if (string.IsNullOrWhiteSpace(OutputPath))
return;
Close(true);
}
// Persist preferences
SelectedFormat = _settingsService.LastExportFormat;
PartitionLimitValue = _settingsService.LastPartitionLimitValue;
MessageFilterValue = _settingsService.LastMessageFilterValue;
ShouldDownloadMedia = _settingsService.LastShouldDownloadMedia;
}
public static class ExportSetupViewModelExtensions
public void Confirm()
{
public static ExportSetupViewModel CreateExportSetupViewModel(this IViewModelFactory factory,
Guild guild, IReadOnlyList<Channel> channels)
// Persist preferences
_settingsService.LastExportFormat = SelectedFormat;
_settingsService.LastPartitionLimitValue = PartitionLimitValue;
_settingsService.LastMessageFilterValue = MessageFilterValue;
_settingsService.LastShouldDownloadMedia = ShouldDownloadMedia;
// If single channel - prompt file path
if (Channels is not null && IsSingleChannel)
{
var viewModel = factory.CreateExportSetupViewModel();
var channel = Channels.Single();
var defaultFileName = ExportRequest.GetDefaultOutputFileName(
Guild!,
channel,
SelectedFormat,
After?.Pipe(Snowflake.FromDate),
Before?.Pipe(Snowflake.FromDate)
);
viewModel.Guild = guild;
viewModel.Channels = channels;
// Filter
var ext = SelectedFormat.GetFileExtension();
var filter = $"{ext.ToUpperInvariant()} files|*.{ext}";
return viewModel;
OutputPath = _dialogManager.PromptSaveFilePath(filter, defaultFileName);
}
// If multiple channels - prompt dir path
else
{
OutputPath = _dialogManager.PromptDirectoryPath();
}
if (string.IsNullOrWhiteSpace(OutputPath))
return;
Close(true);
}
}
public static class ExportSetupViewModelExtensions
{
public static ExportSetupViewModel CreateExportSetupViewModel(this IViewModelFactory factory,
Guild guild, IReadOnlyList<Channel> channels)
{
var viewModel = factory.CreateExportSetupViewModel();
viewModel.Guild = guild;
viewModel.Channels = channels;
return viewModel;
}
}

View File

@@ -1,27 +1,26 @@
using DiscordChatExporter.Gui.ViewModels.Framework;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
public class MessageBoxViewModel : DialogScreen
{
public class MessageBoxViewModel : DialogScreen
public string? Title { get; set; }
public string? Message { get; set; }
}
public static class MessageBoxViewModelExtensions
{
public static MessageBoxViewModel CreateMessageBoxViewModel(
this IViewModelFactory factory,
string title,
string message)
{
public string? Title { get; set; }
var viewModel = factory.CreateMessageBoxViewModel();
public string? Message { get; set; }
}
viewModel.Title = title;
viewModel.Message = message;
public static class MessageBoxViewModelExtensions
{
public static MessageBoxViewModel CreateMessageBoxViewModel(
this IViewModelFactory factory,
string title,
string message)
{
var viewModel = factory.CreateMessageBoxViewModel();
viewModel.Title = title;
viewModel.Message = message;
return viewModel;
}
return viewModel;
}
}

View File

@@ -2,49 +2,48 @@
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.ViewModels.Framework;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
public class SettingsViewModel : DialogScreen
{
public class SettingsViewModel : DialogScreen
private readonly SettingsService _settingsService;
public bool IsAutoUpdateEnabled
{
private readonly SettingsService _settingsService;
public bool IsAutoUpdateEnabled
{
get => _settingsService.IsAutoUpdateEnabled;
set => _settingsService.IsAutoUpdateEnabled = value;
}
public bool IsDarkModeEnabled
{
get => _settingsService.IsDarkModeEnabled;
set => _settingsService.IsDarkModeEnabled = value;
}
public bool IsTokenPersisted
{
get => _settingsService.IsTokenPersisted;
set => _settingsService.IsTokenPersisted = value;
}
public string DateFormat
{
get => _settingsService.DateFormat;
set => _settingsService.DateFormat = value;
}
public int ParallelLimit
{
get => _settingsService.ParallelLimit;
set => _settingsService.ParallelLimit = Math.Clamp(value, 1, 10);
}
public bool ShouldReuseMedia
{
get => _settingsService.ShouldReuseMedia;
set => _settingsService.ShouldReuseMedia = value;
}
public SettingsViewModel(SettingsService settingsService) =>
_settingsService = settingsService;
get => _settingsService.IsAutoUpdateEnabled;
set => _settingsService.IsAutoUpdateEnabled = value;
}
public bool IsDarkModeEnabled
{
get => _settingsService.IsDarkModeEnabled;
set => _settingsService.IsDarkModeEnabled = value;
}
public bool IsTokenPersisted
{
get => _settingsService.IsTokenPersisted;
set => _settingsService.IsTokenPersisted = value;
}
public string DateFormat
{
get => _settingsService.DateFormat;
set => _settingsService.DateFormat = value;
}
public int ParallelLimit
{
get => _settingsService.ParallelLimit;
set => _settingsService.ParallelLimit = Math.Clamp(value, 1, 10);
}
public bool ShouldReuseMedia
{
get => _settingsService.ShouldReuseMedia;
set => _settingsService.ShouldReuseMedia = value;
}
public SettingsViewModel(SettingsService settingsService) =>
_settingsService = settingsService;
}

View File

@@ -6,58 +6,57 @@ using Microsoft.Win32;
using Ookii.Dialogs.Wpf;
using Stylet;
namespace DiscordChatExporter.Gui.ViewModels.Framework
namespace DiscordChatExporter.Gui.ViewModels.Framework;
public class DialogManager
{
public class DialogManager
private readonly IViewManager _viewManager;
public DialogManager(IViewManager viewManager)
{
private readonly IViewManager _viewManager;
_viewManager = viewManager;
}
public DialogManager(IViewManager viewManager)
public async ValueTask<T?> ShowDialogAsync<T>(DialogScreen<T> dialogScreen)
{
var view = _viewManager.CreateAndBindViewForModelIfNecessary(dialogScreen);
void OnDialogOpened(object? sender, DialogOpenedEventArgs openArgs)
{
_viewManager = viewManager;
}
public async ValueTask<T?> ShowDialogAsync<T>(DialogScreen<T> dialogScreen)
{
var view = _viewManager.CreateAndBindViewForModelIfNecessary(dialogScreen);
void OnDialogOpened(object? sender, DialogOpenedEventArgs openArgs)
void OnScreenClosed(object? o, EventArgs closeArgs)
{
void OnScreenClosed(object? o, EventArgs closeArgs)
{
openArgs.Session.Close();
dialogScreen.Closed -= OnScreenClosed;
}
dialogScreen.Closed += OnScreenClosed;
openArgs.Session.Close();
dialogScreen.Closed -= OnScreenClosed;
}
await DialogHost.Show(view, OnDialogOpened);
return dialogScreen.DialogResult;
dialogScreen.Closed += OnScreenClosed;
}
public string? PromptSaveFilePath(string filter = "All files|*.*", string defaultFilePath = "")
await DialogHost.Show(view, OnDialogOpened);
return dialogScreen.DialogResult;
}
public string? PromptSaveFilePath(string filter = "All files|*.*", string defaultFilePath = "")
{
var dialog = new SaveFileDialog
{
var dialog = new SaveFileDialog
{
Filter = filter,
AddExtension = true,
FileName = defaultFilePath,
DefaultExt = Path.GetExtension(defaultFilePath)
};
Filter = filter,
AddExtension = true,
FileName = defaultFilePath,
DefaultExt = Path.GetExtension(defaultFilePath)
};
return dialog.ShowDialog() == true ? dialog.FileName : null;
}
return dialog.ShowDialog() == true ? dialog.FileName : null;
}
public string? PromptDirectoryPath(string defaultDirPath = "")
public string? PromptDirectoryPath(string defaultDirPath = "")
{
var dialog = new VistaFolderBrowserDialog
{
var dialog = new VistaFolderBrowserDialog
{
SelectedPath = defaultDirPath
};
SelectedPath = defaultDirPath
};
return dialog.ShowDialog() == true ? dialog.SelectedPath : null;
}
return dialog.ShowDialog() == true ? dialog.SelectedPath : null;
}
}

View File

@@ -1,22 +1,21 @@
using System;
using Stylet;
namespace DiscordChatExporter.Gui.ViewModels.Framework
namespace DiscordChatExporter.Gui.ViewModels.Framework;
public abstract class DialogScreen<T> : PropertyChangedBase
{
public abstract class DialogScreen<T> : PropertyChangedBase
public T? DialogResult { get; private set; }
public event EventHandler? Closed;
public void Close(T dialogResult)
{
public T? DialogResult { get; private set; }
public event EventHandler? Closed;
public void Close(T dialogResult)
{
DialogResult = dialogResult;
Closed?.Invoke(this, EventArgs.Empty);
}
DialogResult = dialogResult;
Closed?.Invoke(this, EventArgs.Empty);
}
}
public abstract class DialogScreen : DialogScreen<bool?>
{
}
public abstract class DialogScreen : DialogScreen<bool?>
{
}

View File

@@ -1,14 +1,13 @@
using DiscordChatExporter.Gui.ViewModels.Dialogs;
namespace DiscordChatExporter.Gui.ViewModels.Framework
namespace DiscordChatExporter.Gui.ViewModels.Framework;
// Used to instantiate new view models while making use of dependency injection
public interface IViewModelFactory
{
// Used to instantiate new view models while making use of dependency injection
public interface IViewModelFactory
{
ExportSetupViewModel CreateExportSetupViewModel();
ExportSetupViewModel CreateExportSetupViewModel();
MessageBoxViewModel CreateMessageBoxViewModel();
MessageBoxViewModel CreateMessageBoxViewModel();
SettingsViewModel CreateSettingsViewModel();
}
SettingsViewModel CreateSettingsViewModel();
}

View File

@@ -16,247 +16,246 @@ using Gress;
using MaterialDesignThemes.Wpf;
using Stylet;
namespace DiscordChatExporter.Gui.ViewModels
namespace DiscordChatExporter.Gui.ViewModels;
public class RootViewModel : Screen
{
public class RootViewModel : Screen
private readonly IViewModelFactory _viewModelFactory;
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
private readonly UpdateService _updateService;
public ISnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
public IProgressManager ProgressManager { get; } = new ProgressManager();
public bool IsBusy { get; private set; }
public bool IsProgressIndeterminate { get; private set; }
public bool IsBotToken { get; set; }
public string? TokenValue { get; set; }
private IReadOnlyDictionary<Guild, IReadOnlyList<Channel>>? GuildChannelMap { get; set; }
public IReadOnlyList<Guild>? AvailableGuilds => GuildChannelMap?.Keys.ToArray();
public Guild? SelectedGuild { get; set; }
public IReadOnlyList<Channel>? AvailableChannels => SelectedGuild is not null
? GuildChannelMap?[SelectedGuild]
: null;
public IReadOnlyList<Channel>? SelectedChannels { get; set; }
public RootViewModel(
IViewModelFactory viewModelFactory,
DialogManager dialogManager,
SettingsService settingsService,
UpdateService updateService)
{
private readonly IViewModelFactory _viewModelFactory;
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
private readonly UpdateService _updateService;
_viewModelFactory = viewModelFactory;
_dialogManager = dialogManager;
_settingsService = settingsService;
_updateService = updateService;
public ISnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
DisplayName = $"{App.Name} v{App.VersionString}";
public IProgressManager ProgressManager { get; } = new ProgressManager();
// Update busy state when progress manager changes
ProgressManager.Bind(o => o.IsActive, (_, _) =>
IsBusy = ProgressManager.IsActive
);
public bool IsBusy { get; private set; }
ProgressManager.Bind(o => o.IsActive, (_, _) =>
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress is <= 0 or >= 1
);
public bool IsProgressIndeterminate { get; private set; }
ProgressManager.Bind(o => o.Progress, (_, _) =>
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress is <= 0 or >= 1
);
}
public bool IsBotToken { get; set; }
public string? TokenValue { get; set; }
private IReadOnlyDictionary<Guild, IReadOnlyList<Channel>>? GuildChannelMap { get; set; }
public IReadOnlyList<Guild>? AvailableGuilds => GuildChannelMap?.Keys.ToArray();
public Guild? SelectedGuild { get; set; }
public IReadOnlyList<Channel>? AvailableChannels => SelectedGuild is not null
? GuildChannelMap?[SelectedGuild]
: null;
public IReadOnlyList<Channel>? SelectedChannels { get; set; }
public RootViewModel(
IViewModelFactory viewModelFactory,
DialogManager dialogManager,
SettingsService settingsService,
UpdateService updateService)
private async ValueTask CheckForUpdatesAsync()
{
try
{
_viewModelFactory = viewModelFactory;
_dialogManager = dialogManager;
_settingsService = settingsService;
_updateService = updateService;
var updateVersion = await _updateService.CheckForUpdatesAsync();
if (updateVersion is null)
return;
DisplayName = $"{App.Name} v{App.VersionString}";
Notifications.Enqueue($"Downloading update to {App.Name} v{updateVersion}...");
await _updateService.PrepareUpdateAsync(updateVersion);
// Update busy state when progress manager changes
ProgressManager.Bind(o => o.IsActive, (_, _) =>
IsBusy = ProgressManager.IsActive
);
ProgressManager.Bind(o => o.IsActive, (_, _) =>
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress is <= 0 or >= 1
);
ProgressManager.Bind(o => o.Progress, (_, _) =>
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress is <= 0 or >= 1
Notifications.Enqueue(
"Update has been downloaded and will be installed when you exit",
"INSTALL NOW", () =>
{
_updateService.FinalizeUpdate(true);
RequestClose();
}
);
}
private async ValueTask CheckForUpdatesAsync()
catch
{
try
{
var updateVersion = await _updateService.CheckForUpdatesAsync();
if (updateVersion is null)
return;
// Failure to update shouldn't crash the application
Notifications.Enqueue("Failed to perform application update");
}
}
Notifications.Enqueue($"Downloading update to {App.Name} v{updateVersion}...");
await _updateService.PrepareUpdateAsync(updateVersion);
protected override async void OnViewLoaded()
{
base.OnViewLoaded();
Notifications.Enqueue(
"Update has been downloaded and will be installed when you exit",
"INSTALL NOW", () =>
{
_updateService.FinalizeUpdate(true);
RequestClose();
}
);
}
catch
{
// Failure to update shouldn't crash the application
Notifications.Enqueue("Failed to perform application update");
}
_settingsService.Load();
if (_settingsService.LastToken is not null)
{
IsBotToken = _settingsService.LastToken.Kind == AuthTokenKind.Bot;
TokenValue = _settingsService.LastToken.Value;
}
protected override async void OnViewLoaded()
if (_settingsService.IsDarkModeEnabled)
{
base.OnViewLoaded();
_settingsService.Load();
if (_settingsService.LastToken is not null)
{
IsBotToken = _settingsService.LastToken.Kind == AuthTokenKind.Bot;
TokenValue = _settingsService.LastToken.Value;
}
if (_settingsService.IsDarkModeEnabled)
{
App.SetDarkTheme();
}
else
{
App.SetLightTheme();
}
await CheckForUpdatesAsync();
App.SetDarkTheme();
}
else
{
App.SetLightTheme();
}
protected override void OnClose()
{
base.OnClose();
await CheckForUpdatesAsync();
}
_settingsService.Save();
_updateService.FinalizeUpdate(false);
protected override void OnClose()
{
base.OnClose();
_settingsService.Save();
_updateService.FinalizeUpdate(false);
}
public async void ShowSettings()
{
var dialog = _viewModelFactory.CreateSettingsViewModel();
await _dialogManager.ShowDialogAsync(dialog);
}
public void ShowHelp() => ProcessEx.StartShellExecute(App.GitHubProjectWikiUrl);
public bool CanPopulateGuildsAndChannels =>
!IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
public async void PopulateGuildsAndChannels()
{
using var operation = ProgressManager.CreateOperation();
try
{
var tokenValue = TokenValue?.Trim('"', ' ');
if (string.IsNullOrWhiteSpace(tokenValue))
return;
var token = new AuthToken(
IsBotToken ? AuthTokenKind.Bot : AuthTokenKind.User,
tokenValue
);
_settingsService.LastToken = token;
var discord = new DiscordClient(token);
var guildChannelMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
await foreach (var guild in discord.GetUserGuildsAsync())
{
var channels = await discord.GetGuildChannelsAsync(guild.Id);
guildChannelMap[guild] = channels.Where(c => c.IsTextChannel).ToArray();
}
GuildChannelMap = guildChannelMap;
SelectedGuild = guildChannelMap.Keys.FirstOrDefault();
}
public async void ShowSettings()
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
var dialog = _viewModelFactory.CreateSettingsViewModel();
Notifications.Enqueue(ex.Message.TrimEnd('.'));
}
catch (Exception ex)
{
var dialog = _viewModelFactory.CreateMessageBoxViewModel(
"Error pulling guilds and channels",
ex.ToString()
);
await _dialogManager.ShowDialogAsync(dialog);
}
}
public void ShowHelp() => ProcessEx.StartShellExecute(App.GitHubProjectWikiUrl);
public bool CanExportChannels =>
!IsBusy && SelectedGuild is not null && SelectedChannels is not null && SelectedChannels.Any();
public bool CanPopulateGuildsAndChannels =>
!IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
public async void PopulateGuildsAndChannels()
public async void ExportChannels()
{
try
{
using var operation = ProgressManager.CreateOperation();
var token = _settingsService.LastToken;
if (token is null || SelectedGuild is null || SelectedChannels is null || !SelectedChannels.Any())
return;
try
var dialog = _viewModelFactory.CreateExportSetupViewModel(SelectedGuild, SelectedChannels);
if (await _dialogManager.ShowDialogAsync(dialog) != true)
return;
var exporter = new ChannelExporter(token);
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
var successfulExportCount = 0;
await dialog.Channels.Zip(operations).ParallelForEachAsync(async tuple =>
{
var tokenValue = TokenValue?.Trim('"', ' ');
if (string.IsNullOrWhiteSpace(tokenValue))
return;
var (channel, operation) = tuple;
var token = new AuthToken(
IsBotToken ? AuthTokenKind.Bot : AuthTokenKind.User,
tokenValue
);
_settingsService.LastToken = token;
var discord = new DiscordClient(token);
var guildChannelMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
await foreach (var guild in discord.GetUserGuildsAsync())
try
{
var channels = await discord.GetGuildChannelsAsync(guild.Id);
guildChannelMap[guild] = channels.Where(c => c.IsTextChannel).ToArray();
var request = new ExportRequest(
dialog.Guild!,
channel!,
dialog.OutputPath!,
dialog.SelectedFormat,
dialog.After?.Pipe(Snowflake.FromDate),
dialog.Before?.Pipe(Snowflake.FromDate),
dialog.PartitionLimit,
dialog.MessageFilter,
dialog.ShouldDownloadMedia,
_settingsService.ShouldReuseMedia,
_settingsService.DateFormat
);
await exporter.ExportChannelAsync(request, operation);
Interlocked.Increment(ref successfulExportCount);
}
GuildChannelMap = guildChannelMap;
SelectedGuild = guildChannelMap.Keys.FirstOrDefault();
}
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
Notifications.Enqueue(ex.Message.TrimEnd('.'));
}
catch (Exception ex)
{
var dialog = _viewModelFactory.CreateMessageBoxViewModel(
"Error pulling guilds and channels",
ex.ToString()
);
await _dialogManager.ShowDialogAsync(dialog);
}
}
public bool CanExportChannels =>
!IsBusy && SelectedGuild is not null && SelectedChannels is not null && SelectedChannels.Any();
public async void ExportChannels()
{
try
{
var token = _settingsService.LastToken;
if (token is null || SelectedGuild is null || SelectedChannels is null || !SelectedChannels.Any())
return;
var dialog = _viewModelFactory.CreateExportSetupViewModel(SelectedGuild, SelectedChannels);
if (await _dialogManager.ShowDialogAsync(dialog) != true)
return;
var exporter = new ChannelExporter(token);
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
var successfulExportCount = 0;
await dialog.Channels.Zip(operations).ParallelForEachAsync(async tuple =>
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
var (channel, operation) = tuple;
Notifications.Enqueue(ex.Message.TrimEnd('.'));
}
finally
{
operation.Dispose();
}
}, Math.Max(1, _settingsService.ParallelLimit));
try
{
var request = new ExportRequest(
dialog.Guild!,
channel!,
dialog.OutputPath!,
dialog.SelectedFormat,
dialog.After?.Pipe(Snowflake.FromDate),
dialog.Before?.Pipe(Snowflake.FromDate),
dialog.PartitionLimit,
dialog.MessageFilter,
dialog.ShouldDownloadMedia,
_settingsService.ShouldReuseMedia,
_settingsService.DateFormat
);
// Notify of overall completion
if (successfulExportCount > 0)
Notifications.Enqueue($"Successfully exported {successfulExportCount} channel(s)");
}
catch (Exception ex)
{
var dialog = _viewModelFactory.CreateMessageBoxViewModel(
"Error exporting channel(s)",
ex.ToString()
);
await exporter.ExportChannelAsync(request, operation);
Interlocked.Increment(ref successfulExportCount);
}
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
Notifications.Enqueue(ex.Message.TrimEnd('.'));
}
finally
{
operation.Dispose();
}
}, Math.Max(1, _settingsService.ParallelLimit));
// Notify of overall completion
if (successfulExportCount > 0)
Notifications.Enqueue($"Successfully exported {successfulExportCount} channel(s)");
}
catch (Exception ex)
{
var dialog = _viewModelFactory.CreateMessageBoxViewModel(
"Error exporting channel(s)",
ex.ToString()
);
await _dialogManager.ShowDialogAsync(dialog);
}
await _dialogManager.ShowDialogAsync(dialog);
}
}
}

View File

@@ -1,10 +1,9 @@
namespace DiscordChatExporter.Gui.Views.Dialogs
namespace DiscordChatExporter.Gui.Views.Dialogs;
public partial class ExportSetupView
{
public partial class ExportSetupView
public ExportSetupView()
{
public ExportSetupView()
{
InitializeComponent();
}
InitializeComponent();
}
}

View File

@@ -1,10 +1,9 @@
namespace DiscordChatExporter.Gui.Views.Dialogs
namespace DiscordChatExporter.Gui.Views.Dialogs;
public partial class MessageBoxView
{
public partial class MessageBoxView
public MessageBoxView()
{
public MessageBoxView()
{
InitializeComponent();
}
InitializeComponent();
}
}

View File

@@ -1,18 +1,17 @@
using System.Windows;
namespace DiscordChatExporter.Gui.Views.Dialogs
namespace DiscordChatExporter.Gui.Views.Dialogs;
public partial class SettingsView
{
public partial class SettingsView
public SettingsView()
{
public SettingsView()
{
InitializeComponent();
}
private void DarkModeToggleButton_Checked(object sender, RoutedEventArgs e) =>
App.SetDarkTheme();
private void DarkModeToggleButton_Unchecked(object sender, RoutedEventArgs e) =>
App.SetLightTheme();
InitializeComponent();
}
private void DarkModeToggleButton_Checked(object sender, RoutedEventArgs e) =>
App.SetDarkTheme();
private void DarkModeToggleButton_Unchecked(object sender, RoutedEventArgs e) =>
App.SetLightTheme();
}

View File

@@ -1,10 +1,9 @@
namespace DiscordChatExporter.Gui.Views
namespace DiscordChatExporter.Gui.Views;
public partial class RootView
{
public partial class RootView
public RootView()
{
public RootView()
{
InitializeComponent();
}
InitializeComponent();
}
}