diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 17f36e0a5..08dad702f 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -169,14 +169,14 @@ opacity: 1; } -.session-message-wrapper { +.session-message { .react-contextmenu-wrapper { display: inline-flex; width: 100%; } } -.session-message-wrapper { +.session-message { padding-left: 16px; padding-right: 16px; } @@ -186,7 +186,7 @@ padding-right: 10px; } -.session-message-wrapper { +.session-message { display: flow-root; padding-bottom: 4px; padding-top: 4px; diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 0ae16447d..f264053b8 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -588,32 +588,7 @@ label { margin-bottom: 6px; } } -.message-selection-overlay { - display: none; - position: absolute; - left: 0px; - right: 0px; - margin: 0px $session-margin-lg; - align-items: center; - justify-content: space-between; - height: $main-view-header-height; - - .close-button { - float: left; - } - .session-button.default.danger { - display: flex; - width: 80px; - } -} -.message-selection-overlay div[role='button'] { - display: inline-block; -} - -.message-selection-overlay .button-group { - float: right; -} .hidden { display: none; diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 283bf78ea..a37f64df6 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -1,6 +1,8 @@ $composition-container-height: 60px; + + @keyframes fadein { from { opacity: 0; @@ -10,14 +12,93 @@ $composition-container-height: 60px; } } +@keyframes toShadow { + from { + opacity: 1; + } + to { + opacity: 0.25; + } +} +@keyframes fromShadow { + from { + opacity: 0.25; + } + to { + opacity: 1; + } +} + .conversation-item { display: flex; flex-grow: 1; flex-direction: column; height: 100%; + outline: none; + + .selection-mode { + .messages-container > *:not(.message-selected) { + animation: toShadow $session-transition-duration; + opacity: 0.25; + } + + .conversation-header{ + .conversation-header{ + &--items-wrapper{ + .session-icon { + opacity: 0; + } + + user-select: none; + pointer-events: none; + opacity: 0.25; + } + } + } + } +} + + +.conversation-header { + &--items-wrapper{ + display: flex; + flex-grow: 1; + align-items: center; + justify-content: center; + } + + .message-selection-overlay { + position: absolute; + display: flex; + left: 0px; + right: 0px; + margin: 0px $session-margin-md; + align-items: center; + justify-content: space-between; + height: $main-view-header-height; + + .close-button { + float: left; + } + + .session-button.default.danger { + display: flex; + width: 80px; + } + } + .message-selection-overlay div[role='button'] { + display: inline-block; + } + + .message-selection-overlay .button-group { + float: right; + } } + + + .session-conversation-wrapper { position: absolute; width: 100%; diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index ad5a59e56..0528435c1 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -61,7 +61,10 @@ interface Props { isFriendRequestPending: boolean; isOnline?: boolean; - selectedMessages: any; + // We don't pass this as a bool, because in future we + // want to forward messages from Header and will need + // the message ID. + selectedMessages: Array; onSetDisappearingMessages: (seconds: number) => void; onDeleteMessages: () => void; @@ -349,7 +352,7 @@ export class ConversationHeader extends React.Component { } public renderSelectionOverlay() { - const { onDeleteSelectedMessages, onCloseOverlay, i18n } = this.props; + const { onDeleteSelectedMessages, onResetSession, i18n } = this.props; return (
@@ -357,7 +360,7 @@ export class ConversationHeader extends React.Component {
@@ -376,11 +379,11 @@ export class ConversationHeader extends React.Component { public render() { const { id } = this.props; const triggerId = `conversation-${id}-${Date.now()}`; + const selectionMode = !!this.props.selectedMessages.length; return ( - <> - {this.renderSelectionOverlay()} -
+
+
{this.renderBackButton()}
@@ -401,7 +404,9 @@ export class ConversationHeader extends React.Component { {this.renderMenu(triggerId)}
- + + { selectionMode && this.renderSelectionOverlay() } +
); } diff --git a/ts/components/conversation/FriendRequest.tsx b/ts/components/conversation/FriendRequest.tsx index 7998ec859..04c75e355 100644 --- a/ts/components/conversation/FriendRequest.tsx +++ b/ts/components/conversation/FriendRequest.tsx @@ -176,7 +176,7 @@ export class FriendRequest extends React.Component { const { direction } = this.props; return ( -
+
{ const isIncoming = direction === 'incoming'; const shouldHightlight = mentionMe && isIncoming && this.props.isPublic; - const divClasses = ['session-message-wrapper']; + const divClasses = ['session-message']; if (shouldHightlight) { //divClasses.push('message-highlighted'); @@ -1111,11 +1111,7 @@ export class Message extends React.PureComponent { id={id} role="button" onClick={() => { - const selection = window.getSelection(); - if (selection && selection.type === 'Range') { - return; - } - this.props.onSelectMessage(); + id && this.props.onSelectMessage(id); }} > diff --git a/ts/components/conversation/TypingBubble.tsx b/ts/components/conversation/TypingBubble.tsx index 18ea4a759..7df38332a 100644 --- a/ts/components/conversation/TypingBubble.tsx +++ b/ts/components/conversation/TypingBubble.tsx @@ -52,7 +52,7 @@ export class TypingBubble extends React.Component { const { i18n, color } = this.props; return ( -
+
diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index 09302c513..2ddf44012 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import classNames from 'classnames'; import { ConversationHeader } from '../../conversation/ConversationHeader'; import { SessionCompositionBox } from './SessionCompositionBox'; @@ -11,12 +12,6 @@ import { TimerNotification } from '../../conversation/TimerNotification'; import { SessionScrollButton } from '../SessionScrollButton'; -// interface Props { -// getHeaderProps: any; -// conversationKey: any; -// } - - interface State { sendingProgess: number; prevSendingProgess: number; @@ -61,8 +56,11 @@ export class SessionConversation extends React.Component { this.renderTimerNotification = this.renderTimerNotification.bind(this); this.renderFriendRequest = this.renderFriendRequest.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); this.onStartedRecording = this.onStartedRecording.bind(this); this.onStoppedRecording = this.onStoppedRecording.bind(this); + this.selectMessage = this.selectMessage.bind(this); + this.resetSelection = this.resetSelection.bind(this); this.messagesEndRef = React.createRef(); } @@ -103,13 +101,18 @@ export class SessionConversation extends React.Component { const { messages, conversationKey, doneInitialScroll, isRecording } = this.state; const loading = !doneInitialScroll || messages.length === 0; + const selectionMode = !!this.state.selectedMessages.length; const conversation = this.props.conversations.conversationLookup[conversationKey]; const conversationModel = window.getConversationByKey(conversationKey); const isRss = conversation.isRss; return ( -
+
{this.renderHeader()}
@@ -231,12 +234,15 @@ export class SessionConversation extends React.Component { public renderMessage(messageProps: any, firstMessageOfSeries: boolean, quoteProps?: any) { + const selected = !! messageProps?.id + && this.state.selectedMessages.includes(messageProps.id); return ( { onDownload = {messageProps?.onDownload} onReply = {messageProps?.onReply} onRetrySend = {messageProps?.onRetrySend} - onSelectMessage = {messageId => this.onSelectMessage(messageId)} + onSelectMessage = {messageId => this.selectMessage(messageId)} onSelectMessageUnchecked = {messageProps?.onSelectMessageUnchecked} onShowDetail = {messageProps?.onShowDetail} onShowUserDetails = {messageProps?.onShowUserDetails} previews = {messageProps?.previews} quote = {quoteProps || undefined} - selected = {messageProps?.selected} senderIsModerator = {messageProps?.senderIsModerator} status = {messageProps?.status} textPending = {messageProps?.textPending} @@ -378,7 +383,7 @@ export class SessionConversation extends React.Component { // FIXME VINCE: Update unread count // In models/conversations - // Update unread count by geting all divs of .session-message-wrapper + // Update unread count by geting all divs of .session-message // which are currently in view. // Pin scroll to bottom on new message, unless user has scrolled up @@ -403,7 +408,7 @@ export class SessionConversation extends React.Component { const { messages, unreadCount } = this.state; const message = messages[(messages.length - 1) - unreadCount]; - message.id && this.scrollToMessage(message.id); + message && this.scrollToMessage(message.id); } public scrollToMessage(messageId: string) { @@ -455,7 +460,7 @@ export class SessionConversation extends React.Component { ), members, subscriberCount: conversation.get('subscriberCount'), - selectedMessages: conversation.selectedMessages, + selectedMessages: this.state.selectedMessages, expirationSettingName, showBackButton: Boolean(conversation.panels && conversation.panels.length), timerOptions: window.Whisper.ExpirationTimerOptions.map((item: any) => ({ @@ -470,7 +475,7 @@ export class SessionConversation extends React.Component { onDeleteSelectedMessages: () => conversation.deleteSelectedMessages(), onCloseOverlay: () => conversation.resetMessageSelection(), onDeleteContact: () => conversation.deleteContact(), - onResetSession: () => conversation.endSession(), + onResetSession: () => this.resetSelection(), // These are view only and don't update the Conversation model, so they // need a manual update call. @@ -539,12 +544,19 @@ export class SessionConversation extends React.Component { }; }; - public onSelectMessage(messageId: string) { - const selectedMessages = !this.state.selectedMessages.includes(messageId) - ? [...this.state.selectedMessages, messageId] : []; + 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]; - selectedMessages && this.setState({ selectedMessages }); - console.log(`[vince] SelectedMessages: `, selectedMessages); + this.setState({ selectedMessages }, + () => console.log(`[vince] SelectedMessages: `, this.state.selectedMessages) + ); + } + + public resetSelection(){ + this.setState({selectedMessages: []}); } public getGroupSettingsProps() { @@ -610,5 +622,18 @@ export class SessionConversation extends React.Component { isRecording: false, }) } + + private onKeyUp(event: any) { + const selectionMode = !!this.state.selectedMessages.length; + console.log(`[vince][key] event: `, event); + + console.log(`[vince][key] key: `, event.key); + console.log(`[vince][key] key: `, event.keyCode); + if (event.key === 'Escape') { + if (selectionMode){ + this.resetSelection(); + } + } + } }