From 8e240d52186b105e2f7975102cfd9ccdef3303a7 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 18 Mar 2020 17:40:36 +1100 Subject: [PATCH] Sending progress --- stylesheets/_session_conversation.scss | 14 +- ts/components/session/SessionProgress.tsx | 41 +- .../conversation/SessionCompositionBox.tsx | 25 +- .../conversation/SessionConversation.tsx | 558 +++++++++--------- .../conversation/SessionGroupSettings.tsx | 6 +- 5 files changed, 320 insertions(+), 324 deletions(-) diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 7ba849eb5..2d2b1d0ad 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -68,6 +68,7 @@ $composition-container-height: 60px; &__content { display: flex; flex-direction: column; + width: 100%; outline: none; } @@ -76,7 +77,7 @@ $composition-container-height: 60px; height: 100%; right: 0vw; - transition: transform $session-transition-duration ease-in-out; + transition: transform 1.5 * $session-transition-duration ease-in-out; transform: translateX(100%); will-change: transform; @@ -285,16 +286,23 @@ $composition-container-height: 60px; .session-progress { position: relative; - background-color: rgba(30, 30, 30, 0.5); + z-index: 100; &__progress { - transition: opacity 0.15s; + transition: opacity 0.25s; + will-change: transform; + + width: 100%; position: absolute; left: 0px; font-size: 0px; height: 3px; background-color: $session-color-green; + + &.fade { + opacity: 0; + } } } diff --git a/ts/components/session/SessionProgress.tsx b/ts/components/session/SessionProgress.tsx index 063c72089..35b0872a4 100644 --- a/ts/components/session/SessionProgress.tsx +++ b/ts/components/session/SessionProgress.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import classNames from 'classnames'; interface Props { // Value ranges from 0 to 100 @@ -6,12 +7,12 @@ interface Props { // Optional. Load with initial value and have // it shoot to new value immediately prevValue?: number; + sendStatus: -1 | 0 | 1 | 2; visible: boolean; fadeOnComplete: boolean; } interface State { - value: number; visible: boolean; startFade: boolean; } @@ -24,42 +25,39 @@ export class SessionProgress extends React.PureComponent { constructor(props: any) { super(props); - const { visible, value, prevValue } = this.props; + const { visible } = this.props; this.state = { visible, startFade: false, - value: prevValue || value, }; } - public componentWillMount() { - setTimeout(() => { - this.setState({ - value: this.props.value, - }); - }, 20); - } - public render() { - const { startFade, value } = this.state; - const { prevValue } = this.props; + const { startFade } = this.state; + const { value, prevValue, sendStatus } = this.props; // Duration will be the decimal (in seconds) of // the percentage differnce, else 0.25s; // Minimum shift duration of 0.25s; - const shiftDuration = this.getShiftDuration(this.props.value, prevValue); + const shiftDuration = this.getShiftDuration(this.props.value, prevValue).toFixed(2); // 1. Width depends on progress. // 2. Opacity is the inverse of fade. // 3. Transition duration scales with the // distance it needs to travel + + // FIXME VINCE - globalise all JS color references + const sessionBrandColor = '#00f782'; + const sessionDangerAlt = '#ff4538'; + const successColor = sessionBrandColor; + const failureColor = sessionDangerAlt; + const backgroundColor = sendStatus === 2 ? failureColor : successColor; + const style = { - width: `${this.state.value}%`, - opacity: `${Number(!startFade)}`, - transition: `width ${shiftDuration.toFixed( - 2 - )}s cubic-bezier(0.25, 0.46, 0.45, 0.94)`, + 'background-color': backgroundColor, + transform: `translateX(-${100 - value}%)`, + transition: `transform ${shiftDuration}s cubic-bezier(0.25, 0.46, 0.45, 0.94)`, }; if (value >= 100) { @@ -68,7 +66,10 @@ export class SessionProgress extends React.PureComponent { return (
-
+
 
diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx index 96dc61c3e..ffdd947eb 100644 --- a/ts/components/session/conversation/SessionCompositionBox.tsx +++ b/ts/components/session/conversation/SessionCompositionBox.tsx @@ -15,8 +15,12 @@ import { SignalService } from '../../../../ts/protobuf'; interface Props { placeholder?: string; + sendMessage: any; - + onMessageSending: any; + onMessageSuccess: any; + onMessageFailure: any; + onLoadVoiceNoteView: any; onExitVoiceNoteView: any; } @@ -270,23 +274,30 @@ export class SessionCompositionBox extends React.Component { // Send message - const messageSuccess = this.props.sendMessage( + this.props.onMessageSending(); + + this.props.sendMessage( messagePlaintext, attachments, undefined, undefined, null, {}, - ); - - if (messageSuccess) { + ).then(() => { + // Message sending sucess + this.props.onMessageSuccess(); + // Empty attachments // Empty composition box this.setState({ message: '', attachments: [], }); - } + }).catch(() => { + // Message sending failed + this.props.onMessageFailure(); + }); + } private async sendVoiceMessage(audioBlob: Blob) { @@ -333,7 +344,7 @@ export class SessionCompositionBox extends React.Component { } window.pushToast({ - id: window.generateID(), + id: 'audioPermissionNeeded', title: window.i18n('audioPermissionNeededTitle'), description: window.i18n('audioPermissionNeededDescription'), type: 'info', diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index e8cf7fc6d..787ffef1d 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -16,8 +16,14 @@ import { SessionGroupSettings } from './SessionGroupSettings'; interface State { conversationKey: string; - sendingProgess: number; - prevSendingProgess: number; + sendingProgress: number; + prevSendingProgress: number; + // Sending failed: -1 + // Not send yet: 0 + // Sending message: 1 + // Sending success: 2 + sendingProgressStatus: -1 | 0 | 1 | 2; + unreadCount: number; messages: Array; selectedMessages: Array; @@ -36,15 +42,16 @@ export class SessionConversation extends React.Component { constructor(props: any) { super(props); - console.log(`[conv] Props:`, props); - const conversationKey = this.props.conversations.selectedConversation; const conversation = this.props.conversations.conversationLookup[conversationKey]; const unreadCount = conversation.unreadCount; + console.log(`[conv] Conversation:`, conversation); + this.state = { - sendingProgess: 0, - prevSendingProgess: 0, + sendingProgress: 0, + prevSendingProgress: 0, + sendingProgressStatus: 0, conversationKey, unreadCount, messages: [], @@ -54,7 +61,7 @@ export class SessionConversation extends React.Component { displayScrollToBottomButton: false, messageFetchTimestamp: 0, showRecordingView: false, - showOptionsPane: true, + showOptionsPane: false, }; this.handleScroll = this.handleScroll.bind(this); @@ -65,21 +72,34 @@ export class SessionConversation extends React.Component { this.renderTimerNotification = this.renderTimerNotification.bind(this); this.renderFriendRequest = this.renderFriendRequest.bind(this); - // Group options panels - this.toggleOptionsPane = this.toggleOptionsPane.bind(this); + // Group settings panel + this.toggleGroupSettingsPane = this.toggleGroupSettingsPane.bind(this); + this.getGroupSettingsProps = this.getGroupSettingsProps.bind(this); - // Recording View render and unrender + // Recording view this.onLoadVoiceNoteView = this.onLoadVoiceNoteView.bind(this); this.onExitVoiceNoteView = this.onExitVoiceNoteView.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); + // Messages this.selectMessage = this.selectMessage.bind(this); this.resetSelection = this.resetSelection.bind(this); + this.updateSendingProgres = this.updateSendingProgres.bind(this); + this.onMessageSending = this.onMessageSending.bind(this); + this.onMessageSuccess = this.onMessageSuccess.bind(this); + this.onMessageFailure = this.onMessageFailure.bind(this); this.messagesEndRef = React.createRef(); this.messageContainerRef = React.createRef(); + + // Keyboard navigation + this.onKeyDown = this.onKeyDown.bind(this); + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~ LIFECYCLES ~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + public componentDidMount() { this.getMessages().then(() => { // Pause thread to wait for rendering to complete @@ -92,10 +112,6 @@ export class SessionConversation extends React.Component { }); }, 100); }); - - - //FIXME VINCE - // Only now should you renderGroupOptionsPane } public componentDidUpdate(){ @@ -117,9 +133,10 @@ export class SessionConversation extends React.Component { } } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~ RENDER METHODS ~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public render() { - console.log(`[vince][info] Props`, this.props); - const { messages, conversationKey, doneInitialScroll, showRecordingView, showOptionsPane } = this.state; const loading = !doneInitialScroll || messages.length === 0; const selectionMode = !!this.state.selectedMessages.length; @@ -130,6 +147,9 @@ export class SessionConversation extends React.Component { const sendMessageFn = conversationModel.sendMessage.bind(conversationModel); + const shouldRenderGroupSettings = !conversationModel.isPrivate() && !conversationModel.isRss() + const groupSettingsProps = this.getGroupSettingsProps(); + return ( <>
{
@@ -170,6 +191,9 @@ export class SessionConversation extends React.Component { { !isRss && ( @@ -177,36 +201,11 @@ export class SessionConversation extends React.Component {
-
- {/* Don't render this to the DOM unless it needs to be rendered */} - {/* { showOptionsPane && ( */} - ({ - name: item.getName(), - value: item.get('seconds'), - })) - } - isPublic={conversation.isPublic} - isAdmin={conversation.isAdmin} - amMod={conversation.amMod} - onGoBack={this.toggleOptionsPane} - onInviteFriends={() => null} - onLeaveGroup={() => null} - onUpdateGroupName={() => null} - onUpdateGroupMembers={() => null} - onShowLightBox={(options: any) => null} - onSetDisappearingMessages={(seconds: number) => null} - /> - {/* )} */} - -
- + {shouldRenderGroupSettings && ( +
+ +
+ )} ); } @@ -301,89 +300,39 @@ export class SessionConversation extends React.Component { const selected = !! messageProps?.id && this.state.selectedMessages.includes(messageProps.id); + messageProps.i18n = window.i18n; + messageProps.selected = selected; + messageProps.firstMessageOfSeries = firstMessageOfSeries; + messageProps.onSelectMessage = (messageId: string) => this.selectMessage(messageId); + messageProps.quote = quoteProps || undefined; + return ( - this.selectMessage(messageId)} - onSelectMessageUnchecked = {messageProps?.onSelectMessageUnchecked} - onShowDetail = {messageProps?.onShowDetail} - onShowUserDetails = {messageProps?.onShowUserDetails} - previews = {messageProps?.previews} - quote = {quoteProps || undefined} - senderIsModerator = {messageProps?.senderIsModerator} - status = {messageProps?.status} - textPending = {messageProps?.textPending} - /> + ); } public renderTimerNotification(timerProps: any) { + timerProps.i18n = window.i18n; + return ( - + ); } public renderFriendRequest(friendRequestProps: any){ + friendRequestProps.i18n = window.i18n; + return ( - + ); } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~ GETTER METHODS ~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + public async getMessages(numMessages?: number, fetchInterval = window.CONSTANTS.MESSAGE_FETCH_INTERVAL, loopback = false){ const { conversationKey, messageFetchTimestamp } = this.state; const timestamp = getTimestamp(); @@ -441,161 +390,10 @@ export class SessionConversation extends React.Component { return { newTopMessage, previousTopMessage }; } - public updateReadMessages() { - const { isScrolledToBottom, messages, conversationKey } = this.state; - let unread; - - if (!messages || messages.length === 0) { - return; - } - - console.log(`[unread] isScrollToBottom:`, isScrolledToBottom); - - if (isScrolledToBottom) { - unread = messages[messages.length - 1]; - } else { - console.log(`[unread] Calling findNewestVisibleUnread`) - unread = this.findNewestVisibleUnread(); - } - - //console.log(`[unread] Messages:`, messages); - console.log(`[unread] Updating read messages: `, unread); - - if (unread) { - const model = window.ConversationController.get(conversationKey); - model.markRead(unread.attributes.received_at); - } - } - - public findNewestVisibleUnread() { - const messageContainer = this.messageContainerRef.current; - if (!messageContainer) return null; - - const { messages, unreadCount } = this.state; - const { length } = messages; - - const viewportBottom = (messageContainer?.clientHeight + messageContainer?.scrollTop) || 0; - - console.log(`[findNew] messages`, messages); - - // Start with the most recent message, search backwards in time - let foundUnread = 0; - for (let i = length - 1; i >= 0; i -= 1) { - // Search the latest 30, then stop if we believe we've covered all known - // unread messages. The unread should be relatively recent. - // Why? local notifications can be unread but won't be reflected the - // conversation's unread count. - if (i > 30 && foundUnread >= unreadCount) { - console.log(`[findNew] foundUnread > unreadCount`); - return null; - } - - const message = messages[i]; - - if (!message.attributes.unread) { - // eslint-disable-next-line no-continue - console.log(`[findNew] no message.attributes`); - continue; - } - - foundUnread += 1; - - const el = document.getElementById(`${message.id}`); - - if (!el) { - // eslint-disable-next-line no-continue - console.log(`[findNew] no message.id`); - continue; - } - - const top = el.offsetTop; - - // If the bottom fits on screen, we'll call it visible. Even if the - // message is really tall. - const height = el.offsetHeight; - const bottom = top + height; - - // We're fully below the viewport, continue searching up. - if (top > viewportBottom) { - // eslint-disable-next-line no-continue - console.log(`[findNew] top > viewportBottom`); - continue; - } - - if (bottom <= viewportBottom) { - console.log(`[findNew] bottom <= viewportBottom`); - console.log(`[findNew] Message set`); - return message; - } - - // Continue searching up. - } - - return null; - } - - public toggleOptionsPane() { - const { showOptionsPane } = this.state; - this.setState({ showOptionsPane: !showOptionsPane }); - } - - public async handleScroll() { - const messageContainer = this.messageContainerRef.current; - if (!messageContainer) return; - - const isScrolledToBottom = messageContainer.scrollHeight - messageContainer.clientHeight <= messageContainer.scrollTop + 1; - - // Mark messages read - console.log(`[unread] Updating messages from handleScroll`); - this.updateReadMessages(); - - // Pin scroll to bottom on new message, unless user has scrolled up - if (this.state.isScrolledToBottom !== isScrolledToBottom){ - this.setState({ isScrolledToBottom }); - } - - // Fetch more messages when nearing the top of the message list - const shouldFetchMoreMessages = messageContainer.scrollTop <= window.CONSTANTS.MESSAGE_CONTAINER_BUFFER_OFFSET_PX; - - if (shouldFetchMoreMessages){ - const numMessages = this.state.messages.length + window.CONSTANTS.DEFAULT_MESSAGE_FETCH_COUNT; - - // Prevent grabbing messags with scroll more frequently than once per 5s. - const messageFetchInterval = 2; - const previousTopMessage = (await this.getMessages(numMessages, messageFetchInterval, true))?.previousTopMessage; - previousTopMessage && this.scrollToMessage(previousTopMessage); - } - } - - public scrollToUnread() { - const { messages, unreadCount } = this.state; - const message = messages[(messages.length - 1) - unreadCount]; - - if(message) this.scrollToMessage(message.id); - } - - public scrollToMessage(messageId: string) { - const topUnreadMessage = document.getElementById(messageId); - topUnreadMessage?.scrollIntoView(); - } - - public scrollToBottom() { - // FIXME VINCE: Smooth scrolling that isn't slow@! - // this.messagesEndRef.current?.scrollIntoView( - // { behavior: firstLoad ? 'auto' : 'smooth' } - // ); - - const messageContainer = this.messageContainerRef.current; - if (!messageContainer) return; - messageContainer.scrollTop = messageContainer.scrollHeight - messageContainer.clientHeight; - } - public getHeaderProps() { const {conversationKey} = this.state; const conversation = window.getConversationByKey(conversationKey); - console.log(`[header] Conversation`, conversation); - const expireTimer = conversation.get('expireTimer'); const expirationSettingName = expireTimer ? window.Whisper.ExpirationTimerOptions.getName(expireTimer || 0) @@ -707,30 +505,17 @@ export class SessionConversation extends React.Component { userPubKey: pubkey, }); } else if (!conversation.isRss()) { - this.toggleOptionsPane(); + this.toggleGroupSettingsPane(); } }, }; }; - - public selectMessage(messageId: string) { - const selectedMessages = this.state.selectedMessages.includes(messageId) - // Add to array if not selected. Else remove. - ? this.state.selectedMessages.filter(id => id !== messageId) - : [...this.state.selectedMessages, messageId]; - - this.setState({ selectedMessages }, - () => console.log(`[vince] SelectedMessages: `, this.state.selectedMessages) - ); - } - - public resetSelection(){ - this.setState({selectedMessages: []}); - } - + public getGroupSettingsProps() { - const {conversationKey} = this.state; - const conversation = window.getConversationByKey[conversationKey]; + const { conversationKey } = this.state; + const conversation = window.getConversationByKey(conversationKey); + + console.log(`[settings] Conversation:`, conversation); const ourPK = window.textsecure.storage.user.getNumber(); const members = conversation.get('members') || []; @@ -738,6 +523,7 @@ export class SessionConversation extends React.Component { return { id: conversation.id, name: conversation.getName(), + memberCount: members.length, phoneNumber: conversation.getNumber(), profileName: conversation.getProfileName(), color: conversation.getColor(), @@ -746,7 +532,6 @@ export class SessionConversation extends React.Component { isPublic: conversation.isPublic(), isAdmin: conversation.get('groupAdmins').includes(ourPK), isRss: conversation.isRss(), - memberCount: members.length, timerOptions: window.Whisper.ExpirationTimerOptions.map((item: any) => ({ name: item.getName(), @@ -757,7 +542,7 @@ export class SessionConversation extends React.Component { conversation.setDisappearingMessages(seconds), onGoBack: () => { - conversation.hideConversationRight(); + this.toggleGroupSettingsPane(); }, onUpdateGroupName: () => { @@ -780,6 +565,196 @@ export class SessionConversation extends React.Component { }; }; + public toggleGroupSettingsPane() { + const { showOptionsPane } = this.state; + this.setState({ showOptionsPane: !showOptionsPane }); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~ MESSAGE HANDLING ~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + public updateSendingProgres(value: number, status: -1 | 0 | 1 | 2) { + // If you're sending a new message, reset previous value to zero + const prevSendingProgress = status === 1 ? 0 : this.state.sendingProgress; + + this.setState({ + sendingProgress: value, + prevSendingProgress, + sendingProgressStatus: status, + }); + } + + public onMessageSending() { + // Set sending state to random between 10% and 50% to show message sending + const minInitVal = 10; + const maxInitVal = 50; + const initialValue = minInitVal + (maxInitVal - minInitVal) * Math.random(); + + console.log(`[sending] Message Sending`); + this.updateSendingProgres(initialValue, 1); + } + + public onMessageSuccess(){ + console.log(`[sending] Message Sent`); + this.updateSendingProgres(100, 2); + } + + public onMessageFailure(){ + console.log(`[sending] Message Failure`); + this.updateSendingProgres(100, -1); + } + + public updateReadMessages() { + const { isScrolledToBottom, messages, conversationKey } = this.state; + let unread; + + if (!messages || messages.length === 0) { + return; + } + + if (isScrolledToBottom) { + unread = messages[messages.length - 1]; + } else { + unread = this.findNewestVisibleUnread(); + } + + if (unread) { + const model = window.ConversationController.get(conversationKey); + model.markRead(unread.attributes.received_at); + } + } + + public findNewestVisibleUnread() { + const messageContainer = this.messageContainerRef.current; + if (!messageContainer) return null; + + const { messages, unreadCount } = this.state; + const { length } = messages; + + const viewportBottom = (messageContainer?.clientHeight + messageContainer?.scrollTop) || 0; + + // Start with the most recent message, search backwards in time + let foundUnread = 0; + for (let i = length - 1; i >= 0; i -= 1) { + // Search the latest 30, then stop if we believe we've covered all known + // unread messages. The unread should be relatively recent. + // Why? local notifications can be unread but won't be reflected the + // conversation's unread count. + if (i > 30 && foundUnread >= unreadCount) { + return null; + } + + const message = messages[i]; + + if (!message.attributes.unread) { + // eslint-disable-next-line no-continue + continue; + } + + foundUnread += 1; + + const el = document.getElementById(`${message.id}`); + + if (!el) { + // eslint-disable-next-line no-continue + continue; + } + + const top = el.offsetTop; + + // If the bottom fits on screen, we'll call it visible. Even if the + // message is really tall. + const height = el.offsetHeight; + const bottom = top + height; + + // We're fully below the viewport, continue searching up. + if (top > viewportBottom) { + // eslint-disable-next-line no-continue + continue; + } + + if (bottom <= viewportBottom) { + return message; + } + + // Continue searching up. + } + + return null; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~ SCROLLING METHODS ~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + public async handleScroll() { + const messageContainer = this.messageContainerRef.current; + if (!messageContainer) return; + + const isScrolledToBottom = messageContainer.scrollHeight - messageContainer.clientHeight <= messageContainer.scrollTop + 1; + + // Mark messages read + this.updateReadMessages(); + + // Pin scroll to bottom on new message, unless user has scrolled up + if (this.state.isScrolledToBottom !== isScrolledToBottom){ + this.setState({ isScrolledToBottom }); + } + + // Fetch more messages when nearing the top of the message list + const shouldFetchMoreMessages = messageContainer.scrollTop <= window.CONSTANTS.MESSAGE_CONTAINER_BUFFER_OFFSET_PX; + + if (shouldFetchMoreMessages){ + const numMessages = this.state.messages.length + window.CONSTANTS.DEFAULT_MESSAGE_FETCH_COUNT; + + // Prevent grabbing messags with scroll more frequently than once per 5s. + const messageFetchInterval = 2; + const previousTopMessage = (await this.getMessages(numMessages, messageFetchInterval, true))?.previousTopMessage; + previousTopMessage && this.scrollToMessage(previousTopMessage); + } + } + + public scrollToUnread() { + const { messages, unreadCount } = this.state; + const message = messages[(messages.length - 1) - unreadCount]; + + if(message) this.scrollToMessage(message.id); + } + + public scrollToMessage(messageId: string) { + const topUnreadMessage = document.getElementById(messageId); + topUnreadMessage?.scrollIntoView(); + } + + public scrollToBottom() { + // FIXME VINCE: Smooth scrolling that isn't slow@! + // this.messagesEndRef.current?.scrollIntoView( + // { behavior: firstLoad ? 'auto' : 'smooth' } + // ); + + const messageContainer = this.messageContainerRef.current; + if (!messageContainer) return; + messageContainer.scrollTop = messageContainer.scrollHeight - messageContainer.clientHeight; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~ MESSAGE SELECTION ~~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + public selectMessage(messageId: string) { + const selectedMessages = this.state.selectedMessages.includes(messageId) + // Add to array if not selected. Else remove. + ? this.state.selectedMessages.filter(id => id !== messageId) + : [...this.state.selectedMessages, messageId]; + + this.setState({ selectedMessages }); + } + + public resetSelection(){ + this.setState({selectedMessages: []}); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~~ MICROPHONE METHODS ~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private onLoadVoiceNoteView() { this.setState({ showRecordingView: true, @@ -791,10 +766,11 @@ export class SessionConversation extends React.Component { this.setState({ showRecordingView: false, }); - - console.log(`[vince] Stopped recording entirely`); } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~~~~~~~~ KEYBOARD NAVIGATION ~~~~~~~~~~~~ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private onKeyDown(event: any) { const messageContainer = this.messageContainerRef.current; if (!messageContainer) return; diff --git a/ts/components/session/conversation/SessionGroupSettings.tsx b/ts/components/session/conversation/SessionGroupSettings.tsx index d3cb6e70f..9f4bdf262 100644 --- a/ts/components/session/conversation/SessionGroupSettings.tsx +++ b/ts/components/session/conversation/SessionGroupSettings.tsx @@ -15,12 +15,12 @@ interface Props { id: string; name: string; memberCount: number; - description: string; + description?: string; avatarPath: string; timerOptions: Array; isPublic: boolean; - isAdmin: boolean; - amMod: boolean; + isAdmin?: boolean; + amMod?: boolean; onGoBack: () => void; onInviteFriends: () => void;