diff --git a/app/soapbox/features/compose/components/compose-form.tsx b/app/soapbox/features/compose/components/compose-form.tsx index 5940dd7dd..f3ac7a582 100644 --- a/app/soapbox/features/compose/components/compose-form.tsx +++ b/app/soapbox/features/compose/components/compose-form.tsx @@ -88,7 +88,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab const [composeFocused, setComposeFocused] = useState(false); - const formRef = useRef(null); + const formRef = useRef(null); const spoilerTextRef = useRef(null); const autosuggestTextareaRef = useRef(null); diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 565666465..82bd3d27e 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -1,16 +1,15 @@ 'use strict'; -import React, { useState, useEffect, useRef } from 'react'; +import clsx from 'clsx'; +import React, { useEffect, useRef } from 'react'; import { HotKeys } from 'react-hotkeys'; -import { useIntl } from 'react-intl'; import { Switch, useHistory, useLocation, Redirect } from 'react-router-dom'; import { fetchFollowRequests } from 'soapbox/actions/accounts'; import { fetchReports, fetchUsers, fetchConfig } from 'soapbox/actions/admin'; import { fetchAnnouncements } from 'soapbox/actions/announcements'; -import { uploadCompose, resetCompose } from 'soapbox/actions/compose'; +import { resetCompose } from 'soapbox/actions/compose'; import { fetchCustomEmojis } from 'soapbox/actions/custom-emojis'; -import { uploadEventBanner } from 'soapbox/actions/events'; import { fetchFilters } from 'soapbox/actions/filters'; import { fetchMarker } from 'soapbox/actions/markers'; import { openModal } from 'soapbox/actions/modals'; @@ -26,7 +25,7 @@ import SidebarNavigation from 'soapbox/components/sidebar-navigation'; import ThumbNavigation from 'soapbox/components/thumb-navigation'; import { Layout } from 'soapbox/components/ui'; import { useStatContext } from 'soapbox/contexts/stat-context'; -import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useInstance } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useInstance, useDraggedFiles } from 'soapbox/hooks'; import AdminPage from 'soapbox/pages/admin-page'; import ChatsPage from 'soapbox/pages/chats-page'; import DefaultPage from 'soapbox/pages/default-page'; @@ -101,7 +100,6 @@ import { FollowRecommendations, Directory, SidebarMenu, - UploadArea, ProfileHoverCard, StatusHoverCard, Share, @@ -387,16 +385,12 @@ interface IUI { } const UI: React.FC = ({ children }) => { - const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); const { data: pendingPolicy } = usePendingPolicy(); const instance = useInstance(); const statContext = useStatContext(); - const [draggingOver, setDraggingOver] = useState(false); - - const dragTargets = useRef([]); const disconnect = useRef(null); const node = useRef(null); const hotkeys = useRef(null); @@ -411,74 +405,7 @@ const UI: React.FC = ({ children }) => { const streamingUrl = instance.urls.get('streaming_api'); const standalone = useAppSelector(isStandalone); - const handleDragEnter = (e: DragEvent) => { - e.preventDefault(); - - if (e.target && !dragTargets.current.includes(e.target)) { - dragTargets.current.push(e.target); - } - - if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) { - setDraggingOver(true); - } - }; - - const handleDragOver = (e: DragEvent) => { - if (dataTransferIsText(e.dataTransfer)) return false; - e.preventDefault(); - e.stopPropagation(); - - try { - if (e.dataTransfer) { - e.dataTransfer.dropEffect = 'copy'; - } - } catch (err) { - // Do nothing - } - - return false; - }; - - const handleDrop = (e: DragEvent) => { - if (!me) return; - - if (dataTransferIsText(e.dataTransfer)) return; - e.preventDefault(); - - setDraggingOver(false); - dragTargets.current = []; - - dispatch((_, getState) => { - if (e.dataTransfer && e.dataTransfer.files.length >= 1) { - const modals = getState().modals; - const isModalOpen = modals.last()?.modalType === 'COMPOSE'; - const isEventsModalOpen = modals.last()?.modalType === 'COMPOSE_EVENT'; - if (isEventsModalOpen) dispatch(uploadEventBanner(e.dataTransfer.files[0], intl)); - else dispatch(uploadCompose(isModalOpen ? 'compose-modal' : 'home', e.dataTransfer.files, intl)); - } - }); - }; - - const handleDragLeave = (e: DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - - dragTargets.current = dragTargets.current.filter(el => el !== e.target && node.current?.contains(el as Node)); - - if (dragTargets.current.length > 0) { - return; - } - - setDraggingOver(false); - }; - - const dataTransferIsText = (dataTransfer: DataTransfer | null) => { - return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1); - }; - - const closeUploadModal = () => { - setDraggingOver(false); - }; + const { isDragging } = useDraggedFiles(node); const handleServiceWorkerPostMessage = ({ data }: MessageEvent) => { if (data.type === 'navigate') { @@ -501,6 +428,11 @@ const UI: React.FC = ({ children }) => { } }; + const handleDragEnter = (e: DragEvent) => e.preventDefault(); + const handleDragLeave = (e: DragEvent) => e.preventDefault(); + const handleDragOver = (e: DragEvent) => e.preventDefault(); + const handleDrop = (e: DragEvent) => e.preventDefault(); + /** Load initial data when a user is logged in */ const loadAccountData = () => { if (!account) return; @@ -535,11 +467,6 @@ const UI: React.FC = ({ children }) => { }; useEffect(() => { - document.addEventListener('dragenter', handleDragEnter, false); - document.addEventListener('dragover', handleDragOver, false); - document.addEventListener('drop', handleDrop, false); - document.addEventListener('dragleave', handleDragLeave, false); - if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', handleServiceWorkerPostMessage); } @@ -548,12 +475,21 @@ const UI: React.FC = ({ children }) => { window.setTimeout(() => Notification.requestPermission(), 120 * 1000); } + return () => { + disconnectStreaming(); + }; + }, []); + + useEffect(() => { + document.addEventListener('dragenter', handleDragEnter); + document.addEventListener('dragleave', handleDragLeave); + document.addEventListener('dragover', handleDragOver); + document.addEventListener('drop', handleDrop); return () => { document.removeEventListener('dragenter', handleDragEnter); + document.removeEventListener('dragleave', handleDragLeave); document.removeEventListener('dragover', handleDragOver); document.removeEventListener('drop', handleDrop); - document.removeEventListener('dragleave', handleDragLeave); - disconnectStreaming(); }; }, []); @@ -697,6 +633,12 @@ const UI: React.FC = ({ children }) => { return (
+
+
@@ -718,10 +660,6 @@ const UI: React.FC = ({ children }) => {
)} - - {Component => } - - {me && ( {Component => } diff --git a/app/soapbox/pages/home-page.tsx b/app/soapbox/pages/home-page.tsx index 2ba7cac0a..f11e0bcc3 100644 --- a/app/soapbox/pages/home-page.tsx +++ b/app/soapbox/pages/home-page.tsx @@ -1,6 +1,9 @@ +import clsx from 'clsx'; import React, { useRef } from 'react'; +import { useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; +import { uploadCompose } from 'soapbox/actions/compose'; import FeedCarousel from 'soapbox/features/feed-filtering/feed-carousel'; import LinkFooter from 'soapbox/features/ui/components/link-footer'; import { @@ -14,7 +17,7 @@ import { CtaBanner, AnnouncementsPanel, } from 'soapbox/features/ui/util/async-components'; -import { useAppSelector, useOwnAccount, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; +import { useAppSelector, useOwnAccount, useFeatures, useSoapboxConfig, useDraggedFiles, useAppDispatch } from 'soapbox/hooks'; import { Avatar, Card, CardBody, HStack, Layout } from '../components/ui'; import ComposeForm from '../features/compose/components/compose-form'; @@ -25,17 +28,25 @@ interface IHomePage { } const HomePage: React.FC = ({ children }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const me = useAppSelector(state => state.me); const account = useOwnAccount(); const features = useFeatures(); const soapboxConfig = useSoapboxConfig(); + const composeId = 'home'; const composeBlock = useRef(null); const hasPatron = soapboxConfig.extensions.getIn(['patron', 'enabled']) === true; const hasCrypto = typeof soapboxConfig.cryptoAddresses.getIn([0, 'ticker']) === 'string'; const cryptoLimit = soapboxConfig.cryptoDonatePanel.get('limit', 0); + const { isDragging, isDraggedOver } = useDraggedFiles(composeBlock, (files) => { + dispatch(uploadCompose(composeId, files, intl)); + }); + const acct = account ? account.acct : ''; const avatar = account ? account.avatar : ''; @@ -43,7 +54,14 @@ const HomePage: React.FC = ({ children }) => { <> {me && ( - + @@ -52,7 +70,7 @@ const HomePage: React.FC = ({ children }) => {