-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Added chat tab #14
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { TabController } from "../TabController"; | ||
import { ChatTabState, Message } from "./ChatTabState"; | ||
|
||
export class ChatTabController extends TabController<ChatTabState> { | ||
public static Commands = ["say", "whisper"]; | ||
|
||
public execute = () => { | ||
const command: string = this.model.getState().commandToSend; | ||
const m = this.model.getState(); | ||
const recipient: string = m.selectedRecipient; | ||
if (!recipient || recipient === "ALL") { | ||
this.model.requestResource({ | ||
data: "say " + command, | ||
method: "POST", | ||
resourcePath: ["console"], | ||
}); | ||
} else { | ||
const recipientName = recipient.replace("PLAYER_", ""); | ||
this.model.requestResource({ | ||
data: `whisper ${recipientName} ${command}`, | ||
method: "POST", | ||
resourcePath: ["console"], | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've noticed in testing that whispering to a player doesn't give any feedback to the sender if on the frontend. I think it would be helpful, and it can be done by updating the state of the messages: |
||
} | ||
this.model.update({ | ||
commandToSend: "", | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { ActionResult } from "../../io/ActionResult"; | ||
import { IncomingMessage } from "../../io/IncomingMessage"; | ||
import { OnlinePlayerMetadata } from "../../io/OnlinePlayerMetadata"; | ||
import { ResourcePath } from "../../io/ResourcePath"; | ||
import { ResourcePathUtil } from "../../io/ResourcePath"; | ||
import { ResourceSubscriberTabModel } from "../ResourceSubscriberTabModel"; | ||
import { TabController } from "../TabController"; | ||
import { ChatTabController } from "./ChatTabController"; | ||
import { ChatTabState, Message } from "./ChatTabState"; | ||
|
||
export class ChatTabModel extends ResourceSubscriberTabModel<ChatTabState> { | ||
|
||
public getName(): string { | ||
return "Chat"; | ||
} | ||
|
||
public getSubscribedResourcePaths(): ResourcePath[] { | ||
return [ | ||
["onlinePlayers"], | ||
]; | ||
} | ||
|
||
public getDefaultState(): ChatTabState { | ||
return { messages: [], commandToSend: "Type a chat command here...", commands: [] }; | ||
} | ||
|
||
public initController(): TabController<ChatTabState> { | ||
return new ChatTabController(); | ||
} | ||
|
||
public onResourceUpdated(resourcePath: ResourcePath, data: any): void { | ||
if (ResourcePathUtil.equals(resourcePath, ["onlinePlayers"])) { | ||
this.update({ onlinePlayers: data as OnlinePlayerMetadata[] }); | ||
} | ||
} | ||
|
||
public onMessage(message: IncomingMessage) { | ||
super.onMessage(message); | ||
if (message.messageType === "ACTION_RESULT") { | ||
const innerMessage: ActionResult = message.data as ActionResult; | ||
if (innerMessage.status !== "OK") { | ||
// TODO: Maybe show error message here | ||
} | ||
} | ||
if (message.messageType === "RESOURCE_EVENT" && ResourcePathUtil.equals(message.resourcePath, ["console"])) { | ||
const oldState: ChatTabState = this.getState(); | ||
const msg: Message = (message.data) as Message; | ||
if (msg.type === "CHAT" || msg.type === "CLIENT") { | ||
this.update({ messages: oldState.messages.concat(msg) }); | ||
} | ||
if (msg.type === "CONSOLE") { | ||
if (msg.message === "Message sent") { | ||
this.update({ | ||
messageSendStatus: "SENT", | ||
}); | ||
} else if (msg.message === "User with name '[a-zA-Z0-9]+' not found.") { | ||
this.update({ | ||
errorMessage: msg.message, | ||
messageSendStatus: "ERROR", | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { OnlinePlayerMetadata } from "../../io/OnlinePlayerMetadata"; | ||
|
||
export interface Message { | ||
type: "CONSOLE" | "CHAT" | "ERROR" | "NOTIFICATION" | "CLIENT"; | ||
message: string; | ||
} | ||
|
||
export interface ChatTabState { | ||
messages?: Message[]; | ||
commandToSend?: string; | ||
commands?: string[]; | ||
messageSendStatus?: "SENDING" | "SENT" | "ERROR" | "NONE"; | ||
errorMessage?: string; | ||
selectedRecipient?: string; | ||
onlinePlayers?: OnlinePlayerMetadata[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remember that these are like global variables. Try to minimize the amount of variables in the state if you can. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* tslint:disable:prefer-for-of */ | ||
/* tslint:disable:no-bitwise */ | ||
|
||
import RX = require("reactxp"); | ||
import Styles = require("../../styles/main"); | ||
import { TabView } from "../TabView"; | ||
import { ConsoleAutocomplete } from "./../console/ConsoleAutocomplete"; | ||
import { ChatTabController } from "./ChatTabController"; | ||
import { ChatTabState, Message } from "./ChatTabState"; | ||
|
||
interface MessagePart { | ||
text: string; | ||
color: number; | ||
} | ||
|
||
export class ChatTabView extends TabView<ChatTabState> { | ||
|
||
private firstColor: number = 0xE000; | ||
private lastColor: number = 0xEFFF; | ||
private resetColor: number = 0xF000; | ||
private colorStyles: any = {}; | ||
|
||
public render() { | ||
const controller: ChatTabController = this.props.model.getController() as ChatTabController; | ||
return ( | ||
<RX.ScrollView> | ||
<RX.View style={[Styles.flex.column, Styles.flex.fill, Styles.justifyFlexEnd]}> | ||
<RX.View> | ||
{this.renderMessages()} | ||
</RX.View> | ||
</RX.View> | ||
<RX.Text> | ||
{this.renderStatusText()}</RX.Text> | ||
<RX.View style={Styles.flex.row}> | ||
<RX.TextInput | ||
style={[Styles.whiteBox, Styles.commandTextInput]} | ||
value={this.state.commandToSend} | ||
onChangeText={this.onChangeValue} | ||
autoFocus={true} | ||
onKeyPress={(event) => { this.handleKeypress(event, controller); }} /> | ||
{this.renderRecipientList()} | ||
<RX.Button style={Styles.okButton} onPress={controller.execute}><RX.Text>Send</RX.Text></RX.Button> | ||
</RX.View> | ||
</RX.ScrollView> | ||
); | ||
} | ||
|
||
private onChangeValue = (newValue: string) => { | ||
this.props.model.update({ commandToSend: newValue }); | ||
ConsoleAutocomplete.clear(); | ||
} | ||
|
||
private renderRecipientList() { | ||
const allItem = { label: "all", value: "ALL" }; | ||
return ( | ||
<RX.Picker | ||
items={[allItem].concat(this.state.onlinePlayers.map((player) => ({ label: player.name, value: "PLAYER_" + player.name })))} | ||
selectedValue={this.state.selectedRecipient} | ||
onValueChange={(value, position) => { this.props.model.update({ selectedRecipient: value }); }} /> | ||
); | ||
} | ||
|
||
private renderMessages() { | ||
return this.state.messages.map((msg: Message, index: number) => | ||
<RX.Text selectable={true} key={index}>{this.renderMessage(msg.message)}</RX.Text>); | ||
} | ||
|
||
private renderStatusText() { | ||
switch (this.state.messageSendStatus) { | ||
case "SENDING": | ||
return "Sending..."; | ||
case "ERROR": | ||
return this.state.errorMessage; | ||
case "SENT": | ||
return "Sent"; | ||
} | ||
return ""; | ||
} | ||
|
||
private renderMessage(message: string): JSX.Element { | ||
const parts: MessagePart[] = []; | ||
let currentPart: MessagePart = { text: "", color: 0 }; | ||
for (let i: number = 0; i < message.length; i++) { | ||
const charCode: number = message.charCodeAt(i); | ||
if (charCode === this.resetColor) { | ||
parts.push(currentPart); | ||
currentPart = { text: "", color: 0 }; | ||
} else if (charCode >= this.firstColor && charCode <= this.lastColor) { | ||
if (currentPart.text !== "") { | ||
parts.push(currentPart); | ||
} | ||
currentPart = { text: "", color: charCode }; | ||
} else { | ||
currentPart.text += message[i]; | ||
} | ||
} | ||
parts.push(currentPart); | ||
const renderedParts = parts.map((part: MessagePart, index: number) => { | ||
const colorId: string = part.color.toString(); | ||
let colorStyle: RX.Types.TextStyle; | ||
if (this.colorStyles.hasOwnProperty(colorId)) { | ||
colorStyle = this.colorStyles[colorId]; | ||
} else { | ||
colorStyle = RX.Styles.createTextStyle({ color: this.colorIdToRgbString(part.color) }) as RX.Types.TextStyle; | ||
this.colorStyles[colorId] = colorStyle; | ||
} | ||
return <RX.Text key={index} style={colorStyle}>{part.text}</RX.Text>; | ||
}); | ||
return ( | ||
<RX.Text>{renderedParts}</RX.Text> | ||
); | ||
} | ||
|
||
private colorIdToRgbString(colorId: number): string { | ||
const rgb: number = colorId - this.firstColor; | ||
const r: number = ((rgb >> 8) & 0xF) << 4; | ||
const g: number = ((rgb >> 4) & 0xF) << 4; | ||
const b: number = ((rgb >> 0) & 0xF) << 4; | ||
return "rgb(" + r.toString() + "," + g.toString() + "," + b.toString() + ")"; | ||
} | ||
|
||
private handleKeypress(event: any, controller: ChatTabController) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be static. |
||
if (event.keyCode === 13) { | ||
controller.execute(); | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Message
is an unused import.