mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-04-24 23:30:37 -05:00
New Replays: Two column support
Old Replays's two column support was the Panel system most notably shown off by PSDex. It was definitely very nice but a bit hard to port to Preact. This new one, written from scratch, has a few niceities: - topbar can scroll offscreen - only one scrollable area (scroll wheel works everywhere, and the PageUp/PageDown/Spacebar keys are unambiguous) - uses very little JavaScript when resizing; most of the layout work is done by CSS With the drawbacks: - no animation - only two columns supported (not relevant to Replays which has never used over two columns) - uses a lot of modern CSS (overflow: sticky, and flexbox) but should degrade gracefully
This commit is contained in:
parent
689531d3b4
commit
3bb2271bbc
|
|
@ -1639,7 +1639,11 @@ export class BattleScene implements BattleSceneStub {
|
|||
}
|
||||
destroy() {
|
||||
this.log.destroy();
|
||||
if (this.$frame) this.$frame.empty();
|
||||
if (this.$frame) {
|
||||
this.$frame.empty();
|
||||
// listeners set by BattleTooltips
|
||||
this.$frame.off();
|
||||
}
|
||||
if (this.bgm) {
|
||||
this.bgm.destroy();
|
||||
this.bgm = null;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,33 @@
|
|||
.linklist li {
|
||||
padding: 2px 0;
|
||||
}
|
||||
.sidebar {
|
||||
float: left;
|
||||
width: 320px;
|
||||
}
|
||||
.bar-wrapper {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.bar-wrapper.has-sidebar {
|
||||
max-width: 1430px;
|
||||
}
|
||||
.mainbar {
|
||||
margin: 0;
|
||||
padding-right: 1px;
|
||||
}
|
||||
.mainbar.has-sidebar {
|
||||
margin-left: 330px;
|
||||
}
|
||||
.section.first-section {
|
||||
margin-top: 9px;
|
||||
}
|
||||
.blocklink small {
|
||||
white-space: normal;
|
||||
}
|
||||
.button {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/** @jsx preact.h */
|
||||
import preact from 'preact';
|
||||
import {Net} from './utils';
|
||||
import {Net, PSModel} from './utils';
|
||||
import {Battle} from '../../../src/battle';
|
||||
import {BattleSound} from '../../../src/battle-sound';
|
||||
import $ from 'jquery';
|
||||
|
|
@ -53,10 +53,6 @@ class SearchPanel extends preact.Component {
|
|||
loggedInUserIsSysop = false;
|
||||
sort = 'date';
|
||||
override componentDidMount() {
|
||||
Net('https://replay.pokemonshowdown.com/search.json').get().then(result => {
|
||||
this.results = JSON.parse(result);
|
||||
this.forceUpdate();
|
||||
});
|
||||
Net('check-login.php').get().then(result => {
|
||||
if (result.charAt(0) !== ']') return;
|
||||
const [userid, sysop] = result.slice(1).split(',');
|
||||
|
|
@ -64,7 +60,10 @@ class SearchPanel extends preact.Component {
|
|||
this.loggedInUserIsSysop = !!sysop;
|
||||
this.forceUpdate();
|
||||
});
|
||||
this.base!.querySelector<HTMLInputElement>('input[name=private]')!.checked = true;
|
||||
const user = decodeURIComponent(/\buser=([^&]*)/.exec(PSRouter.leftLoc || '')?.[1] || '');
|
||||
const format = decodeURIComponent(/\bformat=([^&]*)/.exec(PSRouter.leftLoc || '')?.[1] || '');
|
||||
const isPrivate = PSRouter.leftLoc!.includes('private=1');
|
||||
this.search(user, format, isPrivate);
|
||||
}
|
||||
parseResponse(response: string, isPrivate?: boolean) {
|
||||
this.results = null;
|
||||
|
|
@ -84,12 +83,26 @@ class SearchPanel extends preact.Component {
|
|||
}
|
||||
this.results = results;
|
||||
}
|
||||
search(format: string, user: string, isPrivate?: boolean) {
|
||||
search(user: string, format: string, isPrivate?: boolean) {
|
||||
this.base!.querySelector<HTMLInputElement>('input[name=user]')!.value = user;
|
||||
this.base!.querySelector<HTMLInputElement>('input[name=format]')!.value = format;
|
||||
this.base!.querySelectorAll<HTMLInputElement>('input[name=private]')[isPrivate ? 1 : 0]!.checked = true;
|
||||
|
||||
if (!format && !user) return this.recent();
|
||||
this.format = format;
|
||||
this.user = user;
|
||||
this.format = format;
|
||||
this.results = null;
|
||||
this.resultError = null;
|
||||
|
||||
if (!format && !user) {
|
||||
PSRouter.replace('')
|
||||
} else {
|
||||
PSRouter.replace('?' + Net.encodeQuery({
|
||||
user: user || undefined,
|
||||
format: format || undefined,
|
||||
private: isPrivate ? '1' : undefined,
|
||||
}));
|
||||
}
|
||||
this.forceUpdate();
|
||||
Net(`/api/replays/${isPrivate ? 'searchprivate' : 'search'}`).get({
|
||||
query: {username: this.user, format: this.format},
|
||||
|
|
@ -123,7 +136,7 @@ class SearchPanel extends preact.Component {
|
|||
const format = this.base!.querySelector<HTMLInputElement>('input[name=format]')?.value || '';
|
||||
const user = this.base!.querySelector<HTMLInputElement>('input[name=user]')?.value || '';
|
||||
const isPrivate = !this.base!.querySelector<HTMLInputElement>('input[name=private]')?.checked;
|
||||
this.search(format, user, isPrivate);
|
||||
this.search(user, format, isPrivate);
|
||||
};
|
||||
cancelForm = (e: Event) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -137,6 +150,16 @@ class SearchPanel extends preact.Component {
|
|||
url(replay: ReplayResult) {
|
||||
return replay.id + (replay.password ? `-${replay.password}pw` : '');
|
||||
}
|
||||
formatid(replay: ReplayResult) {
|
||||
let formatid = replay.format;
|
||||
if (!formatid.startsWith('gen') || !/[0-9]/.test(formatid.charAt(3))) {
|
||||
formatid = 'gen6' + formatid;
|
||||
}
|
||||
if (!/^gen[0-9]+-/.test(formatid)) {
|
||||
formatid = formatid.slice(0, 4) + '-' + formatid.slice(4);
|
||||
}
|
||||
return formatid;
|
||||
}
|
||||
override render() {
|
||||
const searchResults = <ul class="linklist">
|
||||
{(this.resultError && <li>
|
||||
|
|
@ -147,13 +170,13 @@ class SearchPanel extends preact.Component {
|
|||
</li>) ||
|
||||
(this.results?.map(result => <li>
|
||||
<a href={this.url(result)} class="blocklink">
|
||||
<small>[{result.format}]<br /></small>
|
||||
<small>[{this.formatid(result)}]<br /></small>
|
||||
<strong>{result.p1}</strong> vs. <strong>{result.p2}</strong>
|
||||
</a>
|
||||
</li>))}
|
||||
</ul>;
|
||||
const activelySearching = !!(this.format || this.user);
|
||||
return <div><section class="section">
|
||||
return <div class={PSRouter.showingRight() ? 'sidebar' : ''}><section class="section first-section">
|
||||
<h1>Search replays</h1>
|
||||
<form onSubmit={this.submitForm}>
|
||||
<p>
|
||||
|
|
@ -173,7 +196,7 @@ class SearchPanel extends preact.Component {
|
|||
<button type="submit" class="button"><i class="fa fa-search" aria-hidden></i> <strong>Search</strong></button> {}
|
||||
{activelySearching && <button class="button" onClick={this.cancelForm}>Cancel</button>}
|
||||
</p>
|
||||
{activelySearching && <h2>Results</h2>}
|
||||
{activelySearching && <h1 aria-label="Results"></h1>}
|
||||
{activelySearching && searchResults}
|
||||
</form>
|
||||
</section>{!activelySearching && <FeaturedReplays />}{!activelySearching && <section class="section">
|
||||
|
|
@ -204,17 +227,17 @@ class FeaturedReplays extends preact.Component {
|
|||
<ul class="linklist">
|
||||
<li><h2>Fun</h2></li>
|
||||
<li><a href="oumonotype-82345404" class="blocklink">
|
||||
<small>[oumonotype]<br /></small>
|
||||
<small>[gen6-oumonotype]<br /></small>
|
||||
<strong>kdarewolf</strong> vs. <strong>Onox</strong>
|
||||
<small><br />Protean + prediction</small>
|
||||
</a></li>
|
||||
<li><a href="anythinggoes-218380995" class="blocklink">
|
||||
<small>[anythinggoes]<br /></small>
|
||||
<small>[gen6-anythinggoes]<br /></small>
|
||||
<strong>Anta2</strong> vs. <strong>dscottnew</strong>
|
||||
<small><br />Cheek Pouch</small>
|
||||
</a></li>
|
||||
<li><a href="uberssuspecttest-147833524" class="blocklink">
|
||||
<small>[ubers]<br /></small>
|
||||
<small>[gen6-ubers]<br /></small>
|
||||
<strong>Metal Brellow</strong> vs. <strong>zig100</strong>
|
||||
<small><br />Topsy-Turvy</small>
|
||||
</a></li>
|
||||
|
|
@ -222,43 +245,43 @@ class FeaturedReplays extends preact.Component {
|
|||
<button class="button" onClick={this.showMoreFun}>More <i class="fa fa-caret-right" aria-hidden></i></button>
|
||||
</li>}
|
||||
{this.moreFun && <li><a href="smogondoubles-75588440" class="blocklink">
|
||||
<small>[smogondoubles]<br /></small>
|
||||
<small>[gen6-smogondoubles]<br /></small>
|
||||
<strong>jamace6</strong> vs. <strong>DubsWelder</strong>
|
||||
<small><br />Garchomp sweeps 11 pokemon</small>
|
||||
</a></li>}
|
||||
{this.moreFun && <li><a href="ou-20651579" class="blocklink">
|
||||
<small>[ou]<br /></small>
|
||||
<small>[gen5-ou]<br /></small>
|
||||
<strong>RainSeven07</strong> vs. <strong>my body is regi</strong>
|
||||
<small><br />An entire team based on Assist V-create</small>
|
||||
</a></li>}
|
||||
{this.moreFun && <li><a href="balancedhackmons7322360" class="blocklink">
|
||||
<small>[balancedhackmons]<br /></small>
|
||||
<small>[gen5-balancedhackmons]<br /></small>
|
||||
<strong>a ver</strong> vs. <strong>Shuckie</strong>
|
||||
<small><br />To a ver's frustration, PP stall is viable in Balanced Hackmons</small>
|
||||
</a></li>}
|
||||
<h2>Competitive</h2>
|
||||
<li><a href="doublesou-232753081" class="blocklink">
|
||||
<small>[doubles ou]<br /></small>
|
||||
<small>[gen6-doublesou]<br /></small>
|
||||
<strong>Electrolyte</strong> vs. <strong>finally</strong>
|
||||
<small><br />finally steals Electrolyte's spot in the finals of the Doubles Winter Seasonal by outplaying Toxic Aegislash.</small>
|
||||
</a></li>
|
||||
<li><a href="smogtours-gen5ou-59402" class="blocklink">
|
||||
<small>[bw ou]<br /></small>
|
||||
<small>[gen5-ou]<br /></small>
|
||||
<strong>Reymedy</strong> vs. <strong>Leftiez</strong>
|
||||
<small><br />Reymedy's superior grasp over BW OU lead to his claim of victory over Leftiez in the No Johns Tournament.</small>
|
||||
</a></li>
|
||||
<li><a href="smogtours-gen3ou-56583" class="blocklink">
|
||||
<small>[adv ou]<br /></small>
|
||||
<small>[gen3-ou]<br /></small>
|
||||
<strong>pokebasket</strong> vs. <strong>Alf'</strong>
|
||||
<small><br />pokebasket proved Blissey isn't really one to take a Focus Punch well in his victory match over Alf' in the Fuck Trappers ADV OU tournament.</small>
|
||||
</a></li>
|
||||
<li><a href="smogtours-ou-55891" class="blocklink">
|
||||
<small>[oras ou]<br /></small>
|
||||
<small>[gen6-ou]<br /></small>
|
||||
<strong>Marshall.Law</strong> vs. <strong>Malekith</strong>
|
||||
<small><br />In a "match full of reverses", Marshall.Law takes on Malekith in the finals of It's No Use.</small>
|
||||
</a></li>
|
||||
<li><a href="smogtours-ubers-54583" class="blocklink">
|
||||
<small>[custom]<br /></small>
|
||||
<small>[gen6-custom]<br /></small>
|
||||
<strong>hard</strong> vs. <strong>panamaxis</strong>
|
||||
<small><br />Dark horse panamaxis proves his worth as the rightful winner of The Walkthrough Tournament in this exciting final versus hard.</small>
|
||||
</a></li>
|
||||
|
|
@ -266,27 +289,27 @@ class FeaturedReplays extends preact.Component {
|
|||
<button class="button" onClick={this.showMoreCompetitive}>More <i class="fa fa-caret-right" aria-hidden></i></button>
|
||||
</li>}
|
||||
{this.moreCompetitive && <li><a href="smogtours-ubers-34646" class="blocklink">
|
||||
<small>[oras ubers]<br /></small>
|
||||
<small>[gen6-ubers]<br /></small>
|
||||
<strong>steelphoenix</strong> vs. <strong>Jibaku</strong>
|
||||
<small><br />In this SPL Week 4 battle, Jibaku's clever plays with Mega Sableye keep the momentum mostly in his favor.</small>
|
||||
</a></li>}
|
||||
{this.moreCompetitive && <li><a href="smogtours-uu-36860" class="blocklink">
|
||||
<small>[oras uu]<br /></small>
|
||||
<small>[gen6-uu]<br /></small>
|
||||
<strong>IronBullet93</strong> vs. <strong>Laurel</strong>
|
||||
<small><br />Laurel outplays IronBullet's Substitute Tyrantrum with the sly use of a Shuca Berry Cobalion, but luck was inevitably the deciding factor in this SPL Week 6 match.</small>
|
||||
</a></li>}
|
||||
{this.moreCompetitive && <li><a href="smogtours-gen5ou-36900" class="blocklink">
|
||||
<small>[bw ou]<br /></small>
|
||||
<small>[gen5-ou]<br /></small>
|
||||
<strong>Lowgock</strong> vs. <strong>Meridian</strong>
|
||||
<small><br />This SPL Week 6 match features impressive plays, from Jirachi sacrificing itself to paralysis to avoid a burn to some clever late-game switches.</small>
|
||||
</a></li>}
|
||||
{this.moreCompetitive && <li><a href="smogtours-gen4ou-36782" class="blocklink">
|
||||
<small>[dpp ou]<br /></small>
|
||||
<small>[gen4-ou]<br /></small>
|
||||
<strong>Heist</strong> vs. <strong>liberty32</strong>
|
||||
<small><br />Starting out as an entry hazard-filled stallfest, this close match is eventually decided by liberty32's efficient use of Aerodactyl.</small>
|
||||
</a></li>}
|
||||
{this.moreCompetitive && <li><a href="randombattle-213274483" class="blocklink">
|
||||
<small>[randombattle]<br /></small>
|
||||
<small>[gen6-randombattle]<br /></small>
|
||||
<strong>The Immortal</strong> vs. <strong>Amphinobite</strong>
|
||||
<small><br />Substitute Lugia and Rotom-Fan take advantage of Slowking's utility and large HP stat, respectively, in this high ladder match.</small>
|
||||
</a></li>}
|
||||
|
|
@ -330,7 +353,19 @@ class BattlePanel extends preact.Component<{id: string}> {
|
|||
battle: Battle | null;
|
||||
speed = 'normal';
|
||||
override componentDidMount() {
|
||||
Net(`https://replay.pokemonshowdown.com/${this.props.id}.json`).get().then(result => {
|
||||
this.loadBattle(this.props.id);
|
||||
showAd('LeaderboardBTF');
|
||||
}
|
||||
override componentWillReceiveProps(nextProps) {
|
||||
if (this.props.id !== nextProps.id) {
|
||||
this.loadBattle(nextProps.id);
|
||||
}
|
||||
}
|
||||
loadBattle(id: string) {
|
||||
if (this.battle) this.battle.destroy();
|
||||
this.battle = null;
|
||||
this.result = undefined;
|
||||
Net(`https://replay.pokemonshowdown.com/${id}.json`).get().then(result => {
|
||||
const replay: NonNullable<BattlePanel['result']> = JSON.parse(result);
|
||||
this.result = replay;
|
||||
const $base = $(this.base!);
|
||||
|
|
@ -348,12 +383,14 @@ class BattlePanel extends preact.Component<{id: string}> {
|
|||
this.battle.subscribe(_ => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
if (PSRouter.rightLoc!.includes('?p2')) {
|
||||
this.battle.switchSides();
|
||||
}
|
||||
this.forceUpdate();
|
||||
}).catch(_ => {
|
||||
this.result = null;
|
||||
this.forceUpdate();
|
||||
});
|
||||
showAd('LeaderboardBTF');
|
||||
}
|
||||
override componentWillUnmount(): void {
|
||||
this.battle?.destroy();
|
||||
|
|
@ -419,7 +456,7 @@ class BattlePanel extends preact.Component<{id: string}> {
|
|||
this.battle?.setMute(muted === 'off');
|
||||
};
|
||||
renderNotFound() {
|
||||
return <div><section class="section" style={{maxWidth: '200px'}}>
|
||||
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'}><section class="section" style={{maxWidth: '200px'}}>
|
||||
{/* <div style={{margin: '0 -20px'}}>
|
||||
<img src="//play.pokemonshowdown.com/sprites/dex/unown-n.png" alt="" width={120} height={120} style={{marginRight: '-60px'}} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/dex/unown-o.png" alt="" width={120} height={120} style={{marginRight: '-60px'}} />
|
||||
|
|
@ -458,11 +495,21 @@ class BattlePanel extends preact.Component<{id: string}> {
|
|||
const atEnd = this.battle?.atQueueEnd;
|
||||
const atStart = !this.battle?.started;
|
||||
if (this.result === null) return this.renderNotFound();
|
||||
return <div style={{maxWidth: 1100, position: 'relative', margin: '0 auto'}}><div>
|
||||
|
||||
let position: any = {};
|
||||
if (PSRouter.showingLeft()) {
|
||||
if (PSRouter.stickyRight) {
|
||||
position = {position: 'sticky', top: '0'};
|
||||
} else {
|
||||
position = {position: 'sticky', bottom: '0'};
|
||||
}
|
||||
}
|
||||
|
||||
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}><div style={{position: 'relative'}}>
|
||||
<BattleDiv />
|
||||
<BattleLogDiv />
|
||||
<div style={{paddingTop: 10}}>
|
||||
<p>
|
||||
<p>
|
||||
{atEnd ?
|
||||
<button onClick={this.replay} class="button" style={{width: '5em'}}>
|
||||
<i class="fa fa-undo"></i><br />Replay
|
||||
|
|
@ -487,9 +534,7 @@ class BattlePanel extends preact.Component<{id: string}> {
|
|||
</button>
|
||||
<button class={"button button-last" + (atEnd ? " disabled" : "")} onClick={this.lastTurn}>
|
||||
<i class="fa fa-fast-forward"></i><br />Skip to end
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
</button> {}
|
||||
<button class="button" onClick={this.switchSides}>
|
||||
<i class="fa fa-random"></i> Switch sides
|
||||
</button> {}
|
||||
|
|
@ -516,31 +561,113 @@ class BattlePanel extends preact.Component<{id: string}> {
|
|||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<h1 style={{fontWeight: 'normal'}}><strong>{this.result?.format}</strong>: {this.result?.p1} vs. {this.result?.p2}</h1>
|
||||
<p>
|
||||
Uploaded: {new Date(this.result?.uploadtime! * 1000 || 0).toDateString()}
|
||||
{this.result?.rating ? ` | Rating: ${this.result?.rating}` : ''}
|
||||
</p>
|
||||
{!this.result && <h1><em>Loading...</em></h1>}
|
||||
{this.result && <h1 style={{fontWeight: 'normal'}}>
|
||||
<strong>{this.result.format}</strong>: {this.result.p1} vs. {this.result.p2}
|
||||
</h1>}
|
||||
{this.result && <p>
|
||||
{this.result.uploadtime ? new Date(this.result.uploadtime * 1000).toDateString() : "Unknown upload date"}
|
||||
{this.result.rating ? ` | Rating: ${this.result.rating}` : ''}
|
||||
</p>}
|
||||
{!PSRouter.showingLeft() && <p>
|
||||
<a href="." class="button"><i class="fa fa-caret-left"></i> More replays</a>
|
||||
</p>}
|
||||
</div>
|
||||
<div id="LeaderboardBTF"></div>
|
||||
</div></div>;
|
||||
}
|
||||
}
|
||||
|
||||
const PSRouter = new class extends PSModel {
|
||||
baseLoc: string;
|
||||
leftLoc: string | null = null;
|
||||
rightLoc: string | null = null;
|
||||
forceSinglePanel = false;
|
||||
stickyRight = true;
|
||||
constructor() {
|
||||
super();
|
||||
const baseLocSlashIndex = document.location.href.lastIndexOf('/');
|
||||
this.baseLoc = document.location.href.slice(0, baseLocSlashIndex + 1);
|
||||
this.go(document.location.href);
|
||||
this.setSinglePanel(true);
|
||||
if (window.history) window.addEventListener('popstate', e => {
|
||||
PSRouter.popState(e);
|
||||
this.update();
|
||||
});
|
||||
window.onresize = () => {
|
||||
PSRouter.setSinglePanel();
|
||||
};
|
||||
}
|
||||
showingLeft() {
|
||||
return this.leftLoc !== null && (!this.forceSinglePanel || this.rightLoc === null);
|
||||
}
|
||||
showingRight() {
|
||||
return this.rightLoc !== null;
|
||||
}
|
||||
setSinglePanel(init?: boolean) {
|
||||
const singlePanel = window.innerWidth < 1300;
|
||||
const stickyRight = (window.innerHeight > 614);
|
||||
if (this.forceSinglePanel !== singlePanel || this.stickyRight !== stickyRight) {
|
||||
this.forceSinglePanel = singlePanel;
|
||||
this.stickyRight = stickyRight;
|
||||
if (!init) this.update();
|
||||
}
|
||||
}
|
||||
push(href: string): boolean {
|
||||
if (!href.startsWith(this.baseLoc)) return false;
|
||||
|
||||
if (this.go(href)) {
|
||||
window.history?.pushState([this.leftLoc, this.rightLoc], '', href);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/** returns whether the URL should change */
|
||||
go(href: string): boolean {
|
||||
if (!href.startsWith(this.baseLoc)) return false;
|
||||
|
||||
const loc = href.slice(this.baseLoc.length);
|
||||
if (!loc || loc.startsWith('?')) {
|
||||
this.leftLoc = loc;
|
||||
if (this.forceSinglePanel) {
|
||||
this.rightLoc = null;
|
||||
} else {
|
||||
return this.rightLoc === null;
|
||||
}
|
||||
} else {
|
||||
this.rightLoc = loc;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
replace(loc: string) {
|
||||
const href = this.baseLoc + loc;
|
||||
if (this.go(href)) {
|
||||
window.history?.replaceState([this.leftLoc, this.rightLoc], '', href);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
popState(e: PopStateEvent) {
|
||||
if (Array.isArray(e.state)) {
|
||||
const [leftLoc, rightLoc] = e.state;
|
||||
this.leftLoc = leftLoc;
|
||||
this.rightLoc = rightLoc;
|
||||
if (this.forceSinglePanel) this.leftLoc = null;
|
||||
} else {
|
||||
this.leftLoc = null;
|
||||
this.rightLoc = null;
|
||||
this.go(document.location.href);
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
class PSReplays extends preact.Component {
|
||||
override componentDidMount() {
|
||||
PSRouter.subscribe(() => this.forceUpdate());
|
||||
if (window.history) {
|
||||
window.addEventListener('popstate', e => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
const baseLocSlashIndex = document.location.href.lastIndexOf('/');
|
||||
const baseLoc = document.location.href.slice(0, baseLocSlashIndex + 1);
|
||||
this.base!.addEventListener('click', e => {
|
||||
let el = e.target as HTMLElement;
|
||||
for (; el; el = el.parentNode as HTMLElement) {
|
||||
if (el.tagName === 'A' && (el as HTMLAnchorElement).href.startsWith(baseLoc)) {
|
||||
const href = (el as HTMLAnchorElement).href;
|
||||
history.pushState(null, '', href);
|
||||
if (el.tagName === 'A' && PSRouter.push((el as HTMLAnchorElement).href)) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
this.forceUpdate();
|
||||
|
|
@ -551,10 +678,13 @@ class PSReplays extends preact.Component {
|
|||
}
|
||||
}
|
||||
override render() {
|
||||
return <div>{
|
||||
document.location.pathname === '/replays/' ?
|
||||
<SearchPanel /> : <BattlePanel id={document.location.pathname.slice(9)} />
|
||||
}</div>;
|
||||
const position = PSRouter.showingLeft() && PSRouter.showingRight() && !PSRouter.stickyRight ?
|
||||
{display: 'flex', flexDirection: 'column', justifyContent: 'flex-end'} : {};
|
||||
return <div class={'bar-wrapper' + (PSRouter.showingLeft() && PSRouter.showingRight() ? ' has-sidebar' : '')} style={position}>
|
||||
{PSRouter.showingLeft() && <SearchPanel />}
|
||||
{PSRouter.showingRight() && <BattlePanel id={PSRouter.rightLoc!} />}
|
||||
<div style={{clear: 'both'}}></div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ if (!window.console) {
|
|||
*********************************************************************/
|
||||
|
||||
export interface PostData {
|
||||
[key: string]: string | number;
|
||||
[key: string]: string | number | undefined;
|
||||
}
|
||||
export interface NetRequestOptions {
|
||||
method?: 'GET' | 'POST';
|
||||
|
|
@ -159,6 +159,7 @@ Net.encodeQuery = function (data: string | PostData) {
|
|||
if (typeof data === 'string') return data;
|
||||
let urlencodedData = '';
|
||||
for (const key in data) {
|
||||
if ((data as any)[key] === undefined) continue;
|
||||
if (urlencodedData) urlencodedData += '&';
|
||||
urlencodedData += encodeURIComponent(key) + '=' + encodeURIComponent((data as any)[key]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ header {
|
|||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.nav a, .dark .nav a {
|
||||
.nav a {
|
||||
color: white;
|
||||
background: #3a4f88;
|
||||
background: linear-gradient(to bottom, #4c63a3, #273661);
|
||||
|
|
@ -63,6 +63,13 @@ header {
|
|||
margin-left: -1px;
|
||||
font-size: 11pt;
|
||||
}
|
||||
.dark .nav a {
|
||||
/* make sure other styling doesn't override */
|
||||
color: white;
|
||||
background: #3a4f88;
|
||||
background: linear-gradient(to bottom, #4c63a3, #273661);
|
||||
border: 1px solid #222c4a;
|
||||
}
|
||||
.nav a:hover, .dark .nav a:hover {
|
||||
background: linear-gradient(to bottom, #5a77c7, #2f447f);
|
||||
border: 1px solid #222c4a;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user