From 98a125b00c668f74ca6fa43d0c35dae0c3562e87 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Wed, 27 Dec 2023 20:42:57 -0500 Subject: [PATCH] Nuke weighted-options. Set up framework to rebuild it in Jinja. --- WebHostLib/misc.py | 7 +- WebHostLib/static/assets/weighted-options.js | 1200 ----------------- WebHostLib/static/assets/weightedOptions.js | 3 + .../weightedOptions/weightedOptions.css | 324 +++++ .../weightedOptions/weightedOptions.css.map | 1 + .../weightedOptions.scss} | 2 +- .../templates/weightedOptions/macros.html | 96 ++ .../weightedOptions.html} | 11 +- 8 files changed, 435 insertions(+), 1209 deletions(-) delete mode 100644 WebHostLib/static/assets/weighted-options.js create mode 100644 WebHostLib/static/assets/weightedOptions.js create mode 100644 WebHostLib/static/styles/weightedOptions/weightedOptions.css create mode 100644 WebHostLib/static/styles/weightedOptions/weightedOptions.css.map rename WebHostLib/static/styles/{weighted-options.css => weightedOptions/weightedOptions.scss} (99%) create mode 100644 WebHostLib/templates/weightedOptions/macros.html rename WebHostLib/templates/{weighted-options.html => weightedOptions/weightedOptions.html} (78%) diff --git a/WebHostLib/misc.py b/WebHostLib/misc.py index 1e3085e5e4d0..df343c6e22d8 100644 --- a/WebHostLib/misc.py +++ b/WebHostLib/misc.py @@ -52,7 +52,12 @@ def weighted_settings(): @app.route("/weighted-options") @cache.cached() def weighted_options(): - return render_template("weighted-options.html") + return render_template( + "weightedOptions/weightedOptions.html", + worlds=AutoWorldRegister.world_types, + issubclass=issubclass, + Options=Options, + ) # TODO for back compat. remove around 0.4.5 diff --git a/WebHostLib/static/assets/weighted-options.js b/WebHostLib/static/assets/weighted-options.js deleted file mode 100644 index 02c566a81a43..000000000000 --- a/WebHostLib/static/assets/weighted-options.js +++ /dev/null @@ -1,1200 +0,0 @@ -window.addEventListener('load', () => { - fetchSettingData().then((data) => { - let settingHash = localStorage.getItem('weighted-settings-hash'); - if (!settingHash) { - // If no hash data has been set before, set it now - settingHash = md5(JSON.stringify(data)); - localStorage.setItem('weighted-settings-hash', settingHash); - localStorage.removeItem('weighted-settings'); - } - - if (settingHash !== md5(JSON.stringify(data))) { - const userMessage = document.getElementById('user-message'); - userMessage.innerText = "Your settings are out of date! Click here to update them! Be aware this will reset " + - "them all to default."; - userMessage.classList.add('visible'); - userMessage.addEventListener('click', resetSettings); - } - - // Page setup - const settings = new WeightedSettings(data); - settings.buildUI(); - settings.updateVisibleGames(); - adjustHeaderWidth(); - - // Event listeners - document.getElementById('export-options').addEventListener('click', () => settings.export()); - document.getElementById('generate-race').addEventListener('click', () => settings.generateGame(true)); - document.getElementById('generate-game').addEventListener('click', () => settings.generateGame()); - - // Name input field - const nameInput = document.getElementById('player-name'); - nameInput.setAttribute('data-type', 'data'); - nameInput.setAttribute('data-setting', 'name'); - nameInput.addEventListener('keyup', (evt) => settings.updateBaseSetting(evt)); - nameInput.value = settings.current.name; - }); -}); - -const resetSettings = () => { - localStorage.removeItem('weighted-settings'); - localStorage.removeItem('weighted-settings-hash') - window.location.reload(); -}; - -const fetchSettingData = () => new Promise((resolve, reject) => { - fetch(new Request(`${window.location.origin}/static/generated/weighted-options.json`)).then((response) => { - try{ response.json().then((jsonObj) => resolve(jsonObj)); } - catch(error){ reject(error); } - }); -}); - -/// The weighted settings across all games. -class WeightedSettings { - // The data from the server describing the types of settings available for - // each game, as a JSON-safe blob. - data; - - // The settings chosen by the user as they'd appear in the YAML file, stored - // to and retrieved from local storage. - current; - - // A record mapping game names to the associated GameSettings. - games; - - constructor(data) { - this.data = data; - this.current = JSON.parse(localStorage.getItem('weighted-settings')); - this.games = Object.keys(this.data.games).map((game) => new GameSettings(this, game)); - if (this.current) { return; } - - this.current = {}; - - // Transfer base options directly - for (let baseOption of Object.keys(this.data.baseOptions)){ - this.current[baseOption] = this.data.baseOptions[baseOption]; - } - - // Set options per game - for (let game of Object.keys(this.data.games)) { - this.current[game] = {}; - - for (let optionGroup of Object.keys(this.data.games[game].gameOptionGroups)) { - this.current[game][optionGroup] = {}; - - for (let gameSetting of Object.keys(this.data.games[game].gameOptionGroups[optionGroup])) { - this.current[game][optionGroup][gameSetting] = {}; - - const setting = this.data.games[game].gameOptionGroups[optionGroup][gameSetting]; - switch(setting.type){ - case 'select': - setting.options.forEach((option) => { - this.current[game][optionGroup][gameSetting][option.value] = - (setting.hasOwnProperty('defaultValue') && setting.defaultValue === option.value) ? 25 : 0; - }); - break; - case 'range': - case 'named_range': - this.current[game][optionGroup][gameSetting]['random'] = 0; - this.current[game][optionGroup][gameSetting]['random-low'] = 0; - this.current[game][optionGroup][gameSetting]['random-middle'] = 0; - this.current[game][optionGroup][gameSetting]['random-high'] = 0; - if (setting.hasOwnProperty('defaultValue')) { - this.current[game][optionGroup][gameSetting][setting.defaultValue] = 25; - } else { - this.current[game][optionGroup][gameSetting][setting.min] = 25; - } - break; - - case 'items-list': - case 'locations-list': - case 'custom-list': - this.current[game][optionGroup][gameSetting] = setting.defaultValue; - break; - - default: - console.error(`Unknown setting type for ${game} setting ${gameSetting}: ${setting.type}`); - } - } - } - - this.current[game]['Item & Location Options'].start_inventory = {}; - this.current[game]['Item & Location Options'].exclude_locations = []; - this.current[game]['Item & Location Options'].priority_locations = []; - this.current[game]['Item & Location Options'].local_items = []; - this.current[game]['Item & Location Options'].non_local_items = []; - this.current[game]['Item & Location Options'].start_hints = []; - this.current[game]['Item & Location Options'].start_location_hints = []; - } - - this.save(); - } - - // Saves the current settings to local storage. - save() { - localStorage.setItem('weighted-settings', JSON.stringify(this.current)); - } - - buildUI() { - // Build the game-choice div - this.#buildGameChoice(); - - const gamesWrapper = document.getElementById('games-wrapper'); - this.games.forEach((game) => { - gamesWrapper.appendChild(game.buildUI()); - }); - } - - #buildGameChoice() { - const gameChoiceDiv = document.getElementById('game-choice'); - const h2 = document.createElement('h2'); - h2.innerText = 'Game Select'; - gameChoiceDiv.appendChild(h2); - - const gameSelectDescription = document.createElement('p'); - gameSelectDescription.classList.add('setting-description'); - gameSelectDescription.innerText = 'Choose which games you might be required to play.'; - gameChoiceDiv.appendChild(gameSelectDescription); - - const hintText = document.createElement('p'); - hintText.classList.add('hint-text'); - hintText.innerText = 'If a game\'s value is greater than zero, you can click it\'s name to jump ' + - 'to that section.' - gameChoiceDiv.appendChild(hintText); - - // Build the game choice table - const table = document.createElement('table'); - const tbody = document.createElement('tbody'); - - Object.keys(this.data.games).forEach((game) => { - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - const span = document.createElement('span'); - span.innerText = game; - span.setAttribute('id', `${game}-game-option`) - tdLeft.appendChild(span); - 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('data-type', 'weight'); - range.setAttribute('data-setting', 'game'); - range.setAttribute('data-option', game); - range.value = this.current.game[game]; - range.addEventListener('change', (evt) => { - this.updateBaseSetting(evt); - this.updateVisibleGames(); // Show or hide games based on the new settings - }); - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `game-${game}`) - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - tbody.appendChild(tr); - }); - - table.appendChild(tbody); - gameChoiceDiv.appendChild(table); - } - - // Verifies that `this.settings` meets all the requirements for world - // generation, normalizes it for serialization, and returns the result. - #validateSettings() { - const settings = structuredClone(this.current); - const userMessage = document.getElementById('user-message'); - let errorMessage = null; - - // User must choose a name for their file - if ( - !settings.name || - settings.name.toString().trim().length === 0 || - settings.name.toString().toLowerCase().trim() === 'player' - ) { - userMessage.innerText = 'You forgot to set your player name at the top of the page!'; - userMessage.classList.add('visible'); - userMessage.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - return; - } - - // Clean up the settings output - Object.keys(settings.game).forEach((game) => { - // Remove any disabled games - if (settings.game[game] === 0) { - delete settings.game[game]; - delete settings[game]; - return; - } - - Object.keys(settings[game]).forEach((setting) => { - // Remove any disabled options - Object.keys(settings[game][setting]).forEach((option) => { - if (settings[game][setting][option] === 0) { - delete settings[game][setting][option]; - } - }); - - if ( - Object.keys(settings[game][setting]).length === 0 && - !Array.isArray(settings[game][setting]) && - setting !== 'start_inventory' - ) { - errorMessage = `${game} // ${setting} has no values above zero!`; - } - - // Remove weights from options with only one possibility - if ( - Object.keys(settings[game][setting]).length === 1 && - !Array.isArray(settings[game][setting]) && - setting !== 'start_inventory' - ) { - settings[game][setting] = Object.keys(settings[game][setting])[0]; - } - - // Remove empty arrays - else if ( - ['exclude_locations', 'priority_locations', 'local_items', - 'non_local_items', 'start_hints', 'start_location_hints'].includes(setting) && - settings[game][setting].length === 0 - ) { - delete settings[game][setting]; - } - - // Remove empty start inventory - else if ( - setting === 'start_inventory' && - Object.keys(settings[game]['start_inventory']).length === 0 - ) { - delete settings[game]['start_inventory']; - } - }); - }); - - if (Object.keys(settings.game).length === 0) { - errorMessage = 'You have not chosen a game to play!'; - } - - // Remove weights if there is only one game - else if (Object.keys(settings.game).length === 1) { - settings.game = Object.keys(settings.game)[0]; - } - - // If an error occurred, alert the user and do not export the file - if (errorMessage) { - userMessage.innerText = errorMessage; - userMessage.classList.add('visible'); - userMessage.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - return; - } - - // If no error occurred, hide the user message if it is visible - userMessage.classList.remove('visible'); - return settings; - } - - updateVisibleGames() { - Object.entries(this.current.game).forEach(([game, weight]) => { - const gameDiv = document.getElementById(`${game}-div`); - const gameOption = document.getElementById(`${game}-game-option`); - if (parseInt(weight, 10) > 0) { - gameDiv.classList.remove('invisible'); - gameOption.classList.add('jump-link'); - gameOption.addEventListener('click', () => { - const gameDiv = document.getElementById(`${game}-div`); - if (gameDiv.classList.contains('invisible')) { return; } - gameDiv.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - }); - } else { - gameDiv.classList.add('invisible'); - gameOption.classList.remove('jump-link'); - } - }); - } - - updateBaseSetting(event) { - const setting = event.target.getAttribute('data-setting'); - const option = event.target.getAttribute('data-option'); - const type = event.target.getAttribute('data-type'); - - switch(type){ - case 'weight': - this.current[setting][option] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10); - document.getElementById(`${setting}-${option}`).innerText = event.target.value; - break; - case 'data': - this.current[setting] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10); - break; - } - - this.save(); - } - - export() { - const settings = this.#validateSettings(); - if (!settings) { return; } - - const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); - download(`${document.getElementById('player-name').value}.yaml`, yamlText); - } - - generateGame(raceMode = false) { - const settings = this.#validateSettings(); - if (!settings) { return; } - - axios.post('/api/generate', { - weights: { player: JSON.stringify(settings) }, - presetData: { player: JSON.stringify(settings) }, - playerCount: 1, - spoiler: 3, - race: raceMode ? '1' : '0', - }).then((response) => { - window.location.href = response.data.url; - }).catch((error) => { - const userMessage = document.getElementById('user-message'); - userMessage.innerText = 'Something went wrong and your game could not be generated.'; - if (error.response.data.text) { - userMessage.innerText += ' ' + error.response.data.text; - } - userMessage.classList.add('visible'); - userMessage.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - console.error(error); - }); - } -} - -// Settings for an individual game. -class GameSettings { - // The WeightedSettings that contains this game's settings. Used to save - // settings after editing. - #allSettings; - - // The name of this game. - name; - - // The data from the server describing the types of settings available for - // this game, as a JSON-safe blob. - get data() { - return this.#allSettings.data.games[this.name]; - } - - // The settings chosen by the user as they'd appear in the YAML file, stored - // to and retrieved from local storage. - get current() { - return this.#allSettings.current[this.name]; - } - - constructor(allSettings, name) { - this.#allSettings = allSettings; - this.name = name; - } - - // Builds and returns the settings UI for this game. - buildUI() { - // Create game div, invisible by default - const gameDiv = document.createElement('div'); - gameDiv.setAttribute('id', `${this.name}-div`); - gameDiv.classList.add('game-div'); - gameDiv.classList.add('invisible'); - - const gameHeader = document.createElement('h2'); - gameHeader.innerText = this.name; - gameDiv.appendChild(gameHeader); - - const collapseButton = document.createElement('a'); - collapseButton.innerText = '(Collapse)'; - gameDiv.appendChild(collapseButton); - - const expandButton = document.createElement('a'); - expandButton.innerText = '(Expand)'; - expandButton.classList.add('invisible'); - gameDiv.appendChild(expandButton); - - // Sort items and locations alphabetically. - this.data.gameItems.sort(); - this.data.gameLocations.sort(); - - const weightedSettingsDiv = this.#buildWeightedSettingsDiv(); - gameDiv.appendChild(weightedSettingsDiv); - - const itemPoolDiv = this.#buildItemPoolDiv(); - gameDiv.appendChild(itemPoolDiv); - - const hintsDiv = this.#buildHintsDiv(); - gameDiv.appendChild(hintsDiv); - - const locationsDiv = this.#buildPriorityExclusionDiv(); - gameDiv.appendChild(locationsDiv); - - collapseButton.addEventListener('click', () => { - collapseButton.classList.add('invisible'); - weightedSettingsDiv.classList.add('invisible'); - itemPoolDiv.classList.add('invisible'); - hintsDiv.classList.add('invisible'); - locationsDiv.classList.add('invisible'); - expandButton.classList.remove('invisible'); - }); - - expandButton.addEventListener('click', () => { - collapseButton.classList.remove('invisible'); - weightedSettingsDiv.classList.remove('invisible'); - itemPoolDiv.classList.remove('invisible'); - hintsDiv.classList.remove('invisible'); - locationsDiv.classList.remove('invisible'); - expandButton.classList.add('invisible'); - }); - - return gameDiv; - } - - #buildWeightedSettingsDiv() { - const settingsWrapper = document.createElement('div'); - settingsWrapper.classList.add('settings-wrapper'); - - Object.keys(this.data.gameOptionGroups).forEach((optionGroup) => { - const optionGroupHeader = document.createElement('h3'); - optionGroupHeader.classList.add('option-group-header'); - optionGroupHeader.innerText = optionGroup; - settingsWrapper.appendChild(optionGroupHeader); - - Object.keys(this.data.gameOptionGroups[optionGroup]).forEach((settingName) => { - const setting = this.data.gameOptionGroups[optionGroup][settingName]; - const settingWrapper = document.createElement('div'); - settingWrapper.classList.add('setting-wrapper'); - - const settingNameHeader = document.createElement('h4'); - settingNameHeader.innerText = setting.displayName; - settingWrapper.appendChild(settingNameHeader); - - const settingDescription = document.createElement('p'); - settingDescription.classList.add('setting-description'); - settingDescription.innerText = setting.description.replace(/(\n)/g, ' '); - settingWrapper.appendChild(settingDescription); - - switch(setting.type){ - case 'select': - const optionTable = document.createElement('table'); - const tbody = document.createElement('tbody'); - - // Add a weight range for each option - setting.options.forEach((option) => { - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - tdLeft.innerText = option.name; - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('data-game', this.name); - range.setAttribute('data-setting', settingName); - range.setAttribute('data-option', option.value); - range.setAttribute('data-type', setting.type); - range.setAttribute('min', 0); - range.setAttribute('max', 50); - range.addEventListener('change', (evt) => this.#updateRangeSetting(evt)); - range.value = this.current[optionGroup][settingName][option.value]; - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `${this.name}-${settingName}-${option.value}`); - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - - tbody.appendChild(tr); - }); - - optionTable.appendChild(tbody); - settingWrapper.appendChild(optionTable); - break; - - case 'range': - case 'named_range': - const rangeTable = document.createElement('table'); - const rangeTbody = document.createElement('tbody'); - - const hintText = document.createElement('p'); - hintText.classList.add('hint-text'); - hintText.innerHTML = 'This is a range option. You may enter a valid numerical value in the text box ' + - `below, then press the "Add" button to add a weight for it.

Accepted values:
` + - `Normal range: ${setting.min} - ${setting.max}`; - - const acceptedValuesOutsideRange = []; - if (setting.hasOwnProperty('value_names')) { - Object.keys(setting.value_names).forEach((specialName) => { - if ( - (setting.value_names[specialName] < setting.min) || - (setting.value_names[specialName] > setting.max) - ) { - hintText.innerHTML += `
${specialName}: ${setting.value_names[specialName]}`; - acceptedValuesOutsideRange.push(setting.value_names[specialName]); - } - }); - - hintText.innerHTML += '

Certain values have special meaning:'; - Object.keys(setting.value_names).forEach((specialName) => { - hintText.innerHTML += `
${specialName}: ${setting.value_names[specialName]}`; - }); - } - - settingWrapper.appendChild(hintText); - - const addOptionDiv = document.createElement('div'); - addOptionDiv.classList.add('add-option-div'); - const optionInput = document.createElement('input'); - optionInput.setAttribute('id', `${this.name}-${settingName}-option`); - let placeholderText = `${setting.min} - ${setting.max}`; - acceptedValuesOutsideRange.forEach((aVal) => placeholderText += `, ${aVal}`); - optionInput.setAttribute('placeholder', placeholderText); - addOptionDiv.appendChild(optionInput); - const addOptionButton = document.createElement('button'); - addOptionButton.innerText = 'Add'; - addOptionDiv.appendChild(addOptionButton); - settingWrapper.appendChild(addOptionDiv); - optionInput.addEventListener('keydown', (evt) => { - if (evt.key === 'Enter') { addOptionButton.dispatchEvent(new Event('click')); } - }); - - addOptionButton.addEventListener('click', () => { - const optionInput = document.getElementById(`${this.name}-${settingName}-option`); - let option = optionInput.value; - if (!option || !option.trim()) { return; } - option = parseInt(option, 10); - - let optionAcceptable = false; - if ((option >= setting.min) && (option <= setting.max)) { - optionAcceptable = true; - } - if (setting.hasOwnProperty('value_names') && Object.values(setting.value_names).includes(option)){ - optionAcceptable = true; - } - if (!optionAcceptable) { return; } - - optionInput.value = ''; - if (document.getElementById(`${this.name}-${settingName}-${option}-range`)) { return; } - - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - tdLeft.innerText = option; - if ( - setting.hasOwnProperty('value_names') && - Object.values(setting.value_names).includes(parseInt(option, 10)) - ) { - const optionName = Object.keys(setting.value_names).find( - (key) => setting.value_names[key] === parseInt(option, 10) - ); - tdLeft.innerText += ` [${optionName}]`; - } - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('id', `${this.name}-${settingName}-${option}-range`); - range.setAttribute('data-game', this.name); - range.setAttribute('data-setting', settingName); - range.setAttribute('data-option', option); - range.setAttribute('min', 0); - range.setAttribute('max', 50); - range.addEventListener('change', (evt) => this.#updateRangeSetting(evt)); - range.value = this.current[settingName][parseInt(option, 10)]; - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`) - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - - const tdDelete = document.createElement('td'); - tdDelete.classList.add('td-delete'); - const deleteButton = document.createElement('span'); - deleteButton.classList.add('range-option-delete'); - deleteButton.innerText = '❌'; - deleteButton.addEventListener('click', () => { - range.value = 0; - range.dispatchEvent(new Event('change')); - rangeTbody.removeChild(tr); - }); - tdDelete.appendChild(deleteButton); - tr.appendChild(tdDelete); - - rangeTbody.appendChild(tr); - - // Save new option to settings - range.dispatchEvent(new Event('change')); - }); - - Object.keys(this.current[optionGroup][settingName]).forEach((option) => { - // These options are statically generated below, and should always appear even if they are deleted - // from localStorage - if (['random', 'random-low', 'random-middle', 'random-high'].includes(option)) { return; } - - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - tdLeft.innerText = option; - if ( - setting.hasOwnProperty('value_names') && - Object.values(setting.value_names).includes(parseInt(option, 10)) - ) { - const optionName = Object.keys(setting.value_names).find( - (key) => setting.value_names[key] === parseInt(option, 10) - ); - tdLeft.innerText += ` [${optionName}]`; - } - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('id', `${this.name}-${settingName}-${option}-range`); - range.setAttribute('data-game', this.name); - range.setAttribute('data-setting', settingName); - range.setAttribute('data-option', option); - range.setAttribute('min', '0'); - range.setAttribute('max', '50'); - range.addEventListener('change', (evt) => this.#updateRangeSetting(evt)); - range.value = this.current[optionGroup][settingName][parseInt(option, 10)]; - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`) - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - - const tdDelete = document.createElement('td'); - tdDelete.classList.add('td-delete'); - const deleteButton = document.createElement('span'); - deleteButton.classList.add('range-option-delete'); - deleteButton.innerText = '❌'; - deleteButton.addEventListener('click', () => { - range.value = 0; - const changeEvent = new Event('change'); - changeEvent.action = 'rangeDelete'; - range.dispatchEvent(changeEvent); - rangeTbody.removeChild(tr); - }); - tdDelete.appendChild(deleteButton); - tr.appendChild(tdDelete); - - rangeTbody.appendChild(tr); - }); - - ['random', 'random-low', 'random-middle', 'random-high'].forEach((option) => { - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - switch(option){ - case 'random': - tdLeft.innerText = 'Random'; - break; - case 'random-low': - tdLeft.innerText = "Random (Low)"; - break; - case 'random-middle': - tdLeft.innerText = 'Random (Middle)'; - break; - case 'random-high': - tdLeft.innerText = "Random (High)"; - break; - } - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('id', `${this.name}-${settingName}-${option}-range`); - range.setAttribute('data-game', this.name); - range.setAttribute('data-setting', settingName); - range.setAttribute('data-option', option); - range.setAttribute('min', '0'); - range.setAttribute('max', '50'); - range.addEventListener('change', (evt) => this.#updateRangeSetting(evt)); - range.value = this.current[optionGroup][settingName][option]; - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`) - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - rangeTbody.appendChild(tr); - }); - - rangeTable.appendChild(rangeTbody); - settingWrapper.appendChild(rangeTable); - break; - - case 'items-list': - const itemsList = this.#buildItemsDiv(optionGroup, settingName); - settingWrapper.appendChild(itemsList); - break; - - case 'locations-list': - const locationsList = this.#buildLocationsDiv(optionGroup, settingName); - settingWrapper.appendChild(locationsList); - break; - - case 'custom-list': - console.log(this.data); - const customList = this.#buildListDiv(optionGroup, settingName, this.data.gameOptionGroups[optionGroup][settingName].options); - settingWrapper.appendChild(customList); - break; - - default: - console.error(`Unknown setting type for ${this.name} setting ${settingName}: ${setting.type}`); - return; - } - - settingsWrapper.appendChild(settingWrapper); - }); - }); - - return settingsWrapper; - } - - #buildItemPoolDiv() { - const itemsDiv = document.createElement('div'); - itemsDiv.classList.add('items-div'); - - const itemsDivHeader = document.createElement('h3'); - itemsDivHeader.innerText = 'Item Pool'; - itemsDiv.appendChild(itemsDivHeader); - - const itemsDescription = document.createElement('p'); - itemsDescription.classList.add('setting-description'); - itemsDescription.innerText = 'Choose if you would like to start with items, or control if they are placed in ' + - 'your seed or someone else\'s.'; - itemsDiv.appendChild(itemsDescription); - - const itemsHint = document.createElement('p'); - itemsHint.classList.add('hint-text'); - itemsHint.innerText = 'Drag and drop items from one box to another.'; - itemsDiv.appendChild(itemsHint); - - const itemsWrapper = document.createElement('div'); - itemsWrapper.classList.add('items-wrapper'); - - const itemDragoverHandler = (evt) => evt.preventDefault(); - const itemDropHandler = (evt) => this.#itemDropHandler(evt); - - // Create container divs for each category - const availableItemsWrapper = document.createElement('div'); - availableItemsWrapper.classList.add('item-set-wrapper'); - availableItemsWrapper.innerText = 'Available Items'; - const availableItems = document.createElement('div'); - availableItems.classList.add('item-container'); - availableItems.setAttribute('id', `${this.name}-available_items`); - availableItems.addEventListener('dragover', itemDragoverHandler); - availableItems.addEventListener('drop', itemDropHandler); - - const startInventoryWrapper = document.createElement('div'); - startInventoryWrapper.classList.add('item-set-wrapper'); - startInventoryWrapper.innerText = 'Start Inventory'; - const startInventory = document.createElement('div'); - startInventory.classList.add('item-container'); - startInventory.setAttribute('id', `${this.name}-start_inventory`); - startInventory.setAttribute('data-setting', 'start_inventory'); - startInventory.addEventListener('dragover', itemDragoverHandler); - startInventory.addEventListener('drop', itemDropHandler); - - const localItemsWrapper = document.createElement('div'); - localItemsWrapper.classList.add('item-set-wrapper'); - localItemsWrapper.innerText = 'Local Items'; - const localItems = document.createElement('div'); - localItems.classList.add('item-container'); - localItems.setAttribute('id', `${this.name}-local_items`); - localItems.setAttribute('data-setting', 'local_items') - localItems.addEventListener('dragover', itemDragoverHandler); - localItems.addEventListener('drop', itemDropHandler); - - const nonLocalItemsWrapper = document.createElement('div'); - nonLocalItemsWrapper.classList.add('item-set-wrapper'); - nonLocalItemsWrapper.innerText = 'Non-Local Items'; - const nonLocalItems = document.createElement('div'); - nonLocalItems.classList.add('item-container'); - nonLocalItems.setAttribute('id', `${this.name}-non_local_items`); - nonLocalItems.setAttribute('data-setting', 'non_local_items'); - nonLocalItems.addEventListener('dragover', itemDragoverHandler); - nonLocalItems.addEventListener('drop', itemDropHandler); - - // Populate the divs - this.data.gameItems.forEach((item) => { - if (Object.keys(this.current['Item & Location Options'].start_inventory).includes(item)){ - const itemDiv = this.#buildItemQtyDiv(item); - itemDiv.setAttribute('data-setting', 'start_inventory'); - startInventory.appendChild(itemDiv); - } else if (this.current['Item & Location Options'].local_items.includes(item)) { - const itemDiv = this.#buildItemDiv(item); - itemDiv.setAttribute('data-setting', 'local_items'); - localItems.appendChild(itemDiv); - } else if (this.current['Item & Location Options'].non_local_items.includes(item)) { - const itemDiv = this.#buildItemDiv(item); - itemDiv.setAttribute('data-setting', 'non_local_items'); - nonLocalItems.appendChild(itemDiv); - } else { - const itemDiv = this.#buildItemDiv(item); - availableItems.appendChild(itemDiv); - } - }); - - availableItemsWrapper.appendChild(availableItems); - startInventoryWrapper.appendChild(startInventory); - localItemsWrapper.appendChild(localItems); - nonLocalItemsWrapper.appendChild(nonLocalItems); - itemsWrapper.appendChild(availableItemsWrapper); - itemsWrapper.appendChild(startInventoryWrapper); - itemsWrapper.appendChild(localItemsWrapper); - itemsWrapper.appendChild(nonLocalItemsWrapper); - itemsDiv.appendChild(itemsWrapper); - return itemsDiv; - } - - #buildItemDiv(item) { - const itemDiv = document.createElement('div'); - itemDiv.classList.add('item-div'); - itemDiv.setAttribute('id', `${this.name}-${item}`); - itemDiv.setAttribute('data-game', this.name); - itemDiv.setAttribute('data-item', item); - itemDiv.setAttribute('draggable', 'true'); - itemDiv.innerText = item; - itemDiv.addEventListener('dragstart', (evt) => { - evt.dataTransfer.setData('text/plain', itemDiv.getAttribute('id')); - }); - return itemDiv; - } - - #buildItemQtyDiv(item) { - const itemQtyDiv = document.createElement('div'); - itemQtyDiv.classList.add('item-qty-div'); - itemQtyDiv.setAttribute('id', `${this.name}-${item}`); - itemQtyDiv.setAttribute('data-game', this.name); - itemQtyDiv.setAttribute('data-item', item); - itemQtyDiv.setAttribute('draggable', 'true'); - itemQtyDiv.innerText = item; - - const inputWrapper = document.createElement('div'); - inputWrapper.classList.add('item-qty-input-wrapper') - - const itemQty = document.createElement('input'); - itemQty.setAttribute('value', this.current.start_inventory.hasOwnProperty(item) ? - this.current.start_inventory[item] : '1'); - itemQty.setAttribute('data-game', this.name); - itemQty.setAttribute('data-setting', 'start_inventory'); - itemQty.setAttribute('data-option', item); - itemQty.setAttribute('maxlength', '3'); - itemQty.addEventListener('keyup', (evt) => { - evt.target.value = isNaN(parseInt(evt.target.value)) ? 0 : parseInt(evt.target.value); - this.#updateItemSetting(evt); - }); - inputWrapper.appendChild(itemQty); - itemQtyDiv.appendChild(inputWrapper); - - itemQtyDiv.addEventListener('dragstart', (evt) => { - evt.dataTransfer.setData('text/plain', itemQtyDiv.getAttribute('id')); - }); - return itemQtyDiv; - } - - #itemDropHandler(evt) { - evt.preventDefault(); - const sourceId = evt.dataTransfer.getData('text/plain'); - const sourceDiv = document.getElementById(sourceId); - - const item = sourceDiv.getAttribute('data-item'); - - const oldSetting = sourceDiv.hasAttribute('data-setting') ? sourceDiv.getAttribute('data-setting') : null; - const newSetting = evt.target.hasAttribute('data-setting') ? evt.target.getAttribute('data-setting') : null; - - const itemDiv = newSetting === 'start_inventory' ? this.#buildItemQtyDiv(item) : this.#buildItemDiv(item); - - if (oldSetting) { - if (oldSetting === 'start_inventory') { - if (this.current[oldSetting].hasOwnProperty(item)) { - delete this.current[oldSetting][item]; - } - } else { - if (this.current[oldSetting].includes(item)) { - this.current[oldSetting].splice(this.current[oldSetting].indexOf(item), 1); - } - } - } - - if (newSetting) { - itemDiv.setAttribute('data-setting', newSetting); - document.getElementById(`${this.name}-${newSetting}`).appendChild(itemDiv); - if (newSetting === 'start_inventory') { - this.current[newSetting][item] = 1; - } else { - if (!this.current[newSetting].includes(item)){ - this.current[newSetting].push(item); - } - } - } else { - // No setting was assigned, this item has been removed from the settings - document.getElementById(`${this.name}-available_items`).appendChild(itemDiv); - } - - // Remove the source drag object - sourceDiv.parentElement.removeChild(sourceDiv); - - // Save the updated settings - this.save(); - } - - #buildHintsDiv() { - const hintsDiv = document.createElement('div'); - hintsDiv.classList.add('hints-div'); - const hintsHeader = document.createElement('h3'); - hintsHeader.innerText = 'Item & Location Hints'; - hintsDiv.appendChild(hintsHeader); - const hintsDescription = document.createElement('p'); - hintsDescription.classList.add('setting-description'); - hintsDescription.innerText = 'Choose any items or locations to begin the game with the knowledge of where those ' + - ' items are, or what those locations contain.'; - hintsDiv.appendChild(hintsDescription); - - const itemHintsContainer = document.createElement('div'); - itemHintsContainer.classList.add('hints-container'); - - // Item Hints - const itemHintsWrapper = document.createElement('div'); - itemHintsWrapper.classList.add('hints-wrapper'); - itemHintsWrapper.innerText = 'Starting Item Hints'; - - const itemHintsDiv = this.#buildItemsDiv('Item & Location Options', 'start_hints'); - itemHintsWrapper.appendChild(itemHintsDiv); - itemHintsContainer.appendChild(itemHintsWrapper); - - // Starting Location Hints - const locationHintsWrapper = document.createElement('div'); - locationHintsWrapper.classList.add('hints-wrapper'); - locationHintsWrapper.innerText = 'Starting Location Hints'; - - const locationHintsDiv = this.#buildLocationsDiv('Item & Location Options', 'start_location_hints'); - locationHintsWrapper.appendChild(locationHintsDiv); - itemHintsContainer.appendChild(locationHintsWrapper); - - hintsDiv.appendChild(itemHintsContainer); - return hintsDiv; - } - - #buildPriorityExclusionDiv() { - const locationsDiv = document.createElement('div'); - locationsDiv.classList.add('locations-div'); - const locationsHeader = document.createElement('h3'); - locationsHeader.innerText = 'Priority & Exclusion Locations'; - locationsDiv.appendChild(locationsHeader); - const locationsDescription = document.createElement('p'); - locationsDescription.classList.add('setting-description'); - locationsDescription.innerText = 'Priority locations guarantee a progression item will be placed there while ' + - 'excluded locations will not contain progression or useful items.'; - locationsDiv.appendChild(locationsDescription); - - const locationsContainer = document.createElement('div'); - locationsContainer.classList.add('locations-container'); - - // Priority Locations - const priorityLocationsWrapper = document.createElement('div'); - priorityLocationsWrapper.classList.add('locations-wrapper'); - priorityLocationsWrapper.innerText = 'Priority Locations'; - - const priorityLocationsDiv = this.#buildLocationsDiv('Item & Location Options', 'priority_locations'); - priorityLocationsWrapper.appendChild(priorityLocationsDiv); - locationsContainer.appendChild(priorityLocationsWrapper); - - // Exclude Locations - const excludeLocationsWrapper = document.createElement('div'); - excludeLocationsWrapper.classList.add('locations-wrapper'); - excludeLocationsWrapper.innerText = 'Exclude Locations'; - - const excludeLocationsDiv = this.#buildLocationsDiv('Item & Location Options', 'exclude_locations'); - excludeLocationsWrapper.appendChild(excludeLocationsDiv); - locationsContainer.appendChild(excludeLocationsWrapper); - - locationsDiv.appendChild(locationsContainer); - return locationsDiv; - } - - // Builds a div for a setting whose value is a list of locations. - #buildLocationsDiv(optionGroup, setting) { - return this.#buildListDiv(optionGroup, setting, this.data.gameLocations, { - groups: this.data.gameLocationGroups, - descriptions: this.data.gameLocationDescriptions, - }); - } - - // Builds a div for a setting whose value is a list of items. - #buildItemsDiv(optionGroup, setting) { - return this.#buildListDiv(optionGroup, setting, this.data.gameItems, { - groups: this.data.gameItemGroups, - descriptions: this.data.gameItemDescriptions - }); - } - - // Builds a div for a setting named `setting` with a list value that can - // contain `items`. - // - // The `groups` option can be a list of additional options for this list - // (usually `item_name_groups` or `location_name_groups`) that are displayed - // in a special section at the top of the list. - // - // The `descriptions` option can be a map from item names or group names to - // descriptions for the user's benefit. - #buildListDiv(optionGroup, setting, items, {groups = [], descriptions = {}} = {}) { - const div = document.createElement('div'); - div.classList.add('simple-list'); - - groups.forEach((group) => { - const row = this.#addListRow(optionGroup, setting, group, descriptions[group]); - div.appendChild(row); - }); - - if (groups.length > 0) { - div.appendChild(document.createElement('hr')); - } - - items.forEach((item) => { - const row = this.#addListRow(optionGroup, setting, item, descriptions[item]); - div.appendChild(row); - }); - - return div; - } - - // Builds and returns a row for a list of checkboxes. - // - // If `help` is passed, it's displayed as a help tooltip for this list item. - #addListRow(optionGroup, setting, item, help = undefined) { - const row = document.createElement('div'); - row.classList.add('list-row'); - - const label = document.createElement('label'); - label.setAttribute('for', `${this.name}-${setting}-${item}`); - - const checkbox = document.createElement('input'); - checkbox.setAttribute('type', 'checkbox'); - checkbox.setAttribute('id', `${this.name}-${setting}-${item}`); - checkbox.setAttribute('data-game', this.name); - checkbox.setAttribute('data-setting', setting); - checkbox.setAttribute('data-option', item); - if (this.current[optionGroup][setting].includes(item)) { - checkbox.setAttribute('checked', '1'); - } - checkbox.addEventListener('change', (evt) => this.#updateListSetting(evt)); - label.appendChild(checkbox); - - const name = document.createElement('span'); - name.innerText = item; - - if (help) { - const helpSpan = document.createElement('span'); - helpSpan.classList.add('interactive'); - helpSpan.setAttribute('data-tooltip', help); - helpSpan.innerText = '(?)'; - name.innerText += ' '; - name.appendChild(helpSpan); - - // Put the first 7 tooltips below their rows. CSS tooltips in scrolling - // containers can't be visible outside those containers, so this helps - // ensure they won't be pushed out the top. - if (helpSpan.parentNode.childNodes.length < 7) { - helpSpan.classList.add('tooltip-bottom'); - } - } - - label.appendChild(name); - - row.appendChild(label); - return row; - } - - #updateRangeSetting(evt) { - const setting = evt.target.getAttribute('data-setting'); - const option = evt.target.getAttribute('data-option'); - document.getElementById(`${this.name}-${setting}-${option}`).innerText = evt.target.value; - if (evt.action && evt.action === 'rangeDelete') { - delete this.current[setting][option]; - } else { - this.current[setting][option] = parseInt(evt.target.value, 10); - } - this.save(); - } - - #updateListSetting(evt) { - const setting = evt.target.getAttribute('data-setting'); - const option = evt.target.getAttribute('data-option'); - - if (evt.target.checked) { - // If the option is to be enabled and it is already enabled, do nothing - if (this.current[setting].includes(option)) { return; } - - this.current[setting].push(option); - } else { - // If the option is to be disabled and it is already disabled, do nothing - if (!this.current[setting].includes(option)) { return; } - - this.current[setting].splice(this.current[setting].indexOf(option), 1); - } - this.save(); - } - - #updateItemSetting(evt) { - const setting = evt.target.getAttribute('data-setting'); - const option = evt.target.getAttribute('data-option'); - if (setting === 'start_inventory') { - this.current[setting][option] = evt.target.value.trim() ? parseInt(evt.target.value) : 0; - } else { - this.current[setting][option] = isNaN(evt.target.value) ? - evt.target.value : parseInt(evt.target.value, 10); - } - this.save(); - } - - // Saves the current settings to local storage. - save() { - this.#allSettings.save(); - } -} - -/** Create an anchor and trigger a download of a text file. */ -const download = (filename, text) => { - const downloadLink = document.createElement('a'); - downloadLink.setAttribute('href','data:text/yaml;charset=utf-8,'+ encodeURIComponent(text)) - downloadLink.setAttribute('download', filename); - downloadLink.style.display = 'none'; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); -}; diff --git a/WebHostLib/static/assets/weightedOptions.js b/WebHostLib/static/assets/weightedOptions.js new file mode 100644 index 000000000000..cb581038d0b0 --- /dev/null +++ b/WebHostLib/static/assets/weightedOptions.js @@ -0,0 +1,3 @@ +window.addEventListener('load', () => { + console.log('System ready.'); +}); diff --git a/WebHostLib/static/styles/weightedOptions/weightedOptions.css b/WebHostLib/static/styles/weightedOptions/weightedOptions.css new file mode 100644 index 000000000000..8d8643db7f83 --- /dev/null +++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.css @@ -0,0 +1,324 @@ +html { + background-image: url("../../static/backgrounds/grass.png"); + background-repeat: repeat; + background-size: 650px 650px; + scroll-padding-top: 90px; +} + +#weighted-settings { + max-width: 1000px; + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem; + color: #eeffeb; +} + +#weighted-settings h3 { + cursor: unset; +} + +#weighted-settings h3.option-group-header { + margin-top: 0.75rem; + font-weight: bold; +} + +#weighted-settings #games-wrapper { + width: 100%; +} + +#weighted-settings .setting-wrapper { + width: 100%; + margin-bottom: 2rem; +} + +#weighted-settings .setting-wrapper .add-option-div { + display: flex; + flex-direction: row; + justify-content: flex-start; + margin-bottom: 1rem; +} + +#weighted-settings .setting-wrapper .add-option-div button { + width: auto; + height: auto; + margin: 0 0 0 0.15rem; + padding: 0 0.25rem; + border-radius: 4px; + cursor: default; +} + +#weighted-settings .setting-wrapper .add-option-div button:active { + margin-bottom: 1px; +} + +#weighted-settings p.setting-description { + margin: 0 0 1rem; +} + +#weighted-settings p.hint-text { + margin: 0 0 1rem; + font-style: italic; +} + +#weighted-settings .jump-link { + color: #ffef00; + cursor: pointer; + text-decoration: underline; +} + +#weighted-settings table { + width: 100%; +} + +#weighted-settings table th, #weighted-settings table td { + border: none; +} + +#weighted-settings table td { + padding: 5px; +} + +#weighted-settings table .td-left { + font-family: LexendDeca-Regular, sans-serif; + padding-right: 1rem; + width: 200px; +} + +#weighted-settings table .td-middle { + display: flex; + flex-direction: column; + justify-content: space-evenly; + padding-right: 1rem; +} + +#weighted-settings table .td-right { + width: 4rem; + text-align: right; +} + +#weighted-settings table .td-delete { + width: 50px; + text-align: right; +} + +#weighted-settings table .range-option-delete { + cursor: pointer; +} + +#weighted-settings .items-wrapper { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +#weighted-settings .items-div h3 { + margin-bottom: 0.5rem; +} + +#weighted-settings .items-wrapper .item-set-wrapper { + width: 24%; + font-weight: bold; +} + +#weighted-settings .item-container { + border: 1px solid #ffffff; + border-radius: 2px; + width: 100%; + height: 300px; + overflow-y: auto; + overflow-x: hidden; + margin-top: 0.125rem; + font-weight: normal; +} + +#weighted-settings .item-container .item-div { + padding: 0.125rem 0.5rem; + cursor: pointer; +} + +#weighted-settings .item-container .item-div:hover { + background-color: rgba(0, 0, 0, 0.1); +} + +#weighted-settings .item-container .item-qty-div { + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 0.125rem 0.5rem; + cursor: pointer; +} + +#weighted-settings .item-container .item-qty-div .item-qty-input-wrapper { + display: flex; + flex-direction: column; + justify-content: space-around; +} + +#weighted-settings .item-container .item-qty-div input { + min-width: unset; + width: 1.5rem; + text-align: center; +} + +#weighted-settings .item-container .item-qty-div:hover { + background-color: rgba(0, 0, 0, 0.1); +} + +#weighted-settings .hints-div, #weighted-settings .locations-div { + margin-top: 2rem; +} + +#weighted-settings .hints-div h3, #weighted-settings .locations-div h3 { + margin-bottom: 0.5rem; +} + +#weighted-settings .hints-container, #weighted-settings .locations-container { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +#weighted-settings .hints-wrapper, #weighted-settings .locations-wrapper { + width: calc(50% - 0.5rem); + font-weight: bold; +} + +#weighted-settings .hints-wrapper .simple-list, #weighted-settings .locations-wrapper .simple-list { + margin-top: 0.25rem; + height: 300px; + font-weight: normal; +} + +#weighted-settings #weighted-settings-button-row { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 15px; +} + +#weighted-settings code { + background-color: #d9cd8e; + border-radius: 4px; + padding-left: 0.25rem; + padding-right: 0.25rem; + color: #000000; +} + +#weighted-settings #user-message { + display: none; + width: calc(100% - 8px); + background-color: #ffe86b; + border-radius: 4px; + color: #000000; + padding: 4px; + text-align: center; +} + +#weighted-settings #user-message.visible { + display: block; + cursor: pointer; +} + +#weighted-settings h1 { + font-size: 2.5rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + width: 100%; + margin-bottom: 0.5rem; + color: #ffffff; + text-shadow: 1px 1px 4px #000000; +} + +#weighted-settings h2 { + font-size: 2rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + width: 100%; + margin-bottom: 0.5rem; + color: #ffe993; + text-transform: none; + text-shadow: 1px 1px 2px #000000; +} + +#weighted-settings h3, #weighted-settings h4, #weighted-settings h5, #weighted-settings h6 { + color: #ffffff; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); + text-transform: none; +} + +#weighted-settings a { + color: #ffef00; + cursor: pointer; +} + +#weighted-settings input:not([type]) { + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; +} + +#weighted-settings input:not([type]):focus { + border: 1px solid #ffffff; +} + +#weighted-settings select { + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; + background-color: #ffffff; +} + +#weighted-settings .game-options, #weighted-settings .rom-options { + display: flex; + flex-direction: column; +} + +#weighted-settings .simple-list { + display: flex; + flex-direction: column; + max-height: 300px; + overflow-y: auto; + border: 1px solid #ffffff; + border-radius: 4px; +} + +#weighted-settings .simple-list .list-row label { + display: block; + width: calc(100% - 0.5rem); + padding: 0.0625rem 0.25rem; +} + +#weighted-settings .simple-list .list-row label:hover { + background-color: rgba(0, 0, 0, 0.1); +} + +#weighted-settings .simple-list .list-row label input[type=checkbox] { + margin-right: 0.5rem; +} + +#weighted-settings .simple-list hr { + width: calc(100% - 2px); + margin: 2px auto; + border-bottom: 1px solid rgba(255, 255, 255, 0.6); +} + +#weighted-settings .invisible { + display: none; +} + +@media all and (max-width: 1000px), all and (orientation: portrait) { + #weighted-settings .game-options { + justify-content: flex-start; + flex-wrap: wrap; + } + #game-options table label { + display: block; + min-width: 200px; + } +} + +/*# sourceMappingURL=weightedOptions.css.map */ diff --git a/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map b/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map new file mode 100644 index 000000000000..5dccba1e2c82 --- /dev/null +++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["weightedOptions.scss"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EAEA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;IACI;IACA;;EAGJ;IACI;IACA","file":"weightedOptions.css"} \ No newline at end of file diff --git a/WebHostLib/static/styles/weighted-options.css b/WebHostLib/static/styles/weightedOptions/weightedOptions.scss similarity index 99% rename from WebHostLib/static/styles/weighted-options.css rename to WebHostLib/static/styles/weightedOptions/weightedOptions.scss index fbc0d9795bb7..48f779aef27d 100644 --- a/WebHostLib/static/styles/weighted-options.css +++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.scss @@ -1,5 +1,5 @@ html{ - background-image: url('../static/backgrounds/grass.png'); + background-image: url('../../static/backgrounds/grass.png'); background-repeat: repeat; background-size: 650px 650px; scroll-padding-top: 90px; diff --git a/WebHostLib/templates/weightedOptions/macros.html b/WebHostLib/templates/weightedOptions/macros.html new file mode 100644 index 000000000000..8f2a1f71c8ff --- /dev/null +++ b/WebHostLib/templates/weightedOptions/macros.html @@ -0,0 +1,96 @@ +{% macro Toggle(option_name, option) %} + +{% endmacro %} + +{% macro DefaultOnToggle(option_name, option) %} + + {{ Toggle(option_name, option) }} +{% endmacro %} + +{% macro Choice(option_name, option) %} + +{% endmacro %} + +{% macro Range(option_name, option) %} + +{% endmacro %} + +{% macro NamedRange(option_name, option) %} + +{% endmacro %} + +{% macro FreeText(option_name, option) %} + +{% endmacro %} + +{% macro TextChoice(option_name, option) %} + +{% endmacro %} + +{% macro PlandoBosses(option_name, option) %} + + {{ TextChoice(option_name, option) }} +{% endmacro %} + +{% macro ItemDict(option_name, option, world) %} + +{% endmacro %} + +{% macro OptionList(option_name, option) %} + +{% endmacro %} + +{% macro LocationSet(option_name, option, world) %} +
+ {% for group_name in world.location_name_groups.keys()|sort %} + {% if group_name != "Everywhere" %} +
+ + +
+ {% endif %} + {% endfor %} + {% if world.location_name_groups.keys()|length > 1 %} +
 
+ {% endif %} + {% for location_name in world.location_names|sort %} +
+ + +
+ {% endfor %} +
+{% endmacro %} + +{% macro ItemSet(option_name, option, world) %} +
+ {% for group_name in world.item_name_groups.keys()|sort %} + {% if group_name != "Everything" %} +
+ + +
+ {% endif %} + {% endfor %} + {% if world.item_name_groups.keys()|length > 1 %} +
 
+ {% endif %} + {% for item_name in world.item_names|sort %} +
+ + +
+ {% endfor %} +
+{% endmacro %} + +{% macro OptionSet(option_name, option) %} +
+ {% for key in option.valid_keys %} +
+ + +
+ {% endfor %} +
+{% endmacro %} diff --git a/WebHostLib/templates/weighted-options.html b/WebHostLib/templates/weightedOptions/weightedOptions.html similarity index 78% rename from WebHostLib/templates/weighted-options.html rename to WebHostLib/templates/weightedOptions/weightedOptions.html index 032a4eeb905c..a3226475e4de 100644 --- a/WebHostLib/templates/weighted-options.html +++ b/WebHostLib/templates/weightedOptions/weightedOptions.html @@ -1,13 +1,11 @@ {% extends 'pageWrapper.html' %} +{% import 'weightedOptions/macros.html' as inputs %} {% block head %} {{ game }} Options - - - - - + + {% endblock %} {% block body %} @@ -36,13 +34,12 @@

Weighted Options

- + {{ worlds }}
-
{% endblock %}