mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Preact: Improve teambuilder more
Some checks failed
Node.js CI / build (22.x) (push) Has been cancelled
Some checks failed
Node.js CI / build (22.x) (push) Has been cancelled
- New export format
- NOT FINAL, wow did Twitter freak out when they saw this
- I have not made a decision on whether to keep this new export format
- Stub details form
- Checkbox for old export format
- 510 EV limit
- Show full search results (with windowing)
- Choosing generations rather than specific formats
- Searching/filtering/sorting results
- Reverse sorting filter columns (move types/categories and
pokemon types/abilities) is now possible even on oldclient
Bugfixes:
- Gen 1 (no item) species selection
- "Add pokemon" button positioning
- Dark mode results
- Width (to fit the final column of results)
- Highlighted line width
This commit is contained in:
parent
734ce0d71d
commit
b5630d3ff2
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -26,7 +26,7 @@
|
|||
"eslint": "^9.20.1",
|
||||
"globals": "^16.0.0",
|
||||
"mocha": "^6.0.2",
|
||||
"preact": "^8.3.1",
|
||||
"preact": "^10.26.5",
|
||||
"source-map": "^0.7.3",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.1"
|
||||
|
|
@ -5228,12 +5228,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-8.5.3.tgz",
|
||||
"integrity": "sha512-O3kKP+1YdgqHOFsZF2a9JVdtqD+RPzCQc3rP+Ualf7V6rmRDchZ9MJbiGTT7LuyqFKZqlHSOyO/oMFmI2lVTsw==",
|
||||
"version": "10.26.5",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.26.5.tgz",
|
||||
"integrity": "sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
"eslint": "^9.20.1",
|
||||
"globals": "^16.0.0",
|
||||
"mocha": "^6.0.2",
|
||||
"preact": "^8.3.1",
|
||||
"preact": "^10.26.5",
|
||||
"source-map": "^0.7.3",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.1"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ https://psim.us/dev
|
|||
<link rel="shortcut icon" href="favicon.ico" id="dynamic-favicon" />
|
||||
<link rel="stylesheet" href="/style/battle.css?" />
|
||||
<link rel="stylesheet" href="/style/client2.css?" />
|
||||
<link rel="stylesheet" href="/style/utilichart.css?" />
|
||||
<link rel="stylesheet" href="/style/teambuilder.css?" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<!--[if lte IE 8]><script>
|
||||
|
|
@ -69,8 +69,8 @@ https://psim.us/dev
|
|||
document.head.appendChild(linkEl);
|
||||
}
|
||||
linkStyle("/style/sim-types.css");
|
||||
linkStyle("/style/teambuilder.css?");
|
||||
linkStyle("style/battle-search.css");
|
||||
linkStyle("style/utilichart.css?");
|
||||
linkStyle("style/battle-search.css?");
|
||||
linkStyle("/style/font-awesome.css");
|
||||
</script>
|
||||
<script nomodule defer src="/js/lib/ps-polyfill.js"></script>
|
||||
|
|
@ -130,6 +130,8 @@ https://psim.us/dev
|
|||
|
||||
<script defer src="/data/pokedex-mini.js?"></script>
|
||||
<script defer src="/data/pokedex-mini-bw.js?"></script>
|
||||
<script defer src="/data/typechart.js?"></script>
|
||||
<script defer src="/data/aliases.js?"></script>
|
||||
<script defer src="/js/lib/d3.v3.min.js"></script>
|
||||
<script defer src="/js/lib/color-thief.min.js"></script>
|
||||
|
||||
|
|
|
|||
|
|
@ -675,11 +675,11 @@ abstract class BattleTypedSearch<T extends SearchType> {
|
|||
}
|
||||
getResults(filters?: SearchFilter[] | null, sortCol?: string | null, reverseSort?: boolean): SearchRow[] {
|
||||
if (sortCol === 'type') {
|
||||
return [this.sortRow!, ...BattleTypeSearch.prototype.getDefaultResults.call(this)];
|
||||
return [this.sortRow!, ...BattleTypeSearch.prototype.getDefaultResults.call(this, reverseSort)];
|
||||
} else if (sortCol === 'category') {
|
||||
return [this.sortRow!, ...BattleCategorySearch.prototype.getDefaultResults.call(this)];
|
||||
return [this.sortRow!, ...BattleCategorySearch.prototype.getDefaultResults.call(this, reverseSort)];
|
||||
} else if (sortCol === 'ability') {
|
||||
return [this.sortRow!, ...BattleAbilitySearch.prototype.getDefaultResults.call(this)];
|
||||
return [this.sortRow!, ...BattleAbilitySearch.prototype.getDefaultResults.call(this, reverseSort)];
|
||||
}
|
||||
|
||||
if (!this.baseResults) {
|
||||
|
|
@ -1189,14 +1189,15 @@ class BattleAbilitySearch extends BattleTypedSearch<'ability'> {
|
|||
getTable() {
|
||||
return BattleAbilities;
|
||||
}
|
||||
getDefaultResults(): SearchRow[] {
|
||||
getDefaultResults(reverseSort?: boolean): SearchRow[] {
|
||||
const results: SearchRow[] = [];
|
||||
for (let id in BattleAbilities) {
|
||||
results.push(['ability', id as ID]);
|
||||
}
|
||||
if (reverseSort) results.reverse();
|
||||
return results;
|
||||
}
|
||||
getBaseResults() {
|
||||
getBaseResults(): SearchRow[] {
|
||||
if (!this.species) return this.getDefaultResults();
|
||||
const format = this.format;
|
||||
const isHackmons = (format.includes('hackmons') || format.endsWith('bh'));
|
||||
|
|
@ -1884,12 +1885,14 @@ class BattleCategorySearch extends BattleTypedSearch<'category'> {
|
|||
getTable() {
|
||||
return { physical: 1, special: 1, status: 1 };
|
||||
}
|
||||
getDefaultResults(): SearchRow[] {
|
||||
return [
|
||||
getDefaultResults(reverseSort?: boolean): SearchRow[] {
|
||||
const results: SearchRow[] = [
|
||||
['category', 'physical' as ID],
|
||||
['category', 'special' as ID],
|
||||
['category', 'status' as ID],
|
||||
];
|
||||
if (reverseSort) results.reverse();
|
||||
return results;
|
||||
}
|
||||
getBaseResults() {
|
||||
return this.getDefaultResults();
|
||||
|
|
@ -1906,11 +1909,12 @@ class BattleTypeSearch extends BattleTypedSearch<'type'> {
|
|||
getTable() {
|
||||
return window.BattleTypeChart;
|
||||
}
|
||||
getDefaultResults(): SearchRow[] {
|
||||
getDefaultResults(reverseSort?: boolean): SearchRow[] {
|
||||
const results: SearchRow[] = [];
|
||||
for (let id in window.BattleTypeChart) {
|
||||
results.push(['type', id as ID]);
|
||||
}
|
||||
if (reverseSort) results.reverse();
|
||||
return results;
|
||||
}
|
||||
getBaseResults() {
|
||||
|
|
|
|||
|
|
@ -1109,7 +1109,7 @@ export class BattleLog {
|
|||
} else if (name.startsWith(`[Gen ${Dex.gen} `)) {
|
||||
name = '[' + name.slice(`[Gen ${Dex.gen} `.length);
|
||||
}
|
||||
return name;
|
||||
return name || `[Gen ${Dex.gen}]`;
|
||||
}
|
||||
|
||||
static escapeHTML(str: string | number, jsEscapeToo?: boolean) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import preact from "../js/lib/preact";
|
|||
import { Dex, type ID } from "./battle-dex";
|
||||
import type { DexSearch, SearchRow } from "./battle-dex-search";
|
||||
|
||||
export class PSSearchResults extends preact.Component<{ search: DexSearch }> {
|
||||
export class PSSearchResults extends preact.Component<{
|
||||
search: DexSearch, searchInitial?: ID | null, windowing?: number | null, firstRow?: SearchRow,
|
||||
}> {
|
||||
readonly URL_ROOT = `//${Config.routes.dex}/`;
|
||||
|
||||
renderPokemonSortRow() {
|
||||
|
|
@ -211,7 +213,7 @@ export class PSSearchResults extends preact.Component<{ search: DexSearch }> {
|
|||
|
||||
<span class="col typecol">
|
||||
<img
|
||||
src={`${Dex.resourcePrefix}sprites/types/${move.type}.png`}
|
||||
src={`${Dex.resourcePrefix}sprites/types/${encodeURIComponent(move.type)}.png`}
|
||||
alt={move.type} height="14" width="32" class="pixelated"
|
||||
/>
|
||||
<img
|
||||
|
|
@ -242,7 +244,10 @@ export class PSSearchResults extends preact.Component<{ search: DexSearch }> {
|
|||
<span class="col namecol">{this.renderName(name, matchStart, matchEnd)}</span>
|
||||
|
||||
<span class="col typecol">
|
||||
<img src={`${Dex.resourcePrefix}sprites/types/${name}.png`} alt={name} height="14" width="32" class="pixelated" />
|
||||
<img
|
||||
src={`${Dex.resourcePrefix}sprites/types/${encodeURIComponent(name)}.png`}
|
||||
alt={name} height="14" width="32" class="pixelated"
|
||||
/>
|
||||
</span>
|
||||
|
||||
{errorMessage}
|
||||
|
|
@ -373,24 +378,32 @@ export class PSSearchResults extends preact.Component<{ search: DexSearch }> {
|
|||
}
|
||||
return <li>Error: not found</li>;
|
||||
}
|
||||
renderFilters() {
|
||||
const search = this.props.search;
|
||||
return search.filters && <p>
|
||||
Filters: {}
|
||||
{search.filters.map(([type, name]) =>
|
||||
<button class="filter" data-filter={`${type}:${name}`}>
|
||||
{name} <i class="fa fa-times-circle" aria-hidden></i>
|
||||
</button>
|
||||
)}
|
||||
{!search.query && <small style="color: #888">(backspace = delete filter)</small>}
|
||||
</p>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const search = this.props.search;
|
||||
return <ul class="dexlist">
|
||||
{search.filters && <p>
|
||||
Filters: {}
|
||||
{search.filters.map(([type, name]) =>
|
||||
<button class="filter" value={`${type}:${name}`}>
|
||||
${name} <i class="fa fa-times-circle" aria-hidden></i>
|
||||
</button>
|
||||
)}
|
||||
{!search.query && <small style="color: #888">(backspace = delete filter)</small>}
|
||||
</p>}
|
||||
{
|
||||
// TODO: implement windowing
|
||||
// for now, just show first twenty results
|
||||
search.results?.slice(0, 20).map(result => this.renderRow(result))
|
||||
}
|
||||
let results = search.results;
|
||||
let searchInitial: SearchRow | null = null;
|
||||
if (this.props.searchInitial && search.typedSearch) {
|
||||
searchInitial = [search.typedSearch.searchType, this.props.searchInitial];
|
||||
}
|
||||
if (this.props.windowing) results = results?.slice(0, this.props.windowing) || null;
|
||||
|
||||
return <ul class="dexlist" style={`min-height: ${(1 + (search.results?.length || 1)) * 33}px;`}>
|
||||
{this.renderFilters()}
|
||||
{searchInitial && this.renderRow(searchInitial)}
|
||||
{results?.map(result => this.renderRow(result))}
|
||||
</ul>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1537,6 +1537,12 @@ export const PS = new class extends PSModel {
|
|||
width: 570,
|
||||
maxWidth: 640,
|
||||
};
|
||||
case 'team':
|
||||
return {
|
||||
minWidth: 660,
|
||||
width: 660,
|
||||
maxWidth: 660,
|
||||
};
|
||||
case 'battle':
|
||||
return {
|
||||
minWidth: 320,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ class TeamRoom extends PSRoom {
|
|||
team!: Team;
|
||||
gen = Dex.gen;
|
||||
dex: ModdedDex = Dex;
|
||||
search = new DexSearch();
|
||||
searchInitial: ID | null = null;
|
||||
constructor(options: RoomOptions) {
|
||||
super(options);
|
||||
const team = PS.teams.byKey[this.id.slice(5)] || null;
|
||||
|
|
@ -135,9 +137,9 @@ class TeamRoom extends PSRoom {
|
|||
}
|
||||
if (natureOverride) {
|
||||
val *= natureOverride;
|
||||
} else if (BattleNatures[set.nature as "Serious"]?.plus === stat) {
|
||||
} else if (BattleNatures[set.nature!]?.plus === stat) {
|
||||
val *= 1.1;
|
||||
} else if (BattleNatures[set.nature as "Serious"]?.minus === stat) {
|
||||
} else if (BattleNatures[set.nature!]?.minus === stat) {
|
||||
val *= 0.9;
|
||||
}
|
||||
if (!supportsEVs) {
|
||||
|
|
@ -166,31 +168,44 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
sets: Dex.PokemonSet[] = [];
|
||||
textbox: HTMLTextAreaElement = null!;
|
||||
heightTester: HTMLTextAreaElement = null!;
|
||||
compat = false;
|
||||
/** we changed the set but are delaying updates until the selection form is closed */
|
||||
setDirty = false;
|
||||
windowing = true;
|
||||
selection: {
|
||||
setIndex: number,
|
||||
offsetY: number | null,
|
||||
type: SelectionType | null,
|
||||
typeIndex: number,
|
||||
lineRange: [number, number] | null,
|
||||
active: boolean,
|
||||
} | null = null;
|
||||
search = new DexSearch();
|
||||
getYAt(index: number, value: string) {
|
||||
innerFocus: {
|
||||
offsetY: number | null,
|
||||
setIndex: number,
|
||||
type: SelectionType,
|
||||
/** i.e. which move is this */
|
||||
typeIndex: number,
|
||||
range: [number, number],
|
||||
/** if you edit, you'll change the range end, so it needs to be updated with this in mind */
|
||||
rangeEndChar: string,
|
||||
} | null = null;
|
||||
getYAt(index: number, fullLine?: boolean) {
|
||||
if (index < 0) return 10;
|
||||
this.heightTester.value = value.slice(0, index);
|
||||
const newValue = this.textbox.value.slice(0, index);
|
||||
this.heightTester.value = fullLine && !newValue.endsWith('\n') ? newValue + '\n' : newValue;
|
||||
return this.heightTester.scrollHeight;
|
||||
}
|
||||
input = () => this.update();
|
||||
keyUp = () => this.update(true);
|
||||
input = () => this.updateText();
|
||||
keyUp = () => this.updateText(true);
|
||||
click = (ev: MouseEvent | KeyboardEvent) => {
|
||||
if (ev.altKey || ev.ctrlKey || ev.metaKey) return;
|
||||
const oldRange = this.selection?.lineRange;
|
||||
this.update(true, true);
|
||||
this.updateText(true, true);
|
||||
if (this.selection) {
|
||||
// this shouldn't actually update anything, so the reference comparison is enough
|
||||
if (this.selection.lineRange === oldRange) return;
|
||||
if (this.textbox.selectionStart === this.textbox.selectionEnd) {
|
||||
const range = this.getSelectionTypeRange();
|
||||
if (range) this.textbox.setSelectionRange(range.start, range.end);
|
||||
if (range) this.textbox.setSelectionRange(range[0], range[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -203,38 +218,72 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
}
|
||||
break;
|
||||
case 9: // tab
|
||||
if (!this.selection?.active) {
|
||||
this.click(ev);
|
||||
if (!this.innerFocus) {
|
||||
if (
|
||||
this.textbox.selectionStart === this.textbox.value.length &&
|
||||
(this.textbox.value.endsWith('\n\n') || !this.textbox.value)
|
||||
) {
|
||||
this.addPokemon();
|
||||
} else {
|
||||
this.click(ev);
|
||||
}
|
||||
ev.stopImmediatePropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
break;
|
||||
case 80: // p
|
||||
if (ev.metaKey) {
|
||||
PS.alert(PSTeambuilder.exportTeam(this.sets, this.props.room.dex));
|
||||
ev.stopImmediatePropagation();
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
closeMenu = () => {
|
||||
if (this.selection?.active) {
|
||||
this.selection.active = false;
|
||||
this.forceUpdate();
|
||||
if (this.innerFocus) {
|
||||
this.innerFocus = null;
|
||||
if (this.setDirty) {
|
||||
this.updateText();
|
||||
} else {
|
||||
this.forceUpdate();
|
||||
}
|
||||
this.textbox.focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
update = (cursorOnly?: boolean, autoSelect?: boolean) => {
|
||||
updateText = (noTextChange?: boolean, autoSelect?: boolean | SelectionType) => {
|
||||
const textbox = this.textbox;
|
||||
const value = textbox.value;
|
||||
const selectionStart = textbox.selectionStart || 0;
|
||||
const selectionEnd = textbox.selectionEnd || 0;
|
||||
|
||||
if (this.selection?.lineRange) {
|
||||
const [start, end] = this.selection.lineRange;
|
||||
if (this.innerFocus) {
|
||||
if (!noTextChange) {
|
||||
let lineEnd = this.textbox.value.indexOf('\n', this.innerFocus.range[0]);
|
||||
if (lineEnd < 0) lineEnd = this.textbox.value.length;
|
||||
const line = this.textbox.value.slice(this.innerFocus.range[0], lineEnd);
|
||||
if (this.innerFocus.rangeEndChar) {
|
||||
const index = line.indexOf(this.innerFocus.rangeEndChar);
|
||||
if (index >= 0) lineEnd = this.innerFocus.range[0] + index;
|
||||
}
|
||||
this.innerFocus.range[1] = lineEnd;
|
||||
}
|
||||
const [start, end] = this.innerFocus.range;
|
||||
if (selectionStart >= start && selectionStart <= end && selectionEnd >= start && selectionEnd <= end) {
|
||||
if (autoSelect && !this.selection.active) {
|
||||
this.selection.active = true;
|
||||
this.forceUpdate();
|
||||
if (!noTextChange) {
|
||||
this.updateSearch();
|
||||
this.setDirty = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.innerFocus = null;
|
||||
}
|
||||
|
||||
if (this.setDirty) {
|
||||
this.setDirty = false;
|
||||
noTextChange = false;
|
||||
}
|
||||
|
||||
this.heightTester.style.width = `${textbox.offsetWidth}px`;
|
||||
|
|
@ -243,7 +292,7 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
/** for the set we're currently parsing */
|
||||
let setIndex: number | null = null;
|
||||
let nextSetIndex = 0;
|
||||
if (!cursorOnly) this.setInfo = [];
|
||||
if (!noTextChange) this.setInfo = [];
|
||||
this.selection = null;
|
||||
|
||||
while (index < value.length) {
|
||||
|
|
@ -257,12 +306,12 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (setIndex === null && index && !cursorOnly) {
|
||||
this.setInfo[this.setInfo.length - 1].bottomY = this.getYAt(index - 1, value);
|
||||
if (setIndex === null && index && !noTextChange && this.setInfo.length) {
|
||||
this.setInfo[this.setInfo.length - 1].bottomY = this.getYAt(index - 1);
|
||||
}
|
||||
|
||||
if (setIndex === null) {
|
||||
if (!cursorOnly) {
|
||||
if (!noTextChange) {
|
||||
const atIndex = line.indexOf('@');
|
||||
let species = atIndex >= 0 ? line.slice(0, atIndex).trim() : line.trim();
|
||||
if (species.endsWith(' (M)') || species.endsWith(' (F)')) {
|
||||
|
|
@ -297,7 +346,8 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
type = 'move';
|
||||
} else if (
|
||||
!lcLine || lcLine.startsWith('level:') || lcLine.startsWith('gender:') ||
|
||||
lcLine.startsWith('shiny:')
|
||||
(lcLine + ':').startsWith('shiny:') || (lcLine + ':').startsWith('gigantamax:') ||
|
||||
lcLine.startsWith('tera type:') || lcLine.startsWith('dynamax level:')
|
||||
) {
|
||||
type = 'details';
|
||||
} else if (
|
||||
|
|
@ -308,29 +358,31 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
} else {
|
||||
type = 'pokemon';
|
||||
const atIndex = line.indexOf('@');
|
||||
if (atIndex >= 0 && selectionStart > index + atIndex) {
|
||||
type = 'item';
|
||||
start = index + atIndex + 1;
|
||||
} else {
|
||||
end = index + atIndex;
|
||||
if (atIndex >= 0) {
|
||||
if (selectionStart > index + atIndex) {
|
||||
type = 'item';
|
||||
start = index + atIndex + 1;
|
||||
} else {
|
||||
end = index + atIndex;
|
||||
if (line.charAt(atIndex - 1) === ']' || line.charAt(atIndex - 2) === ']') {
|
||||
type = 'ability';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const offsetY = this.getYAt(index - 1, value);
|
||||
if (typeof autoSelect === 'string') autoSelect = autoSelect === type;
|
||||
this.selection = {
|
||||
setIndex, offsetY, type, lineRange: [start, end], active: !!autoSelect,
|
||||
setIndex, type, lineRange: [start, end], typeIndex: 0,
|
||||
};
|
||||
const searchType = type !== 'details' && type !== 'stats' ? type : '';
|
||||
this.search.setType(searchType, this.props.team.format, this.sets[setIndex]);
|
||||
this.search.find('');
|
||||
window.search = this.search;
|
||||
if (autoSelect) this.engageFocus();
|
||||
}
|
||||
|
||||
index = nlIndex + 1;
|
||||
}
|
||||
if (!cursorOnly) {
|
||||
if (!noTextChange) {
|
||||
const end = value.endsWith('\n\n') ? value.length - 1 : value.length;
|
||||
const bottomY = this.getYAt(end, value);
|
||||
const bottomY = this.getYAt(end, true);
|
||||
if (this.setInfo.length) {
|
||||
this.setInfo[this.setInfo.length - 1].bottomY = bottomY;
|
||||
}
|
||||
|
|
@ -340,24 +392,134 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
}
|
||||
this.forceUpdate();
|
||||
};
|
||||
engageFocus(focus?: this['innerFocus']) {
|
||||
if (this.innerFocus) return;
|
||||
const { room } = this.props;
|
||||
|
||||
if (!focus) {
|
||||
if (!this.selection?.type) return;
|
||||
|
||||
const range = this.getSelectionTypeRange();
|
||||
if (!range) return;
|
||||
const { type, setIndex } = this.selection;
|
||||
|
||||
let rangeEndChar = this.textbox.value.charAt(range[1]);
|
||||
if (rangeEndChar === ' ') rangeEndChar += this.textbox.value.charAt(range[1] + 1);
|
||||
focus = {
|
||||
offsetY: this.getYAt(range[0]),
|
||||
setIndex,
|
||||
type,
|
||||
typeIndex: this.selection.typeIndex,
|
||||
range,
|
||||
rangeEndChar,
|
||||
};
|
||||
}
|
||||
this.innerFocus = focus;
|
||||
|
||||
if (focus.type === 'details' || focus.type === 'stats') {
|
||||
this.forceUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
room.search.setType(focus.type, room.team.format, this.sets[focus.setIndex]);
|
||||
this.textbox.setSelectionRange(focus.range[0], focus.range[1]);
|
||||
|
||||
let value = this.textbox.value.slice(focus.range[0], focus.range[1]);
|
||||
room.searchInitial = null;
|
||||
switch (focus.type) {
|
||||
case 'pokemon':
|
||||
if (room.dex.species.get(value).exists) {
|
||||
room.searchInitial = toID(value);
|
||||
value = '';
|
||||
}
|
||||
break;
|
||||
case 'item':
|
||||
if (toID(value) === 'noitem') value = '';
|
||||
if (room.dex.items.get(value).exists) {
|
||||
room.searchInitial = toID(value);
|
||||
value = '';
|
||||
}
|
||||
break;
|
||||
case 'ability':
|
||||
if (toID(value) === 'selectability') value = '';
|
||||
if (toID(value) === 'noability') value = '';
|
||||
if (room.dex.abilities.get(value).exists) {
|
||||
room.searchInitial = toID(value);
|
||||
value = '';
|
||||
}
|
||||
break;
|
||||
case 'move':
|
||||
if (room.dex.moves.get(value).exists) {
|
||||
room.searchInitial = toID(value);
|
||||
value = '';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
room.search.find(value);
|
||||
this.windowing = true;
|
||||
this.forceUpdate();
|
||||
}
|
||||
updateSearch() {
|
||||
if (!this.innerFocus) return;
|
||||
const { range } = this.innerFocus;
|
||||
const { room } = this.props;
|
||||
const value = this.textbox.value.slice(range[0], range[1]);
|
||||
|
||||
room.search.find(value);
|
||||
this.windowing = true;
|
||||
this.forceUpdate();
|
||||
}
|
||||
handleClick = (ev: Event) => {
|
||||
const { room } = this.props;
|
||||
let target = ev.target as HTMLElement | null;
|
||||
while (target && target.className !== 'dexlist') {
|
||||
if (target.tagName === 'A') {
|
||||
const entry = target.getAttribute('data-entry');
|
||||
if (entry) {
|
||||
const [type, name] = entry.split('|');
|
||||
this.changeSet(type as SelectionType, name);
|
||||
if (room.search.addFilter([type, name])) {
|
||||
room.search.find('');
|
||||
if (room.search.query) {
|
||||
this.changeSet(this.selection!.type!, '');
|
||||
} else {
|
||||
this.forceUpdate();
|
||||
}
|
||||
} else {
|
||||
this.changeSet(type as SelectionType, name);
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (target.tagName === 'BUTTON') {
|
||||
const filter = target.getAttribute('data-filter');
|
||||
if (filter) {
|
||||
room.search.removeFilter(filter.split(':') as any);
|
||||
room.search.find('');
|
||||
this.forceUpdate();
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
break;
|
||||
}
|
||||
|
||||
// sort
|
||||
const sort = target.getAttribute('data-sort');
|
||||
if (sort) {
|
||||
room.search.toggleSort(sort);
|
||||
room.search.find('');
|
||||
this.forceUpdate();
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
target = target.parentElement;
|
||||
}
|
||||
};
|
||||
getSelectionTypeRange() {
|
||||
getSelectionTypeRange(): [number, number] | null {
|
||||
const selection = this.selection;
|
||||
if (!selection?.lineRange) return null;
|
||||
|
||||
|
|
@ -395,7 +557,7 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
}
|
||||
}
|
||||
|
||||
return { start, end };
|
||||
return [start, end];
|
||||
}
|
||||
case 'item': {
|
||||
// let atIndex = lcLine.lastIndexOf('@');
|
||||
|
|
@ -404,17 +566,28 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
// if (lcLine.charAt(atIndex + 1) === ' ') atIndex++;
|
||||
// return { start: start + atIndex + 1, end };
|
||||
if (lcLine.startsWith(' ')) start++;
|
||||
return { start, end };
|
||||
return [start, end];
|
||||
}
|
||||
case 'ability': {
|
||||
if (lcLine.startsWith('[')) {
|
||||
start++;
|
||||
if (lcLine.endsWith(' ')) {
|
||||
end--;
|
||||
lcLine = lcLine.slice(0, -1);
|
||||
}
|
||||
if (lcLine.endsWith(']')) {
|
||||
end--;
|
||||
}
|
||||
return [start, end];
|
||||
}
|
||||
if (!lcLine.startsWith('ability:')) return null;
|
||||
start += lcLine.startsWith('ability: ') ? 9 : 8;
|
||||
return { start, end };
|
||||
return [start, end];
|
||||
}
|
||||
case 'move': {
|
||||
if (!lcLine.startsWith('-')) return null;
|
||||
start += lcLine.startsWith('- ') ? 2 : 1;
|
||||
return { start, end };
|
||||
return [start, end];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
@ -425,50 +598,67 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
|
||||
const range = this.getSelectionTypeRange();
|
||||
if (range) {
|
||||
this.replace(name, range.start, range.end);
|
||||
this.update(false, true);
|
||||
this.replace(name, range[0], range[1]);
|
||||
this.updateText(false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'pokemon': {
|
||||
const species = this.props.room.dex.species.get(name);
|
||||
const abilities = Object.values(species.abilities);
|
||||
this.sets[selection.setIndex] ||= {
|
||||
ability: abilities.length === 1 ? abilities[0] : undefined,
|
||||
species: '',
|
||||
moves: [],
|
||||
};
|
||||
this.sets[selection.setIndex].species = name;
|
||||
this.replaceSet(selection.setIndex);
|
||||
this.update(false, true);
|
||||
this.updateText(false, true);
|
||||
break;
|
||||
}
|
||||
case 'ability': {
|
||||
this.sets[selection.setIndex].ability = name;
|
||||
this.replaceSet(selection.setIndex);
|
||||
this.update(false, true);
|
||||
this.updateText(false, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
getSetRange(index: number) {
|
||||
const start = this.setInfo[index]?.index ?? this.textbox.value.length;
|
||||
const end = this.setInfo[index + 1]?.index ?? this.textbox.value.length;
|
||||
return [start, end];
|
||||
}
|
||||
changeCompat = (ev: Event) => {
|
||||
const checkbox = ev.currentTarget as HTMLInputElement;
|
||||
this.compat = checkbox.checked;
|
||||
this.sets = PSTeambuilder.importTeam(this.textbox.value);
|
||||
this.textbox.value = PSTeambuilder.exportTeam(this.sets, this.props.room.dex, this.compat);
|
||||
this.updateText();
|
||||
};
|
||||
replaceSet(index: number) {
|
||||
const { room } = this.props;
|
||||
const { team } = room;
|
||||
if (!team) return;
|
||||
|
||||
let newText = PSTeambuilder.exportSet(this.sets[index], room.dex);
|
||||
const start = this.setInfo[index]?.index || this.textbox.value.length;
|
||||
const end = this.setInfo[index + 1]?.index || this.textbox.value.length;
|
||||
if (start === this.textbox.value.length && !this.textbox.value.endsWith('\n\n')) {
|
||||
let newText = PSTeambuilder.exportSet(this.sets[index], room.dex, this.compat);
|
||||
const [start, end] = this.getSetRange(index);
|
||||
if (start && start === this.textbox.value.length && !this.textbox.value.endsWith('\n\n')) {
|
||||
newText = (this.textbox.value.endsWith('\n') ? '\n' : '\n\n') + newText;
|
||||
}
|
||||
this.replace(newText, start, end, start + newText.length);
|
||||
// we won't do a full update but we do need to update where the end is,
|
||||
// for future updates
|
||||
if (this.setInfo[index + 1]) {
|
||||
this.setInfo[index + 1].index = start + newText.length;
|
||||
if (!this.setInfo[index]) {
|
||||
this.updateText();
|
||||
} else {
|
||||
if (this.setInfo[index + 1]) {
|
||||
this.setInfo[index + 1].index = start + newText.length;
|
||||
}
|
||||
// others don't need to be updated;
|
||||
// TODO: a full update next time we focus the textbox
|
||||
} else if (!this.setInfo[index]) {
|
||||
this.update();
|
||||
// we'll do a full update next time we focus the textbox
|
||||
this.setDirty = true;
|
||||
}
|
||||
}
|
||||
replace(text: string, start: number, end: number, selectionStart = start, selectionEnd = start + text.length) {
|
||||
|
|
@ -490,9 +680,9 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
this.heightTester = this.base!.getElementsByClassName('heighttester')[0] as HTMLTextAreaElement;
|
||||
|
||||
this.sets = PSTeambuilder.unpackTeam(this.props.team.packedTeam);
|
||||
const exportedTeam = PSTeambuilder.exportTeam(this.sets, this.props.room.dex);
|
||||
const exportedTeam = PSTeambuilder.exportTeam(this.sets, this.props.room.dex, this.compat);
|
||||
this.textbox.value = exportedTeam;
|
||||
this.update();
|
||||
this.updateText();
|
||||
}
|
||||
override componentWillUnmount() {
|
||||
this.textbox = null!;
|
||||
|
|
@ -501,32 +691,50 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
clickDetails = (ev: Event) => {
|
||||
const target = ev.currentTarget as HTMLButtonElement;
|
||||
const i = parseInt(target.value || '0');
|
||||
if (this.selection?.active && this.selection?.type === target.name) {
|
||||
this.selection.active = false;
|
||||
if (this.innerFocus?.type === target.name) {
|
||||
this.innerFocus = null;
|
||||
this.forceUpdate();
|
||||
return;
|
||||
}
|
||||
this.selection = {
|
||||
this.innerFocus = {
|
||||
offsetY: null,
|
||||
setIndex: i,
|
||||
offsetY: this.setInfo[i].bottomY,
|
||||
type: target.name as SelectionType,
|
||||
lineRange: null,
|
||||
active: true,
|
||||
typeIndex: 0,
|
||||
range: [0, 0],
|
||||
rangeEndChar: '',
|
||||
};
|
||||
this.forceUpdate();
|
||||
};
|
||||
addPokemon = () => {
|
||||
this.selection = {
|
||||
if (!this.textbox.value.endsWith('\n\n')) {
|
||||
this.textbox.value += this.textbox.value.endsWith('\n') ? '\n' : '\n\n';
|
||||
}
|
||||
const end = this.textbox.value.length;
|
||||
this.textbox.setSelectionRange(end, end);
|
||||
this.textbox.focus();
|
||||
this.engageFocus({
|
||||
offsetY: this.getYAt(end, true),
|
||||
setIndex: this.setInfo.length,
|
||||
offsetY: null,
|
||||
type: 'pokemon',
|
||||
lineRange: null,
|
||||
active: true,
|
||||
};
|
||||
this.search.setType('pokemon', this.props.team.format);
|
||||
this.search.find('');
|
||||
typeIndex: 0,
|
||||
range: [end, end],
|
||||
rangeEndChar: '@',
|
||||
});
|
||||
};
|
||||
scrollResults = (ev: Event) => {
|
||||
this.windowing = false;
|
||||
if (PS.leftPanelWidth !== null) {
|
||||
(ev.currentTarget as any).scrollIntoViewIfNeeded?.();
|
||||
}
|
||||
this.forceUpdate();
|
||||
};
|
||||
windowResults() {
|
||||
if (this.windowing) {
|
||||
return Math.ceil(window.innerHeight / 33);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
renderDetails(set: Dex.PokemonSet, i: number) {
|
||||
const { room } = this.props;
|
||||
const { team } = room;
|
||||
|
|
@ -542,8 +750,11 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
|
||||
return <button class="textbox setdetails" name="details" value={i} onClick={this.clickDetails}>
|
||||
<span class="detailcell"><label>Level</label>{set.level || 100}</span>
|
||||
<span class="detailcell"><label>Gender</label>{gender}</span>
|
||||
<span class="detailcell"><label>Shiny</label>{set.shiny ? 'Yes' : 'No'}</span>
|
||||
{room.gen < 9 && <span class="detailcell"><label>Gender</label>{gender}</span>}
|
||||
{room.gen === 9 && <span class="detailcell">
|
||||
<label>Tera</label>{set.teraType || species.forceTeraType || species.types[0]}
|
||||
</span>}
|
||||
</button>;
|
||||
}
|
||||
|
||||
|
|
@ -610,12 +821,13 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
onInput={this.input} onClick={this.click} onKeyUp={this.keyUp} onKeyDown={this.keyDown}
|
||||
/>
|
||||
<textarea
|
||||
class="textbox teamtextbox heighttester" style="visibility:hidden" tabIndex={-1} aria-hidden={true}
|
||||
class="textbox teamtextbox heighttester" style="visibility:hidden;left:-15px" tabIndex={-1} aria-hidden={true}
|
||||
/>
|
||||
<div class="teamoverlays">
|
||||
{this.setInfo.slice(0, -1).map(info =>
|
||||
<hr style={`top:${info.bottomY - 18}px`} />
|
||||
)}
|
||||
{this.setInfo.length < 6 && !!this.setInfo.length && <hr style={`top:${this.bottomY() - 18}px`} />}
|
||||
{this.setInfo.map((info, i) => {
|
||||
if (!info.species) return null;
|
||||
const set = this.sets[i];
|
||||
|
|
@ -642,32 +854,36 @@ class TeamTextbox extends preact.Component<{ team: Team, room: TeamRoom }> {
|
|||
{this.renderDetails(set, i)}
|
||||
</div>];
|
||||
})}
|
||||
{this.setInfo.length < 6 && [
|
||||
!!this.setInfo.length && <hr style={`top:${this.bottomY() - 18}px`} />,
|
||||
{this.setInfo.length < 6 && !(this.innerFocus && this.innerFocus.setIndex >= this.setInfo.length) && (
|
||||
<div style={`top:${this.bottomY() - 3}px ;left:105px;position:absolute`}>
|
||||
<button class="button" onClick={this.addPokemon}>
|
||||
<i class="fa fa-plus" aria-hidden></i> Add Pokémon
|
||||
</button>
|
||||
</div>,
|
||||
]}
|
||||
{this.selection?.active && this.selection.offsetY !== null && (
|
||||
<div class="teaminnertextbox" style={{ top: this.selection.offsetY - 1 }}></div>
|
||||
</div>
|
||||
)}
|
||||
{this.innerFocus?.offsetY != null && (
|
||||
<div
|
||||
class={`teaminnertextbox teaminnertextbox-${this.innerFocus.type}`} style={{ top: this.innerFocus.offsetY - 21 }}
|
||||
></div>
|
||||
)}
|
||||
</div>
|
||||
{this.selection?.active && (
|
||||
<p>
|
||||
<label class="checkbox"><input type="checkbox" name="compat" onChange={this.changeCompat} /> Old export format</label>
|
||||
</p>
|
||||
{this.innerFocus && (
|
||||
<div
|
||||
class="searchresults" style={{ top: (this.setInfo[this.selection.setIndex]?.bottomY ?? this.bottomY() + 50) - 12 }}
|
||||
onClick={this.handleClick}
|
||||
class="searchresults" style={{ top: (this.setInfo[this.innerFocus.setIndex]?.bottomY ?? this.bottomY() + 50) - 12 }}
|
||||
onClick={this.handleClick} onScroll={this.scrollResults}
|
||||
>
|
||||
<button class="button closesearch" onClick={this.closeMenu}>
|
||||
<i class="fa fa-times" aria-hidden></i> Close
|
||||
<kbd>Esc</kbd> <i class="fa fa-times" aria-hidden></i> Close
|
||||
</button>
|
||||
{this.selection.type === 'stats' ? (
|
||||
<StatForm room={this.props.room} set={this.sets[this.selection.setIndex]} onChange={this.handleSetChange} />
|
||||
) : this.selection.type === 'details' ? (
|
||||
<p>Insert details form here</p>
|
||||
{this.innerFocus.type === 'stats' ? (
|
||||
<StatForm room={this.props.room} set={this.sets[this.innerFocus.setIndex]} onChange={this.handleSetChange} />
|
||||
) : this.innerFocus.type === 'details' ? (
|
||||
<DetailsForm room={this.props.room} set={this.sets[this.innerFocus.setIndex]} onChange={this.handleSetChange} />
|
||||
) : (
|
||||
<PSSearchResults search={this.search} />
|
||||
<PSSearchResults search={room.search} searchInitial={room.searchInitial} windowing={this.windowResults()} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -892,6 +1108,7 @@ class StatForm extends preact.Component<{
|
|||
set.evs = guess.evs;
|
||||
this.plus = guess.plusStat || null;
|
||||
this.minus = guess.minusStat || null;
|
||||
this.updateNatureFromPlusMinus();
|
||||
this.props.onChange();
|
||||
};
|
||||
handleOptimize = () => {
|
||||
|
|
@ -1012,17 +1229,22 @@ class StatForm extends preact.Component<{
|
|||
const target = ev.currentTarget as HTMLInputElement;
|
||||
const { set } = this.props;
|
||||
const statID = target.name.split('-')[1] as Dex.StatName;
|
||||
const value = Math.abs(Number(target.value));
|
||||
if (statID === 'hp') {
|
||||
PS.alert("Natures cannot raise or lower HP.");
|
||||
return;
|
||||
}
|
||||
let value = Math.abs(parseInt(target.value));
|
||||
|
||||
if (target.value.includes('+')) {
|
||||
if (statID === 'hp') {
|
||||
PS.alert("Natures cannot raise or lower HP.");
|
||||
return;
|
||||
}
|
||||
this.plus = statID;
|
||||
} else if (this.plus === statID) {
|
||||
this.plus = null;
|
||||
}
|
||||
if (target.value.includes('-')) {
|
||||
if (statID === 'hp') {
|
||||
PS.alert("Natures cannot raise or lower HP.");
|
||||
return;
|
||||
}
|
||||
this.minus = statID;
|
||||
} else if (this.minus === statID) {
|
||||
this.minus = null;
|
||||
|
|
@ -1033,6 +1255,18 @@ class StatForm extends preact.Component<{
|
|||
set.evs ||= {};
|
||||
set.evs[statID] = value;
|
||||
}
|
||||
if (target.type === 'range') {
|
||||
// enforce limit
|
||||
const maxEv = this.maxEVs();
|
||||
if (maxEv < 6 * 252) {
|
||||
let totalEv = 0;
|
||||
for (const curEv of Object.values(set.evs || {})) totalEv += curEv;
|
||||
if (totalEv > maxEv && totalEv - value <= maxEv) {
|
||||
set.evs![statID] = maxEv - (totalEv - value) - (maxEv % 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateNatureFromPlusMinus();
|
||||
this.props.onChange();
|
||||
};
|
||||
|
|
@ -1095,6 +1329,12 @@ class StatForm extends preact.Component<{
|
|||
set.ivs = { hp, atk, def, spa, spd, spe };
|
||||
this.props.onChange();
|
||||
};
|
||||
maxEVs() {
|
||||
const { room } = this.props;
|
||||
const team = room.team;
|
||||
const useEVs = !team.format.includes('letsgo');
|
||||
return useEVs ? 510 : Infinity;
|
||||
}
|
||||
override render() {
|
||||
const { room, set } = this.props;
|
||||
const team = room.team;
|
||||
|
|
@ -1126,7 +1366,20 @@ class StatForm extends preact.Component<{
|
|||
statID, statNames[statID], room.getStat(statID, set),
|
||||
] as const);
|
||||
|
||||
return <div style="font-size:10pt">
|
||||
let remaining = null;
|
||||
const maxEv = this.maxEVs();
|
||||
if (maxEv < 6 * 252) {
|
||||
let totalEv = 0;
|
||||
for (const ev of Object.values(set.evs || {})) totalEv += ev;
|
||||
if (totalEv <= maxEv) {
|
||||
remaining = (totalEv > (maxEv - 2) ? 0 : (maxEv - 2) - totalEv);
|
||||
} else {
|
||||
remaining = maxEv - totalEv;
|
||||
}
|
||||
remaining ||= null;
|
||||
}
|
||||
|
||||
return <div style="font-size:10pt" role="dialog" aria-label="Stats">
|
||||
<div class="resultheader"><h3>EVs, IVs, and Nature</h3></div>
|
||||
<div class="pad">
|
||||
{this.renderSpreadGuesser()}
|
||||
|
|
@ -1146,7 +1399,7 @@ class StatForm extends preact.Component<{
|
|||
<td class="setstatbar">{this.renderStatbar(stat, statID)}</td>
|
||||
<td><input
|
||||
name={`ev-${statID}`} placeholder={`${defaultEV || ''}`}
|
||||
type="text" inputMode="numeric" class="textbox default-placeholder" size={5}
|
||||
type="text" inputMode="numeric" class="textbox default-placeholder" style="width:40px"
|
||||
onInput={this.changeEV} onChange={this.changeEV}
|
||||
/></td>
|
||||
<td><input
|
||||
|
|
@ -1155,14 +1408,14 @@ class StatForm extends preact.Component<{
|
|||
onInput={this.changeEV} onChange={this.changeEV}
|
||||
/></td>
|
||||
<td><input
|
||||
name={`iv-${statID}`} min={0} max={useIVs ? 31 : 15} placeholder={useIVs ? '31' : '15'}
|
||||
name={`iv-${statID}`} min={0} max={useIVs ? 31 : 15} placeholder={useIVs ? '31' : '15'} style="width:40px"
|
||||
type="number" class="textbox default-placeholder" onInput={this.changeIV} onChange={this.changeIV}
|
||||
/></td>
|
||||
<td style="text-align:right"><strong>{stat}</strong></td>
|
||||
</tr>)}
|
||||
<tr>
|
||||
<td colSpan={3} style="text-align:right">Remaining:</td>
|
||||
<td style="text-align:center">0</td>
|
||||
<td colSpan={3} style="text-align:right">{remaining !== null ? 'Remaining:' : ''}</td>
|
||||
<td style="text-align:center">{remaining && remaining < 0 ? <b class="message-error">{remaining}</b> : remaining}</td>
|
||||
<td colSpan={3} style="text-align:right">{this.renderIVMenu()}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -1185,4 +1438,152 @@ class StatForm extends preact.Component<{
|
|||
}
|
||||
}
|
||||
|
||||
class DetailsForm extends preact.Component<{
|
||||
room: TeamRoom,
|
||||
set: Dex.PokemonSet,
|
||||
onChange: () => void,
|
||||
}> {
|
||||
update(init?: boolean) {
|
||||
const { set } = this.props;
|
||||
const skipID = !init ? this.base!.querySelector<HTMLInputElement>('input:focus')?.name : undefined;
|
||||
|
||||
const nickname = this.base!.querySelector<HTMLInputElement>('input[name="nickname"]');
|
||||
if (nickname && skipID !== 'nickname') nickname.value = set.name || '';
|
||||
}
|
||||
override componentDidMount(): void {
|
||||
this.update(true);
|
||||
}
|
||||
override componentDidUpdate(): void {
|
||||
this.update();
|
||||
}
|
||||
changeNickname = (ev: Event) => {
|
||||
const target = ev.currentTarget as HTMLInputElement;
|
||||
const { set } = this.props;
|
||||
if (target.value) {
|
||||
set.name = target.value.trim();
|
||||
} else {
|
||||
delete set.name;
|
||||
}
|
||||
this.props.onChange();
|
||||
};
|
||||
changeTera = (ev: Event) => {
|
||||
const target = ev.currentTarget as HTMLInputElement;
|
||||
const { set } = this.props;
|
||||
const species = this.props.room.dex.species.get(set.species);
|
||||
if (!target.value || target.value === (species.forceTeraType || species.types[0])) {
|
||||
delete set.teraType;
|
||||
} else {
|
||||
set.teraType = target.value.trim();
|
||||
}
|
||||
this.props.onChange();
|
||||
};
|
||||
render() {
|
||||
const { room, set } = this.props;
|
||||
const species = room.dex.species.get(set.species);
|
||||
return <div style="font-size:10pt" role="dialog" aria-label="Details">
|
||||
<div class="resultheader"><h3>Details</h3></div>
|
||||
<div class="pad">
|
||||
<p><label class="label">Nickname: <input
|
||||
name="nickname" class="textbox default-placeholder" placeholder={species.baseSpecies}
|
||||
onInput={this.changeNickname} onChange={this.changeNickname}
|
||||
/></label></p>
|
||||
|
||||
<p>[insert the rest of the details pane]</p>
|
||||
{/*
|
||||
buf += '<div class="formrow"><label class="formlabel">Level:</label><div><input type="number" min="1" max="100" step="1" name="level" value="' + (typeof set.level === 'number' ? set.level : 100) + '" class="textbox inputform numform" /></div></div>';
|
||||
|
||||
if (this.curTeam.gen > 1) {
|
||||
buf += '<div class="formrow"><label class="formlabel">Gender:</label><div>';
|
||||
if (species.gender && !isHackmons) {
|
||||
var genderTable = { 'M': "Male", 'F': "Female", 'N': "Genderless" };
|
||||
buf += genderTable[species.gender];
|
||||
} else {
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="gender" value="M"' + (set.gender === 'M' ? ' checked' : '') + ' /> Male</label> ';
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="gender" value="F"' + (set.gender === 'F' ? ' checked' : '') + ' /> Female</label> ';
|
||||
if (!isHackmons) {
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="gender" value="N"' + (!set.gender ? ' checked' : '') + ' /> Random</label>';
|
||||
} else {
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="gender" value="N"' + (set.gender === 'N' ? ' checked' : '') + ' /> Genderless</label>';
|
||||
}
|
||||
}
|
||||
buf += '</div></div>';
|
||||
|
||||
if (isLetsGo) {
|
||||
buf += '<div class="formrow"><label class="formlabel">Happiness:</label><div><input type="number" name="happiness" value="70" class="textbox inputform numform" /></div></div>';
|
||||
} else {
|
||||
if (this.curTeam.gen < 8 || isNatDex)
|
||||
buf += '<div class="formrow"><label class="formlabel">Happiness:</label><div><input type="number" min="0" max="255" step="1" name="happiness" value="' + (typeof set.happiness === 'number' ? set.happiness : 255) + '" class="textbox inputform numform" /></div></div>';
|
||||
}
|
||||
|
||||
buf += '<div class="formrow"><label class="formlabel">Shiny:</label><div>';
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="shiny" value="yes"' + (set.shiny ? ' checked' : '') + ' /> Yes</label> ';
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="shiny" value="no"' + (!set.shiny ? ' checked' : '') + ' /> No</label>';
|
||||
buf += '</div></div>';
|
||||
|
||||
if (this.curTeam.gen === 8 && !isBDSP) {
|
||||
if (!species.cannotDynamax) {
|
||||
buf += '<div class="formrow"><label class="formlabel">Dmax Level:</label><div><input type="number" min="0" max="10" step="1" name="dynamaxlevel" value="' + (typeof set.dynamaxLevel === 'number' ? set.dynamaxLevel : 10) + '" class="textbox inputform numform" /></div></div>';
|
||||
}
|
||||
if (species.canGigantamax || species.forme === 'Gmax') {
|
||||
buf += '<div class="formrow"><label class="formlabel">Gigantamax:</label><div>';
|
||||
if (species.forme === 'Gmax') {
|
||||
buf += 'Yes';
|
||||
} else {
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="gigantamax" value="yes"' + (set.gigantamax ? ' checked' : '') + ' /> Yes</label> ';
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="gigantamax" value="no"' + (!set.gigantamax ? ' checked' : '') + ' /> No</label>';
|
||||
}
|
||||
buf += '</div></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.curTeam.gen > 2) {
|
||||
buf += '<div class="formrow" style="display:none"><label class="formlabel">Pokeball:</label><div><select name="pokeball" class="button">';
|
||||
buf += '<option value=""' + (!set.pokeball ? ' selected="selected"' : '') + '></option>'; // unset
|
||||
var balls = this.curTeam.dex.getPokeballs();
|
||||
for (var i = 0; i < balls.length; i++) {
|
||||
buf += '<option value="' + balls[i] + '"' + (set.pokeball === balls[i] ? ' selected="selected"' : '') + '>' + balls[i] + '</option>';
|
||||
}
|
||||
buf += '</select></div></div>';
|
||||
}
|
||||
|
||||
if (!isLetsGo && (this.curTeam.gen === 7 || isNatDex || (isBDSP && species.baseSpecies === 'Unown'))) {
|
||||
buf += '<div class="formrow"><label class="formlabel" title="Hidden Power Type">Hidden Power:</label><div><select name="hptype" class="button">';
|
||||
buf += '<option value=""' + (!set.hpType ? ' selected="selected"' : '') + '>(automatic type)</option>'; // unset
|
||||
var types = Dex.types.all();
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
if (types[i].HPivs) {
|
||||
buf += '<option value="' + types[i].name + '"' + (set.hpType === types[i].name ? ' selected="selected"' : '') + '>' + types[i].name + '</option>';
|
||||
}
|
||||
}
|
||||
buf += '</select></div></div>';
|
||||
}
|
||||
|
||||
*/}
|
||||
{room.gen === 9 && <p>
|
||||
<label class="label" title="Tera Type">
|
||||
Tera Type: {}
|
||||
{species.forceTeraType ? (
|
||||
<select name="teratype" class="button cur" disabled><option>{species.forceTeraType}</option></select>
|
||||
) : (
|
||||
<select name="teratype" class="button" onChange={this.changeTera}>
|
||||
{Dex.types.all().map(type => (
|
||||
<option value={type.name} selected={(set.teraType || species.types[0]) === type.name}>
|
||||
{type.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</label>
|
||||
</p>}
|
||||
{species.cosmeticFormes && <p>
|
||||
<button class="button">
|
||||
Change sprite
|
||||
</button>
|
||||
</p>}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
PS.addRoomType(TeamPanel);
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@
|
|||
import { PS, PSRoom, type Team } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import { TeamBox, TeamFolder } from "./panel-teamdropdown";
|
||||
import { PSUtils, type ID } from "./battle-dex";
|
||||
import { Dex, PSUtils, type ID } from "./battle-dex";
|
||||
|
||||
class TeambuilderRoom extends PSRoom {
|
||||
readonly DEFAULT_FORMAT = 'gen8' as ID;
|
||||
readonly DEFAULT_FORMAT = `gen${Dex.gen}` as ID;
|
||||
|
||||
/**
|
||||
* - `""` - all
|
||||
|
|
|
|||
|
|
@ -95,12 +95,13 @@ export class PSTeambuilder {
|
|||
|
||||
if (
|
||||
set.pokeball || (set.hpType && toID(set.hpType) !== hasHP) || set.gigantamax ||
|
||||
(set.dynamaxLevel !== undefined && set.dynamaxLevel !== 10)
|
||||
(set.dynamaxLevel !== undefined && set.dynamaxLevel !== 10) || set.teraType
|
||||
) {
|
||||
buf += `,${set.hpType || ''}`;
|
||||
buf += `,${toID(set.pokeball)}`;
|
||||
buf += `,${set.gigantamax ? 'G' : ''}`;
|
||||
buf += `,${set.dynamaxLevel !== undefined && set.dynamaxLevel !== 10 ? set.dynamaxLevel : ''}`;
|
||||
buf += `,${set.teraType || ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,12 +188,13 @@ export class PSTeambuilder {
|
|||
|
||||
// happiness
|
||||
if (parts[11]) {
|
||||
let misc = parts[11].split(',', 4);
|
||||
const misc = parts[11].split(',', 6);
|
||||
set.happiness = (misc[0] ? Number(misc[0]) : undefined);
|
||||
set.hpType = misc[1];
|
||||
set.pokeball = misc[2];
|
||||
set.gigantamax = !!misc[3];
|
||||
set.dynamaxLevel = (misc[4] ? Number(misc[4]) : 10);
|
||||
set.dynamaxLevel = (misc[4] ? Number(misc[4]) : undefined);
|
||||
set.teraType = misc[5];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,16 +215,16 @@ export class PSTeambuilder {
|
|||
}
|
||||
if (set.gender === 'M') text += ` (M)`;
|
||||
if (set.gender === 'F') text += ` (F)`;
|
||||
if (set.item) {
|
||||
if (compat && set.item) {
|
||||
text += ` @ ${set.item}`;
|
||||
} else if (!compat && dex.gen > 1) {
|
||||
text += ` @ (No Item)`;
|
||||
}
|
||||
text += `\n`;
|
||||
if (set.ability && set.ability !== 'No Ability') {
|
||||
if ((set.item || set.ability || dex.gen >= 2) && !compat) {
|
||||
if (set.ability || dex.gen >= 3) text += `[${set.ability || '(select ability)'}]`;
|
||||
if (set.item || dex.gen >= 2) text += ` @ ${set.item || "(no item)"}`;
|
||||
text += `\n`;
|
||||
} else if (set.ability && set.ability !== 'No Ability') {
|
||||
text += `Ability: ${set.ability}\n`;
|
||||
} else if (!compat && dex.gen > 2) {
|
||||
text += `Ability: (No Ability)\n`;
|
||||
}
|
||||
|
||||
if (!compat) {
|
||||
|
|
@ -246,7 +248,7 @@ export class PSTeambuilder {
|
|||
if (set.evs || set.nature) {
|
||||
const nature = BattleNatures[set.nature as 'Serious'];
|
||||
for (const stat of Dex.statNames) {
|
||||
const plusMinus = nature?.plus === stat ? '+' : nature?.minus === stat ? '-' : '';
|
||||
const plusMinus = compat ? '' : nature?.plus === stat ? '+' : nature?.minus === stat ? '-' : '';
|
||||
const ev = set.evs?.[stat] || '';
|
||||
if (ev === '' && !plusMinus) continue;
|
||||
text += first ? `EVs: ` : ` / `;
|
||||
|
|
@ -255,6 +257,7 @@ export class PSTeambuilder {
|
|||
}
|
||||
}
|
||||
if (!first) {
|
||||
if (set.nature && !compat) text += ` (${set.nature})`;
|
||||
text += `\n`;
|
||||
}
|
||||
if (set.nature && compat) {
|
||||
|
|
@ -293,7 +296,10 @@ export class PSTeambuilder {
|
|||
text += `Dynamax Level: ${set.dynamaxLevel}\n`;
|
||||
}
|
||||
if (set.gigantamax) {
|
||||
text += `Gigantamax: Yes\n`;
|
||||
text += compat ? `Gigantamax: Yes\n` : `Gigantamax\n`;
|
||||
}
|
||||
if (set.teraType) {
|
||||
text += `Tera Type: ${set.teraType}\n`;
|
||||
}
|
||||
|
||||
if (set.moves && compat) {
|
||||
|
|
@ -301,7 +307,7 @@ export class PSTeambuilder {
|
|||
if (move.startsWith('Hidden Power ')) {
|
||||
const hpType = move.slice(13);
|
||||
move = move.slice(0, 13);
|
||||
move = `${move}[${hpType}]`;
|
||||
move = compat ? `${move}[${hpType}]` : `${move}${hpType}`;
|
||||
}
|
||||
if (move) {
|
||||
text += `- ${move}\n`;
|
||||
|
|
@ -312,11 +318,11 @@ export class PSTeambuilder {
|
|||
text += `\n`;
|
||||
return text;
|
||||
}
|
||||
static exportTeam(sets: Dex.PokemonSet[], dex?: ModdedDex) {
|
||||
static exportTeam(sets: Dex.PokemonSet[], dex?: ModdedDex, compat?: boolean) {
|
||||
let text = '';
|
||||
for (const set of sets) {
|
||||
// core
|
||||
text += PSTeambuilder.exportSet(set, dex);
|
||||
text += PSTeambuilder.exportSet(set, dex, compat);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
|
@ -331,9 +337,11 @@ export class PSTeambuilder {
|
|||
return [buffer.slice(0, delimIndex), buffer.slice(delimIndex + delimiter.length)];
|
||||
}
|
||||
static parseExportedTeamLine(line: string, isFirstLine: boolean, set: Dex.PokemonSet) {
|
||||
if (isFirstLine) {
|
||||
if (isFirstLine || line.startsWith('[')) {
|
||||
let item;
|
||||
[line, item] = line.split(' @ ');
|
||||
[line, item] = line.split('@');
|
||||
line = line.trim();
|
||||
item = item?.trim();
|
||||
if (item) {
|
||||
set.item = item;
|
||||
if (toID(set.item) === 'noitem') set.item = '';
|
||||
|
|
@ -346,68 +354,75 @@ export class PSTeambuilder {
|
|||
set.gender = 'F';
|
||||
line = line.slice(0, -4);
|
||||
}
|
||||
let parenIndex = line.lastIndexOf(' (');
|
||||
if (line.endsWith(')') && parenIndex !== -1) {
|
||||
set.species = Dex.species.get(line.slice(parenIndex + 2, -1)).name;
|
||||
set.name = line.slice(0, parenIndex);
|
||||
} else {
|
||||
set.species = Dex.species.get(line).name;
|
||||
set.name = '';
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
// the ending `]` is necessary to establish this as ability
|
||||
// (rather than nickname starting with `[`)
|
||||
set.ability = line.slice(1, -1);
|
||||
if (toID(set.ability) === 'selectability') {
|
||||
set.ability = '';
|
||||
}
|
||||
} else if (line) {
|
||||
const parenIndex = line.lastIndexOf(' (');
|
||||
if (line.endsWith(')') && parenIndex !== -1) {
|
||||
set.species = Dex.species.get(line.slice(parenIndex + 2, -1)).name;
|
||||
set.name = line.slice(0, parenIndex);
|
||||
} else {
|
||||
set.species = Dex.species.get(line).name;
|
||||
set.name = '';
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith('Trait: ')) {
|
||||
line = line.slice(7);
|
||||
set.ability = line;
|
||||
set.ability = line.slice(7);
|
||||
} else if (line.startsWith('Ability: ')) {
|
||||
line = line.slice(9);
|
||||
set.ability = line;
|
||||
set.ability = line.slice(9);
|
||||
} else if (line.startsWith('Item: ')) {
|
||||
set.item = line.slice(6);
|
||||
} else if (line.startsWith('Nickname: ')) {
|
||||
set.name = line.slice(10);
|
||||
} else if (line.startsWith('Species: ')) {
|
||||
set.species = line.slice(9);
|
||||
} else if (line === 'Shiny: Yes' || line === 'Shiny') {
|
||||
set.shiny = true;
|
||||
} else if (line.startsWith('Level: ')) {
|
||||
line = line.slice(7);
|
||||
set.level = +line;
|
||||
set.level = +line.slice(7);
|
||||
} else if (line.startsWith('Happiness: ')) {
|
||||
line = line.slice(11);
|
||||
set.happiness = +line;
|
||||
set.happiness = +line.slice(11);
|
||||
} else if (line.startsWith('Pokeball: ')) {
|
||||
line = line.slice(10);
|
||||
set.pokeball = line;
|
||||
set.pokeball = line.slice(10);
|
||||
} else if (line.startsWith('Hidden Power: ')) {
|
||||
line = line.slice(14);
|
||||
set.hpType = line;
|
||||
set.hpType = line.slice(14);
|
||||
} else if (line.startsWith('Dynamax Level: ')) {
|
||||
line = line.substr(15);
|
||||
set.dynamaxLevel = +line;
|
||||
} else if (line === 'Gigantamax: Yes') {
|
||||
set.dynamaxLevel = +line.slice(15);
|
||||
} else if (line === 'Gigantamax: Yes' || line === 'Gigantamax') {
|
||||
set.gigantamax = true;
|
||||
} else if (line.startsWith('Tera Type: ')) {
|
||||
set.teraType = line.slice(11);
|
||||
} else if (line.startsWith('EVs: ')) {
|
||||
line = line.slice(5);
|
||||
let evLines = line.split('/');
|
||||
const evLines = line.slice(5).split('(')[0].split('/');
|
||||
set.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
let plus = '', minus = '';
|
||||
for (let evLine of evLines) {
|
||||
evLine = evLine.trim();
|
||||
let spaceIndex = evLine.indexOf(' ');
|
||||
const spaceIndex = evLine.indexOf(' ');
|
||||
if (spaceIndex === -1) continue;
|
||||
let statid = BattleStatIDs[evLine.slice(spaceIndex + 1)];
|
||||
const statid = BattleStatIDs[evLine.slice(spaceIndex + 1)];
|
||||
if (!statid) continue;
|
||||
if (evLine.charAt(spaceIndex - 1) === '+') plus = statid;
|
||||
if (evLine.charAt(spaceIndex - 1) === '-') minus = statid;
|
||||
let statval = parseInt(evLine.slice(0, spaceIndex), 10);
|
||||
set.evs[statid] = statval;
|
||||
set.evs[statid] = parseInt(evLine.slice(0, spaceIndex), 10) || 0;
|
||||
}
|
||||
const nature = this.getNature(plus as StatNameExceptHP, minus as StatNameExceptHP);
|
||||
if (nature !== 'Serious') {
|
||||
set.nature = nature as Dex.NatureName;
|
||||
}
|
||||
} else if (line.startsWith('IVs: ')) {
|
||||
line = line.slice(5);
|
||||
let ivLines = line.split(' / ');
|
||||
const ivLines = line.slice(5).split(' / ');
|
||||
set.ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
||||
for (let ivLine of ivLines) {
|
||||
ivLine = ivLine.trim();
|
||||
let spaceIndex = ivLine.indexOf(' ');
|
||||
const spaceIndex = ivLine.indexOf(' ');
|
||||
if (spaceIndex === -1) continue;
|
||||
let statid = BattleStatIDs[ivLine.slice(spaceIndex + 1)];
|
||||
const statid = BattleStatIDs[ivLine.slice(spaceIndex + 1)];
|
||||
if (!statid) continue;
|
||||
let statval = parseInt(ivLine.slice(0, spaceIndex), 10);
|
||||
if (isNaN(statval)) statval = 31;
|
||||
|
|
@ -417,20 +432,15 @@ export class PSTeambuilder {
|
|||
let natureIndex = line.indexOf(' Nature');
|
||||
if (natureIndex === -1) natureIndex = line.indexOf(' nature');
|
||||
if (natureIndex === -1) return;
|
||||
line = line.substr(0, natureIndex);
|
||||
line = line.slice(0, natureIndex);
|
||||
if (line !== 'undefined') set.nature = line as Dex.NatureName;
|
||||
} else if (line.startsWith('-') || line.startsWith('~')) {
|
||||
} else if (line.startsWith('-') || line.startsWith('~') || line.startsWith('Move:')) {
|
||||
if (line.startsWith('Move:')) line = line.slice(4);
|
||||
line = line.slice(line.charAt(1) === ' ' ? 2 : 1);
|
||||
if (line.startsWith('Hidden Power [')) {
|
||||
const hpType = line.slice(14, -1) as Dex.TypeName;
|
||||
line = 'Hidden Power ' + hpType;
|
||||
if (!set.ivs && Dex.types.isName(hpType)) {
|
||||
set.ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
||||
const hpIVs = Dex.types.get(hpType).HPivs || {};
|
||||
for (let stat in hpIVs) {
|
||||
set.ivs[stat as Dex.StatName] = hpIVs[stat as Dex.StatName]!;
|
||||
}
|
||||
}
|
||||
set.hpType = hpType;
|
||||
}
|
||||
if (line === 'Frustration' && set.happiness === undefined) {
|
||||
set.happiness = 0;
|
||||
|
|
@ -839,6 +849,18 @@ class FormatDropdownPanel extends PSRoomPanel {
|
|||
this.search = (ev.currentTarget as HTMLInputElement).value;
|
||||
this.forceUpdate();
|
||||
};
|
||||
toggleGen = (ev: Event) => {
|
||||
const target = ev.currentTarget as HTMLButtonElement;
|
||||
this.gen = this.gen === target.value ? '' : target.value;
|
||||
this.forceUpdate();
|
||||
};
|
||||
override componentWillUnmount(): void {
|
||||
const { room } = this.props;
|
||||
super.componentWillUnmount();
|
||||
if (this.gen && room.parentElem?.getAttribute('data-selecttype') === 'teambuilder') {
|
||||
this.chooseParentValue(this.gen);
|
||||
}
|
||||
}
|
||||
override render() {
|
||||
const room = this.props.room;
|
||||
if (!room.parentElem) {
|
||||
|
|
@ -856,11 +878,21 @@ class FormatDropdownPanel extends PSRoomPanel {
|
|||
break;
|
||||
}
|
||||
}
|
||||
const curGen = (gen: string) => this.gen === gen ? ' cur' : '';
|
||||
const searchBar = <div style="margin-bottom: 0.5em">
|
||||
<input
|
||||
type="search" name="search" placeholder="Search formats" class="textbox autofocus"
|
||||
onInput={this.updateSearch} onChange={this.updateSearch}
|
||||
/>
|
||||
/> {}
|
||||
<button onClick={this.toggleGen} value="gen9" class={`button button-first${curGen('gen9')}`}>Gen 9</button>
|
||||
<button onClick={this.toggleGen} value="gen8" class={`button button-middle${curGen('gen8')}`}>8</button>
|
||||
<button onClick={this.toggleGen} value="gen7" class={`button button-middle${curGen('gen7')}`}>7</button>
|
||||
<button onClick={this.toggleGen} value="gen6" class={`button button-middle${curGen('gen6')}`}>6</button>
|
||||
<button onClick={this.toggleGen} value="gen5" class={`button button-middle${curGen('gen5')}`}>5</button>
|
||||
<button onClick={this.toggleGen} value="gen4" class={`button button-middle${curGen('gen4')}`}>4</button>
|
||||
<button onClick={this.toggleGen} value="gen3" class={`button button-middle${curGen('gen3')}`}>3</button>
|
||||
<button onClick={this.toggleGen} value="gen2" class={`button button-middle${curGen('gen2')}`}>2</button>
|
||||
<button onClick={this.toggleGen} value="gen1" class={`button button-last${curGen('gen1')}`}>1</button>
|
||||
</div>;
|
||||
if (!formatsLoaded) {
|
||||
return <PSPanelWrapper room={room}><div class="pad">
|
||||
|
|
@ -895,6 +927,8 @@ class FormatDropdownPanel extends PSRoomPanel {
|
|||
if (searchID && !toID(format.name).includes(searchID)) {
|
||||
continue;
|
||||
}
|
||||
if (this.gen && !format.id.startsWith(this.gen)) continue;
|
||||
|
||||
if (format.column !== curColumnNum) {
|
||||
if (curColumn.length) {
|
||||
curColumn = [];
|
||||
|
|
@ -911,7 +945,7 @@ class FormatDropdownPanel extends PSRoomPanel {
|
|||
curColumn.push(format);
|
||||
}
|
||||
|
||||
const width = columns.length * 225 + 30;
|
||||
const width = Math.max(columns.length, 2.1) * 225 + 30;
|
||||
const noResults = curColumn.length === 0;
|
||||
|
||||
return <PSPanelWrapper room={room} width={width}><div class="pad">
|
||||
|
|
|
|||
|
|
@ -285,8 +285,12 @@ export function PSPanelWrapper(props: {
|
|||
}
|
||||
|
||||
export class PSView extends preact.Component {
|
||||
static readonly isIOS = [
|
||||
'iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod',
|
||||
].includes(navigator.platform);
|
||||
static readonly isChrome = navigator.userAgent.includes(' Chrome/');
|
||||
static readonly isSafari = !this.isChrome && navigator.userAgent.includes(' Safari/');
|
||||
static readonly isFirefox = navigator.userAgent.includes(' Firefox/');
|
||||
static readonly isMac = navigator.platform?.startsWith('Mac');
|
||||
static textboxFocused = false;
|
||||
static setTextboxFocused(focused: boolean) {
|
||||
|
|
@ -341,7 +345,7 @@ export class PSView extends preact.Component {
|
|||
super();
|
||||
PS.subscribe(() => this.forceUpdate());
|
||||
|
||||
if (PSView.isSafari) {
|
||||
if (PSView.isIOS) {
|
||||
// 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.
|
||||
|
|
@ -561,7 +565,7 @@ export class PSView extends preact.Component {
|
|||
}
|
||||
static scrollToRoom() {
|
||||
if (document.documentElement.scrollWidth > document.documentElement.clientWidth && window.scrollX === 0) {
|
||||
if (PSView.isSafari && PS.leftPanelWidth === null) {
|
||||
if ((PSView.isIOS || PSView.isFirefox) && 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');
|
||||
|
|
|
|||
|
|
@ -388,9 +388,11 @@ select.button {
|
|||
}
|
||||
.label strong {
|
||||
font-size: 11pt;
|
||||
display: block;
|
||||
}
|
||||
.label .textbox {
|
||||
.label .textbox,
|
||||
.label .button,
|
||||
.label strong {
|
||||
margin-top: 2px;
|
||||
display: block;
|
||||
}
|
||||
.textbox {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
.dexlist .result {
|
||||
height: 32px;
|
||||
padding: 1px 0 0 0;
|
||||
contain: strict;
|
||||
}
|
||||
.result p,
|
||||
.resultheader p,
|
||||
|
|
@ -352,3 +353,64 @@
|
|||
.dexlist .ppsortcol {
|
||||
width: 23px;
|
||||
}
|
||||
|
||||
.dexlist .result a {
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dexlist .result a.cur {
|
||||
border-color: #555555;
|
||||
background: rgba(248, 248, 248, 0.4);
|
||||
}
|
||||
.dexlist .result a:hover,
|
||||
.dexlist .result a.hover {
|
||||
border-color: #D8D8D8;
|
||||
background: #F8F8F8;
|
||||
}
|
||||
.dexlist .result a.cur:hover,
|
||||
.dexlist .result a.cur.hover {
|
||||
border-color: #555555;
|
||||
background: #F8F8F8;
|
||||
}
|
||||
|
||||
/* dark mode */
|
||||
|
||||
.dark .dexlist 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 .dexlist .result a.hover,
|
||||
.dark .dexlist .result a:hover {
|
||||
border-color: #777777;
|
||||
background: rgba(100, 100, 100, 0.5);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.dark .dexlist .result a.cur {
|
||||
border-color: #BBBBBB;
|
||||
background: rgba(100, 100, 100, 0.2);
|
||||
}
|
||||
.dark .dexlist .result a.cur:hover {
|
||||
border-color: #BBBBBB;
|
||||
background: rgba(100, 100, 100, 0.4);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.dark .dexlist .namecol,
|
||||
.dark .dexlist .pokemonnamecol,
|
||||
.dark .dexlist .movenamecol {
|
||||
color: #DDD;
|
||||
}
|
||||
.dark .dexlist .col {
|
||||
color: #DDD;
|
||||
}
|
||||
.dark .dexlist .cur .col {
|
||||
color: #FFF;
|
||||
}
|
||||
.dark .dexlist a:hover .col {
|
||||
color: #FFF;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,6 +276,15 @@
|
|||
box-shadow: inset 0px 1px 2px #D2D2D2, 0px 0px 5px #66AADD;
|
||||
background: transparent;
|
||||
}
|
||||
.teaminnertextbox-stats, .teaminnertextbox-pokemon {
|
||||
width: 420px;
|
||||
}
|
||||
.teaminnertextbox-item, .teaminnertextbox-ability {
|
||||
width: 270px;
|
||||
}
|
||||
.teaminnertextbox-move {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.searchresults {
|
||||
background: #f2f2f2;
|
||||
|
|
@ -285,9 +294,11 @@
|
|||
|
||||
position: absolute;
|
||||
top: 300px;
|
||||
right: -13px;
|
||||
width: 624px;
|
||||
left: -14px;
|
||||
width: 640px;
|
||||
min-height: 150px;
|
||||
overflow: auto;
|
||||
max-height: 80vh;
|
||||
}
|
||||
.dark .searchresults {
|
||||
background: #282828;
|
||||
|
|
@ -297,6 +308,7 @@
|
|||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.teameditor {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user