Skip to content

Commit

Permalink
Enabled event multi select support along with copying multiple events
Browse files Browse the repository at this point in the history
  • Loading branch information
jschick04 authored and bill-long committed May 20, 2024
1 parent c5e380b commit b20fa3b
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 79 deletions.
7 changes: 6 additions & 1 deletion src/EventLogExpert.UI/Store/EventLog/EventLogAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ public sealed record LoadNewEvents;

public sealed record OpenLog(string LogName, LogType LogType, CancellationToken Token = default);

public sealed record SelectEvent(DisplayEventModel? SelectedEvent);
public sealed record SelectEvent(
DisplayEventModel SelectedEvent,
bool IsMultiSelect = false,
bool ShouldStaySelected = false);

public sealed record SelectEvents(IEnumerable<DisplayEventModel> SelectedEvents);

public sealed record SetContinouslyUpdate(bool ContinuouslyUpdate);

Expand Down
34 changes: 31 additions & 3 deletions src/EventLogExpert.UI/Store/EventLog/EventLogReducers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static EventLogState ReduceCloseAll(EventLogState state) =>
state with
{
ActiveLogs = ImmutableDictionary<string, EventLogData>.Empty,
SelectedEvent = null,
SelectedEvents = [],
NewEventBuffer = new List<DisplayEventModel>().AsReadOnly(),
NewEventBufferIsFull = false
};
Expand Down Expand Up @@ -75,9 +75,37 @@ state with
[ReducerMethod]
public static EventLogState ReduceSelectEvent(EventLogState state, EventLogAction.SelectEvent action)
{
if (state.SelectedEvent == action.SelectedEvent) { return state; }
if (!state.SelectedEvents.Contains(action.SelectedEvent))
{
return state with
{
SelectedEvents = action.IsMultiSelect ?
state.SelectedEvents.Add(action.SelectedEvent) : [action.SelectedEvent]
};
}

if (action is { IsMultiSelect: true, ShouldStaySelected: false })
{
return state with { SelectedEvents = state.SelectedEvents.Remove(action.SelectedEvent) };
}

return action.ShouldStaySelected ? state : state with { SelectedEvents = [action.SelectedEvent] };
}

[ReducerMethod]
public static EventLogState ReduceSelectEvents(EventLogState state, EventLogAction.SelectEvents action)
{
List<DisplayEventModel> eventsToAdd = [];

foreach (var selectedEvent in action.SelectedEvents)
{
if (!state.SelectedEvents.Contains(selectedEvent))
{
eventsToAdd.Add(selectedEvent);
}
}

return state with { SelectedEvent = action.SelectedEvent };
return state with { SelectedEvents = state.SelectedEvents.AddRange(eventsToAdd) };
}

[ReducerMethod]
Expand Down
2 changes: 1 addition & 1 deletion src/EventLogExpert.UI/Store/EventLog/EventLogState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ public sealed record EventLogState

public bool NewEventBufferIsFull { get; init; }

public DisplayEventModel? SelectedEvent { get; init; }
public ImmutableList<DisplayEventModel> SelectedEvents { get; init; } = [];
}
5 changes: 5 additions & 0 deletions src/EventLogExpert.UI/Store/LoggingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public override void BeforeDispatch(object action)
_debugLogger.Trace($"Action: {nameof(EventLogAction.SelectEvent)} selected " +
$"{selectEventAction.SelectedEvent?.Source} event ID {selectEventAction.SelectedEvent?.Id}.");

break;
case EventLogAction.SelectEvents selectEventsAction:
_debugLogger.Trace($"Action: {nameof(EventLogAction.SelectEvents)} selected " +
$"{selectEventsAction.SelectedEvents.Count()} events");

break;
case StatusBarAction.SetEventsLoading:
_debugLogger.Trace($"Action: {action.GetType()} {JsonSerializer.Serialize(action, _serializerOptions)}", LogLevel.Debug);
Expand Down
9 changes: 5 additions & 4 deletions src/EventLogExpert/Components/DetailsPane.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Fluxor;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Collections.Immutable;

namespace EventLogExpert.Components;

Expand All @@ -26,7 +27,7 @@ public sealed partial class DetailsPane

private DisplayEventModel? SelectedEvent { get; set; }

[Inject] private IStateSelection<EventLogState, DisplayEventModel?> SelectedEventSelection { get; init; } = null!;
[Inject] private IStateSelection<EventLogState, ImmutableList<DisplayEventModel>> SelectedEventSelection { get; init; } = null!;

[Inject] private IState<SettingsState> SettingsState { get; init; } = null!;

Expand All @@ -42,11 +43,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender)

protected override void OnInitialized()
{
SelectedEventSelection.Select(s => s.SelectedEvent);
SelectedEventSelection.Select(s => s.SelectedEvents);

SelectedEventSelection.SelectedValueChanged += (s, selectedEvent) =>
SelectedEventSelection.SelectedValueChanged += (s, selectedEvents) =>
{
SelectedEvent = selectedEvent;
SelectedEvent = selectedEvents.LastOrDefault();

if (SelectedEvent is not null &&
(!_hasOpened || SettingsState.Value.Config.ShowDisplayPaneOnSelectionChange))
Expand Down
19 changes: 10 additions & 9 deletions src/EventLogExpert/Components/EventTable.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
<SplitLogTabPane />

<div class="table-container">
<table id="eventTable" @onkeyup="HandleKeyUp">
<table id="eventTable" @onkeydown="HandleKeyDown">
<thead @oncontextmenu="InvokeTableColumnMenu">
<tr>
@foreach ((ColumnName column, bool _) in EventTableState.Value.Columns.Where(column => column.Value))
@foreach ((ColumnName column, bool _) in _eventTableState.Columns.Where(column => column.Value))
{
<th class="@column.ToString().ToLower()">
@if (column == ColumnName.DateAndTime)
Expand All @@ -18,9 +18,9 @@
{
@column.ToFullString()
}
@if (EventTableState.Value.OrderBy == column)
@if (_eventTableState.OrderBy == column)
{
<span class="menu-toggle" data-rotate="@EventTableState.Value.IsDescending.ToString().ToLower()" @onclick="ToggleSorting">
<span class="menu-toggle" data-rotate="@_eventTableState.IsDescending.ToString().ToLower()" @onclick="ToggleSorting">
<i class="bi bi-caret-up"></i>
</span>
}
Expand All @@ -31,11 +31,12 @@
</tr>
</thead>
<tbody @oncontextmenu="InvokeContextMenu">
@if (EventTableState.Value.ActiveEventLogId is not null)
@if (_currentTable is not null)
{
<Virtualize Items="EventTableState.Value.EventTables.First(x => x.Id == EventTableState.Value.ActiveEventLogId).DisplayedEvents" Context="evt">
<tr @key="@($"{evt.OwningLog}_{evt.RecordId}")" class="@GetCss(evt)" @onfocus="() => SelectEvent(evt)" tabindex="0">
@foreach ((ColumnName column, bool _) in EventTableState.Value.Columns.Where(column => column.Value))
<Virtualize Context="evt" Items="_currentTable.DisplayedEvents">
<tr class="@GetCss(evt)" @key="@($"{evt.OwningLog}_{evt.RecordId}")"
@onmouseenter="args => DragSelectEvent(args, evt)" @onmousedown="args => SelectEvent(args, evt)" tabindex="0">
@foreach ((ColumnName column, bool _) in _eventTableState.Columns.Where(column => column.Value))
{
<td>
@switch (column)
Expand All @@ -44,7 +45,7 @@
<text><span class="@GetLevelClass(evt.Level)"></span> @evt.Level</text>
break;
case ColumnName.DateAndTime:
@evt.TimeCreated.ConvertTimeZone(TimeZoneSettings.Value)
@evt.TimeCreated.ConvertTimeZone(_timeZoneSettings)
break;
case ColumnName.ActivityId:
@evt.ActivityId
Expand Down
81 changes: 65 additions & 16 deletions src/EventLogExpert/Components/EventTable.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using EventLogExpert.Eventing.Models;
using EventLogExpert.Services;
using EventLogExpert.UI;
using EventLogExpert.UI.Models;
using EventLogExpert.UI.Store.EventLog;
using EventLogExpert.UI.Store.EventTable;
using EventLogExpert.UI.Store.FilterPane;
Expand All @@ -13,14 +14,16 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using System.Collections.Immutable;
using IDispatcher = Fluxor.IDispatcher;

namespace EventLogExpert.Components;

public sealed partial class EventTable
{
private EventTableModel? _currentTable;
private EventTableState _eventTableState = null!;
private DisplayEventModel? _selectedEventState;
private ImmutableList<DisplayEventModel> _selectedEventState = [];
private TimeZoneInfo _timeZoneSettings = null!;

[Inject] private IClipboardService ClipboardService { get; init; } = null!;
Expand All @@ -33,20 +36,25 @@ public sealed partial class EventTable

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

[Inject] private IStateSelection<EventLogState, DisplayEventModel?> SelectedEventState { get; init; } = null!;
[Inject] private IStateSelection<EventLogState, ImmutableList<DisplayEventModel>> SelectedEventState { get; init; } = null!;

[Inject] private IStateSelection<SettingsState, TimeZoneInfo> TimeZoneSettings { get; init; } = null!;

protected override async Task OnInitializedAsync()
{
SelectedEventState.Select(s => s.SelectedEvent);
SelectedEventState.Select(s => s.SelectedEvents);
TimeZoneSettings.Select(settings => settings.Config.TimeZoneInfo);

SubscribeToAction<EventTableAction.LoadColumnsCompleted>(action => RegisterTableEventHandlers().AndForget());
SubscribeToAction<EventTableAction.SetActiveTable>(action => ScrollToSelectedEvent().AndForget());
SubscribeToAction<EventTableAction.LoadColumnsCompleted>(action => RegisterTableEventHandlers().AndForget());
SubscribeToAction<EventTableAction.UpdateCombinedEvents>(action => ScrollToSelectedEvent().AndForget());
SubscribeToAction<EventTableAction.UpdateDisplayedEvents>(action => ScrollToSelectedEvent().AndForget());

_eventTableState = EventTableState.Value;
_currentTable = _eventTableState.EventTables.FirstOrDefault(x => x.Id == _eventTableState.ActiveEventLogId);
_selectedEventState = SelectedEventState.Value;
_timeZoneSettings = TimeZoneSettings.Value;

await base.OnInitializedAsync();
}

Expand All @@ -57,6 +65,7 @@ protected override bool ShouldRender()
TimeZoneSettings.Value.Equals(_timeZoneSettings)) { return false; }

_eventTableState = EventTableState.Value;
_currentTable = _eventTableState.EventTables.FirstOrDefault(x => x.Id == _eventTableState.ActiveEventLogId);
_selectedEventState = SelectedEventState.Value;
_timeZoneSettings = TimeZoneSettings.Value;

Expand All @@ -72,9 +81,16 @@ private static string GetLevelClass(string level) =>
_ => string.Empty,
};

private void DragSelectEvent(MouseEventArgs args, DisplayEventModel @event)
{
if (args.Buttons == 1)
{
Dispatcher.Dispatch(new EventLogAction.SelectEvent(@event, IsMultiSelect: true, ShouldStaySelected: !args.CtrlKey));
}
}

private string GetCss(DisplayEventModel @event) =>
SelectedEventState.Value?.RecordId == @event.RecordId ?
"table-row selected" : $"table-row {GetHighlightedColor(@event)}";
_selectedEventState.Contains(@event) ? "table-row selected" : $"table-row {GetHighlightedColor(@event)}";

private string GetDateColumnHeader() =>
TimeZoneSettings.Value.Equals(TimeZoneInfo.Local) ?
Expand All @@ -92,14 +108,14 @@ private string GetHighlightedColor(DisplayEventModel @event)
return string.Empty;
}

private void HandleKeyUp(KeyboardEventArgs args)
private void HandleKeyDown(KeyboardEventArgs args)
{
// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
switch (args)
{
case { CtrlKey: true, Code: "KeyC" }:
ClipboardService.CopySelectedEvent();
break;
return;
}
}

Expand All @@ -113,23 +129,56 @@ private async Task InvokeTableColumnMenu(MouseEventArgs args) =>

private async Task ScrollToSelectedEvent()
{
var table = EventTableState.Value.EventTables.FirstOrDefault(x => x.Id == EventTableState.Value.ActiveEventLogId);
var entry = _currentTable?.DisplayedEvents.FirstOrDefault(x =>
string.Equals(x.LogName, _selectedEventState.LastOrDefault()?.LogName) &&
x.RecordId == _selectedEventState.LastOrDefault()?.RecordId);

var entry = table?.DisplayedEvents.FirstOrDefault(x =>
string.Equals(x.LogName, SelectedEventState.Value?.LogName) &&
x.RecordId == SelectedEventState.Value?.RecordId);
if (entry is null) { return; }

if (table is null || entry is null) { return; }

var index = table.DisplayedEvents.IndexOf(entry);
var index = _currentTable?.DisplayedEvents.IndexOf(entry);

if (index >= 0)
{
await JSRuntime.InvokeVoidAsync("scrollToRow", index);
}
}

private void SelectEvent(DisplayEventModel @event) => Dispatcher.Dispatch(new EventLogAction.SelectEvent(@event));
private void SelectEvent(MouseEventArgs args, DisplayEventModel @event)
{
switch (args)
{
case { CtrlKey: true }:
Dispatcher.Dispatch(new EventLogAction.SelectEvent(@event, true));
return;
case { ShiftKey: true }:
var startEvent = _selectedEventState.LastOrDefault();

if (startEvent is null || _currentTable is null) { return; }

var startIndex = _currentTable.DisplayedEvents.IndexOf(startEvent);
var endIndex = _currentTable.DisplayedEvents.IndexOf(@event);

if (startIndex < endIndex)
{
Dispatcher.Dispatch(new EventLogAction.SelectEvents(
_currentTable.DisplayedEvents
.Skip(startIndex)
.Take(endIndex - startIndex + 1)));
}
else
{
Dispatcher.Dispatch(new EventLogAction.SelectEvents(
_currentTable.DisplayedEvents
.Skip(endIndex)
.Take(startIndex - endIndex + 1)));
}

return;
default:
Dispatcher.Dispatch(new EventLogAction.SelectEvent(@event));
return;
}
}

private void ToggleSorting() => Dispatcher.Dispatch(new EventTableAction.ToggleSorting());
}
5 changes: 4 additions & 1 deletion src/EventLogExpert/Components/EventTable.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ th, td {
&:last-child { border-right: none; }
}

tr { height: 22px; }
tr {
height: 22px;
user-select: none;
}

.error { color: var(--clr-red); }

Expand Down
Loading

0 comments on commit b20fa3b

Please sign in to comment.