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}

+ {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" }; }