mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-25 01:06:16 -04:00
Bring up to date with master
This commit is contained in:
commit
f473807459
86 changed files with 10685 additions and 4298 deletions
|
@ -51,10 +51,12 @@ class App {
|
|||
*/
|
||||
setup() {
|
||||
document.dispatchEvent(this.manager.appstart);
|
||||
|
||||
this.initialiseSplitter();
|
||||
this.loadLocalStorage();
|
||||
this.populateOperationsList();
|
||||
this.manager.setup();
|
||||
this.manager.output.saveBombe();
|
||||
this.resetLayout();
|
||||
this.setCompileMessage();
|
||||
|
||||
|
@ -106,7 +108,7 @@ class App {
|
|||
handleError(err, logToConsole) {
|
||||
if (logToConsole) log.error(err);
|
||||
const msg = err.displayStr || err.toString();
|
||||
this.alert(msg, this.options.errorTimeout, !this.options.showErrors);
|
||||
this.alert(Utils.escapeHtml(msg), this.options.errorTimeout, !this.options.showErrors);
|
||||
}
|
||||
|
||||
|
||||
|
@ -122,6 +124,9 @@ class App {
|
|||
// Reset attemptHighlight flag
|
||||
this.options.attemptHighlight = true;
|
||||
|
||||
// Remove all current indicators
|
||||
this.manager.recipe.updateBreakpointIndicator(false);
|
||||
|
||||
this.manager.worker.bake(
|
||||
this.getInput(), // The user's input
|
||||
this.getRecipeConfig(), // The configuration of the recipe
|
||||
|
@ -239,7 +244,7 @@ class App {
|
|||
/**
|
||||
* Sets up the adjustable splitter to allow the user to resize areas of the page.
|
||||
*
|
||||
* @param {boolean} [minimise=false] - Set this flag if attempting to minimuse frames to 0 width
|
||||
* @param {boolean} [minimise=false] - Set this flag if attempting to minimise frames to 0 width
|
||||
*/
|
||||
initialiseSplitter(minimise=false) {
|
||||
if (this.columnSplitter) this.columnSplitter.destroy();
|
||||
|
@ -247,9 +252,9 @@ class App {
|
|||
|
||||
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
|
||||
sizes: [20, 30, 50],
|
||||
minSize: minimise ? [0, 0, 0] : [240, 370, 450],
|
||||
minSize: minimise ? [0, 0, 0] : [240, 310, 450],
|
||||
gutterSize: 4,
|
||||
expandToMin: false,
|
||||
expandToMin: true,
|
||||
onDrag: function() {
|
||||
this.manager.recipe.adjustWidth();
|
||||
}.bind(this)
|
||||
|
@ -474,6 +479,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;
|
||||
|
@ -499,6 +505,8 @@ class App {
|
|||
item.querySelector(".breakpoint").click();
|
||||
}
|
||||
|
||||
this.manager.recipe.triggerArgEvents(item);
|
||||
|
||||
this.progress = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -338,7 +338,7 @@ class ControlsWaiter {
|
|||
const saveLink = this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/");
|
||||
|
||||
if (reportBugInfo) {
|
||||
reportBugInfo.innerHTML = `* Version: ${PKG_VERSION + (typeof INLINE === "undefined" ? "" : "s")}
|
||||
reportBugInfo.innerHTML = `* Version: ${PKG_VERSION}
|
||||
* Compile time: ${COMPILE_TIME}
|
||||
* User-Agent:
|
||||
${navigator.userAgent}
|
||||
|
|
|
@ -45,7 +45,7 @@ class HTMLIngredient {
|
|||
*/
|
||||
toHtml() {
|
||||
let html = "",
|
||||
i, m;
|
||||
i, m, eventFn;
|
||||
|
||||
switch (this.type) {
|
||||
case "string":
|
||||
|
@ -151,10 +151,11 @@ class HTMLIngredient {
|
|||
</div>`;
|
||||
break;
|
||||
case "populateOption":
|
||||
case "populateMultiOption":
|
||||
html += `<div class="form-group">
|
||||
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
|
||||
<select
|
||||
class="form-control arg"
|
||||
class="form-control arg no-state-change populate-option"
|
||||
id="${this.id}"
|
||||
arg-name="${this.name}"
|
||||
${this.disabled ? "disabled" : ""}>`;
|
||||
|
@ -164,14 +165,20 @@ class HTMLIngredient {
|
|||
} else if ((m = this.value[i].name.match(/\[\/([a-z0-9 -()^]+)\]/i))) {
|
||||
html += "</optgroup>";
|
||||
} else {
|
||||
html += `<option populate-value="${Utils.escapeHtml(this.value[i].value)}">${this.value[i].name}</option>`;
|
||||
const val = this.type === "populateMultiOption" ?
|
||||
JSON.stringify(this.value[i].value) :
|
||||
this.value[i].value;
|
||||
html += `<option populate-value='${Utils.escapeHtml(val)}'>${this.value[i].name}</option>`;
|
||||
}
|
||||
}
|
||||
html += `</select>
|
||||
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
|
||||
</div>`;
|
||||
|
||||
this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this);
|
||||
eventFn = this.type === "populateMultiOption" ?
|
||||
this.populateMultiOptionChange :
|
||||
this.populateOptionChange;
|
||||
this.manager.addDynamicListener("#" + this.id, "change", eventFn, this);
|
||||
break;
|
||||
case "editableOption":
|
||||
html += `<div class="form-group input-group">
|
||||
|
@ -243,6 +250,27 @@ class HTMLIngredient {
|
|||
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
|
||||
</div>`;
|
||||
break;
|
||||
case "argSelector":
|
||||
html += `<div class="form-group inline">
|
||||
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
|
||||
<select
|
||||
class="form-control arg inline arg-selector"
|
||||
id="${this.id}"
|
||||
arg-name="${this.name}"
|
||||
${this.disabled ? "disabled" : ""}>`;
|
||||
for (i = 0; i < this.value.length; i++) {
|
||||
html += `<option ${this.defaultIndex === i ? "selected" : ""}
|
||||
turnon="${JSON.stringify(this.value[i].on || [])}"
|
||||
turnoff="${JSON.stringify(this.value[i].off || [])}">
|
||||
${this.value[i].name}
|
||||
</option>`;
|
||||
}
|
||||
html += `</select>
|
||||
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
|
||||
</div>`;
|
||||
|
||||
this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -258,11 +286,16 @@ class HTMLIngredient {
|
|||
* @param {event} e
|
||||
*/
|
||||
populateOptionChange(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const el = e.target;
|
||||
const op = el.parentNode.parentNode;
|
||||
const target = op.querySelectorAll(".arg")[this.target];
|
||||
|
||||
target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value");
|
||||
const popVal = el.childNodes[el.selectedIndex].getAttribute("populate-value");
|
||||
if (popVal !== "") target.value = popVal;
|
||||
|
||||
const evt = new Event("change");
|
||||
target.dispatchEvent(evt);
|
||||
|
||||
|
@ -270,6 +303,37 @@ class HTMLIngredient {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for populate multi option changes.
|
||||
* Populates the relevant arguments with the specified values.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
populateMultiOptionChange(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const el = e.target;
|
||||
const op = el.parentNode.parentNode;
|
||||
const args = op.querySelectorAll(".arg");
|
||||
const targets = this.target.map(i => args[i]);
|
||||
const vals = JSON.parse(el.childNodes[el.selectedIndex].getAttribute("populate-value"));
|
||||
const evt = new Event("change");
|
||||
|
||||
for (let i = 0; i < targets.length; i++) {
|
||||
targets[i].value = vals[i];
|
||||
}
|
||||
|
||||
// Fire change event after all targets have been assigned
|
||||
this.manager.recipe.ingChange();
|
||||
|
||||
// Send change event for each target once all have been assigned, to update the label placement.
|
||||
for (const target of targets) {
|
||||
target.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for editable option clicks.
|
||||
* Populates the input box with the selected value.
|
||||
|
@ -290,6 +354,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;
|
||||
|
|
|
@ -336,24 +336,54 @@ class OutputWaiter {
|
|||
|
||||
|
||||
/**
|
||||
* Shows or hides the loading icon.
|
||||
* Save bombe object then remove it from the DOM so that it does not cause performance issues.
|
||||
*/
|
||||
saveBombe() {
|
||||
this.bombeEl = document.getElementById("bombe");
|
||||
this.bombeEl.parentNode.removeChild(this.bombeEl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows or hides the output loading screen.
|
||||
* The animated Bombe SVG, whilst quite aesthetically pleasing, is reasonably CPU
|
||||
* intensive, so we remove it from the DOM when not in use. We only show it if the
|
||||
* recipe is taking longer than 200ms. We add it to the DOM just before that so that
|
||||
* it is ready to fade in without stuttering.
|
||||
*
|
||||
* @param {boolean} value
|
||||
* @param {boolean} value - true == show loader
|
||||
*/
|
||||
toggleLoader(value) {
|
||||
clearTimeout(this.appendBombeTimeout);
|
||||
clearTimeout(this.outputLoaderTimeout);
|
||||
|
||||
const outputLoader = document.getElementById("output-loader"),
|
||||
outputElement = document.getElementById("output-text");
|
||||
outputElement = document.getElementById("output-text"),
|
||||
animation = document.getElementById("output-loader-animation");
|
||||
|
||||
if (value) {
|
||||
this.manager.controls.hideStaleIndicator();
|
||||
this.bakingStatusTimeout = setTimeout(function() {
|
||||
|
||||
// Start a timer to add the Bombe to the DOM just before we make it
|
||||
// visible so that there is no stuttering
|
||||
this.appendBombeTimeout = setTimeout(function() {
|
||||
animation.appendChild(this.bombeEl);
|
||||
}.bind(this), 150);
|
||||
|
||||
// Show the loading screen
|
||||
this.outputLoaderTimeout = setTimeout(function() {
|
||||
outputElement.disabled = true;
|
||||
outputLoader.style.visibility = "visible";
|
||||
outputLoader.style.opacity = 1;
|
||||
this.manager.controls.toggleBakeButtonFunction(true);
|
||||
}.bind(this), 200);
|
||||
} else {
|
||||
clearTimeout(this.bakingStatusTimeout);
|
||||
// Remove the Bombe from the DOM to save resources
|
||||
this.outputLoaderTimeout = setTimeout(function () {
|
||||
try {
|
||||
animation.removeChild(this.bombeEl);
|
||||
} catch (err) {}
|
||||
}.bind(this), 500);
|
||||
outputElement.disabled = false;
|
||||
outputLoader.style.opacity = 0;
|
||||
outputLoader.style.visibility = "hidden";
|
||||
|
|
|
@ -124,16 +124,21 @@ class RecipeWaiter {
|
|||
* @param {event} evt
|
||||
*/
|
||||
opSortEnd(evt) {
|
||||
if (this.removeIntent) {
|
||||
if (evt.item.parentNode.id === "rec-list") {
|
||||
evt.item.remove();
|
||||
}
|
||||
if (this.removeIntent && evt.item.parentNode.id === "rec-list") {
|
||||
evt.item.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reinitialise the popover on the original element in the ops list because for some reason it
|
||||
// gets destroyed and recreated.
|
||||
this.manager.ops.enableOpsListPopovers(evt.clone);
|
||||
// gets destroyed and recreated. If the clone isn't in the ops list, we use the original item instead.
|
||||
let enableOpsElement;
|
||||
if (evt.clone.parentNode && evt.clone.parentNode.classList.contains("op-list")) {
|
||||
enableOpsElement = evt.clone;
|
||||
} else {
|
||||
enableOpsElement = evt.item;
|
||||
$(evt.item).attr("data-toggle", "popover");
|
||||
}
|
||||
this.manager.ops.enableOpsListPopovers(enableOpsElement);
|
||||
|
||||
if (evt.item.parentNode.id !== "rec-list") {
|
||||
return;
|
||||
|
@ -205,6 +210,7 @@ class RecipeWaiter {
|
|||
* @fires Manager#statechange
|
||||
*/
|
||||
ingChange(e) {
|
||||
if (e && e.target && e.target.classList.contains("no-state-change")) return;
|
||||
window.dispatchEvent(this.manager.statechange);
|
||||
}
|
||||
|
||||
|
@ -340,10 +346,11 @@ class RecipeWaiter {
|
|||
/**
|
||||
* Moves or removes the breakpoint indicator in the recipe based on the position.
|
||||
*
|
||||
* @param {number} position
|
||||
* @param {number|boolean} position - If boolean, turn off all indicators
|
||||
*/
|
||||
updateBreakpointIndicator(position) {
|
||||
const operations = document.querySelectorAll("#rec-list li.operation");
|
||||
if (typeof position === "boolean") position = operations.length;
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
if (i === position) {
|
||||
operations[i].classList.add("break");
|
||||
|
@ -429,6 +436,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.
|
||||
*
|
||||
|
@ -438,6 +462,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);
|
||||
}
|
||||
|
||||
|
@ -591,6 +617,23 @@ class RecipeWaiter {
|
|||
ingredientRule.style.gridTemplateColumns = "auto auto auto auto";
|
||||
ingredientChildRule.style.gridColumn = "1 / span 4";
|
||||
}
|
||||
|
||||
// Hide Chef icon on Bake button if the page is compressed
|
||||
const bakeIcon = document.querySelector("#bake img");
|
||||
|
||||
if (recList.clientWidth < 370) {
|
||||
// Hide Chef icon on Bake button
|
||||
bakeIcon.style.display = "none";
|
||||
} else {
|
||||
bakeIcon.style.display = "inline-block";
|
||||
}
|
||||
|
||||
// Scale controls to fit pane width
|
||||
const controls = document.getElementById("controls");
|
||||
const controlsContent = document.getElementById("controls-content");
|
||||
const scale = (controls.clientWidth - 1) / controlsContent.scrollWidth;
|
||||
|
||||
controlsContent.style.transform = `translate(-50%, -50%) scale(${scale})`;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import clippy from "clippyjs";
|
||||
import "./static/clippy_assets/agents/Clippy/agent.js";
|
||||
import clippyMap from "./static/clippy_assets/agents/Clippy/map.png";
|
||||
|
||||
/**
|
||||
* Waiter to handle seasonal events and easter eggs.
|
||||
*/
|
||||
|
@ -18,6 +22,8 @@ class SeasonalWaiter {
|
|||
constructor(app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
|
||||
this.clippyAgent = null;
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,6 +34,14 @@ class SeasonalWaiter {
|
|||
// Konami code
|
||||
this.kkeys = [];
|
||||
window.addEventListener("keydown", this.konamiCodeListener.bind(this));
|
||||
|
||||
// Clippy
|
||||
const now = new Date();
|
||||
if (now.getMonth() === 3 && now.getDate() === 1) {
|
||||
this.addClippyOption();
|
||||
this.manager.addDynamicListener(".option-item #clippy", "change", this.setupClippy, this);
|
||||
this.setupClippy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,6 +65,285 @@ class SeasonalWaiter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an option in the Options menu for turning Clippy on or off
|
||||
*/
|
||||
addClippyOption() {
|
||||
const optionsBody = document.getElementById("options-body"),
|
||||
optionItem = document.createElement("span");
|
||||
|
||||
optionItem.className = "bmd-form-group is-filled";
|
||||
optionItem.innerHTML = `<div class="checkbox option-item">
|
||||
<label for="clippy">
|
||||
<input type="checkbox" option="clippy" id="clippy" checked="">
|
||||
Use the Clippy helper
|
||||
</label>
|
||||
</div>`;
|
||||
optionsBody.appendChild(optionItem);
|
||||
|
||||
if (!this.app.options.hasOwnProperty("clippy")) {
|
||||
this.app.options.clippy = true;
|
||||
}
|
||||
|
||||
this.manager.options.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up Clippy for April Fools Day
|
||||
*/
|
||||
setupClippy() {
|
||||
// Destroy any previous agents
|
||||
if (this.clippyAgent) {
|
||||
this.clippyAgent.closeBalloonImmediately();
|
||||
this.clippyAgent.hide();
|
||||
}
|
||||
|
||||
if (!this.app.options.clippy) {
|
||||
if (this.clippyTimeouts) this.clippyTimeouts.forEach(t => clearTimeout(t));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set base path to # to prevent external network requests
|
||||
const clippyAssets = "#";
|
||||
// Shim the library to prevent external network requests
|
||||
shimClippy(clippy);
|
||||
|
||||
const self = this;
|
||||
clippy.load("Clippy", (agent) => {
|
||||
shimClippyAgent(agent);
|
||||
self.clippyAgent = agent;
|
||||
agent.show();
|
||||
agent.speak("Hello, I'm Clippy, your personal cyber assistant!");
|
||||
}, undefined, clippyAssets);
|
||||
|
||||
// Watch for the Auto Magic button appearing
|
||||
const magic = document.getElementById("magic");
|
||||
const observer = new MutationObserver((mutationsList, observer) => {
|
||||
// Read in message and recipe
|
||||
let msg, recipe;
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.attributeName === "data-original-title") {
|
||||
msg = magic.getAttribute("data-original-title");
|
||||
}
|
||||
if (mutation.attributeName === "data-recipe") {
|
||||
recipe = magic.getAttribute("data-recipe");
|
||||
}
|
||||
}
|
||||
|
||||
// Close balloon if it is currently showing a magic hint
|
||||
const balloon = self.clippyAgent._balloon._balloon;
|
||||
if (balloon.is(":visible") && balloon.text().indexOf("That looks like encoded data") >= 0) {
|
||||
self.clippyAgent._balloon.hide(true);
|
||||
this.clippyAgent._balloon._hidden = true;
|
||||
}
|
||||
|
||||
// If a recipe was found, get Clippy to tell the user
|
||||
if (recipe) {
|
||||
recipe = this.manager.controls.generateStateUrl(true, true, JSON.parse(recipe));
|
||||
msg = `That looks like encoded data!<br><br>${msg}<br><br>Click <a class="clippyMagicRecipe" href="${recipe}">here</a> to load this recipe.`;
|
||||
|
||||
// Stop current balloon activity immediately and trigger speak again
|
||||
this.clippyAgent.closeBalloonImmediately();
|
||||
self.clippyAgent.speak(msg, true);
|
||||
// self.clippyAgent._queue.next();
|
||||
}
|
||||
});
|
||||
observer.observe(document.getElementById("magic"), {attributes: true});
|
||||
|
||||
// Play animations for various things
|
||||
this.manager.addListeners("#search", "click", () => {
|
||||
this.clippyAgent.play("Searching");
|
||||
}, this);
|
||||
this.manager.addListeners("#save,#save-to-file", "click", () => {
|
||||
this.clippyAgent.play("Save");
|
||||
}, this);
|
||||
this.manager.addListeners("#clr-recipe,#clr-io", "click", () => {
|
||||
this.clippyAgent.play("EmptyTrash");
|
||||
}, this);
|
||||
this.manager.addListeners("#bake", "click", e => {
|
||||
if (e.target.closest("button").textContent.toLowerCase().indexOf("bake") >= 0) {
|
||||
this.clippyAgent.play("Thinking");
|
||||
} else {
|
||||
this.clippyAgent.play("EmptyTrash");
|
||||
}
|
||||
this.clippyAgent._queue.clear();
|
||||
}, this);
|
||||
this.manager.addListeners("#input-text", "keydown", () => {
|
||||
this.clippyAgent.play("Writing");
|
||||
this.clippyAgent._queue.clear();
|
||||
}, this);
|
||||
this.manager.addDynamicListener("a.clippyMagicRecipe", "click", (e) => {
|
||||
this.clippyAgent.play("Congratulate");
|
||||
}, this);
|
||||
|
||||
this.clippyTimeouts = [];
|
||||
// Show challenge after timeout
|
||||
this.clippyTimeouts.push(setTimeout(() => {
|
||||
const hex = "1f 8b 08 00 ae a1 9b 5c 00 ff 05 40 a1 12 00 10 0c fd 26 61 5b 76 aa 9d 26 a8 02 02 37 84 f7 fb bb c5 a4 5f 22 c6 09 e5 6e c5 4c 2d 3f e9 30 a6 ea 41 a2 f2 ac 1c 00 00 00";
|
||||
self.clippyAgent.speak(`How about a fun challenge?<br><br>Try decoding this (click to load):<br><a href="#recipe=[]&input=${encodeURIComponent(btoa(hex))}">${hex}</a>`, true);
|
||||
self.clippyAgent.play("GetAttention");
|
||||
}, 1 * 60 * 1000));
|
||||
|
||||
this.clippyTimeouts.push(setTimeout(() => {
|
||||
self.clippyAgent.speak("<i>Did you know?</i><br><br>You can load files into CyberChef up to around 500MB using drag and drop or the load file button.", 15000);
|
||||
self.clippyAgent.play("Wave");
|
||||
}, 2 * 60 * 1000));
|
||||
|
||||
this.clippyTimeouts.push(setTimeout(() => {
|
||||
self.clippyAgent.speak("<i>Did you know?</i><br><br>You can use the 'Fork' operation to split up your input and run the recipe over each branch separately.<br><br><a class='clippyMagicRecipe' href=\"#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA\">Here's an example</a>.", 15000);
|
||||
self.clippyAgent.play("Print");
|
||||
}, 3 * 60 * 1000));
|
||||
|
||||
this.clippyTimeouts.push(setTimeout(() => {
|
||||
self.clippyAgent.speak("<i>Did you know?</i><br><br>The 'Magic' operation uses a number of methods to detect encoded data and the operations which can be used to make sense of it. A technical description of these methods can be found <a href=\"https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic\">here</a>.", 15000);
|
||||
self.clippyAgent.play("Alert");
|
||||
}, 4 * 60 * 1000));
|
||||
|
||||
this.clippyTimeouts.push(setTimeout(() => {
|
||||
self.clippyAgent.speak("<i>Did you know?</i><br><br>You can use parts of the input as arguments to operations.<br><br><a class='clippyMagicRecipe' href=\"#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ\">Click here for an example</a>.", 15000);
|
||||
self.clippyAgent.play("CheckingSomething");
|
||||
}, 5 * 60 * 1000));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shims various ClippyJS functions to modify behaviour.
|
||||
*
|
||||
* @param {Clippy} clippy - The Clippy library
|
||||
*/
|
||||
function shimClippy(clippy) {
|
||||
// Shim _loadSounds so that it doesn't actually try to load any sounds
|
||||
clippy.load._loadSounds = function _loadSounds (name, path) {
|
||||
let dfd = clippy.load._sounds[name];
|
||||
if (dfd) return dfd;
|
||||
|
||||
// set dfd if not defined
|
||||
dfd = clippy.load._sounds[name] = $.Deferred();
|
||||
|
||||
// Resolve immediately without loading
|
||||
dfd.resolve({});
|
||||
|
||||
return dfd.promise();
|
||||
};
|
||||
|
||||
// Shim _loadMap so that it uses the local copy
|
||||
clippy.load._loadMap = function _loadMap (path) {
|
||||
let dfd = clippy.load._maps[path];
|
||||
if (dfd) return dfd;
|
||||
|
||||
// set dfd if not defined
|
||||
dfd = clippy.load._maps[path] = $.Deferred();
|
||||
|
||||
const src = clippyMap;
|
||||
const img = new Image();
|
||||
|
||||
img.onload = dfd.resolve;
|
||||
img.onerror = dfd.reject;
|
||||
|
||||
// start loading the map;
|
||||
img.setAttribute("src", src);
|
||||
|
||||
return dfd.promise();
|
||||
};
|
||||
|
||||
// Make sure we don't request the remote map
|
||||
clippy.Animator.prototype._setupElement = function _setupElement (el) {
|
||||
const frameSize = this._data.framesize;
|
||||
el.css("display", "none");
|
||||
el.css({ width: frameSize[0], height: frameSize[1] });
|
||||
el.css("background", "url('" + clippyMap + "') no-repeat");
|
||||
|
||||
return el;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shims various ClippyJS Agent functions to modify behaviour.
|
||||
*
|
||||
* @param {Agent} agent - The Clippy Agent
|
||||
*/
|
||||
function shimClippyAgent(agent) {
|
||||
// Turn off all sounds
|
||||
agent._animator._playSound = () => {};
|
||||
|
||||
// Improve speak function to support HTML markup
|
||||
const self = agent._balloon;
|
||||
agent._balloon.speak = (complete, text, hold) => {
|
||||
self._hidden = false;
|
||||
self.show();
|
||||
const c = self._content;
|
||||
// set height to auto
|
||||
c.height("auto");
|
||||
c.width("auto");
|
||||
// add the text
|
||||
c.html(text);
|
||||
// set height
|
||||
c.height(c.height());
|
||||
c.width(c.width());
|
||||
c.text("");
|
||||
self.reposition();
|
||||
|
||||
self._complete = complete;
|
||||
self._sayWords(text, hold, complete);
|
||||
if (hold) agent._queue.next();
|
||||
};
|
||||
|
||||
// Improve the _sayWords function to allow HTML and support timeouts
|
||||
agent._balloon.WORD_SPEAK_TIME = 60;
|
||||
agent._balloon._sayWords = (text, hold, complete) => {
|
||||
self._active = true;
|
||||
self._hold = hold;
|
||||
const words = text.split(/[^\S-]/);
|
||||
const time = self.WORD_SPEAK_TIME;
|
||||
const el = self._content;
|
||||
let idx = 1;
|
||||
clearTimeout(self.holdTimeout);
|
||||
|
||||
self._addWord = $.proxy(function () {
|
||||
if (!self._active) return;
|
||||
if (idx > words.length) {
|
||||
delete self._addWord;
|
||||
self._active = false;
|
||||
if (!self._hold) {
|
||||
complete();
|
||||
self.hide();
|
||||
} else if (typeof hold === "number") {
|
||||
self.holdTimeout = setTimeout(() => {
|
||||
self._hold = false;
|
||||
complete();
|
||||
self.hide();
|
||||
}, hold);
|
||||
}
|
||||
} else {
|
||||
el.html(words.slice(0, idx).join(" "));
|
||||
idx++;
|
||||
self._loop = window.setTimeout($.proxy(self._addWord, self), time);
|
||||
}
|
||||
}, self);
|
||||
|
||||
self._addWord();
|
||||
};
|
||||
|
||||
// Add break-word to balloon CSS
|
||||
agent._balloon._balloon.css("word-break", "break-word");
|
||||
|
||||
// Close the balloon on click (unless it was a link)
|
||||
agent._balloon._balloon.click(e => {
|
||||
if (e.target.nodeName !== "A") {
|
||||
agent._balloon.hide(true);
|
||||
agent._balloon._hidden = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Add function to immediately close the balloon even if it is currently doing something
|
||||
agent.closeBalloonImmediately = () => {
|
||||
agent._queue.clear();
|
||||
agent._balloon.hide(true);
|
||||
agent._balloon._hidden = true;
|
||||
agent._queue.next();
|
||||
};
|
||||
}
|
||||
|
||||
export default SeasonalWaiter;
|
||||
|
|
|
@ -81,7 +81,11 @@
|
|||
if (!el.classList.contains("loading"))
|
||||
el.classList.add("loading"); // Causes CSS transition on first message
|
||||
el.innerHTML = msg;
|
||||
} catch (err) {} // Ignore errors if DOM not yet ready
|
||||
} catch (err) {
|
||||
// This error was likely caused by the DOM not being ready yet,
|
||||
// so we wait another second and then try again.
|
||||
setTimeout(changeLoadingMsg, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
changeLoadingMsg();
|
||||
|
@ -127,13 +131,6 @@
|
|||
};
|
||||
window.addEventListener("error", loadingErrorHandler);
|
||||
</script>
|
||||
<% if (htmlWebpackPlugin.options.inline) { %>
|
||||
<meta name="robots" content="noindex" />
|
||||
<% } else { %>
|
||||
<script type="application/ld+json">
|
||||
<% print(JSON.stringify(require("../static/structuredData.json"))); %>
|
||||
</script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Preloader overlay -->
|
||||
|
@ -149,11 +146,7 @@
|
|||
<div id="content-wrapper">
|
||||
<div id="banner" class="row">
|
||||
<div class="col" style="text-align: left; padding-left: 10px;">
|
||||
<% if (htmlWebpackPlugin.options.inline) { %>
|
||||
<span>Version <%= htmlWebpackPlugin.options.version %></span>
|
||||
<% } else { %>
|
||||
<a href="cyberchef.htm" download>Download CyberChef <i class="material-icons">file_download</i></a>
|
||||
<% } %>
|
||||
<a href="CyberChef_v<%= htmlWebpackPlugin.options.version %>.zip" download>Download CyberChef <i class="material-icons">file_download</i></a>
|
||||
</div>
|
||||
<div class="col-md-6" id="notice-wrapper">
|
||||
<span id="notice">
|
||||
|
@ -198,7 +191,7 @@
|
|||
<ul id="rec-list" class="list-area no-select"></ul>
|
||||
|
||||
<div id="controls" class="no-select">
|
||||
<div class="d-flex align-items-center">
|
||||
<div id="controls-content" class="d-flex align-items-center">
|
||||
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
|
||||
Step
|
||||
</button>
|
||||
|
@ -319,7 +312,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="output-loader">
|
||||
<div class="loader"></div>
|
||||
<div id="output-loader-animation">
|
||||
<object id="bombe" data="<%- require('../static/images/bombe.svg') %>" width="100%" height="100%"></object>
|
||||
</div>
|
||||
<div class="loading-msg"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -585,7 +580,7 @@
|
|||
What sort of things can I do with CyberChef?
|
||||
</a>
|
||||
<div class="collapse" id="faq-examples">
|
||||
<p>There are around 200 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
|
||||
<p>There are around 300 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
|
||||
<ul>
|
||||
<li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
|
||||
<li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import "./stylesheets/index.js";
|
||||
|
||||
// Libs
|
||||
import "babel-polyfill";
|
||||
import "arrive";
|
||||
import "snackbarjs";
|
||||
import "bootstrap-material-design";
|
||||
|
|
1
src/web/static/clippy_assets/agents/Clippy/agent.js
Executable file
1
src/web/static/clippy_assets/agents/Clippy/agent.js
Executable file
File diff suppressed because one or more lines are too long
BIN
src/web/static/clippy_assets/agents/Clippy/map.png
Executable file
BIN
src/web/static/clippy_assets/agents/Clippy/map.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
62
src/web/static/clippy_assets/clippy.css
Executable file
62
src/web/static/clippy_assets/clippy.css
Executable file
|
@ -0,0 +1,62 @@
|
|||
.clippy, .clippy-balloon {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clippy-balloon {
|
||||
|
||||
background: #FFC;
|
||||
color: black;
|
||||
padding: 8px;
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
|
||||
}
|
||||
|
||||
.clippy-content {
|
||||
max-width: 200px;
|
||||
min-width: 120px;
|
||||
font-family: "Microsoft Sans", sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.clippy-tip {
|
||||
width: 10px;
|
||||
height: 16px;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAMAAAAlvKiEAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRF///MAAAA////52QwgAAAAAN0Uk5T//8A18oNQQAAAGxJREFUeNqs0kEOwCAIRFHn3//QTUU6xMyyxii+jQosrTPkyPEM6IN3FtzIRk1U4dFeKWQiH6pRRowMVKEmvronEynkwj0uZJgR22+YLopPSo9P34wJSamLSU7lSIWLJU7NkNomNlhqxUeAAQC+TQLZyEuJBwAAAABJRU5ErkJggg==) no-repeat;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.clippy-top-left .clippy-tip {
|
||||
top: 100%;
|
||||
margin-top: 0px;
|
||||
left: 100%;
|
||||
margin-left: -50px;
|
||||
}
|
||||
|
||||
.clippy-top-right .clippy-tip {
|
||||
top: 100%;
|
||||
margin-top: 0px;
|
||||
left: 0;
|
||||
margin-left: 50px;
|
||||
background-position: -10px 0;
|
||||
|
||||
}
|
||||
|
||||
.clippy-bottom-right .clippy-tip {
|
||||
top: 0;
|
||||
margin-top: -16px;
|
||||
left: 0;
|
||||
margin-left: 50px;
|
||||
background-position: -10px -16px;
|
||||
}
|
||||
|
||||
.clippy-bottom-left .clippy-tip {
|
||||
top: 0;
|
||||
margin-top: -16px;
|
||||
left: 100%;
|
||||
margin-left: -50px;
|
||||
background-position: 0px -16px;
|
||||
}
|
||||
|
BIN
src/web/static/clippy_assets/images/border.png
Executable file
BIN
src/web/static/clippy_assets/images/border.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 229 B |
BIN
src/web/static/clippy_assets/images/tip.png
Executable file
BIN
src/web/static/clippy_assets/images/tip.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 238 B |
261
src/web/static/images/bombe.svg
Normal file
261
src/web/static/images/bombe.svg
Normal file
|
@ -0,0 +1,261 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Turing-Welchman Bombe SVG animation
|
||||
|
||||
@author n1474335 [n1474335@gmail.com]
|
||||
@copyright Crown Copyright 2019
|
||||
@license Apache-2.0
|
||||
-->
|
||||
<svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" width="550px" height="350px" viewBox="0 0 550 350" xml:space="preserve" onload="setup(evt)">
|
||||
<script type="text/ecmascript">
|
||||
// <![CDATA[
|
||||
function setup(evt) {
|
||||
const allRotors = evt.target.ownerDocument.querySelectorAll('.rotor');
|
||||
const rotors = [];
|
||||
const initTime = Date.now();
|
||||
const tick = 360/26;
|
||||
const speed = 1000; // Time for one full rotation of the fast rotor
|
||||
|
||||
for (const rotor of allRotors) {
|
||||
const row = parseInt(rotor.classList.value.match(/row(\d)/)[1], 10);
|
||||
const startPos = row === 2 ? tick * Math.floor(Math.random()*26) : 0;
|
||||
const bbox = rotor.getBBox();
|
||||
const x = bbox.width/2 + bbox.x;
|
||||
const y = bbox.height/2 + bbox.y;
|
||||
const wait = row === 0 ? speed/26/1.5 : row === 1 ? speed : speed*26;
|
||||
|
||||
rotor.setAttribute("transform", "rotate(" + startPos + ", " + x + ", " + y + ")");
|
||||
|
||||
rotors.push({
|
||||
el: rotor,
|
||||
pos: startPos,
|
||||
x: x,
|
||||
y: y,
|
||||
last: initTime,
|
||||
wait: wait
|
||||
});
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
const now = Date.now();
|
||||
for (const rotor of rotors) {
|
||||
if (now > (rotor.last + rotor.wait)) {
|
||||
const numTicks = Math.floor((now - rotor.last) / rotor.wait);
|
||||
rotor.pos = (rotor.pos + tick * numTicks) % 360;
|
||||
rotor.last = rotor.last + rotor.wait * numTicks;
|
||||
rotor.el.setAttribute("transform", "rotate(" + rotor.pos + ", " + rotor.x + ", " + rotor.y + ")");
|
||||
} else {
|
||||
// Don't bother looking at the rest
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, speed/26/1.5 - 5);
|
||||
}
|
||||
// ]]>
|
||||
</script>
|
||||
<style>
|
||||
.row0 {--primary-color: #e5d41b;}
|
||||
.row1 {--primary-color: #be1e2d;}
|
||||
.row2 {--primary-color: #337f24;}
|
||||
</style>
|
||||
|
||||
<symbol id="rotor">
|
||||
<g transform="scale(0.1)">
|
||||
<circle id="casing" class="ring-color" style="fill: var(--primary-color, #be1e2d)" cx="692" cy="674" r="505"/>
|
||||
<circle id="alphabet-ring" fill="#7a5340" cx="692" cy="674" r="477"/>
|
||||
<circle id="face" fill="#412612" cx="692" cy="674" r="412"/>
|
||||
<circle id="plate" fill="#F1F2F2" cx="692" cy="674" r="185"/>
|
||||
<g id="alphabet" fill="#ffffff" font-family="sans-serif" font-size="36">
|
||||
<text transform="matrix(0.9731 0.2303 -0.2303 0.9731 779.8848 256.5488)">Z</text>
|
||||
<text transform="matrix(0.8903 0.4554 -0.4554 0.8903 875.2021 288.6948)">Y</text>
|
||||
<text transform="matrix(0.7561 0.6545 -0.6545 0.7561 961.8311 343.6372)">X</text>
|
||||
<text transform="matrix(0.5696 0.8219 -0.8219 0.5696 1033.0146 417.4619)">W</text>
|
||||
<text transform="matrix(0.3454 0.9385 -0.9385 0.3454 1088.1104 515.4634)">V</text>
|
||||
<text transform="matrix(0.1078 0.9942 -0.9942 0.1078 1114.4678 614.5894)">U</text>
|
||||
<text transform="matrix(-0.1302 0.9915 -0.9915 -0.1302 1116.1533 719.1523)">T</text>
|
||||
<text transform="matrix(-0.3623 0.9321 -0.9321 -0.3623 1093.8984 817.2373)">S</text>
|
||||
<text transform="matrix(-0.5767 0.817 -0.817 -0.5767 1048.0635 908.9912)">R</text>
|
||||
<text transform="matrix(-0.7588 0.6514 -0.6514 -0.7588 980.2002 988.5342)">Q</text>
|
||||
<text transform="matrix(-0.8942 0.4476 -0.4476 -0.8942 893.3154 1050.1416)">P</text>
|
||||
<text transform="matrix(-0.9766 0.215 -0.215 -0.9766 797.7471 1087.3965)">O</text>
|
||||
<text transform="matrix(-0.9996 -0.0298 0.0298 -0.9996 692.0405 1100.5684)">N</text>
|
||||
<text transform="matrix(-0.961 -0.2765 0.2765 -0.961 588.2832 1087.9443)">M</text>
|
||||
<text transform="matrix(-0.8654 -0.5011 0.5011 -0.8654 487.3003 1048.2471)">L</text>
|
||||
<text transform="matrix(-0.7244 -0.6894 0.6894 -0.7244 406.814 991.1895)">K</text>
|
||||
<text transform="matrix(-0.5456 -0.838 0.838 -0.5456 339.3418 913.8809)">J</text>
|
||||
<text transform="matrix(-0.3508 -0.9364 0.9364 -0.3508 294.3491 828.2446)">I</text>
|
||||
<text transform="matrix(-0.1295 -0.9916 0.9916 -0.1295 270.9233 742.6519)">H</text>
|
||||
<text transform="matrix(0.1153 -0.9933 0.9933 0.1153 266.8784 638.1958)">G</text>
|
||||
<text transform="matrix(0.3526 -0.9358 0.9358 0.3526 288.9976 533.9849)">F</text>
|
||||
<text transform="matrix(0.5645 -0.8255 0.8255 0.5645 333.0195 443.5317)">E</text>
|
||||
<text transform="matrix(0.7459 -0.666 0.666 0.7459 398.4409 364.5073)">D</text>
|
||||
<text transform="matrix(0.8853 -0.4651 0.4651 0.8853 482.4824 302.3418)">C</text>
|
||||
<text transform="matrix(0.9716 -0.2365 0.2365 0.9716 579.1396 262.5479)">B</text>
|
||||
<text transform="matrix(0.9999 0.0162 -0.0162 0.9999 680.5581 247.4321)">A</text>
|
||||
</g>
|
||||
<g id="holes">
|
||||
<circle stroke="#C49A6C" cx="692" cy="438.782" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="927.219" cy="674" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="692" cy="909.219" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="456.781" cy="674" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="574.391" cy="470.295" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="895.706" cy="556.39" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="809.609" cy="877.706" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="488.295" cy="791.609" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="488.295" cy="556.39" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="809.609" cy="470.293" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="895.706" cy="791.609" r="40.816"/>
|
||||
<circle stroke="#C49A6C" cx="574.391" cy="877.705" r="40.816"/>
|
||||
</g>
|
||||
<g id="plate-screws">
|
||||
<g>
|
||||
<circle fill="#BCBEC0" stroke="#808285" stroke-width="2" cx="693.223" cy="543.521" r="25.342"/>
|
||||
<line fill="#939598" stroke="#808285" stroke-width="7" x1="693.446" y1="519.729" x2="693" y2="567.311"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle fill="#BCBEC0" stroke="#808285" stroke-width="2" cx="822.479" cy="675.221" r="25.342"/>
|
||||
<line fill="#939598" stroke="#808285" stroke-width="7" x1="798.689" y1="674.999" x2="846.271" y2="675.445"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle fill="#BCBEC0" stroke="#808285" stroke-width="2" cx="562.605" cy="673.886" r="25.341"/>
|
||||
<line fill="#939598" stroke="#808285" stroke-width="7" x1="538.814" y1="673.663" x2="586.396" y2="674.108"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle fill="#BCBEC0" stroke="#808285" stroke-width="2" cx="691.863" cy="805.587" r="25.341"/>
|
||||
<line fill="#939598" stroke="#808285" stroke-width="7" x1="692.086" y1="781.798" x2="691.64" y2="829.379"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="pin" fill-rule="evenodd" fill="#D1D3D4" stroke="#939598"
|
||||
d="M956.275,448.71c-0.969,0.389-1.924,0.836-2.848,1.302
|
||||
c-5.875,2.962-10.965,7.197-16.168,11.152c-5.885,4.475-11.93,8.739-17.834,13.187c-10.688,8.049-21.533,15.888-32.24,23.907
|
||||
c-2.199,1.643-4.436,3.238-6.609,4.912c-14.525,11.139-28.867,22.534-43.559,33.452c-9.428,7.004-19.436,13.346-28.354,21.005
|
||||
c-12.459,10.694-24.723,22.592-35.869,34.65c-5.281,5.711-10.656,11.297-16.243,16.711c-3.063,2.967-5.874,5.382-8.114,8.997
|
||||
c-2.256,3.646-4.589,7.558-6.059,11.586c-2.757,7.565,0.999,14.189,3.413,21.241c5.533,16.161-0.56,32.288-11.42,44.675
|
||||
c-6.989,7.974-15.39,15.932-25.247,20.16c-5.45,2.337-12.057,3.965-18.012,4.105c-6.159,0.148-11.914-1.53-17.568-3.802
|
||||
c-5.215-2.094-14.936-7.879-20.029-3.758c-4.529,3.667-8.937,7.59-13.502,11.251c-1.359,1.088-2.961,2.043-4.15,3.33
|
||||
c0.001,0,16.224-17.545,16.596-17.948c2.86-3.092,0.168-9.246-1.066-12.486c-2.088-5.471-3.199-10.951-4.633-16.611
|
||||
c-1.02-4.023-1.841-8.044-1.548-12.215c0.637-9.093,3.98-19.698,8.918-27.356c6.4-9.925,16.834-18.061,27.527-22.879
|
||||
c14.831-6.684,29.543-3.252,44.133,2.23c5.441,2.044,12.285-2.206,16.829-4.831c6.116-3.534,11.542-8.171,16.117-13.547
|
||||
c9.109-10.707,19.505-20.119,29.089-30.368c4.945-5.288,10.229-10.295,15.316-15.45l25.586-29.884l31.963-43.979
|
||||
c0,0,29.025-38.288,29.113-38.409c9.037-11.917,24.822-22.94,25.588-39.161c0.617-13.024-14.27-17.184-24.727-16.841
|
||||
c-7.41,0.242-16.311,0.894-23.117,4.161c-15.1,7.248-28.342,15.616-34.676,31.979c-2.504,6.464-4.865,12.671-6.76,19.319
|
||||
c-2.051,7.208-5.539,11.212-9.826,17.088c-10.779,14.778-24.389,24.73-40.998,32.1c-4.74,2.104-9.229,4.293-14.08,6.129
|
||||
c-3.961,1.5-9.706,3.104-12.91,5.747c-5.948,4.907-10.334,14.214-13.357,21.205c-1.911,4.418-3.278,9.046-5.009,13.535
|
||||
c-2.069,5.37-2.532,11.326-4.88,16.507c-1.33,2.935-1.91,5.994-4.104,8.414c-2.609,2.877-4.623,4.939-8.159,6.693
|
||||
c-3.45,1.713-6.487,3.997-10.305,4.736c-2.717,0.528-5.277,1.418-8.023,1.794c-8.203,1.127-16.54,1.73-24.695,3.159
|
||||
c-3.994,0.7-7.947,2.283-11.792,3.534c-5.167,1.681-10.116,5.972-14.846,8.78c-10.3,6.119-20.007,15.004-27.479,24.277
|
||||
c-5.337,6.625-8.976,14.32-11.926,22.251c-2.169,5.833-4.357,11.754-5.061,17.977c-0.564,5.001-0.074,10.062-0.502,15.077
|
||||
c-0.706,8.26-3.203,17.47-9.294,23.414c-5.363,5.234-14.174,10.834-21.666,12.043c-7.607,1.226-15.016,0.118-20.697-5.407
|
||||
c-5.092-4.954-9.277-11.304-15.816-14.539c-3.873-1.917-8.116-2.357-12.351-1.588c-10.82,1.965-17.767,7.374-18.428,18.637
|
||||
c-0.545,9.325,1.999,15.171,6.731,22.947c4.323,7.103,5.315,15.456,9.255,22.756c4.052,7.503,7.825,15.248,12.169,22.583
|
||||
c3.05,5.156,6.832,9.664,10.749,14.176c1.717,1.978,3.554,4.901,5.732,6.378c5.639,3.827,10.784,3.305,17.032,1.951
|
||||
c2.175-0.473,3.233,0.047,4.694,1.679c1.557,1.74,1.399,1.609,0.505,3.68c-2.732,6.329-4.573,12.085-0.1,18.199
|
||||
c3.421,4.675,8.728,9.01,13.531,12.271c7.165,4.865,14.799,8.835,22.414,12.933c8.94,4.808,18.489,8.188,27.963,11.765
|
||||
c6.597,2.491,11.068,7.997,17.229,11.186c6.945,3.595,13.775,1.032,19.691-3.353c5.688-4.216,9.634-9.578,10.066-16.804
|
||||
c0.415-6.938-1.239-14.501-5.51-20.082c-4.163-5.439-10.751-8.996-13.229-15.664c-2.506-6.741-0.296-14.597,1.313-21.3
|
||||
c1.606-6.687,3.798-12.642,9.227-17.17c5.458-4.554,12.49-7.653,19.583-8.294c7.954-0.721,15.985-0.105,23.912-1.162
|
||||
c7.9-1.052,15.855-4.074,22.918-7.696c5.104-2.616,9.105-6.979,13.309-10.789c8.875-8.052,18.1-16.759,23.735-27.459
|
||||
c4.125-7.834,8.521-15.675,11.016-24.222c1.154-3.962,2.098-8.083,2.316-12.204c0.424-7.886-1.686-16.176,2.564-23.391
|
||||
c5.645-9.582,14.869-17.408,25.563-20.561c8.727-2.571,17.697-4.624,25.963-8.522c7.234-3.413,16-7.686,20.182-14.833
|
||||
c1.822-3.116,3.109-6.775,4.361-10.158c1.752-4.719,3.648-9.389,5.4-14.108c2.082-5.625,4.016-10.898,6.887-16.146
|
||||
c2.551-4.656,6.072-7.849,9.471-11.864c2.504-2.956,4.539-5.815,7.773-8.031c3.229-2.208,6.805-3.835,10.088-5.952
|
||||
c3.469-2.237,6.955-4.47,10.578-6.445c4.242-2.312,8.557-3.716,13.207-4.92c10.176-2.643,19.592-6.376,26.959-14.134
|
||||
c6.977-7.349,13.82-15.747,16.816-25.566c2.938-9.634,3.967-20.147,2.086-30.07c-0.973-5.124-2.291-11.331-5.824-15.367
|
||||
C964.873,446.457,960.432,447.042,956.275,448.71z"/>
|
||||
<circle id="center-nut" fill="#d1a26a" stroke="#a88e75" stroke-width="25" cx="692" cy="674" r="60"/>
|
||||
<g id="pin-screws">
|
||||
<circle fill="#BCBEC0" stroke="#58595B" cx="768.174" cy="545.468" r="18.485"/>
|
||||
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="750.079" y1="545.298" x2="786.273" y2="545.635"/>
|
||||
<path fill="#BCBEC0" stroke="#58595B" d="M819.834,579.439c-10.211-0.094-18.564,8.103-18.66,18.313
|
||||
c-0.094,10.208,8.102,18.562,18.313,18.657c10.205,0.095,18.563-8.102,18.656-18.312
|
||||
C838.24,587.889,830.041,579.535,819.834,579.439z"/>
|
||||
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="819.49" y1="616.02" x2="819.826" y2="579.826"/>
|
||||
<circle fill="#BCBEC0" stroke="#58595B" cx="626.351" cy="736.463" r="18.486"/>
|
||||
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="639.026" y1="749.378" x2="613.672" y2="723.543"/>
|
||||
<circle fill="#BCBEC0" stroke="#58595B" cx="526.668" cy="709.157" r="18.485"/>
|
||||
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="526.498" y1="727.252" x2="526.837" y2="691.059"/>
|
||||
<circle fill="#BCBEC0" stroke="#58595B" cx="654.839" cy="839.752" r="18.486"/>
|
||||
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="636.744" y1="839.583" x2="672.937" y2="839.922"/>
|
||||
</g>
|
||||
<g id="plate-mini-screws">
|
||||
<circle fill="#E6E7E8" stroke="#A7A9AC" cx="786.206" cy="769.987" r="15.332"/>
|
||||
<line fill="#E6E7E8" stroke="#A7A9AC" stroke-width="5" x1="775.494" y1="780.5" x2="796.92" y2="759.472"/>
|
||||
<circle fill="#E6E7E8" stroke="#A7A9AC" cx="599.966" cy="580.227" r="15.333"/>
|
||||
<line fill="#E6E7E8" stroke="#A7A9AC" stroke-width="5" x1="589.254" y1="590.74" x2="610.682" y2="569.712"/>
|
||||
</g>
|
||||
<g id="spring">
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="561.307" y1="722.169" x2="534.592" y2="739.515"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="565.744" y1="726.689" x2="539.028" y2="744.034"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="570.179" y1="731.21" x2="543.465" y2="748.555"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="574.616" y1="735.73" x2="547.901" y2="753.074"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="579.052" y1="740.25" x2="552.336" y2="757.596"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="583.722" y1="745.008" x2="557.007" y2="762.354"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="588.158" y1="749.529" x2="561.443" y2="766.873"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="592.595" y1="754.047" x2="565.879" y2="771.393"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="597.03" y1="758.568" x2="570.315" y2="775.913"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="601.466" y1="763.088" x2="574.751" y2="780.434"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="605.902" y1="767.608" x2="579.188" y2="784.953"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="610.338" y1="772.128" x2="583.623" y2="789.474"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="614.775" y1="776.647" x2="588.06" y2="793.994"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="619.211" y1="781.169" x2="592.496" y2="798.513"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="623.648" y1="785.688" x2="596.933" y2="803.034"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="628.084" y1="790.209" x2="601.369" y2="807.553"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="632.52" y1="794.728" x2="605.806" y2="812.074"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="636.956" y1="799.249" x2="610.241" y2="816.593"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="641.393" y1="803.77" x2="614.678" y2="821.113"/>
|
||||
<line fill="none" stroke="#808285" stroke-width="2" x1="645.83" y1="808.288" x2="619.114" y2="825.634"/>
|
||||
</g>
|
||||
<g id="face-nuts">
|
||||
<g>
|
||||
<polygon fill-rule="evenodd" fill="#E6E7E8" stroke="#939598" points="340.617,715.657 300.423,704.888
|
||||
289.653,664.693 319.077,635.27 359.271,646.04 370.041,686.233 "/>
|
||||
<path fill="#BCBEC0" stroke="#58595B" stroke-width="3" d="M306.759,698.144
|
||||
c-12.516-12.755-12.326-33.236,0.428-45.752c12.752-12.516,33.234-12.324,45.75,0.431c12.516,12.75,12.324,33.233-0.428,45.748
|
||||
C339.755,711.087,319.275,710.895,306.759,698.144z"/>
|
||||
<line fill="#939598" stroke="#808285" stroke-width="7" x1="351.527" y1="654.208" x2="308.171" y2="696.757"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill-rule="evenodd" fill="#E6E7E8" stroke="#939598" points="702.77,354.86 662.576,344.091
|
||||
651.806,303.896 681.23,274.473 721.424,285.243 732.194,325.437 "/>
|
||||
<path fill="#603913" stroke="#3C2415" stroke-width="3" d="M668.912,337.347c-12.516-12.755-12.325-33.236,0.428-45.752c12.752-12.516,33.235-12.324,45.75,0.431
|
||||
c12.516,12.75,12.324,33.233-0.428,45.748C701.909,350.29,681.428,350.098,668.912,337.347z"/>
|
||||
<line fill="#939598" stroke="#3C2415" stroke-width="7" x1="713.68" y1="293.411" x2="670.324" y2="335.96"/>
|
||||
<line fill="#939598" stroke="#3C2415" stroke-width="7" x1="670.324" y1="293.392" x2="713.68" y2="335.941"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill-rule="evenodd" fill="#E6E7E8" stroke="#939598" points="702.77,1072.723 662.576,1061.953
|
||||
651.806,1021.759 681.23,992.335 721.424,1003.105 732.193,1043.299 "/>
|
||||
<path fill="#603913" stroke="#3C2415" stroke-width="3" d="M668.912,1055.21c-12.516-12.756-12.325-33.236,0.428-45.752c12.752-12.516,33.235-12.324,45.75,0.431
|
||||
c12.516,12.75,12.324,33.233-0.428,45.747C701.909,1068.152,681.428,1067.96,668.912,1055.21z"/>
|
||||
<line fill="#939598" stroke="#3C2415" stroke-width="7" x1="713.68" y1="1011.272" x2="670.324" y2="1053.822"/>
|
||||
<line fill="#939598" stroke="#3C2415" stroke-width="7" x1="670.324" y1="1011.254" x2="713.68" y2="1053.804"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill-rule="evenodd" fill="#E6E7E8" stroke="#939598" points="1038.556,715.658 1078.749,704.888
|
||||
1089.521,664.694 1060.097,635.27 1019.901,646.041 1009.132,686.234 "/>
|
||||
<path fill="#BCBEC0" stroke="#58595B" stroke-width="3" d="M1072.413,698.145
|
||||
c12.516-12.755,12.326-33.236-0.428-45.752c-12.752-12.516-33.234-12.324-45.75,0.431c-12.516,12.75-12.324,33.233,0.428,45.748
|
||||
C1039.417,711.087,1059.897,710.896,1072.413,698.145z"/>
|
||||
<line fill="#939598" stroke="#808285" stroke-width="7" x1="1027.646" y1="654.208" x2="1071.001" y2="696.757"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<g class="rotor row0"><use xlink:href="#rotor" x="0" y="0" /></g>
|
||||
<g class="rotor row0"><use xlink:href="#rotor" x="105" y="0" /></g>
|
||||
<g class="rotor row0"><use xlink:href="#rotor" x="210" y="0" /></g>
|
||||
<g class="rotor row0"><use xlink:href="#rotor" x="315" y="0" /></g>
|
||||
<g class="rotor row0"><use xlink:href="#rotor" x="420" y="0" /></g>
|
||||
<g class="rotor row1"><use xlink:href="#rotor" x="0" y="105" /></g>
|
||||
<g class="rotor row1"><use xlink:href="#rotor" x="105" y="105" /></g>
|
||||
<g class="rotor row1"><use xlink:href="#rotor" x="210" y="105" /></g>
|
||||
<g class="rotor row1"><use xlink:href="#rotor" x="315" y="105" /></g>
|
||||
<g class="rotor row1"><use xlink:href="#rotor" x="420" y="105" /></g>
|
||||
<g class="rotor row2"><use xlink:href="#rotor" x="0" y="210" /></g>
|
||||
<g class="rotor row2"><use xlink:href="#rotor" x="105" y="210" /></g>
|
||||
<g class="rotor row2"><use xlink:href="#rotor" x="210" y="210" /></g>
|
||||
<g class="rotor row2"><use xlink:href="#rotor" x="315" y="210" /></g>
|
||||
<g class="rotor row2"><use xlink:href="#rotor" x="420" y="210" /></g>
|
||||
</svg>
|
After Width: | Height: | Size: 20 KiB |
|
@ -86,7 +86,7 @@ div.toggle-string {
|
|||
}
|
||||
|
||||
.operation .form-control {
|
||||
padding: 20px 12px 6px 12px;
|
||||
padding: 20px 12px 6px 12px !important;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
background-image: none;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
/* Libraries */
|
||||
import "highlight.js/styles/vs.css";
|
||||
import "../static/clippy_assets/clippy.css";
|
||||
|
||||
/* Frameworks */
|
||||
import "./vendors/bootstrap.scss";
|
||||
|
|
|
@ -21,6 +21,14 @@
|
|||
background-color: var(--secondary-background-colour);
|
||||
}
|
||||
|
||||
#controls-content {
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transform-origin: center left;
|
||||
}
|
||||
|
||||
#auto-bake-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
|
|
|
@ -73,6 +73,30 @@
|
|||
background-color: var(--primary-background-colour);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
#output-loader-animation {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
top: 10%;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
#output-loader .loading-msg {
|
||||
opacity: 1;
|
||||
font-family: var(--primary-font-family);
|
||||
line-height: var(--primary-line-height);
|
||||
color: var(--primary-font-colour);
|
||||
left: unset;
|
||||
top: 30%;
|
||||
position: relative;
|
||||
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
@ -139,16 +163,6 @@
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#output-loader .loading-msg {
|
||||
opacity: 1;
|
||||
font-family: var(--primary-font-family);
|
||||
line-height: var(--primary-line-height);
|
||||
color: var(--primary-font-colour);
|
||||
top: 50%;
|
||||
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
#magic {
|
||||
opacity: 1;
|
||||
visibility: visibile;
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
left: calc(50% - 200px);
|
||||
top: calc(50% + 50px);
|
||||
text-align: center;
|
||||
margin-top: 50px;
|
||||
opacity: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.loading-msg.loading {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue