mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
optimized window size detection
This commit is contained in:
@@ -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 DownloadIcon from '@mui/icons-material/GetApp';
|
||||
@@ -68,34 +75,37 @@ const levelLabel = (level: LogLevel) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Memoized log entry component to prevent unnecessary re-renders
|
||||
const LogEntryItem = memo(
|
||||
({ entry, compact }: { entry: LogEntry; compact: boolean }) => {
|
||||
const paddedLevelLabel = (level: LogLevel) => {
|
||||
const paddedLevelLabel = (level: LogLevel, compact: boolean) => {
|
||||
const label = levelLabel(level);
|
||||
return compact ? ' ' + label[0] : label.padStart(8, '\xa0');
|
||||
};
|
||||
|
||||
const paddedNameLabel = (name: string) => {
|
||||
const paddedNameLabel = (name: string, compact: boolean) => {
|
||||
const label = '[' + name + ']';
|
||||
return compact ? label : label.padEnd(12, '\xa0');
|
||||
};
|
||||
|
||||
const paddedIDLabel = (id: number) => {
|
||||
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
|
||||
const LogEntryItem = memo(
|
||||
({ entry, compact }: { entry: LogEntry; compact: boolean }) => {
|
||||
return (
|
||||
<div style={{ font: '14px monospace', whiteSpace: 'nowrap' }}>
|
||||
<span>{entry.t}</span>
|
||||
<span>{paddedLevelLabel(entry.l)} </span>
|
||||
<span>{paddedIDLabel(entry.i)} </span>
|
||||
<span>{paddedNameLabel(entry.n)} </span>
|
||||
<span>{paddedLevelLabel(entry.l, compact)} </span>
|
||||
<span>{paddedIDLabel(entry.i, compact)} </span>
|
||||
<span>{paddedNameLabel(entry.n, compact)} </span>
|
||||
<LogEntryLine details={{ level: entry.l }}>{entry.m}</LogEntryLine>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.entry.i === nextProps.entry.i &&
|
||||
prevProps.compact === nextProps.compact
|
||||
);
|
||||
|
||||
const SystemLog = () => {
|
||||
@@ -129,7 +139,7 @@ const SystemLog = () => {
|
||||
const [readOpen, setReadOpen] = useState(false);
|
||||
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
||||
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 ]+$/;
|
||||
|
||||
@@ -140,24 +150,69 @@ const SystemLog = () => {
|
||||
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, {
|
||||
immediate: true,
|
||||
interceptByGlobalResponded: false
|
||||
})
|
||||
.onMessage((message: { data: string }) => {
|
||||
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);
|
||||
}
|
||||
})
|
||||
.onMessage(handleLogMessage)
|
||||
.onError(() => {
|
||||
toast.error('No connection to Log service');
|
||||
});
|
||||
@@ -182,15 +237,19 @@ const SystemLog = () => {
|
||||
await saveData();
|
||||
}, [saveData]);
|
||||
|
||||
// handle scrolling
|
||||
// handle scrolling - optimized to only scroll when needed
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const logWindowRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (logEntries.length && autoscroll) {
|
||||
ref.current?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'end'
|
||||
const container = logWindowRef.current;
|
||||
if (container) {
|
||||
requestAnimationFrame(() => {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [logEntries.length, autoscroll]);
|
||||
|
||||
const sendReadCommand = useCallback(() => {
|
||||
@@ -206,15 +265,6 @@ const SystemLog = () => {
|
||||
}
|
||||
}, [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 = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
@@ -352,14 +402,15 @@ const SystemLog = () => {
|
||||
</Grid>
|
||||
|
||||
<Box
|
||||
ref={logWindowRef}
|
||||
sx={{
|
||||
backgroundColor: 'black',
|
||||
overflowY: 'scroll',
|
||||
position: 'absolute',
|
||||
right: 18,
|
||||
bottom: 18,
|
||||
left: boxPosition().left,
|
||||
top: boxPosition().top,
|
||||
left: boxPosition.left,
|
||||
top: boxPosition.top,
|
||||
p: 1
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user