map-making.app formula selector

in map edition page, adds a formula button at the bottom right

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         map-making.app formula selector
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  in map edition page, adds a formula button at the bottom right
// @author       JanosGeo
// @license      MIT
// @match        https://map-making.app/maps/*
// @run-at       document-idle
// @icon         https://www.google.com/s2/favicons?sz=64&domain=map-making.app
// @grant        none
// ==/UserScript==

// JanosGeo comments:
// Original code developed by .flamby. I've modified the code to
// 1. Use the order and same color for the tags
// 2. Customize the styling
// 3. Add the option to right-click to make an intersection instead of a union.

function addStyle() {
  let style = document.createElement("style");
  style.type = "text/css";
  let css = ".buttonFormula {";
  css += "padding: 5px; ";
  css += "margin: 4px; ";
  css += "color: #C8A675; ";
  css += "background-color: #1A1C1D; ";
  css += "border-radius: 10px; ";
  css += "border-color: #C8A675; ";
  css += "}";
  css += ".buttonFormula:hover{";
  css += "color: #F3DCBB; ";
  css += "border-color: #F3DCBB; ";
  css += "}";
  css += "#buttonExec{";
  css += "margin-right: 16px; ";
  css += "}";

  style.innerHTML = css;
  document.head.appendChild(style);
}

function createDivButton() {
  let divButton = document.createElement("button");
  divButton.textContent = "Selections";
  divButton.id = "divButton";
  divButton.style.position = "fixed";
  divButton.style.right = "110px";
  divButton.style.bottom = "15px";
  divButton.style.borderRadius = "18px";
  divButton.style.fontSize = "15px";
  divButton.style.padding = "5px 10px";
  divButton.style.border = "none";
  divButton.style.color = "white";
  divButton.style.cursor = "pointer";
  divButton.style.backgroundColor = "#4CAF50";

  document.body.appendChild(divButton);
  divButton.addEventListener("click", function () {
    updateTagList();
    document.getElementById("divFormula").style.display = "flex";
  });
}

function unionTag(tag) {
  let original = document.getElementById("inputFormula").value;
  if (original !== "") {
    original += " || ";
  }
  document.getElementById("inputFormula").value = original + "{" + tag + "}";
}

function intersectTag(tag) {
  let original = document.getElementById("inputFormula").value;
  if (original !== "") {
    original = "(" + original + ") && ";
  }
  document.getElementById("inputFormula").value = original + "{" + tag + "}";
}

function handleClick(event, tag) {
  if (event.button == 0) {
    // Left click
    unionTag(tag);
  } else if (event.button == 1) {
    // Wheel click, do nothing for now
  } else if (event.button == 2) {
    // right click
    event.preventDefault();
    intersectTag(tag);
  }
}

function componentToHex(c) {
  var hex = c.toString(16);
  return hex.length == 1 ? "0" + hex : hex;
}

function rgbToHex(arr) {
  return (
    "#" +
    componentToHex(arr[0]) +
    componentToHex(arr[1]) +
    componentToHex(arr[2])
  );
}

function textColor(arr) {
  let r = arr[0];
  let g = arr[1];
  let b = arr[2];
  return r * 0.299 + g * 0.587 + b * 0.114 >= 125 ? "black" : "white";
}

function updateTagList() {
  // Read tags in the proper order
  let tags = [];
  for (const tag in window.editor.tags) {
    tags.push(window.editor.tags[tag]);
  }
  tags.sort((a, b) => a.order - b.order);

  let strHTML = "";
  for (const tag in tags) {
    const name = tags[tag].tag;
    strHTML +=
      "<button id='buttonTag_" +
      name +
      "' class='buttonFormula'>" +
      name +
      "</button>";
  }
  document.getElementById("divTagList").innerHTML = strHTML;

  for (const tag in tags) {
    const name = tags[tag].tag;
    let button = document.getElementById("buttonTag_" + name);
    button.style.backgroundColor = rgbToHex(tags[tag].color);
    button.style.color = textColor(tags[tag].color);
    button.addEventListener("contextmenu", (event) => event.preventDefault());
    button.addEventListener("mousedown", (event) => {
      handleClick(event, name);
    });
  }
}

function createDivFormula() {
  let divFormula = document.createElement("div");
  divFormula.id = "divFormula";
  divFormula.style.display = "none";
  divFormula.style.position = "fixed";
  divFormula.style.top = "50%";
  divFormula.style.left = "50%";
  divFormula.style.transform = "translate(-50%, -50%)";
  divFormula.style.flexDirection = "column";
  divFormula.style.zIndex = 1000;
  divFormula.style.margin = "15px";
  divFormula.style.backgroundColor = "#1A1C1D";
  divFormula.style.borderRadius = "10px";
  divFormula.style.padding = "10px";

  let strHTML =
    "<div id='divTagList' style='display:flex;flex-direction:row;max-width:50vw;flex-wrap:wrap;'>";
  strHTML += "</div>";

  strHTML += "<div style='display:flex;flex-direction:row;margin:15px;'>";
  strHTML +=
    "<span style='margin:5px;'>Formula</span><input id='inputFormula' type='text' style='width:50vw;margin:5px;font-size:16px;' title='Example: ({black_car}||{white_car}) && !{antenna}'/>";
  strHTML +=
    "<button id='buttonExec' class='buttonFormula'>select</button><button id='buttonClear' class='buttonFormula'>clear</button><button id='buttonCancel' class='buttonFormula'>cancel</button>";
  strHTML += "</div>";

  divFormula.innerHTML = strHTML;
  document.body.appendChild(divFormula);

  document
    .getElementById("buttonCancel")
    .addEventListener("click", function () {
      document.getElementById("divFormula").style.display = "none";
    });

  document.getElementById("buttonClear").addEventListener("click", function () {
    document.getElementById("inputFormula").value = "";
  });

  document.getElementById("buttonExec").addEventListener("click", function () {
    let strFormula = document.getElementById("inputFormula").value;
    while (strFormula.includes("{")) {
      let intStart = strFormula.indexOf("{");
      let intEnd = strFormula.indexOf("}");
      if (intStart + 1 >= intEnd) {
        return 0;
      }
      let tagName = strFormula.substring(intStart + 1, intEnd);

      strFormula =
        strFormula.substring(0, intStart) +
        "window.locations[i].tags.includes('" +
        tagName +
        "')" +
        strFormula.substring(intEnd + 1, strFormula.length);
    }

    let locationList = [];
    for (let i = 0; i < window.locations.length; i++) {
      if (eval(strFormula)) {
        locationList.push(window.locations[i]);
      }
    }
    window.editor.selectLocations(locationList);
    document.getElementById("divFormula").style.display = "none";
  });
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// main
(async () => {
  // wait for page load
  while (window.editor == undefined) {
    await sleep(250);
  }
  await sleep(250);

  // add buttons and styles
  addStyle();
  createDivButton();
  createDivFormula();
})();