From 7786d02f86a5062c67e32eb4b68e2acdb319e719 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Mar 2026 23:17:48 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20globe=20clickable=20after=20tab=20switch?= =?UTF-8?q?=20=E2=80=94=20deferred=20init=20with=20ResizeObserver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Globe.gl needs non-zero container dimensions for initialization and click handling. With the tab persistence fix (display:none for hidden tabs), the globe container starts at 0×0 when radio isn't the first tab. Added a separate ResizeObserver that detects when the container becomes visible and triggers globe initialization via containerVisible state dependency. Co-Authored-By: Claude Opus 4.6 --- web/src/App.tsx | 11 +++++-- web/src/plugins/radio/RadioTab.tsx | 48 +++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index be710fa..2e8da78 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -123,15 +123,20 @@ export default function App() {

Plugins werden im Server konfiguriert.

) : ( - /* Render ALL tabs, hide inactive ones with CSS to preserve state */ + /* Render ALL tabs, hide inactive ones to preserve state. + Active tab gets full dimensions; hidden tabs stay in DOM but invisible. */ plugins.map(p => { const Comp = tabComponents[p.name]; if (!Comp) return null; + const isActive = activeTab === p.name; return (
diff --git a/web/src/plugins/radio/RadioTab.tsx b/web/src/plugins/radio/RadioTab.tsx index 728ec4f..60b9c89 100644 --- a/web/src/plugins/radio/RadioTab.tsx +++ b/web/src/plugins/radio/RadioTab.tsx @@ -89,6 +89,7 @@ export default function RadioTab({ data }: { data: any }) { const [volume, setVolume] = useState(0.5); const [voiceStats, setVoiceStats] = useState(null); const [showConnModal, setShowConnModal] = useState(false); + const [containerVisible, setContainerVisible] = useState(false); const searchTimeout = useRef>(undefined); const volumeTimeout = useRef>(undefined); const selectedGuildRef = useRef(selectedGuild); @@ -211,15 +212,47 @@ export default function RadioTab({ data }: { data: any }) { .catch(() => setStationsLoading(false)); }; + // ── Watch container visibility (detects tab becoming active) ── + useEffect(() => { + const el = containerRef.current; + if (!el) return; + + // Check immediately + if (el.clientWidth > 0 && el.clientHeight > 0) { + setContainerVisible(true); + } + + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + const { width, height } = entry.contentRect; + if (width > 0 && height > 0) { + setContainerVisible(true); + } + } + }); + observer.observe(el); + + return () => observer.disconnect(); + }, []); + // ── Initialize globe ── useEffect(() => { if (!containerRef.current || places.length === 0) return; + // If container is hidden (display:none), wait for it to become visible + const cw = containerRef.current.clientWidth; + const ch = containerRef.current.clientHeight; + if (globeRef.current) { globeRef.current.pointsData(places); + // Re-apply dimensions in case we were hidden during init + if (cw > 0 && ch > 0) globeRef.current.width(cw).height(ch); return; } + // Don't initialize globe with zero dimensions — containerVisible will re-trigger + if (cw === 0 || ch === 0) return; + // Read accent color from theme const initStyle = getComputedStyle(containerRef.current.parentElement!); const initRgb = initStyle.getPropertyValue('--accent-rgb').trim() || '230, 126, 34'; @@ -286,21 +319,28 @@ export default function RadioTab({ data }: { data: any }) { const onResize = () => { if (containerRef.current && globeRef.current) { - globeRef.current - .width(containerRef.current.clientWidth) - .height(containerRef.current.clientHeight); + const w = containerRef.current.clientWidth; + const h = containerRef.current.clientHeight; + if (w > 0 && h > 0) { + globeRef.current.width(w).height(h); + } } }; window.addEventListener('resize', onResize); + // ResizeObserver: detects when tab becomes visible (0×0 → real size) + const resizeObserver = new ResizeObserver(() => onResize()); + resizeObserver.observe(el); + return () => { controls.removeEventListener('change', onControlsChange); el.removeEventListener('mousedown', onInteract); el.removeEventListener('touchstart', onInteract); el.removeEventListener('wheel', onInteract); window.removeEventListener('resize', onResize); + resizeObserver.disconnect(); }; - }, [places, pauseRotation]); + }, [places, pauseRotation, containerVisible]); // ── Play handler ── const handlePlay = useCallback(async (