Make use of C# 14 features

This commit is contained in:
Tyrrrz
2025-11-16 20:29:39 +02:00
parent 380dd6d511
commit fbbac2afaa
25 changed files with 337 additions and 287 deletions

View File

@@ -4,13 +4,16 @@ namespace DiscordChatExporter.Cli.Tests.Utils.Extensions;
internal static class StringExtensions internal static class StringExtensions
{ {
public static string ReplaceWhiteSpace(this string str, string replacement = " ") extension(string str)
{ {
var buffer = new StringBuilder(str.Length); public string ReplaceWhiteSpace(string replacement = " ")
{
var buffer = new StringBuilder(str.Length);
foreach (var ch in str) foreach (var ch in str)
buffer.Append(char.IsWhiteSpace(ch) ? replacement : ch); buffer.Append(char.IsWhiteSpace(ch) ? replacement : ch);
return buffer.ToString(); return buffer.ToString();
}
} }
} }

View File

@@ -7,32 +7,35 @@ namespace DiscordChatExporter.Cli.Utils.Extensions;
internal static class ConsoleExtensions internal static class ConsoleExtensions
{ {
public static IAnsiConsole CreateAnsiConsole(this IConsole console) => extension(IConsole console)
AnsiConsole.Create( {
new AnsiConsoleSettings public IAnsiConsole CreateAnsiConsole() =>
{ AnsiConsole.Create(
Ansi = AnsiSupport.Detect, new AnsiConsoleSettings
ColorSystem = ColorSystemSupport.Detect, {
Out = new AnsiConsoleOutput(console.Output), Ansi = AnsiSupport.Detect,
} ColorSystem = ColorSystemSupport.Detect,
); Out = new AnsiConsoleOutput(console.Output),
}
public static Status CreateStatusTicker(this IConsole console) =>
console.CreateAnsiConsole().Status().AutoRefresh(true);
public static Progress CreateProgressTicker(this IConsole console) =>
console
.CreateAnsiConsole()
.Progress()
.AutoClear(false)
.AutoRefresh(true)
.HideCompleted(false)
.Columns(
new TaskDescriptionColumn { Alignment = Justify.Left },
new ProgressBarColumn(),
new PercentageColumn()
); );
public Status CreateStatusTicker() =>
console.CreateAnsiConsole().Status().AutoRefresh(true);
public Progress CreateProgressTicker() =>
console
.CreateAnsiConsole()
.Progress()
.AutoClear(false)
.AutoRefresh(true)
.HideCompleted(false)
.Columns(
new TaskDescriptionColumn { Alignment = Justify.Left },
new ProgressBarColumn(),
new PercentageColumn()
);
}
public static async ValueTask StartTaskAsync( public static async ValueTask StartTaskAsync(
this ProgressContext context, this ProgressContext context,
string description, string description,

View File

@@ -13,24 +13,26 @@ public enum RateLimitPreference
public static class RateLimitPreferenceExtensions public static class RateLimitPreferenceExtensions
{ {
internal static bool IsRespectedFor( extension(RateLimitPreference rateLimitPreference)
this RateLimitPreference rateLimitPreference, {
TokenKind tokenKind internal bool IsRespectedFor(TokenKind tokenKind) =>
) => tokenKind switch
tokenKind switch {
{ TokenKind.User => (rateLimitPreference & RateLimitPreference.RespectForUserTokens)
TokenKind.User => (rateLimitPreference & RateLimitPreference.RespectForUserTokens) != 0, != 0,
TokenKind.Bot => (rateLimitPreference & RateLimitPreference.RespectForBotTokens) != 0, TokenKind.Bot => (rateLimitPreference & RateLimitPreference.RespectForBotTokens)
_ => throw new ArgumentOutOfRangeException(nameof(tokenKind)), != 0,
}; _ => throw new ArgumentOutOfRangeException(nameof(tokenKind)),
};
public static string GetDisplayName(this RateLimitPreference rateLimitPreference) => public string GetDisplayName() =>
rateLimitPreference switch rateLimitPreference switch
{ {
RateLimitPreference.IgnoreAll => "Always ignore", RateLimitPreference.IgnoreAll => "Always ignore",
RateLimitPreference.RespectForUserTokens => "Respect for user tokens", RateLimitPreference.RespectForUserTokens => "Respect for user tokens",
RateLimitPreference.RespectForBotTokens => "Respect for bot tokens", RateLimitPreference.RespectForBotTokens => "Respect for bot tokens",
RateLimitPreference.RespectAll => "Always respect", RateLimitPreference.RespectAll => "Always respect",
_ => throw new ArgumentOutOfRangeException(nameof(rateLimitPreference)), _ => throw new ArgumentOutOfRangeException(nameof(rateLimitPreference)),
}; };
}
} }

View File

@@ -103,7 +103,7 @@ internal partial class ExportAssetDownloader
fileExtension = ""; fileExtension = "";
} }
return PathEx.EscapeFileName( return Path.EscapeFileName(
fileNameWithoutExtension.Truncate(42) + '-' + urlHash + fileExtension fileNameWithoutExtension.Truncate(42) + '-' + urlHash + fileExtension
); );
} }

View File

@@ -13,25 +13,28 @@ public enum ExportFormat
public static class ExportFormatExtensions public static class ExportFormatExtensions
{ {
public static string GetFileExtension(this ExportFormat format) => extension(ExportFormat format)
format switch {
{ public string GetFileExtension() =>
ExportFormat.PlainText => "txt", format switch
ExportFormat.HtmlDark => "html", {
ExportFormat.HtmlLight => "html", ExportFormat.PlainText => "txt",
ExportFormat.Csv => "csv", ExportFormat.HtmlDark => "html",
ExportFormat.Json => "json", ExportFormat.HtmlLight => "html",
_ => throw new ArgumentOutOfRangeException(nameof(format)), ExportFormat.Csv => "csv",
}; ExportFormat.Json => "json",
_ => throw new ArgumentOutOfRangeException(nameof(format)),
};
public static string GetDisplayName(this ExportFormat format) => public string GetDisplayName() =>
format switch format switch
{ {
ExportFormat.PlainText => "TXT", ExportFormat.PlainText => "TXT",
ExportFormat.HtmlDark => "HTML (Dark)", ExportFormat.HtmlDark => "HTML (Dark)",
ExportFormat.HtmlLight => "HTML (Light)", ExportFormat.HtmlLight => "HTML (Light)",
ExportFormat.Csv => "CSV", ExportFormat.Csv => "CSV",
ExportFormat.Json => "JSON", ExportFormat.Json => "JSON",
_ => throw new ArgumentOutOfRangeException(nameof(format)), _ => throw new ArgumentOutOfRangeException(nameof(format)),
}; };
}
} }

View File

@@ -7,7 +7,6 @@ using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exporting.Filtering; using DiscordChatExporter.Core.Exporting.Filtering;
using DiscordChatExporter.Core.Exporting.Partitioning; using DiscordChatExporter.Core.Exporting.Partitioning;
using DiscordChatExporter.Core.Utils;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Core.Exporting; namespace DiscordChatExporter.Core.Exporting;
@@ -145,7 +144,7 @@ public partial class ExportRequest
// File extension // File extension
buffer.Append('.').Append(format.GetFileExtension()); buffer.Append('.').Append(format.GetFileExtension());
return PathEx.EscapeFileName(buffer.ToString()); return Path.EscapeFileName(buffer.ToString());
} }
private static string FormatPath( private static string FormatPath(
@@ -159,7 +158,7 @@ public partial class ExportRequest
path, path,
"%.", "%.",
m => m =>
PathEx.EscapeFileName( Path.EscapeFileName(
m.Value switch m.Value switch
{ {
"%g" => guild.Id.ToString(), "%g" => guild.Id.ToString(),

View File

@@ -6,19 +6,19 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class AsyncCollectionExtensions public static class AsyncCollectionExtensions
{ {
private static async ValueTask<IReadOnlyList<T>> CollectAsync<T>( extension<T>(IAsyncEnumerable<T> asyncEnumerable)
this IAsyncEnumerable<T> asyncEnumerable
)
{ {
var list = new List<T>(); private async ValueTask<IReadOnlyList<T>> CollectAsync()
{
var list = new List<T>();
await foreach (var i in asyncEnumerable) await foreach (var i in asyncEnumerable)
list.Add(i); list.Add(i);
return list; return list;
}
public ValueTaskAwaiter<IReadOnlyList<T>> GetAwaiter() =>
asyncEnumerable.CollectAsync().GetAwaiter();
} }
public static ValueTaskAwaiter<IReadOnlyList<T>> GetAwaiter<T>(
this IAsyncEnumerable<T> asyncEnumerable
) => asyncEnumerable.CollectAsync().GetAwaiter();
} }

View File

@@ -5,15 +5,18 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class BinaryExtensions public static class BinaryExtensions
{ {
public static string ToHex(this byte[] data, bool isUpperCase = true) extension(byte[] data)
{ {
var buffer = new StringBuilder(2 * data.Length); public string ToHex(bool isUpperCase = true)
foreach (var b in data)
{ {
buffer.Append(b.ToString(isUpperCase ? "X2" : "x2", CultureInfo.InvariantCulture)); var buffer = new StringBuilder(2 * data.Length);
}
return buffer.ToString(); foreach (var b in data)
{
buffer.Append(b.ToString(isUpperCase ? "X2" : "x2", CultureInfo.InvariantCulture));
}
return buffer.ToString();
}
} }
} }

View File

@@ -4,25 +4,34 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class CollectionExtensions public static class CollectionExtensions
{ {
public static IEnumerable<T> ToSingletonEnumerable<T>(this T obj) extension<T>(T obj)
{ {
yield return obj; public IEnumerable<T> ToSingletonEnumerable()
{
yield return obj;
}
} }
public static IEnumerable<(T value, int index)> WithIndex<T>(this IEnumerable<T> source) extension<T>(IEnumerable<T> source)
{ {
var i = 0; public IEnumerable<(T value, int index)> WithIndex()
foreach (var o in source) {
yield return (o, i++); var i = 0;
foreach (var o in source)
yield return (o, i++);
}
} }
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) extension<T>(IEnumerable<T?> source)
where T : class where T : class
{ {
foreach (var o in source) public IEnumerable<T> WhereNotNull()
{ {
if (o is not null) foreach (var o in source)
yield return o; {
if (o is not null)
yield return o;
}
} }
} }
} }

View File

@@ -4,11 +4,14 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class ColorExtensions public static class ColorExtensions
{ {
public static Color WithAlpha(this Color color, int alpha) => Color.FromArgb(alpha, color); extension(Color color)
{
public Color WithAlpha(int alpha) => Color.FromArgb(alpha, color);
public static Color ResetAlpha(this Color color) => color.WithAlpha(255); public Color ResetAlpha() => color.WithAlpha(255);
public static int ToRgb(this Color color) => color.ToArgb() & 0xffffff; public int ToRgb() => color.ToArgb() & 0xffffff;
public static string ToHex(this Color color) => $"#{color.R:X2}{color.G:X2}{color.B:X2}"; public string ToHex() => $"#{color.R:X2}{color.G:X2}{color.B:X2}";
}
} }

View File

@@ -5,27 +5,30 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class ExceptionExtensions public static class ExceptionExtensions
{ {
private static void PopulateChildren(this Exception exception, ICollection<Exception> children) extension(Exception exception)
{ {
if (exception is AggregateException aggregateException) private void PopulateChildren(ICollection<Exception> children)
{ {
foreach (var innerException in aggregateException.InnerExceptions) if (exception is AggregateException aggregateException)
{ {
children.Add(innerException); foreach (var innerException in aggregateException.InnerExceptions)
PopulateChildren(innerException, children); {
children.Add(innerException);
PopulateChildren(innerException, children);
}
}
else if (exception.InnerException is not null)
{
children.Add(exception.InnerException);
PopulateChildren(exception.InnerException, children);
} }
} }
else if (exception.InnerException is not null)
public IReadOnlyList<Exception> GetSelfAndChildren()
{ {
children.Add(exception.InnerException); var children = new List<Exception> { exception };
PopulateChildren(exception.InnerException, children); PopulateChildren(exception, children);
return children;
} }
} }
public static IReadOnlyList<Exception> GetSelfAndChildren(this Exception exception)
{
var children = new List<Exception> { exception };
PopulateChildren(exception, children);
return children;
}
} }

View File

@@ -5,12 +5,17 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class GenericExtensions public static class GenericExtensions
{ {
public static TOut Pipe<TIn, TOut>(this TIn input, Func<TIn, TOut> transform) => extension<TIn>(TIn input)
transform(input); {
public TOut Pipe<TOut>(Func<TIn, TOut> transform) => transform(input);
}
public static T? NullIf<T>(this T value, Func<T, bool> predicate) extension<T>(T value)
where T : struct => !predicate(value) ? value : null; where T : struct
{
public T? NullIf(Func<T, bool> predicate) => !predicate(value) ? value : null;
public static T? NullIfDefault<T>(this T value) public T? NullIfDefault() =>
where T : struct => value.NullIf(v => EqualityComparer<T>.Default.Equals(v, default)); value.NullIf(v => EqualityComparer<T>.Default.Equals(v, default));
}
} }

View File

@@ -4,6 +4,9 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class HttpExtensions public static class HttpExtensions
{ {
public static string? TryGetValue(this HttpHeaders headers, string name) => extension(HttpHeaders headers)
headers.TryGetValues(name, out var values) ? string.Concat(values) : null; {
public string? TryGetValue(string name) =>
headers.TryGetValues(name, out var values) ? string.Concat(values) : null;
}
} }

View File

@@ -0,0 +1,29 @@
using System;
using System.IO;
using System.Text;
namespace DiscordChatExporter.Core.Utils.Extensions;
public static class PathExtensions
{
extension(Path)
{
public static string EscapeFileName(string path)
{
var buffer = new StringBuilder(path.Length);
foreach (var c in path)
buffer.Append(!Path.GetInvalidFileNameChars().Contains(c) ? c : '_');
// File names cannot end with a dot on Windows
// https://github.com/Tyrrrz/DiscordChatExporter/issues/977
if (OperatingSystem.IsWindows())
{
while (buffer.Length > 0 && buffer[^1] == '.')
buffer.Remove(buffer.Length - 1, 1);
}
return buffer.ToString();
}
}
}

View File

@@ -5,30 +5,35 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class StringExtensions public static class StringExtensions
{ {
public static string? NullIfWhiteSpace(this string str) => extension(string str)
!string.IsNullOrWhiteSpace(str) ? str : null;
public static string Truncate(this string str, int charCount) =>
str.Length > charCount ? str[..charCount] : str;
public static string ToSpaceSeparatedWords(this string str)
{ {
var builder = new StringBuilder(str.Length * 2); public string? NullIfWhiteSpace() => !string.IsNullOrWhiteSpace(str) ? str : null;
foreach (var c in str) public string Truncate(int charCount) => str.Length > charCount ? str[..charCount] : str;
public string ToSpaceSeparatedWords()
{ {
if (char.IsUpper(c) && builder.Length > 0) var builder = new StringBuilder(str.Length * 2);
builder.Append(' ');
builder.Append(c); foreach (var c in str)
{
if (char.IsUpper(c) && builder.Length > 0)
builder.Append(' ');
builder.Append(c);
}
return builder.ToString();
} }
return builder.ToString(); public T? ParseEnumOrNull<T>(bool ignoreCase = true)
where T : struct, Enum =>
Enum.TryParse<T>(str, ignoreCase, out var result) ? result : null;
} }
public static T? ParseEnumOrNull<T>(this string str, bool ignoreCase = true) extension(StringBuilder builder)
where T : struct, Enum => Enum.TryParse<T>(str, ignoreCase, out var result) ? result : null; {
public StringBuilder AppendIfNotEmpty(char value) =>
public static StringBuilder AppendIfNotEmpty(this StringBuilder builder, char value) => builder.Length > 0 ? builder.Append(value) : builder;
builder.Length > 0 ? builder.Append(value) : builder; }
} }

View File

@@ -7,18 +7,21 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class SuperpowerExtensions public static class SuperpowerExtensions
{ {
public static TextParser<T> Token<T>(this TextParser<T> parser) => extension<T>(TextParser<T> parser)
parser.Between(Character.WhiteSpace.IgnoreMany(), Character.WhiteSpace.IgnoreMany()); {
public TextParser<T> Token() =>
parser.Between(Character.WhiteSpace.IgnoreMany(), Character.WhiteSpace.IgnoreMany());
// Only used for debugging while writing Superpower parsers. // Only used for debugging while writing Superpower parsers.
// From https://twitter.com/nblumhardt/status/1389349059786264578 // From https://twitter.com/nblumhardt/status/1389349059786264578
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public static TextParser<T> Log<T>(this TextParser<T> parser, string description) => public TextParser<T> Log(string description) =>
i => i =>
{ {
Console.WriteLine($"Trying {description} ->"); Console.WriteLine($"Trying {description} ->");
var r = parser(i); var r = parser(i);
Console.WriteLine($"Result was {r}"); Console.WriteLine($"Result was {r}");
return r; return r;
}; };
}
} }

View File

@@ -4,14 +4,17 @@ namespace DiscordChatExporter.Core.Utils.Extensions;
public static class TimeSpanExtensions public static class TimeSpanExtensions
{ {
public static TimeSpan Clamp(this TimeSpan value, TimeSpan min, TimeSpan max) extension(TimeSpan value)
{ {
if (value < min) public TimeSpan Clamp(TimeSpan min, TimeSpan max)
return min; {
if (value < min)
return min;
if (value > max) if (value > max)
return max; return max;
return value; return value;
}
} }
} }

View File

@@ -1,32 +0,0 @@
using System;
using System.Collections.Frozen;
using System.IO;
using System.Text;
namespace DiscordChatExporter.Core.Utils;
public static class PathEx
{
private static readonly FrozenSet<char> InvalidFileNameChars =
[
.. Path.GetInvalidFileNameChars(),
];
public static string EscapeFileName(string path)
{
var buffer = new StringBuilder(path.Length);
foreach (var c in path)
buffer.Append(!InvalidFileNameChars.Contains(c) ? c : '_');
// File names cannot end with a dot on Windows
// https://github.com/Tyrrrz/DiscordChatExporter/issues/977
if (OperatingSystem.IsWindows())
{
while (buffer.Length > 0 && buffer[^1] == '.')
buffer.Remove(buffer.Length - 1, 1);
}
return buffer.ToString();
}
}

View File

@@ -6,28 +6,31 @@ namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class AvaloniaExtensions internal static class AvaloniaExtensions
{ {
public static Window? TryGetMainWindow(this IApplicationLifetime lifetime) => extension(IApplicationLifetime lifetime)
lifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime
? desktopLifetime.MainWindow
: null;
public static TopLevel? TryGetTopLevel(this IApplicationLifetime lifetime) =>
lifetime.TryGetMainWindow()
?? (lifetime as ISingleViewApplicationLifetime)?.MainView?.GetVisualRoot() as TopLevel;
public static bool TryShutdown(this IApplicationLifetime lifetime, int exitCode = 0)
{ {
if (lifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) public Window? TryGetMainWindow() =>
{ lifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime
return desktopLifetime.TryShutdown(exitCode); ? desktopLifetime.MainWindow
} : null;
if (lifetime is IControlledApplicationLifetime controlledLifetime) public TopLevel? TryGetTopLevel() =>
{ lifetime.TryGetMainWindow()
controlledLifetime.Shutdown(exitCode); ?? (lifetime as ISingleViewApplicationLifetime)?.MainView?.GetVisualRoot() as TopLevel;
return true;
}
return false; public bool TryShutdown(int exitCode = 0)
{
if (lifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
return desktopLifetime.TryShutdown(exitCode);
}
if (lifetime is IControlledApplicationLifetime controlledLifetime)
{
controlledLifetime.Shutdown(exitCode);
return true;
}
return false;
}
} }
} }

View File

@@ -6,23 +6,26 @@ namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class DisposableExtensions internal static class DisposableExtensions
{ {
public static void DisposeAll(this IEnumerable<IDisposable> disposables) extension(IEnumerable<IDisposable> disposables)
{ {
var exceptions = default(List<Exception>); public void DisposeAll()
foreach (var disposable in disposables)
{ {
try var exceptions = default(List<Exception>);
{
disposable.Dispose();
}
catch (Exception ex)
{
(exceptions ??= []).Add(ex);
}
}
if (exceptions?.Any() == true) foreach (var disposable in disposables)
throw new AggregateException(exceptions); {
try
{
disposable.Dispose();
}
catch (Exception ex)
{
(exceptions ??= []).Add(ex);
}
}
if (exceptions?.Any() == true)
throw new AggregateException(exceptions);
}
} }
} }

View File

@@ -7,50 +7,47 @@ namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class NotifyPropertyChangedExtensions internal static class NotifyPropertyChangedExtensions
{ {
public static IDisposable WatchProperty<TOwner, TProperty>( extension<TOwner>(TOwner owner)
this TOwner owner,
Expression<Func<TOwner, TProperty>> propertyExpression,
Action callback,
bool watchInitialValue = false
)
where TOwner : INotifyPropertyChanged where TOwner : INotifyPropertyChanged
{ {
var memberExpression = propertyExpression.Body as MemberExpression; public IDisposable WatchProperty<TProperty>(
if (memberExpression?.Member is not PropertyInfo property) Expression<Func<TOwner, TProperty>> propertyExpression,
throw new ArgumentException("Provided expression must reference a property."); Action callback,
bool watchInitialValue = false
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args) )
{ {
if ( var memberExpression = propertyExpression.Body as MemberExpression;
string.IsNullOrWhiteSpace(args.PropertyName) if (memberExpression?.Member is not PropertyInfo property)
|| string.Equals(args.PropertyName, property.Name, StringComparison.Ordinal) throw new ArgumentException("Provided expression must reference a property.");
)
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args)
{ {
callback(); if (
string.IsNullOrWhiteSpace(args.PropertyName)
|| string.Equals(args.PropertyName, property.Name, StringComparison.Ordinal)
)
{
callback();
}
} }
owner.PropertyChanged += OnPropertyChanged;
if (watchInitialValue)
callback();
return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
} }
owner.PropertyChanged += OnPropertyChanged; public IDisposable WatchAllProperties(Action callback, bool watchInitialValues = false)
{
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args) => callback();
owner.PropertyChanged += OnPropertyChanged;
if (watchInitialValue) if (watchInitialValues)
callback(); callback();
return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged); return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
} }
public static IDisposable WatchAllProperties<TOwner>(
this TOwner owner,
Action callback,
bool watchInitialValues = false
)
where TOwner : INotifyPropertyChanged
{
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args) => callback();
owner.PropertyChanged += OnPropertyChanged;
if (watchInitialValues)
callback();
return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
} }
} }

View File

@@ -0,0 +1,17 @@
using System.Diagnostics;
namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class ProcessExtensions
{
extension(Process)
{
public static void StartShellExecute(string path)
{
using var process = new Process();
process.StartInfo = new ProcessStartInfo { FileName = path, UseShellExecute = true };
process.Start();
}
}
}

View File

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

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -102,7 +103,7 @@ public partial class DashboardViewModel : ViewModelBase
await _dialogManager.ShowDialogAsync(_viewModelManager.CreateSettingsViewModel()); await _dialogManager.ShowDialogAsync(_viewModelManager.CreateSettingsViewModel());
[RelayCommand] [RelayCommand]
private void ShowHelp() => ProcessEx.StartShellExecute(Program.ProjectDocumentationUrl); private void ShowHelp() => Process.StartShellExecute(Program.ProjectDocumentationUrl);
private bool CanPullGuilds() => !IsBusy && !string.IsNullOrWhiteSpace(Token); private bool CanPullGuilds() => !IsBusy && !string.IsNullOrWhiteSpace(Token);
@@ -322,11 +323,11 @@ public partial class DashboardViewModel : ViewModelBase
} }
[RelayCommand] [RelayCommand]
private void OpenDiscord() => ProcessEx.StartShellExecute("https://discord.com/app"); private void OpenDiscord() => Process.StartShellExecute("https://discord.com/app");
[RelayCommand] [RelayCommand]
private void OpenDiscordDeveloperPortal() => private void OpenDiscordDeveloperPortal() =>
ProcessEx.StartShellExecute("https://discord.com/developers/applications"); Process.StartShellExecute("https://discord.com/developers/applications");
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {

View File

@@ -5,7 +5,6 @@ using Avalonia;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using DiscordChatExporter.Gui.Framework; using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Services; using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.Utils;
using DiscordChatExporter.Gui.Utils.Extensions; using DiscordChatExporter.Gui.Utils.Extensions;
using DiscordChatExporter.Gui.ViewModels.Components; using DiscordChatExporter.Gui.ViewModels.Components;
@@ -44,7 +43,7 @@ public partial class MainViewModel(
settingsService.Save(); settingsService.Save();
if (await dialogManager.ShowDialogAsync(dialog) == true) if (await dialogManager.ShowDialogAsync(dialog) == true)
ProcessEx.StartShellExecute("https://tyrrrz.me/ukraine?source=discordchatexporter"); Process.StartShellExecute("https://tyrrrz.me/ukraine?source=discordchatexporter");
} }
private async Task ShowDevelopmentBuildMessageAsync() private async Task ShowDevelopmentBuildMessageAsync()
@@ -70,7 +69,7 @@ public partial class MainViewModel(
); );
if (await dialogManager.ShowDialogAsync(dialog) == true) if (await dialogManager.ShowDialogAsync(dialog) == true)
ProcessEx.StartShellExecute(Program.ProjectReleasesUrl); Process.StartShellExecute(Program.ProjectReleasesUrl);
} }
private async Task CheckForUpdatesAsync() private async Task CheckForUpdatesAsync()