nissyapp.mjs (8411B)
1 // Run ./build web from the main folder before running this example. 2 // The necessary modules (including worker.mjs) will be built and / or moved 3 // to this folder when running ./build web. 4 5 var solveButton = document.getElementById("solveButton"); 6 var pauseResumeButton = document.getElementById("pauseResumeButton"); 7 var cancelSolveButton = document.getElementById("cancelSolveButton"); 8 var scrField = document.getElementById("scrambleText"); 9 var resultsLabel = document.getElementById("resultsLabel"); 10 var resultsText = document.getElementById("results"); 11 var solverSelector = document.getElementById("solverSelector"); 12 var toggleLog = document.getElementById("toggleShowLog"); 13 var logPane = document.getElementById("logPane"); 14 var minSlider = document.getElementById("minMovesSlider"); 15 var maxSlider = document.getElementById("maxMovesSlider"); 16 var minLabel = document.getElementById("minLabel"); 17 var maxLabel = document.getElementById("maxLabel"); 18 var maxSolsInput = document.getElementById("maxSolutions"); 19 var optimalInput = document.getElementById("optimalInput"); 20 21 var solveStatus = "run"; // For now this is global 22 var lastCallbackId = 0; 23 var callbacks = new Map(); // Values: { f: function, arg: object } 24 var worker = new Worker("./worker.mjs", { type: "module" }); 25 26 // Periodically send status updates to the worker 27 28 const sendStatusUpdateToWorker = () => 29 worker.postMessage({command: "update status", id: -1, arg: solveStatus }); 30 setInterval(() => sendStatusUpdateToWorker(), 500); 31 32 worker.onmessage = (event) => { 33 if (event.data.command == "log") { 34 logPane.innerText += event.data.object; 35 console.log(event.data.object); 36 } else if (!callbacks.has(event.data.id)) { 37 console.log("[nissy app] Unknown callback " + event.data.id + 38 " for command " + event.data.command); 39 } else { 40 var callback = callbacks.get(event.data.id); 41 callbacks.delete(event.data.id); 42 callback.f(callback.arg, event.data.arg); 43 } 44 }; 45 46 function updateResults(label, results, enable) { 47 resultsLabel.innerText = label; 48 resultsText.innerText = results; 49 solveButton.disabled = !enable; 50 solverSelector.disabled = !enable; 51 minSlider.disabled = !enable; 52 maxSlider.disabled = !enable; 53 maxSolsInput.disabled = !enable; 54 optimalInput.disabled = !enable; 55 } 56 57 scrField.addEventListener("input", (e) => { 58 const scramble = scrField.value; 59 60 const callbackId = ++lastCallbackId; 61 callbacks.set(callbackId, { 62 f: (callbackArg, validateResult) => { 63 if (validateResult) { 64 scrField.style.border = ""; 65 } else { 66 scrField.style.border = "2px solid red"; 67 } 68 }, 69 arg: scramble 70 }); 71 72 worker.postMessage({ 73 command: "validate scramble", 74 id: callbackId, 75 arg: scramble 76 }); 77 }); 78 79 minSlider.addEventListener("input", () => 80 minLabel.innerText = "Minimum moves: " + minSlider.value); 81 82 maxSlider.addEventListener("input", () => 83 maxLabel.innerText = "Maximum moves: " + maxSlider.value); 84 85 function enforceMinMax() { 86 if (this.value != "") { 87 if (parseInt(this.value) < parseInt(this.min)) 88 this.value = this.min; 89 if (parseInt(this.value) > parseInt(this.max)) 90 this.value = this.max; 91 } 92 } 93 94 maxSolsInput.addEventListener("keyup", enforceMinMax); 95 optimalInput.addEventListener("keyup", enforceMinMax); 96 97 var logVisible = false; 98 toggleLog.addEventListener("click", () => { 99 logVisible = !logVisible; 100 if (logVisible) { 101 logPane.style.display = "block"; 102 toggleLog.innerText = "Hide nissy log messages"; 103 } else { 104 logPane.style.display = "none"; 105 toggleLog.innerText = "Show nissy log messages"; 106 } 107 }); 108 109 solveButton.addEventListener("click", () => { 110 const solver = solverSelector.options[solverSelector.selectedIndex].value; 111 const scramble = scrField.value; 112 113 const callbackId = ++lastCallbackId; 114 callbacks.set(callbackId, { 115 f: (callbackArg, validateResult) => { 116 if (validateResult) { 117 loadDataThenSolve(callbackArg.solver, callbackArg.scramble); 118 } else { 119 updateResults("Scramble is invalid", "", true); 120 } 121 }, 122 arg: { 123 solver: solver, 124 scramble: scramble 125 } 126 }); 127 128 worker.postMessage({ 129 command: "validate scramble", 130 id: callbackId, 131 arg: scramble 132 }); 133 }); 134 135 function loadDataThenSolve(solver, scramble) { 136 updateResults("Loading solver " + solver + "...", "", false); 137 138 const callbackId = ++lastCallbackId; 139 callbacks.set(callbackId, { 140 f: (callbackArg, loadResult) => { 141 if (loadResult.success) 142 startSolve(callbackArg.solver, callbackArg.scramble); 143 else 144 askDownloadThenSolve(callbackArg.solver, callbackArg.scramble); 145 }, 146 arg: { solver: solver, scramble: scramble } 147 }); 148 149 worker.postMessage({ 150 command: "load solver data", 151 id: callbackId, 152 arg: solver 153 }); 154 } 155 156 function askDownloadThenSolve(solver, scramble) { 157 var confirmDiv = document.getElementById("confirmDownload"); 158 var cancel = document.getElementById("confirmDownloadCancel"); 159 var download = document.getElementById("confirmDownloadConfirm"); 160 var generate = document.getElementById("confirmDownloadGenerate"); 161 const cleanup = () => { 162 confirmDiv.style.display = "none"; 163 164 // Remove event listeners by cloning the buttons 165 cancel.outerHTML = cancel.outerHTML; 166 download.outerHTML = download.outerHTML; 167 generate.outerHTML = generate.outerHTML; 168 }; 169 170 updateResults("", "", false); 171 confirmDiv.style.display = "block"; 172 173 cancel.addEventListener("click", () => { 174 cleanup(); 175 updateResults("", "", true); 176 }); 177 178 download.addEventListener("click", () => { 179 cleanup(); 180 downloadDataThenSolve(solver, scramble); 181 }); 182 183 generate.addEventListener("click", () => { 184 cleanup(); 185 generateDataThenSolve(solver, scramble); 186 }); 187 } 188 189 const downloadDataThenSolve = (solver, scramble) => 190 downloadOrGenerateThenSolve(solver, scramble, true); 191 192 const generateDataThenSolve = (solver, scramble) => 193 downloadOrGenerateThenSolve(solver, scramble, false); 194 195 function downloadOrGenerateThenSolve(solver, scramble, download) { 196 const msg = download ? "Downloading data for " : "Generating data for "; 197 const command = download ? "download solver data" : "generate solver data"; 198 199 updateResults(msg + solver + "...", "", false); 200 201 const callbackId = ++lastCallbackId; 202 callbacks.set(callbackId, { 203 f: (callbackArg, loadResult) => { 204 if (loadResult.success) 205 startSolve(callbackArg.solver, callbackArg.scramble); 206 else 207 updateResults("Failed", loadResult.message, true); 208 }, 209 arg: { solver: solver, scramble: scramble } 210 }); 211 212 worker.postMessage({ 213 command: command, 214 id: callbackId, 215 arg: solver 216 }); 217 } 218 219 pauseResumeButton.addEventListener("click", (e) => { 220 if (pauseResumeButton.innerText == "Pause") { 221 solveStatus = "pause"; 222 pauseResumeButton.innerText = "Resume"; 223 updateResults("Solver paused - click Resume to continue", "", false); 224 } else { 225 solveStatus = "run"; 226 pauseResumeButton.innerText = "Pause"; 227 updateResults("Solving...", "", false); 228 } 229 }); 230 231 cancelSolveButton.addEventListener("click", (e) => { 232 solveStatus = "stop"; 233 pauseResumeButton.innerText = "Pause"; 234 }); 235 236 function startSolve(solver, scramble) { 237 updateResults("Solving...", "", false); 238 239 const callbackId = ++lastCallbackId; 240 pauseResumeButton.disabled = false; 241 cancelSolveButton.disabled = false; 242 solveStatus = "run"; 243 244 callbacks.set(callbackId, { 245 f: (callbackArg, solveResult) => { 246 pauseResumeButton.disabled = true; 247 cancelSolveButton.disabled = true; 248 pauseResumeButton.innerText = "Pause"; 249 solveStatus = "stop"; 250 251 if (solveResult.success) { 252 const n = solveResult.solutions.length; 253 const label = n == 0 ? "No solution found" : 254 ("Found " + n + " solution" + (n == 1 ? "" : "s")) + ":"; 255 const text = solveResult.solutions.join("\n"); 256 updateResults(label, text, true); 257 } else { 258 updateResults("Unexpected error while solving!", "", true); 259 } 260 }, 261 arg: "" // Currently unused 262 }); 263 264 worker.postMessage({ 265 command: "solve", 266 id: callbackId, 267 arg: { 268 solver: solver, 269 scramble: scramble, 270 minmoves: Number(minSlider.value), 271 maxmoves: Number(maxSlider.value), 272 maxsolutions: Number(maxSolsInput.value), 273 optimal: Number(optimalInput.value), 274 threads: window.navigator.hardwareConcurrency, 275 } 276 }); 277 }