grinat / leaflet-simple-map-screenshoter

Leaflet plugin which take screenshot of map
MIT License
71 stars 19 forks source link

React Functional implementation #16

Closed jonra1993 closed 3 years ago

jonra1993 commented 3 years ago

Hi, thanks for creating this library and make it public. I want to know if you can suggest any way to implement this library using React functional with hooks.

grinat commented 3 years ago

Something like this

function MyMap(props) {
  const mapRef = useRef(null);
  const map = useRef(null);

  // if dom element exist and map not created
  if (myRef.current && !map.current) {
     // create map
     map.current = L.map(myRef.current ).setView([51.505, -0.09], 13)
     L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
    attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
    maxZoom: 18,
    id: 'mapbox/streets-v11',
    tileSize: 512,
    zoomOffset: -1,
}).addTo(map.current)

     // connect plugin
    L.simpleMapScreenshoter().addTo(map.current)
  }

  return (
    <div>
      <div ref={mapRef} style={ height: '180px' }/>
    </div>
  );
}
jonra1993 commented 3 years ago

Thanks, @grinat I have also created another version using material UI and react-leaflet maybe it can help other people

import React, { useState, useRef, useEffect } from 'react';
import {
    makeStyles,
    Dialog,
    Button,
    DialogTitle,
    DialogContent,
    DialogActions,
} from '@material-ui/core';
import PropTypes from 'prop-types';
import {
    MapContainer,
    TileLayer,
    Marker,
    Popup,
    useMapEvents,
    useMap
} from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
import * as L from 'leaflet'
import { Control } from 'leaflet'
import { SimpleMapScreenshoter } from 'leaflet-simple-map-screenshoter'
import { createControlComponent, useLeafletContext } from '@react-leaflet/core'
import fileSaver from 'file-saver'

const useStyles = makeStyles((theme) => ({
    root: {
        marginLeft: 0,
        marginRight: 0,
        height: '100%',
        width: '100%'
    },
    textField: {
        width: '100%',
        marginTop: theme.spacing(1),
        marginBottom: theme.spacing(1),
        fontFamily: 'AIGFutura-Medium',
        font: '15px AIGFutura-Medium'
    },
}));

export const VenueLocationIcon = L.icon({
    iconUrl: require("src/assets/icons/venue_location_icon.svg"),
    iconRetinaUrl: require("src/assets/icons/venue_location_icon.svg"),
    iconAnchor: null,
    shadowUrl: null,
    shadowSize: null,
    shadowAnchor: null,
    iconSize: [40, 40],
    className: "leaflet-venue-icon",
});

const Circle = (props) => {
    const context = useLeafletContext()

    useEffect(() => {
        const container = context.layerContainer || context.map;
        const circle = new L.circle(props.center, { radius: props.radius })
        container.addLayer(circle);

        return () => {
            container.removeLayer(circle)
        }
    })

    return null
}

const SimpleMap = ({refInput}) => {
    const mapSimple = useMap()
    const simpleMapScreenshoter = L.simpleMapScreenshoter({ hidden: true}).addTo(mapSimple);
    const { current } = refInput;

    const takeScreenshot = async () => {
        simpleMapScreenshoter.takeScreen('blob')
        .then(image => {
            //console.log('image', image);
            fileSaver.saveAs(image, 'map.png')
            //saveAs(blob, 'screen.png')
        })
        .catch(e => {
            console.log(e.toString())
        })
    }

    useEffect(() => {
        current.addEventListener('click', takeScreenshot)
        return () => {
            current.removeEventListener('click', ()=>{})
        }
    }, [refInput])

    return (
        null
    )
}

const GeoMap = ({ open, handleClose, guessPosition }) => {
    const classes = useStyles();
    const buttonRef = useRef(null)

    const LocationMarker = () => {
        const map = useMapEvents({
            click: () => {
                guessPosition !== null && map.flyTo(guessPosition, map.getZoom())
            }
        })

        return guessPosition === null ? null : (
            <Marker position={guessPosition} icon={VenueLocationIcon}>
                <Popup>Ubicación del invitado</Popup>
            </Marker>
        )
    }

    return (
        <Dialog
            fullWidth={true}
            maxWidth={'lg'}
            open={open}
            onClose={handleClose}
            aria-labelledby="max-width-dialog-title"
            scroll={'paper'}
        >
            <DialogTitle id="max-width-dialog-title">Mapa</DialogTitle>
            <DialogContent className={classes.root} >
                <MapContainer
                    id='myMap'
                    style={{
                        height: '37vw',
                    }}
                    center={guessPosition}
                    zoom={20}
                    scrollWheelZoom={true}
                >
                    <TileLayer
                        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                    />
                    <LocationMarker />
                    <Circle center={guessPosition} radius={30} />
                    <SimpleMap refInput={buttonRef} />
                </MapContainer>
            </DialogContent>
            <DialogActions>
                <Button ref={buttonRef} >
                    Snapshot
                </Button>
                <Button onClick={handleClose} >
                    Close
                </Button>
            </DialogActions>
        </Dialog >
    )
}

GeoMap.propTypes = {
    open: PropTypes.bool,
    guessPosition: PropTypes.array.isRequired,
    handleClose: PropTypes.func,
}

GeoMap.defaultProps = {
    open: false,
    guessPosition: [-0.29067, -78.5454],
    handleClose: () => { },
};

export default GeoMap