Skip to content

Commit

Permalink
feat: add example for drawing tools (#220)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrMetalWood authored Feb 15, 2024
1 parent 69b2373 commit 75e91c4
Show file tree
Hide file tree
Showing 14 changed files with 646 additions and 0 deletions.
36 changes: 36 additions & 0 deletions examples/drawing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Drawing Tools Example

This example shows how to use the [google.maps.drawing.DrawingManager][drawing-manager] to draw shapes or markers on the map. In addition the example implements an undo/redo flow for the drawing tools. If you only want to add the the drawing tools to your map take a look at the `use-drawing-manager` hook.

## 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="<YOUR API KEY HERE>"
```

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
[drawing-manager]: https://developers.google.com/maps/documentation/javascript/drawinglayer
31 changes: 31 additions & 0 deletions examples/drawing/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>Drawing Tools Example</title>
<meta name="description" content="Drawing Example" />
<style>
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module">
import '@vis.gl/react-google-maps/examples.css';
import '@vis.gl/react-google-maps/examples.js';
import {renderToDom} from './src/app';

renderToDom(document.querySelector('#app'));
</script>
</body>
</html>
14 changes: 14 additions & 0 deletions examples/drawing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"type": "module",
"dependencies": {
"@vis.gl/react-google-maps": "*",
"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"
}
}
48 changes: 48 additions & 0 deletions examples/drawing/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import {createRoot} from 'react-dom/client';
import {
APIProvider,
ControlPosition,
Map,
MapControl
} from '@vis.gl/react-google-maps';

import {UndoRedoControl} from './undo-redo-control';
import {useDrawingManager} from './use-drawing-manager';
import ControlPanel from './control-panel';

const API_KEY =
globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string);

const App = () => {
const drawingManager = useDrawingManager();

return (
<>
<Map
defaultZoom={3}
defaultCenter={{lat: 22.54992, lng: 0}}
gestureHandling={'greedy'}
disableDefaultUI={true}
/>

<ControlPanel />

<MapControl position={ControlPosition.TOP_CENTER}>
<UndoRedoControl drawingManager={drawingManager} />
</MapControl>
</>
);
};

export default App;

export function renderToDom(container: HTMLElement) {
const root = createRoot(container);

root.render(
<APIProvider apiKey={API_KEY}>
<App />
</APIProvider>
);
}
29 changes: 29 additions & 0 deletions examples/drawing/src/control-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from 'react';

function ControlPanel() {
return (
<div className="control-panel">
<h3>Drawing Tools Example</h3>
<p>
Shows how to use the Google Maps drawing tools and implements an
undo/redo flow to show how to integrate the drawing manager and its
events into the state of a react-application.
</p>
<div className="links">
<a
href="https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/drawing"
target="_new">
Try on CodeSandbox ↗
</a>

<a
href="https://github.com/visgl/react-google-maps/tree/main/examples/drawing"
target="_new">
View Code ↗
</a>
</div>
</div>
);
}

export default React.memo(ControlPanel);
79 changes: 79 additions & 0 deletions examples/drawing/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export type OverlayGeometry =
| google.maps.Marker
| google.maps.Polygon
| google.maps.Polyline
| google.maps.Rectangle
| google.maps.Circle;

export interface DrawResult {
type: google.maps.drawing.OverlayType;
overlay: OverlayGeometry;
}

export interface Snapshot {
radius?: number;
center?: google.maps.LatLngLiteral;
position?: google.maps.LatLngLiteral;
path?: Array<google.maps.LatLng>;
bounds?: google.maps.LatLngBoundsLiteral;
}

export interface Overlay {
type: google.maps.drawing.OverlayType;
geometry: OverlayGeometry;
snapshot: Snapshot;
}

export interface State {
past: Array<Array<Overlay>>;
now: Array<Overlay>;
future: Array<Array<Overlay>>;
}

export enum DrawingActionKind {
SET_OVERLAY = 'SET_OVERLAY',
UPDATE_OVERLAYS = 'UPDATE_OVERLAYS',
UNDO = 'UNDO',
REDO = 'REDO'
}

export interface ActionWithTypeOnly {
type: Exclude<DrawingActionKind, DrawingActionKind.SET_OVERLAY>;
}

export interface SetOverlayAction {
type: DrawingActionKind.SET_OVERLAY;
payload: DrawResult;
}

export type Action = ActionWithTypeOnly | SetOverlayAction;

export function isCircle(
overlay: OverlayGeometry
): overlay is google.maps.Circle {
return (overlay as google.maps.Circle).getCenter !== undefined;
}

export function isMarker(
overlay: OverlayGeometry
): overlay is google.maps.Marker {
return (overlay as google.maps.Marker).getPosition !== undefined;
}

export function isPolygon(
overlay: OverlayGeometry
): overlay is google.maps.Polygon {
return (overlay as google.maps.Polygon).getPath !== undefined;
}

export function isPolyline(
overlay: OverlayGeometry
): overlay is google.maps.Polyline {
return (overlay as google.maps.Polyline).getPath !== undefined;
}

export function isRectangle(
overlay: OverlayGeometry
): overlay is google.maps.Rectangle {
return (overlay as google.maps.Rectangle).getBounds !== undefined;
}
60 changes: 60 additions & 0 deletions examples/drawing/src/undo-redo-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, {useReducer, useRef} from 'react';
import {useMap} from '@vis.gl/react-google-maps';

import reducer, {
useDrawingManagerEvents,
useOverlaySnapshots
} from './undo-redo';

import {DrawingActionKind} from './types';

interface Props {
drawingManager: google.maps.drawing.DrawingManager | null;
}

export const UndoRedoControl = ({drawingManager}: Props) => {
const map = useMap();

const [state, dispatch] = useReducer(reducer, {
past: [],
now: [],
future: []
});

// We need this ref to prevent infinite loops in certain cases.
// For example when the radius of circle is set via code (and not by user interaction)
// the radius_changed event gets triggered again. This would cause an infinite loop.
// This solution can be improved by comparing old vs. new values. For now we turn
// off the "updating" when snapshot changes are applied back to the overlays.
const overlaysShouldUpdateRef = useRef<boolean>(false);

useDrawingManagerEvents(drawingManager, overlaysShouldUpdateRef, dispatch);
useOverlaySnapshots(map, state, overlaysShouldUpdateRef);

return (
<div className="drawing-history">
<button
onClick={() => dispatch({type: DrawingActionKind.UNDO})}
disabled={!state.past.length}>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24">
<path d="M280-200v-80h284q63 0 109.5-40T720-420q0-60-46.5-100T564-560H312l104 104-56 56-200-200 200-200 56 56-104 104h252q97 0 166.5 63T800-420q0 94-69.5 157T564-200H280Z" />
</svg>
</button>
<button
onClick={() => dispatch({type: DrawingActionKind.REDO})}
disabled={!state.future.length}>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24">
<path d="M396-200q-97 0-166.5-63T160-420q0-94 69.5-157T396-640h252L544-744l56-56 200 200-200 200-56-56 104-104H396q-63 0-109.5 40T240-420q0 60 46.5 100T396-280h284v80H396Z" />
</svg>
</button>
</div>
);
};
Loading

0 comments on commit 75e91c4

Please sign in to comment.