// グローバルな進行状況管理変数 let total = 0; let complete = 0; let state = 0; let currentProgress = 0; let progressHandler = (state, progress, complete, total) => {}; // 進行状況ハンドラを設定する関数 export const setProgressHandler = newHandler => { progressHandler = newHandler; progressHandler(state, currentProgress, complete, total); }; // 進行状況更新をキューに入れる関数 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); }; // 進行状況を管理する関数 const fetchWithProgress = url => { setState(1); // 使用するCORSプロキシURL const proxyUrl = `https://public-soiz1-cors-proxy.hf.space/?url=` + encodeURIComponent(url); // ここで公開されているプロキシを使用 //const proxyUrl = `https://cors-anywhere.herokuapp.com/` + encodeURIComponent(url); // const proxyUrl = `https://cors-proxy.htmldriven.com/?url=` + encodeURIComponent(url); // 別のプロキシURLを使いたい場合はこちらを使用 // const proxyUrl = `https://api.allorigins.win/raw?url=` + encodeURIComponent(url); // 別のプロキシURLを使いたい場合はこちらを使用 return new Promise((resolve, reject) => { 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', proxyUrl); xhr.send(); }); }; // fetchのオーバーライド 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); }; // Web Workerのメッセージ処理 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); }; }