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
* {