fibrillations / GarnishPhone

// ==UserScript==
// @name         GarnishPhone
// @namespace    https://openuserjs.org/users/fibrillations
// @version      1.01
// @description  Adds keyboard shortcuts to GarticPhone
// @match        https://garticphone.com/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  const opacityDelta = 0.1;

  // This defines the keyCodes and their corresponding action
  // You can test keyCodes here: https://keycode.info/
  const keyHandlers = {
    // [ -> decrease thickness
    219: () => changeThickness(false),
    // ] -> increase thickness
    221: () => changeThickness(true),
    // PageDown -> decrease opacity
    34: () => changeOpacity(-opacityDelta),
    // PageUp -> increase opacity
    33: () => changeOpacity(opacityDelta),
    // E -> toggle between pen and eraser
    69: () => toggleEraser(),
    // L -> select pen tool
    80: () => selectTool("pen"),
    // F -> select fill tool
    70: () => selectTool("fil"),
    // L -> select line tool
    76: () => selectTool("lin"),
  }

  const garticElements = {
    "opacity": () => document.querySelector("input.jsx-340028725[type=range]"),
    "thickness": () => {
      const element = document.querySelector(".jsx-340028725.thickness");
      return element ? element.parentElement : null;
    },
  }

  const garticValueSetters = {
    "opacity": (element) => {
      const valueSetter = Object.getOwnPropertyDescriptor(element, "value").set;
      const prototype = Object.getPrototypeOf(element);
      const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, "value").set;
      const setter = (valueSetter && valueSetter !== prototypeValueSetter) ? prototypeValueSetter : valueSetter;
      return (value) => {
        setter.call(element, value);
        element.dispatchEvent(new Event("input", {
          bubbles: true
        }));
      }
    }
  }

  function mapGet(map, key, arg) {
    if (!map[key]) {
      // assume a tool is being queried
      return document.querySelector(".jsx-4206980828.tool." + key);
    }
    return map[key](arg);
  }

  function getGarticElement(name) {
    return mapGet(garticElements, name);
  }

  function getGarticSetter(name, element) {
    return mapGet(garticValueSetters, name, element);
  }

  function changeOpacity(delta) {
    const element = getGarticElement("opacity");
    if (!element) return;
    const opacity = Number.parseFloat(element.value);
    const newOpacity = opacity + delta;
    if (newOpacity <= 0 || newOpacity > 1) return;
    getGarticSetter("opacity", element)(newOpacity + "");
  }

  function changeThickness(increase) {
    const element = getGarticElement("thickness");
    if (!element) return;
    const selected = element.querySelector(".sel");
    if (!selected) return;
    const target = increase ? selected.nextElementSibling : selected.previousElementSibling;
    if (!target) return;
    target.click();
  }

  function selectTool(name) {
    const element = getGarticElement(name);
    if (!element) return;
    element.click();
  }

  function toggleEraser() {
    const ers = getGarticElement("ers");
    if (!ers) return;
    if (ers.classList.contains("sel")) {
      selectTool("pen");
    }
    else {
      selectTool("ers");
    }
  }

  function handleKeys(e) {
    const handler = keyHandlers[e.keyCode];
    if (!handler) return;
    handler();
    e.preventDefault();
  }

  window.addEventListener("keydown", handleKeys);
})();