<commit_before>
// UI Definition
var GuacamoleUI = {

    "viewport"    : document.getElementById("viewportClone"),
    "display"     : document.getElementById("display"),
    "menu"        : document.getElementById("menu"),
    "menuControl" : document.getElementById("menuControl"),
    "touchMenu"   : document.getElementById("touchMenu"),
    "logo"        : document.getElementById("status-logo"),
    "eventTarget" : document.getElementById("eventTarget"),

    "buttons": {

        "showClipboard": document.getElementById("showClipboard"),
        "showKeyboard" : document.getElementById("showKeyboard"),
        "ctrlAltDelete": document.getElementById("ctrlAltDelete"),
        "reconnect"    : document.getElementById("reconnect"),
        "logout"       : document.getElementById("logout")

    },

    "containers": {
        "state"    : document.getElementById("statusDialog"),
        "clipboard": document.getElementById("clipboardDiv"),
        "keyboard" : document.getElementById("keyboardContainer")
    },

    "state"     : document.getElementById("statusText"),
    "clipboard" : document.getElementById("clipboard")

};

// Constant UI initialization and behavior
(function() {

    var menu_shaded = false;

    var shade_interval = null;
    var show_interval = null;

    // Cache error image (might not be available when error occurs)
    var guacErrorImage = new Image();
    guacErrorImage.src = "images/noguacamole-logo-24.png";

    // Function for adding a class to an element
    var addClass;

    // Function for removing a class from an element
    var removeClass;

    // If Node.classList is supported, implement addClass/removeClass using that
    if (Node.classList) {

        addClass = function(element, classname) {
            element.classList.add(classname);
        };

        removeClass = function(element, classname) {
            element.classList.remove(classname);
        };

    }

    // Otherwise, implement own
    else {

        addClass = function(element, classname) {

            // Simply add new class
            element.className += " " + classname;

        };

        removeClass = function(element, classname) {

            // Filter out classes with given name
            element.className = element.className.replace(/([^ ]+)[ ]*/g,
                function(match, testClassname, spaces, offset, string) {

                    // If same class, remove
                    if (testClassname == classname)
                        return "";

                    // Otherwise, allow
                    return match;

                }
            );

        };

    }

    GuacamoleUI.hideStatus = function() {
        removeClass(document.body, "guac-error");
        GuacamoleUI.containers.state.style.visibility = "hidden";
        GuacamoleUI.display.style.opacity = "1";
    };

    GuacamoleUI.showStatus = function(text) {
        removeClass(document.body, "guac-error");
        GuacamoleUI.containers.state.style.visibility = "visible";
        GuacamoleUI.state.textContent = text;
        GuacamoleUI.display.style.opacity = "1";
    };

    GuacamoleUI.showError = function(error) {
        addClass(document.body, "guac-error");
        GuacamoleUI.state.textContent = error;
        GuacamoleUI.display.style.opacity = "0.1";
    };

    GuacamoleUI.shadeMenu = function() {

        if (!menu_shaded) {

            var step = Math.floor(GuacamoleUI.menu.offsetHeight / 10) + 1;
            var offset = 0;
            menu_shaded = true;

            window.clearInterval(show_interval);
            shade_interval = window.setInterval(function() {

                offset -= step;
                GuacamoleUI.menu.style.top = offset + "px";

                if (offset <= -GuacamoleUI.menu.offsetHeight) {
                    window.clearInterval(shade_interval);
                    GuacamoleUI.menu.style.visiblity = "hidden";
                }

            }, 30);
        }

    };

    GuacamoleUI.showMenu = function() {

        if (menu_shaded) {

            var step = Math.floor(GuacamoleUI.menu.offsetHeight / 5) + 1;
            var offset = -GuacamoleUI.menu.offsetHeight;
            menu_shaded = false;
            GuacamoleUI.menu.style.visiblity = "";

            window.clearInterval(shade_interval);
            show_interval = window.setInterval(function() {

                offset += step;

                if (offset >= 0) {
                    offset = 0;
                    window.clearInterval(show_interval);
                }

                GuacamoleUI.menu.style.top = offset + "px";

            }, 30);
        }

    };

    // Show/Hide clipboard
    GuacamoleUI.buttons.showClipboard.onclick = function() {

        var displayed = GuacamoleUI.containers.clipboard.style.display;
        if (displayed != "block") {
            GuacamoleUI.containers.clipboard.style.display = "block";
            GuacamoleUI.buttons.showClipboard.innerHTML = "Hide Clipboard";
        }
        else {
            GuacamoleUI.containers.clipboard.style.display = "none";
            GuacamoleUI.buttons.showClipboard.innerHTML = "Show Clipboard";
            GuacamoleUI.clipboard.onchange();
        }

    };

    // Assume no native OSK by default
    GuacamoleUI.assumeNativeOSK = false;

    // Show/Hide keyboard
    var keyboardResizeInterval = null;
    GuacamoleUI.buttons.showKeyboard.onclick = function() {

        // If we think the platform has a native OSK, use the event target to
        // cause it to display.
        if (GuacamoleUI.assumeNativeOSK) {
            GuacamoleUI.eventTarget.focus();
            return;
        }

        var displayed = GuacamoleUI.containers.keyboard.style.display;
        if (displayed != "block") {
            GuacamoleUI.containers.keyboard.style.display = "block";
            GuacamoleUI.buttons.showKeyboard.textContent = "Hide Keyboard";

            // Automatically update size
            window.onresize = updateKeyboardSize;
            keyboardResizeInterval = window.setInterval(updateKeyboardSize, 30);

            updateKeyboardSize();
        }
        else {
            GuacamoleUI.containers.keyboard.style.display = "none";
            GuacamoleUI.buttons.showKeyboard.textContent = "Show Keyboard";

            window.onresize = null;
            window.clearInterval(keyboardResizeInterval);
        }

    };

    // Logout
    GuacamoleUI.buttons.logout.onclick = function() {
        window.location.href = "logout";
    };

    // Timeouts for detecting if users wants menu to open or close
    var detectMenuOpenTimeout = null;
    var detectMenuCloseTimeout = null;

    // Clear detection timeouts
    GuacamoleUI.resetMenuDetect = function() {

        if (detectMenuOpenTimeout != null) {
            window.clearTimeout(detectMenuOpenTimeout);
            detectMenuOpenTimeout = null;
        }

        if (detectMenuCloseTimeout != null) {
            window.clearTimeout(detectMenuCloseTimeout);
            detectMenuCloseTimeout = null;
        }

    };

    // Initiate detection of menu open action. If not canceled through some
    // user event, menu will open.
    GuacamoleUI.startMenuOpenDetect = function() {

        if (!detectMenuOpenTimeout) {

            // Clear detection state
            GuacamoleUI.resetMenuDetect();

            // Wait and then show menu
            detectMenuOpenTimeout = window.setTimeout(function() {
                GuacamoleUI.showMenu();
                detectMenuOpenTimeout = null;
            }, 325);

        }

    };

    // Initiate detection of menu close action. If not canceled through some
    // user event, menu will close.
    GuacamoleUI.startMenuCloseDetect = function() {

        if (!detectMenuCloseTimeout) {

            // Clear detection state
            GuacamoleUI.resetMenuDetect();

            // Wait and then shade menu
            detectMenuCloseTimeout = window.setTimeout(function() {
                GuacamoleUI.shadeMenu();
                detectMenuCloseTimeout = null;
            }, 500);

        }

    };

    // Show menu if mouseover any part of menu
    GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.showMenu, true);

    // Stop detecting menu state change intents if mouse is over menu
    GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.resetMenuDetect, true);

    // When mouse hovers over top of screen, start detection of intent to open menu
    GuacamoleUI.menuControl.addEventListener('mousemove', GuacamoleUI.startMenuOpenDetect, true);

    var menuShowLongPressTimeout = null;

    GuacamoleUI.startLongPressDetect = function() {

        if (!menuShowLongPressTimeout) {

            menuShowLongPressTimeout = window.setTimeout(function() {

                menuShowLongPressTimeout = null;

                // Assume native OSK if menu shown via long-press
                GuacamoleUI.assumeNativeOSK = true;
                GuacamoleUI.showMenu();

            }, 800);

        }
    };

    GuacamoleUI.stopLongPressDetect = function() {
        window.clearTimeout(menuShowLongPressTimeout);
        menuShowLongPressTimeout = null;
    };

    // Reset event target (add content, reposition cursor in middle.
    GuacamoleUI.resetEventTarget = function() {
        GuacamoleUI.eventTarget.value = "GUAC";
        GuacamoleUI.eventTarget.selectionStart =
        GuacamoleUI.eventTarget.selectionEnd   = 2;
    };

    // Detect long-press at bottom of screen
    document.body.addEventListener('touchstart', GuacamoleUI.startLongPressDetect, true);

    // Reconnect button
    GuacamoleUI.buttons.reconnect.onclick = function() {
        window.location.reload();
    };

    // On-screen keyboard
    GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty-mobile.xml");
    GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard.getElement());

    // Function for automatically updating keyboard size
    var lastKeyboardWidth;
    function updateKeyboardSize() {
        var currentSize = GuacamoleUI.keyboard.getElement().offsetWidth;
        if (lastKeyboardWidth != currentSize) {
            GuacamoleUI.keyboard.resize(currentSize);
            lastKeyboardWidth = currentSize;
        }
    };

})();

// Tie UI events / behavior to a specific Guacamole client
GuacamoleUI.attach = function(guac) {

    var guac_display = guac.getDisplay();

    // When mouse enters display, start detection of intent to close menu
    guac_display.addEventListener('mouseover', GuacamoleUI.startMenuCloseDetect, true);

    guac_display.onclick = function(e) {
        e.preventDefault();
        return false;
    };

    // Mouse
    var mouse = new Guacamole.Mouse(guac_display);
    mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
        function(mouseState) {

            // Determine mouse position within view
            var mouse_view_x = mouseState.x + guac_display.offsetLeft - window.pageXOffset;
            var mouse_view_y = mouseState.y + guac_display.offsetTop  - window.pageYOffset;

            // Determine viewport dimensioins
            var view_width  = GuacamoleUI.viewport.offsetWidth;
            var view_height = GuacamoleUI.viewport.offsetHeight;

            // Determine scroll amounts based on mouse position relative to document

            var scroll_amount_x;
            if (mouse_view_x > view_width)
                scroll_amount_x = mouse_view_x - view_width;
            else if (mouse_view_x < 0)
                scroll_amount_x = mouse_view_x;
            else
                scroll_amount_x = 0;

            var scroll_amount_y;
            if (mouse_view_y > view_height)
                scroll_amount_y = mouse_view_y - view_height;
            else if (mouse_view_y < 0)
                scroll_amount_y = mouse_view_y;
            else
                scroll_amount_y = 0;

            // Scroll (if necessary) to keep mouse on screen.
            window.scrollBy(scroll_amount_x, scroll_amount_y);

            // Hide menu on movement
            GuacamoleUI.startMenuCloseDetect();

            // Stop detecting long presses if mouse is being used
            GuacamoleUI.stopLongPressDetect();

            // Send mouse event
            guac.sendMouseState(mouseState);

        };

    // Keyboard
    var keyboard = new Guacamole.Keyboard(document);

    function disableKeyboard() {
        keyboard.onkeydown = null;
        keyboard.onkeyup = null;
    }

    function enableKeyboard() {
        keyboard.onkeydown =
            function (keysym) {

                // If we're using native OSK, ensure event target is reset
                // on each key event.
                if (GuacamoleUI.assumeNativeOSK)
                    GuacamoleUI.resetEventTarget();

                guac.sendKeyEvent(1, keysym);
            };

        keyboard.onkeyup =
            function (keysym) {
                guac.sendKeyEvent(0, keysym);
            };
    }

    // Enable keyboard by default
    enableKeyboard();

    // Handle client state change
    guac.onstatechange = function(clientState) {
        switch (clientState) {

            // Idle
            case 0:
                GuacamoleUI.showStatus("Idle.");
                break;

            // Connecting
            case 1:
                GuacamoleUI.shadeMenu();
                GuacamoleUI.showStatus("Connecting...");
                break;

            // Connected + waiting
            case 2:
                GuacamoleUI.showStatus("Connected, waiting for first update...");
                break;

            // Connected
            case 3:

                GuacamoleUI.hideStatus();
                GuacamoleUI.display.className =
                    GuacamoleUI.display.className.replace(/guac-loading/, '');

                GuacamoleUI.menu.className = "connected";
                break;

            // Disconnecting
            case 4:
                GuacamoleUI.showStatus("Disconnecting...");
                break;

            // Disconnected
            case 5:
                GuacamoleUI.showStatus("Disconnected.");
                break;

            // Unknown status code
            default:
                GuacamoleUI.showStatus("[UNKNOWN STATUS]");

        }
    };

    // Name instruction handler
    guac.onname = function(name) {
        document.title = name;
    };

    // Error handler
    guac.onerror = function(error) {

        // Disconnect, if connected
        guac.disconnect();

        // Display error message
        GuacamoleUI.showError(error);

    };

    // Disconnect on close
    window.onunload = function() {
        guac.disconnect();
    };

    // Handle clipboard events
    GuacamoleUI.clipboard.onchange = function() {

        var text = GuacamoleUI.clipboard.value;
        guac.setClipboard(text);

    };

    // Ignore keypresses when clipboard is focused
    GuacamoleUI.clipboard.onfocus = function() {
        disableKeyboard();
    };

    // Capture keypresses when clipboard is not focused
    GuacamoleUI.clipboard.onblur = function() {
        enableKeyboard();
    };

    // Server copy handler
    guac.onclipboard = function(data) {
        GuacamoleUI.clipboard.value = data;
    };

    GuacamoleUI.keyboard.onkeydown = function(keysym) {
        guac.sendKeyEvent(1, keysym);
    };

    GuacamoleUI.keyboard.onkeyup = function(keysym) {
        guac.sendKeyEvent(0, keysym);
    };

    // Send Ctrl-Alt-Delete
    GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {

        var KEYSYM_CTRL   = 0xFFE3;
        var KEYSYM_ALT    = 0xFFE9;
        var KEYSYM_DELETE = 0xFFFF;

        guac.sendKeyEvent(1, KEYSYM_CTRL);
        guac.sendKeyEvent(1, KEYSYM_ALT);
        guac.sendKeyEvent(1, KEYSYM_DELETE);
        guac.sendKeyEvent(0, KEYSYM_DELETE);
        guac.sendKeyEvent(0, KEYSYM_ALT);
        guac.sendKeyEvent(0, KEYSYM_CTRL);
    };

};<commit_msg>Rename assumeNativeOSK to nativeOSK, automatically reset nativeOSK to false if menu open initiated by mouse.<commit_after>
// UI Definition
var GuacamoleUI = {

    "viewport"    : document.getElementById("viewportClone"),
    "display"     : document.getElementById("display"),
    "menu"        : document.getElementById("menu"),
    "menuControl" : document.getElementById("menuControl"),
    "touchMenu"   : document.getElementById("touchMenu"),
    "logo"        : document.getElementById("status-logo"),
    "eventTarget" : document.getElementById("eventTarget"),

    "buttons": {

        "showClipboard": document.getElementById("showClipboard"),
        "showKeyboard" : document.getElementById("showKeyboard"),
        "ctrlAltDelete": document.getElementById("ctrlAltDelete"),
        "reconnect"    : document.getElementById("reconnect"),
        "logout"       : document.getElementById("logout")

    },

    "containers": {
        "state"    : document.getElementById("statusDialog"),
        "clipboard": document.getElementById("clipboardDiv"),
        "keyboard" : document.getElementById("keyboardContainer")
    },

    "state"     : document.getElementById("statusText"),
    "clipboard" : document.getElementById("clipboard")

};

// Constant UI initialization and behavior
(function() {

    var menu_shaded = false;

    var shade_interval = null;
    var show_interval = null;

    // Cache error image (might not be available when error occurs)
    var guacErrorImage = new Image();
    guacErrorImage.src = "images/noguacamole-logo-24.png";

    // Function for adding a class to an element
    var addClass;

    // Function for removing a class from an element
    var removeClass;

    // If Node.classList is supported, implement addClass/removeClass using that
    if (Node.classList) {

        addClass = function(element, classname) {
            element.classList.add(classname);
        };

        removeClass = function(element, classname) {
            element.classList.remove(classname);
        };

    }

    // Otherwise, implement own
    else {

        addClass = function(element, classname) {

            // Simply add new class
            element.className += " " + classname;

        };

        removeClass = function(element, classname) {

            // Filter out classes with given name
            element.className = element.className.replace(/([^ ]+)[ ]*/g,
                function(match, testClassname, spaces, offset, string) {

                    // If same class, remove
                    if (testClassname == classname)
                        return "";

                    // Otherwise, allow
                    return match;

                }
            );

        };

    }

    GuacamoleUI.hideStatus = function() {
        removeClass(document.body, "guac-error");
        GuacamoleUI.containers.state.style.visibility = "hidden";
        GuacamoleUI.display.style.opacity = "1";
    };

    GuacamoleUI.showStatus = function(text) {
        removeClass(document.body, "guac-error");
        GuacamoleUI.containers.state.style.visibility = "visible";
        GuacamoleUI.state.textContent = text;
        GuacamoleUI.display.style.opacity = "1";
    };

    GuacamoleUI.showError = function(error) {
        addClass(document.body, "guac-error");
        GuacamoleUI.state.textContent = error;
        GuacamoleUI.display.style.opacity = "0.1";
    };

    GuacamoleUI.shadeMenu = function() {

        if (!menu_shaded) {

            var step = Math.floor(GuacamoleUI.menu.offsetHeight / 10) + 1;
            var offset = 0;
            menu_shaded = true;

            window.clearInterval(show_interval);
            shade_interval = window.setInterval(function() {

                offset -= step;
                GuacamoleUI.menu.style.top = offset + "px";

                if (offset <= -GuacamoleUI.menu.offsetHeight) {
                    window.clearInterval(shade_interval);
                    GuacamoleUI.menu.style.visiblity = "hidden";
                }

            }, 30);
        }

    };

    GuacamoleUI.showMenu = function() {

        if (menu_shaded) {

            var step = Math.floor(GuacamoleUI.menu.offsetHeight / 5) + 1;
            var offset = -GuacamoleUI.menu.offsetHeight;
            menu_shaded = false;
            GuacamoleUI.menu.style.visiblity = "";

            window.clearInterval(shade_interval);
            show_interval = window.setInterval(function() {

                offset += step;

                if (offset >= 0) {
                    offset = 0;
                    window.clearInterval(show_interval);
                }

                GuacamoleUI.menu.style.top = offset + "px";

            }, 30);
        }

    };

    // Show/Hide clipboard
    GuacamoleUI.buttons.showClipboard.onclick = function() {

        var displayed = GuacamoleUI.containers.clipboard.style.display;
        if (displayed != "block") {
            GuacamoleUI.containers.clipboard.style.display = "block";
            GuacamoleUI.buttons.showClipboard.innerHTML = "Hide Clipboard";
        }
        else {
            GuacamoleUI.containers.clipboard.style.display = "none";
            GuacamoleUI.buttons.showClipboard.innerHTML = "Show Clipboard";
            GuacamoleUI.clipboard.onchange();
        }

    };

    // Assume no native OSK by default
    GuacamoleUI.nativeOSK = false;

    // Show/Hide keyboard
    var keyboardResizeInterval = null;
    GuacamoleUI.buttons.showKeyboard.onclick = function() {

        // If we think the platform has a native OSK, use the event target to
        // cause it to display.
        if (GuacamoleUI.nativeOSK) {
            GuacamoleUI.eventTarget.focus();
            return;
        }

        var displayed = GuacamoleUI.containers.keyboard.style.display;
        if (displayed != "block") {
            GuacamoleUI.containers.keyboard.style.display = "block";
            GuacamoleUI.buttons.showKeyboard.textContent = "Hide Keyboard";

            // Automatically update size
            window.onresize = updateKeyboardSize;
            keyboardResizeInterval = window.setInterval(updateKeyboardSize, 30);

            updateKeyboardSize();
        }
        else {
            GuacamoleUI.containers.keyboard.style.display = "none";
            GuacamoleUI.buttons.showKeyboard.textContent = "Show Keyboard";

            window.onresize = null;
            window.clearInterval(keyboardResizeInterval);
        }

    };

    // Logout
    GuacamoleUI.buttons.logout.onclick = function() {
        window.location.href = "logout";
    };

    // Timeouts for detecting if users wants menu to open or close
    var detectMenuOpenTimeout = null;
    var detectMenuCloseTimeout = null;

    // Clear detection timeouts
    GuacamoleUI.resetMenuDetect = function() {

        if (detectMenuOpenTimeout != null) {
            window.clearTimeout(detectMenuOpenTimeout);
            detectMenuOpenTimeout = null;
        }

        if (detectMenuCloseTimeout != null) {
            window.clearTimeout(detectMenuCloseTimeout);
            detectMenuCloseTimeout = null;
        }

    };

    // Initiate detection of menu open action. If not canceled through some
    // user event, menu will open.
    GuacamoleUI.startMenuOpenDetect = function() {

        if (!detectMenuOpenTimeout) {

            // Clear detection state
            GuacamoleUI.resetMenuDetect();

            // Wait and then show menu
            detectMenuOpenTimeout = window.setTimeout(function() {

                // If menu opened via mouse, do not show native OSK
                GuacamoleUI.nativeOSK = false;

                GuacamoleUI.showMenu();
                detectMenuOpenTimeout = null;
            }, 325);

        }

    };

    // Initiate detection of menu close action. If not canceled through some
    // user mouse event, menu will close.
    GuacamoleUI.startMenuCloseDetect = function() {

        if (!detectMenuCloseTimeout) {

            // Clear detection state
            GuacamoleUI.resetMenuDetect();

            // Wait and then shade menu
            detectMenuCloseTimeout = window.setTimeout(function() {
                GuacamoleUI.shadeMenu();
                detectMenuCloseTimeout = null;
            }, 500);

        }

    };

    // Show menu if mouseover any part of menu
    GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.showMenu, true);

    // Stop detecting menu state change intents if mouse is over menu
    GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.resetMenuDetect, true);

    // When mouse hovers over top of screen, start detection of intent to open menu
    GuacamoleUI.menuControl.addEventListener('mousemove', GuacamoleUI.startMenuOpenDetect, true);

    var menuShowLongPressTimeout = null;

    GuacamoleUI.startLongPressDetect = function() {

        if (!menuShowLongPressTimeout) {

            menuShowLongPressTimeout = window.setTimeout(function() {

                menuShowLongPressTimeout = null;

                // Assume native OSK if menu shown via long-press
                GuacamoleUI.nativeOSK = true;
                GuacamoleUI.showMenu();

            }, 800);

        }
    };

    GuacamoleUI.stopLongPressDetect = function() {
        window.clearTimeout(menuShowLongPressTimeout);
        menuShowLongPressTimeout = null;
    };

    // Reset event target (add content, reposition cursor in middle.
    GuacamoleUI.resetEventTarget = function() {
        GuacamoleUI.eventTarget.value = "GUAC";
        GuacamoleUI.eventTarget.selectionStart =
        GuacamoleUI.eventTarget.selectionEnd   = 2;
    };

    // Detect long-press at bottom of screen
    document.body.addEventListener('touchstart', GuacamoleUI.startLongPressDetect, true);

    // Reconnect button
    GuacamoleUI.buttons.reconnect.onclick = function() {
        window.location.reload();
    };

    // On-screen keyboard
    GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty-mobile.xml");
    GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard.getElement());

    // Function for automatically updating keyboard size
    var lastKeyboardWidth;
    function updateKeyboardSize() {
        var currentSize = GuacamoleUI.keyboard.getElement().offsetWidth;
        if (lastKeyboardWidth != currentSize) {
            GuacamoleUI.keyboard.resize(currentSize);
            lastKeyboardWidth = currentSize;
        }
    };

})();

// Tie UI events / behavior to a specific Guacamole client
GuacamoleUI.attach = function(guac) {

    var guac_display = guac.getDisplay();

    // When mouse enters display, start detection of intent to close menu
    guac_display.addEventListener('mouseover', GuacamoleUI.startMenuCloseDetect, true);

    guac_display.onclick = function(e) {
        e.preventDefault();
        return false;
    };

    // Mouse
    var mouse = new Guacamole.Mouse(guac_display);
    mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
        function(mouseState) {

            // Determine mouse position within view
            var mouse_view_x = mouseState.x + guac_display.offsetLeft - window.pageXOffset;
            var mouse_view_y = mouseState.y + guac_display.offsetTop  - window.pageYOffset;

            // Determine viewport dimensioins
            var view_width  = GuacamoleUI.viewport.offsetWidth;
            var view_height = GuacamoleUI.viewport.offsetHeight;

            // Determine scroll amounts based on mouse position relative to document

            var scroll_amount_x;
            if (mouse_view_x > view_width)
                scroll_amount_x = mouse_view_x - view_width;
            else if (mouse_view_x < 0)
                scroll_amount_x = mouse_view_x;
            else
                scroll_amount_x = 0;

            var scroll_amount_y;
            if (mouse_view_y > view_height)
                scroll_amount_y = mouse_view_y - view_height;
            else if (mouse_view_y < 0)
                scroll_amount_y = mouse_view_y;
            else
                scroll_amount_y = 0;

            // Scroll (if necessary) to keep mouse on screen.
            window.scrollBy(scroll_amount_x, scroll_amount_y);

            // Hide menu on movement
            GuacamoleUI.startMenuCloseDetect();

            // Stop detecting long presses if mouse is being used
            GuacamoleUI.stopLongPressDetect();

            // Send mouse event
            guac.sendMouseState(mouseState);

        };

    // Keyboard
    var keyboard = new Guacamole.Keyboard(document);

    function disableKeyboard() {
        keyboard.onkeydown = null;
        keyboard.onkeyup = null;
    }

    function enableKeyboard() {
        keyboard.onkeydown =
            function (keysym) {

                // If we're using native OSK, ensure event target is reset
                // on each key event.
                if (GuacamoleUI.nativeOSK)
                    GuacamoleUI.resetEventTarget();

                guac.sendKeyEvent(1, keysym);
            };

        keyboard.onkeyup =
            function (keysym) {
                guac.sendKeyEvent(0, keysym);
            };
    }

    // Enable keyboard by default
    enableKeyboard();

    // Handle client state change
    guac.onstatechange = function(clientState) {
        switch (clientState) {

            // Idle
            case 0:
                GuacamoleUI.showStatus("Idle.");
                break;

            // Connecting
            case 1:
                GuacamoleUI.shadeMenu();
                GuacamoleUI.showStatus("Connecting...");
                break;

            // Connected + waiting
            case 2:
                GuacamoleUI.showStatus("Connected, waiting for first update...");
                break;

            // Connected
            case 3:

                GuacamoleUI.hideStatus();
                GuacamoleUI.display.className =
                    GuacamoleUI.display.className.replace(/guac-loading/, '');

                GuacamoleUI.menu.className = "connected";
                break;

            // Disconnecting
            case 4:
                GuacamoleUI.showStatus("Disconnecting...");
                break;

            // Disconnected
            case 5:
                GuacamoleUI.showStatus("Disconnected.");
                break;

            // Unknown status code
            default:
                GuacamoleUI.showStatus("[UNKNOWN STATUS]");

        }
    };

    // Name instruction handler
    guac.onname = function(name) {
        document.title = name;
    };

    // Error handler
    guac.onerror = function(error) {

        // Disconnect, if connected
        guac.disconnect();

        // Display error message
        GuacamoleUI.showError(error);

    };

    // Disconnect on close
    window.onunload = function() {
        guac.disconnect();
    };

    // Handle clipboard events
    GuacamoleUI.clipboard.onchange = function() {

        var text = GuacamoleUI.clipboard.value;
        guac.setClipboard(text);

    };

    // Ignore keypresses when clipboard is focused
    GuacamoleUI.clipboard.onfocus = function() {
        disableKeyboard();
    };

    // Capture keypresses when clipboard is not focused
    GuacamoleUI.clipboard.onblur = function() {
        enableKeyboard();
    };

    // Server copy handler
    guac.onclipboard = function(data) {
        GuacamoleUI.clipboard.value = data;
    };

    GuacamoleUI.keyboard.onkeydown = function(keysym) {
        guac.sendKeyEvent(1, keysym);
    };

    GuacamoleUI.keyboard.onkeyup = function(keysym) {
        guac.sendKeyEvent(0, keysym);
    };

    // Send Ctrl-Alt-Delete
    GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {

        var KEYSYM_CTRL   = 0xFFE3;
        var KEYSYM_ALT    = 0xFFE9;
        var KEYSYM_DELETE = 0xFFFF;

        guac.sendKeyEvent(1, KEYSYM_CTRL);
        guac.sendKeyEvent(1, KEYSYM_ALT);
        guac.sendKeyEvent(1, KEYSYM_DELETE);
        guac.sendKeyEvent(0, KEYSYM_DELETE);
        guac.sendKeyEvent(0, KEYSYM_ALT);
        guac.sendKeyEvent(0, KEYSYM_CTRL);
    };

};<|endoftext|>


<commit_before><commit_msg>don't send users to /all-posts/?tag=_nofilter_<commit_after><|endoftext|>


<commit_before>module.exports = releaseAssets

const fs = require('fs')
const pathResolve = require('path').resolve

const urlTemplate = require('url-template')

const env = require('../../../lib/env')
const getTemporaryRepository = require('../../../lib/temporary-repository')

async function releaseAssets (state) {
  let error
  // create a temporary repository
  const temporaryRepository = getTemporaryRepository({
    request: state.request,
    token: env.FIXTURES_USER_A_TOKEN_FULL_ACCESS,
    org: 'octokit-fixture-org',
    name: 'release-assets'
  })

  await temporaryRepository.create()

  try {
    // https://developer.github.com/v3/repos/contents/#create-a-file
    // (this request gets ignored, we need an existing commit before creating a release)
    const {data: {commit: {sha}}} = await state.request({
      method: 'put',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/contents/README.md`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`,
        'X-Octokit-Fixture-Ignore': 'true'
      },
      data: {
        message: 'initial commit',
        content: Buffer.from('# release-assets').toString('base64')
      }
    })

    // https://developer.github.com/v3/repos/releases/#create-a-release
    // (this request gets ignored, it will create our test release)
    await state.request({
      method: 'post',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`,
        'X-Octokit-Fixture-Ignore': 'true'
      },
      data: {
        tag_name: 'v1.0.0',
        name: 'Version 1.0.0',
        body: 'Initial release',
        target_commitish: sha
      }
    })

    // https://developer.github.com/v3/repos/releases/#create-a-release
    // Get the release. The upload_url from the response must be used for uploads
    const {data: {id: releaseId, upload_url}} = await state.request({
      method: 'get',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/tags/v1.0.0`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      }
    })

    // https://developer.github.com/v3/repos/releases/#upload-a-release-asset
    // upload attachment to release URL returned by create release request
    const FILE_NAME = 'test-upload.txt'
    const filePath = pathResolve(__dirname, FILE_NAME)
    const url = urlTemplate.parse(upload_url).expand({
      name: FILE_NAME,
      label: 'test'
    })
    const size = fs.statSync(filePath).size
    await state.request({
      method: 'post',
      url: url,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`,
        'Content-Type': 'text/plain',
        'Content-Length': size
      },
      data: fs.createReadStream(filePath)
    })

    // https://developer.github.com/v3/repos/releases/#list-assets-for-a-release
    // list assets for release
    const {data: [{id: assetId}]} = await state.request({
      method: 'get',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/${releaseId}/assets`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      }
    })

    // https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
    // get single release asset
    await state.request({
      method: 'get',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/assets/${assetId}`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      }
    })

    // https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
    // Edit name / label of release asset
    await state.request({
      method: 'patch',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/assets/${assetId}`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      },
      data: {
        label: 'new label'
      }
    })

    // https://developer.github.com/v3/repos/releases/#delete-a-release-asset
    // delete a release asset
    await state.request({
      method: 'delete',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/assets/${assetId}`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      }
    })
  } catch (_error) {
    error = _error
  }

  // await temporaryRepository.delete()

  if (error) {
    return Promise.reject(error)
  }
}
<commit_msg>chore(scenario): delete temporary release-assets repository after recording<commit_after>module.exports = releaseAssets

const fs = require('fs')
const pathResolve = require('path').resolve

const urlTemplate = require('url-template')

const env = require('../../../lib/env')
const getTemporaryRepository = require('../../../lib/temporary-repository')

async function releaseAssets (state) {
  let error
  // create a temporary repository
  const temporaryRepository = getTemporaryRepository({
    request: state.request,
    token: env.FIXTURES_USER_A_TOKEN_FULL_ACCESS,
    org: 'octokit-fixture-org',
    name: 'release-assets'
  })

  await temporaryRepository.create()

  try {
    // https://developer.github.com/v3/repos/contents/#create-a-file
    // (this request gets ignored, we need an existing commit before creating a release)
    const {data: {commit: {sha}}} = await state.request({
      method: 'put',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/contents/README.md`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`,
        'X-Octokit-Fixture-Ignore': 'true'
      },
      data: {
        message: 'initial commit',
        content: Buffer.from('# release-assets').toString('base64')
      }
    })

    // https://developer.github.com/v3/repos/releases/#create-a-release
    // (this request gets ignored, it will create our test release)
    await state.request({
      method: 'post',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`,
        'X-Octokit-Fixture-Ignore': 'true'
      },
      data: {
        tag_name: 'v1.0.0',
        name: 'Version 1.0.0',
        body: 'Initial release',
        target_commitish: sha
      }
    })

    // https://developer.github.com/v3/repos/releases/#create-a-release
    // Get the release. The upload_url from the response must be used for uploads
    const {data: {id: releaseId, upload_url}} = await state.request({
      method: 'get',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/tags/v1.0.0`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      }
    })

    // https://developer.github.com/v3/repos/releases/#upload-a-release-asset
    // upload attachment to release URL returned by create release request
    const FILE_NAME = 'test-upload.txt'
    const filePath = pathResolve(__dirname, FILE_NAME)
    const url = urlTemplate.parse(upload_url).expand({
      name: FILE_NAME,
      label: 'test'
    })
    const size = fs.statSync(filePath).size
    await state.request({
      method: 'post',
      url: url,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`,
        'Content-Type': 'text/plain',
        'Content-Length': size
      },
      data: fs.createReadStream(filePath)
    })

    // https://developer.github.com/v3/repos/releases/#list-assets-for-a-release
    // list assets for release
    const {data: [{id: assetId}]} = await state.request({
      method: 'get',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/${releaseId}/assets`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      }
    })

    // https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
    // get single release asset
    await state.request({
      method: 'get',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/assets/${assetId}`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      }
    })

    // https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
    // Edit name / label of release asset
    await state.request({
      method: 'patch',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/assets/${assetId}`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      },
      data: {
        label: 'new label'
      }
    })

    // https://developer.github.com/v3/repos/releases/#delete-a-release-asset
    // delete a release asset
    await state.request({
      method: 'delete',
      url: `/repos/octokit-fixture-org/${temporaryRepository.name}/releases/assets/${assetId}`,
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${env.FIXTURES_USER_A_TOKEN_FULL_ACCESS}`
      }
    })
  } catch (_error) {
    error = _error
  }

  await temporaryRepository.delete()

  if (error) {
    return Promise.reject(error)
  }
}
<|endoftext|>


<commit_before>/*************************************************
 * Copyright (c) 2017 Ansible, Inc.
 *
 * All Rights Reserved
 *************************************************/

import HostNestedGroupListDefinition from './host-nested-groups.list';
import HostNestedGroupsListController from './host-nested-groups-list.controller';

export default
    angular.module('hostNestedGroups', [])
    .value('HostNestedGroupListDefinition', HostNestedGroupListDefinition)
    .controller('HostNestedGroupsListController', HostNestedGroupsListController);
<commit_msg>Fixed hosts groups list<commit_after>/*************************************************
 * Copyright (c) 2017 Ansible, Inc.
 *
 * All Rights Reserved
 *************************************************/

import HostNestedGroupListDefinition from './host-nested-groups.list';
import HostNestedGroupsListController from './host-nested-groups-list.controller';

export default
    angular.module('hostNestedGroups', [])
    .factory('HostNestedGroupListDefinition', HostNestedGroupListDefinition)
    .controller('HostNestedGroupsListController', HostNestedGroupsListController);
<|endoftext|>


<commit_before>$(function () {
    /*
    wrapper to attach events to the condition template, as the template
    is rendered by handlebars after page initialization
    */

    $.widget("as24.conditionify", {
        options: {
            conditions: [],
            triggerAdd: 'data-add-condition-value',
            triggerDelete: 'data-delete',
            triggerNew: 'data-add-condition',

            add: function () { },
            delete: function () { },
            new : function () { }
        },

        baseTemplate : null,
        templates: [],

        render: function (name, type, data) {
            var self = this;

            $("body").on("click", '[' + self.options.triggerAdd + '="' + type + '"]', function () { self._renderItem(this, name, type, data); });

            $(data).each(function (index, value) {
                $("body").off("click", '[' + self.options.triggerDelete + '="{ "' + type + '" : "' + value.value + '" }"]');
                $("body").on("click", '[' + self.options.triggerDelete + '="{ "' + type + '" : "' + value.value + '" }"]', function () {
                    var data = JSON.parse($(this).attr(self.options.triggerDelete));
                    self.options.delete({ name: name, type: type, values: [ data[type] ] });
                    $(this).parent().remove();
                });
            });

            self.renderAddNewCondition(name);
        },

        renderAddNewCondition: function (name) {
            var self = this;
            $("body").off("click", '[' + self.options.triggerNew + '="' + name + '"]');
            $("body").on("click", '[' + self.options.triggerNew + '="' + name + '"]', function () {
                var btn = $(this);
                btn.find('[data-select="condition"]').remove();
                var item = $('
');
                item.hide();
                item.insertBefore(btn);
                $.each(self.options.conditions, function (index, value) {
                    var li = $('
');
                    var a = $('');
                    a.text(value.type);
                    a.data(value.type);
                    item.append(li.append(a));
                    a.click(function () {
                        self.options.new({ name: name, type: value.type });
                        btn.parent().find('[data-select="condition"]').hide('fast');
                        btn.parent().find('[data-select="condition"]').remove();
                    });
                });
                btn.hide('fast');
                item.show('fast');
            });
        },

        _renderItem : function(ele, name, type, data) {
            var self = this;
            var template = self.templates[type];
            if (template) {
                var element = $(self.templates[type](data));
                element.attr('data-adding', 'true');
                $(ele).siblings('[data-adding]').remove();
                $(ele).after(element);
                $(element).find('[data-action="add"]').on('click', function() {
                    var datastate = {
                        name: name,
                        type: type,
                        values: []
                    };
                    $(element).find('[data-value]').each(function(index, value) {
                        datastate.values.push($(this).val());
                    });

                    self.options.add(datastate);
                    data.push(datastate.values[0]);
                    $(element).parent().html(self.baseTemplate({ name: name, type: type, values: data }));
                    $(element).remove();
                });
            }
        },

        _create: function () {
            var self = this;
            self.baseTemplate = Handlebars.compile($(self.element).find('[data-container="condition-item"]').html().trim());
            for (var i = 0; i < this.options.conditions.length; i++) {
                var condition = this.options.conditions[i];
                var source = $(condition.template).html().trim();
                var template = Handlebars.compile(source);
                this.templates[condition.type] = template;
            }
        },

        _destroy: function () {
            $("body").off("click", '[' + self.options.triggerDelete + ']');
            $("body").off("click", '[' + self.options.triggerAdd + ']');
        }
    });
});<commit_msg>Fixed #24 - Conditions can not be deleted<commit_after>$(function () {
    /*
    wrapper to attach events to the condition template, as the template
    is rendered by handlebars after page initialization
    */

    $.widget("as24.conditionify", {
        options: {
            conditions: [],
            triggerAdd: 'data-add-condition-value',
            triggerDelete: 'data-delete',
            triggerNew: 'data-add-condition',

            add: function () { },
            delete: function () { },
            new : function () { }
        },

        baseTemplate : null,
        templates: [],

        render: function (name, type, data) {
            var self = this;

            $("body").on("click", '[' + self.options.triggerAdd + '="' + type + '"]', function () { self._renderItem(this, name, type, data); });

            $(data).each(function (index, value) {
                $("body").off("click", '[' + self.options.triggerDelete + '="{ "' + type + '" : "' + value + '" }"]');
                $("body").on("click", '[' + self.options.triggerDelete + '="{ "' + type + '" : "' + value + '" }"]', function () {
                    var data = JSON.parse($(this).attr(self.options.triggerDelete));
                    self.options.delete({ name: name, type: type, values: [ data[type] ] });
                    $(this).parent().remove();
                });
            });

            self.renderAddNewCondition(name);
        },

        renderAddNewCondition: function (name) {
            var self = this;
            $("body").off("click", '[' + self.options.triggerNew + '="' + name + '"]');
            $("body").on("click", '[' + self.options.triggerNew + '="' + name + '"]', function () {
                var btn = $(this);
                btn.find('[data-select="condition"]').remove();
                var item = $('
');
                item.hide();
                item.insertBefore(btn);
                $.each(self.options.conditions, function (index, value) {
                    var li = $('
');
                    var a = $('');
                    a.text(value.type);
                    a.data(value.type);
                    item.append(li.append(a));
                    a.click(function () {
                        self.options.new({ name: name, type: value.type });
                        btn.parent().find('[data-select="condition"]').hide('fast');
                        btn.parent().find('[data-select="condition"]').remove();
                    });
                });
                btn.hide('fast');
                item.show('fast');
            });
        },

        _renderItem : function(ele, name, type, data) {
            var self = this;
            var template = self.templates[type];
            if (template) {
                var element = $(self.templates[type](data));
                element.attr('data-adding', 'true');
                $(ele).siblings('[data-adding]').remove();
                $(ele).after(element);
                $(element).find('[data-action="add"]').on('click', function() {
                    var datastate = {
                        name: name,
                        type: type,
                        values: []
                    };
                    $(element).find('[data-value]').each(function(index, value) {
                        datastate.values.push($(this).val());
                    });

                    self.options.add(datastate);
                    data.push(datastate.values[0]);
                    $(element).parent().html(self.baseTemplate({ name: name, type: type, values: data }));
                    $(element).remove();
                });
            }
        },

        _create: function () {
            var self = this;
            self.baseTemplate = Handlebars.compile($(self.element).find('[data-container="condition-item"]').html().trim());
            for (var i = 0; i < this.options.conditions.length; i++) {
                var condition = this.options.conditions[i];
                var source = $(condition.template).html().trim();
                var template = Handlebars.compile(source);
                this.templates[condition.type] = template;
            }
        },

        _destroy: function () {
            $("body").off("click", '[' + self.options.triggerDelete + ']');
            $("body").off("click", '[' + self.options.triggerAdd + ']');
        }
    });
});<|endoftext|>


<commit_before>const fs = require('fs').promises;
const http = require('http');
const mime = require('mime-types');
const url = require('url');
const { format } = require('util');

const Sentry = require('@sentry/node');

Sentry.init({
  dsn: 'https://a7fa45b215ae4cf68bb9320a075234d7@sentry.io/1263950',
});

const engine = require('./lyric_engine_js');

function do_not_found(response) {
  response.writeHead(404);
  response.end();
}

const port = process.env.PORT || 8866;
console.log(`Listen to http://localhost:${port}`);

const outputJson = (response, out) => {
  const json_string = JSON.stringify(out);
  response.writeHead(200, {
    'Content-Length': Buffer.byteLength(json_string, 'utf8'),
    'Content-Type': 'application/json',
  });
  response.end(json_string);
};

const handleError = (request, response, error, lyric_url) => {
  console.error('err:', error);

  const out = {
    lyric: `Failed to find lyric of ${lyric_url}`,
  };

  let level = 'error';
  let domain = '';
  if (error instanceof engine.SiteNotSupportError) {
    level = 'warning';

    out.lyric = error.message;
    domain = error.domain;
  }
  if (error instanceof engine.BlockedError) {
    level = 'warning';
    out.lyric = `Failed to get lyric of ${lyric_url}. Blocked by vendor.`;
  }

  Sentry.withScope((scope) => {
    scope.setLevel(level);
    scope.addEventProcessor(async (event) =>
      Sentry.Handlers.parseRequest(event, request)
    );
    if (domain) {
      scope.setFingerprint(['site-not-support-error', domain]);
    }
    Sentry.captureException(error);
  });

  outputJson(response, out);
};

http
  .createServer(async (request, response) => {
    const request_object = url.parse(request.url, true);

    let { pathname } = request_object;
    if (pathname === '/') {
      pathname = '/index.html';
    } else if (pathname === '/former/') {
      pathname = '/former/index.html';
    }

    if (pathname === '/app' || pathname === '/json') {
      const { query } = request_object;
      if (!query || !query.url) {
        console.warn('in app, but no query');
        return do_not_found(response);
      }

      const { url } = query;

      let out = {
        lyric: `Failed to find lyric of ${url}`,
      };

      if (pathname === '/app') {
        try {
          const lyric = await engine.get_full(url);
          out.lyric = lyric;
        } catch (error) {
          return handleError(request, response, error, url);
        }
      } else if (pathname === '/json') {
        try {
          const json = await engine.get_json(url);
          if (json) {
            out = json;
          }
        } catch (error) {
          return handleError(request, response, error, url);
        }
      }

      return outputJson(response, out);
    }

    const source = format('%s%s', 'public', pathname);
    fs.readFile(source)
      .then((data) => {
        response.writeHead(200, {
          'Content-Length': data.length,
          'Content-Type': mime.lookup(pathname) || 'application/octet-stream',
        });
        response.end(data);
      })
      .catch(() => do_not_found(response));

    return '';
  })
  .listen(port);
<commit_msg>add "/info" path<commit_after>const fs = require('fs').promises;
const http = require('http');
const mime = require('mime-types');
const url = require('url');
const { format } = require('util');

const Sentry = require('@sentry/node');

Sentry.init({
  dsn: 'https://a7fa45b215ae4cf68bb9320a075234d7@sentry.io/1263950',
});

const engine = require('./lyric_engine_js');

function do_not_found(response) {
  response.writeHead(404);
  response.end();
}

const port = process.env.PORT || 8866;
console.log(`Listen to http://localhost:${port}`);

const outputJson = (response, out) => {
  const json_string = JSON.stringify(out);
  response.writeHead(200, {
    'Content-Length': Buffer.byteLength(json_string, 'utf8'),
    'Content-Type': 'application/json',
  });
  response.end(json_string);
};

const handleError = (request, response, error, lyric_url) => {
  console.error('err:', error);

  const out = {
    lyric: `Failed to find lyric of ${lyric_url}`,
  };

  let level = 'error';
  let domain = '';
  if (error instanceof engine.SiteNotSupportError) {
    level = 'warning';

    out.lyric = error.message;
    domain = error.domain;
  }
  if (error instanceof engine.BlockedError) {
    level = 'warning';
    out.lyric = `Failed to get lyric of ${lyric_url}. Blocked by vendor.`;
  }

  Sentry.withScope((scope) => {
    scope.setLevel(level);
    scope.addEventProcessor(async (event) =>
      Sentry.Handlers.parseRequest(event, request)
    );
    if (domain) {
      scope.setFingerprint(['site-not-support-error', domain]);
    }
    Sentry.captureException(error);
  });

  outputJson(response, out);
};

http
  .createServer(async (request, response) => {
    const request_object = url.parse(request.url, true);

    let { pathname } = request_object;
    if (pathname === '/') {
      pathname = '/index.html';
    } else if (pathname === '/former/') {
      pathname = '/former/index.html';
    }

    if (pathname === '/info') {
      return outputJson(response, request);
    }

    if (pathname === '/app' || pathname === '/json') {
      const { query } = request_object;
      if (!query || !query.url) {
        console.warn('in app, but no query');
        return do_not_found(response);
      }

      const { url } = query;

      let out = {
        lyric: `Failed to find lyric of ${url}`,
      };

      if (pathname === '/app') {
        try {
          const lyric = await engine.get_full(url);
          out.lyric = lyric;
        } catch (error) {
          return handleError(request, response, error, url);
        }
      } else if (pathname === '/json') {
        try {
          const json = await engine.get_json(url);
          if (json) {
            out = json;
          }
        } catch (error) {
          return handleError(request, response, error, url);
        }
      }

      return outputJson(response, out);
    }

    const source = format('%s%s', 'public', pathname);
    fs.readFile(source)
      .then((data) => {
        response.writeHead(200, {
          'Content-Length': data.length,
          'Content-Type': mime.lookup(pathname) || 'application/octet-stream',
        });
        response.end(data);
      })
      .catch(() => do_not_found(response));

    return '';
  })
  .listen(port);
<|endoftext|>


<commit_before>var io = io(document.location.href.replace("http", "ws"));
var images = {};

$(document).ready(function () {
    $.fn.extend({
        animateCss: function (animationName, callback) {
            var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
            $(this).addClass('animated ' + animationName).one(animationEnd, function () {
                $(this).removeClass('animated ' + animationName);
                if (callback) callback();
            });
        }
    });
    $('body').show();
});

function get(obj, key) { //Search and return a nested element in an object or null
    return key.split(".").reduce(function (o, x) {
        return (typeof o == "undefined" || o === null) ? o : o[x];
    }, obj);
}

io.on('connected', function () {
    console.log('connection ok');
});

io.on('images', function (data) {
    console.log('images');
    images = data;
    for (var image in images) {
        if (images.hasOwnProperty(image)) {
            images[image].image = new Image();
            images[image].image.src = images[image].src;
        }
    }
    console.log(images);
});

io.on('image_change', function (data) {
    console.log('change');
    images[data.key] = {
        image: new Image(),
        src: data.src
    };
    images[data.key].image.src = data.src;

    console.log(images);
});

io.on('key_press', function (data) {
    if (!images[data.key] || !images[data.key].src) return;
    var animate = {
        open: {
            delay: parseFloat(data.options.animate.open.delay || 0.0) * 1000 + "ms",
            type: data.options.animate.open.type || 'fadeIn',
            duration: parseFloat(data.options.animate.open.duration || 2.0) * 1000 + "ms"

        },
        close: {
            delay: parseFloat(data.options.animate.close.delay || 5.0) * 1000 + "ms",
            type: data.options.animate.close.type || 'fadeOut',
            duration: parseFloat(data.options.animate.close.duration || 2.0) * 1000 + "ms"

        }
    };
    var css = (data.options.css || "").replace(/\n/g, "").replace(/\s/g, "");

    console.log(css);

    var newDiv = document.createElement('div');
    var imageDiv = document.createElement('div');

    $(newDiv).append(imageDiv);
    $('body').append(newDiv);

    $(imageDiv).css({
        '-webkit-animation-duration': animate.open.duration,
        '-webkit-animation-delay': animate.open.delay
    });
    $(imageDiv).html("").animateCss(animate.open.type, function () {
        $(imageDiv).css({
            '-webkit-animation-duration': animate.close.duration,
            '-webkit-animation-delay': animate.close.delay
        });
        $(imageDiv).animateCss(animate.close.type, function () {
            $(newDiv).remove();
        });
    });
});
<commit_msg>Removed debug lines and unused function<commit_after>var io = io(document.location.href.replace("http", "ws"));
var images = {};

$(document).ready(function () {
    $.fn.extend({
        animateCss: function (animationName, callback) {
            var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
            $(this).addClass('animated ' + animationName).one(animationEnd, function () {
                $(this).removeClass('animated ' + animationName);
                if (callback) callback();
            });
        }
    });
    $('body').show();
});

io.on('connected', function () {
    console.log('connection ok');
});

io.on('images', function (data) {
    images = data;
    for (var image in images) {
        if (images.hasOwnProperty(image)) {
            images[image].image = new Image();
            images[image].image.src = images[image].src;
        }
    }
});

io.on('image_change', function (data) {
    images[data.key] = {
        image: new Image(),
        src: data.src
    };
    images[data.key].image.src = data.src;
});

io.on('key_press', function (data) {
    if (!images[data.key] || !images[data.key].src) return;
    var animate = {
        open: {
            delay: parseFloat(data.options.animate.open.delay || 0.0) * 1000 + "ms",
            type: data.options.animate.open.type || 'fadeIn',
            duration: parseFloat(data.options.animate.open.duration || 2.0) * 1000 + "ms"

        },
        close: {
            delay: parseFloat(data.options.animate.close.delay || 5.0) * 1000 + "ms",
            type: data.options.animate.close.type || 'fadeOut',
            duration: parseFloat(data.options.animate.close.duration || 2.0) * 1000 + "ms"

        }
    };
    var css = (data.options.css || "").replace(/\n/g, "").replace(/\s/g, "");

    console.log(css);

    var newDiv = document.createElement('div');
    var imageDiv = document.createElement('div');

    $(newDiv).append(imageDiv);
    $('body').append(newDiv);

    $(imageDiv).css({
        '-webkit-animation-duration': animate.open.duration,
        '-webkit-animation-delay': animate.open.delay
    });
    $(imageDiv).html("").animateCss(animate.open.type, function () {
        $(imageDiv).css({
            '-webkit-animation-duration': animate.close.duration,
            '-webkit-animation-delay': animate.close.delay
        });
        $(imageDiv).animateCss(animate.close.type, function () {
            $(newDiv).remove();
        });
    });
});
<|endoftext|>


<commit_before>'use strict';

function play_round(){
    var dcoins = 0;
    var first = core_random_integer({
      'max': 10,
    });
    var multiplier = parseInt(
      document.getElementById('multiplier').value,
      10
    );
    var result = '';
    var second = core_random_integer({
      'max': 10,
    });
    var third = core_random_integer({
      'max': 10,
    });

    // If all numbers match...
    if(first === second
      && second === third){
        // ...give player twice as many coins as the numbers that matched.
        dcoins = first * 2 * multiplier;
        result = 'Three Match! +' + dcoins + ' coins!';

    // If first number matches either of the other two numbers...
    }else if(first === second
      || first === third){
        // ...give player as many coins as the first number.
        dcoins = first * multiplier;
        result = 'Two Match. +' + dcoins + ' coins.';

    // If the second and third numbers match...
    }else if(second === third){
        // ...give player as many coins as second number.
        dcoins = second * multiplier;
        result = 'Two Match. +' + dcoins + ' coins.';

    // If no numbers match...
    }else{
        // ...take away two coins from player.
        dcoins = -2 * multiplier;
        result = 'No Match... ' + dcoins + ' coins...';
    }

    total += 1;
    result = result + '
' + total + ' total games';

    coins += dcoins;
    document.getElementById('coins').innerHTML = coins;
    document.getElementById('number-first').innerHTML = first;
    document.getElementById('number-second').innerHTML = second;
    document.getElementById('number-third').innerHTML = third;
    document.getElementById('result').innerHTML = result;
}

function reset(){
    if(!window.confirm('Reset coins?')){
        return;
    }

    coins = coins_default;

    var ids = {
      'coins': coins,
      'number-first': '',
      'number-second': '',
      'number-third': '',
      'result': '',
    };
    for(var id in ids){
        document.getElementById(id).innerHTML = ids[id];
    }
}

var coins_default = 100;
var coins = coins_default;
var total = 0;

window.onload = function(e){
    core_input_init({
      'keybinds': {
        83: {
          'todo': play_round,
        },
      },
    });

    document.getElementById('coins').innerHTML = coins;
    document.getElementById('multiplier').value = 1;
    document.getElementById('play').onclick = play_round;
    document.getElementById('reset').onclick = reset;
};
<commit_msg>added core_init(), repo_init(), and began global inputs<commit_after>'use strict';

function play_round(){
    var dcoins = 0;
    var first = core_random_integer({
      'max': 10,
    });
    var multiplier = parseInt(
      document.getElementById('multiplier').value,
      10
    );
    var result = '';
    var second = core_random_integer({
      'max': 10,
    });
    var third = core_random_integer({
      'max': 10,
    });

    // If all numbers match...
    if(first === second
      && second === third){
        // ...give player twice as many coins as the numbers that matched.
        dcoins = first * 2 * multiplier;
        result = 'Three Match! +' + dcoins + ' coins!';

    // If first number matches either of the other two numbers...
    }else if(first === second
      || first === third){
        // ...give player as many coins as the first number.
        dcoins = first * multiplier;
        result = 'Two Match. +' + dcoins + ' coins.';

    // If the second and third numbers match...
    }else if(second === third){
        // ...give player as many coins as second number.
        dcoins = second * multiplier;
        result = 'Two Match. +' + dcoins + ' coins.';

    // If no numbers match...
    }else{
        // ...take away two coins from player.
        dcoins = -2 * multiplier;
        result = 'No Match... ' + dcoins + ' coins...';
    }

    total += 1;
    result = result + '
' + total + ' total games';

    coins += dcoins;
    document.getElementById('coins').innerHTML = coins;
    document.getElementById('number-first').innerHTML = first;
    document.getElementById('number-second').innerHTML = second;
    document.getElementById('number-third').innerHTML = third;
    document.getElementById('result').innerHTML = result;
}

function repo_init(){
    core_input_binds_add({
      'keybinds': {
        83: {
          'todo': play_round,
        },
      },
    });

    document.getElementById('coins').innerHTML = coins;
    document.getElementById('multiplier').value = 1;
    document.getElementById('play').onclick = play_round;
    document.getElementById('reset').onclick = reset;
}

function reset(){
    if(!window.confirm('Reset coins?')){
        return;
    }

    coins = coins_default;

    var ids = {
      'coins': coins,
      'number-first': '',
      'number-second': '',
      'number-third': '',
      'result': '',
    };
    for(var id in ids){
        document.getElementById(id).innerHTML = ids[id];
    }
}

var coins_default = 100;
var coins = coins_default;
var total = 0;
<|endoftext|>


<commit_before>// Gulpfile for EtherCSS
// author Alex Devero

'use strict';

var gulp = require('gulp'),
    autoprefixer = require('gulp-autoprefixer'),
    rename = require('gulp-rename'),
    sass = require('gulp-ruby-sass'),
    sourcemaps = require('gulp-sourcemaps'),
    util = require('gulp-util');

// Sass to CSS

gulp.task('sass', function() {
  return sass('src/ethercss.scss', {sourcemap: true})
    .on('error', sass.logError)
    .pipe(autoprefixer({
      browsers: ['last 2 versions']
    }))
    .pipe(sourcemaps.write('.', {
      includeContent: false,
      sourceRoot: 'source'
    }))
    .pipe(gulp.dest('dist'))
});

// Sass to CSS minified

gulp.task('sassMin', function() {
  return sass('src/ethercss.scss', {noCache: true, sourcemap: true, style: 'compressed'})
    .on('error', sass.logError)
    .pipe(autoprefixer({
      browsers: ['last 2 versions']
    }))
    .pipe(rename({
      suffix: '.min'
    }))
    .pipe(sourcemaps.write('.', {
      includeContent: false,
      sourceRoot: 'source'
    }))
    .pipe(gulp.dest('dist'))
});

// Build EtherCSS

gulp.task('build', ['sass', 'sassMin']);
<commit_msg>Implemented csslint.<commit_after>// Gulpfile for EtherCSS
// author Alex Devero

'use strict';

var gulp = require('gulp'),
    autoprefixer = require('gulp-autoprefixer'),
    csslint = require('gulp-csslint'),
    rename = require('gulp-rename'),
    sass = require('gulp-ruby-sass'),
    sourcemaps = require('gulp-sourcemaps'),
    util = require('gulp-util');

// Sass to CSS

gulp.task('sass', function() {
  return sass('src/ethercss.scss', {sourcemap: true})
    .on('error', sass.logError)
    .pipe(csslint('csslintrc.json'))
    .pipe(csslint.reporter())
    .pipe(autoprefixer({
      browsers: ['last 2 versions']
    }))
    .pipe(sourcemaps.write('.', {
      includeContent: false,
      sourceRoot: 'source'
    }))
    .pipe(gulp.dest('dist'))
});

// Sass to CSS minified

gulp.task('sassMin', function() {
  return sass('src/ethercss.scss', {noCache: true, sourcemap: true, style: 'compressed'})
    .on('error', sass.logError)
    .pipe(csslint('csslintrc.json'))
    .pipe(csslint.reporter())
    .pipe(autoprefixer({
      browsers: ['last 2 versions']
    }))
    .pipe(rename({
      suffix: '.min'
    }))
    .pipe(sourcemaps.write('.', {
      includeContent: false,
      sourceRoot: 'source'
    }))
    .pipe(gulp.dest('dist'))
});

// Build EtherCSS

gulp.task('build', ['sass', 'sassMin']);
<|endoftext|>


<commit_before>// Show refresh button and overlay
var isLoading;

// Org Units data in JSON forms
var orgUnits;
var parentChildOUs;

// Search and Display criteria
// var searchName;
var layerLimitNum;
var totalLayers;

/* Adds listeners to search elements, loads all OUs. */
async function onloadOUPage() {
    loginStatus();

    // var searchButton = document.getElementById("search-enter-btn");
    // searchButton.addEventListener("click", function(event) {
    //     searchName = searchBar.value;
    //     getAllOUs();
    // })

    // var searchBar = document.getElementById("search");
    // // Execute a function when the user presses enter or erases the input
    // searchBar.addEventListener("search", function(event) {
    //     searchButton.click();
    // });

    orgUnits = await fetchOUs();
    layerLimitNum = parseInt(document.getElementById("limit-layer-num").value);

    getAllOUs();
}

/*
 * Fetches all OUs from Admin SDK.
*/
async function fetchOUs() {
    // fetch all OUs from API
    try {
        const response = await fetch('https://www.googleapis.com/admin/directory/v1/customer/my_customer/orgunits?orgUnitPath=/&type=all', {
            headers: {
                'authorization': `Bearer ` + token,
            }
        });
        const directoryOUs = await response.json();
        orgUnits = directoryOUs['organizationUnits'];
        return orgUnits;
    } catch (error) {
        console.error('Error:', error);
    }
}

/*
 * Calls all the functions to manipulate OU data, and loads the sidebar and visualization.
*/
function getAllOUs() {
    isLoading = true;
    setLoadingOverlay();

    // MVP 3: check if search criteria. for now, ignore layer limits for this

    orgUnits.sort(ouDepthSort); // sort by depth

    // layer limiting
    var limitedOUs = limitLayers();

    loadSidebar(limitedOUs);
    parentChildOUs = constructD3JSON(limitedOUs); // transform into parent-child JSON
    visualize(parentChildOUs); // visualize with D3
    addListeners();
}

/*
 * Computes an org unit's depth using the number of slashes in its path.
*/
function computeDepth(orgUnit) {
    // +1, as the root OU is considered layer 1
    return (orgUnit['orgUnitPath'].match(/\//g) || []).length + 1;
}

/*
 * Returns a list of OUs within the layer depth limit.
*/
function limitLayers() {
    if (orgUnits.length == 0) {
        return [];
    }
    var lastElementDepth = computeDepth(orgUnits[orgUnits.length - 1]);
    // set the totalLayers global var
    totalLayers = lastElementDepth;
    if (totalLayers <= layerLimitNum) {
        return orgUnits;
    } else {
        limitedOUList = [];
        for (var i = 0; i < orgUnits.length; i++) {
            currentOU = orgUnits[i];
            if (computeDepth(currentOU) > layerLimitNum) {
                break;
            }
            limitedOUList.push(currentOU);
        }
        return limitedOUList;
    }
}

/* Fill in informational fields on the sidebar of the page */
function loadSidebar(limitedOUs) {
    const domainName = document.getElementById("domain-name");
    domainName.innerHTML = "@" + domain;

    const displayOUs = document.getElementById("display-ous");
    const displayLayers = document.getElementById("display-layers");
    const totalOUs = document.getElementById("total-ous");
    const totalLayerCount = document.getElementById("total-layers");

    var displayLimit = document.getElementById("limit-layer-num");
    displayLimit.setAttribute("max", totalLayers);

    // root OU also counts
    displayOUs.innerHTML = limitedOUs.length + 1;
    displayLayers.innerHTML = layerLimitNum;
    totalOUs.innerHTML = orgUnits.length + 1;
    totalLayerCount.innerHTML = computeDepth(orgUnits[orgUnits.length - 1]);
}

/*
 * Compare function; sorts by by file depth, with parents first.
*/
function ouDepthSort(ou1, ou2) {
    ouDepth1 = computeDepth(ou1);
    ouDepth2 = computeDepth(ou2);
    return ouDepth1 - ouDepth2;
}

/*
 * Constructs parent-child JSON by iterating over the OUs from API after sorting.
*/
function constructD3JSON(sortedOUs) {
    // initialize root OU
    var outputJson = {};
    outputJson['name'] = 'root';
    outputJson['children'] = [];

    for (var i = 0; i < sortedOUs.length; i++) {
        addToJSON(sortedOUs[i], outputJson);
    }
    return outputJson;
}

/*
 * Adds each OU to the output JSON in level-order (parents always in before children).
*/
function addToJSON(ou, outputJson) {
    var parentOrgUnitPath = ou['parentOrgUnitPath'];

    if (parentOrgUnitPath === '/') {
        // can add directly as child of root
        var json = {
            "name": ou['name'],
            "children": []
        };
        outputJson['children'].push(json);
        return;
    }

    var parentArr = parentOrgUnitPath.split('/');
     // first element of split will always be empty string
    parentArr.shift();

    currentLevel = outputJson['children'];
    // keep searching deeper until we've reached the OU's direct parent
    while (parentArr.length !== 0) {
        var parentQuery = parentArr.shift(); // pops off highest level parent
        for (var i = 0; i < currentLevel.length; i++) {
            if (currentLevel[i]['name'] === parentQuery) {
                currentLevel = currentLevel[i]['children'];
                break;
            }
        }
    }

    // once reached direct parent, append the OU to children
    var json = {
        "name": ou['name'],
        "children": []
    };
    currentLevel.push(json);
}

/*
 * Given a tree-like JSON, visualizes it with a tree diagram using D3.js.
*/
function visualize(orgUnitsTree) {
    // Set the dimensions and margins of the diagram
    var margin = {top: 40, right: 90, bottom: 50, left: 90},
        width = 1980 - margin.left - margin.right,
        height = 1500 - margin.top - margin.bottom;

    // append the svg object to the #tree-chart div
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    var svg = d3.select("#tree-chart").append("svg")
        .attr("width", width + margin.right + margin.left)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform", "translate("
            + margin.left + "," + margin.top + ")");

    var i = 0,
        duration = 750,
        root;

    // declares a tree layout and assigns the size
    var treemap = d3.tree().size([height, width]);

    // Assigns parent, children, height, depth
    root = d3.hierarchy(orgUnitsTree, function(d) { return d.children; });
    root.x0 = height / 2;
    root.y0 = 0;

    // Collapse after the second level
    root.children.forEach(collapse);

    update(root);

    // Collapse the node and all it's children
    function collapse(d) {
        if (d.children) {
            d._children = d.children
            d._children.forEach(collapse)
            d.children = null
        }
    }

    function update(source) {

        // Assigns the x and y position for the nodes
        var treeData = treemap(root);

        // Compute the new tree layout.
        var nodes = treeData.descendants(),
            links = treeData.descendants().slice(1);

        // Normalize for fixed-depth.
        nodes.forEach(function(d){ d.y = d.depth * 180});

        // ****************** Nodes section ***************************

        // Update the nodes...
        var node = svg.selectAll('g.node')
            .data(nodes, function(d) {return d.id || (d.id = ++i); });

        // Enter any new modes at the parent's previous position.
        var nodeEnter = node.enter().append('g')
            .attr('class', 'node')
            .attr("transform", function(d) {
                return "translate(" + source.x0 + "," + source.y0 + ")";
            })
            .on('click', nodeClick);

        // Add Circle for the nodes
        nodeEnter.append('circle')
            .attr('class', 'node')
            .attr('r', 1e-6)
            .style("fill", function(d) {
                return d._children ? "lightsteelblue" : "#fff";
            });

        // Add labels for the nodes
        nodeEnter.append('text')
            .attr("dy", ".35em")
            .attr("y", function(d) {
                return d.children || d._children ? -18 : 18;
            })
            .attr("text-anchor", "middle")
            .text(function(d) { return d.data.name; });

        // UPDATE
        var nodeUpdate = nodeEnter.merge(node);

        // Transition to the proper position for the node
        nodeUpdate.transition()
            .duration(duration)
            .attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            });

        // Update the node attributes and style
        nodeUpdate.select('circle.node')
            .attr('r', 10)
            .style("fill", function(d) {
                return d._children ? "lightsteelblue" : "#fff";
            })
            .attr('cursor', 'pointer');

        // Remove any exiting nodes
        var nodeExit = node.exit().transition()
            .duration(duration)
            .attr("transform", function(d) {
                return "translate(" + source.x + "," + source.y + ")";
            })
            .remove();

        // On exit reduce the node circles size to 0
        nodeExit.select('circle')
            .attr('r', 1e-6);

        // On exit reduce the opacity of text labels
        nodeExit.select('text')
            .style('fill-opacity', 1e-6);

        // ****************** links section ***************************

        // Update the links...
        var link = svg.selectAll('path.link')
            .data(links, function(d) { return d.id; });

        // Enter any new links at the parent's previous position.
        var linkEnter = link.enter().insert('path', "g")
            .attr("class", "link")
            .attr('d', function(d){
                var o = {x: source.x0, y: source.y0}
                return diagonal(o, o)
            });

        // UPDATE
        var linkUpdate = linkEnter.merge(link);

        // Transition back to the parent element position
        linkUpdate.transition()
            .duration(duration)
            .attr('d', function(d){ return diagonal(d, d.parent) });

        // Remove any exiting links
        var linkExit = link.exit().transition()
            .duration(duration)
            .attr('d', function(d) {
                var o = {x: source.x, y: source.y}
                return diagonal(o, o)
            })
            .remove();

        // Store the old positions for transition.
        nodes.forEach(function(d){
            d.x0 = d.x;
            d.y0 = d.y;
        });

        // Creates a curved (diagonal) path from parent to the child nodes
        function diagonal(s, d) {

            path = `M ${s.x} ${s.y}
                    C ${s.x} ${(s.y + d.y) / 2},
                    ${d.x} ${(s.y + d.y) / 2},
                    ${d.x} ${d.y}`

            return path
        }

        // Toggle children on click.
        function nodeClick(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else {
                d.children = d._children;
                d._children = null;
            }
            update(d);
        }
    }

    // d3 visualization has loaded
    isLoading = false;
    setLoadingOverlay();
}

/*
 * Adds interactivity (zoom, drag) to the D3 visualization; adds onChange functions to form.
*/
function addListeners() {
    var scale = 1,
    panning = false,
    xoff = 0,
    yoff = 0,
    start = {x: 0, y: 0},
    treeChart = document.getElementById("tree-chart");
    editSelect = document.getElementById("edit-choice");

    function setTransform() {
        treeChart.style.transform = "translate(" + xoff + "px, " + yoff + "px) scale(" + scale + ")";
    }

    treeChart.onmousedown = function(e) {
        e.preventDefault();
        start = {x: e.clientX - xoff, y: e.clientY - yoff};
        panning = true;
    }

    treeChart.onmouseup = function(e) {
        panning = false;
    }

    treeChart.onmousemove = function(e) {
        e.preventDefault();
        if (!panning) {
            return;
        }
        xoff = (e.clientX - start.x);
        yoff = (e.clientY - start.y);
        setTransform();

    }

    treeChart.onwheel = function(e) {
        e.preventDefault();
        // take the scale into account with the offset
        var xs = (e.clientX - xoff) / scale,
            ys = (e.clientY - yoff) / scale,
            delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);

        // get scroll direction & set zoom level (with limits on zooming)
        if (delta > 0) {
            (scale < 1.6) ? (scale *= 1.2) : (scale *= 1);
        } else {
            (scale > 0.6) ? (scale /= 1.2) : (scale *= 1);
        }

        // reverse the offset amount with the new scale
        xoff = e.clientX - xs * scale;
        yoff = e.clientY - ys * scale;

        setTransform();
    }

    editSelect.onchange = function(event) {
        createDiv = document.getElementById("edit-create");
        updateDiv = document.getElementById("edit-update");
        deleteDiv = document.getElementById("edit-delete");

        if (editSelect.value == "create") {
            deleteDiv.style.display = "none";
            updateDiv.style.display = "none";
            createDiv.style.display = "block";
        } else if (editSelect.value == "update") {
            deleteDiv.style.display = "none";
            createDiv.style.display = "none";
            updateDiv.style.display = "block";
        } else {
            updateDiv.style.display = "none";
            createDiv.style.display = "none";
            deleteDiv.style.display = "block";
        }
    }
}

/*
 * Rerenders visualization with new layer limit criteria.
*/
function renderLayers() {
    layerLimitNum = parseInt(document.getElementById("limit-layer-num").value);
    if (Number.isNaN(layerLimitNum) || layerLimitNum < 2) {
        layerLimitNum = 3;
    }
    // ensures we never display more than the available number of layers
    if (layerLimitNum > totalLayers) {
        layerLimitNum = totalLayers;
    }
    d3.select("svg").remove();
    getAllOUs();
}<commit_msg>Remove commented code from script<commit_after>// Show refresh button and overlay
var isLoading;

// Org Units data in JSON forms
var orgUnits;
var parentChildOUs;

// Search and Display criteria
var layerLimitNum;
var totalLayers;

/* Adds listeners to search elements, loads all OUs. */
async function onloadOUPage() {
    loginStatus();

    orgUnits = await fetchOUs();
    layerLimitNum = parseInt(document.getElementById("limit-layer-num").value);

    getAllOUs();
}

/*
 * Fetches all OUs from Admin SDK.
*/
async function fetchOUs() {
    // fetch all OUs from API
    try {
        const response = await fetch('https://www.googleapis.com/admin/directory/v1/customer/my_customer/orgunits?orgUnitPath=/&type=all', {
            headers: {
                'authorization': `Bearer ` + token,
            }
        });
        const directoryOUs = await response.json();
        orgUnits = directoryOUs['organizationUnits'];
        return orgUnits;
    } catch (error) {
        console.error('Error:', error);
    }
}

/*
 * Calls all the functions to manipulate OU data, and loads the sidebar and visualization.
*/
function getAllOUs() {
    isLoading = true;
    setLoadingOverlay();

    // MVP 3: check if search criteria. for now, ignore layer limits for this

    orgUnits.sort(ouDepthSort); // sort by depth

    // layer limiting
    var limitedOUs = limitLayers();

    loadSidebar(limitedOUs);
    parentChildOUs = constructD3JSON(limitedOUs); // transform into parent-child JSON
    visualize(parentChildOUs); // visualize with D3
    addListeners();
}

/*
 * Computes an org unit's depth using the number of slashes in its path.
*/
function computeDepth(orgUnit) {
    // +1, as the root OU is considered layer 1
    return (orgUnit['orgUnitPath'].match(/\//g) || []).length + 1;
}

/*
 * Returns a list of OUs within the layer depth limit.
*/
function limitLayers() {
    if (orgUnits.length == 0) {
        return [];
    }
    var lastElementDepth = computeDepth(orgUnits[orgUnits.length - 1]);
    // set the totalLayers global var
    totalLayers = lastElementDepth;
    if (totalLayers <= layerLimitNum) {
        return orgUnits;
    } else {
        limitedOUList = [];
        for (var i = 0; i < orgUnits.length; i++) {
            currentOU = orgUnits[i];
            if (computeDepth(currentOU) > layerLimitNum) {
                break;
            }
            limitedOUList.push(currentOU);
        }
        return limitedOUList;
    }
}

/* Fill in informational fields on the sidebar of the page */
function loadSidebar(limitedOUs) {
    const domainName = document.getElementById("domain-name");
    domainName.innerHTML = "@" + domain;

    const displayOUs = document.getElementById("display-ous");
    const displayLayers = document.getElementById("display-layers");
    const totalOUs = document.getElementById("total-ous");
    const totalLayerCount = document.getElementById("total-layers");

    var displayLimit = document.getElementById("limit-layer-num");
    displayLimit.setAttribute("max", totalLayers);

    // root OU also counts
    displayOUs.innerHTML = limitedOUs.length + 1;
    displayLayers.innerHTML = layerLimitNum;
    totalOUs.innerHTML = orgUnits.length + 1;
    totalLayerCount.innerHTML = computeDepth(orgUnits[orgUnits.length - 1]);
}

/*
 * Compare function; sorts by by file depth, with parents first.
*/
function ouDepthSort(ou1, ou2) {
    ouDepth1 = computeDepth(ou1);
    ouDepth2 = computeDepth(ou2);
    return ouDepth1 - ouDepth2;
}

/*
 * Constructs parent-child JSON by iterating over the OUs from API after sorting.
*/
function constructD3JSON(sortedOUs) {
    // initialize root OU
    var outputJson = {};
    outputJson['name'] = 'root';
    outputJson['children'] = [];

    for (var i = 0; i < sortedOUs.length; i++) {
        addToJSON(sortedOUs[i], outputJson);
    }
    return outputJson;
}

/*
 * Adds each OU to the output JSON in level-order (parents always in before children).
*/
function addToJSON(ou, outputJson) {
    var parentOrgUnitPath = ou['parentOrgUnitPath'];

    if (parentOrgUnitPath === '/') {
        // can add directly as child of root
        var json = {
            "name": ou['name'],
            "children": []
        };
        outputJson['children'].push(json);
        return;
    }

    var parentArr = parentOrgUnitPath.split('/');
     // first element of split will always be empty string
    parentArr.shift();

    currentLevel = outputJson['children'];
    // keep searching deeper until we've reached the OU's direct parent
    while (parentArr.length !== 0) {
        var parentQuery = parentArr.shift(); // pops off highest level parent
        for (var i = 0; i < currentLevel.length; i++) {
            if (currentLevel[i]['name'] === parentQuery) {
                currentLevel = currentLevel[i]['children'];
                break;
            }
        }
    }

    // once reached direct parent, append the OU to children
    var json = {
        "name": ou['name'],
        "children": []
    };
    currentLevel.push(json);
}

/*
 * Given a tree-like JSON, visualizes it with a tree diagram using D3.js.
*/
function visualize(orgUnitsTree) {
    // Set the dimensions and margins of the diagram
    var margin = {top: 40, right: 90, bottom: 50, left: 90},
        width = 1980 - margin.left - margin.right,
        height = 1500 - margin.top - margin.bottom;

    // append the svg object to the #tree-chart div
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    var svg = d3.select("#tree-chart").append("svg")
        .attr("width", width + margin.right + margin.left)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform", "translate("
            + margin.left + "," + margin.top + ")");

    var i = 0,
        duration = 750,
        root;

    // declares a tree layout and assigns the size
    var treemap = d3.tree().size([height, width]);

    // Assigns parent, children, height, depth
    root = d3.hierarchy(orgUnitsTree, function(d) { return d.children; });
    root.x0 = height / 2;
    root.y0 = 0;

    // Collapse after the second level
    root.children.forEach(collapse);

    update(root);

    // Collapse the node and all it's children
    function collapse(d) {
        if (d.children) {
            d._children = d.children
            d._children.forEach(collapse)
            d.children = null
        }
    }

    function update(source) {

        // Assigns the x and y position for the nodes
        var treeData = treemap(root);

        // Compute the new tree layout.
        var nodes = treeData.descendants(),
            links = treeData.descendants().slice(1);

        // Normalize for fixed-depth.
        nodes.forEach(function(d){ d.y = d.depth * 180});

        // ****************** Nodes section ***************************

        // Update the nodes...
        var node = svg.selectAll('g.node')
            .data(nodes, function(d) {return d.id || (d.id = ++i); });

        // Enter any new modes at the parent's previous position.
        var nodeEnter = node.enter().append('g')
            .attr('class', 'node')
            .attr("transform", function(d) {
                return "translate(" + source.x0 + "," + source.y0 + ")";
            })
            .on('click', nodeClick);

        // Add Circle for the nodes
        nodeEnter.append('circle')
            .attr('class', 'node')
            .attr('r', 1e-6)
            .style("fill", function(d) {
                return d._children ? "lightsteelblue" : "#fff";
            });

        // Add labels for the nodes
        nodeEnter.append('text')
            .attr("dy", ".35em")
            .attr("y", function(d) {
                return d.children || d._children ? -18 : 18;
            })
            .attr("text-anchor", "middle")
            .text(function(d) { return d.data.name; });

        // UPDATE
        var nodeUpdate = nodeEnter.merge(node);

        // Transition to the proper position for the node
        nodeUpdate.transition()
            .duration(duration)
            .attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            });

        // Update the node attributes and style
        nodeUpdate.select('circle.node')
            .attr('r', 10)
            .style("fill", function(d) {
                return d._children ? "lightsteelblue" : "#fff";
            })
            .attr('cursor', 'pointer');

        // Remove any exiting nodes
        var nodeExit = node.exit().transition()
            .duration(duration)
            .attr("transform", function(d) {
                return "translate(" + source.x + "," + source.y + ")";
            })
            .remove();

        // On exit reduce the node circles size to 0
        nodeExit.select('circle')
            .attr('r', 1e-6);

        // On exit reduce the opacity of text labels
        nodeExit.select('text')
            .style('fill-opacity', 1e-6);

        // ****************** links section ***************************

        // Update the links...
        var link = svg.selectAll('path.link')
            .data(links, function(d) { return d.id; });

        // Enter any new links at the parent's previous position.
        var linkEnter = link.enter().insert('path', "g")
            .attr("class", "link")
            .attr('d', function(d){
                var o = {x: source.x0, y: source.y0}
                return diagonal(o, o)
            });

        // UPDATE
        var linkUpdate = linkEnter.merge(link);

        // Transition back to the parent element position
        linkUpdate.transition()
            .duration(duration)
            .attr('d', function(d){ return diagonal(d, d.parent) });

        // Remove any exiting links
        var linkExit = link.exit().transition()
            .duration(duration)
            .attr('d', function(d) {
                var o = {x: source.x, y: source.y}
                return diagonal(o, o)
            })
            .remove();

        // Store the old positions for transition.
        nodes.forEach(function(d){
            d.x0 = d.x;
            d.y0 = d.y;
        });

        // Creates a curved (diagonal) path from parent to the child nodes
        function diagonal(s, d) {

            path = `M ${s.x} ${s.y}
                    C ${s.x} ${(s.y + d.y) / 2},
                    ${d.x} ${(s.y + d.y) / 2},
                    ${d.x} ${d.y}`

            return path
        }

        // Toggle children on click.
        function nodeClick(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else {
                d.children = d._children;
                d._children = null;
            }
            update(d);
        }
    }

    // d3 visualization has loaded
    isLoading = false;
    setLoadingOverlay();
}

/*
 * Adds interactivity (zoom, drag) to the D3 visualization; adds onChange functions to form.
*/
function addListeners() {
    var scale = 1,
    panning = false,
    xoff = 0,
    yoff = 0,
    start = {x: 0, y: 0},
    treeChart = document.getElementById("tree-chart");
    editSelect = document.getElementById("edit-choice");

    function setTransform() {
        treeChart.style.transform = "translate(" + xoff + "px, " + yoff + "px) scale(" + scale + ")";
    }

    treeChart.onmousedown = function(e) {
        e.preventDefault();
        start = {x: e.clientX - xoff, y: e.clientY - yoff};
        panning = true;
    }

    treeChart.onmouseup = function(e) {
        panning = false;
    }

    treeChart.onmousemove = function(e) {
        e.preventDefault();
        if (!panning) {
            return;
        }
        xoff = (e.clientX - start.x);
        yoff = (e.clientY - start.y);
        setTransform();

    }

    treeChart.onwheel = function(e) {
        e.preventDefault();
        // take the scale into account with the offset
        var xs = (e.clientX - xoff) / scale,
            ys = (e.clientY - yoff) / scale,
            delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);

        // get scroll direction & set zoom level (with limits on zooming)
        if (delta > 0) {
            (scale < 1.6) ? (scale *= 1.2) : (scale *= 1);
        } else {
            (scale > 0.6) ? (scale /= 1.2) : (scale *= 1);
        }

        // reverse the offset amount with the new scale
        xoff = e.clientX - xs * scale;
        yoff = e.clientY - ys * scale;

        setTransform();
    }

    editSelect.onchange = function(event) {
        createDiv = document.getElementById("edit-create");
        updateDiv = document.getElementById("edit-update");
        deleteDiv = document.getElementById("edit-delete");

        if (editSelect.value == "create") {
            deleteDiv.style.display = "none";
            updateDiv.style.display = "none";
            createDiv.style.display = "block";
        } else if (editSelect.value == "update") {
            deleteDiv.style.display = "none";
            createDiv.style.display = "none";
            updateDiv.style.display = "block";
        } else {
            updateDiv.style.display = "none";
            createDiv.style.display = "none";
            deleteDiv.style.display = "block";
        }
    }
}

/*
 * Rerenders visualization with new layer limit criteria.
*/
function renderLayers() {
    layerLimitNum = parseInt(document.getElementById("limit-layer-num").value);
    if (Number.isNaN(layerLimitNum) || layerLimitNum < 2) {
        layerLimitNum = 3;
    }
    // ensures we never display more than the available number of layers
    if (layerLimitNum > totalLayers) {
        layerLimitNum = totalLayers;
    }
    d3.select("svg").remove();
    getAllOUs();
}
<|endoftext|>