<commit_before>var PUPHPET = {};

/**
 * When element is clicked, update another element.
 *
 * This is good for:
 *
 * 1. User clicks element, user prompted for input, element properties + user input
 *      used to update another element
 * 2. Clicking one element checks another element if target is a radio or checkbox
 *
 * Loops through all data-* type attributes of element
 */
PUPHPET.updateOtherInput = function() {
    $(document).on('click', '.update-other-input', function(e){
        var $parent = $(this);

        $.each($(this).data(), function(key, value) {
            // jQuery changed "data-foo-bar" to "dataFooBar". Change them back.
            key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

            // Only work with data attributes that have "update-"
            if (key.search('update-') !== 0) {
                return;
            }

            key = key.replace('update-', '');

            var $target = $('#' + key);

            // If target element is not defined as #foo, maybe it is an input,name,value target
            if (!$target.length) {
                $target = $('input[name="' + key + '"][value="'+ value +'"]')
            }

            // If target is a radio element, check it, no need to uncheck in future
            if ($target.is(':radio')) {
                $target.prop('checked', true);

                return;
            }

            /**
             * If target is checkbox element, check if clicked element was checked or unchecked.
             *
             * If unchecked, do not update target. We only want to handle positive actions
             */
            if ($target.is(':checkbox') && $parent.is(':checked')) {
                $target.prop('checked', true);

                return;
            }

            $target.val(value);
        });
    });
};

PUPHPET.updateOtherInputSelect = function() {
    $(document).on('change', 'select.update-other-input', function(e){
        var $parent = $(this);

        $('select.update-other-input option:selected').each(function() {
            $.each($(this).data(), function(key, value) {
                // jQuery changed "data-foo-bar" to "dataFooBar". Change them back.
                key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

                // Only work with data attributes that have "update-"
                if (key.search('update-') !== 0) {
                    return;
                }

                key = key.replace('update-', '');

                var $target = $('#' + key);

                // If target element is not defined as #foo, maybe it is an input,name,value target
                if (!$target.length) {
                    $target = $('input[name="' + key + '"][value="'+ value +'"]')
                }

                // If target is a radio element, check it, no need to uncheck in future
                if ($target.is(':radio')) {
                    $target.prop('checked', true);

                    return;
                }

                /**
                 * If target is checkbox element, check if clicked element was checked or unchecked.
                 *
                 * If unchecked, do not update target. We only want to handle positive actions
                 */
                if ($target.is(':checkbox') && $parent.is(':checked')) {
                    $target.prop('checked', true);

                    return;
                }

                $target.val(value);
            });
        });
    });
};

/**
 * Run selectize.js on initial page load, and then re-run it whenever
 * new selectize-enabled elements are dynamically added to the DOM.
 *
 * @param $element
 */
PUPHPET.runSelectize = function($element) {
    // input or select elements; allows user to create their own tags
    var $selectTagsEditable = $('.tags, .select-tags-editable', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: function(input) {
            return {
                value: input,
                text: input
            }
        },
        maxItems: null,
        valueField: 'value',
        labelField: 'text',
        searchField: 'value'
    });

    // select elements; asks user for value of selected tags; cannot create own tags
    var $selectTagsUserInput = PUPHPET.selectizeTagsUserInput($element);

    // select single element; does not allow creating new tag
    var $selectTag = $('.select-tag', $element).selectize({
        persist: false,
        create: false
    });

    // select elements; does not allow creating new tags
    var $selectTags = $('.select-tags', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: false
    });

    PUPHPET._trackSelectize($selectTagsEditable);
    PUPHPET._trackSelectize($selectTagsUserInput);
    PUPHPET._trackSelectize($selectTag);
    PUPHPET._trackSelectize($selectTags);
};

/**
 * Active for select type elements.
 *
 * On user adding option, prompts user for data, and creates a new, matching
 * hidden element containing user input for easier handling of POSTed data.
 *
 * On user remove option, adds the removed element back to the available options
 * list and deletes the hidden element related to removed option.
 *
 * @param $element
 */
PUPHPET.selectizeTagsUserInput = function($element) {
    var $selectTagsUserInput = $('.select-tags-user-input', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: false,
        onItemAdd: function(value, $item) {
            var targetContainer     = '#' + $(this['$input'])[0].getAttribute('data-target-container');
            var targetNameStructure = $(this['$input'])[0].getAttribute('data-target-name');
            var elementName         = targetNameStructure + '[' + this.options[value].text + ']';

            var suffix = prompt('Enter Value:') || '0';
            var label  = this.options[value].text + ' = ' + suffix;
            var data   = $.extend({}, this.options[value], {
                text: label
            });

            // Append this user input as a new hidden element
            $('<input>').attr({
                type:  'hidden',
                name:  elementName,
                value: suffix
            }).appendTo(targetContainer);

            this.updateOption(value, data);
        },
        onItemRemove: function(value, $item) {
            var targetContainer     = '#' + $(this['$input'])[0].getAttribute('data-target-container');
            var targetNameStructure = $(this['$input'])[0].getAttribute('data-target-name');
            var elementName         = targetNameStructure + '[' + this.options[value].value + ']';

            $(targetContainer + ' input[name="' + elementName + '"]').remove();

            var data = $.extend({}, this.options[value], {
                text: value
            });

            this.updateOption(value, data);
        }
    });

    // Adds pre-selected option values to selectize field
    for (var i = 0; i < $selectTagsUserInput.length; i++) {
        var $selectElement = $selectTagsUserInput[i].selectize;
        var targetContainer = '#' + $selectTagsUserInput[i].getAttribute('data-target-container');
        var $selectedItems = $(targetContainer);

        if (!$selectedItems.length) {
            continue;
        }

        $selectedItems.children().each(function() {
            var optionName  = this.getAttribute('data-option-name');
            var optionValue = $(this).val();

            var label = $selectElement.options[optionName].text + ' = ' + optionValue;
            var data  = $.extend({}, $selectElement.options[optionName], {
                text: label
            });

            $selectElement.updateOption(optionName, data);
        });
    }

    return $selectTagsUserInput;
};

var selectizedObjects = [];

/**
 * Keep track of all initialized selectize.js elements
 *
 * @param $selectizeElements
 * @private
 */
PUPHPET._trackSelectize = function($selectizeElements) {
    for (var i = 0; i < $selectizeElements.length; i++) {
        selectizedObjects[$selectizeElements[i].id] = $selectizeElements[i];
    }
};

/**
 * Allows adding an item to a selectize.js element on user click
 */
PUPHPET.selectizeAddClickedToElement = function() {
    $(document).on('click', '.addClickedToSelectizeElement', function(e){
        var target    = this.getAttribute('data-target');
        var itemValue = this.getAttribute('data-value');
        var itemTitle = this.getAttribute('data-title') != null
            ? this.getAttribute('data-title')
            : $(this).text();

        if (!(target in selectizedObjects)) {
            return false;
        }

        var control = selectizedObjects[target].selectize;

        control.addOption({
            value: itemValue,
            text: itemTitle
        });
        control.addItem(itemValue);

        return false;
    });
};

/**
 * Adds repeatable containers based on clicked button's data-source-url value.
 *
 * Adds the response right before clicked element, and then re-runs selectize.js
 * on new elements.
 */
PUPHPET.addRepeatableElement = function() {
    $(document).on('click', 'button.addParentContainer', function(e){
        var sourceUrl = this.getAttribute('data-source-url');
        var buttonEle = $(this);

        $.ajax({
            url: sourceUrl,
            cache: false
        }).done(function(response) {
            var $row = $(response).insertBefore(buttonEle.closest('.row'));
            PUPHPET.runSelectize($row);
        });

    });
};

/**
 * Deletes repeatable containers based on button data id
 */
PUPHPET.delRepeatableElement = function() {
    $(document).on('click', 'button.deleteParentContainer', function(e){
        var parentId = this.getAttribute('data-parent-id');
        var $parentContainer = $('#' + parentId);

        $parentContainer.remove();

    });
};

/**
 * If elements are grouped into tabs, set all non-active tab elements as inactive.
 * Do not auto-disable tabs that belong to multi-selectable groups.
 *
 * This is useful so inactive choices do not get POSTed along with rest of form data.
 *
 * Runs on initial page load
 */
PUPHPET.disableInactiveTabElements = function() {
    $('ul.group-tabs li').each(function() {
        if ($(this).parent().hasClass('multi-select')) {
            return;
        }

        if ($(this).hasClass('active')) {
            return;
        }

        var $anchor = $(this).children()[0];
        var extensionId = $anchor.getAttribute('data-target-element');

        $('#' + extensionId).find('input, textarea, button, select').prop('disabled', true);
    });
};

/**
 * When switching tabs, disable all form elements in non-active tabs and
 * enable all form elements in newly active tab. Do not auto-disable
 * tabs that belong to multi-selectable groups
 */
PUPHPET.enableClickedTabElement = function() {
    $('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
        if ($(this).parent().parent().hasClass('multi-select')) {
            return;
        }

        var original = e.relatedTarget.getAttribute('data-target-element');
        var target   = e.target.getAttribute('data-target-element');

        $('#' + original).find('input, textarea, button, select').prop('disabled', true);
        $('#' + target).find('input, textarea, button, select').prop('disabled', false);
    });
};

/**
 * Display the information about PuPHPet's beloved contributors!
 */
PUPHPET.githubContributors = function() {
    $.get('https://api.github.com/repos/puphpet/puphpet/contributors', function(githubResponse) {
        $.post('/github-contributors', { contributors: githubResponse }, function(response) {
            $('#contributors').html(response);
        });
    });
};

/**
 * Allows user to drag and drop a pre-generated yaml file containing
 * their VMs configuration.
 */
PUPHPET.uploadConfig = function() {
    var dropzone = document.documentElement;
    var tid;

    dropzone.addEventListener('dragover', handleDragOver, false);
    dropzone.addEventListener('dragleave', handleDragLeave, false);
    dropzone.addEventListener('drop', handleFileSelect, false);

    function handleDragOver(e) {
        clearTimeout(tid);
        e.stopPropagation();
        e.preventDefault && e.preventDefault();
        e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.

        $('#drag-drop').fadeIn('slow');
    }

    function handleDragLeave(e) {
        tid = setTimeout(function () {
            e.stopPropagation();
            $('#drag-drop').fadeOut('slow');
        }, 300);
    }

    function handleFileSelect(e) {
        e.stopPropagation();
        e.preventDefault && e.preventDefault();

        $('#drag-drop').fadeOut('slow');

        var files = e.dataTransfer.files; // FileList object.

        // Only proceed when a single file is dropped
        if (files.length > 1 || !files.length) {
            return false;
        }

        var file = files[0];

        // Only allow yaml uploads
        if (file.name.split('.').pop().toLowerCase() !== 'yaml') {
            return false;
        }

        var reader = new FileReader();

        // Closure to capture the file information.
        reader.onload = (function (theFile) {
            return function (e) {
                submitForm(e.target.result);
            };
        })(file);

        // Read in the image file as a data URL.
        reader.readAsText(file);

        return false;
    }

    function submitForm(config) {
        if (!config.length) {
            return;
        }

        var form = $(
            '<form action="' + uploadConfigUrl + '" method="post">' +
                '<input type="hidden" name="config" value="' + config + '" />' +
            '</form>'
        );
        $('body').append(form);
        $(form).submit();
    }
};

/**
 * Controls how the static sidebar scrolls with user
 */
PUPHPET.sidebar = function() {
    $('#nav-sidebar').affix({
        offset: {
            top:  function () {
                return $('#top').height() + 70;
            },
            bottom: function () {
                return $('footer').height() + 140;
            }
        }
    });

    $('body').scrollspy({target: '#nav-sidebar', offset: 300});
};

/**
 * Toggles pills in main container by clicking sidebar links
 */
PUPHPET.sidebarPillToggle = function() {
    $(document).on('click', '#nav-sidebar ul li p a', function(e){
        var tagTarget = this.getAttribute('data-tab-target');
        var $container = $(this).closest('p');

        if (tagTarget != null) {
            $(tagTarget).tab('show');
        }

        return _toggleTabs($container, $(this).find('span.glyphicon')[0]);
    });

    $(document).on('click', 'ul.group-tabs a[data-toggle="tab"]', function(e){
        var id = '#' + this.id;
        var $menuTarget = $('#nav-sidebar ul li p a[data-tab-target="' + id + '"]');
        var $container = $menuTarget.closest('p');

        if ($menuTarget == null) {
            return;
        }

        return _toggleTabs($container, $menuTarget.find('span.glyphicon')[0]);
    });

    function _toggleTabs($container, toActive) {
        $container.siblings().find('span.glyphicon').removeClass('active');
        $(toActive).addClass('active');

        return true;
    }
};

PUPHPET.toggleMultiGroupedTab = function() {
    $(document).on('change', 'input.multiselect-grouped-tab', function(e) {
        var targetContainer     = '#' + this.getAttribute('data-tab-target');
        var targetContainerSpan = '#' + this.getAttribute('data-tab-target') + ' span';

        $(targetContainer).toggleClass('text-success');

        if($(this).is(':checked')){
            $(targetContainerSpan).removeClass('glyphicon-unchecked');
            $(targetContainerSpan).addClass('glyphicon-check');
        } else {
            $(targetContainerSpan).removeClass('glyphicon-check');
            $(targetContainerSpan).addClass('glyphicon-unchecked');
        }
    });
};

$(document).ready(function() {
    PUPHPET.updateOtherInput();
    PUPHPET.updateOtherInputSelect();
    PUPHPET.runSelectize(null);
    PUPHPET.selectizeAddClickedToElement();
    PUPHPET.addRepeatableElement();
    PUPHPET.delRepeatableElement();
    PUPHPET.disableInactiveTabElements();
    PUPHPET.enableClickedTabElement();
    PUPHPET.githubContributors();
    PUPHPET.uploadConfig();
    PUPHPET.sidebar();
    PUPHPET.sidebarPillToggle();
    PUPHPET.toggleMultiGroupedTab();
});
<commit_msg>Added docblock for PUPHPET.updateOtherInputSelect and PUPHPET.toggleMultiGroupedTab<commit_after>var PUPHPET = {};

/**
 * When element is clicked, update another element.
 *
 * This is good for:
 *
 * 1. User clicks element, user prompted for input, element properties + user input
 *      used to update another element
 * 2. Clicking one element checks another element if target is a radio or checkbox
 *
 * Loops through all data-* type attributes of element
 */
PUPHPET.updateOtherInput = function() {
    $(document).on('click', '.update-other-input', function(e){
        var $parent = $(this);

        $.each($(this).data(), function(key, value) {
            // jQuery changed "data-foo-bar" to "dataFooBar". Change them back.
            key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

            // Only work with data attributes that have "update-"
            if (key.search('update-') !== 0) {
                return;
            }

            key = key.replace('update-', '');

            var $target = $('#' + key);

            // If target element is not defined as #foo, maybe it is an input,name,value target
            if (!$target.length) {
                $target = $('input[name="' + key + '"][value="'+ value +'"]')
            }

            // If target is a radio element, check it, no need to uncheck in future
            if ($target.is(':radio')) {
                $target.prop('checked', true);

                return;
            }

            /**
             * If target is checkbox element, check if clicked element was checked or unchecked.
             *
             * If unchecked, do not update target. We only want to handle positive actions
             */
            if ($target.is(':checkbox') && $parent.is(':checked')) {
                $target.prop('checked', true);

                return;
            }

            $target.val(value);
        });
    });
};

/**
 * When a <select> element is chosen, check another element.
 *
 * For example, choosing Debian 6 will auto-select PHP 5.4, as 5.5 is not supported.
 */
PUPHPET.updateOtherInputSelect = function() {
    $(document).on('change', 'select.update-other-input', function(e){
        var $parent = $(this);

        $('select.update-other-input option:selected').each(function() {
            $.each($(this).data(), function(key, value) {
                // jQuery changed "data-foo-bar" to "dataFooBar". Change them back.
                key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

                // Only work with data attributes that have "update-"
                if (key.search('update-') !== 0) {
                    return;
                }

                key = key.replace('update-', '');

                var $target = $('#' + key);

                // If target element is not defined as #foo, maybe it is an input,name,value target
                if (!$target.length) {
                    $target = $('input[name="' + key + '"][value="'+ value +'"]')
                }

                // If target is a radio element, check it, no need to uncheck in future
                if ($target.is(':radio')) {
                    $target.prop('checked', true);

                    return;
                }

                /**
                 * If target is checkbox element, check if clicked element was checked or unchecked.
                 *
                 * If unchecked, do not update target. We only want to handle positive actions
                 */
                if ($target.is(':checkbox') && $parent.is(':checked')) {
                    $target.prop('checked', true);

                    return;
                }

                $target.val(value);
            });
        });
    });
};

/**
 * Run selectize.js on initial page load, and then re-run it whenever
 * new selectize-enabled elements are dynamically added to the DOM.
 *
 * @param $element
 */
PUPHPET.runSelectize = function($element) {
    // input or select elements; allows user to create their own tags
    var $selectTagsEditable = $('.tags, .select-tags-editable', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: function(input) {
            return {
                value: input,
                text: input
            }
        },
        maxItems: null,
        valueField: 'value',
        labelField: 'text',
        searchField: 'value'
    });

    // select elements; asks user for value of selected tags; cannot create own tags
    var $selectTagsUserInput = PUPHPET.selectizeTagsUserInput($element);

    // select single element; does not allow creating new tag
    var $selectTag = $('.select-tag', $element).selectize({
        persist: false,
        create: false
    });

    // select elements; does not allow creating new tags
    var $selectTags = $('.select-tags', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: false
    });

    PUPHPET._trackSelectize($selectTagsEditable);
    PUPHPET._trackSelectize($selectTagsUserInput);
    PUPHPET._trackSelectize($selectTag);
    PUPHPET._trackSelectize($selectTags);
};

/**
 * Active for select type elements.
 *
 * On user adding option, prompts user for data, and creates a new, matching
 * hidden element containing user input for easier handling of POSTed data.
 *
 * On user remove option, adds the removed element back to the available options
 * list and deletes the hidden element related to removed option.
 *
 * @param $element
 */
PUPHPET.selectizeTagsUserInput = function($element) {
    var $selectTagsUserInput = $('.select-tags-user-input', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: false,
        onItemAdd: function(value, $item) {
            var targetContainer     = '#' + $(this['$input'])[0].getAttribute('data-target-container');
            var targetNameStructure = $(this['$input'])[0].getAttribute('data-target-name');
            var elementName         = targetNameStructure + '[' + this.options[value].text + ']';

            var suffix = prompt('Enter Value:') || '0';
            var label  = this.options[value].text + ' = ' + suffix;
            var data   = $.extend({}, this.options[value], {
                text: label
            });

            // Append this user input as a new hidden element
            $('<input>').attr({
                type:  'hidden',
                name:  elementName,
                value: suffix
            }).appendTo(targetContainer);

            this.updateOption(value, data);
        },
        onItemRemove: function(value, $item) {
            var targetContainer     = '#' + $(this['$input'])[0].getAttribute('data-target-container');
            var targetNameStructure = $(this['$input'])[0].getAttribute('data-target-name');
            var elementName         = targetNameStructure + '[' + this.options[value].value + ']';

            $(targetContainer + ' input[name="' + elementName + '"]').remove();

            var data = $.extend({}, this.options[value], {
                text: value
            });

            this.updateOption(value, data);
        }
    });

    // Adds pre-selected option values to selectize field
    for (var i = 0; i < $selectTagsUserInput.length; i++) {
        var $selectElement = $selectTagsUserInput[i].selectize;
        var targetContainer = '#' + $selectTagsUserInput[i].getAttribute('data-target-container');
        var $selectedItems = $(targetContainer);

        if (!$selectedItems.length) {
            continue;
        }

        $selectedItems.children().each(function() {
            var optionName  = this.getAttribute('data-option-name');
            var optionValue = $(this).val();

            var label = $selectElement.options[optionName].text + ' = ' + optionValue;
            var data  = $.extend({}, $selectElement.options[optionName], {
                text: label
            });

            $selectElement.updateOption(optionName, data);
        });
    }

    return $selectTagsUserInput;
};

var selectizedObjects = [];

/**
 * Keep track of all initialized selectize.js elements
 *
 * @param $selectizeElements
 * @private
 */
PUPHPET._trackSelectize = function($selectizeElements) {
    for (var i = 0; i < $selectizeElements.length; i++) {
        selectizedObjects[$selectizeElements[i].id] = $selectizeElements[i];
    }
};

/**
 * Allows adding an item to a selectize.js element on user click
 */
PUPHPET.selectizeAddClickedToElement = function() {
    $(document).on('click', '.addClickedToSelectizeElement', function(e){
        var target    = this.getAttribute('data-target');
        var itemValue = this.getAttribute('data-value');
        var itemTitle = this.getAttribute('data-title') != null
            ? this.getAttribute('data-title')
            : $(this).text();

        if (!(target in selectizedObjects)) {
            return false;
        }

        var control = selectizedObjects[target].selectize;

        control.addOption({
            value: itemValue,
            text: itemTitle
        });
        control.addItem(itemValue);

        return false;
    });
};

/**
 * Adds repeatable containers based on clicked button's data-source-url value.
 *
 * Adds the response right before clicked element, and then re-runs selectize.js
 * on new elements.
 */
PUPHPET.addRepeatableElement = function() {
    $(document).on('click', 'button.addParentContainer', function(e){
        var sourceUrl = this.getAttribute('data-source-url');
        var buttonEle = $(this);

        $.ajax({
            url: sourceUrl,
            cache: false
        }).done(function(response) {
            var $row = $(response).insertBefore(buttonEle.closest('.row'));
            PUPHPET.runSelectize($row);
        });

    });
};

/**
 * Deletes repeatable containers based on button data id
 */
PUPHPET.delRepeatableElement = function() {
    $(document).on('click', 'button.deleteParentContainer', function(e){
        var parentId = this.getAttribute('data-parent-id');
        var $parentContainer = $('#' + parentId);

        $parentContainer.remove();

    });
};

/**
 * If elements are grouped into tabs, set all non-active tab elements as inactive.
 * Do not auto-disable tabs that belong to multi-selectable groups.
 *
 * This is useful so inactive choices do not get POSTed along with rest of form data.
 *
 * Runs on initial page load
 */
PUPHPET.disableInactiveTabElements = function() {
    $('ul.group-tabs li').each(function() {
        if ($(this).parent().hasClass('multi-select')) {
            return;
        }

        if ($(this).hasClass('active')) {
            return;
        }

        var $anchor = $(this).children()[0];
        var extensionId = $anchor.getAttribute('data-target-element');

        $('#' + extensionId).find('input, textarea, button, select').prop('disabled', true);
    });
};

/**
 * When switching tabs, disable all form elements in non-active tabs and
 * enable all form elements in newly active tab. Do not auto-disable
 * tabs that belong to multi-selectable groups
 */
PUPHPET.enableClickedTabElement = function() {
    $('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
        if ($(this).parent().parent().hasClass('multi-select')) {
            return;
        }

        var original = e.relatedTarget.getAttribute('data-target-element');
        var target   = e.target.getAttribute('data-target-element');

        $('#' + original).find('input, textarea, button, select').prop('disabled', true);
        $('#' + target).find('input, textarea, button, select').prop('disabled', false);
    });
};

/**
 * Display the information about PuPHPet's beloved contributors!
 */
PUPHPET.githubContributors = function() {
    $.get('https://api.github.com/repos/puphpet/puphpet/contributors', function(githubResponse) {
        $.post('/github-contributors', { contributors: githubResponse }, function(response) {
            $('#contributors').html(response);
        });
    });
};

/**
 * Allows user to drag and drop a pre-generated yaml file containing
 * their VMs configuration.
 */
PUPHPET.uploadConfig = function() {
    var dropzone = document.documentElement;
    var tid;

    dropzone.addEventListener('dragover', handleDragOver, false);
    dropzone.addEventListener('dragleave', handleDragLeave, false);
    dropzone.addEventListener('drop', handleFileSelect, false);

    function handleDragOver(e) {
        clearTimeout(tid);
        e.stopPropagation();
        e.preventDefault && e.preventDefault();
        e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.

        $('#drag-drop').fadeIn('slow');
    }

    function handleDragLeave(e) {
        tid = setTimeout(function () {
            e.stopPropagation();
            $('#drag-drop').fadeOut('slow');
        }, 300);
    }

    function handleFileSelect(e) {
        e.stopPropagation();
        e.preventDefault && e.preventDefault();

        $('#drag-drop').fadeOut('slow');

        var files = e.dataTransfer.files; // FileList object.

        // Only proceed when a single file is dropped
        if (files.length > 1 || !files.length) {
            return false;
        }

        var file = files[0];

        // Only allow yaml uploads
        if (file.name.split('.').pop().toLowerCase() !== 'yaml') {
            return false;
        }

        var reader = new FileReader();

        // Closure to capture the file information.
        reader.onload = (function (theFile) {
            return function (e) {
                submitForm(e.target.result);
            };
        })(file);

        // Read in the image file as a data URL.
        reader.readAsText(file);

        return false;
    }

    function submitForm(config) {
        if (!config.length) {
            return;
        }

        var form = $(
            '<form action="' + uploadConfigUrl + '" method="post">' +
                '<input type="hidden" name="config" value="' + config + '" />' +
            '</form>'
        );
        $('body').append(form);
        $(form).submit();
    }
};

/**
 * Controls how the static sidebar scrolls with user
 */
PUPHPET.sidebar = function() {
    $('#nav-sidebar').affix({
        offset: {
            top:  function () {
                return $('#top').height() + 70;
            },
            bottom: function () {
                return $('footer').height() + 140;
            }
        }
    });

    $('body').scrollspy({target: '#nav-sidebar', offset: 300});
};

/**
 * Toggles pills in main container by clicking sidebar links
 */
PUPHPET.sidebarPillToggle = function() {
    $(document).on('click', '#nav-sidebar ul li p a', function(e){
        var tagTarget = this.getAttribute('data-tab-target');
        var $container = $(this).closest('p');

        if (tagTarget != null) {
            $(tagTarget).tab('show');
        }

        return _toggleTabs($container, $(this).find('span.glyphicon')[0]);
    });

    $(document).on('click', 'ul.group-tabs a[data-toggle="tab"]', function(e){
        var id = '#' + this.id;
        var $menuTarget = $('#nav-sidebar ul li p a[data-tab-target="' + id + '"]');
        var $container = $menuTarget.closest('p');

        if ($menuTarget == null) {
            return;
        }

        return _toggleTabs($container, $menuTarget.find('span.glyphicon')[0]);
    });

    function _toggleTabs($container, toActive) {
        $container.siblings().find('span.glyphicon').removeClass('active');
        $(toActive).addClass('active');

        return true;
    }
};

/**
 * Multi-group allows focusing on a tabbed element and hiding others, without
 * disabling hidden elements.
 *
 * Useful for grouping related extensions that are not exclusive of each other:
 *      Xdebug, Xhprof, Drush
 */
PUPHPET.toggleMultiGroupedTab = function() {
    $(document).on('change', 'input.multiselect-grouped-tab', function(e) {
        var targetContainer     = '#' + this.getAttribute('data-tab-target');
        var targetContainerSpan = '#' + this.getAttribute('data-tab-target') + ' span';

        $(targetContainer).toggleClass('text-success');

        if($(this).is(':checked')){
            $(targetContainerSpan).removeClass('glyphicon-unchecked');
            $(targetContainerSpan).addClass('glyphicon-check');
        } else {
            $(targetContainerSpan).removeClass('glyphicon-check');
            $(targetContainerSpan).addClass('glyphicon-unchecked');
        }
    });
};

$(document).ready(function() {
    PUPHPET.updateOtherInput();
    PUPHPET.updateOtherInputSelect();
    PUPHPET.runSelectize(null);
    PUPHPET.selectizeAddClickedToElement();
    PUPHPET.addRepeatableElement();
    PUPHPET.delRepeatableElement();
    PUPHPET.disableInactiveTabElements();
    PUPHPET.enableClickedTabElement();
    PUPHPET.githubContributors();
    PUPHPET.uploadConfig();
    PUPHPET.sidebar();
    PUPHPET.sidebarPillToggle();
    PUPHPET.toggleMultiGroupedTab();
});
<|endoftext|>


<commit_before>import monq from 'monq';

import userWorkers from './users/userWorkers';
import leaveWorkers from './leave/leaveRequestWorkers';

class Worker {
    get QUEUE_EVENTS() {
        return {
            USER: 'queue_user',
            LEAVE: 'queue_leave_request'
        };
    }

    get PROCESS_EVENTS() {
        return {
            USER: 'process_new_user',
            CHECK_FOR_USERS: 'process_check_for_users',
            PASSWORD_RESET: 'process_password_reset',
            NEW_LEAVE: 'process_new_leave_request',
            APPROVED_LEAVE: 'process_approved_leave_request',
            REJECTED_LEAVE: 'process_rejected_leave_request'
        };
    }

    constructor() {
        if (!this.client) {
            this.client = monq(process.env.MONGO_HOST);
            this.workers = [];
        }
    }

    async start() {
        this.registerWorkers();
        this.startWorkers();

        // in case users collection is empty,
        // a default admin@admin/admin user will be created
        this.queueCheckForUsers();
    }

    queueNewUser(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.USER);

        queue.enqueue(this.PROCESS_EVENTS.USER, data, () => {});
    }

    queueCheckForUsers() {
        const queue = this.client.queue(this.QUEUE_EVENTS.USER);

        queue.enqueue(this.PROCESS_EVENTS.CHECK_FOR_USERS, {}, () => {});
    }

    queuePasswordReset(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.USER);

        queue.enqueue(this.PROCESS_EVENTS.PASSWORD_RESET, data, () => {});
    }

    queueNewLeaveRequest(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.LEAVE);

        queue.enqueue(this.PROCESS_EVENTS.NEW_LEAVE, data, () => {});
    }

    queueApprovedLeaveRequest(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.LEAVE);

        queue.enqueue(this.PROCESS_EVENTS.APPROVED_LEAVE, data, () => {});
    }

    queueRejectedLeaveRequest(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.LEAVE);

        queue.enqueue(this.PROCESS_EVENTS.REJECTED_LEAVE, data, () => {});
    }

    registerWorkers() {
        this.registerUserWorkers();
        this.registerLeaveRequestWorkers();
    }

    registerUserWorkers() {
        const worker = this.client.worker([this.QUEUE_EVENTS.USER]);

        worker.register({
            process_new_user: userWorkers.handleNewUsers,
            process_password_reset: userWorkers.handlePasswordReset,
            process_check_for_users: userWorkers.createDefaultUser
        });

        this.workers.push(worker);
    }

    registerLeaveRequestWorkers() {
        const worker = this.client.worker([this.QUEUE_EVENTS.LEAVE]);

        worker.register({
            process_new_leave_request: leaveWorkers.handleNewLeaveRequest,
            process_approved_leave_request: leaveWorkers.handleApprovedLeaveRequest,
            process_rejected_leave_request: leaveWorkers.handleRejectedLeaveRequest
        });

        this.workers.push(worker);
    }

    startWorkers() {
        this.workers.forEach(worker => worker.start());
    }
}

export default new Worker;
<commit_msg>not required to be async<commit_after>import monq from 'monq';

import userWorkers from './users/userWorkers';
import leaveWorkers from './leave/leaveRequestWorkers';

class Worker {
    get QUEUE_EVENTS() {
        return {
            USER: 'queue_user',
            LEAVE: 'queue_leave_request'
        };
    }

    get PROCESS_EVENTS() {
        return {
            USER: 'process_new_user',
            CHECK_FOR_USERS: 'process_check_for_users',
            PASSWORD_RESET: 'process_password_reset',
            NEW_LEAVE: 'process_new_leave_request',
            APPROVED_LEAVE: 'process_approved_leave_request',
            REJECTED_LEAVE: 'process_rejected_leave_request'
        };
    }

    constructor() {
        if (!this.client) {
            this.client = monq(process.env.MONGO_HOST);
            this.workers = [];
        }
    }

    start() {
        this.registerWorkers();
        this.startWorkers();

        // in case users collection is empty,
        // a default admin@admin/admin user will be created
        this.queueCheckForUsers();
    }

    queueNewUser(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.USER);

        queue.enqueue(this.PROCESS_EVENTS.USER, data, () => {});
    }

    queueCheckForUsers() {
        const queue = this.client.queue(this.QUEUE_EVENTS.USER);

        queue.enqueue(this.PROCESS_EVENTS.CHECK_FOR_USERS, {}, () => {});
    }

    queuePasswordReset(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.USER);

        queue.enqueue(this.PROCESS_EVENTS.PASSWORD_RESET, data, () => {});
    }

    queueNewLeaveRequest(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.LEAVE);

        queue.enqueue(this.PROCESS_EVENTS.NEW_LEAVE, data, () => {});
    }

    queueApprovedLeaveRequest(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.LEAVE);

        queue.enqueue(this.PROCESS_EVENTS.APPROVED_LEAVE, data, () => {});
    }

    queueRejectedLeaveRequest(data) {
        const queue = this.client.queue(this.QUEUE_EVENTS.LEAVE);

        queue.enqueue(this.PROCESS_EVENTS.REJECTED_LEAVE, data, () => {});
    }

    registerWorkers() {
        this.registerUserWorkers();
        this.registerLeaveRequestWorkers();
    }

    registerUserWorkers() {
        const worker = this.client.worker([this.QUEUE_EVENTS.USER]);

        worker.register({
            process_new_user: userWorkers.handleNewUsers,
            process_password_reset: userWorkers.handlePasswordReset,
            process_check_for_users: userWorkers.createDefaultUser
        });

        this.workers.push(worker);
    }

    registerLeaveRequestWorkers() {
        const worker = this.client.worker([this.QUEUE_EVENTS.LEAVE]);

        worker.register({
            process_new_leave_request: leaveWorkers.handleNewLeaveRequest,
            process_approved_leave_request: leaveWorkers.handleApprovedLeaveRequest,
            process_rejected_leave_request: leaveWorkers.handleRejectedLeaveRequest
        });

        this.workers.push(worker);
    }

    startWorkers() {
        this.workers.forEach(worker => worker.start());
    }
}

export default new Worker;
<|endoftext|>


<commit_before>function initializeOnDomReady(){
    // populate quicktext table
    loadQuicktexts();

    // Extension settings
    function boolFieldHandler(field){
        var input = document.getElementById(field);
        if (input){
            var autocompleteEnabled = Settings.get(field);
            if (autocompleteEnabled) {
                input.setAttribute("checked", "checked");
            }
            input.addEventListener("change", function(e){
                Settings.set(field, this.checked);
            });
        }
    }
    boolFieldHandler("autocompleteEnabled");
    boolFieldHandler("tabcompleteEnabled");

    // add new quick text
    var newButton = document.querySelector("#new-quicktext-button")
    if (newButton){
        newButton.addEventListener("click", function(e){
            this.classList.add("hide");
            // clean the form
            document.querySelector("#qt-id").value = "";
            document.querySelector("#qt-title").value = "";
            document.querySelector("#qt-subject").value = "";
            document.querySelector("#qt-shortcut").value = "";
            document.querySelector("#qt-tags").value = "";
            document.querySelector("#qt-body").value = "";

            var formDiv = document.querySelector("#quicktext-form");
            formDiv.classList.add("show")
            document.querySelector("#qt-title").focus();
        });
    }

    // submit add/edit quicktext
    var submitButton = document.querySelector("#qt-submit");
    if (submitButton){
        submitButton.addEventListener("click", function(e){
            e.preventDefault();
            var id = document.querySelector("#qt-id");
            var title = document.querySelector("#qt-title");
            var subject = document.querySelector("#qt-subject");
            var shortcut = document.querySelector("#qt-shortcut");
            var tags = document.querySelector("#qt-tags");
            var body = document.querySelector("#qt-body");
            var quicktexts = Settings.get('quicktexts');

            if (!title.value){
                alert("Please enter a title");
                title.focus();
                return false;
            }
            if (!body.value){
                alert("Please enter a quicktext");
                body.focus();
                return false;
            }
            if (id.value !== ''){
                var newQuicktexts = [];
                _.each(quicktexts, function(qt){
                    if (qt.id == id.value){
                        qt.title = title.value;
                        qt.subject= subject.value;
                        qt.shortcut = shortcut.value;
                        qt.tags = tags.value;
                        qt.body = body.value;
                    }
                    newQuicktexts.push(qt);
                });
                quicktexts = newQuicktexts;
            } else {
                quicktext = {
                    'title': title.value,
                    'subject': subject.value,
                    'shortcut': shortcut.value,
                    'tags': tags.value,
                    'body': body.value
                };
                quicktext.id = get_id(quicktext);
                quicktexts.push(quicktext);
            }
            Settings.set('quicktexts', quicktexts);
            //syncQuicktexts();

            var dialog = document.querySelector("#dialog-container");
            if (dialog){
                window.close();
            }
            loadQuicktexts();
            document.querySelector("#new-quicktext-button").classList.remove("hide");
            document.querySelector("#quicktext-form").classList.remove("show");
        });
    }

    // search quicktext
    var searchEl = document.querySelector("#search");
    if (searchEl){
        searchEl.addEventListener("keyup", function(){
            // search in title, shortcut and body
            var self = this;
            var query = self.value.toLowerCase();
            var quicktexts = Settings.get('quicktexts');
            function show(id){
                var row = document.querySelector("#qt-" + id);
                row.classList.remove("hide");
            };

            _.each(quicktexts, function(qt){
                if (qt.title.toLowerCase().indexOf(query) !== -1) {return show(qt.id);}
                if (qt.shortcut.toLowerCase().indexOf(query) !== -1) {return show(qt.id);}
                if (qt.tags.toLowerCase().indexOf(query) !== -1) {return show(qt.id);}
                if (qt.body.toLowerCase().indexOf(query) !== -1) {return show(qt.id);}
                document.querySelector("#qt-" + qt.id).classList.add("hide");
            });
        });
    }

    var syncButton = document.querySelector("#sync-button");
    if (syncButton){

        syncButton.addEventListener("click", function(){
            if (syncButton.innerHTML == 'Stop syncronization'){
                syncButton.innerHTML = 'Start syncronization';
                Settings.set('syncEnabled', false);
            } else {
                syncButton.innerHTML = 'Stop syncronization';
                Settings.set('syncEnabled', true);
            }
            // If we are logged in the we should try getting the quicktexts from the website directly
            try {
                syncQuicktexts();
            } catch (e) {
                if (e.message.indexOf("Invalid JSON") !== -1) {
                    // this probably means that the user is not logged in
                    window.location =  Settings.get('baseURL') + "registration"
                } else {
                    throw e;
                }
            }
        });
    }

    var sharingButton = document.querySelector("#sharing-button");
    if (sharingButton){
        sharingButton.addEventListener("click", function(){
            window.location = Settings.get('baseURL') + 'quicktexts';
        });
    }

    var deleteAllButton = document.querySelector("#delete-all-button");
    if (deleteAllButton){
        deleteAllButton.addEventListener("click", function(){
            var r = confirm("Are you sure you want to delete all Quicktexts?\n\nNote: they will NOT be deleted from the syncronization server.");
            if (r === true){
                Settings.set("quicktexts", []);
                loadQuicktexts();
            }
        });
    }
}

function syncQuicktexts(){
    if (Settings.get("syncEnabled") === false){
        return;
    }
    // first we get the quicktexts from the sync server
    result = ajax.getJSON(Settings.get("apiBaseURL") + "sync");
    if (result.status == 0){
        quicktexts = []; // the list that we'll populate
        existing_quicktexts = Settings.get("quicktexts");
        new_ids = [];

        // add all remote quicktexts to the list
        _.each(result.quicktexts, function(remote_qt) {
            remote_qt.id = get_id(remote_qt);// give a id to the remote qt
            new_ids.push(remote_qt.id);
            quicktexts.push(remote_qt);
        });

        // if we don't have the local quicktexts in the remote quicktexts
        // we add them to the list
        _.each(existing_quicktexts, function(local_qt) {
            if (new_ids.indexOf(local_qt.id) === -1){
                quicktexts.push(local_qt)
            }
        })
        Settings.set("quicktexts", quicktexts);
    }
    // now we try to send the quicktexts back to the server for syncronization
    ajax.post(Settings.get("apiBaseURL") + "sync", Settings.get("quicktexts"), function(res){
        //console.log(res);
    });
    loadQuicktexts();
}
// get the unique id for the quicktext in question
function get_id(qt){
    return hex_md5(qt.title + qt.subject + qt.shortcut + qt.tags + qt.body);
}

// delete quicktexts
function deleteClicked(e){
    var self = this;
    var id = self.getAttribute("rel");
    var quicktexts = Settings.get('quicktexts');
    Settings.set('quicktexts', _.filter(quicktexts, function(qt){
        return qt.id != id;
    }));
    document.querySelector("#qt-" + id).remove();
}

function editClicked(e){
    var self = this;
    var id = self.getAttribute("rel");
    var quicktexts = Settings.get('quicktexts');
    // hide add new quicktext
    document.querySelector("#new-quicktext-button").classList.add("hide");
    _.each(quicktexts, function(qt){
        if (qt.id == id) {
            var formDiv = document.querySelector("#quicktext-form");
            formDiv.classList.add('show');
            document.querySelector("#qt-id").value = qt.id;
            document.querySelector("#qt-title").value = qt.title;
            document.querySelector("#qt-subject").value = qt.subject;
            document.querySelector("#qt-tags").value = qt.tags;
            document.querySelector("#qt-shortcut").value = qt.shortcut;
            document.querySelector("#qt-body").value = qt.body;
            return;
        }
    });
    document.querySelector("#qt-title").focus();
}

function loadQuicktexts(){
    var table = document.querySelector("#quicktexts-table");
    if (!table){
        return;
    }
    var quicktexts = Settings.get('quicktexts');
    var isPopup = $('body').hasClass('ispopup');
    var qtTemplate = '<% _.each(quicktexts, function(qt) { %>\
    <tr id="qt-<%= qt.id %>" key="qt-<%= qt.key %>">\
        <%= qt.title %>	\
        <%= qt.subject %>	\
        <%= qt.shortcut %>	\
        <%= qt.tags %>	\

<%= qt.body %>
	\
        <a href="#" class="qt-edit" rel="<%= qt.id %>">Edit	\
        <a href="#" class="qt-delete" rel="<%= qt.id %>">Delete	\

\
    <% }); %>';

    filtered = [];
    _.each(quicktexts, function(qt){
        qt.title= _.str.truncate(qt.title, 30);
        qt.subject = _.str.truncate(qt.subject, 30);
        qt.shortcut = _.str.truncate(qt.shortcut, 15);
        qt.tags = _.str.truncate(qt.tags, 20);
        qt.body = _.str.truncate(qt.body, 100);
        filtered.push(qt);
    });
    var compiled = _.template(qtTemplate, {'quicktexts': filtered});
    $("#quicktexts-table tbody").html(compiled);
    if (isPopup){
        $("#quicktexts-table tbody tr").click(function(){
            // A quicktext item was clicked. Insert it into the compose area
            var id = $(this).attr("id").split("qt-")[1];
            insertQuicktext(id);
        });
    }

    // Attach event handlers to delete actions
    var deleteActions = document.querySelectorAll(".qt-delete")
    _.each(deleteActions, function(el){
        el.addEventListener("click", deleteClicked);
    });
    var editActions = document.querySelectorAll(".qt-edit")
    _.each(editActions, function(el){
        el.addEventListener("click", editClicked);
    });
}

window.addEventListener("DOMContentLoaded", initializeOnDomReady);
<commit_msg>Added titles for longer fields<commit_after>function initializeOnDomReady(){
    // populate quicktext table
    loadQuicktexts();

    // Extension settings
    function boolFieldHandler(field){
        var input = document.getElementById(field);
        if (input){
            var autocompleteEnabled = Settings.get(field);
            if (autocompleteEnabled) {
                input.setAttribute("checked", "checked");
            }
            input.addEventListener("change", function(e){
                Settings.set(field, this.checked);
            });
        }
    }
    boolFieldHandler("autocompleteEnabled");
    boolFieldHandler("tabcompleteEnabled");

    // add new quick text
    var newButton = document.querySelector("#new-quicktext-button")
    if (newButton){
        newButton.addEventListener("click", function(e){
            this.classList.add("hide");
            // clean the form
            document.querySelector("#qt-id").value = "";
            document.querySelector("#qt-title").value = "";
            document.querySelector("#qt-subject").value = "";
            document.querySelector("#qt-shortcut").value = "";
            document.querySelector("#qt-tags").value = "";
            document.querySelector("#qt-body").value = "";

            var formDiv = document.querySelector("#quicktext-form");
            formDiv.classList.add("show")
            document.querySelector("#qt-title").focus();
        });
    }

    // submit add/edit quicktext
    var submitButton = document.querySelector("#qt-submit");
    if (submitButton){
        submitButton.addEventListener("click", function(e){
            e.preventDefault();
            var id = document.querySelector("#qt-id");
            var title = document.querySelector("#qt-title");
            var subject = document.querySelector("#qt-subject");
            var shortcut = document.querySelector("#qt-shortcut");
            var tags = document.querySelector("#qt-tags");
            var body = document.querySelector("#qt-body");
            var quicktexts = Settings.get('quicktexts');

            if (!title.value){
                alert("Please enter a title");
                title.focus();
                return false;
            }
            if (!body.value){
                alert("Please enter a quicktext");
                body.focus();
                return false;
            }
            if (id.value !== ''){
                var newQuicktexts = [];
                _.each(quicktexts, function(qt){
                    if (qt.id == id.value){
                        qt.title = title.value;
                        qt.subject= subject.value;
                        qt.shortcut = shortcut.value;
                        qt.tags = tags.value;
                        qt.body = body.value;
                    }
                    newQuicktexts.push(qt);
                });
                quicktexts = newQuicktexts;
            } else {
                quicktext = {
                    'title': title.value,
                    'subject': subject.value,
                    'shortcut': shortcut.value,
                    'tags': tags.value,
                    'body': body.value
                };
                quicktext.id = get_id(quicktext);
                quicktexts.push(quicktext);
            }
            Settings.set('quicktexts', quicktexts);
            //syncQuicktexts();

            var dialog = document.querySelector("#dialog-container");
            if (dialog){
                window.close();
            }
            loadQuicktexts();
            document.querySelector("#new-quicktext-button").classList.remove("hide");
            document.querySelector("#quicktext-form").classList.remove("show");
        });
    }

    // search quicktext
    var searchEl = document.querySelector("#search");
    if (searchEl){
        searchEl.addEventListener("keyup", function(){
            // search in title, shortcut and body
            var self = this;
            var query = self.value.toLowerCase();
            var quicktexts = Settings.get('quicktexts');
            function show(id){
                var row = document.querySelector("#qt-" + id);
                row.classList.remove("hide");
            };

            _.each(quicktexts, function(qt){
                if (qt.title.toLowerCase().indexOf(query) !== -1) {return show(qt.id);}
                if (qt.shortcut.toLowerCase().indexOf(query) !== -1) {return show(qt.id);}
                if (qt.tags.toLowerCase().indexOf(query) !== -1) {return show(qt.id);}
                if (qt.body.toLowerCase().indexOf(query) !== -1) {return show(qt.id);}
                document.querySelector("#qt-" + qt.id).classList.add("hide");
            });
        });
    }

    var syncButton = document.querySelector("#sync-button");
    if (syncButton){

        syncButton.addEventListener("click", function(){
            if (syncButton.innerHTML == 'Stop syncronization'){
                syncButton.innerHTML = 'Start syncronization';
                Settings.set('syncEnabled', false);
            } else {
                syncButton.innerHTML = 'Stop syncronization';
                Settings.set('syncEnabled', true);
            }
            // If we are logged in the we should try getting the quicktexts from the website directly
            try {
                syncQuicktexts();
            } catch (e) {
                if (e.message.indexOf("Invalid JSON") !== -1) {
                    // this probably means that the user is not logged in
                    window.location =  Settings.get('baseURL') + "registration"
                } else {
                    throw e;
                }
            }
        });
    }

    var sharingButton = document.querySelector("#sharing-button");
    if (sharingButton){
        sharingButton.addEventListener("click", function(){
            window.location = Settings.get('baseURL') + 'quicktexts';
        });
    }

    var deleteAllButton = document.querySelector("#delete-all-button");
    if (deleteAllButton){
        deleteAllButton.addEventListener("click", function(){
            var r = confirm("Are you sure you want to delete all Quicktexts?\n\nNote: they will NOT be deleted from the syncronization server.");
            if (r === true){
                Settings.set("quicktexts", []);
                loadQuicktexts();
            }
        });
    }
}

function syncQuicktexts(){
    if (Settings.get("syncEnabled") === false){
        return;
    }
    // first we get the quicktexts from the sync server
    result = ajax.getJSON(Settings.get("apiBaseURL") + "sync");
    if (result.status == 0){
        quicktexts = []; // the list that we'll populate
        existing_quicktexts = Settings.get("quicktexts");
        new_ids = [];

        // add all remote quicktexts to the list
        _.each(result.quicktexts, function(remote_qt) {
            remote_qt.id = get_id(remote_qt);// give a id to the remote qt
            new_ids.push(remote_qt.id);
            quicktexts.push(remote_qt);
        });

        // if we don't have the local quicktexts in the remote quicktexts
        // we add them to the list
        _.each(existing_quicktexts, function(local_qt) {
            if (new_ids.indexOf(local_qt.id) === -1){
                quicktexts.push(local_qt)
            }
        })
        Settings.set("quicktexts", quicktexts);
    }
    // now we try to send the quicktexts back to the server for syncronization
    ajax.post(Settings.get("apiBaseURL") + "sync", Settings.get("quicktexts"), function(res){
        //console.log(res);
    });
    loadQuicktexts();
}
// get the unique id for the quicktext in question
function get_id(qt){
    return hex_md5(qt.title + qt.subject + qt.shortcut + qt.tags + qt.body);
}

// delete quicktexts
function deleteClicked(e){
    var self = this;
    var id = self.getAttribute("rel");
    var quicktexts = Settings.get('quicktexts');
    Settings.set('quicktexts', _.filter(quicktexts, function(qt){
        return qt.id != id;
    }));
    document.querySelector("#qt-" + id).remove();
}

function editClicked(e){
    var self = this;
    var id = self.getAttribute("rel");
    var quicktexts = Settings.get('quicktexts');
    // hide add new quicktext
    document.querySelector("#new-quicktext-button").classList.add("hide");
    _.each(quicktexts, function(qt){
        if (qt.id == id) {
            var formDiv = document.querySelector("#quicktext-form");
            formDiv.classList.add('show');
            document.querySelector("#qt-id").value = qt.id;
            document.querySelector("#qt-title").value = qt.title;
            document.querySelector("#qt-subject").value = qt.subject;
            document.querySelector("#qt-tags").value = qt.tags;
            document.querySelector("#qt-shortcut").value = qt.shortcut;
            document.querySelector("#qt-body").value = qt.body;
            return;
        }
    });
    document.querySelector("#qt-title").focus();
}

function loadQuicktexts(){
    var table = document.querySelector("#quicktexts-table");
    if (!table){
        return;
    }
    var quicktexts = Settings.get('quicktexts');
    var isPopup = $('body').hasClass('ispopup');
    var qtTemplate = '<% _.each(quicktexts, function(qt) { %>\
    <tr id="qt-<%= qt.id %>" key="qt-<%= qt.key %>">\
        <td class="title-cell" title="<%= qt.title %>"><%= qt.title %>	\
        <%= qt.subject %>	\
        <%= qt.shortcut %>	\
        <%= qt.tags %>	\
        <td class="body-cell" title="<%= qt.body %>">
<%= qt.body_truncated %>
	\
        <a href="#" class="qt-edit" rel="<%= qt.id %>">Edit	\
        <a href="#" class="qt-delete" rel="<%= qt.id %>">Delete	\

\
    <% }); %>';

    filtered = [];
    _.each(quicktexts, function(qt){
        qt.title= _.str.truncate(qt.title, 30);
        qt.subject = _.str.truncate(qt.subject, 30);
        qt.shortcut = _.str.truncate(qt.shortcut, 15);
        qt.tags = _.str.truncate(qt.tags, 20);
        qt.body_truncated = _.str.truncate(qt.body, 100);
        filtered.push(qt);
    });
    var compiled = _.template(qtTemplate, {'quicktexts': filtered});
    $("#quicktexts-table tbody").html(compiled);
    if (isPopup){
        $("#quicktexts-table tbody tr").click(function(){
            // A quicktext item was clicked. Insert it into the compose area
            var id = $(this).attr("id").split("qt-")[1];
            insertQuicktext(id);
        });
    }

    // Attach event handlers to delete actions
    var deleteActions = document.querySelectorAll(".qt-delete")
    _.each(deleteActions, function(el){
        el.addEventListener("click", deleteClicked);
    });
    var editActions = document.querySelectorAll(".qt-edit")
    _.each(editActions, function(el){
        el.addEventListener("click", editClicked);
    });
}

window.addEventListener("DOMContentLoaded", initializeOnDomReady);
<|endoftext|>


<commit_before>// Generic forms user page (Part A)

'use strict';

var incompleteFormNav = require('./components/incomplete-form-nav')
  , _                 = require('mano').i18n.bind('Registration')
  , errorMsg          = require('./_business-process-error-info').errorMsg;

exports._parent = require('./business-process-base');

exports['step-guide'] = { class: { 'step-form': true } };

exports.step = function () {
	exports._formsHeading(this);

	insert(errorMsg(this));

	div({ class: ['disabler-range', _if(not(eq(this.businessProcess._guideProgress, 1)),
				'disabler-active')], id: 'forms-disabler-range' },
		div({ class: 'disabler' }),
		exports._formstabs(this),
		div({ id: 'forms-sections-content' }));

	insert(_if(and(eq(this.businessProcess._guideProgress, 1),
		eq(this.businessProcess.dataForms._progress, 1)),
		div({ class: 'user-next-step-button' },
			a({ href: '/documents/' }, _("Continue to next step"))),
		_if(gt(this.businessProcess.dataForms._progress, 0), section({ class: 'section-warning' },
			incompleteFormNav(this.businessProcess.dataForms.applicable)))
		));

};

exports._formsHeading = function (context) {
	return div(
		{ class: 'capital-first' },
		div("1"),
		div(
			h1(_("Fill the form")),
			p(_("Answer all mandatory questions."))
		)
	);
};

exports._formstabs = function (context) {
	context.businessProcess.dataForms.applicable.forEach(function (section) {
		return a({ href: "/forms/" + section.label.replace(/ /g, '').toLowerCase(), class: 'tab' },
			section._label);
	});
};
<commit_msg>Add section container for forms tabs<commit_after>// Generic forms user page (Part A)

'use strict';

var incompleteFormNav = require('./components/incomplete-form-nav')
  , _                 = require('mano').i18n.bind('Registration')
  , errorMsg          = require('./_business-process-error-info').errorMsg;

exports._parent = require('./business-process-base');

exports['step-guide'] = { class: { 'step-form': true } };

exports.step = function () {
	exports._formsHeading(this);

	insert(errorMsg(this));

	div({ class: ['disabler-range', _if(not(eq(this.businessProcess._guideProgress, 1)),
				'disabler-active')], id: 'forms-disabler-range' },
		div({ class: 'disabler' }),
		section({ class: 'forms-tab-nav' },
			exports._formstabs(this)),
		div({ id: 'forms-sections-content' }));

	insert(_if(and(eq(this.businessProcess._guideProgress, 1),
		eq(this.businessProcess.dataForms._progress, 1)),
		div({ class: 'user-next-step-button' },
			a({ href: '/documents/' }, _("Continue to next step"))),
		_if(gt(this.businessProcess.dataForms._progress, 0), section({ class: 'section-warning' },
			incompleteFormNav(this.businessProcess.dataForms.applicable)))
		));

};

exports._formsHeading = function (context) {
	return div(
		{ class: 'capital-first' },
		div("1"),
		div(
			h1(_("Fill the form")),
			p(_("Answer all mandatory questions."))
		)
	);
};

exports._formstabs = function (context) {
	context.businessProcess.dataForms.applicable.forEach(function (section) {
		return a({ href: "/forms/" + section.label.replace(/ /g, '').toLowerCase(), class: 'tab' },
			section._label);
	});
};
<|endoftext|>


<commit_before>/**
 * @format
 */

import {
  fetchArrayBuffer,
  DataProviderBase,
  S2GeometryProvider,
} from '../../node_modules/mapillary-js/dist/mapillary.module.js';
import {isReconstructionData} from '../util/types.js';
import {DataConverter} from './DataConverter.js';

function* generator(items, map) {
  for (var item of items) {
    yield map(item);
  }
}

export class OpensfmDataProvider extends DataProviderBase {
  constructor(options, geometry) {
    super(geometry ?? new S2GeometryProvider(16));
    this._convert = new DataConverter(options);
    this._inventedImageBuffer = null;
    this._rawData = {};
    this._data = {
      cells: {},
      clusters: {},
      meshes: {},
      images: {},
      sequences: {},
    };

    const endpoint = options.endpoint;
    this._inventedImagePromise = !options.imagePath
      ? this._inventImagePromise().then(buffer => {
          this._inventedImageBuffer = buffer;
          this._inventedImagePromise = null;
          return buffer;
        })
      : null;

    const recs = options.reconstructionPaths;
    const urls = !!recs ? recs.map(path => new URL(path, endpoint).href) : [];

    if (urls.length > 0) {
      this._fetchReconstructions(urls);
    }
  }

  get data() {
    return this._data;
  }

  get loaded() {
    return !this._load && Object.keys(this._rawData).length > 0;
  }

  get loading() {
    return !!this._load;
  }

  get rawData() {
    return this._rawData;
  }

  add(file) {
    if (!this.loaded) {
      throw new Error('Not loaded');
    }
    return new Promise((resolve, reject) => {
      try {
        const created = this._addFile(file);

        // MJS data provider API data create event
        // (required for internal MJS cell updates).
        const type = 'datacreate';
        const event = {
          cellIds: Object.keys(created.data.cells),
          target: this,
          type,
        };
        this.fire(type, event);

        // OpenSfM specific event for simplified
        // cluster data handling.
        const opensfmType = 'opensfmdatacreate';
        const opensfmEvent = {
          data: created.data,
          rawData: created.rawData,
          target: this,
          type: opensfmType,
        };
        this.fire(opensfmType, opensfmEvent);
        resolve();
      } catch (error) {
        reject(error);
      }
    });
  }

  load(file, inventImages) {
    return new Promise((resolve, reject) => {
      if (this.loading) {
        reject(new Error('Already loading'));
        return;
      }

      try {
        this._initialize(file, inventImages);
        resolve();
      } catch (error) {
        reject(error);
      }
    });
  }

  getCoreImages(cellId) {
    return this._getDataLoadedPromise().then(() => {
      const cell = {};
      cell[cellId] = cellId in this._data.cells ? this._data.cells[cellId] : {};
      const images = this._convert.coreImageContract(cellId, cell[cellId]);
      return images;
    });
  }

  getCluster(clusterId) {
    return this._getDataLoadedPromise().then(
      () => this._data.clusters[clusterId],
    );
  }

  getSpatialImages(imageIds) {
    return this._getDataLoadedPromise()
      .then(() => this._filterItems(imageIds, this._data.images))
      .then(images => this._convert.entContracts(images));
  }

  getImages(imageIds) {
    return this._getDataLoadedPromise()
      .then(() => this._filterItems(imageIds, this._data.images))
      .then(images => this._convert.entContracts(images));
  }

  getImageBuffer(url, cancellation) {
    if (this._isInventingImages()) {
      return this._getInventedImageBuffer();
    }

    return fetchArrayBuffer(url, cancellation).catch(() =>
      this._inventImagePromise(),
    );
  }

  getMesh(url) {
    return this._getDataLoadedPromise().then(() => this._data.meshes[url]);
  }

  getSequence(sequenceId) {
    return this._getDataLoadedPromise().then(
      () => this._data.sequences[sequenceId],
    );
  }

  setUserToken() {
    /*noop*/
  }

  _addFile(file) {
    const cells = {};
    const clusters = {};
    const meshes = {};
    const images = {};
    const sequences = {};
    const rawData = {};
    let clusterIndex = Object.keys(this._data.clusters).length;
    for (const cluster of file.data) {
      const clusterId = this._inventClusterId(clusterIndex++);
      rawData[clusterId] = {
        cluster,
        id: clusterId,
        file: {
          url: file.url,
          children: file.children,
          name: file.name,
        },
      };
      const reference = this._convert.reference(cluster.reference_lla);
      clusters[clusterId] = this._convert.cluster(
        cluster,
        clusterId,
        reference,
      );

      const shots = cluster.shots;
      const cameras = cluster.cameras;
      for (const shotId in shots) {
        if (!shots.hasOwnProperty(shotId)) {
          continue;
        }

        const shot = shots[shotId];
        const camera = cameras[shot.camera];
        const image = this._convert.image(
          shotId,
          clusterId,
          shot,
          camera,
          reference,
        );
        const uniqueId = image.id;
        if (this._hasImage(uniqueId)) {
          throw new Error(`Image ${uniqueId} already exists`);
        }
        images[uniqueId] = image;

        const sequenceId = uniqueId;
        if (!(sequenceId in sequences)) {
          sequences[sequenceId] = this._convert.sequence(sequenceId);
        }

        meshes[uniqueId] = this._convert.mesh(shot);

        const cellId = this._geometry.lngLatToCellId(image.geometry);
        if (!(cellId in cells)) {
          cells[cellId] = {};
        }
        cells[cellId][uniqueId] = image;
      }
    }

    const data = {cells, clusters, images, meshes, sequences};
    this._appendData(data);
    Object.assign(this._rawData, rawData);

    return {data, rawData};
  }

  _appendData(data) {
    const tData = this._data;
    for (const cellId of Object.keys(data.cells)) {
      const cell = data.cells[cellId];
      if (!(cellId in tData.cells)) {
        tData.cells[cellId] = cell;
      } else {
        Object.assign(tData.cells[cellId], cell);
      }
    }
    Object.assign(tData.clusters, data.clusters);
    Object.assign(tData.images, data.images);
    Object.assign(tData.meshes, data.meshes);
    Object.assign(tData.sequences, data.sequences);
  }

  _fetchReconstruction(url) {
    return fetch(url, {
      method: 'GET',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    }).then(r => {
      if (r.ok) {
        return r.json();
      } else {
        throw new Error('Failed to fetch reconstruction');
      }
    });
  }

  _fetchReconstructions(urls) {
    const fetchDataGenerator = generator(
      urls,
      this._fetchReconstruction.bind(this),
    );

    this._loadData(fetchDataGenerator);
  }

  _filterItems(ids, items) {
    const filtered = {};
    for (const id of ids) {
      filtered[id] = items[id];
    }
    return filtered;
  }

  _getDataLoadedPromise() {
    return this._load != null ? this._load : Promise.resolve();
  }

  _getInventedImageBuffer() {
    if (this._inventedImageBuffer) {
      return Promise.resolve(this._inventedImageBuffer);
    }
    return this._inventedImagePromise;
  }

  _hasImage(imageId) {
    return imageId in this._data.images;
  }

  _initialize(file, inventImages) {
    if (this.loaded) {
      reject(new Error('Already loaded'));
      return;
    }

    if (!isReconstructionData(file.data)) {
      reject(new Error('Not a reconstruction'));
      return;
    }

    this._addFile(file);
    if (inventImages) {
      this._inventedImagePromise = this._inventImagePromise().then(buffer => {
        this._inventedImageBuffer = buffer;
        this._inventedImagePromise = null;
        return buffer;
      });
    }
    this.fire('load', {target: this});
  }

  _inventClusterId(index) {
    return `cluster_${index}`;
  }

  async _inventImagePromise() {
    const canvas = document.createElement('canvas');
    canvas.width = canvas.height = 1;

    return new Promise(resolve => {
      canvas.toBlob(async blob => {
        const buffer = await blob.arrayBuffer();
        resolve(buffer);
      }, 'image/jpeg');
    });
  }

  _isInventingImages() {
    return !!this._inventedImageBuffer || !!this._inventedImagePromise;
  }

  _loadData(iterator) {
    this._load = new Promise((resolve, reject) => {
      const loaders = [];
      for (const item of iterator) {
        loaders.push(
          item
            .then(async data => {
              if (this.loaded) {
                return this.add({data});
              }

              await this._initialize({data});
              this._load = null;
              resolve();
              return Promise.resolve();
            })
            .catch(error => {
              console.error(error);
              return Promise.reject(error);
            }),
        );
      }

      Promise.allSettled(loaders).then(responses => {
        let succeeded = false;
        for (const response of responses) {
          if (response.status === 'fulfilled') {
            succeeded = true;
            break;
          }
        }

        if (!succeeded) {
          this._load = null;
          reject(new Error('All file loads failed.'));
        }
      });
    });

    return this._load;
  }
}
<commit_msg>fix: correct format for files loaded from url location<commit_after>/**
 * @format
 */

import {
  fetchArrayBuffer,
  DataProviderBase,
  S2GeometryProvider,
} from '../../node_modules/mapillary-js/dist/mapillary.module.js';
import {isReconstructionData} from '../util/types.js';
import {DataConverter} from './DataConverter.js';

function* generator(items, map) {
  for (var item of items) {
    yield map(item);
  }
}

export class OpensfmDataProvider extends DataProviderBase {
  constructor(options, geometry) {
    super(geometry ?? new S2GeometryProvider(16));
    this._convert = new DataConverter(options);
    this._inventedImageBuffer = null;
    this._rawData = {};
    this._data = {
      cells: {},
      clusters: {},
      meshes: {},
      images: {},
      sequences: {},
    };

    const endpoint = options.endpoint;
    this._inventedImagePromise = !options.imagePath
      ? this._inventImagePromise().then(buffer => {
          this._inventedImageBuffer = buffer;
          this._inventedImagePromise = null;
          return buffer;
        })
      : null;

    const recs = options.reconstructionPaths;
    const urls = !!recs ? recs.map(path => new URL(path, endpoint).href) : [];

    if (urls.length > 0) {
      this._fetchReconstructions(urls);
    }
  }

  get data() {
    return this._data;
  }

  get loaded() {
    return !this._load && Object.keys(this._rawData).length > 0;
  }

  get loading() {
    return !!this._load;
  }

  get rawData() {
    return this._rawData;
  }

  add(file) {
    if (!this.loaded) {
      throw new Error('Not loaded');
    }
    return new Promise((resolve, reject) => {
      try {
        const created = this._addFile(file);

        // MJS data provider API data create event
        // (required for internal MJS cell updates).
        const type = 'datacreate';
        const event = {
          cellIds: Object.keys(created.data.cells),
          target: this,
          type,
        };
        this.fire(type, event);

        // OpenSfM specific event for simplified
        // cluster data handling.
        const opensfmType = 'opensfmdatacreate';
        const opensfmEvent = {
          data: created.data,
          rawData: created.rawData,
          target: this,
          type: opensfmType,
        };
        this.fire(opensfmType, opensfmEvent);
        resolve();
      } catch (error) {
        reject(error);
      }
    });
  }

  load(file, inventImages) {
    return new Promise((resolve, reject) => {
      if (this.loading) {
        reject(new Error('Already loading'));
        return;
      }

      try {
        this._initialize(file, inventImages);
        resolve();
      } catch (error) {
        reject(error);
      }
    });
  }

  getCoreImages(cellId) {
    return this._getDataLoadedPromise().then(() => {
      const cell = {};
      cell[cellId] = cellId in this._data.cells ? this._data.cells[cellId] : {};
      const images = this._convert.coreImageContract(cellId, cell[cellId]);
      return images;
    });
  }

  getCluster(clusterId) {
    return this._getDataLoadedPromise().then(
      () => this._data.clusters[clusterId],
    );
  }

  getSpatialImages(imageIds) {
    return this._getDataLoadedPromise()
      .then(() => this._filterItems(imageIds, this._data.images))
      .then(images => this._convert.entContracts(images));
  }

  getImages(imageIds) {
    return this._getDataLoadedPromise()
      .then(() => this._filterItems(imageIds, this._data.images))
      .then(images => this._convert.entContracts(images));
  }

  getImageBuffer(url, cancellation) {
    if (this._isInventingImages()) {
      return this._getInventedImageBuffer();
    }

    return fetchArrayBuffer(url, cancellation).catch(() =>
      this._inventImagePromise(),
    );
  }

  getMesh(url) {
    return this._getDataLoadedPromise().then(() => this._data.meshes[url]);
  }

  getSequence(sequenceId) {
    return this._getDataLoadedPromise().then(
      () => this._data.sequences[sequenceId],
    );
  }

  setUserToken() {
    /*noop*/
  }

  _addFile(file) {
    const cells = {};
    const clusters = {};
    const meshes = {};
    const images = {};
    const sequences = {};
    const rawData = {};
    let clusterIndex = Object.keys(this._data.clusters).length;
    for (const cluster of file.data) {
      const clusterId = this._inventClusterId(clusterIndex++);
      rawData[clusterId] = {
        cluster,
        id: clusterId,
        file: {
          url: file.url,
          children: file.children,
          name: file.name,
        },
      };
      const reference = this._convert.reference(cluster.reference_lla);
      clusters[clusterId] = this._convert.cluster(
        cluster,
        clusterId,
        reference,
      );

      const shots = cluster.shots;
      const cameras = cluster.cameras;
      for (const shotId in shots) {
        if (!shots.hasOwnProperty(shotId)) {
          continue;
        }

        const shot = shots[shotId];
        const camera = cameras[shot.camera];
        const image = this._convert.image(
          shotId,
          clusterId,
          shot,
          camera,
          reference,
        );
        const uniqueId = image.id;
        if (this._hasImage(uniqueId)) {
          throw new Error(`Image ${uniqueId} already exists`);
        }
        images[uniqueId] = image;

        const sequenceId = uniqueId;
        if (!(sequenceId in sequences)) {
          sequences[sequenceId] = this._convert.sequence(sequenceId);
        }

        meshes[uniqueId] = this._convert.mesh(shot);

        const cellId = this._geometry.lngLatToCellId(image.geometry);
        if (!(cellId in cells)) {
          cells[cellId] = {};
        }
        cells[cellId][uniqueId] = image;
      }
    }

    const data = {cells, clusters, images, meshes, sequences};
    this._appendData(data);
    Object.assign(this._rawData, rawData);

    return {data, rawData};
  }

  _appendData(data) {
    const tData = this._data;
    for (const cellId of Object.keys(data.cells)) {
      const cell = data.cells[cellId];
      if (!(cellId in tData.cells)) {
        tData.cells[cellId] = cell;
      } else {
        Object.assign(tData.cells[cellId], cell);
      }
    }
    Object.assign(tData.clusters, data.clusters);
    Object.assign(tData.images, data.images);
    Object.assign(tData.meshes, data.meshes);
    Object.assign(tData.sequences, data.sequences);
  }

  _fetchReconstruction(url) {
    return fetch(url, {
      method: 'GET',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    }).then(r => {
      if (r.ok) {
        return r.json();
      } else {
        throw new Error('Failed to fetch reconstruction');
      }
    });
  }

  _fetchReconstructions(urls) {
    const fetchDataGenerator = generator(urls, url => {
      return {
        url,
        fetch: this._fetchReconstruction(url),
      };
    });

    this._loadData(fetchDataGenerator);
  }

  _filterItems(ids, items) {
    const filtered = {};
    for (const id of ids) {
      filtered[id] = items[id];
    }
    return filtered;
  }

  _getDataLoadedPromise() {
    return this._load != null ? this._load : Promise.resolve();
  }

  _getInventedImageBuffer() {
    if (this._inventedImageBuffer) {
      return Promise.resolve(this._inventedImageBuffer);
    }
    return this._inventedImagePromise;
  }

  _hasImage(imageId) {
    return imageId in this._data.images;
  }

  _initialize(file, inventImages) {
    if (this.loaded) {
      reject(new Error('Already loaded'));
      return;
    }

    if (!isReconstructionData(file.data)) {
      reject(new Error('Not a reconstruction'));
      return;
    }

    this._addFile(file);
    if (inventImages) {
      this._inventedImagePromise = this._inventImagePromise().then(buffer => {
        this._inventedImageBuffer = buffer;
        this._inventedImagePromise = null;
        return buffer;
      });
    }
    this.fire('load', {target: this});
  }

  _inventClusterId(index) {
    return `cluster_${index}`;
  }

  async _inventImagePromise() {
    const canvas = document.createElement('canvas');
    canvas.width = canvas.height = 1;

    return new Promise(resolve => {
      canvas.toBlob(async blob => {
        const buffer = await blob.arrayBuffer();
        resolve(buffer);
      }, 'image/jpeg');
    });
  }

  _isInventingImages() {
    return !!this._inventedImageBuffer || !!this._inventedImagePromise;
  }

  _loadData(iterator) {
    this._load = new Promise((resolve, reject) => {
      const loaders = [];
      for (const item of iterator) {
        loaders.push(
          item.fetch
            .then(async data => {
              const file = {
                children: [],
                data,
                name: item.url,
                url: item.url,
              };

              if (this.loaded) {
                return this.add(file);
              }

              await this._initialize(file);
              this._load = null;
              resolve();
              return Promise.resolve();
            })
            .catch(error => {
              console.error(error);
              return Promise.reject(error);
            }),
        );
      }

      Promise.allSettled(loaders).then(responses => {
        let succeeded = false;
        for (const response of responses) {
          if (response.status === 'fulfilled') {
            succeeded = true;
            break;
          }
        }

        if (!succeeded) {
          this._load = null;
          reject(new Error('All file loads failed.'));
        }
      });
    });

    return this._load;
  }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

const HELP =
"bible --help" +
"\nusage: bible [options]" +
"\n" +
"\nRead the Holy Bible using a NPM application." +
"\n" +
"\noptions:" +
"\n  --v, --version          print the version" +
"\n  --lang, --language      set the Bible language" +
"\n  --ref, --reference      the verse references that you want to read" +
"\n  --onlyVerses            prevent showing additional output" +
"\n  --s, --search           get the verses that match to the string or regular expression provided" +
"\n  --rc, --resultColor     set the result color when searching something" +
"\n  --help                  print this output" +
"\n" +
"\nDocumentation can be found at https://github.com/BibleJS/BibleApp";

// Dependencies
var Bible = require ("bible.js")
  , Couleurs = require ("couleurs")
  , Yargs = require('yargs').usage(HELP)
  , argv = Yargs.argv
  , language = argv.lang || argv.language
  , reference = argv.reference || argv.ref
  , search = argv.s || argv.search
  , searchResultColor = (argv.rc || argv.resultColor || "255, 0, 0").split(",")
  ;

// Parse result color
for (var i = 0; i < 3; ++i) {

    if (!searchResultColor[i]) {
        return console.log ("Invalid result color. Please provide a string in this format: 'r, g, b'. Example: --resultColor '255, 0, 0'");
    }

    searchResultColor[i] = parseInt(searchResultColor[i])
}

// Show version
if (argv.v || argv.version) {
    return console.log("Bible.js v" + require ("./package").version);
}

// Show help
if (argv.help || !language || (!reference && !search)) {
    return console.log(Yargs.help());
}

// Output
if (!argv.onlyVerses) {
    if (reference) {
        console.log("You are reading " + reference);
    }

    if (search) {
        console.log("You are searching " + search);
    }

    console.log("----------------");
}

/**
 * printOutput
 * This function is called when the response from the
 * search or get request comes
 *
 * @param {Error} err An error that ocured while fetching the verses.
 * @param {Array} verses The verses array that was returned by bible.js module.
 * @return {undefined} Returns undefined
 */
function printOutput (err, verses) {

    // Handle error
    if (err) {
        console.log("Error: ", err);
        return;
    }

    // No verses
    if (!verses || !verses.length) {
        console.log("Verses not found");
    }

    // Output each verse
    for (var i in verses) {

        // get the current verse and its reference
        var cVerse = verses[i]
          , cVerseRef = cVerse.bookname + " " + cVerse.chapter + ":" + cVerse.verse + " | "
          ;

        if (search) {
            cVerse.text = cVerse.text.replace (
                new RegExp (search, "g")
              , search.rgb(searchResultColor)
            );
        }

        console.log((!argv.onlyVerses ? cVerseRef : "") + cVerse.text);
    }

    // Output
    if (!argv.onlyVerses) {
        console.log("----------------");
    }
}

// Get the verses
if (reference) {
    (new Bible({language: language})).get(reference, printOutput);
}

// Search verses
if (search) {
    (new Bible({language: language})).search(search, printOutput);
}
<commit_msg>Require the bible-config file<commit_after>#!/usr/bin/env node

// Help output
const HELP =
"bible --help" +
"\nusage: bible [options]" +
"\n" +
"\nRead the Holy Bible using a NPM application." +
"\n" +
"\noptions:" +
"\n  --v, --version          print the version" +
"\n  --lang, --language      set the Bible language" +
"\n  --ref, --reference      the verse references that you want to read" +
"\n  --onlyVerses            prevent showing additional output" +
"\n  --s, --search           get the verses that match to the string or regular expression provided" +
"\n  --rc, --resultColor     set the result color when searching something" +
"\n  --help                  print this output" +
"\n" +
"\nDocumentation can be found at https://github.com/BibleJS/BibleApp";

function getUserHome() {
    return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
}

// Dependencies
var Bible = require ("bible.js")
  , Couleurs = require ("couleurs")
  , Yargs = require('yargs').usage(HELP)
  , argv = Yargs.argv
  , language = argv.lang || argv.language
  , reference = argv.reference || argv.ref
  , search = argv.s || argv.search
  , searchResultColor = (argv.rc || argv.resultColor || "255, 0, 0").split(",")
  , config = require(getUserHome() + "/.bible-config");
  ;

// Parse result color
for (var i = 0; i < 3; ++i) {

    if (!searchResultColor[i]) {
        return console.log ("Invalid result color. Please provide a string in this format: 'r, g, b'. Example: --resultColor '255, 0, 0'");
    }

    searchResultColor[i] = parseInt(searchResultColor[i])
}

// Show version
if (argv.v || argv.version) {
    return console.log("Bible.js v" + require ("./package").version);
}

// Show help
if (argv.help || !language || (!reference && !search)) {
    return console.log(Yargs.help());
}

// Output
if (!argv.onlyVerses) {
    if (reference) {
        console.log("You are reading " + reference);
    }

    if (search) {
        console.log("You are searching " + search);
    }

    console.log("----------------");
}

/**
 * printOutput
 * This function is called when the response from the
 * search or get request comes
 *
 * @param {Error} err An error that ocured while fetching the verses.
 * @param {Array} verses The verses array that was returned by bible.js module.
 * @return {undefined} Returns undefined
 */
function printOutput (err, verses) {

    // Handle error
    if (err) {
        console.log("Error: ", err);
        return;
    }

    // No verses
    if (!verses || !verses.length) {
        console.log("Verses not found");
    }

    // Output each verse
    for (var i in verses) {

        // get the current verse and its reference
        var cVerse = verses[i]
          , cVerseRef = cVerse.bookname + " " + cVerse.chapter + ":" + cVerse.verse + " | "
          ;

        if (search) {
            cVerse.text = cVerse.text.replace (
                new RegExp (search, "g")
              , search.rgb(searchResultColor)
            );
        }

        console.log((!argv.onlyVerses ? cVerseRef : "") + cVerse.text);
    }

    // Output
    if (!argv.onlyVerses) {
        console.log("----------------");
    }
}

var bibleIns = new Bible({language: language});
bibleIns.init(config, function (err) {

    if (err) { throw err; }

    // Get the verses
    if (reference) {
        bibleIns.get(reference, printOutput);
    }

    // Search verses
    if (search) {
        bibleIns.search(search, printOutput);
    }
});
<|endoftext|>