parse gamemaster, use POGOProtos

This commit is contained in:
Jeff Colombo 2019-02-09 18:21:29 -05:00
parent 58a17849a4
commit e23a42fea7
25 changed files with 951 additions and 689 deletions

6
.gitmodules vendored Normal file
View File

@ -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

1
externals/PokeApi vendored Submodule

@ -0,0 +1 @@
Subproject commit 739de49807b0c307a54e537b43fbf6ccda55fbbc

1
externals/pokemongo-game-master vendored Submodule

@ -0,0 +1 @@
Subproject commit ddcf5231b560aed7bf611590923e53e925ee60d3

View File

@ -1,10 +1,11 @@
import * as fs from 'fs'; import fs from 'fs';
import PokemonDescription from 'pokemongo-json-pokedex/output/locales/en-US/pokemon.json'; import POGOProtos from 'pogo-protos';
import Pokemon from 'pokemongo-json-pokedex/output/pokemon.json';
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 { 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<number, Array<IStats>>; type ICpAndTotalFound = Record<number, Array<IStats>>;
interface IStatsDistribution { interface IStatsDistribution {
@ -13,10 +14,6 @@ interface IStatsDistribution {
master : ICpAndTotalFound; master : ICpAndTotalFound;
custom : ICpAndTotalFound; custom : ICpAndTotalFound;
} }
interface ICalculateRelativeStats {
id : string;
value : number;
}
const outPath = './dist/db/'; const outPath = './dist/db/';
@ -26,10 +23,6 @@ const maxPossibleStats : IMaxStats = {
baseDefense: 0, baseDefense: 0,
level: 40, level: 40,
}; };
const pokemonOrderById : Record<string, IBaseStatsRank> = {};
const pokemonBaseStamina : Array<ICalculateRelativeStats> = [];
const pokemonBaseAttack : Array<ICalculateRelativeStats> = [];
const pokemonBaseDefense : Array<ICalculateRelativeStats> = [];
const getClosestCpMultiplierIndex = (value : number) => { const getClosestCpMultiplierIndex = (value : number) => {
let i; let i;
@ -41,96 +34,17 @@ const getClosestCpMultiplierIndex = (value : number) => {
return Math.max(i - 1, 0); return Math.max(i - 1, 0);
}; };
const familyOrder : Array<string> = []; fs.mkdirSync(outPath, { recursive: true });
const familyEvolutionOrder : Record<string, Array<string>> = {};
const familyEncountered : Record<string, Array<ILeaguePokemon>> = {};
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),
};
}
return {
name: monName,
form: null,
};
};
Pokemon.forEach((mon) => { (async () => {
maxPossibleStats.baseStamina = Math.max(mon.stats.baseStamina, maxPossibleStats.baseStamina); const Pokemon = await parseGameMaster();
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,
};
});
pokemonBaseStamina.sort((a, b) => { Pokemon.forEach((mon) => {
return a.value - b.value; const baseHp = mon.stats.baseStamina;
});
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);
});
Pokemon.forEach((mon) => {
const { name, form } = parseNameAndForm(mon.id, mon.name);
const baseAtk = mon.stats.baseAttack; const baseAtk = mon.stats.baseAttack;
const baseDef = mon.stats.baseDefense; 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 = { const pokemon : ILeaguePokemon = {
id: mon.id as PokemonId, ...mon,
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: { pvp: {
great: [], great: [],
ultra: [], ultra: [],
@ -139,41 +53,9 @@ Pokemon.forEach((mon) => {
}, },
}; };
// keep track of family order and membership maxPossibleStats.baseStamina = Math.max(baseHp, maxPossibleStats.baseStamina);
if (typeof familyEncountered[pokemon.family] === 'undefined') { maxPossibleStats.baseAttack = Math.max(baseAtk, maxPossibleStats.baseAttack);
familyOrder.push(pokemon.family); maxPossibleStats.baseDefense = Math.max(baseDef, maxPossibleStats.baseDefense);
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 // calculate stats for all possible IVs
const combinedStatsDistribution : IStatsDistribution = { const combinedStatsDistribution : IStatsDistribution = {
@ -213,6 +95,7 @@ Pokemon.forEach((mon) => {
const combinedStats = maxLeagueCp + pokemonWithIvs.total; const combinedStats = maxLeagueCp + pokemonWithIvs.total;
combinedStatsDistribution[league][combinedStats] = combinedStatsDistribution[league][combinedStats] || []; combinedStatsDistribution[league][combinedStats] = combinedStatsDistribution[league][combinedStats] || [];
combinedStatsDistribution[league][combinedStats].push(pokemonWithIvs); combinedStatsDistribution[league][combinedStats].push(pokemonWithIvs);
// console.log(pokemonWithIvs, key);
}); });
} }
} }
@ -231,7 +114,6 @@ Pokemon.forEach((mon) => {
for (let index = len; index >= 0; index--) { for (let index = len; index >= 0; index--) {
const combinedStats = orderedCombinedStats[index]; const combinedStats = orderedCombinedStats[index];
const percent = (combinedStats - offset) / max; const percent = (combinedStats - offset) / max;
// remove all `Grade.F` stats (to save space in the DB) // remove all `Grade.F` stats (to save space in the DB)
if (percent < 0.6) { if (percent < 0.6) {
delete combinedStatsDistribution[league][combinedStats]; delete combinedStatsDistribution[league][combinedStats];
@ -266,49 +148,33 @@ Pokemon.forEach((mon) => {
} }
}); });
fs.mkdir(outPath, { recursive: true }, () => { try {
fs.writeFile(outPath + mon.id + '.json', JSON.stringify(pokemon), (err) => { const filename = mon.form === POGOProtos.Enums.Form.FORM_UNSET ? POGOProtos.Enums.PokemonId[mon.id] : POGOProtos.Enums.Form[mon.form];
if (err) { fs.writeFileSync(outPath + filename + '.json', JSON.stringify(pokemon));
} catch (error) {
if (error) {
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
return console.error(mon.name, err); return console.error(pokemon.name, error);
}
} }
}); });
});
});
const pokemonOrder : Array<IPokemon> = []; try {
familyOrder.forEach((familyId) => { fs.writeFileSync(outPath + 'order.json', JSON.stringify(Pokemon));
familyEvolutionOrder[familyId].forEach((id, order) => { } catch (error) {
familyEncountered[familyId].some((pokemon, index) => { if (error) {
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 */ /* tslint:disable-next-line:no-console */
return console.error('order', err); return console.error('order', error);
}
} }
});
});
// TODO: add moves try {
fs.mkdir(outPath, { recursive: true }, () => { // TODO: add moves
fs.writeFile(outPath + 'config.json', JSON.stringify({ maxPossibleStats }), (err) => { fs.writeFileSync(outPath + 'config.json', JSON.stringify({ maxPossibleStats }));
if (err) { } catch (error) {
if (error) {
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
return console.error('order', err); return console.error('config', error);
} }
}); }
}); })();

View File

@ -8,7 +8,7 @@
"watch": "yarn build -- --config webpack.config.js --colors --debug --output-pathinfo --progress --watch", "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", "build": "node ./node_modules/webpack/bin/webpack.js --cache=true --display-error-details --profile",
"clean": "rm -rf ./dist/*", "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": { "devDependencies": {
"@babel/core": "^7.2.2", "@babel/core": "^7.2.2",
@ -30,12 +30,15 @@
"babel-plugin-transform-builtin-extend": "^1.1.2", "babel-plugin-transform-builtin-extend": "^1.1.2",
"css-loader": "^2.1.0", "css-loader": "^2.1.0",
"css-modules-typescript-loader": "^1.1.1", "css-modules-typescript-loader": "^1.1.1",
"csvtojson": "^2.0.8",
"file-loader": "^3.0.1", "file-loader": "^3.0.1",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"glob": "^7.1.3", "glob": "^7.1.3",
"mini-css-extract-plugin": "^0.5.0", "mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0", "node-sass": "^4.11.0",
"path": "^0.12.7", "path": "^0.12.7",
"pogo-protos": "^2.31.1",
"pokemongo-game-master": "^1.0.4",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"source-map-concat": "^1.0.1", "source-map-concat": "^1.0.1",
"source-map-dummy": "^1.0.0", "source-map-dummy": "^1.0.0",
@ -71,6 +74,7 @@
"react-window": "^1.5.0", "react-window": "^1.5.0",
"redux": "^4.0.1", "redux": "^4.0.1",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"typesafe-actions": "^3.0.0" "typesafe-actions": "^3.0.0",
"xhr2": "^0.1.4"
} }
} }

144
parseGameMaster.ts Normal file
View File

@ -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<POGOProtos.Enums.Form> } = {};
const pokemonData : { [ key in keyof typeof POGOProtos.Enums.PokemonId ]? : Array<IPokemon> } = {};
const pokemonBaseStamina : Array<ICalculateRelativeStats> = [];
const pokemonBaseAttack : Array<ICalculateRelativeStats> = [];
const pokemonBaseDefense : Array<ICalculateRelativeStats> = [];
const pokemonOrderById : Record<string, IBaseStatsRank> = {};
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+)(?<!_NORMAL)$/) && entry.pokemon_settings) {
const pokemonId = entry.pokemon_settings.pokemon_id || POGOProtos.Enums.PokemonId.MISSINGNO;
const dex = parseInt(RegExp.$1, 10);
const speciesInfo = await apiService.getPokemonSpeciesInfo(pokemonId, dex, POGOProtos.Enums.PokemonId[pokemonId]);
pokemonData[pokemonId] = pokemonData[pokemonId] || [];
const mon = {
...speciesInfo,
id: pokemonId,
form: entry.pokemon_settings.form || POGOProtos.Enums.Form.FORM_UNSET,
family: entry.pokemon_settings.family_id || POGOProtos.Enums.PokemonFamilyId.FAMILY_UNSET,
types: {
type1: entry.pokemon_settings.type || POGOProtos.Enums.PokemonType.POKEMON_TYPE_NONE,
type2: entry.pokemon_settings.type_2 || null,
},
stats: {
baseAttack: (entry.pokemon_settings.stats || {}).base_attack || 0,
baseDefense: (entry.pokemon_settings.stats || {}).base_defense || 0,
baseStamina: (entry.pokemon_settings.stats || {}).base_stamina || 0,
},
statsRank: {
attackRank: -1,
defenseRank: -1,
staminaRank: -1,
},
};
pokemonData[pokemonId]!.push(mon);
const key = POGOProtos.Enums.PokemonId[mon.id] + POGOProtos.Enums.Form[mon.form];
pokemonBaseStamina.push({
id: mon.id,
form: mon.form,
value: mon.stats.baseStamina,
});
pokemonBaseAttack.push({
id: mon.id,
form: mon.form,
value: mon.stats.baseAttack,
});
pokemonBaseDefense.push({
id: mon.id,
form: mon.form,
value: mon.stats.baseDefense,
});
pokemonOrderById[key] = {
staminaRank: -1,
attackRank: -1,
defenseRank: -1,
};
// } else if (entry.template_id.indexOf('FORMS_V') === 0 && entry.form_settings) {
// const pokemonId = entry.form_settings.pokemon || POGOProtos.Enums.PokemonId.MISSINGNO;
// if (entry.form_settings.forms) {
// pokemonForms[pokemonId] = entry.form_settings.forms.reduce((output : Array<POGOProtos.Enums.Form>, 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<Array<IPokemon>>).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<IPokemon> = [];
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;
};

View File

@ -91,7 +91,7 @@ a.list-item {
} }
.nes-container::after, .nes-container::after,
.nes-container.is-rounded::after, { .nes-container.is-rounded::after {
border-color: $main-border-color; border-color: $main-border-color;
} }

237
src/ts/api/AjaxRequest.ts Normal file
View File

@ -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<any>;
ajaxPost(url : string, postBody : any, queryObject? : any, type? : 'text' | 'json') : Promise<any>;
ajaxPostForm(url : string, formData? : FormData, queryObject? : any) : Promise<any>;
ajaxPut(url : string, postBody : any, queryObject? : any) : Promise<any>;
ajaxDelete(url : string, queryObject? : any) : Promise<any>;
}
export class AjaxRequest implements IAjaxRequest {
private XMLHttpRequest : typeof XMLHttpRequest;
constructor(xmlHttpRequest : typeof XMLHttpRequest) {
this.XMLHttpRequest = xmlHttpRequest;
}
public ajaxGet(url : string, queryObject? : any) : Promise<any> {
return new Promise<any>((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<any> {
return new Promise<any>((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<any> {
// 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<any>((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<any> {
return new Promise<any>((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<any> {
return new Promise<any>((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<string> = [];
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);
}
}
}

View File

@ -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<string> = [];
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<any> {
return new Promise<any>((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<any> {
return new Promise<any>((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<any> {
// 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<any>((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<any> {
return new Promise<any>((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<any> {
return new Promise<any>((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();
});
},
};

146
src/ts/api/ApiService.ts Normal file
View File

@ -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<Array<string>>;
getPokemonSpeciesInfo(pokemonId : POGOProtos.Enums.PokemonId, defaultDex : number, defaultName : string) : Promise<IPokemonSpecies>;
getLivePokemonSpeciesInfo(dex : number) : Promise<IPokemonSpecies>;
}
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<IPokeApiSpeciesJSON> = 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<IPokeApiSpeciesNameJSON> = 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(/_/, '-');
}
}

View File

@ -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 { IConfig } from 'app/models/Config';
import { ILeaguePokemon, League } from 'app/models/League'; import { ILeaguePokemon } from 'app/models/League';
import { IPokemon } from 'app/models/Pokemon'; import { IPokemon } from 'app/models/Pokemon';
interface IPokemonJSON extends IPokemon {} interface IPokemonService {
interface ILeaguePokemonJSON extends ILeaguePokemon {} getConfig() : Promise<IConfig>;
interface IPokemonService {} getPokemonList() : Promise<Array<IPokemon>>;
getPokemonLeagueStats(pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) : Promise<ILeaguePokemon>;
}
export class PokemonService implements IPokemonService { export class PokemonService implements IPokemonService {
private static serializePokemonList(jsonPokemonList : Array<IPokemonJSON>) : Array<IPokemon> { private AjaxRequest : AjaxRequest;
const pokemonList = jsonPokemonList.reduce((result : Array<IPokemon>, 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 static serializePokemonLeagueStats(jsonPokemonLeagueStats : ILeaguePokemonJSON) : ILeaguePokemon { constructor() {
let pokemonLeagueStats : ILeaguePokemon; this.AjaxRequest = new AjaxRequest(XMLHttpRequest);
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;
} }
public async getConfig() { public async getConfig() {
const queryParameters = { const queryParameters = {
}; };
const response : IConfig = await AjaxUtils.ajaxGet('/dist/db/config.json', queryParameters); const response : IConfig = await this.AjaxRequest.ajaxGet('/dist/db/config.json', queryParameters);
// TODO: serialize this
return response; return response;
} }
@ -89,15 +31,17 @@ export class PokemonService implements IPokemonService {
const queryParameters = { const queryParameters = {
}; };
const response : Array<IPokemonJSON> = await AjaxUtils.ajaxGet('/dist/db/order.json', queryParameters); const response : Array<IPokemon> = await this.AjaxRequest.ajaxGet('/dist/db/order.json', queryParameters);
return PokemonService.serializePokemonList(response); 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 queryParameters = {
}; };
const response : ILeaguePokemonJSON = await AjaxUtils.ajaxGet(`/dist/db/${ pokemonId }.json`, queryParameters); const response : ILeaguePokemon = await this.AjaxRequest.ajaxGet(`/dist/db/${ fileName }.json`, queryParameters);
return PokemonService.serializePokemonLeagueStats(response); return response;
} }
} }

View File

@ -1,3 +1,5 @@
import POGOProtos from 'pogo-protos';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -40,6 +42,7 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
public render() { public render() {
const { const {
activePokemonId, activePokemonId,
activePokemonForm,
pokemonList, pokemonList,
pokemonListFiltered, pokemonListFiltered,
filterTerm, filterTerm,
@ -55,6 +58,7 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
<PokemonSelectList <PokemonSelectList
isLoading={ this.props.pokemonSelectListState.isLoading } isLoading={ this.props.pokemonSelectListState.isLoading }
activePokemonId={ activePokemonId } activePokemonId={ activePokemonId }
activePokemonForm={ activePokemonForm }
pokemonList={ filterTerm === '' ? pokemonList : pokemonListFiltered } pokemonList={ filterTerm === '' ? pokemonList : pokemonListFiltered }
filterTerm={ this.props.pokemonSelectListState.filterTerm } filterTerm={ this.props.pokemonSelectListState.filterTerm }
handleActivatePokemon={ this.handleActivatePokemon } handleActivatePokemon={ this.handleActivatePokemon }
@ -75,12 +79,12 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
); );
} }
private readonly handleActivatePokemon = (pokemonId : string) => { private readonly handleActivatePokemon = (pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) => {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch(ActionsPokemonSelectList.fetchPokemonLeagueStats(pokemonId)) dispatch(ActionsPokemonSelectList.fetchPokemonLeagueStats(pokemonId, form))
.then((leaguePokemon) => { .then((leaguePokemon) => {
dispatch(ActionsPokemonSelectList.setActivePokemonId(pokemonId)); dispatch(ActionsPokemonSelectList.setActivePokemonId(pokemonId, form));
dispatch(ActionsPokemonExplorer.setIvLevel(null)); dispatch(ActionsPokemonExplorer.setIvLevel(null));
dispatch(ActionsPokemonExplorer.setIvHp(null)); dispatch(ActionsPokemonExplorer.setIvHp(null));
dispatch(ActionsPokemonExplorer.setIvAtk(null)); dispatch(ActionsPokemonExplorer.setIvAtk(null));

View File

@ -1,16 +1,19 @@
import POGOProtos from 'pogo-protos';
import React from 'react'; import React from 'react';
import { ContentRect, default as Measure } from 'react-measure'; import { ContentRect, default as Measure } from 'react-measure';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
import classNames from 'classnames'; import classNames from 'classnames';
import { Grade, IStats, PokemonId } from 'app/models/Pokemon'; import { Grade, IStats } from 'app/models/Pokemon';
import { IIndividualValues } from './types'; import { IIndividualValues } from './types';
import * as styles from './styles/LeagueStatsList.scss'; import * as styles from './styles/LeagueStatsList.scss';
export interface ILeagueStatsListProps { export interface ILeagueStatsListProps {
activePokemonId : PokemonId; activePokemonId : POGOProtos.Enums.PokemonId;
activePokemonForm : POGOProtos.Enums.Form | null;
activeIndividualValues : IIndividualValues; activeIndividualValues : IIndividualValues;
leagueStatsList : Array<IStats>; leagueStatsList : Array<IStats>;
@ -49,7 +52,7 @@ export class LeagueStatsList extends React.Component<ILeagueStatsListProps, ISta
public componentWillReceiveProps(nextProps : ILeagueStatsListProps) { public componentWillReceiveProps(nextProps : ILeagueStatsListProps) {
const activeIvs = nextProps.activeIndividualValues; const activeIvs = nextProps.activeIndividualValues;
if (nextProps.activePokemonId !== this.props.activePokemonId) { if (nextProps.activePokemonId !== this.props.activePokemonId || nextProps.activePokemonForm !== this.props.activePokemonForm) {
this.setState({ activeIndex: -1 }); this.setState({ activeIndex: -1 });
if (this.listRef.current !== null) { if (this.listRef.current !== null) {
this.listRef.current.scrollToItem(0); this.listRef.current.scrollToItem(0);
@ -125,7 +128,7 @@ export class LeagueStatsList extends React.Component<ILeagueStatsListProps, ISta
private padString(value : string, length : number) { private padString(value : string, length : number) {
let output = value; let output = value;
for (let i = value.length - length - 1; i >= 0; i--) { for (let i = length - value.length; i > 0; i--) {
output += String.fromCharCode(160); output += String.fromCharCode(160);
} }
return output; return output;

View File

@ -1,3 +1,5 @@
import POGOProtos from 'pogo-protos';
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
@ -5,7 +7,7 @@ import classNames from 'classnames';
import { ILeaguePokemon, League } from 'app/models/League'; import { ILeaguePokemon, League } from 'app/models/League';
import { Grade, IStats, } from 'app/models/Pokemon'; import { Grade, IStats, } from 'app/models/Pokemon';
import { calculateCp, calculateStatAtLevel } from 'app/utils/calculator'; 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'; import { IIndividualValues, IndividualValueKey } from './types';
@ -188,18 +190,14 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
const pokemonIconCss = classNames( const pokemonIconCss = classNames(
`pokemon-${dex}`, `pokemon-${dex}`,
{ {
alola: leaguePokemon.form === 'ALOLA' alola: alolanForms.indexOf(leaguePokemon.form) > -1
}, },
); );
let type1 : JSX.Element | null = null; const type1 : JSX.Element = <div className={ `${pokemonType} ${formatType(leaguePokemon.types.type1)}` }>{ formatType(leaguePokemon.types.type1) }</div>;
if (leaguePokemon.types.type1) {
type1 = <div className={ `${pokemonType} ${leaguePokemon.types.type1}` }>{ leaguePokemon.types.type1 }</div>;
}
let type2 : JSX.Element | null = null; let type2 : JSX.Element | null = null;
if (leaguePokemon.types.type2) { if (leaguePokemon.types.type2) {
type2 = <div className={ `${pokemonType} ${leaguePokemon.types.type2}` }>{ leaguePokemon.types.type2 }</div>; type2 = <div className={ `${pokemonType} ${formatType(leaguePokemon.types.type2)}` }>{ formatType(leaguePokemon.types.type2) }</div>;
} }
return ( return (
@ -209,8 +207,8 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
<div className={ styles.pokemonInfoLeftColumn }> <div className={ styles.pokemonInfoLeftColumn }>
<i className={ pokemonIconCss } /> <i className={ pokemonIconCss } />
<h4 className={ styles.dexHeader }>No.{ dex }</h4> <h4 className={ styles.dexHeader }>No.{ dex }</h4>
{ leaguePokemon.form && { leaguePokemon.form !== POGOProtos.Enums.Form.FORM_UNSET &&
<h6 className={ styles.formHeader }>{ leaguePokemon.form.toLowerCase().replace('_', ' ') } Form</h6> <h6 className={ styles.formHeader }>{ formatForm(leaguePokemon.form) } Form</h6>
} }
<div className={ styles.pokemonTypeWrapper }> <div className={ styles.pokemonTypeWrapper }>
{ type1 } { type1 }
@ -219,7 +217,7 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
</div> </div>
<div className={ styles.pokemonInfoRightColumn }> <div className={ styles.pokemonInfoRightColumn }>
<h2 className={ styles.pokemonName }>{ leaguePokemon.name }</h2> <h2 className={ styles.pokemonName }>{ leaguePokemon.name }</h2>
<h5>{ leaguePokemon.category }</h5> <h5>{ leaguePokemon.genus }</h5>
<section className={ baseStatsCss }> <section className={ baseStatsCss }>
<h3 className={ containerTitleCss }>Base Stats</h3> <h3 className={ containerTitleCss }>Base Stats</h3>
<StatDisplay <StatDisplay
@ -342,6 +340,7 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
</div> </div>
<LeagueStatsList <LeagueStatsList
activePokemonId={ leaguePokemon.id } activePokemonId={ leaguePokemon.id }
activePokemonForm={ leaguePokemon.form }
activeIndividualValues={ individualValues } activeIndividualValues={ individualValues }
leagueStatsList={ leaguePokemon.pvp[activeLeague] } leagueStatsList={ leaguePokemon.pvp[activeLeague] }
handleActivateLeagueStats={ this.handleActivateLeagueStats } handleActivateLeagueStats={ this.handleActivateLeagueStats }

View File

@ -1,22 +1,25 @@
import POGOProtos from 'pogo-protos';
import React from 'react'; import React from 'react';
import { ContentRect, default as Measure } from 'react-measure'; import { ContentRect, default as Measure } from 'react-measure';
import { VariableSizeList } from 'react-window'; import { VariableSizeList } from 'react-window';
import classNames from 'classnames'; import classNames from 'classnames';
import { formatDexNumber } from 'app/utils/formatter'; import { formatDexNumber, formatForm } from 'app/utils/formatter';
import { IPokemon } from 'app/models/Pokemon'; import { DEFAULT_POKEMON_NAME, IPokemon } from 'app/models/Pokemon';
import * as styles from './styles/PokemonSelectList.scss'; import * as styles from './styles/PokemonSelectList.scss';
export interface IPokemonSelectListProps { export interface IPokemonSelectListProps {
isLoading : boolean; isLoading : boolean;
activePokemonId : string | null; activePokemonId : POGOProtos.Enums.PokemonId | null;
activePokemonForm : POGOProtos.Enums.Form | null;
pokemonList : Array<IPokemon>; pokemonList : Array<IPokemon>;
filterTerm : string; filterTerm : string;
handleActivatePokemon : (pokemonId : string) => void; handleActivatePokemon : (pokemonId : POGOProtos.Enums.PokemonId, form : POGOProtos.Enums.Form) => void;
handleChangeFilter : (filterTerm : string) => Promise<void>; handleChangeFilter : (filterTerm : string) => Promise<void>;
} }
@ -121,7 +124,7 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
{ listLength === 0 && { listLength === 0 &&
<div className={ styles.emptyState }> <div className={ styles.emptyState }>
<i className="pokemon-missing-no" /> <i className="pokemon-missing-no" />
<h3>MissingNo.</h3> <h3>{ DEFAULT_POKEMON_NAME }</h3>
</div> </div>
} }
</div> </div>
@ -134,7 +137,7 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
} }
private readonly calculateRowHeight = (index : number) => { private readonly calculateRowHeight = (index : number) => {
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) { private rowFactory({ index, style } : IRowFactory) {
@ -143,7 +146,7 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
const anchorCss = classNames( const anchorCss = classNames(
'list-item', 'list-item',
{ {
active: this.props.activePokemonId === pokemon.id active: this.props.activePokemonId === pokemon.id && this.props.activePokemonForm === pokemon.form
} }
); );
const dexCss = classNames( const dexCss = classNames(
@ -154,7 +157,7 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
'de-emphasize', 'de-emphasize',
styles.form styles.form
); );
const onClick = () => this.props.handleActivatePokemon(pokemon.id); const onClick = () => this.props.handleActivatePokemon(pokemon.id, pokemon.form);
return ( return (
<a <a
key={ index + pokemon.id } key={ index + pokemon.id }
@ -164,8 +167,8 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
> >
<span>{ pokemon.name }</span> <span>{ pokemon.name }</span>
<span className={ dexCss }>#{ dex }</span> <span className={ dexCss }>#{ dex }</span>
{ pokemon.form && { pokemon.form !== POGOProtos.Enums.Form.FORM_UNSET &&
<span className={ formCss }>{ pokemon.form.toLowerCase().replace('_', ' ') } Form</span> <span className={ formCss }>{ formatForm(pokemon.form) } Form</span>
} }
</a> </a>
); );

View File

@ -1,3 +1,5 @@
import POGOProtos from 'pogo-protos';
import { action } from 'typesafe-actions'; import { action } from 'typesafe-actions';
import { ILeaguePokemon } from 'app/models/League'; import { ILeaguePokemon } from 'app/models/League';
@ -12,9 +14,9 @@ export const setPokemonList = (pokemonList : Array<IPokemon>) => action(PokemonS
export const setPokemonListFiltered = (filterTerm : string, pokemonListFiltered : Array<IPokemon>) => action(PokemonSelectListActionTypes.SET_POKEMON_LIST_FILTERED, { filterTerm, pokemonListFiltered }); export const setPokemonListFiltered = (filterTerm : string, pokemonListFiltered : Array<IPokemon>) => 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 = ( export const filterPokemonList = (
filterTerm : string filterTerm : string
@ -27,7 +29,7 @@ export const filterPokemonList = (
pokemonListFiltered = pokemonList.reduce((result : Array<IPokemon>, pokemon) => { pokemonListFiltered = pokemonList.reduce((result : Array<IPokemon>, pokemon) => {
const pokemonName = pokemon.name.toLowerCase(); const pokemonName = pokemon.name.toLowerCase();
const pokemonDex = '' + pokemon.dex; 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 || if (pokemonName.indexOf(normalizedFilterTerm) === 0 ||
pokemonDex.indexOf(normalizedFilterTerm) === 0 || pokemonDex.indexOf(normalizedFilterTerm) === 0 ||
normalizedFilterTerm === pokemonForm normalizedFilterTerm === pokemonForm
@ -50,10 +52,11 @@ export const fetchPokemonList = (
}; };
export const fetchPokemonLeagueStats = ( export const fetchPokemonLeagueStats = (
pokemonId : string pokemonId : POGOProtos.Enums.PokemonId,
form : POGOProtos.Enums.Form
) : ThunkResult<Promise<ILeaguePokemon>> => { ) : ThunkResult<Promise<ILeaguePokemon>> => {
return async (dispatch, getState, extraArguments) => { 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)); dispatch(setPokemonLeagueStats(pokemonId, pokemonLeagueStats));
return pokemonLeagueStats; return pokemonLeagueStats;
}; };

View File

@ -6,6 +6,7 @@ import { IPokemonSelectListState, PokemonSelectListActionTypes } from './types';
export const initialState : IPokemonSelectListState = { export const initialState : IPokemonSelectListState = {
isLoading: true, isLoading: true,
activePokemonId: null, activePokemonId: null,
activePokemonForm: null,
pokemonList: [], pokemonList: [],
pokemonListFiltered: [], pokemonListFiltered: [],
filterTerm: '', filterTerm: '',
@ -43,6 +44,7 @@ const reduceSetActivePokemonId = (
) : IPokemonSelectListState => ({ ) : IPokemonSelectListState => ({
...state, ...state,
activePokemonId: action.payload.activePokemonId, activePokemonId: action.payload.activePokemonId,
activePokemonForm: action.payload.activePokemonForm,
}); });
const reduceSetPokemonLeagueStats = ( const reduceSetPokemonLeagueStats = (

View File

@ -1,13 +1,16 @@
import POGOProtos from 'pogo-protos';
import { ILeaguePokemon } from 'app/models/League'; import { ILeaguePokemon } from 'app/models/League';
import { IPokemon } from 'app/models/Pokemon'; import { IPokemon } from 'app/models/Pokemon';
export interface IPokemonSelectListState { export interface IPokemonSelectListState {
isLoading : boolean; isLoading : boolean;
activePokemonId : string | null; activePokemonId : POGOProtos.Enums.PokemonId | null;
activePokemonForm : POGOProtos.Enums.Form | null;
pokemonList : Array<IPokemon>; pokemonList : Array<IPokemon>;
pokemonListFiltered : Array<IPokemon>; pokemonListFiltered : Array<IPokemon>;
filterTerm : string; filterTerm : string;
pokemonLeagueStats : { [id : string] : ILeaguePokemon }; pokemonLeagueStats : { [id in keyof typeof POGOProtos.Enums.PokemonId]? : ILeaguePokemon };
} }
export const PokemonSelectListActionTypes = { export const PokemonSelectListActionTypes = {

View File

@ -1,3 +1,7 @@
import POGOProtos from 'pogo-protos';
export const DEFAULT_POKEMON_NAME = 'MissingNo.';
export enum Grade { export enum Grade {
'S', 'S',
'A', 'A',
@ -23,17 +27,20 @@ export interface IMaxStats extends IBaseStats {
level : number; 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 IPokemonSpecies {
export interface IPokemon {
id : PokemonId;
name : string; name : string;
category : string;
form : string | null;
family : string;
dex : number; dex : number;
order : number;
genus : string;
}
export interface IPokemon extends IPokemonSpecies {
id : POGOProtos.Enums.PokemonId;
form : POGOProtos.Enums.Form;
family : POGOProtos.Enums.PokemonFamilyId;
types : { types : {
type1 : Type; type1 : POGOProtos.Enums.PokemonType;
type2 : Type | null; type2 : POGOProtos.Enums.PokemonType | null;
}; };
stats : IBaseStats; stats : IBaseStats;
statsRank : IBaseStatsRank; statsRank : IBaseStatsRank;
@ -52,75 +59,3 @@ export interface IStats {
speciesGrade : Grade; speciesGrade : Grade;
metaGrade : 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';

View File

@ -1,3 +1,5 @@
import POGOProtos from 'pogo-protos';
export const formatDexNumber = (dex : number) => { export const formatDexNumber = (dex : number) => {
let prefix : string = ''; let prefix : string = '';
if (dex < 100) { if (dex < 100) {
@ -8,3 +10,75 @@ export const formatDexNumber = (dex : number) => {
} }
return prefix + dex; 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 '';
}
};

3
src/ts/common/xhr2/index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare module 'xhr2' {
export = XMLHttpRequest;
}

View File

@ -9,6 +9,7 @@
"outDir": "dist", "outDir": "dist",
"sourceMap": true, "sourceMap": true,
"module": "commonjs", "module": "commonjs",
"lib": ["es2017", "dom"],
"target": "es6", "target": "es6",
"jsx": "react", "jsx": "react",
"baseUrl": "./", "baseUrl": "./",
@ -17,19 +18,18 @@
"api/*": ["src/ts/api/*"], "api/*": ["src/ts/api/*"],
"app/*": ["src/ts/app/*"], "app/*": ["src/ts/app/*"],
"common/*": ["src/ts/common/*"], "common/*": ["src/ts/common/*"],
"styles/*": ["src/scss/*"] "externals/*": ["externals/*"],
"styles/*": ["src/scss/*"],
"xhr2": ["src/ts/common/xhr2"],
}, },
"plugins": [{ "plugins": [{
"name": "tslint-language-service", "name": "tslint-language-service",
"disableNoUnusedVariableRule": false "disableNoUnusedVariableRule": false
}], }],
}, },
"include": [
"src/**/*",
"."
],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"dist" "dist",
"externals",
] ]
} }

View File

@ -1,7 +1,7 @@
{ {
"extends": ["tslint:latest", "tslint-react", "tslint-eslint-rules"], "extends": ["tslint:latest", "tslint-react", "tslint-eslint-rules"],
"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-default-export": true,
"no-unused-expression": true, "no-unused-expression": true,
"no-unused-variable": [true, "react"], "no-unused-variable": [true, "react"],

View File

@ -12,6 +12,7 @@ const typescriptResolve = {
'api': path.resolve('./src/ts/api'), 'api': path.resolve('./src/ts/api'),
'app': path.resolve('./src/ts/app'), 'app': path.resolve('./src/ts/app'),
'common': path.resolve('./src/ts/common'), 'common': path.resolve('./src/ts/common'),
'externals': path.resolve('./externals'),
'styles': path.resolve('./src/scss'), 'styles': path.resolve('./src/scss'),
}, },
extensions: ['.ts', '.tsx', '.js'], extensions: ['.ts', '.tsx', '.js'],

108
yarn.lock
View File

@ -726,6 +726,59 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== 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": "@types/classnames@^2.2.7":
version "2.2.7" version "2.2.7"
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.7.tgz#fb68cc9be8487e6ea5b13700e759bfbab7e0fefd" 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" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= 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": "@types/node@^10.12.18":
version "10.12.18" version "10.12.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" 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: dependencies:
chalk "^2.0.1" 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: longest-streak@^2.0.1:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e"
@ -4480,6 +4548,20 @@ pkg-dir@^3.0.0:
dependencies: dependencies:
find-up "^3.0.0" 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: pokemongo-json-pokedex@^3.4.6:
version "3.4.6" version "3.4.6"
resolved "https://registry.yarnpkg.com/pokemongo-json-pokedex/-/pokemongo-json-pokedex-3.4.6.tgz#5d8ad17980cf4088fa573a18224131b65cd5d95b" 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" loose-envify "^1.3.1"
object-assign "^4.1.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: prr@~1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" 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" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= 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" version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
dependencies: 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" resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= 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: xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"