From 618c102eadebe3dece9e21e91fe7233db3c17a75 Mon Sep 17 00:00:00 2001
From: Arkerthan <arkerthan@mailbox.org>
Date: Wed, 1 Mar 2023 18:40:21 +0100
Subject: [PATCH] Refactor broken quick links

---
 css/gui/slaveList/quickList.css    |  99 +++++++++++++++
 css/gui/slaveList/quickListCSS.css | 101 ---------------
 devTools/types/FC/gameState.d.ts   |   1 -
 src/gui/options/options.js         |   4 +-
 src/js/quickListJS.js              |  31 -----
 src/js/slaveListing.js             | 195 ++++++++++++-----------------
 6 files changed, 182 insertions(+), 249 deletions(-)
 create mode 100644 css/gui/slaveList/quickList.css
 delete mode 100644 css/gui/slaveList/quickListCSS.css

diff --git a/css/gui/slaveList/quickList.css b/css/gui/slaveList/quickList.css
new file mode 100644
index 00000000000..e307ca96368
--- /dev/null
+++ b/css/gui/slaveList/quickList.css
@@ -0,0 +1,99 @@
+button.quick-button {
+	width: auto;
+	margin-left: 10px;
+}
+
+.quick-list {
+	text-align: center;
+}
+
+.quick-list button {
+	margin: 10px;
+	border: 2px solid var(--button-border-color);
+	background-color: var(--button-color);
+}
+
+.quick-list button:hover {
+	background-color: var(--button-hover-color);
+}
+
+.quick-list.devotion .mindbroken {
+	background-color: red;
+}
+
+.quick-list.devotion .very-hateful {
+	background-color: darkviolet;
+}
+
+.quick-list.devotion .hateful {
+	background-color: darkviolet;
+}
+
+.quick-list.devotion .resistant {
+	background-color: mediumorchid;
+}
+
+.quick-list.devotion .ambivalent {
+	background-color: yellow;
+	color: #111;
+}
+
+.quick-list.devotion .accepting {
+	background-color: hotpink;
+}
+
+.quick-list.devotion .devoted {
+	background-color: deeppink;
+}
+
+.quick-list.devotion .worshipful {
+	background-color: magenta;
+}
+
+.quick-list.trust .mindbroken {
+	background-color: red;
+}
+
+.quick-list.trust .extremely-terrified {
+	background-color: darkgoldenrod;
+}
+
+.quick-list.trust .terrified {
+	background-color: goldenrod;
+	color: #111;
+}
+
+.quick-list.trust .frightened {
+	background-color: gold;
+	color: #111;
+}
+
+.quick-list.trust .fearful {
+	background-color: yellow;
+	color: #111;
+}
+
+.quick-list.trust .hate-careful {
+	background-color: orange;
+}
+
+.quick-list.trust .careful {
+	background-color: mediumaquamarine;
+	color: #111;
+}
+
+.quick-list.trust .bold {
+	background-color: orangered;
+}
+
+.quick-list.trust .trusting {
+	background-color: mediumseagreen;
+}
+
+.quick-list.trust .defiant {
+	background-color: darkred;
+}
+
+.quick-list.trust .profoundly-trusting {
+	background-color: seagreen;
+}
diff --git a/css/gui/slaveList/quickListCSS.css b/css/gui/slaveList/quickListCSS.css
deleted file mode 100644
index cd6a54705a6..00000000000
--- a/css/gui/slaveList/quickListCSS.css
+++ /dev/null
@@ -1,101 +0,0 @@
-.ql-hidden
-{
-	display:none;
-}
-
-div.quick-list.devotion button.mindbroken
-{
-	background-color:red;
-}
-div.quick-list.devotion button.very-hateful
-{
-	background-color:darkviolet;
-}
-div.quick-list.devotion button.hateful
-{
-	background-color:darkviolet;
-}
-div.quick-list.devotion button.resistant
-{
-	background-color:mediumorchid;
-}
-div.quick-list.devotion button.ambivalent
-{
-	background-color: yellow;
-	color: #444444;
-}
-div.quick-list.devotion button.accepting
-{
-	background-color: hotpink;
-}
-div.quick-list.devotion button.devoted
-{
-	background-color: deeppink;
-}
-div.quick-list.devotion button.worshipful
-{
-	background-color: magenta;
-}
-div.quick-list.trust button.mindbroken
-{
-	background-color:red;
-}
-div.quick-list.trust button.extremely-terrified
-{
-	background-color: darkgoldenrod;
-}
-div.quick-list.trust button.terrified
-{
-	background-color: goldenrod;
-}
-div.quick-list.trust button.frightened
-{
-	background-color: gold;
-	color: coral;
-}
-div.quick-list.trust button.fearful
-{
-	background-color: yellow;
-	color: green;
-}
-div.quick-list.trust button.hate-careful
-{
-	background-color: orange;
-}
-div.quick-list.trust button.careful
-{
-	background-color: mediumaquamarine;
-	color: forestgreen;
-}
-div.quick-list.trust button.bold
-{
-	background-color: orangered;
-}
-div.quick-list.trust button.trusting
-{
-	background-color: mediumseagreen;
-}
-div.quick-list.trust button.defiant
-{
-	background-color: darkred;
-}
-div.quick-list.trust button.profoundly-trusting
-{
-	background-color: seagreen;
-}
-div.quick-list
-{
-	table-layout: fixed;
-	text-align: center;
-	border-collapse: separate;
-	border-spacing: 2px;
-	border-style: hidden;
-	empty-cells: hide;
-	width: 70%;
-}
-div.quick-list button
-{
-	margin-top: 15px;
-	margin-right: 20px;
-	white-space: nowrap;
-}
diff --git a/devTools/types/FC/gameState.d.ts b/devTools/types/FC/gameState.d.ts
index 0eeaaed949a..e9515407344 100644
--- a/devTools/types/FC/gameState.d.ts
+++ b/devTools/types/FC/gameState.d.ts
@@ -102,7 +102,6 @@ declare namespace FC {
 	 */
 	interface TemporaryVariablesInTheGameState {
 		gameover?: string;
-		sortQuickList?: string;
 		/** @deprecated */
 		returnTo: string;
 
diff --git a/src/gui/options/options.js b/src/gui/options/options.js
index 9e5acf93d9c..690127b3c03 100644
--- a/src/gui/options/options.js
+++ b/src/gui/options/options.js
@@ -842,8 +842,8 @@ App.Intro.display = function(isIntro) {
 	options.addOption("Main menu slave tabs are", "useSlaveSummaryTabs")
 		.addValue("Enabled", 1).on().addValue("CardStyle", 2).on().addValue("Disabled", 0).off();
 
-	options.addOption("The slave Quick list in-page scroll-to is", "useSlaveListInPageJSNavigation")
-		.addValue("Enabled", 1).on().addValue("Disabled", 0).off();
+	options.addOption("The slave list in-page scroll-to is", "useSlaveListInPageJSNavigation")
+		.addValue("Always", 2).on().addValue("Collapsable", 1).on().addValue("Disabled", 0).off();
 
 	options.addOption("Condense special slaves into their own tab", "useSlaveSummaryOverviewTab")
 		.addValue("Enabled", 1).on().addValue("Disabled", 0).off();
diff --git a/src/js/quickListJS.js b/src/js/quickListJS.js
index 6f6b45c2bfe..db5cbc3dbe4 100644
--- a/src/js/quickListJS.js
+++ b/src/js/quickListJS.js
@@ -16,37 +16,6 @@ globalThis.sortDomObjects = function(objects, attrName, reverse = 0) {
 	return objects.toArray().sort(sortingByAttr);
 };
 
-globalThis.sortButtonsByDevotion = function() {
-	let $sortedButtons = $('#qlWrapper button').remove();
-	$sortedButtons = sortDomObjects($sortedButtons, 'data-devotion');
-	$($sortedButtons).appendTo($('#qlWrapper'));
-	quickListBuildLinks();
-};
-
-globalThis.sortButtonsByTrust = function() {
-	let $sortedButtons = $('#qlWrapper button').remove();
-	$sortedButtons = sortDomObjects($sortedButtons, 'data-trust');
-	$($sortedButtons).appendTo($('#qlWrapper'));
-	quickListBuildLinks();
-};
-
-globalThis.quickListBuildLinks = function() {
-	$("[data-scroll-to]").click(App.UI.quickBtnScrollToHandler);
-};
-
-App.UI.quickBtnScrollToHandler = function() {
-	let $this = $(this);
-	let $toElement = $this.attr('data-scroll-to');
-	// note the * 1 enforces $offset to be an integer, without
-	// it we scroll to True, which goes nowhere fast.
-	let $offset = parseInt($this.attr('data-scroll-offset') || "0");
-	let $speed = parseInt($this.attr('data-scroll-speed') || "500");
-	// Use javascript scrollTop animation for in page navigation.
-	$('html, body').animate({
-		scrollTop: $($toElement).offset().top + $offset
-	}, $speed);
-};
-
 globalThis.sortNurseryPossiblesByName = function() {
 	let $sortedNurseryPossibles = $('#ql-nursery div.possible').detach();
 	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-name');
diff --git a/src/js/slaveListing.js b/src/js/slaveListing.js
index 4c4831e3267..aab9d64ec23 100644
--- a/src/js/slaveListing.js
+++ b/src/js/slaveListing.js
@@ -21,14 +21,12 @@ App.UI.SlaveList = {};
 
 App.UI.SlaveList.render = function() {
 	const facilityPassages = new Set(
-		["Main", "Head Girl Suite", "Spa", "Brothel", "Club", "Arcade", "Clinic", "Schoolroom", "Dairy", "Farmyard", "Servants' Quarters", "Master Suite", "Cellblock"]);
+		["Main", "Head Girl Suite", "Spa", "Brothel", "Club", "Arcade", "Clinic", "Schoolroom", "Dairy", "Farmyard",
+			"Servants' Quarters", "Master Suite", "Cellblock"]);
 
 	/** @type {string} */
 	let passageName;
 
-	// potentially can be a problem if played long enough to reach Number.MAX_SAFE_INTEGER
-	let listID = Number.MIN_SAFE_INTEGER;
-
 	/** @type {App.Art.SlaveArtBatch} */
 	let batchRenderer = null;
 
@@ -43,6 +41,8 @@ App.UI.SlaveList.render = function() {
 	 */
 	function listDOM(IDs, rejectedSlaves, interactionLink, postNote) {
 		passageName = passage();
+		// required, if multiple list are displayed on the same passage
+		const uuid = generateNewID();
 
 		let res = document.createDocumentFragment();
 
@@ -53,9 +53,7 @@ App.UI.SlaveList.render = function() {
 			batchRenderer = null;
 		}
 
-		if (V.useSlaveListInPageJSNavigation === 1) {
-			res.appendChild(createQuickList(IDs));
-		}
+		res.append(createQuickList(IDs, uuid));
 
 		const fcs = App.Entity.facilities;
 		const penthouse = fcs.penthouse;
@@ -80,7 +78,7 @@ App.UI.SlaveList.render = function() {
 		for (const sid of IDs) {
 			let ss = renderSlave(sid, interactionLink, showTransfers, postNote);
 			let slaveDiv = document.createElement("div");
-			slaveDiv.id = `slave-${sid}`;
+			slaveDiv.id = `slave-${sid}-${uuid}`;
 			slaveDiv.classList.add("slaveSummary");
 			if (V.slavePanelStyle === 2) {
 				slaveDiv.classList.add("card");
@@ -327,117 +325,77 @@ App.UI.SlaveList.render = function() {
 
 	/**
 	 * @param {number[]} IDs
+	 * @param {string} uuid
 	 * @returns {DocumentFragment}
 	 */
-	function createQuickList(IDs) {
+	function createQuickList(IDs, uuid) {
+		if (V.useSlaveListInPageJSNavigation < 1 || IDs.length < 2 || passageName !== "Main") {
+			return new DocumentFragment();
+		}
+
+		const f = new DocumentFragment();
+		let currentSort = null;
+
+		let toggleButton;
+		if (V.useSlaveListInPageJSNavigation === 1) {
+			toggleButton = App.UI.DOM.appendNewElement("button", f, "Quick-List", ["quick-button"]);
+		}
+
+		const indexSorting = document.createElement("div");
+		f.append(indexSorting);
+
+		const sortSpan = App.UI.DOM.makeElement("span", "None", ["bold"]);
+
+		indexSorting.append("Sorting: ", sortSpan, " ",
+			App.UI.DOM.link("Sort by Devotion",
+				() => sortButtons("Devotion", (a, b) => b.devotion - a.devotion, "devotion")),
+			" | ",
+			App.UI.DOM.link("Sort by Trust", () => sortButtons("Trust", (a, b) => b.trust - a.trust, "trust"))
+		);
+
+		const indexContainer = document.createElement("div");
+		indexContainer.classList.add("quick-list");
+		f.append(indexContainer);
 		/**
-		 *
-		 * @param {Node} container
-		 * @param {string} tagName
-		 * @param {string} [content]
-		 * @param {string|string[]} [classNames]
-		 * @param {Object.<string, any>} [attributes]
-		 * @returns {HTMLElement}
+		 * @typedef {object} JumpButton
+		 * @property {App.Entity.SlaveState} slave
+		 * @property {HTMLButtonElement} button
 		 */
-		function makeElement(container, tagName, content, classNames, attributes) {
-			let res = document.createElement(tagName);
-			container.appendChild(res);
-			if (content) {
-				res.textContent = content;
-			}
-			if (Array.isArray(classNames)) {
-				for (const c of classNames) {
-					res.classList.add(c);
-				}
-			} else if (classNames !== undefined) {
-				res.classList.add(classNames);
-			}
-
-			if (attributes) {
-				for (const [k, v] of Object.entries(attributes)) {
-					res.setAttribute(k, v);
-				}
-			}
-			return res;
+		/**
+		 * @type {JumpButton[]}
+		 */
+		const jumpButtons = [];
+		for (const id of IDs) {
+			const btn = document.createElement("button");
+			const slave = getSlave(id);
+			btn.append(SlaveFullName(slave));
+			btn.classList.add(getSlaveDevotionClass(slave), getSlaveTrustClass(slave));
+			btn.onclick = () => $('html, body').animate({scrollTop: $(`#slave-${id}-${uuid}`).offset().top - 50}, 300);
+			jumpButtons.push({slave: slave, button: btn});
+			indexContainer.append(btn);
 		}
 
-		const res = document.createDocumentFragment();
+		if (V.useSlaveListInPageJSNavigation === 1) {
+			App.UI.DOM.elementToggle(toggleButton, [indexSorting, indexContainer]);
+		}
 
-		/* Useful for finding weird combinations — usages of this passage that don't yet generate the quick index.
-		*	<<print 'pass/count/indexed/flag::[' + passageName + '/' + Count + '/' + indexed + '/' + V.SlaveSummaryFiler + ']'>>
-		*/
+		return f;
 
-		if (IDs.length > 1 && passageName === "Main") {
-			const buttons = [];
-			let offset = -50;
-			if (/Select/i.test(passageName)) {
-				offset = -25;
-			}
-			/*
-			 * we want <button data-quick-index="<<= listID>>">...
-			 */
-			const quickIndexBtn = document.createElement("button");
-			res.appendChild(quickIndexBtn);
-			quickIndexBtn.id = `quick-list-toggle${listID}`;
-			quickIndexBtn.setAttribute('data-quick-index', listID.toString());
-			quickIndexBtn.onclick = function(ev) {
-				let which = /** @type {HTMLElement} */ (ev.target).attributes["data-quick-index"].value;
-				let quick = $("div#list_index" + which);
-				quick.toggleClass("ql-hidden");
-			};
-			/*
-			 * we want <div id="list_index3" class=" hidden">...
-			 */
-			const listIndex = makeElement(res, "div", undefined, "ql-hidden");
-			listIndex.id = `list_index${listID}`;
-
-			for (const sID of IDs) {
-				const IndexSlave = slaveStateById(sID);
-				const indexSlaveName = SlaveFullName(IndexSlave);
-				const devotionClass = getSlaveDevotionClass(IndexSlave);
-				const trustClass = getSlaveTrustClass(IndexSlave);
-				buttons.push({
-					"data-name": indexSlaveName,
-					"data-scroll-to": `#slave-${IndexSlave.ID}`,
-					"data-scroll-offset": offset,
-					"data-devotion": IndexSlave.devotion,
-					"data-trust": IndexSlave.trust,
-					"class": `${devotionClass} ${trustClass}`
-				});
-			}
-			if (buttons.length > 0) {
-				V.sortQuickList = V.sortQuickList || 'Devotion';
-				makeElement(listIndex, "em", "Sorting: ");
-				const qlSort = makeElement(listIndex, "span", V.sortQuickList, "strong");
-				qlSort.id = "qlSort";
-				listIndex.appendChild(document.createTextNode(". "));
-				const linkSortByDevotion = makeElement(listIndex, "a", "Sort by Devotion");
-				linkSortByDevotion.onclick = (ev) => {
-					ev.preventDefault();
-					V.sortQuickList = "Devotion";
-					$("#qlSort").text(V.sortQuickList);
-					$("#qlWrapper").removeClass("trust").addClass("devotion");
-					sortButtonsByDevotion();
-				};
-				listIndex.append(" | ");
-				const linkSortByTrust = makeElement(listIndex, "a", "Sort by Trust");
-				linkSortByTrust.onclick = (ev) => {
-					ev.preventDefault();
-					V.sortQuickList = "Trust";
-					$("#qlSort").text(V.sortQuickList);
-					$("#qlWrapper").removeClass("devotion").addClass("trust");
-					sortButtonsByTrust();
-				};
-				makeElement(listIndex, "br");
-				const qlWrapper = makeElement(listIndex, "div", undefined, ["quick-list", "devotion"]);
-				qlWrapper.id = "qlWrapper";
-				for (const button of buttons) {
-					const btn = makeElement(listIndex, 'button', button['data-name'], undefined, button);
-					btn.onclick = App.UI.quickBtnScrollToHandler;
-				}
+		/**
+		 * @param {string} name
+		 * @param {function(FC.SlaveState, FC.SlaveState):number} compareFn
+		 * @param {string} colorClass
+		 */
+		function sortButtons(name, compareFn, colorClass) {
+			$(sortSpan).empty().append(name);
+			jumpButtons.sort((a, b) => compareFn(a.slave, b.slave));
+			$(indexContainer).empty().append(jumpButtons.map(v => v.button));
+			if (currentSort) {
+				indexContainer.classList.remove(currentSort);
 			}
+			indexContainer.classList.add(colorClass);
+			currentSort = colorClass;
 		}
-		return res;
 	}
 }();
 
@@ -548,7 +506,8 @@ App.UI.SlaveList.sortingLinks = function(passage) {
 	const textify = string => capFirstChar(string.replace(/([A-Z])/g, " $1"));
 
 	let span = App.UI.DOM.makeElement("span", "Sort by: ");
-	let order = ["devotion", "trust", "name", "assignment", "seniority", "actualAge", "visualAge", "physicalAge", "weeklyIncome", "beauty", "health", "weight", "muscles", "intelligence", "sexDrive", "pregnancy", "prestige"];
+	let order = ["devotion", "trust", "name", "assignment", "seniority", "actualAge", "visualAge", "physicalAge",
+		"weeklyIncome", "beauty", "health", "weight", "muscles", "intelligence", "sexDrive", "pregnancy", "prestige"];
 	const orderMap = order.map(so => {
 		return {key: so, name: capFirstChar(so)};
 	});
@@ -564,7 +523,9 @@ App.UI.SlaveList.sortingLinks = function(passage) {
 	span = App.UI.DOM.makeElement("span", " Sort direction: ");
 	order = ["descending", "ascending"];
 	span.append(App.UI.DOM.generateLinksStrip(order.map(so => V.sortSlavesOrder !== so
-		? App.UI.DOM.passageLink(textify(so), passage, () => { V.sortSlavesOrder = so; }) : textify(so))));
+		? App.UI.DOM.passageLink(textify(so), passage, () => {
+			V.sortSlavesOrder = so;
+		}) : textify(so))));
 	div.append(span);
 
 	return div;
@@ -1088,7 +1049,9 @@ App.UI.SlaveList.slaveSelectionList = function() {
 	 */
 	let slaveSelectionList = null;
 
-	$(document).on(':passageinit', () => { slaveSelectionList = null; });
+	$(document).on(':passageinit', () => {
+		slaveSelectionList = null;
+	});
 
 	return selection;
 
@@ -1100,7 +1063,9 @@ App.UI.SlaveList.slaveSelectionList = function() {
 	 * @returns {HTMLElement}
 	 */
 	function selection(filter, interactionLink, experienceChecker, postNote) {
-		if (experienceChecker === null) { experienceChecker = undefined; }
+		if (experienceChecker === null) {
+			experienceChecker = undefined;
+		}
 		options = {
 			filter: filter,
 			interactionLink: interactionLink,
@@ -1180,7 +1145,9 @@ App.UI.SlaveList.slaveSelectionList = function() {
 			if (fr === true || (Array.isArray(fr) && fr.length === 0)) {
 				passingIDs.push(id);
 			} else {
-				if (Array.isArray(fr)) { rejects.push({id: id, rejects: fr}); }
+				if (Array.isArray(fr)) {
+					rejects.push({id: id, rejects: fr});
+				}
 			}
 		});
 
-- 
GitLab