mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Preact minor updates batch 19
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
Battles - Fix move types in battle controls - Warn about Gen 1 Substitute self-KOs in tooltips - Hide controls until animations are finished Teambuilder - Implement uploading teams - Fix adding pokemon in teambuilder text mode - Uploaded team management - Automatic Atk/Spe IVs - IV spread chooser also has an auto button Trivial - Constrain teambuilder width better
This commit is contained in:
parent
f77b3fd7dc
commit
bfb2813c72
|
|
@ -1232,6 +1232,7 @@ export class Move implements Effect {
|
|||
readonly pressureTarget: MoveTarget;
|
||||
readonly flags: Readonly<MoveFlags>;
|
||||
readonly critRatio: number;
|
||||
readonly damage?: number | 'level' | false | null;
|
||||
|
||||
readonly desc: string;
|
||||
readonly shortDesc: string;
|
||||
|
|
@ -1273,6 +1274,7 @@ export class Move implements Effect {
|
|||
this.pressureTarget = data.pressureTarget || this.target;
|
||||
this.flags = data.flags || {};
|
||||
this.critRatio = data.critRatio === 0 ? 0 : (data.critRatio || 1);
|
||||
this.damage = data.damage;
|
||||
|
||||
// TODO: move to text.js
|
||||
this.desc = data.desc;
|
||||
|
|
|
|||
|
|
@ -41,11 +41,10 @@ class TeamEditorState extends PSModel {
|
|||
formeLegality: 'normal' | 'hackmons' | 'custom' = 'normal';
|
||||
abilityLegality: 'normal' | 'hackmons' = 'normal';
|
||||
defaultLevel = 100;
|
||||
readonly: boolean;
|
||||
constructor(team: Team, readonly = false) {
|
||||
readonly = false;
|
||||
constructor(team: Team) {
|
||||
super();
|
||||
this.team = team;
|
||||
this.readonly = readonly;
|
||||
this.sets = PSTeambuilder.unpackTeam(team.packedTeam);
|
||||
this.setFormat(team.format);
|
||||
window.search = this.search;
|
||||
|
|
@ -213,7 +212,7 @@ class TeamEditorState extends PSModel {
|
|||
this.searchIndex--;
|
||||
}
|
||||
}
|
||||
getResultValue(result: SearchRow) {
|
||||
getResultValue(result: SearchRow): string {
|
||||
switch (result[0]) {
|
||||
case 'pokemon':
|
||||
return this.dex.species.get(result[1]).name;
|
||||
|
|
@ -231,17 +230,19 @@ class TeamEditorState extends PSModel {
|
|||
return result[1];
|
||||
}
|
||||
}
|
||||
canAdd() {
|
||||
canAdd(): boolean {
|
||||
return this.sets.length < 6 || this.team.isBox;
|
||||
}
|
||||
getHPType(set: Dex.PokemonSet): Dex.TypeName {
|
||||
if (set.hpType) return set.hpType as Dex.TypeName;
|
||||
if (!set.ivs) return this.getHPMove(set) || 'Dark';
|
||||
const hpMove = set.ivs ? null : this.getHPMove(set);
|
||||
if (hpMove) return hpMove;
|
||||
|
||||
const hpTypes = [
|
||||
'Fighting', 'Flying', 'Poison', 'Ground', 'Rock', 'Bug', 'Ghost', 'Steel', 'Fire', 'Water', 'Grass', 'Electric', 'Psychic', 'Ice', 'Dragon', 'Dark',
|
||||
] as const;
|
||||
if (this.gen <= 2) {
|
||||
if (!set.ivs) return 'Dark';
|
||||
// const hpDV = Math.floor(set.ivs.hp / 2);
|
||||
const atkDV = Math.floor(set.ivs.atk / 2);
|
||||
const defDV = Math.floor(set.ivs.def / 2);
|
||||
|
|
@ -254,19 +255,20 @@ class TeamEditorState extends PSModel {
|
|||
// }
|
||||
return hpTypes[4 * (atkDV % 4) + (defDV % 4)];
|
||||
} else {
|
||||
const ivs = set.ivs || this.defaultIVs(set);
|
||||
let hpTypeX = 0;
|
||||
let i = 1;
|
||||
// n.b. this is not our usual order (Spe and SpD are flipped)
|
||||
const statOrder = ['hp', 'atk', 'def', 'spe', 'spa', 'spd'] as const;
|
||||
for (const s of statOrder) {
|
||||
if (set.ivs[s] === undefined) set.ivs[s] = 31;
|
||||
hpTypeX += i * (set.ivs[s] % 2);
|
||||
if (ivs[s] === undefined) ivs[s] = 31;
|
||||
hpTypeX += i * (ivs[s] % 2);
|
||||
i *= 2;
|
||||
}
|
||||
return hpTypes[Math.floor(hpTypeX * 15 / 63)];
|
||||
}
|
||||
};
|
||||
hpTypeMatters(set: Dex.PokemonSet) {
|
||||
hpTypeMatters(set: Dex.PokemonSet): boolean {
|
||||
if (this.gen < 2) return false;
|
||||
if (this.gen > 7) return false;
|
||||
for (const move of set.moves) {
|
||||
|
|
@ -288,6 +290,101 @@ class TeamEditorState extends PSModel {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
getIVs(set: Dex.PokemonSet) {
|
||||
const ivs = this.defaultIVs(set);
|
||||
if (set.ivs) Object.assign(ivs, set.ivs);
|
||||
return ivs;
|
||||
}
|
||||
defaultIVs(set: Dex.PokemonSet, noGuess = !!set.ivs): Record<Dex.StatName, number> {
|
||||
const useIVs = this.gen > 2;
|
||||
const defaultIVs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
||||
if (!useIVs) {
|
||||
for (const stat of Dex.statNames) defaultIVs[stat] = 15;
|
||||
}
|
||||
if (noGuess) return defaultIVs;
|
||||
|
||||
const hpType = this.getHPMove(set);
|
||||
const hpModulo = (useIVs ? 2 : 4);
|
||||
const { minAtk, minSpe } = this.prefersMinStats(set);
|
||||
if (minAtk) defaultIVs['atk'] = 0;
|
||||
if (minSpe) defaultIVs['spe'] = 0;
|
||||
|
||||
if (!useIVs) {
|
||||
const hpDVs = hpType ? this.dex.types.get(hpType).HPdvs : null;
|
||||
if (hpDVs) {
|
||||
for (const stat in hpDVs) defaultIVs[stat as Dex.StatName] = hpDVs[stat as Dex.StatName]!;
|
||||
}
|
||||
} else {
|
||||
const hpIVs = hpType ? this.dex.types.get(hpType).HPivs : null;
|
||||
if (hpIVs) {
|
||||
if (this.canHyperTrain(set)) {
|
||||
if (minSpe) defaultIVs['spe'] = hpIVs['spe'] ?? 31;
|
||||
if (minAtk) defaultIVs['atk'] = hpIVs['atk'] ?? 31;
|
||||
} else {
|
||||
for (const stat in hpIVs) defaultIVs[stat as Dex.StatName] = hpIVs[stat as Dex.StatName]!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hpType) {
|
||||
if (minSpe) defaultIVs['spe'] %= hpModulo;
|
||||
if (minAtk) defaultIVs['atk'] %= hpModulo;
|
||||
}
|
||||
if (minAtk && useIVs) {
|
||||
// min Atk
|
||||
if (['Gouging Fire', 'Iron Boulder', 'Iron Crown', 'Raging Bolt'].includes(set.species)) {
|
||||
// only available with 20 Atk IVs
|
||||
defaultIVs['atk'] = 20;
|
||||
} else if (set.species.startsWith('Terapagos')) {
|
||||
// only available with 15 Atk IVs
|
||||
defaultIVs['atk'] = 15;
|
||||
}
|
||||
}
|
||||
return defaultIVs;
|
||||
}
|
||||
defaultHappiness(set: Dex.PokemonSet) {
|
||||
if (set.moves.includes('Return')) return 255;
|
||||
if (set.moves.includes('Frustration')) return 0;
|
||||
return undefined;
|
||||
}
|
||||
prefersMinStats(set: Dex.PokemonSet) {
|
||||
let minSpe = !set.evs?.spe && set.moves.includes('Gyro Ball');
|
||||
let minAtk = !set.evs?.atk;
|
||||
|
||||
// only available through an event with 31 Spe IVs
|
||||
if (set.species.startsWith('Terapagos')) minSpe = false;
|
||||
|
||||
if (this.format === 'gen7hiddentype') return { minAtk, minSpe };
|
||||
if (this.format.includes('1v1')) return { minAtk, minSpe };
|
||||
|
||||
// only available through an event with 31 Atk IVs
|
||||
if (set.ability === 'Battle Bond' || ['Koraidon', 'Miraidon'].includes(set.species)) {
|
||||
minAtk = false;
|
||||
return { minAtk, minSpe };
|
||||
}
|
||||
if (!set.moves.length) minAtk = false;
|
||||
for (const moveName of set.moves) {
|
||||
if (!moveName) continue;
|
||||
const move = this.dex.moves.get(moveName);
|
||||
if (move.id === 'transform') {
|
||||
const hasMoveBesidesTransform = set.moves.length > 1;
|
||||
if (!hasMoveBesidesTransform) minAtk = false;
|
||||
} else if (
|
||||
move.category === 'Physical' && !move.damage && !move.ohko &&
|
||||
!['foulplay', 'endeavor', 'counter', 'bodypress', 'seismictoss', 'bide', 'metalburst', 'superfang'].includes(move.id) &&
|
||||
!(this.gen < 8 && move.id === 'rapidspin')
|
||||
) {
|
||||
minAtk = false;
|
||||
} else if (
|
||||
['metronome', 'assist', 'copycat', 'mefirst', 'photongeyser', 'shellsidearm', 'terablast'].includes(move.id) ||
|
||||
(this.gen === 5 && move.id === 'naturepower')
|
||||
) {
|
||||
minAtk = false;
|
||||
}
|
||||
}
|
||||
|
||||
return { minAtk, minSpe };
|
||||
}
|
||||
getNickname(set: Dex.PokemonSet) {
|
||||
return set.name || this.dex.species.get(set.species).baseSpecies || '';
|
||||
}
|
||||
|
|
@ -336,7 +433,7 @@ class TeamEditorState extends PSModel {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
getStat(stat: StatName, set: Dex.PokemonSet, evOverride?: number, natureOverride?: number) {
|
||||
getStat(stat: StatName, set: Dex.PokemonSet, ivOverride: number, evOverride?: number, natureOverride?: number) {
|
||||
const team = this.team;
|
||||
|
||||
const supportsEVs = !team.format.includes('letsgo');
|
||||
|
|
@ -350,8 +447,7 @@ class TeamEditorState extends PSModel {
|
|||
const level = set.level || this.defaultLevel;
|
||||
|
||||
const baseStat = species.baseStats[stat];
|
||||
let iv = set.ivs?.[stat] ?? 31;
|
||||
if (this.gen <= 2) iv &= 30;
|
||||
const iv = ivOverride;
|
||||
const ev = evOverride ?? set.evs?.[stat] ?? (this.gen > 2 ? 0 : 252);
|
||||
|
||||
if (stat === 'hp') {
|
||||
|
|
@ -553,7 +649,8 @@ export class TeamEditor extends preact.Component<{
|
|||
</details>;
|
||||
}
|
||||
override render() {
|
||||
this.editor ||= new TeamEditorState(this.props.team, this.props.readonly);
|
||||
this.editor ||= new TeamEditorState(this.props.team);
|
||||
this.editor.readonly = !!this.props.readonly;
|
||||
this.editor.narrow = this.props.narrow ?? document.body.offsetWidth < 500;
|
||||
if (this.props.team.format !== this.editor.format) {
|
||||
this.editor.setFormat(this.props.team.format);
|
||||
|
|
@ -619,19 +716,27 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
|
|||
}
|
||||
input = () => this.updateText();
|
||||
keyUp = () => this.updateText(true);
|
||||
click = (ev: MouseEvent | KeyboardEvent) => {
|
||||
if (ev.altKey || ev.ctrlKey || ev.metaKey) return;
|
||||
contextMenu = (ev: MouseEvent) => {
|
||||
if (!ev.shiftKey) {
|
||||
if (this.closeMenu() || this.openInnerFocus()) {
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
};
|
||||
openInnerFocus() {
|
||||
const oldRange = this.selection?.lineRange;
|
||||
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.selection.lineRange === oldRange) return !!this.innerFocus;
|
||||
if (this.textbox.selectionStart === this.textbox.selectionEnd) {
|
||||
const range = this.getSelectionTypeRange();
|
||||
if (range) this.textbox.setSelectionRange(range[0], range[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
return !!this.innerFocus;
|
||||
}
|
||||
keyDown = (ev: KeyboardEvent) => {
|
||||
const editor = this.editor;
|
||||
switch (ev.keyCode) {
|
||||
|
|
@ -679,6 +784,7 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
|
|||
case 9: // tab
|
||||
case 13: // enter
|
||||
if (ev.keyCode === 13 && ev.shiftKey) return;
|
||||
if (ev.altKey || ev.metaKey) return;
|
||||
if (!this.innerFocus) {
|
||||
if (
|
||||
this.textbox.selectionStart === this.textbox.value.length &&
|
||||
|
|
@ -686,7 +792,7 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
|
|||
) {
|
||||
this.addPokemon();
|
||||
} else {
|
||||
this.click(ev);
|
||||
this.openInnerFocus();
|
||||
}
|
||||
ev.stopImmediatePropagation();
|
||||
ev.preventDefault();
|
||||
|
|
@ -1040,8 +1146,14 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
|
|||
}
|
||||
}
|
||||
getSetRange(index: number) {
|
||||
const start = this.setInfo[index]?.index ?? this.textbox.value.length;
|
||||
const end = this.setInfo[index + 1]?.index ?? this.textbox.value.length;
|
||||
if (!this.setInfo[index]) {
|
||||
if (this.innerFocus?.setIndex === index) {
|
||||
return this.innerFocus.range;
|
||||
}
|
||||
return [this.textbox.value.length, this.textbox.value.length];
|
||||
}
|
||||
const start = this.setInfo[index].index;
|
||||
const end = this.setInfo[index + 1].index;
|
||||
return [start, end];
|
||||
}
|
||||
changeCompat = (ev: Event) => {
|
||||
|
|
@ -1239,7 +1351,7 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
|
|||
<div class="teameditor-text">
|
||||
<textarea
|
||||
class="textbox teamtextbox" style={`padding-left:${editor.narrow ? '50px' : '100px'}`}
|
||||
onInput={this.input} onClick={this.click} onKeyUp={this.keyUp} onKeyDown={this.keyDown}
|
||||
onInput={this.input} onContextMenu={this.contextMenu} onKeyUp={this.keyUp} onKeyDown={this.keyDown}
|
||||
readOnly={editor.readonly}
|
||||
/>
|
||||
<textarea
|
||||
|
|
@ -1816,10 +1928,11 @@ class StatForm extends preact.Component<{
|
|||
static renderStatGraph(set: Dex.PokemonSet, editor: TeamEditorState, evs?: boolean) {
|
||||
// const supportsEVs = !team.format.includes('letsgo');
|
||||
const defaultEV = (editor.gen > 2 ? 0 : 252);
|
||||
const ivs = editor.getIVs(set);
|
||||
return Dex.statNames.map(statID => {
|
||||
if (statID === 'spd' && editor.gen === 1) return null;
|
||||
|
||||
const stat = editor.getStat(statID, set);
|
||||
const stat = editor.getStat(statID, set, ivs[statID]);
|
||||
let ev: number | string = set.evs?.[statID] ?? defaultEV;
|
||||
let width = stat * 75 / 504;
|
||||
if (statID === 'hp') width = stat * 75 / 704;
|
||||
|
|
@ -1849,9 +1962,12 @@ class StatForm extends preact.Component<{
|
|||
|
||||
const hpType = editor.getHPMove(set);
|
||||
const hpIVdata = hpType && !editor.canHyperTrain(set) && editor.getHPIVs(hpType) || null;
|
||||
const autoSpread = set.ivs && editor.defaultIVs(set, false);
|
||||
const autoSpreadValue = autoSpread && Object.values(autoSpread).join('/');
|
||||
if (!hpIVdata) {
|
||||
return <select name="ivspread" class="button" onChange={this.changeIVSpread}>
|
||||
<option value="" selected>IV spreads</option>
|
||||
{autoSpreadValue && <option value="auto">Auto ({autoSpreadValue})</option>}
|
||||
<optgroup label="min Atk">
|
||||
<option value="31/0/31/31/31/31">31/0/31/31/31/31</option>
|
||||
</optgroup>
|
||||
|
|
@ -1871,6 +1987,7 @@ class StatForm extends preact.Component<{
|
|||
|
||||
return <select name="ivspread" class="button" onChange={this.changeIVSpread}>
|
||||
<option value="" selected>Hidden Power {hpType} IVs</option>
|
||||
{autoSpreadValue && <option value="auto">Auto ({autoSpreadValue})</option>}
|
||||
<optgroup label="min Atk">
|
||||
{hpIVs.map(ivs => {
|
||||
const spread = ivs.map((iv, i) => (i === 1 ? minStat : 30) + iv).join('/');
|
||||
|
|
@ -2168,7 +2285,12 @@ class StatForm extends preact.Component<{
|
|||
const statID = target.name.split('-')[1] as StatName;
|
||||
const value = this.dvToIv(target.value);
|
||||
if (value === null) {
|
||||
if (set.ivs) delete set.ivs[statID];
|
||||
if (set.ivs) {
|
||||
delete set.ivs[statID];
|
||||
if (Object.values(set.ivs).every(iv => iv === undefined)) {
|
||||
set.ivs = undefined;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
set.ivs ||= { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
||||
set.ivs[statID] = value;
|
||||
|
|
@ -2191,8 +2313,12 @@ class StatForm extends preact.Component<{
|
|||
const { set } = this.props;
|
||||
if (!target.value) return;
|
||||
|
||||
const [hp, atk, def, spa, spd, spe] = target.value.split('/').map(Number);
|
||||
set.ivs = { hp, atk, def, spa, spd, spe };
|
||||
if (target.value === 'auto') {
|
||||
set.ivs = undefined;
|
||||
} else {
|
||||
const [hp, atk, def, spa, spd, spe] = target.value.split('/').map(Number);
|
||||
set.ivs = { hp, atk, def, spa, spd, spe };
|
||||
}
|
||||
this.props.onChange();
|
||||
};
|
||||
maxEVs() {
|
||||
|
|
@ -2227,8 +2353,9 @@ class StatForm extends preact.Component<{
|
|||
};
|
||||
if (editor.gen === 1) statNames.spa = 'Special';
|
||||
|
||||
const ivs = editor.getIVs(set);
|
||||
const stats = Dex.statNames.filter(statID => editor.gen > 1 || statID !== 'spd').map(statID => [
|
||||
statID, statNames[statID], editor.getStat(statID, set),
|
||||
statID, statNames[statID], editor.getStat(statID, set, ivs[statID]),
|
||||
] as const);
|
||||
|
||||
let remaining = null;
|
||||
|
|
@ -2243,6 +2370,7 @@ class StatForm extends preact.Component<{
|
|||
}
|
||||
remaining ||= null;
|
||||
}
|
||||
const defaultIVs = editor.defaultIVs(set);
|
||||
|
||||
return <div style="font-size:10pt" role="dialog" aria-label="Stats">
|
||||
<div class="resultheader"><h3>EVs, IVs, and Nature</h3></div>
|
||||
|
|
@ -2273,7 +2401,7 @@ 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'} style="width:40px"
|
||||
name={`iv-${statID}`} min={0} max={useIVs ? 31 : 15} placeholder={`${defaultIVs[statID]}`} 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>
|
||||
|
|
@ -2296,7 +2424,7 @@ class StatForm extends preact.Component<{
|
|||
</select>
|
||||
</p>}
|
||||
{editor.gen >= 3 && <p>
|
||||
<small><em>Protip:</em> You can also set natures by typing <kbd>+</kbd> and <kbd>-</kbd> next to a stat.</small>
|
||||
<small><em>Protip:</em> You can also set natures by typing <kbd>+</kbd> and <kbd>-</kbd> in the EV box.</small>
|
||||
</p>}
|
||||
{editor.gen >= 3 && this.renderStatOptimizer()}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { BattleLog } from "./battle-log";
|
|||
import { Move, BattleNatures } from "./battle-dex-data";
|
||||
import { BattleTextParser } from "./battle-text-parser";
|
||||
|
||||
class ModifiableValue {
|
||||
export class ModifiableValue {
|
||||
value = 0;
|
||||
maxValue = 0;
|
||||
comment: string[];
|
||||
|
|
@ -787,10 +787,17 @@ export class BattleTooltips {
|
|||
hpValues.push(hp - 256);
|
||||
}
|
||||
}
|
||||
let failMessage = hpValues.length ? `Will fail if current HP is ${hpValues.join(' or ')}.` : '';
|
||||
let failMessage = hpValues.length ? `Fails if current HP is ${hpValues.join(' or ')}.` : '';
|
||||
if (hpValues.includes(serverPokemon.hp)) failMessage = `<strong class="message-error">${failMessage}</strong>`;
|
||||
if (failMessage) text += `<p>${failMessage}</p>`;
|
||||
}
|
||||
if (this.battle.gen === 1 && !toID(this.battle.tier).includes('stadium') &&
|
||||
move.id === 'substitute') {
|
||||
const selfKO = serverPokemon.maxhp % 4 === 0 ? serverPokemon.maxhp / 4 : null;
|
||||
let failMessage = selfKO ? `KOs yourself if current HP is exactly ${selfKO}.` : '';
|
||||
if (selfKO === serverPokemon.hp) failMessage = `<strong class="message-error">${failMessage}</strong>`;
|
||||
if (failMessage) text += `<p>${failMessage}</p>`;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1744,7 +1744,8 @@ export const PS = new class extends PSModel {
|
|||
* they're dropped.
|
||||
*/
|
||||
dragging: { type: 'room', roomid: RoomID, foreground?: boolean } |
|
||||
{ type: 'team', team: Team, folder: string | null } |
|
||||
{ type: 'team', team: Team | number, folder: string | null } |
|
||||
{ type: '?' } | // just a note not to try to figure out what type the dragged thing is
|
||||
null = null;
|
||||
lastMessageTime = '';
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
type BattleRequest, type BattleMoveRequest, type BattleSwitchRequest, type BattleTeamRequest,
|
||||
} from "./battle-choices";
|
||||
import type { Args } from "./battle-text-parser";
|
||||
import { ModifiableValue } from "./battle-tooltips";
|
||||
|
||||
type BattleDesc = {
|
||||
id: RoomID,
|
||||
|
|
@ -261,6 +262,8 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
static readonly id = 'battle';
|
||||
static readonly routes = ['battle-*'];
|
||||
static readonly Model = BattleRoom;
|
||||
/** last displayed team. will not show the most recent request until the last one is gone. */
|
||||
team: ServerPokemon[] | null = null;
|
||||
send = (text: string, elem?: HTMLElement) => {
|
||||
this.props.room.send(text, elem);
|
||||
};
|
||||
|
|
@ -495,8 +498,13 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
</div>;
|
||||
}
|
||||
renderMoveControls(active: BattleRequestActivePokemon, choices: BattleChoiceBuilder) {
|
||||
const dex = this.props.room.battle.dex;
|
||||
const battle = this.props.room.battle;
|
||||
const dex = battle.dex;
|
||||
const pokemonIndex = choices.index();
|
||||
const activeIndex = battle.mySide.n > 1 ? pokemonIndex + battle.pokemonControlled : pokemonIndex;
|
||||
const serverPokemon = choices.request.side!.pokemon[pokemonIndex];
|
||||
const valueTracker = new ModifiableValue(battle, battle.nearSide.active[activeIndex]!, serverPokemon);
|
||||
const tooltips = (battle.scene as BattleScene).tooltips;
|
||||
|
||||
if (choices.current.max || (active.maxMoves && !active.canDynamax)) {
|
||||
if (!active.maxMoves) {
|
||||
|
|
@ -505,9 +513,11 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
return active.moves.map((moveData, i) => {
|
||||
const move = dex.moves.get(moveData.name);
|
||||
const maxMoveData = active.maxMoves![i];
|
||||
const gmaxTooltip = maxMoveData.id.startsWith('gmax') ? `|${maxMoveData.id}` : ``;
|
||||
const gmax = maxMoveData.id.startsWith('gmax') ? dex.moves.get(maxMoveData.id) : null;
|
||||
const gmaxTooltip = gmax ? `|${maxMoveData.id}` : ``;
|
||||
const tooltip = `maxmove|${moveData.name}|${pokemonIndex}${gmaxTooltip}`;
|
||||
return <MoveButton cmd={`/move ${i + 1} max`} type={move.type} tooltip={tooltip} moveData={moveData}>
|
||||
const moveType = tooltips.getMoveType(move, valueTracker, gmax || true)[0];
|
||||
return <MoveButton cmd={`/move ${i + 1} max`} type={moveType} tooltip={tooltip} moveData={moveData}>
|
||||
{maxMoveData.name}
|
||||
</MoveButton>;
|
||||
});
|
||||
|
|
@ -518,13 +528,15 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
return <div class="message-error">No Z moves</div>;
|
||||
}
|
||||
return active.moves.map((moveData, i) => {
|
||||
const move = dex.moves.get(moveData.name);
|
||||
const zMoveData = active.zMoves![i];
|
||||
if (!zMoveData) {
|
||||
return <button class="movebutton" disabled> </button>;
|
||||
}
|
||||
const specialMove = dex.moves.get(zMoveData.name);
|
||||
const move = specialMove.exists ? specialMove : dex.moves.get(moveData.name);
|
||||
const moveType = tooltips.getMoveType(move, valueTracker)[0];
|
||||
const tooltip = `zmove|${moveData.name}|${pokemonIndex}`;
|
||||
return <MoveButton cmd={`/move ${i + 1} zmove`} type={move.type} tooltip={tooltip} moveData={{ pp: 1, maxpp: 1 }}>
|
||||
return <MoveButton cmd={`/move ${i + 1} zmove`} type={moveType} tooltip={tooltip} moveData={{ pp: 1, maxpp: 1 }}>
|
||||
{zMoveData.name}
|
||||
</MoveButton>;
|
||||
});
|
||||
|
|
@ -533,8 +545,9 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
const special = choices.moveSpecial(choices.current);
|
||||
return active.moves.map((moveData, i) => {
|
||||
const move = dex.moves.get(moveData.name);
|
||||
const moveType = tooltips.getMoveType(move, valueTracker)[0];
|
||||
const tooltip = `move|${moveData.name}|${pokemonIndex}`;
|
||||
return <MoveButton cmd={`/move ${i + 1}${special}`} type={move.type} tooltip={tooltip} moveData={moveData}>
|
||||
return <MoveButton cmd={`/move ${i + 1}${special}`} type={moveType} tooltip={tooltip} moveData={moveData}>
|
||||
{move.name}
|
||||
</MoveButton>;
|
||||
});
|
||||
|
|
@ -614,7 +627,7 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
});
|
||||
}
|
||||
renderTeamList() {
|
||||
const team = this.props.room.battle.myPokemon;
|
||||
const team = this.team;
|
||||
if (!team) return;
|
||||
return <div class="switchcontrols">
|
||||
<h3 class="switchselect">Team</h3>
|
||||
|
|
@ -702,8 +715,19 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
}
|
||||
return buf;
|
||||
}
|
||||
renderPlayerWaitingControls() {
|
||||
return <div class="controls">
|
||||
<div class="whatdo">
|
||||
<button class="button" data-cmd="/ffto end">Skip animation <i class="fa fa-fast-forward" aria-hidden></i></button>
|
||||
</div>
|
||||
{this.renderTeamList()}
|
||||
</div>;
|
||||
}
|
||||
renderPlayerControls(request: BattleRequest) {
|
||||
const room = this.props.room;
|
||||
const atEnd = room.battle.atQueueEnd;
|
||||
if (!atEnd) return this.renderPlayerWaitingControls();
|
||||
|
||||
let choices = room.choices;
|
||||
if (!choices) return 'Error: Missing BattleChoiceBuilder';
|
||||
if (choices.request !== request) {
|
||||
|
|
@ -722,7 +746,10 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
{this.renderTeamList()}
|
||||
</div>;
|
||||
}
|
||||
if (request.side) room.battle.myPokemon = request.side.pokemon;
|
||||
if (request.side) {
|
||||
room.battle.myPokemon = request.side.pokemon;
|
||||
this.team = request.side.pokemon;
|
||||
}
|
||||
switch (request.requestType) {
|
||||
case 'move': {
|
||||
const index = choices.index();
|
||||
|
|
|
|||
|
|
@ -391,12 +391,32 @@ export class MainMenuRoom extends PSRoom {
|
|||
break;
|
||||
case 'teamupload':
|
||||
if (PS.teams.uploading) {
|
||||
PS.teams.uploading.uploaded = {
|
||||
const team = PS.teams.uploading;
|
||||
team.uploaded = {
|
||||
teamid: response.teamid,
|
||||
notLoaded: false,
|
||||
private: response.private,
|
||||
};
|
||||
PS.rooms[`team-${team.key}`]?.update(null);
|
||||
PS.rooms.teambuilder?.update(null);
|
||||
PS.teams.uploading = null;
|
||||
}
|
||||
break;
|
||||
case 'teamupdate':
|
||||
for (const team of PS.teams.list) {
|
||||
if (team.teamid === response.teamid) {
|
||||
team.uploaded = {
|
||||
teamid: response.teamid,
|
||||
notLoaded: false,
|
||||
private: response.private,
|
||||
};
|
||||
PS.rooms[`team-${team.key}`]?.update(null);
|
||||
PS.rooms.teambuilder?.update(null);
|
||||
PS.teams.uploading = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { PS, PSRoom, type RoomOptions, type Team } from "./client-main";
|
|||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import { toID } from "./battle-dex";
|
||||
import { BattleLog } from "./battle-log";
|
||||
import { FormatDropdown } from "./panel-mainmenu";
|
||||
import { TeamEditor } from "./battle-team-editor";
|
||||
import { Net } from "./client-connection";
|
||||
|
||||
|
|
@ -45,6 +44,25 @@ class TeamRoom extends PSRoom {
|
|||
this.update(null);
|
||||
});
|
||||
}
|
||||
upload(isPrivate: boolean) {
|
||||
const team = this.team;
|
||||
const cmd = team.uploaded ? 'update' : 'save';
|
||||
// teamName, formatid, rawPrivacy, rawTeam
|
||||
const buf = [];
|
||||
if (team.uploaded) {
|
||||
buf.push(team.uploaded.teamid);
|
||||
} else if (team.teamid) {
|
||||
return PS.alert(`This team is for a different account. Please log into the correct account to update it.`);
|
||||
}
|
||||
buf.push(team.name, team.format, isPrivate ? 1 : 0);
|
||||
const exported = team.packedTeam;
|
||||
if (!exported) return PS.alert(`Add a Pokemon to your team before uploading it.`);
|
||||
buf.push(exported);
|
||||
PS.teams.uploading = team;
|
||||
PS.send(`|/teams ${cmd} ${buf.join(', ')}`);
|
||||
this.uploaded = true;
|
||||
this.update(null);
|
||||
}
|
||||
save() {
|
||||
PS.teams.save();
|
||||
const title = `[Team] ${this.team?.name || 'Team'}`;
|
||||
|
|
@ -96,25 +114,7 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
|
|||
|
||||
uploadTeam = (ev: Event) => {
|
||||
const room = this.props.room;
|
||||
const team = PS.teams.byKey[room.id.slice(5)];
|
||||
if (!team) return;
|
||||
|
||||
const cmd = team.uploaded ? 'update' : 'save';
|
||||
// teamName, formatid, rawPrivacy, rawTeam
|
||||
const buf = [];
|
||||
if (team.uploaded) {
|
||||
buf.push(team.uploaded.teamid);
|
||||
} else if (team.teamid) {
|
||||
return PS.alert(`This team is for a different account. Please log into the correct account to update it.`);
|
||||
}
|
||||
buf.push(team.name, team.format, PS.prefs.uploadprivacy ? 1 : 0);
|
||||
const exported = team.packedTeam;
|
||||
if (!exported) return PS.alert(`Add a Pokemon to your team before uploading it.`);
|
||||
buf.push(exported);
|
||||
PS.teams.uploading = team;
|
||||
PS.send(`|/teams ${cmd} ${buf.join(', ')}`);
|
||||
room.uploaded = true;
|
||||
this.forceUpdate();
|
||||
room.upload(PS.prefs.uploadprivacy);
|
||||
};
|
||||
|
||||
changePrivacyPref = (ev: Event) => {
|
||||
|
|
@ -161,25 +161,29 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
|
|||
<i class="fa fa-chevron-left" aria-hidden></i> Teams
|
||||
</a> {}
|
||||
{team.uploaded?.private ? (
|
||||
<button class="button cur" disabled>
|
||||
<button class="button" data-href={`teamstorage-${team.key}`}>
|
||||
<i class="fa fa-cloud"></i> Account
|
||||
</button>
|
||||
) : team.uploaded ? (
|
||||
<button class="button cur" disabled>
|
||||
<button class="button" data-href={`teamstorage-${team.key}`}>
|
||||
<i class="fa fa-globe"></i> Account (public)
|
||||
</button>
|
||||
) : team.teamid ? (
|
||||
<button class="button cur" disabled>
|
||||
<button class="button" data-href={`teamstorage-${team.key}`}>
|
||||
<i class="fa fa-plug"></i> Disconnected (wrong account?)
|
||||
</button>
|
||||
) : (
|
||||
<button class="button cur" disabled>
|
||||
<button class="button" data-href={`teamstorage-${team.key}`}>
|
||||
<i class="fa fa-laptop"></i> Local
|
||||
</button>
|
||||
)}
|
||||
<div style="float:right"><FormatDropdown
|
||||
format={team.format} placeholder="" selectType="teambuilder" onChange={this.handleChangeFormat}
|
||||
/></div>
|
||||
<div style="float:right"><button
|
||||
name="format" value={team.format} data-selecttype="teambuilder"
|
||||
class="button" data-href="/formatdropdown" onChange={this.handleChangeFormat}
|
||||
>
|
||||
<i class="fa fa-folder-o"></i> {BattleLog.formatName(team.format)} {}
|
||||
{team.format.length <= 4 && <em>(uncategorized)</em>} <i class="fa fa-caret-down"></i>
|
||||
</button></div>
|
||||
<label class="label teamname">
|
||||
Team name:{}
|
||||
<input
|
||||
|
|
@ -232,4 +236,80 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
|
|||
}
|
||||
}
|
||||
|
||||
PS.addRoomType(TeamPanel);
|
||||
type TeamStorage = 'account' | 'public' | 'disconnected' | 'local';
|
||||
class TeamStoragePanel extends PSRoomPanel {
|
||||
static readonly id = "teamstorage";
|
||||
static readonly routes = ["teamstorage-*"];
|
||||
static readonly location = "semimodal-popup";
|
||||
static readonly noURL = true;
|
||||
|
||||
chooseOption = (ev: MouseEvent) => {
|
||||
const storage = (ev.currentTarget as HTMLButtonElement).value as TeamStorage;
|
||||
const room = this.props.room;
|
||||
const team = this.team();
|
||||
|
||||
if (storage === 'local' && team.uploaded) {
|
||||
PS.mainmenu.send(`/teams delete ${team.uploaded.teamid}`);
|
||||
team.uploaded = undefined;
|
||||
team.teamid = undefined;
|
||||
PS.teams.save();
|
||||
(room.getParent() as TeamRoom).update(null);
|
||||
} else if (storage === 'public' && team.uploaded?.private) {
|
||||
PS.mainmenu.send(`/teams setprivacy ${team.uploaded.teamid},no`);
|
||||
} else if (storage === 'account' && team.uploaded?.private === null) {
|
||||
PS.mainmenu.send(`/teams setprivacy ${team.uploaded.teamid},yes`);
|
||||
} else if (storage === 'public' && !team.teamid) {
|
||||
(room.getParent() as TeamRoom).upload(false);
|
||||
} else if (storage === 'account' && !team.teamid) {
|
||||
(room.getParent() as TeamRoom).upload(true);
|
||||
}
|
||||
ev.stopImmediatePropagation();
|
||||
ev.preventDefault();
|
||||
this.close();
|
||||
};
|
||||
team() {
|
||||
const teamKey = this.props.room.id.slice(12);
|
||||
const team = PS.teams.byKey[teamKey]!;
|
||||
return team;
|
||||
}
|
||||
|
||||
override render() {
|
||||
const room = this.props.room;
|
||||
|
||||
const team = this.team();
|
||||
const storage: TeamStorage = team.uploaded?.private ? (
|
||||
'account'
|
||||
) : team.uploaded ? (
|
||||
'public'
|
||||
) : team.teamid ? (
|
||||
'disconnected'
|
||||
) : (
|
||||
'local'
|
||||
);
|
||||
|
||||
if (storage === 'disconnected') {
|
||||
return <PSPanelWrapper room={room} width={280}><div class="pad">
|
||||
<div><button class="option cur" data-cmd="/close">
|
||||
<i class="fa fa-plug"></i> <strong>Disconnected</strong><br />
|
||||
Not found in the Teams database. Maybe you uploaded it on a different account?
|
||||
</button></div>
|
||||
</div></PSPanelWrapper>;
|
||||
}
|
||||
return <PSPanelWrapper room={room} width={280}><div class="pad">
|
||||
<div><button class={`option${storage === 'local' ? ' cur' : ''}`} onClick={this.chooseOption} value="local">
|
||||
<i class="fa fa-laptop"></i> <strong>Local</strong><br />
|
||||
Stored in cookies on your computer. Warning: Your browser might delete these. Make sure to use backups.
|
||||
</button></div>
|
||||
<div><button class={`option${storage === 'account' ? ' cur' : ''}`} onClick={this.chooseOption} value="account">
|
||||
<i class="fa fa-cloud"></i> <strong>Account</strong><br />
|
||||
Uploaded to the Teams database. You can share with the URL.
|
||||
</button></div>
|
||||
<div><button class={`option${storage === 'public' ? ' cur' : ''}`} onClick={this.chooseOption} value="public">
|
||||
<i class="fa fa-globe"></i> <strong>Account (public)</strong><br />
|
||||
Uploaded to the Teams database publicly. Share with the URL or people can find it by searching.
|
||||
</button></div>
|
||||
</div></PSPanelWrapper>;
|
||||
}
|
||||
}
|
||||
|
||||
PS.addRoomType(TeamPanel, TeamStoragePanel);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { PS, PSRoom, type Team } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import { TeamBox } from "./panel-teamdropdown";
|
||||
import { PSTeambuilder, TeamBox } from "./panel-teamdropdown";
|
||||
import { Dex, PSUtils, toID, type ID } from "./battle-dex";
|
||||
|
||||
class TeambuilderRoom extends PSRoom {
|
||||
|
|
@ -121,20 +121,52 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
|
|||
button.value = '';
|
||||
this.forceUpdate();
|
||||
};
|
||||
/** undefined: not dragging, null: dragging a new team */
|
||||
getDraggedTeam(ev: DragEvent): Team | number | null {
|
||||
if (PS.dragging?.type === 'team') return PS.dragging.team;
|
||||
|
||||
const dataTransfer = ev.dataTransfer;
|
||||
if (!dataTransfer) return null;
|
||||
|
||||
PS.dragging = { type: '?' };
|
||||
console.log(`dragging: ${dataTransfer.types as any} | ${[...dataTransfer.files]?.map(file => file.name) as any}`);
|
||||
if (!dataTransfer.types.includes?.('Files')) return null;
|
||||
// MDN says files will be empty except on a Drop event, but the spec says no such thing
|
||||
// in practice, Chrome gives this info but Firefox doesn't
|
||||
if (dataTransfer.files[0] && !dataTransfer.files[0].name.endsWith('.txt')) return null;
|
||||
|
||||
// We're dragging a file! It might be a team!
|
||||
PS.dragging = {
|
||||
type: 'team',
|
||||
team: 0,
|
||||
folder: null,
|
||||
};
|
||||
return PS.dragging.team;
|
||||
}
|
||||
dragEnterTeam = (ev: DragEvent) => {
|
||||
if (PS.dragging?.type !== 'team') return;
|
||||
const draggedTeam = this.getDraggedTeam(ev);
|
||||
if (draggedTeam === null) return;
|
||||
|
||||
const value = (ev.currentTarget as HTMLElement)?.getAttribute('data-teamkey');
|
||||
const team = value ? PS.teams.byKey[value] : null;
|
||||
if (!team || team === PS.dragging.team) return;
|
||||
const iDragged = PS.teams.list.indexOf(PS.dragging.team);
|
||||
if (!team || team === draggedTeam) return;
|
||||
|
||||
const iOver = PS.teams.list.indexOf(team);
|
||||
if (typeof draggedTeam === 'number') {
|
||||
if (iOver >= draggedTeam) (PS.dragging as any).team = iOver + 1;
|
||||
(PS.dragging as any).team = iOver;
|
||||
this.forceUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
const iDragged = PS.teams.list.indexOf(draggedTeam);
|
||||
if (iDragged < 0 || iOver < 0) return; // shouldn't happen
|
||||
|
||||
PS.teams.list.splice(iDragged, 1);
|
||||
// by coincidence, splicing into iOver works in both directions
|
||||
// before: Dragged goes before Over, splice at i
|
||||
// after: Dragged goes after Over, splice at i - 1 + 1
|
||||
PS.teams.list.splice(iOver, 0, PS.dragging.team);
|
||||
PS.teams.list.splice(iOver, 0, draggedTeam);
|
||||
this.forceUpdate();
|
||||
};
|
||||
dragEnterFolder = (ev: DragEvent) => {
|
||||
|
|
@ -153,20 +185,96 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
|
|||
if (PS.dragging.folder === value) PS.dragging.folder = null;
|
||||
this.forceUpdate();
|
||||
};
|
||||
extractDraggedTeam(ev: DragEvent): Promise<Team | null> {
|
||||
const file = ev.dataTransfer?.files?.[0];
|
||||
if (!file) return Promise.resolve(null);
|
||||
|
||||
let name = file.name;
|
||||
if (name.slice(-4).toLowerCase() !== '.txt') {
|
||||
PS.alert(`Your file "${file.name}" is not a valid team. Team files are ".txt" files.`);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
name = name.slice(0, -4);
|
||||
|
||||
return file.text?.()?.then(result => {
|
||||
let sets;
|
||||
try {
|
||||
sets = PSTeambuilder.importTeam(result);
|
||||
} catch {
|
||||
PS.alert(`Your file "${file.name}" is not a valid team.`);
|
||||
return null;
|
||||
}
|
||||
let format = '';
|
||||
const bracketIndex = name.indexOf(']');
|
||||
let isBox = false;
|
||||
if (bracketIndex >= 0) {
|
||||
format = name.slice(1, bracketIndex);
|
||||
if (!format.startsWith('gen')) format = 'gen6' + format;
|
||||
if (format.endsWith('-box')) {
|
||||
format = format.slice(0, -4);
|
||||
isBox = true;
|
||||
}
|
||||
name = $.trim(name.substr(bracketIndex + 1));
|
||||
}
|
||||
return {
|
||||
name,
|
||||
format: format as ID,
|
||||
folder: '',
|
||||
packedTeam: PSTeambuilder.packTeam(sets),
|
||||
iconCache: null,
|
||||
key: '',
|
||||
isBox,
|
||||
} satisfies Team;
|
||||
});
|
||||
}
|
||||
addDraggedTeam(ev: DragEvent, folder?: string) {
|
||||
let index: number = (PS.dragging as any)?.team;
|
||||
if (typeof index !== 'number') index = 0;
|
||||
this.extractDraggedTeam(ev).then(team => {
|
||||
if (!team) {
|
||||
return;
|
||||
}
|
||||
if (folder?.endsWith('/')) {
|
||||
team.folder = folder.slice(0, -1);
|
||||
} else if (folder) {
|
||||
team.format = folder as ID;
|
||||
}
|
||||
PS.teams.push(team);
|
||||
PS.teams.list.pop();
|
||||
PS.teams.list.splice(index, 0, team);
|
||||
PS.teams.save();
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
dropFolder = (ev: DragEvent) => {
|
||||
const value = (ev.currentTarget as HTMLElement)?.getAttribute('data-value') || null;
|
||||
if (value === null || PS.dragging?.type !== 'team') return;
|
||||
if (value === '++' || value === '') return;
|
||||
|
||||
PS.dragging.folder = null;
|
||||
let team = PS.dragging.team;
|
||||
|
||||
if (typeof team === 'number') {
|
||||
return this.addDraggedTeam(ev, value);
|
||||
}
|
||||
|
||||
if (value.endsWith('/')) {
|
||||
PS.dragging.team.folder = value.slice(0, -1);
|
||||
team.folder = value.slice(0, -1);
|
||||
} else {
|
||||
PS.dragging.team.format = value as ID;
|
||||
team.format = value as ID;
|
||||
}
|
||||
PS.teams.save();
|
||||
ev.stopImmediatePropagation();
|
||||
this.forceUpdate();
|
||||
};
|
||||
dropPanel = (ev: DragEvent) => {
|
||||
if (PS.dragging?.type !== 'team') return;
|
||||
let team = PS.dragging.team;
|
||||
|
||||
if (typeof team === 'number') {
|
||||
return this.addDraggedTeam(ev, this.props.room.curFolder);
|
||||
}
|
||||
};
|
||||
renderFolder(value: string) {
|
||||
const { room } = this.props;
|
||||
const cur = room.curFolder === value;
|
||||
|
|
@ -311,7 +419,11 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
|
|||
const room = this.props.room;
|
||||
let teams: (Team | null)[] = PS.teams.list.slice();
|
||||
|
||||
if (PS.teams.deletedTeams.length) {
|
||||
let isDragging = false;
|
||||
if (PS.dragging?.type === 'team' && typeof PS.dragging.team === 'number') {
|
||||
teams.splice(PS.dragging.team, 0, null);
|
||||
isDragging = true;
|
||||
} else if (PS.teams.deletedTeams.length) {
|
||||
const undeleteIndex = PS.teams.deletedTeams[PS.teams.deletedTeams.length - 1][1];
|
||||
teams.splice(undeleteIndex, 0, null);
|
||||
}
|
||||
|
|
@ -332,7 +444,7 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
|
|||
<div class="folderpane">
|
||||
{this.renderFolderList()}
|
||||
</div>
|
||||
<div class="teampane">
|
||||
<div class="teampane" onDrop={this.dropPanel}>
|
||||
{filterFolder ? (
|
||||
<h2>
|
||||
<i class="fa fa-folder-open" aria-hidden></i> {filterFolder} {}
|
||||
|
|
@ -371,6 +483,10 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
|
|||
null
|
||||
)}
|
||||
</li>
|
||||
) : isDragging ? (
|
||||
<li key="dragging">
|
||||
<div class="team"></div>
|
||||
</li>
|
||||
) : (
|
||||
<li key="undelete">
|
||||
<button data-cmd="/undeleteteam" class="option">
|
||||
|
|
|
|||
|
|
@ -683,10 +683,12 @@ class TeamDropdownPanel extends PSRoomPanel {
|
|||
return true;
|
||||
});
|
||||
}
|
||||
setFormat = (e: MouseEvent) => {
|
||||
const target = e.currentTarget as HTMLButtonElement;
|
||||
setFormat = (ev: MouseEvent) => {
|
||||
const target = ev.currentTarget as HTMLButtonElement;
|
||||
this.format = (target.name === 'format' && target.value) || '';
|
||||
this.gen = (target.name === 'gen' && target.value) || '';
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
this.forceUpdate();
|
||||
};
|
||||
click = (e: MouseEvent) => {
|
||||
|
|
|
|||
|
|
@ -530,6 +530,7 @@ you can't delete it by pressing Backspace */
|
|||
|
||||
.set-button {
|
||||
position: relative;
|
||||
max-width: 660px;
|
||||
}
|
||||
.set-button table {
|
||||
border: 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user