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 { 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)} </span>
|
<span>{paddedLevelLabel(entry.l, compact)} </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
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user