Preact minor updates batch 16

Minor

- Light mode scrollbar in line with dark mode
  - OS default scrollbars all look ugly for some reason, unless you use
    macOS's hidden scrollbars, which are nice except they're hidden.
- Add Aurastic to credits
- Fix subtle notifications
- Fix "Copy" button and HTML popups
  - These are both for the replay upload popup.
- Fix escaping in teambuilder tables
  - They're escaped in Preact but raw HTML in old client, so using
    Unicode make them work correctly in both.
- Fix a weird issue where history.replaceState was being called too
  often
  - I don't know exactly why this is only recently became a problem,
    but it's easy enough to fix...

Trivial

- More ARIA roles (for blind users)
  - The accessibility tree looks great now.
- Fix an unnecessary closure in AvatarsPanel
- Fix non-ASCII in battle.ts
- Fix class="readmore" to work like class="details" (re: don't do
  expand/collapse hover effect if hovering over a link)
- Fix resizing from one-panel to two-panel mode
  - This was really just one bug in `focusRoom` left over from an old
    architecture where mini-rooms could be panels in vertical tab mode.
  - But I took the opportunity to refactor a bunch of panel code to be
    clearer.
- Slightly redesign open team sheets
- The yellow and black "construction" message no longer needs nested
  divs
This commit is contained in:
Guangcong Luo 2025-04-26 02:38:13 +00:00
parent b7a953930b
commit 734ce0d71d
13 changed files with 138 additions and 57 deletions

View File

@ -658,7 +658,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
const greatItems = [['header', "Popular items"]];
const goodItems = [['header', "Items"]];
const specificItems = [['header', "Pokémon-specific items"]];
const specificItems = [['header', "Pok\u00e9mon-specific items"]];
const poorItems = [['header', "Usually useless items"]];
const badItems = [['header', "Useless items"]];
const unreleasedItems = [];

View File

@ -56,7 +56,7 @@ export class DexSearch {
article: 9,
};
static typeName = {
pokemon: 'Pokémon',
pokemon: 'Pok\u00e9mon',
type: 'Type',
tier: 'Tiers',
move: 'Moves',
@ -460,7 +460,7 @@ export class DexSearch {
switch (fType) {
case 'type':
let type = fId.charAt(0).toUpperCase() + fId.slice(1) as Dex.TypeName;
buf.push(['header', `${type}-type Pokémon`]);
buf.push(['header', `${type}-type Pok\u00e9mon`]);
for (let id in BattlePokedex) {
if (!BattlePokedex[id].types) continue;
if (this.dex.species.get(id).types.includes(type)) {
@ -470,7 +470,7 @@ export class DexSearch {
break;
case 'ability':
let ability = Dex.abilities.get(fId).name;
buf.push(['header', `${ability} Pokémon`]);
buf.push(['header', `${ability} Pok\u00e9mon`]);
for (let id in BattlePokedex) {
if (!BattlePokedex[id].abilities) continue;
if (Dex.hasAbility(this.dex.species.get(id), ability)) {

View File

@ -317,7 +317,7 @@ export class BattleLog {
}
return buf;
}).join('');
divHTML = `<div class="infobox"><details><summary>Open Team Sheet for ${side.name}</summary>${exportedTeam}</details></div>`;
divHTML = `<div class="infobox"><details class="details"><summary>Open team sheet for ${side.name}</summary>${exportedTeam}</details></div>`;
break;
}

View File

@ -592,7 +592,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
if (pokemon.maxhp === 100) return `${pokemon.hp}%`;
if (pokemon.maxhp !== 48) return (100 * pokemon.hp / pokemon.maxhp).toFixed(precision) + '%';
let range = Pokemon.getPixelRange(pokemon.hp, pokemon.hpcolor);
return Pokemon.getFormattedRange(range, precision, '');
return Pokemon.getFormattedRange(range, precision, '\u2013');
}
destroy() {
if (this.sprite) this.sprite.destroy();

View File

@ -819,6 +819,7 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
subtleNotify() {
if (PS.isVisible(this)) return;
this.isSubtleNotifying = true;
PS.update();
}
dismissNotification(id: string) {
this.notifications = this.notifications.filter(notification => notification.id !== id);
@ -1394,26 +1395,6 @@ export const PS = new class extends PSModel {
/** Currently active popups, in stack order (bottom to top) */
popups: RoomID[] = [];
/**
* Currently active left room.
*
* In two-panel mode, this will be the visible left panel.
*
* In one-panel mode, this is the visible room only if it is
* `PS.room`. Still tracked when not visible, so we know which
* panels to display if PS is resized to two-panel mode.
*/
leftPanel: PSRoom = null!;
/**
* Currently active right room.
*
* In two-panel mode, this will be the visible right panel.
*
* In one-panel mode, this is the visible room only if it is
* `PS.room`. Still tracked when not visible, so we know which
* panels to display if PS is resized to two-panel mode.
*/
rightPanel: PSRoom | null = null;
/**
* The currently focused room. Should always be the topmost popup
* if it exists. If no popups are open, it should be
@ -1425,13 +1406,34 @@ export const PS = new class extends PSModel {
*/
room: PSRoom = null!;
/**
* The currently active panel. Should always be either `PS.leftRoom`
* or `PS.rightRoom`. If no popups are open, should be `PS.room`.
* The currently active panel. Should always be either `PS.leftPanel`
* or `PS.leftPanel`. If no popups are open, should be `PS.room`.
*
* In one-panel mode, determines whether the left or right panel is
* visible. Otherwise, no effect.
* visible. Otherwise, it just tracks which panel will be in focus
* after all popups are closed.
*/
panel: PSRoom = null!;
/**
* Currently active left room.
*
* In two-panel mode, this will be the visible left panel.
*
* In one-panel mode, this is the visible room only if it is
* `PS.panel`. Still tracked when not visible, so we know which
* panels to display if PS is resized to two-panel mode.
*/
leftPanel: PSRoom = null!;
/**
* Currently active right room.
*
* In two-panel mode, this will be the visible right panel.
*
* In one-panel mode, this is the visible room only if it is
* `PS.panel`. Still tracked when not visible, so we know which
* panels to display if PS is resized to two-panel mode.
*/
rightPanel: PSRoom | null = null;
/**
* * 0 = only one panel visible
* * null = vertical nav layout
@ -1833,13 +1835,12 @@ export const PS = new class extends PSModel {
room.focusNextUpdate = true;
}
if (PS.isNormalRoom(room)) {
if (room.location === 'right' && !this.prefs.onepanel) {
this.rightPanel = this.panel = room;
if (room.location === 'right') {
this.rightPanel = room;
} else {
this.leftPanel = this.panel = room;
this.leftPanel = room;
}
PS.closeAllPopups(true);
this.room = room;
this.panel = this.room = room;
} else { // popup or mini-window
if (room.location === 'mini-window') {
this.leftPanel = this.panel = PS.mainmenu;

View File

@ -408,7 +408,7 @@ class NewsPanel extends PSRoomPanel {
override render() {
const cookieSet = document.cookie.includes('preactalpha=1');
return <PSPanelWrapper room={this.props.room} fullSize scrollable>
<div class="construction"><div class="construction-inner">
<div class="construction">
This is the Preact client alpha test.
<form>
<label class="checkbox">
@ -424,7 +424,7 @@ class NewsPanel extends PSRoomPanel {
Back to the old client
</label>
</form>
</div></div>
</div>
<div class="readable-bg" dangerouslySetInnerHTML={{ __html: PS.newsHTML }}></div>
</PSPanelWrapper>;
}

View File

@ -923,7 +923,7 @@ class AvatarsPanel extends PSRoomPanel {
return <PSPanelWrapper room={room} width={1210}><div class="pad">
<label class="optlabel"><strong>Choose an avatar or </strong>
<button class="button" onClick={() => this.close()}> Cancel</button>
<button class="button" data-cmd="/close"> Cancel</button>
</label>
<div class="avatarlist">
{avatars.map(([i, avatar]) => (
@ -1590,6 +1590,12 @@ class PopupPanel extends PSRoomPanel<PopupRoom> {
if (!textbox) return;
textbox.value = this.props.room.args?.value as string || '';
}
parseMessage(message: string) {
if (message.startsWith('|html|')) {
return BattleLog.sanitizeHTML(message.slice(6));
}
return BattleLog.parseMessage(message);
}
override render() {
const room = this.props.room;
@ -1601,7 +1607,7 @@ class PopupPanel extends PSRoomPanel<PopupRoom> {
return <PSPanelWrapper room={room} width={480}><form class="pad" onSubmit={this.handleSubmit}>
{room.args?.message && <p
style="white-space:pre-wrap;word-wrap:break-word"
dangerouslySetInnerHTML={{ __html: BattleLog.parseMessage(room.args.message as string) }}
dangerouslySetInnerHTML={{ __html: this.parseMessage(room.args.message as string || '') }}
></p>}
{!!type && <p><input name="value" type={type} class="textbox autofocus" style="width:100%;box-sizing:border-box" /></p>}
<p class="buttonbar">

View File

@ -129,11 +129,11 @@ export class PSHeader extends preact.Component<{ style: object }> {
if (!room) return null;
const closable = (id === '' || id === 'rooms' ? '' : ' closable');
const cur = PS.isVisible(room) ? ' cur' : '';
let notifying = '';
let notifying = room.isSubtleNotifying ? ' subtle-notifying' : '';
let hoverTitle = '';
const notifications = room.notifications;
if (notifications.length) {
notifying = room.isSubtleNotifying ? ' subtle-notifying' : ' notifying';
notifying = ' notifying';
for (const notif of notifications) {
if (!notif.body) continue;
hoverTitle += `${notif.title}\n${notif.body}\n`;
@ -218,7 +218,7 @@ export class PSHeader extends preact.Component<{ style: object }> {
alt="Pokémon Showdown! (beta)"
width="50" height="50"
/>
<div class="tablist">
<div class="tablist" role="tablist">
<ul>
{PSHeader.renderRoomTab(PS.leftRoomList[0])}
</ul>
@ -255,8 +255,8 @@ export class PSHeader extends preact.Component<{ style: object }> {
return <div id="header" class="header" style={this.props.style} role="navigation">
<div class="maintabbarbottom"></div>
<div class="tabbar maintabbar"><div class="inner-1"><div class="inner-2">
<ul class="maintabbar-left" style={{ width: `${PS.leftPanelWidth}px` }}>
<div class="tabbar maintabbar"><div class="inner-1" role={PS.leftPanelWidth ? 'none' : 'tablist'}><div class="inner-2">
<ul class="maintabbar-left" style={{ width: `${PS.leftPanelWidth}px` }} role={PS.leftPanelWidth ? 'tablist' : 'none'}>
<li>
<img
class="logo"
@ -268,7 +268,7 @@ export class PSHeader extends preact.Component<{ style: object }> {
{PSHeader.renderRoomTab(PS.leftRoomList[0])}
{PS.leftRoomList.slice(1).map(roomid => PSHeader.renderRoomTab(roomid))}
</ul>
<ul class="maintabbar-right">
<ul class="maintabbar-right" role={PS.leftPanelWidth ? 'tablist' : 'none'}>
{PS.rightRoomList.map(roomid => PSHeader.renderRoomTab(roomid))}
</ul>
</div></div></div>

View File

@ -60,7 +60,8 @@ export class PSRouter {
return url as RoomID;
}
updatePanelState(): { roomid: RoomID, changed: boolean, newTitle: string } {
/** true: roomid changed, false: panelState changed, null: neither changed */
updatePanelState(): { roomid: RoomID, changed: boolean | null, newTitle: string } {
let room = PS.room;
// some popups don't have URLs and don't generate history
// there's definitely a better way to do this but I'm lazy
@ -79,9 +80,10 @@ export class PSRouter {
PS.leftPanel.id + '..' + PS.rightPanel!.id :
room.id);
const newTitle = roomid === '' ? 'Showdown!' : `${room.title} - Showdown!`;
const changed = (roomid !== this.roomid);
let changed: boolean | null = (roomid !== this.roomid);
this.roomid = roomid;
if (this.panelState === panelState) changed = null;
this.panelState = panelState;
return { roomid, changed, newTitle };
}
@ -130,7 +132,7 @@ export class PSRouter {
const { roomid, changed, newTitle } = this.updatePanelState();
if (changed) {
history.pushState(this.panelState, '', `/${roomid}`);
} else {
} else if (changed !== null) {
history.replaceState(this.panelState, '', `/${roomid}`);
}
// n.b. must be done after changing hash, so history entry has the old title
@ -612,6 +614,20 @@ export class PSView extends preact.Component {
parentElem: elem,
});
return true;
case 'copyText':
const dummyInput = document.createElement("input");
// This is a hack. You can only "select" an input field.
// The trick is to create a short lived input element and destroy it after a copy.
// (stolen from the replay code, obviously --mia)
dummyInput.id = "dummyInput";
dummyInput.value = elem.value || (elem as any).href || "";
dummyInput.style.position = 'absolute';
elem.appendChild(dummyInput);
dummyInput.select();
document.execCommand("copy");
elem.removeChild(dummyInput);
elem.innerText = 'Copied!';
return true;
case 'send':
case 'cmd':
const room = PS.getRoom(elem) || PS.mainmenu;
@ -785,7 +801,7 @@ export class PSView extends preact.Component {
rooms.push(this.renderRoom(room));
}
}
return <div class="ps-frame">
return <div class="ps-frame" role="none">
<PSHeader style={{}} />
<PSMiniHeader />
{rooms}

View File

@ -745,10 +745,11 @@ details.readmore summary, details.details summary {
details.readmore summary::-webkit-details-marker details.details summary::-webkit-details-marker {
display: none;
}
details.readmore summary:after {
details.readmore summary:after, details.readmore summary:hover:has(a:hover):after {
content: '[read more]';
font-family: Verdana, sans-serif;
margin-left: 0.5em;
text-decoration: none;
color: #888;
}
details.readmore summary:hover:after {
@ -782,7 +783,7 @@ details[open] .details-preview {
.details summary:hover, .readmore summary:hover {
background: rgba(119, 153, 187, 0.15);
}
.details summary:hover:has(a:hover) {
.details summary:hover:has(a:hover), .readmore summary:hover:has(a:hover) {
background: transparent;
}
.fa.expandbutton, summary:hover:has(a:hover) .fa.expandbutton {

View File

@ -5,7 +5,7 @@ License: GPLv2
*/
@import url(./battle-log.css?v8.1);
@import url(./battle-log.css?v9);
.battle {
position: absolute;

View File

@ -56,17 +56,19 @@ pre {
}
.construction {
background: repeating-linear-gradient(
border: 15px solid transparent;
padding: 5px 10px;
background: #d9ca28;
background-image: linear-gradient(#d9ca28), repeating-linear-gradient(
-45deg,
#d9ca28,
#d9ca28 10px,
#292824 10px,
#292824 20px
);
padding: 15px;
}
.construction-inner {
background: #d9ca28;
background-origin: border-box;
background-clip: padding-box, border-box;
padding: 5px 10px;
font-weight: bold;
color: black;
@ -1520,6 +1522,60 @@ pre.textbox.textbox-empty[placeholder]:before {
margin: 0 0 0 -1px;
}
html:not(.native-scrollbars) *::-webkit-scrollbar {
-webkit-appearance: none
}
html:not(.native-scrollbars) *::-webkit-scrollbar:vertical {
width: 16px
}
html:not(.native-scrollbars) *::-webkit-scrollbar:horizontal {
height: 16px
}
html:not(.native-scrollbars) *::-webkit-scrollbar-button,html:not(.native-scrollbars) *::-webkit-scrollbar-corner {
display: none
}
html:not(.native-scrollbars) *::-webkit-scrollbar-track {
background: transparent;
border: 1px solid transparent;
}
html:not(.native-scrollbars) *::-webkit-scrollbar-track:hover {
background: #c5cbce;
border-color: #959595;
}
html:not(.native-scrollbars) *::-webkit-scrollbar-track:vertical {
border-width: 0 0 0 1px;
}
html:not(.native-scrollbars) *::-webkit-scrollbar-track:vertical:corner-present {
border-width: 0 0 1px 1px;
border-radius: 0 0 0 2px;
}
html:not(.native-scrollbars) *::-webkit-scrollbar-track:horizontal {
border-width: 1px 1px 0 1px;
border-radius: 2px 2px 0 0;
}
html:not(.native-scrollbars) *::-webkit-scrollbar-thumb {
min-height: 2rem;
background: #acaeaf;
background-clip: padding-box;
border: 5px solid transparent;
border-radius: 10px;
}
html:not(.native-scrollbars) *::-webkit-scrollbar-thumb:hover,html:not(.native-scrollbars) *::-webkit-scrollbar-thumb:active {
background-color: #6c6c6f;
border-width: 4px;
}
html.dark:not(.native-scrollbars) *::-webkit-scrollbar-track:hover {
background: #24282a;
border-color: #000000;
}
html.dark:not(.native-scrollbars) *::-webkit-scrollbar-thumb {
background: #6c6c6f;
}
html.dark:not(.native-scrollbars) *::-webkit-scrollbar-thumb:hover,html.dark:not(.native-scrollbars) *::-webkit-scrollbar-thumb:active {
background-color: #949697;
}
/*********************************************************
* Battle
*********************************************************/

View File

@ -100,7 +100,7 @@ includeHeader();
<li><p><a href="http://sailorwanda.deviantart.com/" target="_blank" class="subtle"><strong>Catherine D.</strong> [SailorCosmos, Matryoshkat]</a> <small>&ndash; Art (battle animations)</small></p></li>
<li><p><strong>Cody Thompson</strong> [Rising_Dusk] <small>&ndash; Development</small></p></li>
<li><p>[<strong>Enigami</strong>] <small>&ndash; Development</small></p></li>
<li><p>[<strong>Hisuian Zoroark, HiZo</strong>] <small>&ndash; Development</small></p></li>
<li><p>[<strong>Hisuian Zoroark</strong>, HiZo] <small>&ndash; Development</small></p></li>
<li><p>[<strong>Honko</strong>] <small>&ndash; Development (damage calculator)</small></p></li>
<li><p><strong>Ian Clail</strong> [Layell] <small>&ndash; Art (battle graphics, sprites)</small></p></li>
<li><p><strong>Jacob McLemore</strong> <small>&ndash; Development</small></p></li>
@ -110,6 +110,7 @@ includeHeader();
<li><p><a href="http://leparagon.deviantart.com/" target="_blank" class="subtle">[<strong>leparagon</strong>]</a> <small>&ndash; Art (sprites, minisprite resizing)</small></p></li>
<li><p>[<strong>livid washed</strong>] <small>&ndash; Development</small></p></li>
<li><p><strong>Luke Harmon-Vellotti</strong> [moo, CheeseMuffin] <small>&ndash; Development</small></p></li>
<li><p><a href="https://mayurhiwale.me/" target="_blank" class="subtle"><strong>Mayur H.</strong> [Aurastic]</a> <small>&ndash; Development (client)</small></p></li>
<li><p><strong>Parth Mane</strong> [PartMan] <small>&ndash; Development</small></p></li>
<li><p>[<strong>Plague von Karma</strong>] <small>&ndash; Development (Gen 1), Research</small></p></li>
<li><p><strong>Russell Jones</strong> [SadisticMystic] <small>&ndash; Research (game mechanics)</small></p></li>