Spaces:
Running
Running
// This file implements some extremely terrible tricks to monitor project loading progress. | |
// Please don't use this as a reference for "good JS code" | |
let total = 0; | |
let complete = 0; | |
// 0 - none | |
// 1 - load json | |
// 2 - load assets | |
let state = 0; | |
let currentProgress = 0; | |
let progressHandler = (state, progress, complete, total) => {}; | |
export const setProgressHandler = newHandler => { | |
progressHandler = newHandler; | |
progressHandler(state, currentProgress, complete, total); | |
}; | |
let progressHandlerTimeout = null; | |
const fireProgressHandler = () => { | |
progressHandler(state, currentProgress, complete, total); | |
progressHandlerTimeout = null; | |
}; | |
const queueProgressHandlerUpdate = () => { | |
if (progressHandlerTimeout === null) { | |
progressHandlerTimeout = requestAnimationFrame(fireProgressHandler); | |
} | |
}; | |
const setProgress = progress => { | |
if (progress < 0) { | |
progress = 0; | |
} | |
if (progress > 1) { | |
progress = 1; | |
} | |
currentProgress = progress; | |
queueProgressHandlerUpdate(); | |
}; | |
const setState = newState => { | |
if (state === newState) { | |
return; | |
} | |
state = newState; | |
complete = 0; | |
total = 0; | |
setProgress(0); | |
}; | |
export const fetchWithProgress = url => { | |
setState(1); | |
return new Promise((resolve, reject) => { | |
// fetch() does not support progress, so we use XMLHttpRequest | |
const xhr = new XMLHttpRequest(); | |
xhr.responseType = 'blob'; | |
xhr.onload = () => { | |
resolve(new Response(xhr.response, { | |
status: xhr.status, | |
statusText: xhr.statusText | |
})); | |
}; | |
xhr.onloadend = () => setProgress(1); | |
xhr.onerror = () => reject(new Error('[tw-progress-monitor] xhr failed with status' + xhr.status)); | |
xhr.onprogress = e => { | |
if (e.lengthComputable) { | |
setProgress(e.loaded / e.total); | |
} | |
}; | |
xhr.open('GET', url); | |
xhr.send(); | |
}); | |
}; | |
// Scratch uses fetch() to download the project JSON, so we override it to monitor when the project is being downloaded. | |
const originalFetch = window.fetch; | |
window.fetch = (url, opts) => { | |
const isGET = typeof opts === 'object' && opts && opts.method === 'GET'; | |
const isProjectURL = typeof url === 'string' && /^https:\/\/projects\.scratch\.mit\.edu\/\d+$/.test(url); | |
if (isGET && isProjectURL) { | |
return fetchWithProgress(url); | |
} | |
return originalFetch(url, opts); | |
}; | |
const handleWorkerMessage = e => { | |
const data = e.data; | |
if (Array.isArray(data)) { | |
complete += data.length; | |
setProgress(complete / total); | |
} | |
}; | |
if (window.Worker) { | |
let downloadWorker = null; | |
const originalPostMessage = window.Worker.prototype.postMessage; | |
window.Worker.prototype.postMessage = function (message) { | |
if (downloadWorker === null) { | |
if (message && message.url && message.id && message.options) { | |
downloadWorker = this; | |
downloadWorker.addEventListener('message', handleWorkerMessage); | |
} | |
} | |
if (downloadWorker === this) { | |
setState(2); | |
total++; | |
} | |
originalPostMessage.call(this, message); | |
}; | |
} | |