Roughed out drag and drop to chat boxes and to autosuggest textarea

drag_drop
crockwave 2020-09-22 20:38:03 -05:00
rodzic 3b8eed17b6
commit e57b4dbe00
3 zmienionych plików z 220 dodań i 80 usunięć

Wyświetl plik

@ -7,6 +7,8 @@ import { isRtl } from '../rtl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
import classNames from 'classnames';
import UploadArea from 'soapbox/features/ui/components/upload_area';
import { uploadCompose } from 'soapbox/actions/compose';
const textAtCursorMatchesToken = (str, caretPosition) => {
let word;
@ -50,6 +52,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
autoFocus: PropTypes.bool,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onAttachment: PropTypes.func,
};
static defaultProps = {
@ -62,8 +65,25 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
selectedSuggestion: 0,
lastToken: null,
tokenStart: 0,
draggingOver: false,
};
componentDidMount() {
window.addEventListener('dragenter', this.handleDragEnter, false);
window.addEventListener('dragover', this.handleDragOver, false);
window.addEventListener('drop', this.handleDrop, false);
window.addEventListener('dragleave', this.handleDragLeave, false);
window.addEventListener('dragend', this.handleDragEnd, false);
}
componentWillUnmount() {
window.removeEventListener('dragenter', this.handleDragEnter);
window.removeEventListener('dragover', this.handleDragOver);
window.removeEventListener('drop', this.handleDrop);
window.removeEventListener('dragleave', this.handleDragLeave);
window.removeEventListener('dragend', this.handleDragEnd);
}
onChange = (e) => {
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
@ -170,6 +190,103 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
this.textarea = c;
}
setRef = c => {
this.node = c;
}
handleAttachment = () => {
const { onAttachment } = this.props;
onAttachment(true);
}
dataTransferIsText = (dataTransfer) => {
return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1);
}
handleDragEnter = (e) => {
e.preventDefault();
if (!this.dragTargets) {
this.dragTargets = [];
}
if (this.dragTargets.indexOf(e.target) === -1) {
this.dragTargets.push(e.target);
}
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) {
this.setState({ draggingOver: true });
}
}
handleDragOver = (e) => {
if (this.dataTransferIsText(e.dataTransfer)) return false;
e.preventDefault();
e.stopPropagation();
try {
e.dataTransfer.dropEffect = 'copy';
} catch (err) {
}
return false;
}
handleAttachment = () => {
const { onAttachment } = this.props;
onAttachment(true);
}
handleFiles = (files) => {
const { dispatch } = this.props;
this.setState({ isUploading: true });
const data = new FormData();
data.append('file', files[0]);
dispatch(uploadCompose(data));
dispatch(this.handleAttachment());
// dispatch(uploadMedia(data, this.onUploadProgress)).then(response => {
// this.setState({ attachment: response.data, isUploading: false });
// this.handleAttachment();
// }).catch(() => {
// this.setState({ isUploading: false });
// });
}
handleDrop = (e) => {
const { me } = this.props;
if (!me) return;
if (this.dataTransferIsText(e.dataTransfer)) return;
e.preventDefault();
this.setState({ draggingOver: false });
this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1) {
this.handleFiles(e.dataTransfer.files);
// this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
}
handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
if (this.dragTargets.length > 0) {
return;
}
this.setState({ draggingOver: false });
}
closeUploadModal = () => {
this.setState({ draggingOver: false });
}
onPaste = (e) => {
if (e.clipboardData && e.clipboardData.files.length === 1) {
this.props.onPaste(e.clipboardData.files);
@ -210,7 +327,8 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
return [
<div className='compose-form__autosuggest-wrapper' key='compose-form__autosuggest-wrapper'>
<div className='autosuggest-textarea'>
<div className='autosuggest-textarea' ref={this.setRef}>
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
<label>
<span style={{ display: 'none' }}>{placeholder}</span>

Wyświetl plik

@ -15,6 +15,7 @@ import { uploadMedia } from 'soapbox/actions/media';
import UploadProgress from 'soapbox/features/compose/components/upload_progress';
import { truncateFilename } from 'soapbox/utils/media';
import IconButton from 'soapbox/components/icon_button';
import UploadArea from 'soapbox/features/ui/components/upload_area';
const messages = defineMessages({
placeholder: { id: 'chat_box.input.placeholder', defaultMessage: 'Send a message…' },
@ -40,6 +41,7 @@ class ChatBox extends ImmutablePureComponent {
chat: ImmutablePropTypes.map,
onSetInputRef: PropTypes.func,
me: PropTypes.node,
onAttachment: PropTypes.func,
}
initialState = () => ({
@ -48,10 +50,27 @@ class ChatBox extends ImmutablePureComponent {
isUploading: false,
uploadProgress: 0,
resetFileKey: fileKeyGen(),
draggingOver: false,
})
state = this.initialState()
componentDidMount() {
window.addEventListener('dragenter', this.handleDragEnter, false);
window.addEventListener('dragover', this.handleDragOver, false);
window.addEventListener('drop', this.handleDrop, false);
window.addEventListener('dragleave', this.handleDragLeave, false);
window.addEventListener('dragend', this.handleDragEnd, false);
}
componentWillUnmount() {
window.removeEventListener('dragenter', this.handleDragEnter);
window.removeEventListener('dragover', this.handleDragOver);
window.removeEventListener('drop', this.handleDrop);
window.removeEventListener('dragleave', this.handleDragLeave);
window.removeEventListener('dragend', this.handleDragEnd);
}
clearState = () => {
this.setState(this.initialState());
}
@ -119,14 +138,89 @@ class ChatBox extends ImmutablePureComponent {
setInputRef = (el) => {
const { onSetInputRef } = this.props;
this.inputElem = el;
onSetInputRef(el);
};
setRef = c => {
this.node = c;
}
handleAttachment = () => {
const { onAttachment } = this.props;
onAttachment(true);
}
handleRemoveFile = (e) => {
this.setState({ attachment: undefined, resetFileKey: fileKeyGen() });
}
dataTransferIsText = (dataTransfer) => {
return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1);
}
handleDragEnter = (e) => {
e.preventDefault();
if (!this.dragTargets) {
this.dragTargets = [];
}
if (this.dragTargets.indexOf(e.target) === -1) {
this.dragTargets.push(e.target);
}
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) {
this.setState({ draggingOver: true });
}
}
handleDragOver = (e) => {
if (this.dataTransferIsText(e.dataTransfer)) return false;
e.preventDefault();
e.stopPropagation();
try {
e.dataTransfer.dropEffect = 'copy';
} catch (err) {
}
return false;
}
handleDrop = (e) => {
const { me } = this.props;
if (!me) return;
if (this.dataTransferIsText(e.dataTransfer)) return;
e.preventDefault();
this.setState({ draggingOver: false });
this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1) {
this.handleFiles(e.dataTransfer.files);
// this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
}
handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
if (this.dragTargets.length > 0) {
return;
}
this.setState({ draggingOver: false });
}
closeUploadModal = () => {
this.setState({ draggingOver: false });
}
onUploadProgress = (e) => {
const { loaded, total } = e;
this.setState({ uploadProgress: loaded/total });
@ -142,6 +236,7 @@ class ChatBox extends ImmutablePureComponent {
dispatch(uploadMedia(data, this.onUploadProgress)).then(response => {
this.setState({ attachment: response.data, isUploading: false });
this.handleAttachment();
}).catch(() => {
this.setState({ isUploading: false });
});
@ -178,13 +273,15 @@ class ChatBox extends ImmutablePureComponent {
render() {
const { chatMessageIds, chatId, intl } = this.props;
const { content, isUploading, uploadProgress } = this.state;
const { draggingOver } = this.state;
if (!chatMessageIds) return null;
return (
<div className='chat-box' onMouseOver={this.handleHover}>
<div className='chat-box' ref={this.setRef} onMouseOver={this.handleHover}>
<ChatMessageList chatMessageIds={chatMessageIds} chatId={chatId} />
{this.renderAttachment()}
<UploadProgress active={isUploading} progress={uploadProgress*100} />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
<div className='chat-box__actions simple_form'>
{this.renderActionButton()}
<textarea

Wyświetl plik

@ -13,7 +13,7 @@ import LoadingBarContainer from './containers/loading_bar_container';
import ModalContainer from './containers/modal_container';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose, resetCompose } from '../../actions/compose';
import { resetCompose } from '../../actions/compose';
import { expandHomeTimeline } from '../../actions/timelines';
import { expandNotifications } from '../../actions/notifications';
import { fetchReports } from '../../actions/admin';
@ -22,7 +22,6 @@ import { fetchChats } from 'soapbox/actions/chats';
import { clearHeight } from '../../actions/height_cache';
import { openModal } from '../../actions/modal';
import { WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area';
import TabsBar from './components/tabs_bar';
import LinkFooter from './components/link_footer';
import FeaturesPanel from './components/features_panel';
@ -296,7 +295,6 @@ class UI extends React.PureComponent {
};
state = {
draggingOver: false,
mobile: isMobile(window.innerWidth),
};
@ -316,72 +314,6 @@ class UI extends React.PureComponent {
this.props.dispatch(clearHeight());
}
handleDragEnter = (e) => {
e.preventDefault();
if (!this.dragTargets) {
this.dragTargets = [];
}
if (this.dragTargets.indexOf(e.target) === -1) {
this.dragTargets.push(e.target);
}
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) {
this.setState({ draggingOver: true });
}
}
handleDragOver = (e) => {
if (this.dataTransferIsText(e.dataTransfer)) return false;
e.preventDefault();
e.stopPropagation();
try {
e.dataTransfer.dropEffect = 'copy';
} catch (err) {
}
return false;
}
handleDrop = (e) => {
const { me } = this.props;
if (!me) return;
if (this.dataTransferIsText(e.dataTransfer)) return;
e.preventDefault();
this.setState({ draggingOver: false });
this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1) {
this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
}
handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
if (this.dragTargets.length > 0) {
return;
}
this.setState({ draggingOver: false });
}
dataTransferIsText = (dataTransfer) => {
return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1);
}
closeUploadModal = () => {
this.setState({ draggingOver: false });
}
handleServiceWorkerPostMessage = ({ data }) => {
if (data.type === 'navigate') {
this.context.router.history.push(data.path);
@ -418,12 +350,6 @@ class UI extends React.PureComponent {
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true });
document.addEventListener('dragenter', this.handleDragEnter, false);
document.addEventListener('dragover', this.handleDragOver, false);
document.addEventListener('drop', this.handleDrop, false);
document.addEventListener('dragleave', this.handleDragLeave, false);
document.addEventListener('dragend', this.handleDragEnd, false);
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
}
@ -590,7 +516,7 @@ class UI extends React.PureComponent {
render() {
const { streamingUrl } = this.props;
const { draggingOver, mobile } = this.state;
const { mobile } = this.state;
const { intl, children, isComposing, location, dropdownMenuIsOpen, me } = this.props;
if (me === null || !streamingUrl) return null;
@ -648,7 +574,6 @@ class UI extends React.PureComponent {
<NotificationsContainer />
<LoadingBarContainer className='loading-bar' />
<ModalContainer />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
{me && <SidebarMenu />}
{me && !mobile && <ChatPanes />}
<ProfileHoverCard />