firecmsco / firecms

Awesome Firebase/Firestore-based CMS. The missing admin panel for your Firebase project!
https://firecms.co
Other
1.15k stars 187 forks source link

Geopoint support #112

Open kongweiying2 opened 3 years ago

kongweiying2 commented 3 years ago

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?

fgatti675 commented 3 years ago

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

kongweiying2 commented 3 years ago

Sounds good!

fgatti675 commented 2 years ago

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!)

SKLn-Rad commented 2 years ago

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

fgatti675 commented 2 years ago

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

jbxbergdev commented 1 year ago

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!

fgatti675 commented 1 year ago

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

jbxbergdev commented 1 year ago

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!

fgatti675 commented 1 year ago

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

jbxbergdev commented 1 year ago

@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
    },
// (...)
});
fgatti675 commented 1 year ago

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

jbxbergdev commented 1 year ago

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

singhvedant111 commented 1 year ago

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",
    }),
  },
});
fgatti675 commented 1 year ago

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

singhvedant111 commented 1 year ago

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

lukecarbis commented 1 year ago

@singhvedant111 Did you ever get around to this?

lukecarbis commented 1 year ago

@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;
BartlomiejLewandowski commented 11 months ago

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?