User:Beao/CropToolHotKeys.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
// ==UserScript==
// @name         CropToolHotKeys
// @namespace    https://croptool.toolforge.org/
// @match        https://croptool.toolforge.org/?title=*
// @description  CropToolHotKeys (https://commons.wikimedia.org/wiki/User:Beao/CropToolHotKeys.js)
// @version      0.2
// @icon         https://wikitech.wikimedia.org/favicon.ico
// @grant        none
// ==/UserScript==

// A userscript to add hotkeys to CropTool
// https://croptool.toolforge.org/
// Confirmed to work in Firefox with Tampermonkey 2023-10-02

(function () {
  "use strict";

  angular.element(function () {
    const q = (x) => document.querySelector(x);

    document.addEventListener(
      "keydown",
      function (e) {
        if (e.key.toLowerCase() === "x") {
          q(".crop-tool-hotkeys").classList.toggle("crop-tool-hotkeys-hidden");
          localStorage.setItem(
            "crop-tool-hotkeys-hidden",
            q(".crop-tool-hotkeys").classList.contains("crop-tool-hotkeys-hidden")
          );
        }
        if (e.key === "Enter") {
          if (q("[ladda=ladda]")?.getBoundingClientRect().width > 0) {
            q("[ladda=ladda]").click();
          } else if (q("[ladda=ladda2]")?.getBoundingClientRect().width > 0) {
            q("[ladda=ladda2]").click();
          }
        }
        if (e.key === "Backspace") {
          if (q("[ng-click='back()']")) {
            q("[ng-click='back()']").click();
          }
        }

        keydown(e.key.toLowerCase(), e.shiftKey);
      },
      false
    );

    const keydown = (key, big) => {
      const isCropping = q("[ladda=ladda]")?.getBoundingClientRect().width > 0;
      if (!isCropping) return;

      if (key === "e") {
        q("[ng-click='locateBorder()']").click();
        return;
      }

      if (key === "w") {
        if (q("#crop-tool-image-viewer")) {
          if (q("#crop-tool-image-viewer").style.backgroundColor === "black") {
            q("#crop-tool-image-viewer").style.backgroundColor = "white";
          } else {
            q("#crop-tool-image-viewer").outerHTML = "";
          }
        } else {
          const img = q(".cropper-canvas img");
          const viewer = document.createElement("div");
          viewer.id = "crop-tool-image-viewer";
          viewer.style.position = "fixed";
          viewer.style.top = "0";
          viewer.style.left = "0";
          viewer.style.width = "100%";
          viewer.style.height = "100%";
          viewer.style.backgroundColor = "black";
          viewer.style.zIndex = "10000";
          viewer.style.display = "flex";
          viewer.style.justifyContent = "center";
          viewer.style.alignItems = "center";
          viewer.style.padding = "10px";
          viewer.style.cursor = "pointer";
          viewer.addEventListener("click", () => viewer.outerHTML = "");
          const img2 = img.cloneNode();
          img2.style.maxWidth = "100%";
          img2.style.maxHeight = "100%";
          viewer.appendChild(img2);
          document.body.appendChild(viewer);
        }
        return;
      }

      const x = q("[ng-model='crop_dim.x']");
      const y = q("[ng-model='crop_dim.y']");
      const w = q("[ng-model='crop_dim.w']");
      const h = q("[ng-model='crop_dim.h']");

      const _x = parseInt(x.value, 10);
      const _y = parseInt(y.value, 10);
      const _w = parseInt(w.value, 10);
      const _h = parseInt(h.value, 10);

      if (!x || !y || !w || !h) return;

      if (!q("[translate-value-width]")?.attributes["translate-value-width"]) {
        return;
      }

      const maxW = parseInt(
        q("[translate-value-width]").attributes["translate-value-width"].value,
        10
      );
      const maxH = parseInt(
        q("[translate-value-height]").attributes["translate-value-height"]
          .value,
        10
      );

      const step = big
        ? Math.max(Math.floor(maxW / 100), Math.floor(maxH / 100), 10)
        : 1;

      const c = (dim, delta) => {
        dim.value = parseInt(dim.value, 10) + delta;
      };

      if (key === "r") {
        x.value = 0;
        y.value = 0;
        w.value = maxW;
        h.value = maxH;
      }

      const memRound = (x) => Math.round(x * 10000) / 10000;

      if (key === "n") {
        localStorage.setItem("crop-tool-hotkeys-x", memRound(_x / maxW));
        localStorage.setItem("crop-tool-hotkeys-y", memRound(_y / maxH));
        localStorage.setItem("crop-tool-hotkeys-w", memRound(_w / maxW));
        localStorage.setItem("crop-tool-hotkeys-h", memRound(_h / maxH));
      }

      if (key === "m") {
        const memoryX = parseFloat(localStorage.getItem("crop-tool-hotkeys-x") || "0");
        const memoryY = parseFloat(localStorage.getItem("crop-tool-hotkeys-y") || "0");
        const memoryW = parseFloat(localStorage.getItem("crop-tool-hotkeys-w") || "1");
        const memoryH = parseFloat(localStorage.getItem("crop-tool-hotkeys-h") || "1");
        x.value = Math.round(memoryX * maxW);
        y.value = Math.round(memoryY * maxH);
        w.value = Math.round(memoryW * maxW);
        h.value = Math.round(memoryH * maxH);
      }

      if (key === "a" && _x >= step) {
        c(x, -step);
        c(w, step);
      }
      if (key === "s" && _y >= step) {
        c(y, -step);
        c(h, step);
      }
      if (key === "d" && _h >= step) {
        c(y, step);
        c(h, -step);
      }
      if (key === "f" && _w >= step) {
        c(x, step);
        c(w, -step);
      }

      if (key === "h" && _w >= step) c(w, -step);
      if (key === "j" && _h >= step) c(h, -step);
      if (key === "k" && _h + _y + step <= maxH) c(h, step);
      if (key === "l" && _w + _x + step <= maxW) c(w, step);

      if (key === "g") {
        if (_w > 2 * step) {
          c(x, step);
          c(w, -2 * step);
        }
        if (_h > 2 * step) {
          c(y, step);
          c(h, -2 * step);
        }
      }

      if (key === "t") {
        if (_w + _x + 2 * step <= maxW) {
          c(x, -step);
          c(w, 2 * step);
        }
        if (_h + _y + 2 * step <= maxH) {
          c(y, -step);
          c(h, 2 * step);
        }
      }

      x.dispatchEvent(new Event("input", { bubbles: true }));
      y.dispatchEvent(new Event("input", { bubbles: true }));
      w.dispatchEvent(new Event("input", { bubbles: true }));
      h.dispatchEvent(new Event("input", { bubbles: true }));
    };

    const instructionsStyle = document.createElement("style");
    instructionsStyle.innerHTML = `
      .crop-tool-hotkeys {
        position: fixed;
        bottom: 0;
        right: 0;
        background: black;
        color: white;
        padding: 5px;
        font-size: 10px;
        z-index: 100000;
        font-family: monospace;
        line-height: 1.2;
        gap: 5px;
        opacity: 0.9;
        transition: opacity 0.2s;
        cursor: default;
      }

      .crop-tool-hotkeys-hidden {
        opacity: 0.4;
        cursor: pointer;
      }

      .crop-tool-hotkeys-hidden:hover {
        opacity: 0.9;
      }

      .crop-tool-hotkeys-hidden p {
        margin: 0;
      }

      .crop-tool-hotkeys-hidden section {
        display: none;
      }
    `;
    document.body.appendChild(instructionsStyle);

    const instructions = document.createElement("div");
    instructions.classList.add("crop-tool-hotkeys");
    if (localStorage.getItem("crop-tool-hotkeys-hidden") === "true") {
      instructions.classList.add("crop-tool-hotkeys-hidden");
    }
    instructions.innerHTML = `
      <p><b>CropToolHotKeys</b></p>

      <section>
        <p><kbd>Enter</kbd> to crop or upload</p>
        <p><kbd>Backspace</kbd> to go back</p>

        <p><kbd>a</kbd> <kbd>s</kbd> <kbd>d</kbd> <kbd>f</kbd> to move upper left corner</p>
        <p><kbd>h</kbd> <kbd>j</kbd> <kbd>k</kbd> <kbd>l</kbd> to move lower right corner</p>
        <p><kbd>g</kbd> to shrink crop</p>
        <p><kbd>t</kbd> to grow crop</p>
        <p><kbd>shift</kbd> + <kbd>a/s/d/f/h/j/k/l/g/t</kbd> to move faster</p>

        <p><kbd>r</kbd> to reset crop to full image</p>
        <p><kbd>e</kbd> to auto-detect border</p>
        <p><kbd>w</kbd> to toggle image viewer with black or white background</p>

        <p><kbd>n</kbd> to save current crop percentage to memory</p>
        <p><kbd>m</kbd> to load saved crop percentage</p>

        <p><kbd>x</kbd> or click here to toggle this help</p>
      </section>
    `.replace(/\n\s*/g, "\n");
    instructions.addEventListener("click", () => {
      instructions.classList.toggle("crop-tool-hotkeys-hidden");
      localStorage.setItem(
        "crop-tool-hotkeys-hidden",
        instructions.classList.contains("crop-tool-hotkeys-hidden")
      );
    });
    document.body.appendChild(instructions);
  });
})();