- {canUpload &&
}
-
-
-
- | Action |
- Filename |
- Status |
-
-
-
- {Object.values(files).map((file, i) => {
- return ;
- })}
-
-
+
+ {isDragActive || Object.keys(files).length === 0 ? (
+
+ ) : (
+
+
+
+ {`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}
+ />
+
+ )}
);
}
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 0ee6c46d..79afdd0c 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -2,8 +2,10 @@ 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 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 cf098a4b..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";
@@ -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/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..cf931dbf 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");
@@ -231,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)
);
}),
@@ -241,7 +246,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"));
})
@@ -251,7 +264,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 3be4ae0b..de273086 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,
@@ -126,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]) => {
@@ -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..fbd995ae 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;
@@ -42,13 +42,18 @@ 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 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 +150,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) {
@@ -196,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,
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"