diff --git a/README.md b/README.md
index 2b3122697..1c5c67c29 100644
--- a/README.md
+++ b/README.md
@@ -56,8 +56,8 @@ require v20 or later) and Git, and run `node build` (on Windows) or `./build`
(on other OSes) to build.
You can make and test client changes simply by building after each change,
-and opening `testclient.html`. This will allow you to test changes to the
-client without setting up your own login server.
+and opening `play.pokemonshowdown.com/testclient.html`. This will allow you
+to test changes to the client without setting up your own login server.
### Test keys
@@ -78,7 +78,7 @@ grab it from:
Make sure to put it in `config/` and not `play.pokemonshowdown.com/config/`.
-(This is the only supported method of logging in on the beta Preact testclient.)
+(This is the only supported method of logging in on the beta testclient.)
[5]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
diff --git a/build-tools/update b/build-tools/update
index 25393f95b..18733bcd5 100755
--- a/build-tools/update
+++ b/build-tools/update
@@ -137,7 +137,7 @@ if (!compileOpts.ignore) {
const diff = process.hrtime(compileStartTime);
console.log(
- `(${compiledFiles} ${compiledFiles !== 1 ? "files" : "file"} in ${diff[0] + Math.round(diff[1] / 1e6) / 1e3}s) DONE`
+ `(${compiledFiles} ${compiledFiles !== 1 ? "files" : "file"} in ${(diff[0] + diff[1] / 1e9).toFixed(3)}s) DONE`
);
/*********************************************************
diff --git a/play.pokemonshowdown.com/src/battle-log.ts b/play.pokemonshowdown.com/src/battle-log.ts
index 0473da95d..393628767 100644
--- a/play.pokemonshowdown.com/src/battle-log.ts
+++ b/play.pokemonshowdown.com/src/battle-log.ts
@@ -183,16 +183,7 @@ export class BattleLog {
[divClass, divHTML, noNotify] = this.parseChatMessage(message, name, timestampHtml, isHighlighted);
if (!noNotify && isHighlighted) {
const notifyTitle = "Mentioned by " + name + " in " + (battle?.roomid || '');
- if (window.PS) {
- const room = window.PS.rooms[battle?.roomid || ''];
- room.notify({
- title: notifyTitle,
- body: `"${message}"`,
- id: 'highlight',
- });
- } else {
- window.app?.rooms[battle?.roomid || '']?.notifyOnce(notifyTitle, "\"" + message + "\"", 'highlight');
- }
+ window.app?.rooms[battle?.roomid || '']?.notifyOnce(notifyTitle, "\"" + message + "\"", 'highlight');
}
break;
@@ -1238,9 +1229,9 @@ export class BattleLog {
}
const colorStyle = ` style="color:${BattleLog.usernameColor(toID(name))}"`;
const clickableName = `${BattleLog.escapeHTML(group)}${BattleLog.escapeHTML(name)}`;
- let hlClass = isHighlighted ? ' highlighted' : '';
- let isMine = (window.app?.user?.get('name') === name) || (window.PS?.user.name === name);
- let mineClass = isMine ? ' mine' : '';
+ const isMine = (window.app?.user?.get('name') === name) || (window.PS?.user.name === name);
+ const hlClass = isHighlighted ? ' highlighted' : '';
+ const mineClass = isMine ? ' mine' : '';
let cmd = '';
let target = '';
diff --git a/play.pokemonshowdown.com/src/client-connection.ts b/play.pokemonshowdown.com/src/client-connection.ts
index 488276487..e673789d6 100644
--- a/play.pokemonshowdown.com/src/client-connection.ts
+++ b/play.pokemonshowdown.com/src/client-connection.ts
@@ -50,6 +50,12 @@ export class PSConnection {
tryConnectInWorker(): boolean {
if (this.socket) return false; // must be one or the other
+ if (this.connected) return true;
+
+ if (this.worker) {
+ this.worker.postMessage({ type: 'connect', server: PS.server });
+ return true;
+ }
try {
const worker = new Worker('/js/client-connection-worker.js');
@@ -63,7 +69,6 @@ export class PSConnection {
case 'connected':
console.log('\u2705 (CONNECTED via worker)');
this.connected = true;
- PS.connected = true;
this.queue.forEach(msg => worker.postMessage({ type: 'send', data: msg }));
this.queue = [];
PS.update();
@@ -84,8 +89,8 @@ export class PSConnection {
}
};
- worker.onerror = (e: ErrorEvent) => {
- console.warn('Worker connection error:', e);
+ worker.onerror = (ev: ErrorEvent) => {
+ console.warn('Worker connection error:', ev);
this.worker = null;
this.directConnect(); // fallback
};
@@ -116,7 +121,6 @@ export class PSConnection {
socket.onopen = () => {
console.log('\u2705 (CONNECTED)');
this.connected = true;
- PS.connected = true;
this.reconnectDelay = 1000;
this.queue.forEach(msg => socket.send(msg));
this.queue = [];
@@ -132,7 +136,6 @@ export class PSConnection {
this.handleDisconnect();
console.log('\u2705 (DISCONNECTED)');
this.connected = false;
- PS.connected = false;
PS.isOffline = true;
for (const roomid in PS.rooms) {
const room = PS.rooms[roomid]!;
@@ -143,10 +146,8 @@ export class PSConnection {
};
socket.onerror = (ev: Event) => {
- PS.connected = false;
PS.isOffline = true;
// no useful info to print from the event
- PS.alert(`Connection error`);
this.retryConnection();
PS.update();
};
@@ -154,7 +155,6 @@ export class PSConnection {
private handleDisconnect() {
this.connected = false;
- PS.connected = false;
PS.isOffline = true;
this.socket = null;
for (const roomid in PS.rooms) {
@@ -168,12 +168,14 @@ export class PSConnection {
private retryConnection() {
if (!this.canReconnect()) return;
if (this.reconnectTimer) return;
+
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null;
if (!this.connected && this.canReconnect()) {
PS.mainmenu.send('/reconnect');
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.reconnectCap);
}
+ PS.update();
}, this.reconnectDelay);
}
@@ -182,17 +184,13 @@ export class PSConnection {
this.socket?.close();
this.worker?.terminate();
this.worker = null;
- PS.connection = null;
- PS.connected = false;
- PS.isOffline = true;
+ this.handleDisconnect();
+ PS.update();
}
-
- reconnectTest() {
- this.socket?.close();
- this.worker?.postMessage({ type: 'disconnect' });
- this.worker = null;
- PS.connected = false;
- PS.isOffline = true;
+ reconnect() {
+ if (this.connected) return;
+ if (this.worker && this.tryConnectInWorker()) return;
+ this.directConnect();
}
send(msg: string) {
@@ -213,7 +211,7 @@ export class PSConnection {
if (!PS.connection) {
PS.connection = new PSConnection();
} else {
- PS.connection.directConnect();
+ PS.connection.reconnect();
}
PS.prefs.doAutojoin();
}
diff --git a/play.pokemonshowdown.com/src/client-main.ts b/play.pokemonshowdown.com/src/client-main.ts
index b44c387b4..1be1f2d45 100644
--- a/play.pokemonshowdown.com/src/client-main.ts
+++ b/play.pokemonshowdown.com/src/client-main.ts
@@ -1042,13 +1042,16 @@ export class PSRoom extends PSStreamModel implements RoomOptions {
this.isSubtleNotifying = true;
PS.update();
}
+ dismissNotificationAt(i: number) {
+ try {
+ this.notifications[i].notification?.close();
+ } catch {}
+ this.notifications.splice(i, 1);
+ }
dismissNotification(id: string) {
const index = this.notifications.findIndex(n => n.id === id);
if (index !== -1) {
- try {
- this.notifications[index].notification?.close();
- } catch {}
- this.notifications.splice(index, 1);
+ this.dismissNotificationAt(index);
}
PS.update();
}
@@ -1061,7 +1064,11 @@ export class PSRoom extends PSStreamModel implements RoomOptions {
lastMessageDates[PS.server.id][room.id] = room.lastMessageTime || 0;
PS.prefs.set('logtimes', lastMessageDates);
}
- this.notifications = this.notifications.filter(notification => notification.noAutoDismiss);
+ for (let i = this.notifications.length - 1; i >= 0; i--) {
+ if (!this.notifications[i].noAutoDismiss) {
+ this.dismissNotificationAt(i);
+ }
+ }
this.isSubtleNotifying = false;
}
connect(): void {
@@ -1184,10 +1191,22 @@ export class PSRoom extends PSStreamModel implements RoomOptions {
'logout'() {
PS.user.logOut();
},
- 'reconnect'() {
- if (!PS.isOffline) {
- return this.add(`|error|You are already connected.`);
+ 'reconnect,connect'() {
+ if (this.connected && this.connected !== 'autoreconnect') {
+ return this.errorReply(`You are already connected.`);
}
+
+ if (!PS.isOffline) {
+ // connect to room
+ try {
+ this.connect();
+ } catch (err: any) {
+ this.errorReply(err.message);
+ }
+ return;
+ }
+
+ // connect to server
const uptime = Date.now() - PS.startTime;
if (uptime > 24 * 60 * 60 * 1000) {
PS.confirm(`It's been over a day since you first connected. Please refresh.`, {
@@ -1207,17 +1226,6 @@ export class PSRoom extends PSStreamModel implements RoomOptions {
return this.add(`|error|You are already offline.`);
}
PS.connection?.disconnect();
- this.add(`||You are now offline.`);
- },
- 'connect'() {
- if (this.connected && this.connected !== 'autoreconnect') {
- return this.errorReply(`You are already connected.`);
- }
- try {
- this.connect();
- } catch (err: any) {
- this.errorReply(err.message);
- }
},
'cancelsearch'() {
if (PS.mainmenu.cancelSearch()) {
@@ -1730,7 +1738,6 @@ export const PS = new class extends PSModel {
user = new PSUser();
server = new PSServer();
connection: PSConnection | null = null;
- connected = false;
/**
* While PS is technically disconnected while it's trying to connect,
* it still shows UI like it's connected, so you can click buttons
diff --git a/play.pokemonshowdown.com/src/panel-chat.tsx b/play.pokemonshowdown.com/src/panel-chat.tsx
index 367d16e5f..c86425006 100644
--- a/play.pokemonshowdown.com/src/panel-chat.tsx
+++ b/play.pokemonshowdown.com/src/panel-chat.tsx
@@ -250,31 +250,42 @@ export class ChatRoom extends PSRoom {
handleHighlight = (args: Args) => {
let name;
let message;
- let msgTime = 0;
+ let serverTime = 0;
if (args[0] === 'c:') {
- msgTime = parseInt(args[1]);
+ serverTime = parseInt(args[1]);
name = args[2];
message = args[3];
} else {
name = args[1];
message = args[2];
}
- let lastMessageDates = Dex.prefs('logtimes') || (PS.prefs.set('logtimes', {}), Dex.prefs('logtimes'));
+ if (toID(name) === PS.user.userid) return false;
+ if (message.startsWith(`/raw `)) return false;
+
+ const lastMessageDates = Dex.prefs('logtimes') || (PS.prefs.set('logtimes', {}), Dex.prefs('logtimes'));
if (!lastMessageDates[PS.server.id]) lastMessageDates[PS.server.id] = {};
- let lastMessageDate = lastMessageDates[PS.server.id][this.id] || 0;
+ const lastMessageDate = lastMessageDates[PS.server.id][this.id] || 0;
// because the time offset to the server can vary slightly, subtract it to not have it affect comparisons between dates
- let serverMsgTime = msgTime - (this.timeOffset || 0);
- let mayNotify = serverMsgTime > lastMessageDate && name !== PS.user.userid;
+ const time = serverTime - (this.timeOffset || 0);
if (PS.isVisible(this)) {
this.lastMessageTime = null;
- lastMessageDates[PS.server.id][this.id] = serverMsgTime;
+ lastMessageDates[PS.server.id][this.id] = time;
PS.prefs.set('logtimes', lastMessageDates);
} else {
// To be saved on focus
- let lastMessageTime = this.lastMessageTime || 0;
- if (lastMessageTime < serverMsgTime) this.lastMessageTime = serverMsgTime;
+ const lastMessageTime = this.lastMessageTime || 0;
+ if (lastMessageTime < time) this.lastMessageTime = time;
}
- return !!(ChatRoom.getHighlight(message, this.id) && mayNotify);
+ if (ChatRoom.getHighlight(message, this.id)) {
+ const mayNotify = time > lastMessageDate;
+ if (mayNotify) this.notify({
+ title: `Mentioned by ${name} in ${this.id}`,
+ body: `"${message}"`,
+ id: 'highlight',
+ });
+ return true;
+ }
+ return false;
};
override clientCommands = this.parseClientCommands({
'chall,challenge'(target) {
diff --git a/play.pokemonshowdown.com/src/panel-mainmenu.tsx b/play.pokemonshowdown.com/src/panel-mainmenu.tsx
index 517e3cbd2..5916dbbcd 100644
--- a/play.pokemonshowdown.com/src/panel-mainmenu.tsx
+++ b/play.pokemonshowdown.com/src/panel-mainmenu.tsx
@@ -437,7 +437,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 Jun 2025 12:00:00 UTC; path=/";
+ document.cookie = "preactalpha=1; expires=Thu, 1 Jul 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-topbar.tsx b/play.pokemonshowdown.com/src/panel-topbar.tsx
index dca2ba993..e81a9e010 100644
--- a/play.pokemonshowdown.com/src/panel-topbar.tsx
+++ b/play.pokemonshowdown.com/src/panel-topbar.tsx
@@ -217,7 +217,7 @@ export class PSHeader extends preact.Component {
this.handleResize();
}
renderUser() {
- if (!PS.connected) {
+ if (!PS.connection?.connected) {
return ;
}
if (PS.user.initializing) {
diff --git a/play.pokemonshowdown.com/style/battle-log.css b/play.pokemonshowdown.com/style/battle-log.css
index b5ac18833..1b934b9f0 100644
--- a/play.pokemonshowdown.com/style/battle-log.css
+++ b/play.pokemonshowdown.com/style/battle-log.css
@@ -622,6 +622,35 @@ input[type=range]:active::-webkit-slider-thumb {
}
}
+/*********************************************************
+ * Table
+ *********************************************************/
+
+.table-header {
+ position: relative;
+ position: sticky;
+ top: 0;
+}
+.ladder table, .ladder td, .ladder th,
+.table, .table td, .table th {
+ border-collapse: collapse;
+ border: 1px solid #BBBBBB;
+}
+.ladder td, .ladder th,
+.table td, .table th {
+ padding: 3px 5px;
+}
+.ladder th, .table th {
+ text-align: left;
+ font-size: 9pt;
+ background: #EEEEEE;
+ color: #111111;
+}
+
+.hidden {
+ display: none;
+}
+
/*********************************************************
* Everything else
*********************************************************/
diff --git a/play.pokemonshowdown.com/style/battle.css b/play.pokemonshowdown.com/style/battle.css
index 02f60eaa5..bf8bed318 100644
--- a/play.pokemonshowdown.com/style/battle.css
+++ b/play.pokemonshowdown.com/style/battle.css
@@ -5,7 +5,7 @@ License: GPLv2
*/
-@import url(./battle-log.css?v9.8);
+@import url(./battle-log.css?v10);
.battle {
position: absolute;
diff --git a/play.pokemonshowdown.com/style/client2.css b/play.pokemonshowdown.com/style/client2.css
index 55632cdec..7b9579a92 100644
--- a/play.pokemonshowdown.com/style/client2.css
+++ b/play.pokemonshowdown.com/style/client2.css
@@ -566,6 +566,9 @@ summary:hover .expandbutton,
border-left: 1px solid #AAAAAA;
margin-left: -1px;
}
+.dark .ps-room {
+ border-color: #34373b;
+}
.scrollable {
overflow: auto;
-webkit-overflow-scrolling: touch;
@@ -1029,35 +1032,6 @@ form.menugroup {
.button.mainmenu6:hover { background: linear-gradient(to bottom, hsl(270,40%,62%), hsl(270,40%,42%)); }
.button.mainmenu6:active { background: linear-gradient(to bottom, hsl(270,40%,42%), hsl(300,40%,58%)); }
-/*********************************************************
- * Ladder
- *********************************************************/
-
-.table-header {
- position: relative;
- position: sticky;
- top: 0;
-}
-.ladder table, .ladder td, .ladder th,
-.table, .table td, .table th {
- border-collapse: collapse;
- border: 1px solid #BBBBBB;
-}
-.ladder td, .ladder th,
-.table td, .table th {
- padding: 3px 5px;
-}
-.ladder th, .table th {
- text-align: left;
- font-size: 9pt;
- background: #EEEEEE;
- color: #111111;
-}
-
-.hidden {
- display: none;
-}
-
/*********************************************************
* Room list
*********************************************************/