From e23a42fea7732e840db72ea595e883a960527c08 Mon Sep 17 00:00:00 2001 From: Jeff Colombo Date: Sat, 9 Feb 2019 18:21:29 -0500 Subject: [PATCH] parse gamemaster, use POGOProtos --- .gitmodules | 6 + externals/PokeApi | 1 + externals/pokemongo-game-master | 1 + generatePokemonData.ts | 394 ++++++------------ package.json | 8 +- parseGameMaster.ts | 144 +++++++ src/scss/index.scss | 2 +- src/ts/api/AjaxRequest.ts | 237 +++++++++++ src/ts/api/AjaxUtils.ts | 223 ---------- src/ts/api/ApiService.ts | 146 +++++++ src/ts/api/PokemonService.ts | 96 +---- src/ts/app/PokemonApp.tsx | 10 +- .../PokemonExplorer/LeagueStatsList.tsx | 11 +- .../PokemonExplorer/PokemonExplorer.tsx | 21 +- .../PokemonSelectList/PokemonSelectList.tsx | 23 +- .../components/PokemonSelectList/actions.ts | 13 +- .../components/PokemonSelectList/reducers.ts | 2 + .../app/components/PokemonSelectList/types.ts | 7 +- src/ts/app/models/Pokemon.ts | 95 +---- src/ts/app/utils/formatter.ts | 74 ++++ src/ts/common/xhr2/index.d.ts | 3 + tsconfig.json | 12 +- tslint.json | 2 +- webpack.config.js | 1 + yarn.lock | 108 ++++- 25 files changed, 951 insertions(+), 689 deletions(-) create mode 100644 .gitmodules create mode 160000 externals/PokeApi create mode 160000 externals/pokemongo-game-master create mode 100644 parseGameMaster.ts create mode 100644 src/ts/api/AjaxRequest.ts delete mode 100644 src/ts/api/AjaxUtils.ts create mode 100644 src/ts/api/ApiService.ts create mode 100644 src/ts/common/xhr2/index.d.ts diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1dc5197 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "externals/pokemongo-game-master"] + path = externals/pokemongo-game-master + url = https://github.com/pokemongo-dev-contrib/pokemongo-game-master.git +[submodule "externals/PokeApi"] + path = externals/PokeApi + url = https://github.com/PokeAPI/pokeapi.git diff --git a/externals/PokeApi b/externals/PokeApi new file mode 160000 index 0000000..739de49 --- /dev/null +++ b/externals/PokeApi @@ -0,0 +1 @@ +Subproject commit 739de49807b0c307a54e537b43fbf6ccda55fbbc diff --git a/externals/pokemongo-game-master b/externals/pokemongo-game-master new file mode 160000 index 0000000..ddcf523 --- /dev/null +++ b/externals/pokemongo-game-master @@ -0,0 +1 @@ +Subproject commit ddcf5231b560aed7bf611590923e53e925ee60d3 diff --git a/generatePokemonData.ts b/generatePokemonData.ts index 98eeed8..ff88e5c 100644 --- a/generatePokemonData.ts +++ b/generatePokemonData.ts @@ -1,10 +1,11 @@ -import * as fs from 'fs'; -import PokemonDescription from 'pokemongo-json-pokedex/output/locales/en-US/pokemon.json'; -import Pokemon from 'pokemongo-json-pokedex/output/pokemon.json'; +import fs from 'fs'; +import POGOProtos from 'pogo-protos'; -import { ILeaguePokemon, IMaxCpByLeague, League, MaxCpByLeague } from 'app/models/League'; +import { parseGameMaster } from './parseGameMaster'; + +import { ILeaguePokemon, League, MaxCpByLeague } from 'app/models/League'; import { LevelMultipliers } from 'app/models/LevelMultipliers'; -import { Grade, IBaseStatsRank, IMaxStats, IPokemon, IStats, PokemonId, Type } from 'app/models/Pokemon'; +import { Grade, IMaxStats, IStats } from 'app/models/Pokemon'; type ICpAndTotalFound = Record>; interface IStatsDistribution { @@ -13,10 +14,6 @@ interface IStatsDistribution { master : ICpAndTotalFound; custom : ICpAndTotalFound; } -interface ICalculateRelativeStats { - id : string; - value : number; -} const outPath = './dist/db/'; @@ -26,10 +23,6 @@ const maxPossibleStats : IMaxStats = { baseDefense: 0, level: 40, }; -const pokemonOrderById : Record = {}; -const pokemonBaseStamina : Array = []; -const pokemonBaseAttack : Array = []; -const pokemonBaseDefense : Array = []; const getClosestCpMultiplierIndex = (value : number) => { let i; @@ -41,274 +34,147 @@ const getClosestCpMultiplierIndex = (value : number) => { return Math.max(i - 1, 0); }; -const familyOrder : Array = []; -const familyEvolutionOrder : Record> = {}; -const familyEncountered : Record> = {}; -const parseNameAndForm = (monId : string, monName : string) => { - if (monId.indexOf('_ALOLA') > -1 || - monId.indexOf('CASTFORM_') > -1 || - monId.indexOf('DEOXYS_') > -1 || - monId.indexOf('WORMADAM_') > -1 || - monId.indexOf('BURMY_') > -1 || - monId.indexOf('CHERRIM_') > -1 || - monId.indexOf('SHELLOS_') > -1 || - monId.indexOf('GASTRODON_') > -1 || - monId.indexOf('ROTOM_') > -1 || - monId.indexOf('GIRATINA_') > -1 || - monId.indexOf('SHAYMIN_') > -1 || - monId.indexOf('ARCEUS_') > -1 - ) { - const formTokenIndex = monId.indexOf('_'); - return { - name: monName.substr(0, formTokenIndex), - form: monId.substr(formTokenIndex + 1), +fs.mkdirSync(outPath, { recursive: true }); + +(async () => { + const Pokemon = await parseGameMaster(); + + Pokemon.forEach((mon) => { + const baseHp = mon.stats.baseStamina; + const baseAtk = mon.stats.baseAttack; + const baseDef = mon.stats.baseDefense; + const pokemon : ILeaguePokemon = { + ...mon, + pvp: { + great: [], + ultra: [], + master: [], + custom: [], + }, }; - } - return { - name: monName, - form: null, - }; -}; -Pokemon.forEach((mon) => { - maxPossibleStats.baseStamina = Math.max(mon.stats.baseStamina, maxPossibleStats.baseStamina); - maxPossibleStats.baseAttack = Math.max(mon.stats.baseAttack, maxPossibleStats.baseAttack); - maxPossibleStats.baseDefense = Math.max(mon.stats.baseDefense, maxPossibleStats.baseDefense); - pokemonBaseStamina.push({ - id: mon.id, - value: mon.stats.baseStamina, - }); - pokemonBaseAttack.push({ - id: mon.id, - value: mon.stats.baseAttack, - }); - pokemonBaseDefense.push({ - id: mon.id, - value: mon.stats.baseDefense, - }); - pokemonOrderById[mon.id] = { - staminaRank: -1, - attackRank: -1, - defenseRank: -1, - }; -}); + maxPossibleStats.baseStamina = Math.max(baseHp, maxPossibleStats.baseStamina); + maxPossibleStats.baseAttack = Math.max(baseAtk, maxPossibleStats.baseAttack); + maxPossibleStats.baseDefense = Math.max(baseDef, maxPossibleStats.baseDefense); -pokemonBaseStamina.sort((a, b) => { - return a.value - b.value; -}); -pokemonBaseStamina.forEach((stats, index, array) => { - pokemonOrderById[stats.id].staminaRank = Math.floor((index / (array.length - 1)) * 100); -}); -pokemonBaseAttack.sort((a, b) => { - return a.value - b.value; -}); -pokemonBaseAttack.forEach((stats, index, array) => { - pokemonOrderById[stats.id].attackRank = Math.floor((index / (array.length - 1)) * 100); -}); -pokemonBaseDefense.sort((a, b) => { - return a.value - b.value; -}); -pokemonBaseDefense.forEach((stats, index, array) => { - pokemonOrderById[stats.id].defenseRank = Math.floor((index / (array.length - 1)) * 100); -}); + // calculate stats for all possible IVs + const combinedStatsDistribution : IStatsDistribution = { + great: {}, + ultra: {}, + master: {}, + custom: {}, + }; + for (let ivHp = 15; ivHp >= 0; ivHp--) { + for (let ivAtk = 15; ivAtk >= 0; ivAtk--) { + for (let ivDef = 15; ivDef >= 0; ivDef--) { + let pokemonWithIvs : IStats; + const cpMultiplier = (baseAtk + ivAtk) * Math.sqrt(baseDef + ivDef) * Math.sqrt(baseHp + ivHp); -Pokemon.forEach((mon) => { - const { name, form } = parseNameAndForm(mon.id, mon.name); - const baseAtk = mon.stats.baseAttack; - const baseDef = mon.stats.baseDefense; - const baseHp = mon.stats.baseStamina; - const pokemonDescription = typeof PokemonDescription[mon.id as PokemonId] !== 'undefined' ? PokemonDescription[mon.id as PokemonId] : {}; - const pokemon : ILeaguePokemon = { - id: mon.id as PokemonId, - name: pokemonDescription.name || name || 'MissingNo.', - category: pokemonDescription.category || '', - form, - dex: mon.dex, - types: { - type1: mon.types[0].name.toLowerCase() as Type, - type2: mon.types[1] ? mon.types[1].name.toLowerCase() as Type : null, - }, - stats: mon.stats, - statsRank: pokemonOrderById[mon.id], - family: mon.family.id, - pvp: { - great: [], - ultra: [], - master: [], - custom: [], - }, - }; + Object.keys(MaxCpByLeague).forEach((key) => { + const league = key as League; + const maxCp = MaxCpByLeague[league]; + const maxLeagueLevelMultiplierIndex = getClosestCpMultiplierIndex(Math.sqrt((maxCp * 10) / cpMultiplier)); + const maxLeagueLevelMultiplier = LevelMultipliers[maxLeagueLevelMultiplierIndex]; + const maxLeagueCp = Math.floor((cpMultiplier * Math.pow(maxLeagueLevelMultiplier, 2)) / 10); + const maxLeagueLevel = (maxLeagueLevelMultiplierIndex + 2) / 2; + pokemonWithIvs = { + cp: maxLeagueCp, + level: maxLeagueLevel, + ivHp, + ivAtk, + ivDef, + hp: Math.floor((baseHp + ivHp) * maxLeagueLevelMultiplier), + atk: Math.floor((baseAtk + ivAtk) * maxLeagueLevelMultiplier), + def: Math.floor((baseDef + ivDef) * maxLeagueLevelMultiplier), + total: 0, + speciesGrade: Grade.F, + metaGrade: Grade.F, + }; + pokemonWithIvs.total = pokemonWithIvs.hp + pokemonWithIvs.atk + pokemonWithIvs.def; - // keep track of family order and membership - if (typeof familyEncountered[pokemon.family] === 'undefined') { - familyOrder.push(pokemon.family); - familyEncountered[pokemon.family] = []; - } - familyEncountered[pokemon.family].push(pokemon); - - if (typeof mon.evolution.pastBranch === 'undefined' && - typeof familyEvolutionOrder[pokemon.family] === 'undefined' && - pokemon.id.indexOf('_ALOLA') === -1 // because RAICHU_ALOLA shows up before PICHU, but PICHU is the family origin - ) { - familyEvolutionOrder[pokemon.family] = []; - if (mon.forms.length > 0) { - mon.forms.forEach((monForm) => { - familyEvolutionOrder[pokemon.family].push(monForm.id); - }); - } else { - familyEvolutionOrder[pokemon.family].push(pokemon.id); - } - - // TODO: if `mon.forms.length > 0`, there's a chance the order will get weird by doing this: - if (typeof mon.evolution.futureBranches !== 'undefined') { - (function traverseEvolutionBranches(root) { - root.forEach((evolution) => { - familyEvolutionOrder[pokemon.family].push(evolution.id); - // unfortunate workaround for typescript limitation in JSON parsing being TOO good... - // if (typeof evolution !== 'undefined') { - if ('futureBranches' in evolution) { - // traverseEvolutionBranches(evolution.futureBranches); - traverseEvolutionBranches(evolution['futureBranches']); - } - }); - })(mon.evolution.futureBranches); - } - } - - // calculate stats for all possible IVs - const combinedStatsDistribution : IStatsDistribution = { - great: {}, - ultra: {}, - master: {}, - custom: {}, - }; - for (let ivHp = 15; ivHp >= 0; ivHp--) { - for (let ivAtk = 15; ivAtk >= 0; ivAtk--) { - for (let ivDef = 15; ivDef >= 0; ivDef--) { - let pokemonWithIvs : IStats; - const cpMultiplier = (baseAtk + ivAtk) * Math.sqrt(baseDef + ivDef) * Math.sqrt(baseHp + ivHp); - - Object.keys(MaxCpByLeague).forEach((key) => { - const league = key as League; - const maxCp = MaxCpByLeague[league]; - const maxLeagueLevelMultiplierIndex = getClosestCpMultiplierIndex(Math.sqrt((maxCp * 10) / cpMultiplier)); - const maxLeagueLevelMultiplier = LevelMultipliers[maxLeagueLevelMultiplierIndex]; - const maxLeagueCp = Math.floor((cpMultiplier * Math.pow(maxLeagueLevelMultiplier, 2)) / 10); - const maxLeagueLevel = (maxLeagueLevelMultiplierIndex + 2) / 2; - pokemonWithIvs = { - cp: maxLeagueCp, - level: maxLeagueLevel, - ivHp, - ivAtk, - ivDef, - hp: Math.floor((baseHp + ivHp) * maxLeagueLevelMultiplier), - atk: Math.floor((baseAtk + ivAtk) * maxLeagueLevelMultiplier), - def: Math.floor((baseDef + ivDef) * maxLeagueLevelMultiplier), - total: 0, - speciesGrade: Grade.F, - metaGrade: Grade.F, - }; - pokemonWithIvs.total = pokemonWithIvs.hp + pokemonWithIvs.atk + pokemonWithIvs.def; - - const combinedStats = maxLeagueCp + pokemonWithIvs.total; - combinedStatsDistribution[league][combinedStats] = combinedStatsDistribution[league][combinedStats] || []; - combinedStatsDistribution[league][combinedStats].push(pokemonWithIvs); - }); + const combinedStats = maxLeagueCp + pokemonWithIvs.total; + combinedStatsDistribution[league][combinedStats] = combinedStatsDistribution[league][combinedStats] || []; + combinedStatsDistribution[league][combinedStats].push(pokemonWithIvs); + // console.log(pokemonWithIvs, key); + }); + } } } - } - // process the pokemon stats for league-worthiness - Object.keys(pokemon.pvp).forEach((key) => { - const league = key as League; + // process the pokemon stats for league-worthiness + Object.keys(pokemon.pvp).forEach((key) => { + const league = key as League; - const orderedCombinedStats = Object.keys(combinedStatsDistribution[league]).map((cpTotal) => parseInt(cpTotal, 10)); - orderedCombinedStats.sort((a, b) => a - b); + const orderedCombinedStats = Object.keys(combinedStatsDistribution[league]).map((cpTotal) => parseInt(cpTotal, 10)); + orderedCombinedStats.sort((a, b) => a - b); - const len = orderedCombinedStats.length - 1; - const offset = orderedCombinedStats[1]; - const max = orderedCombinedStats[len] - offset; // index 0 is always `Grade.S` - for (let index = len; index >= 0; index--) { - const combinedStats = orderedCombinedStats[index]; - const percent = (combinedStats - offset) / max; + const len = orderedCombinedStats.length - 1; + const offset = orderedCombinedStats[1]; + const max = orderedCombinedStats[len] - offset; // index 0 is always `Grade.S` + for (let index = len; index >= 0; index--) { + const combinedStats = orderedCombinedStats[index]; + const percent = (combinedStats - offset) / max; + // remove all `Grade.F` stats (to save space in the DB) + if (percent < 0.6) { + delete combinedStatsDistribution[league][combinedStats]; + continue; + } - // remove all `Grade.F` stats (to save space in the DB) - if (percent < 0.6) { - delete combinedStatsDistribution[league][combinedStats]; - continue; - } - - combinedStatsDistribution[league][combinedStats].forEach((pokemonStats) => { - if (index === len) { - pokemonStats.speciesGrade = Grade.S; - } else { - if (percent >= 0.9) { - pokemonStats.speciesGrade = Grade.A; - } else if (percent >= 0.8) { - pokemonStats.speciesGrade = Grade.B; - } else if (percent >= 0.7) { - pokemonStats.speciesGrade = Grade.C; - } else if (percent >= 0.6) { - pokemonStats.speciesGrade = Grade.D; + combinedStatsDistribution[league][combinedStats].forEach((pokemonStats) => { + if (index === len) { + pokemonStats.speciesGrade = Grade.S; + } else { + if (percent >= 0.9) { + pokemonStats.speciesGrade = Grade.A; + } else if (percent >= 0.8) { + pokemonStats.speciesGrade = Grade.B; + } else if (percent >= 0.7) { + pokemonStats.speciesGrade = Grade.C; + } else if (percent >= 0.6) { + pokemonStats.speciesGrade = Grade.D; + } } - } - pokemon.pvp[league].push(pokemonStats); - }); - combinedStatsDistribution[league][combinedStats].sort((a, b) => { - if (a.total !== b.total) { - return a.total < b.total ? -1 : 1; - } - if (a.level !== b.level) { - return a.level < b.level ? -1 : 1; - } - return 0; - }); - } - }); + pokemon.pvp[league].push(pokemonStats); + }); + combinedStatsDistribution[league][combinedStats].sort((a, b) => { + if (a.total !== b.total) { + return a.total < b.total ? -1 : 1; + } + if (a.level !== b.level) { + return a.level < b.level ? -1 : 1; + } + return 0; + }); + } + }); - fs.mkdir(outPath, { recursive: true }, () => { - fs.writeFile(outPath + mon.id + '.json', JSON.stringify(pokemon), (err) => { - if (err) { + try { + const filename = mon.form === POGOProtos.Enums.Form.FORM_UNSET ? POGOProtos.Enums.PokemonId[mon.id] : POGOProtos.Enums.Form[mon.form]; + fs.writeFileSync(outPath + filename + '.json', JSON.stringify(pokemon)); + } catch (error) { + if (error) { /* tslint:disable-next-line:no-console */ - return console.error(mon.name, err); + return console.error(pokemon.name, error); } - }); - }); -}); - -const pokemonOrder : Array = []; -familyOrder.forEach((familyId) => { - familyEvolutionOrder[familyId].forEach((id, order) => { - familyEncountered[familyId].some((pokemon, index) => { - if (id === pokemon.id && order !== index) { - familyEncountered[familyId].splice(order, 0, familyEncountered[familyId].splice(index, 1)[0]); - return true; - } - return false; - }); - }); - - familyEncountered[familyId].forEach((leaguePokemon) => { - const { pvp, ...pokemon } = leaguePokemon; - pokemonOrder.push(pokemon); - }); -}); - -fs.mkdir(outPath, { recursive: true }, () => { - fs.writeFile(outPath + 'order.json', JSON.stringify(pokemonOrder), (err) => { - if (err) { - /* tslint:disable-next-line:no-console */ - return console.error('order', err); } }); -}); -// TODO: add moves -fs.mkdir(outPath, { recursive: true }, () => { - fs.writeFile(outPath + 'config.json', JSON.stringify({ maxPossibleStats }), (err) => { - if (err) { + try { + fs.writeFileSync(outPath + 'order.json', JSON.stringify(Pokemon)); + } catch (error) { + if (error) { /* tslint:disable-next-line:no-console */ - return console.error('order', err); + return console.error('order', error); } - }); -}); + } + + try { + // TODO: add moves + fs.writeFileSync(outPath + 'config.json', JSON.stringify({ maxPossibleStats })); + } catch (error) { + if (error) { + /* tslint:disable-next-line:no-console */ + return console.error('config', error); + } + } +})(); diff --git a/package.json b/package.json index 8b4c54c..ff04d4c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "watch": "yarn build -- --config webpack.config.js --colors --debug --output-pathinfo --progress --watch", "build": "node ./node_modules/webpack/bin/webpack.js --cache=true --display-error-details --profile", "clean": "rm -rf ./dist/*", - "tsnode": "./node_modules/.bin/ts-node -r tsconfig-paths/register" + "tsnode": "node -r ts-node/register -r tsconfig-paths/register" }, "devDependencies": { "@babel/core": "^7.2.2", @@ -30,12 +30,15 @@ "babel-plugin-transform-builtin-extend": "^1.1.2", "css-loader": "^2.1.0", "css-modules-typescript-loader": "^1.1.1", + "csvtojson": "^2.0.8", "file-loader": "^3.0.1", "fs": "^0.0.1-security", "glob": "^7.1.3", "mini-css-extract-plugin": "^0.5.0", "node-sass": "^4.11.0", "path": "^0.12.7", + "pogo-protos": "^2.31.1", + "pokemongo-game-master": "^1.0.4", "sass-loader": "^7.1.0", "source-map-concat": "^1.0.1", "source-map-dummy": "^1.0.0", @@ -71,6 +74,7 @@ "react-window": "^1.5.0", "redux": "^4.0.1", "redux-thunk": "^2.3.0", - "typesafe-actions": "^3.0.0" + "typesafe-actions": "^3.0.0", + "xhr2": "^0.1.4" } } diff --git a/parseGameMaster.ts b/parseGameMaster.ts new file mode 100644 index 0000000..9606154 --- /dev/null +++ b/parseGameMaster.ts @@ -0,0 +1,144 @@ +import fs from 'fs'; +import POGOProtos from 'pogo-protos'; + +import { ApiService } from 'api/ApiService'; + +import { IBaseStatsRank, IPokemon } from 'app/models/Pokemon'; + +interface ICalculateRelativeStats { + id : POGOProtos.Enums.PokemonId; + form : POGOProtos.Enums.Form; + value : number; +} + +export const parseGameMaster = async () => { + const apiService = new ApiService(); + // const pokemonForms : { [ key in keyof typeof POGOProtos.Enums.PokemonId ]? : Array } = {}; + const pokemonData : { [ key in keyof typeof POGOProtos.Enums.PokemonId ]? : Array } = {}; + + const pokemonBaseStamina : Array = []; + const pokemonBaseAttack : Array = []; + const pokemonBaseDefense : Array = []; + const pokemonOrderById : Record = {}; + + const GameMasterProto = fs.readFileSync('externals/pokemongo-game-master/versions/latest/GAME_MASTER.protobuf'); + const GameMaster = POGOProtos.Networking.Responses.DownloadItemTemplatesResponse.decode(GameMasterProto); + // fs.writeFileSync('gamemaster.json', JSON.stringify(decoded, null, 2)); + + const version = GameMaster.timestamp_ms; + + for (const entry of GameMaster.item_templates) { + if (!entry.template_id) { + continue; + } + + // ignore *_NORMAL entries + if (entry.template_id.match(/^V(\d{4})_POKEMON_(\w+)(?, form) => { + // if (form.form) { + // output.push(form.form); + // } + // return output; + // }, []); + // } else if (pokemonId) { + // pokemonForms[pokemonId] = [POGOProtos.Enums.Form.FORM_UNSET]; + // } + } + } + + // const orderedPokemon = Object.values(pokemonData).sort((a, b) => { + const orderedPokemon = (Object.values(pokemonData) as unknown as Array>).sort((a, b) => { + return a[0].order - b[0].order; + }); + orderedPokemon.forEach((value) => { + value.sort((a, b) => { + return a.form - b.form; + }); + }); + + let flatOrderedPokemon : Array = []; + flatOrderedPokemon = flatOrderedPokemon.concat(...orderedPokemon); + + // calculate pokemon relative rankings + + pokemonBaseStamina.sort((a, b) => { + return a.value - b.value; + }); + pokemonBaseStamina.forEach((stats, index, array) => { + const key = POGOProtos.Enums.PokemonId[stats.id] + POGOProtos.Enums.Form[stats.form]; + pokemonOrderById[key].staminaRank = Math.floor((index / (array.length - 1)) * 100); + }); + pokemonBaseAttack.sort((a, b) => { + return a.value - b.value; + }); + pokemonBaseAttack.forEach((stats, index, array) => { + const key = POGOProtos.Enums.PokemonId[stats.id] + POGOProtos.Enums.Form[stats.form]; + pokemonOrderById[key].attackRank = Math.floor((index / (array.length - 1)) * 100); + }); + pokemonBaseDefense.sort((a, b) => { + return a.value - b.value; + }); + pokemonBaseDefense.forEach((stats, index, array) => { + const key = POGOProtos.Enums.PokemonId[stats.id] + POGOProtos.Enums.Form[stats.form]; + pokemonOrderById[key].defenseRank = Math.floor((index / (array.length - 1)) * 100); + }); + flatOrderedPokemon.forEach((mon) => { + const key = POGOProtos.Enums.PokemonId[mon.id] + POGOProtos.Enums.Form[mon.form]; + mon.statsRank = { ...pokemonOrderById[key] }; + }); + + return flatOrderedPokemon; +}; diff --git a/src/scss/index.scss b/src/scss/index.scss index 068ab77..cbcddd6 100644 --- a/src/scss/index.scss +++ b/src/scss/index.scss @@ -91,7 +91,7 @@ a.list-item { } .nes-container::after, -.nes-container.is-rounded::after, { +.nes-container.is-rounded::after { border-color: $main-border-color; } diff --git a/src/ts/api/AjaxRequest.ts b/src/ts/api/AjaxRequest.ts new file mode 100644 index 0000000..3d682ba --- /dev/null +++ b/src/ts/api/AjaxRequest.ts @@ -0,0 +1,237 @@ +export interface IRestApiRejection { + message : string; + status : number; + xhr : XMLHttpRequest; + error : Error | null; + type : string | null; + content : any | null; +} + +interface IAjaxRequest { + ajaxGet(url : string, queryObject? : any) : Promise; + ajaxPost(url : string, postBody : any, queryObject? : any, type? : 'text' | 'json') : Promise; + ajaxPostForm(url : string, formData? : FormData, queryObject? : any) : Promise; + ajaxPut(url : string, postBody : any, queryObject? : any) : Promise; + ajaxDelete(url : string, queryObject? : any) : Promise; +} + +export class AjaxRequest implements IAjaxRequest { + private XMLHttpRequest : typeof XMLHttpRequest; + + constructor(xmlHttpRequest : typeof XMLHttpRequest) { + this.XMLHttpRequest = xmlHttpRequest; + } + + public ajaxGet(url : string, queryObject? : any) : Promise { + return new Promise((resolve, reject) => { + const xhr = new this.XMLHttpRequest(); + const cacheBustingQueryObject = (queryObject || {}); // IE 11 caches GET requests + cacheBustingQueryObject._cache_busting_arg_ = +new Date(); // this parameter name must be in the backend's excluded parameter set (see api_utils.py) + + xhr.open('GET', url + this.buildQueryString(cacheBustingQueryObject)); + xhr.withCredentials = true; + xhr.onload = () => { + if (xhr.readyState === 4 && xhr.status === 200) { + try { + return resolve(JSON.parse(xhr.response)); + } catch (e) { + const rejection : IRestApiRejection = { + message: e.message, + status: xhr.status, + xhr, + error: e, + type: null, + content: null, + }; + // tslint:disable-next-line:no-console + console.error('Failure to parse response', xhr); + return reject(rejection); + } + } else { + return this.handleXhrOnLoadCaughtException(reject, xhr); + } + }; + xhr.onerror = (e) => { + return this.handleXhrOnLoadCaughtException(reject, xhr); + }; + xhr.send(); + }); + } + + public ajaxPost(url : string, postBody : any, queryObject? : any, type? : 'text' | 'json') : Promise { + return new Promise((resolve, reject) => { + const xhr = new this.XMLHttpRequest(); + xhr.open('POST', url + this.buildQueryString(queryObject)); + xhr.withCredentials = true; + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.setRequestHeader('X-CSRFToken', (window as any).CSRF_TOKEN); + xhr.onload = () => { + if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201)) { + if (type === 'text') { + return resolve(xhr.response); + } + try { + return resolve(JSON.parse(xhr.response)); + } catch (e) { + const rejection : IRestApiRejection = { + message: e.message, + status: xhr.status, + xhr, + error: e, + type: null, + content: null, + }; + // tslint:disable-next-line:no-console + console.error('Failure to parse response', xhr); + return reject(rejection); + } + } else { + return this.handleXhrOnLoadCaughtException(reject, xhr); + } + }; + xhr.onerror = (e) => { + return this.handleXhrOnLoadCaughtException(reject, xhr); + }; + xhr.send(JSON.stringify(postBody || undefined)); + }); + } + + public ajaxPostForm(url : string, formData? : FormData, queryObject? : any) : Promise { + // Lesman 4/24/2017 + // NOTE: you do not set the Content-Type on this request, + // the browser will automatically encode the form data and + // set the appropriate Content-Type. The encoding is browser + // specific so you MUST allow the browser to set the header + return new Promise((resolve, reject) => { + const xhr = new this.XMLHttpRequest(); + xhr.open('POST', url + this.buildQueryString(queryObject)); + xhr.withCredentials = true; + xhr.setRequestHeader('X-CSRFToken', (window as any).CSRF_TOKEN); + xhr.onload = () => this.handleXhrOnLoad(resolve, reject, xhr); + xhr.onerror = (e) => { + return this.handleXhrOnLoadCaughtException(reject, xhr); + }; + xhr.send(formData); + }); + } + + public ajaxPut(url : string, postBody : any, queryObject? : any) : Promise { + return new Promise((resolve, reject) => { + const xhr = new this.XMLHttpRequest(); + xhr.open('PUT', url + this.buildQueryString(queryObject)); + xhr.withCredentials = true; + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.setRequestHeader('X-CSRFToken', (window as any).CSRF_TOKEN); + xhr.onload = () => this.handleXhrOnLoad(resolve, reject, xhr); + xhr.onerror = (e) => { + return this.handleXhrOnLoadCaughtException(reject, xhr); + }; + xhr.send(JSON.stringify(postBody || undefined)); + }); + } + + public ajaxDelete(url : string, queryObject? : any) : Promise { + return new Promise((resolve, reject) => { + const xhr = new this.XMLHttpRequest(); + xhr.open('DELETE', url + this.buildQueryString(queryObject)); + xhr.withCredentials = true; + xhr.setRequestHeader('X-CSRFToken', (window as any).CSRF_TOKEN); + xhr.onload = () => this.handleXhrOnLoad(resolve, reject, xhr); + xhr.onerror = (e) => { + return this.handleXhrOnLoadCaughtException(reject, xhr); + }; + xhr.send(); + }); + } + + private buildQueryString(queryObject : any) { + const queryParameters : Array = []; + let queryString = ''; + Object.keys(queryObject || {}).forEach((key : string) => { + queryParameters.push(key + '=' + encodeURIComponent(queryObject[key])); + }); + if (queryParameters.length > 0) { + queryString = '?' + queryParameters.join('&'); + } + return queryString; + } + + private handleXhrOnLoad(resolve : (value : any) => void, reject : (reason : any) => void, xhr : XMLHttpRequest) : void { + if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 204)) { + if (xhr.responseType === '' && xhr.responseText === '') { + return resolve(undefined); + } + if (xhr.responseType === 'text') { + return resolve(xhr.response); + } + try { + const responseJSON = JSON.parse(xhr.response); + + if (typeof(responseJSON.success) !== 'undefined' && !responseJSON.success) { + if (responseJSON.error === 'user_required') { + window.location.reload(); + } + } + + return resolve(responseJSON); + } catch (e) { + const rejection : IRestApiRejection = { + message: e.message, + status: xhr.status, + xhr, + error: e, + type: null, + content: null, + }; + // tslint:disable-next-line:no-console + console.error('Failed to parse response', xhr); + return reject(rejection); + } + } else { + return this.handleXhrOnLoadCaughtException(reject, xhr); + } + } + + private handleXhrOnLoadCaughtException(reject : (reason : any) => void, xhr : XMLHttpRequest) : void { + if (xhr.status === 401) { + window.location.reload(); + } + + if (xhr.responseType === 'text' || xhr.responseType === '') { + const rejection : IRestApiRejection = { + message: xhr.responseText || xhr.statusText, + status: xhr.status, + xhr, + error: null, + type: null, + content: null, + }; + return reject(rejection); + } + try { + const responseJSON = JSON.parse(xhr.response); + + const rejection : IRestApiRejection = { + message: responseJSON.message || xhr.statusText, + status: xhr.status, + xhr, + error: null, + type: responseJSON.exception_type, + content: responseJSON.content, + }; + return reject(rejection); + } catch (e) { + const rejection : IRestApiRejection = { + message: e.message, + status: xhr.status, + xhr, + error: e, + type: null, + content: null, + }; + // tslint:disable-next-line:no-console + console.error('Failed to parse response', xhr); + return reject(rejection); + } + } +} diff --git a/src/ts/api/AjaxUtils.ts b/src/ts/api/AjaxUtils.ts deleted file mode 100644 index cfaf652..0000000 --- a/src/ts/api/AjaxUtils.ts +++ /dev/null @@ -1,223 +0,0 @@ -export interface IRestApiRejection { - message : string; - status : number; - xhr : XMLHttpRequest; - error : Error | null; - type : string | null; - content : any | null; -} - -const buildQueryString = (queryObject : any) => { - const queryParameters : Array = []; - let queryString = ''; - Object.keys(queryObject || {}).forEach((key : string) => { - queryParameters.push(key + '=' + encodeURIComponent(queryObject[key])); - }); - if (queryParameters.length > 0) { - queryString = '?' + queryParameters.join('&'); - } - return queryString; -}; - -const handleXhrOnLoad = (resolve : (value : any) => void, reject : (reason : any) => void, xhr : XMLHttpRequest) : void => { - if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 204)) { - if (xhr.responseType === '' && xhr.responseText === '') { - return resolve(undefined); - } - if (xhr.responseType === 'text') { - return resolve(xhr.response); - } - try { - const responseJSON = JSON.parse(xhr.response); - - if (typeof(responseJSON.success) !== 'undefined' && !responseJSON.success) { - if (responseJSON.error === 'user_required') { - window.location.reload(); - } - } - - return resolve(responseJSON); - } catch (e) { - const rejection : IRestApiRejection = { - message: e.message, - status: xhr.status, - xhr, - error: e, - type: null, - content: null, - }; - // tslint:disable-next-line:no-console - console.error('Failed to parse response', xhr); - return reject(rejection); - } - } else { - return handleXhrOnLoadCaughtException(reject, xhr); - } -}; - -const handleXhrOnLoadCaughtException = (reject : (reason : any) => void, xhr : XMLHttpRequest) : void => { - if (xhr.status === 401) { - window.location.reload(); - } - - if (xhr.responseType === 'text' || xhr.responseType === '') { - const rejection : IRestApiRejection = { - message: xhr.responseText || xhr.statusText, - status: xhr.status, - xhr, - error: null, - type: null, - content: null, - }; - return reject(rejection); - } - try { - const responseJSON = JSON.parse(xhr.response); - - const rejection : IRestApiRejection = { - message: responseJSON.message || xhr.statusText, - status: xhr.status, - xhr, - error: null, - type: responseJSON.exception_type, - content: responseJSON.content, - }; - return reject(rejection); - } catch (e) { - const rejection : IRestApiRejection = { - message: e.message, - status: xhr.status, - xhr, - error: e, - type: null, - content: null, - }; - // tslint:disable-next-line:no-console - console.error('Failed to parse response', xhr); - return reject(rejection); - } -}; - -export const AjaxUtils = { - ajaxGet(url : string, queryObject? : any) : Promise { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - const cacheBustingQueryObject = (queryObject || {}); // IE 11 caches GET requests - cacheBustingQueryObject._cache_busting_arg_ = +new Date(); // this parameter name must be in the backend's excluded parameter set (see api_utils.py) - - xhr.open('GET', url + buildQueryString(cacheBustingQueryObject)); - xhr.withCredentials = true; - xhr.onload = () => { - if (xhr.readyState === 4 && xhr.status === 200) { - try { - return resolve(JSON.parse(xhr.response)); - } catch (e) { - const rejection : IRestApiRejection = { - message: e.message, - status: xhr.status, - xhr, - error: e, - type: null, - content: null, - }; - // tslint:disable-next-line:no-console - console.error('Failure to parse response', xhr); - return reject(rejection); - } - } else { - return handleXhrOnLoadCaughtException(reject, xhr); - } - }; - xhr.onerror = (e) => { - return handleXhrOnLoadCaughtException(reject, xhr); - }; - xhr.send(); - }); - }, - - ajaxPost(url : string, postBody : any, queryObject? : any, type? : 'text' | 'json') : Promise { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('POST', url + buildQueryString(queryObject)); - xhr.withCredentials = true; - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.setRequestHeader('X-CSRFToken', (window as any).CSRF_TOKEN); - xhr.onload = () => { - if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201)) { - if (type === 'text') { - return resolve(xhr.response); - } - try { - return resolve(JSON.parse(xhr.response)); - } catch (e) { - const rejection : IRestApiRejection = { - message: e.message, - status: xhr.status, - xhr, - error: e, - type: null, - content: null, - }; - // tslint:disable-next-line:no-console - console.error('Failure to parse response', xhr); - return reject(rejection); - } - } else { - return handleXhrOnLoadCaughtException(reject, xhr); - } - }; - xhr.onerror = (e) => { - return handleXhrOnLoadCaughtException(reject, xhr); - }; - xhr.send(JSON.stringify(postBody || undefined)); - }); - }, - - ajaxPostForm(url : string, formData? : FormData, queryObject? : any) : Promise { - // Lesman 4/24/2017 - // NOTE: you do not set the Content-Type on this request, - // the browser will automatically encode the form data and - // set the appropriate Content-Type. The encoding is browser - // specific so you MUST allow the browser to set the header - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('POST', url + buildQueryString(queryObject)); - xhr.withCredentials = true; - xhr.setRequestHeader('X-CSRFToken', (window as any).CSRF_TOKEN); - xhr.onload = () => handleXhrOnLoad(resolve, reject, xhr); - xhr.onerror = (e) => { - return handleXhrOnLoadCaughtException(reject, xhr); - }; - xhr.send(formData); - }); - }, - - ajaxPut(url : string, postBody : any, queryObject? : any) : Promise { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('PUT', url + buildQueryString(queryObject)); - xhr.withCredentials = true; - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.setRequestHeader('X-CSRFToken', (window as any).CSRF_TOKEN); - xhr.onload = () => handleXhrOnLoad(resolve, reject, xhr); - xhr.onerror = (e) => { - return handleXhrOnLoadCaughtException(reject, xhr); - }; - xhr.send(JSON.stringify(postBody || undefined)); - }); - }, - - ajaxDelete(url : string, queryObject? : any) : Promise { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('DELETE', url + buildQueryString(queryObject)); - xhr.withCredentials = true; - xhr.setRequestHeader('X-CSRFToken', (window as any).CSRF_TOKEN); - xhr.onload = () => handleXhrOnLoad(resolve, reject, xhr); - xhr.onerror = (e) => { - return handleXhrOnLoadCaughtException(reject, xhr); - }; - xhr.send(); - }); - }, -}; diff --git a/src/ts/api/ApiService.ts b/src/ts/api/ApiService.ts new file mode 100644 index 0000000..81327d7 --- /dev/null +++ b/src/ts/api/ApiService.ts @@ -0,0 +1,146 @@ +import csv from 'csvtojson'; +import POGOProtos from 'pogo-protos'; +import xhr2 from 'xhr2'; + +import { AjaxRequest } from 'api/AjaxRequest'; + +import { DEFAULT_POKEMON_NAME, IPokemonSpecies } from 'app/models/Pokemon'; + +interface IPokeApiSpeciesNameJSON { + pokemon_species_id : string; + local_language_id : '9' | string; + name : string; + genus : string; +} +interface IPokeApiSpeciesJSON { + id : string; + identifier : string; + order : string; +} + +interface ILivePokeApiLanguage { + name : 'en' | string; +} +interface ILivePokeApiSpeciesJSON { + id : number; + names : Array<{ name : string; language : ILivePokeApiLanguage; }>; + order : number; + flavor_text_entries : Array<{ description : string; language : ILivePokeApiLanguage; }>; + genera : Array<{ genus : string; language : ILivePokeApiLanguage; }>; +} +interface ILivePokeApiSpeciesListJSON { + count : number; + results : Array<{ name : string }>; +} + +interface IApiService { + getLivePokemonSpeciesKeys() : Promise>; + getPokemonSpeciesInfo(pokemonId : POGOProtos.Enums.PokemonId, defaultDex : number, defaultName : string) : Promise; + getLivePokemonSpeciesInfo(dex : number) : Promise; +} + +export class ApiService implements IApiService { + private AjaxRequest : AjaxRequest; + + constructor() { + this.AjaxRequest = new AjaxRequest(xhr2); + } + + public async getPokemonSpeciesInfo(pokemonId : POGOProtos.Enums.PokemonId, defaultDex : number, defaultName : string) { + const formattedName = this.formatPokemonIdToPokeApiNameKey(pokemonId); + + const speciesArray : Array = await csv().fromFile('externals/PokeApi/data/v2/csv/pokemon_species.csv'); + + let dex = defaultDex; + let order = defaultDex; + for (const entry of speciesArray) { + if (entry.identifier === formattedName) { + dex = parseInt(entry.id, 10); + order = parseInt(entry.order, 10); + break; + } + } + + const namesArray : Array = await csv().fromFile('externals/PokeApi/data/v2/csv/pokemon_species_names.csv'); + + let name = defaultName; + let genus = ''; + for (const entry of namesArray) { + if (parseInt(entry.pokemon_species_id, 10) === dex && entry.local_language_id === '9') { + name = entry.name; + genus = entry.genus; + break; + } + } + + const serializedResponse : IPokemonSpecies = { + name, + dex, + order, + genus, + }; + return serializedResponse; + } + + public async getLivePokemonSpeciesKeys() { + const queryParameters = { + offset: 0, + limit: 9999, // 807 is current max from PokeApi + }; + + // https://pokeapi.co/docs/v2.html#pokemon-species + const response : ILivePokeApiSpeciesListJSON = await this.AjaxRequest.ajaxGet(`https://pokeapi.co/api/v2/pokemon-species/`, queryParameters); + + return response.results.map((listItem) => { + return listItem.name; + }); + } + + public async getLivePokemonSpeciesInfo(pokemonId : POGOProtos.Enums.PokemonId) { + const formattedName = this.formatPokemonIdToPokeApiNameKey(pokemonId); + + const queryParameters = { + }; + + // https://pokeapi.co/docs/v2.html#pokemon-species + const response : ILivePokeApiSpeciesJSON = await this.AjaxRequest.ajaxGet(`https://pokeapi.co/api/v2/pokemon-species/${formattedName}/`, queryParameters); + + let name = DEFAULT_POKEMON_NAME; + response.names.some((entry) => { + if (entry.language.name === 'en') { + name = entry.name; + return true; + } + return false; + }); + + let genus = ''; + response.genera.some((entry) => { + if (entry.language.name === 'en') { + genus = entry.genus; + return true; + } + return false; + }); + + const serializedResponse : IPokemonSpecies = { + name, + dex: response.id, + order: response.order, + genus, + }; + return serializedResponse; + } + + private formatPokemonIdToPokeApiNameKey(pokemonId : POGOProtos.Enums.PokemonId) { + let key : string; + if (pokemonId === POGOProtos.Enums.PokemonId.NIDORAN_MALE) { + key = 'nidoran-m'; + } else if (pokemonId === POGOProtos.Enums.PokemonId.NIDORAN_FEMALE) { + key = 'nidoran-f'; + } else { + key = POGOProtos.Enums.PokemonId[pokemonId]; + } + return key.toLowerCase().replace(/_/, '-'); + } +} diff --git a/src/ts/api/PokemonService.ts b/src/ts/api/PokemonService.ts index 6131318..18cc37c 100644 --- a/src/ts/api/PokemonService.ts +++ b/src/ts/api/PokemonService.ts @@ -1,87 +1,29 @@ -import { AjaxUtils } from 'api/AjaxUtils'; +import POGOProtos from 'pogo-protos'; + +import { AjaxRequest } from 'api/AjaxRequest'; import { IConfig } from 'app/models/Config'; -import { ILeaguePokemon, League } from 'app/models/League'; +import { ILeaguePokemon } from 'app/models/League'; import { IPokemon } from 'app/models/Pokemon'; -interface IPokemonJSON extends IPokemon {} -interface ILeaguePokemonJSON extends ILeaguePokemon {} -interface IPokemonService {} +interface IPokemonService { + getConfig() : Promise; + getPokemonList() : Promise>; + getPokemonLeagueStats(pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) : Promise; +} export class PokemonService implements IPokemonService { - private static serializePokemonList(jsonPokemonList : Array) : Array { - const pokemonList = jsonPokemonList.reduce((result : Array, pokemonJson) => { - try { - if (typeof pokemonJson.name !== 'string') { - throw new Error('pokemon missing name'); - } - if (typeof pokemonJson.id !== 'string') { - throw new Error('pokemon missing id'); - } - if (typeof pokemonJson.family !== 'string') { - throw new Error('pokemon missing family'); - } - if (typeof pokemonJson.dex !== 'number') { - throw new Error('pokemon missing dex'); - } - if (typeof pokemonJson.stats !== 'object') { - throw new Error('pokemon missing stats'); - } - const pokemon : IPokemon = { ...pokemonJson }; - result.push(pokemon); - } catch (e) { - /* tslint:disable-next-line:no-console */ - console.error(pokemonJson, e.message); - } - return result; - }, []); - return pokemonList; - } + private AjaxRequest : AjaxRequest; - private static serializePokemonLeagueStats(jsonPokemonLeagueStats : ILeaguePokemonJSON) : ILeaguePokemon { - let pokemonLeagueStats : ILeaguePokemon; - try { - if (typeof jsonPokemonLeagueStats.name !== 'string') { - throw new Error('pokemon league stats missing name'); - } - if (typeof jsonPokemonLeagueStats.id !== 'string') { - throw new Error('pokemon league stats missing id'); - } - if (typeof jsonPokemonLeagueStats.family !== 'string') { - throw new Error('pokemon league stats missing family'); - } - if (typeof jsonPokemonLeagueStats.dex !== 'number') { - throw new Error('pokemon league stats missing dex'); - } - if (typeof jsonPokemonLeagueStats.stats !== 'object') { - throw new Error('pokemon league stats missing stats'); - } - if (typeof jsonPokemonLeagueStats.pvp !== 'object') { - throw new Error('pokemon league stats missing pvp'); - } - pokemonLeagueStats = { ...jsonPokemonLeagueStats }; - Object.keys(pokemonLeagueStats.pvp).forEach((key) => { - const league = key as League; - if (!Array.isArray(pokemonLeagueStats.pvp[league])) { - throw new Error('pokemon league not an array'); - } - pokemonLeagueStats.pvp[league] = [ ...jsonPokemonLeagueStats.pvp[league] ]; - }); - } catch (e) { - /* tslint:disable-next-line:no-console */ - console.error(jsonPokemonLeagueStats, e.message); - throw e; - } - - return pokemonLeagueStats; + constructor() { + this.AjaxRequest = new AjaxRequest(XMLHttpRequest); } public async getConfig() { const queryParameters = { }; - const response : IConfig = await AjaxUtils.ajaxGet('/dist/db/config.json', queryParameters); - // TODO: serialize this + const response : IConfig = await this.AjaxRequest.ajaxGet('/dist/db/config.json', queryParameters); return response; } @@ -89,15 +31,17 @@ export class PokemonService implements IPokemonService { const queryParameters = { }; - const response : Array = await AjaxUtils.ajaxGet('/dist/db/order.json', queryParameters); - return PokemonService.serializePokemonList(response); + const response : Array = await this.AjaxRequest.ajaxGet('/dist/db/order.json', queryParameters); + return response; } - public async getPokemonLeagueStats(pokemonId : string) { + public async getPokemonLeagueStats(pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) { + const fileName = form === POGOProtos.Enums.Form.FORM_UNSET ? POGOProtos.Enums.PokemonId[pokemonId] : POGOProtos.Enums.Form[form]; + const queryParameters = { }; - const response : ILeaguePokemonJSON = await AjaxUtils.ajaxGet(`/dist/db/${ pokemonId }.json`, queryParameters); - return PokemonService.serializePokemonLeagueStats(response); + const response : ILeaguePokemon = await this.AjaxRequest.ajaxGet(`/dist/db/${ fileName }.json`, queryParameters); + return response; } } diff --git a/src/ts/app/PokemonApp.tsx b/src/ts/app/PokemonApp.tsx index d039f89..d7ad958 100644 --- a/src/ts/app/PokemonApp.tsx +++ b/src/ts/app/PokemonApp.tsx @@ -1,3 +1,5 @@ +import POGOProtos from 'pogo-protos'; + import React from 'react'; import { connect } from 'react-redux'; @@ -40,6 +42,7 @@ class PokemonApp extends React.Component { public render() { const { activePokemonId, + activePokemonForm, pokemonList, pokemonListFiltered, filterTerm, @@ -55,6 +58,7 @@ class PokemonApp extends React.Component { { ); } - private readonly handleActivatePokemon = (pokemonId : string) => { + private readonly handleActivatePokemon = (pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) => { const { dispatch } = this.props; - dispatch(ActionsPokemonSelectList.fetchPokemonLeagueStats(pokemonId)) + dispatch(ActionsPokemonSelectList.fetchPokemonLeagueStats(pokemonId, form)) .then((leaguePokemon) => { - dispatch(ActionsPokemonSelectList.setActivePokemonId(pokemonId)); + dispatch(ActionsPokemonSelectList.setActivePokemonId(pokemonId, form)); dispatch(ActionsPokemonExplorer.setIvLevel(null)); dispatch(ActionsPokemonExplorer.setIvHp(null)); dispatch(ActionsPokemonExplorer.setIvAtk(null)); diff --git a/src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx b/src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx index aa89523..58b17d3 100644 --- a/src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx +++ b/src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx @@ -1,16 +1,19 @@ +import POGOProtos from 'pogo-protos'; + import React from 'react'; import { ContentRect, default as Measure } from 'react-measure'; import { FixedSizeList } from 'react-window'; import classNames from 'classnames'; -import { Grade, IStats, PokemonId } from 'app/models/Pokemon'; +import { Grade, IStats } from 'app/models/Pokemon'; import { IIndividualValues } from './types'; import * as styles from './styles/LeagueStatsList.scss'; export interface ILeagueStatsListProps { - activePokemonId : PokemonId; + activePokemonId : POGOProtos.Enums.PokemonId; + activePokemonForm : POGOProtos.Enums.Form | null; activeIndividualValues : IIndividualValues; leagueStatsList : Array; @@ -49,7 +52,7 @@ export class LeagueStatsList extends React.Component= 0; i--) { + for (let i = length - value.length; i > 0; i--) { output += String.fromCharCode(160); } return output; diff --git a/src/ts/app/components/PokemonExplorer/PokemonExplorer.tsx b/src/ts/app/components/PokemonExplorer/PokemonExplorer.tsx index 4234e4c..ca354fb 100644 --- a/src/ts/app/components/PokemonExplorer/PokemonExplorer.tsx +++ b/src/ts/app/components/PokemonExplorer/PokemonExplorer.tsx @@ -1,3 +1,5 @@ +import POGOProtos from 'pogo-protos'; + import React from 'react'; import classNames from 'classnames'; @@ -5,7 +7,7 @@ import classNames from 'classnames'; import { ILeaguePokemon, League } from 'app/models/League'; import { Grade, IStats, } from 'app/models/Pokemon'; import { calculateCp, calculateStatAtLevel } from 'app/utils/calculator'; -import { formatDexNumber } from 'app/utils/formatter'; +import { alolanForms, formatDexNumber, formatForm, formatType } from 'app/utils/formatter'; import { IIndividualValues, IndividualValueKey } from './types'; @@ -188,18 +190,14 @@ export class PokemonExplorer extends React.Component -1 }, ); - let type1 : JSX.Element | null = null; - if (leaguePokemon.types.type1) { - type1 =
{ leaguePokemon.types.type1 }
; - } - + const type1 : JSX.Element =
{ formatType(leaguePokemon.types.type1) }
; let type2 : JSX.Element | null = null; if (leaguePokemon.types.type2) { - type2 =
{ leaguePokemon.types.type2 }
; + type2 =
{ formatType(leaguePokemon.types.type2) }
; } return ( @@ -209,8 +207,8 @@ export class PokemonExplorer extends React.Component

No.{ dex }

- { leaguePokemon.form && -
{ leaguePokemon.form.toLowerCase().replace('_', ' ') } Form
+ { leaguePokemon.form !== POGOProtos.Enums.Form.FORM_UNSET && +
{ formatForm(leaguePokemon.form) } Form
}
{ type1 } @@ -219,7 +217,7 @@ export class PokemonExplorer extends React.Component

{ leaguePokemon.name }

-
{ leaguePokemon.category }
+
{ leaguePokemon.genus }

Base Stats

; filterTerm : string; - handleActivatePokemon : (pokemonId : string) => void; + handleActivatePokemon : (pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) => void; handleChangeFilter : (filterTerm : string) => Promise; } @@ -121,7 +124,7 @@ export class PokemonSelectList extends React.Component -

MissingNo.

+

{ DEFAULT_POKEMON_NAME }

}
@@ -134,7 +137,7 @@ export class PokemonSelectList extends React.Component { - return this.props.pokemonList[index].form !== null ? 40 : 25; + return this.props.pokemonList[index].form === POGOProtos.Enums.Form.FORM_UNSET ? 25 : 40; } private rowFactory({ index, style } : IRowFactory) { @@ -143,7 +146,7 @@ export class PokemonSelectList extends React.Component this.props.handleActivatePokemon(pokemon.id); + const onClick = () => this.props.handleActivatePokemon(pokemon.id, pokemon.form); return ( { pokemon.name } #{ dex } - { pokemon.form && - { pokemon.form.toLowerCase().replace('_', ' ') } Form + { pokemon.form !== POGOProtos.Enums.Form.FORM_UNSET && + { formatForm(pokemon.form) } Form } ); diff --git a/src/ts/app/components/PokemonSelectList/actions.ts b/src/ts/app/components/PokemonSelectList/actions.ts index fd6b0a9..6a0585e 100644 --- a/src/ts/app/components/PokemonSelectList/actions.ts +++ b/src/ts/app/components/PokemonSelectList/actions.ts @@ -1,3 +1,5 @@ +import POGOProtos from 'pogo-protos'; + import { action } from 'typesafe-actions'; import { ILeaguePokemon } from 'app/models/League'; @@ -12,9 +14,9 @@ export const setPokemonList = (pokemonList : Array) => action(PokemonS export const setPokemonListFiltered = (filterTerm : string, pokemonListFiltered : Array) => action(PokemonSelectListActionTypes.SET_POKEMON_LIST_FILTERED, { filterTerm, pokemonListFiltered }); -export const setActivePokemonId = (activePokemonId : string | null) => action(PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_ID, { activePokemonId }); +export const setActivePokemonId = (activePokemonId : POGOProtos.Enums.PokemonId | null, activePokemonForm : POGOProtos.Enums.Form | null) => action(PokemonSelectListActionTypes.SET_ACTIVE_POKEMON_ID, { activePokemonId, activePokemonForm }); -export const setPokemonLeagueStats = (pokemonId : string, pokemonLeagueStats : ILeaguePokemon) => action(PokemonSelectListActionTypes.SET_POKEMON_LEAGUE_STATS, { pokemonId, pokemonLeagueStats }); +export const setPokemonLeagueStats = (pokemonId : POGOProtos.Enums.PokemonId, pokemonLeagueStats : ILeaguePokemon) => action(PokemonSelectListActionTypes.SET_POKEMON_LEAGUE_STATS, { pokemonId, pokemonLeagueStats }); export const filterPokemonList = ( filterTerm : string @@ -27,7 +29,7 @@ export const filterPokemonList = ( pokemonListFiltered = pokemonList.reduce((result : Array, pokemon) => { const pokemonName = pokemon.name.toLowerCase(); const pokemonDex = '' + pokemon.dex; - const pokemonForm = (pokemon.form || '').toLowerCase(); + const pokemonForm = pokemon.form === null ? '' : POGOProtos.Enums.Form[pokemon.form].toLowerCase(); if (pokemonName.indexOf(normalizedFilterTerm) === 0 || pokemonDex.indexOf(normalizedFilterTerm) === 0 || normalizedFilterTerm === pokemonForm @@ -50,10 +52,11 @@ export const fetchPokemonList = ( }; export const fetchPokemonLeagueStats = ( - pokemonId : string + pokemonId : POGOProtos.Enums.PokemonId, + form : POGOProtos.Enums.Form ) : ThunkResult> => { return async (dispatch, getState, extraArguments) => { - const pokemonLeagueStats = await extraArguments.services.pokemonService.getPokemonLeagueStats(pokemonId); + const pokemonLeagueStats = await extraArguments.services.pokemonService.getPokemonLeagueStats(pokemonId, form); dispatch(setPokemonLeagueStats(pokemonId, pokemonLeagueStats)); return pokemonLeagueStats; }; diff --git a/src/ts/app/components/PokemonSelectList/reducers.ts b/src/ts/app/components/PokemonSelectList/reducers.ts index 86ded1e..d8ea6ba 100644 --- a/src/ts/app/components/PokemonSelectList/reducers.ts +++ b/src/ts/app/components/PokemonSelectList/reducers.ts @@ -6,6 +6,7 @@ import { IPokemonSelectListState, PokemonSelectListActionTypes } from './types'; export const initialState : IPokemonSelectListState = { isLoading: true, activePokemonId: null, + activePokemonForm: null, pokemonList: [], pokemonListFiltered: [], filterTerm: '', @@ -43,6 +44,7 @@ const reduceSetActivePokemonId = ( ) : IPokemonSelectListState => ({ ...state, activePokemonId: action.payload.activePokemonId, + activePokemonForm: action.payload.activePokemonForm, }); const reduceSetPokemonLeagueStats = ( diff --git a/src/ts/app/components/PokemonSelectList/types.ts b/src/ts/app/components/PokemonSelectList/types.ts index 37f2430..af57426 100644 --- a/src/ts/app/components/PokemonSelectList/types.ts +++ b/src/ts/app/components/PokemonSelectList/types.ts @@ -1,13 +1,16 @@ +import POGOProtos from 'pogo-protos'; + import { ILeaguePokemon } from 'app/models/League'; import { IPokemon } from 'app/models/Pokemon'; export interface IPokemonSelectListState { isLoading : boolean; - activePokemonId : string | null; + activePokemonId : POGOProtos.Enums.PokemonId | null; + activePokemonForm : POGOProtos.Enums.Form | null; pokemonList : Array; pokemonListFiltered : Array; filterTerm : string; - pokemonLeagueStats : { [id : string] : ILeaguePokemon }; + pokemonLeagueStats : { [id in keyof typeof POGOProtos.Enums.PokemonId]? : ILeaguePokemon }; } export const PokemonSelectListActionTypes = { diff --git a/src/ts/app/models/Pokemon.ts b/src/ts/app/models/Pokemon.ts index 4c7f5b8..eddc68a 100644 --- a/src/ts/app/models/Pokemon.ts +++ b/src/ts/app/models/Pokemon.ts @@ -1,3 +1,7 @@ +import POGOProtos from 'pogo-protos'; + +export const DEFAULT_POKEMON_NAME = 'MissingNo.'; + export enum Grade { 'S', 'A', @@ -23,17 +27,20 @@ export interface IMaxStats extends IBaseStats { level : number; } -export type Type = 'bug' | 'dark' | 'dragon' | 'electric' | 'fairy' | 'fighting' | 'fire' | 'flying' | 'ghost' | 'grass' | 'ground' | 'ice' | 'normal' | 'poison' | 'psychic' | 'rock' | 'steel' | 'water'; -export interface IPokemon { - id : PokemonId; +export interface IPokemonSpecies { name : string; - category : string; - form : string | null; - family : string; dex : number; + order : number; + genus : string; +} + +export interface IPokemon extends IPokemonSpecies { + id : POGOProtos.Enums.PokemonId; + form : POGOProtos.Enums.Form; + family : POGOProtos.Enums.PokemonFamilyId; types : { - type1 : Type; - type2 : Type | null; + type1 : POGOProtos.Enums.PokemonType; + type2 : POGOProtos.Enums.PokemonType | null; }; stats : IBaseStats; statsRank : IBaseStatsRank; @@ -52,75 +59,3 @@ export interface IStats { speciesGrade : Grade; metaGrade : Grade; } - -export type PokemonId = 'BULBASAUR' | 'IVYSAUR' | 'VENUSAUR' | 'CHARMANDER' | -'CHARMELEON' | 'CHARIZARD' | 'SQUIRTLE' | 'WARTORTLE' | 'BLASTOISE' | -'CATERPIE' | 'METAPOD' | 'BUTTERFREE' | 'WEEDLE' | 'KAKUNA' | 'BEEDRILL' | -'PIDGEY' | 'PIDGEOTTO' | 'PIDGEOT' | 'RATTATA' | 'RATTATA_ALOLA' | 'RATICATE' | -'RATICATE_ALOLA' | 'SPEAROW' | 'FEAROW' | 'EKANS' | 'ARBOK' | 'PIKACHU' | -'RAICHU' | 'RAICHU_ALOLA' | 'SANDSHREW' | 'SANDSHREW_ALOLA' | 'SANDSLASH' | -'SANDSLASH_ALOLA' | 'NIDORAN_FEMALE' | 'NIDORINA' | 'NIDOQUEEN' | -'NIDORAN_MALE' | 'NIDORINO' | 'NIDOKING' | 'CLEFAIRY' | 'CLEFABLE' | 'VULPIX' | -'VULPIX_ALOLA' | 'NINETALES' | 'NINETALES_ALOLA' | 'JIGGLYPUFF' | -'WIGGLYTUFF' | 'ZUBAT' | 'GOLBAT' | 'ODDISH' | 'GLOOM' | 'VILEPLUME' | -'PARAS' | 'PARASECT' | 'VENONAT' | 'VENOMOTH' | 'DIGLETT' | 'DIGLETT_ALOLA' | -'DUGTRIO' | 'DUGTRIO_ALOLA' | 'MEOWTH' | 'MEOWTH_ALOLA' | 'PERSIAN' | -'PERSIAN_ALOLA' | 'PSYDUCK' | 'GOLDUCK' | 'MANKEY' | 'PRIMEAPE' | 'GROWLITHE' | -'ARCANINE' | 'POLIWAG' | 'POLIWHIRL' | 'POLIWRATH' | 'ABRA' | 'KADABRA' | -'ALAKAZAM' | 'MACHOP' | 'MACHOKE' | 'MACHAMP' | 'BELLSPROUT' | 'WEEPINBELL' | -'VICTREEBEL' | 'TENTACOOL' | 'TENTACRUEL' | 'GEODUDE' | 'GEODUDE_ALOLA' | -'GRAVELER' | 'GRAVELER_ALOLA' | 'GOLEM' | 'GOLEM_ALOLA' | 'PONYTA' | -'RAPIDASH' | 'SLOWPOKE' | 'SLOWBRO' | 'MAGNEMITE' | 'MAGNETON' | 'FARFETCHD' | -'DODUO' | 'DODRIO' | 'SEEL' | 'DEWGONG' | 'GRIMER' | 'GRIMER_ALOLA' | 'MUK' | -'MUK_ALOLA' | 'SHELLDER' | 'CLOYSTER' | 'GASTLY' | 'HAUNTER' | 'GENGAR' | -'ONIX' | 'DROWZEE' | 'HYPNO' | 'KRABBY' | 'KINGLER' | 'VOLTORB' | 'ELECTRODE' | -'EXEGGCUTE' | 'EXEGGUTOR' | 'EXEGGUTOR_ALOLA' | 'CUBONE' | 'MAROWAK' | -'MAROWAK_ALOLA' | 'HITMONLEE' | 'HITMONCHAN' | 'LICKITUNG' | 'KOFFING' | -'WEEZING' | 'RHYHORN' | 'RHYDON' | 'CHANSEY' | 'TANGELA' | 'KANGASKHAN' | -'HORSEA' | 'SEADRA' | 'GOLDEEN' | 'SEAKING' | 'STARYU' | 'STARMIE' | -'MR_MIME' | 'SCYTHER' | 'JYNX' | 'ELECTABUZZ' | 'MAGMAR' | 'PINSIR' | -'TAUROS' | 'MAGIKARP' | 'GYARADOS' | 'LAPRAS' | 'DITTO' | 'EEVEE' | -'VAPOREON' | 'JOLTEON' | 'FLAREON' | 'PORYGON' | 'OMANYTE' | 'OMASTAR' | -'KABUTO' | 'KABUTOPS' | 'AERODACTYL' | 'SNORLAX' | 'ARTICUNO' | 'ZAPDOS' | -'MOLTRES' | 'DRATINI' | 'DRAGONAIR' | 'DRAGONITE' | 'MEWTWO' | 'MEW' | -'CHIKORITA' | 'BAYLEEF' | 'MEGANIUM' | 'CYNDAQUIL' | 'QUILAVA' | 'TYPHLOSION' | -'TOTODILE' | 'CROCONAW' | 'FERALIGATR' | 'SENTRET' | 'FURRET' | 'HOOTHOOT' | -'NOCTOWL' | 'LEDYBA' | 'LEDIAN' | 'SPINARAK' | 'ARIADOS' | 'CROBAT' | -'CHINCHOU' | 'LANTURN' | 'PICHU' | 'CLEFFA' | 'IGGLYBUFF' | 'TOGEPI' | -'TOGETIC' | 'NATU' | 'XATU' | 'MAREEP' | 'FLAAFFY' | 'AMPHAROS' | 'BELLOSSOM' | -'MARILL' | 'AZUMARILL' | 'SUDOWOODO' | 'POLITOED' | 'HOPPIP' | 'SKIPLOOM' | -'JUMPLUFF' | 'AIPOM' | 'SUNKERN' | 'SUNFLORA' | 'YANMA' | 'WOOPER' | -'QUAGSIRE' | 'ESPEON' | 'UMBREON' | 'MURKROW' | 'SLOWKING' | 'MISDREAVUS' | -'UNOWN' | 'WOBBUFFET' | 'GIRAFARIG' | 'PINECO' | 'FORRETRESS' | 'DUNSPARCE' | -'GLIGAR' | 'STEELIX' | 'SNUBBULL' | 'GRANBULL' | 'QWILFISH' | 'SCIZOR' | -'SHUCKLE' | 'HERACROSS' | 'SNEASEL' | 'TEDDIURSA' | 'URSARING' | 'SLUGMA' | -'MAGCARGO' | 'SWINUB' | 'PILOSWINE' | 'CORSOLA' | 'REMORAID' | 'OCTILLERY' | -'DELIBIRD' | 'MANTINE' | 'SKARMORY' | 'HOUNDOUR' | 'HOUNDOOM' | 'KINGDRA' | -'PHANPY' | 'DONPHAN' | 'PORYGON2' | 'STANTLER' | 'SMEARGLE' | 'TYROGUE' | -'HITMONTOP' | 'SMOOCHUM' | 'ELEKID' | 'MAGBY' | 'MILTANK' | 'BLISSEY' | -'RAIKOU' | 'ENTEI' | 'SUICUNE' | 'LARVITAR' | 'PUPITAR' | 'TYRANITAR' | -'LUGIA' | 'HO_OH' | 'CELEBI' | 'TREECKO' | 'GROVYLE' | 'SCEPTILE' | 'TORCHIC' | -'COMBUSKEN' | 'BLAZIKEN' | 'MUDKIP' | 'MARSHTOMP' | 'SWAMPERT' | -'POOCHYENA' | 'MIGHTYENA' | 'ZIGZAGOON' | 'LINOONE' | 'WURMPLE' | 'SILCOON' | -'BEAUTIFLY' | 'CASCOON' | 'DUSTOX' | 'LOTAD' | 'LOMBRE' | 'LUDICOLO' | -'SEEDOT' | 'NUZLEAF' | 'SHIFTRY' | 'TAILLOW' | 'SWELLOW' | 'WINGULL' | -'PELIPPER' | 'RALTS' | 'KIRLIA' | 'GARDEVOIR' | 'SURSKIT' | 'MASQUERAIN' | -'SHROOMISH' | 'BRELOOM' | 'SLAKOTH' | 'VIGOROTH' | 'SLAKING' | 'NINCADA' | -'NINJASK' | 'SHEDINJA' | 'WHISMUR' | 'LOUDRED' | 'EXPLOUD' | 'MAKUHITA' | -'HARIYAMA' | 'AZURILL' | 'NOSEPASS' | 'SKITTY' | 'DELCATTY' | 'SABLEYE' | -'MAWILE' | 'ARON' | 'LAIRON' | 'AGGRON' | 'MEDITITE' | 'MEDICHAM' | -'ELECTRIKE' | 'MANECTRIC' | 'PLUSLE' | 'MINUN' | 'VOLBEAT' | 'ILLUMISE' | -'ROSELIA' | 'GULPIN' | 'SWALOT' | 'CARVANHA' | 'SHARPEDO' | 'WAILMER' | -'WAILORD' | 'NUMEL' | 'CAMERUPT' | 'TORKOAL' | 'SPOINK' | 'GRUMPIG' | -'SPINDA' | 'TRAPINCH' | 'VIBRAVA' | 'FLYGON' | 'CACNEA' | 'CACTURNE' | -'SWABLU' | 'ALTARIA' | 'ZANGOOSE' | 'SEVIPER' | 'LUNATONE' | 'SOLROCK' | -'BARBOACH' | 'WHISCASH' | 'CORPHISH' | 'CRAWDAUNT' | 'BALTOY' | 'CLAYDOL' | -'LILEEP' | 'CRADILY' | 'ANORITH' | 'ARMALDO' | 'FEEBAS' | 'MILOTIC' | -'CASTFORM' | 'CASTFORM_RAINY' | 'CASTFORM_SNOWY' | 'CASTFORM_SUNNY' | -'KECLEON' | 'SHUPPET' | 'BANETTE' | 'DUSKULL' | 'DUSCLOPS' | 'TROPIUS' | -'CHIMECHO' | 'ABSOL' | 'WYNAUT' | 'SNORUNT' | 'GLALIE' | 'SPHEAL' | 'SEALEO' | -'WALREIN' | 'CLAMPERL' | 'HUNTAIL' | 'GOREBYSS' | 'RELICANTH' | 'LUVDISC' | -'BAGON' | 'SHELGON' | 'SALAMENCE' | 'BELDUM' | 'METANG' | 'METAGROSS' | -'REGIROCK' | 'REGICE' | 'REGISTEEL' | 'LATIAS' | 'LATIOS' | 'KYOGRE' | -'GROUDON' | 'RAYQUAZA' | 'JIRACHI' | 'DEOXYS' | 'DEOXYS_ATTACK' | -'DEOXYS_DEFENSE' | 'DEOXYS_SPEED'; diff --git a/src/ts/app/utils/formatter.ts b/src/ts/app/utils/formatter.ts index 5da5f91..4b3b6b3 100644 --- a/src/ts/app/utils/formatter.ts +++ b/src/ts/app/utils/formatter.ts @@ -1,3 +1,5 @@ +import POGOProtos from 'pogo-protos'; + export const formatDexNumber = (dex : number) => { let prefix : string = ''; if (dex < 100) { @@ -8,3 +10,75 @@ export const formatDexNumber = (dex : number) => { } return prefix + dex; }; + +export const formatType = (type : POGOProtos.Enums.PokemonType) => { + switch (type) { + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_NORMAL: + return 'normal'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_FIGHTING: + return 'fighting'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_FLYING: + return 'flying'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_POISON: + return 'poison'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_GROUND: + return 'ground'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_ROCK: + return 'rock'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_BUG: + return 'bug'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_GHOST: + return 'ghost'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_STEEL: + return 'steel'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_FIRE: + return 'fire'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_WATER: + return 'water'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_GRASS: + return 'grass'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_ELECTRIC: + return 'electric'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_PSYCHIC: + return 'psychic'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_ICE: + return 'ice'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_DRAGON: + return 'dragon'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_DARK: + return 'dark'; + case POGOProtos.Enums.PokemonType.POKEMON_TYPE_FAIRY: + return 'fairy'; + default: + return ''; + } +}; + +export const alolanForms = [ + POGOProtos.Enums.Form.RATTATA_ALOLA, + POGOProtos.Enums.Form.RATICATE_ALOLA, + POGOProtos.Enums.Form.RAICHU_ALOLA, + POGOProtos.Enums.Form.SANDSHREW_ALOLA, + POGOProtos.Enums.Form.SANDSLASH_ALOLA, + POGOProtos.Enums.Form.VULPIX_ALOLA, + POGOProtos.Enums.Form.NINETALES_ALOLA, + POGOProtos.Enums.Form.DIGLETT_ALOLA, + POGOProtos.Enums.Form.DUGTRIO_ALOLA, + POGOProtos.Enums.Form.MEOWTH_ALOLA, + POGOProtos.Enums.Form.PERSIAN_ALOLA, + POGOProtos.Enums.Form.GEODUDE_ALOLA, + POGOProtos.Enums.Form.GRAVELER_ALOLA, + POGOProtos.Enums.Form.GOLEM_ALOLA, + POGOProtos.Enums.Form.GRIMER_ALOLA, + POGOProtos.Enums.Form.MUK_ALOLA, + POGOProtos.Enums.Form.EXEGGUTOR_ALOLA, + POGOProtos.Enums.Form.MAROWAK_ALOLA, +]; + +export const formatForm = (form : POGOProtos.Enums.Form) => { + if (alolanForms.indexOf(form) > -1) { + return 'Alola'; + } else { + return ''; + } +}; diff --git a/src/ts/common/xhr2/index.d.ts b/src/ts/common/xhr2/index.d.ts new file mode 100644 index 0000000..cd5080e --- /dev/null +++ b/src/ts/common/xhr2/index.d.ts @@ -0,0 +1,3 @@ +declare module 'xhr2' { + export = XMLHttpRequest; +} diff --git a/tsconfig.json b/tsconfig.json index cee3d39..b164630 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "outDir": "dist", "sourceMap": true, "module": "commonjs", + "lib": ["es2017", "dom"], "target": "es6", "jsx": "react", "baseUrl": "./", @@ -17,19 +18,18 @@ "api/*": ["src/ts/api/*"], "app/*": ["src/ts/app/*"], "common/*": ["src/ts/common/*"], - "styles/*": ["src/scss/*"] + "externals/*": ["externals/*"], + "styles/*": ["src/scss/*"], + "xhr2": ["src/ts/common/xhr2"], }, "plugins": [{ "name": "tslint-language-service", "disableNoUnusedVariableRule": false }], }, - "include": [ - "src/**/*", - "." - ], "exclude": [ "node_modules", - "dist" + "dist", + "externals", ] } diff --git a/tslint.json b/tslint.json index 29312ec..adb754d 100644 --- a/tslint.json +++ b/tslint.json @@ -1,7 +1,7 @@ { "extends": ["tslint:latest", "tslint-react", "tslint-eslint-rules"], "rules": { - "no-implicit-dependencies": [true, ["api", "app", "common", "styles"]], + "no-implicit-dependencies": [true, "dev", ["api", "app", "common", "externals", "styles"]], "no-default-export": true, "no-unused-expression": true, "no-unused-variable": [true, "react"], diff --git a/webpack.config.js b/webpack.config.js index b54871e..ab9a6e3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,6 +12,7 @@ const typescriptResolve = { 'api': path.resolve('./src/ts/api'), 'app': path.resolve('./src/ts/app'), 'common': path.resolve('./src/ts/common'), + 'externals': path.resolve('./externals'), 'styles': path.resolve('./src/scss'), }, extensions: ['.ts', '.tsx', '.js'], diff --git a/yarn.lock b/yarn.lock index d47516c..bb56d1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -726,6 +726,59 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + "@types/classnames@^2.2.7": version "2.2.7" resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.7.tgz#fb68cc9be8487e6ea5b13700e759bfbab7e0fefd" @@ -736,6 +789,16 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/long@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" + integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q== + +"@types/node@^10.1.0": + version "10.12.24" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.24.tgz#b13564af612a22a20b5d95ca40f1bffb3af315cf" + integrity sha512-GWWbvt+z9G5otRBW8rssOFgRY87J9N/qbhqfjMZ+gUuL6zoL+Hm6gP/8qQBG4jjimqdaNLCehcVapZ/Fs2WjCQ== + "@types/node@^10.12.18": version "10.12.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" @@ -3575,6 +3638,11 @@ log-symbols@^2.0.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + longest-streak@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" @@ -4480,6 +4548,20 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" +pogo-protos@^2.31.1: + version "2.31.1" + resolved "https://registry.yarnpkg.com/pogo-protos/-/pogo-protos-2.31.1.tgz#5b389faba0ce2278771f04306af186d2c809a930" + integrity sha512-/iFoUWrewfBXLp6+7jzRCPSygMGVySiH9677q9AZRWK62xmBScVtEKohEtlVixTg7wX3xqGXsE1ZcNSuhk2NTQ== + dependencies: + protobufjs "^6.8.8" + +pokemongo-game-master@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/pokemongo-game-master/-/pokemongo-game-master-1.0.4.tgz#b5edb22f4b317142fb4dea2ab5645f6af4712ccb" + integrity sha512-QyNx47mtVd0y/9FW25Gbrxt9HxwpBpVvdUWk+kljUO/+XXr6n9OVe3yu1RIqgKNG6QBAg+qFgi32kmQ1K+CE9w== + dependencies: + request "^2.83.0" + pokemongo-json-pokedex@^3.4.6: version "3.4.6" resolved "https://registry.yarnpkg.com/pokemongo-json-pokedex/-/pokemongo-json-pokedex-3.4.6.tgz#5d8ad17980cf4088fa573a18224131b65cd5d95b" @@ -4676,6 +4758,25 @@ prop-types@^15.6.2: loose-envify "^1.3.1" object-assign "^4.1.1" +protobufjs@^6.8.8: + version "6.8.8" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" + integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + "@types/node" "^10.1.0" + long "^4.0.0" + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -5093,7 +5194,7 @@ replace-ext@1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= -request@^2.87.0, request@^2.88.0: +request@^2.83.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" dependencies: @@ -6457,6 +6558,11 @@ x-is-string@^0.1.0: resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= +xhr2@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" + integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8= + xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"