From 6c0254f664d7f0a810eb429adaf5d1bcc886b6bf Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Wed, 3 Jul 2024 14:52:13 +0100 Subject: [PATCH] add a bestiary search form and catalog --- app/routes/get_creature.py | 7 +- app/tables/creature.py | 4 + web/bestiary.html | 72 ++++++++++++ web/bestiary.js | 226 +++++++++++++++++++++++++++++++++++++ web/catalog.js | 46 ++++++++ web/index.html | 7 +- 6 files changed, 358 insertions(+), 4 deletions(-) create mode 100644 web/bestiary.html create mode 100644 web/bestiary.js create mode 100644 web/catalog.js diff --git a/app/routes/get_creature.py b/app/routes/get_creature.py index eb31429..65edd80 100644 --- a/app/routes/get_creature.py +++ b/app/routes/get_creature.py @@ -4,15 +4,16 @@ from app.tables.creature import Creatures namespace = Namespace('rules', description='Gamma World Rules') parser = reqparse.RequestParser() -parser.add_argument('creature', type=str, help='the name of a Gamma World creature to search for.') +parser.add_argument('creature', type=str, required=False, help='the name of a Gamma World creature to search for.') @namespace.route('/creature') # resolves to: /rules/creature class RollEncounter(Resource): @namespace.expect(parser) def get(self): + creatures = Creatures() creature = request.args.get('creature', default=None, type=str) if creature is None: - return {'error': 'Provide the name of a Gamma World creature to search for'}, 400 - creatures = Creatures() + return creatures.get_creature_list(), 200 return creatures.get_creature(creature), 200 + diff --git a/app/tables/creature.py b/app/tables/creature.py index 318708b..5c3d967 100644 --- a/app/tables/creature.py +++ b/app/tables/creature.py @@ -17,3 +17,7 @@ class Creatures: def get_creature(self, creature_name): """Returns the dictionary of the specified creature.""" return self.creatures.get(creature_name) + + def get_creature_list(self): + """Returns a list of all creatures.""" + return sorted(list(self.creatures.keys())) diff --git a/web/bestiary.html b/web/bestiary.html new file mode 100644 index 0000000..1e7de93 --- /dev/null +++ b/web/bestiary.html @@ -0,0 +1,72 @@ + + + + + + + Gamma World Bestiary + + + + + +

Look Up A Creature

+ +
+
+
+ + + +
+
+ + +
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/web/bestiary.js b/web/bestiary.js new file mode 100644 index 0000000..32eaa8c --- /dev/null +++ b/web/bestiary.js @@ -0,0 +1,226 @@ +document.getElementById('searchForm').addEventListener('submit', function (event) { + event.preventDefault(); + const creatureName = document.getElementById('creature').value; + const queryParams = new URLSearchParams({ + creature: creatureName, + }); + + // Insert the loading.gif before making the fetch request + let resultSection = document.getElementById('resultSection'); + resultSection.innerHTML = ''; + let imgElement = document.createElement('img'); + imgElement.src = 'img/checking.gif'; + imgElement.style.width = '500px'; + imgElement.style.height = '500px'; + imgElement.style.display = 'block'; + imgElement.style.marginLeft = 'auto'; + imgElement.style.marginRight = 'auto'; + resultSection.appendChild(imgElement); + + setTimeout(() => { + fetch(`${window.BASE_URL}/rules/creature?${queryParams}`) + .then((response) => { + if (!response.ok) { + throw response; + } + return response.json(); // we only get here if there is no error + }) + .then((json) => { + if (json === null) { // if json is null update the image and stop execution + imgElement.src = 'img/404.jpg'; + throw new Error('No data found'); // this will stop the execution and go to the catch block + } + resultSection.innerHTML = ''; // Clear previous content + if (creatureName === 'android') { + androidResultTable(json); + } else { + setCreatureTable(json); + } + }) + .catch((err) => { + console.log(err); + if(!resultSection.hasChildNodes()) { // Only update the image if it wasn't already updated + imgElement.src = 'img/404.jpg'; + } + }); + + function setResultImage(name) { + let imgElement = document.createElement('img'); + imgElement.src = 'img/' + name + ".jpg"; + console.log(imgElement.src); + imgElement.onerror = function () { + this.src = 'img/404.jpg'; + }; + resultSection.appendChild(imgElement); + } + + function formatDiceMnemonic(data) { + return Array.isArray(data) ? + data[0] + "d" + data[1] + (data[2] === 0 ? "" : (data[2] > 0 ? "+" : "") + data[2]) : + data.toString(); + } + + function formatSpeedMnemonic(data){ + return data[0] + '/' + data[1] + '/' + data[2]; + } + + function androidResultTable(data) { + let container = document.createElement('div'); + container.className = 'container android'; + + let creatureTitle = document.createElement('h2'); + creatureTitle.textContent = creatureName.toUpperCase(); + container.appendChild(creatureTitle) + + let image = document.createElement('img'); + image.src = 'img/android.jpg'; + image.onerror = () => { image.src = 'img/404.jpg' }; + image.style.width = '275px'; + image.style.height = '275px'; + container.appendChild(image); + + let description = createDescriptionSection(data); + container.appendChild(description); + + Object.entries(data).forEach(([key, value]) => { + + if (key === 'thinker' || key === 'worker' || key === 'warrior') { + let typeTitle = document.createElement('h2'); + typeTitle.textContent = key.toUpperCase(); + container.appendChild(typeTitle); + + // Create a div for each type (thinker, worker, or warrior) + let typeContainer = document.createElement('div'); + typeContainer.className = 'typeContainer'; // we'll use this in css + + let profile = createProfileSection(value); + let abilities = createAbilitiesSection(value); + + // Append profile and abilities to the typeContainer, instead of the main container + typeContainer.appendChild(profile); + typeContainer.appendChild(abilities); + + // Finally, append the typeContainer to the main container. + container.appendChild(typeContainer); + } + }); + + resultSection.appendChild(container); + } + + function createProfileSection(data) { + let profile = document.createElement('div'); + profile.className = 'creature_profile'; + let profileHTML = ` +

Profile

+ + + + + + + `; + if(data['land speed']){ + profileHTML += ``; + } + if(data['water speed']){ + profileHTML += ``; + } + if(data['air speed']){ + profileHTML += ``; + } + profileHTML += '
NUMBER ${formatDiceMnemonic(data.number)}
MORALE ${formatDiceMnemonic(data.morale)}
HIT DICE ${formatDiceMnemonic(data['hit dice'])}
ARMOUR ${data.armour}
ENVIRON ${data.environ.join(', ')}
LAND SPEED ${formatSpeedMnemonic(data['land speed'])}
WATER SPEED ${formatSpeedMnemonic(data['water speed'])}
AIR SPEED ${formatSpeedMnemonic(data['air speed'])}
'; + + profile.innerHTML = profileHTML; + return profile; + } + + function createAbilitiesSection(data) { + // Abilities section + let abilities = document.createElement('div'); + abilities.className = 'creature_abilities'; + abilities.innerHTML = ` +

Abilities

+ + + + + + + +
MS ${formatDiceMnemonic(data.ms)}
IN ${formatDiceMnemonic(data.in)}
DX ${formatDiceMnemonic(data.dx)}
CH ${formatDiceMnemonic(data.ch)}
CN ${formatDiceMnemonic(data.cn)}
PS ${formatDiceMnemonic(data.ps)}
+ `; + return abilities; + } + + function createAttacksSection(data) { + // Attacks section + let attacks = document.createElement('div'); + attacks.className = 'creature_attacks'; + let attacksHTML = '

Attacks

'; + let attacksTable = ''; + for (let attack in data.attacks) { + let mnemonic_display; + mnemonic_display = formatDiceMnemonic(data.attacks[attack]); + if (mnemonic_display === "0d0") { + mnemonic_display = 'See Description'; + } + attacksTable += `` + } + attacksTable += '
${attack}${mnemonic_display}
'; + attacks.innerHTML = attacksHTML + attacksTable; + return attacks + } + + function createMutationsSection(mutarray) { + // Mutations section + let mutations = document.createElement('div'); + mutations.className = 'creature_mutations'; + let mutationsHTML = '

Mutations

'; + let mutationsList = mutarray.reduce((result, mutation) => result + `
  • ${mutation}
  • `, ''); + mutationsHTML += ``; + mutations.innerHTML = mutationsHTML + return mutations + } + + function createDescriptionSection(data) { + // Description section + let description = document.createElement('div'); + description.className = 'creature_description'; + let descriptionHTML = ` +

    Description

    +

    ${data.description}

    + `; + description.innerHTML = descriptionHTML; + return description + } + + function setCreatureTable(data) { + let container = document.createElement('div'); + container.className = 'container'; + + // Creature ID section + let creatureId = document.createElement('div'); + creatureId.className = 'creature_id'; + creatureId.innerHTML = ` +

    ${creatureName.toUpperCase()}

    + + `; + + let profile = createProfileSection(data); + let abilities = createAbilitiesSection(data); + let attacks = createAttacksSection(data); + let mutations = createMutationsSection(data.mutations); // Notice the data difference + let description = createDescriptionSection(data); + + container.appendChild(creatureId); + container.appendChild(profile); + container.appendChild(abilities); + container.appendChild(attacks); + container.appendChild(mutations); + container.appendChild(description); + + resultSection.appendChild(container); + } + }, 1000); +}); \ No newline at end of file diff --git a/web/catalog.js b/web/catalog.js new file mode 100644 index 0000000..0680875 --- /dev/null +++ b/web/catalog.js @@ -0,0 +1,46 @@ +document.getElementById('catalog-button').addEventListener('click', function (event) { + event.preventDefault(); + + // Insert the loading.gif + let resultSection = document.getElementById('resultSection'); + resultSection.innerHTML = ''; + let imgElement = document.createElement('img'); + imgElement.src = 'img/checking.gif'; + imgElement.style.width = '500px'; + imgElement.style.height = '500px'; + imgElement.style.display = 'block'; + imgElement.style.marginLeft = 'auto'; + imgElement.style.marginRight = 'auto'; + resultSection.appendChild(imgElement); + + fetch(`${window.BASE_URL}/rules/creature`) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + resultSection.innerHTML = ''; // Clear results + + // Generate clickable elements for each creature + let table = document.createElement('table'); + data.forEach(item => { + let row = document.createElement('tr'); + let cell = document.createElement('td'); + let btn = document.createElement('button'); + btn.textContent = item; + btn.addEventListener('click', function () { + document.getElementById('creature').value = item; + document.getElementById('search-button').click(); + }); + cell.appendChild(btn); + row.appendChild(cell); + table.appendChild(row); + }); + resultSection.appendChild(table); + }) + .catch(error => { + console.error('There has been a problem with your fetch operation:', error); + }); +}); \ No newline at end of file diff --git a/web/index.html b/web/index.html index d15b0fd..91f181b 100644 --- a/web/index.html +++ b/web/index.html @@ -25,7 +25,8 @@
    - + +

    @@ -35,6 +36,10 @@ document.addEventListener('DOMContentLoaded', function () { loadContent("intro.html"); + document.getElementById("bestiary").addEventListener("click", function () { + loadContent("bestiary.html?t=" + new Date().getTime()); + }); + document.getElementById("abcheck").addEventListener("click", function () { loadContent("ability_check.html?t=" + new Date().getTime()); });