From ceb0675bbc2693357c56e3ec59e8b4eacb5b6bca Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sun, 13 Sep 2020 13:30:56 -0700 Subject: [PATCH 1/7] Use dropzone --- package.json | 1 + src/App.js | 40 ++++++++++++++++++------------------- src/DropTarget.js | 33 +++++++++++++++++++++++++++--- src/hooks.js | 51 ----------------------------------------------- yarn.lock | 26 ++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index 584a73a6..f11272c5 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "md5-file": "^4.0.0", "react": "^16.8.6", "react-dom": "^16.8.6", + "react-dropzone": "^11.1.0", "react-helmet": "^5.2.1", "react-redux": "^7.2.1", "react-scripts": "^3.4.1", diff --git a/src/App.js b/src/App.js index 39978892..685dc9b0 100644 --- a/src/App.js +++ b/src/App.js @@ -16,11 +16,12 @@ import { useDropFiles, useActionCreator, } from "./hooks"; -import { SCREENSHOT_WIDTH, SKIN_RATIO } from "./constants"; +import { SCREENSHOT_WIDTH, SKIN_RATIO, SHOW_UPLOAD } from "./constants"; import UploadGrid from "./UploadGrid"; import DropTarget from "./DropTarget"; import Metadata from "./components/Metadata"; import SkinReadme from "./SkinReadme"; +import { useDropzone } from "react-dropzone"; const getTableDimensions = (windowWidth, scale) => { const columnCount = Math.floor(windowWidth / (SCREENSHOT_WIDTH * scale)); @@ -29,21 +30,6 @@ 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 { @@ -55,17 +41,29 @@ function App(props) { windowWidthWithScrollabar - scrollbarWidth, props.scale ); + const gotFiles = useActionCreator(Actions.gotFiles); + + const onDrop = useCallback( + (acceptedFiles) => { + gotFiles(acceptedFiles); + // Do something with the files + }, + [gotFiles] + ); + const { + getRootProps, + getInputProps, + isDragActive: areDragging, + } = useDropzone({ onDrop }); const fileExplorerOpen = useSelector(Selectors.getFileExplorerOpen); - const areDragging = useDropTarget(); - return ( -
+
- {areDragging ? ( - + {areDragging && SHOW_UPLOAD ? ( + ) : props.uploadViewOpen ? ( ) : ( diff --git a/src/DropTarget.js b/src/DropTarget.js index c4f0982a..34c783ec 100644 --- a/src/DropTarget.js +++ b/src/DropTarget.js @@ -1,10 +1,37 @@ import React from "react"; import { HEADING_HEIGHT } from "./constants"; -function DropTarget() { +function DropTarget({ getInputProps }) { return ( -
-

Drop

+
+
+ Drop Skins Here + +
); } diff --git a/src/hooks.js b/src/hooks.js index cf098a4b..b3eef1fa 100644 --- a/src/hooks.js +++ b/src/hooks.js @@ -100,54 +100,3 @@ 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/yarn.lock b/yarn.lock index 78fc2191..8fca766c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2618,6 +2618,11 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +attr-accept@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" + integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== + autoprefixer@^9.6.1: version "9.7.5" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.5.tgz#8df10b9ff9b5814a8d411a5cfbab9c793c392376" @@ -6015,6 +6020,13 @@ file-loader@4.3.0: loader-utils "^1.2.3" schema-utils "^2.5.0" +file-selector@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.13.tgz#5efd977ca2bca1700992df1b10e254f4e73d2df4" + integrity sha512-T2efCBY6Ps+jLIWdNQsmzt/UnAjKOEAlsZVdnQztg/BtAZGNL4uX1Jet9cMM8gify/x4CSudreji2HssGBNVIQ== + dependencies: + tslib "^2.0.1" + file-size@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/file-size/-/file-size-0.0.5.tgz#057d43c3a3ed735da3f90d6052ab380f1e6d5e3b" @@ -11962,6 +11974,15 @@ react-dom@^16.8.6: prop-types "^15.6.2" scheduler "^0.19.1" +react-dropzone@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-11.1.0.tgz#c225f3c53450c80fbd80954361dc039090bfc14c" + integrity sha512-gJT6iJadyTbevrigm6KZFaei/yNWfokzs1idumO7fXtRNPiGFDUpsQ+trHWwUO3yWOtJibpbo5tLZggjm+KV5w== + dependencies: + attr-accept "^2.0.0" + file-selector "^0.1.12" + prop-types "^15.7.2" + react-error-overlay@^6.0.3, react-error-overlay@^6.0.7: version "6.0.7" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" @@ -14119,6 +14140,11 @@ tslib@^1, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" + integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" From 2539f671451a15f0566dbcd609e2bda5dfd9a162 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sun, 13 Sep 2020 14:14:37 -0700 Subject: [PATCH 2/7] Make the upload page its own route --- src/App.js | 31 +++--- src/DropTarget.js | 38 -------- src/Header.js | 184 ++++++++++++++++-------------------- src/UploadButton.js | 27 +++--- src/UploadGrid.js | 69 ++++++++++---- src/constants.js | 1 + src/redux/actionCreators.js | 7 +- src/redux/epics.js | 3 + src/redux/reducer.js | 11 ++- src/redux/selectors.js | 10 +- 10 files changed, 184 insertions(+), 197 deletions(-) delete mode 100644 src/DropTarget.js diff --git a/src/App.js b/src/App.js index 685dc9b0..6e99266c 100644 --- a/src/App.js +++ b/src/App.js @@ -10,15 +10,9 @@ 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, - useDropFiles, - useActionCreator, -} from "./hooks"; -import { SCREENSHOT_WIDTH, SKIN_RATIO, SHOW_UPLOAD } from "./constants"; +import { useWindowSize, useScrollbarWidth, 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"; import { useDropzone } from "react-dropzone"; @@ -50,11 +44,10 @@ function App(props) { }, [gotFiles] ); - const { - getRootProps, - getInputProps, - isDragActive: areDragging, - } = useDropzone({ onDrop }); + const { getRootProps, getInputProps, isDragActive } = useDropzone({ + onDrop, + noClick: true, + }); const fileExplorerOpen = useSelector(Selectors.getFileExplorerOpen); @@ -62,10 +55,8 @@ function App(props) {
- {areDragging && SHOW_UPLOAD ? ( - - ) : props.uploadViewOpen ? ( - + {props.uploadViewOpen || isDragActive ? ( + ) : ( )} - {props.aboutPage ? ( + {props.page === ABOUT_PAGE ? ( @@ -100,9 +91,9 @@ function App(props) { const mapStateToProps = (state) => ({ selectedSkinHash: Selectors.getSelectedSkinHash(state), overlayShouldAnimate: Selectors.overlayShouldAnimate(state), - aboutPage: Selectors.getActiveContentPage(state) === ABOUT_PAGE, + page: Selectors.getActiveContentPage(state), scale: state.scale, - uploadViewOpen: Selectors.getHaveUploadFiles(state), + uploadViewOpen: Selectors.getUploadViewOpen(state), }); export default connect(mapStateToProps)(App); diff --git a/src/DropTarget.js b/src/DropTarget.js deleted file mode 100644 index 34c783ec..00000000 --- a/src/DropTarget.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import { HEADING_HEIGHT } from "./constants"; - -function DropTarget({ getInputProps }) { - return ( -
-
- Drop Skins Here - -
-
- ); -} -export default DropTarget; diff --git a/src/Header.js b/src/Header.js index 3e35b7fa..8fa8f507 100644 --- a/src/Header.js +++ b/src/Header.js @@ -1,10 +1,9 @@ -import * as React from "react"; -import { connect } from "react-redux"; +import React, { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; import * as Utils from "./utils"; import * as Selectors from "./redux/selectors"; import * as Actions from "./redux/actionCreators"; -import Disposable from "./Disposable"; -import { useWindowSize } from "./hooks"; +import { useActionCreator, useWindowSize } from "./hooks"; import { ReactComponent as AlgoliaLogo } from "./searchByAlgoliaDarkbBackground.svg"; import algoliaLogoSmallUrl from "./searchByAlgoliaSmall.png"; import UploadButton from "./UploadButton"; @@ -23,67 +22,73 @@ function SearchLogo() { ); } -class Header extends React.Component { - constructor(props) { - super(); - this._disposable = new Disposable(); - this._inputRef = null; - } +function useFocusOnSlash() { + const [input, setInput] = useState(null); - componentDidMount() { + useEffect(() => { + if (input == null) { + return; + } const handler = (e) => { // slash if (e.keyCode === 191) { - if (this._inputRef == null) { - return; - } - if (this._inputRef !== document.activeElement) { - this._inputRef.focus(); + if (input !== document.activeElement) { + input.focus(); e.preventDefault(); } } }; window.document.addEventListener("keydown", handler); - this._disposable.add(() => { + return () => { window.document.removeEventListener("keydown", handler); - }); - } + }; + }, [input]); - componentWillUnmount() { - this._disposable.dispose(); - } - render() { - return ( - - ); - } + setSearchQuery(e.target.value)} + value={searchQuery || ""} + placeholder={"Search..."} + ref={setInput} + /> + + + + )} + +
+ ); } -const mapStateToProps = (state) => ({ - searchQuery: Selectors.getSearchQuery(state), - scale: state.scale, - uploadViewOpen: Selectors.getHaveUploadFiles(state), -}); - -const mapDispatchToProps = (dispatch) => ({ - setSearchQuery(query) { - dispatch(Actions.searchQueryChanged(query)); - }, - requestRandomSkin() { - dispatch(Actions.requestedRandomSkin()); - }, - requestedAboutPage() { - dispatch(Actions.requestedAboutPage()); - }, - setScale(scale) { - dispatch({ type: "SET_SCALE", scale }); - }, -}); -export default connect(mapStateToProps, mapDispatchToProps)(Header); +export default Header; diff --git a/src/UploadButton.js b/src/UploadButton.js index 32a47135..0212a5c2 100644 --- a/src/UploadButton.js +++ b/src/UploadButton.js @@ -5,27 +5,28 @@ import * as Actions from "./redux/actionCreators"; import { SHOW_UPLOAD } from "./constants"; import UploadIcon from "./components/icons/UploadIcon"; import CloseIcon from "./components/icons/CloseIcon"; -import { promptForFileReferences } from "./utils"; import * as Selectors from "./redux/selectors"; function UploadButton() { - const uploadViewOpen = useSelector(Selectors.getHaveUploadFiles); - const gotFiles = useActionCreator(Actions.gotFiles); + const uploadViewOpen = useSelector(Selectors.getUploadViewOpen); const closeUploadFiles = useActionCreator(Actions.closeUploadFiles); + const requestedUploadPage = useActionCreator(Actions.requestedUploadPage); if (!SHOW_UPLOAD) { - return null; + // return null; } - const style = { paddingLeft: "0.2rem", paddingRight: "0.2rem" }; + const style = { + paddingLeft: "0.2rem", + paddingRight: "0.2rem", + }; + + // TODO: Make these buttons links. if (uploadViewOpen) { return ( } - - - - - - - - - - {Object.values(files).map((file, i) => { - return ; - })} - -
ActionFilenameStatus
+
+ {isDragActive || Object.keys(files).length === 0 ? ( +
+ Drop Skins Here + +
+ ) : ( + <> + {canUpload && ( + + )} + + + + + + + + + + {Object.values(files).map((file, i) => { + return ; + })} + +
ActionFilenameStatus
+ + )}
); } diff --git a/src/constants.js b/src/constants.js index 0ee6c46d..28ecf4cf 100644 --- a/src/constants.js +++ b/src/constants.js @@ -2,6 +2,7 @@ export const SCREENSHOT_WIDTH = 275; export const SCREENSHOT_HEIGHT = 348; export const SKIN_RATIO = SCREENSHOT_HEIGHT / SCREENSHOT_WIDTH; export const ABOUT_PAGE = "ABOUT_PAGE"; +export const UPLOAD_PAGE = "UPLOAD_PAGE"; // export const CDN = "https://s3.amazonaws.com/webamp-uploaded-skins"; // export const CDN = "https://s3.amazonaws.com/cdn.webampskins.org"; export const SCREENSHOT_CDN = "https://cdn.webampskins.org"; diff --git a/src/redux/actionCreators.js b/src/redux/actionCreators.js index 3fd6c3ba..0ea38f2d 100644 --- a/src/redux/actionCreators.js +++ b/src/redux/actionCreators.js @@ -1,3 +1,4 @@ +import { ABOUT_PAGE, UPLOAD_PAGE } from "../constants"; export function closeModal() { return { type: "CLOSE_MODAL" }; } @@ -109,7 +110,11 @@ export function gotFocusedSkinFile(content) { } export function requestedAboutPage() { - return { type: "REQUESTED_ABOUT_PAGE" }; + return { type: "REQUESTED_PAGE", page: ABOUT_PAGE }; +} + +export function requestedUploadPage() { + return { type: "REQUESTED_PAGE", page: UPLOAD_PAGE }; } export function selectRelativeSkin(offset) { diff --git a/src/redux/epics.js b/src/redux/epics.js index eddc0e35..7f92bfb7 100644 --- a/src/redux/epics.js +++ b/src/redux/epics.js @@ -27,6 +27,9 @@ const urlChangedEpic = (actions) => if (action.location.pathname === "/about/") { return of(Actions.requestedAboutPage()); } + if (action.location.pathname === "/upload/") { + return of(Actions.requestedUploadPage()); + } const params = new URLSearchParams(action.location.search); const query = params != null && params.get("query"); diff --git a/src/redux/reducer.js b/src/redux/reducer.js index 3be4ae0b..1ee5f2d2 100644 --- a/src/redux/reducer.js +++ b/src/redux/reducer.js @@ -1,4 +1,4 @@ -import { ABOUT_PAGE, CHUNK_SIZE } from "../constants"; +import { CHUNK_SIZE, UPLOAD_PAGE } from "../constants"; const defaultState = { searchQuery: null, @@ -26,6 +26,9 @@ export default function reducer(state = defaultState, action) { case "SET_SCALE": { return { ...state, scale: action.scale }; } + case "GOT_FILES": { + return { ...state, activeContentPage: UPLOAD_PAGE }; + } case "GOT_FILE": { return { ...state, @@ -141,6 +144,7 @@ export default function reducer(state = defaultState, action) { case "CLOSE_UPLOAD_FILES": { return { ...state, + activeContentPage: null, fileUploads: {}, }; } @@ -248,10 +252,11 @@ export default function reducer(state = defaultState, action) { content: action.content, }, }; - case "REQUESTED_ABOUT_PAGE": + case "REQUESTED_PAGE": + console.log(action); return { ...state, - activeContentPage: ABOUT_PAGE, + activeContentPage: action.page, }; case "OPEN_FILE_EXPLORER": return { diff --git a/src/redux/selectors.js b/src/redux/selectors.js index 941aa06a..dee75eb5 100644 --- a/src/redux/selectors.js +++ b/src/redux/selectors.js @@ -1,6 +1,6 @@ import { createSelector } from "reselect"; import * as Utils from "../utils"; -import { ABOUT_PAGE } from "../constants"; +import { ABOUT_PAGE, UPLOAD_PAGE } from "../constants"; export function getSelectedSkinHash(state) { return state.selectedSkinHash; @@ -47,8 +47,9 @@ export const getFileToUpload = (state) => { ); }; -export const getHaveUploadFiles = (state) => - Object.keys(state.fileUploads).length > 0; +export const getUploadViewOpen = (state) => { + return state.activeContentPage === UPLOAD_PAGE; +}; export const getAreDragging = (state) => state.areDragging; @@ -145,6 +146,9 @@ export const getUrl = createSelector( if (activeContentPage === ABOUT_PAGE) { return "/about/"; } + if (activeContentPage === UPLOAD_PAGE) { + return "/upload/"; + } if (hash) { const skinUrl = getPermalinkUrlFromHash(hash); if (fileExplorerOpen && focusedSkinFile) { From 11a635822493d41c60b609d0c399e1ae7c798ff5 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 14 Sep 2020 11:32:40 -0700 Subject: [PATCH 3/7] Ability to upload files --- src/UploadGrid.js | 221 ++++++++++++++++++++++--------------- src/components/Metadata.js | 6 +- src/constants.js | 1 + src/hooks.js | 2 +- src/redux/epics.js | 11 +- src/redux/reducer.js | 2 +- src/redux/selectors.js | 14 ++- src/uploadUtils.js | 2 +- src/utils.js | 4 + 9 files changed, 163 insertions(+), 100 deletions(-) diff --git a/src/UploadGrid.js b/src/UploadGrid.js index 63b8b026..361da52a 100644 --- a/src/UploadGrid.js +++ b/src/UploadGrid.js @@ -1,67 +1,63 @@ -import React from "react"; +import React, { useMemo } from "react"; import { useSelector } from "react-redux"; import { HEADING_HEIGHT } from "./constants"; import { useActionCreator } from "./hooks"; import * as Actions from "./redux/actionCreators"; -import * as Selectors from "./redux/selectors"; import * as Utils from "./utils"; -function UploadRow({ file }) { - const tryToUploadFile = useActionCreator(Actions.tryToUploadFile); - function getStatus() { - switch (file.status) { - case "MISSING": - return ; - case "ARCHIVED": - return ( - - Added! - - ); - case "FOUND": - return ( - - View - - ); - case "UPLOADING": - return "Uploading..."; - case "NEW": - return "Parsing..."; - default: - return file.status; - } +function DropTarget({ getInputProps }) { + return ( +
+
Drop Skins Here
+
+
+ We'll analyzed them in your browser to find
+ any that are missing from the museum +
+ + +
+ ); +} + +function Section({ files, filter, title, render }) { + const matches = useMemo(() => files.filter(filter), [files, filter]); + if (matches.length === 0) { + return null; } return ( - - {getStatus()} - {file.file.name} - {file.status} - - {(file.status === "FOUND" || file.status === "ARCHIVED") && ( - {file.file.name} - )} - - + <> +

+ {matches.length.toLocaleString()} {title} +

+
    + {matches.map((match, i) => { + return
  • {render(match)}
  • ; + })} +
+ ); } function UploadGrid({ getInputProps, isDragActive }) { const files = useSelector((state) => state.fileUploads); const tryToUploadAllFiles = useActionCreator(Actions.tryToUploadAllFiles); - const canUpload = useSelector(Selectors.getFileToUpload) != null; + const filesArr = Object.values(files); + return (
{isDragActive || Object.keys(files).length === 0 ? ( -
- Drop Skins Here - -
+ ) : ( - <> - {canUpload && ( - - )} - - - - - - - - - - {Object.values(files).map((file, i) => { - return ; - })} - -
ActionFilenameStatus
- +
+
+ This feature is still in beta. If you have any issues, please reach + out to{" "} + + jordan@jordaneldredge.com + + . +
+

+ {`You've dragged in ${filesArr.length.toLocaleString()} files`} + {filesArr.some((file) => file.status === "NEW") && + " (Analyzing...)"} +

+
+ are new skins! + {filesArr.some((file) => file.status === "MISSING") && ( +
+ +
+ )} + + } + filter={(file) => + file.status === "MISSING" || + file.status === "UPLOADING" || + file.status === "UPLOAD_FAILED" || + file.status === "ARCHIVED" + } + render={(file) => { + switch (file.status) { + case "MISSING": + return file.file.name; + case "UPLOADING": + return <>{file.file.name} (🚀 Uploading...); + case "UPLOAD_FAILED": + return <>{file.file.name} (❌ Upload Failed); + case "ARCHIVED": + return ( + <> + + {file.file.name} + {" "} + (✅ Added!) + + ); + default: + console.error(`Unexpected file status: ${file.status}`); + return null; + } + }} + /> +
file.status === "INVALID_FILE_EXTENSION"} + render={(file) => file.file.name} + /> +
file.status === "NOT_CLASSIC_SKIN"} + render={(file) => file.file.name} + /> +
file.status === "FOUND"} + render={(file) => ( + + {file.file.name} + + )} + /> +
)}
); diff --git a/src/components/Metadata.js b/src/components/Metadata.js index aab3d685..15758f88 100644 --- a/src/components/Metadata.js +++ b/src/components/Metadata.js @@ -3,10 +3,10 @@ import DownloadLink from "./DownloadLink"; import * as Utils from "../utils"; import LinkInput from "./LinkInput"; import { API_URL } from "../constants"; -import * as Actions from "../redux/actionCreators"; +// import * as Actions from "../redux/actionCreators"; import * as Selectors from "../redux/selectors"; import { useSelector } from "react-redux"; -import { useActionCreator } from "../hooks"; +// import { useActionCreator } from "../hooks"; import DownloadText from "./DownloadText"; function Metadata() { @@ -17,7 +17,7 @@ function Metadata() { const permalink = useSelector( Selectors.getAbsolutePermalinkUrlFromHashGetter )(hash); - const toggleFileExplorer = useActionCreator(Actions.toggleFileExplorer); + // const toggleFileExplorer = useActionCreator(Actions.toggleFileExplorer); const focusedSkinFile = useSelector(Selectors.getFocusedSkinFile); const [showLink, setShowLink] = useState(false); // TODO: Move to Epic diff --git a/src/constants.js b/src/constants.js index 28ecf4cf..79afdd0c 100644 --- a/src/constants.js +++ b/src/constants.js @@ -5,6 +5,7 @@ export const ABOUT_PAGE = "ABOUT_PAGE"; export const UPLOAD_PAGE = "UPLOAD_PAGE"; // export const CDN = "https://s3.amazonaws.com/webamp-uploaded-skins"; // export const CDN = "https://s3.amazonaws.com/cdn.webampskins.org"; +export const S3_SCREENSHOT_CDN = "https://s3.amazonaws.com/cdn.webampskins.org"; export const SCREENSHOT_CDN = "https://cdn.webampskins.org"; export const SKIN_CDN = "https://cdn.webampskins.org"; export const API_URL = "https://api.webampskins.org"; diff --git a/src/hooks.js b/src/hooks.js index b3eef1fa..df0f65f4 100644 --- a/src/hooks.js +++ b/src/hooks.js @@ -1,5 +1,5 @@ import * as Utils from "./utils"; -import { useMemo, useState, useEffect, useCallback, useRef } from "react"; +import { useMemo, useState, useEffect, useCallback } from "react"; import { useDispatch } from "react-redux"; import { delay } from "rxjs/operators"; diff --git a/src/redux/epics.js b/src/redux/epics.js index 7f92bfb7..1d1de004 100644 --- a/src/redux/epics.js +++ b/src/redux/epics.js @@ -254,7 +254,16 @@ function uploadActions(file) { return concat( of(Actions.startingFileUpload(file.id)), from(UploadUtils.upload(file.file)).pipe( - map((response) => Actions.archivedSkin(file.id, response)), + map((response) => { + if (response.status === "ADDED") { + return Actions.archivedSkin(file.id, response); + } + if (response.status === "FOUND") { + // Maybe we could do something better here? + } + console.error(response); + return Actions.uploadFailed(file.id); + }), catchError(() => of(Actions.uploadFailed(file.id))) ) ); diff --git a/src/redux/reducer.js b/src/redux/reducer.js index 1ee5f2d2..de273086 100644 --- a/src/redux/reducer.js +++ b/src/redux/reducer.js @@ -129,8 +129,8 @@ export default function reducer(state = defaultState, action) { } else if (foundSet.has(file.md5)) { return { ...file, status: "FOUND" }; } - return file; } + return file; } const newFileUploads = {}; Object.entries(state.fileUploads).forEach(([key, file]) => { diff --git a/src/redux/selectors.js b/src/redux/selectors.js index dee75eb5..fbd995ae 100644 --- a/src/redux/selectors.js +++ b/src/redux/selectors.js @@ -42,9 +42,13 @@ export const getCurrentSkinCount = createSelector( ); export const getFileToUpload = (state) => { - return Object.values(state.fileUploads).find( - (file) => file.status === "MISSING" - ); + return Object.values(state.fileUploads).find((file) => { + if (file == null) { + console.warn("Got a nullish file"); + return false; + } + return file.status === "MISSING"; + }); }; export const getUploadViewOpen = (state) => { @@ -200,7 +204,5 @@ export function getAreReadyToCheckMissingUploads(state) { } export function getUploadedFilesMd5s(state) { - return Object.values(state.fileUploads) - .map((file) => file.md5) - .filter(Boolean); + return Object.values(state.fileUploads).map((file) => file.md5); } diff --git a/src/uploadUtils.js b/src/uploadUtils.js index 16b8a9dc..2b5bbb0a 100644 --- a/src/uploadUtils.js +++ b/src/uploadUtils.js @@ -27,7 +27,7 @@ export async function hashFile(file) { } /* SKIN UPLOAD UTILS */ -const validSkinFilename = /(\.wsz)|(\.zip)$/i; +const validSkinFilename = /(\.wsz)|(\.zip)|(.wal)$/i; export function isValidSkinFilename(filename) { return validSkinFilename.test(filename); diff --git a/src/utils.js b/src/utils.js index 1f4fbe78..dd50c6a7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -8,6 +8,10 @@ export function skinUrlFromHash(hash) { return `${SKIN_CDN}/skins/${hash}.wsz`; } +export function museumUrlFromHash(hash) { + return `/skin/${hash}`; +} + export function getWindowSize() { var w = window, d = document, From 0464d1ba3b2b96344c182df0a7c888946f033304 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 14 Sep 2020 11:52:06 -0700 Subject: [PATCH 4/7] Typo --- src/UploadGrid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UploadGrid.js b/src/UploadGrid.js index 361da52a..a8c5f1f4 100644 --- a/src/UploadGrid.js +++ b/src/UploadGrid.js @@ -25,7 +25,7 @@ function DropTarget({ getInputProps }) {
Drop Skins Here

- We'll analyzed them in your browser to find
+ We'll analyze them in your browser to find
any that are missing from the museum
From 4950e06b1cc986efe53ba166ee7b7394158a5fcb Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 14 Sep 2020 11:53:30 -0700 Subject: [PATCH 5/7] Add missing paren --- src/UploadGrid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UploadGrid.js b/src/UploadGrid.js index a8c5f1f4..e639e063 100644 --- a/src/UploadGrid.js +++ b/src/UploadGrid.js @@ -141,7 +141,7 @@ function UploadGrid({ getInputProps, isDragActive }) { />
file.status === "NOT_CLASSIC_SKIN"} render={(file) => file.file.name} /> From 0107572757f915b14a8c5f6372269cc54f2c5c8e Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 14 Sep 2020 13:29:40 -0700 Subject: [PATCH 6/7] Alert if we have an API error --- src/redux/epics.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/redux/epics.js b/src/redux/epics.js index 1d1de004..a4debae7 100644 --- a/src/redux/epics.js +++ b/src/redux/epics.js @@ -244,7 +244,15 @@ const checkIfUploadsAreMissingEpic = (actions, state) => .pipe( map(({ missing, found }) => Actions.gotMissingAndFoundMd5s({ missing, found }) - ) + ), + catchError((e) => { + console.error("Failed fo check missing skins", e); + // TODO: A real error here. + alert( + "Sorry. We had a problem checking which files are missing. Please contact jordan@jordaneldredge.com for help." + ); + return of(Actions.closeUploadFiles()); + }) ) .pipe(takeUntilAction(actions, "CLOSE_UPLOAD_FILES")); }) From b23b60812bde430e64fcb43a391230bf699b4d9b Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 14 Sep 2020 14:04:31 -0700 Subject: [PATCH 7/7] Check more cases --- src/redux/epics.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/redux/epics.js b/src/redux/epics.js index a4debae7..cf931dbf 100644 --- a/src/redux/epics.js +++ b/src/redux/epics.js @@ -234,7 +234,9 @@ const checkIfUploadsAreMissingEpic = (actions, state) => actions.pipe( filter((action) => { return ( - action.type === "GOT_FILE_MD5" && + (action.type === "GOT_FILE_MD5" || + action.type === "NOT_CLASSIC_SKIN" || + action.type === "INVALID_FILE_EXTENSION") && Selectors.getAreReadyToCheckMissingUploads(state.value) ); }),