<NME> main.js
<BEF> /**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// It's important that the polyfills and error fallback get loaded first!
import {} from "./browser/Polyfills.js"
import {hookErrorHandler} from "./fallback.js"
hookErrorHandler();
import {doDetectIssues} from "./issues.js"
doDetectIssues();

import {CircuitStats} from "./circuit/CircuitStats.js"
import {CooldownThrottle} from "./base/CooldownThrottle.js"
import {Config} from "./Config.js"
import {DisplayedInspector} from "./ui/DisplayedInspector.js"
import {Painter} from "./draw/Painter.js"
import {Rect} from "./math/Rect.js"
import {RestartableRng} from "./base/RestartableRng.js"
import {Revision} from "./base/Revision.js"
import {initSerializer, fromJsonText_CircuitDefinition} from "./circuit/Serializer.js"
import {TouchScrollBlocker} from "./browser/TouchScrollBlocker.js"
import {Util} from "./base/Util.js"
import {initializedWglContext} from "./webgl/WglContext.js"
import {watchDrags, isMiddleClicking, eventPosRelativeTo} from "./browser/MouseWatcher.js"
import {ObservableValue, ObservableSource} from "./base/Obs.js"
import {initExports, obsExportsIsShowing} from "./ui/exports.js"
import {initForge, obsForgeIsShowing} from "./ui/forge.js"
import {initMenu, obsMenuIsShowing, closeMenu} from "./ui/menu.js"
import {initUndoRedo} from "./ui/undo.js"
import {initClear} from "./ui/clear.js"
import {initUrlCircuitSync} from "./ui/url.js"
import {initTitleSync} from "./ui/title.js"
import {simulate} from "./ui/sim.js"
import {GatePainting} from "./draw/GatePainting.js"
import {GATE_CIRCUIT_DRAWER} from "./ui/DisplayedCircuit.js"
import {GateColumn} from "./circuit/GateColumn.js";
import {Point} from "./math/Point.js";
initSerializer(
    GatePainting.LABEL_DRAWER,
    GatePainting.MATRIX_DRAWER,
    GATE_CIRCUIT_DRAWER,
    GatePainting.LOCATION_INDEPENDENT_GATE_DRAWER);

const canvasDiv = document.getElementById("canvasDiv");

//noinspection JSValidateTypes
/** @type {!HTMLCanvasElement} */
const canvas = document.getElementById("drawCanvas");
//noinspection JSValidateTypes
if (!canvas) {
    throw new Error("Couldn't find 'drawCanvas'");
}
canvas.width = canvasDiv.clientWidth;
canvas.height = window.innerHeight*0.9;
let haveLoaded = false;
const semiStableRng = (() => {
    const target = {cur: new RestartableRng()};
    let cycleRng;
    cycleRng = () => {
        target.cur = new RestartableRng();
        //noinspection DynamicallyGeneratedCodeJS
        setTimeout(cycleRng, Config.SEMI_STABLE_RANDOM_VALUE_LIFETIME_MILLIS*0.99);
    };
    cycleRng();
    return target;
})();

//noinspection JSValidateTypes
/** @type {!HTMLDivElement} */
const inspectorDiv = document.getElementById("inspectorDiv");

/** @type {ObservableValue.<!DisplayedInspector>} */
const displayed = new ObservableValue(
    DisplayedInspector.empty(new Rect(0, 0, canvas.clientWidth, canvas.clientHeight)));
const mostRecentStats = new ObservableValue(CircuitStats.EMPTY);
/** @type {!Revision} */
let revision = Revision.startingAt(displayed.get().snapshot());

revision.latestActiveCommit().subscribe(jsonText => {
    let circuitDef = fromJsonText_CircuitDefinition(jsonText);
    let newInspector = displayed.get().withCircuitDefinition(circuitDef);
    displayed.set(newInspector);
});

/**
 * @param {!DisplayedInspector} curInspector
 * @returns {{w: number, h: !number}}
 */
let desiredCanvasSizeFor = curInspector => {
    return {
        w: Math.max(canvasDiv.clientWidth, curInspector.desiredWidth()),
        h: curInspector.desiredHeight()
    };
};

/**
 * @param {!DisplayedInspector} ins
 * @returns {!DisplayedInspector}
 */
const syncArea = ins => {
    let size = desiredCanvasSizeFor(ins);
    ins.updateArea(new Rect(0, 0, size.w, size.h));
    return ins;
};

// Gradually fade out old errors as user manipulates circuit.
displayed.observable().
    map(e => e.displayedCircuit.circuitDefinition).
    whenDifferent(Util.CUSTOM_IS_EQUAL_TO_EQUALITY).
    subscribe(() => {
        let errDivStyle = document.getElementById('error-div').style;
        errDivStyle.opacity *= 0.9;
        if (errDivStyle.opacity < 0.06) {
            errDivStyle.display = 'None'
        }
    });

/** @type {!CooldownThrottle} */
let redrawThrottle;
const scrollBlocker = new TouchScrollBlocker(canvasDiv);
const redrawNow = () => {
    if (!haveLoaded) {
        // Don't draw while loading. It's a huge source of false-positive circuit-load-failed errors during development.
        return;
    }

    let shown = syncArea(displayed.get()).previewDrop();
    if (displayed.get().hand.isHoldingSomething() && !shown.hand.isHoldingSomething()) {
        shown = shown.withHand(shown.hand.withHeldGateColumn(new GateColumn([]), new Point(0, 0)))
    }
    let stats = simulate(shown.displayedCircuit.circuitDefinition);
    mostRecentStats.set(stats);

    let size = desiredCanvasSizeFor(shown);
    canvas.width = size.w;
    canvas.height = size.h;
    let painter = new Painter(canvas, semiStableRng.cur.restarted());
    shown.updateArea(painter.paintableArea());
    shown.paint(painter, stats);
    painter.paintDeferred();

    displayed.get().hand.paintCursor(painter);
    scrollBlocker.setBlockers(painter.touchBlockers, painter.desiredCursorStyle);
    canvas.style.cursor = painter.desiredCursorStyle || 'auto';

    let dt = displayed.get().stableDuration();
    if (dt < Infinity) {
        window.requestAnimationFrame(() => redrawThrottle.trigger());
    }
};

redrawThrottle = new CooldownThrottle(redrawNow, Config.REDRAW_COOLDOWN_MILLIS, 0.1, true);
window.addEventListener('resize', () => redrawThrottle.trigger(), false);
displayed.observable().subscribe(() => redrawThrottle.trigger());

/** @type {undefined|!string} */
let clickDownGateButtonKey = undefined;
canvasDiv.addEventListener('click', ev => {
    let pt = eventPosRelativeTo(ev, canvasDiv);
    let curInspector = displayed.get();
    if (curInspector.tryGetHandOverButtonKey() !== clickDownGateButtonKey) {
        return;
    }
    let clicked = syncArea(curInspector.withHand(curInspector.hand.withPos(pt))).tryClick();
    if (clicked !== undefined) {
        revision.commit(clicked.afterTidyingUp().snapshot());
    }
});

watchDrags(canvasDiv,
    /**
     * Grab
     * @param {!Point} pt
     * @param {!MouseEvent|!TouchEvent} ev
     */
    (pt, ev) => {
        let oldInspector = displayed.get();
        let newHand = oldInspector.hand.withPos(pt);
        let newInspector = syncArea(oldInspector.withHand(newHand));
        clickDownGateButtonKey = (
            ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey ? undefined : newInspector.tryGetHandOverButtonKey());
        if (clickDownGateButtonKey !== undefined) {
            displayed.set(newInspector);
            return;
        }

        newInspector = newInspector.afterGrabbing(ev.shiftKey, ev.ctrlKey || ev.metaKey);
        if (displayed.get().isEqualTo(newInspector) || !newInspector.hand.isBusy()) {
            return;
        }

        // Add extra wire temporarily.
        revision.startedWorkingOnCommit();
        displayed.set(
            syncArea(oldInspector.withHand(newHand).withJustEnoughWires(newInspector.hand, 1)).
                afterGrabbing(ev.shiftKey, ev.ctrlKey || ev.metaKey, false, ev.altKey));

        ev.preventDefault();
    },
    /**
     * Cancel
     * @param {!MouseEvent|!TouchEvent} ev
     */
    ev => {
        revision.cancelCommitBeingWorkedOn();
        ev.preventDefault();
    },
    /**
     * Drag
     * @param {undefined|!Point} pt
     * @param {!MouseEvent|!TouchEvent} ev
     */
    (pt, ev) => {
        if (!displayed.get().hand.isBusy()) {
            return;
        }

        let newHand = displayed.get().hand.withPos(pt);
        let newInspector = displayed.get().withHand(newHand);
        displayed.set(newInspector);
        ev.preventDefault();
    },
    /**
     * Drop
     * @param {undefined|!Point} pt
     * @param {!MouseEvent|!TouchEvent} ev
     */
    (pt, ev) => {
        if (!displayed.get().hand.isBusy()) {
            return;
        }

        let newHand = displayed.get().hand.withPos(pt);
        let newInspector = syncArea(displayed.get()).withHand(newHand).afterDropping().afterTidyingUp();
        let clearHand = newInspector.hand.withPos(undefined);
        let clearInspector = newInspector.withJustEnoughWires(clearHand, 0);
        revision.commit(clearInspector.snapshot());
        ev.preventDefault();
    });

// Middle-click to delete a gate.
canvasDiv.addEventListener('mousedown', ev => {
    if (!isMiddleClicking(ev)) {
        return;
    }
document.addEventListener("keydown", e => {
    const Y_KEY = 89;
    const Z_KEY = 90;
    let isUndo = e.keyCode == Z_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
    let isRedo1 = e.keyCode == Z_KEY && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;
    let isRedo2 = e.keyCode == Y_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
    if (isUndo) {
        restore(revision.undo());
    }
        newInspector = cur.
            withHand(newHand).
            afterGrabbing(false, false, true, false). // Grab the gate.
            withHand(newHand). // Lose the gate.
            afterTidyingUp().
            withJustEnoughWires(newHand, 0);
    }
    if (!displayed.get().isEqualTo(newInspector)) {
        revision.commit(newInspector.snapshot());
        ev.preventDefault();
    }
});

// When mouse moves without dragging, track it (for showing hints and things).
canvasDiv.addEventListener('mousemove', ev => {
    if (!displayed.get().hand.isBusy()) {
        let newHand = displayed.get().hand.withPos(eventPosRelativeTo(ev, canvas));
        let newInspector = displayed.get().withHand(newHand);
        displayed.set(newInspector);
    }
});
canvasDiv.addEventListener('mouseleave', () => {
    if (!displayed.get().hand.isBusy()) {
        let newHand = displayed.get().hand.withPos(undefined);
        let newInspector = displayed.get().withHand(newHand);
        displayed.set(newInspector);
    }
});

let obsIsAnyOverlayShowing = new ObservableSource();
initUrlCircuitSync(revision);
initExports(revision, mostRecentStats, obsIsAnyOverlayShowing.observable());
initForge(revision, obsIsAnyOverlayShowing.observable());
initUndoRedo(revision, obsIsAnyOverlayShowing.observable());
initClear(revision, obsIsAnyOverlayShowing.observable());
initMenu(revision, obsIsAnyOverlayShowing.observable());
initTitleSync(revision);
obsForgeIsShowing.
    zipLatest(obsExportsIsShowing, (e1, e2) => e1 || e2).
    zipLatest(obsMenuIsShowing, (e1, e2) => e1 || e2).
    whenDifferent().
    subscribe(e => {
        obsIsAnyOverlayShowing.send(e);
        canvasDiv.tabIndex = e ? -1 : 0;
    });

// If the webgl initialization is going to fail, don't fail during the module loading phase.
haveLoaded = true;
setTimeout(() => {
    inspectorDiv.style.display = 'block';
    redrawNow();
    document.getElementById("loading-div").style.display = 'none';
    document.getElementById("close-menu-button").style.display = 'block';
    if (!displayed.get().displayedCircuit.circuitDefinition.isEmpty()) {
        closeMenu();
    }

    try {
        initializedWglContext().onContextRestored = () => redrawThrottle.trigger();
    } catch (ex) {
        // If that failed, the user is already getting warnings about WebGL not being supported.
        // Just silently log it.
        console.error(ex);
    }
}, 0);

<MSG> Fixing several == instead ===

<DFF> @@ -258,9 +258,9 @@ window.addEventListener('resize', scheduleRedraw, false);
 document.addEventListener("keydown", e => {
     const Y_KEY = 89;
     const Z_KEY = 90;
-    let isUndo = e.keyCode == Z_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
-    let isRedo1 = e.keyCode == Z_KEY && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;
-    let isRedo2 = e.keyCode == Y_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
+    let isUndo = e.keyCode === Z_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
+    let isRedo1 = e.keyCode === Z_KEY && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;
+    let isRedo2 = e.keyCode === Y_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
     if (isUndo) {
         restore(revision.undo());
     }

{'commit': 'diff --git a/src/base/Seq.js b/src/base/Seq.js\nindex 0eb461f..d26dd3b 100644\n--- a/src/base/Seq.js\n+++ b/src/base/Seq.js\n@@ -657,13 +657,13 @@ class Seq {\n             }\n             let i = 0;\n             for (let e of self._iterable) {\n-                if (i == index) {\n+                if (i === index) {\n                     yield item;\n                 }\n                 i++;\n                 yield e;\n             }\n-            if (i == index) {\n+            if (i === index) {\n                 yield item;\n             }\n             if (i < index) {\ndiff --git a/src/circuit/CircuitDefinition.js b/src/circuit/CircuitDefinition.js\nindex 0becbad..7c7d0cc 100644\n--- a/src/circuit/CircuitDefinition.js\n+++ b/src/circuit/CircuitDefinition.js\n@@ -153,7 +153,7 @@ class CircuitDefinition {\n         for (let i = 1; i < width && col + i < this.columns.length; i++) {\n             for (let j = 0; j < height; j++) {\n                 let otherGate = this.findGateCoveringSlot(col+i, row+j);\n-                if (otherGate === undefined || otherGate.col == col) {\n+                if (otherGate === undefined || otherGate.col === col) {\n                     continue;\n                 }\n                 return {col: otherGate.col, row: otherGate.row};\n@@ -416,7 +416,7 @@ class CircuitDefinition {\n         let pts = Seq.range(this.numWires).\n             map(row => new Point(col, row)).\n             filter(pt => this.gateInSlot(pt.x, pt.y) === Gates.Special.SwapHalf);\n-        return !pts.any(pt => this.gateAtLocIsDisabledReason(pt) !== undefined) && pts.count() == 2;\n+        return !pts.any(pt => this.gateAtLocIsDisabledReason(pt) !== undefined) && pts.count() === 2;\n     }\n \n     /**\ndiff --git a/src/circuit/CircuitStats.js b/src/circuit/CircuitStats.js\nindex 3599f6d..7c211a6 100644\n--- a/src/circuit/CircuitStats.js\n+++ b/src/circuit/CircuitStats.js\n@@ -249,7 +249,7 @@ export default class CircuitStats {\n                 1,\n                 circuitDefinition.colIsMeasuredMask(col),\n                 // All wires have an output display in the after-last column.\n-                col == numCols ? -1 : circuitDefinition.colHasSingleQubitDisplayMask(col))\n+                col === numCols ? -1 : circuitDefinition.colHasSingleQubitDisplayMask(col))\n         ).toArray();\n         let qubitPairDensities = seq(pixelData.qubitPairDensityTexes).mapWithIndex((pixels, col) =>\n             CircuitStats.scatterAndDecohereDensities(\ndiff --git a/src/circuit/Controls.js b/src/circuit/Controls.js\nindex fc3fc25..bffb08a 100644\n--- a/src/circuit/Controls.js\n+++ b/src/circuit/Controls.js\n@@ -42,8 +42,8 @@ class Controls {\n      */\n     isEqualTo(other) {\n         return other instanceof Controls &&\n-            this.inclusionMask == other.inclusionMask &&\n-            this.desiredValueMask == other.desiredValueMask;\n+            this.inclusionMask === other.inclusionMask &&\n+            this.desiredValueMask === other.desiredValueMask;\n     }\n \n     /**\ndiff --git a/src/main.js b/src/main.js\nindex a5af6c2..bfb164e 100644\n--- a/src/main.js\n+++ b/src/main.js\n@@ -258,9 +258,9 @@ window.addEventListener(\'resize\', scheduleRedraw, false);\n document.addEventListener("keydown", e => {\n     const Y_KEY = 89;\n     const Z_KEY = 90;\n-    let isUndo = e.keyCode == Z_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n-    let isRedo1 = e.keyCode == Z_KEY && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;\n-    let isRedo2 = e.keyCode == Y_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n+    let isUndo = e.keyCode === Z_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n+    let isRedo1 = e.keyCode === Z_KEY && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;\n+    let isRedo2 = e.keyCode === Y_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n     if (isUndo) {\n         restore(revision.undo());\n     }\ndiff --git a/src/ui/GateFactory.js b/src/ui/GateFactory.js\nindex d37e24d..9cb170c 100644\n--- a/src/ui/GateFactory.js\n+++ b/src/ui/GateFactory.js\n@@ -56,7 +56,7 @@ const paintGateSymbol = args => {\n     }\n \n     let parts = symbol.split("^");\n-    if (parts.length != 2 || parts[0] === "" || parts[1] === "") {\n+    if (parts.length !== 2 || parts[0] === "" || parts[1] === "") {\n         painter.print(\n             symbol,\n             rect.x + rect.w/2,\ndiff --git a/src/widgets/InspectorWidget.js b/src/widgets/InspectorWidget.js\nindex 821d88b..5cdb6f1 100644\n--- a/src/widgets/InspectorWidget.js\n+++ b/src/widgets/InspectorWidget.js\n@@ -170,7 +170,7 @@ export default class InspectorWidget {\n         let hand = this.hand;\n         let circuitWidget = this.circuitWidget;\n         let previewCircuit = circuitWidget.previewDrop(hand);\n-        let previewHand = previewCircuit == circuitWidget ? hand : hand.withDrop();\n+        let previewHand = previewCircuit === circuitWidget ? hand : hand.withDrop();\n         return this.withHand(previewHand).withCircuitWidget(previewCircuit);\n     }\n \n', 'commit_subject': 'Fixing several == instead ===', 'repo_name': 'Strilanc/Quirk', 'committer': {'name': 'Craig Gidney', 'email': '4a8c8431fb5d2eab9aaa5bedb253e5c73287c466@gmail.com', 'time_sec': '1462180643', 'tz_offset': '-240', 'date': {'seconds': '1462180643'}}, 'commit_tree': 'e211ffbff9af12fc0a30e66d7cc79c7ec8136ab8', 'commit_message': 'Fixing several == instead ===\n', 'language_name': 'HTML', 'license': 'apache-2.0', 'hunks': '@@ -258,9 +258,9 @@ window.addEventListener(\'resize\', scheduleRedraw, false);\n document.addEventListener("keydown", e => {\n     const Y_KEY = 89;\n     const Z_KEY = 90;\n-    let isUndo = e.keyCode == Z_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n-    let isRedo1 = e.keyCode == Z_KEY && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;\n-    let isRedo2 = e.keyCode == Y_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n+    let isUndo = e.keyCode === Z_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n+    let isRedo1 = e.keyCode === Z_KEY && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;\n+    let isRedo2 = e.keyCode === Y_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n     if (isUndo) {\n         restore(revision.undo());\n     }\n', 'addition_count': 3, 'deletion_count': 3, 'src_file': 'a/src/main.js', 'tgt_file': 'b/src/main.js', 'file_extension': '.js', 'before_file': '/**\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// It\'s important that the polyfills and error fallback get loaded first!\nimport {} from "./browser/Polyfills.js"\nimport {hookErrorHandler} from "./fallback.js"\nhookErrorHandler();\nimport {doDetectIssues} from "./issues.js"\ndoDetectIssues();\n\nimport {CircuitStats} from "./circuit/CircuitStats.js"\nimport {CooldownThrottle} from "./base/CooldownThrottle.js"\nimport {Config} from "./Config.js"\nimport {DisplayedInspector} from "./ui/DisplayedInspector.js"\nimport {Painter} from "./draw/Painter.js"\nimport {Rect} from "./math/Rect.js"\nimport {RestartableRng} from "./base/RestartableRng.js"\nimport {Revision} from "./base/Revision.js"\nimport {initSerializer, fromJsonText_CircuitDefinition} from "./circuit/Serializer.js"\nimport {TouchScrollBlocker} from "./browser/TouchScrollBlocker.js"\nimport {Util} from "./base/Util.js"\nimport {initializedWglContext} from "./webgl/WglContext.js"\nimport {watchDrags, isMiddleClicking, eventPosRelativeTo} from "./browser/MouseWatcher.js"\nimport {ObservableValue, ObservableSource} from "./base/Obs.js"\nimport {initExports, obsExportsIsShowing} from "./ui/exports.js"\nimport {initForge, obsForgeIsShowing} from "./ui/forge.js"\nimport {initMenu, obsMenuIsShowing, closeMenu} from "./ui/menu.js"\nimport {initUndoRedo} from "./ui/undo.js"\nimport {initClear} from "./ui/clear.js"\nimport {initUrlCircuitSync} from "./ui/url.js"\nimport {initTitleSync} from "./ui/title.js"\nimport {simulate} from "./ui/sim.js"\nimport {GatePainting} from "./draw/GatePainting.js"\nimport {GATE_CIRCUIT_DRAWER} from "./ui/DisplayedCircuit.js"\nimport {GateColumn} from "./circuit/GateColumn.js";\nimport {Point} from "./math/Point.js";\ninitSerializer(\n    GatePainting.LABEL_DRAWER,\n    GatePainting.MATRIX_DRAWER,\n    GATE_CIRCUIT_DRAWER,\n    GatePainting.LOCATION_INDEPENDENT_GATE_DRAWER);\n\nconst canvasDiv = document.getElementById("canvasDiv");\n\n//noinspection JSValidateTypes\n/** @type {!HTMLCanvasElement} */\nconst canvas = document.getElementById("drawCanvas");\n//noinspection JSValidateTypes\nif (!canvas) {\n    throw new Error("Couldn\'t find \'drawCanvas\'");\n}\ncanvas.width = canvasDiv.clientWidth;\ncanvas.height = window.innerHeight*0.9;\nlet haveLoaded = false;\nconst semiStableRng = (() => {\n    const target = {cur: new RestartableRng()};\n    let cycleRng;\n    cycleRng = () => {\n        target.cur = new RestartableRng();\n        //noinspection DynamicallyGeneratedCodeJS\n        setTimeout(cycleRng, Config.SEMI_STABLE_RANDOM_VALUE_LIFETIME_MILLIS*0.99);\n    };\n    cycleRng();\n    return target;\n})();\n\n//noinspection JSValidateTypes\n/** @type {!HTMLDivElement} */\nconst inspectorDiv = document.getElementById("inspectorDiv");\n\n/** @type {ObservableValue.<!DisplayedInspector>} */\nconst displayed = new ObservableValue(\n    DisplayedInspector.empty(new Rect(0, 0, canvas.clientWidth, canvas.clientHeight)));\nconst mostRecentStats = new ObservableValue(CircuitStats.EMPTY);\n/** @type {!Revision} */\nlet revision = Revision.startingAt(displayed.get().snapshot());\n\nrevision.latestActiveCommit().subscribe(jsonText => {\n    let circuitDef = fromJsonText_CircuitDefinition(jsonText);\n    let newInspector = displayed.get().withCircuitDefinition(circuitDef);\n    displayed.set(newInspector);\n});\n\n/**\n * @param {!DisplayedInspector} curInspector\n * @returns {{w: number, h: !number}}\n */\nlet desiredCanvasSizeFor = curInspector => {\n    return {\n        w: Math.max(canvasDiv.clientWidth, curInspector.desiredWidth()),\n        h: curInspector.desiredHeight()\n    };\n};\n\n/**\n * @param {!DisplayedInspector} ins\n * @returns {!DisplayedInspector}\n */\nconst syncArea = ins => {\n    let size = desiredCanvasSizeFor(ins);\n    ins.updateArea(new Rect(0, 0, size.w, size.h));\n    return ins;\n};\n\n// Gradually fade out old errors as user manipulates circuit.\ndisplayed.observable().\n    map(e => e.displayedCircuit.circuitDefinition).\n    whenDifferent(Util.CUSTOM_IS_EQUAL_TO_EQUALITY).\n    subscribe(() => {\n        let errDivStyle = document.getElementById(\'error-div\').style;\n        errDivStyle.opacity *= 0.9;\n        if (errDivStyle.opacity < 0.06) {\n            errDivStyle.display = \'None\'\n        }\n    });\n\n/** @type {!CooldownThrottle} */\nlet redrawThrottle;\nconst scrollBlocker = new TouchScrollBlocker(canvasDiv);\nconst redrawNow = () => {\n    if (!haveLoaded) {\n        // Don\'t draw while loading. It\'s a huge source of false-positive circuit-load-failed errors during development.\n        return;\n    }\n\n    let shown = syncArea(displayed.get()).previewDrop();\n    if (displayed.get().hand.isHoldingSomething() && !shown.hand.isHoldingSomething()) {\n        shown = shown.withHand(shown.hand.withHeldGateColumn(new GateColumn([]), new Point(0, 0)))\n    }\n    let stats = simulate(shown.displayedCircuit.circuitDefinition);\n    mostRecentStats.set(stats);\n\n    let size = desiredCanvasSizeFor(shown);\n    canvas.width = size.w;\n    canvas.height = size.h;\n    let painter = new Painter(canvas, semiStableRng.cur.restarted());\n    shown.updateArea(painter.paintableArea());\n    shown.paint(painter, stats);\n    painter.paintDeferred();\n\n    displayed.get().hand.paintCursor(painter);\n    scrollBlocker.setBlockers(painter.touchBlockers, painter.desiredCursorStyle);\n    canvas.style.cursor = painter.desiredCursorStyle || \'auto\';\n\n    let dt = displayed.get().stableDuration();\n    if (dt < Infinity) {\n        window.requestAnimationFrame(() => redrawThrottle.trigger());\n    }\n};\n\nredrawThrottle = new CooldownThrottle(redrawNow, Config.REDRAW_COOLDOWN_MILLIS, 0.1, true);\nwindow.addEventListener(\'resize\', () => redrawThrottle.trigger(), false);\ndisplayed.observable().subscribe(() => redrawThrottle.trigger());\n\n/** @type {undefined|!string} */\nlet clickDownGateButtonKey = undefined;\ncanvasDiv.addEventListener(\'click\', ev => {\n    let pt = eventPosRelativeTo(ev, canvasDiv);\n    let curInspector = displayed.get();\n    if (curInspector.tryGetHandOverButtonKey() !== clickDownGateButtonKey) {\n        return;\n    }\n    let clicked = syncArea(curInspector.withHand(curInspector.hand.withPos(pt))).tryClick();\n    if (clicked !== undefined) {\n        revision.commit(clicked.afterTidyingUp().snapshot());\n    }\n});\n\nwatchDrags(canvasDiv,\n    /**\n     * Grab\n     * @param {!Point} pt\n     * @param {!MouseEvent|!TouchEvent} ev\n     */\n    (pt, ev) => {\n        let oldInspector = displayed.get();\n        let newHand = oldInspector.hand.withPos(pt);\n        let newInspector = syncArea(oldInspector.withHand(newHand));\n        clickDownGateButtonKey = (\n            ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey ? undefined : newInspector.tryGetHandOverButtonKey());\n        if (clickDownGateButtonKey !== undefined) {\n            displayed.set(newInspector);\n            return;\n        }\n\n        newInspector = newInspector.afterGrabbing(ev.shiftKey, ev.ctrlKey || ev.metaKey);\n        if (displayed.get().isEqualTo(newInspector) || !newInspector.hand.isBusy()) {\n            return;\n        }\n\n        // Add extra wire temporarily.\n        revision.startedWorkingOnCommit();\n        displayed.set(\n            syncArea(oldInspector.withHand(newHand).withJustEnoughWires(newInspector.hand, 1)).\n                afterGrabbing(ev.shiftKey, ev.ctrlKey || ev.metaKey, false, ev.altKey));\n\n        ev.preventDefault();\n    },\n    /**\n     * Cancel\n     * @param {!MouseEvent|!TouchEvent} ev\n     */\n    ev => {\n        revision.cancelCommitBeingWorkedOn();\n        ev.preventDefault();\n    },\n    /**\n     * Drag\n     * @param {undefined|!Point} pt\n     * @param {!MouseEvent|!TouchEvent} ev\n     */\n    (pt, ev) => {\n        if (!displayed.get().hand.isBusy()) {\n            return;\n        }\n\n        let newHand = displayed.get().hand.withPos(pt);\n        let newInspector = displayed.get().withHand(newHand);\n        displayed.set(newInspector);\n        ev.preventDefault();\n    },\n    /**\n     * Drop\n     * @param {undefined|!Point} pt\n     * @param {!MouseEvent|!TouchEvent} ev\n     */\n    (pt, ev) => {\n        if (!displayed.get().hand.isBusy()) {\n            return;\n        }\n\n        let newHand = displayed.get().hand.withPos(pt);\n        let newInspector = syncArea(displayed.get()).withHand(newHand).afterDropping().afterTidyingUp();\n        let clearHand = newInspector.hand.withPos(undefined);\n        let clearInspector = newInspector.withJustEnoughWires(clearHand, 0);\n        revision.commit(clearInspector.snapshot());\n        ev.preventDefault();\n    });\n\n// Middle-click to delete a gate.\ncanvasDiv.addEventListener(\'mousedown\', ev => {\n    if (!isMiddleClicking(ev)) {\n        return;\n    }\ndocument.addEventListener("keydown", e => {\n    const Y_KEY = 89;\n    const Z_KEY = 90;\n    let isUndo = e.keyCode == Z_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n    let isRedo1 = e.keyCode == Z_KEY && e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;\n    let isRedo2 = e.keyCode == Y_KEY && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;\n    if (isUndo) {\n        restore(revision.undo());\n    }\n        newInspector = cur.\n            withHand(newHand).\n            afterGrabbing(false, false, true, false). // Grab the gate.\n            withHand(newHand). // Lose the gate.\n            afterTidyingUp().\n            withJustEnoughWires(newHand, 0);\n    }\n    if (!displayed.get().isEqualTo(newInspector)) {\n        revision.commit(newInspector.snapshot());\n        ev.preventDefault();\n    }\n});\n\n// When mouse moves without dragging, track it (for showing hints and things).\ncanvasDiv.addEventListener(\'mousemove\', ev => {\n    if (!displayed.get().hand.isBusy()) {\n        let newHand = displayed.get().hand.withPos(eventPosRelativeTo(ev, canvas));\n        let newInspector = displayed.get().withHand(newHand);\n        displayed.set(newInspector);\n    }\n});\ncanvasDiv.addEventListener(\'mouseleave\', () => {\n    if (!displayed.get().hand.isBusy()) {\n        let newHand = displayed.get().hand.withPos(undefined);\n        let newInspector = displayed.get().withHand(newHand);\n        displayed.set(newInspector);\n    }\n});\n\nlet obsIsAnyOverlayShowing = new ObservableSource();\ninitUrlCircuitSync(revision);\ninitExports(revision, mostRecentStats, obsIsAnyOverlayShowing.observable());\ninitForge(revision, obsIsAnyOverlayShowing.observable());\ninitUndoRedo(revision, obsIsAnyOverlayShowing.observable());\ninitClear(revision, obsIsAnyOverlayShowing.observable());\ninitMenu(revision, obsIsAnyOverlayShowing.observable());\ninitTitleSync(revision);\nobsForgeIsShowing.\n    zipLatest(obsExportsIsShowing, (e1, e2) => e1 || e2).\n    zipLatest(obsMenuIsShowing, (e1, e2) => e1 || e2).\n    whenDifferent().\n    subscribe(e => {\n        obsIsAnyOverlayShowing.send(e);\n        canvasDiv.tabIndex = e ? -1 : 0;\n    });\n\n// If the webgl initialization is going to fail, don\'t fail during the module loading phase.\nhaveLoaded = true;\nsetTimeout(() => {\n    inspectorDiv.style.display = \'block\';\n    redrawNow();\n    document.getElementById("loading-div").style.display = \'none\';\n    document.getElementById("close-menu-button").style.display = \'block\';\n    if (!displayed.get().displayedCircuit.circuitDefinition.isEmpty()) {\n        closeMenu();\n    }\n\n    try {\n        initializedWglContext().onContextRestored = () => redrawThrottle.trigger();\n    } catch (ex) {\n        // If that failed, the user is already getting warnings about WebGL not being supported.\n        // Just silently log it.\n        console.error(ex);\n    }\n}, 0);\n', 'message': 'Fixing several == instead ===\n', 'author_name': 'Craig Gidney', 'lang': 'js'}