import preact from "../js/lib/preact"; import { toID, toRoomid, toUserid, Dex } from "./battle-dex"; import type { ID } from "./battle-dex-data"; import { BattleLog } from "./battle-log"; import { PSLoginServer } from "./client-connection"; import { PSBackground } from "./client-core"; import { PS, PSRoom, Config, type RoomOptions, type PSLoginState, type RoomID, type TimestampOptions, } from "./client-main"; import { type BattleRoom } from "./panel-battle"; import { ChatUserList, type ChatRoom } from "./panel-chat"; import { PSRoomPanel, PSPanelWrapper, PSView } from "./panels"; import { PSHeader } from "./panel-topbar"; /** * User popup */ export class UserRoom extends PSRoom { override readonly classType = 'user'; userid!: ID; name!: string; isSelf!: boolean; constructor(options: RoomOptions) { super(options); const userid = (this.id.split('-')[1] || '') as ID; this.setName(options.args?.username as string || userid); } setName(name: string) { this.name = name; this.userid = toID(name); this.isSelf = (this.userid === PS.user.userid); if (/[a-zA-Z0-9]/.test(this.name.charAt(0))) this.name = ' ' + this.name; this.update(null); if (this.userid) PS.send(`|/cmd userdetails ${this.userid}`); } } class UserPanel extends PSRoomPanel { static readonly id = 'user'; static readonly routes = ['user-*', 'viewuser-*', 'users']; static readonly Model = UserRoom; static readonly location = 'popup'; renderUser() { const room = this.props.room; if (!room.userid) return null; const user = PS.mainmenu.userdetailsCache[room.userid] || { userid: room.userid, name: room.name.slice(1), avatar: '[loading]', }; if (!user.avatar) { // offline; server doesn't know the actual username user.name = room.name; } const hideInteraction = room.id.startsWith('viewuser-'); const group = PS.server.getGroup(room.name); let groupName: preact.ComponentChild = group.name || null; if (group.type === 'punishment') { groupName = {groupName}; } const globalGroup = PS.server.getGroup(user.group); let globalGroupName: preact.ComponentChild = globalGroup.name && `Global ${globalGroup.name}` || null; if (globalGroup.type === 'punishment') { globalGroupName = {globalGroupName}; } if (globalGroup.name === group.name) groupName = null; let roomsList: preact.ComponentChild = null; if (user.rooms) { let battlebuf = []; let chatbuf = []; let privatebuf = []; for (let roomid in user.rooms) { if (roomid === 'global') continue; const curRoom = user.rooms[roomid]; let roomrank: preact.ComponentChild = null; if (!/[A-Za-z0-9]/.test(roomid.charAt(0))) { roomrank = {roomid.charAt(0)}; } roomid = toRoomid(roomid); if (roomid.substr(0, 7) === 'battle-') { const p1 = curRoom.p1!.substr(1); const p2 = curRoom.p2!.substr(1); const ownBattle = (PS.user.userid === toUserid(p1) || PS.user.userid === toUserid(p2)); const roomLink = {roomrank}{roomid.substr(7)}; if (curRoom.isPrivate) { if (privatebuf.length) privatebuf.push(', '); privatebuf.push(roomLink); } else { if (battlebuf.length) battlebuf.push(', '); battlebuf.push(roomLink); } } else { const roomLink = {roomrank}{roomid} ; if (curRoom.isPrivate) { if (privatebuf.length) privatebuf.push(", "); privatebuf.push(roomLink); } else { if (chatbuf.length) chatbuf.push(', '); chatbuf.push(roomLink); } } } if (battlebuf.length) battlebuf.unshift(
, Battles:, " "); if (chatbuf.length) chatbuf.unshift(
, Chatrooms:, " "); if (privatebuf.length) privatebuf.unshift(
, Private rooms:, " "); if (battlebuf.length || chatbuf.length || privatebuf.length) { roomsList = {battlebuf}{chatbuf}{privatebuf}; } } else if (user.rooms === false) { roomsList = OFFLINE; } const isSelf = user.userid === PS.user.userid; let away = false; let status = null; if (user.status) { away = user.status.startsWith('!'); status = away ? user.status.slice(1) : user.status; } const buttonbar = []; if (!hideInteraction) { buttonbar.push(isSelf ? (

{}

) : !PS.user.named ? (

{} {}

) : (

{} {}

)); if (isSelf) { buttonbar.push(
,

{}

); } } const avatar = user.avatar !== '[loading]' ? Dex.resolveAvatar(`${user.avatar || 'unknown'}`) : null; return [
{avatar && (room.isSelf ? ( ) : ( ))} {user.name}
{status &&
{status}
} {groupName &&
{groupName}
} {globalGroupName &&
{globalGroupName}
} {user.customgroup &&
{user.customgroup}
} {!hideInteraction && roomsList}
, buttonbar]; } lookup = (ev: Event) => { ev.preventDefault(); ev.stopImmediatePropagation(); const room = this.props.room; const username = this.base!.querySelector('input[name=username]')?.value; room.setName(username || ''); }; maybeReset = (ev: Event) => { const room = this.props.room; const username = this.base!.querySelector('input[name=username]')?.value; if (toID(username) !== room.userid) { room.setName(''); } }; override render() { const room = this.props.room; const showLookup = room.id === 'users'; return
{showLookup &&
{!room.userid &&

{}

} {!!room.userid &&
}
} {this.renderUser()}
; } } class UserOptionsPanel extends PSRoomPanel { static readonly id = 'useroptions'; static readonly routes = ['useroptions-*']; static readonly location = 'popup'; static readonly noURL = true; declare state: { showMuteInput?: boolean, showBanInput?: boolean, showLockInput?: boolean, showConfirm?: boolean, requestSent?: boolean, data?: Record, }; getTargets() { const [, targetUser, targetRoomid] = this.props.room.id.split('-'); let targetRoom = (PS.rooms[targetRoomid] || null) as ChatRoom | null; if (targetRoom?.type !== 'chat') targetRoom = targetRoom?.getParent() as ChatRoom; if (targetRoom?.type !== 'chat') targetRoom = targetRoom?.getParent() as ChatRoom; if (targetRoom?.type !== 'chat') targetRoom = null; return { targetUser: targetUser as ID, targetRoomid: targetRoomid as RoomID, targetRoom }; } handleMute = (ev: Event) => { this.setState({ showMuteInput: true, showBanInput: false, showLockInput: false }); ev.preventDefault(); ev.stopImmediatePropagation(); }; handleBan = (ev: Event) => { this.setState({ showBanInput: true, showMuteInput: false, showLockInput: false }); ev.preventDefault(); ev.stopImmediatePropagation(); }; handleLock = (ev: Event) => { this.setState({ showLockInput: true, showMuteInput: false, showBanInput: false }); ev.preventDefault(); ev.stopImmediatePropagation(); }; handleCancel = (ev: Event) => { this.setState({ showBanInput: false, showMuteInput: false, showLockInput: false, showConfirm: false }); ev.preventDefault(); ev.stopImmediatePropagation(); }; handleConfirm = (ev: Event) => { const data = this.state.data; if (!data) return; const { targetUser, targetRoom } = this.getTargets(); let cmd = ''; if (data.action === "Mute") { cmd += data.duration === "1 hour" ? "/hourmute " : "/mute "; } else if (data.action === "Ban") { cmd += data.duration === "1 week" ? "/weekban " : "/ban "; } else if (data.action === "Lock") { cmd += data.duration === "1 week" ? "/weeklock " : "/lock "; } else if (data.action === "Namelock") { cmd += "/namelock "; } else { return; } cmd += `${targetUser} ${data.reason ? ',' + data.reason : ''}`; targetRoom?.send(cmd); this.close(); }; handleAddFriend = (ev: Event) => { const { targetUser, targetRoom } = this.getTargets(); targetRoom?.send(`/friend add ${targetUser}`); this.setState({ requestSent: true }); ev.preventDefault(); ev.stopImmediatePropagation(); }; handleIgnore = () => { const { targetUser, targetRoom } = this.getTargets(); targetRoom?.send(`/ignore ${targetUser}`); this.close(); }; handleUnignore = () => { const { targetUser, targetRoom } = this.getTargets(); targetRoom?.send(`/unignore ${targetUser}`); this.close(); }; muteUser = (ev: Event) => { this.setState({ showMuteInput: false }); const hrMute = (ev.currentTarget as HTMLButtonElement).value === "1hr"; const reason = this.base?.querySelector("input[name=mutereason]")?.value; const data = { action: 'Mute', reason, duration: hrMute ? "1 hour" : "7 minutes", }; this.setState({ data, showConfirm: true }); ev.preventDefault(); ev.stopImmediatePropagation(); }; banUser = (ev: Event) => { this.setState({ showBanInput: false }); const weekBan = (ev.currentTarget as HTMLButtonElement).value === "1wk"; const reason = this.base?.querySelector("input[name=banreason]")?.value; const data = { action: 'Ban', reason, duration: weekBan ? "1 week" : "2 days", }; this.setState({ data, showConfirm: true }); ev.preventDefault(); ev.stopImmediatePropagation(); }; lockUser = (ev: Event) => { this.setState({ showLockInput: false }); const weekLock = (ev.currentTarget as HTMLButtonElement).value === "1wk"; const isNamelock = (ev.currentTarget as HTMLButtonElement).value === "nmlk"; const reason = this.base?.querySelector("input[name=lockreason]")?.value; const data = { action: isNamelock ? 'Namelock' : 'Lock', reason, duration: weekLock ? "1 week" : "2 days", }; this.setState({ data, showConfirm: true }); ev.preventDefault(); ev.stopImmediatePropagation(); }; isIgnoringUser = (userid: string) => { const ignoring = PS.prefs.ignore || {}; if (ignoring[userid] === 1) return true; return false; }; override render() { const room = this.props.room; const banPerms = ["@", "#", "~"]; const mutePerms = ["%", ...banPerms]; const { targetUser, targetRoom } = this.getTargets(); const userRoomGroup = targetRoom?.users[PS.user.userid].charAt(0) || ''; const canMute = mutePerms.includes(userRoomGroup); const canBan = banPerms.includes(userRoomGroup); const canLock = mutePerms.includes(PS.user.group); const isVisible = (actionName: string) => { if (actionName === 'mute') { return canMute && !this.state.showLockInput && !this.state.showBanInput && !this.state.showConfirm; } if (actionName === 'ban') { return canBan && !this.state.showLockInput && !this.state.showMuteInput && !this.state.showConfirm; } if (actionName === 'lock') { return canLock && !this.state.showBanInput && !this.state.showMuteInput && !this.state.showConfirm; } }; return

{this.isIgnoringUser(targetUser) ? ( ) : ( )}

{this.state.requestSent ? ( ) : ( )}

{(canMute || canBan || canLock) &&
} {this.state.showConfirm &&

{this.state.data?.action} {targetUser} {} {!this.state.data?.action.endsWith('ock') ? <>from {targetRoom?.title} : ''} for {this.state.data?.duration}?

{}

}

{isVisible('mute') && (this.state.showMuteInput ? (

{}
{} {}
) : ( ))} {} {isVisible('ban') && (this.state.showBanInput ? (

{} {}
) : ( ))} {} {isVisible('lock') && (this.state.showLockInput ? (

{} {} {}
) : ( ))}

; } } class UserListPanel extends PSRoomPanel { static readonly id = 'userlist'; static readonly routes = ['userlist']; static readonly location = 'semimodal-popup'; static readonly noURL = true; override render() { const room = this.props.room; const parentRoom = room.getParent() as ChatRoom; if (parentRoom.type !== 'chat' && parentRoom.type !== 'battle') { throw new Error(`UserListPanel: ${room.id} is not a chat room`); } return
; } } class VolumePanel extends PSRoomPanel { static readonly id = 'volume'; static readonly routes = ['volume']; static readonly location = 'popup'; setVolume = (e: Event) => { const slider = e.currentTarget as HTMLInputElement; PS.prefs.set(slider.name as 'effectvolume', Number(slider.value)); this.forceUpdate(); }; setMute = (e: Event) => { const checkbox = e.currentTarget as HTMLInputElement; PS.prefs.set('mute', !!checkbox.checked); PS.update(); }; override componentDidMount() { super.componentDidMount(); this.subscriptions.push(PS.prefs.subscribe(() => { this.forceUpdate(); })); } override render() { const room = this.props.room; return

Volume

{PS.prefs.mute ? (muted) : }

{PS.prefs.mute ? (muted) : }

{PS.prefs.mute ? (muted) : }

; } } class OptionsPanel extends PSRoomPanel { static readonly id = 'options'; static readonly routes = ['options']; static readonly location = 'semimodal-popup'; declare state: { showStatusInput?: boolean, showStatusUpdated?: boolean }; override componentDidMount() { super.componentDidMount(); this.subscribeTo(PS.user); } setTheme = (e: Event) => { const theme = (e.currentTarget as HTMLSelectElement).value as 'light' | 'dark' | 'system'; PS.prefs.set('theme', theme); this.forceUpdate(); }; setLayout = (e: Event) => { const layout = (e.currentTarget as HTMLSelectElement).value; switch (layout) { case '': PS.prefs.set('onepanel', null); PS.rightPanel ||= PS.rooms['rooms'] || null; break; case 'onepanel': PS.prefs.set('onepanel', true); break; case 'vertical': PS.prefs.set('onepanel', 'vertical'); break; } PS.update(); }; setChatroomTimestamp = (ev: Event) => { const timestamp = (ev.currentTarget as HTMLSelectElement).value as TimestampOptions; PS.prefs.set('timestamps', { ...PS.prefs.timestamps, chatrooms: timestamp || undefined }); }; setPMsTimestamp = (ev: Event) => { const timestamp = (ev.currentTarget as HTMLSelectElement).value as TimestampOptions; PS.prefs.set('timestamps', { ...PS.prefs.timestamps, pms: timestamp || undefined }); }; handleShowStatusInput = (ev: Event) => { ev.preventDefault(); ev.stopImmediatePropagation(); this.setState({ showStatusInput: !this.state.showStatusInput }); }; handleOnChange = (ev: Event) => { let elem = ev.currentTarget as HTMLInputElement; let setting = elem.name; let value = elem.checked; switch (setting) { case 'blockPMs': { PS.prefs.set("blockPMs", value); PS.send(value ? '/blockpms' : '/unblockpms'); break; } case 'blockChallenges': { PS.prefs.set("blockChallenges", value); PS.send(value ? '/blockchallenges' : '/unblockchallenges'); break; } case 'bwgfx': { PS.prefs.set('bwgfx', value); Dex.loadSpriteData(value || PS.prefs.noanim ? 'bw' : 'xy'); break; } case 'language': { PS.prefs.set(setting, elem.value); PS.send('/language ' + elem.value); break; } case 'tournaments': { if (elem.value === "hide") PS.prefs.set(setting, elem.value); if (elem.value === "notify") PS.prefs.set(setting, elem.value); if (!elem.value) PS.prefs.set(setting, null); break; } case 'refreshprompt': case 'noanim': case 'nopastgens': case 'noselfhighlight': case 'leavePopupRoom': case 'inchatpm': PS.prefs.set(setting, value); break; } }; editStatus = (ev: Event) => { const statusInput = this.base!.querySelector('input[name=statustext]'); PS.send(statusInput?.value?.length ? `|/status ${statusInput.value}` : `|/clearstatus`); this.setState({ showStatusUpdated: true, showStatusInput: false }); ev.preventDefault(); ev.stopImmediatePropagation(); }; override render() { const room = this.props.room; return

{} {PS.user.name}

{this.state.showStatusInput ? (

) : (

)} {PS.user.named && (PS.user.registered?.userid === PS.user.userid ? : )}

Graphics


Chat


{PS.user.named ?

{}

:

}
; } } class GooglePasswordBox extends preact.Component<{ name: string }> { override componentDidMount() { window.gapiCallback = (response: any) => { PS.user.changeNameWithPassword(this.props.name, response.credential, { needsGoogle: true }); }; PS.user.gapiLoaded = true; const script = document.createElement('script'); script.async = true; script.src = 'https://accounts.google.com/gsi/client'; document.getElementsByTagName('head')[0].appendChild(script); } override render() { return
; } } class LoginPanel extends PSRoomPanel { static readonly id = 'login'; static readonly routes = ['login']; static readonly location = 'semimodal-popup'; declare state: { passwordShown?: boolean }; override componentDidMount() { super.componentDidMount(); this.subscriptions.push(PS.user.subscribe(args => { if (args) { if (args.success) { this.close(); return; } this.props.room.args = args; setTimeout(() => this.focus(), 1); } this.forceUpdate(); })); } getUsername() { const loginName = PS.user.loggingIn || this.props.room.args?.name as string; if (loginName) return loginName; const input = this.base?.querySelector('input[name=username]'); if (input && !input.disabled) { return input.value; } return PS.user.named ? PS.user.name : ''; } handleSubmit = (ev: Event) => { ev.preventDefault(); const passwordBox = this.base!.querySelector('input[name=password]'); if (passwordBox) { PS.user.changeNameWithPassword(this.getUsername(), passwordBox.value); } else { PS.user.changeName(this.getUsername()); } }; update = () => { this.forceUpdate(); }; override focus() { const passwordBox = this.base!.querySelector('input[name=password]'); const usernameBox = this.base!.querySelector('input[name=username]'); (passwordBox || usernameBox)?.select(); } reset = (ev: Event) => { ev.preventDefault(); ev.stopImmediatePropagation(); this.props.room.args = null; this.forceUpdate(); }; handleShowPassword = (ev: Event) => { ev.preventDefault(); ev.stopImmediatePropagation(); this.setState({ passwordShown: !this.state.passwordShown }); }; override render() { const room = this.props.room; const loginState = room.args as PSLoginState; return

Log in

{loginState?.error &&

{loginState.error}

}

{PS.user.named && !loginState &&

(Others will be able to see your name change. To change name privately, use "Log out")

} {loginState?.needsPassword &&

if you registered this name:

} {loginState?.needsGoogle && <>

if you registered this name:

}

{PS.user.loggingIn ? ( ) : loginState?.needsPassword ? ( <> {} ) : loginState?.needsGoogle ? ( ) : ( <> {} )} {}

{loginState?.name &&

if not:

This is someone else's account. Sorry.

}
; } } class AvatarsPanel extends PSRoomPanel { static readonly id = 'avatars'; static readonly routes = ['avatars']; static readonly location = 'semimodal-popup'; override render() { const room = this.props.room; const avatars: [number, string][] = []; for (let i = 1; i <= 293; i++) { if (i === 162 || i === 168) continue; avatars.push([i, window.BattleAvatarNumbers?.[i] || `${i}`]); } return
{avatars.map(([i, avatar]) => ( ))}

; } } class BattleForfeitPanel extends PSRoomPanel { static readonly id = 'forfeit'; static readonly routes = ['forfeitbattle']; static readonly location = 'semimodal-popup'; static readonly noURL = true; override render() { const room = this.props.room; const battleRoom = room.getParent() as BattleRoom; return

Forfeiting makes you lose the battle. Are you sure?

{} {} {!battleRoom.battle.rated && } {}

; } } class ReplacePlayerPanel extends PSRoomPanel { static readonly id = 'replaceplayer'; static readonly routes = ['replaceplayer']; static readonly location = 'semimodal-popup'; static readonly noURL = true; handleReplacePlayer = (ev: Event) => { const room = this.props.room; const battleRoom = room.getParent()?.getParent() as BattleRoom; const newPlayer = this.base?.querySelector("input[name=newplayer]")?.value; if (!newPlayer?.length) return battleRoom.add("|error|Enter player's name"); if (battleRoom.battle.ended) return battleRoom.add("|error|Cannot replace player, battle has already ended."); let playerSlot = battleRoom.battle.p1.id === PS.user.userid ? "p1" : "p2"; battleRoom.send('/leavebattle'); battleRoom.send(`/addplayer ${newPlayer}, ${playerSlot}`); this.close(); ev.preventDefault(); }; override render() { const room = this.props.room; return

Replacement player's name:

{}

; } } class ChangePasswordPanel extends PSRoomPanel { static readonly id = "changepassword"; static readonly routes = ["changepassword"]; static readonly location = "semimodal-popup"; static readonly noURL = true; declare state: { errorMsg: string }; handleChangePassword = (ev: Event) => { ev.preventDefault(); let oldpassword = this.base?.querySelector('input[name=oldpassword]')?.value; let password = this.base?.querySelector('input[name=password]')?.value; let cpassword = this.base?.querySelector('input[name=cpassword]')?.value; if (!oldpassword?.length || !password?.length || !cpassword?.length) return this.setState({ errorMsg: "All fields are required" }); if (password !== cpassword) return this.setState({ errorMsg: 'Passwords do not match' }); PSLoginServer.query("changepassword", { oldpassword, password, cpassword, }).then(data => { if (data?.actionerror) return this.setState({ errorMsg: data?.actionerror }); PS.alert("Your password was successfully changed!"); }).catch(err => { console.error(err); this.setState({ errorMsg: err.message }); }); this.setState({ errorMsg: '' }); }; override render() { const room = this.props.room; return
{ !!this.state.errorMsg?.length &&

{this.state.errorMsg}

}

Change your password:

{}

; } } class RegisterPanel extends PSRoomPanel { static readonly id = "register"; static readonly routes = ["register"]; static readonly location = "semimodal-popup"; static readonly noURL = true; static readonly rightPopup = true; declare state: { errorMsg: string }; handleRegisterUser = (ev: Event) => { ev.preventDefault(); let captcha = this.base?.querySelector('input[name=captcha]')?.value; let password = this.base?.querySelector('input[name=password]')?.value; let cpassword = this.base?.querySelector('input[name=cpassword]')?.value; if (!captcha?.length || !password?.length || !cpassword?.length) return this.setState({ errorMsg: "All fields are required" }); if (password !== cpassword) return this.setState({ errorMsg: 'Passwords do not match' }); PSLoginServer.query("register", { captcha, password, cpassword, username: PS.user.name, challstr: PS.user.challstr, }).then(data => { if (data?.actionerror) this.setState({ errorMsg: data?.actionerror }); if (data?.curuser?.loggedin) { let name = data.curuser.username; PS.user.registered = { name, userid: toID(name) }; if (data?.assertion) PS.user.handleAssertion(name, data?.assertion); this.close(); PS.alert("You have been successfully registered."); } }).catch(err => { console.error(err); this.setState({ errorMsg: err.message }); }); this.setState({ errorMsg: '' }); }; override render() { const room = this.props.room; return
{ !!this.state.errorMsg?.length &&

{this.state.errorMsg}

}

Register your account:

{}

; } } class BackgroundListPanel extends PSRoomPanel { static readonly id = 'changebackground'; static readonly routes = ['changebackground']; static readonly location = 'semimodal-popup'; static readonly noURL = true; declare state: { status?: string }; setBg = (ev: Event) => { let curtarget = ev.currentTarget as HTMLButtonElement; let bg = curtarget.value; PSBackground.set('', bg); ev.preventDefault(); ev.stopImmediatePropagation(); this.forceUpdate(); }; uploadBg = (ev: Event) => { this.setState({ status: undefined }); const input = this.base?.querySelector('input[name=bgfile]'); if (!input?.files?.[0]) return; const file = input.files[0]; const reader = new FileReader(); reader.onload = () => { const base64Image = reader.result as string; PSBackground.set(base64Image, 'custom'); this.forceUpdate(); }; reader.onerror = () => { this.setState({ status: "Failed to load background image." }); }; reader.readAsDataURL(file); ev.preventDefault(); ev.stopImmediatePropagation(); }; override render() { const room = this.props.room; const option = (val: string) => val === PSBackground.id ? 'option cur' : 'option'; return

Default

Official

Custom

Upload:

{!!this.state.status &&

{this.state.status}

}

; } } class ChatFormattingPanel extends PSRoomPanel { static readonly id = 'chatformatting'; static readonly routes = ['chatformatting']; static readonly location = 'semimodal-popup'; static readonly noURL = true; handleOnChange = (ev: Event) => { const setting = "hide" + (ev.currentTarget as HTMLInputElement).name; const value = (ev.currentTarget as HTMLInputElement).checked; let curPref = PS.prefs.chatformatting; curPref[setting] = value; PS.prefs.set("chatformatting", curPref); ev.preventDefault(); ev.stopImmediatePropagation(); }; override render() { const room = this.props.room; const ctrl = PSView.isMac ? 'Cmd' : 'Ctrl'; return

Usable formatting:

**bold** ({ctrl}+B)

__italics__ ({ctrl}+I)

``code formatting`` (Ctrl+`)

~~strikethrough~~

^^superscript^^

\\subscript\\

; } } class LeaveRoomPanel extends PSRoomPanel { static readonly id = 'confirmleaveroom'; static readonly routes = ['confirmleaveroom']; static readonly location = 'semimodal-popup'; static readonly noURL = true; override render() { const room = this.props.room; const parentRoomId = (this.props.room.parentElem as HTMLInputElement).value; return

Are you sure you want to exit this room?

{}

; } } class BattleOptionsPanel extends PSRoomPanel { static readonly id = 'battleoptions'; static readonly routes = ['battleoptions']; static readonly location = 'semimodal-popup'; static readonly noURL = true; handleHardcoreMode = (ev: Event) => { const mode = (ev.currentTarget as HTMLInputElement).checked; const room = this.getBattleRoom(); if (!room) return this.close(); room.battle.setHardcoreMode(mode); if (mode) { room.add(`||Hardcore mode ON: Information not available in-game is now hidden.`); } else { room.add(`||Hardcore mode OFF: Information not available in-game is now shown.`); } room.update(null); }; handleIgnoreSpectators = (ev: Event | boolean) => { const value = typeof ev === "object" ? (ev.currentTarget as HTMLInputElement).checked : ev; const room = this.getBattleRoom(); if (!room) return this.close(); room.battle.ignoreSpects = value; room.add(`||Spectators ${room.battle.ignoreSpects ? '' : 'no longer '}ignored.`); const chats = document.querySelectorAll('.battle-log .chat'); const displaySetting = room.battle.ignoreSpects ? 'none' : ''; for (const chat of chats) { const small = chat.querySelector('small'); if (!small) continue; const text = small.innerText; const isPlayerChat = text.includes('\u2606') || text.includes('\u2605'); if (!isPlayerChat) { chat.style.display = displaySetting; } } room.battle.scene.log.updateScroll(); }; handleIgnoreOpponent = (ev: Event | boolean) => { const value = typeof ev === "object" ? (ev.currentTarget as HTMLInputElement).checked : ev; const room = this.getBattleRoom(); if (!room) return this.close(); room.battle.ignoreOpponent = value; room.battle.resetToCurrentTurn(); }; handleIgnoreNicks = (ev: Event | boolean) => { const value = typeof ev === "object" ? (ev.currentTarget as HTMLInputElement).checked : ev; const room = this.getBattleRoom(); if (!room) return this.close(); room.battle.ignoreNicks = value; room.battle.resetToCurrentTurn(); }; handleAllSettings = (ev: Event) => { const setting = (ev.currentTarget as HTMLInputElement).name; const value = (ev.currentTarget as HTMLInputElement).checked; const room = this.getBattleRoom(); switch (setting) { case 'autotimer': { PS.prefs.set('autotimer', value); if (value) { room?.send('/timer on'); } break; } case 'ignoreopp': { PS.prefs.set('ignoreopp', value); this.handleIgnoreOpponent(value); break; } case 'ignorespects': { PS.prefs.set('ignorespects', value); this.handleIgnoreSpectators(value); break; } case 'ignorenicks': { PS.prefs.set('ignorenicks', value); this.handleIgnoreNicks(value); break; } case 'rightpanel': { PS.prefs.set('rightpanelbattles', value); break; } case 'disallowspectators': { PS.prefs.set('disallowspectators', value); PS.mainmenu.disallowSpectators = value; break; } } }; getBattleRoom() { const battleRoom = this.props.room.getParent() as BattleRoom | null; return battleRoom?.battle ? battleRoom : null; } override render() { const room = this.props.room; const battleRoom = this.getBattleRoom(); const isPlayer = !!battleRoom?.battle.myPokemon; const canOfferTie = battleRoom && ((battleRoom.battle.turn >= 100 && isPlayer) || PS.user.group === '~'); return
{battleRoom && <>

In this battle

}

All battles

{!PS.prefs.onepanel && document.body.offsetWidth >= 800 &&

}

{} {battleRoom && }

; } } class PopupRoom extends PSRoom { returnValue: unknown = this.args?.cancelValue; override destroy() { (this.args?.callback as any)?.(this.returnValue); super.destroy(); } } class PopupPanel extends PSRoomPanel { static readonly id = 'popup'; static readonly routes = ['popup-*']; static readonly location = 'semimodal-popup'; static readonly noURL = true; static readonly Model = PopupRoom; handleSubmit = (ev: Event) => { ev.preventDefault(); ev.stopImmediatePropagation(); const room = this.props.room; room.returnValue = room.args?.okValue; const textbox = this.base!.querySelector('input[name=value]'); if (textbox) { room.returnValue = textbox.value; } this.close(); }; override componentDidMount() { super.componentDidMount(); const textbox = this.base!.querySelector('input[name=value]'); if (!textbox) return; textbox.value = this.props.room.args?.value as string || ''; } parseMessage(message: string) { if (message.startsWith('|html|')) { return BattleLog.sanitizeHTML(message.slice(6)); } return BattleLog.parseMessage(message); } override render() { const room = this.props.room; const okButton = room.args?.okButton as string || 'OK'; const cancelButton = room.args?.cancelButton as string | undefined; const otherButtons = room.args?.otherButtons as preact.ComponentChildren; const value = room.args?.value as string | undefined; const type = (room.args?.type || (typeof value === 'string' ? 'text' : null)) as string | null; const message = room.args?.message; return
{message &&

} {!!type &&

}

{} {otherButtons} {} {!!cancelButton && }

; } } class RoomTabListPanel extends PSRoomPanel { static readonly id = 'roomtablist'; static readonly routes = ['roomtablist']; static readonly location = 'semimodal-popup'; static readonly noURL = true; startingLayout = PS.prefs.onepanel; handleLayoutChange = (ev: Event) => { const checkbox = ev.currentTarget as HTMLInputElement; PS.prefs.onepanel = checkbox.checked ? 'vertical' : this.startingLayout; PS.update(); }; override render() { const verticalTabs = PS.prefs.onepanel === 'vertical'; return
    {PS.leftRoomList.map(roomid => PSHeader.renderRoomTab(roomid, true))}
    {PS.rightRoomList.map(roomid => PSHeader.renderRoomTab(roomid, true))}
; } } class BattleTimerPanel extends PSRoomPanel { static readonly id = 'battletimer'; static readonly routes = ['battletimer']; static readonly location = 'semimodal-popup'; static readonly noURL = true; override render() { const room = this.props.room.getParent() as BattleRoom; return
{room.battle.kickingInactive ? ( ) : ( )}
; } } PS.addRoomType( UserPanel, UserOptionsPanel, UserListPanel, VolumePanel, OptionsPanel, LoginPanel, AvatarsPanel, ChangePasswordPanel, RegisterPanel, BattleForfeitPanel, ReplacePlayerPanel, BackgroundListPanel, LeaveRoomPanel, ChatFormattingPanel, PopupPanel, RoomTabListPanel, BattleOptionsPanel, BattleTimerPanel );