Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 53 additions & 39 deletions src/components/AppPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ const AppPanelInner = (props: any) => {

// Add throttling for data processing to prevent event loop blocking
const pendingDataUpdates = useRef<PGN[]>([])
const dataUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const dataUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const currentListRef = useRef<any>({})

const processPendingDataUpdates = useCallback(() => {
if (pendingDataUpdates.current.length === 0) {
Expand All @@ -191,16 +192,21 @@ const AppPanelInner = (props: any) => {
const updates = [...pendingDataUpdates.current]
pendingDataUpdates.current = []

let selectedPgnUpdate: { current: PGN; history: PGN[] } | null = null

// Process all pending updates in a single batch
updates.forEach((pgn) => {
if (infoPGNS.indexOf(pgn!.pgn) === -1 || filterOptionsRef.current?.showInfoPgns) {
setList((prev: any) => {
const rowKey = getRowKey(pgn!, filterOptionsRef.current || undefined)
const maxHistorySize = filterOptionsRef.current?.maxHistorySize ?? 10

if (prev[rowKey]) {
// Create new object instead of mutating
const newList = { ...prev }

if (newList[rowKey]) {
// Move current to history and update current
let newHistory = [prev[rowKey].current, ...prev[rowKey].history]
let newHistory = [newList[rowKey].current, ...newList[rowKey].history]

// Limit history size if maxHistorySize > 0, otherwise disable history
if (maxHistorySize === 0) {
Expand All @@ -209,13 +215,13 @@ const AppPanelInner = (props: any) => {
newHistory = newHistory.slice(0, maxHistorySize)
}

prev[rowKey] = {
newList[rowKey] = {
current: pgn,
history: newHistory,
}
} else {
// New entry
prev[rowKey] = {
newList[rowKey] = {
current: pgn,
history: [],
}
Expand All @@ -224,30 +230,35 @@ const AppPanelInner = (props: any) => {
// Check if this update corresponds to the currently selected PGN
if (selectedPgnKeyRef.current === rowKey) {
selectedPgn.next(pgn!)
selectedPgnWithHistory.next({
selectedPgnUpdate = {
current: pgn!,
history: prev[rowKey].history,
})
history: newList[rowKey].history,
}
}

return prev
// Update the ref to keep it in sync
currentListRef.current = newList
return newList
})
}

if (currentSrcs.indexOf(pgn!.src!) === -1) {
setCurrentSrcs((prev) => {
prev.push(pgn!.src!)
availableSrcs.next([...prev.sort((a, b) => a - b)])
return prev
})
}
// Always check for duplicates inside state update to avoid batching issues
setCurrentSrcs((prev) => {
if (prev.indexOf(pgn!.src!) === -1) {
const newSrcs = [...prev, pgn!.src!]
availableSrcs.next([...newSrcs.sort((a, b) => a - b)])
return newSrcs
}
return prev
})

if (infoPGNS.indexOf(pgn!.pgn) !== -1) {
setCurrentInfo((prev) => {
prev[pgn!.src!] = prev[pgn!.src!] || { src: pgn!.src!, info: {} }
prev[pgn!.src!].info[pgn!.pgn! as PgnNumber] = pgn
deviceInfo.next({ ...prev })
return prev
const newInfo = { ...prev }
newInfo[pgn!.src!] = newInfo[pgn!.src!] || { src: pgn!.src!, info: {} }
newInfo[pgn!.src!].info[pgn!.pgn! as PgnNumber] = pgn
deviceInfo.next({ ...newInfo })
return newInfo
})
}

Expand All @@ -261,11 +272,11 @@ const AppPanelInner = (props: any) => {
}
})

// Trigger data update after processing all items
setList((prev: any) => {
data.next({ ...prev })
return prev
})
// Emit data efficiently using the current ref
data.next({ ...currentListRef.current })
if (selectedPgnUpdate) {
selectedPgnWithHistory.next(selectedPgnUpdate)
}
}, [])

// Handler for tab changes with persistence
Expand Down Expand Up @@ -445,7 +456,7 @@ const AppPanelInner = (props: any) => {

useEffect(() => {
let webSocket: WebSocket | any = null
let reconnectTimeout: NodeJS.Timeout | null = null
let reconnectTimeout: ReturnType<typeof setTimeout> | null = null

const handleWebSocketMessage = (messageData: string) => {
try {
Expand All @@ -464,26 +475,29 @@ const AppPanelInner = (props: any) => {
// Clear all data when reconnecting
console.log('NMEA connection established')

setList((prev: any) => {
data.next({})
deleteAllKeys(prev)
return prev
setList(() => {
const emptyList = {}
data.next(emptyList)
return emptyList
})

setCurrentSrcs((prev) => {
availableSrcs.next([])
prev.length = 0
return prev
setCurrentSrcs(() => {
const emptySrcs: number[] = []
availableSrcs.next(emptySrcs)
return emptySrcs
})

setCurrentInfo((prev) => {
deviceInfo.next([])
deleteAllKeys(prev)
return prev
setCurrentInfo(() => {
const emptyInfo = {}
deviceInfo.next(emptyInfo)
return emptyInfo
})

// Clear selected PGN when connection resets
// Clear selected PGN and history when connection resets
setSelectedPgnKey(null)
selectedPgnKeyRef.current = null
selectedPgn.next({} as PGN)
selectedPgnWithHistory.next({ current: {} as PGN, history: [] })

sentInfoReq.length = 0
// Reset out available when reconnecting
Expand Down
107 changes: 63 additions & 44 deletions src/components/DataList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,20 +228,34 @@ export const DataList = (props: DataListProps) => {
const isExpanded = expandedRows.has(rowKey)
const hasHistory = entry.history.length > 0
const isEvenRow = index % 2 === 0

return (
<React.Fragment key={rowKey}>
<tr style={{ backgroundColor: isEvenRow ? '#ffffff' : 'rgba(0,0,0,.05)' }}>
<td>
<td style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
{hasHistory && (
<i
className={`fas fa-chevron-${isExpanded ? 'down' : 'right'}`}
style={{ cursor: 'pointer' }}
<button
type="button"
className="btn btn-sm p-0"
style={{
cursor: 'pointer',
border: 'none',
background: 'none',
fontSize: '14px',
width: '20px',
height: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}
onClick={() => toggleRowExpansion(rowKey)}
/>
title={isExpanded ? 'Collapse history' : 'Expand history'}
>
{isExpanded ? '▼' : '▶'}
</button>
)}
{hasHistory && (
<span className="badge bg-info ms-1" title={`${entry.history.length} previous entries`}>
<span className="badge bg-info" title={`${entry.history.length} previous entries`}>
{entry.history.length}
</span>
)}
Expand Down Expand Up @@ -270,44 +284,49 @@ export const DataList = (props: DataListProps) => {
{hasHistory && (
<tr style={{ backgroundColor: 'transparent' }}>
<td colSpan={6} style={{ padding: 0, borderTop: 'none', backgroundColor: 'transparent' }}>
<div className={isExpanded ? 'collapse show' : 'collapse'}>
<div style={{ backgroundColor: '#f8f9fa', padding: '8px' }}>
<div style={{ marginBottom: '8px', fontSize: '0.875rem', fontWeight: 'bold' }}>
History ({entry.history.length} previous entries):
</div>
<table className="table table-sm table-bordered" style={{ marginBottom: 0 }}>
<thead>
<tr style={{ backgroundColor: '#e9ecef' }}>
<th>Timestamp</th>
<th>pgn</th>
<th>src</th>
<th>dst</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{entry.history.map((historicalRow: PGN, index: number) => (
<tr
key={`${rowKey}-history-${index}`}
style={{ cursor: 'pointer' }}
onClick={() => handleRowClick(historicalRow)}
>
<td style={{ fontFamily: 'monospace', fontSize: '0.8rem' }}>
{new Date(historicalRow.timestamp!).toLocaleTimeString([], { hour12: false })}
</td>
<td style={{ fontSize: '0.8rem' }}>{historicalRow.pgn}</td>
<td style={{ fontSize: '0.8rem' }}>{historicalRow.src}</td>
<td style={{ fontSize: '0.8rem' }}>{historicalRow.dst}</td>
<td style={{ fontSize: '0.8rem' }}>
<span style={{ fontFamily: 'monospace' }}>
{historicalRow.getDefinition().Description}
</span>
</td>
</tr>
))}
</tbody>
</table>
<div
style={{
display: isExpanded ? 'block' : 'none',
backgroundColor: '#f8f9fa',
padding: '8px',
animation: isExpanded ? 'fadeIn 0.3s' : undefined,
}}
>
<div style={{ marginBottom: '8px', fontSize: '0.875rem', fontWeight: 'bold' }}>
History ({entry.history.length} previous entries):
</div>
<table className="table table-sm table-bordered" style={{ marginBottom: 0 }}>
<thead>
<tr style={{ backgroundColor: '#e9ecef' }}>
<th>Timestamp</th>
<th>pgn</th>
<th>src</th>
<th>dst</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{entry.history.map((historicalRow: PGN, index: number) => (
<tr
key={`${rowKey}-history-${index}`}
style={{ cursor: 'pointer' }}
onClick={() => handleRowClick(historicalRow)}
>
<td style={{ fontFamily: 'monospace', fontSize: '0.8rem' }}>
{new Date(historicalRow.timestamp!).toLocaleTimeString([], { hour12: false })}
</td>
<td style={{ fontSize: '0.8rem' }}>{historicalRow.pgn}</td>
<td style={{ fontSize: '0.8rem' }}>{historicalRow.src}</td>
<td style={{ fontSize: '0.8rem' }}>{historicalRow.dst}</td>
<td style={{ fontSize: '0.8rem' }}>
<span style={{ fontFamily: 'monospace' }}>
{historicalRow.getDefinition().Description}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</td>
</tr>
Expand Down
Loading