mirror of
				https://github.com/MarioSpore/Grinch-AP.git
				synced 2025-10-21 20:21:32 -06:00 
			
		
		
		
	
		
			
	
	
		
			224 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			224 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|   | let deletedOptions = {}; | ||
|  | 
 | ||
|  | window.addEventListener('load', () => { | ||
|  |   const worldName = document.querySelector('#weighted-options').getAttribute('data-game'); | ||
|  | 
 | ||
|  |   // Generic change listener. Detecting unique qualities and acting on them here reduces initial JS initialisation time
 | ||
|  |   // and handles dynamically created elements
 | ||
|  |   document.addEventListener('change', (evt) => { | ||
|  |     // Handle updates to range inputs
 | ||
|  |     if (evt.target.type === 'range') { | ||
|  |       // Update span containing range value. All ranges have a corresponding `{rangeId}-value` span
 | ||
|  |       document.getElementById(`${evt.target.id}-value`).innerText = evt.target.value; | ||
|  | 
 | ||
|  |       // If the changed option was the name of a game, determine whether to show or hide that game's div
 | ||
|  |       if (evt.target.id.startsWith('game||')) { | ||
|  |         const gameName = evt.target.id.split('||')[1]; | ||
|  |         const gameDiv = document.getElementById(`${gameName}-container`); | ||
|  |         if (evt.target.value > 0) { | ||
|  |           gameDiv.classList.remove('hidden'); | ||
|  |         } else { | ||
|  |           gameDiv.classList.add('hidden'); | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |   }); | ||
|  | 
 | ||
|  |   // Generic click listener
 | ||
|  |   document.addEventListener('click', (evt) => { | ||
|  |     // Handle creating new rows for Range options
 | ||
|  |     if (evt.target.classList.contains('add-range-option-button')) { | ||
|  |       const optionName = evt.target.getAttribute('data-option'); | ||
|  |       addRangeRow(optionName); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Handle deleting range rows
 | ||
|  |     if (evt.target.classList.contains('range-option-delete')) { | ||
|  |       const targetRow = document.querySelector(`tr[data-row="${evt.target.getAttribute('data-target')}"]`); | ||
|  |       setDeletedOption( | ||
|  |         targetRow.getAttribute('data-option-name'), | ||
|  |         targetRow.getAttribute('data-value'), | ||
|  |       ); | ||
|  |       targetRow.parentElement.removeChild(targetRow); | ||
|  |     } | ||
|  |   }); | ||
|  | 
 | ||
|  |   // Listen for enter presses on inputs intended to add range rows
 | ||
|  |   document.addEventListener('keydown', (evt) => { | ||
|  |     if (evt.key === 'Enter') { | ||
|  |       evt.preventDefault(); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (evt.key === 'Enter' && evt.target.classList.contains('range-option-value')) { | ||
|  |       const optionName = evt.target.getAttribute('data-option'); | ||
|  |       addRangeRow(optionName); | ||
|  |     } | ||
|  |   }); | ||
|  | 
 | ||
|  |   // Detect form submission
 | ||
|  |   document.getElementById('weighted-options-form').addEventListener('submit', (evt) => { | ||
|  |     // Save data to localStorage
 | ||
|  |     const weightedOptions = {}; | ||
|  |     document.querySelectorAll('input[name]').forEach((input) => { | ||
|  |       const keys = input.getAttribute('name').split('||'); | ||
|  | 
 | ||
|  |       // Determine keys
 | ||
|  |       const optionName = keys[0] ?? null; | ||
|  |       const subOption = keys[1] ?? null; | ||
|  | 
 | ||
|  |       // Ensure keys exist
 | ||
|  |       if (!weightedOptions[optionName]) { weightedOptions[optionName] = {}; } | ||
|  |       if (subOption && !weightedOptions[optionName][subOption]) { | ||
|  |         weightedOptions[optionName][subOption] = null; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (subOption) { return weightedOptions[optionName][subOption] = determineValue(input); } | ||
|  |       if (optionName) { return weightedOptions[optionName] = determineValue(input); } | ||
|  |     }); | ||
|  | 
 | ||
|  |     localStorage.setItem(`${worldName}-weights`, JSON.stringify(weightedOptions)); | ||
|  |     localStorage.setItem(`${worldName}-deletedOptions`, JSON.stringify(deletedOptions)); | ||
|  |   }); | ||
|  | 
 | ||
|  |   // Remove all deleted values as specified by localStorage
 | ||
|  |   deletedOptions = JSON.parse(localStorage.getItem(`${worldName}-deletedOptions`) || '{}'); | ||
|  |   Object.keys(deletedOptions).forEach((optionName) => { | ||
|  |     deletedOptions[optionName].forEach((value) => { | ||
|  |       const targetRow = document.querySelector(`tr[data-row="${value}-row"]`); | ||
|  |       targetRow.parentElement.removeChild(targetRow); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   // Populate all settings from localStorage on page initialisation
 | ||
|  |   const previousSettingsJson = localStorage.getItem(`${worldName}-weights`); | ||
|  |   if (previousSettingsJson) { | ||
|  |     const previousSettings = JSON.parse(previousSettingsJson); | ||
|  |     Object.keys(previousSettings).forEach((option) => { | ||
|  |       if (typeof previousSettings[option] === 'string') { | ||
|  |         return document.querySelector(`input[name="${option}"]`).value = previousSettings[option]; | ||
|  |       } | ||
|  | 
 | ||
|  |       Object.keys(previousSettings[option]).forEach((value) => { | ||
|  |         const input = document.querySelector(`input[name="${option}||${value}"]`); | ||
|  |         if (!input?.type) { | ||
|  |           return console.error(`Unable to populate option with name ${option}||${value}.`); | ||
|  |         } | ||
|  | 
 | ||
|  |         switch (input.type) { | ||
|  |           case 'checkbox': | ||
|  |             input.checked = (parseInt(previousSettings[option][value], 10) === 1); | ||
|  |             break; | ||
|  |           case 'range': | ||
|  |             input.value = parseInt(previousSettings[option][value], 10); | ||
|  |             break; | ||
|  |           case 'number': | ||
|  |             input.value = previousSettings[option][value].toString(); | ||
|  |             break; | ||
|  |           default: | ||
|  |             console.error(`Found unsupported input type: ${input.type}`); | ||
|  |         } | ||
|  |       }); | ||
|  |     }); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | const addRangeRow = (optionName) => { | ||
|  |   const inputQuery = `input[type=number][data-option="${optionName}"].range-option-value`; | ||
|  |   const inputTarget = document.querySelector(inputQuery); | ||
|  |   const newValue = inputTarget.value; | ||
|  |   if (!/^-?\d+$/.test(newValue)) { | ||
|  |     alert('Range values must be a positive or negative integer!'); | ||
|  |     return; | ||
|  |   } | ||
|  |   inputTarget.value = ''; | ||
|  |   const tBody = document.querySelector(`table[data-option="${optionName}"].range-rows tbody`); | ||
|  |   const tr = document.createElement('tr'); | ||
|  |   tr.setAttribute('data-row', `${optionName}-${newValue}-row`); | ||
|  |   tr.setAttribute('data-option-name', optionName); | ||
|  |   tr.setAttribute('data-value', newValue); | ||
|  |   const tdLeft = document.createElement('td'); | ||
|  |   tdLeft.classList.add('td-left'); | ||
|  |   const label = document.createElement('label'); | ||
|  |   label.setAttribute('for', `${optionName}||${newValue}`); | ||
|  |   label.innerText = newValue.toString(); | ||
|  |   tdLeft.appendChild(label); | ||
|  |   tr.appendChild(tdLeft); | ||
|  |   const tdMiddle = document.createElement('td'); | ||
|  |   tdMiddle.classList.add('td-middle'); | ||
|  |   const range = document.createElement('input'); | ||
|  |   range.setAttribute('type', 'range'); | ||
|  |   range.setAttribute('min', '0'); | ||
|  |   range.setAttribute('max', '50'); | ||
|  |   range.setAttribute('value', '0'); | ||
|  |   range.setAttribute('id', `${optionName}||${newValue}`); | ||
|  |   range.setAttribute('name', `${optionName}||${newValue}`); | ||
|  |   tdMiddle.appendChild(range); | ||
|  |   tr.appendChild(tdMiddle); | ||
|  |   const tdRight = document.createElement('td'); | ||
|  |   tdRight.classList.add('td-right'); | ||
|  |   const valueSpan = document.createElement('span'); | ||
|  |   valueSpan.setAttribute('id', `${optionName}||${newValue}-value`); | ||
|  |   valueSpan.innerText = '0'; | ||
|  |   tdRight.appendChild(valueSpan); | ||
|  |   tr.appendChild(tdRight); | ||
|  |   const tdDelete = document.createElement('td'); | ||
|  |   const deleteSpan = document.createElement('span'); | ||
|  |   deleteSpan.classList.add('range-option-delete'); | ||
|  |   deleteSpan.classList.add('js-required'); | ||
|  |   deleteSpan.setAttribute('data-target', `${optionName}-${newValue}-row`); | ||
|  |   deleteSpan.innerText = '❌'; | ||
|  |   tdDelete.appendChild(deleteSpan); | ||
|  |   tr.appendChild(tdDelete); | ||
|  |   tBody.appendChild(tr); | ||
|  | 
 | ||
|  |   // Remove this option from the set of deleted options if it exists
 | ||
|  |   unsetDeletedOption(optionName, newValue); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Determines the value of an input element, or returns a 1 or 0 if the element is a checkbox | ||
|  |  * | ||
|  |  * @param {object} input - The input element. | ||
|  |  * @returns {number} The value of the input element. | ||
|  |  */ | ||
|  | const determineValue = (input) => { | ||
|  |   switch (input.type) { | ||
|  |     case 'checkbox': | ||
|  |       return (input.checked ? 1 : 0); | ||
|  |     case 'range': | ||
|  |       return parseInt(input.value, 10); | ||
|  |     default: | ||
|  |       return input.value; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Sets the deleted option value for a given world and option name. | ||
|  |  * If the world or option does not exist, it creates the necessary entries. | ||
|  |  * | ||
|  |  * @param {string} optionName - The name of the option. | ||
|  |  * @param {*} value - The value to be set for the deleted option. | ||
|  |  * @returns {void} | ||
|  |  */ | ||
|  | const setDeletedOption = (optionName, value) => { | ||
|  |   deletedOptions[optionName] = deletedOptions[optionName] || []; | ||
|  |   deletedOptions[optionName].push(`${optionName}-${value}`); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Removes a specific value from the deletedOptions object. | ||
|  |  * | ||
|  |  * @param {string} optionName - The name of the option. | ||
|  |  * @param {*} value - The value to be removed | ||
|  |  * @returns {void} | ||
|  |  */ | ||
|  | const unsetDeletedOption = (optionName, value) => { | ||
|  |   if (!deletedOptions.hasOwnProperty(optionName)) { return; } | ||
|  |   if (deletedOptions[optionName].includes(`${optionName}-${value}`)) { | ||
|  |     deletedOptions[optionName].splice(deletedOptions[optionName].indexOf(`${optionName}-${value}`), 1); | ||
|  |   } | ||
|  |   if (deletedOptions[optionName].length === 0) { | ||
|  |     delete deletedOptions[optionName]; | ||
|  |   } | ||
|  | }; |