Preact: Support smogon sets in teambuilder (#2466)

---------

Co-authored-by: Guangcong Luo <guangcongluo@gmail.com>
This commit is contained in:
Dieter Reinert 2025-07-31 22:48:09 +02:00 committed by GitHub
parent fca55b15bc
commit a3ecfed7e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 115 additions and 8 deletions

View File

@ -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"],
};

View File

@ -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<string, Promise<void>> = {};
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<Dex.PokemonSet> = 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 <div class="team-focus-editor">
<ul class="tabbar">
<li class="home-li"><button class="button" onClick={this.setFocus}>
@ -1998,10 +2074,28 @@ class TeamWizard extends preact.Component<{
/>
{PSSearchResults.renderFilters(editor.search)}
</div>
<div class="wizardsearchresults" onScroll={this.scrollResults}><PSSearchResults
search={editor.search} hideFilters resultIndex={editor.searchIndex}
onSelect={this.selectResult} windowing={this.windowResults()}
/></div>
<div class="wizardsearchresults" onScroll={this.scrollResults}>
<PSSearchResults
search={editor.search} hideFilters resultIndex={editor.searchIndex}
onSelect={this.selectResult} windowing={this.windowResults()}
/>
{sampleSets?.length !== 0 && (
<div class="sample-sets">
<h3>Sample sets</h3>
{sampleSets ? (
<div>
{sampleSets.map(setName => <>
<button class="button" onClick={() => this.loadSampleSet(setName)}>
{setName}
</button> {}
</>)}
</div>
) : (
<div>Loading...</div>
)}
</div>
)}
</div>
</div>
)}
</div>;

View File

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