From 27fb646e947b470e32f09c028aa3c7e0e987039e Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Thu, 31 Jul 2025 23:55:50 +0000 Subject: [PATCH] Preact minor updates batch 27 Minor - Don't close popups when receiving `|init|` Trivial - Update teambuilder if teams updated elsewhere - Bump cookie expiration - Code style in PHP - Improve documentation of `Team` --- .../src/battle-team-editor.tsx | 13 ++++-- play.pokemonshowdown.com/src/client-main.ts | 42 +++++++++++++------ .../src/panel-mainmenu.tsx | 5 ++- .../src/panel-teambuilder-team.tsx | 7 ---- play.pokemonshowdown.com/src/panels.tsx | 4 +- replay.pokemonshowdown.com/replays.lib.php | 8 ++-- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/play.pokemonshowdown.com/src/battle-team-editor.tsx b/play.pokemonshowdown.com/src/battle-team-editor.tsx index e3240d271..3fecae8b2 100644 --- a/play.pokemonshowdown.com/src/battle-team-editor.tsx +++ b/play.pokemonshowdown.com/src/battle-team-editor.tsx @@ -30,6 +30,7 @@ type SampleSetsTable = { dex?: SampleSets, stats?: SampleSets }; class TeamEditorState extends PSModel { team: Team; sets: Dex.PokemonSet[] = []; + lastPackedTeam = ''; gen = Dex.gen; dex: ModdedDex = Dex; deletedSet: { @@ -60,12 +61,15 @@ class TeamEditorState extends PSModel { constructor(team: Team) { super(); this.team = team; - this.sets = Teams.unpack(team.packedTeam); + this.updateTeam(false); this.setFormat(team.format); window.search = this.search; } - setReadonly(readonly: boolean) { - if (!readonly && this.readonly) this.sets = Teams.unpack(this.team.packedTeam); + updateTeam(readonly: boolean) { + if (this.lastPackedTeam !== this.team.packedTeam) { + this.sets = Teams.unpack(this.team.packedTeam); + this.lastPackedTeam = this.team.packedTeam; + } this.readonly = readonly; } setFormat(format: string) { @@ -630,6 +634,7 @@ class TeamEditorState extends PSModel { } save() { this.team.packedTeam = Teams.pack(this.sets); + this.lastPackedTeam = this.team.packedTeam; this.team.iconCache = null; } @@ -750,7 +755,7 @@ export class TeamEditor extends preact.Component<{ } const editor = this.editor; window.editor = editor; // debug - editor.setReadonly(!!this.props.readOnly); + editor.updateTeam(!!this.props.readOnly); editor.narrow = this.props.narrow ?? document.body.offsetWidth < 500; if (this.props.team.format !== editor.format) { editor.setFormat(this.props.team.format); diff --git a/play.pokemonshowdown.com/src/client-main.ts b/play.pokemonshowdown.com/src/client-main.ts index 313674f38..6f39e3231 100644 --- a/play.pokemonshowdown.com/src/client-main.ts +++ b/play.pokemonshowdown.com/src/client-main.ts @@ -293,7 +293,7 @@ class PSPrefs extends PSStreamModel { } let rooms = autojoin[PS.server.id] || ''; for (let title of rooms.split(",")) { - PS.addRoom({ id: toID(title) as string as RoomID, title, connected: true }, true); + PS.addRoom({ id: toID(title) as string as RoomID, title, connected: true, autofocus: false }); }; const cmd = `/autojoin ${rooms}`; if (PS.connection?.queue.includes(cmd)) { @@ -322,16 +322,21 @@ export interface Team { name: string; format: ID; folder: string; - /** note that this can be wrong if .uploaded?.loaded === false */ + /** Note that this can be wrong if `.uploaded?.notLoaded` */ packedTeam: string; /** The icon cache must be cleared (to `null`) whenever `packedTeam` is modified */ iconCache: preact.ComponentChildren; + /** Used in roomids (`team-[key]`) to refer to the team. Always persists within + * a single session, but not always between refreshes. As long as a team still + * exists, pointers to a Team are equivalent to a key. */ key: string; - /** `uploaded` will only exist if you're logged into the correct account. otherwise teamid is still tracked */ isBox: boolean; + /** uploaded team ID. will not exist for teams that are not uploaded. tracked locally */ teamid?: number; + /** `uploaded` will only exist if you're logged into the correct account. otherwise teamid is still tracked */ uploaded?: { teamid: number, + /** Promise = loading. */ notLoaded: boolean | Promise, /** password, if private. null = public, undefined = unknown, not loaded yet */ private?: string | null, @@ -1893,14 +1898,16 @@ export const PS = new class extends PSModel { this.addRoom({ id: 'rooms' as RoomID, title: "Rooms", - }, true); + autofocus: false, + }); this.rightPanel = this.rooms['rooms']!; if (this.newsHTML) { this.addRoom({ id: 'news' as RoomID, title: "News", - }, true); + autofocus: false, + }); } // Create rooms before /autojoin is sent to the server @@ -1911,7 +1918,7 @@ export const PS = new class extends PSModel { } let rooms = autojoin[this.server.id] || ''; for (let title of rooms.split(",")) { - this.addRoom({ id: toID(title) as unknown as RoomID, title, connected: true }, true); + this.addRoom({ id: toID(title) as unknown as RoomID, title, connected: true, autofocus: false }); } } @@ -2067,7 +2074,11 @@ export const PS = new class extends PSModel { id: roomid2, type, connected: true, - }, roomid === 'staff' || roomid === 'upperstaff'); + autofocus: roomid !== 'staff' && roomid !== 'upperstaff', + // probably the only use for `autoclosePopups: false`. + // (the server sometimes sends a popup error message and a new room at the same time) + autoclosePopups: false, + }); } else { room.type = type; this.updateRoomTypes(); @@ -2381,8 +2392,15 @@ export const PS = new class extends PSModel { } /** * Low-level add room. You usually want `join`. + * + * By default, focuses the room after adding it. (`options.autofocus = false` to suppress) + * + * By default, when autofocusing, closes popups that aren't the parent of the added room. + * (`options.autoclosePopups = false` to suppress) */ - addRoom(options: RoomOptions, noFocus = false) { + addRoom(options: RoomOptions & { autoclosePopups?: boolean, autofocus?: boolean }) { + options.autofocus ??= true; + options.autoclosePopups ??= options.autofocus; // support hardcoded PM room-IDs if (options.id.startsWith('challenge-')) { this.requestNotifications(); @@ -2407,7 +2425,7 @@ export const PS = new class extends PSModel { preexistingRoom = this.rooms[options.id]; } if (preexistingRoom) { - if (!noFocus) { + if (options.autofocus) { if (options.args?.challengeMenuOpen) { (preexistingRoom as ChatRoom).openChallenge(); } @@ -2415,7 +2433,7 @@ export const PS = new class extends PSModel { } return preexistingRoom; } - if (!noFocus) { + if (options.autoclosePopups) { let parentPopup = parentRoom; if ((options.parentElem as HTMLButtonElement)?.name === 'closeRoom') { // We want to close all popups above the parent element. @@ -2431,13 +2449,13 @@ export const PS = new class extends PSModel { this.rooms[room.id] = room; const location = room.location; room.location = null!; - this.moveRoom(room, location, noFocus); + this.moveRoom(room, location, !options.autofocus); if (options.backlog) { for (const args of options.backlog) { room.receiveLine(args); } } - if (!noFocus) room.focusNextUpdate = true; + if (options.autofocus) room.focusNextUpdate = true; return room; } hideRightRoom() { diff --git a/play.pokemonshowdown.com/src/panel-mainmenu.tsx b/play.pokemonshowdown.com/src/panel-mainmenu.tsx index c8366a9db..e2ab8adb8 100644 --- a/play.pokemonshowdown.com/src/panel-mainmenu.tsx +++ b/play.pokemonshowdown.com/src/panel-mainmenu.tsx @@ -340,7 +340,8 @@ export class MainMenuRoom extends PSRoom { PS.addRoom({ id: roomid, args: { pmTarget }, - }, true); + autofocus: false, + }); room = PS.rooms[roomid] as ChatRoom; } else { room.updateTarget(pmTarget); @@ -437,7 +438,7 @@ class NewsPanel extends PSRoomPanel { change = (ev: Event) => { const target = ev.currentTarget as HTMLInputElement; if (target.value === '1') { - document.cookie = "preactalpha=1; expires=Thu, 1 Aug 2025 12:00:00 UTC; path=/"; + document.cookie = "preactalpha=1; expires=Thu, 1 Sep 2025 12:00:00 UTC; path=/"; } else { document.cookie = "preactalpha=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; } diff --git a/play.pokemonshowdown.com/src/panel-teambuilder-team.tsx b/play.pokemonshowdown.com/src/panel-teambuilder-team.tsx index d3c05abde..651a5d53e 100644 --- a/play.pokemonshowdown.com/src/panel-teambuilder-team.tsx +++ b/play.pokemonshowdown.com/src/panel-teambuilder-team.tsx @@ -42,13 +42,6 @@ class TeamRoom extends PSRoom { } load() { PS.teams.loadTeam(this.team, true)?.then(() => { - const team = this.team; - if (team.uploadedPackedTeam && team.uploadedPackedTeam !== team.packedTeam) { - if (this.stripNicknames(team.packedTeam) === team.uploadedPackedTeam) { - // Team matches but nicknames were stripped; treat as unmodified - team.uploadedPackedTeam = team.packedTeam; - } - } this.update(null); }); } diff --git a/play.pokemonshowdown.com/src/panels.tsx b/play.pokemonshowdown.com/src/panels.tsx index 7fb6ce255..4d8bc7965 100644 --- a/play.pokemonshowdown.com/src/panels.tsx +++ b/play.pokemonshowdown.com/src/panels.tsx @@ -171,8 +171,8 @@ export class PSRouter { if (typeof e.state === 'string') { const [leftRoomid, rightRoomid] = e.state.split('..') as RoomID[]; if (rightRoomid) { - PS.addRoom({ id: leftRoomid, location: 'left' }, true); - PS.addRoom({ id: rightRoomid, location: 'right' }, true); + PS.addRoom({ id: leftRoomid, location: 'left', autofocus: false }); + PS.addRoom({ id: rightRoomid, location: 'right', autofocus: false }); PS.leftPanel = PS.rooms[leftRoomid] || PS.leftPanel; PS.rightPanel = PS.rooms[rightRoomid] || PS.rightPanel; } diff --git a/replay.pokemonshowdown.com/replays.lib.php b/replay.pokemonshowdown.com/replays.lib.php index 2a93d928a..d9e13bc33 100644 --- a/replay.pokemonshowdown.com/replays.lib.php +++ b/replay.pokemonshowdown.com/replays.lib.php @@ -56,25 +56,25 @@ class Replays { $res = $this->db->prepare("UPDATE replays SET private = 3, password = NULL WHERE id = ? LIMIT 1"); $res->execute([$replay['id']]); $res = $this->db->prepare("UPDATE replayplayers SET private = 3, password = NULL WHERE id = ?"); - $res->execute([$replay['id']]) + $res->execute([$replay['id']]); } else if ($replay['private'] === 2) { $replay['private'] = 1; $replay['password'] = NULL; $res = $this->db->prepare("UPDATE replays SET private = 1, password = NULL WHERE id = ? LIMIT 1"); $res->execute([$replay['id']]); $res = $this->db->prepare("UPDATE replayplayers SET private = 1, password = NULL WHERE id = ?"); - $res->execute([$replay['id']]) + $res->execute([$replay['id']]); } else if ($replay['private']) { if (!$replay['password']) $replay['password'] = $this->genPassword(); $res = $this->db->prepare("UPDATE replays SET private = 1, password = ? WHERE id = ? LIMIT 1"); $res->execute([$replay['password'], $replay['id']]); $res = $this->db->prepare("UPDATE replayplayers SET private = 1, password = ? WHERE id = ?"); - $res->execute([$replay['password'], $replay['id']]) + $res->execute([$replay['password'], $replay['id']]); } else { $res = $this->db->prepare("UPDATE replays SET private = 0, password = NULL WHERE id = ? LIMIT 1"); $res->execute([$replay['id']]); $res = $this->db->prepare("UPDATE replayplayers SET private = 0, password = NULL WHERE id = ?"); - $res->execute([$replay['id']]) + $res->execute([$replay['id']]); } return; }