From 0431c3fce00bd56033f6b5966b631367edccf8b0 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Sat, 1 Jan 2022 16:59:58 -0500 Subject: [PATCH] Much more work on weighted-setting page. Still needs support for range options and item/location settings. --- WebHostLib/options.py | 2 +- WebHostLib/static/assets/weighted-settings.js | 304 ++++++++++++------ .../static/styles/weighted-settings.css | 70 ++-- WebHostLib/templates/weighted-settings.html | 8 +- 4 files changed, 244 insertions(+), 140 deletions(-) diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 9ade10e8..a1c7b0df 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -103,7 +103,7 @@ def create(): weighted_settings["baseOptions"]["game"][game_name] = 0 weighted_settings["games"][game_name] = {} - weighted_settings["games"][game_name]["gameOptions"] = game_options + weighted_settings["games"][game_name]["gameSettings"] = game_options weighted_settings["games"][game_name]["gameItems"] = tuple(world.item_name_to_id.keys()) weighted_settings["games"][game_name]["gameLocations"] = tuple(world.location_name_to_id.keys()) diff --git a/WebHostLib/static/assets/weighted-settings.js b/WebHostLib/static/assets/weighted-settings.js index 21902eca..26744c76 100644 --- a/WebHostLib/static/assets/weighted-settings.js +++ b/WebHostLib/static/assets/weighted-settings.js @@ -18,7 +18,8 @@ window.addEventListener('load', () => { // Page setup createDefaultSettings(results); - // buildUI(results); + buildUI(results); + updateVisibleGames(); adjustHeaderWidth(); // Event listeners @@ -29,7 +30,9 @@ window.addEventListener('load', () => { // Name input field const weightedSettings = JSON.parse(localStorage.getItem('weighted-settings')); const nameInput = document.getElementById('player-name'); - nameInput.addEventListener('keyup', (event) => updateBaseSetting(event)); + nameInput.setAttribute('data-type', 'data'); + nameInput.setAttribute('data-setting', 'name'); + nameInput.addEventListener('keyup', updateBaseSetting); nameInput.value = weightedSettings.name; }); }); @@ -61,9 +64,27 @@ const createDefaultSettings = (settingData) => { // Initialize game object newSettings[game] = {}; - // Transfer game options - for (let gameOption of Object.keys(settingData.games[game].gameOptions)){ - newSettings[game][gameOption] = settingData.games[game].gameOptions[gameOption].defaultValue; + // Transfer game settings + for (let gameSetting of Object.keys(settingData.games[game].gameSettings)){ + newSettings[game][gameSetting] = {}; + + const setting = settingData.games[game].gameSettings[gameSetting]; + switch(setting.type){ + case 'select': + setting.options.forEach((option) => { + newSettings[game][gameSetting][option.value] = + (setting.hasOwnProperty('defaultValue') && setting.defaultValue === option.value) ? 25 : 0; + }); + break; + case 'range': + for (let i = setting.min; i <= setting.max; ++i){ + newSettings[game][gameSetting][i] = + (setting.hasOwnProperty('defaultValue') && setting.defaultValue === i) ? 25 : 0; + } + break; + default: + console.error(`Unknown setting type for ${game} setting ${gameSetting}: ${setting.type}`); + } } newSettings[game].start_inventory = []; @@ -77,121 +98,212 @@ const createDefaultSettings = (settingData) => { } }; -// TODO: Update this function for use with weighted-settings // TODO: Include item configs: start_inventory, local_items, non_local_items, start_hints // TODO: Include location configs: exclude_locations const buildUI = (settingData) => { - // Game Options - const leftGameOpts = {}; - const rightGameOpts = {}; - Object.keys(settingData.gameOptions).forEach((key, index) => { - if (index < Object.keys(settingData.gameOptions).length / 2) { leftGameOpts[key] = settingData.gameOptions[key]; } - else { rightGameOpts[key] = settingData.gameOptions[key]; } + // Build the game-choice div + buildGameChoice(settingData.games); + + const gamesWrapper = document.getElementById('games-wrapper'); + Object.keys(settingData.games).forEach((game) => { + // Create game div, invisible by default + const gameDiv = document.createElement('div'); + gameDiv.setAttribute('id', `${game}-div`); + gameDiv.classList.add('game-div'); + gameDiv.classList.add('invisible'); + + const gameHeader = document.createElement('h2'); + gameHeader.innerText = game; + gameDiv.appendChild(gameHeader); + + gameDiv.appendChild(buildOptionsDiv(game, settingData.games[game].gameSettings)); + gamesWrapper.appendChild(gameDiv); }); - document.getElementById('game-options-left').appendChild(buildOptionsTable(leftGameOpts)); - document.getElementById('game-options-right').appendChild(buildOptionsTable(rightGameOpts)); }; -const buildOptionsTable = (settings, romOpts = false) => { - const currentSettings = JSON.parse(localStorage.getItem(gameName)); +const buildGameChoice = (games) => { + const settings = JSON.parse(localStorage.getItem('weighted-settings')); + 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); + + // Build the game choice table const table = document.createElement('table'); const tbody = document.createElement('tbody'); - Object.keys(settings).forEach((setting) => { + Object.keys(games).forEach((game) => { const tr = document.createElement('tr'); + const tdLeft = document.createElement('td'); + tdLeft.classList.add('td-left'); + tdLeft.innerText = game; + tr.appendChild(tdLeft); - // td Left - const tdl = document.createElement('td'); - const label = document.createElement('label'); - label.setAttribute('for', setting); - label.setAttribute('data-tooltip', settings[setting].description); - label.innerText = `${settings[setting].displayName}:`; - tdl.appendChild(label); - tr.appendChild(tdl); + 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 = settings.game[game]; + range.addEventListener('change', (evt) => { + updateBaseSetting(evt); + updateVisibleGames(); // Show or hide games based on the new settings + }); + tdMiddle.appendChild(range); + tr.appendChild(tdMiddle); - // td Right - const tdr = document.createElement('td'); - let element = null; - - switch(settings[setting].type){ - case 'select': - element = document.createElement('div'); - element.classList.add('select-container'); - let select = document.createElement('select'); - select.setAttribute('id', setting); - select.setAttribute('data-key', setting); - if (romOpts) { select.setAttribute('data-romOpt', '1'); } - settings[setting].options.forEach((opt) => { - const option = document.createElement('option'); - option.setAttribute('value', opt.value); - option.innerText = opt.name; - if ((isNaN(currentSettings[gameName][setting]) && - (parseInt(opt.value, 10) === parseInt(currentSettings[gameName][setting]))) || - (opt.value === currentSettings[gameName][setting])) - { - option.selected = true; - } - select.appendChild(option); - }); - select.addEventListener('change', (event) => updateGameSetting(event)); - element.appendChild(select); - break; - - case 'range': - element = document.createElement('div'); - element.classList.add('range-container'); - - let range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('data-key', setting); - range.setAttribute('min', settings[setting].min); - range.setAttribute('max', settings[setting].max); - range.value = currentSettings[gameName][setting]; - range.addEventListener('change', (event) => { - document.getElementById(`${setting}-value`).innerText = event.target.value; - updateGameSetting(event); - }); - element.appendChild(range); - - let rangeVal = document.createElement('span'); - rangeVal.classList.add('range-value'); - rangeVal.setAttribute('id', `${setting}-value`); - rangeVal.innerText = currentSettings[gameName][setting] ?? settings[setting].defaultValue; - element.appendChild(rangeVal); - break; - - default: - console.error(`Unknown setting type: ${settings[setting].type}`); - console.error(setting); - return; - } - - tdr.appendChild(element); - tr.appendChild(tdr); + 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); - return table; + gameChoiceDiv.appendChild(table); +}; + +const buildOptionsDiv = (game, settings) => { + const currentSettings = JSON.parse(localStorage.getItem('weighted-settings')); + const optionsWrapper = document.createElement('div'); + optionsWrapper.classList.add('settings-wrapper'); + + Object.keys(settings).forEach((settingName) => { + const setting = settings[settingName]; + const settingWrapper = document.createElement('div'); + settingWrapper.classList.add('setting-wrapper'); + + switch(setting.type){ + case 'select': + 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); + + 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', game); + 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', updateGameSetting); + range.value = currentSettings[game][settingName][option.value]; + tdMiddle.appendChild(range); + tr.appendChild(tdMiddle); + + const tdRight = document.createElement('td'); + tdRight.setAttribute('id', `${game}-${settingName}-${option.value}`) + tdRight.classList.add('td-right'); + tdRight.innerText = range.value; + tr.appendChild(tdRight); + + tbody.appendChild(tr); + }); + + optionTable.appendChild(tbody); + settingWrapper.appendChild(optionTable); + optionsWrapper.appendChild(settingWrapper); + break; + + case 'range': + // TODO: Include range settings + break; + + default: + console.error(`Unknown setting type for ${game} setting ${setting}: ${settings[setting].type}`); + return; + } + }); + + return optionsWrapper; +}; + +const updateVisibleGames = () => { + const settings = JSON.parse(localStorage.getItem('weighted-settings')); + Object.keys(settings.game).forEach((game) => { + const gameDiv = document.getElementById(`${game}-div`); + (parseInt(settings.game[game], 10) > 0) ? + gameDiv.classList.remove('invisible') : + gameDiv.classList.add('invisible') + }); }; const updateBaseSetting = (event) => { - const options = JSON.parse(localStorage.getItem(gameName)); - options[event.target.getAttribute('data-key')] = isNaN(event.target.value) ? - event.target.value : parseInt(event.target.value); - localStorage.setItem(gameName, JSON.stringify(options)); + const settings = JSON.parse(localStorage.getItem('weighted-settings')); + 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': + settings[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': + settings[setting] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10); + break; + } + + localStorage.setItem('weighted-settings', JSON.stringify(settings)); }; const updateGameSetting = (event) => { - const options = JSON.parse(localStorage.getItem(gameName)); - options[gameName][event.target.getAttribute('data-key')] = isNaN(event.target.value) ? + const options = JSON.parse(localStorage.getItem('weighted-settings')); + const game = event.target.getAttribute('data-game'); + const setting = event.target.getAttribute('data-setting'); + const option = event.target.getAttribute('data-option'); + const type = event.target.getAttribute('data-type'); + switch (type){ + case 'select': + console.log(`${game}-${setting}-${option}`); + document.getElementById(`${game}-${setting}-${option}`).innerText = event.target.value; + break; + case 'range': + break; + } + options[game][setting][option] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10); - localStorage.setItem(gameName, JSON.stringify(options)); + localStorage.setItem('weighted-settings', JSON.stringify(options)); }; const exportSettings = () => { - const settings = JSON.parse(localStorage.getItem(gameName)); - if (!settings.name || settings.name.trim().length === 0) { settings.name = "noname"; } + const settings = JSON.parse(localStorage.getItem('weighted-settings')); + if (!settings.name || settings.name.trim().length === 0 || settings.name.toLowerCase().trim() === 'player') { + const userMessage = document.getElementById('user-message'); + userMessage.innerText = 'You forgot to set your player name at the top of the page!'; + userMessage.classList.add('visible'); + window.scrollTo(0, 0); + return; + } const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); download(`${document.getElementById('player-name').value}.yaml`, yamlText); }; @@ -209,8 +321,8 @@ const download = (filename, text) => { const generateGame = (raceMode = false) => { axios.post('/api/generate', { - weights: { player: localStorage.getItem(gameName) }, - presetData: { player: localStorage.getItem(gameName) }, + weights: { player: localStorage.getItem('weighted-settings') }, + presetData: { player: localStorage.getItem('weighted-settings') }, playerCount: 1, race: raceMode ? '1' : '0', }).then((response) => { diff --git a/WebHostLib/static/styles/weighted-settings.css b/WebHostLib/static/styles/weighted-settings.css index 3a56604b..70a7b34b 100644 --- a/WebHostLib/static/styles/weighted-settings.css +++ b/WebHostLib/static/styles/weighted-settings.css @@ -14,6 +14,35 @@ html{ color: #eeffeb; } +#weighted-settings #games-wrapper{ + width: 100%; +} + +#weighted-settings .setting-wrapper{ + width: 100%; + margin-bottom: 2rem; +} + +#weighted-settings .setting-description{ + font-weight: bold; + margin: 0 0 1rem; +} + +#weighted-settings table{ + width: 100%; +} + +#weighted-settings table .td-left{ + padding-right: 1rem; +} + +#weighted-settings table .td-middle{ + display: flex; + flex-direction: column; + justify-content: space-evenly; + padding-right: 1rem; +} + #weighted-settings #weighted-settings-button-row{ display: flex; flex-direction: row; @@ -94,42 +123,11 @@ html{ #weighted-settings .game-options, #weighted-settings .rom-options{ display: flex; - flex-direction: row; + flex-direction: column; } -#weighted-settings .left, #weighted-settings .right{ - flex-grow: 1; -} - -#weighted-settings table .select-container{ - display: flex; - flex-direction: row; -} - -#weighted-settings table .select-container select{ - min-width: 200px; - flex-grow: 1; -} - -#weighted-settings table .range-container{ - display: flex; - flex-direction: row; -} - -#weighted-settings table .range-container input[type=range]{ - flex-grow: 1; -} - -#weighted-settings table .range-value{ - min-width: 20px; - margin-left: 0.25rem; -} - -#weighted-settings table label{ - display: block; - min-width: 200px; - margin-right: 4px; - cursor: default; +#weighted-settings .invisible{ + display: none; } @media all and (max-width: 1000px), all and (orientation: portrait){ @@ -138,10 +136,6 @@ html{ flex-wrap: wrap; } - #weighted-settings .left, #weighted-settings .right{ - flex-grow: unset; - } - #game-options table label{ display: block; min-width: 200px; diff --git a/WebHostLib/templates/weighted-settings.html b/WebHostLib/templates/weighted-settings.html index 09cd4771..353d82fb 100644 --- a/WebHostLib/templates/weighted-settings.html +++ b/WebHostLib/templates/weighted-settings.html @@ -24,15 +24,13 @@

-
+
-

(Game Name) Options

-
-
-
+
+