diff --git a/src/App.js b/src/App.js
index 14ec30f6..a37e170a 100644
--- a/src/App.js
+++ b/src/App.js
@@ -9,13 +9,14 @@ 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 { ABOUT_PAGE, REVIEW_PAGE } from "./constants";
import { useWindowSize, useScrollbarWidth, useActionCreator } from "./hooks";
import { SCREENSHOT_WIDTH, SKIN_RATIO } from "./constants";
import UploadGrid from "./upload/UploadGrid";
import Metadata from "./components/Metadata";
import SkinReadme from "./SkinReadme";
import { useDropzone } from "react-dropzone";
+import ReviewPage from "./ReviewPage";
const getTableDimensions = (windowWidth, scale) => {
const columnCount = Math.floor(windowWidth / (SCREENSHOT_WIDTH * scale));
@@ -51,6 +52,10 @@ function App(props) {
const fileExplorerOpen = useSelector(Selectors.getFileExplorerOpen);
+ if (props.page === REVIEW_PAGE) {
+ return ;
+ }
+
return (
diff --git a/src/ReviewPage.js b/src/ReviewPage.js
new file mode 100644
index 00000000..8714f744
--- /dev/null
+++ b/src/ReviewPage.js
@@ -0,0 +1,116 @@
+import * as Utils from "./utils";
+import { API_URL } from "./constants";
+import React, { useState, useEffect } from "react";
+
+function warmScreenshotImage(hash) {
+ const screenshotUrl = Utils.screenshotUrlFromHash(hash);
+ new Image().src = screenshotUrl;
+}
+
+function useQueuedSkin() {
+ const [queue, setQueue] = useState([]);
+ function remove() {
+ setQueue((queue) => {
+ const newQueue = [...queue];
+ newQueue.shift();
+ return newQueue;
+ });
+ }
+
+ useEffect(() => {
+ if (queue.length > 10) {
+ return;
+ }
+ let canceled = false;
+ fetch(`${API_URL}/to_review`, {
+ mode: "cors",
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status === 403) {
+ window.location = `${API_URL}/auth`;
+ }
+ return response;
+ })
+ .then((response) => response.json())
+ .then((response) => {
+ if (canceled) {
+ return;
+ }
+ warmScreenshotImage(response.md5);
+ setQueue((queue) => [...queue, response]);
+ });
+
+ return () => (canceled = true);
+ }, [queue]);
+ const skin = queue[0];
+
+ return [skin || null, remove];
+}
+
+export default function ReviewPage() {
+ const [skin, remove] = useQueuedSkin();
+ async function approve() {
+ remove();
+ const response = await fetch(`${API_URL}/skins/${skin.md5}/approve`, {
+ method: "POST",
+ mode: "cors",
+ credentials: "include",
+ });
+ if (response.status === 403) {
+ window.location = `${API_URL}/auth`;
+ }
+ }
+ async function reject() {
+ remove();
+ const response = await fetch(`${API_URL}/skins/${skin.md5}/reject`, {
+ method: "POST",
+ mode: "cors",
+ credentials: "include",
+ });
+
+ if (response.status === 403) {
+ window.location = `${API_URL}/auth`;
+ }
+ }
+
+ useEffect(() => {
+ if (skin == null) {
+ return;
+ }
+ function handleKeypress(e) {
+ switch (e.key) {
+ case "ArrowUp":
+ approve();
+ break;
+ case "ArrowDown":
+ reject();
+ break;
+ default:
+ // noop
+ }
+ }
+ document.body.addEventListener("keydown", handleKeypress);
+ return () => {
+ document.body.removeEventListener("keydown", handleKeypress);
+ };
+ });
+ if (skin == null) {
+ return Loading...
;
+ }
+
+ return (
+
+
{skin.filename}
+
})
+
+
+
+
Press up arrow to approve or down arrow to reject.
+
+ );
+}
diff --git a/src/constants.js b/src/constants.js
index 0d54b2e6..99bec48c 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -3,12 +3,14 @@ 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 REVIEW_PAGE = "REVIEW_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";
+// export const API_URL = "https://dev.webamp.org";
export const HEADING_HEIGHT = 46;
export const CHUNK_SIZE = 300;
export const SENTRY_DSN =
diff --git a/src/redux/actionCreators.js b/src/redux/actionCreators.js
index 9a3344e8..f2975e5e 100644
--- a/src/redux/actionCreators.js
+++ b/src/redux/actionCreators.js
@@ -1,4 +1,4 @@
-import { ABOUT_PAGE, UPLOAD_PAGE } from "../constants";
+import { ABOUT_PAGE, UPLOAD_PAGE, REVIEW_PAGE } from "../constants";
export function closeModal() {
return { type: "CLOSE_MODAL" };
}
@@ -125,6 +125,10 @@ export function requestedAboutPage() {
return { type: "REQUESTED_PAGE", page: ABOUT_PAGE };
}
+export function requestedReviewPage() {
+ return { type: "REQUESTED_PAGE", page: REVIEW_PAGE };
+}
+
export function requestedUploadPage() {
return { type: "REQUESTED_PAGE", page: UPLOAD_PAGE };
}
diff --git a/src/redux/epics.js b/src/redux/epics.js
index 7f2004c5..fb597050 100644
--- a/src/redux/epics.js
+++ b/src/redux/epics.js
@@ -33,6 +33,9 @@ const urlChangedEpic = (actions) =>
if (action.location.pathname === "/upload/") {
return of(Actions.requestedUploadPage());
}
+ if (action.location.pathname === "/review/") {
+ return of(Actions.requestedReviewPage());
+ }
const params = new URLSearchParams(action.location.search);
const query = params != null && params.get("query");
diff --git a/src/redux/selectors.js b/src/redux/selectors.js
index 338115a2..6d5e9ab9 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, UPLOAD_PAGE } from "../constants";
+import { ABOUT_PAGE, UPLOAD_PAGE, REVIEW_PAGE } from "../constants";
export function getSelectedSkinHash(state) {
return state.selectedSkinHash;
@@ -162,6 +162,9 @@ export const getRouteData = createSelector(
getPermalinkUrlFromHash,
skinData
) => {
+ if (activeContentPage === REVIEW_PAGE) {
+ return { url: "/review/", title: "Review" };
+ }
if (activeContentPage === ABOUT_PAGE) {
return { url: "/about/", title: "About" };
}