diff --git a/src/model/AppActions.js b/src/model/AppActions.js index 13f1e02..cab4ffa 100644 --- a/src/model/AppActions.js +++ b/src/model/AppActions.js @@ -18,6 +18,7 @@ import type { PlayerColor } from './types'; import {distinct} from '../util/collection'; +import {debounce} from '../util/debounce'; const APP_STATE_SAVE_KEY = 'savedAppState'; @@ -426,6 +427,14 @@ export class AppActions { this._client.sendMessage({type: 'CHAT', text: body, channelId: conversationId}); } + onDraftChat = debounce((body: string, conversationId: number) => { + this._store.dispatch({ + type: 'SAVE_CHAT_DRAFT', + conversationId: conversationId, + text: body + }); + }, 250) + onSendGameChat = (body: string, gameId: number) => { if (this._isOffline()) { return; diff --git a/src/model/conversation.js b/src/model/conversation.js index c79fb4f..583383d 100644 --- a/src/model/conversation.js +++ b/src/model/conversation.js @@ -18,6 +18,7 @@ function createConversation(msg: KgsMessage) { let convo: Conversation = { id: msg.channelId, messages: [], + draft: '', status: isTempId(msg.channelId) ? 'pending' : 'created' }; if (msg.callbackKey) { @@ -170,6 +171,17 @@ export function handleConversationMessage( }; return {...prevState, conversationsById}; } + } else if (msg.type === 'SAVE_CHAT_DRAFT') { + let convoId = msg.conversationId; + let conversationsById: Index = {...prevState.conversationsById}; + if (conversationsById[convoId]) { + conversationsById[convoId] = { + ...conversationsById[convoId], + draft: msg.text + }; + return {...prevState, conversationsById}; + } } + return prevState; } diff --git a/src/model/types.js b/src/model/types.js index 0f13e5a..7bdabbe 100644 --- a/src/model/types.js +++ b/src/model/types.js @@ -97,6 +97,7 @@ export type Conversation = { lastSeen?: number, unseenCount?: number, chatsDisabled?: boolean, + draft: string, messages: Array, callbackKey?: ?number, status: 'pending' | 'created' | 'userNotFound' | 'closed' diff --git a/src/ui/chat/ChatMessageBar.js b/src/ui/chat/ChatMessageBar.js index b1de4c6..541c5d5 100644 --- a/src/ui/chat/ChatMessageBar.js +++ b/src/ui/chat/ChatMessageBar.js @@ -6,19 +6,37 @@ import type {Conversation} from '../../model'; type Props = { conversation: ?Conversation, onSubmit: string => any; + onDraft?: string => any; +}; + +type State = { + draft: string }; export default class ChatMessageBar extends Component { props: Props; + state: State = {draft: ''}; _input: ?HTMLInputElement; componentDidUpdate(prevProps: Props) { let prevConvoId = prevProps.conversation && prevProps.conversation.id; let convoId = this.props.conversation && this.props.conversation.id; + let newDraft = this.props.conversation && this.props.conversation.draft; if (convoId !== prevConvoId && this._input && !isTouchDevice()) { this._input.focus(); + this.setState({draft: newDraft || ''}); + } + } + + _onChange = (event: SyntheticEvent) => { + let target: EventTarget = event.target; + if (target instanceof HTMLInputElement) { + this.setState({draft: target.value}); + if (this.props.onDraft) { + this.props.onDraft(target.value); + } } } @@ -39,6 +57,8 @@ export default class ChatMessageBar extends Component { className='ChatMessageBar-input' type='text' placeholder={placeholder} + value={this.state.draft} + onChange={this._onChange} autoFocus={!isTouchDevice()} /> diff --git a/src/ui/chat/ChatScreen.js b/src/ui/chat/ChatScreen.js index 4d21a7d..58769ab 100644 --- a/src/ui/chat/ChatScreen.js +++ b/src/ui/chat/ChatScreen.js @@ -400,6 +400,7 @@ export default class ChatScreen extends Component { usersByName={usersByName} onUserDetail={actions.onUserDetail} onSendChat={this._onSendChat} + onDraftChat={this._onDraftChat} setMessagesDivRef={this._setMessagesDivRef} setMessageInputRef={this._setMessageInputRef} /> : null} {activeConv && activeRoom ? @@ -420,6 +421,7 @@ export default class ChatScreen extends Component { onJoinGame={actions.onJoinGame} onSelectChallenge={actions.onSelectChallenge} onSendChat={this._onSendChat} + onDraftChat={this._onDraftChat} setMessagesDivRef={this._setMessagesDivRef} setMessageInputRef={this._setMessageInputRef} />) : null} @@ -456,6 +458,14 @@ export default class ChatScreen extends Component { this.props.actions.onSendChat(body, activeConversationId); } + _onDraftChat = (body: string) => { + let {activeConversationId} = this.state; + if (!activeConversationId) { + return; + } + this.props.actions.onDraftChat(body, activeConversationId); + } + _onShowList = () => { this.setState({showingList: true}, () => { window.scrollTo(0, 0); diff --git a/src/ui/chat/RoomChat.js b/src/ui/chat/RoomChat.js index 2a2696b..7ca49e8 100644 --- a/src/ui/chat/RoomChat.js +++ b/src/ui/chat/RoomChat.js @@ -29,6 +29,7 @@ export default class RoomChat extends Component { onSelectChallenge: number => any, onShowGames: (filter: GameFilter) => any, onSendChat: string => any, + onDraftChat: string => any, setMessagesDivRef: HTMLElement => any, setMessageInputRef: HTMLElement => any }; @@ -41,6 +42,7 @@ export default class RoomChat extends Component { usersByName, games, onUserDetail, + onDraftChat, onSendChat, onJoinGame, onSelectChallenge, @@ -81,6 +83,7 @@ export default class RoomChat extends Component {
{!isMobileScreen() ? diff --git a/src/ui/chat/UserChat.js b/src/ui/chat/UserChat.js index 1cb1096..81a2757 100644 --- a/src/ui/chat/UserChat.js +++ b/src/ui/chat/UserChat.js @@ -20,6 +20,7 @@ export default class UserChat extends Component { usersByName: Index, onUserDetail: string => any, onSendChat: string => any, + onDraftChat: string => any, setMessagesDivRef: HTMLElement => any, setMessageInputRef: HTMLElement => any }; @@ -32,6 +33,7 @@ export default class UserChat extends Component { usersByName, onUserDetail, onSendChat, + onDraftChat, setMessagesDivRef, setMessageInputRef } = this.props; @@ -68,6 +70,7 @@ export default class UserChat extends Component {
diff --git a/src/ui/game/GameScreen.js b/src/ui/game/GameScreen.js index 01ab379..d8e9759 100644 --- a/src/ui/game/GameScreen.js +++ b/src/ui/game/GameScreen.js @@ -225,9 +225,11 @@ export default class GameScreen extends Component { id: 0, messages: [], status: 'created', + draft: '', chatsDisabled: !game.tree }} - onSubmit={this._onChat} /> + onSubmit={this._onChat} + /> diff --git a/src/util/debounce.js b/src/util/debounce.js new file mode 100644 index 0000000..ffbd23d --- /dev/null +++ b/src/util/debounce.js @@ -0,0 +1,14 @@ +// @flow + +export function debounce(callback: Function, delay: number): Function { + let timeout: number; + return function() { + let context: Object = this; + let args: arguments = arguments; + let debounced: Function = () => { + callback.apply(context, args); + }; + clearTimeout(timeout); + timeout = setTimeout(debounced, delay); + }; +}