From 7fdc0b6850825fc4cea2db1ba99ee18d1b79a822 Mon Sep 17 00:00:00 2001
From: Erix <milllner.eric@gmail.com>
Date: Wed, 13 Mar 2024 18:35:05 +0000
Subject: [PATCH] Dressing room extras

---
 css/facilities/dressingRoom.css             |   8 +-
 js/003-data/gameVariableData.js             |   1 +
 src/art/genAI/clothesPromptPart.js          |  16 +-
 src/facilities/dressingRoom/dressingRoom.js | 238 +++++++++++++++++++-
 4 files changed, 251 insertions(+), 12 deletions(-)

diff --git a/css/facilities/dressingRoom.css b/css/facilities/dressingRoom.css
index 65a70082daa..4d8de0900e6 100644
--- a/css/facilities/dressingRoom.css
+++ b/css/facilities/dressingRoom.css
@@ -13,7 +13,13 @@
 }
 
 .dressing-room-title {
-    margin: 0.5rem 1.5rem;
+    margin: 0.4rem 1.4rem;
+	display: grid;
+	align-items: left;
+}
+
+.dressing-room-label {
+	margin: .1rem;
 }
 
 
diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 3b9361f56d0..e91ea4f1897 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -198,6 +198,7 @@ App.Data.defaultGameStateVariables = {
 	aiUpscaleScale: 1.75,
 	aiUpscaler: "SwinIR_4x",
 	aiWidth: 512,
+	customClothesPrompts: {},
 
 	showAgeDetail: 1,
 	showAppraisal: 1,
diff --git a/src/art/genAI/clothesPromptPart.js b/src/art/genAI/clothesPromptPart.js
index 50cfd634dc6..461c3a691b8 100644
--- a/src/art/genAI/clothesPromptPart.js
+++ b/src/art/genAI/clothesPromptPart.js
@@ -446,7 +446,7 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 	 */
 	getClothes() {
 		let clothes = this.slave.clothes;
-		if (!clothesPrompts.hasOwnProperty(clothes)) {
+		if (!clothesPrompts.hasOwnProperty(clothes) && !V.customClothesPrompts.hasOwnProperty(clothes)) {
 			clothes = "no clothing";
 		}
 		if (this.slave.race === "catgirl") {
@@ -512,7 +512,13 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 	 * @returns {string}
 	 */
 	positive() {
-		const basePrompt = clothesPrompts[this.getClothes()];
+		let basePrompt;
+		if (V.customClothesPrompts.hasOwnProperty(this.getClothes()) && V.customClothesPrompts[this.getClothes()].positive !== '') {
+			basePrompt = V.customClothesPrompts[this.getClothes()];
+		} else {
+			basePrompt = clothesPrompts[this.getClothes()];
+		}
+
 		const coloredPrompt = this.colorReplacer(basePrompt.positive, basePrompt.colors);
 		return this.bodyPartReplacer(coloredPrompt);
 	}
@@ -521,6 +527,10 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 	 * @returns {string}
 	 */
 	negative() {
-		return clothesPrompts[this.getClothes()].negative;
+		if (V.customClothesPrompts.hasOwnProperty(this.getClothes()) && V.customClothesPrompts[this.getClothes()].negative !== '') {
+			return V.customClothesPrompts[this.getClothes()].negative;
+		} else {
+			return clothesPrompts[this.getClothes()].negative;
+		}
 	}
 };
diff --git a/src/facilities/dressingRoom/dressingRoom.js b/src/facilities/dressingRoom/dressingRoom.js
index b1ff138c7c0..1727a3c9ddc 100644
--- a/src/facilities/dressingRoom/dressingRoom.js
+++ b/src/facilities/dressingRoom/dressingRoom.js
@@ -13,6 +13,8 @@ App.UI.DressingRoom.render = function() {
 		App.UI.DressingRoom.modelId = defaultModel?.ID;
 	}
 
+	let customClothesPrompts = V.customClothesPrompts;
+
 	const el = document.createElement("p");
 	el.id = "dressing-room";
 
@@ -57,6 +59,8 @@ App.UI.DressingRoom.render = function() {
 
 	App.UI.DOM.appendNewElement("h2", el, `Outfits`);
 
+	el.append(clothesSearch(model.clothes));
+
 	el.append(clothesBlock());
 
 	return el;
@@ -109,17 +113,235 @@ App.UI.DressingRoom.render = function() {
 				}
 			}
 
-			let txt = clothesProperties.name;
+			el.appendChild(createLabel());
 
-			App.UI.DOM.appendNewElement(
-				'div',
-				el,
-				txt,
-				['dressing-room-title']
-			);
+			return el;
 
+			function createLabel() {
+				const label = document.createElement('div');
+				label.className = 'dressing-room-label';
 
-			return el;
+				label.append(createTitle());
+
+				if (customClothesPrompts[clothingName]) {
+					jQuery(label).append(createOptions());
+				}
+
+				return label;
+
+				/**
+				 * Container for clothing name and links
+				 * @returns {HTMLDivElement} The created title element
+				 */
+				function createTitle() {
+					const title = document.createElement('div');
+					title.className = 'dressing-room-title';
+
+					let hidden = !customClothesPrompts[clothingName];
+					let optElem;
+
+					const links = [
+						App.UI.DOM.link(
+							` Details`,
+							() => {
+								if (hidden) {
+									if (!customClothesPrompts[clothingName]) {
+										optElem = createOptions();
+										jQuery(label).append(optElem);
+									}
+									jQuery(label).children(".options-group").show();
+									hidden = false;
+								} else {
+									jQuery(label).children(".options-group").hide();
+									hidden = true;
+								}
+							}
+						),
+						App.UI.DOM.link(
+							` Reset`,
+							() => {
+								if (customClothesPrompts[clothingName]) {
+									delete customClothesPrompts[clothingName];
+									jQuery(label).children(".options-group").remove();
+									hidden = true;
+								}
+							}
+						)
+					];
+
+					const linkStrip = App.UI.DOM.generateLinksStrip(links);
+
+					title.textContent = clothesProperties.name;
+
+					title.append(linkStrip);
+
+					return title;
+				}
+
+				/**
+				 * Create prompt changing options
+				 * @returns {HTMLDivElement} The created option element
+				 */
+				function createOptions() {
+					const optGroup = new App.UI.OptionsGroup();
+					optGroup.customRefresh(() => {
+						jQuery(label).children(".options-group").remove();
+						jQuery(label).append(createOptions());
+					});
+
+					optGroup.addOption(
+						"Pos Prompt", 'positive',
+						promptPart()).showTextBox()
+						.addComment(createComment('positive'));
+
+					optGroup.addOption(
+						"Neg Prompt", 'negative',
+						promptPart()).showTextBox()
+						.addComment(createComment('negative'));
+
+
+					let optElem = optGroup.render();
+
+					optElem.id = clothesProperties.name + " options";
+
+					return optElem;
+
+					function promptPart() {
+						if (!customClothesPrompts[clothingName]) {
+							customClothesPrompts[clothingName] = {
+								"positive": "",
+								"negative": "",
+							};
+						}
+						return customClothesPrompts[clothingName];
+					}
+				}
+
+				/**
+				 * @param {string} type positive | negative
+				 * @returns {string} prompts
+				 */
+				function createComment(type) {
+					if (customClothesPrompts[clothingName] && customClothesPrompts[clothingName][type] !== '') {
+						return customClothesPrompts[clothingName][type];
+					} else if (clothesPrompts[clothingName]) {
+						return clothesPrompts[clothingName][type];
+					} else {
+						return 'no prompts';
+					}
+				}
+			}
+		}
+	}
+
+	function clothesSearch(clothes) {
+		const el = document.createElement("div");
+		App.UI.DOM.appendNewElement("h4", el, "Search clothes: ");
+
+		const searchBar = document.createElement("input");
+		searchBar.type = "text";
+		searchBar.addEventListener("change", updateSearch);
+		searchBar.addEventListener("submit", updateSearch);
+
+		el.appendChild(searchBar);
+
+		let searchMode;
+
+		el.appendChild(createLinkStrip());
+
+		return el;
+
+		/** onChange Event for search bar */
+		function updateSearch(ev) {
+			const query = ev?.target.value.toLowerCase() ? ev.target.value.toLowerCase() : searchBar.value.toLowerCase();
+
+			jQuery('#id-dressing-room').children().show().not((i, v) => {
+				return ifClothesMatch(v);
+			}).hide();
+
+			/**
+			 * will match for:
+			 * clothing name, presence in prompts
+			 * FS Conformance
+			 * Returns true if the element matches the search query
+			 */
+			function ifClothesMatch(v) {
+				let match = false;
+				switch (searchMode) {
+					case 'names':
+						match = !!v.id.toLowerCase().match(query);
+						break;
+					case 'positive prompts':
+						match = !!customClothesPrompts[v.id]?.positive.toLowerCase().match(query) || !!clothesPrompts[v.id]?.positive.toLowerCase().match(query);
+						break;
+					case 'negative prompts':
+						match = !!clothesPrompts[v.id]?.negative.toLowerCase().match(query) || !!customClothesPrompts[v.id]?.negative.toLowerCase().match(query);
+						break;
+					case 'fs loves':
+						App.Data.clothes.get(v.id)?.fs?.loves?.forEach((v) => {
+							match = !!v.toLowerCase().match(query);
+						});
+						break;
+					case 'fs tolerates':
+						App.Data.clothes.get(v.id)?.fs?.tolerates?.forEach((v) => {
+							match = !!v.toLowerCase().match(query);
+						});
+						break;
+					case 'unlocked':
+						match = (!!V.arcologies[0][App.Data.clothes.get(v.id)?.fs?.unlocks] || !!App.Data.clothes.get(v.id).requirements || !App.Data.clothes.get(v.id).hasOwnProperty('requirements')) && !!v.id.toLowerCase().match(query);
+						break;
+					case 'customized':
+						match = customClothesPrompts[v.id] && !!v.id.toLowerCase().match(query);
+						break;
+					default:
+						match = false;
+						break;
+				}
+
+				return match;
+			}
+		}
+		/**
+		 *
+		 * @returns {HTMLUListElement} linkStrip element populated with searchModes
+		 */
+		function createLinkStrip() {
+			const searchModes = ['customized', 'unlocked', 'names', 'positive prompts', 'negative prompts', 'fs loves', 'fs tolerates'];
+			let linkStrip;
+
+			if (!searchMode) {
+				searchMode = searchModes[1];
+			} else if (customClothesPrompts[clothes]) {
+				searchMode = searchModes[0];
+			}
+
+			return createLinks();
+
+			function createLinks() {
+				const links = [
+					App.UI.DOM.disabledLink(
+						searchMode.toUpperFirst(),
+						['Already selected']
+					)
+				];
+
+				searchModes.filter((v) => {
+					return v !== searchMode;
+				}).forEach((v) => {
+					links.push(
+						App.UI.DOM.link(
+							v.toUpperFirst(),
+							() => {
+								searchMode = v;
+								jQuery('#search-modes').empty().append(createLinks());
+								updateSearch();
+							}
+						));
+				});
+				linkStrip = App.UI.DOM.generateLinksStrip(links);
+				linkStrip.id = 'search-modes';
+				return linkStrip;
+			}
 		}
 	}
 };
-- 
GitLab