Preact minor updates batch 20
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run

Battles
- Fix render issues on mobile layout
- Fix join/leave batching
- Support disabled buttons

Teambuilder
- Redesign upload UI to be clearer
- Support scrolling tab bar for Boxes in focus editor
- Don't overwrite local team with remote team
- Improve Defensive Coverage ability support
- Support importing from pokepaste (fixes #2422)
This commit is contained in:
Guangcong Luo 2025-05-14 14:28:36 +00:00
parent 924400bba7
commit a35a62cfce
9 changed files with 223 additions and 143 deletions

View File

@ -141,7 +141,7 @@ export class BattleLog {
let divClass = 'chat';
let divHTML = '';
let noNotify: boolean | undefined;
if (!['join', 'j', 'leave', 'l'].includes(args[0])) this.joinLeave = null;
if (!['join', 'j', 'leave', 'l', 'turn'].includes(args[0])) this.joinLeave = null;
if (!['name', 'n'].includes(args[0])) this.lastRename = null;
switch (args[0]) {
case 'chat': case 'c': case 'c:':

View File

@ -15,6 +15,7 @@ import { PSSearchResults } from "./battle-searchresults";
import { BattleNatures, BattleStatNames, type StatName } from "./battle-dex-data";
import { BattleStatGuesser, BattleStatOptimizer } from "./battle-tooltips";
import { PSModel } from "./client-core";
import { Net } from "./client-connection";
type SelectionType = 'pokemon' | 'ability' | 'item' | 'move' | 'stats' | 'details';
@ -49,6 +50,10 @@ class TeamEditorState extends PSModel {
this.setFormat(team.format);
window.search = this.search;
}
setReadonly(readonly: boolean) {
if (!readonly && this.readonly) this.sets = PSTeambuilder.unpackTeam(this.team.packedTeam);
this.readonly = readonly;
}
setFormat(format: string) {
const team = this.team;
const formatid = toID(format);
@ -499,6 +504,9 @@ class TeamEditorState extends PSModel {
if (attackType === 'Ground' && abilityid === 'eartheater') return 0;
if (attackType === 'Fire' && abilityid === 'wellbakedbody') return 0;
if (attackType === 'Fire' && abilityid === 'primordialsea') return 0;
if (attackType === 'Water' && abilityid === 'desolateland') return 0;
if (abilityid === 'wonderguard') {
for (const type of types) {
if (this.getTypeWeakness(type, attackType) <= 1) return 0;
@ -506,6 +514,14 @@ class TeamEditorState extends PSModel {
}
let factor = 1;
if ((attackType === 'Fire' || attackType === 'Ice') && abilityid === 'thickfat') factor *= 0.5;
if (attackType === 'Fire' && abilityid === 'waterbubble') factor *= 0.5;
if (attackType === 'Fire' && abilityid === 'heatproof') factor *= 0.5;
if (attackType === 'Ghost' && abilityid === 'purifyingsalt') factor *= 0.5;
if (attackType === 'Fire' && abilityid === 'fluffy') factor *= 2;
if ((attackType === 'Electric' || attackType === 'Rock' || attackType === 'Ice') && abilityid === 'deltastream') {
factor *= 0.5;
}
for (const type of types) {
factor *= this.getTypeWeakness(type, attackType);
}
@ -650,7 +666,7 @@ export class TeamEditor extends preact.Component<{
}
override render() {
this.editor ||= new TeamEditorState(this.props.team);
this.editor.readonly = !!this.props.readonly;
this.editor.setReadonly(!!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);
@ -714,7 +730,10 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
this.heightTester.value = fullLine && !newValue.endsWith('\n') ? newValue + '\n' : newValue;
return this.heightTester.scrollHeight;
}
input = () => this.updateText();
input = () => {
this.updateText();
this.save();
};
keyUp = () => this.updateText(true);
contextMenu = (ev: MouseEvent) => {
if (!ev.shiftKey) {
@ -786,13 +805,15 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
if (ev.keyCode === 13 && ev.shiftKey) return;
if (ev.altKey || ev.metaKey) return;
if (!this.innerFocus) {
if (
if (this.maybeReplaceLine()) {
// do nothing else
} else if (
this.textbox.selectionStart === this.textbox.value.length &&
(this.textbox.value.endsWith('\n\n') || !this.textbox.value)
) {
this.addPokemon();
} else {
this.openInnerFocus();
} else if (!this.openInnerFocus()) {
break;
}
ev.stopImmediatePropagation();
ev.preventDefault();
@ -820,6 +841,24 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
}
}
};
maybeReplaceLine = () => {
if (this.textbox.selectionStart !== this.textbox.selectionEnd) return;
const current = this.textbox.selectionEnd;
const lineStart = this.textbox.value.lastIndexOf('\n', current) + 1;
const value = this.textbox.value.slice(lineStart, current);
const pokepaste = /^https?:\/\/pokepast.es\/([a-z0-9]+)(?:\/.*)?$/.exec(value)?.[1];
if (pokepaste) {
Net(`https://pokepast.es/${pokepaste}/json`).get().then(json => {
const paste = JSON.parse(json);
// make sure it's still there:
const valueIndex = this.textbox.value.indexOf(value);
this.replace(paste.paste.replace(/\r\n/g, '\n'), valueIndex, valueIndex + value.length);
});
return true;
}
return false;
};
getInnerFocusValue() {
if (!this.innerFocus) return '';
return this.textbox.value.slice(this.innerFocus.range[0], this.innerFocus.range[1]);
@ -840,6 +879,7 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
this.clearInnerFocus();
if (this.setDirty) {
this.updateText();
this.save();
} else {
this.forceUpdate();
}
@ -986,7 +1026,6 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
}
textbox.style.height = `${bottomY + 100}px`;
this.save();
}
this.forceUpdate();
};
@ -1180,6 +1219,7 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
// for future updates
if (!this.setInfo[index]) {
this.updateText();
this.save();
} else {
if (this.setInfo[index + 1]) {
this.setInfo[index + 1].index = start + newText.length;
@ -1352,7 +1392,7 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?:
<textarea
class="textbox teamtextbox" style={`padding-left:${editor.narrow ? '50px' : '100px'}`}
onInput={this.input} onContextMenu={this.contextMenu} onKeyUp={this.keyUp} onKeyDown={this.keyDown}
readOnly={editor.readonly}
readOnly={editor.readonly} onChange={this.maybeReplaceLine}
/>
<textarea
class="textbox teamtextbox heighttester" tabIndex={-1} aria-hidden
@ -1876,7 +1916,7 @@ class TeamWizard extends preact.Component<{
<i class="fa fa-plus"></i>
</button></li>}
</ul>
<div class="pad">{this.renderButton(set, setIndex)}</div>
<div class="pad" style="padding-top:0">{this.renderButton(set, setIndex)}</div>
{type === 'stats' ? (
<StatForm editor={editor} set={set!} onChange={this.handleSetChange} />
) : type === 'details' ? (

View File

@ -284,6 +284,8 @@ export interface Team {
/** password, if private. null = public, undefined = unknown, not loaded yet */
private?: string | null,
};
/** team at the point it was last uploaded. outside of `uploaded` so it can track loading state */
uploadedPackedTeam?: string;
}
interface UploadedTeam {
name: string;
@ -435,14 +437,9 @@ class PSTeams extends PSStreamModel<'team' | 'format'> {
if (!team) {
continue;
}
const compare = this.compareTeams(team, localTeam);
if (compare !== true) {
if (!localTeam.name.endsWith(' (local version)')) localTeam.name += ' (local version)';
continue;
}
localTeam.uploaded = {
teamid: team.teamid,
notLoaded: true,
notLoaded: false,
private: team.private,
};
delete teams[localTeam.teamid];
@ -465,7 +462,7 @@ class PSTeams extends PSStreamModel<'team' | 'format'> {
localTeam.teamid = team.teamid;
localTeam.uploaded = {
teamid: team.teamid,
notLoaded: true,
notLoaded: false,
private: team.private,
};
break;
@ -495,10 +492,10 @@ class PSTeams extends PSStreamModel<'team' | 'format'> {
loadTeam(team: Team | undefined | null, ifNeeded: true): void | Promise<void>;
loadTeam(team: Team | undefined | null): Promise<void>;
loadTeam(team: Team | undefined | null, ifNeeded?: boolean): void | Promise<void> {
if (!team) return ifNeeded ? undefined : Promise.resolve();
if (!team.uploaded?.notLoaded) return ifNeeded ? undefined : Promise.resolve();
if (team.uploaded.notLoaded !== true) return team.uploaded.notLoaded;
if (!team?.uploaded || team.uploadedPackedTeam) return ifNeeded ? undefined : Promise.resolve();
if (team.uploaded.notLoaded && team.uploaded.notLoaded !== true) return team.uploaded.notLoaded;
const notLoaded = team.uploaded.notLoaded;
return (team.uploaded.notLoaded = PSLoginServer.query('getteam', {
teamid: team.uploaded.teamid,
}).then(data => {
@ -508,8 +505,11 @@ class PSTeams extends PSStreamModel<'team' | 'format'> {
return;
}
team.uploaded.notLoaded = false;
team.packedTeam = data.team;
PS.teams.save();
team.uploadedPackedTeam = data.team;
if (notLoaded) {
team.packedTeam = data.team;
PS.teams.save();
}
}));
}
compareTeams(serverTeam: UploadedTeam, localTeam: Team) {

View File

@ -152,55 +152,6 @@ class BattleDiv extends preact.Component<{ room: BattleRoom }> {
}
}
function MoveButton(props: {
cmd: string, type: Dex.TypeName, tooltip: string, moveData: { pp?: number, maxpp?: number, disabled?: boolean },
children: string,
}) {
const pp = props.moveData.maxpp ? `${props.moveData.pp!}/${props.moveData.maxpp}` : '&ndash;';
return <button
data-cmd={props.cmd} data-tooltip={props.tooltip}
class={`movebutton type-${props.type} has-tooltip${props.moveData.disabled ? ' disabled' : ''}`}
>
{props.children}<br />
<small class="type">{props.type}</small> <small class="pp">{pp}</small>&nbsp;
</button>;
}
function PokemonButton(props: {
pokemon: Pokemon | ServerPokemon | null, cmd: string, noHPBar?: boolean, disabled?: boolean | 'fade', tooltip: string,
}) {
const pokemon = props.pokemon;
if (!pokemon) {
return <button
data-cmd={props.cmd} class={`${props.disabled ? 'disabled ' : ''}has-tooltip`}
style={{ opacity: props.disabled === 'fade' ? 0.5 : 1 }} data-tooltip={props.tooltip}
>
(empty slot)
</button>;
}
let hpColorClass;
switch (BattleScene.getHPColor(pokemon)) {
case 'y': hpColorClass = 'hpbar hpbar-yellow'; break;
case 'r': hpColorClass = 'hpbar hpbar-red'; break;
default: hpColorClass = 'hpbar'; break;
}
return <button
data-cmd={props.cmd} class={`${props.disabled ? 'disabled ' : ''}has-tooltip`}
style={{ opacity: props.disabled === 'fade' ? 0.5 : 1 }} data-tooltip={props.tooltip}
>
<PSIcon pokemon={pokemon} />
{pokemon.name}
{
!props.noHPBar && !pokemon.fainted &&
<span class={hpColorClass}>
<span style={{ width: Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1 }}></span>
</span>
}
{!props.noHPBar && pokemon.status && <span class={`status ${pokemon.status}`}></span>}
</button>;
}
class TimerButton extends preact.Component<{ room: BattleRoom }> {
timerInterval: number | null = null;
override componentWillUnmount() {
@ -442,6 +393,57 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
</p>
</div>;
}
renderMoveButton(props: {
name: string,
cmd: string, type: Dex.TypeName, tooltip: string, moveData: { pp?: number, maxpp?: number, disabled?: boolean },
} | null) {
if (!props) {
return <button class="movebutton" disabled>&nbsp;</button>;
}
const pp = props.moveData.maxpp ? `${props.moveData.pp!}/${props.moveData.maxpp}` : '\u2014';
return <button
data-cmd={props.cmd} data-tooltip={props.tooltip}
class={`movebutton type-${props.type} has-tooltip${props.moveData.disabled ? ' disabled' : ''}`}
>
{props.name}<br />
<small class="type">{props.type}</small> <small class="pp">{pp}</small>&nbsp;
</button>;
}
renderPokemonButton(props: {
pokemon: Pokemon | ServerPokemon | null, cmd: string, noHPBar?: boolean, disabled?: boolean | 'fade', tooltip: string,
}) {
const pokemon = props.pokemon;
if (!pokemon) {
return <button
data-cmd={props.cmd} class={`${props.disabled ? 'disabled ' : ''}has-tooltip`}
style={{ opacity: props.disabled === 'fade' ? 0.5 : 1 }} data-tooltip={props.tooltip}
>
(empty slot)
</button>;
}
let hpColorClass;
switch (BattleScene.getHPColor(pokemon)) {
case 'y': hpColorClass = 'hpbar hpbar-yellow'; break;
case 'r': hpColorClass = 'hpbar hpbar-red'; break;
default: hpColorClass = 'hpbar'; break;
}
return <button
data-cmd={props.cmd} class={`${props.disabled ? 'disabled ' : ''}has-tooltip`}
style={{ opacity: props.disabled === 'fade' ? 0.5 : 1 }} data-tooltip={props.tooltip}
>
{PSIcon({ pokemon })}
{pokemon.name}
{
!props.noHPBar && !pokemon.fainted &&
<span class={hpColorClass}>
<span style={{ width: Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1 }}></span>
</span>
}
{!props.noHPBar && pokemon.status && <span class={`status ${pokemon.status}`}></span>}
</button>;
}
renderMoveMenu(choices: BattleChoiceBuilder) {
const moveRequest = choices.currentMoveRequest()!;
@ -521,9 +523,13 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
}
const gmaxTooltip = maxMoveData.id.startsWith('gmax') ? `|${maxMoveData.id}` : ``;
const tooltip = `maxmove|${moveData.name}|${pokemonIndex}${gmaxTooltip}`;
return <MoveButton cmd={`/move ${i + 1} max`} type={moveType} tooltip={tooltip} moveData={moveData}>
{maxMoveData.name}
</MoveButton>;
return this.renderMoveButton({
name: maxMoveData.name,
cmd: `/move ${i + 1} max`,
type: moveType,
tooltip,
moveData,
});
});
}
@ -534,15 +540,19 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
return active.moves.map((moveData, i) => {
const zMoveData = active.zMoves![i];
if (!zMoveData) {
return <button class="movebutton" disabled>&nbsp;</button>;
return this.renderMoveButton(null);
}
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={moveType} tooltip={tooltip} moveData={{ pp: 1, maxpp: 1 }}>
{zMoveData.name}
</MoveButton>;
return this.renderMoveButton({
name: zMoveData.name,
cmd: `/move ${i + 1} zmove`,
type: moveType,
tooltip,
moveData: { pp: 1, maxpp: 1 },
});
});
}
@ -551,9 +561,13 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
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={moveType} tooltip={tooltip} moveData={moveData}>
{move.name}
</MoveButton>;
return this.renderMoveButton({
name: move.name,
cmd: `/move ${i + 1}${special}`,
type: moveType,
tooltip,
moveData,
});
});
}
renderMoveTargetControls(request: BattleMoveRequest, choices: BattleChoiceBuilder) {
@ -577,10 +591,12 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
}
if (pokemon?.fainted) pokemon = null;
return <PokemonButton
pokemon={pokemon}
cmd={disabled ? `` : `/${moveChoice} +${i + 1}`} disabled={disabled && 'fade'} tooltip={`activepokemon|1|${i}`}
/>;
return this.renderPokemonButton({
pokemon,
cmd: disabled ? `` : `/${moveChoice} +${i + 1}`,
disabled: disabled && 'fade',
tooltip: `activepokemon|1|${i}`,
});
}).reverse(),
<div style="clear: left"></div>,
battle.nearSide.active.map((pokemon, i) => {
@ -593,10 +609,12 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
if (moveTarget !== 'adjacentAllyOrSelf' && userSlot === i) disabled = true;
if (pokemon?.fainted) pokemon = null;
return <PokemonButton
pokemon={pokemon}
cmd={disabled ? `` : `/${moveChoice} -${i + 1}`} disabled={disabled && 'fade'} tooltip={`activepokemon|0|${i}`}
/>;
return this.renderPokemonButton({
pokemon,
cmd: disabled ? `` : `/${moveChoice} -${i + 1}`,
disabled: disabled && 'fade',
tooltip: `activepokemon|0|${i}`,
});
}),
];
}
@ -616,18 +634,24 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
</em>}
{request.side.pokemon.map((serverPokemon, i) => {
const cantSwitch = trapped || i < numActive || choices.alreadySwitchingIn.includes(i + 1) || serverPokemon.fainted;
return <PokemonButton
pokemon={serverPokemon} cmd={`/switch ${i + 1}`} disabled={cantSwitch} tooltip={`switchpokemon|${i}`}
/>;
return this.renderPokemonButton({
pokemon: serverPokemon,
cmd: `/switch ${i + 1}`,
disabled: cantSwitch,
tooltip: `switchpokemon|${i}`,
});
})}
</div>;
}
renderTeamControls(request: | BattleTeamRequest, choices: BattleChoiceBuilder) {
renderTeamPreviewChooser(request: | BattleTeamRequest, choices: BattleChoiceBuilder) {
return request.side.pokemon.map((serverPokemon, i) => {
const cantSwitch = choices.alreadySwitchingIn.includes(i + 1);
return <PokemonButton
pokemon={serverPokemon} cmd={`/switch ${i + 1}`} noHPBar disabled={cantSwitch && 'fade'} tooltip={`switchpokemon|${i}`}
/>;
return this.renderPokemonButton({
pokemon: serverPokemon,
cmd: `/switch ${i + 1}`,
disabled: cantSwitch && 'fade',
tooltip: `switchpokemon|${i}`,
});
});
}
renderTeamList() {
@ -637,9 +661,12 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
<h3 class="switchselect">Team</h3>
<div class="switchmenu">
{team.map((serverPokemon, i) => {
return <PokemonButton
pokemon={serverPokemon} cmd="" noHPBar disabled tooltip={`switchpokemon|${i}`}
/>;
return this.renderPokemonButton({
pokemon: serverPokemon,
cmd: "",
disabled: true,
tooltip: `switchpokemon|${i}`,
});
})}
</div>
</div>;
@ -647,9 +674,12 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
renderChosenTeam(request: BattleTeamRequest, choices: BattleChoiceBuilder) {
return choices.alreadySwitchingIn.map(slot => {
const serverPokemon = request.side.pokemon[slot - 1];
return <PokemonButton
pokemon={serverPokemon} cmd={`/switch ${slot}`} disabled tooltip={`switchpokemon|${slot - 1}`}
/>;
return this.renderPokemonButton({
pokemon: serverPokemon,
cmd: `/switch ${slot}`,
disabled: true,
tooltip: `switchpokemon|${slot - 1}`,
});
});
}
renderOldChoices(request: BattleRequest, choices: BattleChoiceBuilder) {
@ -821,7 +851,7 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
Choose {choices.alreadySwitchingIn.length <= 0 ? `lead` : `slot ${choices.alreadySwitchingIn.length + 1}`}
</h3>
<div class="switchmenu">
{this.renderTeamControls(request, choices)}
{this.renderTeamPreviewChooser(request, choices)}
<div style="clear:left"></div>
</div>
</div>

View File

@ -1221,7 +1221,7 @@ export class ChatLog extends preact.Component<{
if (controlsElem && controlsElem.className !== 'controls') controlsElem = undefined;
if (!jsx) {
if (!controlsElem) return;
preact.render(null, elem, controlsElem);
elem.removeChild(controlsElem);
this.updateScroll();
return;
}
@ -1230,7 +1230,9 @@ export class ChatLog extends preact.Component<{
controlsElem.className = 'controls';
elem.appendChild(controlsElem);
}
preact.render(<div class="controls">{jsx}</div>, elem, controlsElem);
// for some reason, the replaceNode feature isn't working?
if (controlsElem.children[0]) controlsElem.removeChild(controlsElem.children[0]);
preact.render(<div>{jsx}</div>, controlsElem);
this.updateScroll();
}
updateScroll() {

View File

@ -16,7 +16,6 @@ class TeamRoom extends PSRoom {
/** Doesn't _literally_ always exist, but does in basically all code
* and constantly checking for its existence is legitimately annoying... */
team!: Team;
uploaded = false;
override clientCommands = this.parseClientCommands({
'validate'(target) {
if (this.team.format.length <= 4) {
@ -32,7 +31,6 @@ class TeamRoom extends PSRoom {
this.team = team!;
this.title = `[Team] ${this.team?.name || 'Error'}`;
if (team) this.setFormat(team.format);
this.uploaded = !!team?.uploaded;
this.load();
}
setFormat(format: string) {
@ -60,7 +58,7 @@ class TeamRoom extends PSRoom {
buf.push(exported);
PS.teams.uploading = team;
PS.send(`|/teams ${cmd} ${buf.join(', ')}`);
this.uploaded = true;
team.uploadedPackedTeam = exported;
this.update(null);
}
save() {
@ -114,11 +112,10 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
uploadTeam = (ev: Event) => {
const room = this.props.room;
room.upload(PS.prefs.uploadprivacy);
room.upload(room.team.uploaded ? !!room.team.uploaded.private : PS.prefs.uploadprivacy);
};
changePrivacyPref = (ev: Event) => {
this.props.room.uploaded = false;
PS.prefs.uploadprivacy = !(ev.currentTarget as HTMLInputElement).checked;
PS.prefs.save();
this.forceUpdate();
@ -136,7 +133,6 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
};
save = () => {
this.props.room.save();
this.props.room.uploaded = false;
this.forceUpdate();
};
@ -156,18 +152,20 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
const info = TeamPanel.formatResources[team.format];
const formatName = BattleLog.formatName(team.format);
const unsaved = team.uploaded ? team.uploadedPackedTeam !== team.packedTeam : false;
return <PSPanelWrapper room={room} scrollable><div class="pad">
<a class="button" href="teambuilder" data-target="replace">
<i class="fa fa-chevron-left" aria-hidden></i> Teams
</a> {}
{team.uploaded?.private ? (
<button class="button" data-href={`teamstorage-${team.key}`}>
<i class="fa fa-cloud"></i> Account
</button>
) : team.uploaded ? (
<button class="button" data-href={`teamstorage-${team.key}`}>
<i class="fa fa-globe"></i> Account (public)
</button>
{team.uploaded ? (
<>
<button class={`button${unsaved ? ' button-first' : ''}`} data-href={`teamstorage-${team.key}`}>
<i class="fa fa-globe"></i> Account {team.uploaded.private ? '' : "(public)"}
</button>
{unsaved && <button class="button button-last" onClick={this.uploadTeam}>
<strong>Upload changes</strong>
</button>}
</>
) : team.teamid ? (
<button class="button" data-href={`teamstorage-${team.key}`}>
<i class="fa fa-plug"></i> Disconnected (wrong account?)
@ -191,31 +189,36 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
onInput={this.handleRename} onChange={this.handleRename} onKeyUp={this.handleRename}
/>
</label>
<TeamEditor team={team} onChange={this.save} readonly={!!team.teamid && !team.uploaded}>
<TeamEditor team={team} onChange={this.save} readonly={!!team.teamid && !team.uploadedPackedTeam}>
{!!(team.packedTeam && team.format.length > 4) && <p>
<button data-cmd="/validate" class="button"><i class="fa fa-check"></i> Validate</button>
</p>}
{team.uploaded?.private === null && <p>
<small>Share URL:</small> {}
<input type="text" class="textbox" value={`https://psim.us/t/${team.uploaded.teamid}`} readOnly size={24} />
</p>}
{!!(team.packedTeam || team.uploaded) && <p>
<label class="checkbox inline">
{!!(team.packedTeam || team.uploaded) && <p class="infobox" style="padding: 5px 8px">
{team.uploadedPackedTeam && !team.uploaded ? <>
Uploading...
</> : team.uploaded ? <>
<small>Share URL:</small> {}
<input
name="teamprivacy" checked={!PS.prefs.uploadprivacy}
type="checkbox" onChange={this.changePrivacyPref}
/> Public
</label>
{room.uploaded ? (
<button class="button exportbutton" disabled>
<i class="fa fa-check"></i> Saved to your account
</button>
) : (
type="text" class="textbox" readOnly size={45}
value={`https://psim.us/t/${team.uploaded.teamid}${team.uploaded.private ? '-' + team.uploaded.private : ''}`}
/> {}
{unsaved && <div style="padding-top:5px">
<button class="button" onClick={this.uploadTeam}>
<i class="fa fa-upload"></i> Upload changes
</button>
</div>}
</> : !team.teamid ? <>
<label class="checkbox inline">
<input
name="teamprivacy" checked={!PS.prefs.uploadprivacy}
type="checkbox" onChange={this.changePrivacyPref}
/> Public
</label>
<button class="button exportbutton" onClick={this.uploadTeam}>
<i class="fa fa-upload"></i> Save to my account {}
({PS.prefs.uploadprivacy ? 'use on other devices' : 'share and make searchable'})
<i class="fa fa-upload"></i> Upload for shareable URL {}
{PS.prefs.uploadprivacy ? '' : '(and make searchable)'}
</button>
)}
</> : null}
</p>}
</TeamEditor>
{!!(info && (info.resources.length || info.url)) && (
@ -252,6 +255,7 @@ class TeamStoragePanel extends PSRoomPanel {
PS.mainmenu.send(`/teams delete ${team.uploaded.teamid}`);
team.uploaded = undefined;
team.teamid = undefined;
team.uploadedPackedTeam = undefined;
PS.teams.save();
(room.getParent() as TeamRoom).update(null);
} else if (storage === 'public' && team.uploaded?.private) {

View File

@ -470,9 +470,9 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
{teams.map(team => team ? (
<li key={team.key} onDragEnter={this.dragEnterTeam} data-teamkey={team.key}>
<TeamBox team={team} /> {}
<button data-cmd={`/deleteteam ${team.key}`} class="option">
{!team.uploaded && <button data-cmd={`/deleteteam ${team.key}`} class="option">
<i class="fa fa-trash" aria-hidden></i> Delete
</button> {}
</button>} {}
{team.uploaded?.private ? (
<i class="fa fa-cloud gray"></i>
) : team.uploaded ? (

View File

@ -296,7 +296,7 @@ li::marker {
content: "";
display: block;
height: 6px;
margin: -1px 0 -1px 0;
margin: -1px 0 0 0;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.2);
box-shadow: 0 1px 2px rgba(0,0,0,.2);
@ -1993,6 +1993,7 @@ pre.textbox.textbox-empty[placeholder]:before {
.switchmenu button.disabled,
.switchmenu button:disabled,
.movebutton.disabled,
.movebutton:disabled {
cursor: default;
background: #F3F3F3 !important;
@ -2080,6 +2081,7 @@ pre.textbox.textbox-empty[placeholder]:before {
text-align: center;
/* individual pokemon in a team have margin-right -4px so this compensates */
margin: 0 -1px 0 -5px;
white-space: normal;
}
.team small span {
margin-right: -4px;

View File

@ -536,7 +536,7 @@ you can't delete it by pressing Backspace */
border: 0;
border-spacing: 0;
table-layout: fixed;
margin: 4px 0;
margin: 1px 0;
width: 100%;
}
.set-button td {
@ -570,10 +570,11 @@ you can't delete it by pressing Backspace */
.set-button .set-nickname {
position: absolute;
height: 35px;
height: 32px;
width: 120px;
top: 5px;
left: -5px;
padding: 0 3px;
}
.tiny-layout .set-button .set-nickname {
width: 90px;
@ -661,8 +662,9 @@ you can't delete it by pressing Backspace */
}
.team-focus-editor .tabbar {
/* note to self: make this scrollable later */
/* overflow: auto; */
overflow: auto;
white-space: nowrap;
min-height: 59px;
}
.tabbar .button.picontab {
width: 80px;