Items sorter with RW weapon filter

Allows you to sort your items, by RW or price.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Items sorter with RW weapon filter
// @namespace    [email protected]
// @description  Allows you to sort your items, by RW or price.
// @version      0.1
// @author       Nurv [669537]
// @license      Copyright IronNerd.me
// @match        https://www.torn.com/item.php*

// ==/UserScript==

(function () {
  "use strict";


  function init() {
    const titleBarEl = document.querySelector(".title-black.hospital-dark.top-round.scroll-dark[role='heading'][aria-level='5']");

    if (!titleBarEl) {
      console.error("Title bar element not found!");
      return;
    }

    if (document.getElementById("sort-default")) {
      console.log("Sorting buttons already added.");
      return;
    }

    const controlPanel = document.createElement("div");
    controlPanel.style.display = "inline-block";
    controlPanel.style.marginLeft = "20px";
    controlPanel.style.gap = "10px";
    controlPanel.style.verticalAlign = "middle";

    const buttonStyle = `
      padding: 8px 12px;
      cursor: pointer;
      color: white;
      background-color: #555;
      border: none;
      border-radius: 4px;
      font-size: 14px;
      transition: background-color 0.3s;
    `;

    const styleSheet = document.createElement("style");
    styleSheet.type = "text/css";
    styleSheet.innerText = `
      #sort-default:hover { background-color: #777; }
      #sort-rw:hover { background-color: #888; }
      #sort-price:hover { background-color: #999; }

      #sort-default, #sort-rw, #sort-price {
        border: 1px solid #333;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
      }

      @media (max-width: 600px) {
        #sort-default, #sort-rw, #sort-price {
          font-size: 12px;
          padding: 6px 8px;
        }

        .control-panel {
          flex-direction: column;
          align-items: flex-start;
        }
      }
    `;
    document.head.appendChild(styleSheet);

    controlPanel.innerHTML = `
      <button id="sort-default" style="${buttonStyle}" aria-label="Sort items to default order">Default</button>
      <button id="sort-rw" style="${buttonStyle}" aria-label="Sort RW weapons">RW</button>
      <button id="sort-price" style="${buttonStyle}" aria-label="Sort items by price">Price</button>
    `;

    const itemsSorterIcon = titleBarEl.querySelector(".items-sorter");
    if (itemsSorterIcon && itemsSorterIcon.parentNode) {
      itemsSorterIcon.parentNode.appendChild(controlPanel);
      console.log("Sorting buttons added to the page.");
    } else {
      console.error("Items sorter icon's parent not found.");
      return;
    }

    const categoriesList = document.querySelector("#categoriesList");
    if (!categoriesList) {
      console.error("Categories list not found!");
      return;
    }

    let sortState = "default";
    let parentElement;
    let itemsOriginal;
    let posOriginal;
    let loadedAll = false;

    document.getElementById("sort-default").addEventListener("click", () => {
      resetSorting();
    });

    document.getElementById("sort-rw").addEventListener("click", async () => {
      await handleSorting("rw");
    });

    document.getElementById("sort-price").addEventListener("click", async () => {
      await handleSorting("price");
    });

    categoriesList.addEventListener("click", () => {
      handleTabChange();
    });

    async function handleSorting(type) {
      parentElement = document.querySelectorAll('[aria-hidden="false"]');

      if (type === "price" && !document.querySelector(".tt-item-price")) {
        alert(
          "Inventory Sorter requires Torn Tools to work properly. Make sure you install it before using this script!"
        );
        return;
      }

      if (!posOriginal && !loadedAll) {
        posOriginal = window.scrollY;
        await loadAllItems();
      }

      if (!itemsOriginal || sortState === "default") {
        cacheOriginalItems();
      }

      if (type === "rw") {
        sortItems(sortRW([...itemsOriginal]), parentElement);
        sortState = "rw";
      } else if (type === "price") {
        sortItems(sortPrices([...itemsOriginal]), parentElement);
      }

      localStorage.setItem("sortState", sortState);
    }

    function cacheOriginalItems() {
      itemsOriginal = Array.from(parentElement[0].childNodes).map((itemEl) => {
        const priceText = itemEl.querySelector(".tt-item-price")?.lastChild?.textContent || "0";
        const price = +priceText.replace(/[^0-9.-]+/g, "");

        const imageWrap = itemEl.querySelector(".image-wrap");
        const isRW =
          imageWrap &&
          (imageWrap.classList.contains("glow-yellow") ||
            imageWrap.classList.contains("glow-orange") ||
            imageWrap.classList.contains("glow-red"));

        let colorPriority = 4;
        if (imageWrap.classList.contains("glow-red")) colorPriority = 1;
        else if (imageWrap.classList.contains("glow-orange")) colorPriority = 2;
        else if (imageWrap.classList.contains("glow-yellow")) colorPriority = 3;

        return { element: itemEl, price, isRW, colorPriority };
      });
    }

    function sortRW(items) {
      return items.sort((a, b) => {
        if (a.isRW && !b.isRW) return -1;
        if (!a.isRW && b.isRW) return 1;
        return a.colorPriority - b.colorPriority;
      });
    }

    function sortPrices(items) {
      if (sortState === "price-descending") {
        sortState = "price-ascending";
        return items.sort((a, b) => a.price - b.price);
      } else {
        sortState = "price-descending";
        return items.sort((a, b) => b.price - a.price);
      }
    }

    function resetSorting() {
      if (itemsOriginal && parentElement) {
        sortItems(itemsOriginal, parentElement);
      }
      sortState = "default";
      localStorage.setItem("sortState", sortState);
      itemsOriginal = null;
      posOriginal = null;
      loadedAll = false;
    }

    function sortItems(_items, _parentElement) {
      _items.forEach((item) => _parentElement[0].appendChild(item.element));
    }

    function handleTabChange() {
      resetSorting();
    }

    async function loadAllItems() {
      const loadMoreDesc = document.querySelector("#load-more-items-desc");
      if (!loadMoreDesc) {
        console.error("#load-more-items-desc element not found!");
        return;
      }

      const text = loadMoreDesc.textContent;

      if (text.toLowerCase().includes("full")) {
        window.scroll(0, posOriginal);
        loadedAll = true;
        return;
      }

      if (text.toLowerCase().includes("load more")) {
        const lastItem = document.querySelector(".items-wrap").lastElementChild;
        if (lastItem) {
          lastItem.scrollIntoView();
          await new Promise((resolve) => setTimeout(resolve, 500));
          return loadAllItems();
        }
      }
    }

    const savedSortState = localStorage.getItem("sortState");
    if (savedSortState && savedSortState !== "default") {
      handleSorting(savedSortState);
    }

    if (window.matchMedia("(max-width: 600px)").matches) {
      titleBarEl.addEventListener("click", () => {
        if (sortState === "default") {
          handleSorting("rw");
        } else {
          resetSorting();
        }
      });
    }
  }

  const observer = new MutationObserver((mutations, obs) => {
    const titleBarEl = document.querySelector(".title-black.hospital-dark.top-round.scroll-dark[role='heading'][aria-level='5']");
    if (titleBarEl) {
      init();
      obs.disconnect();
    }
  });

  observer.observe(document.body, { childList: true, subtree: true });
})();