diff --git a/src/App.js b/src/App.js index 08d080ca..39978892 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback } from "react"; import { connect } from "react-redux"; import Head from "./Head"; import About from "./About"; @@ -8,10 +8,17 @@ import SkinTable from "./SkinTable"; import FocusedSkin from "./FocusedSkin"; import { useSelector } from "react-redux"; import * as Selectors from "./redux/selectors"; +import * as Actions from "./redux/actionCreators"; import { ABOUT_PAGE } from "./constants"; -import { useWindowSize, useScrollbarWidth } from "./hooks"; +import { + useWindowSize, + useScrollbarWidth, + useDropFiles, + useActionCreator, +} from "./hooks"; import { SCREENSHOT_WIDTH, SKIN_RATIO } from "./constants"; import UploadGrid from "./UploadGrid"; +import DropTarget from "./DropTarget"; import Metadata from "./components/Metadata"; import SkinReadme from "./SkinReadme"; @@ -22,6 +29,21 @@ const getTableDimensions = (windowWidth, scale) => { return { columnWidth, rowHeight, columnCount }; }; +function useDropTarget() { + const gotFiles = useActionCreator(Actions.gotFiles); + const areDragging = useSelector(Selectors.getAreDragging); + + const onDrop = useCallback( + (e) => { + gotFiles(Array.from(e.dataTransfer.files)); + }, + [gotFiles] + ); + const setDragging = useActionCreator(Actions.setDragging); + useDropFiles({ onDrop, setDragging }); + return areDragging; +} + function App(props) { const scrollbarWidth = useScrollbarWidth(); const { @@ -36,11 +58,15 @@ function App(props) { const fileExplorerOpen = useSelector(Selectors.getFileExplorerOpen); + const areDragging = useDropTarget(); + return (
- {props.uploadViewOpen ? ( + {areDragging ? ( + + ) : props.uploadViewOpen ? ( ) : ( +

Drop

+
+ ); +} +export default DropTarget; diff --git a/src/Header.js b/src/Header.js index 5602c02f..3e35b7fa 100644 --- a/src/Header.js +++ b/src/Header.js @@ -25,7 +25,7 @@ function SearchLogo() { class Header extends React.Component { constructor(props) { - super(props); + super(); this._disposable = new Disposable(); this._inputRef = null; } diff --git a/src/UploadButton.js b/src/UploadButton.js index 33bed09e..32a47135 100644 --- a/src/UploadButton.js +++ b/src/UploadButton.js @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { useSelector } from "react-redux"; import { useActionCreator } from "./hooks"; import * as Actions from "./redux/actionCreators"; @@ -12,6 +12,7 @@ function UploadButton() { const uploadViewOpen = useSelector(Selectors.getHaveUploadFiles); const gotFiles = useActionCreator(Actions.gotFiles); const closeUploadFiles = useActionCreator(Actions.closeUploadFiles); + if (!SHOW_UPLOAD) { return null; } diff --git a/src/hooks.js b/src/hooks.js index df0f65f4..cf098a4b 100644 --- a/src/hooks.js +++ b/src/hooks.js @@ -1,5 +1,5 @@ import * as Utils from "./utils"; -import { useMemo, useState, useEffect, useCallback } from "react"; +import { useMemo, useState, useEffect, useCallback, useRef } from "react"; import { useDispatch } from "react-redux"; import { delay } from "rxjs/operators"; @@ -100,3 +100,54 @@ export function useWebampAnimation({ initialPosition }) { handleWebampLoaded: () => webampLoadedEvents.next(null), }; } + +export function useDropFiles({ onDrop, setDragging }) { + const onDragEnterCallback = useCallback( + (e) => { + e.preventDefault(); + setDragging(true); + }, + [setDragging] + ); + + const onDragLeaveCallback = useCallback( + (e) => { + e.preventDefault(); + if (e.target === window.document.documentElement) { + setDragging(false); + } + }, + [setDragging] + ); + + const onDropCallback = useCallback( + (e) => { + e.preventDefault(); + setDragging(false); + onDrop(e); + }, + [onDrop, setDragging] + ); + + const onDragoverCallback = useCallback((e) => { + e.preventDefault(); + }, []); + + useEffect(() => { + window.addEventListener("drop", onDropCallback); + window.addEventListener("dragover", onDragoverCallback); + window.addEventListener("dragenter", onDragEnterCallback); + window.addEventListener("dragleave", onDragLeaveCallback); + return () => { + window.removeEventListener("drop", onDropCallback); + window.removeEventListener("dragover", onDragoverCallback); + window.removeEventListener("dragenter", onDragEnterCallback); + window.removeEventListener("dragleave", onDragLeaveCallback); + }; + }, [ + onDropCallback, + onDragoverCallback, + onDragEnterCallback, + onDragLeaveCallback, + ]); +} diff --git a/src/redux/actionCreators.js b/src/redux/actionCreators.js index a708bb5e..3fd6c3ba 100644 --- a/src/redux/actionCreators.js +++ b/src/redux/actionCreators.js @@ -38,6 +38,10 @@ export function doesNotConcentToNsfw() { return { type: "DOES_NOT_CONCENT_TO_NSFW" }; } +export function setDragging(dragging) { + return { type: "SET_DRAGGING", dragging }; +} + export function toggleUploadView() { return { type: "TOGGLE_UPLOAD_VIEW" }; } diff --git a/src/redux/reducer.js b/src/redux/reducer.js index d1cc4731..3be4ae0b 100644 --- a/src/redux/reducer.js +++ b/src/redux/reducer.js @@ -10,6 +10,7 @@ const defaultState = { focusedSkinFile: null, fileExplorerOpen: false, uploadViewOpen: false, + areDragging: false, activeContentPage: null, totalNumberOfSkins: null, scale: 0.5, @@ -20,6 +21,8 @@ const defaultState = { export default function reducer(state = defaultState, action) { switch (action.type) { + case "SET_DRAGGING": + return { ...state, areDragging: action.dragging }; case "SET_SCALE": { return { ...state, scale: action.scale }; } diff --git a/src/redux/selectors.js b/src/redux/selectors.js index 431a1581..941aa06a 100644 --- a/src/redux/selectors.js +++ b/src/redux/selectors.js @@ -50,6 +50,8 @@ export const getFileToUpload = (state) => { export const getHaveUploadFiles = (state) => Object.keys(state.fileUploads).length > 0; +export const getAreDragging = (state) => state.areDragging; + /** * Skin Interface * {