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); map.removeControl(map.zoomControl); map.on('mouseover', function(e) { map.addControl( map.zoomControl ); }); map.on('mouseout', function(e) { map.removeControl( map.zoomControl ); }); if (isArrow) { const setView = () => { const [lat, lng, heading] = $position.value.split(","); marker.setHeading(heading); marker.setLatLng([lat, lng]); map.setView([lat, lng], map.getZoom()); }; $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)); }; }, };