diff --git a/eslint-ps-standard.mjs b/eslint-ps-standard.mjs index 38c7965cf..223f83b95 100644 --- a/eslint-ps-standard.mjs +++ b/eslint-ps-standard.mjs @@ -332,7 +332,8 @@ export const defaultRulesES3 = { "no-invalid-this": "error", "no-new-wrappers": "error", // Map/Set can be polyfilled but it's nontrivial and it's easier just to use bare objects - "no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet", "WeakMap", "Set", "Map"], + // fetch can be polyfilled, but our standard is to use $.get or Net as appropriate. + "no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet", "WeakMap", "Set", "Map", "fetch"], "unicode-bom": "error", }; @@ -345,7 +346,9 @@ export const defaultRulesES3 = { export const defaultRulesES3TSChecked = { ...defaultRulesTSChecked, "radix": "off", - "no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet", "WeakMap", "Set", "Map"], + // Map/Set can be polyfilled but it's nontrivial and it's easier just to use bare objects + // fetch can be polyfilled, but our standard is to use $.get or Net as appropriate. + "no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet", "WeakMap", "Set", "Map", "fetch"], "no-restricted-syntax": ["error", "YieldExpression", "AwaitExpression", "BigIntLiteral"], }; diff --git a/play.pokemonshowdown.com/src/battle-team-editor.tsx b/play.pokemonshowdown.com/src/battle-team-editor.tsx index 95044874f..e3240d271 100644 --- a/play.pokemonshowdown.com/src/battle-team-editor.tsx +++ b/play.pokemonshowdown.com/src/battle-team-editor.tsx @@ -7,7 +7,7 @@ */ import preact from "../js/lib/preact"; -import { type Team } from "./client-main"; +import { type Team, Config } from "./client-main"; import { PSTeambuilder } from "./panel-teamdropdown"; import { Dex, type ModdedDex, toID, type ID, PSUtils } from "./battle-dex"; import { Teams } from './battle-teams'; @@ -20,6 +20,13 @@ import { Net } from "./client-connection"; type SelectionType = 'pokemon' | 'ability' | 'item' | 'move' | 'stats' | 'details'; +type SampleSets = { + [speciesName: string]: { + [setName: string]: Dex.PokemonSet, + }, +}; +type SampleSetsTable = { dex?: SampleSets, stats?: SampleSets }; + class TeamEditorState extends PSModel { team: Team; sets: Dex.PokemonSet[] = []; @@ -135,6 +142,7 @@ class TeamEditorState extends PSModel { break; } } + if (type === 'item') (this.search.prependResults ||= []).push(['item', '' as ID]); this.search.find(value || ''); this.searchIndex = this.search.results?.[0]?.[0] === 'header' ? 1 : 0; @@ -624,6 +632,49 @@ class TeamEditorState extends PSModel { this.team.packedTeam = Teams.pack(this.sets); this.team.iconCache = null; } + + /** undefined: loading, null: unavailable */ + static sampleSets: { [formatid: string]: SampleSetsTable | null } = {}; + // not static for complicated reasons. either way leads to an obscure + // race condition if fetchSampleSets is called simultaneously from + // different TeamEditorState instances, but this way just means two + // network requests rather than the UI getting out of sync. + _sampleSetPromises: Record> = {}; + fetchSampleSets(formatid: ID) { + if (formatid in TeamEditorState.sampleSets) return; + if (formatid.length <= 4) { + TeamEditorState.sampleSets[formatid] = null; + return; + } + if (!(formatid in this._sampleSetPromises)) { + this._sampleSetPromises[formatid] = Net( + `https://${Config.routes.client}/data/sets/${formatid}.json` + ).get().then(json => { + const data = JSON.parse(json); + TeamEditorState.sampleSets[formatid] = data; + this.update(); + }).catch(() => { + TeamEditorState.sampleSets[formatid] = null; + }); + } + } + /** returns null if sample sets aren't done loading */ + getSampleSets(set: Dex.PokemonSet): string[] | null { + const d = TeamEditorState.sampleSets[this.format]; + if (d === undefined) { + this.fetchSampleSets(this.format); + return null; + } + if (!d?.dex) return []; + const speciesid = toID(set.species); + const all = { + ...d.dex[set.species], + ...d.dex[speciesid], + ...d.stats?.[set.species], + ...d.stats?.[speciesid], + }; + return Object.keys(all); + } } export class TeamEditor extends preact.Component<{ @@ -691,7 +742,12 @@ export class TeamEditor extends preact.Component<{ this.forceUpdate(); }; override render() { - this.editor ||= new TeamEditorState(this.props.team); + if (!this.editor) { + this.editor = new TeamEditorState(this.props.team); + this.editor.subscribe(() => { + this.forceUpdate(); + }); + } const editor = this.editor; window.editor = editor; // debug editor.setReadonly(!!this.props.readOnly); @@ -1833,6 +1889,25 @@ class TeamWizard extends preact.Component<{ this.forceUpdate(); } }; + loadSampleSet = (setName: string) => { + const { editor } = this.props; + const setIndex = editor.innerFocus!.setIndex; + const set = editor.sets[setIndex]; + if (!set?.species) return; + + const data = TeamEditorState.sampleSets?.[editor.format]; + const sid = toID(set.species); + const setTemplate = data?.dex?.[set.species]?.[setName] ?? data?.dex?.[sid]?.[setName] ?? + data?.stats?.[set.species]?.[setName] ?? data?.stats?.[sid]?.[setName]; + if (!setTemplate) return; + + const applied: Partial = JSON.parse(JSON.stringify(setTemplate)); + Object.assign(set, applied); + + editor.save(); + this.props.onUpdate?.(); + this.forceUpdate(); + }; updateSearch = (ev: Event) => { const searchBox = ev.currentTarget as HTMLInputElement; this.props.editor.setSearchValue(searchBox.value); @@ -1967,6 +2042,7 @@ class TeamWizard extends preact.Component<{ const { type, setIndex } = editor.innerFocus; const set = this.props.editor.sets[setIndex] as Dex.PokemonSet | undefined; const cur = (i: number) => setIndex === i ? ' cur' : ''; + const sampleSets = type === 'ability' ? editor.getSampleSets(set!) : []; return
-
+
+ + {sampleSets?.length !== 0 && ( +
+

Sample sets

+ {sampleSets ? ( +
+ {sampleSets.map(setName => <> + {} + )} +
+ ) : ( +
Loading...
+ )} +
+ )} +
)} ; diff --git a/play.pokemonshowdown.com/style/teambuilder.css b/play.pokemonshowdown.com/style/teambuilder.css index 6d4bb11e1..b2c9bba11 100644 --- a/play.pokemonshowdown.com/style/teambuilder.css +++ b/play.pokemonshowdown.com/style/teambuilder.css @@ -727,3 +727,13 @@ you can't delete it by pressing Backspace */ .wizardsearchresults .dexlist, .searchresults .dexlist { min-width: 624px; } + +.sample-sets { + border: 1px solid #888; + border-radius: 6px; + margin: 5px 15px 12px 15px; + padding: 12px; +} +.sample-sets h3 { + margin: 0 0 10px; +}