mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Preact minor update batch 11
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
Minor
- Unhide right panel when choosing "Two panels" layout option
- Refactor focusing
- Correctly focus next room when closing currently active room
- Correctly focus room when joining new room
- Use strict mode on all compiled files
- Fix router when started on `/` (it previously required starting on a
non-empty room ID, which wasn't noticeable back when the URL needed
to be `/preactalpha`)
- Update teambuilder sidebar CSS, to make it easier to add regular text
- This is mainly for the "Tournaments" button in the main menu,
which shares the CSS
- Fix new tournament elim tree text in Safari
- Update new tournament elim tree highlighted links to reliably
link every still-playing game
- Remove latest gen from format name displays everywhere
- Previously, they would only be removed from the format dropdown,
but now they're also gone from the Ladder tab, battle tabs, and
`/rank`
- Support async d3 loading
- This allows chatrooms to be loaded way before all our dependencies
are fully downloaded
- Remove "[Gen 9]" from format names everywhere (previously it was only
removed from the format dropdown)
- Also add "[Gen 6]" to unlabeled formats in `/rank` (Gen 6 was the
last time we didn't have format generation as part of format names)
Trivial
- Stricter JSX linting
- (unfortunately, most of the JSX style enforcement I actually want
isn't possible in @stylistic)
- Make room.subscribeTo's second parameter optional
- Rearrange and comment loading order
- Rename hiddenInit -> focusNextUpdate (clarity)
- Rename PSMain -> PSView (clarity)
- Fix button spacing in Change Password
- Add `touch-action: manipulation` to <a> tags
- Refactor `nodeSize` in elim tour trees
This commit is contained in:
parent
7ee7c6604a
commit
5971e5151a
4
.babelrc
4
.babelrc
|
|
@ -29,7 +29,9 @@
|
||||||
|
|
||||||
// ES3
|
// ES3
|
||||||
"@babel/plugin-transform-member-expression-literals",
|
"@babel/plugin-transform-member-expression-literals",
|
||||||
"@babel/plugin-transform-property-literals"
|
"@babel/plugin-transform-property-literals",
|
||||||
|
|
||||||
|
"@babel/plugin-transform-strict-mode"
|
||||||
],
|
],
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"src/globals.d.ts"
|
"src/globals.d.ts"
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,6 @@ trim_trailing_whitespace = false
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 8
|
indent_size = 8
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[COMMIT_EDITMSG]
|
||||||
|
indent_style = space
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ export const defaultRules = {
|
||||||
"@stylistic/jsx-one-expression-per-line": "off",
|
"@stylistic/jsx-one-expression-per-line": "off",
|
||||||
"@stylistic/jsx-max-props-per-line": "off",
|
"@stylistic/jsx-max-props-per-line": "off",
|
||||||
"@stylistic/jsx-function-call-newline": "off",
|
"@stylistic/jsx-function-call-newline": "off",
|
||||||
|
"@stylistic/jsx-child-element-spacing": "error",
|
||||||
"no-restricted-syntax": ["error",
|
"no-restricted-syntax": ["error",
|
||||||
{ selector: "CallExpression[callee.name='Symbol']", message: "Annoying to serialize, just use a string" },
|
{ selector: "CallExpression[callee.name='Symbol']", message: "Annoying to serialize, just use a string" },
|
||||||
],
|
],
|
||||||
|
|
|
||||||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -11,6 +11,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.26.10",
|
"@babel/core": "^7.26.10",
|
||||||
"@babel/plugin-transform-react-jsx": "^7.25.9",
|
"@babel/plugin-transform-react-jsx": "^7.25.9",
|
||||||
|
"@babel/plugin-transform-strict-mode": "^7.25.9",
|
||||||
"@babel/preset-env": "^7.26.9",
|
"@babel/preset-env": "^7.26.9",
|
||||||
"@babel/preset-typescript": "^7.27.0",
|
"@babel/preset-typescript": "^7.27.0",
|
||||||
"babel-plugin-remove-import-export": "^1.1.1",
|
"babel-plugin-remove-import-export": "^1.1.1",
|
||||||
|
|
@ -1264,6 +1265,21 @@
|
||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/plugin-transform-strict-mode": {
|
||||||
|
"version": "7.25.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-strict-mode/-/plugin-transform-strict-mode-7.25.9.tgz",
|
||||||
|
"integrity": "sha512-DplEwkN9xt6XCz/4oC9l8FJGn7LnOGPU7v08plq+OclMT55zAR9lkX7QIbQ9XscvvJNYpLUfYO4IYz/7JGkbXQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.25.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/plugin-transform-template-literals": {
|
"node_modules/@babel/plugin-transform-template-literals": {
|
||||||
"version": "7.26.8",
|
"version": "7.26.8",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.26.10",
|
"@babel/core": "^7.26.10",
|
||||||
"@babel/plugin-transform-react-jsx": "^7.25.9",
|
"@babel/plugin-transform-react-jsx": "^7.25.9",
|
||||||
|
"@babel/plugin-transform-strict-mode": "^7.25.9",
|
||||||
"@babel/preset-env": "^7.26.9",
|
"@babel/preset-env": "^7.26.9",
|
||||||
"@babel/preset-typescript": "^7.27.0",
|
"@babel/preset-typescript": "^7.27.0",
|
||||||
"babel-plugin-remove-import-export": "^1.1.1",
|
"babel-plugin-remove-import-export": "^1.1.1",
|
||||||
|
|
|
||||||
|
|
@ -612,8 +612,8 @@
|
||||||
TournamentBox.nodeSize = {
|
TournamentBox.nodeSize = {
|
||||||
width: 160, height: 30,
|
width: 160, height: 30,
|
||||||
radius: 5,
|
radius: 5,
|
||||||
separationX: 20, separationY: 20,
|
separationX: 20, separationY: 10,
|
||||||
textOffset: -1
|
textOffset: 4
|
||||||
};
|
};
|
||||||
|
|
||||||
TournamentBox.prototype.generateBracket = function (data, abbreviated) {
|
TournamentBox.prototype.generateBracket = function (data, abbreviated) {
|
||||||
|
|
@ -655,9 +655,15 @@
|
||||||
child.highlightLink = true;
|
child.highlightLink = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (node.state === 'inprogress' || node.state === 'available' || node.state === 'challenging') {
|
} else if (
|
||||||
|
node.state === 'inprogress' || node.state === 'available' || node.state === 'challenging' ||
|
||||||
|
node.state === 'unavailable'
|
||||||
|
) {
|
||||||
for (var i = 0; i < node.children.length; i++) {
|
for (var i = 0; i < node.children.length; i++) {
|
||||||
node.children[i].highlightLink = true;
|
var child = node.children[i];
|
||||||
|
if (child.team && !child.team.startsWith('(')) {
|
||||||
|
child.highlightLink = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (highlightName) {
|
} else if (highlightName) {
|
||||||
for (var i = 0; i < node.children.length; i++) {
|
for (var i = 0; i < node.children.length; i++) {
|
||||||
|
|
@ -763,6 +769,7 @@
|
||||||
if (node.team === name) rect.attr('stroke-dasharray', '5,5').attr('stroke-width', 2);
|
if (node.team === name) rect.attr('stroke-dasharray', '5,5').attr('stroke-width', 2);
|
||||||
|
|
||||||
elem.append('svg:text').classed('tournament-bracket-tree-node-team', true)
|
elem.append('svg:text').classed('tournament-bracket-tree-node-team', true)
|
||||||
|
.attr('y', nodeSize.textOffset)
|
||||||
.classed('tournament-bracket-tree-node-team-draw', true)
|
.classed('tournament-bracket-tree-node-team-draw', true)
|
||||||
.text(node.team || '');
|
.text(node.team || '');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
4
play.pokemonshowdown.com/js/client-endload.js
Normal file
4
play.pokemonshowdown.com/js/client-endload.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
PS.libsLoaded.loaded();
|
||||||
|
//# sourceMappingURL=client-endload.js.map
|
||||||
|
|
@ -1339,11 +1339,6 @@
|
||||||
bufs[curBuf] += BattleLog.escapeHTML(curSection) + '</strong></summary>';
|
bufs[curBuf] += BattleLog.escapeHTML(curSection) + '</strong></summary>';
|
||||||
}
|
}
|
||||||
var formatName = BattleLog.escapeFormat(format.id);
|
var formatName = BattleLog.escapeFormat(format.id);
|
||||||
if (formatName.charAt(0) !== '[') formatName = '[Gen 6] ' + formatName;
|
|
||||||
formatName = formatName.replace('[Gen 9] ', '');
|
|
||||||
formatName = formatName.replace('[Gen 9 ', '[');
|
|
||||||
formatName = formatName.replace('[Gen 8 ', '[');
|
|
||||||
formatName = formatName.replace('[Gen 7 ', '[');
|
|
||||||
bufs[curBuf] += (
|
bufs[curBuf] += (
|
||||||
'<li><button name="selectFormat" value="' + i +
|
'<li><button name="selectFormat" value="' + i +
|
||||||
'" class="option' + (curFormat === i ? ' cur' : '') + '">' + formatName +
|
'" class="option' + (curFormat === i ? ' cur' : '') + '">' + formatName +
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@
|
||||||
if (room.title && room.title.charAt(0) === '[') {
|
if (room.title && room.title.charAt(0) === '[') {
|
||||||
var closeBracketIndex = room.title.indexOf(']');
|
var closeBracketIndex = room.title.indexOf(']');
|
||||||
if (closeBracketIndex > 0) {
|
if (closeBracketIndex > 0) {
|
||||||
return buf + ' draggable="true"><i class="text">' + BattleLog.escapeFormat(room.title.slice(1, closeBracketIndex)) + '</i><span>' + BattleLog.escapeHTML(room.title.slice(closeBracketIndex + 1)) + '</span></a><button class="closebutton" name="closeRoom" value="' + id + '" aria-label="Close"><i class="fa fa-times-circle"></i></a></li>';
|
return buf + ' draggable="true"><i class="text">' + BattleLog.escapeHTML(room.title.slice(1, closeBracketIndex)) + '</i><span>' + BattleLog.escapeHTML(room.title.slice(closeBracketIndex + 1)) + '</span></a><button class="closebutton" name="closeRoom" value="' + id + '" aria-label="Close"><i class="fa fa-times-circle"></i></a></li>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buf + ' draggable="true"><i class="fa fa-file-text-o"></i> <span>' + (BattleLog.escapeHTML(room.title) || id) + '</span></a><button class="closebutton" name="closeRoom" value="' + id + '" aria-label="Close"><i class="fa fa-times-circle"></i></a></li>';
|
return buf + ' draggable="true"><i class="fa fa-file-text-o"></i> <span>' + (BattleLog.escapeHTML(room.title) || id) + '</span></a><button class="closebutton" name="closeRoom" value="' + id + '" aria-label="Close"><i class="fa fa-times-circle"></i></a></li>';
|
||||||
|
|
|
||||||
|
|
@ -72,13 +72,14 @@ https://psim.us/dev
|
||||||
document.head.appendChild(linkEl);
|
document.head.appendChild(linkEl);
|
||||||
}
|
}
|
||||||
linkStyle("/style/sim-types.css");
|
linkStyle("/style/sim-types.css");
|
||||||
linkStyle("/style/teambuilder.css");
|
linkStyle("/style/teambuilder.css?");
|
||||||
linkStyle("style/battle-search.css");
|
linkStyle("style/battle-search.css");
|
||||||
linkStyle("/style/font-awesome.css");
|
linkStyle("/style/font-awesome.css");
|
||||||
</script>
|
</script>
|
||||||
<script nomodule defer src="/js/lib/ps-polyfill.js"></script>
|
<script nomodule defer src="/js/lib/ps-polyfill.js"></script>
|
||||||
<script defer src="/config/config.js?"></script>
|
<script defer src="/config/config.js?"></script>
|
||||||
<script defer src="/js/client-core.js?"></script>
|
<script defer src="/js/client-core.js?"></script>
|
||||||
|
<!-- At this point, background and dark mode are loaded -->
|
||||||
|
|
||||||
<script defer src="/js/battle-dex-data.js?"></script>
|
<script defer src="/js/battle-dex-data.js?"></script>
|
||||||
<script defer src="/js/battle-dex.js?"></script>
|
<script defer src="/js/battle-dex.js?"></script>
|
||||||
|
|
@ -97,10 +98,16 @@ https://psim.us/dev
|
||||||
<script defer src="/js/panel-mainmenu.js?"></script>
|
<script defer src="/js/panel-mainmenu.js?"></script>
|
||||||
<script defer src="/js/panel-rooms.js?"></script>
|
<script defer src="/js/panel-rooms.js?"></script>
|
||||||
<script defer src="/js/panel-topbar.js?"></script>
|
<script defer src="/js/panel-topbar.js?"></script>
|
||||||
<script defer src="/js/panel-popups.js?"></script>
|
<!-- at this point, the main view is loaded and usable -->
|
||||||
|
|
||||||
<script defer src="/js/miniedit.js?"></script>
|
<script defer src="/js/miniedit.js?"></script>
|
||||||
<script defer src="/js/panel-chat-tournament.js?"></script>
|
<script defer src="/js/panel-chat-tournament.js?"></script>
|
||||||
<script defer src="/js/panel-chat.js?"></script>
|
<script defer src="/js/panel-chat.js?"></script>
|
||||||
|
<!-- at this point, chatrooms are usable -->
|
||||||
|
|
||||||
|
<script defer src="/js/panel-popups.js?"></script>
|
||||||
|
<script defer src="/js/panel-page.js?"></script>
|
||||||
|
<script defer src="/js/panel-ladder.js?"></script>
|
||||||
|
|
||||||
<script defer src="/js/battle-sound.js"></script>
|
<script defer src="/js/battle-sound.js"></script>
|
||||||
<script defer src="/js/lib/jquery-2.2.4.min.js"></script>
|
<script defer src="/js/lib/jquery-2.2.4.min.js"></script>
|
||||||
|
|
@ -122,11 +129,11 @@ https://psim.us/dev
|
||||||
<script defer src="/js/battle-dex-search.js?"></script>
|
<script defer src="/js/battle-dex-search.js?"></script>
|
||||||
<script defer src="/js/battle-searchresults.js?"></script>
|
<script defer src="/js/battle-searchresults.js?"></script>
|
||||||
<script defer src="/js/panel-teambuilder-team.js?"></script>
|
<script defer src="/js/panel-teambuilder-team.js?"></script>
|
||||||
<script defer src="/js/panel-ladder.js?"></script>
|
|
||||||
<script defer src="/js/panel-page.js?"></script>
|
|
||||||
|
|
||||||
<script defer src="/data/pokedex-mini.js?"></script>
|
<script defer src="/data/pokedex-mini.js?"></script>
|
||||||
<script defer src="/data/pokedex-mini-bw.js?"></script>
|
<script defer src="/data/pokedex-mini-bw.js?"></script>
|
||||||
<script defer src="/js/lib/d3.v3.min.js"></script>
|
<script defer src="/js/lib/d3.v3.min.js"></script>
|
||||||
|
|
||||||
|
<script defer src="/js/client-endload.js?"></script>
|
||||||
|
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
|
||||||
|
|
@ -1056,30 +1056,42 @@ export class BattleLog {
|
||||||
static escapeFormat(formatid = ''): string {
|
static escapeFormat(formatid = ''): string {
|
||||||
let atIndex = formatid.indexOf('@@@');
|
let atIndex = formatid.indexOf('@@@');
|
||||||
if (atIndex >= 0) {
|
if (atIndex >= 0) {
|
||||||
return this.escapeFormat(formatid.slice(0, atIndex)) +
|
return this.escapeHTML(this.formatName(formatid.slice(0, atIndex))) +
|
||||||
'<br />Custom rules: ' + this.escapeHTML(formatid.slice(atIndex + 3));
|
'<br />Custom rules: ' + this.escapeHTML(formatid.slice(atIndex + 3));
|
||||||
}
|
}
|
||||||
if (window.BattleFormats && BattleFormats[formatid]) {
|
return this.escapeHTML(this.formatName(formatid));
|
||||||
return this.escapeHTML(BattleFormats[formatid].name);
|
|
||||||
}
|
|
||||||
if (window.NonBattleGames && NonBattleGames[formatid]) {
|
|
||||||
return this.escapeHTML(NonBattleGames[formatid]);
|
|
||||||
}
|
|
||||||
return this.escapeHTML(formatid);
|
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Do not store this output anywhere; it removes the generation number
|
||||||
|
* for the current gen.
|
||||||
|
*/
|
||||||
static formatName(formatid = ''): string {
|
static formatName(formatid = ''): string {
|
||||||
|
if (!formatid) return '';
|
||||||
|
|
||||||
let atIndex = formatid.indexOf('@@@');
|
let atIndex = formatid.indexOf('@@@');
|
||||||
if (atIndex >= 0) {
|
if (atIndex >= 0) {
|
||||||
return this.formatName(formatid.slice(0, atIndex)) +
|
return this.formatName(formatid.slice(0, atIndex)) +
|
||||||
' (Custom rules: ' + this.escapeHTML(formatid.slice(atIndex + 3)) + ')';
|
' (Custom rules: ' + this.escapeHTML(formatid.slice(atIndex + 3)) + ')';
|
||||||
}
|
}
|
||||||
|
if (!formatid.startsWith('gen')) {
|
||||||
|
formatid = `gen6${formatid}`;
|
||||||
|
}
|
||||||
|
let name = formatid;
|
||||||
if (window.BattleFormats && BattleFormats[formatid]) {
|
if (window.BattleFormats && BattleFormats[formatid]) {
|
||||||
return BattleFormats[formatid].name;
|
name = BattleFormats[formatid].name;
|
||||||
}
|
}
|
||||||
if (window.NonBattleGames && NonBattleGames[formatid]) {
|
if (window.NonBattleGames && NonBattleGames[formatid]) {
|
||||||
return NonBattleGames[formatid];
|
name = NonBattleGames[formatid];
|
||||||
}
|
}
|
||||||
return formatid;
|
if (name.startsWith('gen')) {
|
||||||
|
name = name.replace(/^gen([0-9])/, '[Gen $1] ');
|
||||||
|
}
|
||||||
|
if (name.startsWith(`[Gen ${Dex.gen}] `)) {
|
||||||
|
name = name.slice(`[Gen ${Dex.gen}] `.length);
|
||||||
|
} else if (name.startsWith(`[Gen ${Dex.gen} `)) {
|
||||||
|
name = '[' + name.slice(`[Gen ${Dex.gen} `.length);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static escapeHTML(str: string | number, jsEscapeToo?: boolean) {
|
static escapeHTML(str: string | number, jsEscapeToo?: boolean) {
|
||||||
|
|
|
||||||
3
play.pokemonshowdown.com/src/client-endload.ts
Normal file
3
play.pokemonshowdown.com/src/client-endload.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { PS } from "./client-main";
|
||||||
|
|
||||||
|
PS.libsLoaded.loaded();
|
||||||
|
|
@ -696,6 +696,18 @@ type ParsedClientCommands = {
|
||||||
[command: `parsed${string}`]: (this: PSRoom, target: string, cmd: string) => string | boolean | null | void,
|
[command: `parsed${string}`]: (this: PSRoom, target: string, cmd: string) => string | boolean | null | void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class PSLoadTracker extends Promise<void> {
|
||||||
|
resolver!: (value: void) => void;
|
||||||
|
constructor() {
|
||||||
|
super(resolve => {
|
||||||
|
this.resolver = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
loaded() {
|
||||||
|
this.resolver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As a PSStreamModel, PSRoom can emit `Args` to mean "we received a message",
|
* As a PSStreamModel, PSRoom can emit `Args` to mean "we received a message",
|
||||||
* and `null` to mean "tell Preact to re-render this room"
|
* and `null` to mean "tell Preact to re-render this room"
|
||||||
|
|
@ -728,11 +740,13 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
|
||||||
width = 0;
|
width = 0;
|
||||||
height = 0;
|
height = 0;
|
||||||
/**
|
/**
|
||||||
* popups sometimes initialize hidden, to calculate their position from their
|
* Preact means that the DOM state lags behind the app state. This means
|
||||||
* width/height without flickering. But hidden popups can't be focused, so
|
* rooms frequently have `display: none` at the time we want to focus them.
|
||||||
* we need to track their focus timing here.
|
* And popups sometimes initialize hidden, to calculate their position from
|
||||||
|
* their width/height without flickering. But hidden HTML elements can't be
|
||||||
|
* focused, so this is a note-to-self to focus the next time they can be.
|
||||||
*/
|
*/
|
||||||
hiddenInit = false;
|
focusNextUpdate = false;
|
||||||
parentElem: HTMLElement | null = null;
|
parentElem: HTMLElement | null = null;
|
||||||
parentRoomid: RoomID | null = null;
|
parentRoomid: RoomID | null = null;
|
||||||
rightPopup = false;
|
rightPopup = false;
|
||||||
|
|
@ -1134,6 +1148,8 @@ export const PS = new class extends PSModel {
|
||||||
|
|
||||||
newsHTML = document.querySelector('#room-news .mini-window-body')?.innerHTML || '';
|
newsHTML = document.querySelector('#room-news .mini-window-body')?.innerHTML || '';
|
||||||
|
|
||||||
|
libsLoaded = new PSLoadTracker();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
|
@ -1300,12 +1316,11 @@ export const PS = new class extends PSModel {
|
||||||
room = PS.rooms[roomid2];
|
room = PS.rooms[roomid2];
|
||||||
const [, type] = args;
|
const [, type] = args;
|
||||||
if (!room) {
|
if (!room) {
|
||||||
this.addRoom({
|
room = this.addRoom({
|
||||||
id: roomid2,
|
id: roomid2,
|
||||||
type,
|
type,
|
||||||
connected: true,
|
connected: true,
|
||||||
}, roomid === 'staff' || roomid === 'upperstaff');
|
}, roomid === 'staff' || roomid === 'upperstaff');
|
||||||
room = PS.rooms[roomid2];
|
|
||||||
} else {
|
} else {
|
||||||
room.type = type;
|
room.type = type;
|
||||||
room.connected = true;
|
room.connected = true;
|
||||||
|
|
@ -1471,8 +1486,11 @@ export const PS = new class extends PSModel {
|
||||||
if (this.leftPanel === room) this.leftPanel = newRoom;
|
if (this.leftPanel === room) this.leftPanel = newRoom;
|
||||||
if (this.rightPanel === room) this.rightPanel = newRoom;
|
if (this.rightPanel === room) this.rightPanel = newRoom;
|
||||||
if (this.panel === room) this.panel = newRoom;
|
if (this.panel === room) this.panel = newRoom;
|
||||||
if (this.room === room) this.room = newRoom;
|
|
||||||
if (roomid === '') this.mainmenu = newRoom as MainMenuRoom;
|
if (roomid === '') this.mainmenu = newRoom as MainMenuRoom;
|
||||||
|
if (this.room === room) {
|
||||||
|
this.room = newRoom;
|
||||||
|
newRoom.focusNextUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
|
@ -1490,7 +1508,7 @@ export const PS = new class extends PSModel {
|
||||||
}
|
}
|
||||||
this.closePopupsAbove(room, true);
|
this.closePopupsAbove(room, true);
|
||||||
if (!this.isVisible(room)) {
|
if (!this.isVisible(room)) {
|
||||||
room.hiddenInit = true;
|
room.focusNextUpdate = true;
|
||||||
}
|
}
|
||||||
if (PS.isNormalRoom(room)) {
|
if (PS.isNormalRoom(room)) {
|
||||||
if (room.location === 'right' && !this.prefs.onepanel) {
|
if (room.location === 'right' && !this.prefs.onepanel) {
|
||||||
|
|
@ -1574,42 +1592,6 @@ export const PS = new class extends PSModel {
|
||||||
}
|
}
|
||||||
return this.focusRoom(rooms[index + 1]);
|
return this.focusRoom(rooms[index + 1]);
|
||||||
}
|
}
|
||||||
focusPreview(room: PSRoom) {
|
|
||||||
if (room !== this.room) return '';
|
|
||||||
|
|
||||||
const verticalBuf = this.verticalFocusPreview();
|
|
||||||
if (verticalBuf) return verticalBuf;
|
|
||||||
|
|
||||||
const isMiniRoom = this.room.location === 'mini-window';
|
|
||||||
const { rooms, index } = this.horizontalNav();
|
|
||||||
if (index === -1) return '';
|
|
||||||
|
|
||||||
let buf = ' ';
|
|
||||||
const leftRoom = this.rooms[rooms[index - 1]];
|
|
||||||
if (leftRoom) buf += `\u2190 ${leftRoom.title}`;
|
|
||||||
buf += (this.arrowKeysUsed || isMiniRoom ? " | " : " (use arrow keys) ");
|
|
||||||
const rightRoom = this.rooms[rooms[index + 1]];
|
|
||||||
if (rightRoom) buf += `${rightRoom.title} \u2192`;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
verticalFocusPreview() {
|
|
||||||
const { rooms, index } = this.verticalNav();
|
|
||||||
if (index === -1) return '';
|
|
||||||
|
|
||||||
const upRoom = this.rooms[rooms[index - 1]];
|
|
||||||
let downRoom = this.rooms[rooms[index + 1]];
|
|
||||||
if (index === rooms.length - 2 && rooms[index + 1] === 'news') downRoom = undefined;
|
|
||||||
if (!upRoom && !downRoom) return '';
|
|
||||||
|
|
||||||
let buf = ' ';
|
|
||||||
// const altLabel = navigator.platform?.startsWith('Mac') ? '⌥' : 'ᴀʟᴛ';
|
|
||||||
const altLabel = navigator.platform?.startsWith('Mac') ? 'ᴏᴘᴛ' : 'ᴀʟᴛ';
|
|
||||||
if (upRoom) buf += `${altLabel}\u2191 ${upRoom.title}`;
|
|
||||||
buf += " | ";
|
|
||||||
if (downRoom) buf += `${altLabel}\u2193 ${downRoom.title}`;
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
alert(message: string) {
|
alert(message: string) {
|
||||||
this.join(`popup-${this.popups.length}` as RoomID, {
|
this.join(`popup-${this.popups.length}` as RoomID, {
|
||||||
args: { message },
|
args: { message },
|
||||||
|
|
@ -1676,6 +1658,7 @@ export const PS = new class extends PSModel {
|
||||||
room.receiveLine(args);
|
room.receiveLine(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!noFocus) room.focusNextUpdate = true;
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
hideRightRoom() {
|
hideRightRoom() {
|
||||||
|
|
@ -1809,6 +1792,7 @@ export const PS = new class extends PSModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeRoom(room: PSRoom) {
|
removeRoom(room: PSRoom) {
|
||||||
|
const wasFocused = this.room === room;
|
||||||
room.destroy();
|
room.destroy();
|
||||||
delete PS.rooms[room.id];
|
delete PS.rooms[room.id];
|
||||||
|
|
||||||
|
|
@ -1848,7 +1832,9 @@ export const PS = new class extends PSModel {
|
||||||
PS.room = this.popups.length ? PS.rooms[this.popups[this.popups.length - 1]]! : PS.panel;
|
PS.room = this.popups.length ? PS.rooms[this.popups[this.popups.length - 1]]! : PS.panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
PS.setFocus(PS.room);
|
if (wasFocused) {
|
||||||
|
this.room.focusNextUpdate = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/** do NOT use this in a while loop: see `closePopupsUntil */
|
/** do NOT use this in a while loop: see `closePopupsUntil */
|
||||||
closePopup(skipUpdate?: boolean) {
|
closePopup(skipUpdate?: boolean) {
|
||||||
|
|
|
||||||
|
|
@ -662,7 +662,7 @@ export class TournamentBracket extends preact.Component<{
|
||||||
export class TournamentTreeBracket extends preact.Component<{
|
export class TournamentTreeBracket extends preact.Component<{
|
||||||
data: TournamentTreeBracketData, abbreviated?: boolean,
|
data: TournamentTreeBracketData, abbreviated?: boolean,
|
||||||
}> {
|
}> {
|
||||||
d3Loaded = true;
|
d3Loader: Promise<void> | null = null;
|
||||||
forEachTreeNode<T extends TreeNode>(node: T, callback: (node: T, depth: number) => void, depth = 0) {
|
forEachTreeNode<T extends TreeNode>(node: T, callback: (node: T, depth: number) => void, depth = 0) {
|
||||||
callback(node, depth);
|
callback(node, depth);
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
|
|
@ -678,11 +678,15 @@ export class TournamentTreeBracket extends preact.Component<{
|
||||||
}
|
}
|
||||||
return clonedNode;
|
return clonedNode;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Customize tree size. Height is for a single player, a full node is double that.
|
||||||
|
*/
|
||||||
static nodeSize = {
|
static nodeSize = {
|
||||||
width: 160, height: 30,
|
width: 160, height: 15,
|
||||||
radius: 5,
|
radius: 5,
|
||||||
separationX: 20, separationY: 10,
|
separationX: 20, separationY: 10,
|
||||||
textOffset: -1,
|
// Safari bug: some issue with dominant-baseline. whatever, we can just manually v-align text
|
||||||
|
textOffset: 4,
|
||||||
};
|
};
|
||||||
generateTreeBracket(data: TournamentTreeBracketData, abbreviated?: boolean) {
|
generateTreeBracket(data: TournamentTreeBracketData, abbreviated?: boolean) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
|
|
@ -698,11 +702,13 @@ export class TournamentTreeBracket extends preact.Component<{
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
if (!window.d3) {
|
if (!window.d3) {
|
||||||
this.d3Loaded = false;
|
|
||||||
div.innerHTML = `<b>d3 not loaded yet</b>`;
|
div.innerHTML = `<b>d3 not loaded yet</b>`;
|
||||||
|
this.d3Loader ||= PS.libsLoaded.then(() => {
|
||||||
|
this.forceUpdate();
|
||||||
|
});
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
this.d3Loaded = true;
|
this.d3Loader = null;
|
||||||
|
|
||||||
let name = PS.user.name;
|
let name = PS.user.name;
|
||||||
|
|
||||||
|
|
@ -729,9 +735,14 @@ export class TournamentTreeBracket extends preact.Component<{
|
||||||
child.highlightLink = true;
|
child.highlightLink = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (node.state === 'inprogress' || node.state === 'available' || node.state === 'challenging') {
|
} else if (
|
||||||
|
node.state === 'inprogress' || node.state === 'available' || node.state === 'challenging' ||
|
||||||
|
node.state === 'unavailable'
|
||||||
|
) {
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
child.highlightLink = true;
|
if (child.team && !child.team.startsWith('(')) {
|
||||||
|
child.highlightLink = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (highlightName) {
|
} else if (highlightName) {
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
|
|
@ -755,24 +766,26 @@ export class TournamentTreeBracket extends preact.Component<{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Setting `breadthCompression` to 0.8 for 3+ depths with leaves is
|
||||||
|
// an extremely approximate guess for how tall a double+ elim tree
|
||||||
|
// should be. The old algorithm also approximated, but its
|
||||||
|
// approximation was arguably worse. This one is basically perfect
|
||||||
|
// for single elim and pretty good for double elim.
|
||||||
const depthsWithLeaves = hasLeafAtDepth.filter(Boolean).length;
|
const depthsWithLeaves = hasLeafAtDepth.filter(Boolean).length;
|
||||||
const breadthCompression = depthsWithLeaves > 2 ? 0.8 : 2;
|
const breadthCompression = depthsWithLeaves > 2 ? 0.8 : 2;
|
||||||
const maxBreadth = numLeaves - (depthsWithLeaves - 1) / breadthCompression;
|
const maxBreadth = numLeaves - (depthsWithLeaves - 1) / breadthCompression;
|
||||||
const maxDepth = hasLeafAtDepth.length;
|
const maxDepth = hasLeafAtDepth.length;
|
||||||
|
|
||||||
const nodeSize: any = { ...TournamentTreeBracket.nodeSize };
|
const nodeSize = TournamentTreeBracket.nodeSize;
|
||||||
nodeSize.realWidth = nodeSize.width;
|
|
||||||
nodeSize.realHeight = nodeSize.height;
|
|
||||||
nodeSize.smallRealHeight = nodeSize.height / 2;
|
|
||||||
const size = {
|
const size = {
|
||||||
width: nodeSize.realWidth * maxDepth + nodeSize.separationX * (maxDepth + 1),
|
width: nodeSize.width * maxDepth + nodeSize.separationX * (maxDepth + 1),
|
||||||
height: nodeSize.realHeight * (maxBreadth + 0.5) + nodeSize.separationY * maxBreadth,
|
height: nodeSize.height * 2 * (maxBreadth + 0.5) + nodeSize.separationY * maxBreadth,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make d3 layout the tree
|
// Make d3 layout the tree
|
||||||
|
|
||||||
const tree = d3.layout.tree<TournamentElimBracketNode>()
|
const tree = d3.layout.tree<TournamentElimBracketNode>()
|
||||||
.size([size.height, size.width - nodeSize.realWidth - nodeSize.separationX])
|
.size([size.height, size.width - nodeSize.width - nodeSize.separationX])
|
||||||
.separation(() => 1)
|
.separation(() => 1)
|
||||||
.children(node => (
|
.children(node => (
|
||||||
node.children?.length ? node.children : null!
|
node.children?.length ? node.children : null!
|
||||||
|
|
@ -785,16 +798,16 @@ export class TournamentTreeBracket extends preact.Component<{
|
||||||
const layoutRoot = d3.select(div)
|
const layoutRoot = d3.select(div)
|
||||||
.append('svg:svg').attr('width', size.width).attr('height', size.height)
|
.append('svg:svg').attr('width', size.width).attr('height', size.height)
|
||||||
.append('svg:g')
|
.append('svg:g')
|
||||||
.attr('transform', `translate(${-(nodeSize.realWidth) / 2 - 6},0)`);
|
.attr('transform', `translate(${-(nodeSize.width) / 2 - 6},0)`);
|
||||||
|
|
||||||
// Style the links between the nodes
|
// Style the links between the nodes
|
||||||
|
|
||||||
const diagonalLink = d3.svg.diagonal()
|
const diagonalLink = d3.svg.diagonal()
|
||||||
.source(link => ({
|
.source(link => ({
|
||||||
x: link.source.x, y: link.source.y + nodeSize.realWidth / 2,
|
x: link.source.x, y: link.source.y + nodeSize.width / 2,
|
||||||
}))
|
}))
|
||||||
.target(link => ({
|
.target(link => ({
|
||||||
x: link.target.x, y: link.target.y - nodeSize.realWidth / 2,
|
x: link.target.x, y: link.target.y - nodeSize.width / 2,
|
||||||
}))
|
}))
|
||||||
.projection(link => [
|
.projection(link => [
|
||||||
size.width - link.y, link.x,
|
size.width - link.y, link.x,
|
||||||
|
|
@ -818,8 +831,8 @@ export class TournamentTreeBracket extends preact.Component<{
|
||||||
const outerElem = elem;
|
const outerElem = elem;
|
||||||
|
|
||||||
if (node.abbreviated) {
|
if (node.abbreviated) {
|
||||||
elem.append('svg:text').attr('y', -nodeSize.realHeight / 4 + 4)
|
elem.append('svg:text').attr('y', -nodeSize.height / 2 + 4)
|
||||||
.attr('x', -nodeSize.realWidth / 2 - 7).classed('tournament-bracket-tree-abbreviated', true)
|
.attr('x', -nodeSize.width / 2 - 7).classed('tournament-bracket-tree-abbreviated', true)
|
||||||
.text('...');
|
.text('...');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -841,28 +854,29 @@ export class TournamentTreeBracket extends preact.Component<{
|
||||||
if (node.team && !node.team1 && !node.team2) {
|
if (node.team && !node.team1 && !node.team2) {
|
||||||
const rect = elem.append('svg:rect').classed('tournament-bracket-tree-draw', true)
|
const rect = elem.append('svg:rect').classed('tournament-bracket-tree-draw', true)
|
||||||
.attr('rx', nodeSize.radius)
|
.attr('rx', nodeSize.radius)
|
||||||
.attr('x', -nodeSize.realWidth / 2).attr('width', nodeSize.realWidth);
|
.attr('x', -nodeSize.width / 2).attr('width', nodeSize.width)
|
||||||
rect.attr('y', -nodeSize.smallRealHeight / 2).attr('height', nodeSize.smallRealHeight);
|
.attr('y', -nodeSize.height / 2).attr('height', nodeSize.height);
|
||||||
if (node.team === name) rect.attr('stroke-dasharray', '5,5').attr('stroke-width', 2);
|
if (node.team === name) rect.attr('stroke-dasharray', '5,5').attr('stroke-width', 2);
|
||||||
|
|
||||||
elem.append('svg:text').classed('tournament-bracket-tree-node-team', true)
|
elem.append('svg:text').classed('tournament-bracket-tree-node-team', true)
|
||||||
|
.attr('y', nodeSize.textOffset)
|
||||||
.classed('tournament-bracket-tree-node-team-draw', true)
|
.classed('tournament-bracket-tree-node-team-draw', true)
|
||||||
.text(node.team || '');
|
.text(node.team || '');
|
||||||
} else {
|
} else {
|
||||||
const rect1 = elem.append('svg:rect')
|
const rect1 = elem.append('svg:rect')
|
||||||
.attr('rx', nodeSize.radius)
|
.attr('rx', nodeSize.radius)
|
||||||
.attr('x', -nodeSize.realWidth / 2).attr('width', nodeSize.realWidth)
|
.attr('x', -nodeSize.width / 2).attr('width', nodeSize.width)
|
||||||
.attr('y', -nodeSize.smallRealHeight).attr('height', nodeSize.smallRealHeight);
|
.attr('y', -nodeSize.height).attr('height', nodeSize.height);
|
||||||
const rect2 = elem.append('svg:rect')
|
const rect2 = elem.append('svg:rect')
|
||||||
.attr('rx', nodeSize.radius)
|
.attr('rx', nodeSize.radius)
|
||||||
.attr('x', -nodeSize.realWidth / 2).attr('width', nodeSize.realWidth)
|
.attr('x', -nodeSize.width / 2).attr('width', nodeSize.width)
|
||||||
.attr('y', 0).attr('height', nodeSize.smallRealHeight);
|
.attr('y', 0).attr('height', nodeSize.height);
|
||||||
if (node.team1 === name) rect1.attr('stroke-dasharray', '5,5').attr('stroke-width', 2);
|
if (node.team1 === name) rect1.attr('stroke-dasharray', '5,5').attr('stroke-width', 2);
|
||||||
if (node.team2 === name) rect2.attr('stroke-dasharray', '5,5').attr('stroke-width', 2);
|
if (node.team2 === name) rect2.attr('stroke-dasharray', '5,5').attr('stroke-width', 2);
|
||||||
|
|
||||||
const row1 = elem.append('svg:text').attr('y', -nodeSize.realHeight / 4 + nodeSize.textOffset)
|
const row1 = elem.append('svg:text').attr('y', -nodeSize.height / 2 + nodeSize.textOffset)
|
||||||
.classed('tournament-bracket-tree-node-row1', true);
|
.classed('tournament-bracket-tree-node-row1', true);
|
||||||
const row2 = elem.append('svg:text').attr('y', nodeSize.realHeight / 4 + nodeSize.textOffset)
|
const row2 = elem.append('svg:text').attr('y', nodeSize.height / 2 + nodeSize.textOffset)
|
||||||
.classed('tournament-bracket-tree-node-row2', true);
|
.classed('tournament-bracket-tree-node-row2', true);
|
||||||
|
|
||||||
const team1 = row1.append('svg:tspan').classed('tournament-bracket-tree-team', true)
|
const team1 = row1.append('svg:tspan').classed('tournament-bracket-tree-team', true)
|
||||||
|
|
@ -907,7 +921,7 @@ export class TournamentTreeBracket extends preact.Component<{
|
||||||
this.base!.appendChild(this.generateTreeBracket(this.props.data, this.props.abbreviated));
|
this.base!.appendChild(this.generateTreeBracket(this.props.data, this.props.abbreviated));
|
||||||
}
|
}
|
||||||
override shouldComponentUpdate(props: { data: TournamentTreeBracketData }) {
|
override shouldComponentUpdate(props: { data: TournamentTreeBracketData }) {
|
||||||
if (props.data === this.props.data && this.d3Loaded) return false;
|
if (props.data === this.props.data && !this.d3Loader) return false;
|
||||||
this.base!.replaceChild(this.generateTreeBracket(props.data), this.base!.children[0]);
|
this.base!.replaceChild(this.generateTreeBracket(props.data), this.base!.children[0]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -923,7 +937,7 @@ export class TourPopOutPanel extends PSRoomPanel {
|
||||||
static readonly noURL = true;
|
static readonly noURL = true;
|
||||||
override componentDidMount() {
|
override componentDidMount() {
|
||||||
const tour = this.props.room.args?.tour as ChatTournament;
|
const tour = this.props.room.args?.tour as ChatTournament;
|
||||||
if (tour) this.subscribeTo(tour, () => this.forceUpdate());
|
if (tour) this.subscribeTo(tour);
|
||||||
}
|
}
|
||||||
override render() {
|
override render() {
|
||||||
const room = this.props.room;
|
const room = this.props.room;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
import preact from "../js/lib/preact";
|
import preact from "../js/lib/preact";
|
||||||
import type { PSSubscription } from "./client-core";
|
import type { PSSubscription } from "./client-core";
|
||||||
import { PS, PSRoom, type RoomOptions, type RoomID, type Team } from "./client-main";
|
import { PS, PSRoom, type RoomOptions, type RoomID, type Team } from "./client-main";
|
||||||
import { PSMain, PSPanelWrapper, PSRoomPanel } from "./panels";
|
import { PSView, PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||||
import { TeamForm } from "./panel-mainmenu";
|
import { TeamForm } from "./panel-mainmenu";
|
||||||
import { BattleLog } from "./battle-log";
|
import { BattleLog } from "./battle-log";
|
||||||
import type { Battle } from "./battle";
|
import type { Battle } from "./battle";
|
||||||
|
|
@ -113,7 +113,8 @@ export class ChatRoom extends PSRoom {
|
||||||
this.title = `Console`;
|
this.title = `Console`;
|
||||||
} else if (this.id.startsWith('dm-')) {
|
} else if (this.id.startsWith('dm-')) {
|
||||||
const id = this.id.slice(3);
|
const id = this.id.slice(3);
|
||||||
if (!name || toID(name) !== id) name = this.pmTarget || id;
|
if (toID(name) !== id) name = null;
|
||||||
|
name ||= this.pmTarget || id;
|
||||||
if (/[A-Za-z0-9]/.test(name.charAt(0))) name = ` ${name}`;
|
if (/[A-Za-z0-9]/.test(name.charAt(0))) name = ` ${name}`;
|
||||||
const nameWithGroup = name;
|
const nameWithGroup = name;
|
||||||
name = name.slice(1);
|
name = name.slice(1);
|
||||||
|
|
@ -194,7 +195,7 @@ export class ChatRoom extends PSRoom {
|
||||||
if (!formatTargeting ||
|
if (!formatTargeting ||
|
||||||
formats[formatId] ||
|
formats[formatId] ||
|
||||||
gens[formatId.slice(0, 4)] ||
|
gens[formatId.slice(0, 4)] ||
|
||||||
(gens['gen6'] && formatId.substr(0, 3) !== 'gen')) {
|
(gens['gen6'] && !formatId.startsWith('gen'))) {
|
||||||
buffer += '<tr>';
|
buffer += '<tr>';
|
||||||
} else {
|
} else {
|
||||||
buffer += '<tr class="hidden">';
|
buffer += '<tr class="hidden">';
|
||||||
|
|
@ -704,10 +705,10 @@ export class ChatTextEntry extends preact.Component<{
|
||||||
onInput={this.update}
|
onInput={this.update}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
style={{ resize: 'none', width: '100%', height: '16px', padding: '2px 3px 1px 3px' }}
|
style={{ resize: 'none', width: '100%', height: '16px', padding: '2px 3px 1px 3px' }}
|
||||||
placeholder={PS.focusPreview(this.props.room)}
|
placeholder={PSView.focusPreview(this.props.room)}
|
||||||
/> : <ChatTextBox
|
/> : <ChatTextBox
|
||||||
disabled={!this.props.room.connected || !canTalk}
|
disabled={!this.props.room.connected || !canTalk}
|
||||||
placeholder={PS.focusPreview(this.props.room)}
|
placeholder={PSView.focusPreview(this.props.room)}
|
||||||
/>}
|
/>}
|
||||||
</form>
|
</form>
|
||||||
{!canTalk && <button data-href="login" class="button autofocus">
|
{!canTalk && <button data-href="login" class="button autofocus">
|
||||||
|
|
@ -725,14 +726,14 @@ class ChatTextBox extends preact.Component<{ placeholder: string, disabled?: boo
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
handleFocus = () => {
|
handleFocus = () => {
|
||||||
PSMain.setTextboxFocused(true);
|
PSView.setTextboxFocused(true);
|
||||||
};
|
};
|
||||||
handleBlur = () => {
|
handleBlur = () => {
|
||||||
PSMain.setTextboxFocused(false);
|
PSView.setTextboxFocused(false);
|
||||||
};
|
};
|
||||||
override render() {
|
override render() {
|
||||||
return <pre
|
return <pre
|
||||||
class={`textbox textbox-empty ${this.props.disabled ? ' disabled' : ''}`} placeholder={this.props.placeholder}
|
class={`textbox textbox-empty ${this.props.disabled ? ' disabled' : ' autofocus'}`} placeholder={this.props.placeholder}
|
||||||
onFocus={this.handleFocus} onBlur={this.handleBlur}
|
onFocus={this.handleFocus} onBlur={this.handleBlur}
|
||||||
>{'\n'}</pre>;
|
>{'\n'}</pre>;
|
||||||
}
|
}
|
||||||
|
|
@ -745,10 +746,10 @@ class ChatPanel extends PSRoomPanel<ChatRoom> {
|
||||||
static readonly location = 'right';
|
static readonly location = 'right';
|
||||||
static readonly icon = <i class="fa fa-comment-o"></i>;
|
static readonly icon = <i class="fa fa-comment-o"></i>;
|
||||||
override componentDidMount(): void {
|
override componentDidMount(): void {
|
||||||
|
super.componentDidMount();
|
||||||
this.subscribeTo(PS.user, () => {
|
this.subscribeTo(PS.user, () => {
|
||||||
this.props.room.updateTarget();
|
this.props.room.updateTarget();
|
||||||
});
|
});
|
||||||
super.componentDidMount();
|
|
||||||
}
|
}
|
||||||
send = (text: string) => {
|
send = (text: string) => {
|
||||||
this.props.room.send(text);
|
this.props.room.send(text);
|
||||||
|
|
@ -818,7 +819,7 @@ class ChatPanel extends PSRoomPanel<ChatRoom> {
|
||||||
|
|
||||||
return <PSPanelWrapper room={room} focusClick>
|
return <PSPanelWrapper room={room} focusClick>
|
||||||
<ChatLog class="chat-log" room={this.props.room} left={tinyLayout ? 0 : 146} top={room.tour?.info.isActive ? 30 : 0}>
|
<ChatLog class="chat-log" room={this.props.room} left={tinyLayout ? 0 : 146} top={room.tour?.info.isActive ? 30 : 0}>
|
||||||
{challengeTo || challengeFrom && [challengeTo, challengeFrom]}
|
{challengeTo}{challengeFrom}
|
||||||
</ChatLog>
|
</ChatLog>
|
||||||
{room.tour && <TournamentBox tour={room.tour} left={tinyLayout ? 0 : 146} />}
|
{room.tour && <TournamentBox tour={room.tour} left={tinyLayout ? 0 : 146} />}
|
||||||
<ChatTextEntry
|
<ChatTextEntry
|
||||||
|
|
|
||||||
|
|
@ -229,9 +229,7 @@ class LadderListPanel extends PSRoomPanel {
|
||||||
static readonly title = 'Ladder';
|
static readonly title = 'Ladder';
|
||||||
|
|
||||||
override componentDidMount() {
|
override componentDidMount() {
|
||||||
this.subscribeTo(PS.teams, () => {
|
this.subscribeTo(PS.teams);
|
||||||
this.forceUpdate();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
renderList() {
|
renderList() {
|
||||||
if (!window.BattleFormats) {
|
if (!window.BattleFormats) {
|
||||||
|
|
|
||||||
|
|
@ -129,22 +129,19 @@ class UserPanel extends PSRoomPanel<UserRoom> {
|
||||||
buttonbar.push(isSelf ? (
|
buttonbar.push(isSelf ? (
|
||||||
<p class="buttonbar">
|
<p class="buttonbar">
|
||||||
<button class="button" disabled>Challenge</button> {}
|
<button class="button" disabled>Challenge</button> {}
|
||||||
<button class="button" data-href="/dm-">Chat Self</button>
|
<button class="button" data-href="dm-">Chat Self</button>
|
||||||
</p>
|
</p>
|
||||||
) : !PS.user.named ? (
|
) : !PS.user.named ? (
|
||||||
<p class="buttonbar">
|
<p class="buttonbar">
|
||||||
<button class="button" disabled>Challenge</button> {}
|
<button class="button" disabled>Challenge</button> {}
|
||||||
<button class="button" disabled>Chat</button>
|
<button class="button" disabled>Chat</button> {}
|
||||||
|
<button class="button" disabled>{'\u2026'}</button>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p class="buttonbar">
|
<p class="buttonbar">
|
||||||
<button class="button" data-href={`/challenge-${user.userid}`}>Challenge</button> {}
|
<button class="button" data-href={`challenge-${user.userid}`}>Challenge</button> {}
|
||||||
<button class="button" data-href={`/dm-${user.userid}`}>Chat</button> {}
|
<button class="button" data-href={`dm-${user.userid}`}>Chat</button> {}
|
||||||
<button
|
<button class="button" data-href={`useroptions-${user.userid}-${room.parentRoomid || ''}`}>{'\u2026'}</button>
|
||||||
class="button"
|
|
||||||
data-href="/useroptions"
|
|
||||||
value={`${room.userid as string},${room.parentRoomid as string}`}
|
|
||||||
>{'\u2026'}</button>
|
|
||||||
</p>
|
</p>
|
||||||
));
|
));
|
||||||
if (isSelf) {
|
if (isSelf) {
|
||||||
|
|
@ -158,13 +155,13 @@ class UserPanel extends PSRoomPanel<UserRoom> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const avatar = user.avatar !== '[loading]' ? Dex.resolveAvatar(`${user.avatar || 'unknown'}`) : null;
|
||||||
return [<div class="userdetails">
|
return [<div class="userdetails">
|
||||||
{user.avatar !== '[loading]' &&
|
{avatar && (room.isSelf ? (
|
||||||
<img
|
<img src={avatar} class="trainersprite yours" data-href="avatars" />
|
||||||
{...(room.isSelf ? { 'data-href': 'avatars' } : {})}
|
) : (
|
||||||
class={'trainersprite' + (room.isSelf ? ' yours' : '')}
|
<img src={avatar} class="trainersprite" />
|
||||||
src={Dex.resolveAvatar(`${user.avatar || 'unknown'}`)}
|
))}
|
||||||
/>}
|
|
||||||
<strong><a
|
<strong><a
|
||||||
href={`//${Config.routes.users}/${user.userid}`} target="_blank"
|
href={`//${Config.routes.users}/${user.userid}`} target="_blank"
|
||||||
style={{ color: away ? '#888888' : BattleLog.usernameColor(user.userid) }}
|
style={{ color: away ? '#888888' : BattleLog.usernameColor(user.userid) }}
|
||||||
|
|
@ -201,7 +198,7 @@ class UserPanel extends PSRoomPanel<UserRoom> {
|
||||||
return <PSPanelWrapper room={room}><div class="pad">
|
return <PSPanelWrapper room={room}><div class="pad">
|
||||||
{showLookup && <form onSubmit={this.lookup} style={{ minWidth: '278px' }}>
|
{showLookup && <form onSubmit={this.lookup} style={{ minWidth: '278px' }}>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
Username:
|
Username: {}
|
||||||
<input type="search" name="username" class="textbox autofocus" onInput={this.maybeReset} onChange={this.maybeReset} />
|
<input type="search" name="username" class="textbox autofocus" onInput={this.maybeReset} onChange={this.maybeReset} />
|
||||||
</label>
|
</label>
|
||||||
{!room.userid && <p class="buttonbar">
|
{!room.userid && <p class="buttonbar">
|
||||||
|
|
@ -218,7 +215,7 @@ class UserPanel extends PSRoomPanel<UserRoom> {
|
||||||
|
|
||||||
class UserOptionsPanel extends PSRoomPanel {
|
class UserOptionsPanel extends PSRoomPanel {
|
||||||
static readonly id = 'useroptions';
|
static readonly id = 'useroptions';
|
||||||
static readonly routes = ['useroptions'];
|
static readonly routes = ['useroptions-*'];
|
||||||
static readonly location = 'popup';
|
static readonly location = 'popup';
|
||||||
static readonly noURL = true;
|
static readonly noURL = true;
|
||||||
declare state: {
|
declare state: {
|
||||||
|
|
@ -228,6 +225,12 @@ class UserOptionsPanel extends PSRoomPanel {
|
||||||
requestSent?: boolean,
|
requestSent?: boolean,
|
||||||
data?: Record<string, string>,
|
data?: Record<string, string>,
|
||||||
};
|
};
|
||||||
|
getTargets() {
|
||||||
|
const [, targetUser, targetRoomid] = this.props.room.id.split('-');
|
||||||
|
let targetRoom = (PS.rooms[targetRoomid] || null) as ChatRoom | null;
|
||||||
|
if (targetRoom?.type !== 'chat') targetRoom = null;
|
||||||
|
return { targetUser: targetUser as ID, targetRoomid: targetRoomid as RoomID, targetRoom };
|
||||||
|
}
|
||||||
|
|
||||||
handleMute = (ev: Event) => {
|
handleMute = (ev: Event) => {
|
||||||
this.setState({ showMuteInput: true, showBanInput: false });
|
this.setState({ showMuteInput: true, showBanInput: false });
|
||||||
|
|
@ -247,52 +250,42 @@ class UserOptionsPanel extends PSRoomPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleConfirm = (ev: Event) => {
|
handleConfirm = (ev: Event) => {
|
||||||
let data = this.state.data;
|
const data = this.state.data;
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
let roomid = toRoomid(data.room);
|
const { targetUser, targetRoom } = this.getTargets();
|
||||||
let room = PS.rooms[roomid];
|
|
||||||
|
|
||||||
let cmd = '';
|
let cmd = '';
|
||||||
if (data.action === "Mute") {
|
if (data.action === "Mute") {
|
||||||
cmd += data.duration === "1 hour" ? "/hourmute " : "/mute ";
|
cmd += data.duration === "1 hour" ? "/hourmute " : "/mute ";
|
||||||
cmd += `${data.targetUser} ${data.reason ? ',' + data.reason : ''}`;
|
cmd += `${targetUser} ${data.reason ? ',' + data.reason : ''}`;
|
||||||
} else {
|
} else {
|
||||||
cmd += data.duration === "1 week" ? "/weekban " : "/ban ";
|
cmd += data.duration === "1 week" ? "/weekban " : "/ban ";
|
||||||
cmd += `${data.targetUser} ${data.reason ? ',' + data.reason : ''}`;
|
cmd += `${targetUser} ${data.reason ? ',' + data.reason : ''}`;
|
||||||
}
|
}
|
||||||
room?.send(cmd);
|
targetRoom?.send(cmd);
|
||||||
this.close();
|
this.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAddFriend = (ev: Event) => {
|
handleAddFriend = (ev: Event) => {
|
||||||
let args = (this.props.room?.parentElem as HTMLInputElement).value.split(",");
|
const { targetUser, targetRoom } = this.getTargets();
|
||||||
let [targetUser, roomid] = args;
|
targetRoom?.send(`/friend add ${targetUser}`);
|
||||||
PS.rooms[roomid]?.send(`/friend add ${targetUser}`);
|
|
||||||
this.setState({ requestSent: true });
|
this.setState({ requestSent: true });
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopImmediatePropagation();
|
ev.stopImmediatePropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
handleIgnore = () => {
|
handleIgnore = () => {
|
||||||
let args = (this.props.room?.parentElem as HTMLInputElement).value.split(",");
|
const { targetUser, targetRoom } = this.getTargets();
|
||||||
let [targetUser, roomid] = args;
|
targetRoom?.send(`/ignore ${targetUser}`);
|
||||||
let room = PS.rooms[roomid];
|
|
||||||
room?.send(`/ignore ${targetUser}`);
|
|
||||||
this.close();
|
this.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
muteUser = (ev: Event) => {
|
muteUser = (ev: Event) => {
|
||||||
this.setState({ showMuteInput: false });
|
this.setState({ showMuteInput: false });
|
||||||
let hrMute = (ev.currentTarget as HTMLButtonElement).value === "1hr";
|
const hrMute = (ev.currentTarget as HTMLButtonElement).value === "1hr";
|
||||||
let args = (this.props.room?.parentElem as HTMLInputElement).value.split(",");
|
const reason = this.base?.querySelector<HTMLInputElement>("input[name=mutereason]")?.value;
|
||||||
let [targetUser, roomid] = args;
|
const data = {
|
||||||
let room = PS.rooms[roomid];
|
|
||||||
if (room?.type !== "chat") return; // should never happen
|
|
||||||
let reason = this.base?.querySelector<HTMLInputElement>("input[name=mutereason]")?.value;
|
|
||||||
let data = {
|
|
||||||
action: 'Mute',
|
action: 'Mute',
|
||||||
targetUser,
|
|
||||||
room: room?.title,
|
|
||||||
reason,
|
reason,
|
||||||
duration: hrMute ? "1 hour" : "7 minutes",
|
duration: hrMute ? "1 hour" : "7 minutes",
|
||||||
};
|
};
|
||||||
|
|
@ -303,16 +296,10 @@ class UserOptionsPanel extends PSRoomPanel {
|
||||||
|
|
||||||
banUser = (ev: Event) => {
|
banUser = (ev: Event) => {
|
||||||
this.setState({ showBanInput: false });
|
this.setState({ showBanInput: false });
|
||||||
let weekBan = (ev.currentTarget as HTMLButtonElement).value === "1wk";
|
const weekBan = (ev.currentTarget as HTMLButtonElement).value === "1wk";
|
||||||
let args = (this.props.room?.parentElem as HTMLInputElement).value.split(",");
|
const reason = this.base?.querySelector<HTMLInputElement>("input[name=banreason]")?.value;
|
||||||
let [targetUser, roomid] = args;
|
const data = {
|
||||||
let room = PS.rooms[roomid];
|
|
||||||
if (room?.type !== "chat") return; // should never happen
|
|
||||||
let reason = this.base?.querySelector<HTMLInputElement>("input[name=banreason]")?.value;
|
|
||||||
let data = {
|
|
||||||
action: 'Ban',
|
action: 'Ban',
|
||||||
targetUser,
|
|
||||||
room: room?.title,
|
|
||||||
reason,
|
reason,
|
||||||
duration: weekBan ? "1 week" : "2 days",
|
duration: weekBan ? "1 week" : "2 days",
|
||||||
};
|
};
|
||||||
|
|
@ -321,20 +308,16 @@ class UserOptionsPanel extends PSRoomPanel {
|
||||||
ev.stopImmediatePropagation();
|
ev.stopImmediatePropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
update = () => {
|
|
||||||
this.forceUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const room = this.props.room;
|
const room = this.props.room;
|
||||||
const parentRoom = PS.rooms[this.props.room.parentRoomid! || ''] as ChatRoom;
|
|
||||||
let canMute = false;
|
let canMute = false;
|
||||||
let canBan = false;
|
let canBan = false;
|
||||||
if (parentRoom?.type === "chat") {
|
const { targetUser, targetRoom } = this.getTargets();
|
||||||
let banPerms = ["@", "#", "~"];
|
if (targetRoom) {
|
||||||
let mutePerms = ["%", ...banPerms];
|
const banPerms = ["@", "#", "~"];
|
||||||
canMute = mutePerms.includes(parentRoom.users[PS.user.userid].charAt(0));
|
const mutePerms = ["%", ...banPerms];
|
||||||
canBan = banPerms.includes(parentRoom.users[PS.user.userid].charAt(0));
|
canMute = mutePerms.includes(targetRoom.users[PS.user.userid]?.charAt(0));
|
||||||
|
canBan = banPerms.includes(targetRoom.users[PS.user.userid]?.charAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return <PSPanelWrapper room={room} width={280}><div class="pad">
|
return <PSPanelWrapper room={room} width={280}><div class="pad">
|
||||||
|
|
@ -344,16 +327,15 @@ class UserOptionsPanel extends PSRoomPanel {
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<button
|
<button data-href={`view-help-request-report-user-${targetUser}`} class="button">
|
||||||
class="button"
|
|
||||||
data-href={`view-help-request-report-user-${(room.parentElem as HTMLInputElement).value.split(",")[0]}`}
|
|
||||||
>
|
|
||||||
Report
|
Report
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{this.state.requestSent ? (
|
{this.state.requestSent ? (
|
||||||
<button class="button disabled"> Sent request </button>
|
<button class="button disabled">
|
||||||
|
Sent request
|
||||||
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={this.handleAddFriend} class="button">
|
<button onClick={this.handleAddFriend} class="button">
|
||||||
Add friend
|
Add friend
|
||||||
|
|
@ -378,7 +360,8 @@ class UserOptionsPanel extends PSRoomPanel {
|
||||||
<p class="buttonbar">
|
<p class="buttonbar">
|
||||||
{canMute && !this.state.showBanInput && !this.state.showConfirm && (this.state.showMuteInput ? (
|
{canMute && !this.state.showBanInput && !this.state.showConfirm && (this.state.showMuteInput ? (
|
||||||
<div>
|
<div>
|
||||||
<label class="inputlabel"> Reason:
|
<label class="label">
|
||||||
|
Reason: {}
|
||||||
<input name="mutereason" class="textbox autofocus" placeholder="Mute reason (optional)" />
|
<input name="mutereason" class="textbox autofocus" placeholder="Mute reason (optional)" />
|
||||||
</label> {} <br />
|
</label> {} <br />
|
||||||
<button class="button" onClick={this.muteUser} value="7min">For 7 Mins</button> {}
|
<button class="button" onClick={this.muteUser} value="7min">For 7 Mins</button> {}
|
||||||
|
|
@ -392,9 +375,10 @@ class UserOptionsPanel extends PSRoomPanel {
|
||||||
))} {}
|
))} {}
|
||||||
{canBan && !this.state.showMuteInput && !this.state.showConfirm && (this.state.showBanInput ? (
|
{canBan && !this.state.showMuteInput && !this.state.showConfirm && (this.state.showBanInput ? (
|
||||||
<div>
|
<div>
|
||||||
<label class="inputlabel"> Reason:
|
<label class="label">
|
||||||
|
Reason: {}
|
||||||
<input name="banreason" class="textbox autofocus" placeholder="Ban reason (optional)" />
|
<input name="banreason" class="textbox autofocus" placeholder="Ban reason (optional)" />
|
||||||
</label> <br />
|
</label><br />
|
||||||
<button class="button" onClick={this.banUser} value="2d">For 2 Days</button> {}
|
<button class="button" onClick={this.banUser} value="2d">For 2 Days</button> {}
|
||||||
<button class="button" onClick={this.banUser} value="1wk">For 1 Week</button> {}
|
<button class="button" onClick={this.banUser} value="1wk">For 1 Week</button> {}
|
||||||
<button class="button" onClick={this.handleCancel}>Cancel</button>
|
<button class="button" onClick={this.handleCancel}>Cancel</button>
|
||||||
|
|
@ -483,6 +467,10 @@ class OptionsPanel extends PSRoomPanel {
|
||||||
static readonly location = 'popup';
|
static readonly location = 'popup';
|
||||||
declare state: { showStatusInput?: boolean, showStatusUpdated?: boolean };
|
declare state: { showStatusInput?: boolean, showStatusUpdated?: boolean };
|
||||||
|
|
||||||
|
override componentDidMount() {
|
||||||
|
super.componentDidMount();
|
||||||
|
this.subscribeTo(PS.user);
|
||||||
|
}
|
||||||
setTheme = (e: Event) => {
|
setTheme = (e: Event) => {
|
||||||
const theme = (e.currentTarget as HTMLSelectElement).value as 'light' | 'dark' | 'system';
|
const theme = (e.currentTarget as HTMLSelectElement).value as 'light' | 'dark' | 'system';
|
||||||
PS.prefs.set('theme', theme);
|
PS.prefs.set('theme', theme);
|
||||||
|
|
@ -493,6 +481,7 @@ class OptionsPanel extends PSRoomPanel {
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
case '':
|
case '':
|
||||||
PS.prefs.set('onepanel', null);
|
PS.prefs.set('onepanel', null);
|
||||||
|
PS.rightPanel ||= PS.rooms['rooms'] || null;
|
||||||
break;
|
break;
|
||||||
case 'onepanel':
|
case 'onepanel':
|
||||||
PS.prefs.set('onepanel', true);
|
PS.prefs.set('onepanel', true);
|
||||||
|
|
@ -580,7 +569,7 @@ class OptionsPanel extends PSRoomPanel {
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{PS.user.named && (PS.user.registered ?
|
{PS.user.named && (PS.user.registered?.userid === PS.user.userid ?
|
||||||
<button className="button" data-href="changepassword">Password...</button> :
|
<button className="button" data-href="changepassword">Password...</button> :
|
||||||
<button className="button" data-href="register">Register</button>)}
|
<button className="button" data-href="register">Register</button>)}
|
||||||
|
|
||||||
|
|
@ -767,7 +756,8 @@ class LoginPanel extends PSRoomPanel {
|
||||||
</p>}
|
</p>}
|
||||||
{loginState?.needsPassword && <p>
|
{loginState?.needsPassword && <p>
|
||||||
<i class="fa fa-level-up fa-rotate-90"></i> <strong>if you registered this name:</strong>
|
<i class="fa fa-level-up fa-rotate-90"></i> <strong>if you registered this name:</strong>
|
||||||
<label class="label">Password: {}
|
<label class="label">
|
||||||
|
Password: {}
|
||||||
<input
|
<input
|
||||||
class="textbox" type={this.state.passwordShown ? 'text' : 'password'} name="password"
|
class="textbox" type={this.state.passwordShown ? 'text' : 'password'} name="password"
|
||||||
autocomplete="current-password" style="width:173px"
|
autocomplete="current-password" style="width:173px"
|
||||||
|
|
@ -960,7 +950,9 @@ class ReplacePlayerPanel extends PSRoomPanel {
|
||||||
<input name="newplayer" class="textbox autofocus" />
|
<input name="newplayer" class="textbox autofocus" />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<button type="submit" class="button"><strong>Replace</strong></button> {}
|
<button type="submit" class="button">
|
||||||
|
<strong>Replace</strong>
|
||||||
|
</button> {}
|
||||||
<button type="button" data-cmd="/close" class="button">
|
<button type="button" data-cmd="/close" class="button">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1017,58 +1009,36 @@ class ChangePasswordPanel extends PSRoomPanel {
|
||||||
</p> }
|
</p> }
|
||||||
<p>Change your password:</p>
|
<p>Change your password:</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label">Username:
|
<label class="label">
|
||||||
<strong><input
|
Username: {}
|
||||||
type="text"
|
<input name="username" value={PS.user.name} readOnly={true} autocomplete="username" class="textbox disabled" />
|
||||||
name="username"
|
</label>
|
||||||
value={PS.user.name}
|
|
||||||
style="
|
|
||||||
color: inherit;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
font: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
display: block;
|
|
||||||
"
|
|
||||||
readOnly={true}
|
|
||||||
autocomplete="username"
|
|
||||||
/></strong></label>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label">Old password:
|
<label class="label">
|
||||||
<input
|
Old password: {}
|
||||||
class="textbox autofocus"
|
<input name="oldpassword" type="password" autocomplete="current-password" class="textbox autofocus" />
|
||||||
type="password"
|
</label>
|
||||||
name="oldpassword"
|
|
||||||
autocomplete="current-password"
|
|
||||||
/></label>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label">New password:
|
<label class="label">
|
||||||
<input
|
New password: {}
|
||||||
class="textbox"
|
<input name="password" type="password" autocomplete="new-password" class="textbox" />
|
||||||
type="password"
|
</label>
|
||||||
name="password"
|
|
||||||
autocomplete="new-password"
|
|
||||||
/></label>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label">New password (confirm):
|
<label class="label">
|
||||||
<input
|
New password (confirm): {}
|
||||||
class="textbox"
|
<input name="cpassword" type="password" autocomplete="new-password" class="textbox" />
|
||||||
type="password"
|
</label>
|
||||||
name="cpassword"
|
|
||||||
autocomplete="new-password"
|
|
||||||
/></label>
|
|
||||||
</p>
|
</p>
|
||||||
<p class="buttonbar">
|
<p class="buttonbar">
|
||||||
<button type="submit" class="button">
|
<button type="submit" class="button">
|
||||||
<strong>Change password</strong>
|
<strong>Change password</strong>
|
||||||
</button>
|
</button> {}
|
||||||
<button type="button" data-cmd="/close" class="button">Cancel</button>
|
<button type="button" data-cmd="/close" class="button">Cancel</button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</PSPanelWrapper>;
|
</PSPanelWrapper>;
|
||||||
}
|
}
|
||||||
|
|
@ -1083,10 +1053,6 @@ class RegisterPanel extends PSRoomPanel {
|
||||||
|
|
||||||
declare state: { errorMsg: string };
|
declare state: { errorMsg: string };
|
||||||
|
|
||||||
update = () => {
|
|
||||||
this.forceUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
handleRegisterUser = (ev: Event) => {
|
handleRegisterUser = (ev: Event) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
let captcha = this.base?.querySelector<HTMLInputElement>('input[name=captcha]')?.value;
|
let captcha = this.base?.querySelector<HTMLInputElement>('input[name=captcha]')?.value;
|
||||||
|
|
@ -1130,55 +1096,37 @@ class RegisterPanel extends PSRoomPanel {
|
||||||
</p> }
|
</p> }
|
||||||
<p>Register your account:</p>
|
<p>Register your account:</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label">Username:
|
<label class="label">
|
||||||
<strong><input
|
Username: {}
|
||||||
type="text"
|
<input name="name" value={PS.user.name} readOnly={true} autocomplete="username" class="textbox disabled" />
|
||||||
name="name"
|
</label>
|
||||||
value={PS.user.name}
|
|
||||||
style="
|
|
||||||
color: inherit;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
font: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
display: block;
|
|
||||||
"
|
|
||||||
readOnly={true}
|
|
||||||
autocomplete="username"
|
|
||||||
/></strong></label>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label">Password:
|
<label class="label">
|
||||||
<input
|
Password: {}
|
||||||
class="textbox autofocus"
|
<input name="password" type="password" autocomplete="new-password" class="textbox autofocus" />
|
||||||
type="password"
|
</label>
|
||||||
name="password"
|
|
||||||
autocomplete="new-password"
|
|
||||||
/></label>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label">Password (confirm):
|
<label class="label">
|
||||||
<input
|
Password (confirm): {}
|
||||||
class="textbox"
|
<input name="cpassword" type="password" autocomplete="new-password" class="textbox" />
|
||||||
type="password"
|
</label>
|
||||||
name="cpassword"
|
|
||||||
autocomplete="new-password"
|
|
||||||
/></label>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label"> <img
|
<label class="label"><img
|
||||||
src="https://play.pokemonshowdown.com/sprites/gen5ani/pikachu.gif"
|
src="https://play.pokemonshowdown.com/sprites/gen5ani/pikachu.gif"
|
||||||
alt="An Electric-type mouse that is the mascot of the Pokémon franchise."
|
alt="An Electric-type mouse that is the mascot of the Pokémon franchise."
|
||||||
/></label>
|
/></label>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label class="label">What is this pokemon?
|
<label class="label">
|
||||||
<input
|
What is this pokemon?{}
|
||||||
class="textbox" type="text" name="captcha" value=""
|
<input name="captcha" class="textbox" />
|
||||||
/></label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
<p class="buttonbar">
|
<p class="buttonbar">
|
||||||
<button type="submit" class="button"><strong>Register</strong></button>
|
<button type="submit" class="button"><strong>Register</strong></button> {}
|
||||||
<button type="button" data-cmd="/close" class="button">Cancel</button>
|
<button type="button" data-cmd="/close" class="button">Cancel</button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
|
||||||
<i class="fa fa-chevron-left"></i> List
|
<i class="fa fa-chevron-left"></i> List
|
||||||
</button>
|
</button>
|
||||||
<label class="label teamname">
|
<label class="label teamname">
|
||||||
Team name:
|
Team name:{}
|
||||||
<input
|
<input
|
||||||
class="textbox" type="text" value={team.name} onInput={this.rename} onChange={this.rename} onKeyUp={this.rename}
|
class="textbox" type="text" value={team.name} onInput={this.rename} onChange={this.rename} onKeyUp={this.rename}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
import preact from "../js/lib/preact";
|
import preact from "../js/lib/preact";
|
||||||
import { PS, type PSRoom, type RoomID } from "./client-main";
|
import { PS, type PSRoom, type RoomID } from "./client-main";
|
||||||
import { PSMain } from "./panels";
|
import { PSView } from "./panels";
|
||||||
import type { Battle } from "./battle";
|
import type { Battle } from "./battle";
|
||||||
import { BattleLog } from "./battle-log";
|
import { BattleLog } from "./battle-log";
|
||||||
|
|
||||||
|
|
@ -194,7 +194,7 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
renderVertical() {
|
renderVertical() {
|
||||||
return <div id="header" class="header-vertical" style={this.props.style} onClick={PSMain.scrollToHeader}>
|
return <div id="header" class="header-vertical" style={this.props.style} onClick={PSView.scrollToHeader}>
|
||||||
<div class="maintabbarbottom"></div>
|
<div class="maintabbarbottom"></div>
|
||||||
<div class="scrollable-part">
|
<div class="scrollable-part">
|
||||||
<img
|
<img
|
||||||
|
|
@ -233,7 +233,7 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
||||||
}
|
}
|
||||||
override render() {
|
override render() {
|
||||||
if (PS.leftPanelWidth === null) {
|
if (PS.leftPanelWidth === null) {
|
||||||
if (!PSMain.textboxFocused) {
|
if (!PSView.textboxFocused) {
|
||||||
document.documentElement.classList?.add('scroll-snap-enabled');
|
document.documentElement.classList?.add('scroll-snap-enabled');
|
||||||
}
|
}
|
||||||
return this.renderVertical();
|
return this.renderVertical();
|
||||||
|
|
@ -301,11 +301,11 @@ export class PSMiniHeader extends preact.Component {
|
||||||
const menuButton = showMenuButton ? (
|
const menuButton = showMenuButton ? (
|
||||||
null
|
null
|
||||||
) : window.scrollX ? (
|
) : window.scrollX ? (
|
||||||
<button onClick={PSMain.scrollToHeader} class={`mini-header-left ${notifying}`} aria-label="Menu">
|
<button onClick={PSView.scrollToHeader} class={`mini-header-left ${notifying}`} aria-label="Menu">
|
||||||
<i class="fa fa-arrow-left"></i>
|
<i class="fa fa-arrow-left"></i>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={PSMain.scrollToRoom} class="mini-header-left" aria-label="Menu">
|
<button onClick={PSView.scrollToRoom} class="mini-header-left" aria-label="Menu">
|
||||||
<i class="fa fa-arrow-right"></i>
|
<i class="fa fa-arrow-right"></i>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
@ -319,4 +319,4 @@ export class PSMiniHeader extends preact.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preact.render(<PSMain />, document.body, document.getElementById('ps-frame')!);
|
preact.render(<PSView />, document.body, document.getElementById('ps-frame')!);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export class PSRouter {
|
||||||
panelState = '';
|
panelState = '';
|
||||||
constructor() {
|
constructor() {
|
||||||
const currentRoomid = location.pathname.slice(1);
|
const currentRoomid = location.pathname.slice(1);
|
||||||
if (/^[a-z0-9-]+$/.test(currentRoomid)) {
|
if (/^[a-z0-9-]*$/.test(currentRoomid)) {
|
||||||
this.subscribeHistory();
|
this.subscribeHistory();
|
||||||
} else if (location.pathname.endsWith('.html')) {
|
} else if (location.pathname.endsWith('.html')) {
|
||||||
this.subscribeHash();
|
this.subscribeHash();
|
||||||
|
|
@ -96,8 +96,6 @@ export class PSRouter {
|
||||||
const currentRoomid = location.hash.slice(1);
|
const currentRoomid = location.hash.slice(1);
|
||||||
if (/^[a-z0-9-]+$/.test(currentRoomid)) {
|
if (/^[a-z0-9-]+$/.test(currentRoomid)) {
|
||||||
PS.join(currentRoomid as RoomID);
|
PS.join(currentRoomid as RoomID);
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PS.subscribeAndRun(() => {
|
PS.subscribeAndRun(() => {
|
||||||
|
|
@ -120,8 +118,6 @@ export class PSRouter {
|
||||||
const currentRoomid = location.pathname.slice(1);
|
const currentRoomid = location.pathname.slice(1);
|
||||||
if (/^[a-z0-9-]+$/.test(currentRoomid)) {
|
if (/^[a-z0-9-]+$/.test(currentRoomid)) {
|
||||||
PS.join(currentRoomid as RoomID);
|
PS.join(currentRoomid as RoomID);
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!window.history) return;
|
if (!window.history) return;
|
||||||
PS.subscribeAndRun(() => {
|
PS.subscribeAndRun(() => {
|
||||||
|
|
@ -157,13 +153,14 @@ PS.router = new PSRouter();
|
||||||
|
|
||||||
export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ room: T }> {
|
export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ room: T }> {
|
||||||
subscriptions: PSSubscription[] = [];
|
subscriptions: PSSubscription[] = [];
|
||||||
subscribeTo<M>(model: PSModel<M> | PSStreamModel<M>, callback: (value: M) => void): PSSubscription {
|
subscribeTo<M>(
|
||||||
|
model: PSModel<M> | PSStreamModel<M>, callback: (value: M) => void = () => { this.forceUpdate(); }
|
||||||
|
): PSSubscription {
|
||||||
const subscription = model.subscribe(callback);
|
const subscription = model.subscribe(callback);
|
||||||
this.subscriptions.push(subscription);
|
this.subscriptions.push(subscription);
|
||||||
return subscription;
|
return subscription;
|
||||||
}
|
}
|
||||||
override componentDidMount() {
|
override componentDidMount() {
|
||||||
if (PS.room === this.props.room) this.focus();
|
|
||||||
this.props.room.onParentEvent = (id: string, e?: Event) => {
|
this.props.room.onParentEvent = (id: string, e?: Event) => {
|
||||||
if (id === 'focus') this.focus();
|
if (id === 'focus') this.focus();
|
||||||
};
|
};
|
||||||
|
|
@ -171,7 +168,7 @@ export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ r
|
||||||
if (!args) this.forceUpdate();
|
if (!args) this.forceUpdate();
|
||||||
else this.receiveLine(args);
|
else this.receiveLine(args);
|
||||||
}));
|
}));
|
||||||
this.updateDimensions();
|
this.componentDidUpdate();
|
||||||
}
|
}
|
||||||
justUpdatedDimensions = false;
|
justUpdatedDimensions = false;
|
||||||
updateDimensions() {
|
updateDimensions() {
|
||||||
|
|
@ -194,14 +191,11 @@ export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ r
|
||||||
}
|
}
|
||||||
override componentDidUpdate() {
|
override componentDidUpdate() {
|
||||||
const room = this.props.room;
|
const room = this.props.room;
|
||||||
if (this.base && ['popup', 'semimodal-popup'].includes(room.location)) {
|
const currentlyHidden = !room.width && room.parentElem && ['popup', 'semimodal-popup'].includes(room.location);
|
||||||
if (room.width && room.hiddenInit) {
|
this.updateDimensions();
|
||||||
room.hiddenInit = false;
|
if (currentlyHidden) return;
|
||||||
this.focus();
|
if (room.focusNextUpdate) {
|
||||||
}
|
room.focusNextUpdate = false;
|
||||||
this.updateDimensions();
|
|
||||||
} else if (this.base && room.hiddenInit) {
|
|
||||||
room.hiddenInit = false;
|
|
||||||
this.focus();
|
this.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -259,49 +253,86 @@ export function PSPanelWrapper(props: {
|
||||||
}
|
}
|
||||||
return <div
|
return <div
|
||||||
id={`room-${room.id}`} class={'mini-window-contents ps-room-light' + (props.scrollable === true ? ' scrollable' : '')}
|
id={`room-${room.id}`} class={'mini-window-contents ps-room-light' + (props.scrollable === true ? ' scrollable' : '')}
|
||||||
onClick={props.focusClick ? PSMain.focusIfNoSelection : undefined}
|
onClick={props.focusClick ? PSView.focusIfNoSelection : undefined}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
if (PS.isPopup(room)) {
|
if (PS.isPopup(room)) {
|
||||||
const style = PSMain.getPopupStyle(room, props.width, props.fullSize);
|
const style = PSView.getPopupStyle(room, props.width, props.fullSize);
|
||||||
return <div class="ps-popup" id={`room-${room.id}`} style={style}>
|
return <div class="ps-popup" id={`room-${room.id}`} style={style}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
const style = PSMain.posStyle(room) as any;
|
const style = PSView.posStyle(room) as any;
|
||||||
if (props.scrollable === 'hidden') style.overflow = 'hidden';
|
if (props.scrollable === 'hidden') style.overflow = 'hidden';
|
||||||
return <div
|
return <div
|
||||||
class={'ps-room' + (room.id === '' ? '' : ' ps-room-light') + (props.scrollable === true ? ' scrollable' : '')}
|
class={'ps-room' + (room.id === '' ? '' : ' ps-room-light') + (props.scrollable === true ? ' scrollable' : '')}
|
||||||
id={`room-${room.id}`}
|
id={`room-${room.id}`}
|
||||||
style={style} onClick={props.focusClick ? PSMain.focusIfNoSelection : undefined}
|
style={style} onClick={props.focusClick ? PSView.focusIfNoSelection : undefined}
|
||||||
>
|
>
|
||||||
{room.caughtError ? <div class="broadcast broadcast-red"><pre>{room.caughtError}</pre></div> : props.children}
|
{room.caughtError ? <div class="broadcast broadcast-red"><pre>{room.caughtError}</pre></div> : props.children}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PSMain extends preact.Component {
|
export class PSView extends preact.Component {
|
||||||
static readonly isChrome = navigator.userAgent.includes(' Chrome/');
|
static readonly isChrome = navigator.userAgent.includes(' Chrome/');
|
||||||
static readonly isSafari = !this.isChrome && navigator.userAgent.includes(' Safari/');
|
static readonly isSafari = !this.isChrome && navigator.userAgent.includes(' Safari/');
|
||||||
|
static readonly isMac = navigator.platform?.startsWith('Mac');
|
||||||
static textboxFocused = false;
|
static textboxFocused = false;
|
||||||
static setTextboxFocused(focused: boolean) {
|
static setTextboxFocused(focused: boolean) {
|
||||||
if (!PSMain.isChrome || PS.leftPanelWidth !== null) return;
|
if (!PSView.isChrome || PS.leftPanelWidth !== null) return;
|
||||||
// Chrome bug: on Android, it insistently scrolls everything leftmost when scroll snap is enabled
|
// Chrome bug: on Android, it insistently scrolls everything leftmost when scroll snap is enabled
|
||||||
|
|
||||||
this.textboxFocused = focused;
|
this.textboxFocused = focused;
|
||||||
if (focused) {
|
if (focused) {
|
||||||
document.documentElement.classList.remove('scroll-snap-enabled');
|
document.documentElement.classList.remove('scroll-snap-enabled');
|
||||||
PSMain.scrollToRoom();
|
PSView.scrollToRoom();
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.add('scroll-snap-enabled');
|
document.documentElement.classList.add('scroll-snap-enabled');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static focusPreview(room: PSRoom) {
|
||||||
|
if (room !== PS.room) return '';
|
||||||
|
|
||||||
|
const verticalBuf = this.verticalFocusPreview();
|
||||||
|
if (verticalBuf) return verticalBuf;
|
||||||
|
|
||||||
|
const isMiniRoom = PS.room.location === 'mini-window';
|
||||||
|
const { rooms, index } = PS.horizontalNav();
|
||||||
|
if (index === -1) return '';
|
||||||
|
|
||||||
|
let buf = ' ';
|
||||||
|
const leftRoom = PS.rooms[rooms[index - 1]];
|
||||||
|
if (leftRoom) buf += `\u2190 ${leftRoom.title}`;
|
||||||
|
buf += (PS.arrowKeysUsed || isMiniRoom ? " | " : " (use arrow keys) ");
|
||||||
|
const rightRoom = PS.rooms[rooms[index + 1]];
|
||||||
|
if (rightRoom) buf += `${rightRoom.title} \u2192`;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
static verticalFocusPreview() {
|
||||||
|
const { rooms, index } = PS.verticalNav();
|
||||||
|
if (index === -1) return '';
|
||||||
|
|
||||||
|
const upRoom = PS.rooms[rooms[index - 1]];
|
||||||
|
let downRoom = PS.rooms[rooms[index + 1]];
|
||||||
|
if (index === rooms.length - 2 && rooms[index + 1] === 'news') downRoom = undefined;
|
||||||
|
if (!upRoom && !downRoom) return '';
|
||||||
|
|
||||||
|
let buf = ' ';
|
||||||
|
// const altLabel = PSMain.isMac ? '⌥' : 'ᴀʟᴛ';
|
||||||
|
const altLabel = PSView.isMac ? 'ᴏᴘᴛ' : 'ᴀʟᴛ';
|
||||||
|
if (upRoom) buf += `${altLabel}\u2191 ${upRoom.title}`;
|
||||||
|
buf += " | ";
|
||||||
|
if (downRoom) buf += `${altLabel}\u2193 ${downRoom.title}`;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
PS.subscribe(() => this.forceUpdate());
|
PS.subscribe(() => this.forceUpdate());
|
||||||
|
|
||||||
if (PSMain.isSafari) {
|
if (PSView.isSafari) {
|
||||||
// I don't want to prevent users from being able to zoom, but iOS Safari
|
// I don't want to prevent users from being able to zoom, but iOS Safari
|
||||||
// auto-zooms when focusing textboxes (unless the font size is 16px),
|
// auto-zooms when focusing textboxes (unless the font size is 16px),
|
||||||
// and this apparently fixes it while still allowing zooming.
|
// and this apparently fixes it while still allowing zooming.
|
||||||
|
|
@ -401,7 +432,7 @@ export class PSMain extends preact.Component {
|
||||||
PS.update();
|
PS.update();
|
||||||
}
|
}
|
||||||
if (clickedRoom && !PS.isPopup(clickedRoom)) {
|
if (clickedRoom && !PS.isPopup(clickedRoom)) {
|
||||||
PSMain.scrollToRoom();
|
PSView.scrollToRoom();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -494,7 +525,7 @@ export class PSMain extends preact.Component {
|
||||||
}
|
}
|
||||||
static scrollToRoom() {
|
static scrollToRoom() {
|
||||||
if (document.documentElement.scrollWidth > document.documentElement.clientWidth && window.scrollX === 0) {
|
if (document.documentElement.scrollWidth > document.documentElement.clientWidth && window.scrollX === 0) {
|
||||||
if (PSMain.isSafari && PS.leftPanelWidth === null) {
|
if (PSView.isSafari && PS.leftPanelWidth === null) {
|
||||||
// Safari bug: `scrollBy` doesn't actually work when scroll snap is enabled
|
// Safari bug: `scrollBy` doesn't actually work when scroll snap is enabled
|
||||||
// note: interferes with the `PSMain.textboxFocused` workaround for a Chrome bug
|
// note: interferes with the `PSMain.textboxFocused` workaround for a Chrome bug
|
||||||
document.documentElement.classList.remove('scroll-snap-enabled');
|
document.documentElement.classList.remove('scroll-snap-enabled');
|
||||||
|
|
@ -600,7 +631,7 @@ export class PSMain extends preact.Component {
|
||||||
return { maxWidth: width || 480 };
|
return { maxWidth: width || 480 };
|
||||||
}
|
}
|
||||||
if (!room.width || !room.height) {
|
if (!room.width || !room.height) {
|
||||||
room.hiddenInit = true;
|
room.focusNextUpdate = true;
|
||||||
return {
|
return {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
visibility: 'hidden',
|
visibility: 'hidden',
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
* Buttons
|
* Buttons
|
||||||
*********************************************************/
|
*********************************************************/
|
||||||
|
|
||||||
button, summary {
|
a, button, summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1006,6 +1006,9 @@ p.or:after {
|
||||||
.roomlist {
|
.roomlist {
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
.roomlist .subrooms {
|
.roomlist .subrooms {
|
||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
|
|
@ -1344,7 +1347,6 @@ a.ilink.yours {
|
||||||
.tournament-bracket-tree-node text, .tournament-bracket-tree-node {
|
.tournament-bracket-tree-node text, .tournament-bracket-tree-node {
|
||||||
font-size: 8.5pt;
|
font-size: 8.5pt;
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
dominant-baseline: central;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.tournament-bracket-tree-node a {
|
.tournament-bracket-tree-node a {
|
||||||
|
|
@ -2131,6 +2133,7 @@ a.ilink.yours {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
color: #777777;
|
color: #777777;
|
||||||
}
|
}
|
||||||
|
.folderpane .text,
|
||||||
.folderpane h3 {
|
.folderpane h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 13px 0 0 0;
|
padding: 13px 0 0 0;
|
||||||
|
|
@ -2140,6 +2143,10 @@ a.ilink.yours {
|
||||||
color: black;
|
color: black;
|
||||||
border-right: 1px solid #888888;
|
border-right: 1px solid #888888;
|
||||||
}
|
}
|
||||||
|
.folderpane .text {
|
||||||
|
height: 23px;
|
||||||
|
padding: 7px 0 0 7px;
|
||||||
|
}
|
||||||
.folder .selectFolder {
|
.folder .selectFolder {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0 0 0 7px;
|
padding: 0 0 0 7px;
|
||||||
|
|
@ -2185,6 +2192,7 @@ a.ilink.yours {
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
border-top-left-radius: 3px;
|
border-top-left-radius: 3px;
|
||||||
border-bottom-left-radius: 3px;
|
border-bottom-left-radius: 3px;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* believe me, there was no other way to do this */
|
/* believe me, there was no other way to do this */
|
||||||
|
|
@ -3215,6 +3223,7 @@ a.ilink.yours {
|
||||||
.dark .folderlistafter:before,
|
.dark .folderlistafter:before,
|
||||||
.dark .folderlistbefore:before,
|
.dark .folderlistbefore:before,
|
||||||
.dark .folderpane h3,
|
.dark .folderpane h3,
|
||||||
|
.dark .folderpane .text,
|
||||||
.dark .folder .selectFolder,
|
.dark .folder .selectFolder,
|
||||||
.dark .folderhack1,
|
.dark .folderhack1,
|
||||||
.dark .folderhack2 {
|
.dark .folderhack2 {
|
||||||
|
|
|
||||||
|
|
@ -1228,6 +1228,7 @@ pre.textbox.textbox-empty[placeholder]:before {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.tournament-title:hover {
|
.tournament-title:hover {
|
||||||
background: rgba(242, 247, 250, 0.85);
|
background: rgba(242, 247, 250, 0.85);
|
||||||
|
|
@ -1249,6 +1250,7 @@ pre.textbox.textbox-empty[placeholder]:before {
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tournament-box {
|
.tournament-box {
|
||||||
|
|
@ -1375,7 +1377,6 @@ pre.textbox.textbox-empty[placeholder]:before {
|
||||||
.tournament-bracket-tree-node text, .tournament-bracket-tree-node {
|
.tournament-bracket-tree-node text, .tournament-bracket-tree-node {
|
||||||
font-size: 8.5pt;
|
font-size: 8.5pt;
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
dominant-baseline: central;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.tournament-bracket-tree-node a {
|
.tournament-bracket-tree-node a {
|
||||||
|
|
@ -2331,104 +2332,3 @@ pre.textbox.textbox-empty[placeholder]:before {
|
||||||
.dark .chat.mine {
|
.dark .chat.mine {
|
||||||
background: rgba(255,255,255,0.05);
|
background: rgba(255,255,255,0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* teambuilder */
|
|
||||||
|
|
||||||
.dark .folderpane {
|
|
||||||
border-left-color: #484848;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .folderlist .foldersep:before,
|
|
||||||
.dark .folderlistafter:before,
|
|
||||||
.dark .folderlistbefore:before,
|
|
||||||
.dark .folderpane h3,
|
|
||||||
.dark .folder .selectFolder,
|
|
||||||
.dark .folderhack1,
|
|
||||||
.dark .folderhack2 {
|
|
||||||
background: #484848;
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .folder .selectFolder:hover {
|
|
||||||
background: #686868;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .folderpane i {
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .folder .selectFolder.active,
|
|
||||||
.dark .folder .selectFolder:active {
|
|
||||||
background: #27333c;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .folder.cur .selectFolder {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* teambuilder set */
|
|
||||||
|
|
||||||
.dark .utilichart h3, .dark .dexentry h3, .dark .resultheader h3 {
|
|
||||||
background: #636363;
|
|
||||||
color: #F1F1F1;
|
|
||||||
border: 1px solid #A9A9A9;
|
|
||||||
text-shadow: 1px 1px 0 rgb(40, 43, 45);
|
|
||||||
box-shadow: inset 0px 1px 0 rgb(49, 49, 49);
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .teambar button {
|
|
||||||
background: #5A6570;
|
|
||||||
color: #F1F1F1;
|
|
||||||
border: 1px solid #AAAAAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .teambar button:hover {
|
|
||||||
background: #444C54;
|
|
||||||
color: #F1F1F1;
|
|
||||||
border: 1px solid #AAAAAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .teambar button:disabled, .dark .teambar button:disabled:hover, .dark .teambar button:disabled:active {
|
|
||||||
color: #ffffff;
|
|
||||||
background: #2d343a;
|
|
||||||
border-color: #6bacc5;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .teambuilder-results .result a.hover,
|
|
||||||
.dark .teambuilder-results .result a:hover,
|
|
||||||
.dark .setmenu button:hover,
|
|
||||||
.dark .teamlist button:hover {
|
|
||||||
border-color: #777777;
|
|
||||||
background: rgba(100, 100, 100, 0.5);
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
.dark .teambuilder-results .result a.cur {
|
|
||||||
border-color: #BBBBBB;
|
|
||||||
background: rgba(100, 100, 100, 0.2);
|
|
||||||
}
|
|
||||||
.dark .teambuilder-results .result a.cur:hover {
|
|
||||||
border-color: #BBBBBB;
|
|
||||||
background: rgba(100, 100, 100, 0.4);
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .setmenu button,
|
|
||||||
.dark .teamlist button,
|
|
||||||
.dark .folder.cur .selectFolder,
|
|
||||||
.dark .utilichart .namecol,
|
|
||||||
.dark .utilichart .pokemonnamecol,
|
|
||||||
.dark .utilichart .movenamecol {
|
|
||||||
color: #DDD;
|
|
||||||
}
|
|
||||||
.dark .utilichart .col {
|
|
||||||
color: #DDD;
|
|
||||||
}
|
|
||||||
.dark .utilichart .cur .col {
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
.dark .utilichart a:hover .col {
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@
|
||||||
width: 16px;
|
width: 16px;
|
||||||
color: #777777;
|
color: #777777;
|
||||||
}
|
}
|
||||||
|
.folderpane .text,
|
||||||
.folderpane h3 {
|
.folderpane h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 13px 0 0 0;
|
padding: 13px 0 0 0;
|
||||||
|
|
@ -97,6 +98,10 @@
|
||||||
color: black;
|
color: black;
|
||||||
border-right: 1px solid #888888;
|
border-right: 1px solid #888888;
|
||||||
}
|
}
|
||||||
|
.folderpane .text {
|
||||||
|
height: 23px;
|
||||||
|
padding: 7px 0 0 7px;
|
||||||
|
}
|
||||||
.folder .selectFolder {
|
.folder .selectFolder {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0 0 0 7px;
|
padding: 0 0 0 7px;
|
||||||
|
|
@ -199,6 +204,41 @@
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* dark */
|
||||||
|
|
||||||
|
.dark .folderpane {
|
||||||
|
border-left-color: #484848;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .folderlist .foldersep:before,
|
||||||
|
.dark .folderlistafter:before,
|
||||||
|
.dark .folderlistbefore:before,
|
||||||
|
.dark .folderpane h3,
|
||||||
|
.dark .folderpane .text,
|
||||||
|
.dark .folder .selectFolder,
|
||||||
|
.dark .folderhack1,
|
||||||
|
.dark .folderhack2 {
|
||||||
|
background: #484848;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .folder .selectFolder:hover {
|
||||||
|
background: #686868;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .folderpane i {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .folder .selectFolder.active,
|
||||||
|
.dark .folder .selectFolder:active {
|
||||||
|
background: #27333c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .folder.cur .selectFolder {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/*********************************************************
|
/*********************************************************
|
||||||
* Teambuilder editor
|
* Teambuilder editor
|
||||||
|
|
@ -271,3 +311,69 @@
|
||||||
left: 10px;
|
left: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* teambuilder set */
|
||||||
|
|
||||||
|
.dark .utilichart h3, .dark .dexentry h3, .dark .resultheader h3 {
|
||||||
|
background: #636363;
|
||||||
|
color: #F1F1F1;
|
||||||
|
border: 1px solid #A9A9A9;
|
||||||
|
text-shadow: 1px 1px 0 rgb(40, 43, 45);
|
||||||
|
box-shadow: inset 0px 1px 0 rgb(49, 49, 49);
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .teambar button {
|
||||||
|
background: #5A6570;
|
||||||
|
color: #F1F1F1;
|
||||||
|
border: 1px solid #AAAAAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .teambar button:hover {
|
||||||
|
background: #444C54;
|
||||||
|
color: #F1F1F1;
|
||||||
|
border: 1px solid #AAAAAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .teambar button:disabled, .dark .teambar button:disabled:hover, .dark .teambar button:disabled:active {
|
||||||
|
color: #ffffff;
|
||||||
|
background: #2d343a;
|
||||||
|
border-color: #6bacc5;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .teambuilder-results .result a.hover,
|
||||||
|
.dark .teambuilder-results .result a:hover,
|
||||||
|
.dark .setmenu button:hover,
|
||||||
|
.dark .teamlist button:hover {
|
||||||
|
border-color: #777777;
|
||||||
|
background: rgba(100, 100, 100, 0.5);
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
.dark .teambuilder-results .result a.cur {
|
||||||
|
border-color: #BBBBBB;
|
||||||
|
background: rgba(100, 100, 100, 0.2);
|
||||||
|
}
|
||||||
|
.dark .teambuilder-results .result a.cur:hover {
|
||||||
|
border-color: #BBBBBB;
|
||||||
|
background: rgba(100, 100, 100, 0.4);
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .setmenu button,
|
||||||
|
.dark .teamlist button,
|
||||||
|
.dark .folder.cur .selectFolder,
|
||||||
|
.dark .utilichart .namecol,
|
||||||
|
.dark .utilichart .pokemonnamecol,
|
||||||
|
.dark .utilichart .movenamecol {
|
||||||
|
color: #DDD;
|
||||||
|
}
|
||||||
|
.dark .utilichart .col {
|
||||||
|
color: #DDD;
|
||||||
|
}
|
||||||
|
.dark .utilichart .cur .col {
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
.dark .utilichart a:hover .col {
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,10 +121,16 @@
|
||||||
<script src="js/panel-mainmenu.js"></script>
|
<script src="js/panel-mainmenu.js"></script>
|
||||||
<script src="js/panel-rooms.js"></script>
|
<script src="js/panel-rooms.js"></script>
|
||||||
<script src="js/panel-topbar.js"></script>
|
<script src="js/panel-topbar.js"></script>
|
||||||
<script src="js/panel-popups.js"></script>
|
<!-- at this point, the main view is loaded and usable -->
|
||||||
|
|
||||||
<script src="js/miniedit.js"></script>
|
<script src="js/miniedit.js"></script>
|
||||||
<script src="js/panel-chat-tournament.js"></script>
|
<script src="js/panel-chat-tournament.js"></script>
|
||||||
<script src="js/panel-chat.js"></script>
|
<script src="js/panel-chat.js"></script>
|
||||||
|
<!-- at this point, chatrooms are usable -->
|
||||||
|
|
||||||
|
<script src="js/panel-popups.js"></script>
|
||||||
|
<script src="js/panel-page.js?"></script>
|
||||||
|
<script src="js/panel-ladder.js?"></script>
|
||||||
|
|
||||||
<script src="js/battle-sound.js"></script>
|
<script src="js/battle-sound.js"></script>
|
||||||
<script src="js/lib/jquery-2.2.4.min.js"></script>
|
<script src="js/lib/jquery-2.2.4.min.js"></script>
|
||||||
|
|
@ -146,11 +152,11 @@
|
||||||
<script src="js/battle-dex-search.js?"></script>
|
<script src="js/battle-dex-search.js?"></script>
|
||||||
<script src="js/battle-searchresults.js?"></script>
|
<script src="js/battle-searchresults.js?"></script>
|
||||||
<script src="js/panel-teambuilder-team.js?"></script>
|
<script src="js/panel-teambuilder-team.js?"></script>
|
||||||
<script src="js/panel-ladder.js?"></script>
|
|
||||||
<script src="js/panel-page.js?"></script>
|
|
||||||
|
|
||||||
<script src="https://play.pokemonshowdown.com/data/pokedex-mini.js"></script>
|
<script src="https://play.pokemonshowdown.com/data/pokedex-mini.js"></script>
|
||||||
<script src="https://play.pokemonshowdown.com/data/pokedex-mini-bw.js"></script>
|
<script src="https://play.pokemonshowdown.com/data/pokedex-mini-bw.js"></script>
|
||||||
<script src="js/lib/d3.v3.min.js"></script>
|
<script src="js/lib/d3.v3.min.js"></script>
|
||||||
|
|
||||||
|
<script src="js/client-endload.js?"></script>
|
||||||
|
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
|
||||||
|
|
@ -422,7 +422,7 @@ export class BattlePanel extends preact.Component<{ id: string }> {
|
||||||
</form>
|
</form>
|
||||||
<p>
|
<p>
|
||||||
<em>Pro tip:</em> You don't need to click "Skip to turn" if you have a keyboard, just start typing
|
<em>Pro tip:</em> You don't need to click "Skip to turn" if you have a keyboard, just start typing
|
||||||
the turn number and press <kbd>Enter</kbd>. For more shortcuts, press <kbd>Shift</kbd>+<kbd>/</kbd>
|
the turn number and press <kbd>Enter</kbd>. For more shortcuts, press <kbd>Shift</kbd>+<kbd>/</kbd> {}
|
||||||
when a text box isn't focused.
|
when a text box isn't focused.
|
||||||
</p>
|
</p>
|
||||||
</section></div>;
|
</section></div>;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user