mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Message selection
This commit is contained in:
parent
548db7150d
commit
3030b028eb
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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}`,
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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')}
|
||||
>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue