diff --git a/src/panel-mainmenu.tsx b/src/panel-mainmenu.tsx index f5de49428..356e14f46 100644 --- a/src/panel-mainmenu.tsx +++ b/src/panel-mainmenu.tsx @@ -5,7 +5,7 @@ * @license AGPLv3 */ -type RoomInfo = {title: string, desc: string, userCount: number, subRooms?: string[]}; +type RoomInfo = {title: string, desc?: string, userCount?: number, subRooms?: string[]}; class MainMenuRoom extends PSRoom { readonly classType: string = 'mainmenu'; diff --git a/src/panel-rooms.tsx b/src/panel-rooms.tsx index b706cac62..a2808dacf 100644 --- a/src/panel-rooms.tsx +++ b/src/panel-rooms.tsx @@ -15,6 +15,8 @@ class RoomsRoom extends PSRoom { class RoomsPanel extends PSRoomPanel { hidden = false; + search = ''; + lastKeyCode = 0; componentDidMount() { super.componentDidMount(); this.subscriptions.push(PS.user.subscribe(() => { @@ -28,12 +30,107 @@ class RoomsPanel extends PSRoomPanel { this.forceUpdate(); PS.update(); }; + changeSearch = (e: Event) => { + const target = (e.currentTarget as HTMLInputElement); + if (target.selectionStart !== target.selectionEnd) return; + this.search = target.value; + this.forceUpdate(); + }; + keyDownSearch = (e: KeyboardEvent) => { + this.lastKeyCode = e.keyCode; + if (e.keyCode === 13) { + const target = (e.currentTarget as HTMLInputElement); + let value = target.value; + const arrowIndex = value.indexOf(' \u21d2 '); + if (arrowIndex >= 0) value = value.slice(arrowIndex + 3); + if (!/^[a-z0-9-]$/.test(value)) value = toId(value); + + e.preventDefault(); + e.stopImmediatePropagation(); + target.value = ''; + + PS.join(value as RoomID); + } + }; + runSearch() { + const searchid = toId(this.search); + let exactMatch = false; + + const rooms = PS.mainmenu.roomsCache; + let roomList = [...(rooms.official || []), ...(rooms.pspl || []), ...(rooms.chat || [])]; + for (const room of roomList) { + if (!room.subRooms) continue; + for (const title of room.subRooms) { + roomList.push({ + title, + desc: `Subroom of ${room.title}`, + }); + } + } + + let start = roomList.filter(room => { + const titleid = toId(room.title); + if (titleid === searchid) exactMatch = true; + return titleid.startsWith(searchid) || + toId(room.title.replace(/^The /, '')).startsWith(searchid); + }); + roomList = roomList.filter(room => !start.includes(room)); + + let abbr = roomList.filter(room => + toId(room.title.toLowerCase().replace(/\b([a-z0-9])[a-z0-9]*\b/g, '$1')).startsWith(searchid) || + room.title.replace(/[^A-Z0-9]+/g, '').toLowerCase().startsWith(searchid) + ); + + const hidden = !exactMatch ? [{title: this.search, desc: "(Private room?)"}] : []; + + const autoFill = this.lastKeyCode !== 127 && this.lastKeyCode >= 32; + if (autoFill) { + const firstTitle = (start[0] || abbr[0] || hidden[0]).title; + let firstTitleOffset = 0; + while ( + searchid !== toId(firstTitle.slice(0, firstTitleOffset)) && + firstTitleOffset < firstTitle.length // should never happen, but sanity against infinite loop + ) { + firstTitleOffset++; + } + let autoFillValue = firstTitle.slice(firstTitleOffset); + if (!autoFillValue && toId(firstTitle) !== searchid) { + autoFillValue = ' \u21d2 ' + firstTitle; + } + const oldSearch = this.search; + const searchElem = this.base!.querySelector('input[type=search]') as HTMLInputElement; + searchElem.value = oldSearch + autoFillValue; + searchElem.setSelectionRange(oldSearch.length, oldSearch.length + autoFillValue.length); + } + + return {start, abbr, hidden}; + } + focus() { + (this.base!.querySelector('input[type=search]') as HTMLInputElement).focus(); + } render() { if (this.hidden && PS.isVisible(this.props.room)) this.hidden = false; if (this.hidden) { return {null}; } const rooms = PS.mainmenu.roomsCache; + + let roomList; + if (this.search) { + const search = this.runSearch(); + roomList = [ + this.renderRoomList("Search results", search.start), + this.renderRoomList("Search results (acronym)", search.abbr), + this.renderRoomList("Possible hidden room", search.hidden), + ]; + } else { + roomList = [ + this.renderRoomList("Official chat rooms", rooms.official), + this.renderRoomList("PSPL winner", rooms.pspl), + this.renderRoomList("Chat rooms", rooms.chat), + ]; + } + return
+
+ +
{rooms.userCount === undefined &&

Connecting...

} - {this.renderRoomList("Official chat rooms", rooms.official)} - {this.renderRoomList("PSPL winner", rooms.pspl)} - {this.renderRoomList("Chat rooms", rooms.chat)} + {roomList}
; } renderRoomList(title: string, rooms?: RoomInfo[]) { @@ -68,9 +170,9 @@ class RoomsPanel extends PSRoomPanel {

{title}

{rooms.map(roomInfo =>
- ({roomInfo.userCount} users) + {roomInfo.userCount !== undefined && ({roomInfo.userCount} users)} {roomInfo.title}
- {roomInfo.desc} + {roomInfo.desc || ''} {roomInfo.subRooms &&
Subrooms: {roomInfo.subRooms.map((roomName, i) => [