<commit_before>// ==UserScript==
// @name         Stack Overflow Extras (SOX)
// @namespace    https://github.com/soscripted/sox
// @version      DEV 1.0.1
// @description  Adds a bunch of optional features to sites in the Stack Overflow Network.
// @contributor  ᴉʞuǝ (stackoverflow.com/users/1454538/)
// @contributor  ᔕᖺᘎᕊ (stackexchange.com/users/4337810/)
// @updateURL    https://rawgit.com/soscripted/sox/master/sox.user.js
// @match        *://*.stackoverflow.com/*
// @match        *://*.stackexchange.com/*
// @match        *://*.superuser.com/*
// @match        *://*.serverfault.com/*
// @match        *://*.askubuntu.com/*
// @match        *://*.stackapps.com/*
// @match        *://*.mathoverflow.net/*
// @require      https://code.jquery.com/jquery-2.1.4.min.js
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js
// @require      https://cdn.rawgit.com/timdown/rangyinputs/master/rangyinputs-jquery-src.js
// @require      https://cdn.rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js
// @require      https://cdn.rawgit.com/camagu/jquery-feeds/master/jquery.feeds.js
// @require      https://rawgit.com/soscripted/sox/dev/sox.helpers.js?v=02092016a
// @require      https://rawgit.com/soscripted/sox/dev/sox.features.js?v=02092016a
// @resource     settingsDialog https://rawgit.com/soscripted/sox/dev/sox-dialog.html
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_getResourceText
// ==/UserScript==
/*jshint multistr: true */
(function(sox, $, undefined) {
    const SOX = "Stack Overflow Extras";
    const SOX_SETTINGS = "SOXSETTINGS";

    var $settingsDialog = $(GM_getResourceText("settingsDialog")),
        $soxSettingsDialog,
        $soxSettingsDialogFeatures,
        $soxSettingsSave,
        $soxSettingsReset,
        $soxSettingsClose;

    sox.init = function() {

        // add sox CSS file and font-awesome CSS file
        $("head").append("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css'>")
                 .append("<link rel='stylesheet' type='text/css' href='https://rawgit.com/soscripted/sox/dev/sox.css' />");
        $("body").append($settingsDialog);

        $soxSettingsDialog = $("#sox-settings-dialog");
        $soxSettingsDialogFeatures = $soxSettingsDialog.find("#sox-settings-dialog-features");
        $soxSettingsSave = $soxSettingsDialog.find("#sox-settings-dialog-save");
        $soxSettingsReset = $soxSettingsDialog.find("#sox-settings-dialog-reset");
        $soxSettingsClose = $soxSettingsDialog.find("#sox-settings-dialog-close");

        loadFeatures(); //load all the features in the settings dialog

        // add settings icon to navbar
        var $soxSettingsButton = $("<a/>", {
                id: "soxSettingsButton",
                class: "topbar-icon yes-hover sox-settings-button",
                title: "Change SOX Settings",
                style: "color: #A1A1A1",
                click: function(e) {
                    e.preventDefault();
                    $("#sox-settings-dialog").toggle();
                }
            }),
            $icon = $("<i/>", {
                class: "fa fa-cogs"
            });
        $soxSettingsButton.append($icon).appendTo("div.network-items");

        // add click handlers for the buttons in the settings dialog
        $soxSettingsClose.on("click", function() {
            $soxSettingsDialog.hide();
        });
        $soxSettingsReset.on("click", function() {
            reset();
            location.reload(); // reload page to reflect changed settings
        });
        $soxSettingsSave.on("click", function() {
            extras = [];
            $soxSettingsDialogFeatures.find('input[type=checkbox]:checked').each(function() {
                var x = $(this).attr('id');
                extras.push(x); //Add the function's ID (also the checkbox's ID) to the array
            });
            save(extras);
            location.reload(); // reload page to reflect changed settings
        });

        // check if settings exist and execute desired functions
        if (isAvailable()) {
            extras = getSettings();
            if (isDeprecated()) { //if the setting is set but a deprecated, non-existent feature exists, then delete the setting and act as if it is new
                reset();
            } else {
                for (i = 0; i < extras.length; ++i) {
                    //console.log(extras[i]);
                    $soxSettingsDialogFeatures.find("#" + extras[i]).prop('checked', true);
                    //$('#sox-settings-dialog #' + extras[i]).prop('checked', true);
                    features[extras[i]](); //Call the functions that were chosen
                }
            }
        } else {
            // no settings found, mark all inputs as checked and display settings dialog
            $soxSettingsDialogFeatures.find("input").prop("checked", true);

            setTimeout(function(){
                $soxSettingsDialog.show();
            }, 500);

            //$soxSettingsDialog.delay(800).show(); //not working -- why!?
        }

    };

    function isAvailable() {
        //return ~GM_getValue("sox-featureOptions", -1) ? false : true;
        return (GM_getValue(SOX_SETTINGS, -1) == -1 ? false : true);
    }

    function getSettings() {
        return JSON.parse(GM_getValue(SOX_SETTINGS));
    }

    function reset() {
        GM_deleteValue(SOX_SETTINGS);
    }

    function save(options) {
        GM_setValue(SOX_SETTINGS, JSON.stringify(options));
        console.log("sox settings saved: " + JSON.stringify(options));
    }

    function isDeprecated() { //checks whether the saved settings contain a deprecated feature
        //TODO: add function names to an array and loop instead of || .. || .. ||
        settings = getSettings();
        if (settings.indexOf('answerCountSidebar') != -1 ||
            settings.indexOf('highlightClosedQuestions') != -1 ||
            settings.indexOf('unHideAnswer') != -1 ||
            settings.indexOf('flaggingPercentages') != -1) {
            return true;
        }
    }

    function addFeatures(features) {
        $.each(features, function(i, o) {
            var $div = $("<div/>"),
                $label = $("<label/>"),
                $input = $("<input/>", {
                    id: o[0],
                    "type": "checkbox",
                    style: "margin-right: 5px;"
                });
            $div.append($label);
            $label.append($input);
            $input.after(o[1]);
            $soxSettingsDialogFeatures.append($div);
        });
    }

    function addCategory(name) {
        var $div = $("<div/>"),
            $h3 = $("<h3/>", {
                text: name
            });
        $div.append($h3);
        $soxSettingsDialogFeatures.append($div);
    }

    function loadFeatures() {
        addCategory("Appearance");
        addFeatures([
            ["grayOutVotes", "Gray out deleted votes"],
            ["moveBounty", "Move 'start bounty' to top"],
            ["dragBounty", "Make bounty box draggable"],
            ["renameChat", "Prepend 'Chat - ' to chat tabs' titles"],
            ["exclaim", "Remove exclamation mark on message"],
            ["employeeStar", "Add star after employee names"],
            ["bulletReplace", "Replace triangular bullets with normal ones"],
            ["addEllipsis", "Add ellipsis to long names"],
            ["fixedTopbar", "Fix topbar position"],
            ["displayName", "Display name before gravatar"],
            ["unspoil", "Add a link to show all spoilers in a post"],
            ["spoilerTip", "Differentiate spoilers from empty blockquotes"],
            ["stickyVoteButtons", "Make vote buttons next to posts sticky whilst scrolling on that post"],
            ["betterCSS", "Add extra CSS for voting signs and favourite button (currently only on Android SE)"],
            ["standOutDupeCloseMigrated", "Add colourful, more apparent signs to questions that are on hold, duplicates, closed or migrated on question lists"],
            ["colorAnswerer", "Color answerer's comments"],
            ["highlightQuestions", "Alternate favourite questions highlighing"],
            ["metaNewQuestionAlert", "Add an extra mod diamond to the topbar that alerts you if there is a new question posted on the child-meta of the current site"],
            ["hideHotNetworkQuestions", "Hide the Hot Network Questions module"],
            ["hideHireMe", "Hide the Looking for a Job module"],
            ["hideCommunityBulletin", "Hide the Community Bulletin module"],
            ["hideSearchBar", "Replaces the searchbar with an link to the search page"]
        ]);

        addCategory("Flags");
        addFeatures([
            ["flagOutcomeTime", "Show the flag outcome time when viewing your Flag History"],
            ["flagPercentages", "Show flagging percentages for each type in the Flag Summary"]
            // lots more to come
        ]);

        addCategory("Editing");
        addFeatures([
            ["kbdAndBullets", "Add KBD and list buttons to editor toolbar"],
            ["editComment", "Pre-defined edit comment options"],
            ["editReasonTooltip", "Add a tooltip to posts showing the latest revision's comment on 'edited [date] at [time]'"],
            ["addSBSBtn", "Add a button the the editor toolbar to start side-by-side editing"],
            ["linkQuestionAuthorName", "Add a button in the editor toolbar to insert a hyperlink to a post and add the author automatically"],
            ["titleEditDiff", "Make title edits show seperately rather than merged"]
        ]);
        addCategory("Comments");

        addFeatures([
            ["moveCommentsLink", "Move 'show x more comments' to the top"],
            ["commentShortcuts", "Use Ctrl+I,B,K (to italicise, bolden and add code backticks) in comments"],
            ["quickCommentShortcutsMain", "Add shortcuts to add pre-defined comments to comment fields"],
            ["commentReplies", "Add reply links to comments for quick replying (without having to type someone's username)"],
            ["autoShowCommentImages", "View linked images (to imgur) in comments inline"],
            ["showCommentScores", "Show your comment and comment replies scores in your profile tabs"]
        ]);

        addCategory("Unsorted");
        addFeatures([
            //other
            ["shareLinksMarkdown", "Change 'share' link to format of [post-name](url)"],
            ["parseCrossSiteLinks", "Parse titles to links cross-SE-sites"],
            ["confirmNavigateAway", "Add a confirmation dialog before navigating away on pages whilst you are still typing a comment"],
            ["sortByBountyAmount", "Add an option to filter bounties by their amount"],
            ["isQuestionHot", "Add a label on questions which are hot-network questions"],
            ["answerTagsSearch", "Show tags for the question an answer belongs to on search pages (for better context)"],
            ["metaChatBlogStackExchangeButton", "Show meta, chat and blog buttons on hover of a site under the StackExchange button"],
            ["alwaysShowImageUploadLinkBox", "Always show the 'Link from the web' box when uploading an image"],
            ["addAuthorNameToInboxNotifications", "Add the author's name to notifications in the inbox"],
            ["scrollToTop", "Add Scroll To Top button"],
            ["linkedPostsInline", "Display linked posts inline (with an arrow)"]
        ]);
    }

}(window.sox = window.sox || {}, jQuery));

// initialize sox
sox.init(); //TODO: not sure if we really need this to be a function, script is wrapped in IIFE so init code could just go at the top.
<commit_msg>fixes typo (no.1 of #11)<commit_after>// ==UserScript==
// @name         Stack Overflow Extras (SOX)
// @namespace    https://github.com/soscripted/sox
// @version      DEV 1.0.1
// @description  Adds a bunch of optional features to sites in the Stack Overflow Network.
// @contributor  ᴉʞuǝ (stackoverflow.com/users/1454538/)
// @contributor  ᔕᖺᘎᕊ (stackexchange.com/users/4337810/)
// @updateURL    https://rawgit.com/soscripted/sox/master/sox.user.js
// @match        *://*.stackoverflow.com/*
// @match        *://*.stackexchange.com/*
// @match        *://*.superuser.com/*
// @match        *://*.serverfault.com/*
// @match        *://*.askubuntu.com/*
// @match        *://*.stackapps.com/*
// @match        *://*.mathoverflow.net/*
// @require      https://code.jquery.com/jquery-2.1.4.min.js
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js
// @require      https://cdn.rawgit.com/timdown/rangyinputs/master/rangyinputs-jquery-src.js
// @require      https://cdn.rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js
// @require      https://cdn.rawgit.com/camagu/jquery-feeds/master/jquery.feeds.js
// @require      https://rawgit.com/soscripted/sox/dev/sox.helpers.js?v=02092016a
// @require      https://rawgit.com/soscripted/sox/dev/sox.features.js?v=02092016a
// @resource     settingsDialog https://rawgit.com/soscripted/sox/dev/sox-dialog.html
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_getResourceText
// ==/UserScript==
/*jshint multistr: true */
(function(sox, $, undefined) {
    const SOX = "Stack Overflow Extras";
    const SOX_SETTINGS = "SOXSETTINGS";

    var $settingsDialog = $(GM_getResourceText("settingsDialog")),
        $soxSettingsDialog,
        $soxSettingsDialogFeatures,
        $soxSettingsSave,
        $soxSettingsReset,
        $soxSettingsClose;

    sox.init = function() {

        // add sox CSS file and font-awesome CSS file
        $("head").append("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css'>")
                 .append("<link rel='stylesheet' type='text/css' href='https://rawgit.com/soscripted/sox/dev/sox.css' />");
        $("body").append($settingsDialog);

        $soxSettingsDialog = $("#sox-settings-dialog");
        $soxSettingsDialogFeatures = $soxSettingsDialog.find("#sox-settings-dialog-features");
        $soxSettingsSave = $soxSettingsDialog.find("#sox-settings-dialog-save");
        $soxSettingsReset = $soxSettingsDialog.find("#sox-settings-dialog-reset");
        $soxSettingsClose = $soxSettingsDialog.find("#sox-settings-dialog-close");

        loadFeatures(); //load all the features in the settings dialog

        // add settings icon to navbar
        var $soxSettingsButton = $("<a/>", {
                id: "soxSettingsButton",
                class: "topbar-icon yes-hover sox-settings-button",
                title: "Change SOX Settings",
                style: "color: #A1A1A1",
                click: function(e) {
                    e.preventDefault();
                    $("#sox-settings-dialog").toggle();
                }
            }),
            $icon = $("<i/>", {
                class: "fa fa-cogs"
            });
        $soxSettingsButton.append($icon).appendTo("div.network-items");

        // add click handlers for the buttons in the settings dialog
        $soxSettingsClose.on("click", function() {
            $soxSettingsDialog.hide();
        });
        $soxSettingsReset.on("click", function() {
            reset();
            location.reload(); // reload page to reflect changed settings
        });
        $soxSettingsSave.on("click", function() {
            extras = [];
            $soxSettingsDialogFeatures.find('input[type=checkbox]:checked').each(function() {
                var x = $(this).attr('id');
                extras.push(x); //Add the function's ID (also the checkbox's ID) to the array
            });
            save(extras);
            location.reload(); // reload page to reflect changed settings
        });

        // check if settings exist and execute desired functions
        if (isAvailable()) {
            extras = getSettings();
            if (isDeprecated()) { //if the setting is set but a deprecated, non-existent feature exists, then delete the setting and act as if it is new
                reset();
            } else {
                for (i = 0; i < extras.length; ++i) {
                    //console.log(extras[i]);
                    $soxSettingsDialogFeatures.find("#" + extras[i]).prop('checked', true);
                    //$('#sox-settings-dialog #' + extras[i]).prop('checked', true);
                    features[extras[i]](); //Call the functions that were chosen
                }
            }
        } else {
            // no settings found, mark all inputs as checked and display settings dialog
            $soxSettingsDialogFeatures.find("input").prop("checked", true);

            setTimeout(function(){
                $soxSettingsDialog.show();
            }, 500);

            //$soxSettingsDialog.delay(800).show(); //not working -- why!?
        }

    };

    function isAvailable() {
        //return ~GM_getValue("sox-featureOptions", -1) ? false : true;
        return (GM_getValue(SOX_SETTINGS, -1) == -1 ? false : true);
    }

    function getSettings() {
        return JSON.parse(GM_getValue(SOX_SETTINGS));
    }

    function reset() {
        GM_deleteValue(SOX_SETTINGS);
    }

    function save(options) {
        GM_setValue(SOX_SETTINGS, JSON.stringify(options));
        console.log("sox settings saved: " + JSON.stringify(options));
    }

    function isDeprecated() { //checks whether the saved settings contain a deprecated feature
        //TODO: add function names to an array and loop instead of || .. || .. ||
        settings = getSettings();
        if (settings.indexOf('answerCountSidebar') != -1 ||
            settings.indexOf('highlightClosedQuestions') != -1 ||
            settings.indexOf('unHideAnswer') != -1 ||
            settings.indexOf('flaggingPercentages') != -1) {
            return true;
        }
    }

    function addFeatures(features) {
        $.each(features, function(i, o) {
            var $div = $("<div/>"),
                $label = $("<label/>"),
                $input = $("<input/>", {
                    id: o[0],
                    "type": "checkbox",
                    style: "margin-right: 5px;"
                });
            $div.append($label);
            $label.append($input);
            $input.after(o[1]);
            $soxSettingsDialogFeatures.append($div);
        });
    }

    function addCategory(name) {
        var $div = $("<div/>"),
            $h3 = $("<h3/>", {
                text: name
            });
        $div.append($h3);
        $soxSettingsDialogFeatures.append($div);
    }

    function loadFeatures() {
        addCategory("Appearance");
        addFeatures([
            ["grayOutVotes", "Gray out deleted votes"],
            ["moveBounty", "Move 'start bounty' to top"],
            ["dragBounty", "Make bounty box draggable"],
            ["renameChat", "Prepend 'Chat - ' to chat tabs' titles"],
            ["exclaim", "Remove exclamation mark on message"],
            ["employeeStar", "Add star after employee names"],
            ["bulletReplace", "Replace triangular bullets with normal ones"],
            ["addEllipsis", "Add ellipsis to long names"],
            ["fixedTopbar", "Fix topbar position"],
            ["displayName", "Display name before gravatar"],
            ["unspoil", "Add a link to show all spoilers in a post"],
            ["spoilerTip", "Differentiate spoilers from empty blockquotes"],
            ["stickyVoteButtons", "Make vote buttons next to posts sticky whilst scrolling on that post"],
            ["betterCSS", "Add extra CSS for voting signs and favourite button (currently only on Android SE)"],
            ["standOutDupeCloseMigrated", "Add colourful, more apparent signs to questions that are on hold, duplicates, closed or migrated on question lists"],
            ["colorAnswerer", "Color answerer's comments"],
            ["highlightQuestions", "Alternate favourite questions highlighing"],
            ["metaNewQuestionAlert", "Add an extra mod diamond to the topbar that alerts you if there is a new question posted on the child-meta of the current site"],
            ["hideHotNetworkQuestions", "Hide the Hot Network Questions module"],
            ["hideHireMe", "Hide the Looking for a Job module"],
            ["hideCommunityBulletin", "Hide the Community Bulletin module"],
            ["hideSearchBar", "Replaces the searchbar with an link to the search page"]
        ]);

        addCategory("Flags");
        addFeatures([
            ["flagOutcomeTime", "Show the flag outcome time when viewing your Flag History"],
            ["flagPercentages", "Show flagging percentages for each type in the Flag Summary"]
            // lots more to come
        ]);

        addCategory("Editing");
        addFeatures([
            ["kbdAndBullets", "Add KBD and list buttons to editor toolbar"],
            ["editComment", "Pre-defined edit comment options"],
            ["editReasonTooltip", "Add a tooltip to posts showing the latest revision's comment on 'edited [date] at [time]'"],
            ["addSBSBtn", "Add a button to the editor toolbar to start side-by-side editing"],
            ["linkQuestionAuthorName", "Add a button in the editor toolbar to insert a hyperlink to a post and add the author automatically"],
            ["titleEditDiff", "Make title edits show seperately rather than merged"]
        ]);
        addCategory("Comments");

        addFeatures([
            ["moveCommentsLink", "Move 'show x more comments' to the top"],
            ["commentShortcuts", "Use Ctrl+I,B,K (to italicise, bolden and add code backticks) in comments"],
            ["quickCommentShortcutsMain", "Add shortcuts to add pre-defined comments to comment fields"],
            ["commentReplies", "Add reply links to comments for quick replying (without having to type someone's username)"],
            ["autoShowCommentImages", "View linked images (to imgur) in comments inline"],
            ["showCommentScores", "Show your comment and comment replies scores in your profile tabs"]
        ]);

        addCategory("Unsorted");
        addFeatures([
            //other
            ["shareLinksMarkdown", "Change 'share' link to format of [post-name](url)"],
            ["parseCrossSiteLinks", "Parse titles to links cross-SE-sites"],
            ["confirmNavigateAway", "Add a confirmation dialog before navigating away on pages whilst you are still typing a comment"],
            ["sortByBountyAmount", "Add an option to filter bounties by their amount"],
            ["isQuestionHot", "Add a label on questions which are hot-network questions"],
            ["answerTagsSearch", "Show tags for the question an answer belongs to on search pages (for better context)"],
            ["metaChatBlogStackExchangeButton", "Show meta, chat and blog buttons on hover of a site under the StackExchange button"],
            ["alwaysShowImageUploadLinkBox", "Always show the 'Link from the web' box when uploading an image"],
            ["addAuthorNameToInboxNotifications", "Add the author's name to notifications in the inbox"],
            ["scrollToTop", "Add Scroll To Top button"],
            ["linkedPostsInline", "Display linked posts inline (with an arrow)"]
        ]);
    }

}(window.sox = window.sox || {}, jQuery));

// initialize sox
sox.init(); //TODO: not sure if we really need this to be a function, script is wrapped in IIFE so init code could just go at the top.
<|endoftext|>


<commit_before>(function() {

   var replaceInput = "

";
   var markup = [
    "
",
        "

",
        "

",
        "
",
    "
"
    ].join("");

    var defaultOpts = {
        color: "red",
        move: function() { },
        close: function() { },
        open: function() { },
		flat: false
    };

    function spectrum(element, o) {

        var opts = $.extend({ }, defaultOpts, o),
            doc = element.ownerDocument,
            body = doc.body,
            container = $(markup),
            dragger = container.find(".spectrum-color"),
            dragHelper = container.find(".spectrum-drag-helper"),
			slider = container.find(".spectrum-slide"),
			slideHelper = container.find(".spectrum-slide-helper"),
            dragging = false,
			sliding = false,
			visible = false,
            offsetX = 0,
            offsetY = 0,
			dragWidth = 0,
            dragHeight = 0,
			slideHeight = 0,
			slideWidth = 0,
			currentX = 0,
			currentY = 0,
			currentHue = 0;

		var el;
		if ($(element).is("input")) {
			el = $(replaceInput);
			$(element).hide().after(el);
		}
		else {
			el = $(element);
		}

		el.addClass("spectrum-element");

        /* Public API */
        el.bind("spectrum.show", function() {
			if (visible) { return; }
			visible = true;

			$(doc).bind("mouseup",   docMouseup);
			$(doc).bind("mousemove", docMousemove);
			$(doc).bind("click",     docClick);

			var elOffset = el.offset();
			elOffset.left += el.width();
            container.show().offset(elOffset);
            var off = dragger.offset();
            offsetX = off.left;
            offsetY = off.top;
            dragWidth = dragger.width();
            dragHeight = dragger.height();
            slideWidth = slider.width();
            slideHeight = slider.height();
        });

        el.bind("spectrum.hide", function() {
			if (!visible) { return; }
			visible = false;

			$(doc).unbind("mouseup",   docMouseup);
			$(doc).unbind("mousemove", docMousemove);
			$(doc).unbind("click",     docClick);

            container.hide();
        });

        el.bind("spectrum.set", function(e, c) {
            dragger.css("background-color", c);
			if (true) {

			}
        });

		el.click(function(e){
			if (visible) {
				$(this).trigger("spectrum.hide");
			}
			else {
				$(this).trigger("spectrum.show");
			}
			e.stopPropagation();
		});

        /* DOM event handlers */

        function docMouseup() {
            dragging = false;
			sliding = false;
		}
		function docClick() {
			el.trigger("spectrum.hide");
		}
		function docMousemove(e) {
            if (dragging) {
                 drag(e);
            }
			if (sliding) {
				slide(e);
			}
            $("#console").text(e.layerX + " " + e.layerY + " " + e.clientX+ " " + e.clientY);
		}
		function move() {
			var h = getCurrentHue();
			var s = getCurrentSaturation();
            var v = getCurrentValue();

            var c = hsv2rgb(h, s, v);

			$("#console").css('background-color', c.hex);
		}

		// Don't let click event go up to document
		container.bind("click", function(e) {
			e.stopPropagation();
		});

        slider.mousedown(function(e) {
			if (e.button == 0) {
				sliding = true;
				slide(e);
			}
		});
        dragger.mousedown(function(e) {
			if (e.button == 0) {
				dragging = true;
				drag(e);
			}
        });

        function coordinatesFromEvent(e, maxHeight, maxWidth) {
            var top = e.pageY - offsetY;
            var left = e.pageX - offsetX;

            return {
                top: Math.max(0, Math.min(maxHeight, top)),
                left: Math.max(0, Math.min(maxWidth, left))
            };
        }

        function drag(e) {
            var h = dragHelper.height();
            var c = coordinatesFromEvent(e, dragHeight, dragWidth);

			currentX = c.top;
			currentY = c.left;

            dragHelper.css({
                "top": currentX - (h / 2),
                "left": currentY - (h / 2)
            });

			move();
        }

        function slide(e) {
            var h = slideHelper.height();
            var c = coordinatesFromEvent(e, slideHeight, slideWidth);

			currentHue = c.top;

            slideHelper.css({
                "top": currentHue - (h / 2)
            });

			var c = hsv2rgb(getCurrentHue(), 1, 1);
			dragger.css("background-color", c.hex);
			move();
        }

		function getCurrentSaturation() {
			return currentY / dragWidth;
		}
		function getCurrentValue() {
			return (dragHeight - currentX) / dragHeight
		}
		function getCurrentHue() {
			return (currentHue / slideHeight) * 360;
		}

        $(body).append(container.hide());

		if (opts.flat) {
			el.trigger("spectrum.show");
		}
        el.trigger("spectrum.set", opts.color);
    }

    $.fn.spectrum = function(opts) {
        return this.each(function() {
            spectrum(this, opts);
        });
    };

    /**
     * Convert HSV representation to RGB HEX string.
     * Credits to http://www.raphaeljs.com
     */
    function hsv2rgb(h, s, v) {
        var R, G, B, X, C;
        h = (h % 360) / 60;
            C = v * s;
        X = C * (1 - Math.abs(h % 2 - 1));
        R = G = B = v - C;

        h = ~~h;
        R += [C, X, 0, 0, X, C][h];
        G += [X, C, C, X, 0, 0][h];
        B += [0, 0, X, C, C, X][h];

        var r = R * 255,
            g = G * 255,
            b = B * 255;
        return { r: r, g: g, b: b, hex: "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1) };
    }

    /**
     * Convert RGB representation to HSV.
     * r, g, b can be either in <0,1> range or <0,255> range.
     * Credits to http://www.raphaeljs.com
     */
    function rgb2hsv(r, g, b) {
        if (r > 1 || g > 1 || b > 1) {
            r /= 255;
            g /= 255;
            b /= 255;
        }
        var H, S, V, C;
        V = Math.max(r, g, b);
        C = V - Math.min(r, g, b);
        H = (C == 0 ? null :
             V == r ? (g - b) / C :
             V == g ? (b - r) / C + 2 :
                      (r - g) / C + 4);
        H = (H % 6) * 60;
        S = C == 0 ? 0 : C / V;
        return { h: H, s: S, v: V };
    }

    window.spectrum = spectrum;

})();

<commit_msg>use my draggable helper instead, and use a coordinate system that isnt backwards<commit_after>(function() {

   var replaceInput = "

";
   var markup = [
    "
",
        "

",
        "

",
        "
",
    "
"
    ].join("");

    var defaultOpts = {
        color: "red",
        move: function() { },
        close: function() { },
        open: function() { },
		flat: false
    };

    function spectrum(element, o) {

        var opts = $.extend({ }, defaultOpts, o),
            doc = element.ownerDocument,
            body = doc.body,
            container = $(markup),
            dragger = container.find(".spectrum-color"),
            dragHelper = container.find(".spectrum-drag-helper"),
			slider = container.find(".spectrum-slide"),
			slideHelper = container.find(".spectrum-slide-helper"),
            dragging = false,
			sliding = false,
			visible = false,
            offsetX = 0,
            offsetY = 0,
			dragWidth = 0,
            dragHeight = 0,
			slideHeight = 0,
			slideWidth = 0,
			currentX = 0,
			currentY = 0,
			currentHue = 0;

		var el;
		if ($(element).is("input")) {
			el = $(replaceInput);
			$(element).hide().after(el);
		}
		else {
			el = $(element);
		}

		el.addClass("spectrum-element");

        /* Public API */
        el.bind("spectrum.show", function() {
			if (visible) { return; }
			visible = true;

			$(doc).bind("click", docClick);

			var elOffset = el.offset();
			elOffset.left += el.width();
            container.show().offset(elOffset);
            var off = dragger.offset();
            offsetX = off.left;
            offsetY = off.top;
            dragWidth = dragger.width();
            dragHeight = dragger.height();
            slideWidth = slider.width();
            slideHeight = slider.height();
        });

        el.bind("spectrum.hide", function() {
			if (!visible) { return; }
			visible = false;

			$(doc).unbind("click", docClick);
            container.hide();
        });

        el.bind("spectrum.set", function(e, c) {
            dragger.css("background-color", c);
			if (true) {

			}
        });

		el.click(function(e){
			if (visible) {
				$(this).trigger("spectrum.hide");
			}
			else {
				$(this).trigger("spectrum.show");
			}
			e.stopPropagation();
		});

        /* DOM event handlers */

		function docClick() {
			el.trigger("spectrum.hide");
		}

		function move() {
			var h = getCurrentHue();
			var s = getCurrentSaturation();
            var v = getCurrentValue();

            var c = hsv2rgb(h, s, v);

			$("#console").css('background-color', c.hex);
		}

		// Don't let click event go up to document
		container.bind("click", function(e) {
			e.stopPropagation();
		});

		draggable(slider, function(e) { slide(e.dragX, e.dragY); });
		draggable(dragger, function(e) { drag(e.dragX, e.dragY); });

        function coordinatesFromEvent(e, maxHeight, maxWidth) {
            var top = e.pageY - offsetY;
            var left = e.pageX - offsetX;

            return {
                top: Math.max(0, Math.min(maxHeight, top)),
                left: Math.max(0, Math.min(maxWidth, left))
            };
        }

        function drag(x, y) {
            var h = dragHelper.height();
			currentX = x;
			currentY = y;

            dragHelper.css({
                "top": currentY - (h / 2),
                "left": currentX - (h / 2)
            });

            $("#console").text(currentX + " " + currentY);
			move();
        }

        function slide(x, y) {
            var h = slideHelper.height();
			currentHue = y;

            slideHelper.css({
                "top": currentHue - (h / 2)
            });

			var c = hsv2rgb(getCurrentHue(), 1, 1);
			dragger.css("background-color", c.hex);
			move();
        }

		function getCurrentSaturation() {
			return currentX / dragWidth;
		}
		function getCurrentValue() {
			return (dragHeight - currentY) / dragHeight
		}
		function getCurrentHue() {
			return (currentHue / slideHeight) * 360;
		}

        $(body).append(container.hide());

		if (opts.flat) {
			el.trigger("spectrum.show");
		}
        el.trigger("spectrum.set", opts.color);
    }

    $.fn.spectrum = function(opts) {
        return this.each(function() {
            spectrum(this, opts);
        });
    };

    /**
     * Convert HSV representation to RGB HEX string.
     * Credits to http://www.raphaeljs.com
     */
    function hsv2rgb(h, s, v) {
        var R, G, B, X, C;
        h = (h % 360) / 60;
            C = v * s;
        X = C * (1 - Math.abs(h % 2 - 1));
        R = G = B = v - C;

        h = ~~h;
        R += [C, X, 0, 0, X, C][h];
        G += [X, C, C, X, 0, 0][h];
        B += [0, 0, X, C, C, X][h];

        var r = R * 255,
            g = G * 255,
            b = B * 255;
        return { r: r, g: g, b: b, hex: "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1) };
    }

    /**
     * Convert RGB representation to HSV.
     * r, g, b can be either in <0,1> range or <0,255> range.
     * Credits to http://www.raphaeljs.com
     */
    function rgb2hsv(r, g, b) {
        if (r > 1 || g > 1 || b > 1) {
            r /= 255;
            g /= 255;
            b /= 255;
        }
        var H, S, V, C;
        V = Math.max(r, g, b);
        C = V - Math.min(r, g, b);
        H = (C == 0 ? null :
             V == r ? (g - b) / C :
             V == g ? (b - r) / C + 2 :
                      (r - g) / C + 4);
        H = (H % 6) * 60;
        S = C == 0 ? 0 : C / V;
        return { h: H, s: S, v: V };
    }

    function draggable(element, onmove, onstart, onstop) {
		onmove = onmove || function() { };
		onstart = onstart || function() { };
		onstop = onstop || function() { };
		var doc = element.ownerDocument || document;
		var dragging = false;
		var offset = { };
		var maxHeight = 0;
		var maxWidth = 0;

		function move(e) {
			if (dragging) {
				e.dragX = Math.max(0, Math.min(e.pageX - offset.left, maxWidth));
				e.dragY = Math.max(0, Math.min(e.pageY - offset.top, maxHeight));

				onmove.apply(element, [e]);
			}
		}
		function start(e) {
			if (e.button == 0 && !dragging) {
				maxHeight = $(element).height();
				maxWidth = $(element).width();
				offset = $(element).offset();
				$(doc).bind({
					"mouseup": stop,
					"mousemove": move
				});
				onstart.apply(element, arguments);
			}
			dragging = true;
		}
		function stop() {
			if (dragging) {
				$(doc).unbind("mouseup", stop);
				onstop.apply(element, arguments);
			}
			dragging = false;
		}

		$(element).bind("mousedown", start);
	}

    window.spectrum = spectrum;

})();

<|endoftext|>


<commit_before>// https://stackoverflow.com/questions/30987218/update-progressbar-in-each-loop/31654481
function delayedLoop(collection, delay, callback, context) {
    context = context || null;

    let i = 0;
    const nextIteration = function () {
        if (i === collection.length) {
            return;
        }
        callback.call(context, collection[i], i);
        i++;
        setTimeout(nextIteration, delay);
    };

    nextIteration();
}

function selectGroup(index, numGroups, elem) {
    $("div").removeClass("chosen");
    $(elem).addClass("chosen");

    const yId = 'yasgui' + index;
    let x = document.getElementById(yId);
    if (x) {
        x.style.display = "block";
    } else {
        delayedLoop([0, 1, 2], 10, (_, idx) => {
            if (idx === 0) {
                $("#spinner").css("visibility", "visible");
            } else if (idx === 1) {
                createYasgui(index);
            } else /* idx === 2*/ {
                $("#spinner").css("visibility", "hidden");
            }
        })
    }

    for (i = 0; i <= numGroups; i++) {
        x = document.getElementById('yasgui' + i);
        if (x) {
            if (i === index) {
                x.style.display = "block";
            } else {
                x.style.display = "none";
            }
        }
    }
}

function createYasgui(i) {
    let group = window.portalConfig.tabGroups[i - 1];
    const yId = `yasgui${i}`;
    $('#yasguis').append(`<div id='${yId}' class="predefined"></div>`);

    const y = YASGUI(document.getElementById(yId), {
        yasqe: {
            sparql: {endpoint: window.endpointUrl},
            persistent: null
        }
    });

    for (let tabId in y.tabs) {
        y.closeTab(tabId);
    }

    delayedLoop(group.tabs, 1, (t, _) => {
        let tab = y.addTab();
        tab.rename(t.name);
        tab.setQuery(t.query);
        //console.log(t.name, new Date().toLocaleTimeString());
    });

    y.selectTab(Object.keys(y.tabs)[0]);
    return yId;
}

$(() => {
    // fetch data form the toml file
    fetch('ontop/portalConfig')
        .then(response => response.json())
        .then(
            config => {
                window.portalConfig = config;
                // console.log("ontop/portalConfig fetched", new Date().toLocaleTimeString());
                const $switcher = $("#switcher");
                if ($.isEmptyObject(config)) {
                    // we hide the switcher since in this case the endpoint was not initialized with a .toml file
                    $switcher.hide();
                } else {
                    if (config.title) $("#title").text(config.title);

                    if (config.tabGroups) {
                        let numGroups = config.tabGroups.length;
                        $switcher.append(`<div class="choice-option" id="select0" onclick='selectGroup(0, ${numGroups}, this)'>Playground</div>`);
                        for (let i = 0; i < numGroups; i++) {
                            let group = config.tabGroups[i];
                            $switcher.append(`<div class="choice-option" id='select${i + 1}' onclick='selectGroup(${i + 1}, ${numGroups}, this)'> ${group.name} </div>`)
                        }

                        selectGroup(0, numGroups, "#select0");
                    }
                }

                const endpointUrl = new Request('sparql').url;
                window.endpointUrl = endpointUrl;
                $('#endpoint').text(endpointUrl);
                var yasgui = YASGUI(document.getElementById("yasgui0"), {
                    yasqe: {sparql: {endpoint: endpointUrl}}
                });
            }
        );
});

<commit_msg>Add option to define default endpoint query<commit_after>// https://stackoverflow.com/questions/30987218/update-progressbar-in-each-loop/31654481
function delayedLoop(collection, delay, callback, context) {
    context = context || null;

    let i = 0;
    const nextIteration = function () {
        if (i === collection.length) {
            return;
        }
        callback.call(context, collection[i], i);
        i++;
        setTimeout(nextIteration, delay);
    };

    nextIteration();
}

function selectGroup(index, numGroups, elem) {
    $("div").removeClass("chosen");
    $(elem).addClass("chosen");

    const yId = 'yasgui' + index;
    let x = document.getElementById(yId);
    if (x) {
        x.style.display = "block";
    } else {
        delayedLoop([0, 1, 2], 10, (_, idx) => {
            if (idx === 0) {
                $("#spinner").css("visibility", "visible");
            } else if (idx === 1) {
                createYasgui(index);
            } else /* idx === 2*/ {
                $("#spinner").css("visibility", "hidden");
            }
        })
    }

    for (i = 0; i <= numGroups; i++) {
        x = document.getElementById('yasgui' + i);
        if (x) {
            if (i === index) {
                x.style.display = "block";
            } else {
                x.style.display = "none";
            }
        }
    }
}

function createYasgui(i) {
    let group = window.portalConfig.tabGroups[i - 1];
    const yId = `yasgui${i}`;
    $('#yasguis').append(`<div id='${yId}' class="predefined"></div>`);

    const y = YASGUI(document.getElementById(yId), {
        yasqe: {
            sparql: {endpoint: window.endpointUrl},
            persistent: null
        }
    });

    for (let tabId in y.tabs) {
        y.closeTab(tabId);
    }

    delayedLoop(group.tabs, 1, (t, _) => {
        let tab = y.addTab();
        tab.rename(t.name);
        tab.setQuery(t.query);
        //console.log(t.name, new Date().toLocaleTimeString());
    });

    y.selectTab(Object.keys(y.tabs)[0]);
    return yId;
}

$(() => {
    // fetch data form the toml file
    fetch('ontop/portalConfig')
        .then(response => response.json())
        .then(
            config => {
                window.portalConfig = config;
                // console.log("ontop/portalConfig fetched", new Date().toLocaleTimeString());
                const $switcher = $("#switcher");
                if ($.isEmptyObject(config)) {
                    // we hide the switcher since in this case the endpoint was not initialized with a .toml file
                    $switcher.hide();
                } else {
                    if (config.title) $("#title").text(config.title);

                    // If a portal file is provided with default query provided, set the default tab
                    if(config.defaultTab.query) { YASGUI.YASQE.defaults.value = config.defaultTab.query; }

                    if (config.tabGroups) {
                        let numGroups = config.tabGroups.length;
                        $switcher.append(`<div class="choice-option" id="select0" onclick='selectGroup(0, ${numGroups}, this)'>Playground</div>`);
                        for (let i = 0; i < numGroups; i++) {
                            let group = config.tabGroups[i];
                            $switcher.append(`<div class="choice-option" id='select${i + 1}' onclick='selectGroup(${i + 1}, ${numGroups}, this)'> ${group.name} </div>`)
                        }

                        selectGroup(0, numGroups, "#select0");
                    }
                }

                const endpointUrl = new Request('sparql').url;
                window.endpointUrl = endpointUrl;
                $('#endpoint').text(endpointUrl);
                var yasgui = YASGUI(document.getElementById("yasgui0"), {
                    yasqe: {sparql: {endpoint: endpointUrl}}
                });
            }
        );
});

<|endoftext|>


<commit_before>myAppModule.controller("addRepairDialogController", [ '$scope', '$mdDialog', 'repairFactory', function($scope, $mdDialog, repairFactory) {
    $scope.cancel = function() {
        $mdDialog.cancel();
    };
    $scope.hide = function() {
        $mdDialog.hide();
    };
    $scope.addRepair = function() {
        repairFactory.addRepair($scope.newRepair);
        $mdDialog.hide()
    };

    $scope.noSundayPredicate = function(date) {
        var day = date.getDay();
        return day !== 0;
    };
}]);<commit_msg>error handling<commit_after>myAppModule.controller("addRepairDialogController", [ '$scope', '$mdDialog', 'repairFactory', function($scope, $mdDialog, repairFactory) {
    $scope.cancel = function() {
        $mdDialog.cancel();
    };
    $scope.hide = function() {
        $mdDialog.hide();
    };
    $scope.addRepair = function() {
        repairFactory.addRepair($scope.newRepair).then(function(){
            $mdDialog.hide();
        },
        function(){
            //handle the error
        });
    };

    $scope.noSundayPredicate = function(date) {
        var day = date.getDay();
        return day !== 0;
    };
}]);<|endoftext|>


<commit_before>/* eslint spaced-comment: 0 */
/* eslint no-redeclare: 0 */
/* eslint no-unused-vars: 0 */
/* eslint no-prototype-builtins: 0 */

const assert = require('chai').assert;

/// title: Linked Hash Map
/// type: arcade-mode

/// categories:
/// data structures

/// difficulty: 3

/// description:
/// In a hash map, the order of the keys is usually unspecified. We can use
/// Linked hash map to retain the insertion order of the keys, when iterating
/// through the hash map.
/// Instructions:
/// Implement a Linked hash map. It should have the following functions:
///   set for adding data with specific key
///   get for retrieving data with special key
///   delete for deleting data with specific key
///   has for checking if key exists.
///   keys for returning all keys in insertion order
///   forEach to iterate through the hash map

/// challengeSeed:
function LinkedHashMap() {
  // Good luck!
  this.set = (key, data) => {
  };

  this.get = key => {
  };

  this.delete = key => {
  };

  this.keys = () => {
  };

  /* Call fn(key, value) */
  this.forEach = fn => {
  };
}

/// solutions:
function LinkedHashMap() {
  this.orderList = [];
  this.hashMap = {};

  this.set = (key, data) => {
    if (!this.has(key)) {
      this.orderList.push(key);
    }
    this.hashMap[key] = data;
  };

  this.get = key => this.hashMap[key];

  this.delete = key => {
    if (this.has(key)) {
      this.orderList.splice(this.orderList.indexOf(key));
      delete this.hashMap[key];
    }
  };

  this.has = key => (
    this.hashMap.hasOwnProperty(key)
  );

  this.keys = () => this.orderList;

  this.forEach = fn => {
    this.orderList.forEach((key, index) => {
      fn(key, this.hashMap[key]);
    });
  };
}

/// tail:
const hashmap = new LinkedHashMap();
hashmap.set('aaa', 'bbb');

const hashmap2 = new LinkedHashMap();
hashmap2.set('123', '123');
hashmap2.delete('123');

const hashmap3 = new LinkedHashMap();
hashmap3.set('123', '123');
hashmap3.set('567', '123');
hashmap3.set('890', '123');

const testArr4 = [1, 2, 3, 4, 5, 6];
const hashmap4 = new LinkedHashMap();
testArr4.forEach(num => { hashmap4.set(num, num); });

const accumul = function(hm) {
  let result = 0;
  hm.forEach((key, value) => {
    result += value;
  });
  return result;
};
const correctSum = testArr4.reduce((acc, val) => acc + val);

/// tests:
assert.equal(typeof LinkedHashMap, 'function');
assert.equal(hashmap.get('aaa'), 'bbb');
assert.deepEqual(hashmap.keys(), ['aaa']);
assert.equal(hashmap2.keys().length, 0);
assert.deepEqual(hashmap3.keys(), ['123', '567', '890']);
assert.equal(accumul(hashmap4), correctSum);
<commit_msg>Minor cleaning up.<commit_after>/* eslint spaced-comment: 0 */
/* eslint no-redeclare: 0 */
/* eslint no-unused-vars: 0 */
/* eslint no-prototype-builtins: 0 */

const assert = require('chai').assert;

/// title: Linked Hash Map
/// type: arcade-mode
/// difficulty: 3

/// categories:
/// data structures

/// description:
/// In a hash map, the order of the keys is usually unspecified. We can use
/// Linked hash map to retain the insertion order of the keys, when iterating
/// through the hash map.
/// Instructions:
/// Implement a Linked hash map. It should have the following functions:
///   set for adding data with specific key
///   get for retrieving data with special key
///   delete for deleting data with specific key
///   has for checking if key exists.
///   keys for returning all keys in insertion order
///   forEach to iterate through the hash map

/// challengeSeed:
function LinkedHashMap() {
  // Good luck!
  this.set = (key, data) => {
  };

  this.get = key => {
  };

  this.delete = key => {
  };

  this.keys = () => {
  };

  /* Call fn(key, value) */
  this.forEach = fn => {
  };
}

/// solutions:
function LinkedHashMap() {
  this.orderList = [];
  this.hashMap = {};

  this.set = (key, data) => {
    if (!this.has(key)) {
      this.orderList.push(key);
    }
    this.hashMap[key] = data;
  };

  this.get = key => this.hashMap[key];

  this.delete = key => {
    if (this.has(key)) {
      this.orderList.splice(this.orderList.indexOf(key));
      delete this.hashMap[key];
    }
  };

  this.has = key => (
    this.hashMap.hasOwnProperty(key)
  );

  this.keys = () => this.orderList;

  this.forEach = fn => {
    this.orderList.forEach((key, index) => {
      fn(key, this.hashMap[key]);
    });
  };
}

/// tail:
const hashmap = new LinkedHashMap();
hashmap.set('aaa', 'bbb');

const hashmap2 = new LinkedHashMap();
hashmap2.set('123', '123');
hashmap2.delete('123');

const hashmap3 = new LinkedHashMap();
hashmap3.set('123', '123');
hashmap3.set('567', '123');
hashmap3.set('890', '123');

const testArr4 = [1, 2, 3, 4, 5, 6];
const hashmap4 = new LinkedHashMap();
testArr4.forEach(num => { hashmap4.set(num, num); });

const accumul = function(hm) {
  let result = 0;
  hm.forEach((key, value) => {
    result += value;
  });
  return result;
};
const correctSum = testArr4.reduce((acc, val) => acc + val);

/// tests:
assert.equal(typeof LinkedHashMap, 'function');
assert.equal(hashmap.get('aaa'), 'bbb');
assert.deepEqual(hashmap.keys(), ['aaa']);
assert.equal(hashmap2.keys().length, 0);
assert.deepEqual(hashmap3.keys(), ['123', '567', '890']);
assert.equal(accumul(hashmap4), correctSum);
<|endoftext|>


<commit_before>import KEY from './image-search-key';

export default class ImageSearchService {
  constructor($http) {
    this.$inject = ['$http'];
    this.$http = $http;
    this.host = 'https://api.cognitive.microsoft.com';
    this.path = '/bing/v7.0/images/search';
    this.subKey = KEY.IMAGE_API_KEY;
  }

  imageSearch(query) {
    return this.$http({
      method: 'GET',
      url: this.host + this.path + '?q=' + encodeURIComponent(query),
      headers: {
        'Ocp-Apim-Subscription-Key': this.subKey
      }
    }).then((resp) => {
      let imageArray = resp.data.value;
      let images = [];
      for (let i = 0; i < 6; i++) {
        images.push(imageArray[i].thumbnailUrl);
      }
      return images;
    });
  }
}<commit_msg>Add env variable<commit_after>import KEY from './image-search-key';

export default class ImageSearchService {
  constructor($http) {
    this.$inject = ['$http'];
    this.$http = $http;
    this.host = 'https://api.cognitive.microsoft.com';
    this.path = '/bing/v7.0/images/search';
    this.subKey = process.env.IMAGE_API_KEY || KEY.IMAGE_API_KEY;
  }

  imageSearch(query) {
    return this.$http({
      method: 'GET',
      url: this.host + this.path + '?q=' + encodeURIComponent(query),
      headers: {
        'Ocp-Apim-Subscription-Key': this.subKey
      }
    }).then((resp) => {
      let imageArray = resp.data.value;
      let images = [];
      for (let i = 0; i < 6; i++) {
        images.push(imageArray[i].thumbnailUrl);
      }
      return images;
    });
  }
}<|endoftext|>


<commit_before>class BBOX {
  constructor(...points) {
    Object.assign(this, {
      min: { x: Infinity, y: Infinity },
      max: { x: -Infinity, y: -Infinity },
    });
    this.add(points);
  }

  get width() {
    return this.max.x - this.min.x;
  }

  get height() {
    return this.max.y - this.min.y;
  }

  add(points) {
    points.forEach(([x, y]) => {
      const { min, max } = this;
      this.min = {
        x: Math.min(x, min.x),
        y: Math.min(y, min.y),
      };
      this.max = {
        x: Math.max(x, max.x),
        y: Math.max(y, max.y),
      };
    });
  }

  toString() {
    const { min, max } = this;
    return [min.x, min.y, max.x, max.y].join(',');
  }
}

export default BBOX;
<commit_msg>added BBOX#grow(amount) method<commit_after>class BBOX {
  constructor(...points) {
    Object.assign(this, {
      min: { x: Infinity, y: Infinity },
      max: { x: -Infinity, y: -Infinity },
    });
    this.add(points);
  }

  get width() {
    return this.max.x - this.min.x;
  }

  get height() {
    return this.max.y - this.min.y;
  }

  add(points) {
    points.forEach(([x, y]) => {
      const { min, max } = this;
      this.min = { x: Math.min(x, min.x), y: Math.min(y, min.y) };
      this.max = { x: Math.max(x, max.x), y: Math.max(y, max.y) };
    });
    0;
  }

  grow(amount) {
    this.min.x -= amount;
    this.min.y -= amount;
    this.max.x += amount;
    this.max.y += amount;
  }

  toString() {
    const { min, max } = this;
    return [min.x, min.y, max.x, max.y].join(',');
  }
}

export default BBOX;
<|endoftext|>


<commit_before>import minimongo from 'minimongo-cache';
process.nextTick = setImmediate;

const db = new minimongo();
db.debug = false;

export default {
  _endpoint: null,
  _options: null,
  ddp: null,
  subscriptions: {},
  db: db,
  calls: [],
  hasBeenConnected: false,

  getUrl() {
    return this._endpoint.substring(0, this._endpoint.indexOf('/websocket'));
  },

  waitDdpReady(cb) {
    if(this.ddp) {
      cb();
    } else {
      setTimeout(()=>{
        this.waitDdpReady(cb);
      }, 10);
    }
  },

  _cbs: [],
  onChange(cb) {
    this.db.on('change', cb);
    this.ddp.on('connected', cb);
    this.ddp.on('disconnected', cb);
    this.on('loggingIn', cb);
  },
  offChange(cb) {
    this.db.off('change', cb);
    this.ddp.off('connected', cb);
    this.ddp.off('disconnected', cb);
    this.off('loggingIn', cb);
  },
  on(eventName, cb) {
    this._cbs.push({
      eventName: eventName,
      callback: cb
    });
  },
  off(eventName, cb) {
    this._cbs.splice(this._cbs.findIndex(_cb=>_cb.callback == cb && _cb.eventName == eventName), 1);
  },
  _notifyLoggingIn() {
    this._cbs.map(cb=>{
      if(cb.eventName=='loggingIn' && typeof cb.callback=='function') {
        cb.callback();
      }
    });
  }
}
<commit_msg>Optimize perfs<commit_after>import React from 'react-native';
import minimongo from 'minimongo-cache';
process.nextTick = setImmediate;

const db = new minimongo();
db.debug = false;
db.batchedUpdates = React.addons.batchedUpdates;

export default {
  _endpoint: null,
  _options: null,
  ddp: null,
  subscriptions: {},
  db: db,
  calls: [],
  hasBeenConnected: false,

  getUrl() {
    return this._endpoint.substring(0, this._endpoint.indexOf('/websocket'));
  },

  waitDdpReady(cb) {
    if(this.ddp) {
      cb();
    } else {
      setTimeout(()=>{
        this.waitDdpReady(cb);
      }, 10);
    }
  },

  _cbs: [],
  onChange(cb) {
    this.db.on('change', cb);
    this.ddp.on('connected', cb);
    this.ddp.on('disconnected', cb);
    this.on('loggingIn', cb);
  },
  offChange(cb) {
    this.db.off('change', cb);
    this.ddp.off('connected', cb);
    this.ddp.off('disconnected', cb);
    this.off('loggingIn', cb);
  },
  on(eventName, cb) {
    this._cbs.push({
      eventName: eventName,
      callback: cb
    });
  },
  off(eventName, cb) {
    this._cbs.splice(this._cbs.findIndex(_cb=>_cb.callback == cb && _cb.eventName == eventName), 1);
  },
  _notifyLoggingIn() {
    this._cbs.map(cb=>{
      if(cb.eventName=='loggingIn' && typeof cb.callback=='function') {
        cb.callback();
      }
    });
  }
}
<|endoftext|>


<commit_before>import classNames from 'classnames';
import React, { PropTypes } from 'react';

import { Button, FormGroup, ControlLabel } from 'react-bootstrap';

// form control
import { FormControl } from 'react-bootstrap';

export default React.createClass({
  propTypes: {
    fieldsModel: PropTypes.array.isRequired,
    defaultData: PropTypes.object,
    onChange: PropTypes.func,
    onSubmit: PropTypes.func,
    onReset: PropTypes.func
  },
  getInitialState() {
    return {
      formData: {...this.props.defaultData}
    };
  },

  handleChange(id, e) {
    const newState = {
      formData: this.state.formData
    };
    newState.formData[id] = e.target.value;
    this.setState(newState);
    this.props.onChange(e, id);
  },

  handleSubmit(event) {
    if (this.props.onSubmit) {
      this.props.onSubmit(event, this.state.formData);
    }
  },

  handleReset(event) {
    if (this.props.onReset) {
      this.props.onReset(event);
    }
  },

  render() {
    const { fieldsModel, className } = this.props;
    return (
      <form className={classNames(className)}>
        {
          fieldsModel.map((fieldModel, index) => {
            return (
              <FormGroup
                key={index}
                controlId={`formBasicText-${fieldModel.id}`}
              >
                <ControlLabel>{fieldModel.label}</ControlLabel>
                <FormControl
                  type="text"
                  value={this.state.formData[fieldModel.id]}
                  placeholder={fieldModel.placeholder}
                  onChange={this.handleChange.bind(this, fieldModel.id)}
                />
                <FormControl.Feedback />
              </FormGroup>
            );
          })
        }
        <Button onClick={this.handleSubmit} type="submit">保存</Button>
        <Button onClick={this.handleReset} type="reset">清空</Button>
      </form>
    );
  }
});
<commit_msg>add checkbox<commit_after>import classNames from 'classnames';
import React, { PropTypes } from 'react';

import { Button, FormGroup, ControlLabel } from 'react-bootstrap';

// form control
import { FormControl } from 'react-bootstrap';

export default React.createClass({
  propTypes: {
    fieldsModel: PropTypes.array.isRequired,
    defaultData: PropTypes.object,
    onChange: PropTypes.func,
    onSubmit: PropTypes.func,
    onReset: PropTypes.func
  },
  getInitialState() {
    return {
      formData: {...this.props.defaultData}
    };
  },

  handleChange(id, e) {
    const newState = {
      formData: this.state.formData
    };
    newState.formData[id] = e.target.value;
    this.setState(newState);
    this.props.onChange(e, id);
  },

  handleSubmit(event) {
    if (this.props.onSubmit) {
      this.props.onSubmit(event, this.state.formData);
    }
  },

  handleReset(event) {
    if (this.props.onReset) {
      this.props.onReset(event);
    }
  },

  render() {
    const { fieldsModel, className } = this.props;
    return (
      <form className={classNames(className)}>
        {
          fieldsModel.map((fieldModel, index) => {
            let formCtrl;
            switch(fieldModel.type) {
              default:
              case 'string': // 0
              case 'double': // 2
              case 'ref': // 5
              case 'enum': // 6
                formCtrl = (
                  <FormControl
                    type="text"
                    value={this.state.formData[fieldModel.id]}
                    placeholder={fieldModel.placeholder}
                    onChange={this.handleChange.bind(this, fieldModel.id)}
                  />
                );
                break;
              // case 'date': // 3
              //   formCtrl = (
              //     <DatePicker
              //       id={id}
              //       value={this.state.datePickerValue}
              //       onChange={this.handleDatePickerChange.bind(this)}
              //     />
              //   );
              //   break;
              case 'boolean': // 4
                formCtrl = (
                  <Checkbox checked={this.state.formData[fieldModel.id]}
                    onChange={this.handleChange.bind(this, fieldModel.id)}
                  />
                );
                break;
            }
            return (
              <FormGroup
                key={index}
                controlId={`formBasicText-${fieldModel.id}`}
              >
                <ControlLabel>{fieldModel.label}</ControlLabel>
                {formCtrl}
                <FormControl.Feedback />
              </FormGroup>
            );
          })
        }
        <Button onClick={this.handleSubmit} type="submit">保存</Button>
        <Button onClick={this.handleReset} type="reset">清空</Button>
      </form>
    );
  }
});
<|endoftext|>


<commit_before>import { action, computed, observe, observable, asMap } from 'mobx';
import _ from 'lodash';

import Base from './Base';
import Validator from './Validator';
import Events from './Events';
import State from './State';
import Field from './Field';

export default class Form extends Base {

  name;

  state;

  validator;

  @observable $validating = false;

  @observable fields = observable.map ? observable.map({}) : asMap({});

  constructor(initial = {}, {

    options = {},
    plugins = {},
    bindings = {},

  } = {}) {
    super();

    const data = _.merge({ options, plugins, bindings }, { setup: initial });

    _.map(data, (val, key) =>
      _.isFunction(this[key])
        && _.merge(data, { [key]: this[key](this) }));

    this.name = data.name;

    this.state = new State({
      form: this,
      initial: data.setup,
      options: data.options,
      bindings: data.bindings,
    });

    this.validator = new Validator({
      options: this.state.options,
      plugins: data.plugins,
      schema: data.setup.schema,
    });

    this.initFields(data.setup);

    this.validateOnInit();

    // execute onInit() if exist
    if (_.isFunction(this.onInit)) this.onInit(this);
  }

  /* ------------------------------------------------------------------ */
  /* COMPUTED */

  @computed get validating() {
    return this.$validating;
  }

  @computed get hasError() {
    return _.isString(this.validator.genericErrorMessage)
     || this.check('hasError', true);
  }

  @computed get isValid() {
    return !_.isString(this.validator.genericErrorMessage)
      && this.check('isValid', true);
  }

  @computed get isDirty() {
    return this.check('isDirty', true);
  }

  @computed get isPristine() {
    return this.check('isPristine', true);
  }

  @computed get isDefault() {
    return this.check('isDefault', true);
  }

  @computed get isEmpty() {
    return this.check('isEmpty', true);
  }

  @computed get focus() {
    return this.check('focus', true);
  }

  @computed get touched() {
    return this.check('touched', true);
  }

  @computed get changed() {
    return this.check('changed', true);
  }

  @computed get disabled() {
    return this.check('disabled', true);
  }

  @computed get error() {
    return this.validator.genericErrorMessage;
  }

  /* ------------------------------------------------------------------ */
  /* EVENTS */

  /**
    On Submit
   */
  onSubmit = (e, o = {}) => {
    e.preventDefault();
    this.submit(o);
  };
}

export const prototypes = {

  makeField(data) {
    return new Field(data);
  },

  on(event, callback) {
    observe(Events.getRunning(), ({ name, oldValue, object }) => {
      if (!event.includes('@')) return;

      const $event = _.split(event, '@');
      const $path = Events.path(name);

      if ($event[0] === name
        && $event[1] === $path
        && oldValue && !object[name]) {
        callback({ form: this, path: $path });
      }
    });
  },

  validateOnInit() {
    if (this.state.options.get('validateOnInit') === false) return;
    // execute validation on form initialization
    this.validate({ showErrors: this.state.options.get('showErrorsOnInit') });
  },

  validate(opt = {}, obj = {}) {
    action(() => (this.$validating = true))();
    this.validator.resetGenericError();

    const $path = _.has(opt, 'path') ? opt.path : opt;

    let $field = null;
    if (_.has(opt, 'field')) $field = opt.field;
    else if (_.isString($path)) $field = this.select($path);

    let showErrors = true;
    if (_.has(opt, 'showErrors')) showErrors = opt.showErrors;
    else if (_.has(obj, 'showErrors')) showErrors = obj.showErrors;

    let related = false;
    if (_.has(opt, 'related')) related = opt.related;
    else if (_.has(obj, 'related')) related = obj.related;

    Events.setRunning('validate', true, $field ? $field.path : null);

    // look running events and choose when show errors messages
    const notShowErrorsEvents = ['clear', 'reset'];
    if (this.state.options.get('showErrorsOnUpdate') === false) notShowErrorsEvents.push('update');
    const $showErrors = showErrors && !Events.running(notShowErrorsEvents);

    if (_.isPlainObject(opt) && !_.isString($path)) {
      // validate all fields
      return new Promise((resolve) => {
        this.validator.validateAll({
          related,
          form: this,
          showErrors: $showErrors,
        });
        // wait all promises then resolve
        return Promise.all(this.validator.promises)
          .then(action(() => (this.$validating = false)))
          .then(() => Events.setRunning('validate', false))
          .then(() => resolve(this.isValid));
      });
    }

    // validate single field
    return new Promise((resolve) => {
      // validate single field by path
      this.validator
        .validateField({
          related,
          form: this,
          path: $path,
          field: $field,
          showErrors: $showErrors,
        });
      // wait all promises then resolve
      return Promise.all(this.validator.promises)
        .then(() => Events.setRunning('validate', false))
        .then(() => resolve($field.isValid));
    });
  },

  invalidate(message) {
    this.validator.invalidate(message);
  },

  /* ------------------------------------------------------------------ */
  /* ACTIONS */

  /**
    Clear Form Fields
  */
  @action clear() {
    this.deepAction('clear', this.fields);
  },

  /**
    Reset Form Fields
  */
  @action reset() {
    this.deepAction('reset', this.fields);
  },

  /**
    Submit Form
  */
  @action submit(o = {}) {
    const execOnSuccess = _.has(o, 'onSuccess') ? o.onSuccess : this.onSuccess;
    const execOnError = _.has(o, 'onError') ? o.onError : this.onError;

    this.validate()
      .then(isValid => isValid
        ? execOnSuccess(this)
        : execOnError(this));
  },

};
<commit_msg>fix: form $validating prop on single field<commit_after>import { action, computed, observe, observable, asMap } from 'mobx';
import _ from 'lodash';

import Base from './Base';
import Validator from './Validator';
import Events from './Events';
import State from './State';
import Field from './Field';

export default class Form extends Base {

  name;

  state;

  validator;

  @observable $validating = false;

  @observable fields = observable.map ? observable.map({}) : asMap({});

  constructor(initial = {}, {

    options = {},
    plugins = {},
    bindings = {},

  } = {}) {
    super();

    const data = _.merge({ options, plugins, bindings }, { setup: initial });

    _.map(data, (val, key) =>
      _.isFunction(this[key])
        && _.merge(data, { [key]: this[key](this) }));

    this.name = data.name;

    this.state = new State({
      form: this,
      initial: data.setup,
      options: data.options,
      bindings: data.bindings,
    });

    this.validator = new Validator({
      options: this.state.options,
      plugins: data.plugins,
      schema: data.setup.schema,
    });

    this.initFields(data.setup);

    this.validateOnInit();

    // execute onInit() if exist
    if (_.isFunction(this.onInit)) this.onInit(this);
  }

  /* ------------------------------------------------------------------ */
  /* COMPUTED */

  @computed get validating() {
    return this.$validating;
  }

  @computed get hasError() {
    return _.isString(this.validator.genericErrorMessage)
     || this.check('hasError', true);
  }

  @computed get isValid() {
    return !_.isString(this.validator.genericErrorMessage)
      && this.check('isValid', true);
  }

  @computed get isDirty() {
    return this.check('isDirty', true);
  }

  @computed get isPristine() {
    return this.check('isPristine', true);
  }

  @computed get isDefault() {
    return this.check('isDefault', true);
  }

  @computed get isEmpty() {
    return this.check('isEmpty', true);
  }

  @computed get focus() {
    return this.check('focus', true);
  }

  @computed get touched() {
    return this.check('touched', true);
  }

  @computed get changed() {
    return this.check('changed', true);
  }

  @computed get disabled() {
    return this.check('disabled', true);
  }

  @computed get error() {
    return this.validator.genericErrorMessage;
  }

  /* ------------------------------------------------------------------ */
  /* EVENTS */

  /**
    On Submit
   */
  onSubmit = (e, o = {}) => {
    e.preventDefault();
    this.submit(o);
  };
}

export const prototypes = {

  makeField(data) {
    return new Field(data);
  },

  on(event, callback) {
    observe(Events.getRunning(), ({ name, oldValue, object }) => {
      if (!event.includes('@')) return;

      const $event = _.split(event, '@');
      const $path = Events.path(name);

      if ($event[0] === name
        && $event[1] === $path
        && oldValue && !object[name]) {
        callback({ form: this, path: $path });
      }
    });
  },

  validateOnInit() {
    if (this.state.options.get('validateOnInit') === false) return;
    // execute validation on form initialization
    this.validate({ showErrors: this.state.options.get('showErrorsOnInit') });
  },

  validate(opt = {}, obj = {}) {
    action(() => (this.$validating = true))();
    this.validator.resetGenericError();

    const $path = _.has(opt, 'path') ? opt.path : opt;

    let $field = null;
    if (_.has(opt, 'field')) $field = opt.field;
    else if (_.isString($path)) $field = this.select($path);

    let showErrors = true;
    if (_.has(opt, 'showErrors')) showErrors = opt.showErrors;
    else if (_.has(obj, 'showErrors')) showErrors = obj.showErrors;

    let related = false;
    if (_.has(opt, 'related')) related = opt.related;
    else if (_.has(obj, 'related')) related = obj.related;

    Events.setRunning('validate', true, $field ? $field.path : null);

    // look running events and choose when show errors messages
    const notShowErrorsEvents = ['clear', 'reset'];
    if (this.state.options.get('showErrorsOnUpdate') === false) notShowErrorsEvents.push('update');
    const $showErrors = showErrors && !Events.running(notShowErrorsEvents);

    if (_.isPlainObject(opt) && !_.isString($path)) {
      // validate all fields
      return new Promise((resolve) => {
        this.validator.validateAll({
          related,
          form: this,
          showErrors: $showErrors,
        });
        // wait all promises then resolve
        return Promise.all(this.validator.promises)
          .then(action(() => (this.$validating = false)))
          .then(() => Events.setRunning('validate', false))
          .then(() => resolve(this.isValid));
      });
    }

    // validate single field
    return new Promise((resolve) => {
      // validate single field by path
      this.validator
        .validateField({
          related,
          form: this,
          path: $path,
          field: $field,
          showErrors: $showErrors,
        });
      // wait all promises then resolve
      return Promise.all(this.validator.promises)
        .then(action(() => (this.$validating = false)))
        .then(() => Events.setRunning('validate', false))
        .then(() => resolve($field.isValid));
    });
  },

  invalidate(message) {
    this.validator.invalidate(message);
  },

  /* ------------------------------------------------------------------ */
  /* ACTIONS */

  /**
    Clear Form Fields
  */
  @action clear() {
    this.deepAction('clear', this.fields);
  },

  /**
    Reset Form Fields
  */
  @action reset() {
    this.deepAction('reset', this.fields);
  },

  /**
    Submit Form
  */
  @action submit(o = {}) {
    const execOnSuccess = _.has(o, 'onSuccess') ? o.onSuccess : this.onSuccess;
    const execOnError = _.has(o, 'onError') ? o.onError : this.onError;

    this.validate()
      .then(isValid => isValid
        ? execOnSuccess(this)
        : execOnError(this));
  },

};
<|endoftext|>


<commit_before>import Element from './Element';
import Formio from './Formio';
import Wizard from './Wizard';
import PDF from './PDF';
import Webform from './Webform';
import templates from './templates';
import { sanitize } from 'dompurify';

export default class Form extends Element {
  /**
   * Creates an easy to use interface for embedding webforms, pdfs, and wizards into your application.
   *
   * @param {Object} element - The DOM element you wish to render this form within.
   * @param {Object | string} form - Either a Form JSON schema or the URL of a hosted form via. form.io.
   * @param {Object} options - The options to create a new form instance.
   * @param {boolean} options.readOnly - Set this form to readOnly
   * @param {boolean} options.noAlerts - Set to true to disable the alerts dialog.
   * @param {boolean} options.i18n - The translation file for this rendering. @see https://github.com/formio/formio.js/blob/master/i18n.js
   * @param {boolean} options.template - Provides a way to inject custom logic into the creation of every element rendered within the form.
   *
   * @example
   * import Form from 'formiojs/Form';
   * const form = new Form(document.getElementById('formio'), 'https://examples.form.io/example');
   * form.build();
   */
  constructor(...args) {
    super(...args);
    this.ready = new Promise((resolve, reject) => {
      this.readyResolve = resolve;
      this.readyReject = reject;
    });

    this.instance = null;
    if (args[0] instanceof HTMLElement) {
      this.element = args[0];
      this.options = args[2];
      this.setForm(args[1])
        .then(() => this.readyResolve(this.instance))
        .catch(this.readyReject);
    }
    else if (args[0]) {
      this.element = null;
      this.options = args[1];
      this.setForm(args[0])
        .then(() => this.readyResolve(this.instance))
        .catch(this.readyReject);
    }
    else {
      this.element = null;
      this.options = null;
    }
  }

  /**
   * Create a new form instance provided the display of the form.
   *
   * @param {string} display - The display of the form, either "wizard", "form", or "pdf"
   * @return {*}
   */
  create(display) {
    if (this.options && this.options.flatten) {
      display = 'form';
    }
    switch (display) {
      case 'wizard':
        return new Wizard(this.element, this.options);
      case 'pdf':
        return new PDF(this.element, this.options);
      default:
        return new Webform(this.element, this.options);
    }
  }

  /**
   * Sets the form. Either as JSON or a URL to a form JSON schema.
   *
   * @param {string|object} formParam - Either the form JSON or the URL of the form json.
   * @return {*}
   */
  set form(formParam) {
    return this.setForm(formParam);
  }

  setForm(formParam) {
    formParam = formParam || this.form;
    if (this.instance) {
      this.instance.destroy();
    }
    if (typeof formParam === 'string') {
      return (new Formio(formParam)).loadForm().then((form) => {
        this.instance = this.create(form.display);
        this.instance.url = formParam;
        this.instance.nosubmit = false;
        this._form = this.instance.form = form;
        return this.instance.ready.then(() => {
          if (this.instance.loadSubmission) {
            return this.instance.loadSubmission();
          }
        });
      });
    }
    else {
      this.instance = this.create(formParam.display);
      this._form = this.instance.form = formParam;
      return this.instance.ready;
    }
  }

  /**
   * Returns the loaded forms JSON.
   *
   * @return {object} - The loaded form's JSON
   */
  get form() {
    return this._form;
  }

  /**
   * Changes the display of the form.
   *
   * @param {string} display - The display to set this form. Either "wizard", "form", or "pdf"
   * @return {Promise<T>}
   */
  setDisplay(display) {
    this.form.display = display;
    this.setForm(this.form);
    return this.build();
  }

  empty() {
    if (this.element) {
      while (this.element.firstChild) {
        this.element.removeChild(this.element.firstChild);
      }
    }
  }

  static embed(embed) {
    return new Promise((resolve, reject) => {
      if (!embed || !embed.src) {
        resolve();
      }
      const id = this.id || `formio-${Math.random().toString(36).substring(7)}`;
      const className = embed.class || 'formio-form-wrapper';
      let code = embed.styles ? `<link rel="stylesheet" href="${embed.styles}">` : '';
      code += `<div id="${id}" class="${className}"></div>`;
      document.write(code);
      let attempts = 0;
      const wait = setInterval(() => {
        attempts++;
        const formElement = document.getElementById(id);
        if (formElement || attempts > 10) {
          resolve(new Form(formElement, embed.src).ready);
          clearInterval(wait);
        }
      }, 10);
    });
  }

  /**
   * Sanitize an html string.
   *
   * @param string
   * @returns {*}
   */
  sanitize(dirty) {
    return sanitize(dirty, {
      ADD_ATTR: ['ref'],
      USE_PROFILES: {
        html: true
      }
    });
  }

  setContent(element, content) {
    if (element instanceof HTMLElement) {
      element.innerHTML = this.sanitize(content);
      return true;
    }
    return false;
  }

  /**
   * Build a new form.
   *
   * @return {Promise<T>}
   */
  build() {
    if (!this.instance) {
      return Promise.reject('Form not ready. Use form.ready promise');
    }

    if (!this.element) {
      return Promise.reject('No DOM element for form.');
    }

    // Add temporary loader.
    const template = (this.options && this.options.template) ? this.options.template : 'bootstrap';
    const loader = templates[template].loader || templates.bootstrap.loader;
    this.setContent(this.element, loader.form);

    return this.render().then(html => {
      this.setContent(this.element, html);
      return this.attach(this.element).then(() => this.instance);
    })
      .then((param) => {
        this.emit('build', param);
        return param;
      });
  }

  render() {
    if (!this.instance) {
      return Promise.reject('Form not ready. Use form.ready promise');
    }
    return Promise.resolve(this.instance.render())
      .then((param) => {
        this.emit('render', param);
        return param;
      });
  }

  attach(element) {
    if (!this.instance) {
      return Promise.reject('Form not ready. Use form.ready promise');
    }
    this.element = element;
    return this.instance.attach(this.element)
      .then((param) => {
        this.emit('attach', param);
        return param;
      });
  }
}

// Allow simple embedding.
Formio.embedForm = (embed) => Form.embed(embed);

/**
 * Factory that creates a new form based on the form parameters.
 *
 * @param element {HMTLElement} - The HTML Element to add this form to.
 * @param form {string|Object} - The src of the form, or a form object.
 * @param options {Object} - The options to create this form.
 *
 * @return {Promise} - When the form is instance is ready.
 */
Formio.createForm = (...args) => {
  return (new Form(...args).ready);
};

Formio.Form = Form;
<commit_msg>Fix crash when instantiating forms.<commit_after>import Element from './Element';
import Formio from './Formio';
import Wizard from './Wizard';
import PDF from './PDF';
import Webform from './Webform';
import templates from './templates';
import { sanitize } from 'dompurify';

export default class Form extends Element {
  /**
   * Creates an easy to use interface for embedding webforms, pdfs, and wizards into your application.
   *
   * @param {Object} element - The DOM element you wish to render this form within.
   * @param {Object | string} form - Either a Form JSON schema or the URL of a hosted form via. form.io.
   * @param {Object} options - The options to create a new form instance.
   * @param {boolean} options.readOnly - Set this form to readOnly
   * @param {boolean} options.noAlerts - Set to true to disable the alerts dialog.
   * @param {boolean} options.i18n - The translation file for this rendering. @see https://github.com/formio/formio.js/blob/master/i18n.js
   * @param {boolean} options.template - Provides a way to inject custom logic into the creation of every element rendered within the form.
   *
   * @example
   * import Form from 'formiojs/Form';
   * const form = new Form(document.getElementById('formio'), 'https://examples.form.io/example');
   * form.build();
   */
  constructor(...args) {
    super(...args);
    this.ready = new Promise((resolve, reject) => {
      this.readyResolve = resolve;
      this.readyReject = reject;
    });

    this.instance = null;
    if (args[0] instanceof HTMLElement) {
      this.element = args[0];
      this.options = args[2];
      this.setForm(args[1])
        .then(() => this.readyResolve(this.instance))
        .catch(this.readyReject);
    }
    else if (args[0]) {
      this.element = null;
      this.options = args[1];
      this.setForm(args[0])
        .then(() => this.readyResolve(this.instance))
        .catch(this.readyReject);
    }
    else {
      this.element = null;
      this.options = null;
    }
  }

  /**
   * Create a new form instance provided the display of the form.
   *
   * @param {string} display - The display of the form, either "wizard", "form", or "pdf"
   * @return {*}
   */
  create(display) {
    if (this.options && this.options.flatten) {
      display = 'form';
    }
    switch (display) {
      case 'wizard':
        return new Wizard(this.element, this.options);
      case 'pdf':
        return new PDF(this.element, this.options);
      default:
        return new Webform(this.element, this.options);
    }
  }

  /**
   * Sets the form. Either as JSON or a URL to a form JSON schema.
   *
   * @param {string|object} formParam - Either the form JSON or the URL of the form json.
   * @return {*}
   */
  set form(formParam) {
    return this.setForm(formParam);
  }

  setForm(formParam) {
    formParam = formParam || this.form;
    if (typeof formParam === 'string') {
      return (new Formio(formParam)).loadForm().then((form) => {
        this.instance = this.instance || this.create(form.display);
        this.instance.url = formParam;
        this.instance.nosubmit = false;
        this._form = this.instance.form = form;
        return this.instance.ready.then(() => {
          if (this.instance.loadSubmission) {
            return this.instance.loadSubmission();
          }
        });
      });
    }
    else {
      this.instance = this.instance || this.create(formParam.display);
      this._form = this.instance.form = formParam;
      return this.instance.ready;
    }
  }

  /**
   * Returns the loaded forms JSON.
   *
   * @return {object} - The loaded form's JSON
   */
  get form() {
    return this._form;
  }

  /**
   * Changes the display of the form.
   *
   * @param {string} display - The display to set this form. Either "wizard", "form", or "pdf"
   * @return {Promise<T>}
   */
  setDisplay(display) {
    this.form.display = display;
    if (this.instance && this.instance.form.display !== display) {
      if (this.instance) {
        this.instance.destroy();
      }
      this.instance = this.create(display);
    }
    return this.setForm(this.form);
  }

  empty() {
    if (this.element) {
      while (this.element.firstChild) {
        this.element.removeChild(this.element.firstChild);
      }
    }
  }

  static embed(embed) {
    return new Promise((resolve, reject) => {
      if (!embed || !embed.src) {
        resolve();
      }
      const id = this.id || `formio-${Math.random().toString(36).substring(7)}`;
      const className = embed.class || 'formio-form-wrapper';
      let code = embed.styles ? `<link rel="stylesheet" href="${embed.styles}">` : '';
      code += `<div id="${id}" class="${className}"></div>`;
      document.write(code);
      let attempts = 0;
      const wait = setInterval(() => {
        attempts++;
        const formElement = document.getElementById(id);
        if (formElement || attempts > 10) {
          resolve(new Form(formElement, embed.src).ready);
          clearInterval(wait);
        }
      }, 10);
    });
  }

  /**
   * Sanitize an html string.
   *
   * @param string
   * @returns {*}
   */
  sanitize(dirty) {
    return sanitize(dirty, {
      ADD_ATTR: ['ref'],
      USE_PROFILES: {
        html: true
      }
    });
  }

  setContent(element, content) {
    if (element instanceof HTMLElement) {
      element.innerHTML = this.sanitize(content);
      return true;
    }
    return false;
  }

  /**
   * Build a new form.
   *
   * @return {Promise<T>}
   */
  build() {
    if (!this.instance) {
      return Promise.reject('Form not ready. Use form.ready promise');
    }

    if (!this.element) {
      return Promise.reject('No DOM element for form.');
    }

    // Add temporary loader.
    const template = (this.options && this.options.template) ? this.options.template : 'bootstrap';
    const loader = templates[template].loader || templates.bootstrap.loader;
    this.setContent(this.element, loader.form);

    return this.render().then(html => {
      this.setContent(this.element, html);
      return this.attach(this.element).then(() => this.instance);
    })
      .then((param) => {
        this.emit('build', param);
        return param;
      });
  }

  render() {
    if (!this.instance) {
      return Promise.reject('Form not ready. Use form.ready promise');
    }
    return Promise.resolve(this.instance.render())
      .then((param) => {
        this.emit('render', param);
        return param;
      });
  }

  attach(element) {
    if (!this.instance) {
      return Promise.reject('Form not ready. Use form.ready promise');
    }
    this.element = element;
    return this.instance.attach(this.element)
      .then((param) => {
        this.emit('attach', param);
        return param;
      });
  }
}

// Allow simple embedding.
Formio.embedForm = (embed) => Form.embed(embed);

/**
 * Factory that creates a new form based on the form parameters.
 *
 * @param element {HMTLElement} - The HTML Element to add this form to.
 * @param form {string|Object} - The src of the form, or a form object.
 * @param options {Object} - The options to create this form.
 *
 * @return {Promise} - When the form is instance is ready.
 */
Formio.createForm = (...args) => {
  return (new Form(...args).ready);
};

Formio.Form = Form;
<|endoftext|>


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

/**
 * External dependencies
 */
import invariant from 'invariant';
import { keyBy, omit } from 'lodash';

/**
 * Internal dependencies
 */
import { get } from 'googlesitekit-api';

const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION';
const FETCH_NOTIFICATIONS = 'FETCH_NOTIFICATIONS';
const RECEIVE_NOTIFICATIONS = 'RECEIVE_NOTIFICATIONS';
const RECEIVE_NOTIFICATIONS_FAILED = 'RECEIVE_NOTIFICATIONS_FAILED';

// This should remain private for now, hence not be exported on Data.
export const createNotificationsStore = ( type, identifier, datapoint ) => {
	const INITIAL_STATE = {
		serverNotifications: undefined,
		clientNotifications: {},
		isFetchingNotifications: false,
	};

	const actions = {
		addNotification( notification ) {
			return {
				payload: { notification },
				type: ADD_NOTIFICATION,
			};
		},

		removeNotification( id ) {
			return {
				payload: { id },
				type: REMOVE_NOTIFICATION,
			};
		},

		fetchNotifications() {
			return {
				payload: {},
				type: FETCH_NOTIFICATIONS,
			};
		},

		receiveNotifications( notifications ) {
			invariant( notifications, 'notifications is required.' );

			return {
				payload: { notifications },
				type: RECEIVE_NOTIFICATIONS,
			};
		},

		receiveNotificationsFailed( error ) {
			invariant( error, 'error is required.' );

			return {
				payload: {},
				type: RECEIVE_NOTIFICATIONS_FAILED,
			};
		},
	};

	const controls = {
		[ FETCH_NOTIFICATIONS ]: () => {
			return get( type, identifier, datapoint );
		},
	};

	const reducer = ( state = INITIAL_STATE, action ) => {
		switch ( action.type ) {
			case ADD_NOTIFICATION: {
				const { notification } = action.payload;

				return {
					...state,
					clientNotifications: {
						...state.clientNotifications,
						...keyBy( [ notification ], 'id' ),
					},
				};
			}

			case REMOVE_NOTIFICATION: {
				const { id } = action.payload;

				return {
					...state,
					clientNotifications: omit( state.clientNotifications, [ id ] ),
				};
			}

			case FETCH_NOTIFICATIONS: {
				return {
					...state,
					isFetchingNotifications: true,
				};
			}

			case RECEIVE_NOTIFICATIONS: {
				const { notifications } = action.payload;

				return {
					...state,
					isFetchingNotifications: false,
					serverNotifications: {
						...keyBy( notifications, 'id' ),
					},
				};
			}

			case RECEIVE_NOTIFICATIONS_FAILED: {
				return {
					...state,
					isFetchingNotifications: false,
				};
			}

			default: {
				return { ...state };
			}
		}
	};

	const resolvers = {
		*getNotifications() {
			try {
				const notifications = yield actions.fetchNotifications();
				return actions.receiveNotifications( notifications );
			} catch ( err ) {
				return actions.receiveNotificationsFailed( err );
			}
		},
	};

	const selectors = {
		getNotifications( state ) {
			const { serverNotifications, clientNotifications } = state;

			return Object.values( {
				...( serverNotifications || {} ),
				...clientNotifications,
			} );
		},
	};

	return {
		INITIAL_STATE,
		actions,
		controls,
		reducer,
		resolvers,
		selectors,
	};
};
<commit_msg>Eliminate lodash usage in favor of ES6 in createNotificationsStore.<commit_after>/**
 * Provides API functions to create a datastore for notifications.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import { get } from 'googlesitekit-api';

const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION';
const FETCH_NOTIFICATIONS = 'FETCH_NOTIFICATIONS';
const RECEIVE_NOTIFICATIONS = 'RECEIVE_NOTIFICATIONS';
const RECEIVE_NOTIFICATIONS_FAILED = 'RECEIVE_NOTIFICATIONS_FAILED';

// This should remain private for now, hence not be exported on Data.
export const createNotificationsStore = ( type, identifier, datapoint ) => {
	const INITIAL_STATE = {
		serverNotifications: undefined,
		clientNotifications: {},
		isFetchingNotifications: false,
	};

	const actions = {
		addNotification( notification ) {
			return {
				payload: { notification },
				type: ADD_NOTIFICATION,
			};
		},

		removeNotification( id ) {
			return {
				payload: { id },
				type: REMOVE_NOTIFICATION,
			};
		},

		fetchNotifications() {
			return {
				payload: {},
				type: FETCH_NOTIFICATIONS,
			};
		},

		receiveNotifications( notifications ) {
			invariant( notifications, 'notifications is required.' );

			return {
				payload: { notifications },
				type: RECEIVE_NOTIFICATIONS,
			};
		},

		receiveNotificationsFailed( error ) {
			invariant( error, 'error is required.' );

			return {
				payload: {},
				type: RECEIVE_NOTIFICATIONS_FAILED,
			};
		},
	};

	const controls = {
		[ FETCH_NOTIFICATIONS ]: () => {
			return get( type, identifier, datapoint );
		},
	};

	const reducer = ( state = INITIAL_STATE, action ) => {
		switch ( action.type ) {
			case ADD_NOTIFICATION: {
				const { notification } = action.payload;

				return {
					...state,
					clientNotifications: {
						...state.clientNotifications,
						[ notification.id ]: notification,
					},
				};
			}

			case REMOVE_NOTIFICATION: {
				const { id } = action.payload;

				const newNotifications = { ...state.clientNotifications };
				delete newNotifications[ id ];

				return {
					...state,
					clientNotifications: newNotifications,
				};
			}

			case FETCH_NOTIFICATIONS: {
				return {
					...state,
					isFetchingNotifications: true,
				};
			}

			case RECEIVE_NOTIFICATIONS: {
				const { notifications } = action.payload;

				return {
					...state,
					isFetchingNotifications: false,
					serverNotifications: notifications.reduce(
						( acc, notification ) => {
							return {
								...acc,
								[ notification.id ]: notification,
							};
						},
						{}
					),
				};
			}

			case RECEIVE_NOTIFICATIONS_FAILED: {
				return {
					...state,
					isFetchingNotifications: false,
				};
			}

			default: {
				return { ...state };
			}
		}
	};

	const resolvers = {
		*getNotifications() {
			try {
				const notifications = yield actions.fetchNotifications();
				return actions.receiveNotifications( notifications );
			} catch ( err ) {
				return actions.receiveNotificationsFailed( err );
			}
		},
	};

	const selectors = {
		getNotifications( state ) {
			const { serverNotifications, clientNotifications } = state;

			return Object.values( {
				...( serverNotifications || {} ),
				...clientNotifications,
			} );
		},
	};

	return {
		INITIAL_STATE,
		actions,
		controls,
		reducer,
		resolvers,
		selectors,
	};
};
<|endoftext|>