Source: main.js

import "./style.css";

import { Map, View, Overlay } from "ol";

import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import { TileJSON, Vector as VectorSource } from "ol/source";
import { useGeographic } from "ol/proj.js";
import { defaults as defaultControls } from "ol/control.js";

import { Popover } from "bootstrap";

import searchBar from "./modules/searchBar.js";
import fetchPins from "./modules/fetchPins.js";
import ButtonControl from "./modules/newButton.js";
import changeResolutionHandler from "./modules/changeResolutionHandler.js";
import pointerMoveHandler from "./modules/pointerMoveHandler.js";
import searchInputHander from "./modules/searchInputHandler.js";

///

const search = searchBar;

let pinning = false;

let popover;

/** Vector source for pins
 * @constant {ol.source.VectorSource} */
const pinSource = new VectorSource();
/** Vector layer for pin vector source
 * @constant {ol.layer.VectorLayer} */
const pinLayer = new VectorLayer({
  source: pinSource,
});

useGeographic();

fetchPins(pinSource);

/** Main map
 * @constant {ol.Map} */
const map = new Map({
  controls: defaultControls().extend([new ButtonControl()]),
  target: document.getElementById("map"),
  layers: [
    new TileLayer({
      source: new TileJSON({
        url: `https://api.maptiler.com/maps/hybrid/tiles.json?key=${
          import.meta.env.VITE_MAPTILER_KEY
        }`,
        tileSize: 512,
        crossOrigin: "anonymous",
      }),
    }),
  ],
  view: new View({
    center: [0, 0],
    zoom: 2,
    maxZoom: 20,
  }),
});

/** Overlay element for pin popups
 * @constant {ol.Overlay} */
const popup = new Overlay({
  element: document.getElementById("popup"),
  positioning: "bottom-center",
  stopEvent: false,
});

map.addOverlay(popup);

map.addControl(search);

const searchInput = search.getInputField();
searchInput.onkeydown = (event) => {
  searchInputHander(event, map);
};

/** Removes store information popup from screen
 */
const disposePopover = () => {
  if (popover) {
    popover.dispose();
    popover = undefined;
  }
};

/** Handles user clicks. If user clicks on a pixel that contains a pin, display popup with store information
 * @param {Event} event - User click event
 * @param {bootstrap.Popover} popover - Popover object from Bootstrap/Popper
 * @param {ol.Overlay} popup - Overlay element for pin popups
 * @param {ol.Map} map - OpenLayers map
 */

map.on("click", (event) => {
  disposePopover();

  const feature = map.forEachFeatureAtPixel(event.pixel, (feature) => feature);
  if (!feature) return;

  popup.setPosition(event.coordinate);

  const element = document.querySelector("#popup");

  popover = new Popover(element, {
    placement: "top",
    html: true,
    title: feature.get("store"),
    content: function () {
      const container = document.createElement("div");

      const cheapestItem = document.createElement("div");
      cheapestItem.textContent = `Cheapest Item: ${feature.get(
        "cheapestItem"
      )}`;
      container.appendChild(cheapestItem);

      const cheapestPerFloz = document.createElement("div");
      cheapestPerFloz.textContent = `$ / oz.: ${feature.get("pricePerOz")}`;
      container.appendChild(cheapestPerFloz);

      const link = document.createElement("a");
      link.href = "#";
      link.onclick = function (event) {
        event.preventDefault();
        window.ReactNativeWebView?.postMessage(
          `open@${feature.getGeometry().getCoordinates()}`
        );
      };
      link.textContent = "More...";
      container.appendChild(link);

      return container;
    },
  });

  popover.show();
});

map.on("pointermove", (event) => {
  pointerMoveHandler(event, map);
});

map.on("movestart", disposePopover);

map.getView().on("change:resolution", () => {
  changeResolutionHandler(pinning, map, pinLayer);
});

/** Centers location on user based on location information passed in from expo-location
 * @param {number} lon - User's longitude
 * @param {number} lat - User's latitude
 */
window.passLocation = (lon, lat) => {
  const view = new View({
    center: [lon, lat],
    zoom: 18,
    maxZoom: 20,
  });

  map.setView(view);
  map.getLayers().extend([pinLayer]);

  // sets NEW listener to listen for zoom changes
  view.on("change:resolution", () => {
    view.getZoom() < 5
      ? map.getLayers().removeAt(1)
      : (() => {
          if (map.getLayers().getLength() === 1) {
            if (!pinning) map.getLayers().extend([pinLayer]);
          }
        })();
  });
};

/** Begins store creation process if user clicks on "New" button
 */
window.placePin = () => {
  button.removeEventListener("click", newButtonHandler, false);
  pinning = true;
  map.getLayers().removeAt(1);
  // adds pin in center of view
  document.getElementById("centerPin").style.display = "block";
  // changes new button to OK button
  button.textContent = "OK";
  // OK button has event listener that on click
  function OKButtonHandler() {
    // removes listener on button in case someone wants to mash it
    button.removeEventListener("click", OKButtonHandler, false);
    // removes pin in center of view
    document.getElementById("centerPin").style.display = "none";
    // emits to RN
    window.ReactNativeWebView?.postMessage(
      `create@${map.getView().getCenter()}`
    );
  }
  button.addEventListener("click", OKButtonHandler, false);
};