From 0909f82dc77792f5055382fed3dd58964f1a8499 Mon Sep 17 00:00:00 2001 From: Martin Schuhfuss Date: Sat, 30 Mar 2024 01:09:55 +0100 Subject: [PATCH 1/2] refactor: replace map-div with `gmp-map` custom element --- examples/basic-map/src/app.tsx | 2 +- examples/multiple-maps/src/app.tsx | 2 +- src/components/map/index.tsx | 29 +++++++++++++++++++++++--- src/components/map/use-map-instance.ts | 23 +++++++++++++++----- 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/examples/basic-map/src/app.tsx b/examples/basic-map/src/app.tsx index 191c14e4..fb4009b3 100644 --- a/examples/basic-map/src/app.tsx +++ b/examples/basic-map/src/app.tsx @@ -8,7 +8,7 @@ const API_KEY = globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); const App = () => ( - + { }, []); return ( - +
) => { } return ( -
{map ? ( @@ -196,7 +198,28 @@ export const Map = (props: PropsWithChildren) => { {children} ) : null} -
+ ); }; + Map.deckGLViewProps = true; + +type CustomElement = Partial< + T & + DOMAttributes & + RefAttributes & { + // for whatever reason, anything else doesn't work as children + // of a custom element, so we allow `any` here + // eslint-disable-next-line @typescript-eslint/no-explicit-any + children: any; + } +>; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace JSX { + interface IntrinsicElements { + ['gmp-map']: CustomElement; + } + } +} diff --git a/src/components/map/use-map-instance.ts b/src/components/map/use-map-instance.ts index 9a1f10fd..2e842963 100644 --- a/src/components/map/use-map-instance.ts +++ b/src/components/map/use-map-instance.ts @@ -11,6 +11,16 @@ import { useTrackedCameraStateRef } from './use-tracked-camera-state-ref'; +function useMapElementDefined() { + const [isDefined, setIsDefined] = useState(false); + + useEffect(() => { + customElements.whenDefined('gmp-map').then(() => setIsDefined(true)); + }, []); + + return isDefined; +} + /** * The main hook takes care of creating map-instances and registering them in * the api-provider context. @@ -23,12 +33,13 @@ export function useMapInstance( context: APIProviderContextValue ): readonly [ map: google.maps.Map | null, - containerRef: Ref, + containerRef: Ref, cameraStateRef: CameraStateRef ] { const apiIsLoaded = useApiIsLoaded(); + const mapElementDefined = useMapElementDefined(); const [map, setMap] = useState(null); - const [container, containerRef] = useCallbackRef(); + const [container, containerRef] = useCallbackRef(); const cameraStateRef = useTrackedCameraStateRef(map); @@ -63,11 +74,13 @@ export function useMapInstance( // create the map instance and register it in the context useEffect( () => { - if (!container || !apiIsLoaded) return; + if (!container || !apiIsLoaded || !mapElementDefined) return; const {addMapInstance, removeMapInstance} = context; const mapId = props.mapId; - const newMap = new google.maps.Map(container, mapOptions); + // const newMap = new google.maps.Map(container, mapOptions); + const newMap = container.innerMap; + newMap.setOptions(mapOptions); setMap(newMap); addMapInstance(newMap, id); @@ -105,7 +118,7 @@ export function useMapInstance( // changes should be ignored // - mapOptions has special hooks that take care of updating the options // eslint-disable-next-line react-hooks/exhaustive-deps - [container, apiIsLoaded, id, props.mapId] + [container, apiIsLoaded, mapElementDefined, id, props.mapId] ); return [map, containerRef, cameraStateRef] as const; From e41a74c58dcc4c94f3e72e2fbc0c97df2cedd8a4 Mon Sep 17 00:00:00 2001 From: Martin Schuhfuss Date: Wed, 3 Apr 2024 22:00:52 +0200 Subject: [PATCH 2/2] add simple ECL compatibility example --- examples/ecl-compat/README.md | 38 +++++++++++++++++++++++ examples/ecl-compat/index.html | 31 ++++++++++++++++++ examples/ecl-compat/package.json | 15 +++++++++ examples/ecl-compat/src/app.tsx | 37 ++++++++++++++++++++++ examples/ecl-compat/src/control-panel.tsx | 28 +++++++++++++++++ examples/ecl-compat/vite.config.js | 17 ++++++++++ 6 files changed, 166 insertions(+) create mode 100644 examples/ecl-compat/README.md create mode 100644 examples/ecl-compat/index.html create mode 100644 examples/ecl-compat/package.json create mode 100644 examples/ecl-compat/src/app.tsx create mode 100644 examples/ecl-compat/src/control-panel.tsx create mode 100644 examples/ecl-compat/vite.config.js diff --git a/examples/ecl-compat/README.md b/examples/ecl-compat/README.md new file mode 100644 index 00000000..8cee5537 --- /dev/null +++ b/examples/ecl-compat/README.md @@ -0,0 +1,38 @@ +# Basic Google Maps Setup Example + +![image](https://user-images.githubusercontent.com/39244966/208682692-d5b23518-9e51-4a87-8121-29f71e41c777.png) + +This is an example to show how to setup a simple Google Maps Map with the `` component of the Google Maps React +library. + +## Google Maps API key + +This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. +See [the official documentation][get-api-key] on how to create and configure your own key. + +The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a +file named `.env` in the example directory with the following content: + +```shell title=".env" +GOOGLE_MAPS_API_KEY="" +``` + +If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) + +## Development + +Go into the example-directory and run + +```shell +npm install +``` + +To start the example with the local library run + +```shell +npm run start-local +``` + +The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) + +[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key diff --git a/examples/ecl-compat/index.html b/examples/ecl-compat/index.html new file mode 100644 index 00000000..7dba1401 --- /dev/null +++ b/examples/ecl-compat/index.html @@ -0,0 +1,31 @@ + + + + + + Example: + + + + +
+ + + diff --git a/examples/ecl-compat/package.json b/examples/ecl-compat/package.json new file mode 100644 index 00000000..cb759f0b --- /dev/null +++ b/examples/ecl-compat/package.json @@ -0,0 +1,15 @@ +{ + "type": "module", + "dependencies": { + "@googlemaps/extended-component-library": "^0.6.9", + "@vis.gl/react-google-maps": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "vite": "^5.0.4" + }, + "scripts": { + "start": "vite", + "start-local": "vite --config ../vite.config.local.js", + "build": "vite build" + } +} diff --git a/examples/ecl-compat/src/app.tsx b/examples/ecl-compat/src/app.tsx new file mode 100644 index 00000000..cb86753d --- /dev/null +++ b/examples/ecl-compat/src/app.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import {createRoot} from 'react-dom/client'; +import {APIProvider, Map} from '@vis.gl/react-google-maps'; +import {RouteOverview} from '@googlemaps/extended-component-library/react'; +import ControlPanel from './control-panel'; + +const API_KEY = + globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); + +const App = () => ( + + + + + + +); +export default App; + +export function renderToDom(container: HTMLElement) { + const root = createRoot(container); + + root.render( + + + + ); +} diff --git a/examples/ecl-compat/src/control-panel.tsx b/examples/ecl-compat/src/control-panel.tsx new file mode 100644 index 00000000..734fd9cd --- /dev/null +++ b/examples/ecl-compat/src/control-panel.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; + +function ControlPanel() { + return ( +
+

Example Template

+

+ Add a brief description of the example here and update the link below +

+ + +
+ ); +} + +export default React.memo(ControlPanel); diff --git a/examples/ecl-compat/vite.config.js b/examples/ecl-compat/vite.config.js new file mode 100644 index 00000000..522c6cb9 --- /dev/null +++ b/examples/ecl-compat/vite.config.js @@ -0,0 +1,17 @@ +import {defineConfig, loadEnv} from 'vite'; + +export default defineConfig(({mode}) => { + const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); + + return { + define: { + 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) + }, + resolve: { + alias: { + '@vis.gl/react-google-maps/examples.js': + 'https://visgl.github.io/react-google-maps/scripts/examples.js' + } + } + }; +});