forked from accius/openhamclock
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPluginLayer.jsx
More file actions
92 lines (86 loc) · 2.38 KB
/
PluginLayer.jsx
File metadata and controls
92 lines (86 loc) · 2.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
* PluginLayer Component
* Renders a single plugin layer using its hook, wrapped in an error boundary.
*
* Validates the Leaflet map instance before passing it to hooks.
* A map whose container has been removed from the DOM (or whose panes
* are gone after map.remove()) will cause getPane().appendChild errors.
*
* The error boundary catches crashes from both render and useEffect
* phases so a single broken plugin never takes down the whole dashboard.
*/
import React, { useRef, useEffect } from 'react';
function isMapAlive(map) {
if (!map) return false;
try {
// map._container is null after map.remove(); _panes is cleared too
return !!(map._container && map._panes && map._panes.mapPane);
} catch {
return false;
}
}
// Inner functional component that calls the hook
const PluginLayerInner = ({
plugin,
enabled,
opacity,
map,
onDXChange,
mapBandFilter,
callsign,
locator,
lowMemoryMode,
satellites,
allUnits,
config,
}) => {
const layerFunc = plugin.useLayer || plugin.hook;
const safeMap = isMapAlive(map) ? map : null;
if (typeof layerFunc === 'function') {
layerFunc({
map: safeMap,
enabled,
opacity,
onDXChange,
callsign,
locator,
mapBandFilter,
lowMemoryMode,
satellites,
allUnits,
config,
});
}
return null;
};
// Error boundary that catches render AND effect errors from plugin hooks.
// A crashed plugin is silently disabled rather than crashing the dashboard.
class PluginErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error(`[PluginLayer:${this.props.pluginId}] Crashed:`, error, info);
}
componentDidUpdate(prevProps) {
// Reset the boundary when the map changes (projection switch) so the
// plugin gets another chance with the new map instance.
if (this.state.hasError && prevProps.map !== this.props.map) {
this.setState({ hasError: false });
}
}
render() {
if (this.state.hasError) return null;
return this.props.children;
}
}
export const PluginLayer = (props) => (
<PluginErrorBoundary pluginId={props.plugin?.id} map={props.map}>
<PluginLayerInner {...props} />
</PluginErrorBoundary>
);
export default PluginLayer;