fix(web): keep older trajectory history loadable
This commit is contained in:
12 files changed
+175
-25
No files matched your search
@@ -55,6 +55,40 @@
|
||||
animation: history-loading-spin 700ms linear infinite;
|
||||
}
|
||||
|
||||
.table tbody .historyLoadRow td {
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border-bottom-color: var(--dsw-alias-border-l2);
|
||||
}
|
||||
|
||||
.historyLoadButton {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 29px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
border: 0;
|
||||
background: var(--dsw-alias-bg-layer-1);
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
cursor: pointer;
|
||||
font: var(--dsw-font-xxs-12);
|
||||
}
|
||||
|
||||
.historyLoadButton:hover:not(:disabled) {
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.historyLoadButton:focus-visible {
|
||||
outline: 2px solid var(--dsw-alias-state-business-primary);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.historyLoadButton:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.table:not([data-scroll-ready='true']) {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -137,7 +171,7 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.table tbody tr:not([data-collapsed-summary]):not([data-virtual-spacer]) {
|
||||
.table tbody tr:not([data-collapsed-summary]):not([data-virtual-spacer]):not([data-history-load]) {
|
||||
cursor: default;
|
||||
outline: none;
|
||||
transition:
|
||||
@@ -149,7 +183,7 @@
|
||||
opacity: 0.24;
|
||||
}
|
||||
|
||||
.table tbody tr:not([data-collapsed-summary]):not([data-virtual-spacer]):not([data-selected='true']):hover {
|
||||
.table tbody tr:not([data-collapsed-summary]):not([data-virtual-spacer]):not([data-history-load]):not([data-selected='true']):hover {
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
|
||||
|
||||
@@ -364,6 +364,8 @@ export interface TrajectoryTableProps {
|
||||
recordFocus?: { readonly index: number } | null
|
||||
/** Whether the initial history tail is still loading. */
|
||||
historyLoading?: boolean
|
||||
/** Whether another surface is loading one older history page. */
|
||||
olderHistoryLoading?: boolean
|
||||
/** First loaded raw event, used to preserve scroll position after prepending a page. */
|
||||
historyStartSeq?: number | undefined
|
||||
/** Whether one older history page can be requested. */
|
||||
@@ -1698,6 +1700,7 @@ export function TrajectoryTable({
|
||||
recordSelection = null,
|
||||
recordFocus = null,
|
||||
historyLoading = false,
|
||||
olderHistoryLoading = false,
|
||||
historyStartSeq,
|
||||
hasOlderRecords = false,
|
||||
onLoadOlder,
|
||||
@@ -2123,12 +2126,13 @@ export function TrajectoryTable({
|
||||
virtualIndexByRecordId,
|
||||
virtualizationEnabled,
|
||||
])
|
||||
const requestOlder = useCallback((pane: HTMLDivElement) => {
|
||||
const requestOlder = useCallback((pane: HTMLDivElement, requireTop: boolean) => {
|
||||
if (
|
||||
!hasOlderRecords
|
||||
|| onLoadOlder === undefined
|
||||
|| loadingOlder.current
|
||||
|| pane.scrollTop > OLDER_LOAD_THRESHOLD_PX
|
||||
|| olderHistoryLoading
|
||||
|| (requireTop && pane.scrollTop > OLDER_LOAD_THRESHOLD_PX)
|
||||
) return
|
||||
loadingOlder.current = true
|
||||
setOlderLoading(true)
|
||||
@@ -2143,7 +2147,7 @@ export function TrajectoryTable({
|
||||
loadingOlder.current = false
|
||||
setOlderLoading(false)
|
||||
})
|
||||
}, [hasOlderRecords, historyStartSeq, onLoadOlder])
|
||||
}, [hasOlderRecords, historyStartSeq, olderHistoryLoading, onLoadOlder])
|
||||
useLayoutEffect(() => {
|
||||
const pane = tablePaneRef.current
|
||||
if (pane === null) return
|
||||
@@ -2176,10 +2180,9 @@ export function TrajectoryTable({
|
||||
virtualizationEnabled,
|
||||
])
|
||||
|
||||
const loadingLabel = olderLoading
|
||||
? 'Loading earlier history…'
|
||||
: 'Loading trajectory…'
|
||||
const showLoading = historyLoading || olderLoading || !tableScrollReady
|
||||
const olderBusy = olderHistoryLoading || olderLoading
|
||||
const showInitialLoading = historyLoading || !tableScrollReady
|
||||
const historyRowOffset = hasOlderRecords ? 1 : 0
|
||||
|
||||
return (
|
||||
<div ref={rootRef} className={css.split} style={splitStyle}>
|
||||
@@ -2192,30 +2195,55 @@ export function TrajectoryTable({
|
||||
followsTableTail.current =
|
||||
pane.scrollHeight - pane.clientHeight - pane.scrollTop
|
||||
<= BOTTOM_FOLLOW_THRESHOLD_PX
|
||||
requestOlder(pane)
|
||||
requestOlder(pane, true)
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) clearAllSelections()
|
||||
}}
|
||||
>
|
||||
{showLoading && (
|
||||
{showInitialLoading && (
|
||||
<div className={css.historyLoading} role="status" aria-live="polite">
|
||||
<span className={css.historyLoadingBar}>
|
||||
<span className={css.historyLoadingSpinner} aria-hidden="true" />
|
||||
{loadingLabel}
|
||||
Loading trajectory…
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<table
|
||||
className={css.table}
|
||||
data-scroll-ready={tableScrollReady || undefined}
|
||||
aria-rowcount={records.length}
|
||||
aria-rowcount={records.length + historyRowOffset}
|
||||
>
|
||||
<colgroup>
|
||||
<col className={css.eventColumn} />
|
||||
<col className={css.contentColumn} />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{hasOlderRecords && (
|
||||
<tr className={css.historyLoadRow} data-history-load="">
|
||||
<td colSpan={2}>
|
||||
<button
|
||||
type="button"
|
||||
className={css.historyLoadButton}
|
||||
disabled={olderBusy || onLoadOlder === undefined}
|
||||
onClick={() => {
|
||||
const pane = tablePaneRef.current
|
||||
if (pane !== null) requestOlder(pane, false)
|
||||
}}
|
||||
>
|
||||
{olderBusy && (
|
||||
<span className={css.historyLoadingSpinner} aria-hidden="true" />
|
||||
)}
|
||||
<span
|
||||
role={olderBusy ? 'status' : undefined}
|
||||
aria-live={olderBusy ? 'polite' : undefined}
|
||||
>
|
||||
{olderBusy ? 'Loading earlier history…' : 'Load earlier history'}
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{virtualTop > 0 && (
|
||||
<tr className={css.virtualSpacer} data-virtual-spacer="top" aria-hidden="true">
|
||||
<td
|
||||
@@ -2263,7 +2291,7 @@ export function TrajectoryTable({
|
||||
return (
|
||||
<tr
|
||||
tabIndex={isRequestOnly ? -1 : 0}
|
||||
aria-rowindex={position + 1}
|
||||
aria-rowindex={position + 1 + historyRowOffset}
|
||||
aria-label={isCollapsedSummary
|
||||
? `Collapsed ${record.collapsedSummaryKind} summary, ${record.collapsedSummary}`
|
||||
: isRequestOnly
|
||||
|
||||
@@ -141,8 +141,8 @@ export function TrajectoryView({
|
||||
} | null>(null)
|
||||
const inspection = useSession(snapshot =>
|
||||
snapshot.views.get('trajectory') ?? EMPTY_TRAJECTORY_SNAPSHOT)
|
||||
const historyLoading = useSession(snapshot =>
|
||||
snapshot.openState === 'loading' || snapshot.loadingOlder)
|
||||
const historyLoading = useSession(snapshot => snapshot.openState === 'loading')
|
||||
const olderHistoryLoading = useSession(snapshot => snapshot.loadingOlder)
|
||||
const hasOlderHistory = useSession(snapshot => snapshot.hasMore)
|
||||
const nodes = inspection.eventNodes
|
||||
const eventLocations = inspection.eventLocations
|
||||
@@ -488,6 +488,7 @@ export function TrajectoryView({
|
||||
recordSelection={timelineRecordSelection}
|
||||
recordFocus={timelineRecordFocus}
|
||||
historyLoading={historyLoading}
|
||||
olderHistoryLoading={olderHistoryLoading}
|
||||
historyStartSeq={historyBaseSeq}
|
||||
hasOlderRecords={hasOlderHistory}
|
||||
onLoadOlder={loadEarlierHistory}
|
||||
|
||||
Reference in New Issue
Block a user