text: const LANG = navigator.languages
    ? navigator.languages[0]
    : navigator.language || navigator.userLanguage;
  
  function toLocalTime(dateStr, opts) {
    const date = new Date(dateStr);
  
    return date instanceof Date && !isNaN(date.valueOf())
      ? date.toLocaleTimeString(LANG, opts)
      : "–";
  }
  
  function toLocalDate(dateStr, opts) {
    const date = new Date(dateStr);
  
    return date instanceof Date && !isNaN(date.valueOf())
      ? date.toLocaleDateString(LANG, opts)
      : "–";
  }
  
  export const Dropdown = {
    mounted() {
      const $el = this.el;
  
      $el.querySelector("button").addEventListener("click", (e) => {
        e.stopPropagation();
        $el.classList.toggle("is-active");
      });
  
      document.addEventListener("click", () => {
        $el.classList.remove("is-active");
      });
    },
  };
  
  export const LocalTime = {
    mounted() {
      this.el.innerText = toLocalTime(this.el.dataset.date);
    },
  
    updated() {
      this.el.innerText = toLocalTime(this.el.dataset.date);
    },
  };
  
  export const LocalTimeRange = {
    exec() {
      const date = toLocalDate(this.el.dataset.startDate, {
        year: "numeric",
        month: "short",
        day: "numeric",
      });
  
      const time = [this.el.dataset.startDate, this.el.dataset.endDate]
        .map((date) =>
          toLocalTime(date, { hour: "2-digit", minute: "2-digit", hour12: false })
        )
        .join(" – ");
  
      this.el.innerText = `${date}, ${time}`;
    },
  
    mounted() {
      this.exec();
    },
    updated() {
      this.exec();
    },
  };
  
  export const ConfirmGeoFenceDeletion = {
    mounted() {
      const { id, msg } = this.el.dataset;
  
      this.el.addEventListener("click", () => {
        if (window.confirm(msg)) {
          this.pushEvent("delete", { id });
        }
      });
    },
  };
  
  import {
    Map as M,
    TileLayer,
    LatLng,
    Control,
    Marker,
    Icon,
    Circle,
    CircleMarker,
  } from "leaflet";
  
  import markerIcon from "leaflet/dist/images/marker-icon.png";
  import markerShadow from "leaflet/dist/images/marker-shadow.png";
  
  const icon = new Icon({
    iconUrl: markerIcon,
    shadowUrl: markerShadow,
    iconAnchor: [12, 40],
    popupAnchor: [0, -25],
  });
  
  const DirectionArrow = CircleMarker.extend({
    initialize(latLng, heading, options) {
      this._heading = heading;
      CircleMarker.prototype.initialize.call(this, latLng, {
        fillOpacity: 1,
        radius: 5,
        ...options,
      });
    },
  
    setHeading(heading) {
      this._heading = heading;
      this.redraw();
    },
  
    _updatePath() {
      const { x, y } = this._point;
  
      if (this._heading === "")
        return CircleMarker.prototype._updatePath.call(this);
  
      this.getElement().setAttributeNS(
        null,
        "transform",
        `translate(${x},${y}) rotate(${this._heading})`
      );
  
      const path = this._empty() ? "" : `M0,${3} L-4,${5} L0,${-5} L4,${5} z}`;
  
      this._renderer._setPath(this, path);
    },
  });
  
  function createMap(opts) {
    const map = new M(opts.elId != null ? `map_${opts.elId}` : "map", opts);
  
    const osm = new TileLayer(
      "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
      { maxZoom: 19 }
    );
  
    if (opts.enableHybridLayer) {
      const hybrid = new TileLayer(
        "http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}",
        { maxZoom: 20, subdomains: ["mt0", "mt1", "mt2", "mt3"] }
      );
  
      new Control.Layers({ OSM: osm, Hybrid: hybrid }).addTo(map);
    }
  
    map.addLayer(osm);
  
    return map;
  }
  
  export const SimpleMap = {
    mounted() {
      const $position = document.querySelector(`#position_${this.el.dataset.id}`);
  
      const map = createMap({
        elId: this.el.dataset.id,
        zoomControl: !!this.el.dataset.zoom,
        boxZoom: false,
        doubleClickZoom: false,
        keyboard: false,
        scrollWheelZoom: false,
        tap: false,
        dragging: false,
        touchZoom: false,
      });
  
      const isArrow = this.el.dataset.marker === "arrow";
      const [lat, lng, heading] = $position.value.split(",");
  
      const marker = isArrow
        ? new DirectionArrow([lat, lng], heading)
        : new Marker([lat, lng], { icon });
  
      map.setView([lat, lng], 17);
      marker.addTo(map);
  
      if (isArrow) {
        const setView = () => {
          const [lat, lng, heading] = $position.value.split(",");
          marker.setHeading(heading);
          marker.setLatLng([lat, lng]);
          map.setView([lat, lng], 17);
        };
  
        $position.addEventListener("change", setView);
      }
    },
  };
  
  export const TriggerChange = {
    updated() {
      this.el.dispatchEvent(new CustomEvent("change"));
    },
  };
  
  import("leaflet-control-geocoder");
  import("@geoman-io/leaflet-geoman-free");
  
  export const Map = {
    mounted() {
      const geoFence = (name) =>
        document.querySelector(`input[name='geo_fence[${name}]']`);
  
      const $radius = geoFence("radius");
      const $latitude = geoFence("latitude");
      const $longitude = geoFence("longitude");
  
      const location = new LatLng($latitude.value, $longitude.value);
  
      const controlOpts = {
        position: "topleft",
        cutPolygon: false,
        drawCircle: false,
        drawCircleMarker: false,
        drawMarker: false,
        drawPolygon: false,
        drawPolyline: false,
        drawRectangle: false,
        removalMode: false,
      };
  
      const editOpts = {
        allowSelfIntersection: false,
        preventMarkerRemoval: true,
      };
  
      const map = createMap({ enableHybridLayer: true });
      map.setView(location, 17, { animate: false });
      map.pm.setLang(LANG);
      map.pm.addControls(controlOpts);
      map.pm.enableGlobalEditMode(editOpts);
  
      const circle = new Circle(location, { radius: $radius.value })
        .addTo(map)
        .on("pm:edit", (e) => {
          const { lat, lng } = e.target.getLatLng();
          const radius = Math.round(e.target.getRadius());
  
          $radius.value = radius;
          $latitude.value = lat;
          $longitude.value = lng;
  
          const mBox = map.getBounds();
          const cBox = circle.getBounds();
          const bounds = mBox.contains(cBox) ? mBox : cBox;
          map.fitBounds(bounds);
        });
  
      new Control.geocoder({ defaultMarkGeocode: false })
        .on("markgeocode", (e) => {
          const { bbox, center } = e.geocode;
  
          const poly = L.polygon([
            bbox.getSouthEast(),
            bbox.getNorthEast(),
            bbox.getNorthWest(),
            bbox.getSouthWest(),
          ]);
  
          circle.setLatLng(center);
  
          const lBox = poly.getBounds();
          const cBox = circle.getBounds();
          const bounds = cBox.contains(lBox) ? cBox : lBox;
  
          map.fitBounds(bounds);
          map.pm.enableGlobalEditMode();
  
          const { lat, lng } = center;
          $latitude.value = lat;
          $longitude.value = lng;
        })
        .addTo(map);
  
      map.fitBounds(circle.getBounds(), { animate: false });
    },
  };
  
  export const Modal = {
    _freeze() {
      document.documentElement.classList.add("is-clipped");
    },
  
    _unfreeze() {
      document.documentElement.classList.remove("is-clipped");
    },
  
    mounted() {
      // assumption: 'is-active' is always added after the initial mount
    },
  
    updated() {
      this.el.classList.contains("is-active") ? this._freeze() : this._unfreeze();
    },
  
    destroyed() {
      this._unfreeze();
    },
  };
  
  export const NumericInput = {
    mounted() {
      this.el.onkeypress = (evt) => {
        const charCode = evt.which ? evt.which : evt.keyCode;
        return !(charCode > 31 && (charCode < 48 || charCode > 57));
      };
    },
  };
  