Filter Collections

Adds [<10], [10-99], [100-999], [>1000], Not dungeon and D1-4 filter buttons to Collections

目前為 2025-12-07 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Filter Collections
// @namespace    http://tampermonkey.net/
// @version      2025-12-06-3
// @description  Adds [<10], [10-99], [100-999], [>1000], Not dungeon and D1-4 filter buttons to Collections
// @license      MIT
// @author       sentientmilk
// @match        https://www.milkywayidle.com/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @grant        none
// ==/UserScript==

/*
	Changelog
	=========

	v2025-12-06
		- Initial version
	v2025-12-06-2
		- FIXED: "<10" button was showing 10s
	v2025-12-06-3
		- Made checkbox label clickable

	        TODO
	====================
*/


(function() {
	async function waitFnRepeatedFor (cond, callback) {
		let notified = false;
		return new Promise((resolve) => {
			function check () {
				const r = cond();
				setTimeout(check, 1000/30); // Schedule first to allow the callback to throw
				if (r && !notified) {
					notified = true;
					resolve();
					if (callback) {
						callback();
					}
				} else if (r && notified) {
					// Skip, wait for cond to be false again
				} else {
					notified = false;
				}
			}
			check();
		});
	}

	function unformatNumber (s) {
		if (s.endsWith("M")) {
			return parseFloat(s) * 1000 * 1000;
		} else if (s.endsWith("K")) {
			return parseFloat(s) * 1000;
		} else if (("" + parseFloat(s)) == s) {
			return parseFloat(s);
		}
	}

	const dungeonsItems = {
		"d1": new Set([
			"chimerical_chest",
			"chimerical_refinement_chest",
			"chimerical_token",
			"chimerical_quiver",
			"chimerical_quiver_refined",
			"griffin_leather",
			"manticore_sting",
			"jackalope_antler",
			"dodocamel_plume",
			"griffin_talon",
			"chimerical_refinement_shard",
			"chimerical_essence",
			"shield_bash",
			"crippling_slash",
			"pestilent_shot",
			"griffin_tunic",
			"griffin_chaps",
			"manticore_shield",
			"jackalope_staff",
			"dodocamel_gauntlets",
			"griffin_bulwark",
		]),
		"d2": new Set([
			"sinister_chest",
			"sinister_refinement_chest",
			"sinister_token",
			"sinister_cape",
			"sinister_cape_refined",
			"acrobats_ribbon",
			"magicians_cloth",
			"chaotic_chain",
			"cursed_ball",
			"sinister_refinement_shard",
			"sinister_essence",
			"penetrating_strike",
			"pestilent_shot",
			"smoke_burst",
			"acrobatic_hood",
			"magicians_hat",
			"chaotic_flail",
			"cursed_bow",
		]),
		"d3": new Set([
			"enchanted_chest",
			"enchanted_refinement_chest",
			"enchanted_token",
			"enchanted_cloak",
			"enchanted_cloak_refined",
			"royal_cloth",
			"knights_ingot",
			"bishops_scroll",
			"regal_jewel",
			"sundering_jewel",
			"enchanted_refinement_shard",
			"enchanted_essence",
			"crippling_slash",
			"penetrating_shot",
			"retribution",
			"mana_spring",
			"knights_aegis",
			"bishops_codex",
			"royal_water_robe_top",
			"royal_water_robe_bottoms",
			"royal_nature_robe_top",
			"royal_nature_robe_bottoms",
			"royal_fire_robe_top",
			"royal_fire_robe_bottoms",
			"furious_spear",
			"regal_sword",
			"sundering_crossbow",
		]),
		"d4": new Set([
			"pirate_chest",
			"pirate_refinement_chest",
			"pirate_token",
			"marksman_brooch",
			"corsair_crest",
			"damaged_anchor",
			"maelstrom_plating",
			"kraken_leather",
			"kraken_fang",
			"pirate_refinement_shard",
			"pirate_essence",
			"shield_bash",
			"fracturing_impact",
			"life_drain",
			"marksman_bracers",
			"corsair_helmet",
			"anchorbound_plate_body",
			"anchorbound_plate_legs",
			"maelstrom_plate_body",
			"maelstrom_plate_legs",
			"kraken_tunic",
			"kraken_chaps",
			"rippling_trident",
			"blooming_trident",
			"blazing_trident",
		]),
	};

	function checkbox ({ label, className, checked }) {
		return `<div class="AchievementsPanel_checkboxControl__3e6CJ ${className}">
				<label class="MuiFormControlLabel-root MuiFormControlLabel-labelPlacementEnd Checkbox_checkbox__dP0DH css-1jaw3da">
					<span class="MuiButtonBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall PrivateSwitchBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall ${checked ? "Mui-checked" : ""} MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall css-zun73v">`
						+ (checked ? `<svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall css-1k33q06" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="CheckBoxIcon">
							<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path>
						</svg>` : "")
						+ (!checked ? `<svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall css-1k33q06" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="CheckBoxOutlineBlankIcon">
							<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path>
						</svg>` : "")
					+ `</span>
					<span class="MuiTypography-root MuiTypography-body1 MuiFormControlLabel-label css-9l3uo3">${label}</span>
				</label>
			</div>`;
	}


	let flags = [
		{ label: "<10", className: "c10", from: 0, to: 9, checked: true },
		{ label: "10-99", className: "c99", from: 10, to: 99, checked: true },
		{ label: "100-999", className: "c999", from: 100, to: 999, checked: true },
		{ label: "1000+", className: "c1000", from: 1000, to: Infinity, checked: true },
		{ label: "Not dungeon", className: "nod", checked: true },
		{ label: "D1", className: "d1", dungeon: "d1", checked: true },
		{ label: "D2", className: "d2", dungeon: "d2", checked: true },
		{ label: "D3", className: "d3", dungeon: "d3", checked: true },
		{ label: "D4", className: "d4", dungeon: "d4", checked: true },
	];

	function rerenderCollectionsFiltering (panelEl) {
		const catsEl = panelEl.parentElement.querySelector(".AchievementsPanel_categories__34hno");

		catsEl.querySelectorAll(".Collection_collectionContainer__3ZlUO").forEach((el) => {
			const n = unformatNumber(el.querySelector(".Collection_count__3oj-t")?.textContent ?? "0");
			const f = flags.find((f) => (f.from || f.to) && f.from <= n && n <= f.to);
			el.dataset.count = f.className;

			for (let d in dungeonsItems) {
				const itemId = el.querySelector("use").getAttribute("href").split("#")[1];
				if (dungeonsItems[d].has(itemId)) {
					el.dataset.dungeon = d;
				}
			}
			if (!el.dataset.dungeon) {
				el.dataset.dungeon = "nod";
			}
		});

		let containerEl = panelEl.querySelector(".ucf-userscript");

		if (!containerEl) {
			panelEl.insertAdjacentHTML("beforeend", `<div class="ucf-userscript" style="display: flex"></div>`);
			containerEl = panelEl.querySelector(".ucf-userscript");
		}

		function updateShowClass (f) {
			if (f.checked) {
				catsEl.classList.add(`show-${f.className}-ucf-userscript`);
			} else {
				catsEl.classList.remove(`show-${f.className}-ucf-userscript`);
			}
		}

		containerEl.innerHTML = flags.map(checkbox).join("");

		flags.forEach((f) => {
			containerEl.querySelector("." + f.className).onclick = (event) => {
				event.stopPropagation();
				f.checked = !f.checked;
				rerenderCollectionsFiltering(panelEl);
			};

			updateShowClass(f);
		});

		catsEl.classList.add("ucf-userscript");
	}

	function isCollectionsOnScreen () {
		return document.querySelector(".TabPanel_tabPanel__tXMJF:not(.TabPanel_hidden__26UM3) .AchievementsPanel_collections__qA6CY .Collection_collectionContainer__3ZlUO");
	}

	function addCollectionsFiltering () {
		const panelEl =  document.querySelector(".TabPanel_tabPanel__tXMJF:not(.TabPanel_hidden__26UM3) .AchievementsPanel_collections__qA6CY .AchievementsPanel_controls__3bGFT");
		if (!panelEl.classList.contains("ucf-userscript")) {
			rerenderCollectionsFiltering(panelEl);

			panelEl.querySelector(".AchievementsPanel_refreshButton__3RYCh").onclick = () => {
				setTimeout(() => {
					rerenderCollectionsFiltering(panelEl);
				}, 500);
			};

			panelEl.parentElement.querySelector(".AchievementsPanel_controls__3bGFT > .AchievementsPanel_checkboxControl__3e6CJ").onclick = () => {
				requestAnimationFrame(() => {
					rerenderCollectionsFiltering(panelEl);
				});
			};
		}
	}

    document.body.insertAdjacentHTML("beforeend", `<style class="ucf-userscript">`
		+ `.AchievementsPanel_categories__34hno.ucf-userscript .Collection_collectionContainer__3ZlUO { display: none; }\n`
		+ flags.map((f) => {
			return ["nod", ...Object.keys(dungeonsItems)].map((d) => {
				return `.AchievementsPanel_categories__34hno.ucf-userscript.show-${f.className}-ucf-userscript.show-${d}-ucf-userscript .Collection_collectionContainer__3ZlUO[data-count="${f.className}"][data-dungeon="${d}"] { display: initial; }\n`
			}).join("");
		}).join("")
		+ ` </style>`);

	waitFnRepeatedFor(isCollectionsOnScreen, addCollectionsFiltering);
})();