demarches-normaliennes/app/javascript/components/shared/maplibre/MapLibre.tsx
2024-09-23 14:45:36 +02:00

148 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
useState,
useContext,
useRef,
useEffect,
useMemo,
ReactNode,
createContext,
useCallback
} from 'react';
import { createPortal } from 'react-dom';
import { Map, NavigationControl } from 'maplibre-gl';
import type { StyleSpecification, IControl } from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import invariant from 'tiny-invariant';
import { useStyle, useElementVisible } from './hooks';
import { StyleSwitch } from './StyleControl';
const Context = createContext<{ map?: Map | null }>({});
type MapLibreProps = {
layers: string[];
children: ReactNode;
};
export function useMapLibre() {
const context = useContext(Context);
invariant(context.map, 'Maplibre not initialized');
return context.map;
}
export function MapLibre({ children, layers }: MapLibreProps) {
const isSupported = useMemo(() => isWebglSupported(), []);
const containerRef = useRef<HTMLDivElement>(null);
const visible = useElementVisible(containerRef);
const [map, setMap] = useState<Map | null>();
const [styleControlElement, setStyleControlElement] =
useState<HTMLElement | null>(null);
const onStyleChange = useCallback(
(style: StyleSpecification) => {
if (map) {
map.setStyle(style);
}
},
[map]
);
const { style, ...mapStyleProps } = useStyle(layers, onStyleChange);
useEffect(() => {
if (isSupported && visible && !map) {
invariant(containerRef.current, 'Map container not found');
const map = new Map({
container: containerRef.current,
style
});
map.addControl(new NavigationControl({}), 'top-right');
const styleControl = new ReactControl();
map.addControl(styleControl, 'bottom-left');
map.on('load', () => {
setMap(map);
setStyleControlElement(styleControl.container);
});
}
}, [map, style, visible, isSupported]);
if (!isSupported) {
return (
<div
style={{ marginBottom: '20px' }}
className="outdated-browser-banner site-banner"
>
<div className="container">
<div className="site-banner-icon"></div>
<div className="site-banner-text">
Nous ne pouvons pas afficher la carte car elle est incompatible avec
votre navigateur. Nous vous conseillons de le mettre à jour ou
dutiliser{' '}
<a
href="https://browser-update.org/fr/update.html"
target="_blank"
rel="noopener noreferrer"
>
un navigateur plus récent
</a>
.
</div>
</div>
</div>
);
}
return (
<Context.Provider value={{ map }}>
<div ref={containerRef} style={{ height: '500px' }}>
{styleControlElement != null
? createPortal(
<StyleSwitch styleId={style.id} {...mapStyleProps} />,
styleControlElement
)
: null}
{map ? children : null}
</div>
</Context.Provider>
);
}
function isWebglSupported() {
if (window.WebGLRenderingContext) {
const canvas = document.createElement('canvas');
try {
// Note that { failIfMajorPerformanceCaveat: true } can be passed as a second argument
// to canvas.getContext(), causing the check to fail if hardware rendering is not available. See
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
// for more details.
const context = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (context && typeof context.getParameter == 'function') {
return true;
}
} catch (e) {
// WebGL is supported, but disabled
}
return false;
}
// WebGL not supported
return false;
}
export class ReactControl implements IControl {
#container: HTMLElement | null = null;
get container(): HTMLElement | null {
return this.#container;
}
onAdd(): HTMLElement {
this.#container = document.createElement('div');
this.#container.className = 'maplibregl-ctrl maplibregl-ctrl-group ds-ctrl';
return this.#container;
}
onRemove(): void {
this.#container?.remove();
this.#container = null;
}
}