optimized window size detection

This commit is contained in:
proddy
2025-11-03 17:51:51 +01:00
parent 8f7c0a1d97
commit 17bdd87576

View File

@@ -1,4 +1,11 @@
import { memo, useCallback, useEffect, useRef, useState } from 'react'; import {
memo,
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState
} from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp'; import DownloadIcon from '@mui/icons-material/GetApp';
@@ -68,34 +75,37 @@ const levelLabel = (level: LogLevel) => {
} }
}; };
const paddedLevelLabel = (level: LogLevel, compact: boolean) => {
const label = levelLabel(level);
return compact ? ' ' + label[0] : label.padStart(8, '\xa0');
};
const paddedNameLabel = (name: string, compact: boolean) => {
const label = '[' + name + ']';
return compact ? label : label.padEnd(12, '\xa0');
};
const paddedIDLabel = (id: number, compact: boolean) => {
const label = id + ':';
return compact ? label : label.padEnd(7, '\xa0');
};
// Memoized log entry component to prevent unnecessary re-renders // Memoized log entry component to prevent unnecessary re-renders
const LogEntryItem = memo( const LogEntryItem = memo(
({ entry, compact }: { entry: LogEntry; compact: boolean }) => { ({ entry, compact }: { entry: LogEntry; compact: boolean }) => {
const paddedLevelLabel = (level: LogLevel) => {
const label = levelLabel(level);
return compact ? ' ' + label[0] : label.padStart(8, '\xa0');
};
const paddedNameLabel = (name: string) => {
const label = '[' + name + ']';
return compact ? label : label.padEnd(12, '\xa0');
};
const paddedIDLabel = (id: number) => {
const label = id + ':';
return compact ? label : label.padEnd(7, '\xa0');
};
return ( return (
<div style={{ font: '14px monospace', whiteSpace: 'nowrap' }}> <div style={{ font: '14px monospace', whiteSpace: 'nowrap' }}>
<span>{entry.t}</span> <span>{entry.t}</span>
<span>{paddedLevelLabel(entry.l)}&nbsp;</span> <span>{paddedLevelLabel(entry.l, compact)}&nbsp;</span>
<span>{paddedIDLabel(entry.i)} </span> <span>{paddedIDLabel(entry.i, compact)} </span>
<span>{paddedNameLabel(entry.n)} </span> <span>{paddedNameLabel(entry.n, compact)} </span>
<LogEntryLine details={{ level: entry.l }}>{entry.m}</LogEntryLine> <LogEntryLine details={{ level: entry.l }}>{entry.m}</LogEntryLine>
</div> </div>
); );
} },
(prevProps, nextProps) =>
prevProps.entry.i === nextProps.entry.i &&
prevProps.compact === nextProps.compact
); );
const SystemLog = () => { const SystemLog = () => {
@@ -129,7 +139,7 @@ const SystemLog = () => {
const [readOpen, setReadOpen] = useState(false); const [readOpen, setReadOpen] = useState(false);
const [logEntries, setLogEntries] = useState<LogEntry[]>([]); const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
const [autoscroll, setAutoscroll] = useState(true); const [autoscroll, setAutoscroll] = useState(true);
const [lastId, setLastId] = useState<number>(-1); const [boxPosition, setBoxPosition] = useState({ top: 0, left: 0 });
const ALPHA_NUMERIC_DASH_REGEX = /^[a-fA-F0-9 ]+$/; const ALPHA_NUMERIC_DASH_REGEX = /^[a-fA-F0-9 ]+$/;
@@ -140,24 +150,69 @@ const SystemLog = () => {
updateDataValue as (value: unknown) => void updateDataValue as (value: unknown) => void
); );
// Calculate box position after layout
useLayoutEffect(() => {
const logWindow = document.getElementById('log-window');
if (!logWindow) {
return;
}
const updatePosition = () => {
const windowElement = document.getElementById('log-window');
if (!windowElement) {
return;
}
const rect = windowElement.getBoundingClientRect();
setBoxPosition({ top: rect.bottom, left: rect.left });
};
updatePosition();
// Debounce resize events with requestAnimationFrame
let rafId: number;
const handleResize = () => {
cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(updatePosition);
};
// Update position on window resize
window.addEventListener('resize', handleResize);
const resizeObserver = new ResizeObserver(handleResize);
resizeObserver.observe(logWindow);
return () => {
window.removeEventListener('resize', handleResize);
resizeObserver.disconnect();
cancelAnimationFrame(rafId);
};
}, [data]); // Recalculate when data changes (in case layout shifts)
// Memoize message handler to avoid recreating on every render
const handleLogMessage = useCallback((message: { data: string }) => {
const rawData = message.data;
const logentry = JSON.parse(rawData) as LogEntry;
setLogEntries((log) => {
// Skip if this is a duplicate entry (check last entry id)
if (log.length > 0) {
const lastEntry = log[log.length - 1];
if (lastEntry && logentry.i <= lastEntry.i) {
return log;
}
}
const newLog = [...log, logentry];
// Limit log entries to prevent memory issues - only slice when necessary
if (newLog.length > MAX_LOG_ENTRIES) {
return newLog.slice(-MAX_LOG_ENTRIES);
}
return newLog;
});
}, []);
useSSE(fetchLogES, { useSSE(fetchLogES, {
immediate: true, immediate: true,
interceptByGlobalResponded: false interceptByGlobalResponded: false
}) })
.onMessage((message: { data: string }) => { .onMessage(handleLogMessage)
const rawData = message.data;
const logentry = JSON.parse(rawData) as LogEntry;
if (lastId < logentry.i) {
setLogEntries((log) => {
const newLog = [...log, logentry];
// Limit log entries to prevent memory issues
return newLog.length > MAX_LOG_ENTRIES
? newLog.slice(-MAX_LOG_ENTRIES)
: newLog;
});
setLastId(logentry.i);
}
})
.onError(() => { .onError(() => {
toast.error('No connection to Log service'); toast.error('No connection to Log service');
}); });
@@ -182,14 +237,18 @@ const SystemLog = () => {
await saveData(); await saveData();
}, [saveData]); }, [saveData]);
// handle scrolling // handle scrolling - optimized to only scroll when needed
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const logWindowRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
if (logEntries.length && autoscroll) { if (logEntries.length && autoscroll) {
ref.current?.scrollIntoView({ const container = logWindowRef.current;
behavior: 'smooth', if (container) {
block: 'end' requestAnimationFrame(() => {
}); container.scrollTop = container.scrollHeight;
});
}
} }
}, [logEntries.length, autoscroll]); }, [logEntries.length, autoscroll]);
@@ -206,15 +265,6 @@ const SystemLog = () => {
} }
}, [readValue, readOpen, send]); }, [readValue, readOpen, send]);
const boxPosition = () => {
const logWindow = document.getElementById('log-window');
if (!logWindow) {
return { top: 0, left: 0 };
}
const rect = logWindow.getBoundingClientRect();
return { top: rect.bottom, left: rect.left };
};
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
@@ -352,14 +402,15 @@ const SystemLog = () => {
</Grid> </Grid>
<Box <Box
ref={logWindowRef}
sx={{ sx={{
backgroundColor: 'black', backgroundColor: 'black',
overflowY: 'scroll', overflowY: 'scroll',
position: 'absolute', position: 'absolute',
right: 18, right: 18,
bottom: 18, bottom: 18,
left: boxPosition().left, left: boxPosition.left,
top: boxPosition().top, top: boxPosition.top,
p: 1 p: 1
}} }}
> >