Greasy Fork

Greasy Fork is available in English.

MouseHunt - Marketplace UI Tweaks

Adds useful features and tweaks to the Marketplace rework

当前为 2019-03-04 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         MouseHunt - Marketplace UI Tweaks
// @author       Tran Situ (tsitu)
// @namespace    http://greasyfork.icu/en/users/232363-tsitu
// @version      1.0
// @description  Adds useful features and tweaks to the Marketplace rework
// @match        http://www.mousehuntgame.com/*
// @match        https://www.mousehuntgame.com/*
// ==/UserScript==

(function() {
  /**
   * [ Notes ]
   * innerText has poor retrieval perf, use textContent
   *   (http://perfectionkills.com/the-poor-misunderstood-innerText/)
   * Is there a better way to center scrollRow vertically within table?
   *   (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView)
   */

  MutationObserver =
    window.MutationObserver ||
    window.WebKitMutationObserver ||
    window.MozMutationObserver;

  // Initialize 'Browse' tab item caching
  if (localStorage.getItem("marketplace-browse-cache-tsitu") === null) {
    const cacheObj = {
      Cheese: 0,
      "Baskets & Kits": 0,
      Charms: 0,
      Crafting: 0,
      Special: 0,
      Collectibles: 0,
      Weapons: 0,
      Skins: 0
    };
    localStorage.setItem(
      "marketplace-browse-cache-tsitu",
      JSON.stringify(cacheObj)
    );
  }

  // Only observe changes to the #overlayPopup element
  const observerTarget = document.querySelector("#overlayPopup");

  const observer = new MutationObserver(function() {
    // Check if the Marketplace interface is open
    if (document.querySelector(".marketplaceView")) {
      // Disconnect and reconnect later to prevent mutation loop
      observer.disconnect();

      const browseTab = document.querySelector("[data-tab=browse].active");
      const backButton = document.querySelector("a.marketplaceView-breadcrumb");

      if (browseTab && !backButton) {
        /* Browse tab logic (active Browse tab + inactive 'Back' button) */

        const sidebar = document.querySelector(
          ".marketplaceView-browse-sidebar"
        );
        const itemType = sidebar.querySelector(
          ".marketplaceView-browse-sidebar-link.active"
        );
        const groupName = itemType
          ? `Browse Tab - ${itemType.textContent}`
          : "Browse Tab";

        console.group(groupName);
        console.time("[Total]");

        // Feature: Make item images 40x40 px
        document.querySelectorAll(".marketplaceView-itemImage").forEach(el => {
          el.style.width = "40px";
          el.style.height = "40px";
          el.style.backgroundSize = "100%";
        });

        let totalValueSum = 0;
        /**
         * Abbreviates large number values up to 1 decimal point
         * k = 1,000 and m = 1,000,000
         * @param {number} num Integer to abbreviate
         * @return {string}
         */
        function abbrev(num) {
          if (num <= 999) {
            return "" + num;
          } else if (num >= 1000 && num <= 999999) {
            let pre = Math.floor(num / 1000);
            let post = Math.round((num % 1000) / 100);
            if (post === 10) {
              post = 0;
              pre += 1;
            }
            return `${pre}.${post}k`;
          } else if (num >= 1000000) {
            let pre = Math.floor(num / 1000000);
            let post = Math.round((num % 1000000) / 100000);
            if (post === 10) {
              post = 0;
              pre += 1;
            }
            return `${pre}.${post}m`;
          }
        }

        const rows = document.querySelectorAll("tr[data-item-id]");
        if (rows.length > 0) {
          const avgPriceHeader = document.querySelector(
            "th.marketplaceView-table-averagePrice"
          );
          const valueHeader = document.createElement("th");
          valueHeader.className =
            "marketplaceView-table-value marketplaceView-table-numeric";
          valueHeader.onclick =
            "hg.views.MarketplaceView.setSortOrder(this); return false;";
          valueHeader.innerText = "Value";

          if (
            avgPriceHeader &&
            !document.querySelector(".marketplaceView-table-value")
          ) {
            // Add 'Value' column header
            avgPriceHeader.insertAdjacentElement("afterend", valueHeader);

            console.time("Row Logic");

            rows.forEach(row => {
              // Add click handlers to the <a>'s that open up an item page
              row.querySelectorAll("a").forEach(el => {
                const aText = el.onclick;
                if (aText) {
                  if (aText.toString().indexOf("showItem") >= 0) {
                    el.addEventListener("click", function() {
                      // Parse current item name and type for caching
                      const name = row.querySelector(
                        ".marketplaceView-table-name"
                      );

                      if (name && itemType) {
                        // Retrieve and overwrite localStorage
                        const lsText = localStorage.getItem(
                          "marketplace-browse-cache-tsitu"
                        );
                        if (lsText) {
                          const lsObj = JSON.parse(lsText);
                          lsObj[itemType.textContent] = name.textContent;
                          localStorage.setItem(
                            "marketplace-browse-cache-tsitu",
                            JSON.stringify(lsObj)
                          );
                        }
                      }
                    });
                  }
                }
              });

              // Parse owned quantity
              let ownedNum = 0;
              const ownedText = row.querySelector(
                ".marketplaceView-table-quantity"
              ).textContent;
              if (ownedText !== "-") {
                ownedNum = parseInt(ownedText.split(",").join(""));
              }

              // Parse average prices
              let priceNum = 0;
              const priceText = row.querySelector(".marketplaceView-goldValue");
              if (priceText.children.length > 0) {
                priceNum = parseInt(
                  priceText.children[0].title
                    .split(" ")[0]
                    .split(",")
                    .join("")
                );
              }

              const multValue = ownedNum * priceNum;
              if (multValue > 0) {
                totalValueSum += multValue;
                row.title = `${multValue.toLocaleString()} Gold`;
              }

              let outputText = abbrev(multValue);
              if (priceNum === 0) {
                // Avg. Price currently unavailable, but value isn't necessarily 0
                outputText = "N/A";
              }

              const valueColumn = document.createElement("td");
              valueColumn.innerText = outputText;
              valueColumn.className =
                "marketplaceView-table-numeric value-column-tsitu";

              // Feature: Insert 'Own' x 'Avg. Price' = 'Value' column data
              row
                .querySelector(".marketplaceView-table-averagePrice")
                .insertAdjacentElement("afterend", valueColumn);
            });

            console.timeEnd("Row Logic");
          }
        }

        // Add info to the sidebar
        if (sidebar && !document.querySelector(".marketplace-sidebar-tsitu")) {
          // Container div
          const div = document.createElement("div");
          div.className = "marketplace-sidebar-tsitu";
          div.style.margin = "20px";

          // Highlighted text
          const span1 = document.createElement("span");
          span1.style.backgroundColor = "#D6EBA1";
          span1.innerText = "Highlighted row";

          // Other text
          const span2 = document.createElement("span");
          span2.innerText = " indicates the most recently viewed item";

          // Inject into DOM
          div.appendChild(span1);
          div.appendChild(span2);
          sidebar.appendChild(div);
        }

        // Feature: Add <span> with sum of values on current tab
        const filterDiv = document.querySelector(
          ".marketplaceView-browse-filterContainer"
        );
        if (
          filterDiv &&
          !document.querySelector(".marketplace-total-value-tsitu")
        ) {
          const span = document.createElement("span");
          span.className = "marketplace-total-value-tsitu";
          span.innerText = `(Total estimated value on this tab: ${abbrev(
            totalValueSum
          )} or ${totalValueSum.toLocaleString()} Gold)`;
          span.style.fontSize = "12px";
          span.style.fontWeight = "normal";

          const emptySpace = document.createElement("span");
          emptySpace.innerHTML = "\u00A0\u00A0\u00A0";

          // Inject into DOM
          filterDiv.insertAdjacentElement("beforebegin", emptySpace);
          filterDiv.insertAdjacentElement("beforebegin", span);
        }

        // Feature: Check cache for most recently clicked item and scroll to it
        const lsText = localStorage.getItem("marketplace-browse-cache-tsitu");
        if (lsText) {
          const lsObj = JSON.parse(lsText);
          const itemType = sidebar.querySelector(
            ".marketplaceView-browse-sidebar-link.active"
          );
          if (itemType) {
            const name = lsObj[itemType.textContent];
            if (name && name !== 0) {
              /**
               * Return row element that matches existing cached item name
               * @param {string} name Cached item name
               * @return {HTMLElement|false} <tr> that should be highlighted and scrolled to
               */
              function findElement(name) {
                for (let el of document.querySelectorAll("tr[data-item-id]")) {
                  const aTags = el.querySelectorAll("a");
                  if (aTags.length === 5) {
                    if (name === aTags[2].textContent) {
                      return el;
                    }
                  }
                }

                return false;
              }

              // Calculate index for nth-child
              const targetEl = findElement(name);
              let nthChildValue = 0;
              for (let i = 0; i < rows.length; i++) {
                const el = rows[i];
                if (el === targetEl) {
                  nthChildValue = i + 2;
                  break;
                }
              }

              // tr:nth-child value (min = 2)
              const recentRow = document.querySelector(
                `.marketplaceView-table tr:nth-child(${nthChildValue})`
              );
              if (recentRow) {
                recentRow.style.backgroundColor = "#D6EBA1";

                // Try scrolling up to 4 rows down
                let scrollRow = recentRow;
                for (let i = 4; i > 0; i--) {
                  const row = document.querySelector(
                    `.marketplaceView-table tr:nth-child(${nthChildValue + i})`
                  );
                  if (row) {
                    scrollRow = row;
                    break;
                  }
                }

                console.time("Render + Scroll");

                scrollRow.scrollIntoView({
                  // Seems to wait for XHR & render - slow initially but gets moderately faster
                  behavior: "auto",
                  block: "nearest",
                  inline: "nearest"
                });

                console.timeEnd("Render + Scroll");
              }
            }
          }
        }

        console.timeEnd("[Total]");
        console.groupEnd(groupName);
      } else if (backButton) {
        /* Listing logic (active 'Back' button) */

        // 'Quick Purchase' rows only
        // Passes on Buy/Sell screens, but not on 'View More' b/c only NodeList(2)
        const goldTable = document.querySelectorAll(
          ".marketplaceView-roundedTable"
        )[2];

        if (goldTable) {
          const goldValues = goldTable.querySelectorAll(
            "td .marketplaceView-goldValue"
          );

          if (goldValues.length > 0) {
            goldValues.forEach(el => {
              const rawVal = el.textContent;
              if (typeof rawVal === "string") {
                // Feature: Inject tariff info into DOM

                const value = parseInt(rawVal.split(",").join(""));
                // Floating point imprecision leads to .999's and other glitches
                // Math.floor(value / 1.1) is insufficient
                const preTax = Math.floor((value / 1.1).toFixed(3));
                const tax = value - preTax;
                const titleString = `\nOriginal: ${preTax.toLocaleString()} Gold\nTariff: ${tax.toLocaleString()} Gold`;

                // Check for repeated appends
                if (el.title.indexOf("Tariff") < 0) el.title += titleString;

                // Add a reversible onclick with same info as title
                if (!el.onclick) {
                  function initHandler() {
                    el.className = "tsitu-null";
                    el.innerText = el.title;
                    el.onclick = revertHandler;
                  }

                  function revertHandler() {
                    el.className = "marketplaceView-goldValue";
                    el.innerText = el.title.split("Gold")[0].trim();
                    el.onclick = initHandler;
                  }

                  el.onclick = initHandler;
                }
              }
            });
          }
        }
      }

      // Re-observe after mutation-inducing logic
      observer.observe(observerTarget, {
        childList: true,
        subtree: true
      });
    }
  });

  // Initial observe
  observer.observe(observerTarget, {
    childList: true,
    subtree: true
  });
})();