diff --git a/src/core/operations/Bombe.mjs b/src/core/operations/Bombe.mjs index 6b277a03..ea3210fa 100644 --- a/src/core/operations/Bombe.mjs +++ b/src/core/operations/Bombe.mjs @@ -30,28 +30,42 @@ class Bombe extends Operation { this.presentType = "html"; this.args = [ { - name: "1st (right-hand) rotor", - type: "editableOption", - value: ROTORS, - defaultIndex: 2 + name: "Model", + type: "argSelector", + value: [ + { + name: "3-rotor", + off: [1] + }, + { + name: "4-rotor", + on: [1] + } + ] }, { - name: "2nd (middle) rotor", + name: "Left-most rotor", + type: "editableOption", + value: ROTORS_FOURTH, + defaultIndex: 0 + }, + { + name: "Left-hand rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 0 + }, + { + name: "Middle rotor", type: "editableOption", value: ROTORS, defaultIndex: 1 }, { - name: "3rd (left-hand) rotor", + name: "Right-hand rotor", type: "editableOption", value: ROTORS, - defaultIndex: 0 - }, - { - name: "4th (left-most, only some models) rotor", - type: "editableOption", - value: ROTORS_FOURTH, - defaultIndex: 0 + defaultIndex: 2 }, { name: "Reflector", @@ -93,23 +107,26 @@ class Bombe extends Operation { * @returns {string} */ run(input, args) { - const reflectorstr = args[4]; - let crib = args[5]; - const offset = args[6]; - const check = args[7]; + const model = args[0]; + const reflectorstr = args[5]; + let crib = args[6]; + const offset = args[7]; + const check = args[8]; const rotors = []; for (let i=0; i<4; i++) { - if (i === 3 && args[i] === "") { + if (i === 0 && model === "3-rotor") { // No fourth rotor - break; + continue; } - let rstr = args[i]; + let rstr = args[i + 1]; // The Bombe doesn't take stepping into account so we'll just ignore it here if (rstr.includes("<")) { rstr = rstr.split("<", 2)[0]; } rotors.push(rstr); } + // Rotors are handled in reverse + rotors.reverse(); if (crib.length === 0) { throw new OperationError("Crib cannot be empty"); } diff --git a/src/core/operations/Enigma.mjs b/src/core/operations/Enigma.mjs index 4af79993..ace50604 100644 --- a/src/core/operations/Enigma.mjs +++ b/src/core/operations/Enigma.mjs @@ -28,67 +28,81 @@ class Enigma extends Operation { this.outputType = "string"; this.args = [ { - name: "1st (right-hand) rotor", + name: "Model", + type: "argSelector", + value: [ + { + name: "3-rotor", + off: [1, 2, 3] + }, + { + name: "4-rotor", + on: [1, 2, 3] + } + ] + }, + { + name: "Left-most rotor", + type: "editableOption", + value: ROTORS_FOURTH, + defaultIndex: 0 + }, + { + name: "Left-most rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "Left-most rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Left-hand rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 0 + }, + { + name: "Left-hand rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "Left-hand rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Middle rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 1 + }, + { + name: "Middle rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "Middle rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Right-hand rotor", type: "editableOption", value: ROTORS, // Default config is the rotors I-III *left to right* defaultIndex: 2 }, { - name: "1st rotor ring setting", + name: "Right-hand rotor ring setting", type: "option", value: LETTERS }, { - name: "1st rotor initial value", - type: "option", - value: LETTERS - }, - { - name: "2nd (middle) rotor", - type: "editableOption", - value: ROTORS, - defaultIndex: 1 - }, - { - name: "2nd rotor ring setting", - type: "option", - value: LETTERS - }, - { - name: "2nd rotor initial value", - type: "option", - value: LETTERS - }, - { - name: "3rd (left-hand) rotor", - type: "editableOption", - value: ROTORS, - defaultIndex: 0 - }, - { - name: "3rd rotor ring setting", - type: "option", - value: LETTERS - }, - { - name: "3rd rotor initial value", - type: "option", - value: LETTERS - }, - { - name: "4th (left-most, only some models) rotor", - type: "editableOption", - value: ROTORS_FOURTH, - defaultIndex: 0 - }, - { - name: "4th rotor ring setting", - type: "option", - value: LETTERS - }, - { - name: "4th rotor initial value", + name: "Right-hand rotor initial value", type: "option", value: LETTERS }, @@ -135,18 +149,21 @@ class Enigma extends Operation { * @returns {string} */ run(input, args) { - const reflectorstr = args[12]; - const plugboardstr = args[13]; - const removeOther = args[14]; + const model = args[0]; + const reflectorstr = args[13]; + const plugboardstr = args[14]; + const removeOther = args[15]; const rotors = []; for (let i=0; i<4; i++) { - if (i === 3 && args[i*3] === "") { - // No fourth rotor - break; + if (i === 0 && model === "3-rotor") { + // Skip the 4th rotor settings + continue; } - const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*3], 1); - rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*3 + 1], args[i*3 + 2])); + const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*3 + 1], 1); + rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*3 + 2], args[i*3 + 3])); } + // Rotors are handled in reverse + rotors.reverse(); const reflector = new Reflector(reflectorstr); const plugboard = new Plugboard(plugboardstr); if (removeOther) { diff --git a/src/web/App.mjs b/src/web/App.mjs index e203b85c..04846fb6 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -472,6 +472,7 @@ class App { const item = this.manager.recipe.addOperation(recipeConfig[i].op); // Populate arguments + log.debug(`Populating arguments for ${recipeConfig[i].op}`); const args = item.querySelectorAll(".arg"); for (let j = 0; j < args.length; j++) { if (recipeConfig[i].args[j] === undefined) continue; @@ -497,6 +498,8 @@ class App { item.querySelector(".breakpoint").click(); } + this.manager.recipe.triggerArgEvents(item); + this.progress = 0; } diff --git a/src/web/HTMLIngredient.mjs b/src/web/HTMLIngredient.mjs index c7c024fb..19c816ea 100755 --- a/src/web/HTMLIngredient.mjs +++ b/src/web/HTMLIngredient.mjs @@ -240,6 +240,27 @@ class HTMLIngredient { ${this.hint ? "" + this.hint + "" : ""} `; break; + case "argSelector": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + + this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this); + break; default: break; } @@ -321,6 +342,33 @@ class HTMLIngredient { this.manager.recipe.ingChange(); } + + /** + * Handler for argument selector changes. + * Shows or hides the relevant arguments for this operation. + * + * @param {event} e + */ + argSelectorChange(e) { + e.preventDefault(); + e.stopPropagation(); + + const option = e.target.options[e.target.selectedIndex]; + const op = e.target.closest(".operation"); + const args = op.querySelectorAll(".ingredients .form-group"); + const turnon = JSON.parse(option.getAttribute("turnon")); + const turnoff = JSON.parse(option.getAttribute("turnoff")); + + args.forEach((arg, i) => { + if (turnon.includes(i)) { + arg.classList.remove("d-none"); + } + if (turnoff.includes(i)) { + arg.classList.add("d-none"); + } + }); + } + } export default HTMLIngredient; diff --git a/src/web/RecipeWaiter.mjs b/src/web/RecipeWaiter.mjs index 4c568c8b..4eca4af7 100755 --- a/src/web/RecipeWaiter.mjs +++ b/src/web/RecipeWaiter.mjs @@ -393,15 +393,6 @@ class RecipeWaiter { this.buildRecipeOperation(item); document.getElementById("rec-list").appendChild(item); - // Trigger populateOption events - const populateOptions = item.querySelectorAll(".populate-option"); - const evt = new Event("change", {bubbles: true}); - if (populateOptions.length) { - for (const el of populateOptions) { - el.dispatchEvent(evt); - } - } - item.dispatchEvent(this.manager.operationadd); return item; } @@ -439,6 +430,23 @@ class RecipeWaiter { } + /** + * Triggers various change events for operation arguments that have just been initialised. + * + * @param {HTMLElement} op + */ + triggerArgEvents(op) { + // Trigger populateOption and argSelector events + const triggerableOptions = op.querySelectorAll(".populate-option, .arg-selector"); + const evt = new Event("change", {bubbles: true}); + if (triggerableOptions.length) { + for (const el of triggerableOptions) { + el.dispatchEvent(evt); + } + } + } + + /** * Handler for operationadd events. * @@ -448,6 +456,8 @@ class RecipeWaiter { */ opAdd(e) { log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`); + + this.triggerArgEvents(e.target); window.dispatchEvent(this.manager.statechange); } diff --git a/tests/operations/tests/Bombe.mjs b/tests/operations/tests/Bombe.mjs index 0f00f1be..9e5a79c6 100644 --- a/tests/operations/tests/Bombe.mjs +++ b/tests/operations/tests/Bombe.mjs @@ -16,10 +16,11 @@ TestRegister.addTests([ { "op": "Bombe", "args": [ - "BDFHJLCPRTXVZNYEIWGAKMUSQO