From 4c0f0a16c9430f51dad0cd3bae4237e4be0cb9ff Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Sun, 22 Aug 2021 20:01:58 -0400 Subject: [PATCH] Updates to WebHost - Support displayname option for Options module - Improvements to landing page - Added multi-language capable FAQ page - Removed weighted-settings page - Removed references to weighted-settings page --- WebHostLib/__init__.py | 21 +- WebHostLib/options.py | 4 +- WebHostLib/static/assets/faq.js | 50 ++ WebHostLib/static/assets/faq/faq_en.md | 10 + WebHostLib/static/assets/player-settings.js | 2 +- .../assets/tutorial/zelda3/multiworld_en.md | 16 - WebHostLib/static/assets/weightedSettings.js | 486 ------------------ WebHostLib/static/styles/landing.css | 2 +- WebHostLib/static/styles/markdown.css | 120 +++++ WebHostLib/static/styles/weightedSettings.css | 162 ------ WebHostLib/templates/faq.html | 17 + WebHostLib/templates/generate.html | 3 +- WebHostLib/templates/header/baseHeader.html | 6 +- WebHostLib/templates/landing.html | 12 +- WebHostLib/templates/player-settings.html | 4 +- WebHostLib/templates/weightedSettings.html | 77 --- 16 files changed, 231 insertions(+), 761 deletions(-) create mode 100644 WebHostLib/static/assets/faq.js create mode 100644 WebHostLib/static/assets/faq/faq_en.md delete mode 100644 WebHostLib/static/assets/weightedSettings.js create mode 100644 WebHostLib/static/styles/markdown.css delete mode 100644 WebHostLib/static/styles/weightedSettings.css create mode 100644 WebHostLib/templates/faq.html delete mode 100644 WebHostLib/templates/weightedSettings.html diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index 23494113..59091f6a 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -106,6 +106,21 @@ games_list = { an unknown bacteria. The planet's automatic quarantine will shoot you down if you try to leave. You must find a cure for yourself, build an escape rocket, and leave the planet. """), + "Ocarina of Time": ("The Legend of Zelda: Ocarina of Time", + """ + The Legend of Zelda: Ocarina of Time was the first three dimensional Zelda game. Journey as + Link as he quests to fulfil his destiny. Journey across Hyrule and defeat the evil masters of + corrupted temples or seek out the pieces of the Triforce. Defeat the evil Ganondorf to become + the Hero of Time and save Hyrule! + """), + "Super Metroid": ("Super Metroid", + """ + Samus is back in her first 16 bit adventure! Space pirates have attacked Ceres station and stolen + the last living Metroid. Go to planet Zebes and search out the abilities you will need to power + up your suit and defeat the villainous leader of the space pirates, Mother Brain. + """), + # "Ori and the Blind Forest": ("Ori and the Blind Forest", "Coming Soon™"), + # "Hollow Knight": ("Hollow Knight", "Coming Soon™"), } @@ -143,9 +158,9 @@ def tutorial_landing(): return render_template("tutorialLanding.html") -@app.route('/weighted-settings') -def weighted_settings(): - return render_template("weightedSettings.html") +@app.route('/faq//') +def faq(lang): + return render_template("faq.html", lang=lang) @app.route('/seed/') diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 6cf3a2e4..56d01737 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -31,7 +31,7 @@ def create(): if option.options: this_option = { "type": "select", - "friendlyName": option.friendly_name if hasattr(option, "friendly_name") else option_name, + "displayName": option.displayname if hasattr(option, "displayname") else option_name, "description": option.__doc__ if option.__doc__ else "Please document me!", "defaultValue": None, "options": [] @@ -51,7 +51,7 @@ def create(): elif hasattr(option, "range_start") and hasattr(option, "range_end"): game_options[option_name] = { "type": "range", - "friendlyName": option.friendly_name if hasattr(option, "friendly_name") else option_name, + "displayName": option.displayname if hasattr(option, "displayname") else option_name, "description": option.__doc__ if option.__doc__ else "Please document me!", "defaultValue": option.default if hasattr(option, "default") else option.range_start, "min": option.range_start, diff --git a/WebHostLib/static/assets/faq.js b/WebHostLib/static/assets/faq.js new file mode 100644 index 00000000..57b2a8c6 --- /dev/null +++ b/WebHostLib/static/assets/faq.js @@ -0,0 +1,50 @@ +window.addEventListener('load', () => { + const tutorialWrapper = document.getElementById('faq-wrapper'); + new Promise((resolve, reject) => { + const ajax = new XMLHttpRequest(); + ajax.onreadystatechange = () => { + if (ajax.readyState !== 4) { return; } + if (ajax.status === 404) { + reject("Sorry, the tutorial is not available in that language yet."); + return; + } + if (ajax.status !== 200) { + reject("Something went wrong while loading the tutorial."); + return; + } + resolve(ajax.responseText); + }; + ajax.open('GET', `${window.location.origin}/static/assets/faq/` + + `faq_${tutorialWrapper.getAttribute('data-lang')}.md`, true); + ajax.send(); + }).then((results) => { + // Populate page with HTML generated from markdown + tutorialWrapper.innerHTML += (new showdown.Converter()).makeHtml(results); + adjustHeaderWidth(); + + // Reset the id of all header divs to something nicer + const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')); + const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/); + for (let i=0; i < headers.length; i++){ + const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase() + headers[i].setAttribute('id', headerId); + headers[i].addEventListener('click', () => + window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`); + } + + // Manually scroll the user to the appropriate header if anchor navigation is used + if (scrollTargetIndex > -1) { + try{ + const scrollTarget = window.location.href.substring(scrollTargetIndex + 1); + document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" }); + } catch(error) { + console.error(error); + } + } + }).catch((error) => { + console.error(error); + tutorialWrapper.innerHTML = + `

This page is out of logic!

+

Click here to return to safety.

`; + }); +}); diff --git a/WebHostLib/static/assets/faq/faq_en.md b/WebHostLib/static/assets/faq/faq_en.md new file mode 100644 index 00000000..fae9f302 --- /dev/null +++ b/WebHostLib/static/assets/faq/faq_en.md @@ -0,0 +1,10 @@ +# Frequently Asked Questions + +## What is a randomizer? +Who's on first. + +## What is a multi-world? +What's on second. + +## What does multi-game mean? +I don't know's on third. diff --git a/WebHostLib/static/assets/player-settings.js b/WebHostLib/static/assets/player-settings.js index 79a713d2..fcd4d120 100644 --- a/WebHostLib/static/assets/player-settings.js +++ b/WebHostLib/static/assets/player-settings.js @@ -83,7 +83,7 @@ const buildOptionsTable = (settings, romOpts = false) => { const label = document.createElement('label'); label.setAttribute('for', setting); label.setAttribute('data-tooltip', settings[setting].description); - label.innerText = `${settings[setting].friendlyName}:`; + label.innerText = `${settings[setting].displayName}:`; tdl.appendChild(label); tr.appendChild(tdl); diff --git a/WebHostLib/static/assets/tutorial/zelda3/multiworld_en.md b/WebHostLib/static/assets/tutorial/zelda3/multiworld_en.md index 2d0fabe8..260eec98 100644 --- a/WebHostLib/static/assets/tutorial/zelda3/multiworld_en.md +++ b/WebHostLib/static/assets/tutorial/zelda3/multiworld_en.md @@ -53,22 +53,6 @@ can all have different options. The [Generate Game](/player-settings) page on the website allows you to configure your personal settings and export a YAML file from them. -### Advanced YAML configuration -A more advanced version of the YAML file can be created using the [Weighted Settings](/weighted-settings) page, -which allows you to configure up to three presets. The Weighted Settings page has many options which are -primarily represented with sliders. This allows you to choose how likely certain options are to occur relative -to other options within a category. - -For example, imagine the generator creates a bucket labeled "Map Shuffle", and places folded pieces of paper -into the bucket for each sub-option. Also imagine your chosen value for "On" is 20, and your value for "Off" is 40. - -In this example, sixty pieces of paper are put into the bucket. Twenty for "On" and forty for "Off". When the -generator is deciding whether or not to turn on map shuffle for your game, it reaches into this bucket and pulls -out a piece of paper at random. In this example, you are much more likely to have map shuffle turned off. - -If you never want an option to be chosen, simply set its value to zero. Remember that each setting must have at -lease one option set to a number greater than zero. - ### Verifying your YAML file If you would like to validate your YAML file to make sure it works, you may do so on the [YAML Validator](/mysterycheck) page. diff --git a/WebHostLib/static/assets/weightedSettings.js b/WebHostLib/static/assets/weightedSettings.js deleted file mode 100644 index 185b16c6..00000000 --- a/WebHostLib/static/assets/weightedSettings.js +++ /dev/null @@ -1,486 +0,0 @@ -let spriteData = null; - -window.addEventListener('load', () => { - const gameSettings = document.getElementById('weighted-settings'); - Promise.all([fetchWeightedSettingsYaml(), fetchWeightedSettingsJson(), fetchSpriteData()]).then((results) => { - // Load YAML into object - const sourceData = jsyaml.safeLoad(results[0], { json: true }); - const wsVersion = sourceData.ws_version; - delete sourceData.ws_version; // Do not include the settings version number in the export - - // Check if settings exist in localStorage. If no settings are present, this is a first load (or reset to default) - // and the version number should be silently updated - if (!localStorage.getItem('weightedSettings1')) { - localStorage.setItem('wsVersion', wsVersion); - } - - // Update localStorage with three settings objects. Preserve original objects if present. - for (let i=1; i<=3; i++) { - const localSettings = JSON.parse(localStorage.getItem(`weightedSettings${i}`)); - const updatedObj = localSettings ? Object.assign(sourceData, localSettings) : sourceData; - localStorage.setItem(`weightedSettings${i}`, JSON.stringify(updatedObj)); - } - - // Build the entire UI - buildUI(JSON.parse(results[1]), JSON.parse(results[2])); - - // Populate the UI and add event listeners - populateSettings(); - document.getElementById('preset-number').addEventListener('change', populateSettings); - gameSettings.addEventListener('change', handleOptionChange); - gameSettings.addEventListener('keyup', handleOptionChange); - - document.getElementById('export-button').addEventListener('click', exportSettings); - document.getElementById('reset-to-default').addEventListener('click', resetToDefaults); - adjustHeaderWidth(); - - if (localStorage.getItem('wsVersion') !== wsVersion) { - const userWarning = document.getElementById('user-warning'); - const messageSpan = document.createElement('span'); - messageSpan.innerHTML = "A new version of the weighted settings file is available. Click here to update!" + - "
Be aware this will also reset your presets, so you should export them now if you want to save them."; - userWarning.appendChild(messageSpan); - userWarning.style.display = 'block'; - userWarning.addEventListener('click', resetToDefaults); - } - }).catch((error) => { - console.error(error); - gameSettings.innerHTML = ` -

Something went wrong while loading your game settings page.

-

${error}

-

Click here to return to safety!

- ` - }); - document.getElementById('generate-game').addEventListener('click', () => generateGame()); - document.getElementById('generate-race').addEventListener('click', () => generateGame(true)); -}); - -const fetchWeightedSettingsYaml = () => new Promise((resolve, reject) => { - const ajax = new XMLHttpRequest(); - ajax.onreadystatechange = () => { - if (ajax.readyState !== 4) { return; } - if (ajax.status !== 200) { - reject("Unable to fetch source yaml file."); - return; - } - resolve(ajax.responseText); - }; - ajax.open('GET', `${window.location.origin}/static/static/weightedSettings.yaml` ,true); - ajax.send(); -}); - -const fetchWeightedSettingsJson = () => new Promise((resolve, reject) => { - const ajax = new XMLHttpRequest(); - ajax.onreadystatechange = () => { - if (ajax.readyState !== 4) { return; } - if (ajax.status !== 200) { - reject('Unable to fetch JSON schema file'); - return; - } - resolve(ajax.responseText); - }; - ajax.open('GET', `${window.location.origin}/static/static/weightedSettings.json`, true); - ajax.send(); -}); - -const fetchSpriteData = () => new Promise((resolve, reject) => { - const ajax = new XMLHttpRequest(); - ajax.onreadystatechange = () => { - if (ajax.readyState !== 4) { return; } - if (ajax.status !== 200) { - reject('Unable to fetch sprite data.'); - return; - } - resolve(ajax.responseText); - }; - ajax.open('GET', `${window.location.origin}/static/generated/spriteData.json`, true); - ajax.send(); -}); - -const handleOptionChange = (event) => { - if(!event.target.matches('.setting')) { return; } - const presetNumber = document.getElementById('preset-number').value; - const settings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)) - const settingString = event.target.getAttribute('data-setting'); - document.getElementById(settingString).innerText = event.target.value; - if(getSettingValue(settings, settingString) !== false){ - const keys = settingString.split('.'); - switch (keys.length) { - case 1: - settings[keys[0]] = isNaN(event.target.value) ? - event.target.value : parseInt(event.target.value, 10); - break; - case 2: - settings[keys[0]][keys[1]] = isNaN(event.target.value) ? - event.target.value : parseInt(event.target.value, 10); - break; - case 3: - settings[keys[0]][keys[1]][keys[2]] = isNaN(event.target.value) ? - event.target.value : parseInt(event.target.value, 10); - break; - default: - console.warn(`Unknown setting string received: ${settingString}`) - return; - } - - // Save the updated settings object bask to localStorage - localStorage.setItem(`weightedSettings${presetNumber}`, JSON.stringify(settings)); - }else{ - console.warn(`Unknown setting string received: ${settingString}`) - } -}; - -const populateSettings = () => { - buildSpriteOptions(); - const presetNumber = document.getElementById('preset-number').value; - const settings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)) - const settingsInputs = Array.from(document.querySelectorAll('.setting')); - settingsInputs.forEach((input) => { - const settingString = input.getAttribute('data-setting'); - const settingValue = getSettingValue(settings, settingString); - if(settingValue !== false){ - input.value = settingValue; - document.getElementById(settingString).innerText = settingValue; - } - }); -}; - -/** - * Returns the value of the settings object, or false if the settings object does not exist - * @param settings - * @param keyString - * @returns {string} | bool - */ -const getSettingValue = (settings, keyString) => { - const keys = keyString.split('.'); - let currentVal = settings; - keys.forEach((key) => { - if(typeof(key) === 'string' && currentVal.hasOwnProperty(key)){ - currentVal = currentVal[key]; - }else{ - currentVal = false; - } - }); - return currentVal; -}; - -const exportSettings = () => { - const presetNumber = document.getElementById('preset-number').value; - const settings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)); - const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); - download(`${settings.description}.yaml`, yamlText); -}; - -const resetToDefaults = () => { - [1, 2, 3].forEach((presetNumber) => localStorage.removeItem(`weightedSettings${presetNumber}`)); - location.reload(); -}; - -/** 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); -}; - -const buildUI = (settings, spriteData) => { - const settingsWrapper = document.getElementById('settings-wrapper'); - const settingTypes = { - gameOptions: 'Game Options', - romOptions: 'ROM Options', - } - - Object.keys(settingTypes).forEach((settingTypeKey) => { - const sectionHeader = document.createElement('h2'); - sectionHeader.innerText = settingTypes[settingTypeKey]; - settingsWrapper.appendChild(sectionHeader); - - Object.values(settings[settingTypeKey]).forEach((setting) => { - if (typeof(setting.inputType) === 'undefined' || !setting.inputType){ - console.error(setting); - throw new Error('Setting with no inputType specified.'); - } - - switch(setting.inputType){ - case 'text': - // Currently, all text input is handled manually because there is very little of it - return; - case 'range': - buildRangeSettings(settingsWrapper, setting); - return; - default: - console.error(setting); - throw new Error('Unhandled inputType specified.'); - } - }); - }); - - // Build sprite options - const spriteOptionsHeader = document.createElement('h2'); - spriteOptionsHeader.innerText = 'Sprite Options'; - settingsWrapper.appendChild(spriteOptionsHeader); - - const spriteOptionsWrapper = document.createElement('div'); - spriteOptionsWrapper.setAttribute('id', 'sprite-options-wrapper'); - spriteOptionsWrapper.className = 'setting-wrapper'; - settingsWrapper.appendChild(spriteOptionsWrapper); - - // Append sprite picker - settingsWrapper.appendChild(buildSpritePicker(spriteData)); -}; - -const buildSpriteOptions = () => { - const spriteOptionsWrapper = document.getElementById('sprite-options-wrapper'); - - // Clear the contents of the wrapper div - while(spriteOptionsWrapper.firstChild){ - spriteOptionsWrapper.removeChild(spriteOptionsWrapper.lastChild); - } - - const spriteOptionsTitle = document.createElement('span'); - spriteOptionsTitle.className = 'title-span'; - spriteOptionsTitle.innerText = 'Alternate Sprites'; - spriteOptionsWrapper.appendChild(spriteOptionsTitle); - - const spriteOptionsDescription = document.createElement('span'); - spriteOptionsDescription.className = 'description-span'; - spriteOptionsDescription.innerHTML = 'Choose an alternate sprite to play the game with. Additional randomization ' + - 'options are documented in the ' + - 'settings file.'; - spriteOptionsWrapper.appendChild(spriteOptionsDescription); - - const spriteOptionsTable = document.createElement('table'); - spriteOptionsTable.setAttribute('id', 'sprite-options-table'); - spriteOptionsTable.className = 'option-set'; - const tbody = document.createElement('tbody'); - tbody.setAttribute('id', 'sprites-tbody'); - - const currentPreset = document.getElementById('preset-number').value; - const playerSettings = JSON.parse(localStorage.getItem(`weightedSettings${currentPreset}`)); - - // Manually add a row for random sprites - addSpriteRow(tbody, playerSettings, 'random'); - - // Add a row for each sprite currently present in the player's settings - Object.keys(playerSettings.rom.sprite).forEach((spriteName) => { - if(['random'].indexOf(spriteName) > -1) return; - addSpriteRow(tbody, playerSettings, spriteName) - }); - - spriteOptionsTable.appendChild(tbody); - spriteOptionsWrapper.appendChild(spriteOptionsTable); -}; - -const buildRangeSettings = (parentElement, settings) => { - // Ensure we are operating on a range-specific setting - if(typeof(settings.inputType) === 'undefined' || settings.inputType !== 'range'){ - throw new Error('Invalid input type provided to buildRangeSettings func.'); - } - - const settingWrapper = document.createElement('div'); - settingWrapper.className = 'setting-wrapper'; - - if(typeof(settings.friendlyName) !== 'undefined' && settings.friendlyName){ - const sectionTitle = document.createElement('span'); - sectionTitle.className = 'title-span'; - sectionTitle.innerText = settings.friendlyName; - settingWrapper.appendChild(sectionTitle); - } - - if(settings.description){ - const description = document.createElement('span'); - description.className = 'description-span'; - description.innerText = settings.description; - settingWrapper.appendChild(description); - } - - // Create table - const optionSetTable = document.createElement('table'); - optionSetTable.className = 'option-set'; - - // Create table body - const tbody = document.createElement('tbody'); - Object.keys(settings.subOptions).forEach((setting) => { - // Overwrite setting key name with real object - setting = settings.subOptions[setting]; - const settingId = (Math.random() * 1000000).toString(); - - // Create rows for each option - const optionRow = document.createElement('tr'); - - // Option name td - const optionName = document.createElement('td'); - optionName.className = 'option-name'; - const optionLabel = document.createElement('label'); - optionLabel.setAttribute('for', settingId); - optionLabel.setAttribute('data-tooltip', setting.description); - optionLabel.innerText = setting.friendlyName; - optionName.appendChild(optionLabel); - optionRow.appendChild(optionName); - - // Option value td - const optionValue = document.createElement('td'); - optionValue.className = 'option-value'; - const input = document.createElement('input'); - input.className = 'setting'; - input.setAttribute('id', settingId); - input.setAttribute('type', 'range'); - input.setAttribute('min', '0'); - input.setAttribute('max', '100'); - input.setAttribute('data-setting', setting.keyString); - input.value = setting.defaultValue; - optionValue.appendChild(input); - const valueDisplay = document.createElement('span'); - valueDisplay.setAttribute('id', setting.keyString); - valueDisplay.innerText = setting.defaultValue; - optionValue.appendChild(valueDisplay); - optionRow.appendChild(optionValue); - tbody.appendChild(optionRow); - }); - - optionSetTable.appendChild(tbody); - settingWrapper.appendChild(optionSetTable); - parentElement.appendChild(settingWrapper); -}; - -const addSpriteRow = (tbody, playerSettings, spriteName) => { - const rowId = (Math.random() * 1000000).toString(); - const optionId = (Math.random() * 1000000).toString(); - const tr = document.createElement('tr'); - tr.setAttribute('id', rowId); - - // Option Name - const optionName = document.createElement('td'); - optionName.className = 'option-name'; - const label = document.createElement('label'); - label.htmlFor = optionId; - label.innerText = spriteName; - optionName.appendChild(label); - - if(['random', 'random_sprite_on_event'].indexOf(spriteName) === -1) { - const deleteButton = document.createElement('span'); - deleteButton.setAttribute('data-sprite', spriteName); - deleteButton.setAttribute('data-row-id', rowId); - deleteButton.innerText = ' (❌)'; - deleteButton.className = 'delete-button'; - optionName.appendChild(deleteButton); - deleteButton.addEventListener('click', removeSpriteOption); - } - - tr.appendChild(optionName); - - // Option Value - const optionValue = document.createElement('td'); - optionValue.className = 'option-value'; - const input = document.createElement('input'); - input.className = 'setting'; - input.setAttribute('id', optionId); - input.setAttribute('type', 'range'); - input.setAttribute('min', '0'); - input.setAttribute('max', '100'); - input.setAttribute('data-setting', `rom.sprite.${spriteName}`); - input.value = "50"; - optionValue.appendChild(input); - - // Value display - const valueDisplay = document.createElement('span'); - valueDisplay.setAttribute('id', `rom.sprite.${spriteName}`); - valueDisplay.innerText = playerSettings.rom.sprite.hasOwnProperty(spriteName) ? - playerSettings.rom.sprite[spriteName] : '0'; - optionValue.appendChild(valueDisplay); - - tr.appendChild(optionValue); - tbody.appendChild(tr); -}; - -const addSpriteOption = (event) => { - const presetNumber = document.getElementById('preset-number').value; - const playerSettings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)); - const spriteName = event.target.getAttribute('data-sprite'); - - if (Object.keys(playerSettings.rom.sprite).indexOf(spriteName) !== -1) { - // Do not add the same sprite twice - return; - } - - // Add option to playerSettings object - playerSettings.rom.sprite[event.target.getAttribute('data-sprite')] = 50; - localStorage.setItem(`weightedSettings${presetNumber}`, JSON.stringify(playerSettings)); - - // Add to #sprite-options-table - const tbody = document.getElementById('sprites-tbody'); - addSpriteRow(tbody, playerSettings, spriteName); -}; - -const removeSpriteOption = (event) => { - const presetNumber = document.getElementById('preset-number').value; - const playerSettings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)); - const spriteName = event.target.getAttribute('data-sprite'); - - // Remove option from playerSettings object - delete playerSettings.rom.sprite[spriteName]; - localStorage.setItem(`weightedSettings${presetNumber}`, JSON.stringify(playerSettings)); - - // Remove from #sprite-options-table - const tr = document.getElementById(event.target.getAttribute('data-row-id')); - tr.parentNode.removeChild(tr); -}; - -const buildSpritePicker = (spriteData) => { - const spritePicker = document.createElement('div'); - spritePicker.setAttribute('id', 'sprite-picker'); - - // Build description - const description = document.createElement('span'); - description.innerText = 'To add a sprite to your playable list, click the one you want below.'; - spritePicker.appendChild(description); - - const sprites = document.createElement('div'); - sprites.setAttribute('id', 'sprite-picker-sprites'); - spriteData.sprites.forEach((sprite) => { - const spriteImg = document.createElement('img'); - let spriteGifFile = sprite.file.split('.'); - spriteGifFile.pop(); - spriteGifFile = spriteGifFile.join('.') + '.gif'; - spriteImg.setAttribute('src', `static/generated/sprites/${spriteGifFile}`); - spriteImg.setAttribute('data-sprite', sprite.file.split('.')[0]); - spriteImg.setAttribute('alt', sprite.name); - - // Wrap the image in a span to allow for tooltip presence - const imgWrapper = document.createElement('span'); - imgWrapper.className = 'sprite-img-wrapper'; - imgWrapper.setAttribute('data-tooltip', `${sprite.name}${sprite.author ? `, by ${sprite.author}` : ''}`); - imgWrapper.appendChild(spriteImg); - imgWrapper.setAttribute('data-sprite', sprite.name); - sprites.appendChild(imgWrapper); - imgWrapper.addEventListener('click', addSpriteOption); - }); - - spritePicker.appendChild(sprites); - return spritePicker; -}; - -const generateGame = (raceMode = false) => { - const presetNumber = document.getElementById('preset-number').value; - axios.post('/api/generate', { - weights: { player: localStorage.getItem(`weightedSettings${presetNumber}`) }, - presetData: { player: localStorage.getItem(`weightedSettings${presetNumber}`) }, - playerCount: 1, - 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'); - window.scrollTo(0, 0); - console.error(error); - }); -}; diff --git a/WebHostLib/static/styles/landing.css b/WebHostLib/static/styles/landing.css index 1192584f..40f85bb1 100644 --- a/WebHostLib/static/styles/landing.css +++ b/WebHostLib/static/styles/landing.css @@ -60,7 +60,7 @@ html{ width: 200px; height: calc(156px - 40px); padding-top: 40px; - cursor: default; + cursor: pointer; } #mid-left-button{ diff --git a/WebHostLib/static/styles/markdown.css b/WebHostLib/static/styles/markdown.css new file mode 100644 index 00000000..a00f1b1c --- /dev/null +++ b/WebHostLib/static/styles/markdown.css @@ -0,0 +1,120 @@ +.markdown{ + display: flex; + flex-direction: column; + max-width: 70rem; + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem 1rem 3rem; + color: #eeffeb; +} + +.markdown img{ + max-width: 100%; + border-radius: 6px; +} + +.markdown p{ + margin-top: 0; +} + +.markdown a{ + color: #ffef00; +} + +.markdown h1{ + font-size: 2.5rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + cursor: pointer; + width: 100%; + margin-bottom: 0.5rem; + color: #ffffff; + text-shadow: 1px 1px 4px #000000; +} + +.markdown h2{ + font-size: 2rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + cursor: pointer; + width: 100%; + margin-bottom: 0.5rem; + color: #ffe993; + text-transform: lowercase; + text-shadow: 1px 1px 2px #000000; +} + +.markdown h3{ + font-size: 1.70rem; + font-weight: normal; + text-align: left; + cursor: pointer; + width: 100%; + margin-bottom: 0.5rem; +} + +.markdown h4{ + font-size: 1.5rem; + font-weight: normal; + cursor: pointer; + margin-bottom: 0.5rem; +} + +.markdown h5{ + font-size: 1.25rem; + font-weight: normal; + cursor: pointer; +} + +.markdown h6{ + font-size: 1.25rem; + font-weight: normal; + cursor: pointer; + color: #434343; +} + +.markdown h3, .markdown h4, .markdown h5,.markdown h6{ + color: #ffffff; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); +} + +.markdown ul{ + +} + +.markdown ol{ + +} + +.markdown li{ + +} + +.markdown pre{ + margin-top: 0; + padding: 0.5rem 0.25rem; + background-color: #ffeeab; + border: 1px solid #9f916a; + border-radius: 6px; + color: #000000; +} + +.markdown code{ + background-color: #ffeeab; + border-radius: 4px; + padding-left: 0.25rem; + padding-right: 0.25rem; + color: #000000; +} + +.markdown #tutorial-video-container{ + width: 100%; + text-align: center; +} + +.markdown #language-selector-wrapper{ + width: 100%; + text-align: right; +} diff --git a/WebHostLib/static/styles/weightedSettings.css b/WebHostLib/static/styles/weightedSettings.css deleted file mode 100644 index 95c004b2..00000000 --- a/WebHostLib/static/styles/weightedSettings.css +++ /dev/null @@ -1,162 +0,0 @@ -#weighted-settings{ - width: 60rem; - margin-left: auto; - margin-right: auto; - background-color: rgba(0, 0, 0, 0.15); - border-radius: 8px; - padding: 1rem; - color: #eeffeb; -} - -#user-warning, #weighted-settings #user-message{ - display: none; - width: calc(100% - 8px); - background-color: #ffe86b; - border-radius: 4px; - color: #000000; - padding: 4px; - text-align: center; - cursor: pointer; -} - -#weighted-settings #user-message.visible{ - display: block; -} - -#weighted-settings code{ - background-color: #d9cd8e; - border-radius: 4px; - padding-left: 0.25rem; - padding-right: 0.25rem; - color: #000000; -} - -#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: lowercase; - 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); -} - -#weighted-settings .instructions{ - text-align: left; -} - -#weighted-settings #settings-wrapper .setting-wrapper{ - display: flex; - flex-direction: column; - justify-content: flex-start; - width: 100%; -} - -#weighted-settings #settings-wrapper .setting-wrapper .title-span{ - font-weight: 600; - font-size: 1.25rem; -} - -#weighted-settings #settings-wrapper{ - margin-top: 1.5rem; -} - -#weighted-settings #settings-wrapper #sprite-picker{ - margin-bottom: 2rem; -} - -#weighted-settings #settings-wrapper #sprite-picker #sprite-picker-sprites{ - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: flex-start; -} - -#weighted-settings #settings-wrapper #sprite-picker .sprite-img-wrapper{ - cursor: pointer; - margin: 10px; - image-rendering: pixelated; -} - -/* Center tooltip text for sprite images */ -#weighted-settings #settings-wrapper #sprite-picker .sprite-img-wrapper::after{ - text-align: center; -} - -#weighted-settings #settings-wrapper #sprite-picker .sprite-img-wrapper img{ - width: 32px; - height: 48px; -} - -#weighted-settings table.option-set{ - width: 100%; - margin-bottom: 1.5rem; -} - -#weighted-settings table.option-set td.option-name{ - width: 150px; - font-weight: 400; - font-size: 1rem; - line-height: 2rem; -} - -#weighted-settings table.option-set td.option-name .delete-button{ - cursor: pointer; -} - -#weighted-settings table.option-set td.option-value{ - line-height: 2rem; -} - -#weighted-settings table.option-set td.option-value input[type=range]{ - width: 90%; - min-width: 300px; - vertical-align: middle; -} - -#weighted-settings #weighted-settings-button-row{ - display: flex; - flex-direction: row; - justify-content: space-between; - width: 100%; -} - -#weighted-settings a{ - color: #ffef00; -} - -#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; -} - diff --git a/WebHostLib/templates/faq.html b/WebHostLib/templates/faq.html new file mode 100644 index 00000000..76bdb96d --- /dev/null +++ b/WebHostLib/templates/faq.html @@ -0,0 +1,17 @@ +{% extends 'pageWrapper.html' %} + +{% block head %} + {% include 'header/grassHeader.html' %} + Frequently Asked Questions + + + +{% endblock %} + +{% block body %} +
+ +
+{% endblock %} diff --git a/WebHostLib/templates/generate.html b/WebHostLib/templates/generate.html index 69b50d7a..13720313 100644 --- a/WebHostLib/templates/generate.html +++ b/WebHostLib/templates/generate.html @@ -15,8 +15,7 @@

This page allows you to generate a game by uploading a yaml file or a zip file containing yaml files. If you do not have a config (yaml) file yet, you may create one on the - Player Settings page. If you would like more advanced options, - the Weighted Settings page might be what you're looking for. + Player Settings page.

{% if race -%} diff --git a/WebHostLib/templates/header/baseHeader.html b/WebHostLib/templates/header/baseHeader.html index e7d82dec..dfe42e51 100644 --- a/WebHostLib/templates/header/baseHeader.html +++ b/WebHostLib/templates/header/baseHeader.html @@ -11,9 +11,11 @@ archipelago

{% endblock %} diff --git a/WebHostLib/templates/landing.html b/WebHostLib/templates/landing.html index 21f78de8..ec187dad 100644 --- a/WebHostLib/templates/landing.html +++ b/WebHostLib/templates/landing.html @@ -10,14 +10,14 @@

ARCHIPELAGO

-

multiworld randomizer ecosystem

+

multiworld multi-game randomizer

diff --git a/WebHostLib/templates/player-settings.html b/WebHostLib/templates/player-settings.html index cdfcb950..096c2581 100644 --- a/WebHostLib/templates/player-settings.html +++ b/WebHostLib/templates/player-settings.html @@ -14,9 +14,7 @@

Player Settings

Choose the options you would like to play with! You may generate a single-player game from this page, - or download a settings file you can use to participate in a MultiWorld. If you would like to make - your settings extra random, check out the advanced weighted settings - page.

+ or download a settings file you can use to participate in a MultiWorld.

A list of all games you have generated can be found here.

diff --git a/WebHostLib/templates/weightedSettings.html b/WebHostLib/templates/weightedSettings.html deleted file mode 100644 index c92cdb88..00000000 --- a/WebHostLib/templates/weightedSettings.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends 'pageWrapper.html' %} - -{% block head %} - Player Settings - - - - -{% endblock %} - -{% block body %} - {% include 'header/grassHeader.html' %} -
-
-
-

Weighted Settings

-
- This page is used to configure your weighted settings. You have three presets you can control, which - you can access using the dropdown menu below. These settings will be usable when generating a - single player game, or you can export them to a .yaml file and use them in a multiworld. - If you already have a settings file you would like to validate, you may do so on the - verification page. -
- -
-
- Choose a preset and optionally assign it a nickname, which will be used as the file's description if - you download it. - - - - - - - - - - - -
- - - -
- - - -
-
- - Choose a name you want to represent you in-game. This will appear when you send items - to other people in multiworld games. - - - - - - - -
- - - -
-
-
- - - - -
-
-{% endblock %}