move drop file logic to SessionConversation component

Having it in another component was causing issues like not being able to
scroll on the conversation list even if the component was not shown
pull/1387/head
Audric Ackermann 5 years ago
parent a7bdc93ca8
commit 700a93362e
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -25,6 +25,7 @@ import { Message } from '../../conversation/media-gallery/types/Message';
import { AttachmentType } from '../../../types/Attachment'; import { AttachmentType } from '../../../types/Attachment';
import { ToastUtils } from '../../../session/utils'; import { ToastUtils } from '../../../session/utils';
import * as MIME from '../../../types/MIME'; import * as MIME from '../../../types/MIME';
import { SessionFileDropzone } from './SessionFileDropzone';
interface State { interface State {
conversationKey: string; conversationKey: string;
@ -56,6 +57,7 @@ interface State {
infoViewState?: 'safetyNumber' | 'messageDetails'; infoViewState?: 'safetyNumber' | 'messageDetails';
stagedAttachments: Array<StagedAttachmentType>; stagedAttachments: Array<StagedAttachmentType>;
isDraggingFile: boolean;
// quoted message // quoted message
quotedMessageTimestamp?: number; quotedMessageTimestamp?: number;
@ -72,6 +74,8 @@ interface Props {
export class SessionConversation extends React.Component<Props, State> { export class SessionConversation extends React.Component<Props, State> {
private readonly compositionBoxRef: React.RefObject<HTMLDivElement>; private readonly compositionBoxRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<HTMLDivElement>;
private dragCounter: number;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
@ -102,8 +106,11 @@ export class SessionConversation extends React.Component<Props, State> {
showOptionsPane: false, showOptionsPane: false,
infoViewState: undefined, infoViewState: undefined,
stagedAttachments: [], stagedAttachments: [],
isDraggingFile: false,
}; };
this.compositionBoxRef = React.createRef(); this.compositionBoxRef = React.createRef();
this.messageContainerRef = React.createRef();
this.dragCounter = 0;
// Group settings panel // Group settings panel
this.toggleGroupSettingsPane = this.toggleGroupSettingsPane.bind(this); this.toggleGroupSettingsPane = this.toggleGroupSettingsPane.bind(this);
@ -138,6 +145,10 @@ export class SessionConversation extends React.Component<Props, State> {
this.addAttachments = this.addAttachments.bind(this); this.addAttachments = this.addAttachments.bind(this);
this.removeAttachment = this.removeAttachment.bind(this); this.removeAttachment = this.removeAttachment.bind(this);
this.onChoseAttachments = this.onChoseAttachments.bind(this); this.onChoseAttachments = this.onChoseAttachments.bind(this);
this.handleDragIn = this.handleDragIn.bind(this);
this.handleDragOut = this.handleDragOut.bind(this);
this.handleDrag = this.handleDrag.bind(this);
this.handleDrop = this.handleDrop.bind(this);
const conversationModel = window.ConversationController.getOrThrow( const conversationModel = window.ConversationController.getOrThrow(
this.state.conversationKey this.state.conversationKey
@ -161,12 +172,29 @@ export class SessionConversation extends React.Component<Props, State> {
this.setState({ initialFetchComplete: true }); this.setState({ initialFetchComplete: true });
} }
public componentWillUnmount() {
const div = this.messageContainerRef.current;
div?.removeEventListener('dragenter', this.handleDragIn);
div?.removeEventListener('dragleave', this.handleDragOut);
div?.removeEventListener('dragover', this.handleDrag);
div?.removeEventListener('drop', this.handleDrop);
}
public componentDidMount() { public componentDidMount() {
// Pause thread to wait for rendering to complete // Pause thread to wait for rendering to complete
setTimeout(() => { setTimeout(() => {
this.setState({ this.setState(
doneInitialScroll: true, {
}); doneInitialScroll: true,
},
() => {
const div = this.messageContainerRef.current;
div?.addEventListener('dragenter', this.handleDragIn);
div?.addEventListener('dragleave', this.handleDragOut);
div?.addEventListener('dragover', this.handleDrag);
div?.addEventListener('drop', this.handleDrop);
}
);
}, 100); }, 100);
} }
@ -200,7 +228,6 @@ export class SessionConversation extends React.Component<Props, State> {
const showSafetyNumber = this.state.infoViewState === 'safetyNumber'; const showSafetyNumber = this.state.infoViewState === 'safetyNumber';
const showMessageDetails = this.state.infoViewState === 'messageDetails'; const showMessageDetails = this.state.infoViewState === 'messageDetails';
const messagesListProps = this.getMessagesListProps();
return ( return (
<SessionTheme theme={this.props.theme}> <SessionTheme theme={this.props.theme}>
@ -238,11 +265,12 @@ export class SessionConversation extends React.Component<Props, State> {
{lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)} {lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)}
<div className="conversation-messages"> <div className="conversation-messages">
<SessionConversationMessagesList {...messagesListProps} /> <SessionConversationMessagesList {...this.getMessagesListProps()} />
{showRecordingView && ( {showRecordingView && (
<div className="conversation-messages__blocking-overlay" /> <div className="conversation-messages__blocking-overlay" />
)} )}
{this.state.isDraggingFile && <SessionFileDropzone />}
</div> </div>
{!isRss && ( {!isRss && (
@ -504,7 +532,7 @@ export class SessionConversation extends React.Component<Props, State> {
replyToMessage: this.replyToMessage, replyToMessage: this.replyToMessage,
doneInitialScroll: this.state.doneInitialScroll, doneInitialScroll: this.state.doneInitialScroll,
onClickAttachment: this.onClickAttachment, onClickAttachment: this.onClickAttachment,
handleFilesDropped: this.onChoseAttachments, messageContainerRef: this.messageContainerRef,
}; };
} }
@ -883,6 +911,7 @@ export class SessionConversation extends React.Component<Props, State> {
private addAttachments(newAttachments: Array<StagedAttachmentType>) { private addAttachments(newAttachments: Array<StagedAttachmentType>) {
const { stagedAttachments } = this.state; const { stagedAttachments } = this.state;
let newAttachmentsFiltered: Array<StagedAttachmentType> = [];
if (newAttachments?.length > 0) { if (newAttachments?.length > 0) {
if ( if (
newAttachments.some(a => a.isVoiceMessage) && newAttachments.some(a => a.isVoiceMessage) &&
@ -890,10 +919,14 @@ export class SessionConversation extends React.Component<Props, State> {
) { ) {
throw new Error('A voice note cannot be sent with other attachments'); throw new Error('A voice note cannot be sent with other attachments');
} }
// do not add already added attachments
newAttachmentsFiltered = newAttachments.filter(
a => !stagedAttachments.some(b => b.file.path === a.file.path)
);
} }
this.setState({ this.setState({
stagedAttachments: [...stagedAttachments, ...newAttachments], stagedAttachments: [...stagedAttachments, ...newAttachmentsFiltered],
}); });
} }
@ -1135,4 +1168,40 @@ export class SessionConversation extends React.Component<Props, State> {
]); ]);
} }
} }
private handleDrag(e: any) {
e.preventDefault();
e.stopPropagation();
}
private handleDragIn(e: any) {
e.preventDefault();
e.stopPropagation();
this.dragCounter++;
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
this.setState({ isDraggingFile: true });
}
}
private handleDragOut(e: any) {
e.preventDefault();
e.stopPropagation();
this.dragCounter--;
if (this.dragCounter === 0) {
this.setState({ isDraggingFile: false });
}
}
private handleDrop(e: DragEvent) {
e.preventDefault();
e.stopPropagation();
if (e?.dataTransfer?.files && e.dataTransfer.files.length > 0) {
void this.onChoseAttachments(e.dataTransfer.files);
e.dataTransfer.clearData();
this.dragCounter = 0;
this.setState({ isDraggingFile: false });
}
}
} }

@ -8,7 +8,6 @@ import { ResetSessionNotification } from '../../conversation/ResetSessionNotific
import { Constants } from '../../../session'; import { Constants } from '../../../session';
import _ from 'lodash'; import _ from 'lodash';
import { ConversationModel } from '../../../../js/models/conversations'; import { ConversationModel } from '../../../../js/models/conversations';
import { SessionFileDropzone } from './SessionFileDropzone';
interface State { interface State {
isScrolledToBottom: boolean; isScrolledToBottom: boolean;
@ -24,6 +23,7 @@ interface Props {
initialFetchComplete: boolean; initialFetchComplete: boolean;
conversationModel: ConversationModel; conversationModel: ConversationModel;
conversation: any; conversation: any;
messageContainerRef: React.RefObject<any>;
selectMessage: (messageId: string) => void; selectMessage: (messageId: string) => void;
getMessages: ( getMessages: (
numMessages: number, numMessages: number,
@ -31,7 +31,6 @@ interface Props {
) => Promise<{ previousTopMessage: string }>; ) => Promise<{ previousTopMessage: string }>;
replyToMessage: (messageId: number) => Promise<void>; replyToMessage: (messageId: number) => Promise<void>;
onClickAttachment: (attachment: any, message: any) => void; onClickAttachment: (attachment: any, message: any) => void;
handleFilesDropped: (droppedFiles: FileList) => void;
} }
export class SessionConversationMessagesList extends React.Component< export class SessionConversationMessagesList extends React.Component<
@ -39,7 +38,7 @@ export class SessionConversationMessagesList extends React.Component<
State State
> { > {
private readonly messagesEndRef: React.RefObject<HTMLDivElement>; private readonly messagesEndRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<HTMLDivElement>; private readonly messageContainerRef: React.RefObject<any>;
public constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
@ -55,7 +54,7 @@ export class SessionConversationMessagesList extends React.Component<
this.scrollToBottom = this.scrollToBottom.bind(this); this.scrollToBottom = this.scrollToBottom.bind(this);
this.messagesEndRef = React.createRef(); this.messagesEndRef = React.createRef();
this.messageContainerRef = React.createRef(); this.messageContainerRef = this.props.messageContainerRef;
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -100,24 +99,19 @@ export class SessionConversationMessagesList extends React.Component<
return <div className="messages-container__loading" />; return <div className="messages-container__loading" />;
} }
return ( return (
<> <div
<div className="messages-container"
className="messages-container" onScroll={this.handleScroll}
onScroll={this.handleScroll} ref={this.messageContainerRef}
ref={this.messageContainerRef} >
> {this.renderMessages(messages)}
{this.renderMessages(messages)} <div ref={this.messagesEndRef} />
<div ref={this.messagesEndRef} />
</div>
<SessionScrollButton <SessionScrollButton
show={showScrollButton} show={showScrollButton}
onClick={this.scrollToBottom} onClick={this.scrollToBottom}
/> />
<SessionFileDropzone </div>
handleDrop={this.props.handleFilesDropped}
handleWheel={this.handleScroll}
/>
</>
); );
} }
@ -237,7 +231,8 @@ export class SessionConversationMessagesList extends React.Component<
const { length } = messages; const { length } = messages;
const viewportBottom = const viewportBottom =
messageContainer?.clientHeight + messageContainer?.scrollTop || 0; (messageContainer?.clientHeight as number) +
(messageContainer?.scrollTop as number) || 0;
// Start with the most recent message, search backwards in time // Start with the most recent message, search backwards in time
let foundUnread = 0; let foundUnread = 0;

@ -1,119 +1,48 @@
import React, { Component } from 'react'; import React from 'react';
import styled from 'styled-components';
import { Flex } from '../Flex'; import { Flex } from '../Flex';
import { SessionIcon, SessionIconSize, SessionIconType } from '../icon'; import { SessionIcon, SessionIconSize, SessionIconType } from '../icon';
interface Props { // padding-inline-end: ${props => props.theme.common.margins.md};
handleDrop: (files: FileList) => void; // padding-inline-start: ${props => props.theme.common.margins.md};
}
const DropZoneContainer = styled.div`
interface State { display: inline-block;
dragging: boolean; position: absolute;
} width: 100%;
height: 100%;
export class SessionFileDropzone extends Component<Props, State> { pointer-events: none;
private readonly dropRef: React.RefObject<any>; `;
private dragCounter: number;
const DropZoneWithBorder = styled.div`
constructor(props: any) { border: dashed 4px ${props => props.theme.colors.accent};
super(props); background-color: ${props => props.theme.colors.clickableHovered};
this.state = { position: absolute;
dragging: false, top: 0;
}; bottom: 0;
left: 0;
this.dragCounter = 0; right: 0;
this.dropRef = React.createRef(); z-index: 20;
} opacity: 0.5;
pointer-events: none;
public handleDrag = (e: any) => { `;
e.preventDefault();
e.stopPropagation(); export const SessionFileDropzone = () => {
}; return (
<DropZoneContainer>
public handleDragIn = (e: any) => { <DropZoneWithBorder>
e.preventDefault(); <Flex
e.stopPropagation(); container={true}
this.dragCounter++; justifyContent="space-around"
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { height="100%"
this.setState({ dragging: true }); alignItems="center"
}
};
public handleDragOut = (e: any) => {
e.preventDefault();
e.stopPropagation();
this.dragCounter--;
if (this.dragCounter === 0) {
this.setState({ dragging: false });
}
};
public handleDrop = (e: any) => {
e.preventDefault();
e.stopPropagation();
this.setState({ dragging: false });
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
this.props.handleDrop(e.dataTransfer.files);
e.dataTransfer.clearData();
this.dragCounter = 0;
}
};
public componentDidMount() {
const div = this.dropRef.current;
div.addEventListener('dragenter', this.handleDragIn);
div.addEventListener('dragleave', this.handleDragOut);
div.addEventListener('dragover', this.handleDrag);
div.addEventListener('drop', this.handleDrop);
}
public componentWillUnmount() {
const div = this.dropRef.current;
div.removeEventListener('dragenter', this.handleDragIn);
div.removeEventListener('dragleave', this.handleDragOut);
div.removeEventListener('dragover', this.handleDrag);
div.removeEventListener('drop', this.handleDrop);
}
public render() {
return (
<div
style={{
display: 'inline-block',
position: 'absolute',
width: '100%',
height: '100%',
}}
ref={this.dropRef}
>
<div
style={{
border: 'dashed grey 4px',
backgroundColor: 'rgba(255,255,255,0.5)',
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: 100,
opacity: this.state.dragging ? 1.0 : 0,
transition: '0.25s',
}}
> >
<Flex <SessionIcon
container={true} iconSize={SessionIconSize.Max}
justifyContent="space-around" iconType={SessionIconType.CirclePlus}
height="100%" />
alignItems="center" </Flex>
> </DropZoneWithBorder>
<SessionIcon </DropZoneContainer>
iconSize={SessionIconSize.Max} );
iconType={SessionIconType.CirclePlus} };
/>
</Flex>
</div>
{this.props.children}
</div>
);
}
}

Loading…
Cancel
Save