mui-toolpad icon indicating copy to clipboard operation
mui-toolpad copied to clipboard

Support map component in Toolpad

Open prakhargupta1 opened this issue 2 years ago • 5 comments

Duplicates

  • [X] I have searched the existing issues

Latest version

  • [X] I have tested the latest version

Summary 💡

I want to create an app that uses a map component to show start location and destination.

Note: Currently, it is supported through custom components: https://mui.com/toolpad/studio/how-to-guides/map-display/

Examples 🌈

No response

Motivation 🔦

No response

prakhargupta1 avatar Aug 27 '22 05:08 prakhargupta1

There's a way to use the Custom components to build a Map component from scratch inside Toolpad. Here's an example using Leaflet:

import * as React from "react";
import { createComponent } from "@mui/toolpad-core";

import * as L from "https://esm.sh/leaflet";

export interface LeafletProps {
  lat: number;
  long: number;
  zoom: number;
}

async function createLeafletStyles(doc) {
  let styles = doc.getElementById("leaflet-css");
  if (styles) {
    return;
  }
  const res = await fetch("https://esm.sh/leaflet/dist/leaflet.css");
  if (!res.ok) {
    throw new Error(`HTTP ${res.status}: ${res.statusText}`);
  }
  const css = await res.text();
  styles = doc.createElement("style");
  styles.id = "leaflet-css";
  styles.appendChild(doc.createTextNode(css));
  doc.head.appendChild(styles);
}

function Leaflet({ lat, long, zoom }: LeafletProps) {
  const root = React.useRef(null);
  const mapRef = React.useRef<any>();
  const [stylesInitialized, setStylesIitialized] = React.useState(false);
  const [error, setError] = React.useState<Error>();

  React.useEffect(() => {
    const doc = root.current.ownerDocument;
    createLeafletStyles(doc).then(
      () => setStylesIitialized(true),
      (err) => setError(err)
    );
  }, []);

  React.useEffect(() => {
    if (!mapRef.current && stylesInitialized) {
      mapRef.current = L.map(root.current);
      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
        maxZoom: 19,
        attribution: "© OpenStreetMap",
      }).addTo(mapRef.current);
    }

    if (mapRef.current) {
      mapRef.current.setView([lat, long], zoom);
    }
  }, [stylesInitialized, lat, long, zoom]);

  return (
    <div style={{ height: 400, width: 600 }}>
      {error ? (
        error.message
      ) : (
        <div style={{ width: "100%", height: "100%" }} ref={root} />
      )}
    </div>
  );
}

export default createComponent(Leaflet, {
  argTypes: {
    lat: {
      typeDef: { type: "number" },
      defaultValue: 51.505,
    },
    long: {
      typeDef: { type: "number" },
      defaultValue: -0.09,
    },
    zoom: {
      typeDef: { type: "number" },
      defaultValue: 13,
    },
  },
});

bharatkashyap avatar Sep 05 '22 21:09 bharatkashyap

Nice!! Thanks, I'll check it out.

prakhargupta1 avatar Sep 06 '22 07:09 prakhargupta1

Can we make it permanent in Toolpad's component library? Another solution could be that we provide it as a Custom component by default. It will give user a better idea of what custom component is.

A user shared that he got confused what Components was, until he discovered component library. This can give users an example to understand about custom components.

I am opening this issue.

prakhargupta1 avatar Jan 03 '23 12:01 prakhargupta1

Sure, some parts that are missing in this implementation that probably must make it into a native component

  • When the user drags or zooms, it doesn't update the lat, long, zoom props.
  • a readonly mode that only serves as output, e.g. the user can't use the mouse to drag or zoom
  • Are we sure we want to use leaflet? If we make it a core component we may be less constraint in which library we can import.

Janpot avatar Jan 04 '23 12:01 Janpot

top

casi-z avatar Feb 26 '24 12:02 casi-z