Skip to content

Commit 23613af

Browse files
authored
Merge pull request #160 from metheos/main
Enhance view state management by integrating UiStore for scroll position and selected event persistence
2 parents eb8bb3c + 5f78f28 commit 23613af

File tree

3 files changed

+83
-11
lines changed

3 files changed

+83
-11
lines changed

src/components/view/view-event-list.tsx

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
RTCConnection,
1818
TlsTunnel
1919
} from '../../types';
20+
import { UiStore } from '../../model/ui/ui-store';
2021

2122
import {
2223
getSummaryColor,
@@ -54,6 +55,7 @@ interface ViewEventListProps {
5455
isPaused: boolean;
5556

5657
contextMenuBuilder: ViewEventContextMenuBuilder;
58+
uiStore: UiStore;
5759

5860
moveSelection: (distance: number) => void;
5961
onSelected: (event: CollectedEvent | undefined) => void;
@@ -767,6 +769,17 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
767769
private listBodyRef = React.createRef<HTMLDivElement>();
768770
private listRef = React.createRef<List>();
769771

772+
private setListBodyRef = (element: HTMLDivElement | null) => {
773+
// Update the ref
774+
(this.listBodyRef as any).current = element;
775+
776+
// If the element is being mounted and we haven't restored state yet, do it now
777+
if (element && !this.hasRestoredScrollState) {
778+
this.restoreScrollPosition();
779+
this.hasRestoredScrollState = true;
780+
}
781+
};
782+
770783
private KeyBoundListWindow = observer(
771784
React.forwardRef<HTMLDivElement>(
772785
(props: any, ref) => <div
@@ -816,7 +829,7 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
816829
: <AutoSizer>{({ height, width }) =>
817830
<Observer>{() =>
818831
<List
819-
innerRef={this.listBodyRef}
832+
innerRef={this.setListBodyRef}
820833
outerElementType={this.KeyBoundListWindow}
821834
ref={this.listRef}
822835

@@ -881,17 +894,28 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
881894
}
882895

883896
private wasListAtBottom = true;
897+
884898
private updateScrolledState = () => {
885899
requestAnimationFrame(() => { // Measure async, once the scroll has actually happened
886900
this.wasListAtBottom = this.isListAtBottom();
901+
902+
// Only save scroll position after we've restored the initial state
903+
if (this.hasRestoredScrollState) {
904+
const listWindow = this.listBodyRef.current?.parentElement;
905+
906+
const scrollPosition = this.wasListAtBottom
907+
? 'end'
908+
: (listWindow?.scrollTop ?? 'end');
909+
if (listWindow) {
910+
this.props.uiStore.setViewScrollPosition(scrollPosition);
911+
}
912+
}
887913
});
888914
}
889915

890-
componentDidMount() {
891-
this.updateScrolledState();
892-
}
916+
private hasRestoredScrollState = false;
893917

894-
componentDidUpdate() {
918+
componentDidUpdate(prevProps: ViewEventListProps) {
895919
if (this.listBodyRef.current?.parentElement?.contains(document.activeElement)) {
896920
// If we previously had something here focused, and we've updated, update
897921
// the focus too, to make sure it's in the right place.
@@ -902,6 +926,25 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
902926
// scroll there again ourselves now.
903927
if (this.wasListAtBottom && !this.isListAtBottom()) {
904928
this.listRef.current?.scrollToItem(this.props.events.length - 1);
929+
} else if (prevProps.selectedEvent !== this.props.selectedEvent && this.props.selectedEvent) {
930+
// If the selected event changed and we have a selected event, scroll to it
931+
// This handles restoring the selected event when returning to the tab
932+
this.scrollToEvent(this.props.selectedEvent);
933+
}
934+
}
935+
936+
private restoreScrollPosition = () => {
937+
const savedPosition = this.props.uiStore.viewScrollPosition;
938+
const listWindow = this.listBodyRef.current?.parentElement;
939+
if (listWindow) {
940+
if (savedPosition === 'end') {
941+
listWindow.scrollTop = listWindow.scrollHeight;
942+
} else {
943+
// Only restore if we're not close to the current position (avoid unnecessary scrolling)
944+
if (Math.abs(listWindow.scrollTop - savedPosition) > 10) {
945+
listWindow.scrollTop = savedPosition;
946+
}
947+
}
905948
}
906949
}
907950

src/components/view/view-page.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,13 @@ class ViewPage extends React.Component<ViewPageProps> {
207207
filteredEventCount: [filteredEvents.length, events.length]
208208
};
209209
}
210-
211210
@computed
212211
get selectedEvent() {
212+
// First try to use the URL-based eventId, then fallback to the persisted selection
213+
const targetEventId = this.props.eventId || this.props.uiStore.selectedEventId;
214+
213215
return _.find(this.props.eventsStore.events, {
214-
id: this.props.eventId
216+
id: targetEventId
215217
});
216218
}
217219

@@ -242,12 +244,11 @@ class ViewPage extends React.Component<ViewPageProps> {
242244
);
243245

244246
componentDidMount() {
245-
// After first render, scroll to the selected event (or the end of the list) by default:
247+
// After first render, if we're jumping to an event, then scroll to it:
246248
requestAnimationFrame(() => {
247249
if (this.props.eventId && this.selectedEvent) {
250+
this.props.uiStore.setSelectedEventId(this.props.eventId);
248251
this.onScrollToCenterEvent(this.selectedEvent);
249-
} else {
250-
this.onScrollToEnd();
251252
}
252253
});
253254

@@ -328,6 +329,15 @@ class ViewPage extends React.Component<ViewPageProps> {
328329
);
329330
}
330331

332+
componentDidUpdate(prevProps: ViewPageProps) {
333+
// Only clear persisted selection if we're explicitly navigating to a different event via URL
334+
// Don't clear it when going from eventId to no eventId (which happens when clearing selection)
335+
if (this.props.eventId && prevProps.eventId && this.props.eventId !== prevProps.eventId) {
336+
// Clear persisted selection only when explicitly navigating between different events via URL
337+
this.props.uiStore.setSelectedEventId(undefined);
338+
}
339+
}
340+
331341
isSendAvailable() {
332342
return versionSatisfies(serverVersion.value as string, SERVER_SEND_API_SUPPORTED);
333343
}
@@ -447,8 +457,8 @@ class ViewPage extends React.Component<ViewPageProps> {
447457

448458
moveSelection={this.moveSelection}
449459
onSelected={this.onSelected}
450-
451460
contextMenuBuilder={this.contextMenuBuilder}
461+
uiStore={this.props.uiStore}
452462

453463
ref={this.listRef}
454464
/>
@@ -491,6 +501,8 @@ class ViewPage extends React.Component<ViewPageProps> {
491501

492502
@action.bound
493503
onSelected(event: CollectedEvent | undefined) {
504+
this.props.uiStore.setSelectedEventId(event?.id);
505+
494506
this.props.navigate(event
495507
? `/view/${event.id}`
496508
: '/view'

src/model/ui/ui-store.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,12 @@ export class UiStore {
251251
@observable
252252
expandedViewCard: ExpandableViewCardKey | undefined;
253253

254+
@observable
255+
viewScrollPosition: number | 'end' = 'end';
256+
257+
@observable
258+
selectedEventId: string | undefined;
259+
254260
@computed
255261
get viewCardProps() {
256262
return _.mapValues(this.viewCardStates, (state, key) => ({
@@ -436,6 +442,17 @@ export class UiStore {
436442
@persist @observable
437443
exportSnippetFormat: string | undefined;
438444

445+
// Actions for persisting view state when switching tabs
446+
@action.bound
447+
setViewScrollPosition(position: number | 'end') {
448+
this.viewScrollPosition = position;
449+
}
450+
451+
@action.bound
452+
setSelectedEventId(eventId: string | undefined) {
453+
this.selectedEventId = eventId;
454+
}
455+
439456
/**
440457
* This tracks the context menu state *only if it's not handled natively*. This state
441458
* is rendered by React as a fallback when DesktopApi.openContextMenu is not available.

0 commit comments

Comments
 (0)