/** @jsx preact.h */ /** @jsxFrag preact.Fragment */ import preact from '../../play.pokemonshowdown.com/js/lib/preact'; import { Net, PSIcon, getShowdownUsername, unpackTeam } from './utils'; import { BattleLog } from '../../play.pokemonshowdown.com/src/battle-log'; import type { PageProps } from './teams'; import { Dex } from '../../play.pokemonshowdown.com/src/battle-dex'; import { BattleStatNames } from '../../play.pokemonshowdown.com/src/battle-dex-data'; import { Config } from '../../play.pokemonshowdown.com/src/client-main'; declare const toID: (str: any) => string; declare const BattleAliases: Record; interface Team { team: string; title: string; views: number; ownerid: string; format: string; teamid: string; private: number; } function exportSet(set: Dex.PokemonSet) { let out = ``; // core if (set.name && set.name !== set.species) { out += `${set.name} (${set.species})`; } else { out += set.species; } if (set.gender === 'M') out += ` (M)`; if (set.gender === 'F') out += ` (F)`; if (set.item) out += ` @ ${set.item}`; out += ` \n`; if (set.ability) { out += `Ability: ${set.ability} \n`; } // details if (set.level && set.level !== 100) { out += `Level: ${set.level} \n`; } if (set.shiny) { out += `Shiny: Yes \n`; } if (typeof set.happiness === 'number' && set.happiness !== 255 && !isNaN(set.happiness)) { out += `Happiness: ${set.happiness} \n`; } if (set.pokeball) { out += `Pokeball: ${set.pokeball} \n`; } if (set.hpType) { out += `Hidden Power: ${set.hpType} \n`; } if (typeof set.dynamaxLevel === 'number' && set.dynamaxLevel !== 10 && !isNaN(set.dynamaxLevel)) { out += `Dynamax Level: ${set.dynamaxLevel} \n`; } if (set.gigantamax) { out += `Gigantamax: Yes \n`; } if (set.teraType) { out += `Tera Type: ${set.teraType} \n`; } // stats let first = true; if (set.evs) { for (const stat of Dex.statNames) { if (!set.evs[stat]) continue; if (first) { out += `EVs: `; first = false; } else { out += ` / `; } out += `${set.evs[stat]} ${BattleStatNames[stat]}`; } } if (!first) { out += ` \n`; } if (set.nature) { out += `${set.nature} Nature \n`; } first = true; if (set.ivs) { for (const stat of Dex.statNames) { if (set.ivs[stat] === undefined || isNaN(set.ivs[stat]) || set.ivs[stat] === 31) continue; if (first) { out += `IVs: `; first = false; } else { out += ` / `; } out += `${set.ivs[stat]} ${BattleStatNames[stat]}`; } } if (!first) { out += ` \n`; } // moves for (let move of set.moves) { if (move.startsWith(`Hidden Power `) && move.charAt(13) !== '[') { move = `Hidden Power [${move.slice(13)}]`; } out += `- ${move} \n`; } return out; } function PokemonSet({ set }: { set: Dex.PokemonSet }) { return
{set.name && set.name !== set.species ? <>{set.name} ({set.species}) : <>{set.species}} {set.gender ? <> ({set.gender}) : <>} {set.item ? <> @ {set.item} : <>}
{set.ability ? <>Ability: {set.ability}
: <>} {set.level && set.level !== 100 ? <>Level: {set.level}
: <>} {set.shiny ? <>Shiny: Yes
: <>} {set.teraType ? <>Tera Type: {set.teraType}
: <>} {set.evs ? <>{Dex.statNames.filter(stat => set.evs![stat]).map((stat, index, arr) => ( <> {index === 0 ? 'EVs: ' : ''} {set.evs![stat]} {BattleStatNames[stat]} {index !== (arr.length - 1) ? ' / ' : ''} ))}
: <>} {set.nature ? <>{set.nature} Nature
: <>} {set.ivs ? <>{Dex.statNames .filter(stat => !(set.ivs![stat] === undefined || isNaN(set.ivs![stat]) || set.ivs![stat] === 31)) .map((stat, index, arr) => <> {index === 0 ? 'IVs: ' : ''} {set.ivs![stat]} {BattleStatNames[stat]} {index !== (arr.length - 1) ? ' / ' : ''} )}
: <>} {set.moves ? set.moves.map(move => { if (move.substr(0, 13) === 'Hidden Power ') { const hpType = move.slice(13); move = move.slice(0, 13); move = `${move}[${hpType}]`; } // hide the alt so it doesn't interfere w/ copy/pasting return <>- {move}
; }) : <>} {typeof set.happiness === 'number' && set.happiness !== 255 && !isNaN(set.happiness) ? <>Happiness: {set.happiness}
: <>} {typeof set.dynamaxLevel === 'number' && set.dynamaxLevel !== 10 && !isNaN(set.dynamaxLevel) ? <>Dynamax Level: {set.dynamaxLevel}
: <>} {set.gigantamax ? <>Gigantamax: Yes
: <>}
; } class SetBlock extends preact.Component<{ set: Dex.PokemonSet, gen: number, mode?: string | null, }> { render() { const set = this.props.set; const gen = this.props.gen || (Dex.prefs('noanim') ? 5 : 6); const spriteData = Dex.getSpriteData( Dex.species.get(set.species), true, { gen, shiny: set.shiny, gender: set.gender as 'F' } ); let forceResize = 110; if (matchMedia("(max-width: 450px)").matches && this.props.mode === '2col') { forceResize = 55; } if (spriteData.w > forceResize) { const w = spriteData.w; spriteData.w *= (forceResize / w); spriteData.h *= (forceResize / w); } return
{set.item ? : <>}
; } } export class TeamViewer extends preact.Component { id: string; pw?: string; override state = { team: undefined as null | void | Team, error: undefined as string | undefined, copyButtonMsg: false as boolean, displayMode: localStorage.getItem('teamdisplaymode') || null, spriteGen: Number(localStorage.getItem('spritegen')) || 6, manageOpen: false, changesMade: false, teamEdits: null as { format?: string, private?: number } | null, editError: null as null | string, }; constructor(props: PageProps) { super(props); this.id = props.args.id; this.checkTeamID(); } render() { if (this.state.error) { return
{this.state.error}
; } if (!this.state.team) { return
{ typeof this.state.team === 'undefined' ? 'Loading...' : <>

Team not found.


Either it doesn't exist or it's password protected. Check the link? }
; } const { team, title, ownerid, format, views } = this.state.team; const teamData = unpackTeam(team); const is2Col = this.state.displayMode === '2col'; const isDark = document.querySelector('html')?.classList[0] === 'dark'; const link = this.id + (this.state.team.private ? `-${this.state.team.private}` : ''); return

{title}

Owner: {ownerid}
Format: {format}
Views: {views}
https://psim.us/t/{link}

{toID(getShowdownUsername()) === this.state.team.ownerid && }

{this.state.editError && <>
{this.state.editError}

} {this.state.manageOpen && <>
this.editTeamValue('format', ev)} /> {this.state.changesMade && <>
}
}
{teamData.map( set => (
) )}
; } checkTeamID() { if (this.id.includes('-')) { [this.id, this.pw] = this.id.split('-'); } if (!/^\d+$/.test(this.id)) { this.setState({ error: "Invalid team ID: " + JSON.stringify(this.props.args) }); return; } this.loadTeamData(); } changeManage() { this.setState({ manageOpen: !this.state.manageOpen }); } changeSpriteGen(event: any) { localStorage.setItem('spritegen', `${event.target.value}`); this.setState({ spriteGen: event.target.value }); } changeDisplayMode() { if (this.state.displayMode === '2col') { this.state.displayMode = 'default'; } else { this.state.displayMode = '2col'; } localStorage.setItem('teamdisplaymode', this.state.displayMode); this.setState({ displayMode: this.state.displayMode }); } changeColorMode() { const classList = document.querySelector('html')?.classList; const isDark = classList?.[0] === 'dark'; if (isDark) { classList.remove('dark'); localStorage.removeItem('darkmode'); } else { classList?.add('dark'); localStorage.setItem('darkmode', 'true'); } // not something we keep in elem state but... still gotta poke it to update this.forceUpdate(); } loadTeamData() { void Net('/api/getteam').get({ query: { teamid: this.id, password: this.pw, full: 1 } }).then(resultText => { if (resultText.startsWith(']')) resultText = resultText.slice(1); let result; try { result = JSON.parse(resultText); } catch { result = { actionerror: "Malformed response received. Try again later." }; } if (result.actionerror) { this.setState({ error: result.actionerror }); } else { this.setState({ team: result.team === null ? result.team : result }); } }).catch(e => { this.setState({ error: `HTTP${e.code}: ${e.message}` }); }); } copyTeam() { if (!this.state.team) return; const team = unpackTeam(this.state.team.team); navigator.clipboard.writeText(team.map(exportSet).join('\n')); this.setState({ copyButtonMsg: true }); setTimeout(() => { this.setState({ copyButtonMsg: false }); }, 1000); } editTeamValue(val: 'format' | 'private', { currentTarget }: any) { (this.state.teamEdits ||= {})[val] = currentTarget.value; let changes; if (val === 'format') { let format = toID(currentTarget.value); format = toID(BattleAliases[format]) || format; if (!/^gen\d+/.test(format)) { format = `gen9${format}`; } changes = changes || format !== this.state.team?.format; } else if (val === 'private') { changes = changes || currentTarget.value !== this.state.team?.private; } this.setState({ teamEdits: this.state.teamEdits, changesMade: changes, }); } commitEdit() { if (!this.state.changesMade || !this.state.team) return; void Net('/api/editteam').get({ query: { teamid: this.id, ...this.state.teamEdits }, }).then(resultText => { if (resultText.startsWith(']')) resultText = resultText.slice(1); let result; try { result = JSON.parse(resultText); } catch { result = { actionerror: "Malformed response received. Try again later." }; } if (result.team.private !== this.state.team?.private) { history.pushState({}, '', new URL( location.href.split(this.id)[0] + this.id + (result.team.private ? `-${result.team.private}` : '') )); } if (result.actionerror) { this.setState({ editError: result.actionerror, changesMade: false, teamEdits: null }); } else { this.setState({ team: result.team, changesMade: false, teamEdits: null }); } }); } }