diff --git a/src/App.js b/src/App.js
index ec74f68c..14ec30f6 100644
--- a/src/App.js
+++ b/src/App.js
@@ -12,7 +12,7 @@ import * as Actions from "./redux/actionCreators";
import { ABOUT_PAGE } from "./constants";
import { useWindowSize, useScrollbarWidth, useActionCreator } from "./hooks";
import { SCREENSHOT_WIDTH, SKIN_RATIO } from "./constants";
-import UploadGrid from "./UploadGrid";
+import UploadGrid from "./upload/UploadGrid";
import Metadata from "./components/Metadata";
import SkinReadme from "./SkinReadme";
import { useDropzone } from "react-dropzone";
diff --git a/src/UploadGrid.js b/src/UploadGrid.js
deleted file mode 100644
index 501e11ef..00000000
--- a/src/UploadGrid.js
+++ /dev/null
@@ -1,215 +0,0 @@
-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 Utils from "./utils";
-import DropTarget from "./DropTarget";
-
-function Section({ files, title, render, open = false }) {
- if (files.length === 0) {
- return null;
- }
- return (
-
-
- {title}
-
-
- {files.map((match, i) => {
- return - {render(match)}
;
- })}
-
-
- );
-}
-
-function useBucketed(filesArr) {
- return useMemo(() => {
- const missing = [];
- const notSkins = [];
- const foundSkins = [];
- filesArr.forEach((file) => {
- switch (file.status) {
- case "MISSING":
- case "UPLOADING":
- case "UPLOAD_FAILED":
- case "ARCHIVED":
- missing.push(file);
- break;
- case "INVALID_FILE_EXTENSION":
- case "INVALID_ARCHIVE":
- notSkins.push(file);
- break;
- case "FOUND":
- foundSkins.push(file);
- break;
- default:
- }
- });
- return { missing, notSkins, foundSkins };
- }, [filesArr]);
-}
-
-function Plural({ count, single, plural }) {
- return count === 1 ? single : plural;
-}
-
-function Inner({ files }) {
- const tryToUploadAllFiles = useActionCreator(Actions.tryToUploadAllFiles);
- const filesArr = Object.values(files);
- const { missing, notSkins, foundSkins } = useBucketed(filesArr);
-
- if (filesArr.some((file) => file.status === "NEW")) {
- return (
-
- Analyzing {filesArr.length.toLocaleString()}{" "}
-
- ...
-
- );
- }
-
- return (
- <>
-
- You have {missing.length.toLocaleString()}{" "}
- that we
- are missing!
-
- {filesArr.some((file) => file.status === "MISSING") && (
-
-
-
- )}
-
-
-
- | Status |
- Filename |
-
-
-
- {missing
- .map((file) => {
- const fileName = {file.file.name};
- switch (file.status) {
- case "MISSING":
- return (
- <>
- |
- {fileName} |
- >
- );
- case "UPLOADING":
- return (
- <>
- 🚀 Uploading... |
- {fileName} |
- >
- );
- case "UPLOAD_FAILED":
- return (
- <>
- ❌ Upload Failed |
- {fileName} |
- >
- );
- case "ARCHIVED":
- return (
- <>
- ✅ Added! |
-
- {file.skinType === "CLASSIC" ? (
-
- {fileName}
-
- ) : file.skinType === "MODERN" ? (
- <>
- {fileName} (Note: Modern skins are not yet visible
- in the museum)
- >
- ) : // TODO: Throw?
- null}
- |
- >
- );
- default:
- console.error(`Unexpected file status: ${file.status}`);
- return null;
- }
- })
- .map((rows, i) => (
- {rows}
- ))}
-
-
-
- Files that are not valid skins ({notSkins.length.toLocaleString()})
- >
- }
- render={(file) => {file.file.name}}
- />
-
- Already in the museum ({foundSkins.length.toLocaleString()})>
- }
- render={(file) => {
- if (file.skinType === "MODERN") {
- return (
- <>
- {file.file.name} (Note: Modern skins are not yet
- visible in the museum)
- >
- );
- }
- return (
-
- {file.file.name}
-
- );
- }}
- />
- >
- );
-}
-
-function UploadGrid({ getInputProps, isDragActive, ...props }) {
- const files = useSelector((state) => state.fileUploads);
- return (
-
- {isDragActive || Object.keys(files).length === 0 ? (
-
- ) : (
-
-
-
- )}
-
- );
-}
-export default UploadGrid;
diff --git a/src/components/Metadata.js b/src/components/Metadata.js
index dfc26651..2a6252a1 100644
--- a/src/components/Metadata.js
+++ b/src/components/Metadata.js
@@ -2,7 +2,7 @@ import React, { useState } from "react";
import DownloadLink from "./DownloadLink";
import * as Utils from "../utils";
import LinkInput from "./LinkInput";
-import { API_URL } from "../constants";
+// import { API_URL } from "../constants";
// import * as Actions from "../redux/actionCreators";
import * as Selectors from "../redux/selectors";
import * as Actions from "../redux/actionCreators";
diff --git a/src/redux/epics.js b/src/redux/epics.js
index e09df907..d43bf57e 100644
--- a/src/redux/epics.js
+++ b/src/redux/epics.js
@@ -18,7 +18,7 @@ import {
import { search } from "../algolia";
import queryParser from "../queryParser";
import { API_URL, CHUNK_SIZE } from "../constants";
-import * as UploadUtils from "../uploadUtils";
+import * as UploadUtils from "../upload/uploadUtils";
const urlChangedEpic = (actions) =>
actions.pipe(
diff --git a/src/upload/UploadGrid.js b/src/upload/UploadGrid.js
new file mode 100644
index 00000000..513a7630
--- /dev/null
+++ b/src/upload/UploadGrid.js
@@ -0,0 +1,128 @@
+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 DropTarget from "../DropTarget";
+import UploadSection from "./UploadSection";
+
+function useBucketed(filesArr) {
+ return useMemo(() => {
+ const missing = [];
+ const notSkins = [];
+ const foundSkins = [];
+ filesArr.forEach((file) => {
+ switch (file.status) {
+ case "MISSING":
+ case "UPLOADING":
+ case "UPLOAD_FAILED":
+ case "ARCHIVED":
+ missing.push(file);
+ break;
+ case "INVALID_FILE_EXTENSION":
+ case "INVALID_ARCHIVE":
+ notSkins.push(file);
+ break;
+ case "FOUND":
+ foundSkins.push(file);
+ break;
+ default:
+ }
+ });
+ return { missing, notSkins, foundSkins };
+ }, [filesArr]);
+}
+
+function Plural({ count, single, plural }) {
+ return count === 1 ? single : plural;
+}
+
+function Inner({ files }) {
+ const tryToUploadAllFiles = useActionCreator(Actions.tryToUploadAllFiles);
+ const filesArr = Object.values(files);
+ const { missing, notSkins, foundSkins } = useBucketed(filesArr);
+
+ const analyzing = filesArr.some((file) => file.status === "NEW");
+
+ if (analyzing) {
+ return (
+
+ Analyzing {filesArr.length.toLocaleString()}{" "}
+
+ ...
+
+ );
+ }
+
+ const stillHaveFilesToUpload = filesArr.some(
+ (file) => file.status === "MISSING" || file.status === "UPLOADING"
+ );
+
+ const filesToUpload = missing.length;
+
+ const getTitle = () => {
+ if (filesToUpload > 0) {
+ if (stillHaveFilesToUpload) {
+ return (
+ <>
+ Found {filesToUpload.toLocaleString()}{" "}
+ to
+ upload
+ >
+ );
+ } else {
+ return `Thanks for your contribution!`;
+ }
+ }
+ return `No missing skins found`;
+ };
+
+ return (
+
+
{getTitle()}
+
+ Upload All
+
+ )
+ }
+ files={missing}
+ />
+
+
+
+ );
+}
+
+function UploadGrid({ getInputProps, isDragActive, ...props }) {
+ const files = useSelector((state) => state.fileUploads);
+ return (
+
+ {isDragActive || Object.keys(files).length === 0 ? (
+
+ ) : (
+
+
+
+ )}
+
+ );
+}
+export default UploadGrid;
diff --git a/src/upload/UploadRow.js b/src/upload/UploadRow.js
new file mode 100644
index 00000000..aab6e526
--- /dev/null
+++ b/src/upload/UploadRow.js
@@ -0,0 +1,117 @@
+import React from "react";
+import * as Utils from "../utils";
+
+function Row({ name, loading, right, complete }) {
+ return (
+
+ {(loading != null || complete) && (
+
+ )}
+
+ {name}
+ {right && (
+
+ {right}
+
+ )}
+
+
+ );
+}
+
+function SkinLink({ md5, children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+// TODO: This is a component
+function getRight(file) {
+ switch (file.status) {
+ case "ARCHIVED":
+ switch (file.skinType) {
+ case "MODERN":
+ return "archived";
+ case "CLASSIC":
+ return added;
+ default:
+ throw new Error(`Unknown skinType "${file.skinType}"`);
+ }
+ case "FOUND":
+ switch (file.skinType) {
+ case "MODERN":
+ return "archived";
+ case "CLASSIC":
+ return view;
+ default:
+ throw new Error(`Unknown skinType "${file.skinType}"`);
+ }
+ case "MISSING":
+ return "missing";
+ case "UPLOADING":
+ return "uploading...";
+ case "UPLOAD_FAILED":
+ return "upload failed";
+ case "INVALID_ARCHIVE":
+ return "corrupt";
+ case "INVALID_FILE_EXTENSION":
+ return "not skin";
+ default:
+ return file.status;
+ }
+}
+
+function UploadRow({ file }) {
+ return (
+
+ );
+}
+
+export default UploadRow;
diff --git a/src/upload/UploadSection.js b/src/upload/UploadSection.js
new file mode 100644
index 00000000..feffb179
--- /dev/null
+++ b/src/upload/UploadSection.js
@@ -0,0 +1,34 @@
+import React from "react";
+import UploadRow from "./UploadRow";
+
+function UploadSection({ files, title, extra }) {
+ if (files.length === 0) {
+ return null;
+ }
+ return (
+
+
+
+ {title} ({files.length.toLocaleString()})
+
+
{extra}
+
+ {files.map((file) => (
+
+ ))}
+
+ );
+}
+
+export default UploadSection;
diff --git a/src/uploadUtils.js b/src/upload/uploadUtils.js
similarity index 92%
rename from src/uploadUtils.js
rename to src/upload/uploadUtils.js
index 916ab860..c06187ef 100644
--- a/src/uploadUtils.js
+++ b/src/upload/uploadUtils.js
@@ -1,4 +1,4 @@
-import { API_URL } from "./constants";
+import { API_URL } from "../constants";
export async function upload(file) {
const formData = new FormData();
formData.append("skin", file, file.name);
@@ -22,7 +22,7 @@ export async function checkMd5sAreMissing(md5s) {
}
export async function hashFile(file) {
- const { hashFile: hasher } = await import("./hashFile");
+ const { hashFile: hasher } = await import("../hashFile");
return hasher(file);
}