diff --git a/.jshintrc b/.jshintrc
index ce7eed7d7..897804370 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,11 +1,9 @@
{
"browser": true,
- "jquery": true,
- "mootools": true,
- "node": true,
"undef": true,
"sub": true,
"newcap": false,
+ "esversion": 9,
"indent": 4,
"globals": {
"alert": true,
diff --git a/tnoodle-ui/src/main/api/tnoodle.api.js b/tnoodle-ui/src/main/api/tnoodle.api.js
index 87cad7c4a..f4ef4fb26 100644
--- a/tnoodle-ui/src/main/api/tnoodle.api.js
+++ b/tnoodle-ui/src/main/api/tnoodle.api.js
@@ -9,7 +9,7 @@ let bestMbldAttemptEndpoint = "/frontend/mbld/best";
let wcaEventsEndpoint = "/frontend/data/events";
let formatsEndpoint = "/frontend/data/formats";
-export const fetchZip = (wcif, mbld, password, translations) => {
+export const fetchZip = (scrambleClient, wcif, mbld, password, translations) => {
let payload = {
wcif,
multiCubes: { requestedScrambles: mbld },
@@ -20,11 +20,20 @@ export const fetchZip = (wcif, mbld, password, translations) => {
payload.zipPassword = password;
}
- return postToTnoodle(zipEndpoint, payload)
- .then((response) => response.blob())
+ let targetMarker = wcif.id;
+
+ return scrambleClient.loadScrambles(zipEndpoint, payload, targetMarker)
+ .then((result) => convertToBlob(result))
.catch((error) => console.error(error));
};
+const convertToBlob = async (result) => {
+ let {contentType, payload} = result;
+ let res = await fetch(`data:${contentType};base64,${payload}`);
+
+ return await res.blob();
+};
+
export const fetchWcaEvents = () => {
return fetch(baseUrl + wcaEventsEndpoint)
.then((response) => response.json())
diff --git a/tnoodle-ui/src/main/api/tnoodle.socket.js b/tnoodle-ui/src/main/api/tnoodle.socket.js
new file mode 100644
index 000000000..29a777e20
--- /dev/null
+++ b/tnoodle-ui/src/main/api/tnoodle.socket.js
@@ -0,0 +1,77 @@
+export class ScrambleClient {
+ constructor(onHandshake, onProgress) {
+ this.onHandshake = onHandshake;
+ this.onProgress = onProgress;
+
+ this.state = SCRAMBLING_STATES.IDLE;
+
+ this.contentType = null;
+ this.resultPayload = null;
+ }
+
+ loadScrambles(endpoint, payload, targetMarker) {
+ let that = this;
+
+ return new Promise(function (resolve, reject) {
+ let ws = new WebSocket(BASE_URL + endpoint);
+
+ ws.onopen = () => {
+ that.state = SCRAMBLING_STATES.INITIATE;
+ ws.send(JSON.stringify(payload));
+ };
+
+ ws.onerror = (err) => {
+ reject(err);
+ };
+
+ ws.onclose = (cls) => {
+ if (that.state === SCRAMBLING_STATES.DONE && cls.wasClean) {
+ let resultObject = {
+ contentType: that.contentType,
+ payload: that.resultPayload
+ };
+
+ resolve(resultObject);
+ } else {
+ reject(cls);
+ }
+ };
+
+ ws.onmessage = (msg) => {
+ if (that.state === SCRAMBLING_STATES.INITIATE) {
+ that.state = SCRAMBLING_STATES.SCRAMBLING;
+
+ let rawPayload = msg.data.toString();
+ let targetPayload = JSON.parse(rawPayload);
+
+ that.onHandshake(targetPayload);
+ } else if (that.state === SCRAMBLING_STATES.SCRAMBLING) {
+ if (msg.data === targetMarker) {
+ that.state = SCRAMBLING_STATES.COMPUTED_TYPE;
+ } else {
+ that.onProgress(msg.data);
+ }
+ } else if (that.state === SCRAMBLING_STATES.COMPUTED_TYPE) {
+ that.state = SCRAMBLING_STATES.COMPUTED_DATA;
+
+ that.contentType = msg.data;
+ } else if (that.state === SCRAMBLING_STATES.COMPUTED_DATA) {
+ that.state = SCRAMBLING_STATES.DONE;
+
+ that.resultPayload = msg.data;
+ }
+ };
+ });
+ }
+}
+
+const BASE_URL = window.location.origin.replace(/^https?:\/\//,'ws://');
+
+const SCRAMBLING_STATES = {
+ IDLE: "IDLE",
+ INITIATE: "INITIATE",
+ SCRAMBLING: "SCRAMBLING",
+ COMPUTED_TYPE: "COMPUTED_TYPE",
+ COMPUTED_DATA: "COMPUTED_DATA",
+ DONE: "DONE"
+};
diff --git a/tnoodle-ui/src/main/components/EventPicker.jsx b/tnoodle-ui/src/main/components/EventPicker.jsx
index ca53a08a3..025b6ae5d 100644
--- a/tnoodle-ui/src/main/components/EventPicker.jsx
+++ b/tnoodle-ui/src/main/components/EventPicker.jsx
@@ -9,11 +9,16 @@ import {
import MbldDetail from "./MbldDetail";
import FmcTranslationsDetail from "./FmcTranslationsDetail";
import "./EventPicker.css";
+import { ProgressBar } from "react-bootstrap";
const mapStateToProps = (store) => ({
editingDisabled: store.editingDisabled,
wcif: store.wcif,
wcaFormats: store.wcaFormats,
+ generatingScrambles: store.generatingScrambles,
+ scramblingProgressTarget: store.scramblingProgressTarget,
+ scramblingProgressCurrent: store.scramblingProgressCurrent,
+ fileZipBlob: store.fileZipBlob
});
const mapDispatchToProps = {
@@ -185,10 +190,38 @@ const EventPicker = connect(
);
};
- render() {
- let wcaEvent = this.props.wcif.events.find(
- (event) => event.id === this.props.event.id
+ maybeShowProgressBar = (rounds) => {
+ let eventId = this.props.event.id;
+
+ let current = this.props.scramblingProgressCurrent[eventId] || 0;
+ let target = this.props.scramblingProgressTarget[eventId];
+
+ if (rounds.length === 0 || !this.props.generatingScrambles || target === undefined) {
+ return;
+ }
+
+ let progress = (current / target) * 100
+ let miniThreshold = 2;
+
+ if (progress === 0) {
+ progress = miniThreshold;
+ }
+
+ return (
+