Add export dialog and implement export as plain text

pull/17/head
Alexey Golub 7 years ago
parent 42bb4e72d2
commit 28a67ab60d

@ -19,9 +19,10 @@ namespace DiscordChatExporter
// View models // View models
SimpleIoc.Default.Register<IErrorViewModel, ErrorViewModel>(true); SimpleIoc.Default.Register<IErrorViewModel, ErrorViewModel>(true);
SimpleIoc.Default.Register<IExportDoneViewModel, ExportDoneViewModel>(true);
SimpleIoc.Default.Register<IExportSetupViewModel, ExportSetupViewModel>(true);
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>(true); SimpleIoc.Default.Register<IMainViewModel, MainViewModel>(true);
SimpleIoc.Default.Register<ISettingsViewModel, SettingsViewModel>(true); SimpleIoc.Default.Register<ISettingsViewModel, SettingsViewModel>(true);
SimpleIoc.Default.Register<IExportDoneViewModel, ExportDoneViewModel>(true);
// Load settings // Load settings
ServiceLocator.Current.GetInstance<ISettingsService>().Load(); ServiceLocator.Current.GetInstance<ISettingsService>().Load();
@ -34,8 +35,9 @@ namespace DiscordChatExporter
} }
public IErrorViewModel ErrorViewModel => ServiceLocator.Current.GetInstance<IErrorViewModel>(); public IErrorViewModel ErrorViewModel => ServiceLocator.Current.GetInstance<IErrorViewModel>();
public IExportDoneViewModel ExportDoneViewModel => ServiceLocator.Current.GetInstance<IExportDoneViewModel>();
public IExportSetupViewModel ExportSetupViewModel => ServiceLocator.Current.GetInstance<IExportSetupViewModel>();
public IMainViewModel MainViewModel => ServiceLocator.Current.GetInstance<IMainViewModel>(); public IMainViewModel MainViewModel => ServiceLocator.Current.GetInstance<IMainViewModel>();
public ISettingsViewModel SettingsViewModel => ServiceLocator.Current.GetInstance<ISettingsViewModel>(); public ISettingsViewModel SettingsViewModel => ServiceLocator.Current.GetInstance<ISettingsViewModel>();
public IExportDoneViewModel ExportDoneViewModel => ServiceLocator.Current.GetInstance<IExportDoneViewModel>();
} }
} }

@ -84,14 +84,20 @@
<Compile Include="Exceptions\HttpErrorStatusCodeException.cs" /> <Compile Include="Exceptions\HttpErrorStatusCodeException.cs" />
<Compile Include="Messages\ShowErrorMessage.cs" /> <Compile Include="Messages\ShowErrorMessage.cs" />
<Compile Include="Messages\ShowExportDoneMessage.cs" /> <Compile Include="Messages\ShowExportDoneMessage.cs" />
<Compile Include="Messages\ShowExportSetupMessage.cs" />
<Compile Include="Messages\ShowSettingsMessage.cs" /> <Compile Include="Messages\ShowSettingsMessage.cs" />
<Compile Include="Messages\StartExportMessage.cs" />
<Compile Include="Models\AttachmentType.cs" /> <Compile Include="Models\AttachmentType.cs" />
<Compile Include="Models\ChannelChatLog.cs" /> <Compile Include="Models\ChannelChatLog.cs" />
<Compile Include="Models\ChannelType.cs" /> <Compile Include="Models\ChannelType.cs" />
<Compile Include="Models\ExportFormat.cs" />
<Compile Include="Models\Extensions.cs" />
<Compile Include="Services\IMessageGroupService.cs" /> <Compile Include="Services\IMessageGroupService.cs" />
<Compile Include="Services\MessageGroupService.cs" /> <Compile Include="Services\MessageGroupService.cs" />
<Compile Include="ViewModels\ErrorViewModel.cs" /> <Compile Include="ViewModels\ErrorViewModel.cs" />
<Compile Include="ViewModels\ExportSetupViewModel.cs" />
<Compile Include="ViewModels\IErrorViewModel.cs" /> <Compile Include="ViewModels\IErrorViewModel.cs" />
<Compile Include="ViewModels\IExportSetupViewModel.cs" />
<Compile Include="ViewModels\ISettingsViewModel.cs" /> <Compile Include="ViewModels\ISettingsViewModel.cs" />
<Compile Include="ViewModels\IExportDoneViewModel.cs" /> <Compile Include="ViewModels\IExportDoneViewModel.cs" />
<Compile Include="ViewModels\SettingsViewModel.cs" /> <Compile Include="ViewModels\SettingsViewModel.cs" />
@ -102,6 +108,9 @@
<Compile Include="Views\ExportDoneDialog.ammy.cs"> <Compile Include="Views\ExportDoneDialog.ammy.cs">
<DependentUpon>ExportDoneDialog.ammy</DependentUpon> <DependentUpon>ExportDoneDialog.ammy</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\ExportSetupDialog.ammy.cs">
<DependentUpon>ExportSetupDialog.ammy</DependentUpon>
</Compile>
<Compile Include="Views\SettingsDialog.ammy.cs"> <Compile Include="Views\SettingsDialog.ammy.cs">
<DependentUpon>SettingsDialog.ammy</DependentUpon> <DependentUpon>SettingsDialog.ammy</DependentUpon>
</Compile> </Compile>
@ -120,6 +129,11 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<DependentUpon>ExportDoneDialog.ammy</DependentUpon> <DependentUpon>ExportDoneDialog.ammy</DependentUpon>
</Page> </Page>
<Page Include="Views\ExportSetupDialog.g.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
<DependentUpon>ExportSetupDialog.ammy</DependentUpon>
</Page>
<Page Include="Views\MainWindow.g.xaml"> <Page Include="Views\MainWindow.g.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
@ -174,6 +188,7 @@
</None> </None>
<None Include="Views\ErrorDialog.ammy" /> <None Include="Views\ErrorDialog.ammy" />
<None Include="Views\ExportDoneDialog.ammy" /> <None Include="Views\ExportDoneDialog.ammy" />
<None Include="Views\ExportSetupDialog.ammy" />
<None Include="Views\MainWindow.ammy" /> <None Include="Views\MainWindow.ammy" />
<None Include="Views\SettingsDialog.ammy" /> <None Include="Views\SettingsDialog.ammy" />
</ItemGroup> </ItemGroup>

@ -0,0 +1,17 @@
using DiscordChatExporter.Models;
namespace DiscordChatExporter.Messages
{
public class ShowExportSetupMessage
{
public Guild Guild { get; }
public Channel Channel { get; }
public ShowExportSetupMessage(Guild guild, Channel channel)
{
Guild = guild;
Channel = channel;
}
}
}

@ -0,0 +1,20 @@
using DiscordChatExporter.Models;
namespace DiscordChatExporter.Messages
{
public class StartExportMessage
{
public Channel Channel { get; }
public string FilePath { get; }
public ExportFormat Format { get; }
public StartExportMessage(Channel channel, string filePath, ExportFormat format)
{
Channel = channel;
FilePath = filePath;
Format = format;
}
}
}

@ -0,0 +1,8 @@
namespace DiscordChatExporter.Models
{
public enum ExportFormat
{
Text,
Html
}
}

@ -0,0 +1,14 @@
namespace DiscordChatExporter.Models
{
public static class Extensions
{
public static string GetFileExtension(this ExportFormat format)
{
if (format == ExportFormat.Text)
return "txt";
if (format == ExportFormat.Html)
return "html";
return null;
}
}
}

@ -1,4 +1,5 @@
using System.IO; using System;
using System.IO;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
using System.Resources; using System.Resources;
@ -19,12 +20,53 @@ namespace DiscordChatExporter.Services
_settingsService = settingsService; _settingsService = settingsService;
} }
public async Task ExportAsync(string filePath, ChannelChatLog log, Theme theme) public async Task ExportAsTextAsync(string filePath, ChannelChatLog log)
{
var dateFormat = _settingsService.DateFormat;
using (var writer = new StreamWriter(filePath, false, Encoding.UTF8, 128 * 1024))
{
// Guild and channel info
await writer.WriteLineAsync("=".Repeat(16));
await writer.WriteLineAsync($"Guild: {log.Guild}");
await writer.WriteLineAsync($"Channel: {log.Channel}");
await writer.WriteLineAsync($"Messages: {log.TotalMessageCount:N0}");
await writer.WriteLineAsync("=".Repeat(16));
await writer.WriteLineAsync();
// Chat log
foreach (var group in log.MessageGroups)
{
var timeStampFormatted = group.TimeStamp.ToString(dateFormat);
await writer.WriteLineAsync($"{group.Author} [{timeStampFormatted}]");
// Messages
foreach (var message in group.Messages)
{
if (message.Content.IsNotBlank())
{
var contentFormatted = message.Content.Replace("\n", Environment.NewLine);
await writer.WriteLineAsync(contentFormatted);
}
// Attachments
foreach (var attachment in message.Attachments)
{
await writer.WriteLineAsync(attachment.Url);
}
}
await writer.WriteLineAsync();
}
}
}
public async Task ExportAsHtmlAsync(string filePath, ChannelChatLog log, Theme theme)
{ {
var themeCss = GetThemeCss(theme); var themeCss = GetThemeCss(theme);
var dateFormat = _settingsService.DateFormat; var dateFormat = _settingsService.DateFormat;
using (var writer = new StreamWriter(filePath, false, Encoding.UTF8, 128*1024)) using (var writer = new StreamWriter(filePath, false, Encoding.UTF8, 128 * 1024))
{ {
// Generation info // Generation info
await writer.WriteLineAsync("<!-- https://github.com/Tyrrrz/DiscordChatExporter -->"); await writer.WriteLineAsync("<!-- https://github.com/Tyrrrz/DiscordChatExporter -->");
@ -35,7 +77,7 @@ namespace DiscordChatExporter.Services
// HEAD // HEAD
await writer.WriteLineAsync("<head>"); await writer.WriteLineAsync("<head>");
await writer.WriteLineAsync($"<title>{log.Guild.Name} - {log.Channel.Name}</title>"); await writer.WriteLineAsync($"<title>{log.Guild} - {log.Channel}</title>");
await writer.WriteLineAsync("<meta charset=\"utf-8\" />"); await writer.WriteLineAsync("<meta charset=\"utf-8\" />");
await writer.WriteLineAsync("<meta name=\"viewport\" content=\"width=device-width\" />"); await writer.WriteLineAsync("<meta name=\"viewport\" content=\"width=device-width\" />");
await writer.WriteLineAsync($"<style>{themeCss}</style>"); await writer.WriteLineAsync($"<style>{themeCss}</style>");
@ -50,8 +92,8 @@ namespace DiscordChatExporter.Services
await writer.WriteLineAsync($"<img class=\"guild-icon\" src=\"{log.Guild.IconUrl}\" />"); await writer.WriteLineAsync($"<img class=\"guild-icon\" src=\"{log.Guild.IconUrl}\" />");
await writer.WriteLineAsync("</div>"); // info-left await writer.WriteLineAsync("</div>"); // info-left
await writer.WriteLineAsync("<div class=\"info-right\">"); await writer.WriteLineAsync("<div class=\"info-right\">");
await writer.WriteLineAsync($"<div class=\"guild-name\">{log.Guild.Name}</div>"); await writer.WriteLineAsync($"<div class=\"guild-name\">{log.Guild}</div>");
await writer.WriteLineAsync($"<div class=\"channel-name\">{log.Channel.Name}</div>"); await writer.WriteLineAsync($"<div class=\"channel-name\">{log.Channel}</div>");
await writer.WriteLineAsync($"<div class=\"misc\">{log.TotalMessageCount:N0} messages</div>"); await writer.WriteLineAsync($"<div class=\"misc\">{log.TotalMessageCount:N0} messages</div>");
await writer.WriteLineAsync("</div>"); // info-right await writer.WriteLineAsync("</div>"); // info-right
await writer.WriteLineAsync("</div>"); // info await writer.WriteLineAsync("</div>"); // info
@ -66,18 +108,20 @@ namespace DiscordChatExporter.Services
await writer.WriteLineAsync("</div>"); await writer.WriteLineAsync("</div>");
await writer.WriteLineAsync("<div class=\"msg-right\">"); await writer.WriteLineAsync("<div class=\"msg-right\">");
await writer.WriteLineAsync($"<span class=\"msg-user\">{HtmlEncode(group.Author.Name)}</span>"); await writer.WriteAsync($"<span class=\"msg-user\" title=\"{HtmlEncode(group.Author)}\">");
await writer.WriteAsync(HtmlEncode(group.Author.Name));
await writer.WriteLineAsync("</span>");
var timeStampFormatted = HtmlEncode(group.TimeStamp.ToString(dateFormat)); var timeStampFormatted = HtmlEncode(group.TimeStamp.ToString(dateFormat));
await writer.WriteLineAsync($"<span class=\"msg-date\">{timeStampFormatted}</span>"); await writer.WriteLineAsync($"<span class=\"msg-date\">{timeStampFormatted}</span>");
// Message // Messages
foreach (var message in group.Messages) foreach (var message in group.Messages)
{ {
// Content // Content
if (message.Content.IsNotBlank()) if (message.Content.IsNotBlank())
{ {
await writer.WriteLineAsync("<div class=\"msg-content\">"); await writer.WriteLineAsync("<div class=\"msg-content\">");
var contentFormatted = FormatMessageContent(message.Content); var contentFormatted = FormatMessageContentHtml(message.Content);
await writer.WriteAsync(contentFormatted); await writer.WriteAsync(contentFormatted);
// Edited timestamp // Edited timestamp
@ -99,7 +143,8 @@ namespace DiscordChatExporter.Services
{ {
await writer.WriteLineAsync("<div class=\"msg-attachment\">"); await writer.WriteLineAsync("<div class=\"msg-attachment\">");
await writer.WriteLineAsync($"<a href=\"{attachment.Url}\">"); await writer.WriteLineAsync($"<a href=\"{attachment.Url}\">");
await writer.WriteLineAsync($"<img class=\"msg-attachment\" src=\"{attachment.Url}\" />"); await writer.WriteLineAsync(
$"<img class=\"msg-attachment\" src=\"{attachment.Url}\" />");
await writer.WriteLineAsync("</a>"); await writer.WriteLineAsync("</a>");
await writer.WriteLineAsync("</div>"); await writer.WriteLineAsync("</div>");
} }
@ -148,6 +193,11 @@ namespace DiscordChatExporter.Services
return WebUtility.HtmlEncode(str); return WebUtility.HtmlEncode(str);
} }
private static string HtmlEncode(object obj)
{
return WebUtility.HtmlEncode(obj.ToString());
}
private static string FormatFileSize(long fileSize) private static string FormatFileSize(long fileSize)
{ {
string[] units = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; string[] units = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
@ -163,7 +213,7 @@ namespace DiscordChatExporter.Services
return $"{size:0.#} {units[unit]}"; return $"{size:0.#} {units[unit]}";
} }
private static string FormatMessageContent(string content) private static string FormatMessageContentHtml(string content)
{ {
// Encode HTML // Encode HTML
content = HtmlEncode(content); content = HtmlEncode(content);

@ -5,6 +5,7 @@ namespace DiscordChatExporter.Services
{ {
public interface IExportService public interface IExportService
{ {
Task ExportAsync(string filePath, ChannelChatLog log, Theme theme); Task ExportAsTextAsync(string filePath, ChannelChatLog log);
Task ExportAsHtmlAsync(string filePath, ChannelChatLog log, Theme theme);
} }
} }

@ -4,11 +4,13 @@ namespace DiscordChatExporter.Services
{ {
public interface ISettingsService public interface ISettingsService
{ {
string Token { get; set; }
Theme Theme { get; set; } Theme Theme { get; set; }
string DateFormat { get; set; } string DateFormat { get; set; }
int MessageGroupLimit { get; set; } int MessageGroupLimit { get; set; }
string LastToken { get; set; }
ExportFormat LastExportFormat { get; set; }
void Load(); void Load();
void Save(); void Save();
} }

@ -5,11 +5,13 @@ namespace DiscordChatExporter.Services
{ {
public class SettingsService : SettingsManager, ISettingsService public class SettingsService : SettingsManager, ISettingsService
{ {
public string Token { get; set; }
public Theme Theme { get; set; } public Theme Theme { get; set; }
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm"; public string DateFormat { get; set; } = "dd-MMM-yy hh:mm";
public int MessageGroupLimit { get; set; } = 20; public int MessageGroupLimit { get; set; } = 20;
public string LastToken { get; set; }
public ExportFormat LastExportFormat { get; set; } = ExportFormat.Html;
public SettingsService() public SettingsService()
{ {
Configuration.StorageSpace = StorageSpace.Instance; Configuration.StorageSpace = StorageSpace.Instance;

@ -9,7 +9,11 @@ namespace DiscordChatExporter.ViewModels
public ErrorViewModel() public ErrorViewModel()
{ {
MessengerInstance.Register<ShowErrorMessage>(this, m => Message = m.Message); // Messages
MessengerInstance.Register<ShowErrorMessage>(this, m =>
{
Message = m.Message;
});
} }
} }
} }

@ -14,10 +14,14 @@ namespace DiscordChatExporter.ViewModels
public ExportDoneViewModel() public ExportDoneViewModel()
{ {
MessengerInstance.Register<ShowExportDoneMessage>(this, m => _filePath = m.FilePath);
// Commands // Commands
OpenCommand = new RelayCommand(Open); OpenCommand = new RelayCommand(Open);
// Messages
MessengerInstance.Register<ShowExportDoneMessage>(this, m =>
{
_filePath = m.FilePath;
});
} }
private void Open() private void Open()

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DiscordChatExporter.Messages;
using DiscordChatExporter.Models;
using DiscordChatExporter.Services;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using Tyrrrz.Extensions;
namespace DiscordChatExporter.ViewModels
{
public class ExportSetupViewModel : ViewModelBase, IExportSetupViewModel
{
private readonly ISettingsService _settingsService;
private string _filePath;
private ExportFormat _format;
public Guild Guild { get; private set; }
public Channel Channel { get; private set; }
public string FilePath
{
get => _filePath;
set
{
Set(ref _filePath, value);
ExportCommand.RaiseCanExecuteChanged();
}
}
public IReadOnlyList<ExportFormat> AvailableFormats { get; }
public ExportFormat SelectedFormat
{
get => _format;
set => Set(ref _format, value);
}
// Commands
public RelayCommand ExportCommand { get; }
public ExportSetupViewModel(ISettingsService settingsService)
{
_settingsService = settingsService;
// Defaults
AvailableFormats = Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
// Commands
ExportCommand = new RelayCommand(Export, () => FilePath.IsNotBlank());
// Messages
MessengerInstance.Register<ShowExportSetupMessage>(this, m =>
{
Guild = m.Guild;
Channel = m.Channel;
SelectedFormat = _settingsService.LastExportFormat;
FilePath = Path.Combine($"{Guild} - {Channel}.{SelectedFormat.GetFileExtension()}");
});
}
private void Export()
{
_settingsService.LastExportFormat = SelectedFormat;
MessengerInstance.Send(new StartExportMessage(Channel, FilePath, SelectedFormat));
}
}
}

@ -0,0 +1,17 @@
using System.Collections.Generic;
using DiscordChatExporter.Models;
using GalaSoft.MvvmLight.CommandWpf;
namespace DiscordChatExporter.ViewModels
{
public interface IExportSetupViewModel
{
Guild Guild { get; }
Channel Channel { get; }
string FilePath { get; set; }
IReadOnlyList<ExportFormat> AvailableFormats { get; }
ExportFormat SelectedFormat { get; set; }
RelayCommand ExportCommand { get; }
}
}

@ -16,8 +16,8 @@ namespace DiscordChatExporter.ViewModels
IReadOnlyList<Channel> AvailableChannels { get; } IReadOnlyList<Channel> AvailableChannels { get; }
RelayCommand PullDataCommand { get; } RelayCommand PullDataCommand { get; }
RelayCommand<Channel> ExportChannelCommand { get; }
RelayCommand ShowSettingsCommand { get; } RelayCommand ShowSettingsCommand { get; }
RelayCommand ShowAboutCommand { get; } RelayCommand ShowAboutCommand { get; }
RelayCommand<Channel> ShowExportSetupCommand { get; }
} }
} }

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using DiscordChatExporter.Exceptions; using DiscordChatExporter.Exceptions;
@ -9,7 +8,6 @@ using DiscordChatExporter.Models;
using DiscordChatExporter.Services; using DiscordChatExporter.Services;
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf; using GalaSoft.MvvmLight.CommandWpf;
using Microsoft.Win32;
using Tyrrrz.Extensions; using Tyrrrz.Extensions;
namespace DiscordChatExporter.ViewModels namespace DiscordChatExporter.ViewModels
@ -36,7 +34,7 @@ namespace DiscordChatExporter.ViewModels
{ {
Set(ref _isBusy, value); Set(ref _isBusy, value);
PullDataCommand.RaiseCanExecuteChanged(); PullDataCommand.RaiseCanExecuteChanged();
ExportChannelCommand.RaiseCanExecuteChanged(); ShowExportSetupCommand.RaiseCanExecuteChanged();
} }
} }
@ -44,13 +42,13 @@ namespace DiscordChatExporter.ViewModels
public string Token public string Token
{ {
get => _settingsService.Token; get => _settingsService.LastToken;
set set
{ {
// Remove invalid chars // Remove invalid chars
value = value?.Trim('"'); value = value?.Trim('"');
_settingsService.Token = value; _settingsService.LastToken = value;
PullDataCommand.RaiseCanExecuteChanged(); PullDataCommand.RaiseCanExecuteChanged();
} }
} }
@ -72,7 +70,7 @@ namespace DiscordChatExporter.ViewModels
{ {
Set(ref _selectedGuild, value); Set(ref _selectedGuild, value);
AvailableChannels = value != null ? _guildChannelsMap[value] : new Channel[0]; AvailableChannels = value != null ? _guildChannelsMap[value] : new Channel[0];
ExportChannelCommand.RaiseCanExecuteChanged(); ShowExportSetupCommand.RaiseCanExecuteChanged();
} }
} }
@ -83,9 +81,9 @@ namespace DiscordChatExporter.ViewModels
} }
public RelayCommand PullDataCommand { get; } public RelayCommand PullDataCommand { get; }
public RelayCommand<Channel> ExportChannelCommand { get; }
public RelayCommand ShowSettingsCommand { get; } public RelayCommand ShowSettingsCommand { get; }
public RelayCommand ShowAboutCommand { get; } public RelayCommand ShowAboutCommand { get; }
public RelayCommand<Channel> ShowExportSetupCommand { get; }
public MainViewModel(ISettingsService settingsService, IDataService dataService, public MainViewModel(ISettingsService settingsService, IDataService dataService,
IMessageGroupService messageGroupService, IExportService exportService) IMessageGroupService messageGroupService, IExportService exportService)
@ -99,9 +97,15 @@ namespace DiscordChatExporter.ViewModels
// Commands // Commands
PullDataCommand = new RelayCommand(PullData, () => Token.IsNotBlank() && !IsBusy); PullDataCommand = new RelayCommand(PullData, () => Token.IsNotBlank() && !IsBusy);
ExportChannelCommand = new RelayCommand<Channel>(ExportChannel, _ => !IsBusy);
ShowSettingsCommand = new RelayCommand(ShowSettings); ShowSettingsCommand = new RelayCommand(ShowSettings);
ShowAboutCommand = new RelayCommand(ShowAbout); ShowAboutCommand = new RelayCommand(ShowAbout);
ShowExportSetupCommand = new RelayCommand<Channel>(ShowExportSetup, _ => !IsBusy);
// Messages
MessengerInstance.Register<StartExportMessage>(this, m =>
{
Export(m.Channel, m.FilePath, m.Format);
});
} }
private async void PullData() private async void PullData()
@ -142,29 +146,25 @@ namespace DiscordChatExporter.ViewModels
IsBusy = false; IsBusy = false;
} }
private async void ExportChannel(Channel channel) private void ShowSettings()
{ {
IsBusy = true; MessengerInstance.Send(new ShowSettingsMessage());
}
// Get safe file names private void ShowAbout()
var safeGuildName = SelectedGuild.Name.Replace(Path.GetInvalidFileNameChars(), '_'); {
var safeChannelName = channel.Name.Replace(Path.GetInvalidFileNameChars(), '_'); Process.Start("https://github.com/Tyrrrz/DiscordChatExporter");
}
// Ask for path private void ShowExportSetup(Channel channel)
var sfd = new SaveFileDialog {
{ MessengerInstance.Send(new ShowExportSetupMessage(SelectedGuild, channel));
FileName = $"{safeGuildName} - {safeChannelName}.html", }
Filter = "HTML files (*.html)|*.html|All files (*.*)|*.*",
DefaultExt = "html", private async void Export(Channel channel, string filePath, ExportFormat format)
AddExtension = true {
}; IsBusy = true;
if (sfd.ShowDialog() != true)
{
IsBusy = false;
return;
}
// Export
try try
{ {
// Get messages // Get messages
@ -177,10 +177,13 @@ namespace DiscordChatExporter.ViewModels
var chatLog = new ChannelChatLog(SelectedGuild, channel, messageGroups, messages.Count); var chatLog = new ChannelChatLog(SelectedGuild, channel, messageGroups, messages.Count);
// Export // Export
await _exportService.ExportAsync(sfd.FileName, chatLog, _settingsService.Theme); if (format == ExportFormat.Text)
await _exportService.ExportAsTextAsync(filePath, chatLog);
else if (format == ExportFormat.Html)
await _exportService.ExportAsHtmlAsync(filePath, chatLog, _settingsService.Theme);
// Show dialog // Notify completion
MessengerInstance.Send(new ShowExportDoneMessage(sfd.FileName)); MessengerInstance.Send(new ShowExportDoneMessage(filePath));
} }
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden) catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{ {
@ -190,15 +193,5 @@ namespace DiscordChatExporter.ViewModels
IsBusy = false; IsBusy = false;
} }
private void ShowSettings()
{
MessengerInstance.Send(new ShowSettingsMessage());
}
private void ShowAbout()
{
Process.Start("https://github.com/Tyrrrz/DiscordChatExporter");
}
} }
} }

@ -1,10 +1,4 @@
using System; namespace DiscordChatExporter.Views
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DiscordChatExporter.Views
{ {
public partial class ErrorDialog public partial class ErrorDialog
{ {

@ -18,7 +18,8 @@ UserControl "DiscordChatExporter.Views.ExportDoneDialog" {
HorizontalAlignment: Right HorizontalAlignment: Right
// Open // Open
Button { Button "OpenButton" {
Click: OpenButton_Click
Command: bind OpenCommand Command: bind OpenCommand
Content: "OPEN IT" Content: "OPEN IT"
Margin: 8 Margin: 8

@ -1,8 +1,5 @@
using System; using System.Windows;
using System.Collections.Generic; using MaterialDesignThemes.Wpf;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DiscordChatExporter.Views namespace DiscordChatExporter.Views
{ {
@ -12,5 +9,10 @@ namespace DiscordChatExporter.Views
{ {
InitializeComponent(); InitializeComponent();
} }
public void OpenButton_Click(object sender, RoutedEventArgs args)
{
DialogHost.CloseDialogCommand.Execute(null, null);
}
} }
} }

@ -0,0 +1,58 @@
using MaterialDesignThemes.Wpf
UserControl "DiscordChatExporter.Views.ExportSetupDialog" {
DataContext: bind ExportSetupViewModel from $resource Container
Width: 350
StackPanel {
// File path
Grid {
#TwoColumns("*", "Auto")
TextBox {
Grid.Column: 0
Margin: "16 16 4 8"
HintAssist.Hint: "Output file"
HintAssist.IsFloating: true
Text: bind FilePath
set [ UpdateSourceTrigger: PropertyChanged ]
}
Button "LocateFilePathButton" {
Grid.Column: 1
Margin: "4 16 16 8"
Padding: 4
Click: LocateFilePathButton_Click
Style: resource dyn "MaterialDesignFlatButton"
PackIcon {
Kind: PackIconKind.DotsHorizontal
Width: 24
Height: 24
}
}
}
// Buttons
@StackPanelHorizontal {
HorizontalAlignment: Right
// Export
Button "ExportButton" {
Click: ExportButton_Click
Command: bind ExportCommand
Content: "EXPORT"
Margin: 8
Style: resource dyn "MaterialDesignFlatButton"
}
// Cancel
Button {
Command: DialogHost.CloseDialogCommand
Content: "CANCEL"
Margin: 8
Style: resource dyn "MaterialDesignFlatButton"
}
}
}
}

@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Windows;
using DiscordChatExporter.Models;
using DiscordChatExporter.ViewModels;
using MaterialDesignThemes.Wpf;
using Microsoft.Win32;
using Tyrrrz.Extensions;
namespace DiscordChatExporter.Views
{
public partial class ExportSetupDialog
{
public ExportSetupDialog()
{
InitializeComponent();
}
private IExportSetupViewModel ViewModel => (IExportSetupViewModel) DataContext;
private string GetOutputFileFilter()
{
var filters = new List<string>();
foreach (var format in ViewModel.AvailableFormats)
{
var ext = format.GetFileExtension();
filters.Add($"{format} (*.{ext})|*.{ext}");
}
return filters.JoinToString("|");
}
public void LocateFilePathButton_Click(object sender, RoutedEventArgs args)
{
var sfd = new SaveFileDialog
{
FileName = ViewModel.FilePath,
Filter = GetOutputFileFilter(),
FilterIndex = ViewModel.AvailableFormats.IndexOf(ViewModel.SelectedFormat) + 1,
AddExtension = true,
Title = "Select output file"
};
if (sfd.ShowDialog() == true)
{
ViewModel.FilePath = sfd.FileName;
ViewModel.SelectedFormat = ViewModel.AvailableFormats[sfd.FilterIndex - 1];
}
}
public void ExportButton_Click(object sender, RoutedEventArgs args)
{
DialogHost.CloseDialogCommand.Execute(null, null);
}
}
}

@ -167,7 +167,7 @@ Window "DiscordChatExporter.Views.MainWindow" {
Cursor: CursorType.Hand Cursor: CursorType.Hand
InputBindings: [ InputBindings: [
MouseBinding { MouseBinding {
Command: bind DataContext.ExportChannelCommand from $ancestor<ItemsControl> Command: bind DataContext.ShowExportSetupCommand from $ancestor<ItemsControl>
CommandParameter: bind CommandParameter: bind
MouseAction: LeftClick MouseAction: LeftClick
} }

@ -18,8 +18,9 @@ namespace DiscordChatExporter.Views
Title += $" v{Assembly.GetExecutingAssembly().GetName().Version}"; Title += $" v{Assembly.GetExecutingAssembly().GetName().Version}";
Messenger.Default.Register<ShowErrorMessage>(this, m => DialogHost.Show(new ErrorDialog()).Forget()); Messenger.Default.Register<ShowErrorMessage>(this, m => DialogHost.Show(new ErrorDialog()).Forget());
Messenger.Default.Register<ShowSettingsMessage>(this, m => DialogHost.Show(new SettingsDialog()).Forget());
Messenger.Default.Register<ShowExportDoneMessage>(this, m => DialogHost.Show(new ExportDoneDialog()).Forget()); Messenger.Default.Register<ShowExportDoneMessage>(this, m => DialogHost.Show(new ExportDoneDialog()).Forget());
Messenger.Default.Register<ShowExportSetupMessage>(this, m => DialogHost.Show(new ExportSetupDialog()).Forget());
Messenger.Default.Register<ShowSettingsMessage>(this, m => DialogHost.Show(new SettingsDialog()).Forget());
} }
public void TokenTextBox_KeyDown(object sender, KeyEventArgs e) public void TokenTextBox_KeyDown(object sender, KeyEventArgs e)

Loading…
Cancel
Save