This commit is contained in:
Tyrrrz
2020-10-24 21:15:58 +03:00
parent 0763a99765
commit 1da80956dd
34 changed files with 299 additions and 261 deletions

View File

@@ -7,13 +7,17 @@ namespace DiscordChatExporter.Gui
{
public partial class App
{
private static readonly Assembly Assembly = typeof(App).Assembly;
private static Assembly Assembly { get; } = typeof(App).Assembly;
public static string Name => Assembly.GetName().Name!;
public static string Name { get; } = Assembly.GetName().Name!;
public static Version Version => Assembly.GetName().Version!;
public static Version Version { get; } = Assembly.GetName().Version!;
public static string VersionString => Version.ToString(3);
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

View File

@@ -31,9 +31,9 @@ namespace DiscordChatExporter.Gui.Behaviors
private bool _viewHandled;
private bool _modelHandled;
public IList SelectedItems
public IList? SelectedItems
{
get => (IList) GetValue(SelectedItemsProperty);
get => (IList?) GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}

View File

@@ -17,7 +17,8 @@ namespace DiscordChatExporter.Gui
{
base.OnStart();
// Light theme is the default
// Set default theme
// (preferred theme will be chosen later, once the settings are loaded)
App.SetLightTheme();
}

View File

@@ -18,9 +18,7 @@ namespace DiscordChatExporter.Gui.Converters
return default(string);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
}

View File

@@ -11,8 +11,7 @@ namespace DiscordChatExporter.Gui.Internal
UseShellExecute = true
};
using (Process.Start(startInfo))
{ }
using (Process.Start(startInfo)) {}
}
}
}

View File

@@ -16,7 +16,7 @@ namespace DiscordChatExporter.Gui.Services
public int ParallelLimit { get; set; } = 1;
public bool ShouldReuseMedia { get; set; } = false;
public bool ShouldReuseMedia { get; set; }
public AuthToken? LastToken { get; set; }

View File

@@ -10,7 +10,8 @@ namespace DiscordChatExporter.Gui.Services
{
private readonly IUpdateManager _updateManager = new UpdateManager(
new GithubPackageResolver("Tyrrrz", "DiscordChatExporter", "DiscordChatExporter.zip"),
new ZipPackageExtractor());
new ZipPackageExtractor()
);
private readonly SettingsService _settingsService;

View File

@@ -75,10 +75,16 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
_settingsService.LastShouldDownloadMedia = ShouldDownloadMedia;
// If single channel - prompt file path
if (IsSingleChannel)
if (Channels != null && IsSingleChannel)
{
var channel = Channels.Single();
var defaultFileName = ExportRequest.GetDefaultOutputFileName(Guild!, channel, SelectedFormat, After, Before);
var defaultFileName = ExportRequest.GetDefaultOutputFileName(
Guild!,
channel,
SelectedFormat,
After,
Before
);
// Filter
var ext = SelectedFormat.GetFileExtension();
@@ -92,11 +98,24 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
OutputPath = _dialogManager.PromptDirectoryPath();
}
// If canceled - return
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

@@ -19,13 +19,10 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
public async ValueTask<T> ShowDialogAsync<T>(DialogScreen<T> dialogScreen)
{
// Get the view that renders this viewmodel
var view = _viewManager.CreateAndBindViewForModelIfNecessary(dialogScreen);
// Set up event routing that will close the view when called from viewmodel
void OnDialogOpened(object? sender, DialogOpenedEventArgs openArgs)
{
// Delegate to close the dialog and unregister event handler
void OnScreenClosed(object? o, EventArgs closeArgs)
{
openArgs.Session.Close();
@@ -35,37 +32,31 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
dialogScreen.Closed += OnScreenClosed;
}
// Show view
await DialogHost.Show(view, OnDialogOpened);
// Return the result
return dialogScreen.DialogResult;
}
public string? PromptSaveFilePath(string filter = "All files|*.*", string defaultFilePath = "")
{
// Create dialog
var dialog = new SaveFileDialog
{
Filter = filter,
AddExtension = true,
FileName = defaultFilePath,
DefaultExt = Path.GetExtension(defaultFilePath) ?? ""
DefaultExt = Path.GetExtension(defaultFilePath)
};
// Show dialog and return result
return dialog.ShowDialog() == true ? dialog.FileName : null;
}
public string? PromptDirectoryPath(string defaultDirPath = "")
{
// Create dialog
var dialog = new VistaFolderBrowserDialog
{
SelectedPath = defaultDirPath
};
// Show dialog and return result
return dialog.ShowDialog() == true ? dialog.SelectedPath : null;
}
}

View File

@@ -1,19 +0,0 @@
using System.Collections.Generic;
using DiscordChatExporter.Domain.Discord.Models;
using DiscordChatExporter.Gui.ViewModels.Dialogs;
namespace DiscordChatExporter.Gui.ViewModels.Framework
{
public static class Extensions
{
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

@@ -8,7 +8,9 @@ using DiscordChatExporter.Domain.Discord.Models;
using DiscordChatExporter.Domain.Exceptions;
using DiscordChatExporter.Domain.Exporting;
using DiscordChatExporter.Domain.Utilities;
using DiscordChatExporter.Gui.Internal;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.ViewModels.Dialogs;
using DiscordChatExporter.Gui.ViewModels.Framework;
using Gress;
using MaterialDesignThemes.Wpf;
@@ -63,14 +65,21 @@ namespace DiscordChatExporter.Gui.ViewModels
// Update busy state when progress manager changes
ProgressManager.Bind(o => o.IsActive,
(sender, args) => IsBusy = ProgressManager.IsActive);
(sender, args) => IsBusy = ProgressManager.IsActive
);
ProgressManager.Bind(o => o.IsActive,
(sender, args) => IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1));
(sender, args) => IsProgressIndeterminate =
ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1)
);
ProgressManager.Bind(o => o.Progress,
(sender, args) => IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1));
(sender, args) => IsProgressIndeterminate =
ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1)
);
}
private async ValueTask HandleAutoUpdateAsync()
private async ValueTask CheckForUpdatesAsync()
{
try
{
@@ -117,7 +126,7 @@ namespace DiscordChatExporter.Gui.ViewModels
App.SetLightTheme();
}
await HandleAutoUpdateAsync();
await CheckForUpdatesAsync();
}
protected override void OnClose()
@@ -134,6 +143,8 @@ namespace DiscordChatExporter.Gui.ViewModels
await _dialogManager.ShowDialogAsync(dialog);
}
public void ShowHelp() => ProcessEx.StartShellExecute(App.GitHubProjectWikiUrl);
public bool CanPopulateGuildsAndChannels =>
!IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
@@ -187,8 +198,8 @@ namespace DiscordChatExporter.Gui.ViewModels
var exporter = new ChannelExporter(token);
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
var successfulExportCount = 0;
await dialog.Channels.Zip(operations).ParallelForEachAsync(async tuple =>
{
var (channel, operation) = tuple;

View File

@@ -95,7 +95,7 @@
materialDesign:HintAssist.IsFloating="True"
DisplayDateEnd="{Binding BeforeDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}"
SelectedDate="{Binding AfterDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}"
ToolTip="If this is set, only messages sent after this date will be exported" />
ToolTip="Only include messages sent after this date" />
<DatePicker
Grid.Row="0"
Grid.Column="1"
@@ -104,7 +104,7 @@
materialDesign:HintAssist.IsFloating="True"
DisplayDateStart="{Binding AfterDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}"
SelectedDate="{Binding BeforeDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}"
ToolTip="If this is set, only messages sent before this date will be exported" />
ToolTip="Only include messages sent before this date" />
<materialDesign:TimePicker
Grid.Row="1"
Grid.Column="0"
@@ -113,7 +113,7 @@
materialDesign:HintAssist.IsFloating="True"
IsEnabled="{Binding IsAfterDateSet}"
SelectedTime="{Binding AfterTime, Converter={x:Static converters:TimeSpanToDateTimeConverter.Instance}}"
ToolTip="If this is set, only messages sent after this time will be exported" />
ToolTip="Only include messages sent after this time" />
<materialDesign:TimePicker
Grid.Row="1"
Grid.Column="1"
@@ -122,7 +122,7 @@
materialDesign:HintAssist.IsFloating="True"
IsEnabled="{Binding IsBeforeDateSet}"
SelectedTime="{Binding BeforeTime, Converter={x:Static converters:TimeSpanToDateTimeConverter.Instance}}"
ToolTip="If this is set, only messages sent before this time will be exported" />
ToolTip="Only include messages sent before this time" />
</Grid>
<!-- Partitioning -->
@@ -131,10 +131,10 @@
materialDesign:HintAssist.Hint="Messages per partition"
materialDesign:HintAssist.IsFloating="True"
Text="{Binding PartitionLimit, TargetNullValue=''}"
ToolTip="If this is set, the exported file will be split into multiple partitions, each containing no more than specified number of messages" />
ToolTip="Split output into partitions limited to this number of messages" />
<!-- Download media -->
<Grid Margin="16,16" ToolTip="If this is set, the export will include additional files such as user avatars, attached files, embedded images, etc">
<Grid Margin="16,16" ToolTip="Download referenced media content (user avatars, attached files, embedded images, etc)">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
@@ -143,7 +143,7 @@
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="Download referenced media content" />
Text="Download media" />
<ToggleButton
Grid.Column="1"
HorizontalAlignment="Right"
@@ -168,9 +168,9 @@
Height="24"
Margin="12"
Cursor="Hand"
Loaded="AdvancedSectionToggleButton_OnLoaded"
IsChecked="{Binding IsAdvancedSectionDisplayedByDefault, Mode=OneTime}"
Style="{DynamicResource MaterialDesignHamburgerToggleButton}"
ToolTip="Show advanced options" />
ToolTip="Toggle advanced options" />
<Button
Grid.Column="2"

View File

@@ -1,7 +1,4 @@
using System.Windows;
using DiscordChatExporter.Gui.ViewModels.Dialogs;
namespace DiscordChatExporter.Gui.Views.Dialogs
namespace DiscordChatExporter.Gui.Views.Dialogs
{
public partial class ExportSetupView
{
@@ -9,11 +6,5 @@ namespace DiscordChatExporter.Gui.Views.Dialogs
{
InitializeComponent();
}
private void AdvancedSectionToggleButton_OnLoaded(object sender, RoutedEventArgs e)
{
if (DataContext is ExportSetupViewModel vm)
AdvancedSectionToggleButton.IsChecked = vm.IsAdvancedSectionDisplayedByDefault;
}
}
}

View File

@@ -65,15 +65,15 @@
IsChecked="{Binding IsTokenPersisted}" />
</DockPanel>
<!-- Reuse Media -->
<!-- Reuse media -->
<DockPanel
Background="Transparent"
LastChildFill="False"
ToolTip="If the media folder already exists, reuse media inside it to skip downloads">
ToolTip="Reuse already existing media content to skip redundant downloads">
<TextBlock
Margin="16,8"
DockPanel.Dock="Left"
Text="Reuse previously downloaded media" />
Text="Reuse downloaded media" />
<ToggleButton
Margin="16,8"
DockPanel.Dock="Right"
@@ -86,7 +86,7 @@
materialDesign:HintAssist.Hint="Date format"
materialDesign:HintAssist.IsFloating="True"
Text="{Binding DateFormat}"
ToolTip="Format used when rendering dates (uses .NET date formatting rules)" />
ToolTip="Format used when writing dates (uses .NET date formatting rules)" />
<!-- Parallel limit -->
<StackPanel Background="Transparent" ToolTip="How many channels can be exported at the same time">

View File

@@ -9,14 +9,10 @@ namespace DiscordChatExporter.Gui.Views.Dialogs
InitializeComponent();
}
private void DarkModeToggleButton_Checked(object sender, RoutedEventArgs e)
{
private void DarkModeToggleButton_Checked(object sender, RoutedEventArgs e) =>
App.SetDarkTheme();
}
private void DarkModeToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
private void DarkModeToggleButton_Unchecked(object sender, RoutedEventArgs e) =>
App.SetLightTheme();
}
}
}

View File

@@ -191,10 +191,11 @@
Foreground="{DynamicResource PrimaryHueMidBrush}"
Kind="Account" />
</InlineUIContainer>
<Run Text="in the text box above." />
</TextBlock>
<TextBlock Margin="0,24,0,0" FontSize="14">
<Run Text="For more information, check out the" />
<Hyperlink NavigateUri="https://github.com/Tyrrrz/DiscordChatExporter/wiki" RequestNavigate="Hyperlink_OnRequestNavigate">wiki</Hyperlink><Run Text="." />
<Hyperlink Command="{s:Action ShowHelp}">wiki</Hyperlink><Run Text="." />
</TextBlock>
</StackPanel>
@@ -229,10 +230,11 @@
Foreground="{DynamicResource PrimaryHueMidBrush}"
Kind="Robot" />
</InlineUIContainer>
<Run Text="in the text box above." />
</TextBlock>
<TextBlock Margin="0,24,0,0" FontSize="14">
<Run Text="For more information, check out the" />
<Hyperlink NavigateUri="https://github.com/Tyrrrz/DiscordChatExporter/wiki" RequestNavigate="Hyperlink_OnRequestNavigate">wiki</Hyperlink><Run Text="." />
<Hyperlink Command="{s:Action ShowHelp}">wiki</Hyperlink><Run Text="." />
</TextBlock>
</StackPanel>
</Grid>

View File

@@ -1,7 +1,4 @@
using System.Windows.Navigation;
using DiscordChatExporter.Gui.Internal;
namespace DiscordChatExporter.Gui.Views
namespace DiscordChatExporter.Gui.Views
{
public partial class RootView
{
@@ -9,11 +6,5 @@ namespace DiscordChatExporter.Gui.Views
{
InitializeComponent();
}
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
ProcessEx.StartShellExecute(e.Uri.AbsoluteUri);
e.Handled = true;
}
}
}