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
|
||||
"@babel/plugin-transform-member-expression-literals",
|
||||
"@babel/plugin-transform-property-literals"
|
||||
"@babel/plugin-transform-property-literals",
|
||||
|
||||
"@babel/plugin-transform-strict-mode"
|
||||
],
|
||||
"ignore": [
|
||||
"src/globals.d.ts"
|
||||
|
|
|
|||
|
|
@ -25,3 +25,6 @@ trim_trailing_whitespace = false
|
|||
indent_style = tab
|
||||
indent_size = 8
|
||||
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-max-props-per-line": "off",
|
||||
"@stylistic/jsx-function-call-newline": "off",
|
||||
"@stylistic/jsx-child-element-spacing": "error",
|
||||
"no-restricted-syntax": ["error",
|
||||
{ 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": {
|
||||
"@babel/core": "^7.26.10",
|
||||
"@babel/plugin-transform-react-jsx": "^7.25.9",
|
||||
"@babel/plugin-transform-strict-mode": "^7.25.9",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-typescript": "^7.27.0",
|
||||
"babel-plugin-remove-import-export": "^1.1.1",
|
||||
|
|
@ -1264,6 +1265,21 @@
|
|||
"@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": {
|
||||
"version": "7.26.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"dependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
"@babel/plugin-transform-react-jsx": "^7.25.9",
|
||||
"@babel/plugin-transform-strict-mode": "^7.25.9",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-typescript": "^7.27.0",
|
||||
"babel-plugin-remove-import-export": "^1.1.1",
|
||||
|
|
|
|||
|
|
@ -612,8 +612,8 @@
|
|||
TournamentBox.nodeSize = {
|
||||
width: 160, height: 30,
|
||||
radius: 5,
|
||||
separationX: 20, separationY: 20,
|
||||
textOffset: -1
|
||||
separationX: 20, separationY: 10,
|
||||
textOffset: 4
|
||||
};
|
||||
|
||||
TournamentBox.prototype.generateBracket = function (data, abbreviated) {
|
||||
|
|
@ -655,9 +655,15 @@
|
|||
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++) {
|
||||
node.children[i].highlightLink = true;
|
||||
var child = node.children[i];
|
||||
if (child.team && !child.team.startsWith('(')) {
|
||||
child.highlightLink = true;
|
||||
}
|
||||
}
|
||||
} else if (highlightName) {
|
||||
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);
|
||||
|
||||
elem.append('svg:text').classed('tournament-bracket-tree-node-team', true)
|
||||
.attr('y', nodeSize.textOffset)
|
||||
.classed('tournament-bracket-tree-node-team-draw', true)
|
||||
.text(node.team || '');
|
||||
} 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>';
|
||||
}
|
||||
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] += (
|
||||
'<li><button name="selectFormat" value="' + i +
|
||||
'" class="option' + (curFormat === i ? ' cur' : '') + '">' + formatName +
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@
|
|||
if (room.title && room.title.charAt(0) === '[') {
|
||||
var closeBracketIndex = room.title.indexOf(']');
|
||||
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>';
|
||||
|
|
|
|||
|
|
@ -72,13 +72,14 @@ https://psim.us/dev
|
|||
document.head.appendChild(linkEl);
|
||||
}
|
||||
linkStyle("/style/sim-types.css");
|
||||
linkStyle("/style/teambuilder.css");
|
||||
linkStyle("/style/teambuilder.css?");
|
||||
linkStyle("style/battle-search.css");
|
||||
linkStyle("/style/font-awesome.css");
|
||||
</script>
|
||||
<script nomodule defer src="/js/lib/ps-polyfill.js"></script>
|
||||
<script defer src="/config/config.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.js?"></script>
|
||||
|
|
@ -97,10 +98,16 @@ https://psim.us/dev
|
|||
<script defer src="/js/panel-mainmenu.js?"></script>
|
||||
<script defer src="/js/panel-rooms.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/panel-chat-tournament.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/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-searchresults.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-bw.js?"></script>
|
||||
<script defer src="/js/lib/d3.v3.min.js"></script>
|
||||
|
||||
<script defer src="/js/client-endload.js?"></script>
|
||||
|
||||
</body></html>
|
||||
|
|
|
|||
|
|
@ -1056,30 +1056,42 @@ export class BattleLog {
|
|||
static escapeFormat(formatid = ''): string {
|
||||
let atIndex = formatid.indexOf('@@@');
|
||||
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));
|
||||
}
|
||||
if (window.BattleFormats && BattleFormats[formatid]) {
|
||||
return this.escapeHTML(BattleFormats[formatid].name);
|
||||
}
|
||||
if (window.NonBattleGames && NonBattleGames[formatid]) {
|
||||
return this.escapeHTML(NonBattleGames[formatid]);
|
||||
}
|
||||
return this.escapeHTML(formatid);
|
||||
return this.escapeHTML(this.formatName(formatid));
|
||||
}
|
||||
/**
|
||||
* Do not store this output anywhere; it removes the generation number
|
||||
* for the current gen.
|
||||
*/
|
||||
static formatName(formatid = ''): string {
|
||||
if (!formatid) return '';
|
||||
|
||||
let atIndex = formatid.indexOf('@@@');
|
||||
if (atIndex >= 0) {
|
||||
return this.formatName(formatid.slice(0, atIndex)) +
|
||||
' (Custom rules: ' + this.escapeHTML(formatid.slice(atIndex + 3)) + ')';
|
||||
}
|
||||
if (!formatid.startsWith('gen')) {
|
||||
formatid = `gen6${formatid}`;
|
||||
}
|
||||
let name = formatid;
|
||||
if (window.BattleFormats && BattleFormats[formatid]) {
|
||||
return BattleFormats[formatid].name;
|
||||
name = BattleFormats[formatid].name;
|
||||
}
|
||||
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) {
|
||||
|
|
|
|||
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,
|
||||
};
|
||||
|
||||
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",
|
||||
* 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;
|
||||
height = 0;
|
||||
/**
|
||||
* popups sometimes initialize hidden, to calculate their position from their
|
||||
* width/height without flickering. But hidden popups can't be focused, so
|
||||
* we need to track their focus timing here.
|
||||
* Preact means that the DOM state lags behind the app state. This means
|
||||
* rooms frequently have `display: none` at the time we want to focus them.
|
||||
* 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;
|
||||
parentRoomid: RoomID | null = null;
|
||||
rightPopup = false;
|
||||
|
|
@ -1134,6 +1148,8 @@ export const PS = new class extends PSModel {
|
|||
|
||||
newsHTML = document.querySelector('#room-news .mini-window-body')?.innerHTML || '';
|
||||
|
||||
libsLoaded = new PSLoadTracker();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
|
@ -1300,12 +1316,11 @@ export const PS = new class extends PSModel {
|
|||
room = PS.rooms[roomid2];
|
||||
const [, type] = args;
|
||||
if (!room) {
|
||||
this.addRoom({
|
||||
room = this.addRoom({
|
||||
id: roomid2,
|
||||
type,
|
||||
connected: true,
|
||||
}, roomid === 'staff' || roomid === 'upperstaff');
|
||||
room = PS.rooms[roomid2];
|
||||
} else {
|
||||
room.type = type;
|
||||
room.connected = true;
|
||||
|
|
@ -1471,8 +1486,11 @@ export const PS = new class extends PSModel {
|
|||
if (this.leftPanel === room) this.leftPanel = newRoom;
|
||||
if (this.rightPanel === room) this.rightPanel = newRoom;
|
||||
if (this.panel === room) this.panel = newRoom;
|
||||
if (this.room === room) this.room = newRoom;
|
||||
if (roomid === '') this.mainmenu = newRoom as MainMenuRoom;
|
||||
if (this.room === room) {
|
||||
this.room = newRoom;
|
||||
newRoom.focusNextUpdate = true;
|
||||
}
|
||||
|
||||
updated = true;
|
||||
}
|
||||
|
|
@ -1490,7 +1508,7 @@ export const PS = new class extends PSModel {
|
|||
}
|
||||
this.closePopupsAbove(room, true);
|
||||
if (!this.isVisible(room)) {
|
||||
room.hiddenInit = true;
|
||||
room.focusNextUpdate = true;
|
||||
}
|
||||
if (PS.isNormalRoom(room)) {
|
||||
if (room.location === 'right' && !this.prefs.onepanel) {
|
||||
|
|
@ -1574,42 +1592,6 @@ export const PS = new class extends PSModel {
|
|||
}
|
||||
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) {
|
||||
this.join(`popup-${this.popups.length}` as RoomID, {
|
||||
args: { message },
|
||||
|
|
@ -1676,6 +1658,7 @@ export const PS = new class extends PSModel {
|
|||
room.receiveLine(args);
|
||||
}
|
||||
}
|
||||
if (!noFocus) room.focusNextUpdate = true;
|
||||
return room;
|
||||
}
|
||||
hideRightRoom() {
|
||||
|
|
@ -1809,6 +1792,7 @@ export const PS = new class extends PSModel {
|
|||
}
|
||||
}
|
||||
removeRoom(room: PSRoom) {
|
||||
const wasFocused = this.room === room;
|
||||
room.destroy();
|
||||
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.setFocus(PS.room);
|
||||
if (wasFocused) {
|
||||
this.room.focusNextUpdate = true;
|
||||
}
|
||||
}
|
||||
/** do NOT use this in a while loop: see `closePopupsUntil */
|
||||
closePopup(skipUpdate?: boolean) {
|
||||
|
|
|
|||
|
|
@ -662,7 +662,7 @@ export class TournamentBracket extends preact.Component<{
|
|||
export class TournamentTreeBracket extends preact.Component<{
|
||||
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) {
|
||||
callback(node, depth);
|
||||
if (node.children) {
|
||||
|
|
@ -678,11 +678,15 @@ export class TournamentTreeBracket extends preact.Component<{
|
|||
}
|
||||
return clonedNode;
|
||||
}
|
||||
/**
|
||||
* Customize tree size. Height is for a single player, a full node is double that.
|
||||
*/
|
||||
static nodeSize = {
|
||||
width: 160, height: 30,
|
||||
width: 160, height: 15,
|
||||
radius: 5,
|
||||
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) {
|
||||
const div = document.createElement('div');
|
||||
|
|
@ -698,11 +702,13 @@ export class TournamentTreeBracket extends preact.Component<{
|
|||
return div;
|
||||
}
|
||||
if (!window.d3) {
|
||||
this.d3Loaded = false;
|
||||
div.innerHTML = `<b>d3 not loaded yet</b>`;
|
||||
this.d3Loader ||= PS.libsLoaded.then(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
return div;
|
||||
}
|
||||
this.d3Loaded = true;
|
||||
this.d3Loader = null;
|
||||
|
||||
let name = PS.user.name;
|
||||
|
||||
|
|
@ -729,9 +735,14 @@ export class TournamentTreeBracket extends preact.Component<{
|
|||
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) {
|
||||
child.highlightLink = true;
|
||||
if (child.team && !child.team.startsWith('(')) {
|
||||
child.highlightLink = true;
|
||||
}
|
||||
}
|
||||
} else if (highlightName) {
|
||||
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 breadthCompression = depthsWithLeaves > 2 ? 0.8 : 2;
|
||||
const maxBreadth = numLeaves - (depthsWithLeaves - 1) / breadthCompression;
|
||||
const maxDepth = hasLeafAtDepth.length;
|
||||
|
||||
const nodeSize: any = { ...TournamentTreeBracket.nodeSize };
|
||||
nodeSize.realWidth = nodeSize.width;
|
||||
nodeSize.realHeight = nodeSize.height;
|
||||
nodeSize.smallRealHeight = nodeSize.height / 2;
|
||||
const nodeSize = TournamentTreeBracket.nodeSize;
|
||||
const size = {
|
||||
width: nodeSize.realWidth * maxDepth + nodeSize.separationX * (maxDepth + 1),
|
||||
height: nodeSize.realHeight * (maxBreadth + 0.5) + nodeSize.separationY * maxBreadth,
|
||||
width: nodeSize.width * maxDepth + nodeSize.separationX * (maxDepth + 1),
|
||||
height: nodeSize.height * 2 * (maxBreadth + 0.5) + nodeSize.separationY * maxBreadth,
|
||||
};
|
||||
|
||||
// Make d3 layout the tree
|
||||
|
||||
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)
|
||||
.children(node => (
|
||||
node.children?.length ? node.children : null!
|
||||
|
|
@ -785,16 +798,16 @@ export class TournamentTreeBracket extends preact.Component<{
|
|||
const layoutRoot = d3.select(div)
|
||||
.append('svg:svg').attr('width', size.width).attr('height', size.height)
|
||||
.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
|
||||
|
||||
const diagonalLink = d3.svg.diagonal()
|
||||
.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 => ({
|
||||
x: link.target.x, y: link.target.y - nodeSize.realWidth / 2,
|
||||
x: link.target.x, y: link.target.y - nodeSize.width / 2,
|
||||
}))
|
||||
.projection(link => [
|
||||
size.width - link.y, link.x,
|
||||
|
|
@ -818,8 +831,8 @@ export class TournamentTreeBracket extends preact.Component<{
|
|||
const outerElem = elem;
|
||||
|
||||
if (node.abbreviated) {
|
||||
elem.append('svg:text').attr('y', -nodeSize.realHeight / 4 + 4)
|
||||
.attr('x', -nodeSize.realWidth / 2 - 7).classed('tournament-bracket-tree-abbreviated', true)
|
||||
elem.append('svg:text').attr('y', -nodeSize.height / 2 + 4)
|
||||
.attr('x', -nodeSize.width / 2 - 7).classed('tournament-bracket-tree-abbreviated', true)
|
||||
.text('...');
|
||||
}
|
||||
|
||||
|
|
@ -841,28 +854,29 @@ export class TournamentTreeBracket extends preact.Component<{
|
|||
if (node.team && !node.team1 && !node.team2) {
|
||||
const rect = elem.append('svg:rect').classed('tournament-bracket-tree-draw', true)
|
||||
.attr('rx', nodeSize.radius)
|
||||
.attr('x', -nodeSize.realWidth / 2).attr('width', nodeSize.realWidth);
|
||||
rect.attr('y', -nodeSize.smallRealHeight / 2).attr('height', nodeSize.smallRealHeight);
|
||||
.attr('x', -nodeSize.width / 2).attr('width', nodeSize.width)
|
||||
.attr('y', -nodeSize.height / 2).attr('height', nodeSize.height);
|
||||
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)
|
||||
.attr('y', nodeSize.textOffset)
|
||||
.classed('tournament-bracket-tree-node-team-draw', true)
|
||||
.text(node.team || '');
|
||||
} else {
|
||||
const rect1 = elem.append('svg:rect')
|
||||
.attr('rx', nodeSize.radius)
|
||||
.attr('x', -nodeSize.realWidth / 2).attr('width', nodeSize.realWidth)
|
||||
.attr('y', -nodeSize.smallRealHeight).attr('height', nodeSize.smallRealHeight);
|
||||
.attr('x', -nodeSize.width / 2).attr('width', nodeSize.width)
|
||||
.attr('y', -nodeSize.height).attr('height', nodeSize.height);
|
||||
const rect2 = elem.append('svg:rect')
|
||||
.attr('rx', nodeSize.radius)
|
||||
.attr('x', -nodeSize.realWidth / 2).attr('width', nodeSize.realWidth)
|
||||
.attr('y', 0).attr('height', nodeSize.smallRealHeight);
|
||||
.attr('x', -nodeSize.width / 2).attr('width', nodeSize.width)
|
||||
.attr('y', 0).attr('height', nodeSize.height);
|
||||
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);
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
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]);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -923,7 +937,7 @@ export class TourPopOutPanel extends PSRoomPanel {
|
|||
static readonly noURL = true;
|
||||
override componentDidMount() {
|
||||
const tour = this.props.room.args?.tour as ChatTournament;
|
||||
if (tour) this.subscribeTo(tour, () => this.forceUpdate());
|
||||
if (tour) this.subscribeTo(tour);
|
||||
}
|
||||
override render() {
|
||||
const room = this.props.room;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import preact from "../js/lib/preact";
|
||||
import type { PSSubscription } from "./client-core";
|
||||
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 { BattleLog } from "./battle-log";
|
||||
import type { Battle } from "./battle";
|
||||
|
|
@ -113,7 +113,8 @@ export class ChatRoom extends PSRoom {
|
|||
this.title = `Console`;
|
||||
} else if (this.id.startsWith('dm-')) {
|
||||
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}`;
|
||||
const nameWithGroup = name;
|
||||
name = name.slice(1);
|
||||
|
|
@ -194,7 +195,7 @@ export class ChatRoom extends PSRoom {
|
|||
if (!formatTargeting ||
|
||||
formats[formatId] ||
|
||||
gens[formatId.slice(0, 4)] ||
|
||||
(gens['gen6'] && formatId.substr(0, 3) !== 'gen')) {
|
||||
(gens['gen6'] && !formatId.startsWith('gen'))) {
|
||||
buffer += '<tr>';
|
||||
} else {
|
||||
buffer += '<tr class="hidden">';
|
||||
|
|
@ -704,10 +705,10 @@ export class ChatTextEntry extends preact.Component<{
|
|||
onInput={this.update}
|
||||
onKeyDown={this.onKeyDown}
|
||||
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
|
||||
disabled={!this.props.room.connected || !canTalk}
|
||||
placeholder={PS.focusPreview(this.props.room)}
|
||||
placeholder={PSView.focusPreview(this.props.room)}
|
||||
/>}
|
||||
</form>
|
||||
{!canTalk && <button data-href="login" class="button autofocus">
|
||||
|
|
@ -725,14 +726,14 @@ class ChatTextBox extends preact.Component<{ placeholder: string, disabled?: boo
|
|||
return false;
|
||||
}
|
||||
handleFocus = () => {
|
||||
PSMain.setTextboxFocused(true);
|
||||
PSView.setTextboxFocused(true);
|
||||
};
|
||||
handleBlur = () => {
|
||||
PSMain.setTextboxFocused(false);
|
||||
PSView.setTextboxFocused(false);
|
||||
};
|
||||
override render() {
|
||||
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}
|
||||
>{'\n'}</pre>;
|
||||
}
|
||||
|
|
@ -745,10 +746,10 @@ class ChatPanel extends PSRoomPanel<ChatRoom> {
|
|||
static readonly location = 'right';
|
||||
static readonly icon = <i class="fa fa-comment-o"></i>;
|
||||
override componentDidMount(): void {
|
||||
super.componentDidMount();
|
||||
this.subscribeTo(PS.user, () => {
|
||||
this.props.room.updateTarget();
|
||||
});
|
||||
super.componentDidMount();
|
||||
}
|
||||
send = (text: string) => {
|
||||
this.props.room.send(text);
|
||||
|
|
@ -818,7 +819,7 @@ class ChatPanel extends PSRoomPanel<ChatRoom> {
|
|||
|
||||
return <PSPanelWrapper room={room} focusClick>
|
||||
<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>
|
||||
{room.tour && <TournamentBox tour={room.tour} left={tinyLayout ? 0 : 146} />}
|
||||
<ChatTextEntry
|
||||
|
|
|
|||
|
|
@ -229,9 +229,7 @@ class LadderListPanel extends PSRoomPanel {
|
|||
static readonly title = 'Ladder';
|
||||
|
||||
override componentDidMount() {
|
||||
this.subscribeTo(PS.teams, () => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
this.subscribeTo(PS.teams);
|
||||
}
|
||||
renderList() {
|
||||
if (!window.BattleFormats) {
|
||||
|
|
|
|||
|
|
@ -129,22 +129,19 @@ class UserPanel extends PSRoomPanel<UserRoom> {
|
|||
buttonbar.push(isSelf ? (
|
||||
<p class="buttonbar">
|
||||
<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>
|
||||
) : !PS.user.named ? (
|
||||
<p class="buttonbar">
|
||||
<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 class="buttonbar">
|
||||
<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="/useroptions"
|
||||
value={`${room.userid as string},${room.parentRoomid as string}`}
|
||||
>{'\u2026'}</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={`useroptions-${user.userid}-${room.parentRoomid || ''}`}>{'\u2026'}</button>
|
||||
</p>
|
||||
));
|
||||
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">
|
||||
{user.avatar !== '[loading]' &&
|
||||
<img
|
||||
{...(room.isSelf ? { 'data-href': 'avatars' } : {})}
|
||||
class={'trainersprite' + (room.isSelf ? ' yours' : '')}
|
||||
src={Dex.resolveAvatar(`${user.avatar || 'unknown'}`)}
|
||||
/>}
|
||||
{avatar && (room.isSelf ? (
|
||||
<img src={avatar} class="trainersprite yours" data-href="avatars" />
|
||||
) : (
|
||||
<img src={avatar} class="trainersprite" />
|
||||
))}
|
||||
<strong><a
|
||||
href={`//${Config.routes.users}/${user.userid}`} target="_blank"
|
||||
style={{ color: away ? '#888888' : BattleLog.usernameColor(user.userid) }}
|
||||
|
|
@ -201,7 +198,7 @@ class UserPanel extends PSRoomPanel<UserRoom> {
|
|||
return <PSPanelWrapper room={room}><div class="pad">
|
||||
{showLookup && <form onSubmit={this.lookup} style={{ minWidth: '278px' }}>
|
||||
<label class="label">
|
||||
Username:
|
||||
Username: {}
|
||||
<input type="search" name="username" class="textbox autofocus" onInput={this.maybeReset} onChange={this.maybeReset} />
|
||||
</label>
|
||||
{!room.userid && <p class="buttonbar">
|
||||
|
|
@ -218,7 +215,7 @@ class UserPanel extends PSRoomPanel<UserRoom> {
|
|||
|
||||
class UserOptionsPanel extends PSRoomPanel {
|
||||
static readonly id = 'useroptions';
|
||||
static readonly routes = ['useroptions'];
|
||||
static readonly routes = ['useroptions-*'];
|
||||
static readonly location = 'popup';
|
||||
static readonly noURL = true;
|
||||
declare state: {
|
||||
|
|
@ -228,6 +225,12 @@ class UserOptionsPanel extends PSRoomPanel {
|
|||
requestSent?: boolean,
|
||||
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) => {
|
||||
this.setState({ showMuteInput: true, showBanInput: false });
|
||||
|
|
@ -247,52 +250,42 @@ class UserOptionsPanel extends PSRoomPanel {
|
|||
};
|
||||
|
||||
handleConfirm = (ev: Event) => {
|
||||
let data = this.state.data;
|
||||
const data = this.state.data;
|
||||
if (!data) return;
|
||||
let roomid = toRoomid(data.room);
|
||||
let room = PS.rooms[roomid];
|
||||
const { targetUser, targetRoom } = this.getTargets();
|
||||
|
||||
let cmd = '';
|
||||
if (data.action === "Mute") {
|
||||
cmd += data.duration === "1 hour" ? "/hourmute " : "/mute ";
|
||||
cmd += `${data.targetUser} ${data.reason ? ',' + data.reason : ''}`;
|
||||
cmd += `${targetUser} ${data.reason ? ',' + data.reason : ''}`;
|
||||
} else {
|
||||
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();
|
||||
};
|
||||
|
||||
handleAddFriend = (ev: Event) => {
|
||||
let args = (this.props.room?.parentElem as HTMLInputElement).value.split(",");
|
||||
let [targetUser, roomid] = args;
|
||||
PS.rooms[roomid]?.send(`/friend add ${targetUser}`);
|
||||
const { targetUser, targetRoom } = this.getTargets();
|
||||
targetRoom?.send(`/friend add ${targetUser}`);
|
||||
this.setState({ requestSent: true });
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
};
|
||||
|
||||
handleIgnore = () => {
|
||||
let args = (this.props.room?.parentElem as HTMLInputElement).value.split(",");
|
||||
let [targetUser, roomid] = args;
|
||||
let room = PS.rooms[roomid];
|
||||
room?.send(`/ignore ${targetUser}`);
|
||||
const { targetUser, targetRoom } = this.getTargets();
|
||||
targetRoom?.send(`/ignore ${targetUser}`);
|
||||
this.close();
|
||||
};
|
||||
|
||||
muteUser = (ev: Event) => {
|
||||
this.setState({ showMuteInput: false });
|
||||
let hrMute = (ev.currentTarget as HTMLButtonElement).value === "1hr";
|
||||
let args = (this.props.room?.parentElem as HTMLInputElement).value.split(",");
|
||||
let [targetUser, roomid] = args;
|
||||
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 = {
|
||||
const hrMute = (ev.currentTarget as HTMLButtonElement).value === "1hr";
|
||||
const reason = this.base?.querySelector<HTMLInputElement>("input[name=mutereason]")?.value;
|
||||
const data = {
|
||||
action: 'Mute',
|
||||
targetUser,
|
||||
room: room?.title,
|
||||
reason,
|
||||
duration: hrMute ? "1 hour" : "7 minutes",
|
||||
};
|
||||
|
|
@ -303,16 +296,10 @@ class UserOptionsPanel extends PSRoomPanel {
|
|||
|
||||
banUser = (ev: Event) => {
|
||||
this.setState({ showBanInput: false });
|
||||
let weekBan = (ev.currentTarget as HTMLButtonElement).value === "1wk";
|
||||
let args = (this.props.room?.parentElem as HTMLInputElement).value.split(",");
|
||||
let [targetUser, roomid] = args;
|
||||
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 = {
|
||||
const weekBan = (ev.currentTarget as HTMLButtonElement).value === "1wk";
|
||||
const reason = this.base?.querySelector<HTMLInputElement>("input[name=banreason]")?.value;
|
||||
const data = {
|
||||
action: 'Ban',
|
||||
targetUser,
|
||||
room: room?.title,
|
||||
reason,
|
||||
duration: weekBan ? "1 week" : "2 days",
|
||||
};
|
||||
|
|
@ -321,20 +308,16 @@ class UserOptionsPanel extends PSRoomPanel {
|
|||
ev.stopImmediatePropagation();
|
||||
};
|
||||
|
||||
update = () => {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
override render() {
|
||||
const room = this.props.room;
|
||||
const parentRoom = PS.rooms[this.props.room.parentRoomid! || ''] as ChatRoom;
|
||||
let canMute = false;
|
||||
let canBan = false;
|
||||
if (parentRoom?.type === "chat") {
|
||||
let banPerms = ["@", "#", "~"];
|
||||
let mutePerms = ["%", ...banPerms];
|
||||
canMute = mutePerms.includes(parentRoom.users[PS.user.userid].charAt(0));
|
||||
canBan = banPerms.includes(parentRoom.users[PS.user.userid].charAt(0));
|
||||
const { targetUser, targetRoom } = this.getTargets();
|
||||
if (targetRoom) {
|
||||
const banPerms = ["@", "#", "~"];
|
||||
const mutePerms = ["%", ...banPerms];
|
||||
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">
|
||||
|
|
@ -344,16 +327,15 @@ class UserOptionsPanel extends PSRoomPanel {
|
|||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<button
|
||||
class="button"
|
||||
data-href={`view-help-request-report-user-${(room.parentElem as HTMLInputElement).value.split(",")[0]}`}
|
||||
>
|
||||
<button data-href={`view-help-request-report-user-${targetUser}`} class="button">
|
||||
Report
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
{this.state.requestSent ? (
|
||||
<button class="button disabled"> Sent request </button>
|
||||
<button class="button disabled">
|
||||
Sent request
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={this.handleAddFriend} class="button">
|
||||
Add friend
|
||||
|
|
@ -378,7 +360,8 @@ class UserOptionsPanel extends PSRoomPanel {
|
|||
<p class="buttonbar">
|
||||
{canMute && !this.state.showBanInput && !this.state.showConfirm && (this.state.showMuteInput ? (
|
||||
<div>
|
||||
<label class="inputlabel"> Reason:
|
||||
<label class="label">
|
||||
Reason: {}
|
||||
<input name="mutereason" class="textbox autofocus" placeholder="Mute reason (optional)" />
|
||||
</label> {} <br />
|
||||
<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 ? (
|
||||
<div>
|
||||
<label class="inputlabel"> Reason:
|
||||
<label class="label">
|
||||
Reason: {}
|
||||
<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="1wk">For 1 Week</button> {}
|
||||
<button class="button" onClick={this.handleCancel}>Cancel</button>
|
||||
|
|
@ -483,6 +467,10 @@ class OptionsPanel extends PSRoomPanel {
|
|||
static readonly location = 'popup';
|
||||
declare state: { showStatusInput?: boolean, showStatusUpdated?: boolean };
|
||||
|
||||
override componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.subscribeTo(PS.user);
|
||||
}
|
||||
setTheme = (e: Event) => {
|
||||
const theme = (e.currentTarget as HTMLSelectElement).value as 'light' | 'dark' | 'system';
|
||||
PS.prefs.set('theme', theme);
|
||||
|
|
@ -493,6 +481,7 @@ class OptionsPanel extends PSRoomPanel {
|
|||
switch (layout) {
|
||||
case '':
|
||||
PS.prefs.set('onepanel', null);
|
||||
PS.rightPanel ||= PS.rooms['rooms'] || null;
|
||||
break;
|
||||
case 'onepanel':
|
||||
PS.prefs.set('onepanel', true);
|
||||
|
|
@ -580,7 +569,7 @@ class OptionsPanel extends PSRoomPanel {
|
|||
</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="register">Register</button>)}
|
||||
|
||||
|
|
@ -767,7 +756,8 @@ class LoginPanel extends PSRoomPanel {
|
|||
</p>}
|
||||
{loginState?.needsPassword && <p>
|
||||
<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
|
||||
class="textbox" type={this.state.passwordShown ? 'text' : 'password'} name="password"
|
||||
autocomplete="current-password" style="width:173px"
|
||||
|
|
@ -960,7 +950,9 @@ class ReplacePlayerPanel extends PSRoomPanel {
|
|||
<input name="newplayer" class="textbox autofocus" />
|
||||
</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">
|
||||
Cancel
|
||||
</button>
|
||||
|
|
@ -1017,58 +1009,36 @@ class ChangePasswordPanel extends PSRoomPanel {
|
|||
</p> }
|
||||
<p>Change your password:</p>
|
||||
<p>
|
||||
<label class="label">Username:
|
||||
<strong><input
|
||||
type="text"
|
||||
name="username"
|
||||
value={PS.user.name}
|
||||
style="
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
font: inherit;
|
||||
font-size: inherit;
|
||||
display: block;
|
||||
"
|
||||
readOnly={true}
|
||||
autocomplete="username"
|
||||
/></strong></label>
|
||||
<label class="label">
|
||||
Username: {}
|
||||
<input name="username" value={PS.user.name} readOnly={true} autocomplete="username" class="textbox disabled" />
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="label">Old password:
|
||||
<input
|
||||
class="textbox autofocus"
|
||||
type="password"
|
||||
name="oldpassword"
|
||||
autocomplete="current-password"
|
||||
/></label>
|
||||
<label class="label">
|
||||
Old password: {}
|
||||
<input name="oldpassword" type="password" autocomplete="current-password" class="textbox autofocus" />
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="label">New password:
|
||||
<input
|
||||
class="textbox"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
/></label>
|
||||
<label class="label">
|
||||
New password: {}
|
||||
<input name="password" type="password" autocomplete="new-password" class="textbox" />
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="label">New password (confirm):
|
||||
<input
|
||||
class="textbox"
|
||||
type="password"
|
||||
name="cpassword"
|
||||
autocomplete="new-password"
|
||||
/></label>
|
||||
<label class="label">
|
||||
New password (confirm): {}
|
||||
<input name="cpassword" type="password" autocomplete="new-password" class="textbox" />
|
||||
</label>
|
||||
</p>
|
||||
<p class="buttonbar">
|
||||
<button type="submit" class="button">
|
||||
<strong>Change password</strong>
|
||||
</button>
|
||||
</button> {}
|
||||
<button type="button" data-cmd="/close" class="button">Cancel</button>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</PSPanelWrapper>;
|
||||
}
|
||||
|
|
@ -1083,10 +1053,6 @@ class RegisterPanel extends PSRoomPanel {
|
|||
|
||||
declare state: { errorMsg: string };
|
||||
|
||||
update = () => {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
handleRegisterUser = (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
let captcha = this.base?.querySelector<HTMLInputElement>('input[name=captcha]')?.value;
|
||||
|
|
@ -1130,55 +1096,37 @@ class RegisterPanel extends PSRoomPanel {
|
|||
</p> }
|
||||
<p>Register your account:</p>
|
||||
<p>
|
||||
<label class="label">Username:
|
||||
<strong><input
|
||||
type="text"
|
||||
name="name"
|
||||
value={PS.user.name}
|
||||
style="
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
font: inherit;
|
||||
font-size: inherit;
|
||||
display: block;
|
||||
"
|
||||
readOnly={true}
|
||||
autocomplete="username"
|
||||
/></strong></label>
|
||||
<label class="label">
|
||||
Username: {}
|
||||
<input name="name" value={PS.user.name} readOnly={true} autocomplete="username" class="textbox disabled" />
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="label">Password:
|
||||
<input
|
||||
class="textbox autofocus"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
/></label>
|
||||
<label class="label">
|
||||
Password: {}
|
||||
<input name="password" type="password" autocomplete="new-password" class="textbox autofocus" />
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="label">Password (confirm):
|
||||
<input
|
||||
class="textbox"
|
||||
type="password"
|
||||
name="cpassword"
|
||||
autocomplete="new-password"
|
||||
/></label>
|
||||
<label class="label">
|
||||
Password (confirm): {}
|
||||
<input name="cpassword" type="password" autocomplete="new-password" class="textbox" />
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="label"> <img
|
||||
<label class="label"><img
|
||||
src="https://play.pokemonshowdown.com/sprites/gen5ani/pikachu.gif"
|
||||
alt="An Electric-type mouse that is the mascot of the Pokémon franchise."
|
||||
/></label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="label">What is this pokemon?
|
||||
<input
|
||||
class="textbox" type="text" name="captcha" value=""
|
||||
/></label>
|
||||
<label class="label">
|
||||
What is this pokemon?{}
|
||||
<input name="captcha" class="textbox" />
|
||||
</label>
|
||||
</p>
|
||||
<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>
|
||||
</p>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
|
|||
<i class="fa fa-chevron-left"></i> List
|
||||
</button>
|
||||
<label class="label teamname">
|
||||
Team name:
|
||||
Team name:{}
|
||||
<input
|
||||
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 { PS, type PSRoom, type RoomID } from "./client-main";
|
||||
import { PSMain } from "./panels";
|
||||
import { PSView } from "./panels";
|
||||
import type { Battle } from "./battle";
|
||||
import { BattleLog } from "./battle-log";
|
||||
|
||||
|
|
@ -194,7 +194,7 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
|||
</span>;
|
||||
}
|
||||
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="scrollable-part">
|
||||
<img
|
||||
|
|
@ -233,7 +233,7 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
|||
}
|
||||
override render() {
|
||||
if (PS.leftPanelWidth === null) {
|
||||
if (!PSMain.textboxFocused) {
|
||||
if (!PSView.textboxFocused) {
|
||||
document.documentElement.classList?.add('scroll-snap-enabled');
|
||||
}
|
||||
return this.renderVertical();
|
||||
|
|
@ -301,11 +301,11 @@ export class PSMiniHeader extends preact.Component {
|
|||
const menuButton = showMenuButton ? (
|
||||
null
|
||||
) : 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>
|
||||
</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>
|
||||
</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 = '';
|
||||
constructor() {
|
||||
const currentRoomid = location.pathname.slice(1);
|
||||
if (/^[a-z0-9-]+$/.test(currentRoomid)) {
|
||||
if (/^[a-z0-9-]*$/.test(currentRoomid)) {
|
||||
this.subscribeHistory();
|
||||
} else if (location.pathname.endsWith('.html')) {
|
||||
this.subscribeHash();
|
||||
|
|
@ -96,8 +96,6 @@ export class PSRouter {
|
|||
const currentRoomid = location.hash.slice(1);
|
||||
if (/^[a-z0-9-]+$/.test(currentRoomid)) {
|
||||
PS.join(currentRoomid as RoomID);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
PS.subscribeAndRun(() => {
|
||||
|
|
@ -120,8 +118,6 @@ export class PSRouter {
|
|||
const currentRoomid = location.pathname.slice(1);
|
||||
if (/^[a-z0-9-]+$/.test(currentRoomid)) {
|
||||
PS.join(currentRoomid as RoomID);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (!window.history) return;
|
||||
PS.subscribeAndRun(() => {
|
||||
|
|
@ -157,13 +153,14 @@ PS.router = new PSRouter();
|
|||
|
||||
export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ room: T }> {
|
||||
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);
|
||||
this.subscriptions.push(subscription);
|
||||
return subscription;
|
||||
}
|
||||
override componentDidMount() {
|
||||
if (PS.room === this.props.room) this.focus();
|
||||
this.props.room.onParentEvent = (id: string, e?: Event) => {
|
||||
if (id === 'focus') this.focus();
|
||||
};
|
||||
|
|
@ -171,7 +168,7 @@ export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ r
|
|||
if (!args) this.forceUpdate();
|
||||
else this.receiveLine(args);
|
||||
}));
|
||||
this.updateDimensions();
|
||||
this.componentDidUpdate();
|
||||
}
|
||||
justUpdatedDimensions = false;
|
||||
updateDimensions() {
|
||||
|
|
@ -194,14 +191,11 @@ export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ r
|
|||
}
|
||||
override componentDidUpdate() {
|
||||
const room = this.props.room;
|
||||
if (this.base && ['popup', 'semimodal-popup'].includes(room.location)) {
|
||||
if (room.width && room.hiddenInit) {
|
||||
room.hiddenInit = false;
|
||||
this.focus();
|
||||
}
|
||||
this.updateDimensions();
|
||||
} else if (this.base && room.hiddenInit) {
|
||||
room.hiddenInit = false;
|
||||
const currentlyHidden = !room.width && room.parentElem && ['popup', 'semimodal-popup'].includes(room.location);
|
||||
this.updateDimensions();
|
||||
if (currentlyHidden) return;
|
||||
if (room.focusNextUpdate) {
|
||||
room.focusNextUpdate = false;
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
|
@ -259,49 +253,86 @@ export function PSPanelWrapper(props: {
|
|||
}
|
||||
return <div
|
||||
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}
|
||||
</div>;
|
||||
}
|
||||
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}>
|
||||
{props.children}
|
||||
</div>;
|
||||
}
|
||||
const style = PSMain.posStyle(room) as any;
|
||||
const style = PSView.posStyle(room) as any;
|
||||
if (props.scrollable === 'hidden') style.overflow = 'hidden';
|
||||
return <div
|
||||
class={'ps-room' + (room.id === '' ? '' : ' ps-room-light') + (props.scrollable === true ? ' scrollable' : '')}
|
||||
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}
|
||||
</div>;
|
||||
}
|
||||
|
||||
export class PSMain extends preact.Component {
|
||||
export class PSView extends preact.Component {
|
||||
static readonly isChrome = navigator.userAgent.includes(' Chrome/');
|
||||
static readonly isSafari = !this.isChrome && navigator.userAgent.includes(' Safari/');
|
||||
static readonly isMac = navigator.platform?.startsWith('Mac');
|
||||
static textboxFocused = false;
|
||||
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
|
||||
|
||||
this.textboxFocused = focused;
|
||||
if (focused) {
|
||||
document.documentElement.classList.remove('scroll-snap-enabled');
|
||||
PSMain.scrollToRoom();
|
||||
PSView.scrollToRoom();
|
||||
} else {
|
||||
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() {
|
||||
super();
|
||||
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
|
||||
// auto-zooms when focusing textboxes (unless the font size is 16px),
|
||||
// and this apparently fixes it while still allowing zooming.
|
||||
|
|
@ -401,7 +432,7 @@ export class PSMain extends preact.Component {
|
|||
PS.update();
|
||||
}
|
||||
if (clickedRoom && !PS.isPopup(clickedRoom)) {
|
||||
PSMain.scrollToRoom();
|
||||
PSView.scrollToRoom();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -494,7 +525,7 @@ export class PSMain extends preact.Component {
|
|||
}
|
||||
static scrollToRoom() {
|
||||
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
|
||||
// note: interferes with the `PSMain.textboxFocused` workaround for a Chrome bug
|
||||
document.documentElement.classList.remove('scroll-snap-enabled');
|
||||
|
|
@ -600,7 +631,7 @@ export class PSMain extends preact.Component {
|
|||
return { maxWidth: width || 480 };
|
||||
}
|
||||
if (!room.width || !room.height) {
|
||||
room.hiddenInit = true;
|
||||
room.focusNextUpdate = true;
|
||||
return {
|
||||
position: 'absolute',
|
||||
visibility: 'hidden',
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
* Buttons
|
||||
*********************************************************/
|
||||
|
||||
button, summary {
|
||||
a, button, summary {
|
||||
cursor: pointer;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1006,6 +1006,9 @@ p.or:after {
|
|||
.roomlist {
|
||||
max-width: 480px;
|
||||
text-align: left;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.roomlist .subrooms {
|
||||
font-size: 8pt;
|
||||
|
|
@ -1344,7 +1347,6 @@ a.ilink.yours {
|
|||
.tournament-bracket-tree-node text, .tournament-bracket-tree-node {
|
||||
font-size: 8.5pt;
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
font-weight: bold;
|
||||
}
|
||||
.tournament-bracket-tree-node a {
|
||||
|
|
@ -2131,6 +2133,7 @@ a.ilink.yours {
|
|||
width: 16px;
|
||||
color: #777777;
|
||||
}
|
||||
.folderpane .text,
|
||||
.folderpane h3 {
|
||||
margin: 0;
|
||||
padding: 13px 0 0 0;
|
||||
|
|
@ -2140,6 +2143,10 @@ a.ilink.yours {
|
|||
color: black;
|
||||
border-right: 1px solid #888888;
|
||||
}
|
||||
.folderpane .text {
|
||||
height: 23px;
|
||||
padding: 7px 0 0 7px;
|
||||
}
|
||||
.folder .selectFolder {
|
||||
display: block;
|
||||
padding: 0 0 0 7px;
|
||||
|
|
@ -2185,6 +2192,7 @@ a.ilink.yours {
|
|||
padding-left: 6px;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* believe me, there was no other way to do this */
|
||||
|
|
@ -3215,6 +3223,7 @@ a.ilink.yours {
|
|||
.dark .folderlistafter:before,
|
||||
.dark .folderlistbefore:before,
|
||||
.dark .folderpane h3,
|
||||
.dark .folderpane .text,
|
||||
.dark .folder .selectFolder,
|
||||
.dark .folderhack1,
|
||||
.dark .folderhack2 {
|
||||
|
|
|
|||
|
|
@ -1228,6 +1228,7 @@ pre.textbox.textbox-empty[placeholder]:before {
|
|||
width: 100%;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tournament-title:hover {
|
||||
background: rgba(242, 247, 250, 0.85);
|
||||
|
|
@ -1249,6 +1250,7 @@ pre.textbox.textbox-empty[placeholder]:before {
|
|||
line-height: 30px;
|
||||
font-weight: normal;
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.tournament-box {
|
||||
|
|
@ -1375,7 +1377,6 @@ pre.textbox.textbox-empty[placeholder]:before {
|
|||
.tournament-bracket-tree-node text, .tournament-bracket-tree-node {
|
||||
font-size: 8.5pt;
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
font-weight: bold;
|
||||
}
|
||||
.tournament-bracket-tree-node a {
|
||||
|
|
@ -2331,104 +2332,3 @@ pre.textbox.textbox-empty[placeholder]:before {
|
|||
.dark .chat.mine {
|
||||
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;
|
||||
color: #777777;
|
||||
}
|
||||
.folderpane .text,
|
||||
.folderpane h3 {
|
||||
margin: 0;
|
||||
padding: 13px 0 0 0;
|
||||
|
|
@ -97,6 +98,10 @@
|
|||
color: black;
|
||||
border-right: 1px solid #888888;
|
||||
}
|
||||
.folderpane .text {
|
||||
height: 23px;
|
||||
padding: 7px 0 0 7px;
|
||||
}
|
||||
.folder .selectFolder {
|
||||
display: block;
|
||||
padding: 0 0 0 7px;
|
||||
|
|
@ -199,6 +204,41 @@
|
|||
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
|
||||
|
|
@ -271,3 +311,69 @@
|
|||
left: 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-rooms.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/panel-chat-tournament.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/lib/jquery-2.2.4.min.js"></script>
|
||||
|
|
@ -146,11 +152,11 @@
|
|||
<script src="js/battle-dex-search.js?"></script>
|
||||
<script src="js/battle-searchresults.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-bw.js"></script>
|
||||
<script src="js/lib/d3.v3.min.js"></script>
|
||||
|
||||
<script src="js/client-endload.js?"></script>
|
||||
|
||||
</body></html>
|
||||
|
|
|
|||
|
|
@ -422,7 +422,7 @@ export class BattlePanel extends preact.Component<{ id: string }> {
|
|||
</form>
|
||||
<p>
|
||||
<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.
|
||||
</p>
|
||||
</section></div>;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user