Message selection

This commit is contained in:
Vincent 2020-02-28 11:48:21 +11:00
parent 548db7150d
commit 3030b028eb
8 changed files with 143 additions and 61 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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%;

View file

@ -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<string>;
onSetDisappearingMessages: (seconds: number) => void;
onDeleteMessages: () => void;
@ -349,7 +352,7 @@ export class ConversationHeader extends React.Component<Props> {
}
public renderSelectionOverlay() {
const { onDeleteSelectedMessages, onCloseOverlay, i18n } = this.props;
const { onDeleteSelectedMessages, onResetSession, i18n } = this.props;
return (
<div className="message-selection-overlay">
@ -357,7 +360,7 @@ export class ConversationHeader extends React.Component<Props> {
<SessionIconButton
iconType={SessionIconType.Exit}
iconSize={SessionIconSize.Medium}
onClick={onCloseOverlay}
onClick={onResetSession}
/>
</div>
@ -376,11 +379,11 @@ export class ConversationHeader extends React.Component<Props> {
public render() {
const { id } = this.props;
const triggerId = `conversation-${id}-${Date.now()}`;
const selectionMode = !!this.props.selectedMessages.length;
return (
<>
{this.renderSelectionOverlay()}
<div className="module-conversation-header">
<div className="module-conversation-header">
<div className="conversation-header--items-wrapper">
{this.renderBackButton()}
<div className="module-conversation-header__title-container">
<div className="module-conversation-header__title-flex">
@ -401,7 +404,9 @@ export class ConversationHeader extends React.Component<Props> {
{this.renderMenu(triggerId)}
</div>
</>
{ selectionMode && this.renderSelectionOverlay() }
</div>
);
}

View file

@ -176,7 +176,7 @@ export class FriendRequest extends React.Component<Props> {
const { direction } = this.props;
return (
<div className={'session-message-wrapper'}>
<div className={'session-message'}>
<div
className={classNames(
`module-message module-message--${direction}`,

View file

@ -1090,7 +1090,7 @@ export class Message extends React.PureComponent<Props, State> {
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<Props, State> {
id={id}
role="button"
onClick={() => {
const selection = window.getSelection();
if (selection && selection.type === 'Range') {
return;
}
this.props.onSelectMessage();
id && this.props.onSelectMessage(id);
}}
>
<ContextMenuTrigger id={rightClickTriggerId}>

View file

@ -52,7 +52,7 @@ export class TypingBubble extends React.Component<Props> {
const { i18n, color } = this.props;
return (
<div className="session-message-wrapper">
<div className="session-message">
<div
className={classNames('module-message', 'module-message--incoming')}
>

View file

@ -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<any, State> {
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<any, State> {
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 (
<div className="conversation-item">
<div
className={classNames('conversation-item', selectionMode && 'selection-mode')}
tabIndex={0}
onKeyUp={this.onKeyUp}
>
<div className="conversation-header">
{this.renderHeader()}
</div>
@ -231,12 +234,15 @@ export class SessionConversation extends React.Component<any, State> {
public renderMessage(messageProps: any, firstMessageOfSeries: boolean, quoteProps?: any) {
const selected = !! messageProps?.id
&& this.state.selectedMessages.includes(messageProps.id);
return (
<Message
i18n = {window.i18n}
text = {messageProps?.text}
direction = {messageProps?.direction}
selected = {selected}
timestamp = {messageProps?.timestamp}
attachments = {messageProps?.attachments}
authorAvatarPath = {messageProps?.authorAvatarPath}
@ -266,13 +272,12 @@ export class SessionConversation extends React.Component<any, State> {
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<any, State> {
// 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<any, State> {
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<any, State> {
),
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<any, State> {
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<any, State> {
};
};
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<any, State> {
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();
}
}
}
}