<commit_before>
/*
 *  Guacamole - Clientless Remote Desktop
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// UI Definition
var GuacamoleUI = {

    /* Detection Constants */

    "LONG_PRESS_DETECT_TIMEOUT"     : 800, /* milliseconds */
    "LONG_PRESS_MOVEMENT_THRESHOLD" : 10,  /* pixels */
    "KEYBOARD_AUTO_RESIZE_INTERVAL" : 30,  /* milliseconds */

    /* UI Components */

    "viewport" : document.getElementById("viewportClone"),
    "display"  : document.getElementById("display"),
    "logo"     : document.getElementById("status-logo"),

    "buttons": {
        "reconnect" : document.getElementById("reconnect")
    },

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

    "state"        : document.getElementById("statusText"),
    "client"       : null,
    "sessionState" : new GuacamoleSessionState()

};

/**
 * Array of all supported audio mimetypes, populated when this script is
 * loaded.
 */
GuacamoleUI.supportedAudio = [];

/**
 * Array of all supported video mimetypes, populated when this script is
 * loaded.
 */
GuacamoleUI.supportedVideo = [];

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

GuacamoleUI.lastKeyboardWidth = 0;

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

GuacamoleUI.keyboardResizeInterval = null;

// Show/Hide keyboard
GuacamoleUI.toggleKeyboard = function() {

    // If Guac OSK shown, hide it.
    var displayed = GuacamoleUI.containers.keyboard.style.display;
    if (displayed == "block") {

        // Hide keyboard
        GuacamoleUI.containers.keyboard.style.display = "none";

        // Stop automatic periodic update
        window.clearInterval(GuacamoleUI.keyboardResizeInterval);

    }

    // Otherwise, show it
    else {

        // Show keyboard
        GuacamoleUI.containers.keyboard.style.display = "block";

        // Start periodic update of keyboard size
        GuacamoleUI.keyboardResizeInterval = window.setInterval(
            GuacamoleUI.updateKeyboardSize,
            GuacamoleUI.KEYBOARD_AUTO_RESIZE_INTERVAL);

        // Update keyboard size initially
        GuacamoleUI.updateKeyboardSize();

    }

};

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

    // 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";
    };

    function positionCentered(element) {
        element.style.left =
            ((GuacamoleUI.viewport.offsetWidth - element.offsetWidth) / 2
            + window.pageXOffset)
            + "px";

        element.style.top =
            ((GuacamoleUI.viewport.offsetHeight - element.offsetHeight) / 2
            + window.pageYOffset)
            + "px";
    }

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

    // Query audio support
    if (!GuacamoleUI.sessionState.getProperty("disable-sound"))
        (function () {
            var probably_supported = [];
            var maybe_supported = [];

            // Build array of supported audio formats
            [
                'audio/ogg; codecs="vorbis"',
                'audio/mp4; codecs="mp4a.40.5"',
                'audio/mpeg; codecs="mp3"',
                'audio/webm; codecs="vorbis"',
                'audio/wav; codecs=1'
            ].forEach(function(mimetype) {

                var audio = new Audio();
                var support_level = audio.canPlayType(mimetype);

                // Trim semicolon and trailer
                var semicolon = mimetype.indexOf(";");
                if (semicolon != -1)
                    mimetype = mimetype.substring(0, semicolon);

                // Partition by probably/maybe
                if (support_level == "probably")
                    probably_supported.push(mimetype);
                else if (support_level == "maybe")
                    maybe_supported.push(mimetype);

            });

            Array.prototype.push.apply(GuacamoleUI.supportedAudio, probably_supported);
            Array.prototype.push.apply(GuacamoleUI.supportedAudio, maybe_supported);
        })();

    // Query video support
    (function () {
        var probably_supported = [];
        var maybe_supported = [];

        // Build array of supported video formats
        [
            'video/ogg; codecs="theora, vorbis"',
            'video/mp4; codecs="avc1.4D401E, mp4a.40.5"',
            'video/webm; codecs="vp8.0, vorbis"'
        ].forEach(function(mimetype) {

            var video = document.createElement("video");
            var support_level = video.canPlayType(mimetype);

            // Trim semicolon and trailer
            var semicolon = mimetype.indexOf(";");
            if (semicolon != -1)
                mimetype = mimetype.substring(0, semicolon);

            // Partition by probably/maybe
            if (support_level == "probably")
                probably_supported.push(mimetype);
            else if (support_level == "maybe")
                maybe_supported.push(mimetype);

        });

        Array.prototype.push.apply(GuacamoleUI.supportedVideo, probably_supported);
        Array.prototype.push.apply(GuacamoleUI.supportedVideo, maybe_supported);
    })();

})();

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

    GuacamoleUI.client = guac;

    var title_prefix = null;
    var connection_name = "Guacamole";

    var guac_display = guac.getDisplay();

    // Set document title appropriately, based on prefix and connection name
    function updateTitle() {

        // Use title prefix if present
        if (title_prefix) {

            document.title = title_prefix;

            // Include connection name, if present
            if (connection_name)
                document.title += " " + connection_name;

        }

        // Otherwise, just set to connection name
        else if (connection_name)
            document.title = connection_name;

    }

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

    // Mouse
    var mouse = new Guacamole.Mouse(guac_display);
    var touch = new Guacamole.Mouse.Touchpad(guac_display);
    touch.onmousedown = touch.onmouseup = touch.onmousemove =
    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);

            // Scale event by current scale
            var scaledState = new Guacamole.Mouse.State(
                    mouseState.x / guac.getScale(),
                    mouseState.y / guac.getScale(),
                    mouseState.left,
                    mouseState.middle,
                    mouseState.right,
                    mouseState.up,
                    mouseState.down);

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

        };

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

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

        // If key is NOT one of the expected keys, gesture not possible
        if (keysym != 0xFFE3 && keysym != 0xFFE9 && keysym != 0xFFE1)
            show_keyboard_gesture_possible = false;

    };

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

        // If lifting up on shift, toggle keyboard if rest of gesture
        // conditions satisfied
        if (show_keyboard_gesture_possible && keysym == 0xFFE1) {
            if (keyboard.pressed[0xFFE3] && keyboard.pressed[0xFFE9])
                GuacamoleUI.toggleKeyboard();
        }

        // Detect if no keys are pressed
        var reset_gesture = true;
        for (var pressed in keyboard.pressed) {
            reset_gesture = false;
            break;
        }

        // Reset gesture state if possible
        if (reset_gesture)
            show_keyboard_gesture_possible = true;

    };

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

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

    function isTypableCharacter(keysym) {
        return (keysym & 0xFFFF00) != 0xFF00;
    }

    function updateThumbnail() {

        // Get screenshot
        var canvas = guac.flatten();

        // Calculate scale of thumbnail (max 320x240, max zoom 100%)
        var scale = Math.min(
            320 / canvas.width,
            240 / canvas.height,
            1
        );

        // Create thumbnail canvas
        var thumbnail = document.createElement("canvas");
        thumbnail.width  = canvas.width*scale;
        thumbnail.height = canvas.height*scale;

        // Scale screenshot to thumbnail
        var context = thumbnail.getContext("2d");
        context.drawImage(canvas,
            0, 0, canvas.width, canvas.height,
            0, 0, thumbnail.width, thumbnail.height
        );

        // Save thumbnail to history
        var id = decodeURIComponent(window.location.search.substring(4));
        GuacamoleHistory.update(id, thumbnail.toDataURL());

    }

    function updateDisplayScale() {

        // If auto-fit is enabled, scale display
        if (GuacamoleUI.sessionState.getProperty("auto-fit")) {

            // Calculate scale to fit screen
            var fit_scale = Math.min(
                window.innerWidth / guac.getWidth(),
                window.innerHeight / guac.getHeight()
            );

            // Scale client
            if (fit_scale != guac.getScale())
                guac.scale(fit_scale);

        }

        // Otherwise, scale to 100%
        else if (guac.getScale() != 1.0)
            guac.scale(1.0);

    }

    // Handle resize
    guac.onresize = function(width, height) {
        updateDisplayScale();
    }

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

        switch (clientState) {

            // Idle
            case 0:
                GuacamoleUI.showStatus("Idle.");
                title_prefix = "[Idle]";
                break;

            // Connecting
            case 1:
                GuacamoleUI.showStatus("Connecting...");
                title_prefix = "[Connecting...]";
                break;

            // Connected + waiting
            case 2:
                GuacamoleUI.showStatus("Connected, waiting for first update...");
                title_prefix = "[Waiting...]";
                break;

            // Connected
            case 3:

                GuacamoleUI.hideStatus();
                title_prefix = null;

                // Update clipboard with current data
                if (GuacamoleUI.sessionState.getProperty("clipboard"))
                    guac.setClipboard(GuacamoleUI.sessionState.getProperty("clipboard"));

                // Regularly update screenshot
                window.setInterval(updateThumbnail, 1000);

                break;

            // Disconnecting
            case 4:
                GuacamoleUI.showStatus("Disconnecting...");
                title_prefix = "[Disconnecting...]";
                break;

            // Disconnected
            case 5:
                GuacamoleUI.showStatus("Disconnected.");
                title_prefix = "[Disconnected]";
                break;

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

        }

        updateTitle();
    };

    // Name instruction handler
    guac.onname = function(name) {
        connection_name = name;
        updateTitle();
    };

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

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

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

    };

    // Disconnect and update thumbnail on close
    window.onunload = function() {

        updateThumbnail();
        guac.disconnect();

    };

    // Send size events on resize
    window.onresize = function() {

        guac.sendSize(window.innerWidth, window.innerHeight);
        updateDisplayScale();
        GuacamoleUI.updateKeyboardSize();

    };

    // Server copy handler
    guac.onclipboard = function(data) {
        GuacamoleUI.sessionState.setProperty("clipboard", data);
    };

    GuacamoleUI.sessionState.onchange = function(old_state, new_state, name) {
        if (name == "clipboard")
            guac.setClipboard(new_state[name]);
        else if (name == "auto-fit")
            updateDisplayScale();

    };

};
<commit_msg>Show/hide keyboard with three-finger tap.<commit_after>
/*
 *  Guacamole - Clientless Remote Desktop
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// UI Definition
var GuacamoleUI = {

    /* Constants */

    "KEYBOARD_AUTO_RESIZE_INTERVAL" : 30,  /* milliseconds */

    /* UI Components */

    "viewport" : document.getElementById("viewportClone"),
    "display"  : document.getElementById("display"),
    "logo"     : document.getElementById("status-logo"),

    "buttons": {
        "reconnect" : document.getElementById("reconnect")
    },

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

    "state"        : document.getElementById("statusText"),
    "client"       : null,
    "sessionState" : new GuacamoleSessionState()

};

/**
 * Array of all supported audio mimetypes, populated when this script is
 * loaded.
 */
GuacamoleUI.supportedAudio = [];

/**
 * Array of all supported video mimetypes, populated when this script is
 * loaded.
 */
GuacamoleUI.supportedVideo = [];

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

GuacamoleUI.lastKeyboardWidth = 0;

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

GuacamoleUI.keyboardResizeInterval = null;

// Show/Hide keyboard
GuacamoleUI.toggleKeyboard = function() {

    // If Guac OSK shown, hide it.
    var displayed = GuacamoleUI.containers.keyboard.style.display;
    if (displayed == "block") {

        // Hide keyboard
        GuacamoleUI.containers.keyboard.style.display = "none";

        // Stop automatic periodic update
        window.clearInterval(GuacamoleUI.keyboardResizeInterval);

    }

    // Otherwise, show it
    else {

        // Show keyboard
        GuacamoleUI.containers.keyboard.style.display = "block";

        // Start periodic update of keyboard size
        GuacamoleUI.keyboardResizeInterval = window.setInterval(
            GuacamoleUI.updateKeyboardSize,
            GuacamoleUI.KEYBOARD_AUTO_RESIZE_INTERVAL);

        // Update keyboard size initially
        GuacamoleUI.updateKeyboardSize();

    }

};

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

    // 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";
    };

    // Detect three-finger tap
    GuacamoleUI.display.addEventListener('touchstart', function(e) {

        // If three touches, toggle keyboard
        if (e.touches.length == 3)
            GuacamoleUI.toggleKeyboard();

    }, true);

    function positionCentered(element) {
        element.style.left =
            ((GuacamoleUI.viewport.offsetWidth - element.offsetWidth) / 2
            + window.pageXOffset)
            + "px";

        element.style.top =
            ((GuacamoleUI.viewport.offsetHeight - element.offsetHeight) / 2
            + window.pageYOffset)
            + "px";
    }

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

    // Query audio support
    if (!GuacamoleUI.sessionState.getProperty("disable-sound"))
        (function () {
            var probably_supported = [];
            var maybe_supported = [];

            // Build array of supported audio formats
            [
                'audio/ogg; codecs="vorbis"',
                'audio/mp4; codecs="mp4a.40.5"',
                'audio/mpeg; codecs="mp3"',
                'audio/webm; codecs="vorbis"',
                'audio/wav; codecs=1'
            ].forEach(function(mimetype) {

                var audio = new Audio();
                var support_level = audio.canPlayType(mimetype);

                // Trim semicolon and trailer
                var semicolon = mimetype.indexOf(";");
                if (semicolon != -1)
                    mimetype = mimetype.substring(0, semicolon);

                // Partition by probably/maybe
                if (support_level == "probably")
                    probably_supported.push(mimetype);
                else if (support_level == "maybe")
                    maybe_supported.push(mimetype);

            });

            Array.prototype.push.apply(GuacamoleUI.supportedAudio, probably_supported);
            Array.prototype.push.apply(GuacamoleUI.supportedAudio, maybe_supported);
        })();

    // Query video support
    (function () {
        var probably_supported = [];
        var maybe_supported = [];

        // Build array of supported video formats
        [
            'video/ogg; codecs="theora, vorbis"',
            'video/mp4; codecs="avc1.4D401E, mp4a.40.5"',
            'video/webm; codecs="vp8.0, vorbis"'
        ].forEach(function(mimetype) {

            var video = document.createElement("video");
            var support_level = video.canPlayType(mimetype);

            // Trim semicolon and trailer
            var semicolon = mimetype.indexOf(";");
            if (semicolon != -1)
                mimetype = mimetype.substring(0, semicolon);

            // Partition by probably/maybe
            if (support_level == "probably")
                probably_supported.push(mimetype);
            else if (support_level == "maybe")
                maybe_supported.push(mimetype);

        });

        Array.prototype.push.apply(GuacamoleUI.supportedVideo, probably_supported);
        Array.prototype.push.apply(GuacamoleUI.supportedVideo, maybe_supported);
    })();

})();

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

    GuacamoleUI.client = guac;

    var title_prefix = null;
    var connection_name = "Guacamole";

    var guac_display = guac.getDisplay();

    // Set document title appropriately, based on prefix and connection name
    function updateTitle() {

        // Use title prefix if present
        if (title_prefix) {

            document.title = title_prefix;

            // Include connection name, if present
            if (connection_name)
                document.title += " " + connection_name;

        }

        // Otherwise, just set to connection name
        else if (connection_name)
            document.title = connection_name;

    }

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

    // Mouse
    var mouse = new Guacamole.Mouse(guac_display);
    var touch = new Guacamole.Mouse.Touchpad(guac_display);
    touch.onmousedown = touch.onmouseup = touch.onmousemove =
    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);

            // Scale event by current scale
            var scaledState = new Guacamole.Mouse.State(
                    mouseState.x / guac.getScale(),
                    mouseState.y / guac.getScale(),
                    mouseState.left,
                    mouseState.middle,
                    mouseState.right,
                    mouseState.up,
                    mouseState.down);

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

        };

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

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

        // If key is NOT one of the expected keys, gesture not possible
        if (keysym != 0xFFE3 && keysym != 0xFFE9 && keysym != 0xFFE1)
            show_keyboard_gesture_possible = false;

    };

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

        // If lifting up on shift, toggle keyboard if rest of gesture
        // conditions satisfied
        if (show_keyboard_gesture_possible && keysym == 0xFFE1) {
            if (keyboard.pressed[0xFFE3] && keyboard.pressed[0xFFE9])
                GuacamoleUI.toggleKeyboard();
        }

        // Detect if no keys are pressed
        var reset_gesture = true;
        for (var pressed in keyboard.pressed) {
            reset_gesture = false;
            break;
        }

        // Reset gesture state if possible
        if (reset_gesture)
            show_keyboard_gesture_possible = true;

    };

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

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

    function isTypableCharacter(keysym) {
        return (keysym & 0xFFFF00) != 0xFF00;
    }

    function updateThumbnail() {

        // Get screenshot
        var canvas = guac.flatten();

        // Calculate scale of thumbnail (max 320x240, max zoom 100%)
        var scale = Math.min(
            320 / canvas.width,
            240 / canvas.height,
            1
        );

        // Create thumbnail canvas
        var thumbnail = document.createElement("canvas");
        thumbnail.width  = canvas.width*scale;
        thumbnail.height = canvas.height*scale;

        // Scale screenshot to thumbnail
        var context = thumbnail.getContext("2d");
        context.drawImage(canvas,
            0, 0, canvas.width, canvas.height,
            0, 0, thumbnail.width, thumbnail.height
        );

        // Save thumbnail to history
        var id = decodeURIComponent(window.location.search.substring(4));
        GuacamoleHistory.update(id, thumbnail.toDataURL());

    }

    function updateDisplayScale() {

        // If auto-fit is enabled, scale display
        if (GuacamoleUI.sessionState.getProperty("auto-fit")) {

            // Calculate scale to fit screen
            var fit_scale = Math.min(
                window.innerWidth / guac.getWidth(),
                window.innerHeight / guac.getHeight()
            );

            // Scale client
            if (fit_scale != guac.getScale())
                guac.scale(fit_scale);

        }

        // Otherwise, scale to 100%
        else if (guac.getScale() != 1.0)
            guac.scale(1.0);

    }

    // Handle resize
    guac.onresize = function(width, height) {
        updateDisplayScale();
    }

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

        switch (clientState) {

            // Idle
            case 0:
                GuacamoleUI.showStatus("Idle.");
                title_prefix = "[Idle]";
                break;

            // Connecting
            case 1:
                GuacamoleUI.showStatus("Connecting...");
                title_prefix = "[Connecting...]";
                break;

            // Connected + waiting
            case 2:
                GuacamoleUI.showStatus("Connected, waiting for first update...");
                title_prefix = "[Waiting...]";
                break;

            // Connected
            case 3:

                GuacamoleUI.hideStatus();
                title_prefix = null;

                // Update clipboard with current data
                if (GuacamoleUI.sessionState.getProperty("clipboard"))
                    guac.setClipboard(GuacamoleUI.sessionState.getProperty("clipboard"));

                // Regularly update screenshot
                window.setInterval(updateThumbnail, 1000);

                break;

            // Disconnecting
            case 4:
                GuacamoleUI.showStatus("Disconnecting...");
                title_prefix = "[Disconnecting...]";
                break;

            // Disconnected
            case 5:
                GuacamoleUI.showStatus("Disconnected.");
                title_prefix = "[Disconnected]";
                break;

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

        }

        updateTitle();
    };

    // Name instruction handler
    guac.onname = function(name) {
        connection_name = name;
        updateTitle();
    };

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

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

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

    };

    // Disconnect and update thumbnail on close
    window.onunload = function() {

        updateThumbnail();
        guac.disconnect();

    };

    // Send size events on resize
    window.onresize = function() {

        guac.sendSize(window.innerWidth, window.innerHeight);
        updateDisplayScale();
        GuacamoleUI.updateKeyboardSize();

    };

    // Server copy handler
    guac.onclipboard = function(data) {
        GuacamoleUI.sessionState.setProperty("clipboard", data);
    };

    GuacamoleUI.sessionState.onchange = function(old_state, new_state, name) {
        if (name == "clipboard")
            guac.setClipboard(new_state[name]);
        else if (name == "auto-fit")
            updateDisplayScale();

    };

};
<|endoftext|>


<commit_before>/*jshint browser: true */
/*jshint unused: false */
/*global require, window, exports, Backbone, EJS, $, templateEngine, arangoHelper, Joi*/

(function() {
  "use strict";

  window.CollectionListItemView = Backbone.View.extend({

    tagName: "div",
    className: "tile",
    template: templateEngine.createTemplate("collectionsItemView.ejs"),

    initialize: function () {
      this.collectionsView = this.options.collectionsView;
    },
    events: {
      'click .iconSet.icon_arangodb_settings2': 'createEditPropertiesModal',
      'click .pull-left' : 'noop',
      'click .icon_arangodb_settings2' : 'editProperties',
//      'click #editCollection' : 'editProperties',
      'click .spanInfo' : 'showProperties',
      'click': 'selectCollection'
    },

    render: function () {
      $(this.el).html(this.template.render({
        model: this.model
      }));
      $(this.el).attr('id', 'collection_' + this.model.get('name'));
      return this;
    },

    editProperties: function (event) {
      event.stopPropagation();
      this.createEditPropertiesModal();
    },

    showProperties: function(event) {
      event.stopPropagation();
      this.createInfoModal();
/*
      window.App.navigate(
        "collectionInfo/" + encodeURIComponent(this.model.get("id")), {trigger: true}
      );
*/
    },

    selectCollection: function(event) {

      //check if event was fired from disabled button
      if ($(event.target).hasClass("disabled")) {
        return 0;
      }

      window.App.navigate(
        "collection/" + encodeURIComponent(this.model.get("name")) + "/documents/1", {trigger: true}
      );
    },

    noop: function(event) {
      event.stopPropagation();
    },

    unloadCollection: function () {
      this.model.unloadCollection();
      this.render();
      window.modalView.hide();
    },

    loadCollection: function () {
      this.model.loadCollection();
      this.render();
      window.modalView.hide();
    },

    deleteCollection: function () {
      this.model.destroy(
        {
          error: function() {
            arangoHelper.arangoError('Could not delete collection.');
          },
          success: function() {
            window.modalView.hide();
          }
        }
      );
      this.collectionsView.render();
    },

    saveModifiedCollection: function() {
      var newname;
      if (window.isCoordinator()) {
        newname = this.model.get('name');
      }
      else {
        newname = $('#change-collection-name').val();
      }

      var status = this.model.get('status');

      if (status === 'loaded') {
        var result;
        if (this.model.get('name') !== newname) {
          result = this.model.renameCollection(newname);
        }

        var wfs = $('#change-collection-sync').val();
        var journalSize;
        try {
          journalSize = JSON.parse($('#change-collection-size').val() * 1024 * 1024);
        }
        catch (e) {
          arangoHelper.arangoError('Please enter a valid number');
          return 0;
        }
        var changeResult = this.model.changeCollection(wfs, journalSize);

        if (result !== true) {
          if (result !== undefined) {
            arangoHelper.arangoError("Collection error: " + result);
            return 0;
          }
        }

        if (changeResult !== true) {
          arangoHelper.arangoNotification("Collection error", changeResult);
          return 0;
        }

        if (changeResult === true) {
          this.collectionsView.render();
          window.modalView.hide();
        }
      }
      else if (status === 'unloaded') {
        if (this.model.get('name') !== newname) {
          var result2 = this.model.renameCollection(newname);
          if (result2 === true) {
            this.collectionsView.render();
            window.modalView.hide();
          }
          else {
            arangoHelper.arangoError("Collection error: " + result2);
          }
        }
        else {
          window.modalView.hide();
        }
      }
    },

    //modal dialogs

    createEditPropertiesModal: function() {

      var collectionIsLoaded = false;

      if (this.model.get('status') === "loaded") {
        collectionIsLoaded = true;
      }

      var buttons = [],
        tableContent = [];

      if (! window.isCoordinator()) {
        tableContent.push(
          window.modalView.createTextEntry(
            "change-collection-name",
            "Name",
            this.model.get('name'),
            false,
            "",
            true,
            [
              {
                rule: Joi.string().regex(/^[a-zA-Z]/),
                msg: "Collection name must always start with a letter."
              },
              {
                rule: Joi.string().regex(/^[a-zA-Z0-9\-_]*$/),
                msg: 'Only Symbols "_" and "-" are allowed.'
              },
              {
                rule: Joi.string().required(),
                msg: "No collection name given."
              }
            ]
          )
        );
      }

      if (collectionIsLoaded) {
        // needs to be refactored. move getProperties into model
        var journalSize = this.model.getProperties().journalSize;
        journalSize = journalSize/(1024*1024);

        tableContent.push(
          window.modalView.createTextEntry(
            "change-collection-size",
            "Journal size",
            journalSize,
            "The maximal size of a journal or datafile (in MB). Must be at least 1.",
            "",
            true,
            [
              {
                rule: Joi.string().required(),
                msg: "No journal size given."
              },
              {
                rule: Joi.string().regex(/^[0-9]*$/),
                msg: "Must be a number."
              }
            ]
          )
        );
      }

      if(collectionIsLoaded) {
        // prevent "unexpected sync method error"
                var wfs = this.model.getProperties().waitForSync;
                tableContent.push(
          window.modalView.createSelectEntry(
            "change-collection-sync",
            "Wait for sync",
            wfs,
            "Synchronise to disk before returning from a create or update of a document.",
            [{value: false, label: "No"}, {value: true, label: "Yes"}]        )
        );
      }

      tableContent.push(
        window.modalView.createReadOnlyEntry(
          "change-collection-id", "ID", this.model.get('id'), ""
        )
      );
      tableContent.push(
        window.modalView.createReadOnlyEntry(
          "change-collection-type", "Type", this.model.get('type'), ""
        )
      );
      tableContent.push(
        window.modalView.createReadOnlyEntry(
          "change-collection-status", "Status", this.model.get('status'), ""
        )
      );
      if(collectionIsLoaded) {
        buttons.push(
          window.modalView.createNotificationButton(
            "Unload",
            this.unloadCollection.bind(this)
          )
        );
      } else {
        buttons.push(
          window.modalView.createNotificationButton(
            "Load",
            this.loadCollection.bind(this)
          )
        );
      }

      buttons.push(
        window.modalView.createDeleteButton(
          "Delete",
          this.deleteCollection.bind(this)
        )
      );
      buttons.push(
        window.modalView.createSuccessButton(
          "Save",
          this.saveModifiedCollection.bind(this)
        )
      );

      window.modalView.show(
        "modalTable.ejs",
        "Modify Collection",
        buttons,
        tableContent
      );
    },

    createInfoModal: function() {
      var buttons = [],
        tableContent = this.model;
      window.modalView.show(
        "modalCollectionInfo.ejs",
        "Collection: " + this.model.get('name'),
        buttons,
        tableContent
      );

    }

  });
}());
<commit_msg>added feature for truncating a collection<commit_after>/*jshint browser: true */
/*jshint unused: false */
/*global require, window, exports, Backbone, EJS, $, templateEngine, arangoHelper, Joi*/

(function() {
  "use strict";

  window.CollectionListItemView = Backbone.View.extend({

    tagName: "div",
    className: "tile",
    template: templateEngine.createTemplate("collectionsItemView.ejs"),

    initialize: function () {
      this.collectionsView = this.options.collectionsView;
    },
    events: {
      'click .iconSet.icon_arangodb_settings2': 'createEditPropertiesModal',
      'click .pull-left' : 'noop',
      'click .icon_arangodb_settings2' : 'editProperties',
//      'click #editCollection' : 'editProperties',
      'click .spanInfo' : 'showProperties',
      'click': 'selectCollection'
    },

    render: function () {
      $(this.el).html(this.template.render({
        model: this.model
      }));
      $(this.el).attr('id', 'collection_' + this.model.get('name'));
      return this;
    },

    editProperties: function (event) {
      event.stopPropagation();
      this.createEditPropertiesModal();
    },

    showProperties: function(event) {
      event.stopPropagation();
      this.createInfoModal();
/*
      window.App.navigate(
        "collectionInfo/" + encodeURIComponent(this.model.get("id")), {trigger: true}
      );
*/
    },

    selectCollection: function(event) {

      //check if event was fired from disabled button
      if ($(event.target).hasClass("disabled")) {
        return 0;
      }

      window.App.navigate(
        "collection/" + encodeURIComponent(this.model.get("name")) + "/documents/1", {trigger: true}
      );
    },

    noop: function(event) {
      event.stopPropagation();
    },

    unloadCollection: function () {
      this.model.unloadCollection();
      this.render();
      window.modalView.hide();
    },

    loadCollection: function () {
      this.model.loadCollection();
      this.render();
      window.modalView.hide();
    },

    truncateCollection: function () {
      this.model.truncateCollection();
      this.render();
      window.modalView.hide();
    },

    deleteCollection: function () {
      this.model.destroy(
        {
          error: function() {
            arangoHelper.arangoError('Could not delete collection.');
          },
          success: function() {
            window.modalView.hide();
          }
        }
      );
      this.collectionsView.render();
    },

    saveModifiedCollection: function() {
      var newname;
      if (window.isCoordinator()) {
        newname = this.model.get('name');
      }
      else {
        newname = $('#change-collection-name').val();
      }

      var status = this.model.get('status');

      if (status === 'loaded') {
        var result;
        if (this.model.get('name') !== newname) {
          result = this.model.renameCollection(newname);
        }

        var wfs = $('#change-collection-sync').val();
        var journalSize;
        try {
          journalSize = JSON.parse($('#change-collection-size').val() * 1024 * 1024);
        }
        catch (e) {
          arangoHelper.arangoError('Please enter a valid number');
          return 0;
        }
        var changeResult = this.model.changeCollection(wfs, journalSize);

        if (result !== true) {
          if (result !== undefined) {
            arangoHelper.arangoError("Collection error: " + result);
            return 0;
          }
        }

        if (changeResult !== true) {
          arangoHelper.arangoNotification("Collection error", changeResult);
          return 0;
        }

        if (changeResult === true) {
          this.collectionsView.render();
          window.modalView.hide();
        }
      }
      else if (status === 'unloaded') {
        if (this.model.get('name') !== newname) {
          var result2 = this.model.renameCollection(newname);
          if (result2 === true) {
            this.collectionsView.render();
            window.modalView.hide();
          }
          else {
            arangoHelper.arangoError("Collection error: " + result2);
          }
        }
        else {
          window.modalView.hide();
        }
      }
    },

    //modal dialogs

    createEditPropertiesModal: function() {

      var collectionIsLoaded = false;

      if (this.model.get('status') === "loaded") {
        collectionIsLoaded = true;
      }

      var buttons = [],
        tableContent = [];

      if (! window.isCoordinator()) {
        tableContent.push(
          window.modalView.createTextEntry(
            "change-collection-name",
            "Name",
            this.model.get('name'),
            false,
            "",
            true,
            [
              {
                rule: Joi.string().regex(/^[a-zA-Z]/),
                msg: "Collection name must always start with a letter."
              },
              {
                rule: Joi.string().regex(/^[a-zA-Z0-9\-_]*$/),
                msg: 'Only Symbols "_" and "-" are allowed.'
              },
              {
                rule: Joi.string().required(),
                msg: "No collection name given."
              }
            ]
          )
        );
      }

      if (collectionIsLoaded) {
        // needs to be refactored. move getProperties into model
        var journalSize = this.model.getProperties().journalSize;
        journalSize = journalSize/(1024*1024);

        tableContent.push(
          window.modalView.createTextEntry(
            "change-collection-size",
            "Journal size",
            journalSize,
            "The maximal size of a journal or datafile (in MB). Must be at least 1.",
            "",
            true,
            [
              {
                rule: Joi.string().required(),
                msg: "No journal size given."
              },
              {
                rule: Joi.string().regex(/^[0-9]*$/),
                msg: "Must be a number."
              }
            ]
          )
        );
      }

      if(collectionIsLoaded) {
        // prevent "unexpected sync method error"
                var wfs = this.model.getProperties().waitForSync;
                tableContent.push(
          window.modalView.createSelectEntry(
            "change-collection-sync",
            "Wait for sync",
            wfs,
            "Synchronise to disk before returning from a create or update of a document.",
            [{value: false, label: "No"}, {value: true, label: "Yes"}]        )
        );
      }

      tableContent.push(
        window.modalView.createReadOnlyEntry(
          "change-collection-id", "ID", this.model.get('id'), ""
        )
      );
      tableContent.push(
        window.modalView.createReadOnlyEntry(
          "change-collection-type", "Type", this.model.get('type'), ""
        )
      );
      tableContent.push(
        window.modalView.createReadOnlyEntry(
          "change-collection-status", "Status", this.model.get('status'), ""
        )
      );
      buttons.push(
        window.modalView.createDeleteButton(
          "Delete",
          this.deleteCollection.bind(this)
        )
      );
      buttons.push(
        window.modalView.createDeleteButton(
          "Truncate",
          this.truncateCollection.bind(this)
        )
      );
      if(collectionIsLoaded) {
        buttons.push(
          window.modalView.createNotificationButton(
            "Unload",
            this.unloadCollection.bind(this)
          )
        );
      } else {
        buttons.push(
          window.modalView.createNotificationButton(
            "Load",
            this.loadCollection.bind(this)
          )
        );
      }

      buttons.push(
        window.modalView.createSuccessButton(
          "Save",
          this.saveModifiedCollection.bind(this)
        )
      );

      window.modalView.show(
        "modalTable.ejs",
        "Modify Collection",
        buttons,
        tableContent
      );
    },

    createInfoModal: function() {
      var buttons = [],
        tableContent = this.model;
      window.modalView.show(
        "modalCollectionInfo.ejs",
        "Collection: " + this.model.get('name'),
        buttons,
        tableContent
      );

    }

  });
}());
<|endoftext|>


<commit_before>//= require esphinx/support/jquery
//= require esphinx/support/ajax
//= require esphinx/support/string
//= require esphinx/support/array

"use strict";

var
    jQuery,
    Ajax;

const
    MODULE_CSS = "esphinx-ui";

(function ($) {

    $.prototype.autocomplete = function (obj, args, callback) {
        var
            self = $(this),
            ajax = Ajax,
            amountChar,

            composeMatch = function (mapped, content, domContentArr) {
                var
                    composedArr = [],
                    leftUnitName,
                    rightUnitName,
                    searchIndex,
                    sliceFound,
                    itemAsLower,
                    domObj;

                domContentArr.spaceOut().capitalize()
                    .eachAttrs(function (value, i) {
                    if (mapped.hasOwnProperty(i)) {
                        domObj = mapped[i].get(0);
                        itemAsLower = value.toLowerCase();
                        sliceFound = domObj.textContent;
                        searchIndex = itemAsLower.search(sliceFound);
                        leftUnitName = itemAsLower.slice(0, searchIndex);
                        rightUnitName = itemAsLower.slice(searchIndex + sliceFound.length, itemAsLower.length);

                        if (leftUnitName.present()) {
                            composedArr.push(capitalizeIf(leftUnitName, content));
                        } else {
                            domObj.textContent = capitalizeIf(domObj
                                .textContent, content);
                        }

                        composedArr.push(domObj);

                        if (rightUnitName.present()) {
                            composedArr.push(rightUnitName);
                        }
                    } else {
                        composedArr.push(value);
                    }

                });

                return composedArr;
            },

            matches = function (textBoxContentArr, textContent) {
                var
                    i = 0,
                    count = 0,
                    searchIndex;

                while (true) {
                    searchIndex = textContent.search(textBoxContentArr[i]);
                    if (searchIndex !== -1) {
                        count += 1;
                    }

                    i += 1;
                    if (i === textBoxContentArr.length) {
                        break;
                    }

                }

                if (count === textBoxContentArr.length) {
                    return true;
                }

                return false;
            },

            map = function (textBoxObj, content, domContentArr) {
                var
                    textBoxContentArr = textBoxObj.val().toLowerCase().trim()
                        .split(" "),
                    tempI = 0,
                    tempArr,
                    searchIndex,
                    index,
                    mapped = {};

                if (matches(textBoxContentArr, content)) {
                    tempArr = domContentArr.copy();
                    textBoxContentArr.forEach(function (value) {
                        tempI = 0;

                        while (true) {
                            if (tempArr.length) {
                                searchIndex = tempArr[tempI].trim().search(value.trim());
                                if (searchIndex !== -1) {
                                    index = domContentArr
                                        .indexOf(tempArr[tempI]);
                                    mapped[index] = spanize(value);
                                    tempArr.delete(tempI);
                                } else {
                                    tempI += 1;
                                }
                            }

                            if (tempArr.length === tempI) {
                                break;
                            }
                        }
                    });

                    return mapped;
                }

                return false;
            },

            composeName = function (composedArr, domObj) {
                var
                    copy = $(domObj.cloneNode());

                composedArr.forEach(function (v) {
                    copy.append(v);
                });

                return copy;
            },

            capitalizeIf = function (word, content) {
                var
                    searchIndex;

                searchIndex = content.toLowerCase().trim().search(word);

                if (![searchIndex-1].present) {
                    return word.capitalize();
                } else {
                    return word;
                }
            },

            spanize = function (sliceFound) {
                var
                    spanizedSliceFound;

                spanizedSliceFound = $("");
                spanizedSliceFound
                    .addClass(MODULE_CSS + "-slice-found");

                spanizedSliceFound.text(sliceFound);

                return spanizedSliceFound;
            },

            resolveAnswer = function (obj, order) {
                var
                    found = [],
                    tagName,
                    mapped,
                    composed,
                    domContent,
                    domContentArr;

                if (!order) {
                    order = "asc";
                }
                order = order.toLocaleLowerCase();

                tagName = obj.prop("tagName").toLowerCase();

                if (tagName === "li") {
                    obj.sortByTextContent(order).each(function (i, domObj) {
                        domContent = domObj.textContent.toLowerCase().trim();
                        domContentArr = domContent.split(" ");
                        mapped = map(self, domContent, domContentArr);
                        if (mapped) {
                            composed = composeMatch(mapped, domContent,
                                domContentArr);
                            found.push(composeName(composed, domObj));
                        }

                    });
                }

                // caso necessário, usar recursividade para reenviar formatado
                // como li
                return $(found);
            },

            resolveAmountChar = function () {
                if (args.afterAmountChar) {
                    amountChar = args.afterAmountChar;
                } else {
                    amountChar = 1;
                }
            },

            unsetKeyUpEvent = function () {
                self.off("keyup");
            },

            offlineSearch = function () {
                unsetKeyUpEvent();
                resolveAmountChar();

                self.on("keyup", function (e) {

                    if (self.val()) {
                        if (self.val().length >= amountChar) {
                            callback(resolveAnswer(obj, args.order), e);
                        }
                    } else {
                        callback([], e);
                    }
                });
            },

            remoteSearch = function () {
                var
                    jqObj;

                unsetKeyUpEvent();
                resolveAmountChar();

                self.on("keyup", function (e) {
                    if (self.val().length === amountChar) {
                        args.remote.query(e, function (q) {
                            ajax({
                                url: args.remote.url,
                                params: q
                            })
                            .processing(function (xhr) {
                                if (args.remote.processing) {
                                    args.remote.processing(xhr, e);
                                }
                                $(e.target).on("keypress", function (e) {
                                    e.preventDefault();
                                });
                            })
                            .done(function (a) {
                                $(e.target).off("keypress");
                                jqObj = $(a).find("li");
                                callback(resolveAnswer(jqObj, args.order), e);
                                if (jqObj.length) {
                                    self.autocomplete(jqObj, args, callback);
                                }
                            });
                        });
                    }
                });
            };

        // init
        if (obj instanceof jQuery) {
            offlineSearch();
        } else if (obj instanceof Object) {
            callback = args;
            args = obj;

            remoteSearch();
        }
    };

})(jQuery);
<commit_msg>ajusta bug e refatora autocomplete<commit_after>//= require esphinx/support/jquery
//= require esphinx/support/ajax
//= require esphinx/support/string
//= require esphinx/support/array

"use strict";

var
    jQuery,
    Ajax;

const
    MODULE_CSS = "esphinx-ui";

(function ($) {

    $.prototype.autocomplete = function (obj, args, callback) {
        var
            self = $(this),
            ajax = Ajax,
            amountChar,

            composeMatch = function (mapped, content, domContentArr) {
                var
                    composedArr = [],
                    leftUnitName,
                    rightUnitName,
                    searchIndex,
                    sliceFound,
                    itemAsLower,
                    domObj;

                domContentArr.spaceOut().capitalize()
                    .eachAttrs(function (value, i) {
                    if (mapped.hasOwnProperty(i)) {
                        domObj = mapped[i].get(0);
                        itemAsLower = value.toLowerCase();
                        sliceFound = domObj.textContent;
                        searchIndex = itemAsLower.search(sliceFound);
                        leftUnitName = itemAsLower.slice(0, searchIndex);
                        rightUnitName = itemAsLower.slice(searchIndex + sliceFound.length, itemAsLower.length);

                        if (leftUnitName.present()) {
                            composedArr.push(capitalizeIf(leftUnitName, content));
                        } else {
                            domObj.textContent = capitalizeIf(domObj
                                .textContent, content);
                        }

                        composedArr.push(domObj);

                        if (rightUnitName.present()) {
                            composedArr.push(rightUnitName);
                        }
                    } else {
                        composedArr.push(value);
                    }

                });

                return composedArr;
            },

            matches = function (textBoxContentArr, textContent) {
                var
                    i = 0,
                    count = 0,
                    searchIndex;

                while (true) {
                    searchIndex = textContent.search(textBoxContentArr[i]);
                    if (searchIndex !== -1) {
                        count += 1;
                    }

                    i += 1;
                    if (i === textBoxContentArr.length) {
                        break;
                    }

                }

                if (count === textBoxContentArr.length) {
                    return true;
                }

                return false;
            },

            map = function (textBoxObj, content, domContentArr) {
                var
                    textBoxContentArr = textBoxObj.val().toLowerCase().trim()
                        .split(" "),
                    mapped = {};

                if (matches(textBoxContentArr, content)) {
                    textBoxContentArr.forEach(function (value) {
                        domContentArr.indexesOfSlice(value)
                            .forEach(function (i) {
                            mapped[i] = spanize(value);
                        });
                    });

                    return mapped;
                }

                return false;
            },

            composeName = function (composedArr, domObj) {
                var
                    copy = $(domObj.cloneNode());

                composedArr.forEach(function (v) {
                    copy.append(v);
                });

                return copy;
            },

            capitalizeIf = function (word, content) {
                var
                    searchIndex;

                searchIndex = content.toLowerCase().trim().search(word);

                if (![searchIndex-1].present) {
                    return word.capitalize();
                } else {
                    return word;
                }
            },

            spanize = function (sliceFound) {
                var
                    spanizedSliceFound;

                spanizedSliceFound = $("");
                spanizedSliceFound
                    .addClass(MODULE_CSS + "-slice-found");

                spanizedSliceFound.text(sliceFound);

                return spanizedSliceFound;
            },

            resolveAnswer = function (obj, order) {
                var
                    found = [],
                    tagName,
                    mapped,
                    composed,
                    domContent,
                    domContentArr;

                if (!order) {
                    order = "asc";
                }
                order = order.toLocaleLowerCase();

                tagName = obj.prop("tagName").toLowerCase();

                if (tagName === "li") {
                    obj.sortByTextContent(order).each(function (i, domObj) {
                        domContent = domObj.textContent.toLowerCase().trim();
                        domContentArr = domContent.split(" ");
                        mapped = map(self, domContent, domContentArr);
                        if (mapped) {
                            composed = composeMatch(mapped, domContent,
                                domContentArr);
                            found.push(composeName(composed, domObj));
                        }

                    });
                }

                // caso necessário, usar recursividade para reenviar formatado
                // como li
                return $(found);
            },

            resolveAmountChar = function () {
                if (args.afterAmountChar) {
                    amountChar = args.afterAmountChar;
                } else {
                    amountChar = 1;
                }
            },

            unsetKeyUpEvent = function () {
                self.off("keyup");
            },

            offlineSearch = function () {
                unsetKeyUpEvent();
                resolveAmountChar();

                self.on("keyup", function (e) {

                    if (self.val()) {
                        if (self.val().length >= amountChar) {
                            callback(resolveAnswer(obj, args.order), e);
                        }
                    } else {
                        callback([], e);
                    }
                });
            },

            remoteSearch = function () {
                var
                    jqObj;

                unsetKeyUpEvent();
                resolveAmountChar();

                self.on("keyup", function (e) {
                    if (self.val().length === amountChar) {
                        args.remote.query(e, function (q) {
                            ajax({
                                url: args.remote.url,
                                params: q
                            })
                            .processing(function (xhr) {
                                if (args.remote.processing) {
                                    args.remote.processing(xhr, e);
                                }
                                $(e.target).on("keypress", function (e) {
                                    e.preventDefault();
                                });
                            })
                            .done(function (a) {
                                $(e.target).off("keypress");
                                jqObj = $(a).find("li");
                                callback(resolveAnswer(jqObj, args.order), e);
                                if (jqObj.length) {
                                    self.autocomplete(jqObj, args, callback);
                                }
                            });
                        });
                    }
                });
            };

        // init
        if (obj instanceof jQuery) {
            offlineSearch();
        } else if (obj instanceof Object) {
            callback = args;
            args = obj;

            remoteSearch();
        }
    };

})(jQuery);
<|endoftext|>


<commit_before>'use strict';

// MODULES //

var isObjectLike = require( '@stdlib/assert/is-object-like' );
var hasOwnProp = require( '@stdlib/assert/has-own-property' );

// MAIN //

/**
* Returns a boolean indicating whether an object has a nested property.
*
* @private
* @param {ObjectLike} obj - input object
* @param {Array} props - list of properties defining a key path
* @returns {boolean} boolean indicating whether an object has a nested property
*/
function deepHasOwn( obj, props ) {
	var len = props.length;
	var i;
	for ( i = 0; i < len; i++ ) {
		if (
			isObjectLike( obj ) &&
			hasOwnProp( obj, props[ i ] )
		) {
			obj = obj[ props[ i ] ];
		} else {
			return false;
		}
	}
	return true;
} // end FUNCTION deepHasOwn()

// EXPORTS //

module.exports = deepHasOwn;
<commit_msg>Remove line breaks<commit_after>'use strict';

// MODULES //

var isObjectLike = require( '@stdlib/assert/is-object-like' );
var hasOwnProp = require( '@stdlib/assert/has-own-property' );

// MAIN //

/**
* Returns a boolean indicating whether an object has a nested property.
*
* @private
* @param {ObjectLike} obj - input object
* @param {Array} props - list of properties defining a key path
* @returns {boolean} boolean indicating whether an object has a nested property
*/
function deepHasOwn( obj, props ) {
	var len = props.length;
	var i;
	for ( i = 0; i < len; i++ ) {
		if ( isObjectLike( obj ) && hasOwnProp( obj, props[i] ) ) {
			obj = obj[ props[ i ] ];
		} else {
			return false;
		}
	}
	return true;
} // end FUNCTION deepHasOwn()

// EXPORTS //

module.exports = deepHasOwn;
<|endoftext|>


<commit_before>'use strict';

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );
var isObject = require( '@stdlib/utils/is-plain-object' );
var randu = require( '@stdlib/math/base/random/randu' );
var validate = require( './validate.js' );
var uniform0 = require( './_uniform.js' );

// MAIN //

/**
* Returns a pseudorandom number generator for generating uniformly distributed random numbers.
*
* @param {number} [a] - minimum support
* @param {number} [b] - maximum support
* @param {Options} [options] - function options
* @param {*} [options.seed] - pseudorandom number generator seed
* @throws {TypeError} a argument must be a number
* @throws {TypeError} b argument must be a number
* @throws {RangeError} `a` must be less than `b`
* @throws {TypeError} options must be an object
* @throws {TypeError} must provide valid options
* @returns {Function} pseudorandom number generator
*
* @example
* var uniform = factory( 0.0, 1.0 );
* var v = uniform();
* // returns <number>
*
* @example
* var uniform = factory( -3.0, -1.0, {
*     'seed': 297
* });
* var v = uniform();
* // returns <number>
*/
function factory() {
	var opts;
	var rand;
	var prng;
	var err;
	var a;
	var b;

	if ( arguments.length === 0 ) {
		rand = randu.factory();
	} else if ( arguments.length === 1 ) {
		opts = arguments[ 0 ];
		if ( !isObject( opts ) ) {
			throw new TypeError( 'invalid input argument. Options argument must be an object. Value: `'+opts+'`.' );
		}
		rand = randu.factory( opts );
	} else {
		a = arguments[ 0 ];
		b = arguments[ 1 ];
		err = validate( a, b );
		if ( err ) {
			throw err;
		}
		if ( arguments.length > 2 ) {
			opts = arguments[ 2 ];
			if ( !isObject( opts ) ) {
				throw new TypeError( 'invalid input argument. Options argument must be an object. Value: `'+opts+'`.' );
			}
			rand = randu.factory( opts );
		} else {
			rand = randu.factory();
		}
	}
	if ( a === void 0 ) {
		prng = uniform2;
	} else {
		prng = uniform1;
	}
	/**
	* Returns a uniformly distributed pseudorandom number with minimum support `a` and maximum support `b`.
	*
	* @private
	* @returns {number} pseudorandom number
	*
	* @example
	* var v = uniform1();
	* // returns <number>
	*/
	function uniform1() {
		return uniform0( rand, a, b );
	} // end FUNCTION uniform1()

	/**
	* Returns a uniformly distributed pseudorandom number with minimum support `a` and maximum support `b`.
	*
	* @private
	* @param {number} a - minimum support
	* @param {number} b - maximum support
	* @throws {TypeError} first argument must be a number
	* @throws {TypeError} second argument must be a number
	* @throws {RangeError} `a` must be less than `b`
	* @returns {number} pseudorandom number
	*
	* @example
	* var v = uniform2( 0.0, 1.0 );
	* // returns <number>
	*/
	function uniform2( a, b ) {
		var err = validate( a, b );
		if ( err ) {
			throw err;
		}
		return uniform0( rand, a, b );
	} // end FUNCTION uniform2()

	setReadOnly( prng, 'NAME', 'uniform' );
	setReadOnly( prng, 'SEED', rand.SEED );
	setReadOnly( prng, 'PRNG', rand );

	return prng;
} // end FUNCTION factory()

// EXPORTS //

module.exports = factory;
<commit_msg>Update param description<commit_after>'use strict';

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );
var isObject = require( '@stdlib/utils/is-plain-object' );
var randu = require( '@stdlib/math/base/random/randu' );
var validate = require( './validate.js' );
var uniform0 = require( './_uniform.js' );

// MAIN //

/**
* Returns a pseudorandom number generator for generating uniformly distributed random numbers.
*
* @param {number} [a] - minimum support
* @param {number} [b] - maximum support
* @param {Options} [options] - function options
* @param {*} [options.seed] - pseudorandom number generator seed
* @throws {TypeError} `a` must be a number
* @throws {TypeError} `b` must be a number
* @throws {RangeError} `a` must be less than `b`
* @throws {TypeError} options must be an object
* @throws {TypeError} must provide valid options
* @returns {Function} pseudorandom number generator
*
* @example
* var uniform = factory( 0.0, 1.0 );
* var v = uniform();
* // returns <number>
*
* @example
* var uniform = factory( -3.0, -1.0, {
*     'seed': 297
* });
* var v = uniform();
* // returns <number>
*/
function factory() {
	var opts;
	var rand;
	var prng;
	var err;
	var a;
	var b;

	if ( arguments.length === 0 ) {
		rand = randu.factory();
	} else if ( arguments.length === 1 ) {
		opts = arguments[ 0 ];
		if ( !isObject( opts ) ) {
			throw new TypeError( 'invalid input argument. Options argument must be an object. Value: `'+opts+'`.' );
		}
		rand = randu.factory( opts );
	} else {
		a = arguments[ 0 ];
		b = arguments[ 1 ];
		err = validate( a, b );
		if ( err ) {
			throw err;
		}
		if ( arguments.length > 2 ) {
			opts = arguments[ 2 ];
			if ( !isObject( opts ) ) {
				throw new TypeError( 'invalid input argument. Options argument must be an object. Value: `'+opts+'`.' );
			}
			rand = randu.factory( opts );
		} else {
			rand = randu.factory();
		}
	}
	if ( a === void 0 ) {
		prng = uniform2;
	} else {
		prng = uniform1;
	}
	/**
	* Returns a uniformly distributed pseudorandom number with minimum support `a` and maximum support `b`.
	*
	* @private
	* @returns {number} pseudorandom number
	*
	* @example
	* var v = uniform1();
	* // returns <number>
	*/
	function uniform1() {
		return uniform0( rand, a, b );
	} // end FUNCTION uniform1()

	/**
	* Returns a uniformly distributed pseudorandom number with minimum support `a` and maximum support `b`.
	*
	* @private
	* @param {number} a - minimum support
	* @param {number} b - maximum support
	* @throws {TypeError} first argument must be a number
	* @throws {TypeError} second argument must be a number
	* @throws {RangeError} `a` must be less than `b`
	* @returns {number} pseudorandom number
	*
	* @example
	* var v = uniform2( 0.0, 1.0 );
	* // returns <number>
	*/
	function uniform2( a, b ) {
		var err = validate( a, b );
		if ( err ) {
			throw err;
		}
		return uniform0( rand, a, b );
	} // end FUNCTION uniform2()

	setReadOnly( prng, 'NAME', 'uniform' );
	setReadOnly( prng, 'SEED', rand.SEED );
	setReadOnly( prng, 'PRNG', rand );

	return prng;
} // end FUNCTION factory()

// EXPORTS //

module.exports = factory;
<|endoftext|>