mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 20:35:07 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Bug fix ## What is the current behavior? Resolves #46026 1. **Memory Leak**: The `<Globe />` component in the `www` app creates a new `debouncedResize` event listener every time the theme changes, but in the `useEffect` cleanup function, it attempts to remove `onResize` instead. Because these are two different function references in memory, the listener is never removed, causing a memory leak that grows with theme toggling. 2. **Resolution Scaling**: The WebGL `cobe` globe instance is not notified of width changes dynamically. When a user resizes their browser window, the `<canvas>` resizes via CSS, but the internal 3D rendering stretches and becomes distorted because `state.width` and `state.height` are not updated inside the `onRender` loop. 3. **Local Dev Rendering**: Due to async CSS loading in Next.js development environments, `offsetWidth` evaluates to 0 on initial mount, causing the globe to render invisibly until manually resized. ## What is the new behavior? 1. **Memory Leak Fixed**: Switched from a global `window.addEventListener('resize')` to a `ResizeObserver` attached directly to the canvas container, properly disconnecting it on unmount. 2. **Resolution Scaling Fixed**: Added `state.width` and `state.height` updates directly inside the `onRender` callback. The globe now maintains its aspect ratio and high resolution dynamically as the user resizes the browser. 3. **Local Dev Rendering Fixed**: The `ResizeObserver` correctly triggers a size recalculation exactly when the local Next.js CSS finishes injecting, allowing the globe to render immediately in local dev. 4. **Safe Timeout**: Wrapped the initial opacity update in a null check and correctly store/clear the `timeoutId` during unmount. ## Additional context ## Before https://github.com/user-attachments/assets/813e077d-bcf4-4584-b3da-46c01b6af001 https://github.com/user-attachments/assets/73cef575-27e3-44d2-a191-fd192dec3ef9 ## After https://github.com/user-attachments/assets/cd68c66d-6a35-43d0-971f-04627ef532fb <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Globe now adapts instantly to container size changes for smoother, consistent rendering. * Improved lifecycle cleanup to reliably stop background tasks and clear timers, reducing memory leaks. * Enhanced stability and performance for longer-running sessions of the Globe component. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46025?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
import createGlobe from 'cobe'
|
|
import { useTheme } from 'next-themes'
|
|
import { useEffect, useRef } from 'react'
|
|
|
|
const Globe = () => {
|
|
const { resolvedTheme } = useTheme()
|
|
const canvasRef = useRef<any | null>(null)
|
|
|
|
useEffect(() => {
|
|
let rotation = 0
|
|
let width = 0
|
|
let previousWidth = 0
|
|
|
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
for (const entry of entries) {
|
|
width = Math.round(entry.contentRect.width)
|
|
}
|
|
})
|
|
|
|
if (canvasRef.current) {
|
|
resizeObserver.observe(canvasRef.current)
|
|
width = Math.round(canvasRef.current.getBoundingClientRect().width)
|
|
}
|
|
|
|
const cobe = createGlobe(canvasRef.current, {
|
|
devicePixelRatio: 2,
|
|
width: width * 2,
|
|
height: width * 2,
|
|
phi: 0,
|
|
theta: 0.3,
|
|
dark: resolvedTheme?.includes('dark') ? 1 : 0,
|
|
diffuse: 3,
|
|
scale: 1,
|
|
opacity: 0.8,
|
|
mapSamples: 20000,
|
|
mapBrightness: 4,
|
|
baseColor: [255 / 255, 255 / 255, 255 / 255],
|
|
markerColor: [62 / 255, 207 / 255, 142 / 255],
|
|
glowColor: [255 / 255, 255 / 255, 255 / 255],
|
|
markers: [
|
|
{ location: [53.4084, 2.9916], size: 0.06 },
|
|
{ location: [1.3521, 103.8198], size: 0.06 },
|
|
{ location: [-40.9006, 174.886], size: 0.06 },
|
|
{ location: [14.0583, 108.2772], size: 0.06 },
|
|
{ location: [37.7749, -122.4194], size: 0.06 },
|
|
{ location: [41.3874, 2.1686], size: 0.06 },
|
|
{ location: [49.2827, -123.1207], size: 0.06 },
|
|
{ location: [-36.9848, 143.3906], size: 0.06 },
|
|
{ location: [42.3601, -71.0589], size: 0.06 },
|
|
{ location: [52.52, 13.405], size: 0.06 },
|
|
{ location: [33.749, -84.388], size: 0.06 },
|
|
{ location: [35.6762, 139.6503], size: 0.06 },
|
|
{ location: [9.19, -75.0152], size: 0.06 },
|
|
{ location: [-25.2521, -52.0215], size: 0.06 },
|
|
],
|
|
onRender: (state) => {
|
|
state.phi = rotation
|
|
rotation += 0.0025
|
|
if (width !== previousWidth) {
|
|
state.width = width * 2
|
|
state.height = width * 2
|
|
previousWidth = width
|
|
}
|
|
},
|
|
})
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
if (canvasRef.current) {
|
|
canvasRef.current.style.opacity = '0.8'
|
|
}
|
|
}, 10)
|
|
|
|
return () => {
|
|
resizeObserver.disconnect()
|
|
clearTimeout(timeoutId)
|
|
cobe.destroy()
|
|
}
|
|
}, [resolvedTheme])
|
|
|
|
return (
|
|
<canvas
|
|
ref={canvasRef}
|
|
className="absolute inset-0 w-full h-full opacity-0 transition-opacity object-contain"
|
|
/>
|
|
)
|
|
}
|
|
|
|
export default Globe
|