mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-06-04 09:31:05 +03:00
Add channel & user filters to the app
This commit is contained in:
parent
c0123b9f91
commit
96e125b812
@ -8,6 +8,7 @@
|
|||||||
<entry key="Desktop/Dialogs/MessageDialog.axaml" value="Desktop/Desktop.csproj" />
|
<entry key="Desktop/Dialogs/MessageDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||||
<entry key="Desktop/Dialogs/ProgressDialog.axaml" value="Desktop/Desktop.csproj" />
|
<entry key="Desktop/Dialogs/ProgressDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||||
<entry key="Desktop/Main/AboutWindow.axaml" value="Desktop/Desktop.csproj" />
|
<entry key="Desktop/Main/AboutWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/Controls/FilterPanel.axaml" value="Desktop/Desktop.csproj" />
|
||||||
<entry key="Desktop/Main/Controls/StatusBar.axaml" value="Desktop/Desktop.csproj" />
|
<entry key="Desktop/Main/Controls/StatusBar.axaml" value="Desktop/Desktop.csproj" />
|
||||||
<entry key="Desktop/Main/MainContentScreen.axaml" value="Desktop/Desktop.csproj" />
|
<entry key="Desktop/Main/MainContentScreen.axaml" value="Desktop/Desktop.csproj" />
|
||||||
<entry key="Desktop/Main/MainWindow.axaml" value="Desktop/Desktop.csproj" />
|
<entry key="Desktop/Main/MainWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
60
app/Desktop/Main/Controls/FilterPanel.axaml
Normal file
60
app/Desktop/Main/Controls/FilterPanel.axaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:Class="DHT.Desktop.Main.Controls.FilterPanel">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<controls:FilterPanelModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="WrapPanel">
|
||||||
|
<Setter Property="Background" Value="#FFFFFF" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="WrapPanel > StackPanel">
|
||||||
|
<Setter Property="Margin" Value="0 20 40 0" />
|
||||||
|
<Setter Property="Spacing" Value="4" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid > Label">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid > CalendarDatePicker">
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="IsTodayHighlighted" Value="True" />
|
||||||
|
<Setter Property="SelectedDateFormat" Value="Short" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Margin" Value="0 0 0 8" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<WrapPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<CheckBox IsChecked="{Binding FilterByDate}">Filter by Date</CheckBox>
|
||||||
|
<Grid ColumnDefinitions="Auto, 4, 140" RowDefinitions="Auto, 4, Auto" Margin="4 0">
|
||||||
|
<Label Grid.Row="0" Grid.Column="0">From:</Label>
|
||||||
|
<CalendarDatePicker Grid.Row="0" Grid.Column="2" x:Name="StartDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||||
|
<Label Grid.Row="2" Grid.Column="0">To:</Label>
|
||||||
|
<CalendarDatePicker Grid.Row="2" Grid.Column="2" x:Name="EndDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<CheckBox IsChecked="{Binding FilterByChannel}">Filter by Channel</CheckBox>
|
||||||
|
<Button Command="{Binding OpenChannelFilterDialog}" IsEnabled="{Binding FilterByChannel}">Select Channels...</Button>
|
||||||
|
<TextBlock Text="{Binding ChannelFilterLabel}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<CheckBox IsChecked="{Binding FilterByUser}">Filter by User</CheckBox>
|
||||||
|
<Button Command="{Binding OpenUserFilterDialog}" IsEnabled="{Binding FilterByUser}">Select Users...</Button>
|
||||||
|
<TextBlock Text="{Binding UserFilterLabel}" />
|
||||||
|
</StackPanel>
|
||||||
|
</WrapPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
21
app/Desktop/Main/Controls/FilterPanel.axaml.cs
Normal file
21
app/Desktop/Main/Controls/FilterPanel.axaml.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Controls {
|
||||||
|
public class FilterPanel : UserControl {
|
||||||
|
public FilterPanel() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CalendarDatePicker_OnSelectedDateChanged(object? sender, SelectionChangedEventArgs e) {
|
||||||
|
if (DataContext is FilterPanelModel model) {
|
||||||
|
model.StartDate = this.FindControl<CalendarDatePicker>("StartDatePicker").SelectedDate;
|
||||||
|
model.EndDate = this.FindControl<CalendarDatePicker>("EndDatePicker").SelectedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
234
app/Desktop/Main/Controls/FilterPanelModel.cs
Normal file
234
app/Desktop/Main/Controls/FilterPanelModel.cs
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using DHT.Desktop.Dialogs;
|
||||||
|
using DHT.Desktop.Models;
|
||||||
|
using DHT.Server.Data;
|
||||||
|
using DHT.Server.Data.Filters;
|
||||||
|
using DHT.Server.Database;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Controls {
|
||||||
|
public class FilterPanelModel : BaseModel {
|
||||||
|
private static readonly HashSet<string> FilterProperties = new () {
|
||||||
|
nameof(FilterByDate),
|
||||||
|
nameof(StartDate),
|
||||||
|
nameof(EndDate),
|
||||||
|
nameof(FilterByChannel),
|
||||||
|
nameof(IncludedChannels),
|
||||||
|
nameof(FilterByUser),
|
||||||
|
nameof(IncludedUsers)
|
||||||
|
};
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? FilterPropertyChanged;
|
||||||
|
|
||||||
|
private bool filterByDate = false;
|
||||||
|
private DateTime? startDate = null;
|
||||||
|
private DateTime? endDate = null;
|
||||||
|
private bool filterByChannel = false;
|
||||||
|
private HashSet<ulong>? includedChannels = null;
|
||||||
|
private bool filterByUser = false;
|
||||||
|
private HashSet<ulong>? includedUsers = null;
|
||||||
|
|
||||||
|
public bool FilterByDate {
|
||||||
|
get => filterByDate;
|
||||||
|
set => Change(ref filterByDate, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? StartDate {
|
||||||
|
get => startDate;
|
||||||
|
set => Change(ref startDate, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? EndDate {
|
||||||
|
get => endDate;
|
||||||
|
set => Change(ref endDate, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FilterByChannel {
|
||||||
|
get => filterByChannel;
|
||||||
|
set => Change(ref filterByChannel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashSet<ulong> IncludedChannels {
|
||||||
|
get => includedChannels ?? db.GetAllChannels().Select(channel => channel.Id).ToHashSet();
|
||||||
|
set => Change(ref includedChannels, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FilterByUser {
|
||||||
|
get => filterByUser;
|
||||||
|
set => Change(ref filterByUser, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HashSet<ulong> IncludedUsers {
|
||||||
|
get => includedUsers ?? db.GetAllUsers().Select(user => user.Id).ToHashSet();
|
||||||
|
set => Change(ref includedUsers, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string channelFilterLabel = "";
|
||||||
|
|
||||||
|
public string ChannelFilterLabel {
|
||||||
|
get => channelFilterLabel;
|
||||||
|
set => Change(ref channelFilterLabel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string userFilterLabel = "";
|
||||||
|
|
||||||
|
public string UserFilterLabel {
|
||||||
|
get => userFilterLabel;
|
||||||
|
set => Change(ref userFilterLabel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Window window;
|
||||||
|
private readonly IDatabaseFile db;
|
||||||
|
|
||||||
|
[Obsolete("Designer")]
|
||||||
|
public FilterPanelModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||||
|
|
||||||
|
public FilterPanelModel(Window window, IDatabaseFile db) {
|
||||||
|
this.window = window;
|
||||||
|
this.db = db;
|
||||||
|
|
||||||
|
UpdateChannelFilterLabel();
|
||||||
|
UpdateUserFilterLabel();
|
||||||
|
|
||||||
|
PropertyChanged += OnPropertyChanged;
|
||||||
|
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
|
if (e.PropertyName != null && FilterProperties.Contains(e.PropertyName)) {
|
||||||
|
FilterPropertyChanged?.Invoke(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.PropertyName is nameof(FilterByChannel) or nameof(IncludedChannels)) {
|
||||||
|
UpdateChannelFilterLabel();
|
||||||
|
}
|
||||||
|
else if (e.PropertyName is nameof(FilterByUser) or nameof(IncludedUsers)) {
|
||||||
|
UpdateUserFilterLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
|
if (e.PropertyName == nameof(DatabaseStatistics.TotalChannels)) {
|
||||||
|
UpdateChannelFilterLabel();
|
||||||
|
}
|
||||||
|
else if (e.PropertyName == nameof(DatabaseStatistics.TotalUsers)) {
|
||||||
|
UpdateUserFilterLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OpenChannelFilterDialog() {
|
||||||
|
var servers = db.GetAllServers().ToDictionary(server => server.Id);
|
||||||
|
var items = new List<CheckBoxItem<ulong>>();
|
||||||
|
var included = IncludedChannels;
|
||||||
|
|
||||||
|
foreach (var channel in db.GetAllChannels()) {
|
||||||
|
var channelId = channel.Id;
|
||||||
|
var channelName = channel.Name;
|
||||||
|
|
||||||
|
string title;
|
||||||
|
if (servers.TryGetValue(channel.Server, out var server)) {
|
||||||
|
var titleBuilder = new StringBuilder();
|
||||||
|
var serverType = server.Type;
|
||||||
|
|
||||||
|
titleBuilder.Append('[')
|
||||||
|
.Append(ServerTypes.ToString(serverType))
|
||||||
|
.Append("] ");
|
||||||
|
|
||||||
|
if (serverType == ServerType.DirectMessage) {
|
||||||
|
titleBuilder.Append(channelName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
titleBuilder.Append(server.Name)
|
||||||
|
.Append(" - ")
|
||||||
|
.Append(channelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
title = titleBuilder.ToString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
title = channelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.Add(new CheckBoxItem<ulong>(channelId) {
|
||||||
|
Title = title,
|
||||||
|
Checked = included.Contains(channelId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await OpenIdFilterDialog(window, "Included Channels", items);
|
||||||
|
if (result != null) {
|
||||||
|
IncludedChannels = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OpenUserFilterDialog() {
|
||||||
|
var items = new List<CheckBoxItem<ulong>>();
|
||||||
|
var included = IncludedUsers;
|
||||||
|
|
||||||
|
foreach (var user in db.GetAllUsers()) {
|
||||||
|
var name = user.Name;
|
||||||
|
var discriminator = user.Discriminator;
|
||||||
|
|
||||||
|
items.Add(new CheckBoxItem<ulong>(user.Id) {
|
||||||
|
Title = discriminator == null ? name : name + " #" + discriminator,
|
||||||
|
Checked = included.Contains(user.Id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await OpenIdFilterDialog(window, "Included Users", items);
|
||||||
|
if (result != null) {
|
||||||
|
IncludedUsers = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateChannelFilterLabel() {
|
||||||
|
long total = db.Statistics.TotalChannels;
|
||||||
|
long included = FilterByChannel ? IncludedChannels.Count : total;
|
||||||
|
ChannelFilterLabel = "Selected " + included + " / " + total + (total == 1 ? " channel." : " channels.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUserFilterLabel() {
|
||||||
|
long total = db.Statistics.TotalUsers;
|
||||||
|
long included = FilterByUser ? IncludedUsers.Count : total;
|
||||||
|
UserFilterLabel = "Selected " + included + " / " + total + (total == 1 ? " user." : " users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageFilter CreateFilter() {
|
||||||
|
MessageFilter filter = new();
|
||||||
|
|
||||||
|
if (FilterByDate) {
|
||||||
|
filter.StartDate = StartDate;
|
||||||
|
filter.EndDate = EndDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FilterByChannel) {
|
||||||
|
filter.ChannelIds = new HashSet<ulong>(IncludedChannels);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FilterByUser) {
|
||||||
|
filter.UserIds = new HashSet<ulong>(IncludedUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<HashSet<ulong>?> OpenIdFilterDialog(Window window, string title, List<CheckBoxItem<ulong>> items) {
|
||||||
|
items.Sort((item1, item2) => item1.Title.CompareTo(item2.Title));
|
||||||
|
|
||||||
|
var model = new CheckBoxDialogModel<ulong>(items) {
|
||||||
|
Title = title
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = new CheckBoxDialog { DataContext = model };
|
||||||
|
var result = await dialog.ShowDialog<DialogResult.OkCancel>(window);
|
||||||
|
|
||||||
|
return result == DialogResult.OkCancel.Ok ? model.SelectedItems.Select(item => item.Item).ToHashSet() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Pages.ViewerPage">
|
x:Class="DHT.Desktop.Main.Pages.ViewerPage">
|
||||||
|
|
||||||
@ -10,36 +11,13 @@
|
|||||||
<pages:ViewerPageModel />
|
<pages:ViewerPageModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<UserControl.Styles>
|
<StackPanel Orientation="Vertical">
|
||||||
<Style Selector="Grid > Label">
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid > CalendarDatePicker">
|
|
||||||
<Setter Property="CornerRadius" Value="0" />
|
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
||||||
<Setter Property="IsTodayHighlighted" Value="True" />
|
|
||||||
<Setter Property="SelectedDateFormat" Value="Short" />
|
|
||||||
</Style>
|
|
||||||
</UserControl.Styles>
|
|
||||||
|
|
||||||
<StackPanel Orientation="Vertical" Spacing="20">
|
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
|
||||||
<Button Command="{Binding OnClickOpenViewer}" Margin="0 0 5 0">Open Viewer</Button>
|
<Button Command="{Binding OnClickOpenViewer}" Margin="0 0 5 0">Open Viewer</Button>
|
||||||
<Button Command="{Binding OnClickSaveViewer}" Margin="5 0 0 0">Save Viewer</Button>
|
<Button Command="{Binding OnClickSaveViewer}" Margin="5 0 0 0">Save Viewer</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TextBlock Text="{Binding ExportedMessageText}" />
|
<TextBlock Text="{Binding ExportedMessageText}" Margin="0 20 0 0" />
|
||||||
<StackPanel>
|
<controls:FilterPanel DataContext="{Binding FilterModel}" />
|
||||||
<CheckBox IsChecked="{Binding FilterByDate}">Filter by Date</CheckBox>
|
|
||||||
<Grid ColumnDefinitions="Auto, 4, 140" RowDefinitions="Auto, 4, Auto" Margin="4">
|
|
||||||
<Label Grid.Row="0" Grid.Column="0">From:</Label>
|
|
||||||
<CalendarDatePicker Grid.Row="0" Grid.Column="2" x:Name="StartDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
|
||||||
<Label Grid.Row="2" Grid.Column="0">To:</Label>
|
|
||||||
<CalendarDatePicker Grid.Row="2" Grid.Column="2" x:Name="EndDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@ -10,12 +10,5 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CalendarDatePicker_OnSelectedDateChanged(object? sender, SelectionChangedEventArgs e) {
|
|
||||||
if (DataContext is ViewerPageModel model) {
|
|
||||||
model.StartDate = this.FindControl<CalendarDatePicker>("StartDatePicker").SelectedDate;
|
|
||||||
model.EndDate = this.FindControl<CalendarDatePicker>("EndDatePicker").SelectedDate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ using System.IO;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using DHT.Desktop.Main.Controls;
|
||||||
using DHT.Desktop.Models;
|
using DHT.Desktop.Models;
|
||||||
using DHT.Desktop.Resources;
|
using DHT.Desktop.Resources;
|
||||||
using DHT.Server.Data.Filters;
|
|
||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Server.Database.Export;
|
using DHT.Server.Database.Export;
|
||||||
|
|
||||||
@ -16,26 +16,7 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
public class ViewerPageModel : BaseModel {
|
public class ViewerPageModel : BaseModel {
|
||||||
public string ExportedMessageText { get; private set; } = "";
|
public string ExportedMessageText { get; private set; } = "";
|
||||||
|
|
||||||
private bool filterByDate = false;
|
private FilterPanelModel FilterModel { get; }
|
||||||
|
|
||||||
public bool FilterByDate {
|
|
||||||
get => filterByDate;
|
|
||||||
set => Change(ref filterByDate, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DateTime? startDate = null;
|
|
||||||
|
|
||||||
public DateTime? StartDate {
|
|
||||||
get => startDate;
|
|
||||||
set => Change(ref startDate, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DateTime? endDate = null;
|
|
||||||
|
|
||||||
public DateTime? EndDate {
|
|
||||||
get => endDate;
|
|
||||||
set => Change(ref endDate, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Window window;
|
private readonly Window window;
|
||||||
private readonly IDatabaseFile db;
|
private readonly IDatabaseFile db;
|
||||||
@ -47,15 +28,14 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
this.window = window;
|
this.window = window;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
|
||||||
this.PropertyChanged += OnPropertyChanged;
|
this.FilterModel = new FilterPanelModel(window, db);
|
||||||
|
this.FilterModel.FilterPropertyChanged += OnFilterPropertyChanged;
|
||||||
this.db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
this.db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||||
UpdateStatistics();
|
UpdateStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
private void OnFilterPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
if (e.PropertyName is nameof(FilterByDate) or nameof(StartDate) or nameof(EndDate)) {
|
UpdateStatistics();
|
||||||
UpdateStatistics();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
@ -64,24 +44,13 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageFilter CreateFilter() {
|
|
||||||
MessageFilter filter = new();
|
|
||||||
|
|
||||||
if (FilterByDate) {
|
|
||||||
filter.StartDate = StartDate;
|
|
||||||
filter.EndDate = EndDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateStatistics() {
|
private void UpdateStatistics() {
|
||||||
ExportedMessageText = "Will export " + db.CountMessages(CreateFilter()) + " out of " + db.Statistics.TotalMessages + " message(s).";
|
ExportedMessageText = "Will export " + db.CountMessages(FilterModel.CreateFilter()) + " out of " + db.Statistics.TotalMessages + " message(s).";
|
||||||
OnPropertyChanged(nameof(ExportedMessageText));
|
OnPropertyChanged(nameof(ExportedMessageText));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GenerateViewerContents() {
|
private async Task<string> GenerateViewerContents() {
|
||||||
string json = ViewerJsonExport.Generate(db, CreateFilter());
|
string json = ViewerJsonExport.Generate(db, FilterModel.CreateFilter());
|
||||||
|
|
||||||
string index = await ResourceLoader.ReadTextAsync("Viewer/index.html");
|
string index = await ResourceLoader.ReadTextAsync("Viewer/index.html");
|
||||||
string viewer = index.Replace("/*[JS]*/", await ResourceLoader.ReadJoinedAsync("Viewer/scripts/", '\n'))
|
string viewer = index.Replace("/*[JS]*/", await ResourceLoader.ReadJoinedAsync("Viewer/scripts/", '\n'))
|
||||||
|
@ -6,6 +6,8 @@ namespace DHT.Server.Data.Filters {
|
|||||||
public DateTime? StartDate { get; set; }
|
public DateTime? StartDate { get; set; }
|
||||||
public DateTime? EndDate { get; set; }
|
public DateTime? EndDate { get; set; }
|
||||||
|
|
||||||
public HashSet<ulong> MessageIds { get; } = new();
|
public HashSet<ulong>? ChannelIds { get; set; } = null;
|
||||||
|
public HashSet<ulong>? UserIds { get; set; } = null;
|
||||||
|
public HashSet<ulong>? MessageIds { get; set; } = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ namespace DHT.Server.Database {
|
|||||||
public class DatabaseStatistics : INotifyPropertyChanged {
|
public class DatabaseStatistics : INotifyPropertyChanged {
|
||||||
private long totalServers;
|
private long totalServers;
|
||||||
private long totalChannels;
|
private long totalChannels;
|
||||||
|
private long totalUsers;
|
||||||
private long totalMessages;
|
private long totalMessages;
|
||||||
|
|
||||||
public long TotalServers {
|
public long TotalServers {
|
||||||
@ -17,6 +18,11 @@ namespace DHT.Server.Database {
|
|||||||
internal set => Change(out totalChannels, value);
|
internal set => Change(out totalChannels, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long TotalUsers {
|
||||||
|
get => totalUsers;
|
||||||
|
internal set => Change(out totalUsers, value);
|
||||||
|
}
|
||||||
|
|
||||||
public long TotalMessages {
|
public long TotalMessages {
|
||||||
get => totalMessages;
|
get => totalMessages;
|
||||||
internal set => Change(out totalMessages, value);
|
internal set => Change(out totalMessages, value);
|
||||||
@ -33,6 +39,7 @@ namespace DHT.Server.Database {
|
|||||||
return new DatabaseStatistics {
|
return new DatabaseStatistics {
|
||||||
totalServers = totalServers,
|
totalServers = totalServers,
|
||||||
totalChannels = totalChannels,
|
totalChannels = totalChannels,
|
||||||
|
totalUsers = TotalUsers,
|
||||||
totalMessages = totalMessages
|
totalMessages = totalMessages
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -8,26 +8,48 @@ using DHT.Server.Data.Filters;
|
|||||||
namespace DHT.Server.Database.Export {
|
namespace DHT.Server.Database.Export {
|
||||||
public static class ViewerJsonExport {
|
public static class ViewerJsonExport {
|
||||||
public static string Generate(IDatabaseFile db, MessageFilter? filter = null) {
|
public static string Generate(IDatabaseFile db, MessageFilter? filter = null) {
|
||||||
JsonSerializerOptions opts = new();
|
var includedUserIds = new HashSet<ulong>();
|
||||||
|
var includedChannelIds = new HashSet<ulong>();
|
||||||
|
var includedServerIds = new HashSet<ulong>();
|
||||||
|
|
||||||
|
var includedMessages = db.GetMessages(filter);
|
||||||
|
var includedChannels = new List<Channel>();
|
||||||
|
|
||||||
|
foreach (var message in includedMessages) {
|
||||||
|
includedUserIds.Add(message.Sender);
|
||||||
|
includedChannelIds.Add(message.Channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var channel in db.GetAllChannels()) {
|
||||||
|
if (includedChannelIds.Contains(channel.Id)) {
|
||||||
|
includedChannels.Add(channel);
|
||||||
|
includedServerIds.Add(channel.Server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts = new JsonSerializerOptions();
|
||||||
opts.Converters.Add(new ViewerJsonSnowflakeSerializer());
|
opts.Converters.Add(new ViewerJsonSnowflakeSerializer());
|
||||||
|
|
||||||
var users = GenerateUserList(db, out var userindex, out var userIndices);
|
var users = GenerateUserList(db, includedUserIds, out var userindex, out var userIndices);
|
||||||
var servers = GenerateServerList(db, out var serverindex);
|
var servers = GenerateServerList(db, includedServerIds, out var serverindex);
|
||||||
var channels = GenerateChannelList(db, serverindex);
|
var channels = GenerateChannelList(includedChannels, serverindex);
|
||||||
|
|
||||||
return JsonSerializer.Serialize(new {
|
return JsonSerializer.Serialize(new {
|
||||||
meta = new { users, userindex, servers, channels },
|
meta = new { users, userindex, servers, channels },
|
||||||
data = GenerateMessageList(db, filter, userIndices)
|
data = GenerateMessageList(includedMessages, userIndices)
|
||||||
}, opts);
|
}, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static dynamic GenerateUserList(IDatabaseFile db, out List<string> userindex, out Dictionary<ulong, int> userIndices) {
|
private static dynamic GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<string> userindex, out Dictionary<ulong, int> userIndices) {
|
||||||
var users = new Dictionary<string, dynamic>();
|
var users = new Dictionary<string, dynamic>();
|
||||||
userindex = new List<string>();
|
userindex = new List<string>();
|
||||||
userIndices = new Dictionary<ulong, int>();
|
userIndices = new Dictionary<ulong, int>();
|
||||||
|
|
||||||
foreach (var user in db.GetAllUsers()) {
|
foreach (var user in db.GetAllUsers()) {
|
||||||
var id = user.Id.ToString();
|
var id = user.Id;
|
||||||
|
if (!userIds.Contains(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
dynamic obj = new ExpandoObject();
|
dynamic obj = new ExpandoObject();
|
||||||
obj.name = user.Name;
|
obj.name = user.Name;
|
||||||
@ -40,20 +62,26 @@ namespace DHT.Server.Database.Export {
|
|||||||
obj.tag = user.Discriminator;
|
obj.tag = user.Discriminator;
|
||||||
}
|
}
|
||||||
|
|
||||||
userIndices[user.Id] = users.Count;
|
var idStr = id.ToString();
|
||||||
userindex.Add(id);
|
userIndices[id] = users.Count;
|
||||||
users[id] = obj;
|
userindex.Add(idStr);
|
||||||
|
users[idStr] = obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static dynamic GenerateServerList(IDatabaseFile db, out Dictionary<ulong, int> serverIndices) {
|
private static dynamic GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, int> serverIndices) {
|
||||||
var servers = new List<dynamic>();
|
var servers = new List<dynamic>();
|
||||||
serverIndices = new Dictionary<ulong, int>();
|
serverIndices = new Dictionary<ulong, int>();
|
||||||
|
|
||||||
foreach (var server in db.GetAllServers()) {
|
foreach (var server in db.GetAllServers()) {
|
||||||
serverIndices[server.Id] = servers.Count;
|
var id = server.Id;
|
||||||
|
if (!serverIds.Contains(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverIndices[id] = servers.Count;
|
||||||
servers.Add(new {
|
servers.Add(new {
|
||||||
name = server.Name,
|
name = server.Name,
|
||||||
type = ServerTypes.ToJsonViewerString(server.Type)
|
type = ServerTypes.ToJsonViewerString(server.Type)
|
||||||
@ -63,10 +91,10 @@ namespace DHT.Server.Database.Export {
|
|||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static dynamic GenerateChannelList(IDatabaseFile db, Dictionary<ulong, int> serverIndices) {
|
private static dynamic GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, int> serverIndices) {
|
||||||
var channels = new Dictionary<string, dynamic>();
|
var channels = new Dictionary<string, dynamic>();
|
||||||
|
|
||||||
foreach (var channel in db.GetAllChannels()) {
|
foreach (var channel in includedChannels) {
|
||||||
dynamic obj = new ExpandoObject();
|
dynamic obj = new ExpandoObject();
|
||||||
obj.server = serverIndices[channel.Server];
|
obj.server = serverIndices[channel.Server];
|
||||||
obj.name = channel.Name;
|
obj.name = channel.Name;
|
||||||
@ -93,10 +121,10 @@ namespace DHT.Server.Database.Export {
|
|||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static dynamic GenerateMessageList(IDatabaseFile db, MessageFilter? filter, Dictionary<ulong, int> userIndices) {
|
private static dynamic GenerateMessageList(List<Message> includedMessages, Dictionary<ulong, int> userIndices) {
|
||||||
var data = new Dictionary<string, Dictionary<string, dynamic>>();
|
var data = new Dictionary<string, Dictionary<string, dynamic>>();
|
||||||
|
|
||||||
foreach (var grouping in db.GetMessages(filter).GroupBy(message => message.Channel)) {
|
foreach (var grouping in includedMessages.GroupBy(message => message.Channel)) {
|
||||||
var channel = grouping.Key.ToString();
|
var channel = grouping.Key.ToString();
|
||||||
var channelData = new Dictionary<string, dynamic>();
|
var channelData = new Dictionary<string, dynamic>();
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
this.Statistics = new DatabaseStatistics();
|
this.Statistics = new DatabaseStatistics();
|
||||||
UpdateServerStatistics();
|
UpdateServerStatistics();
|
||||||
UpdateChannelStatistics();
|
UpdateChannelStatistics();
|
||||||
|
UpdateUserStatistics();
|
||||||
UpdateMessageStatistics();
|
UpdateMessageStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +130,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx.Commit();
|
tx.Commit();
|
||||||
|
UpdateUserStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<User> GetAllUsers() {
|
public List<User> GetAllUsers() {
|
||||||
@ -369,6 +371,11 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
Statistics.TotalChannels = cmd.ExecuteScalar() as long? ?? 0;
|
Statistics.TotalChannels = cmd.ExecuteScalar() as long? ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateUserStatistics() {
|
||||||
|
using var cmd = conn.Command("SELECT COUNT(*) FROM users");
|
||||||
|
Statistics.TotalUsers = cmd.ExecuteScalar() as long? ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateMessageStatistics() {
|
private void UpdateMessageStatistics() {
|
||||||
using var cmd = conn.Command("SELECT COUNT(*) FROM messages");
|
using var cmd = conn.Command("SELECT COUNT(*) FROM messages");
|
||||||
Statistics.TotalMessages = cmd.ExecuteScalar() as long? ?? 0L;
|
Statistics.TotalMessages = cmd.ExecuteScalar() as long? ?? 0L;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using DHT.Server.Data.Filters;
|
using DHT.Server.Data.Filters;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Sqlite {
|
namespace DHT.Server.Database.Sqlite {
|
||||||
@ -20,8 +19,16 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
conditions.Add("timestamp <= " + new DateTimeOffset(filter.EndDate.Value).ToUnixTimeMilliseconds());
|
conditions.Add("timestamp <= " + new DateTimeOffset(filter.EndDate.Value).ToUnixTimeMilliseconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.MessageIds.Count > 0) {
|
if (filter.ChannelIds != null) {
|
||||||
conditions.Add("(" + string.Join(" OR ", filter.MessageIds.Select(id => "message_id = " + id)) + ")");
|
conditions.Add("channel_id IN (" + string.Join(",", filter.ChannelIds) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.UserIds != null) {
|
||||||
|
conditions.Add("sender_id IN (" + string.Join(",", filter.UserIds) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.MessageIds != null) {
|
||||||
|
conditions.Add("message_id IN (" + string.Join(",", filter.MessageIds) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
return conditions.Count == 0 ? "" : " WHERE " + string.Join(" AND ", conditions);
|
return conditions.Count == 0 ? "" : " WHERE " + string.Join(" AND ", conditions);
|
||||||
|
@ -22,17 +22,19 @@ namespace DHT.Server.Endpoints {
|
|||||||
throw new HttpException(HttpStatusCode.BadRequest, "Expected root element to be an array.");
|
throw new HttpException(HttpStatusCode.BadRequest, "Expected root element to be an array.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var addedMessageIdFilter = new MessageFilter();
|
var addedMessageIds = new HashSet<ulong>();
|
||||||
var messages = new Message[root.GetArrayLength()];
|
var messages = new Message[root.GetArrayLength()];
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (JsonElement ele in root.EnumerateArray()) {
|
foreach (JsonElement ele in root.EnumerateArray()) {
|
||||||
var message = ReadMessage(ele, "message");
|
var message = ReadMessage(ele, "message");
|
||||||
messages[i++] = message;
|
messages[i++] = message;
|
||||||
addedMessageIdFilter.MessageIds.Add(message.Id);
|
addedMessageIds.Add(message.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool anyNewMessages = Db.CountMessages(addedMessageIdFilter) < messages.Length;
|
var addedMessageFilter = new MessageFilter { MessageIds = addedMessageIds };
|
||||||
|
bool anyNewMessages = Db.CountMessages(addedMessageFilter) < messages.Length;
|
||||||
|
|
||||||
Db.AddMessages(messages);
|
Db.AddMessages(messages);
|
||||||
|
|
||||||
return (HttpStatusCode.OK, anyNewMessages ? 1 : 0);
|
return (HttpStatusCode.OK, anyNewMessages ? 1 : 0);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user