<commit_before>"use strict";

SF.registerModule("Validator", function () {

    SF.Validator = function (_valOptions) {
        this.valOptions = $.extend({
            prefix: "",
            controllerUrl: null,
            showInlineErrors: true,
            fixedInlineErrorText: "*", //Set to "" for it to be populated from ModelState error messages
            parentDiv: "",
            requestExtraJsonData: null,
            ajaxError: null,
            errorSummaryId: null
        }, _valOptions);
    };

    SF.Validator.inputErrorClass = "input-validation-error";

    SF.Validator.prototype = {

        fieldErrorClass: "sf-field-validation-error",
        inputErrorClass: SF.Validator.inputErrorClass,
        summaryErrorClass: "validation-summary-errors",
        inlineErrorVal: "inlineVal",
        globalErrorsKey: "sfGlobalErrors",
        globalValidationSummary: "sfGlobalValidationSummary",

        pf: function (s) {
            return "#" + SF.compose(this.valOptions.prefix, s);
        },

        constructRequestData: function () {
            SF.log("Validator constructRequestData");
            var formChildren = SF.isEmpty(this.valOptions.parentDiv) ?
            $("form :input") : $("#" + this.valOptions.parentDiv + " :input")
                .add("#" + SF.Keys.tabId)
                .add("input:hidden[name=" + SF.Keys.antiForgeryToken + "]")
                .add("#" + SF.Keys.reactive);

            var searchControlInputs = $(".sf-search-control :input");
            formChildren = formChildren.not(searchControlInputs);

            var serializer = new SF.Serializer();
            serializer.add(formChildren.serialize())
                .add("prefix", this.valOptions.prefix)
                .add(this.valOptions.requestExtraJsonData);

            return serializer.serialize();
        },

        trySave: function () {
            SF.log("Validator trySave");
            SF.Notify.info(lang.signum.saving);
            var returnValue = false;
            var self = this;
            SF.ajax({
                type: "POST",
                url: this.valOptions.controllerUrl,
                async: false,
                data: this.constructRequestData(),
                success: function (msg) {
                    if (typeof msg === "object") {
                        if (msg.result != "ModelState") {
                            throw "Validator trySave: Incorrect result type " + msg.result;
                        }
                        var modelState = msg.ModelState;
                        returnValue = self.showErrors(modelState, true);
                        SF.Notify.error(lang.signum.error, 2000);
                    }
                    else {
                        if (SF.isEmpty(self.valOptions.parentDiv)) {
                            $("#content").html(msg.substring(msg.indexOf("<form"), msg.indexOf("</form>") + 7));
                            SF.triggerNewContent($("#content"));
                        }
                        else {
                            $("#" + self.valOptions.parentDiv).html(msg.substring(msg.indexOf("<form"), msg.indexOf("</form>") + 7));
                            SF.triggerNewContent($("#" + self.valOptions.parentDiv));
                        }
                        returnValue = true;
                        SF.Notify.info(lang.signum.saved, 2000);
                    }
                }
            });
            return returnValue;
        },

        validate: function () {
            SF.log("Validator validate");
            var returnValue = false;
            var self = this;
            SF.ajax({
                type: "POST",
                url: this.valOptions.controllerUrl,
                async: false,
                data: this.constructRequestData(),
                success: function (msg) {
                    if (typeof msg === "object") {
                        if (msg.result != "ModelState") {
                            throw "Validator validate: Incorrect result type " + msg.result;
                        }
                        var modelState = msg.ModelState;
                        returnValue = self.showErrors(modelState, true);
                    }
                    else {
                        returnValue = true;
                    }
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    if (self.ajaxError != null) {
                        self.ajaxError(xhr, ajaxOptions, thrownError);
                    }
                }
            });
            return returnValue;
        },

        isValid: function (modelState) {
            SF.log("Validator isValid");
            var controlID;
            for (controlID in modelState) {
                if (modelState.hasOwnProperty(controlID) && modelState[controlID].length) {
                    return false; //Stop as soon as I find an error
                }
            }
            return true;
        },

        showErrors: function (modelState, showPathErrors) {
            SF.log("Validator showErrors");
            //Remove previous errors
            $('.' + this.fieldErrorClass).replaceWith("");
            $('.' + this.inputErrorClass).removeClass(this.inputErrorClass);
            $('.' + this.summaryErrorClass).replaceWith("");

            var allErrors = [];
            var inlineErrorStart = '';
            var inlineErrorEnd = "";

            var controlID;
            for (controlID in modelState) {
                if (modelState.hasOwnProperty(controlID)) {
                    var errorsArray = modelState[controlID],
                        errorMessage = [],
                        partialErrors = [],
                        j;

                    for (j = 0; j < errorsArray.length; j++) {
                        errorMessage.push(errorsArray[j]);
                        partialErrors.push("
" + errorsArray[j] + "
");
                        allErrors.push(partialErrors);
                    }
                    var fixedInlineErrorText = this.valOptions.fixedInlineErrorText;

                    if (controlID != this.globalErrorsKey && controlID != "") {
                        var $control = $('#' + controlID);
                        $control.addClass(this.inputErrorClass);
                        if (this.valOptions.showInlineErrors && $control.hasClass(this.inlineErrorVal)) {
                            if ($control.next().hasClass("ui-datepicker-trigger")) {
                                if (SF.isEmpty(fixedInlineErrorText)) {
                                    $control.next().after(inlineErrorStart + errorMessage.join('') + inlineErrorEnd);
                                } else {
                                    $control.next().after(inlineErrorStart + fixedInlineErrorText + inlineErrorEnd);
                                }
                            }
                            else {
                                if (SF.isEmpty(fixedInlineErrorText)) {
                                    $control.after(inlineErrorStart + errorMessage.join('') + inlineErrorEnd);
                                }
                                else {
                                    $control.after(inlineErrorStart + fixedInlineErrorText + inlineErrorEnd);
                                }
                            }
                        }
                    }
                    this.setPathErrors(controlID, partialErrors.join(''), showPathErrors);
                }
            }

            if (allErrors.length) {
                SF.log("(Errors Validator showErrors): " + allErrors.join(''));
                return false;

            }
            return true;
        },

        //This will mark all the path with the error class, and it will also set summary error entries for the controls more inner than the current one
        setPathErrors: function (controlID, partialErrors, showPathErrors) {
            var pathPrefixes = (controlID != this.globalErrorsKey) ? SF.getPathPrefixes(controlID) : new Array("");
            for (var i = 0, l = pathPrefixes.length; i < l; i++) {
                var currPrefix = pathPrefixes[i];
                if (currPrefix != undefined) {
                    var isEqual = (currPrefix === this.valOptions.prefix);
                    var isMoreInner = !isEqual && (currPrefix.indexOf(this.valOptions.prefix) > -1);
                    if (showPathErrors || isMoreInner) {
                        $('#' + SF.compose(currPrefix, SF.Keys.toStr)).addClass(this.inputErrorClass);
                        $('#' + SF.compose(currPrefix, SF.Keys.link)).addClass(this.inputErrorClass);
                    }
                    if ((isMoreInner || isEqual) && $('#' + SF.compose(currPrefix, this.globalValidationSummary)).length > 0 && !SF.isEmpty(partialErrors)) {
                        var currentSummary = !SF.isEmpty(this.valOptions.errorSummaryId) ?
                                             $('#' + this.valOptions.errorSummaryId) :
                                             SF.isEmpty(this.valOptions.parentDiv) ?
                                                $('#' + SF.compose(currPrefix, this.globalValidationSummary)) :
                                                $('#' + this.valOptions.parentDiv + " #" + SF.compose(currPrefix, this.globalValidationSummary));
                        var summaryUL = currentSummary.children('.' + this.summaryErrorClass);
                        if (summaryUL.length === 0) {
                            currentSummary.append('
\n' + partialErrors + '
');
                        }
                        else {
                            summaryUL.append(partialErrors);
                        }
                    }
                }
            }
        }
    };

    //PartialValidatorOptions = ValidatorOptions + type + id + onFinish
    SF.PartialValidator = function (_pvalOptions) {
        var self = this;
        SF.Validator.call(this, $.extend({
            parentDiv: SF.compose(_pvalOptions.prefix, "panelPopup"),
            type: null,
            id: null
        }, _pvalOptions));

        this.constructRequestDataForSaving = function () {
            SF.log("PartialValidator constructRequestDataForSaving");
            var prefix = this.valOptions.prefix;
            var formChildren = $("#" + this.valOptions.parentDiv + " *, #" + SF.Keys.tabId + ", input:hidden[name=" + SF.Keys.antiForgeryToken + "]").add(SF.getInfoParams(prefix));
            formChildren = formChildren.not(".sf-search-control *, #" + SF.Keys.reactive);

            var serializer = new SF.Serializer();
            serializer.add(formChildren.serialize());

            if (!SF.isEmpty(prefix)) {
                serializer.add("prefix", prefix);
            }

            if (formChildren.filter(this.pf(SF.Keys.runtimeInfo)).length === 0) {
                serializer.add(SF.compose(prefix, SF.Keys.runtimeInfo),
                    new SF.RuntimeInfo(prefix).createValue(this.valOptions.type, '', 'n', ''));
            }

            serializer.add(this.valOptions.requestExtraJsonData);

            return serializer.serialize();
        };

        this.createValidatorResult = function (r) {
            var validatorResult = {
                "modelState": r["ModelState"],
                "isValid": this.isValid(r["ModelState"]),
                "newToStr": r[SF.Keys.toStr],
                "newLink": r[SF.Keys.link]
            };
            return validatorResult;
        };

        this.trySave = function () {
            SF.log("PartialValidator trySave");
            SF.Notify.info(lang.signum.saving);
            var validatorResult = null;
            var self = this;
            SF.ajax({
                type: "POST",
                url: this.valOptions.controllerUrl,
                async: false,
                data: this.constructRequestDataForSaving(),
                dataType: "JSON",
                success: function (result) {
                    validatorResult = self.createValidatorResult(result);
                    self.showErrors(validatorResult.modelState);
                }
            });
            if (validatorResult != null && validatorResult.isValid) {
                SF.Notify.info(lang.signum.saved, 2000);
            }
            else
                SF.Notify.error(lang.signum.error, 2000);
            return validatorResult;
        };

        this.constructRequestDataForValidating = function () {
            SF.log("PartialValidator constructRequestDataForValidating");

            //Send main form to be able to construct a typecontext if EmbeddedEntity
            var staticInfo = SF.StaticInfo(this.valOptions.prefix);
            var isEmbedded = false;
            if (staticInfo.find().length == 0 && !SF.isEmpty(this.valOptions.prefix)) { //If List => use parent
                var lastPrefix = this.valOptions.prefix.substr(0, this.valOptions.prefix.lastIndexOf(SF.Keys.separator));
                staticInfo = SF.StaticInfo(lastPrefix);
            }
            if (staticInfo.find().length > 0 && staticInfo.isEmbedded()) {
                isEmbedded = true;
            }

            var formChildren = null;
            if (isEmbedded) {
                formChildren = $("form :input, #" + SF.Keys.tabId + ", input:hidden[name=" + SF.Keys.antiForgeryToken + "]");
            }
            if (!SF.isEmpty(this.valOptions.parentDiv)) {
                if (formChildren == null) {
                    formChildren = $("#" + this.valOptions.parentDiv + " :input, #" + SF.Keys.tabId + ", input:hidden[name=" + SF.Keys.antiForgeryToken + "]");
                }
                else {
                    formChildren = formChildren.add($("#" + this.valOptions.parentDiv + " :input"));
                }
            }
            formChildren = formChildren.not(".sf-search-control :input, #" + SF.Keys.reactive);

            var serializer = new SF.Serializer().add(formChildren.serialize());

            var myRuntimeInfoKey = SF.compose(this.valOptions.prefix, SF.Keys.runtimeInfo);
            if (formChildren.filter("[name=" + myRuntimeInfoKey + "]").length === 0) {
                var info = new SF.RuntimeInfo(this.valOptions.prefix);
                var infoField = info.find();

                var value;

                if (SF.isEmpty(this.valOptions.type)) {
                    value = SF.isEmpty(info.runtimeType())
                        ? info.createValue(SF.StaticInfo(this.valOptions.prefix).singleType(), info.id(), 'n', '')
                        : infoField.val();
                }
                else {
                    if (infoField.length === 0) {
                        value = info.createValue(this.valOptions.type, SF.isEmpty(!this.valOptions.id) ? this.valOptions.id : '', 'n', '');
                    }
                    else {
                        var mixedVal = new SF.RuntimeInfo("Temp");
                        var currTicks = ($('#' + SF.Keys.reactive).length > 0) ? new Date().getTime() : "";
                        value = mixedVal.createValue(this.valOptions.type, this.valOptions.id, null, currTicks);
                    }
                }

                serializer.add(myRuntimeInfoKey, value);
            }

            serializer.add("prefix", this.valOptions.prefix);
            serializer.add(this.valOptions.requestExtraJsonData);

            return serializer.serialize();
        };

        this.validate = function () {
            SF.log("PartialValidator validate");
            var validatorResult = null;
            var self = this;
            SF.ajax({
                type: "POST",
                url: this.valOptions.controllerUrl,
                async: false,
                data: this.constructRequestDataForValidating(),
                dataType: "json",
                success: function (result) {
                    validatorResult = self.createValidatorResult(result);
                    self.showErrors(validatorResult.modelState);
                }
            });
            return validatorResult;
        };
    };

    SF.PartialValidator.prototype = new SF.Validator();

    SF.EntityIsValid = function (validationOptions, onSuccess) {
        SF.log("Validator EntityIsValid");

        SF.Notify.info(lang.signum.validating);

        var isValid;

        if (SF.isEmpty(validationOptions.prefix)) {
            isValid = new SF.Validator(validationOptions).validate();
        }
        else {
            var info = new SF.RuntimeInfo(validationOptions.prefix);
            isValid = new SF.PartialValidator($.extend(validationOptions, {
                type: info.runtimeType(),
                id: info.id()
            })).validate().isValid;
        }

        if (isValid) {
            SF.Notify.clear();
            if (onSuccess != null) {
                onSuccess();
            }
        }
        else {
            SF.Notify.error(lang.signum.error, 2000);
            alert(lang.signum.popupErrorsStop);
        }
    };
});
<commit_msg>OperationOptions has now a complete ValidationOptions<commit_after>"use strict";

SF.registerModule("Validator", function () {

    SF.Validator = function (_valOptions) {
        this.valOptions = $.extend({
            prefix: "",
            controllerUrl: null,
            showInlineErrors: true,
            fixedInlineErrorText: "*", //Set to "" for it to be populated from ModelState error messages
            parentDiv: "",
            requestExtraJsonData: null,
            ajaxError: null,
            errorSummaryId: null
        }, _valOptions);
    };

    SF.Validator.inputErrorClass = "input-validation-error";

    SF.Validator.prototype = {

        fieldErrorClass: "sf-field-validation-error",
        inputErrorClass: SF.Validator.inputErrorClass,
        summaryErrorClass: "validation-summary-errors",
        inlineErrorVal: "inlineVal",
        globalErrorsKey: "sfGlobalErrors",
        globalValidationSummary: "sfGlobalValidationSummary",

        pf: function (s) {
            return "#" + SF.compose(this.valOptions.prefix, s);
        },

        constructRequestData: function () {
            SF.log("Validator constructRequestData");
            var formChildren = SF.isEmpty(this.valOptions.parentDiv) ?
            $("form :input") : $("#" + this.valOptions.parentDiv + " :input")
                .add("#" + SF.Keys.tabId)
                .add("input:hidden[name=" + SF.Keys.antiForgeryToken + "]")
                .add("#" + SF.Keys.reactive);

            var searchControlInputs = $(".sf-search-control :input");
            formChildren = formChildren.not(searchControlInputs);

            var serializer = new SF.Serializer();
            serializer.add(formChildren.serialize())
                .add("prefix", this.valOptions.prefix)
                .add(this.valOptions.requestExtraJsonData);

            return serializer.serialize();
        },

        trySave: function () {
            SF.log("Validator trySave");
            SF.Notify.info(lang.signum.saving);
            var returnValue = false;
            var self = this;
            SF.ajax({
                type: "POST",
                url: this.valOptions.controllerUrl,
                async: false,
                data: this.constructRequestData(),
                success: function (msg) {
                    if (typeof msg === "object") {
                        if (msg.result != "ModelState") {
                            throw "Validator trySave: Incorrect result type " + msg.result;
                        }
                        var modelState = msg.ModelState;
                        returnValue = self.showErrors(modelState, true);
                        SF.Notify.error(lang.signum.error, 2000);
                    }
                    else {
                        if (SF.isEmpty(self.valOptions.parentDiv)) {
                            $("#content").html(msg.substring(msg.indexOf("<form"), msg.indexOf("</form>") + 7));
                            SF.triggerNewContent($("#content"));
                        }
                        else {
                            $("#" + self.valOptions.parentDiv).html(msg.substring(msg.indexOf("<form"), msg.indexOf("</form>") + 7));
                            SF.triggerNewContent($("#" + self.valOptions.parentDiv));
                        }
                        returnValue = true;
                        SF.Notify.info(lang.signum.saved, 2000);
                    }
                }
            });
            return returnValue;
        },

        validate: function () {
            SF.log("Validator validate");
            var returnValue = false;
            var self = this;
            SF.ajax({
                type: "POST",
                url: this.valOptions.controllerUrl,
                async: false,
                data: this.constructRequestData(),
                success: function (msg) {
                    if (typeof msg === "object") {
                        if (msg.result != "ModelState") {
                            throw "Validator validate: Incorrect result type " + msg.result;
                        }
                        var modelState = msg.ModelState;
                        returnValue = self.showErrors(modelState, true);
                    }
                    else {
                        returnValue = true;
                    }
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    if (self.ajaxError != null) {
                        self.ajaxError(xhr, ajaxOptions, thrownError);
                    }
                }
            });
            return returnValue;
        },

        isValid: function (modelState) {
            SF.log("Validator isValid");
            var controlID;
            for (controlID in modelState) {
                if (modelState.hasOwnProperty(controlID) && modelState[controlID].length) {
                    return false; //Stop as soon as I find an error
                }
            }
            return true;
        },

        showErrors: function (modelState, showPathErrors) {
            SF.log("Validator showErrors");
            //Remove previous errors
            $('.' + this.fieldErrorClass).replaceWith("");
            $('.' + this.inputErrorClass).removeClass(this.inputErrorClass);
            $('.' + this.summaryErrorClass).replaceWith("");

            var allErrors = [];
            var inlineErrorStart = '';
            var inlineErrorEnd = "";

            var controlID;
            for (controlID in modelState) {
                if (modelState.hasOwnProperty(controlID)) {
                    var errorsArray = modelState[controlID],
                        errorMessage = [],
                        partialErrors = [],
                        j;

                    for (j = 0; j < errorsArray.length; j++) {
                        errorMessage.push(errorsArray[j]);
                        partialErrors.push("
" + errorsArray[j] + "
");
                        allErrors.push(partialErrors);
                    }
                    var fixedInlineErrorText = this.valOptions.fixedInlineErrorText;

                    if (controlID != this.globalErrorsKey && controlID != "") {
                        var $control = $('#' + controlID);
                        $control.addClass(this.inputErrorClass);
                        if (this.valOptions.showInlineErrors && $control.hasClass(this.inlineErrorVal)) {
                            if ($control.next().hasClass("ui-datepicker-trigger")) {
                                if (SF.isEmpty(fixedInlineErrorText)) {
                                    $control.next().after(inlineErrorStart + errorMessage.join('') + inlineErrorEnd);
                                } else {
                                    $control.next().after(inlineErrorStart + fixedInlineErrorText + inlineErrorEnd);
                                }
                            }
                            else {
                                if (SF.isEmpty(fixedInlineErrorText)) {
                                    $control.after(inlineErrorStart + errorMessage.join('') + inlineErrorEnd);
                                }
                                else {
                                    $control.after(inlineErrorStart + fixedInlineErrorText + inlineErrorEnd);
                                }
                            }
                        }
                    }
                    this.setPathErrors(controlID, partialErrors.join(''), showPathErrors);
                }
            }

            if (allErrors.length) {
                SF.log("(Errors Validator showErrors): " + allErrors.join(''));
                return false;

            }
            return true;
        },

        //This will mark all the path with the error class, and it will also set summary error entries for the controls more inner than the current one
        setPathErrors: function (controlID, partialErrors, showPathErrors) {
            var pathPrefixes = (controlID != this.globalErrorsKey) ? SF.getPathPrefixes(controlID) : new Array("");
            for (var i = 0, l = pathPrefixes.length; i < l; i++) {
                var currPrefix = pathPrefixes[i];
                if (currPrefix != undefined) {
                    var isEqual = (currPrefix === this.valOptions.prefix);
                    var isMoreInner = !isEqual && (currPrefix.indexOf(this.valOptions.prefix) > -1);
                    if (showPathErrors || isMoreInner) {
                        $('#' + SF.compose(currPrefix, SF.Keys.toStr)).addClass(this.inputErrorClass);
                        $('#' + SF.compose(currPrefix, SF.Keys.link)).addClass(this.inputErrorClass);
                    }
                    if ((isMoreInner || isEqual) && $('#' + SF.compose(currPrefix, this.globalValidationSummary)).length > 0 && !SF.isEmpty(partialErrors)) {
                        var currentSummary = !SF.isEmpty(this.valOptions.errorSummaryId) ?
                                             $('#' + this.valOptions.errorSummaryId) :
                                             SF.isEmpty(this.valOptions.parentDiv) ?
                                                $('#' + SF.compose(currPrefix, this.globalValidationSummary)) :
                                                $('#' + this.valOptions.parentDiv + " #" + SF.compose(currPrefix, this.globalValidationSummary));
                        var summaryUL = currentSummary.children('.' + this.summaryErrorClass);
                        if (summaryUL.length === 0) {
                            currentSummary.append('
\n' + partialErrors + '
');
                        }
                        else {
                            summaryUL.append(partialErrors);
                        }
                    }
                }
            }
        }
    };

    //PartialValidatorOptions = ValidatorOptions + type + id + onFinish
    SF.PartialValidator = function (_pvalOptions) {
        var self = this;
        SF.Validator.call(this, $.extend({
            parentDiv: SF.compose(_pvalOptions.prefix, "panelPopup"),
            type: null,
            id: null
        }, _pvalOptions));

        this.constructRequestDataForSaving = function () {
            SF.log("PartialValidator constructRequestDataForSaving");
            var prefix = this.valOptions.prefix;
            var formChildren = $("#" + this.valOptions.parentDiv + " *, #" + SF.Keys.tabId + ", input:hidden[name=" + SF.Keys.antiForgeryToken + "]").add(SF.getInfoParams(prefix));
            formChildren = formChildren.not(".sf-search-control *, #" + SF.Keys.reactive);

            var serializer = new SF.Serializer();
            serializer.add(formChildren.serialize());

            if (!SF.isEmpty(prefix)) {
                serializer.add("prefix", prefix);
            }

            if (formChildren.filter(this.pf(SF.Keys.runtimeInfo)).length === 0) {
                serializer.add(SF.compose(prefix, SF.Keys.runtimeInfo),
                    new SF.RuntimeInfo(prefix).createValue(this.valOptions.type, '', 'n', ''));
            }

            serializer.add(this.valOptions.requestExtraJsonData);

            return serializer.serialize();
        };

        this.createValidatorResult = function (r) {
            var validatorResult = {
                "modelState": r["ModelState"],
                "isValid": this.isValid(r["ModelState"]),
                "newToStr": r[SF.Keys.toStr],
                "newLink": r[SF.Keys.link]
            };
            return validatorResult;
        };

        this.trySave = function () {
            SF.log("PartialValidator trySave");
            SF.Notify.info(lang.signum.saving);
            var validatorResult = null;
            var self = this;
            SF.ajax({
                type: "POST",
                url: this.valOptions.controllerUrl,
                async: false,
                data: this.constructRequestDataForSaving(),
                dataType: "JSON",
                success: function (result) {
                    validatorResult = self.createValidatorResult(result);
                    self.showErrors(validatorResult.modelState);
                }
            });
            if (validatorResult != null && validatorResult.isValid) {
                SF.Notify.info(lang.signum.saved, 2000);
            }
            else
                SF.Notify.error(lang.signum.error, 2000);
            return validatorResult;
        };

        this.constructRequestDataForValidating = function () {
            SF.log("PartialValidator constructRequestDataForValidating");

            //Send main form to be able to construct a typecontext if EmbeddedEntity
            var staticInfo = SF.StaticInfo(this.valOptions.prefix);
            var isEmbedded = false;
            if (staticInfo.find().length == 0 && !SF.isEmpty(this.valOptions.prefix)) { //If List => use parent
                var lastPrefix = this.valOptions.prefix.substr(0, this.valOptions.prefix.lastIndexOf(SF.Keys.separator));
                staticInfo = SF.StaticInfo(lastPrefix);
            }
            if (staticInfo.find().length > 0 && staticInfo.isEmbedded()) {
                isEmbedded = true;
            }

            var formChildren = null;
            if (isEmbedded) {
                formChildren = $("form :input, #" + SF.Keys.tabId + ", input:hidden[name=" + SF.Keys.antiForgeryToken + "]");
            }
            if (!SF.isEmpty(this.valOptions.parentDiv)) {
                if (formChildren == null) {
                    formChildren = $("#" + this.valOptions.parentDiv + " :input, #" + SF.Keys.tabId + ", input:hidden[name=" + SF.Keys.antiForgeryToken + "]");
                }
                else {
                    formChildren = formChildren.add($("#" + this.valOptions.parentDiv + " :input"));
                }
            }
            formChildren = formChildren.not(".sf-search-control :input, #" + SF.Keys.reactive);

            var serializer = new SF.Serializer().add(formChildren.serialize());

            var myRuntimeInfoKey = SF.compose(this.valOptions.prefix, SF.Keys.runtimeInfo);
            if (formChildren.filter("[name=" + myRuntimeInfoKey + "]").length === 0) {
                var info = new SF.RuntimeInfo(this.valOptions.prefix);
                var infoField = info.find();

                var value;

                if (SF.isEmpty(this.valOptions.type)) {
                    value = SF.isEmpty(info.runtimeType())
                        ? info.createValue(SF.StaticInfo(this.valOptions.prefix).singleType(), info.id(), 'n', '')
                        : infoField.val();
                }
                else {
                    if (infoField.length === 0) {
                        value = info.createValue(this.valOptions.type, SF.isEmpty(!this.valOptions.id) ? this.valOptions.id : '', 'n', '');
                    }
                    else {
                        var mixedVal = new SF.RuntimeInfo("Temp");
                        var currTicks = ($('#' + SF.Keys.reactive).length > 0) ? new Date().getTime() : "";
                        value = mixedVal.createValue(this.valOptions.type, this.valOptions.id, null, currTicks);
                    }
                }

                serializer.add(myRuntimeInfoKey, value);
            }

            serializer.add("prefix", this.valOptions.prefix);
            serializer.add(this.valOptions.requestExtraJsonData);

            return serializer.serialize();
        };

        this.validate = function () {
            SF.log("PartialValidator validate");
            var validatorResult = null;
            var self = this;
            SF.ajax({
                type: "POST",
                url: this.valOptions.controllerUrl,
                async: false,
                data: this.constructRequestDataForValidating(),
                dataType: "json",
                success: function (result) {
                    validatorResult = self.createValidatorResult(result);
                    self.showErrors(validatorResult.modelState);
                }
            });
            return validatorResult;
        };
    };

    SF.PartialValidator.prototype = new SF.Validator();

    SF.EntityIsValid = function (validationOptions, onSuccess, sender) {
        SF.log("Validator EntityIsValid");

        SF.Notify.info(lang.signum.validating);

        var isValid;

        if (SF.isEmpty(validationOptions.prefix)) {
            isValid = new SF.Validator(validationOptions).validate();
        }
        else {
            var info = new SF.RuntimeInfo(validationOptions.prefix);
            isValid = new SF.PartialValidator($.extend(validationOptions, {
                type: info.runtimeType(),
                id: info.id()
            })).validate().isValid;
        }

        if (isValid) {
            SF.Notify.clear();
            if (onSuccess != null) {
                if (typeof sender != "undefined") {
                    onSuccess.call(sender);
                }
                else {
                    onSuccess();
                }
            }
        }
        else {
            SF.Notify.error(lang.signum.error, 2000);
            alert(lang.signum.popupErrorsStop);
        }
    };
});
<|endoftext|>


<commit_before>var keywordBoxTipShowing = false;
var keywordChanged = false;

function redirectKeyword(keyword) {
    if (keyword.indexOf('/') >= 0) {
        alert("Keywords shouldn't contain '/' mate.");
    } else if (keyword == '') {
        document.location.href = '/';
    } else {
        document.location.href = '/keyword/' + keyword;
    }
}

function removeKeywordBoxTip() {
    if (!keywordBoxTipShowing) {
        return false;
    }

    $('#keyword-form #keyword-input').data('tooltipsy').hide();
    $('#keyword-form #keyword-input').data('tooltipsy').destroy();

    keywordBoxTipShowing = false;

    return true;
}

function showKeywordBoxTip(message) {
    if (keywordBoxTipShowing) {
        return false;
    }

    keywordBoxTipShowing = true;

    $('#keyword-form #keyword-input').tooltipsy({
        content: message,
        showEvent: null,
        hideEvent: null,
        offset: [0, 1],
        show: function(e, $el) {
            $el.fadeIn(300);
        },
    });
    $('#keyword-form #keyword-input').data('tooltipsy').show();

    return true;
}

$(document).ready(function() {
    $('#keyword-form #keyword-input').focus();

    $('#keyword-form').submit(function() {
        redirectKeyword($('#keyword-form #keyword-input').val());
    });

    $('#keyword-form #keyword-input').focus(function() {
        $(this).select();
    });

    $('#keyword-form #keyword-input').trigger('focus');

    if ($('#keyword-form #keyword-input').val() == '') {
        showKeywordBoxTip("Type a word and press enter.");
    } else {
        function showKeywordBoxTipMessage() {
            keywordChanged = true;
            showKeywordBoxTip("Press enter to change keyword.");
        }

        $('#keyword-form #keyword-input').on('input', function() {
            showKeywordBoxTipMessage();
        });

        $('#keyword-form #keyword-input').focus(function() {
            if (keywordChanged) {
                showKeywordBoxTipMessage();
            }
        });

        $('#keyword-form #keyword-input').on('blur', function() {
            removeKeywordBoxTip();
        });
    }
});
<commit_msg>Fix keyword tip bug on window resize or scroll<commit_after>var keywordBoxTipShowing = false;
var keywordBoxTipLastMessage = '';
var keywordChanged = false;

function redirectKeyword(keyword) {
    if (keyword.indexOf('/') >= 0) {
        alert("Keywords shouldn't contain '/' mate.");
    } else if (keyword == '') {
        document.location.href = '/';
    } else {
        document.location.href = '/keyword/' + keyword;
    }
}

function removeKeywordBoxTip() {
    if (!keywordBoxTipShowing) {
        return false;
    }

    $('#keyword-form #keyword-input').data('tooltipsy').hide();
    $('#keyword-form #keyword-input').data('tooltipsy').destroy();

    keywordBoxTipShowing = false;

    return true;
}

function reshowKeywordBoxTip() {
    if (keywordBoxTipShowing) {
        removeKeywordBoxTip();
        showKeywordBoxTip(keywordBoxTipLastMessage, false);
    }
}

function showKeywordBoxTip(message, fadeIn) {
    fadeIn = typeof fadeIn !== 'undefined' ? fadeIn : true;

    if (keywordBoxTipShowing) {
        return false;
    }

    keywordBoxTipShowing = true;
    keywordBoxTipLastMessage = message;

    $('#keyword-form #keyword-input').tooltipsy({
        content: message,
        showEvent: null,
        hideEvent: null,
        offset: [0, 1],
        show: fadeIn ? function(e, $el) {
            $el.fadeIn(300);
        } : function(e, $el) {
            $el.fadeIn(0);
        },
    });
    $('#keyword-form #keyword-input').data('tooltipsy').show();

    return true;
}

$(document).ready(function() {
    $('#keyword-form #keyword-input').focus();

    $('#keyword-form').submit(function() {
        redirectKeyword($('#keyword-form #keyword-input').val());
    });

    $('#keyword-form #keyword-input').focus(function() {
        $(this).select();
    });

    $('#keyword-form #keyword-input').trigger('focus');

    $(window).resize(function() {
        reshowKeywordBoxTip();
    });

    $(window).scroll(function() {
        reshowKeywordBoxTip();
    });

    if ($('#keyword-form #keyword-input').val() == '') {
        showKeywordBoxTip("Type a word and press enter.");
    } else {
        function showKeywordBoxTipMessage() {
            keywordChanged = true;
            showKeywordBoxTip("Press enter to change keyword.");
        }

        $('#keyword-form #keyword-input').on('input', function() {
            showKeywordBoxTipMessage();
        });

        $('#keyword-form #keyword-input').focus(function() {
            if (keywordChanged) {
                showKeywordBoxTipMessage();
            }
        });

        $('#keyword-form #keyword-input').on('blur', function() {
            removeKeywordBoxTip();
        });
    }
});
<|endoftext|>


<commit_before>/*
|--------------------------------------------------------------------------
| Utils
|--------------------------------------------------------------------------
*/

import path from 'path';
import fs from 'fs';
import util from 'util';
import globby from 'globby';

const mmd = require('music-metadata');

const stat = util.promisify(fs.stat);

/**
 * Parse an int to a more readable string
 *
 * @param int duration
 * @return string
 */

const parseDuration = (duration) => {
  if(duration !== null && duration !== undefined) {
    let hours   = parseInt(duration / 3600);
    let minutes = parseInt(duration / 60) % 60;
    let seconds = parseInt(duration % 60);

    hours = hours < 10 ? `0${hours}` : hours;
    minutes = minutes < 10 ? `0${minutes}` : minutes;
    seconds = seconds < 10 ? `0${seconds}` : seconds;
    let result = hours > 0 ? `${hours}:` : '';
    result += `${minutes}:${seconds}`;

    return result;
  }

  return '00:00';
};

/**
 * Parse an URI, encoding some characters
 *
 * @param string uri
 * @return string
 */
const parseUri = (uri) => {
  const root = process.platform === 'win32' ? '' : path.parse(uri).root;
  const location = uri
    .split(path.sep)
    .map((d, i) => {
      return i === 0 ? d : encodeURIComponent(d);
    })
    .reduce((a, b) => path.join(a, b));
  return `file://${root}${location}`;
};

/**
 * Parse data to be used by img/background-image with base64
 *
 * @param string format of the image
 * @param string data base64 string
 * @return string
 */

const parseBase64 = (format, data) => {
  return `data:image/${format};base64,${data}`;
};

/**
 * Sort an array of int by ASC or DESC, then remove all duplicates
 *
 * @param array  array of int to be sorted
 * @param string 'asc' or 'desc' depending of the sort needed
 * @return array
 */
const simpleSort = (array, sorting) => {
  if(sorting === 'asc') {
    array.sort((a, b) => {
      return a - b;
    });
  } else if (sorting === 'desc') {
    array.sort((a, b) => {
      return b - a;
    });
  }

  const result = [];
  array.forEach((item) => {
    if(!result.includes(item)) result.push(item);
  });

  return result;
};

/**
 * Strip accent from String. From https://jsperf.com/strip-accents
 *
 * @param String str
 * @return String
 */
const stripAccents = (str) => {
  const accents = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž';
  const fixes = 'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz';
  const split = accents.split('').join('|');
  const reg = new RegExp(`(${split})`, 'g');

  function replacement(a) {
    return fixes[accents.indexOf(a)] || '';
  }

  return str.replace(reg, replacement).toLowerCase();
};

/**
 * Remove duplicates (realpath) and useless children folders
 *
 * @param array the array of folders path
 * @return array
 */
const removeUselessFolders = (folders) => {
  // Remove duplicates
  let filteredFolders = folders.filter((elem, index) => {
    return folders.indexOf(elem) === index;
  });

  const foldersToBeRemoved = [];

  filteredFolders.forEach((folder, i) => {
    filteredFolders.forEach((subfolder, j) => {
      if(subfolder.includes(folder) && i !== j && !foldersToBeRemoved.includes(folder)) {
        foldersToBeRemoved.push(subfolder);
      }
    });
  });

  filteredFolders = filteredFolders.filter((elem) => {
    return !foldersToBeRemoved.includes(elem);
  });

  return filteredFolders;
};

/**
 * Cut an array in smaller chunks
 *
 * @param array the array to be chunked
 * @param int the length of each chunk
 * @return array
 */
const chunkArray = (array, chunkLength) => {
  const chunks = [];

  for(let i = 0, length = array.length; i < length; i += chunkLength) {
    chunks.push(array.slice(i, i + chunkLength));
  }

  return chunks;
};

const getDefaultMetadata = () => {
  return {
    album        : 'Unknown',
    artist       : ['Unknown artist'],
    disk         : {
      no: 0,
      of: 0,
    },
    duration     : 0,
    genre        : [],
    loweredMetas : {},
    path         : '',
    playCount    : 0,
    title        : '',
    track        : {
      no: 0,
      of: 0,
    },
    year         : null,
  };
};

const parseMusicMetadata = (data, trackPath) => {
  if (typeof data === 'object') {
    const { common, format } = data;

    const metadata = {
      album    : common.album,
      artist   : common.artists || (common.artist && [common.artist]) || (common.albumartist && [common.albumartist]),
      disk     : common.disk,
      duration : format.duration,
      genre    : common.genre,
      title    : common.title || path.parse(trackPath).base,
      track    : common.track,
      year     : common.year,
    };

    // HACK Yes, this is terribly hacky, I want to change that
    // remove all the undefined values to avoid conflicts with the spread
    // notation
    for (const key in metadata) {
      if (metadata[key] === undefined) {
        delete metadata[key];
      }
    }

    return metadata;
  }

  return {};
};

const getLoweredMeta = (metadata) => {
  return {
    artist: metadata.artist.map((meta) => stripAccents(meta.toLowerCase())),
    album: stripAccents(metadata.album.toLowerCase()),
    title: stripAccents(metadata.title.toLowerCase()),
    genre: metadata.genre.map((meta) => stripAccents(meta.toLowerCase())),
  };
};

/**
 * Get a file metadata
 *
 * @param path (string)
 * @return object
 *
 */
const getMetadata = async (trackPath) => {
  let data;

  try {
    const stats = await stat(trackPath);
    data = await mmd.parseFile(trackPath, { native: true, skipCovers: true, fileSize: stats.size, duration: true });
  } catch (err) {
    console.warn(`An error occured while reading ${trackPath} id3 tags: ${err}`);
  }

  // Let's try to define something with what we got so far...
  const parsedData = parseMusicMetadata(data, trackPath);
  const defaultMetadata = getDefaultMetadata();

  const metadata = {
    ...defaultMetadata,
    ...parsedData,
    path: trackPath,
  };

  metadata.loweredMetas = getLoweredMeta(metadata);

  // Let's try another wat to retrieve a track duration
  if (!metadata.duration) {
    try {
      metadata.duration = await getAudioDurationAsync(trackPath);
    } catch (err) {
      console.warn(`An error occured while getting ${trackPath} duration: ${err}`);
    }
  }

  return metadata;
};

const getAudioDurationAsync = (path) => {
  const audio = new Audio;

  return new Promise((resolve, reject) => {
    audio.addEventListener('loadedmetadata', () => {
      resolve(audio.duration);
    });

    audio.addEventListener('error', (e) => {
      const message = `Error getting audio duration: (${e.target.error.code}) ${path}`;
      reject(new Error(message));
    });

    audio.preload = 'metadata';
    audio.src = path;
  });
};

const fetchCover = async (trackPath) => {
  if(!trackPath) {
    return null;
  }

  const data = await mmd.parseFile(trackPath);
  const picture = data.common.picture[0];

  if(picture) { // If cover in id3
    return parseBase64(picture.format, picture.data.toString('base64'));
  }

  // scan folder for any cover image
  const folder = path.dirname(trackPath);
  const pattern = path.join(folder, '*');
  const matches = await globby(pattern, { follow: false });

  return matches.find((elem) => {
    const parsedPath = path.parse(elem);

    return ['album', 'albumart', 'folder', 'cover'].includes(parsedPath.name.toLowerCase())
      && ['.png', '.jpg', '.bmp', '.gif'].includes(parsedPath.ext.toLowerCase()) ;
  });
};

export default {
  parseDuration,
  parseUri,
  simpleSort,
  stripAccents,
  removeUselessFolders,
  chunkArray,
  getMetadata,
  fetchCover,
};
<commit_msg>Fix fetchCover warning<commit_after>/*
|--------------------------------------------------------------------------
| Utils
|--------------------------------------------------------------------------
*/

import path from 'path';
import fs from 'fs';
import util from 'util';
import globby from 'globby';

const mmd = require('music-metadata');

const stat = util.promisify(fs.stat);

/**
 * Parse an int to a more readable string
 *
 * @param int duration
 * @return string
 */

const parseDuration = (duration) => {
  if(duration !== null && duration !== undefined) {
    let hours   = parseInt(duration / 3600);
    let minutes = parseInt(duration / 60) % 60;
    let seconds = parseInt(duration % 60);

    hours = hours < 10 ? `0${hours}` : hours;
    minutes = minutes < 10 ? `0${minutes}` : minutes;
    seconds = seconds < 10 ? `0${seconds}` : seconds;
    let result = hours > 0 ? `${hours}:` : '';
    result += `${minutes}:${seconds}`;

    return result;
  }

  return '00:00';
};

/**
 * Parse an URI, encoding some characters
 *
 * @param string uri
 * @return string
 */
const parseUri = (uri) => {
  const root = process.platform === 'win32' ? '' : path.parse(uri).root;
  const location = uri
    .split(path.sep)
    .map((d, i) => {
      return i === 0 ? d : encodeURIComponent(d);
    })
    .reduce((a, b) => path.join(a, b));
  return `file://${root}${location}`;
};

/**
 * Parse data to be used by img/background-image with base64
 *
 * @param string format of the image
 * @param string data base64 string
 * @return string
 */

const parseBase64 = (format, data) => {
  return `data:image/${format};base64,${data}`;
};

/**
 * Sort an array of int by ASC or DESC, then remove all duplicates
 *
 * @param array  array of int to be sorted
 * @param string 'asc' or 'desc' depending of the sort needed
 * @return array
 */
const simpleSort = (array, sorting) => {
  if(sorting === 'asc') {
    array.sort((a, b) => {
      return a - b;
    });
  } else if (sorting === 'desc') {
    array.sort((a, b) => {
      return b - a;
    });
  }

  const result = [];
  array.forEach((item) => {
    if(!result.includes(item)) result.push(item);
  });

  return result;
};

/**
 * Strip accent from String. From https://jsperf.com/strip-accents
 *
 * @param String str
 * @return String
 */
const stripAccents = (str) => {
  const accents = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž';
  const fixes = 'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz';
  const split = accents.split('').join('|');
  const reg = new RegExp(`(${split})`, 'g');

  function replacement(a) {
    return fixes[accents.indexOf(a)] || '';
  }

  return str.replace(reg, replacement).toLowerCase();
};

/**
 * Remove duplicates (realpath) and useless children folders
 *
 * @param array the array of folders path
 * @return array
 */
const removeUselessFolders = (folders) => {
  // Remove duplicates
  let filteredFolders = folders.filter((elem, index) => {
    return folders.indexOf(elem) === index;
  });

  const foldersToBeRemoved = [];

  filteredFolders.forEach((folder, i) => {
    filteredFolders.forEach((subfolder, j) => {
      if(subfolder.includes(folder) && i !== j && !foldersToBeRemoved.includes(folder)) {
        foldersToBeRemoved.push(subfolder);
      }
    });
  });

  filteredFolders = filteredFolders.filter((elem) => {
    return !foldersToBeRemoved.includes(elem);
  });

  return filteredFolders;
};

/**
 * Cut an array in smaller chunks
 *
 * @param array the array to be chunked
 * @param int the length of each chunk
 * @return array
 */
const chunkArray = (array, chunkLength) => {
  const chunks = [];

  for(let i = 0, length = array.length; i < length; i += chunkLength) {
    chunks.push(array.slice(i, i + chunkLength));
  }

  return chunks;
};

const getDefaultMetadata = () => {
  return {
    album        : 'Unknown',
    artist       : ['Unknown artist'],
    disk         : {
      no: 0,
      of: 0,
    },
    duration     : 0,
    genre        : [],
    loweredMetas : {},
    path         : '',
    playCount    : 0,
    title        : '',
    track        : {
      no: 0,
      of: 0,
    },
    year         : null,
  };
};

const parseMusicMetadata = (data, trackPath) => {
  if (typeof data === 'object') {
    const { common, format } = data;

    const metadata = {
      album    : common.album,
      artist   : common.artists || (common.artist && [common.artist]) || (common.albumartist && [common.albumartist]),
      disk     : common.disk,
      duration : format.duration,
      genre    : common.genre,
      title    : common.title || path.parse(trackPath).base,
      track    : common.track,
      year     : common.year,
    };

    // HACK Yes, this is terribly hacky, I want to change that
    // remove all the undefined values to avoid conflicts with the spread
    // notation
    for (const key in metadata) {
      if (metadata[key] === undefined) {
        delete metadata[key];
      }
    }

    return metadata;
  }

  return {};
};

const getLoweredMeta = (metadata) => {
  return {
    artist: metadata.artist.map((meta) => stripAccents(meta.toLowerCase())),
    album: stripAccents(metadata.album.toLowerCase()),
    title: stripAccents(metadata.title.toLowerCase()),
    genre: metadata.genre.map((meta) => stripAccents(meta.toLowerCase())),
  };
};

/**
 * Get a file metadata
 *
 * @param path (string)
 * @return object
 *
 */
const getMetadata = async (trackPath) => {
  let data;

  try {
    const stats = await stat(trackPath);
    data = await mmd.parseFile(trackPath, { native: true, skipCovers: true, fileSize: stats.size, duration: true });
  } catch (err) {
    console.warn(`An error occured while reading ${trackPath} id3 tags: ${err}`);
  }

  // Let's try to define something with what we got so far...
  const parsedData = parseMusicMetadata(data, trackPath);
  const defaultMetadata = getDefaultMetadata();

  const metadata = {
    ...defaultMetadata,
    ...parsedData,
    path: trackPath,
  };

  metadata.loweredMetas = getLoweredMeta(metadata);

  // Let's try another wat to retrieve a track duration
  if (!metadata.duration) {
    try {
      metadata.duration = await getAudioDurationAsync(trackPath);
    } catch (err) {
      console.warn(`An error occured while getting ${trackPath} duration: ${err}`);
    }
  }

  return metadata;
};

const getAudioDurationAsync = (path) => {
  const audio = new Audio;

  return new Promise((resolve, reject) => {
    audio.addEventListener('loadedmetadata', () => {
      resolve(audio.duration);
    });

    audio.addEventListener('error', (e) => {
      const message = `Error getting audio duration: (${e.target.error.code}) ${path}`;
      reject(new Error(message));
    });

    audio.preload = 'metadata';
    audio.src = path;
  });
};

const fetchCover = async (trackPath) => {
  if(!trackPath) {
    return null;
  }

  const data = await mmd.parseFile(trackPath);
  const picture = data.common.picture && data.common.picture[0];

  if(picture) { // If cover in id3
    return parseBase64(picture.format, picture.data.toString('base64'));
  }

  // scan folder for any cover image
  const folder = path.dirname(trackPath);
  const pattern = path.join(folder, '*');
  const matches = await globby(pattern, { follow: false });

  return matches.find((elem) => {
    const parsedPath = path.parse(elem);

    return ['album', 'albumart', 'folder', 'cover'].includes(parsedPath.name.toLowerCase())
      && ['.png', '.jpg', '.bmp', '.gif'].includes(parsedPath.ext.toLowerCase()) ;
  });
};

export default {
  parseDuration,
  parseUri,
  simpleSort,
  stripAccents,
  removeUselessFolders,
  chunkArray,
  getMetadata,
  fetchCover,
};
<|endoftext|>


<commit_before>var async = require('async');
var _ = require('underscore');
var questions = require(__dirname + '/../../app/controllers/questions.js');
var answers = require(__dirname + '/../../app/controllers/answers.js');

function Game(gameID, io) {
  this.io = io;
  this.gameID = gameID;
  this.players = []; // Contains array of player models
  this.table = []; // Contains array of {card: card, player: player.id}
  this.winningCard = -1; // Index in this.table
  this.winner = -1; // Index in this.players
  this.winnerAutopicked = false;
  this.czar = -1; // Index in this.players
  this.playerMinLimit = 3;
  this.playerMaxLimit = 6;
  this.pointLimit = 5;
  this.state = "awaiting players";
  this.round = 0;
  this.questions = null;
  this.answers = null;
  this.curQuestion = null;
  this.timeLimits = {
    stateChoosing: 15000,
    stateJudging: 10000,
    stateResults: 5000
  };
  // setTimeout ID that triggers the czar judging state
  // Used to automatically run czar judging if players don't pick before time limit
  // Gets cleared if players finish picking before time limit.
  this.choosingTimeout = 0;
  // setTimeout ID that triggers the result state
  // Used to automatically run result if czar doesn't decide before time limit
  // Gets cleared if czar finishes judging before time limit.
  this.judgingTimeout = 0;
  this.resultsTimeout = 0;
}

Game.prototype.payload = function() {
  var players = [];
  this.players.forEach(function(player,index) {
    players.push({
      hand: player.hand,
      points: player.points,
      username: player.username,
      avatarURL: player.avatarURL,
      //userID: player.userID, // Do we really need to send this?
      socketID: player.socket.id,
      color: player.color
    });
  });
  return {
    players: players,
    czar: this.czar,
    state: this.state,
    round: this.round,
    winningCard: this.winningCard,
    winnerAutopicked: this.winnerAutopicked,
    table: this.table,
    curQuestion: this.curQuestion
  };
};

Game.prototype.sendNotification = function(msg) {
  this.io.sockets.in(this.gameID).emit('notification', {notification: msg});
};

// Currently called on each joinGame event from socket.js
// Also called on removePlayer IF game is in 'awaiting players' state
Game.prototype.assignPlayerColors = function() {
  this.players.forEach(function(player,index) {
    player.color = index;
  });
};

Game.prototype.prepareGame = function() {
  this.state = "game in progress";

  this.io.sockets.in(this.gameID).emit('prepareGame',
    {
      playerMinLimit: this.playerMinLimit,
      playerMaxLimit: this.playerMaxLimit,
      pointLimit: this.pointLimit,
      timeLimits: this.timeLimits
    });

  var self = this;
  async.parallel([
    this.getQuestions,
    this.getAnswers
    ],
    function(err, results){
      if (err) {
        console.log(err);
      }
      self.questions = results[0];
      self.answers = results[1];

      self.startGame();
    });
};

Game.prototype.startGame = function() {
  console.log(this.state);
  this.shuffleCards(this.questions);
  this.shuffleCards(this.answers);
  this.stateChoosing(this);
};

Game.prototype.sendUpdate = function() {
  this.io.sockets.in(this.gameID).emit('gameUpdate', this.payload());
};

Game.prototype.stateChoosing = function(self) {
  self.state = "waiting for players to pick";
  console.log(self.state);
  self.table = [];
  self.winningCard = -1;
  self.winnerAutopicked = false;
  self.curQuestion = self.questions.pop();
  self.round++;
  self.dealAnswers();
  // Rotate card czar
  if (self.czar >= self.players.length - 1) {
    self.czar = 0;
  } else {
    self.czar++;
  }
  self.sendUpdate();

  self.choosingTimeout = setTimeout(function() {
    self.stateJudging(self);
  }, self.timeLimits.stateChoosing);
};

Game.prototype.selectFirst = function() {
  if (this.table.length) {
    this.winningCard = 0;
    var winnerIndex = this._findPlayerIndexBySocket(this.table[0].player);
    this.players[winnerIndex].points++;
    this.winnerAutopicked = true;
    this.stateResults(this);
  } else {
    console.log('no cards were picked!');
    this.stateChoosing(this);
  }
};

Game.prototype.stateJudging = function(self) {
  self.state = "waiting for czar to decide";
  console.log(self.state);

  if (self.table.length <= 1) {
    // Automatically select a card if only one card was submitted
    self.selectFirst();
  } else {
    self.sendUpdate();
    self.judgingTimeout = setTimeout(function() {
      // Automatically select the first submitted card when time runs out.
      self.selectFirst();
    }, self.timeLimits.stateJudging);
  }
};

Game.prototype.stateResults = function(self) {
  self.state = "winner has been chosen";
  console.log(self.state);
  // TODO: do stuff
  var endGame = false;
  for (var i = 0; i < self.players.length; i++) {
    if (self.players[i].points >= self.pointLimit) {
      endGame = true;
    }
  }
  var curQuestionArr = self.curQuestion.text.split('_');
  if (curQuestionArr.length > 1) {
    // Handling just one answer for now. It should be an array of answers later.
    console.log('self.table',self.table);
    console.log('self.winningCard',self.winningCard);
    console.log('winning card text',self.table[self.winningCard].card[0].text);
    curQuestionArr.splice(1,0,self.table[self.winningCard].card[0].text);
    if (self.curQuestion.numAnswers === 2) {
      curQuestionArr.splice(3,0,self.table[self.winningCard].card[1].text);
    }
    self.curQuestion.text = curQuestionArr.join("");
  } else {
    self.curQuestion.text += ' '+self.table[self.winningCard].card[0].text;
  }

  self.sendUpdate();
  self.resultsTimeout = setTimeout(function() {
    if (endGame) {
      self.stateEndGame(i);
    } else {
      self.stateChoosing(self);
    }
  }, self.timeLimits.stateResults);
};

Game.prototype.stateEndGame = function(winner) {
  this.state = "game ended";
  this.winner = winner;
  this.sendUpdate();
};

Game.prototype.getQuestions = function(cb) {
  questions.allQuestionsForGame(function(data){
    cb(null,data);
  });
};

Game.prototype.getAnswers = function(cb) {
  answers.allAnswersForGame(function(data){
    cb(null,data);
  });
};

Game.prototype.shuffleCards = function(cards) {
  var shuffleIndex = cards.length;
  var temp;
  var randNum;

  while(shuffleIndex) {
    randNum = Math.floor(Math.random() * shuffleIndex--);
    temp = cards[randNum];
    cards[randNum] = cards[shuffleIndex];
    cards[shuffleIndex] = temp;
  }

  return cards;
};

Game.prototype.dealAnswers = function(maxAnswers) {
  maxAnswers = maxAnswers || 10;
  for (var i = 0; i < this.players.length; i++) {
    while (this.players[i].hand.length < maxAnswers) {
      this.players[i].hand.push(this.answers.pop());
    }
  }
};

Game.prototype._findPlayerIndexBySocket = function(thisPlayer) {
  var playerIndex = -1;
  _.each(this.players, function(player, index) {
    if (player.socket.id === thisPlayer) {
      playerIndex = index;
    }
  });
  return playerIndex;
};

Game.prototype.pickCards = function(thisCardArray, thisPlayer) {
  // Only accept cards when we expect players to pick a card
  if (this.state === "waiting for players to pick") {
    // Find the player's position in the players array
    var playerIndex = this._findPlayerIndexBySocket(thisPlayer);
    console.log('player is at index',playerIndex);
    if (playerIndex !== -1) {
      // Verify that the player hasn't previously picked a card
      var previouslySubmitted = false;
      _.each(this.table, function(pickedSet, index) {
        if (pickedSet.player === thisPlayer) {
          previouslySubmitted = true;
        }
      });
      if (!previouslySubmitted) {
        // Find the indices of the cards in the player's hand (given the card ids)
        var tableCard = [];
        for (var i = 0; i < thisCardArray.length; i++ ) {
          var cardIndex = null;
          for (var j = 0; j < this.players[playerIndex].hand.length; j++) {
            if (this.players[playerIndex].hand[j].id === thisCardArray[i]) {
              cardIndex = j;
            }
          }
          console.log('card',i,'is at index',cardIndex);
          if (cardIndex !== null) {
            tableCard.push(this.players[playerIndex].hand.splice(cardIndex,1)[0]);
          }
          console.log('table object at',cardIndex,':',tableCard);
        }
        if (tableCard.length === this.curQuestion.numAnswers) {
          this.table.push({
            card: tableCard,
            player: this.players[playerIndex].socket.id
          });
        }
        console.log('final table object',this.table);
        if (this.table.length === this.players.length-1) {
          clearTimeout(this.choosingTimeout);
          this.stateJudging(this);
        } else {
          this.sendUpdate();
        }
      }
    }
  } else {
    console.log('NOTE:',thisPlayer,'picked a card during',this.state);
  }
};

Game.prototype.removePlayer = function(thisPlayer) {
  var playerIndex = this._findPlayerIndexBySocket(thisPlayer);

  // Just used to send the remaining players a notification
  var playerName = this.players[playerIndex].username;

  // Remove player from this.players
  this.players.splice(playerIndex,1);

  if (this.state === "awaiting players") {
    this.assignPlayerColors();
  }

  // Check if the player is the czar
  if (this.czar === playerIndex) {
    // If the player is the czar...
    // If players are currently picking a card, advance to a new round.
    if (this.state === "waiting for players to pick") {
      clearTimeout(this.choosingTimeout);
      this.sendNotification('The Czar left the game! Starting a new round.');
      return this.stateChoosing();
    } else if (this.state === "waiting for czar to decide") {
      // If players are waiting on a czar to pick, auto pick.
      this.sendNotification('The Czar left the game! First answer/s submitted wins!')
      this.pickWinning(this.table[0].card[0].id, thisPlayer, true);
    }
  } else {
    this.sendNotification(playerName+' has left the game.');
  }

  this.sendUpdate();
};

Game.prototype.pickWinning = function(thisCard, thisPlayer, autopicked) {
  console.log(thisCard);
  autopicked = autopicked || false;
  var playerIndex = this._findPlayerIndexBySocket(thisPlayer);
  if ((playerIndex === this.czar || autopicked) && this.state === "waiting for czar to decide") {
    var cardIndex = -1;
    _.each(this.table, function(winningSet, index) {
      if (winningSet.card[0].id === thisCard) {
        cardIndex = index;
      }
    });
    console.log('winning card is at index',cardIndex);
    if (cardIndex !== -1) {
      this.winningCard = cardIndex;
      var winnerIndex = this._findPlayerIndexBySocket(this.table[cardIndex].player);
      this.sendNotification(this.player[winnerIndex].username+' has won the round!');
      this.players[winnerIndex].points++;
      clearTimeout(this.judgingTimeout);
      this.winnerAutopicked = autopicked;
      this.stateResults(this);
    } else {
      console.log('WARNING: czar',thisPlayer,'picked a card that was not on the table.');
    }
  } else {
    // TODO: Do something?
    this.sendUpdate();
  }
};

Game.prototype.killGame = function() {
  console.log('killing game');
  clearTimeout(this.resultsTimeout);
  clearTimeout(this.choosingTimeout);
  clearTimeout(this.judgingTimeout);
};

module.exports = Game;

<commit_msg>Fixed minor bug.<commit_after>var async = require('async');
var _ = require('underscore');
var questions = require(__dirname + '/../../app/controllers/questions.js');
var answers = require(__dirname + '/../../app/controllers/answers.js');

function Game(gameID, io) {
  this.io = io;
  this.gameID = gameID;
  this.players = []; // Contains array of player models
  this.table = []; // Contains array of {card: card, player: player.id}
  this.winningCard = -1; // Index in this.table
  this.winner = -1; // Index in this.players
  this.winnerAutopicked = false;
  this.czar = -1; // Index in this.players
  this.playerMinLimit = 3;
  this.playerMaxLimit = 6;
  this.pointLimit = 5;
  this.state = "awaiting players";
  this.round = 0;
  this.questions = null;
  this.answers = null;
  this.curQuestion = null;
  this.timeLimits = {
    stateChoosing: 15000,
    stateJudging: 10000,
    stateResults: 5000
  };
  // setTimeout ID that triggers the czar judging state
  // Used to automatically run czar judging if players don't pick before time limit
  // Gets cleared if players finish picking before time limit.
  this.choosingTimeout = 0;
  // setTimeout ID that triggers the result state
  // Used to automatically run result if czar doesn't decide before time limit
  // Gets cleared if czar finishes judging before time limit.
  this.judgingTimeout = 0;
  this.resultsTimeout = 0;
}

Game.prototype.payload = function() {
  var players = [];
  this.players.forEach(function(player,index) {
    players.push({
      hand: player.hand,
      points: player.points,
      username: player.username,
      avatarURL: player.avatarURL,
      //userID: player.userID, // Do we really need to send this?
      socketID: player.socket.id,
      color: player.color
    });
  });
  return {
    players: players,
    czar: this.czar,
    state: this.state,
    round: this.round,
    winningCard: this.winningCard,
    winnerAutopicked: this.winnerAutopicked,
    table: this.table,
    curQuestion: this.curQuestion
  };
};

Game.prototype.sendNotification = function(msg) {
  this.io.sockets.in(this.gameID).emit('notification', {notification: msg});
};

// Currently called on each joinGame event from socket.js
// Also called on removePlayer IF game is in 'awaiting players' state
Game.prototype.assignPlayerColors = function() {
  this.players.forEach(function(player,index) {
    player.color = index;
  });
};

Game.prototype.prepareGame = function() {
  this.state = "game in progress";

  this.io.sockets.in(this.gameID).emit('prepareGame',
    {
      playerMinLimit: this.playerMinLimit,
      playerMaxLimit: this.playerMaxLimit,
      pointLimit: this.pointLimit,
      timeLimits: this.timeLimits
    });

  var self = this;
  async.parallel([
    this.getQuestions,
    this.getAnswers
    ],
    function(err, results){
      if (err) {
        console.log(err);
      }
      self.questions = results[0];
      self.answers = results[1];

      self.startGame();
    });
};

Game.prototype.startGame = function() {
  console.log(this.state);
  this.shuffleCards(this.questions);
  this.shuffleCards(this.answers);
  this.stateChoosing(this);
};

Game.prototype.sendUpdate = function() {
  this.io.sockets.in(this.gameID).emit('gameUpdate', this.payload());
};

Game.prototype.stateChoosing = function(self) {
  self.state = "waiting for players to pick";
  console.log(self.state);
  self.table = [];
  self.winningCard = -1;
  self.winnerAutopicked = false;
  self.curQuestion = self.questions.pop();
  self.round++;
  self.dealAnswers();
  // Rotate card czar
  if (self.czar >= self.players.length - 1) {
    self.czar = 0;
  } else {
    self.czar++;
  }
  self.sendUpdate();

  self.choosingTimeout = setTimeout(function() {
    self.stateJudging(self);
  }, self.timeLimits.stateChoosing);
};

Game.prototype.selectFirst = function() {
  if (this.table.length) {
    this.winningCard = 0;
    var winnerIndex = this._findPlayerIndexBySocket(this.table[0].player);
    this.players[winnerIndex].points++;
    this.winnerAutopicked = true;
    this.stateResults(this);
  } else {
    console.log('no cards were picked!');
    this.stateChoosing(this);
  }
};

Game.prototype.stateJudging = function(self) {
  self.state = "waiting for czar to decide";
  console.log(self.state);

  if (self.table.length <= 1) {
    // Automatically select a card if only one card was submitted
    self.selectFirst();
  } else {
    self.sendUpdate();
    self.judgingTimeout = setTimeout(function() {
      // Automatically select the first submitted card when time runs out.
      self.selectFirst();
    }, self.timeLimits.stateJudging);
  }
};

Game.prototype.stateResults = function(self) {
  self.state = "winner has been chosen";
  console.log(self.state);
  // TODO: do stuff
  var endGame = false;
  for (var i = 0; i < self.players.length; i++) {
    if (self.players[i].points >= self.pointLimit) {
      endGame = true;
    }
  }
  var curQuestionArr = self.curQuestion.text.split('_');
  if (curQuestionArr.length > 1) {
    // Handling just one answer for now. It should be an array of answers later.
    console.log('self.table',self.table);
    console.log('self.winningCard',self.winningCard);
    console.log('winning card text',self.table[self.winningCard].card[0].text);
    curQuestionArr.splice(1,0,self.table[self.winningCard].card[0].text);
    if (self.curQuestion.numAnswers === 2) {
      curQuestionArr.splice(3,0,self.table[self.winningCard].card[1].text);
    }
    self.curQuestion.text = curQuestionArr.join("");
  } else {
    self.curQuestion.text += ' '+self.table[self.winningCard].card[0].text;
  }

  self.sendUpdate();
  self.resultsTimeout = setTimeout(function() {
    if (endGame) {
      self.stateEndGame(i);
    } else {
      self.stateChoosing(self);
    }
  }, self.timeLimits.stateResults);
};

Game.prototype.stateEndGame = function(winner) {
  this.state = "game ended";
  this.winner = winner;
  this.sendUpdate();
};

Game.prototype.getQuestions = function(cb) {
  questions.allQuestionsForGame(function(data){
    cb(null,data);
  });
};

Game.prototype.getAnswers = function(cb) {
  answers.allAnswersForGame(function(data){
    cb(null,data);
  });
};

Game.prototype.shuffleCards = function(cards) {
  var shuffleIndex = cards.length;
  var temp;
  var randNum;

  while(shuffleIndex) {
    randNum = Math.floor(Math.random() * shuffleIndex--);
    temp = cards[randNum];
    cards[randNum] = cards[shuffleIndex];
    cards[shuffleIndex] = temp;
  }

  return cards;
};

Game.prototype.dealAnswers = function(maxAnswers) {
  maxAnswers = maxAnswers || 10;
  for (var i = 0; i < this.players.length; i++) {
    while (this.players[i].hand.length < maxAnswers) {
      this.players[i].hand.push(this.answers.pop());
    }
  }
};

Game.prototype._findPlayerIndexBySocket = function(thisPlayer) {
  var playerIndex = -1;
  _.each(this.players, function(player, index) {
    if (player.socket.id === thisPlayer) {
      playerIndex = index;
    }
  });
  return playerIndex;
};

Game.prototype.pickCards = function(thisCardArray, thisPlayer) {
  // Only accept cards when we expect players to pick a card
  if (this.state === "waiting for players to pick") {
    // Find the player's position in the players array
    var playerIndex = this._findPlayerIndexBySocket(thisPlayer);
    console.log('player is at index',playerIndex);
    if (playerIndex !== -1) {
      // Verify that the player hasn't previously picked a card
      var previouslySubmitted = false;
      _.each(this.table, function(pickedSet, index) {
        if (pickedSet.player === thisPlayer) {
          previouslySubmitted = true;
        }
      });
      if (!previouslySubmitted) {
        // Find the indices of the cards in the player's hand (given the card ids)
        var tableCard = [];
        for (var i = 0; i < thisCardArray.length; i++ ) {
          var cardIndex = null;
          for (var j = 0; j < this.players[playerIndex].hand.length; j++) {
            if (this.players[playerIndex].hand[j].id === thisCardArray[i]) {
              cardIndex = j;
            }
          }
          console.log('card',i,'is at index',cardIndex);
          if (cardIndex !== null) {
            tableCard.push(this.players[playerIndex].hand.splice(cardIndex,1)[0]);
          }
          console.log('table object at',cardIndex,':',tableCard);
        }
        if (tableCard.length === this.curQuestion.numAnswers) {
          this.table.push({
            card: tableCard,
            player: this.players[playerIndex].socket.id
          });
        }
        console.log('final table object',this.table);
        if (this.table.length === this.players.length-1) {
          clearTimeout(this.choosingTimeout);
          this.stateJudging(this);
        } else {
          this.sendUpdate();
        }
      }
    }
  } else {
    console.log('NOTE:',thisPlayer,'picked a card during',this.state);
  }
};

Game.prototype.removePlayer = function(thisPlayer) {
  var playerIndex = this._findPlayerIndexBySocket(thisPlayer);

  // Just used to send the remaining players a notification
  var playerName = this.players[playerIndex].username;

  // Remove player from this.players
  this.players.splice(playerIndex,1);

  if (this.state === "awaiting players") {
    this.assignPlayerColors();
  }

  // Check if the player is the czar
  if (this.czar === playerIndex) {
    // If the player is the czar...
    // If players are currently picking a card, advance to a new round.
    if (this.state === "waiting for players to pick") {
      clearTimeout(this.choosingTimeout);
      this.sendNotification('The Czar left the game! Starting a new round.');
      return this.stateChoosing();
    } else if (this.state === "waiting for czar to decide") {
      // If players are waiting on a czar to pick, auto pick.
      this.sendNotification('The Czar left the game! First answer/s submitted wins!')
      this.pickWinning(this.table[0].card[0].id, thisPlayer, true);
    }
  } else {
    this.sendNotification(playerName+' has left the game.');
  }

  this.sendUpdate();
};

Game.prototype.pickWinning = function(thisCard, thisPlayer, autopicked) {
  console.log(thisCard);
  autopicked = autopicked || false;
  var playerIndex = this._findPlayerIndexBySocket(thisPlayer);
  if ((playerIndex === this.czar || autopicked) && this.state === "waiting for czar to decide") {
    var cardIndex = -1;
    _.each(this.table, function(winningSet, index) {
      if (winningSet.card[0].id === thisCard) {
        cardIndex = index;
      }
    });
    console.log('winning card is at index',cardIndex);
    if (cardIndex !== -1) {
      this.winningCard = cardIndex;
      var winnerIndex = this._findPlayerIndexBySocket(this.table[cardIndex].player);
      this.sendNotification(this.players[winnerIndex].username+' has won the round!');
      this.players[winnerIndex].points++;
      clearTimeout(this.judgingTimeout);
      this.winnerAutopicked = autopicked;
      this.stateResults(this);
    } else {
      console.log('WARNING: czar',thisPlayer,'picked a card that was not on the table.');
    }
  } else {
    // TODO: Do something?
    this.sendUpdate();
  }
};

Game.prototype.killGame = function() {
  console.log('killing game');
  clearTimeout(this.resultsTimeout);
  clearTimeout(this.choosingTimeout);
  clearTimeout(this.judgingTimeout);
};

module.exports = Game;

<|endoftext|>


<commit_before>/**
 * @author: @AngularClass
 */

const helpers = require('./helpers');
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const webpackMergeDll = webpackMerge.strategy({plugins: 'replace'});
const commonConfig = require('./webpack.common.js'); // the settings that are common to prod and dev

/**
 * Webpack Plugins
 */
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin');
const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');

/**
 * Webpack Constants
 */
const ENV = process.env.ENV = process.env.NODE_ENV = 'development';
const HOST = process.env.HOST || 'localhost';
const PORT = process.env.PORT || 3000;
const HMR = helpers.hasProcessFlag('hot');
const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, {
  host: HOST,
  port: PORT,
  ENV: ENV,
  HMR: HMR
});

const DllBundlesPlugin = require('webpack-dll-bundles-plugin').DllBundlesPlugin;

/**
 * Webpack configuration
 *
 * See: http://webpack.github.io/docs/configuration.html#cli
 */
module.exports = function (options) {
  return webpackMerge(commonConfig({env: ENV}), {

    /**
     * Developer tool to enhance debugging
     *
     * See: http://webpack.github.io/docs/configuration.html#devtool
     * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps
     */
    devtool: 'cheap-module-source-map',

    /**
     * Options affecting the output of the compilation.
     *
     * See: http://webpack.github.io/docs/configuration.html#output
     */
    output: {

      /**
       * The output directory as absolute path (required).
       *
       * See: http://webpack.github.io/docs/configuration.html#output-path
       */
      path: helpers.root('dist'),

      /**
       * Specifies the name of each output file on disk.
       * IMPORTANT: You must not specify an absolute path here!
       *
       * See: http://webpack.github.io/docs/configuration.html#output-filename
       */
      filename: '[name].bundle.js',

      /**
       * The filename of the SourceMaps for the JavaScript files.
       * They are inside the output.path directory.
       *
       * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename
       */
      sourceMapFilename: '[file].map',

      /** The filename of non-entry chunks as relative path
       * inside the output.path directory.
       *
       * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename
       */
      chunkFilename: '[id].chunk.js',

      library: 'ac_[name]',
      libraryTarget: 'var',
    },

    module: {

      rules: [

        /*
         * css loader support for *.css files (styles directory only)
         * Loads external css styles into the DOM, supports HMR
         *
         */
        {
          test: /\.css$/,
          use: ['style-loader', 'css-loader'],
          include: [helpers.root('src', 'styles')]
        },

        /*
         * sass loader support for *.scss files (styles directory only)
         * Loads external sass styles into the DOM, supports HMR
         *
         */
        {
          test: /\.scss$/,
          use: ['style-loader', 'css-loader', 'sass-loader'],
          include: [helpers.root('src', 'styles')]
        },

      ]

    },

    plugins: [

      /**
       * Plugin: DefinePlugin
       * Description: Define free variables.
       * Useful for having development builds with debug logging or adding global constants.
       *
       * Environment helpers
       *
       * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
       */
      // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts
      new DefinePlugin({
        'ENV': JSON.stringify(METADATA.ENV),
        'HMR': METADATA.HMR,
        'process.env': {
          'ENV': JSON.stringify(METADATA.ENV),
          'NODE_ENV': JSON.stringify(METADATA.ENV),
          'HMR': METADATA.HMR,
        }
      }),

      new DllBundlesPlugin({
        bundles: {
          polyfills: [
            'core-js',
            {
              name: 'zone.js',
              path: 'zone.js/dist/zone.js'
            },
            {
              name: 'zone.js',
              path: 'zone.js/dist/long-stack-trace-zone.js'
            },
            'ts-helpers',
          ],
          vendor: [
            '@angular/platform-browser',
            '@angular/platform-browser-dynamic',
            '@angular/core',
            '@angular/common',
            '@angular/forms',
            '@angular/http',
            '@angular/router',
            '@angularclass/hmr',
            'rxjs',
          ]
        },
        dllDir: helpers.root('dll'),
        webpackConfig: webpackMergeDll(commonConfig({env: ENV}), {
          devtool: 'cheap-module-source-map',
          plugins: []
        })
      }),

      /**
       * Plugin: AddAssetHtmlPlugin
       * Description: Adds the given JS or CSS file to the files
       * Webpack knows about, and put it into the list of assets
       * html-webpack-plugin injects into the generated html.
       *
       * See: https://github.com/SimenB/add-asset-html-webpack-plugin
       */
      new AddAssetHtmlPlugin([
        { filepath: helpers.root(`dll/${DllBundlesPlugin.resolveFile('polyfills')}`) },
        { filepath: helpers.root(`dll/${DllBundlesPlugin.resolveFile('vendor')}`) }
      ]),

      /**
       * Plugin: NamedModulesPlugin (experimental)
       * Description: Uses file names as module name.
       *
       * See: https://github.com/webpack/webpack/commit/a04ffb928365b19feb75087c63f13cadfc08e1eb
       */
      // new NamedModulesPlugin(),

      /**
       * Plugin LoaderOptionsPlugin (experimental)
       *
       * See: https://gist.github.com/sokra/27b24881210b56bbaff7
       */
      new LoaderOptionsPlugin({
        debug: true,
        options: {

        }
      }),

    ],

    /**
     * Webpack Development Server configuration
     * Description: The webpack-dev-server is a little node.js Express server.
     * The server emits information about the compilation state to the client,
     * which reacts to those events.
     *
     * See: https://webpack.github.io/docs/webpack-dev-server.html
     */
    devServer: {
      port: METADATA.port,
      host: METADATA.host,
      historyApiFallback: true,
      watchOptions: {
        aggregateTimeout: 300,
        poll: 1000
      }
    },

    /*
     * Include polyfills or mocks for various node stuff
     * Description: Node configuration
     *
     * See: https://webpack.github.io/docs/configuration.html#node
     */
    node: {
      global: true,
      crypto: 'empty',
      process: true,
      module: false,
      clearImmediate: false,
      setImmediate: false
    }

  });
}
<commit_msg>fix(webpack): skip cheap source maps to fix advanced typescript debugging<commit_after>/**
 * @author: @AngularClass
 */

const helpers = require('./helpers');
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const webpackMergeDll = webpackMerge.strategy({plugins: 'replace'});
const commonConfig = require('./webpack.common.js'); // the settings that are common to prod and dev

/**
 * Webpack Plugins
 */
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin');
const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');

/**
 * Webpack Constants
 */
const ENV = process.env.ENV = process.env.NODE_ENV = 'development';
const HOST = process.env.HOST || 'localhost';
const PORT = process.env.PORT || 3000;
const HMR = helpers.hasProcessFlag('hot');
const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, {
  host: HOST,
  port: PORT,
  ENV: ENV,
  HMR: HMR
});

const DllBundlesPlugin = require('webpack-dll-bundles-plugin').DllBundlesPlugin;

/**
 * Webpack configuration
 *
 * See: http://webpack.github.io/docs/configuration.html#cli
 */
module.exports = function (options) {
  return webpackMerge(commonConfig({env: ENV}), {

    /**
     * Developer tool to enhance debugging
     *
     * See: http://webpack.github.io/docs/configuration.html#devtool
     * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps
     */
    devtool: 'source-map',

    /**
     * Options affecting the output of the compilation.
     *
     * See: http://webpack.github.io/docs/configuration.html#output
     */
    output: {

      /**
       * The output directory as absolute path (required).
       *
       * See: http://webpack.github.io/docs/configuration.html#output-path
       */
      path: helpers.root('dist'),

      /**
       * Specifies the name of each output file on disk.
       * IMPORTANT: You must not specify an absolute path here!
       *
       * See: http://webpack.github.io/docs/configuration.html#output-filename
       */
      filename: '[name].bundle.js',

      /**
       * The filename of the SourceMaps for the JavaScript files.
       * They are inside the output.path directory.
       *
       * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename
       */
      sourceMapFilename: '[file].map',

      /** The filename of non-entry chunks as relative path
       * inside the output.path directory.
       *
       * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename
       */
      chunkFilename: '[id].chunk.js',

      library: 'ac_[name]',
      libraryTarget: 'var',
    },

    module: {

      rules: [

        /*
         * css loader support for *.css files (styles directory only)
         * Loads external css styles into the DOM, supports HMR
         *
         */
        {
          test: /\.css$/,
          use: ['style-loader', 'css-loader'],
          include: [helpers.root('src', 'styles')]
        },

        /*
         * sass loader support for *.scss files (styles directory only)
         * Loads external sass styles into the DOM, supports HMR
         *
         */
        {
          test: /\.scss$/,
          use: ['style-loader', 'css-loader', 'sass-loader'],
          include: [helpers.root('src', 'styles')]
        },

      ]

    },

    plugins: [

      /**
       * Plugin: DefinePlugin
       * Description: Define free variables.
       * Useful for having development builds with debug logging or adding global constants.
       *
       * Environment helpers
       *
       * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
       */
      // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts
      new DefinePlugin({
        'ENV': JSON.stringify(METADATA.ENV),
        'HMR': METADATA.HMR,
        'process.env': {
          'ENV': JSON.stringify(METADATA.ENV),
          'NODE_ENV': JSON.stringify(METADATA.ENV),
          'HMR': METADATA.HMR,
        }
      }),

      new DllBundlesPlugin({
        bundles: {
          polyfills: [
            'core-js',
            {
              name: 'zone.js',
              path: 'zone.js/dist/zone.js'
            },
            {
              name: 'zone.js',
              path: 'zone.js/dist/long-stack-trace-zone.js'
            },
            'ts-helpers',
          ],
          vendor: [
            '@angular/platform-browser',
            '@angular/platform-browser-dynamic',
            '@angular/core',
            '@angular/common',
            '@angular/forms',
            '@angular/http',
            '@angular/router',
            '@angularclass/hmr',
            'rxjs',
          ]
        },
        dllDir: helpers.root('dll'),
        webpackConfig: webpackMergeDll(commonConfig({env: ENV}), {
          devtool: 'cheap-module-source-map',
          plugins: []
        })
      }),

      /**
       * Plugin: AddAssetHtmlPlugin
       * Description: Adds the given JS or CSS file to the files
       * Webpack knows about, and put it into the list of assets
       * html-webpack-plugin injects into the generated html.
       *
       * See: https://github.com/SimenB/add-asset-html-webpack-plugin
       */
      new AddAssetHtmlPlugin([
        { filepath: helpers.root(`dll/${DllBundlesPlugin.resolveFile('polyfills')}`) },
        { filepath: helpers.root(`dll/${DllBundlesPlugin.resolveFile('vendor')}`) }
      ]),

      /**
       * Plugin: NamedModulesPlugin (experimental)
       * Description: Uses file names as module name.
       *
       * See: https://github.com/webpack/webpack/commit/a04ffb928365b19feb75087c63f13cadfc08e1eb
       */
      // new NamedModulesPlugin(),

      /**
       * Plugin LoaderOptionsPlugin (experimental)
       *
       * See: https://gist.github.com/sokra/27b24881210b56bbaff7
       */
      new LoaderOptionsPlugin({
        debug: true,
        options: {

        }
      }),

    ],

    /**
     * Webpack Development Server configuration
     * Description: The webpack-dev-server is a little node.js Express server.
     * The server emits information about the compilation state to the client,
     * which reacts to those events.
     *
     * See: https://webpack.github.io/docs/webpack-dev-server.html
     */
    devServer: {
      port: METADATA.port,
      host: METADATA.host,
      historyApiFallback: true,
      watchOptions: {
        aggregateTimeout: 300,
        poll: 1000
      }
    },

    /*
     * Include polyfills or mocks for various node stuff
     * Description: Node configuration
     *
     * See: https://webpack.github.io/docs/configuration.html#node
     */
    node: {
      global: true,
      crypto: 'empty',
      process: true,
      module: false,
      clearImmediate: false,
      setImmediate: false
    }

  });
}
<|endoftext|>


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

/**
 * @fileoverview Factory for calculating the statistics of a particular state.
 */

oppia.factory('StateRulesStatsService', [
  '$http', '$injector', 'AngularNameService', 'AnswerClassificationService',
  'ContextService', 'UrlInterpolationService',
  function(
      $http, $injector, AngularNameService, AnswerClassificationService,
      ContextService, UrlInterpolationService) {
    return {
      /**
       * TODO(brianrodri): Consider moving this into a visualization domain
       * object.
       *
       * @param {Object!} state
       * @return {Boolean} whether given state has an implementation for
       *     displaying the issues overview tab in the State Editor.
       */
      stateSupportsIssuesOverview: function(state) {
        return state.interaction.id === 'TextInput';
      },

      /**
       * Returns a promise which will provide details of the given state's
       * answer-statistics.
       *
       * @param {Object!} state
       */
      computeStateRulesStats: function(state) {
        var interactionRulesService = $injector.get(
          AngularNameService.getNameOfInteractionRulesService(
            state.interaction.id));
        var explorationId = ContextService.getExplorationId();

        return $http.get(
          UrlInterpolationService.interpolateUrl(
            '/createhandler/state_rules_stats/<exploration_id>/<state_name>',
            {exploration_id: explorationId, state_name: state.name})
        ).then(function(response) {
          return {
            state_name: state.name,
            exploration_id: explorationId,
            visualizations_info: response.data.visualizations_info.map(
              function(vizInfo) {
                var vizInfoDataWithAddressedInfo = {};
                if (vizInfo.addressed_info_is_supported) {
                  vizInfoDataWithAddressedInfo = {
                    data: vizInfo.data.map(function(vizInfoDatum) {
                      return Object.assign({
                        is_addressed: (
                          AnswerClassificationService
                            .isClassifiedExplicitlyOrGoesToNewState(
                              explorationId, state.name, state,
                              vizInfoDatum.answer, interactionRulesService))
                      }, vizInfoDatum);
                    })
                  };
                }

                return Object.assign({}, vizInfo, vizInfoDataWithAddressedInfo);
              })
          };
        });
      }
    };
  }
]);
<commit_msg>Manually Construct URL (#5345)<commit_after>// Copyright 2018 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Factory for calculating the statistics of a particular state.
 */

oppia.factory('StateRulesStatsService', [
  '$http', '$injector', 'AngularNameService', 'AnswerClassificationService',
  'ContextService', 'UrlInterpolationService',
  function(
      $http, $injector, AngularNameService, AnswerClassificationService,
      ContextService, UrlInterpolationService) {
    return {
      /**
       * TODO(brianrodri): Consider moving this into a visualization domain
       * object.
       *
       * @param {Object!} state
       * @return {Boolean} whether given state has an implementation for
       *     displaying the issues overview tab in the State Editor.
       */
      stateSupportsIssuesOverview: function(state) {
        return state.interaction.id === 'TextInput';
      },

      /**
       * Returns a promise which will provide details of the given state's
       * answer-statistics.
       *
       * @param {Object!} state
       */
      computeStateRulesStats: function(state) {
        var interactionRulesService = $injector.get(
          AngularNameService.getNameOfInteractionRulesService(
            state.interaction.id));
        var explorationId = ContextService.getExplorationId();

        return $http.get(
          '/createhandler/state_rules_stats/' + [
            encodeURIComponent(explorationId),
            encodeURIComponent(state.name)
          ].join('/')
        ).then(function(response) {
          return {
            state_name: state.name,
            exploration_id: explorationId,
            visualizations_info: response.data.visualizations_info.map(
              function(vizInfo) {
                var vizInfoDataWithAddressedInfo = {};
                if (vizInfo.addressed_info_is_supported) {
                  vizInfoDataWithAddressedInfo = {
                    data: vizInfo.data.map(function(vizInfoDatum) {
                      return Object.assign({
                        is_addressed: (
                          AnswerClassificationService
                            .isClassifiedExplicitlyOrGoesToNewState(
                              explorationId, state.name, state,
                              vizInfoDatum.answer, interactionRulesService))
                      }, vizInfoDatum);
                    })
                  };
                }

                return Object.assign({}, vizInfo, vizInfoDataWithAddressedInfo);
              })
          };
        });
      }
    };
  }
]);
<|endoftext|>