Skip to content

Commit accae46

Browse files
committed
feat(Avalonia): add fully functional LogViewer
1 parent 10809dd commit accae46

File tree

4 files changed

+407
-94
lines changed

4 files changed

+407
-94
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<windowing:AppWindow
2+
Closed="AppWindow_OnClosed"
3+
Height="800"
4+
Loaded="AppWindow_OnLoaded"
5+
Title="Log Viewer"
6+
Width="900"
7+
x:Class="SnapX.Avalonia.Views.LogViewer"
8+
xmlns="https://github.com/avaloniaui"
9+
xmlns:utils="clr-namespace:SnapX.Core.Utils;assembly=SnapX.Core"
10+
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
11+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
12+
13+
<DockPanel>
14+
<StackPanel
15+
DockPanel.Dock="Bottom"
16+
HorizontalAlignment="Right"
17+
Margin="10"
18+
Orientation="Horizontal"
19+
Spacing="8">
20+
<Button Click="CopyButton_OnClick" Content="Copy" />
21+
<Button Click="UploadLogButton_OnClick" Content="Upload" />
22+
<Button Click="OpenLogFolderButton_OnClick" Content="Open Log Folder" />
23+
<Button Click="ClearButton_OnClick" Content="Clear" />
24+
<Button Click="Close_Click" Content="{x:Static utils:Lang.Close}" />
25+
</StackPanel>
26+
27+
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="ScrollViewer">
28+
<StackPanel Margin="10" Spacing="10">
29+
<SelectableTextBlock
30+
FontSize="16"
31+
SizeChanged="LogTextBlock_OnSizeChanged"
32+
TextWrapping="Wrap"
33+
x:Name="LogTextBlock" />
34+
</StackPanel>
35+
</ScrollViewer>
36+
</DockPanel>
37+
</windowing:AppWindow>
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
2+
using Avalonia;
3+
using Avalonia.Controls;
4+
using Avalonia.Controls.Documents;
5+
using Avalonia.Interactivity;
6+
using Avalonia.Media;
7+
using Avalonia.Threading;
8+
using FluentAvalonia.UI.Windowing;
9+
using Serilog.Events;
10+
using SnapX.Core;
11+
using SnapX.Core.Upload;
12+
using SnapX.Core.Utils;
13+
14+
namespace SnapX.Avalonia.Views;
15+
16+
public partial class LogViewer : AppWindow
17+
{
18+
private ScrollViewer? _scrollViewer;
19+
private SelectableTextBlock? _logTextBlock;
20+
private int _lastDisplayedLogCount;
21+
private DispatcherTimer _refreshTimer;
22+
public LogViewer()
23+
{
24+
InitializeComponent();
25+
_refreshTimer = new DispatcherTimer
26+
{
27+
Interval = TimeSpan.FromMilliseconds(10)
28+
};
29+
_refreshTimer.Tick += (_, _) => RefreshLogs();
30+
}
31+
32+
33+
private void AppWindow_OnLoaded(object? sender, RoutedEventArgs e)
34+
{
35+
_scrollViewer = this.FindControl<ScrollViewer>("ScrollViewer");
36+
_logTextBlock = this.FindControl<SelectableTextBlock>("LogTextBlock");
37+
_lastDisplayedLogCount = 0;
38+
_refreshTimer.Start();
39+
}
40+
41+
private void AppWindow_OnClosed(object? sender, EventArgs e)
42+
{
43+
_lastDisplayedLogCount = 0;
44+
_refreshTimer.Stop();
45+
}
46+
47+
private void Close_Click(object? sender, RoutedEventArgs e) => Close();
48+
49+
private void RefreshLogs()
50+
{
51+
if (DebugHelper.LogEvents.Count() <= _lastDisplayedLogCount) return;
52+
for (var i = _lastDisplayedLogCount; i < DebugHelper.LogEvents.Count(); i++)
53+
{
54+
var logEvent = DebugHelper.LogEvents.ElementAt(i);
55+
var timestamp = logEvent.Timestamp.ToString("hh:mm:ss");
56+
var level = logEvent.Level.ToString().ToUpper();
57+
if (level == "INFORMATION") level = "INFO";
58+
var message = logEvent.RenderMessage();
59+
if (_logTextBlock is null)
60+
{
61+
DebugHelper.WriteLine($"{nameof(RefreshLogs)}: {nameof(_logTextBlock)} is null!");
62+
return;
63+
}
64+
65+
_logTextBlock.Inlines!.Add(new Run($"{timestamp} ")
66+
{
67+
Foreground = Brushes.DimGray // Use DimGray for a softer grey that works well on dark backgrounds
68+
});
69+
70+
_logTextBlock.Inlines.Add(new Run($"[{level}] ")
71+
{
72+
Foreground = GetBrushForLevel(logEvent.Level)
73+
});
74+
75+
_logTextBlock.Inlines.Add(new Run($"{message}\n")
76+
{
77+
Foreground = Brushes.White
78+
});
79+
80+
if (logEvent.Exception != null)
81+
{
82+
_logTextBlock.Inlines.Add(new Run(logEvent.Exception + "\n")
83+
{
84+
Foreground = Brushes.DarkRed
85+
});
86+
}
87+
_lastDisplayedLogCount = DebugHelper.LogEvents.Count();
88+
}
89+
}
90+
private IBrush GetBrushForLevel(LogEventLevel level)
91+
{
92+
return level switch
93+
{
94+
LogEventLevel.Verbose => new LinearGradientBrush
95+
{
96+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
97+
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
98+
GradientStops = new GradientStops
99+
{
100+
new GradientStop(Color.FromRgb(60, 60, 60), 0),
101+
new GradientStop(Color.FromRgb(40, 40, 40), 1)
102+
}
103+
},
104+
105+
LogEventLevel.Debug => new LinearGradientBrush
106+
{
107+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
108+
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
109+
GradientStops = new GradientStops
110+
{
111+
new GradientStop(Color.FromRgb(50, 100, 150), 0),
112+
new GradientStop(Color.FromRgb(30, 70, 120), 1)
113+
}
114+
},
115+
116+
LogEventLevel.Information => new LinearGradientBrush
117+
{
118+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
119+
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
120+
GradientStops = new GradientStops
121+
{
122+
new GradientStop(Color.FromRgb(70, 130, 70), 0),
123+
new GradientStop(Color.FromRgb(40, 90, 40), 1)
124+
}
125+
},
126+
127+
LogEventLevel.Warning => new LinearGradientBrush
128+
{
129+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
130+
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
131+
GradientStops = new GradientStops
132+
{
133+
new GradientStop(Color.FromRgb(200, 160, 0), 0),
134+
new GradientStop(Color.FromRgb(150, 110, 0), 1)
135+
}
136+
},
137+
138+
LogEventLevel.Error => new LinearGradientBrush
139+
{
140+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
141+
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
142+
GradientStops = new GradientStops
143+
{
144+
new GradientStop(Color.FromRgb(180, 50, 50), 0),
145+
new GradientStop(Color.FromRgb(130, 30, 30), 1)
146+
}
147+
},
148+
149+
LogEventLevel.Fatal => new LinearGradientBrush
150+
{
151+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
152+
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
153+
GradientStops = new GradientStops
154+
{
155+
new GradientStop(Color.FromRgb(100, 0, 0), 0),
156+
new GradientStop(Color.FromRgb(30, 0, 0), 1)
157+
}
158+
},
159+
160+
_ => new SolidColorBrush(Colors.DimGray)
161+
};
162+
}
163+
164+
private void ClearButton_OnClick(object? Sender, RoutedEventArgs E)
165+
{
166+
_logTextBlock?.Inlines?.Clear();
167+
}
168+
169+
private void OpenLogFolderButton_OnClick(object? Sender, RoutedEventArgs E)
170+
{
171+
URLHelpers.OpenURL(Core.SnapX.LogsFolder);
172+
}
173+
174+
private void UploadLogButton_OnClick(object? Sender, RoutedEventArgs E)
175+
{
176+
UploadManager.UploadText(_logTextBlock.Inlines?.OfType<Run>()
177+
.Aggregate("", (current, run) => current + run.Text)!);
178+
}
179+
180+
private void CopyButton_OnClick(object? Sender, RoutedEventArgs E)
181+
{
182+
Clipboard?.SetTextAsync(_logTextBlock.Inlines?.OfType<Run>().Aggregate("", (current, run) => current + run.Text)!);
183+
}
184+
185+
private void LogTextBlock_OnSizeChanged(object? Sender, SizeChangedEventArgs e)
186+
{
187+
if (!e.HeightChanged || _scrollViewer is null) return;
188+
var shouldScrollToEnd = Math.Abs(_scrollViewer.Offset.Y - _scrollViewer.Extent.Height + _scrollViewer.Viewport.Height) < 5; // Very small tolerance!
189+
if (shouldScrollToEnd) _scrollViewer.ScrollToEnd();
190+
}
191+
}

0 commit comments

Comments
 (0)