Skip to content

Commit 4e52023

Browse files
committed
Added details pane height preference
1 parent 23c883b commit 4e52023

File tree

6 files changed

+153
-15
lines changed

6 files changed

+153
-15
lines changed

src/EventLogExpert.UI/Interfaces/IPreferencesProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public interface IPreferencesProvider
1212

1313
IDictionary<ColumnName, int> ColumnWidthsPreference { get; set; }
1414

15+
int DetailsPaneHeightPreference { get; set; }
16+
1517
IEnumerable<string> DisabledDatabasesPreference { get; set; }
1618

1719
bool DisplayPaneSelectionPreference { get; set; }

src/EventLogExpert/Components/DetailsPane.razor.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515

1616
namespace EventLogExpert.Components;
1717

18-
public sealed partial class DetailsPane : IDisposable
18+
public sealed partial class DetailsPane
1919
{
20+
private DotNetObjectReference<DetailsPane>? _dotNetRef;
2021
private bool _hasOpened = false;
2122
private bool _isVisible = false;
2223
private bool _isXmlVisible = false;
@@ -27,6 +28,8 @@ public sealed partial class DetailsPane : IDisposable
2728

2829
[Inject] private IJSRuntime JSRuntime { get; init; } = null!;
2930

31+
[Inject] private IPreferencesProvider PreferencesProvider { get; init; } = null!;
32+
3033
private DisplayEventModel? SelectedEvent { get; set; }
3134

3235
[Inject] private IStateSelection<EventLogState, ImmutableList<DisplayEventModel>> SelectedEventSelection { get; init; } = null!;
@@ -35,13 +38,42 @@ public sealed partial class DetailsPane : IDisposable
3538

3639
[Inject] private ITraceLogger TraceLogger { get; init; } = null!;
3740

38-
public void Dispose() => SelectedEventSelection.SelectedValueChanged -= OnSelectedEventChanged;
41+
[JSInvokable]
42+
public void OnDetailsPaneHeightChanged(int height)
43+
{
44+
if (height > 0)
45+
{
46+
PreferencesProvider.DetailsPaneHeightPreference = height;
47+
}
48+
}
49+
50+
protected override async ValueTask DisposeAsyncCore(bool disposing)
51+
{
52+
if (disposing)
53+
{
54+
SelectedEventSelection.SelectedValueChanged -= OnSelectedEventChanged;
55+
56+
try
57+
{
58+
await JSRuntime.InvokeVoidAsync("disposeDetailsPaneResizer");
59+
}
60+
catch (JSDisconnectedException) { }
61+
62+
_dotNetRef?.Dispose();
63+
}
64+
65+
await base.DisposeAsyncCore(disposing);
66+
}
3967

4068
protected override async Task OnAfterRenderAsync(bool firstRender)
4169
{
4270
if (firstRender)
4371
{
44-
await JSRuntime.InvokeVoidAsync("enableDetailsPaneResizer");
72+
_dotNetRef = DotNetObjectReference.Create(this);
73+
await JSRuntime.InvokeVoidAsync(
74+
"enableDetailsPaneResizer",
75+
_dotNetRef,
76+
PreferencesProvider.DetailsPaneHeightPreference);
4577
}
4678

4779
await base.OnAfterRenderAsync(firstRender);
@@ -60,6 +92,8 @@ protected override void OnInitialized()
6092

6193
private void HandleKeyDown(KeyboardEventArgs e)
6294
{
95+
if (e.Repeat) { return; }
96+
6397
if (e.Key is "Enter" or " ")
6498
{
6599
ToggleMenu();
@@ -68,6 +102,8 @@ private void HandleKeyDown(KeyboardEventArgs e)
68102

69103
private void HandleKeyDownXml(KeyboardEventArgs e)
70104
{
105+
if (e.Repeat) { return; }
106+
71107
if (e.Key is "Enter" or " ")
72108
{
73109
ToggleXml();

src/EventLogExpert/Services/PreferencesProvider.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public sealed class PreferencesProvider : IPreferencesProvider
1313
{
1414
private const string ColumnOrder = "column-order";
1515
private const string ColumnWidths = "column-widths";
16+
private const string DetailsPaneHeight = "details-pane-height";
1617
private const string DisabledDatabases = "disabled-databases";
1718
private const string DisplaySelectionEnabled = "display-selection-enabled";
1819
private const string EnabledEventTableColumns = "enabled-event-table-columns";
@@ -36,6 +37,12 @@ public IDictionary<ColumnName, int> ColumnWidthsPreference
3637
set => Preferences.Default.Set(ColumnWidths, JsonSerializer.Serialize(value));
3738
}
3839

40+
public int DetailsPaneHeightPreference
41+
{
42+
get => Preferences.Default.Get(DetailsPaneHeight, 0);
43+
set => Preferences.Default.Set(DetailsPaneHeight, value);
44+
}
45+
3946
public IEnumerable<string> DisabledDatabasesPreference
4047
{
4148
get => JsonSerializer.Deserialize<List<string>>(Preferences.Default.Get(DisabledDatabases, "[]")) ?? [];

src/EventLogExpert/Shared/Components/TableColumnMenu.razor.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ protected override void OnInitialized()
3030

3131
private static void HandleActivationKey(KeyboardEventArgs args, Action action)
3232
{
33+
// Ignore auto-repeat so holding Enter/Space doesn't dispatch the action
34+
// repeatedly while the key is held down.
35+
if (args.Repeat)
36+
{
37+
return;
38+
}
39+
3340
if (args.Key is "Enter" or " ")
3441
{
3542
action();
Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,108 @@
1-
window.enableDetailsPaneResizer = () => {
1+
const detailsPaneState = {
2+
activeDocumentListeners: [],
3+
controller: null,
4+
dotNetRef: null
5+
};
6+
7+
function trackDetailsDocumentListener(event, handler) {
8+
const entry = { event, handler };
9+
const options = detailsPaneState.controller ? { signal: detailsPaneState.controller.signal } : undefined;
10+
document.addEventListener(event, handler, options);
11+
detailsPaneState.activeDocumentListeners.push(entry);
12+
13+
return () => {
14+
document.removeEventListener(event, handler);
15+
const i = detailsPaneState.activeDocumentListeners.indexOf(entry);
16+
if (i !== -1) {
17+
detailsPaneState.activeDocumentListeners.splice(i, 1);
18+
}
19+
};
20+
}
21+
22+
window.enableDetailsPaneResizer = (dotNetRef, savedHeight) => {
23+
window.disposeDetailsPaneResizer();
24+
225
const detailsPane = document.getElementById("details-pane");
326
const resizer = document.getElementById("details-resizer");
427

5-
if (detailsPane == null || resizer == null) { return; }
28+
if (detailsPane == null || resizer == null) {
29+
return;
30+
}
31+
32+
// Apply persisted height (if any) before user interaction. CSS supplies
33+
// the default height when no saved value exists. Clamp so a height saved
34+
// on a larger window can't overwhelm a smaller viewport on next launch.
35+
if (savedHeight && savedHeight > 0) {
36+
const maxHeight = Math.max(60, Math.floor(window.innerHeight * 0.8));
37+
detailsPane.style.height = `${Math.min(savedHeight, maxHeight)}px`;
38+
}
39+
40+
detailsPaneState.dotNetRef = dotNetRef;
41+
detailsPaneState.controller = new AbortController();
42+
const signal = detailsPaneState.controller.signal;
643

7-
let y, h = 0;
44+
let y = 0;
45+
let h = 0;
46+
let untrackMove = null;
47+
let untrackUp = null;
848

949
const mouseMoveHandler = function(e) {
1050
const distance = e.clientY - y;
51+
// Match the column-resize minimum (avoids the pane vanishing entirely
52+
// and being un-grabbable). CSS min-height still applies on top.
53+
const newHeight = Math.max(30, h - distance);
1154

12-
detailsPane.style.height = `${h - distance}px`;
55+
detailsPane.style.height = `${newHeight}px`;
1356
};
1457

1558
const mouseUpHandler = function() {
16-
document.removeEventListener("mousemove", mouseMoveHandler);
17-
document.removeEventListener("mouseup", mouseUpHandler);
59+
if (untrackMove) {
60+
untrackMove();
61+
untrackMove = null;
62+
}
63+
if (untrackUp) {
64+
untrackUp();
65+
untrackUp = null;
66+
}
67+
68+
const ref = detailsPaneState.dotNetRef;
69+
70+
if (ref && detailsPane.isConnected) {
71+
const newHeight = parseInt(window.getComputedStyle(detailsPane).height, 10);
72+
// Catch rejection in case the .NET object was disposed mid-drag.
73+
ref.invokeMethodAsync("OnDetailsPaneHeightChanged", newHeight).catch(() => {});
74+
}
1875
};
1976

2077
const mouseDownHandler = function(e) {
78+
// Only respond to primary (left) button so right-click context menus
79+
// and middle-click are not intercepted.
80+
if (e.button !== 0) {
81+
return;
82+
}
83+
2184
y = e.clientY;
2285

2386
const styles = window.getComputedStyle(detailsPane);
2487
h = parseInt(styles.height, 10);
2588

26-
document.addEventListener("mousemove", mouseMoveHandler);
27-
document.addEventListener("mouseup", mouseUpHandler);
89+
untrackMove = trackDetailsDocumentListener("mousemove", mouseMoveHandler);
90+
untrackUp = trackDetailsDocumentListener("mouseup", mouseUpHandler);
2891
};
2992

30-
resizer.addEventListener("mousedown", mouseDownHandler);
93+
resizer.addEventListener("mousedown", mouseDownHandler, { signal });
94+
};
95+
96+
window.disposeDetailsPaneResizer = () => {
97+
if (detailsPaneState.controller) {
98+
detailsPaneState.controller.abort();
99+
detailsPaneState.controller = null;
100+
}
101+
102+
for (const { event, handler } of detailsPaneState.activeDocumentListeners) {
103+
document.removeEventListener(event, handler);
104+
}
105+
106+
detailsPaneState.activeDocumentListeners = [];
107+
detailsPaneState.dotNetRef = null;
31108
};

src/EventLogExpert/wwwroot/js/event_table.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@
150150
const newWidth = parseInt(window.getComputedStyle(column).width, 10);
151151

152152
if (dotNetRef && colName) {
153-
dotNetRef.invokeMethodAsync("OnColumnResized", colName, newWidth);
153+
// Catch rejection in case the .NET object was disposed mid-drag
154+
// (component teardown, column set change, etc.).
155+
dotNetRef.invokeMethodAsync("OnColumnResized", colName, newWidth).catch(() => { });
154156
}
155157

156158
// Rebuild dividers after resize
@@ -181,6 +183,11 @@
181183
return;
182184
}
183185

186+
// Suppress the WebView's default arrow-key scroll while a focused
187+
// divider is being keyboard-resized.
188+
e.preventDefault();
189+
e.stopPropagation();
190+
184191
// Debounce keyboard resize persistence
185192
if (keyboardResizeTimer) {
186193
clearTimeout(keyboardResizeTimer);
@@ -191,7 +198,7 @@
191198
const newWidth = parseInt(window.getComputedStyle(column).width, 10);
192199

193200
if (dotNetRef && colName) {
194-
dotNetRef.invokeMethodAsync("OnColumnResized", colName, newWidth);
201+
dotNetRef.invokeMethodAsync("OnColumnResized", colName, newWidth).catch(() => { });
195202
}
196203

197204
keyboardResizeTimer = null;
@@ -298,7 +305,9 @@
298305
const sourceColName = getColumnName(dragSource);
299306

300307
if (dotNetRef && sourceColName) {
301-
dotNetRef.invokeMethodAsync("OnColumnReordered", sourceColName, pendingTarget, pendingInsertAfter);
308+
// Catch rejection in case the .NET object was disposed
309+
// mid-drag (component teardown, column set change, etc.).
310+
dotNetRef.invokeMethodAsync("OnColumnReordered", sourceColName, pendingTarget, pendingInsertAfter).catch(() => { });
302311
}
303312
}
304313

0 commit comments

Comments
 (0)