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 { ToastUtils } from '../../../session/utils';
import * as MIME from '../../../types/MIME';
import { SessionFileDropzone } from './SessionFileDropzone';
interface State {
conversationKey: string;
@ -56,6 +57,7 @@ interface State {
infoViewState?: 'safetyNumber' | 'messageDetails';
stagedAttachments: Array<StagedAttachmentType>;
isDraggingFile: boolean;
// quoted message
quotedMessageTimestamp?: number;
@ -72,6 +74,8 @@ interface Props {
export class SessionConversation extends React.Component<Props, State> {
private readonly compositionBoxRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<HTMLDivElement>;
private dragCounter: number;
constructor(props: any) {
super(props);
@ -102,8 +106,11 @@ export class SessionConversation extends React.Component<Props, State> {
showOptionsPane: false,
infoViewState: undefined,
stagedAttachments: [],
isDraggingFile: false,
};
this.compositionBoxRef = React.createRef();
this.messageContainerRef = React.createRef();
this.dragCounter = 0;
// Group settings panel
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.removeAttachment = this.removeAttachment.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(
this.state.conversationKey
@ -161,12 +172,29 @@ export class SessionConversation extends React.Component<Props, State> {
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() {
// Pause thread to wait for rendering to complete
setTimeout(() => {
this.setState({
doneInitialScroll: true,
});
this.setState(
{
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);
}
@ -200,7 +228,6 @@ export class SessionConversation extends React.Component<Props, State> {
const showSafetyNumber = this.state.infoViewState === 'safetyNumber';
const showMessageDetails = this.state.infoViewState === 'messageDetails';
const messagesListProps = this.getMessagesListProps();
return (
<SessionTheme theme={this.props.theme}>
@ -238,11 +265,12 @@ export class SessionConversation extends React.Component<Props, State> {
{lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)}
<div className="conversation-messages">
<SessionConversationMessagesList {...messagesListProps} />
<SessionConversationMessagesList {...this.getMessagesListProps()} />
{showRecordingView && (
<div className="conversation-messages__blocking-overlay" />
)}
{this.state.isDraggingFile && <SessionFileDropzone />}
</div>
{!isRss && (
@ -504,7 +532,7 @@ export class SessionConversation extends React.Component<Props, State> {
replyToMessage: this.replyToMessage,
doneInitialScroll: this.state.doneInitialScroll,
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>) {
const { stagedAttachments } = this.state;
let newAttachmentsFiltered: Array<StagedAttachmentType> = [];
if (newAttachments?.length > 0) {
if (
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');
}
// do not add already added attachments
newAttachmentsFiltered = newAttachments.filter(
a => !stagedAttachments.some(b => b.file.path === a.file.path)
);
}
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 _ from 'lodash';
import { ConversationModel } from '../../../../js/models/conversations';
import { SessionFileDropzone } from './SessionFileDropzone';
interface State {
isScrolledToBottom: boolean;
@ -24,6 +23,7 @@ interface Props {
initialFetchComplete: boolean;
conversationModel: ConversationModel;
conversation: any;
messageContainerRef: React.RefObject<any>;
selectMessage: (messageId: string) => void;
getMessages: (
numMessages: number,
@ -31,7 +31,6 @@ interface Props {
) => Promise<{ previousTopMessage: string }>;
replyToMessage: (messageId: number) => Promise<void>;
onClickAttachment: (attachment: any, message: any) => void;
handleFilesDropped: (droppedFiles: FileList) => void;
}
export class SessionConversationMessagesList extends React.Component<
@ -39,7 +38,7 @@ export class SessionConversationMessagesList extends React.Component<
State
> {
private readonly messagesEndRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<any>;
public constructor(props: Props) {
super(props);
@ -55,7 +54,7 @@ export class SessionConversationMessagesList extends React.Component<
this.scrollToBottom = this.scrollToBottom.bind(this);
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"
onScroll={this.handleScroll}
ref={this.messageContainerRef}
>
{this.renderMessages(messages)}
<div ref={this.messagesEndRef} />
</div>
<div
className="messages-container"
onScroll={this.handleScroll}
ref={this.messageContainerRef}
>
{this.renderMessages(messages)}
<div ref={this.messagesEndRef} />
<SessionScrollButton
show={showScrollButton}
onClick={this.scrollToBottom}
/>
<SessionFileDropzone
handleDrop={this.props.handleFilesDropped}
handleWheel={this.handleScroll}
/>
</>
</div>
);
}
@ -237,7 +231,8 @@ export class SessionConversationMessagesList extends React.Component<
const { length } = messages;
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
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 { SessionIcon, SessionIconSize, SessionIconType } from '../icon';
interface Props {
handleDrop: (files: FileList) => void;
}
interface State {
dragging: boolean;
}
export class SessionFileDropzone extends Component<Props, State> {
private readonly dropRef: React.RefObject<any>;
private dragCounter: number;
constructor(props: any) {
super(props);
this.state = {
dragging: false,
};
this.dragCounter = 0;
this.dropRef = React.createRef();
}
public handleDrag = (e: any) => {
e.preventDefault();
e.stopPropagation();
};
public handleDragIn = (e: any) => {
e.preventDefault();
e.stopPropagation();
this.dragCounter++;
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
this.setState({ dragging: true });
}
};
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',
}}
// padding-inline-end: ${props => props.theme.common.margins.md};
// padding-inline-start: ${props => props.theme.common.margins.md};
const DropZoneContainer = styled.div`
display: inline-block;
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
`;
const DropZoneWithBorder = styled.div`
border: dashed 4px ${props => props.theme.colors.accent};
background-color: ${props => props.theme.colors.clickableHovered};
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 20;
opacity: 0.5;
pointer-events: none;
`;
export const SessionFileDropzone = () => {
return (
<DropZoneContainer>
<DropZoneWithBorder>
<Flex
container={true}
justifyContent="space-around"
height="100%"
alignItems="center"
>
<Flex
container={true}
justifyContent="space-around"
height="100%"
alignItems="center"
>
<SessionIcon
iconSize={SessionIconSize.Max}
iconType={SessionIconType.CirclePlus}
/>
</Flex>
</div>
{this.props.children}
</div>
);
}
}
<SessionIcon
iconSize={SessionIconSize.Max}
iconType={SessionIconType.CirclePlus}
/>
</Flex>
</DropZoneWithBorder>
</DropZoneContainer>
);
};

Loading…
Cancel
Save