diff --git a/src/web/App.mjs b/src/web/App.mjs index 20c25ad2..a6b21627 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -120,7 +120,6 @@ class App { * * @param {boolean} [step] - Set to true if we should only execute one operation instead of the * whole recipe. - * @param input - The inputs to bake */ bake(step=false, input) { // if (this.baking) return; @@ -132,7 +131,6 @@ class App { this.manager.recipe.updateBreakpointIndicator(false); this.manager.worker.bake( - input, // The user's input this.getRecipeConfig(), // The configuration of the recipe this.options, // Options set by the user this.progress, // The current position in the recipe diff --git a/src/web/InputWaiter.mjs b/src/web/InputWaiter.mjs index f66cf9be..bb64a785 100644 --- a/src/web/InputWaiter.mjs +++ b/src/web/InputWaiter.mjs @@ -215,9 +215,6 @@ class InputWaiter { case "terminateLoaderWorker": this.removeLoaderWorker(this.getLoaderWorker(r.data)); break; - case "allInputs": - this.app.bake(false, r.data); - break; case "refreshTabs": this.refreshTabs(r.data.nums, r.data.activeTab); break; @@ -234,7 +231,7 @@ class InputWaiter { this.showLoadingInfo(r.data); break; case "setInput": - this.set(r.data, true); + this.set(r.data.inputObj, r.data.silent); break; case "inputAdded": this.inputAdded(r.data.changeTab, r.data.inputNum); @@ -242,6 +239,12 @@ class InputWaiter { case "addInputs": this.addInputs(r.data); break; + case "queueInput": + this.manager.worker.queueInput(r.data); + break; + case "bake": + this.app.bake(false); + break; default: log.error(`Unknown action ${r.action}.`); } @@ -281,10 +284,11 @@ class InputWaiter { * @param {boolean} [silent=false] */ set(inputData, silent=false) { - const inputText = document.getElementById("input-text"); const activeTab = this.getActiveTab(); if (inputData.inputNum !== activeTab) return; + const inputText = document.getElementById("input-text"); + if (typeof inputData.input === "string") { inputText.value = inputData.input; // close file @@ -370,16 +374,19 @@ class InputWaiter { fileLoaded.textContent = progress + "%"; if (progress < 100) { - // setTimeout(function() { - // this.inputWorker.postMessage({ - // action: "getInputProgress", - // data: activeTab - // }); - // }.bind(this), 100); + setTimeout(function() { + this.inputWorker.postMessage({ + action: "getInputProgress", + data: activeTab + }); + }.bind(this), 100); } else { this.inputWorker.postMessage({ action: "setInput", - data: inputNum + data: { + inputNum: inputNum, + silent: true + } }); } } @@ -531,21 +538,35 @@ class InputWaiter { } /** - * Load files from the UI into the inputWorker, creating tabs if needed + * Load files from the UI into the inputWorker * * @param files */ loadUIFiles(files) { const numFiles = files.length; + const activeTab = this.getActiveTab(); log.debug(`Loading ${numFiles} files.`); // Show something in the UI to make it clear we're loading files - this.inputWorker.postMessage({ - action: "loadUIFiles", - data: files + this.hideLoadingMessage(); + this.showLoadingInfo({ + pending: numFiles, + loading: 0, + loaded: 0, + total: numFiles, + activeProgress: { + inputNum: activeTab, + progress: 0 + } }); - this.hideLoadingMessage(); + this.inputWorker.postMessage({ + action: "loadUIFiles", + data: { + files: files, + activeTab: activeTab + } + }); } /** @@ -601,7 +622,14 @@ class InputWaiter { // setinputInfo /** * Display the loaded files information in the input header - * @param loadedData + * @param {object} loadedData + * @param {number} loadedData.pending + * @param {number} loadedData.loading + * @param {number} loadedData.loaded + * @param {number} loadedData.total + * @param {object} loadedData.activeProgress + * @param {number} loadedData.activeProgress.inputNum + * @param {number} loadedData.activeProgress.progress */ showLoadingInfo(loadedData) { const pending = loadedData.pending; @@ -629,6 +657,15 @@ class InputWaiter { document.getElementById("input-files-info").innerHTML = msg; this.updateFileProgress(loadedData.activeProgress.inputNum, loadedData.activeProgress.progress); + + if (loaded < total) { + setTimeout(function() { + this.inputWorker.postMessage({ + action: "getLoadProgress", + data: this.getActiveTab() + }); + }.bind(this), 100); + } } // displayTabInfo // simple getInput for each tab @@ -754,7 +791,10 @@ class InputWaiter { } else { this.inputWorker.postMessage({ action: "setInput", - data: inputNum + data: { + inputNum: inputNum, + silent: true + } }); } @@ -890,10 +930,19 @@ class InputWaiter { * @param {array} inputNums */ addInputs(inputNums) { + const activeTab = this.getActiveTab(); for (let i = 0; i < inputNums.length; i++) { + if (inputNums[i] === activeTab) continue; this.manager.output.addOutput(inputNums[i], false); } - this.changeTab(inputNums[inputNums.length - 1], this.app.options.syncTabs); + // this.changeTab(inputNums[inputNums.length - 1], this.app.options.syncTabs); + this.inputWorker.postMessage({ + action: "refreshTabs", + data: { + inputNum: this.getActiveTab(), + direction: "left" + } + }); } /** @@ -959,6 +1008,14 @@ class InputWaiter { } }); } + + /** + * Handler for go to tab button clicked + */ + goToTab() { + const tabNum = parseInt(window.prompt("Enter tab number:", this.getActiveTab().toString()), 10); + this.changeTab(tabNum, this.app.options.syncTabs); + } } export default InputWaiter; diff --git a/src/web/InputWorker.mjs b/src/web/InputWorker.mjs index b5b9fc04..38f8b2a3 100644 --- a/src/web/InputWorker.mjs +++ b/src/web/InputWorker.mjs @@ -13,6 +13,9 @@ self.pendingFiles = []; self.inputs = {}; self.loaderWorkerPorts = []; self.currentInputNum = 1; +self.numInputs = 0; +self.pendingInputs = 0; +self.loadingInputs = 0; /** * Respond to message from parent thread. @@ -82,21 +85,10 @@ self.addEventListener("message", function(e) { }); self.getLoadProgress = function(inputNum) { - const inputNums = Object.keys(self.inputs); - const total = inputNums.length; + const total = self.numInputs; const pending = self.pendingFiles.length; - let loaded = 0; - let loading = 0; - - for (let i = 0; i < inputNums.length; i++) { - switch (self.inputs[inputNums[i]].status) { - case "loading": - loading++; - break; - case "loaded": - loaded++; - } - } + const loading = self.loadingInputs; + const loaded = total - pending - loading; self.postMessage({ action: "loadingInfo", @@ -111,12 +103,6 @@ self.getLoadProgress = function(inputNum) { } } }); - - if (loaded < total) { - setTimeout(function(inputNum) { - self.getLoadProgress(inputNum); - }, 100); - } }; self.autoBake = function(inputNum) { @@ -127,17 +113,19 @@ self.autoBake = function(inputNum) { inputData = inputData.fileBuffer; } self.postMessage({ - action: "allInputs", - data: [{ + action: "queueInput", + data: { input: inputData, inputNum: parseInt(inputNum, 10) - }] + } + }); + self.postMessage({ + action: "bake" }); } }; self.getAllInputs = function() { - const inputs = []; const inputNums = Object.keys(self.inputs); for (let i = 0; i < inputNums.length; i++) { @@ -146,16 +134,17 @@ self.getAllInputs = function() { if (typeof inputData !== "string") { inputData = inputData.fileBuffer; } - inputs.push({ - input: inputData, - inputNum: inputNums[i] + self.postMessage({ + action: "queueInput", + data: { + input: inputData, + inputNum: inputNums[i] + } }); } } - self.postMessage({ - action: "allInputs", - data: inputs + action: "bake" }); }; @@ -165,15 +154,11 @@ self.getInputObj = function(inputNum) { }; self.getInputValue = function(inputNum) { - for (let i = 0; i < self.inputs.length; i++) { - if (self.inputs[i].inputNum === inputNum) { - if (self.inputs[i].status === "loaded") { - let inputData = self.inputs[i].data; - if (typeof inputData !== "string") { - inputData = inputData.fileBuffer; - } - return inputData; - } + if (self.inputs[inputNum]) { + if (typeof self.inputs[inputNum].data === "string") { + return self.inputs[inputNum].data; + } else { + return self.inputs[inputNum].fileBuffer; } } return ""; @@ -308,7 +293,9 @@ self.updateTabHeader = function(inputNum) { }); }; -self.setInput = function(inputNum) { +self.setInput = function(inputData) { + const inputNum = inputData.inputNum; + const silent = inputData.silent; const input = self.getInputObj(inputNum); if (input === undefined || input === null) return; @@ -327,7 +314,10 @@ self.setInput = function(inputNum) { self.postMessage({ action: "setInput", - data: inputObj + data: { + inputObj: inputObj, + silent: silent + } }); self.getInputProgress(inputNum); }; @@ -426,12 +416,29 @@ self.handleLoaderMessage = function(e) { inputNum = r.inputNum; } + if (r.hasOwnProperty("error")) { + self.updateInputStatus(r.inputNum, "error"); + self.updateInputProgress(r.inputNum, 0); + + log.error(r.error); + self.loadingInputs--; + + self.terminateLoaderWorker(r.id); + self.activateLoaderWorker(); + + return; + } + if (r.hasOwnProperty("fileBuffer")) { log.debug(`Input file ${inputNum} loaded.`); + self.loadingInputs--; self.updateInputValue({ inputNum: inputNum, value: r.fileBuffer }); + + self.setInput({inputNum: inputNum, silent: false}); + const idx = self.getLoaderWorkerIdx(r.id); self.loadNextFile(idx); } else if (r.hasOwnProperty("progress")) { @@ -450,6 +457,7 @@ self.loadNextFile = function(workerIdx) { const nextFile = self.pendingFiles.splice(0, 1)[0]; self.loaderWorkerPorts[workerIdx].inputNum = nextFile.inputNum; + self.loadingInputs++; port.postMessage({ action: "loadInput", data: { @@ -484,16 +492,31 @@ self.terminateLoaderWorker = function(id) { /** * Loads files using LoaderWorkers + * + * @param {object} filesData + * @param filesData.files + * @param {number} filesData.activeTab */ -self.loadFiles = function(files) { +self.loadFiles = function(filesData) { + const files = filesData.files; + const activeTab = filesData.activeTab; let lastInputNum = -1; const inputNums = []; for (let i = 0; i < files.length; i++) { - lastInputNum = self.addInput(false, "file", { - name: files[i].name, - size: files[i].size.toLocaleString(), - type: files[i].type || "unknown" - }); + if (i === 0 && self.getInputValue(activeTab) === "") { + self.removeInput(activeTab); + lastInputNum = self.addInput(false, "file", { + name: files[i].name, + size: files[i].size.toLocaleString(), + type: files[i].type || "unknown" + }, activeTab); + } else { + lastInputNum = self.addInput(false, "file", { + name: files[i].name, + size: files[i].size.toLocaleString(), + type: files[i].type || "unknown" + }); + } inputNums.push(lastInputNum); self.pendingFiles.push({ @@ -526,9 +549,10 @@ self.loadFiles = function(files) { * @param {string} fileData.name * @param {string} fileData.size * @param {string} fileData.type + * @param {number} inputNum */ -self.addInput = function(changeTab=false, type, fileData={name: "unknown", size: "unknown", type: "unknown"}) { - const inputNum = self.currentInputNum++; +self.addInput = function(changeTab=false, type, fileData={name: "unknown", size: "unknown", type: "unknown"}, inputNum = self.currentInputNum++) { + self.numInputs++; const newInputObj = { inputNum: inputNum }; @@ -571,15 +595,24 @@ self.addInput = function(changeTab=false, type, fileData={name: "unknown", size: self.removeInput = function(removeInputData) { const inputNum = removeInputData.inputNum; const refreshTabs = removeInputData.refreshTabs; + self.numInputs--; delete self.inputs[inputNum]; for (let i = 0; i < self.loaderWorkerPorts.length; i++) { if (self.loaderWorkerPorts[i].inputNum === inputNum) { + self.loadingInputs--; self.terminateLoaderWorker(self.loaderWorkerPorts[i].id); } } + for (let i = 0; i < self.pendingFiles.length; i++) { + if (self.pendingFiles[i].inputNum === inputNum) { + self.pendingFiles.splice(i, 1); + break; + } + } + if (refreshTabs) { self.refreshTabs(inputNum, "left"); } diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 2834575a..83b316ee 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -160,7 +160,7 @@ class Manager { document.getElementById("btn-new-tab").addEventListener("click", this.input.addInput.bind(this.input)); document.getElementById("btn-previous-input-tab").addEventListener("click", this.input.changeTabLeft.bind(this.input)); document.getElementById("btn-next-input-tab").addEventListener("click", this.input.changeTabRight.bind(this.input)); - // document.getElementById("btn-go-to-input-tab").addEventListener("click", this.input.goToTab.bind(this.input)); + document.getElementById("btn-go-to-input-tab").addEventListener("click", this.input.goToTab.bind(this.input)); // document.getElementById("btn-find-input-tab").addEventListener("click", this.input.findTab.bind(this.input)); this.addDynamicListener("#input-tabs li .btn-close-tab i", "click", this.input.removeTabClick, this.input); this.addDynamicListener("#input-tabs li .input-tab-content", "click", this.input.changeTabClick, this.input); diff --git a/src/web/OutputWaiter.mjs b/src/web/OutputWaiter.mjs index 193ae483..ea1c853c 100755 --- a/src/web/OutputWaiter.mjs +++ b/src/web/OutputWaiter.mjs @@ -27,6 +27,7 @@ class OutputWaiter { this.manager = manager; this.outputs = {}; + this.activeTab = -1; this.maxTabs = 4; // Calculate this } @@ -466,6 +467,7 @@ class OutputWaiter { for (let i = 0; i < tabs.length; i++) { if (tabs.item(i).getAttribute("inputNum") === inputNum.toString()) { tabs.item(i).classList.add("active-output-tab"); + this.activeTab = inputNum; found = true; } else { tabs.item(i).classList.remove("active-output-tab"); @@ -482,6 +484,7 @@ class OutputWaiter { tabs.item(i).setAttribute("inputNum", newOutputs[i].toString()); this.displayTabInfo(newOutputs[i]); if (newOutputs[i] === inputNum) { + this.activeTab = inputNum; tabs.item(i).classList.add("active-output-tab"); } } @@ -740,13 +743,7 @@ class OutputWaiter { * @returns {number} */ getActiveTab() { - const activeTabs = document.getElementsByClassName("active-output-tab"); - if (activeTabs.length > 0) { - const activeTab = activeTabs.item(0); - const tabNum = activeTab.getAttribute("inputNum"); - return parseInt(tabNum, 10); - } - return -1; + return this.activeTab; } /** diff --git a/src/web/WorkerWaiter.mjs b/src/web/WorkerWaiter.mjs index 2d4da8d7..e3ab2895 100644 --- a/src/web/WorkerWaiter.mjs +++ b/src/web/WorkerWaiter.mjs @@ -310,53 +310,46 @@ class WorkerWaiter { this.chefWorkers[workerIdx].inputNum = nextInput.inputNum; this.chefWorkers[workerIdx].active = true; - this.chefWorkers[workerIdx].worker.postMessage({ - action: "bake", - data: { - input: nextInput.input, - recipeConfig: this.recipeConfig, - options: this.options, - progress: this.progress, - step: this.step, - inputNum: nextInput.inputNum - } - }); + const input = nextInput.input; + if (typeof input === "string") { + this.chefWorkers[workerIdx].worker.postMessage({ + action: "bake", + data: { + input: input, + recipeConfig: this.recipeConfig, + options: this.options, + progress: this.progress, + step: this.step, + inputNum: nextInput.inputNum + } + }); + } else { + this.chefWorkers[workerIdx].worker.postMessage({ + action: "bake", + data: { + input: input, + recipeConfig: this.recipeConfig, + options: this.options, + progress: this.progress, + step: this.step, + inputNum: nextInput.inputNum + } + }, [nextInput.input]); + } } /** * Bakes the current input using the current recipe. * - * @param {string | Array} input * @param {Object[]} recipeConfig * @param {Object} options * @param {number} progress * @param {boolean} step */ - bake(input, recipeConfig, options, progress, step) { + bake(recipeConfig, options, progress, step) { this.setBakingStatus(true); this.bakeStartTime = new Date().getTime(); - if (typeof input === "string") { - input = [{ - input: input, - inputNum: this.manager.input.getActiveTab() - }]; - } - - for (let i = 0; i < input.length; i++) { - this.manager.output.updateOutputStatus("pending", input[i].inputNum); - - for (let x = 0; x < this.inputs.length; x++) { - if (this.inputs[x].inputNum === input[i].inputNum) { - this.inputs.splice(x, 1); - break; - } - } - } - - this.totalOutputs += input.length; - this.inputs = input; - this.recipeConfig = recipeConfig; this.options = options; this.progress = progress; @@ -368,49 +361,20 @@ class WorkerWaiter { this.bakeNextInput(workerIdx); } this.displayProgress(); - return; + } + /** + * Queues an input ready to be baked + * + * @param {object} inputData + * @param {string | ArrayBuffer} inputData.input + * @param {number} inputData.inputNum + */ + queueInput(inputData) { + this.manager.output.updateOutputStatus("pending", inputData.inputNum); - for (let i = 0; i < input.length; i++) { - this.totalOutputs++; - this.manager.output.updateOutputStatus("pending", input[i].inputNum); - this.manager.output.updateOutputMessage(`Input ${input[i].inputNum} has not been baked yet.`, input[i].inputNum); - // If an input exists for the current inputNum, remove it - for (let x = 0; x < this.inputs.length; x++) { - if (this.inputs[x].inputNum === input[i].inputNum) { - this.inputs.splice(x, 1); - } - } - const workerId = this.addChefWorker(); - if (workerId !== -1) { - // Send the input to the ChefWorker - this.manager.output.updateOutputStatus("baking", input[i].inputNum); - this.manager.output.updateOutputMessage("Baking...", input[i].inputNum); - this.chefWorkers[workerId].active = true; - this.chefWorkers[workerId].inputNum = input[i].inputNum; - this.chefWorkers[workerId].worker.postMessage({ - action: "bake", - data: { - input: input[i].input, - recipeConfig: recipeConfig, - options: options, - progress: progress, - step: step, - inputNum: input[i].inputNum - } - }); - } else { - // Add the input to inputs so it can be processed when ready - this.inputs.push({ - input: input[i].input, - recipeConfig: recipeConfig, - options: options, - progress: progress, - step: step, - inputNum: input[i].inputNum - }); - } - } + this.totalOutputs++; + this.inputs.push(inputData); } /** @@ -474,6 +438,9 @@ class WorkerWaiter { */ displayProgress() { const progress = this.getBakeProgress(); + + if (progress.total === progress.baked) return; + const percentComplete = ((progress.pending + progress.baking) / progress.total) * 100; const bakeButton = document.getElementById("bake"); if (this.app.baking) {