firecms icon indicating copy to clipboard operation
firecms copied to clipboard

Geopoint support

Open kongweiying2 opened this issue 3 years ago • 18 comments

I understand that Geopoint isn't currently supported, however is there a potential roadmap for adding support? Or does the custom view support allow us to add it ourselves?

kongweiying2 avatar Aug 03 '21 05:08 kongweiying2

Hi @TheAussieStew Geopoint support is currently not in the roadmap. In any case, you should be able to implement your own custom field supporting it :) https://firecms.co/docs/entities/custom_fields Let me know if you have an implementation so we can try to merge it into the library

fgatti675 avatar Aug 05 '21 13:08 fgatti675

Sounds good!

kongweiying2 avatar Sep 15 '21 05:09 kongweiying2

I am marking this feature as a good first issue. It can be implemented in the same way that a developer would implement a "custom field": https://firecms.co/docs/entities/custom_fields That means that it is not necessary to develop the feature in the source code (though that would be the best!)

fgatti675 avatar Nov 23 '21 11:11 fgatti675

Anyone manage to get a working implementation before I go and make one myself tonight? Worth an ask :)

SKLn-Rad avatar Jun 04 '22 11:06 SKLn-Rad

H @SKLn-Rad, as far as I know this has not been implemented yet! It would be great if you can share it when you are done so we can add it to the core

fgatti675 avatar Jun 05 '22 08:06 fgatti675

Any news on the status of this? Would love to see all Firestore data types supported. Could somebody please share a code snippet that would be required to add support for Firestore geopoints? Thanks!

jbxbergdev avatar Dec 23 '22 21:12 jbxbergdev

This feature is not planned at this moment. We are happy to merge PRs or to do sponsored developments!

fgatti675 avatar Dec 25 '22 18:12 fgatti675

Ok, thanks for your reply. Could you share how the workaround you suggested, using custom fields would work? Something really simple, like giving the user a text field where comma separated latitude,longitude can be entered would work. Thanks!

jbxbergdev avatar Dec 26 '22 18:12 jbxbergdev

Hi @jbxbergdev You have an example on how to implement a custom field here: https://firecms.co/docs/properties/custom_fields

fgatti675 avatar Dec 26 '22 22:12 fgatti675

@fgatti675 hm ok. I tried a custom field implementation as per your link for the type GeoPoint. However, in the CMS, I still get the error "Currently the field geopoint is not supported". Any idea what I'm doing wrong? Code:

export default function GeopointField({
                                                 property,
                                                 value,
                                                 setValue,
                                                 customProps,
                                                 touched,
                                                 error,
                                                 isSubmitting,
                                                 context,
                                                 ...props
                                             }: FieldProps<GeoPoint, any>) {


    return (
        <>
            <TextField required={property.validation?.required}
                       error={!!error}
                       disabled={isSubmitting}
                       label={property.name}
                       value={ value.latitude + "," + value.longitude }
                       onChange={(evt: any) => {
                        const latLon: string[] = evt.target.value.split(",");
                        const lat: number = parseFloat(latLon[0]);
                        const lon: number = parseFloat(latLon[1]);
                        const geopoint = new GeoPoint(lat, lon);
                        setValue(geopoint);
                       }}
                       helperText={error}
                       fullWidth
                       variant={"filled"}/>

            <FieldDescription property={property}/>
        </>

    );

}

// ....
const eventCollection = buildCollection<Event>({
  // (...)
  properties: {
   // (...)
    location: {
      name: "Ort",
      dataType: "geopoint",
      Field: GeopointField
    },
// (...)
});

jbxbergdev avatar Dec 29 '22 21:12 jbxbergdev

Hi @jbxbergdev did you also link the field in your corresponding property? { dataType: "geopoint", name: "...", Field: GeopointField}

fgatti675 avatar Dec 30 '22 12:12 fgatti675

@fgatti675 ah yeah, I did. I'll update the code snippet. So in the create/edit view the field is showing the error message.

jbxbergdev avatar Jan 02 '23 09:01 jbxbergdev

Here is a working implementation. I have made the following react jsx element.

interface Location {
  lat: number;
  lng: number;
}

interface MapProps {
  apiKey: string;
  location: Location;
  zoom: number;
  onLocationChange: (location: Location) => void;
  onZoomChange: (zoom: number) => void;
}

const GoogleMapLocationPicker = ({
  apiKey,
  location,
  zoom,
  onLocationChange,
  onZoomChange,
}: MapProps) => {
  const [map, setMap] = useState<google.maps.Map | null>(null);
  const [marker, setMarker] = useState<google.maps.Marker | null>(null);

  useEffect(() => {
    const initMap = () => {
      const mapOptions: google.maps.MapOptions = {
        center: location,
        zoom: zoom,
      };
      const map = new google.maps.Map(
        document.getElementById("map")!,
        mapOptions
      );
      setMap(map);

      const marker = new google.maps.Marker({
        position: location,
        map: map,
        draggable: true,
      });
      setMarker(marker);

      marker.addListener("dragend", () => {
        const position = marker.getPosition();
        const location = {
          lat: position!.lat(),
          lng: position!.lng(),
        };
        onLocationChange(location);
      });

      map.addListener("zoom_changed", () => {
        const zoom = map.getZoom();
        onZoomChange(zoom!);
      });
    };

    if (!window.google) {
      const script = document.createElement("script");
      script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}`;
      script.onload = initMap;
      document.body.appendChild(script);
    } else {
      initMap();
    }
  }, [apiKey, location, onLocationChange, onZoomChange, zoom]);

  return <div id="map" style={{ height: "500px" }} />;
};

export default GoogleMapLocationPicker;

This can be used in a custom field as follows:

const DEFAULT_GEO_POINT = {
  lat: 37.749692,
  lng:-122.415284
};
export default function GeopointField({
  property,
  value = new GeoPoint(DEFAULT_GEO_POINT.lat, DEFAULT_GEO_POINT.lng),
  setValue,
  customProps,
  touched,
  error,
  isSubmitting,
  context,
  ...props
}: FieldProps<GeoPoint, any>) {
  const [defaultposition, setDefaultPosition] = useState(DEFAULT_GEO_POINT);
  const [defaultzoom, setDefaultZoom] = useState(15);
  return (
    <>
      <p>{property.name}</p>
      <GoogleMapLocationPicker
        apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
        location={
          value["_lat"]
            ? { lat: value["_lat"], lng: value["_long"] }
            : defaultposition
        }
        zoom={defaultzoom}
        onZoomChange={(zoom) => {
          setDefaultZoom(zoom);
        }}
        onLocationChange={(location) => {
          setDefaultPosition({ lat: location.lat, lng: location.lng });
          setValue(new GeoPoint(location.lat, location.lng));
        }}
      />
      <FieldDescription property={property} />
    </>
  );
}

This can be added to your collection as follows:

export const interventionCollection = buildCollection<Intervention>({
 ...
  properties: {
    ...
    location: buildProperty({
      name: "User Location",
      Field: GeopointField,
      validation: { required: true },
      dataType: "geopoint",
      description: "Select the location",
    }),
  },
});

singhvedant111 avatar Apr 06 '23 08:04 singhvedant111

Awesome! Think you could submit a PR? You can start by checking how other fields are integrated in this file: form_field_configs.tsx

fgatti675 avatar Apr 06 '23 09:04 fgatti675

Sure, I'll try and do that sometime this week @fgatti675 .

singhvedant111 avatar Apr 06 '23 12:04 singhvedant111

@singhvedant111 Did you ever get around to this?

lukecarbis avatar Jun 13 '23 22:06 lukecarbis

@singhvedant111 – I like your map implementation. I ended up working something out based on yours that's a simple text input, accepting a comma separated latitude, longitude.

import React, { FunctionComponent } from "react";
import { FieldDescription, FieldProps, GeoPoint } from "firecms";
import {TextField} from "@mui/material";

const GeoPointField: FunctionComponent<FieldProps<GeoPoint>> = ({
    property,
    value,
    setValue,
    error,
    isSubmitting,
}) => {
    const geoPointToString = (geoPoint: GeoPoint) : string => {
        return `${geoPoint.latitude}, ${geoPoint.longitude}`;
    }

    const stringToGeoPoint = (str: string) : GeoPoint => {
        const [latitude, longitude] = str.split(',');
        return new GeoPoint(parseFloat(latitude), parseFloat(longitude));
    }

    return (
        <>
            <TextField
                required={property.validation?.required}
                error={!!error}
                disabled={isSubmitting}
                label={property.name}
                helperText={error}
                fullWidth
                variant={"filled"}
                value={value?.latitude && value.longitude ? geoPointToString(value) : ''} onChange={(evt) => {
                setValue(
                    stringToGeoPoint(evt.target.value)
                );
            }}/>
            <FieldDescription property={property} />
        </>
    );
}

export default GeoPointField;

lukecarbis avatar Jun 15 '23 10:06 lukecarbis

I'm having a bit of trouble implementing this custom field. The implementation complains that the field is not supported. Currently I have fallen back to using the string dataType and mapping in the component.

Are custom fields not supported? How does one use a different datatype?

BartlomiejLewandowski avatar Nov 25 '23 14:11 BartlomiejLewandowski