mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-04-10 13:22:24 +00:00
Add command line interface and change solution structure (#26)
This commit is contained in:
69
DiscordChatExporter.Gui/App.ammy
Normal file
69
DiscordChatExporter.Gui/App.ammy
Normal file
@@ -0,0 +1,69 @@
|
||||
Application "DiscordChatExporter.Gui.App" {
|
||||
StartupUri: "Views/MainWindow.g.xaml"
|
||||
Startup: App_Startup
|
||||
Exit: App_Exit
|
||||
|
||||
Resources: ResourceDictionary {
|
||||
// Material Design
|
||||
#MergeDictionary("pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml")
|
||||
#MergeDictionary("pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml")
|
||||
|
||||
// Colors
|
||||
Color Key="PrimaryColor" { "#343838" }
|
||||
Color Key="PrimaryLightColor" { "#5E6262" }
|
||||
Color Key="PrimaryDarkColor" { "#0D1212" }
|
||||
Color Key="AccentColor" { "#F9A825" }
|
||||
Color Key="TextColor" { "#000000" }
|
||||
Color Key="InverseTextColor" { "#FFFFFF" }
|
||||
|
||||
// Brushes
|
||||
SolidColorBrush Key="PrimaryHueLightBrush" { Color: resource dyn "PrimaryLightColor" }
|
||||
SolidColorBrush Key="PrimaryHueLightForegroundBrush" { Color: resource dyn "InverseTextColor" }
|
||||
SolidColorBrush Key="PrimaryHueMidBrush" { Color: resource dyn "PrimaryColor" }
|
||||
SolidColorBrush Key="PrimaryHueMidForegroundBrush" { Color: resource dyn "InverseTextColor" }
|
||||
SolidColorBrush Key="PrimaryHueDarkBrush" { Color: resource dyn "PrimaryDarkColor" }
|
||||
SolidColorBrush Key="PrimaryHueDarkForegroundBrush" { Color: resource dyn "InverseTextColor" }
|
||||
SolidColorBrush Key="SecondaryAccentBrush" { Color: resource dyn "AccentColor" }
|
||||
SolidColorBrush Key="SecondaryAccentForegroundBrush" { Color: resource dyn "TextColor" }
|
||||
SolidColorBrush Key="PrimaryTextBrush" { Color: resource dyn "TextColor", Opacity: 0.87 }
|
||||
SolidColorBrush Key="SecondaryTextBrush" { Color: resource dyn "TextColor", Opacity: 0.64 }
|
||||
SolidColorBrush Key="DimTextBrush" { Color: resource dyn "TextColor", Opacity: 0.45 }
|
||||
SolidColorBrush Key="PrimaryInverseTextBrush" { Color: resource dyn "InverseTextColor", Opacity: 1 }
|
||||
SolidColorBrush Key="SecondaryInverseTextBrush" { Color: resource dyn "InverseTextColor", Opacity: 0.7 }
|
||||
SolidColorBrush Key="DimInverseTextBrush" { Color: resource dyn "InverseTextColor", Opacity: 0.52 }
|
||||
SolidColorBrush Key="AccentTextBrush" { Color: resource dyn "AccentColor", Opacity: 1 }
|
||||
SolidColorBrush Key="DividerBrush" { Color: resource dyn "TextColor", Opacity: 0.12 }
|
||||
SolidColorBrush Key="InverseDividerBrush" { Color: resource dyn "InverseTextColor", Opacity: 0.12 }
|
||||
|
||||
// Styles
|
||||
Style {
|
||||
TargetType: "Image"
|
||||
#Setter("RenderOptions.BitmapScalingMode", "HighQuality")
|
||||
}
|
||||
|
||||
Style {
|
||||
TargetType: "ProgressBar"
|
||||
BasedOn: resource "MaterialDesignLinearProgressBar"
|
||||
#Setter("Foreground", resource dyn "SecondaryAccentBrush")
|
||||
#Setter("Height", 2)
|
||||
#Setter("Minimum", 0)
|
||||
#Setter("Maximum", 1)
|
||||
#Setter("BorderThickness", 0)
|
||||
}
|
||||
|
||||
Style {
|
||||
TargetType: "TextBox"
|
||||
BasedOn: resource "MaterialDesignTextBox"
|
||||
#Setter("Foreground", resource dyn "PrimaryTextBrush")
|
||||
}
|
||||
|
||||
Style {
|
||||
TargetType: "ComboBox"
|
||||
BasedOn: resource "MaterialDesignComboBox"
|
||||
#Setter("Foreground", resource dyn "PrimaryTextBrush")
|
||||
}
|
||||
|
||||
// Container
|
||||
Container Key="Container" { }
|
||||
}
|
||||
}
|
||||
19
DiscordChatExporter.Gui/App.ammy.cs
Normal file
19
DiscordChatExporter.Gui/App.ammy.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace DiscordChatExporter.Gui
|
||||
{
|
||||
public partial class App
|
||||
{
|
||||
private Container Container => (Container) Resources["Container"];
|
||||
|
||||
private void App_Startup(object sender, StartupEventArgs e)
|
||||
{
|
||||
Container.Init();
|
||||
}
|
||||
|
||||
private void App_Exit(object sender, ExitEventArgs e)
|
||||
{
|
||||
Container.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
DiscordChatExporter.Gui/Container.cs
Normal file
49
DiscordChatExporter.Gui/Container.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using DiscordChatExporter.Gui.ViewModels;
|
||||
using GalaSoft.MvvmLight.Ioc;
|
||||
using Microsoft.Practices.ServiceLocation;
|
||||
|
||||
namespace DiscordChatExporter.Gui
|
||||
{
|
||||
public class Container
|
||||
{
|
||||
public IErrorViewModel ErrorViewModel => Resolve<IErrorViewModel>();
|
||||
public IExportDoneViewModel ExportDoneViewModel => Resolve<IExportDoneViewModel>();
|
||||
public IExportSetupViewModel ExportSetupViewModel => Resolve<IExportSetupViewModel>();
|
||||
public IMainViewModel MainViewModel => Resolve<IMainViewModel>();
|
||||
public ISettingsViewModel SettingsViewModel => Resolve<ISettingsViewModel>();
|
||||
|
||||
private T Resolve<T>(string key = null)
|
||||
{
|
||||
return ServiceLocator.Current.GetInstance<T>(key);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
|
||||
SimpleIoc.Default.Reset();
|
||||
|
||||
// Services
|
||||
SimpleIoc.Default.Register<IDataService, DataService>();
|
||||
SimpleIoc.Default.Register<IExportService, ExportService>();
|
||||
SimpleIoc.Default.Register<IMessageGroupService, MessageGroupService>();
|
||||
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
|
||||
|
||||
// Load settings
|
||||
Resolve<ISettingsService>().Load();
|
||||
|
||||
// View models
|
||||
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<ISettingsViewModel, SettingsViewModel>(true);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
// Save settings
|
||||
ServiceLocator.Current.GetInstance<ISettingsService>().Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
202
DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
Normal file
202
DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
Normal file
@@ -0,0 +1,202 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{732A67AF-93DE-49DF-B10F-FD74710B7863}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>DiscordChatExporter.Gui</RootNamespace>
|
||||
<AssemblyName>DiscordChatExporter</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="AmmySidekick, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7c1296d24569a67d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ammy.WPF.1.2.87\lib\net40\AmmySidekick.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Costura, Version=1.6.2.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="GalaSoft.MvvmLight, Version=5.3.0.19026, Culture=neutral, PublicKeyToken=e7570ab207bcb616, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GalaSoft.MvvmLight.Extras, Version=5.3.0.19032, Culture=neutral, PublicKeyToken=669f0b5e8f868abf, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Extras.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.3.0.19032, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MaterialDesignColors, Version=1.1.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MaterialDesignColors.1.1.3\lib\net45\MaterialDesignColors.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MaterialDesignThemes.Wpf, Version=2.3.1.953, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MaterialDesignThemes.2.3.1.953\lib\net45\MaterialDesignThemes.Wpf.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\System.Windows.Interactivity.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xaml">
|
||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Tyrrrz.Extensions, Version=1.5.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Tyrrrz.Extensions.1.5.0\lib\net45\Tyrrrz.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Messages\ShowErrorMessage.cs" />
|
||||
<Compile Include="Messages\ShowExportDoneMessage.cs" />
|
||||
<Compile Include="Messages\ShowExportSetupMessage.cs" />
|
||||
<Compile Include="Messages\ShowSettingsMessage.cs" />
|
||||
<Compile Include="Messages\StartExportMessage.cs" />
|
||||
<Compile Include="ViewModels\ErrorViewModel.cs" />
|
||||
<Compile Include="ViewModels\ExportSetupViewModel.cs" />
|
||||
<Compile Include="ViewModels\IErrorViewModel.cs" />
|
||||
<Compile Include="ViewModels\IExportSetupViewModel.cs" />
|
||||
<Compile Include="ViewModels\ISettingsViewModel.cs" />
|
||||
<Compile Include="ViewModels\IExportDoneViewModel.cs" />
|
||||
<Compile Include="ViewModels\SettingsViewModel.cs" />
|
||||
<Compile Include="ViewModels\ExportDoneViewModel.cs" />
|
||||
<Compile Include="Views\ErrorDialog.ammy.cs">
|
||||
<DependentUpon>ErrorDialog.ammy</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\ExportDoneDialog.ammy.cs">
|
||||
<DependentUpon>ExportDoneDialog.ammy</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\ExportSetupDialog.ammy.cs">
|
||||
<DependentUpon>ExportSetupDialog.ammy</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\SettingsDialog.ammy.cs">
|
||||
<DependentUpon>SettingsDialog.ammy</DependentUpon>
|
||||
</Compile>
|
||||
<Page Include="App.g.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>XamlIntelliSenseFileGenerator</Generator>
|
||||
<DependentUpon>App.ammy</DependentUpon>
|
||||
</Page>
|
||||
<Page Include="Views\ErrorDialog.g.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<DependentUpon>ErrorDialog.ammy</DependentUpon>
|
||||
</Page>
|
||||
<Page Include="Views\ExportDoneDialog.g.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<DependentUpon>ExportDoneDialog.ammy</DependentUpon>
|
||||
</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">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<DependentUpon>MainWindow.ammy</DependentUpon>
|
||||
</Page>
|
||||
<Compile Include="App.ammy.cs">
|
||||
<DependentUpon>App.ammy</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Container.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="ViewModels\IMainViewModel.cs" />
|
||||
<Compile Include="ViewModels\MainViewModel.cs" />
|
||||
<Compile Include="Views\MainWindow.ammy.cs">
|
||||
<DependentUpon>MainWindow.ammy</DependentUpon>
|
||||
</Compile>
|
||||
<Page Include="Views\SettingsDialog.g.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<DependentUpon>SettingsDialog.ammy</DependentUpon>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Include="App.ammy" />
|
||||
<None Include="lib.ammy" />
|
||||
<None Include="packages.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="Views\ErrorDialog.ammy" />
|
||||
<None Include="Views\ExportDoneDialog.ammy" />
|
||||
<None Include="Views\ExportSetupDialog.ammy" />
|
||||
<None Include="Views\MainWindow.ammy" />
|
||||
<None Include="Views\SettingsDialog.ammy" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="..\favicon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordChatExporter.Core\DiscordChatExporter.Core.csproj">
|
||||
<Project>{707c0cd0-a7e0-4cab-8db9-07a45cb87377}</Project>
|
||||
<Name>DiscordChatExporter.Core</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="FodyWeavers.xml" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\Ammy.1.2.87\build\Ammy.targets" Condition="Exists('..\packages\Ammy.1.2.87\build\Ammy.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Ammy.1.2.87\build\Ammy.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Ammy.1.2.87\build\Ammy.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.1.6.2\build\dotnet\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.1.6.2\build\dotnet\Costura.Fody.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Fody.2.3.18\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.2.3.18\build\Fody.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Costura.Fody.1.6.2\build\dotnet\Costura.Fody.targets" Condition="Exists('..\packages\Costura.Fody.1.6.2\build\dotnet\Costura.Fody.targets')" />
|
||||
<Import Project="..\packages\Fody.2.3.18\build\Fody.targets" Condition="Exists('..\packages\Fody.2.3.18\build\Fody.targets')" />
|
||||
</Project>
|
||||
4
DiscordChatExporter.Gui/FodyWeavers.xml
Normal file
4
DiscordChatExporter.Gui/FodyWeavers.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers>
|
||||
<Costura />
|
||||
</Weavers>
|
||||
12
DiscordChatExporter.Gui/Messages/ShowErrorMessage.cs
Normal file
12
DiscordChatExporter.Gui/Messages/ShowErrorMessage.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace DiscordChatExporter.Gui.Messages
|
||||
{
|
||||
public class ShowErrorMessage
|
||||
{
|
||||
public string Message { get; }
|
||||
|
||||
public ShowErrorMessage(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
DiscordChatExporter.Gui/Messages/ShowExportDoneMessage.cs
Normal file
12
DiscordChatExporter.Gui/Messages/ShowExportDoneMessage.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace DiscordChatExporter.Gui.Messages
|
||||
{
|
||||
public class ShowExportDoneMessage
|
||||
{
|
||||
public string FilePath { get; }
|
||||
|
||||
public ShowExportDoneMessage(string filePath)
|
||||
{
|
||||
FilePath = filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
DiscordChatExporter.Gui/Messages/ShowExportSetupMessage.cs
Normal file
17
DiscordChatExporter.Gui/Messages/ShowExportSetupMessage.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using DiscordChatExporter.Core.Models;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Messages
|
||||
{
|
||||
public class ShowExportSetupMessage
|
||||
{
|
||||
public Guild Guild { get; }
|
||||
|
||||
public Channel Channel { get; }
|
||||
|
||||
public ShowExportSetupMessage(Guild guild, Channel channel)
|
||||
{
|
||||
Guild = guild;
|
||||
Channel = channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
DiscordChatExporter.Gui/Messages/ShowSettingsMessage.cs
Normal file
6
DiscordChatExporter.Gui/Messages/ShowSettingsMessage.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace DiscordChatExporter.Gui.Messages
|
||||
{
|
||||
public class ShowSettingsMessage
|
||||
{
|
||||
}
|
||||
}
|
||||
28
DiscordChatExporter.Gui/Messages/StartExportMessage.cs
Normal file
28
DiscordChatExporter.Gui/Messages/StartExportMessage.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Messages
|
||||
{
|
||||
public class StartExportMessage
|
||||
{
|
||||
public Channel Channel { get; }
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public ExportFormat Format { get; }
|
||||
|
||||
public DateTime? From { get; }
|
||||
|
||||
public DateTime? To { get; }
|
||||
|
||||
public StartExportMessage(Channel channel, string filePath, ExportFormat format,
|
||||
DateTime? from, DateTime? to)
|
||||
{
|
||||
Channel = channel;
|
||||
FilePath = filePath;
|
||||
Format = format;
|
||||
From = from;
|
||||
To = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
DiscordChatExporter.Gui/Program.cs
Normal file
17
DiscordChatExporter.Gui/Program.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using AmmySidekick;
|
||||
|
||||
namespace DiscordChatExporter.Gui
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main()
|
||||
{
|
||||
var app = new App();
|
||||
app.InitializeComponent();
|
||||
RuntimeUpdateHandler.Register(app, $"/{Ammy.GetAssemblyName(app)};component/App.g.xaml");
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
7
DiscordChatExporter.Gui/Properties/AssemblyInfo.cs
Normal file
7
DiscordChatExporter.Gui/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyTitle("DiscordChatExporter")]
|
||||
[assembly: AssemblyCompany("Tyrrrz")]
|
||||
[assembly: AssemblyCopyright("Copyright (c) 2017-2018 Alexey Golub")]
|
||||
[assembly: AssemblyVersion("2.3")]
|
||||
[assembly: AssemblyFileVersion("2.3")]
|
||||
63
DiscordChatExporter.Gui/Properties/Resources.Designer.cs
generated
Normal file
63
DiscordChatExporter.Gui/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace DiscordChatExporter.Gui.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DiscordChatExporter.Gui.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
DiscordChatExporter.Gui/Properties/Resources.resx
Normal file
117
DiscordChatExporter.Gui/Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
19
DiscordChatExporter.Gui/ViewModels/ErrorViewModel.cs
Normal file
19
DiscordChatExporter.Gui/ViewModels/ErrorViewModel.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class ErrorViewModel : ViewModelBase, IErrorViewModel
|
||||
{
|
||||
public string Message { get; private set; }
|
||||
|
||||
public ErrorViewModel()
|
||||
{
|
||||
// Messages
|
||||
MessengerInstance.Register<ShowErrorMessage>(this, m =>
|
||||
{
|
||||
Message = m.Message;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
32
DiscordChatExporter.Gui/ViewModels/ExportDoneViewModel.cs
Normal file
32
DiscordChatExporter.Gui/ViewModels/ExportDoneViewModel.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Diagnostics;
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class ExportDoneViewModel : ViewModelBase, IExportDoneViewModel
|
||||
{
|
||||
private string _filePath;
|
||||
|
||||
// Commands
|
||||
public RelayCommand OpenCommand { get; }
|
||||
|
||||
public ExportDoneViewModel()
|
||||
{
|
||||
// Commands
|
||||
OpenCommand = new RelayCommand(Open);
|
||||
|
||||
// Messages
|
||||
MessengerInstance.Register<ShowExportDoneMessage>(this, m =>
|
||||
{
|
||||
_filePath = m.FilePath;
|
||||
});
|
||||
}
|
||||
|
||||
private void Open()
|
||||
{
|
||||
Process.Start(_filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
DiscordChatExporter.Gui/ViewModels/ExportSetupViewModel.cs
Normal file
100
DiscordChatExporter.Gui/ViewModels/ExportSetupViewModel.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class ExportSetupViewModel : ViewModelBase, IExportSetupViewModel
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private string _filePath;
|
||||
private ExportFormat _format;
|
||||
private DateTime? _from;
|
||||
private DateTime? _to;
|
||||
|
||||
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);
|
||||
|
||||
// Replace extension in path
|
||||
var newExt = value.GetFileExtension();
|
||||
if (FilePath != null && !FilePath.EndsWith(newExt))
|
||||
FilePath = FilePath.SubstringUntilLast(".") + "." + newExt;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime? From
|
||||
{
|
||||
get => _from;
|
||||
set => Set(ref _from, value);
|
||||
}
|
||||
|
||||
public DateTime? To
|
||||
{
|
||||
get => _to;
|
||||
set => Set(ref _to, 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 = $"{Guild} - {Channel}.{SelectedFormat.GetFileExtension()}"
|
||||
.Replace(Path.GetInvalidFileNameChars(), '_');
|
||||
From = null;
|
||||
To = null;
|
||||
});
|
||||
}
|
||||
|
||||
private void Export()
|
||||
{
|
||||
// Save format
|
||||
_settingsService.LastExportFormat = SelectedFormat;
|
||||
|
||||
// Start export
|
||||
MessengerInstance.Send(new StartExportMessage(Channel, FilePath, SelectedFormat, From, To));
|
||||
}
|
||||
}
|
||||
}
|
||||
7
DiscordChatExporter.Gui/ViewModels/IErrorViewModel.cs
Normal file
7
DiscordChatExporter.Gui/ViewModels/IErrorViewModel.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface IErrorViewModel
|
||||
{
|
||||
string Message { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface IExportDoneViewModel
|
||||
{
|
||||
RelayCommand OpenCommand { get; }
|
||||
}
|
||||
}
|
||||
20
DiscordChatExporter.Gui/ViewModels/IExportSetupViewModel.cs
Normal file
20
DiscordChatExporter.Gui/ViewModels/IExportSetupViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface IExportSetupViewModel
|
||||
{
|
||||
Guild Guild { get; }
|
||||
Channel Channel { get; }
|
||||
string FilePath { get; set; }
|
||||
IReadOnlyList<ExportFormat> AvailableFormats { get; }
|
||||
ExportFormat SelectedFormat { get; set; }
|
||||
DateTime? From { get; set; }
|
||||
DateTime? To { get; set; }
|
||||
|
||||
RelayCommand ExportCommand { get; }
|
||||
}
|
||||
}
|
||||
23
DiscordChatExporter.Gui/ViewModels/IMainViewModel.cs
Normal file
23
DiscordChatExporter.Gui/ViewModels/IMainViewModel.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface IMainViewModel
|
||||
{
|
||||
bool IsBusy { get; }
|
||||
bool IsDataAvailable { get; }
|
||||
|
||||
string Token { get; set; }
|
||||
|
||||
IReadOnlyList<Guild> AvailableGuilds { get; }
|
||||
Guild SelectedGuild { get; set; }
|
||||
IReadOnlyList<Channel> AvailableChannels { get; }
|
||||
|
||||
RelayCommand PullDataCommand { get; }
|
||||
RelayCommand ShowSettingsCommand { get; }
|
||||
RelayCommand ShowAboutCommand { get; }
|
||||
RelayCommand<Channel> ShowExportSetupCommand { get; }
|
||||
}
|
||||
}
|
||||
8
DiscordChatExporter.Gui/ViewModels/ISettingsViewModel.cs
Normal file
8
DiscordChatExporter.Gui/ViewModels/ISettingsViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface ISettingsViewModel
|
||||
{
|
||||
string DateFormat { get; set; }
|
||||
int MessageGroupLimit { get; set; }
|
||||
}
|
||||
}
|
||||
211
DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
Normal file
211
DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using DiscordChatExporter.Core.Exceptions;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class MainViewModel : ViewModelBase, IMainViewModel
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IMessageGroupService _messageGroupService;
|
||||
private readonly IExportService _exportService;
|
||||
|
||||
private readonly Dictionary<Guild, IReadOnlyList<Channel>> _guildChannelsMap;
|
||||
|
||||
private bool _isBusy;
|
||||
private string _token;
|
||||
private IReadOnlyList<Guild> _availableGuilds;
|
||||
private Guild _selectedGuild;
|
||||
private IReadOnlyList<Channel> _availableChannels;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
private set
|
||||
{
|
||||
Set(ref _isBusy, value);
|
||||
PullDataCommand.RaiseCanExecuteChanged();
|
||||
ShowExportSetupCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDataAvailable => AvailableGuilds.NotNullAndAny();
|
||||
|
||||
public string Token
|
||||
{
|
||||
get => _token;
|
||||
set
|
||||
{
|
||||
// Remove invalid chars
|
||||
value = value?.Trim('"');
|
||||
|
||||
Set(ref _token, value);
|
||||
PullDataCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<Guild> AvailableGuilds
|
||||
{
|
||||
get => _availableGuilds;
|
||||
private set
|
||||
{
|
||||
Set(ref _availableGuilds, value);
|
||||
RaisePropertyChanged(() => IsDataAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
public Guild SelectedGuild
|
||||
{
|
||||
get => _selectedGuild;
|
||||
set
|
||||
{
|
||||
Set(ref _selectedGuild, value);
|
||||
AvailableChannels = value != null ? _guildChannelsMap[value] : new Channel[0];
|
||||
ShowExportSetupCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<Channel> AvailableChannels
|
||||
{
|
||||
get => _availableChannels;
|
||||
private set => Set(ref _availableChannels, value);
|
||||
}
|
||||
|
||||
public RelayCommand PullDataCommand { get; }
|
||||
public RelayCommand ShowSettingsCommand { get; }
|
||||
public RelayCommand ShowAboutCommand { get; }
|
||||
public RelayCommand<Channel> ShowExportSetupCommand { get; }
|
||||
|
||||
public MainViewModel(ISettingsService settingsService, IDataService dataService,
|
||||
IMessageGroupService messageGroupService, IExportService exportService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_dataService = dataService;
|
||||
_messageGroupService = messageGroupService;
|
||||
_exportService = exportService;
|
||||
|
||||
_guildChannelsMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
|
||||
|
||||
// Commands
|
||||
PullDataCommand = new RelayCommand(PullData, () => Token.IsNotBlank() && !IsBusy);
|
||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||
ShowAboutCommand = new RelayCommand(ShowAbout);
|
||||
ShowExportSetupCommand = new RelayCommand<Channel>(ShowExportSetup, _ => !IsBusy);
|
||||
|
||||
// Messages
|
||||
MessengerInstance.Register<StartExportMessage>(this, m =>
|
||||
{
|
||||
Export(m.Channel, m.FilePath, m.Format, m.From, m.To);
|
||||
});
|
||||
|
||||
// Defaults
|
||||
_token = _settingsService.LastToken;
|
||||
}
|
||||
|
||||
private async void PullData()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
// Copy token so it doesn't get mutated
|
||||
var token = Token;
|
||||
|
||||
// Save token
|
||||
_settingsService.LastToken = token;
|
||||
|
||||
// Clear existing
|
||||
_guildChannelsMap.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
// Get DM channels
|
||||
{
|
||||
var channels = await _dataService.GetDirectMessageChannelsAsync(token);
|
||||
var guild = Guild.DirectMessages;
|
||||
_guildChannelsMap[guild] = channels.ToArray();
|
||||
}
|
||||
|
||||
// Get guild channels
|
||||
{
|
||||
var guilds = await _dataService.GetUserGuildsAsync(token);
|
||||
foreach (var guild in guilds)
|
||||
{
|
||||
var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id);
|
||||
_guildChannelsMap[guild] = channels.Where(c => c.Type == ChannelType.GuildTextChat).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
const string message = "Unauthorized to perform request. Make sure token is valid.";
|
||||
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||
}
|
||||
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
const string message = "Forbidden to perform request. The account may be locked by 2FA.";
|
||||
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||
}
|
||||
|
||||
AvailableGuilds = _guildChannelsMap.Keys.ToArray();
|
||||
SelectedGuild = AvailableGuilds.FirstOrDefault();
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
private void ShowSettings()
|
||||
{
|
||||
MessengerInstance.Send(new ShowSettingsMessage());
|
||||
}
|
||||
|
||||
private void ShowAbout()
|
||||
{
|
||||
Process.Start("https://github.com/Tyrrrz/DiscordChatExporter");
|
||||
}
|
||||
|
||||
private void ShowExportSetup(Channel channel)
|
||||
{
|
||||
MessengerInstance.Send(new ShowExportSetupMessage(SelectedGuild, channel));
|
||||
}
|
||||
|
||||
private async void Export(Channel channel, string filePath, ExportFormat format, DateTime? from, DateTime? to)
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
// Get last used token
|
||||
var token = _settingsService.LastToken;
|
||||
|
||||
try
|
||||
{
|
||||
// Get messages
|
||||
var messages = await _dataService.GetChannelMessagesAsync(token, channel.Id, from, to);
|
||||
|
||||
// Group them
|
||||
var messageGroups = _messageGroupService.GroupMessages(messages);
|
||||
|
||||
// Create log
|
||||
var log = new ChannelChatLog(SelectedGuild, channel, messageGroups, messages.Count);
|
||||
|
||||
// Export
|
||||
await _exportService.ExportAsync(format, filePath, log);
|
||||
|
||||
// Notify completion
|
||||
MessengerInstance.Send(new ShowExportDoneMessage(filePath));
|
||||
}
|
||||
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
const string message = "Forbidden to view messages in that channel.";
|
||||
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||
}
|
||||
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
DiscordChatExporter.Gui/ViewModels/SettingsViewModel.cs
Normal file
28
DiscordChatExporter.Gui/ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using GalaSoft.MvvmLight;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : ViewModelBase, ISettingsViewModel
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public string DateFormat
|
||||
{
|
||||
get => _settingsService.DateFormat;
|
||||
set => _settingsService.DateFormat = value;
|
||||
}
|
||||
|
||||
public int MessageGroupLimit
|
||||
{
|
||||
get => _settingsService.MessageGroupLimit;
|
||||
set => _settingsService.MessageGroupLimit = value.ClampMin(0);
|
||||
}
|
||||
|
||||
public SettingsViewModel(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
DiscordChatExporter.Gui/Views/ErrorDialog.ammy
Normal file
25
DiscordChatExporter.Gui/Views/ErrorDialog.ammy
Normal file
@@ -0,0 +1,25 @@
|
||||
using MaterialDesignThemes.Wpf
|
||||
|
||||
UserControl "DiscordChatExporter.Gui.Views.ErrorDialog" {
|
||||
DataContext: bind ErrorViewModel from $resource Container
|
||||
Width: 250
|
||||
|
||||
StackPanel {
|
||||
// Message
|
||||
TextBlock {
|
||||
Margin: 16
|
||||
FontSize: 16
|
||||
TextWrapping: WrapWithOverflow
|
||||
Text: bind Message
|
||||
}
|
||||
|
||||
// OK
|
||||
Button {
|
||||
Margin: 8
|
||||
Command: DialogHost.CloseDialogCommand
|
||||
Content: "OK"
|
||||
HorizontalAlignment: Right
|
||||
Style: resource dyn "MaterialDesignFlatButton"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
DiscordChatExporter.Gui/Views/ErrorDialog.ammy.cs
Normal file
10
DiscordChatExporter.Gui/Views/ErrorDialog.ammy.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace DiscordChatExporter.Gui.Views
|
||||
{
|
||||
public partial class ErrorDialog
|
||||
{
|
||||
public ErrorDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
38
DiscordChatExporter.Gui/Views/ExportDoneDialog.ammy
Normal file
38
DiscordChatExporter.Gui/Views/ExportDoneDialog.ammy
Normal file
@@ -0,0 +1,38 @@
|
||||
using MaterialDesignThemes.Wpf
|
||||
|
||||
UserControl "DiscordChatExporter.Gui.Views.ExportDoneDialog" {
|
||||
DataContext: bind ExportDoneViewModel from $resource Container
|
||||
Width: 250
|
||||
|
||||
StackPanel {
|
||||
// Message
|
||||
TextBlock {
|
||||
Margin: 16
|
||||
FontSize: 16
|
||||
TextWrapping: WrapWithOverflow
|
||||
Text: "Finished exporting chat log"
|
||||
}
|
||||
|
||||
// Buttons
|
||||
@StackPanelHorizontal {
|
||||
HorizontalAlignment: Right
|
||||
|
||||
// Open
|
||||
Button "OpenButton" {
|
||||
Margin: 8
|
||||
Click: OpenButton_Click
|
||||
Command: bind OpenCommand
|
||||
Content: "OPEN IT"
|
||||
Style: resource dyn "MaterialDesignFlatButton"
|
||||
}
|
||||
|
||||
// Dismiss
|
||||
Button {
|
||||
Margin: 8
|
||||
Command: DialogHost.CloseDialogCommand
|
||||
Content: "DISMISS"
|
||||
Style: resource dyn "MaterialDesignFlatButton"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
DiscordChatExporter.Gui/Views/ExportDoneDialog.ammy.cs
Normal file
18
DiscordChatExporter.Gui/Views/ExportDoneDialog.ammy.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Windows;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Views
|
||||
{
|
||||
public partial class ExportDoneDialog
|
||||
{
|
||||
public ExportDoneDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void OpenButton_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
DialogHost.CloseDialogCommand.Execute(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
DiscordChatExporter.Gui/Views/ExportSetupDialog.ammy
Normal file
88
DiscordChatExporter.Gui/Views/ExportSetupDialog.ammy
Normal file
@@ -0,0 +1,88 @@
|
||||
using DiscordChatExporter.Core.Models
|
||||
using MaterialDesignThemes.Wpf
|
||||
|
||||
UserControl "DiscordChatExporter.Gui.Views.ExportSetupDialog" {
|
||||
DataContext: bind ExportSetupViewModel from $resource Container
|
||||
Width: 325
|
||||
|
||||
StackPanel {
|
||||
// File path
|
||||
TextBox {
|
||||
Margin: [16, 16, 16, 8]
|
||||
HintAssist.Hint: "Output file"
|
||||
HintAssist.IsFloating: true
|
||||
IsReadOnly: true
|
||||
Text: bind FilePath
|
||||
set [ UpdateSourceTrigger: PropertyChanged ]
|
||||
}
|
||||
|
||||
// Format
|
||||
ComboBox {
|
||||
Margin: [16, 8, 16, 8]
|
||||
HintAssist.Hint: "Export format"
|
||||
HintAssist.IsFloating: true
|
||||
IsReadOnly: true
|
||||
ItemsSource: bind AvailableFormats
|
||||
SelectedItem: bind SelectedFormat
|
||||
|
||||
ItemTemplate: DataTemplate {
|
||||
TextBlock {
|
||||
Text: bind
|
||||
convert (ExportFormat f) => Extensions.GetDisplayName(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Date range
|
||||
Grid {
|
||||
#TwoColumns("*", "*")
|
||||
|
||||
DatePicker {
|
||||
#Cell(0, 0)
|
||||
Margin: [16, 20, 8, 8]
|
||||
HintAssist.Hint: "From (optional)"
|
||||
HintAssist.IsFloating: true
|
||||
SelectedDate: bind From
|
||||
}
|
||||
|
||||
DatePicker {
|
||||
#Cell(0, 1)
|
||||
Margin: [8, 20, 16, 8]
|
||||
HintAssist.Hint: "To (optional)"
|
||||
HintAssist.IsFloating: true
|
||||
SelectedDate: bind To
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
@StackPanelHorizontal {
|
||||
HorizontalAlignment: Right
|
||||
|
||||
// Browse
|
||||
Button "BrowseButton" {
|
||||
Margin: 8
|
||||
Click: BrowseButton_Click
|
||||
Content: "BROWSE"
|
||||
Style: resource dyn "MaterialDesignFlatButton"
|
||||
}
|
||||
|
||||
// Export
|
||||
Button "ExportButton" {
|
||||
Margin: 8
|
||||
Click: ExportButton_Click
|
||||
Command: bind ExportCommand
|
||||
Content: "EXPORT"
|
||||
IsDefault: true
|
||||
Style: resource dyn "MaterialDesignFlatButton"
|
||||
}
|
||||
|
||||
// Cancel
|
||||
Button {
|
||||
Margin: 8
|
||||
Command: DialogHost.CloseDialogCommand
|
||||
Content: "CANCEL"
|
||||
Style: resource dyn "MaterialDesignFlatButton"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
DiscordChatExporter.Gui/Views/ExportSetupDialog.ammy.cs
Normal file
44
DiscordChatExporter.Gui/Views/ExportSetupDialog.ammy.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Windows;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using DiscordChatExporter.Gui.ViewModels;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Views
|
||||
{
|
||||
public partial class ExportSetupDialog
|
||||
{
|
||||
private IExportSetupViewModel ViewModel => (IExportSetupViewModel) DataContext;
|
||||
|
||||
public ExportSetupDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void BrowseButton_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
// Get file extension of the selected format
|
||||
var ext = ViewModel.SelectedFormat.GetFileExtension();
|
||||
|
||||
// Open dialog
|
||||
var sfd = new SaveFileDialog
|
||||
{
|
||||
FileName = ViewModel.FilePath,
|
||||
Filter = $"{ext.ToUpperInvariant()} Files|*.{ext}|All Files|*.*",
|
||||
AddExtension = true,
|
||||
Title = "Select output file"
|
||||
};
|
||||
|
||||
// Assign new file path if dialog was successful
|
||||
if (sfd.ShowDialog() == true)
|
||||
{
|
||||
ViewModel.FilePath = sfd.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExportButton_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
DialogHost.CloseDialogCommand.Execute(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
271
DiscordChatExporter.Gui/Views/MainWindow.ammy
Normal file
271
DiscordChatExporter.Gui/Views/MainWindow.ammy
Normal file
@@ -0,0 +1,271 @@
|
||||
using MaterialDesignThemes.Wpf
|
||||
using MaterialDesignThemes.Wpf.Transitions
|
||||
|
||||
Window "DiscordChatExporter.Gui.Views.MainWindow" {
|
||||
Title: "DiscordChatExporter"
|
||||
Width: 600
|
||||
Height: 550
|
||||
Background: resource dyn "MaterialDesignPaper"
|
||||
DataContext: bind MainViewModel from $resource Container
|
||||
FocusManager.FocusedElement: bind from "TokenTextBox"
|
||||
FontFamily: resource dyn "MaterialDesignFont"
|
||||
Icon: "/DiscordChatExporter;component/favicon.ico"
|
||||
SnapsToDevicePixels: true
|
||||
TextElement.FontSize: 13
|
||||
TextElement.FontWeight: Regular
|
||||
TextElement.Foreground: resource dyn "SecondaryTextBrush"
|
||||
TextOptions.TextFormattingMode: Ideal
|
||||
TextOptions.TextRenderingMode: Auto
|
||||
UseLayoutRounding: true
|
||||
WindowStartupLocation: CenterScreen
|
||||
|
||||
DialogHost {
|
||||
DockPanel {
|
||||
IsEnabled: bind IsBusy
|
||||
convert (bool b) => b ? false : true
|
||||
|
||||
// Toolbar
|
||||
Border {
|
||||
DockPanel.Dock: Top
|
||||
Background: resource dyn "PrimaryHueMidBrush"
|
||||
TextElement.Foreground: resource dyn "SecondaryInverseTextBrush"
|
||||
StackPanel {
|
||||
Grid {
|
||||
#TwoColumns("*", "Auto")
|
||||
|
||||
Card {
|
||||
#Cell(0, 0)
|
||||
Margin: [6, 6, 0, 6]
|
||||
|
||||
Grid {
|
||||
#TwoColumns("*", "Auto")
|
||||
|
||||
// Token
|
||||
TextBox "TokenTextBox" {
|
||||
#Cell(0, 0)
|
||||
Margin: 6
|
||||
BorderThickness: 0
|
||||
HintAssist.Hint: "Token"
|
||||
FontSize: 16
|
||||
Text: bind Token
|
||||
set [ UpdateSourceTrigger: PropertyChanged ]
|
||||
TextFieldAssist.DecorationVisibility: Hidden
|
||||
TextFieldAssist.TextBoxViewMargin: [0, 0, 2, 0]
|
||||
}
|
||||
|
||||
// Submit
|
||||
Button {
|
||||
#Cell(0, 1)
|
||||
Margin: [0, 6, 6, 6]
|
||||
Padding: 4
|
||||
Command: bind PullDataCommand
|
||||
IsDefault: true
|
||||
Style: resource dyn "MaterialDesignFlatButton"
|
||||
|
||||
PackIcon {
|
||||
Width: 24
|
||||
Height: 24
|
||||
Kind: PackIconKind.ArrowRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Popup menu
|
||||
PopupBox {
|
||||
#Cell(0, 1)
|
||||
Foreground: resource dyn "PrimaryHueMidForegroundBrush"
|
||||
PlacementMode: LeftAndAlignTopEdges
|
||||
|
||||
StackPanel {
|
||||
Button {
|
||||
Command: bind ShowSettingsCommand
|
||||
Content: "Settings"
|
||||
}
|
||||
Button {
|
||||
Command: bind ShowAboutCommand
|
||||
Content: "About"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Progress
|
||||
ProgressBar {
|
||||
Background: Transparent
|
||||
IsIndeterminate: true
|
||||
Visibility: bind IsBusy
|
||||
convert (bool b) => b ? Visibility.Visible : Visibility.Hidden
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Grid {
|
||||
DockPanel {
|
||||
Background: resource dyn "MaterialDesignCardBackground"
|
||||
Visibility: bind IsDataAvailable
|
||||
convert (bool b) => b ? Visibility.Visible : Visibility.Hidden
|
||||
|
||||
// Guilds
|
||||
Border {
|
||||
DockPanel.Dock: Left
|
||||
BorderBrush: resource dyn "DividerBrush"
|
||||
BorderThickness: "0 0 1 0"
|
||||
|
||||
ListBox {
|
||||
ItemsSource: bind AvailableGuilds
|
||||
ScrollViewer.VerticalScrollBarVisibility: Hidden
|
||||
SelectedItem: bind SelectedGuild
|
||||
VirtualizingStackPanel.IsVirtualizing: false
|
||||
|
||||
ItemTemplate: DataTemplate {
|
||||
TransitioningContent {
|
||||
OpeningEffect: TransitionEffect {
|
||||
Duration: "0:0:0.3"
|
||||
Kind: SlideInFromLeft
|
||||
}
|
||||
|
||||
Border {
|
||||
Margin: -8
|
||||
Background: Transparent
|
||||
Cursor: CursorType.Hand
|
||||
|
||||
Image {
|
||||
Margin: [12, 4, 12, 4]
|
||||
Width: 48
|
||||
Height: 48
|
||||
Source: bind IconUrl
|
||||
ToolTip: bind Name
|
||||
OpacityMask: RadialGradientBrush {
|
||||
GradientStops: [
|
||||
GradientStop { Color: "#FF000000", Offset: 0 }
|
||||
GradientStop { Color: "#FF000000", Offset: 0.96 }
|
||||
GradientStop { Color: "#00000000", Offset: 1 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Channels
|
||||
Border {
|
||||
ListBox {
|
||||
ItemsSource: bind AvailableChannels
|
||||
HorizontalContentAlignment: Stretch
|
||||
VirtualizingStackPanel.IsVirtualizing: false
|
||||
|
||||
ItemTemplate: DataTemplate {
|
||||
TransitioningContent {
|
||||
OpeningEffect: TransitionEffect {
|
||||
Duration: "0:0:0.3"
|
||||
Kind: SlideInFromLeft
|
||||
}
|
||||
|
||||
@StackPanelHorizontal {
|
||||
Margin: -8
|
||||
Background: Transparent
|
||||
Cursor: CursorType.Hand
|
||||
InputBindings: [
|
||||
MouseBinding {
|
||||
Command: bind DataContext.ShowExportSetupCommand from $ancestor<ItemsControl>
|
||||
CommandParameter: bind
|
||||
MouseAction: LeftClick
|
||||
}
|
||||
]
|
||||
|
||||
PackIcon {
|
||||
Margin: [16, 7, 0, 6]
|
||||
Kind: PackIconKind.Pound
|
||||
VerticalAlignment: Center
|
||||
}
|
||||
TextBlock {
|
||||
Margin: [3, 8, 8, 8]
|
||||
FontSize: 14
|
||||
Text: bind Name
|
||||
VerticalAlignment: Center
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content placeholder
|
||||
StackPanel {
|
||||
Margin: [32, 32, 8, 8]
|
||||
Visibility: bind IsDataAvailable
|
||||
convert (bool b) => b ? Visibility.Hidden : Visibility.Visible
|
||||
|
||||
TextBlock {
|
||||
FontSize: 18
|
||||
Text: "DiscordChatExporter needs your authorization token to work."
|
||||
}
|
||||
|
||||
TextBlock {
|
||||
Margin: [0, 8, 0, 0]
|
||||
FontSize: 16
|
||||
Text: "To obtain it, follow these steps:"
|
||||
}
|
||||
|
||||
TextBlock {
|
||||
Margin: [8, 0, 0, 0]
|
||||
FontSize: 14
|
||||
|
||||
Run {
|
||||
Text: "1. Open the Discord app"
|
||||
}
|
||||
LineBreak { }
|
||||
Run {
|
||||
Text: "2. Log in if you haven't"
|
||||
}
|
||||
LineBreak { }
|
||||
Run {
|
||||
Text: "3. Press"
|
||||
}
|
||||
Run {
|
||||
Text: "Ctrl+Shift+I"
|
||||
Foreground: resource dyn "PrimaryTextBrush"
|
||||
}
|
||||
LineBreak { }
|
||||
Run {
|
||||
Text: "4. Navigate to"
|
||||
}
|
||||
Run {
|
||||
Text: "Application"
|
||||
Foreground: resource dyn "PrimaryTextBrush"
|
||||
}
|
||||
Run { Text: "tab" }
|
||||
LineBreak { }
|
||||
Run {
|
||||
Text: "5. Expand"
|
||||
}
|
||||
Run {
|
||||
Text: "Storage > Local Storage > https://discordapp.com"
|
||||
Foreground: resource dyn "PrimaryTextBrush"
|
||||
}
|
||||
LineBreak { }
|
||||
Run {
|
||||
Text: "6. Find"
|
||||
}
|
||||
Run {
|
||||
Text: ""token""
|
||||
Foreground: resource dyn "PrimaryTextBrush"
|
||||
}
|
||||
Run {
|
||||
Text: "under key and copy the value"
|
||||
}
|
||||
LineBreak { }
|
||||
Run {
|
||||
Text: "7. Paste the value in the textbox above"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
DiscordChatExporter.Gui/Views/MainWindow.ammy.cs
Normal file
27
DiscordChatExporter.Gui/Views/MainWindow.ammy.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Reflection;
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Title += $" v{Assembly.GetExecutingAssembly().GetName().Version}";
|
||||
|
||||
// Dialogs
|
||||
Messenger.Default.Register<ShowErrorMessage>(this,
|
||||
m => DialogHost.Show(new ErrorDialog()).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());
|
||||
}
|
||||
}
|
||||
}
|
||||
33
DiscordChatExporter.Gui/Views/SettingsDialog.ammy
Normal file
33
DiscordChatExporter.Gui/Views/SettingsDialog.ammy
Normal file
@@ -0,0 +1,33 @@
|
||||
using MaterialDesignThemes.Wpf
|
||||
|
||||
UserControl "DiscordChatExporter.Gui.Views.SettingsDialog" {
|
||||
DataContext: bind SettingsViewModel from $resource Container
|
||||
Width: 250
|
||||
|
||||
StackPanel {
|
||||
// Date format
|
||||
TextBox {
|
||||
Margin: [16, 16, 16, 8]
|
||||
HintAssist.Hint: "Date format"
|
||||
HintAssist.IsFloating: true
|
||||
Text: bind DateFormat
|
||||
}
|
||||
|
||||
// Group limit
|
||||
TextBox {
|
||||
Margin: [16, 8, 16, 8]
|
||||
HintAssist.Hint: "Message group limit"
|
||||
HintAssist.IsFloating: true
|
||||
Text: bind MessageGroupLimit
|
||||
}
|
||||
|
||||
// Save
|
||||
Button {
|
||||
Margin: 8
|
||||
Command: DialogHost.CloseDialogCommand
|
||||
Content: "SAVE"
|
||||
HorizontalAlignment: Right
|
||||
Style: resource dyn "MaterialDesignFlatButton"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
DiscordChatExporter.Gui/Views/SettingsDialog.ammy.cs
Normal file
10
DiscordChatExporter.Gui/Views/SettingsDialog.ammy.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace DiscordChatExporter.Gui.Views
|
||||
{
|
||||
public partial class SettingsDialog
|
||||
{
|
||||
public SettingsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
238
DiscordChatExporter.Gui/lib.ammy
Normal file
238
DiscordChatExporter.Gui/lib.ammy
Normal file
@@ -0,0 +1,238 @@
|
||||
mixin TwoColumns (one = "*", two = "*") for Grid {
|
||||
combine ColumnDefinitions: [
|
||||
ColumnDefinition { Width: $one }
|
||||
ColumnDefinition { Width: $two }
|
||||
]
|
||||
}
|
||||
|
||||
mixin ThreeColumns (one = none, two = none, three = none) for Grid {
|
||||
#TwoColumns($one, $two)
|
||||
combine ColumnDefinitions: ColumnDefinition { Width: $three }
|
||||
}
|
||||
|
||||
mixin FourColumns (one = none, two = none, three = none, four = none) for Grid {
|
||||
#ThreeColumns($one, $two, $three)
|
||||
combine ColumnDefinitions: ColumnDefinition { Width: $four }
|
||||
}
|
||||
|
||||
mixin FiveColumns (one = none, two = none, three = none, four = none, five = none) for Grid {
|
||||
#FourColumns($one, $two, $three, $four)
|
||||
combine ColumnDefinitions: ColumnDefinition { Width: $five }
|
||||
}
|
||||
|
||||
mixin TwoRows (one = none, two = none) for Grid
|
||||
{
|
||||
combine RowDefinitions: [
|
||||
RowDefinition { Height: $one }
|
||||
RowDefinition { Height: $two }
|
||||
]
|
||||
}
|
||||
|
||||
mixin ThreeRows (one = none, two = none, three = none) for Grid
|
||||
{
|
||||
#TwoRows($one, $two)
|
||||
combine RowDefinitions: RowDefinition { Height: $three }
|
||||
}
|
||||
|
||||
mixin FourRows (one = none, two = none, three = none, four = none) for Grid
|
||||
{
|
||||
#ThreeRows($one, $two, $three)
|
||||
combine RowDefinitions: RowDefinition { Height: $four }
|
||||
}
|
||||
|
||||
mixin FiveRows (one = none, two = none, three = none, four = none, five = none) for Grid
|
||||
{
|
||||
#FourRows($one, $two, $three, $four)
|
||||
combine RowDefinitions: RowDefinition { Height: $five }
|
||||
}
|
||||
|
||||
mixin Cell (row = none, column = none, rowSpan = none, columnSpan = none) for FrameworkElement {
|
||||
Grid.Row: $row
|
||||
Grid.Column: $column
|
||||
Grid.RowSpan: $rowSpan
|
||||
Grid.ColumnSpan: $columnSpan
|
||||
}
|
||||
|
||||
alias ImageCached(source) {
|
||||
Image {
|
||||
Source: BitmapImage {
|
||||
UriCachePolicy: "Revalidate"
|
||||
UriSource: $source
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mixin Setter(property, value, targetName=none) for Style {
|
||||
Setter { Property: $property, Value: $value, TargetName: $targetName }
|
||||
}
|
||||
|
||||
/*
|
||||
mixin AddSetter(property, value, targetName=none) for Style {
|
||||
combine Setters: #Setter($property, $value, $targetName) {}
|
||||
}*/
|
||||
|
||||
alias DataTrigger(binding, bindingValue) {
|
||||
DataTrigger { Binding: $binding, Value: $bindingValue }
|
||||
}
|
||||
|
||||
alias Trigger(property, value) {
|
||||
Trigger { Property: $property, Value: $value }
|
||||
}
|
||||
|
||||
alias EventTrigger(event, sourceName=none) {
|
||||
EventTrigger { RoutedEvent: $event, SourceName: $sourceName }
|
||||
}
|
||||
|
||||
alias DataTrigger_SetProperty(binding, bindingValue, property, propertyValue) {
|
||||
@DataTrigger ($binding, $bindingValue) {
|
||||
#Setter($property, $propertyValue)
|
||||
}
|
||||
}
|
||||
|
||||
alias Trigger_SetProperty(triggerProperty, triggerValue, property, propertyValue) {
|
||||
@Trigger ($triggerProperty, $triggerValue) {
|
||||
#Setter($property, $propertyValue)
|
||||
}
|
||||
}
|
||||
|
||||
alias EventTrigger_SetProperty(event, property, propertyValue) {
|
||||
@EventTrigger ($event) {
|
||||
#Setter($property, $propertyValue)
|
||||
}
|
||||
}
|
||||
alias VisibleIf_DataTrigger(binding, valueForVisible) {
|
||||
@DataTrigger_SetProperty($binding, $valueForVisible, "Visibility", "Visible") {}
|
||||
}
|
||||
|
||||
alias CollapsedIf_DataTrigger(binding, valueForCollapsed) {
|
||||
@DataTrigger_SetProperty($binding, $valueForCollapsed, "Visibility", "Collapsed") {}
|
||||
}
|
||||
|
||||
alias StackPanelHorizontal() {
|
||||
StackPanel {
|
||||
Orientation: Horizontal
|
||||
}
|
||||
}
|
||||
|
||||
alias GridItemsControl() {
|
||||
ItemsControl {
|
||||
ScrollViewer.HorizontalScrollBarVisibility: Disabled,
|
||||
|
||||
ItemsPanel: ItemsPanelTemplate {
|
||||
WrapPanel {
|
||||
IsItemsHost: true
|
||||
Orientation: Horizontal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Animations //
|
||||
////////////////
|
||||
|
||||
alias DoubleAnimation(property, frm = "0", to = "1", duration = "0:0:1", targetName=none, beginTime=none) {
|
||||
DoubleAnimation {
|
||||
Storyboard.TargetProperty: $property
|
||||
Storyboard.TargetName: $targetName
|
||||
From: $frm
|
||||
To: $to
|
||||
Duration: $duration
|
||||
BeginTime: $beginTime
|
||||
}
|
||||
}
|
||||
|
||||
alias DoubleAnimationStoryboard (property, frm = "0", to = "1", duration = "0:0:1", targetName=none) {
|
||||
BeginStoryboard {
|
||||
Storyboard {
|
||||
@DoubleAnimation($property, $frm, $to, $duration, $targetName) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mixin DoubleAnimation_PropertyTrigger(triggerProperty, triggerValue, animationProperty, frm, to, duration) for Style {
|
||||
combine Triggers: @Trigger ($triggerProperty, $triggerValue) {
|
||||
EnterActions: @DoubleAnimationStoryboard($animationProperty, $frm, $to, $duration) {}
|
||||
}
|
||||
}
|
||||
|
||||
mixin DoubleAnimation_PropertyTrigger_Toggle(triggerProperty, triggerValue, animationProperty, frm, to, duration) for Style {
|
||||
combine Triggers: @Trigger ($triggerProperty, $triggerValue) {
|
||||
EnterActions: @DoubleAnimationStoryboard($animationProperty, $frm, $to, $duration) {}
|
||||
ExitActions: @DoubleAnimationStoryboard($animationProperty, $to, $frm, $duration) {}
|
||||
}
|
||||
}
|
||||
|
||||
mixin DoubleAnimation_EventTrigger(triggerEvent, animationProperty, frm, to, duration) for Style {
|
||||
combine Triggers: EventTrigger {
|
||||
RoutedEvent: $triggerEvent
|
||||
@DoubleAnimationStoryboard($animationProperty, $frm, $to, $duration) {}
|
||||
}
|
||||
}
|
||||
|
||||
mixin DoubleAnimation_DataTrigger(binding, value, animationProperty, frm, to, duration) for Style {
|
||||
combine Triggers: DataTrigger {
|
||||
Binding: $binding
|
||||
Value: $value
|
||||
EnterActions: @DoubleAnimationStoryboard($animationProperty, $frm, $to, $duration) {}
|
||||
}
|
||||
}
|
||||
|
||||
mixin FadeIn_OnProperty(property, value, frm = "0", to = "1", duration = "0:0:1") for Style {
|
||||
#DoubleAnimation_PropertyTrigger($property, $value, "Opacity", $frm, $to, $duration)
|
||||
}
|
||||
|
||||
mixin FadeOut_OnProperty(property, value, frm = "1", to = "0", duration = "0:0:1") for Style {
|
||||
#DoubleAnimation_PropertyTrigger($property, $value, "Opacity", $frm, $to, $duration)
|
||||
}
|
||||
|
||||
mixin FadeIn_OnEvent(event, frm = "0", to = "1", duration = "0:0:1") for Style {
|
||||
#DoubleAnimation_EventTrigger($event, "Opacity", $frm, $to, $duration)
|
||||
}
|
||||
|
||||
mixin FadeOut_OnEvent(event, frm = "1", to = "0", duration = "0:0:1") for Style {
|
||||
#DoubleAnimation_EventTrigger($event, "Opacity", $frm, $to, $duration)
|
||||
}
|
||||
|
||||
mixin FadeIn_OnData(binding, value, from_ = "0", to = "1", duration = "0:0:1") for Style {
|
||||
#DoubleAnimation_DataTrigger($binding, $value, "Opacity", $from_, $to, $duration)
|
||||
}
|
||||
|
||||
mixin FadeOut_OnData(binding, value, from_ = "1", to = "0", duration = "0:0:1") for Style {
|
||||
#DoubleAnimation_DataTrigger($binding, $value, "Opacity", $from_, $to, $duration)
|
||||
}
|
||||
|
||||
mixin Property_OnBinding(binding, bindingValue, property, propertyValue, initialValue) for Style {
|
||||
#Setter("Visibility", $initialValue)
|
||||
combine Triggers: [
|
||||
@DataTrigger_SetProperty($binding, $bindingValue, $property, $propertyValue) {}
|
||||
]
|
||||
}
|
||||
|
||||
mixin Visibility_OnBinding(binding, bindingValue, visibilityValue="Visible", initialValue="Collapsed") for Style {
|
||||
#Property_OnBinding($binding, $bindingValue, "Visibility", $visibilityValue, $initialValue)
|
||||
}
|
||||
|
||||
mixin Fade_OnBinding(binding, bindingValue) for Style {
|
||||
#Setter("Visibility", "Visible")
|
||||
#Setter("Opacity", "0")
|
||||
|
||||
combine Triggers: [
|
||||
@DataTrigger($binding, $bindingValue) {
|
||||
EnterActions: [
|
||||
@DoubleAnimationStoryboard("Opacity", 0, 1, "0:0:0.5") {}
|
||||
]
|
||||
ExitActions: [
|
||||
@DoubleAnimationStoryboard("Opacity", 1, 0, "0:0:0.5") {}
|
||||
]
|
||||
#Setter("Opacity", 1)
|
||||
}
|
||||
@Trigger("Opacity", 0) {
|
||||
#Setter("Visibility", "Hidden")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
mixin MergeDictionary (source) for ResourceDictionary {
|
||||
combine MergedDictionaries: ResourceDictionary { Source: $source }
|
||||
}
|
||||
12
DiscordChatExporter.Gui/packages.config
Normal file
12
DiscordChatExporter.Gui/packages.config
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Ammy" version="1.2.87" targetFramework="net461" />
|
||||
<package id="Ammy.WPF" version="1.2.87" targetFramework="net461" />
|
||||
<package id="CommonServiceLocator" version="1.3" targetFramework="net461" />
|
||||
<package id="Costura.Fody" version="1.6.2" targetFramework="net461" developmentDependency="true" />
|
||||
<package id="Fody" version="2.3.18" targetFramework="net461" developmentDependency="true" />
|
||||
<package id="MaterialDesignColors" version="1.1.3" targetFramework="net461" />
|
||||
<package id="MaterialDesignThemes" version="2.3.1.953" targetFramework="net461" />
|
||||
<package id="MvvmLightLibs" version="5.3.0.0" targetFramework="net461" />
|
||||
<package id="Tyrrrz.Extensions" version="1.5.0" targetFramework="net461" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user