<commit_before>const PreactRefreshPlugin = require(`@prefresh/webpack`)

export function onCreateBabelConfig({ actions, stage }) {
  if (stage === `develop`) {
    // enable react-refresh babel plugin to enable hooks
    // @see https://github.com/JoviDeCroock/prefresh/tree/master/packages/webpack#using-hooks
    actions.setBabelPlugin({
      name: `@prefresh/babel-plugin`,
      stage,
    })
  }
}

export function onCreateWebpackConfig({ stage, actions, getConfig }) {
  const webpackPlugins = []
  const webpackConfig = getConfig()

  if (webpackConfig.resolve?.alias) {
    delete webpackConfig.resolve.alias.react
    delete webpackConfig.resolve.alias[`react-dom`]
  }

  if (stage === `develop`) {
    webpackPlugins.push(
      new PreactRefreshPlugin({
        overlay: {
          module: require.resolve(`gatsby/dist/utils/fast-refresh-module`),
        },
      })
    )

    // remove React refresh plugin, we want to add preact refresh instead.
    webpackConfig.plugins = webpackConfig.plugins.filter(
      plugin => plugin.constructor.name !== `ReactRefreshPlugin`
    )

    // add webpack-hot-middleware/client to the commons entry
    webpackConfig.entry.commons.unshift(
      `@gatsbyjs/webpack-hot-middleware/client`
    )
  }

  // add preact to the framework bundle
  if (stage === `build-javascript` || stage === `develop`) {
    if (
      webpackConfig?.optimization?.splitChunks?.cacheGroups?.framework?.test
    ) {
      const frameworkRegex =
        webpackConfig.optimization.splitChunks.cacheGroups.framework.test

      // replace react libs with preact
      webpackConfig.optimization.splitChunks.cacheGroups.framework.test =
        module =>
          /(?<!node_modules.*)[\\/]node_modules[\\/](preact)[\\/]/.test(
            module.resource
          ) || frameworkRegex.test(module.resource)
    }
  }

  actions.replaceWebpackConfig(webpackConfig)

  const extension =
    stage === `build-javascript` || stage === `develop` ? `.module.js` : `.js`
  actions.setWebpackConfig({
    resolve: {
      alias: {
        react: require.resolve(`preact/compat`).replace(`.js`, extension),
        "react-dom/server": require
          .resolve(`preact/compat/server`)
          .replace(`.js`, extension),
        "react-dom": require.resolve(`preact/compat`).replace(`.js`, extension),
        "react/jsx-runtime": require
          .resolve(`preact/jsx-runtime`)
          .replace(`.js`, extension),
      },
    },
    plugins: webpackPlugins,
  })
}
<commit_msg>fix(gatsby-plugin-preact): Fix alias for react-dom/server (#34694)<commit_after>const PreactRefreshPlugin = require(`@prefresh/webpack`)

export function onCreateBabelConfig({ actions, stage }) {
  if (stage === `develop`) {
    // enable react-refresh babel plugin to enable hooks
    // @see https://github.com/JoviDeCroock/prefresh/tree/master/packages/webpack#using-hooks
    actions.setBabelPlugin({
      name: `@prefresh/babel-plugin`,
      stage,
    })
  }
}

export function onCreateWebpackConfig({ stage, actions, getConfig }) {
  const webpackPlugins = []
  const webpackConfig = getConfig()

  if (webpackConfig.resolve?.alias) {
    delete webpackConfig.resolve.alias.react
    delete webpackConfig.resolve.alias[`react-dom`]
  }

  if (stage === `develop`) {
    webpackPlugins.push(
      new PreactRefreshPlugin({
        overlay: {
          module: require.resolve(`gatsby/dist/utils/fast-refresh-module`),
        },
      })
    )

    // remove React refresh plugin, we want to add preact refresh instead.
    webpackConfig.plugins = webpackConfig.plugins.filter(
      plugin => plugin.constructor.name !== `ReactRefreshPlugin`
    )

    // add webpack-hot-middleware/client to the commons entry
    webpackConfig.entry.commons.unshift(
      `@gatsbyjs/webpack-hot-middleware/client`
    )
  }

  // add preact to the framework bundle
  if (stage === `build-javascript` || stage === `develop`) {
    if (
      webpackConfig?.optimization?.splitChunks?.cacheGroups?.framework?.test
    ) {
      const frameworkRegex =
        webpackConfig.optimization.splitChunks.cacheGroups.framework.test

      // replace react libs with preact
      webpackConfig.optimization.splitChunks.cacheGroups.framework.test =
        module =>
          /(?<!node_modules.*)[\\/]node_modules[\\/](preact)[\\/]/.test(
            module.resource
          ) || frameworkRegex.test(module.resource)
    }
  }

  actions.replaceWebpackConfig(webpackConfig)

  const extension =
    stage === `build-javascript` || stage === `develop` ? `.module.js` : `.js`
  actions.setWebpackConfig({
    resolve: {
      alias: {
        react: require.resolve(`preact/compat`).replace(`.js`, extension),
        "react-dom/server": require.resolve(`preact/compat/server`),
        "react-dom": require.resolve(`preact/compat`).replace(`.js`, extension),
        "react/jsx-runtime": require
          .resolve(`preact/jsx-runtime`)
          .replace(`.js`, extension),
      },
    },
    plugins: webpackPlugins,
  })
}
<|endoftext|>


<commit_before>import { _millisDateRepresentation } from '/resources/reflection/tg-date-utils.js';
import { TgReflector } from '/app/tg-reflector.js';
import { TgAppConfig } from '/app/tg-app-config.js';

export const TgEgiDataRetrievalBehavior = {

    created: function () {
        this._reflector = new TgReflector();
        this._appConfig = new TgAppConfig();
    },

    isHyperlinkProp: function (entity, column) {
        return column.type === 'Hyperlink' && this.getValueFromEntity(entity, column) !== null
    },

    isBooleanProp: function (entity, column) {
        return column.type === 'Boolean' && this.getValueFromEntity(entity, column) !== null
    },

    isNotBooleanOrHyperlinkProp: function (entity, column) {
        return !(this.isBooleanProp(entity, column) || this.isHyperlinkProp(entity, column));
    },

    getAttachmentIfPossible: function (entity, column) {
        const valueFromEntity = this.getValueFromEntity(entity, column);
        if (entity.type && entity.constructor.prototype.type.call(entity).notEnhancedFullClassName() === "ua.com.fielden.platform.attachment.Attachment") {
            return entity;
        } else if (valueFromEntity && valueFromEntity.type &&
            valueFromEntity.type().notEnhancedFullClassName() === "ua.com.fielden.platform.attachment.Attachment") {
            return valueFromEntity;
        } else if (this._reflector.entityPropOwner(this.getRealEntity(entity, column), this.getRealProperty(column))) {
            const owner = this._reflector.entityPropOwner(this.getRealEntity(entity, column), this.getRealProperty(column));
            if (owner.type().notEnhancedFullClassName() === "ua.com.fielden.platform.attachment.Attachment") {
                return owner;
            }
            return null;
        } else {
            return null;
        }
    },

    getRealEntity: function (entity, column) {
        return entity && column.collectionalProperty ? this.getCollectionalItem(entity, column) : entity;
    },

    getRealProperty: function (column) {
        return column.collectionalProperty ? column.valueProperty : column.property;
    },

    getCollectionalItem: function (entity, column) {
        if (column.collectionalProperty) {
            const collection = entity.get(column.collectionalProperty);
            const item = collection.find(cItem => column.property === cItem[column.keyProperty]);
            return item || null;
        }
    },

    getValueFromEntity: function (entity, column) {
        const realEntity = this.getRealEntity(entity, column);
        const realProperty = this.getRealProperty(column);
        return realEntity && realEntity.get(realProperty);
    },

    getBindedValue: function (entity, column) {
        return this.getValue(this.getRealEntity(entity, column), this.getRealProperty(column), column.type);
    },

    getValue: function (entity, property, type) {
        if (entity === null || property === null || type === null) {
            return '';
        } else {
            const value = entity.get(property);
            if (value === null) {
                return "";
            } else if (this._reflector.findTypeByName(type)) {
                return this._reflector.tg_toString(this._reflector.tg_convert(value), entity.type(), property);
            } else if (type.lastIndexOf('Date', 0) === 0) { // check whether type startsWith 'Date'. Type can be like 'Date', 'Date:UTC:' or 'Date:Europe/London:'
                return this._reflector.tg_toString(this._reflector.tg_convert(value), entity.type(), property);
            } else if (typeof value === 'number') {
                if (type === 'BigDecimal') {
                    return this._reflector.tg_toStringForDisplay(this._reflector.tg_convert(value), entity.type(), property, this._appConfig.locale);
                } else {
                    return this._reflector.tg_toStringForDisplay(this._reflector.tg_convert(value), entity.type(), property, this._appConfig.locale);
                }
            } else if (type === 'Money') {
                const metaProp = this._reflector.getEntityTypeProp(entity, property);
                return this._reflector.tg_formatMoney(value, this._appConfig.locale, metaProp && metaProp.scale(), metaProp && metaProp.trailingZeros());
            } else if (type === 'Colour') {
                return this._reflector.tg_toStringForDisplay(this._reflector.tg_convert(value), entity.type(), property);
            } else if (type === 'Hyperlink') {
                return this._reflector.tg_toString(this._reflector.tg_convert(value), entity.type(), property);
            } else {
                return value;
            }
        }
    }

};<commit_msg>#1044 6. Reused tg_toStringForDisplay with Money handling in EGI (value and tooltips).<commit_after>import { _millisDateRepresentation } from '/resources/reflection/tg-date-utils.js';
import { TgReflector } from '/app/tg-reflector.js';
import { TgAppConfig } from '/app/tg-app-config.js';

export const TgEgiDataRetrievalBehavior = {

    created: function () {
        this._reflector = new TgReflector();
        this._appConfig = new TgAppConfig();
    },

    isHyperlinkProp: function (entity, column) {
        return column.type === 'Hyperlink' && this.getValueFromEntity(entity, column) !== null
    },

    isBooleanProp: function (entity, column) {
        return column.type === 'Boolean' && this.getValueFromEntity(entity, column) !== null
    },

    isNotBooleanOrHyperlinkProp: function (entity, column) {
        return !(this.isBooleanProp(entity, column) || this.isHyperlinkProp(entity, column));
    },

    getAttachmentIfPossible: function (entity, column) {
        const valueFromEntity = this.getValueFromEntity(entity, column);
        if (entity.type && entity.constructor.prototype.type.call(entity).notEnhancedFullClassName() === "ua.com.fielden.platform.attachment.Attachment") {
            return entity;
        } else if (valueFromEntity && valueFromEntity.type &&
            valueFromEntity.type().notEnhancedFullClassName() === "ua.com.fielden.platform.attachment.Attachment") {
            return valueFromEntity;
        } else if (this._reflector.entityPropOwner(this.getRealEntity(entity, column), this.getRealProperty(column))) {
            const owner = this._reflector.entityPropOwner(this.getRealEntity(entity, column), this.getRealProperty(column));
            if (owner.type().notEnhancedFullClassName() === "ua.com.fielden.platform.attachment.Attachment") {
                return owner;
            }
            return null;
        } else {
            return null;
        }
    },

    getRealEntity: function (entity, column) {
        return entity && column.collectionalProperty ? this.getCollectionalItem(entity, column) : entity;
    },

    getRealProperty: function (column) {
        return column.collectionalProperty ? column.valueProperty : column.property;
    },

    getCollectionalItem: function (entity, column) {
        if (column.collectionalProperty) {
            const collection = entity.get(column.collectionalProperty);
            const item = collection.find(cItem => column.property === cItem[column.keyProperty]);
            return item || null;
        }
    },

    getValueFromEntity: function (entity, column) {
        const realEntity = this.getRealEntity(entity, column);
        const realProperty = this.getRealProperty(column);
        return realEntity && realEntity.get(realProperty);
    },

    getBindedValue: function (entity, column) {
        return this.getValue(this.getRealEntity(entity, column), this.getRealProperty(column), column.type);
    },

    getValue: function (entity, property, type) {
        if (entity === null || property === null || type === null) {
            return '';
        } else {
            const value = entity.get(property);
            if (value === null) {
                return "";
            } else if (this._reflector.findTypeByName(type)) {
                return this._reflector.tg_toString(this._reflector.tg_convert(value), entity.type(), property);
            } else if (type.lastIndexOf('Date', 0) === 0) { // check whether type startsWith 'Date'. Type can be like 'Date', 'Date:UTC:' or 'Date:Europe/London:'
                return this._reflector.tg_toString(this._reflector.tg_convert(value), entity.type(), property);
            } else if (typeof value === 'number') {
                if (type === 'BigDecimal') {
                    return this._reflector.tg_toStringForDisplay(this._reflector.tg_convert(value), entity.type(), property, this._appConfig.locale);
                } else {
                    return this._reflector.tg_toStringForDisplay(this._reflector.tg_convert(value), entity.type(), property, this._appConfig.locale);
                }
            } else if (type === 'Money') {
                return this._reflector.tg_toStringForDisplay(this._reflector.tg_convert(value), entity.type(), property, this._appConfig.locale);
            } else if (type === 'Colour') {
                return this._reflector.tg_toStringForDisplay(this._reflector.tg_convert(value), entity.type(), property);
            } else if (type === 'Hyperlink') {
                return this._reflector.tg_toString(this._reflector.tg_convert(value), entity.type(), property);
            } else {
                return value;
            }
        }
    }

};<|endoftext|>


<commit_before>/*jslint vars: false, browser: true, nomen: true */
/*global MOOC: true, Backbone, $, _, YT, async */

if (_.isUndefined(window.MOOC)) {
    window.MOOC = {};
}

MOOC.views = {};

MOOC.views.Unit = Backbone.View.extend({
    events: {
        "click li span.kq": "showKQ",
        "click li span.q": "showQ",
        "click li span.a": "showA"
    },

    render: function () {
        "use strict";
        var html = '

';
        this.model.get("knowledgeQuantumList").each(function (kq) {
            html += '
' + kq.get("title") + '';
            if (kq.has("question")) {
                html += ' ' + MOOC.trans.classroom.q + ' ';
                html += '/ ' + MOOC.trans.classroom.a + '';
            }
            html += '
';
        });
        html += '

';
        this.$el.html(html);
        return this;
    },

    showKQ: function (evt) {
        "use strict";
        var kq = $(evt.target).parent().attr("id").split("kq")[1];
        MOOC.router.navigate(this.id + "/kq" + kq, { trigger: true });
    },

    showQ: function (evt) {
        "use strict";
        var kq = $(evt.target).parent().attr("id").split("kq")[1];
        MOOC.router.navigate(this.id + "/kq" + kq + "/q", { trigger: true });
    },

    showA: function (evt) {
        "use strict";
        var kq = $(evt.target).parent().attr("id").split("kq")[1];
        MOOC.router.navigate(this.id + "/kq" + kq + "/a", { trigger: true });
    }
});

MOOC.views.unitViews = {};

MOOC.views.KnowledgeQuantum = Backbone.View.extend({
    render: function () {
        "use strict";
        var height = this.getVideoHeight(),
            player,
            html,
            unit,
            order,
            kq,
            kqObj,
            comments,
            supplementary;

        html = '';
        $("#kq-video").html(html);

        $("#kq-q-buttons").addClass("hide");
        $("#kq-next-container").addClass("offset4");

        if (this.model.has("question")) {
            kqObj = this.model;
            // Load Question Data
            MOOC.ajax.getResource(kqObj.get("question"), function (data, textStatus, jqXHR) {
                var aux = _.pick(data, "id", "last_frame", "solutionID"),
                    question = new MOOC.models.Question({
                        id: aux.id,
                        lastFrame: aux.last_frame,
                        solution: aux.solutionID
                    });
                kqObj.set("questionInstance", question);
                // Load Options for Question
                MOOC.ajax.getOptionsByQuestion(question.get("id"), function (data, textStatus, jqXHR) {
                    var options = _.map(data.objects, function (opt) {
                        return new MOOC.models.Option(_.pick(opt, "id", "optiontype", "x", "y", "width", "height", "solution"));
                    });
                    question.set("optionList", new MOOC.models.OptionList(options));
                });
                // Load Answer for Question
                MOOC.ajax.getAnswerByQuestion(question.get("id"), function (data, textStatus, jqXHR) {
                    var obj, replies, answer;
                    if (data.objects.length === 1) {
                        obj = data.objects[0];
                        replies = _.map(obj.replyList, function (reply) {
                            return new MOOC.models.Reply(_.pick(reply, "option", "value"));
                        });
                        answer = new MOOC.models.Answer({
                            id: question.get('id'),
                            date: obj.date,
                            replyList: new MOOC.models.ReplyList(replies)
                        });
                    } else {
                        answer = new MOOC.models.Answer({
                            date: null,
                            replyList: new MOOC.models.ReplyList([])
                        });
                    }
                    question.set("answer", answer);
                });
            });
            this.repeatedlyCheckIfPlayer();
        }

        $("#kq-title").html(this.model.get("title"));

        unit = MOOC.models.course.getByKQ(this.model.get("id"));
        $("#unit-selector").find("div.collapse").removeClass("in");
        $("#unit" + unit.get("id")).addClass("in");

        this.setEventForNavigation("#kq-previous", unit, this.model, false);
        this.setEventForNavigation("#kq-next", unit, this.model, true);

        this.$el.parent().children().removeClass("active");
        this.$el.addClass("active");

        comments = this.model.get("teacher_comments") || '';
        $("#comments").html(comments);

        supplementary = this.model.get("supplementary_material") || '';
        $("#suplementary").html(supplementary);

        return this;
    },

    getVideoHeight: function () {
        "use strict";
        var width = $("#kq-video").css("width"),
            height;

        if (width.indexOf('p') > 0) {
            width = parseInt(width.split('p')[0], 10);
        } else {
            width = parseInt(width, 10);
        }
        height = Math.round((width * 6) / 10);
        if (height < 200) {
            height = 200;
        }

        return height;
    },

    setEventForNavigation: function (selector, unit, kq, next) {
        "use strict";
        var path = window.location.hash,
            order = kq.get("order"),
            getUrlForOtherKQ,
            target,
            url;

        getUrlForOtherKQ = function (position, answer) {
            var aux = unit.get("knowledgeQuantumList").getByPosition(position),
                url;
            if (_.isUndefined(aux)) {
                $(selector).addClass("disabled");
            } else {
                url = "unit" + unit.get("id") + "/kq" + aux.get("id");
                if (answer && aux.has("question")) {
                    url += "/a";
                }
                return url;
            }
        };

        $(selector).unbind("click");

        if (/#[\w\/]+\/q/.test(path)) { // Viewing question
            target = next ? "answer" : "same";
        } else if (/#[\w\/]+\/a/.test(path)) { // Viewing answer
            target = next ? "next" : "question";
        } else { // Viewing kq
            target = next ? "question" : "prev";
        }
        if (target === "question" && !kq.has("question")) {
            target = "next";
        }

        switch (target) {
        case "question":
            url = "unit" + unit.get("id") + "/kq" + kq.get("id") + "/q";
            break;
        case "answer":
            url = "unit" + unit.get("id") + "/kq" + kq.get("id") + "/a";
            break;
        case "next":
            url = getUrlForOtherKQ(order + 1, false);
            break;
        case "prev":
            url = getUrlForOtherKQ(order - 1, true);
            break;
        case "same":
            url = "unit" + unit.get("id") + "/kq" + kq.get("id");
            break;
        }

        if (_.isUndefined(url)) {
            $(selector).addClass("disabled");
        } else {
            $(selector).removeClass("disabled");
            $(selector).click(function (evt) {
                MOOC.router.navigate(url, { trigger: true });
            });
        }
    },

    player: null,

    repeatedlyCheckIfPlayer: function (callback) {
        "use strict";
        if (MOOC.YTready) {
            this.player = new YT.Player("ytplayer", {
                events: {
                    onStateChange: _.bind(this.loadQuestion, this)
                }
            });
            if (!_.isUndefined(callback)) {
                callback();
            }
        } else {
            _.delay(_.bind(this.repeatedlyCheckIfPlayer, this), 200, callback);
        }
    },

    setupListernerFor: function (model, property, callback) {
        "use strict";
        if (model.has(property)) {
            callback();
        } else {
            model.on("change", function (evt) {
                if (this.has(property)) {
                    this.off("change");
                    callback();
                }
            }, model);
        }
    },

    loadQuestion: function (evt) {
        "use strict";
        if (evt.data === YT.PlayerState.ENDED) {
            var toExecute = [];

            if (!this.model.has("questionInstance")) {
                toExecute.push(async.apply(this.setupListernerFor, this.model, "questionInstance"));
            }

            toExecute.push(_.bind(function (callback) {
                var question = this.model.get("questionInstance");
                this.setupListernerFor(question, "optionList", callback);
            }, this));

            toExecute.push(_.bind(function (callback) {
                var question = this.model.get("questionInstance");
                this.setupListernerFor(question, "answer", callback);
            }, this));

            toExecute.push(_.bind(function (callback) {
                var question = this.model.get("questionInstance"),
                    view = MOOC.views.questionViews[question.get("id")],
                    destroyPlayer = _.bind(function () {
                        this.player.destroy();
                        this.player = null;
                    }, this),
                    data = {
                        kq: this.model.get("id")
                    },
                    path = window.location.hash.substring(1),
                    unit = MOOC.models.course.getByKQ(this.model);

                if (!(/[\w\/]+\/q/.test(path))) {
                    path = path + "/q";
                    MOOC.router.navigate(path, { trigger: false });
                }
                this.setEventForNavigation("#kq-previous", unit, this.model, false);
                this.setEventForNavigation("#kq-next", unit, this.model, true);

                MOOC.ajax.updateUserActivity(data);

                if (_.isUndefined(view)) {
                    view = new MOOC.views.Question({
                        model: question,
                        el: $("#kq-video")[0]
                    });
                    MOOC.views.questionViews[question.get("id")] = view;
                }
                // We need to destroy the iframe with a callback because the
                // question view needs to read the width/height of the video
                view.render(destroyPlayer);
            }, this));

            async.series(toExecute);
        }

        return this;
    },

    loadSolution: function () {
        "use strict";
        var toExecute = [];

        toExecute.push(_.bind(this.repeatedlyCheckIfPlayer, this));
        toExecute.push(_.bind(function (callback) {
            this.player.destroy();
            this.player = null;
            callback();
        }, this));

        if (!this.model.has("questionInstance")) {
            toExecute.push(async.apply(this.setupListernerFor, this.model, "questionInstance"));
        }

        toExecute.push(_.bind(function (callback) {
            var height = this.getVideoHeight(),
                html;

            html = '';
            $("#kq-video").html(html);
            $("#kq-q-buttons").addClass("hide");
            $("#kq-next-container").addClass("offset4");

            callback();
        }, this));

        async.series(toExecute);

        return this;
    }
});

MOOC.views.kqViews = {};

MOOC.views.Question = Backbone.View.extend({
    render: function (destroyPlayer) {
        "use strict";
        var width = this.$el.children().css("width"),
            height = this.$el.children().css("height"),
            kqPath = window.location.hash.substring(1, window.location.hash.length - 2), // Remove trailing /q
            html = '';
        destroyPlayer();
        this.$el.html(html);

        $("#kq-q-buttons").removeClass("hide");
        $("#kq-q-showkq").click(function () {
            MOOC.router.navigate(kqPath, { trigger: true });
        });
        $("#kq-q-submit").click(_.bind(function () {
            this.submitAnswer(this.model);
        }, this));
        $("#kq-next-container").removeClass("offset4");

        this.model.get("optionList").each(function (opt) {
            var view = MOOC.views.optionViews[opt.get("id")];
            if (_.isUndefined(view)) {
                view = new MOOC.views.Option({
                    model: opt,
                    el: $("#kq-video")[0]
                });
                MOOC.views.optionViews[opt.get("id")] = view;
            }
            view.render();
        });

        return this;
    },

    submitAnswer: function (question) {
        "use strict";
        var answer = question.get('answer'), replies;

        replies = question.get("optionList").map(function (opt) {
            var view = MOOC.views.optionViews[opt.get("id")],
                input = view.$el.find("input#option" + opt.get("id")),
                type = input.attr("type"),
                value;

            if (type === "text") {
                value = input.val();
            } else {
                value = !_.isUndefined(input.attr("checked"));
            }

            return new MOOC.models.Reply({
                option: view.model.get("id"),
                value: value,
            });
        });
        answer.set('replyList', replies);
        answer.set('date', new Date());

        MOOC.ajax.sendAnswer(answer, question.get('id'), function (data, textStatus, jqXHR) {
            if (jqXHR.status === 201) {
                answer.set('id', question.get('id'));
                question.set('answer', answer);

                MOOC.alerts.show(MOOC.alerts.SUCCESS,
                                 MOOC.trans.classroom.answersSent,
                                 MOOC.trans.classroom.answersSentMsg);
            }
        });
    }
});

MOOC.views.questionViews = {};

MOOC.views.Option = Backbone.View.extend({
    types: {
        t: "text",
        c: "checkbox",
        r: "radio"
    },

    render: function () {
        "use strict";
        var optiontype = this.model.get('optiontype'),
            answer = this.model.get('answer'),
            attributes = {
                type: this.types[optiontype],
                id: 'option' + this.model.get('id'),
                style: [
                    'top: ' + this.model.get('y') + 'px;',
                    'left: ' + this.model.get('x') + 'px;',
                    'width: ' + this.model.get('width') + 'px;',
                    'height: ' + this.model.get('height') + 'px;'
                ].join(' ')
            };
        if (optiontype === 'r') {
            attributes.name = 'radio';
        }
        if (answer) {
            if (optiontype === 't') {
                attributes.value = answer;
            } else {
                if (answer === 'True') {
                    attributes.checked = 'checked';
                }
            }
        }
        this.$el.append(this.make('input', attributes));
        return this;
    }
});

MOOC.views.optionViews = {};
<commit_msg>Fix a bad comma<commit_after>/*jslint vars: false, browser: true, nomen: true */
/*global MOOC: true, Backbone, $, _, YT, async */

if (_.isUndefined(window.MOOC)) {
    window.MOOC = {};
}

MOOC.views = {};

MOOC.views.Unit = Backbone.View.extend({
    events: {
        "click li span.kq": "showKQ",
        "click li span.q": "showQ",
        "click li span.a": "showA"
    },

    render: function () {
        "use strict";
        var html = '

';
        this.model.get("knowledgeQuantumList").each(function (kq) {
            html += '
' + kq.get("title") + '';
            if (kq.has("question")) {
                html += ' ' + MOOC.trans.classroom.q + ' ';
                html += '/ ' + MOOC.trans.classroom.a + '';
            }
            html += '
';
        });
        html += '

';
        this.$el.html(html);
        return this;
    },

    showKQ: function (evt) {
        "use strict";
        var kq = $(evt.target).parent().attr("id").split("kq")[1];
        MOOC.router.navigate(this.id + "/kq" + kq, { trigger: true });
    },

    showQ: function (evt) {
        "use strict";
        var kq = $(evt.target).parent().attr("id").split("kq")[1];
        MOOC.router.navigate(this.id + "/kq" + kq + "/q", { trigger: true });
    },

    showA: function (evt) {
        "use strict";
        var kq = $(evt.target).parent().attr("id").split("kq")[1];
        MOOC.router.navigate(this.id + "/kq" + kq + "/a", { trigger: true });
    }
});

MOOC.views.unitViews = {};

MOOC.views.KnowledgeQuantum = Backbone.View.extend({
    render: function () {
        "use strict";
        var height = this.getVideoHeight(),
            player,
            html,
            unit,
            order,
            kq,
            kqObj,
            comments,
            supplementary;

        html = '';
        $("#kq-video").html(html);

        $("#kq-q-buttons").addClass("hide");
        $("#kq-next-container").addClass("offset4");

        if (this.model.has("question")) {
            kqObj = this.model;
            // Load Question Data
            MOOC.ajax.getResource(kqObj.get("question"), function (data, textStatus, jqXHR) {
                var aux = _.pick(data, "id", "last_frame", "solutionID"),
                    question = new MOOC.models.Question({
                        id: aux.id,
                        lastFrame: aux.last_frame,
                        solution: aux.solutionID
                    });
                kqObj.set("questionInstance", question);
                // Load Options for Question
                MOOC.ajax.getOptionsByQuestion(question.get("id"), function (data, textStatus, jqXHR) {
                    var options = _.map(data.objects, function (opt) {
                        return new MOOC.models.Option(_.pick(opt, "id", "optiontype", "x", "y", "width", "height", "solution"));
                    });
                    question.set("optionList", new MOOC.models.OptionList(options));
                });
                // Load Answer for Question
                MOOC.ajax.getAnswerByQuestion(question.get("id"), function (data, textStatus, jqXHR) {
                    var obj, replies, answer;
                    if (data.objects.length === 1) {
                        obj = data.objects[0];
                        replies = _.map(obj.replyList, function (reply) {
                            return new MOOC.models.Reply(_.pick(reply, "option", "value"));
                        });
                        answer = new MOOC.models.Answer({
                            id: question.get('id'),
                            date: obj.date,
                            replyList: new MOOC.models.ReplyList(replies)
                        });
                    } else {
                        answer = new MOOC.models.Answer({
                            date: null,
                            replyList: new MOOC.models.ReplyList([])
                        });
                    }
                    question.set("answer", answer);
                });
            });
            this.repeatedlyCheckIfPlayer();
        }

        $("#kq-title").html(this.model.get("title"));

        unit = MOOC.models.course.getByKQ(this.model.get("id"));
        $("#unit-selector").find("div.collapse").removeClass("in");
        $("#unit" + unit.get("id")).addClass("in");

        this.setEventForNavigation("#kq-previous", unit, this.model, false);
        this.setEventForNavigation("#kq-next", unit, this.model, true);

        this.$el.parent().children().removeClass("active");
        this.$el.addClass("active");

        comments = this.model.get("teacher_comments") || '';
        $("#comments").html(comments);

        supplementary = this.model.get("supplementary_material") || '';
        $("#suplementary").html(supplementary);

        return this;
    },

    getVideoHeight: function () {
        "use strict";
        var width = $("#kq-video").css("width"),
            height;

        if (width.indexOf('p') > 0) {
            width = parseInt(width.split('p')[0], 10);
        } else {
            width = parseInt(width, 10);
        }
        height = Math.round((width * 6) / 10);
        if (height < 200) {
            height = 200;
        }

        return height;
    },

    setEventForNavigation: function (selector, unit, kq, next) {
        "use strict";
        var path = window.location.hash,
            order = kq.get("order"),
            getUrlForOtherKQ,
            target,
            url;

        getUrlForOtherKQ = function (position, answer) {
            var aux = unit.get("knowledgeQuantumList").getByPosition(position),
                url;
            if (_.isUndefined(aux)) {
                $(selector).addClass("disabled");
            } else {
                url = "unit" + unit.get("id") + "/kq" + aux.get("id");
                if (answer && aux.has("question")) {
                    url += "/a";
                }
                return url;
            }
        };

        $(selector).unbind("click");

        if (/#[\w\/]+\/q/.test(path)) { // Viewing question
            target = next ? "answer" : "same";
        } else if (/#[\w\/]+\/a/.test(path)) { // Viewing answer
            target = next ? "next" : "question";
        } else { // Viewing kq
            target = next ? "question" : "prev";
        }
        if (target === "question" && !kq.has("question")) {
            target = "next";
        }

        switch (target) {
        case "question":
            url = "unit" + unit.get("id") + "/kq" + kq.get("id") + "/q";
            break;
        case "answer":
            url = "unit" + unit.get("id") + "/kq" + kq.get("id") + "/a";
            break;
        case "next":
            url = getUrlForOtherKQ(order + 1, false);
            break;
        case "prev":
            url = getUrlForOtherKQ(order - 1, true);
            break;
        case "same":
            url = "unit" + unit.get("id") + "/kq" + kq.get("id");
            break;
        }

        if (_.isUndefined(url)) {
            $(selector).addClass("disabled");
        } else {
            $(selector).removeClass("disabled");
            $(selector).click(function (evt) {
                MOOC.router.navigate(url, { trigger: true });
            });
        }
    },

    player: null,

    repeatedlyCheckIfPlayer: function (callback) {
        "use strict";
        if (MOOC.YTready) {
            this.player = new YT.Player("ytplayer", {
                events: {
                    onStateChange: _.bind(this.loadQuestion, this)
                }
            });
            if (!_.isUndefined(callback)) {
                callback();
            }
        } else {
            _.delay(_.bind(this.repeatedlyCheckIfPlayer, this), 200, callback);
        }
    },

    setupListernerFor: function (model, property, callback) {
        "use strict";
        if (model.has(property)) {
            callback();
        } else {
            model.on("change", function (evt) {
                if (this.has(property)) {
                    this.off("change");
                    callback();
                }
            }, model);
        }
    },

    loadQuestion: function (evt) {
        "use strict";
        if (evt.data === YT.PlayerState.ENDED) {
            var toExecute = [];

            if (!this.model.has("questionInstance")) {
                toExecute.push(async.apply(this.setupListernerFor, this.model, "questionInstance"));
            }

            toExecute.push(_.bind(function (callback) {
                var question = this.model.get("questionInstance");
                this.setupListernerFor(question, "optionList", callback);
            }, this));

            toExecute.push(_.bind(function (callback) {
                var question = this.model.get("questionInstance");
                this.setupListernerFor(question, "answer", callback);
            }, this));

            toExecute.push(_.bind(function (callback) {
                var question = this.model.get("questionInstance"),
                    view = MOOC.views.questionViews[question.get("id")],
                    destroyPlayer = _.bind(function () {
                        this.player.destroy();
                        this.player = null;
                    }, this),
                    data = {
                        kq: this.model.get("id")
                    },
                    path = window.location.hash.substring(1),
                    unit = MOOC.models.course.getByKQ(this.model);

                if (!(/[\w\/]+\/q/.test(path))) {
                    path = path + "/q";
                    MOOC.router.navigate(path, { trigger: false });
                }
                this.setEventForNavigation("#kq-previous", unit, this.model, false);
                this.setEventForNavigation("#kq-next", unit, this.model, true);

                MOOC.ajax.updateUserActivity(data);

                if (_.isUndefined(view)) {
                    view = new MOOC.views.Question({
                        model: question,
                        el: $("#kq-video")[0]
                    });
                    MOOC.views.questionViews[question.get("id")] = view;
                }
                // We need to destroy the iframe with a callback because the
                // question view needs to read the width/height of the video
                view.render(destroyPlayer);
            }, this));

            async.series(toExecute);
        }

        return this;
    },

    loadSolution: function () {
        "use strict";
        var toExecute = [];

        toExecute.push(_.bind(this.repeatedlyCheckIfPlayer, this));
        toExecute.push(_.bind(function (callback) {
            this.player.destroy();
            this.player = null;
            callback();
        }, this));

        if (!this.model.has("questionInstance")) {
            toExecute.push(async.apply(this.setupListernerFor, this.model, "questionInstance"));
        }

        toExecute.push(_.bind(function (callback) {
            var height = this.getVideoHeight(),
                html;

            html = '';
            $("#kq-video").html(html);
            $("#kq-q-buttons").addClass("hide");
            $("#kq-next-container").addClass("offset4");

            callback();
        }, this));

        async.series(toExecute);

        return this;
    }
});

MOOC.views.kqViews = {};

MOOC.views.Question = Backbone.View.extend({
    render: function (destroyPlayer) {
        "use strict";
        var width = this.$el.children().css("width"),
            height = this.$el.children().css("height"),
            kqPath = window.location.hash.substring(1, window.location.hash.length - 2), // Remove trailing /q
            html = '';
        destroyPlayer();
        this.$el.html(html);

        $("#kq-q-buttons").removeClass("hide");
        $("#kq-q-showkq").click(function () {
            MOOC.router.navigate(kqPath, { trigger: true });
        });
        $("#kq-q-submit").click(_.bind(function () {
            this.submitAnswer(this.model);
        }, this));
        $("#kq-next-container").removeClass("offset4");

        this.model.get("optionList").each(function (opt) {
            var view = MOOC.views.optionViews[opt.get("id")];
            if (_.isUndefined(view)) {
                view = new MOOC.views.Option({
                    model: opt,
                    el: $("#kq-video")[0]
                });
                MOOC.views.optionViews[opt.get("id")] = view;
            }
            view.render();
        });

        return this;
    },

    submitAnswer: function (question) {
        "use strict";
        var answer = question.get('answer'), replies;

        replies = question.get("optionList").map(function (opt) {
            var view = MOOC.views.optionViews[opt.get("id")],
                input = view.$el.find("input#option" + opt.get("id")),
                type = input.attr("type"),
                value;

            if (type === "text") {
                value = input.val();
            } else {
                value = !_.isUndefined(input.attr("checked"));
            }

            return new MOOC.models.Reply({
                option: view.model.get("id"),
                value: value
            });
        });
        answer.set('replyList', replies);
        answer.set('date', new Date());

        MOOC.ajax.sendAnswer(answer, question.get('id'), function (data, textStatus, jqXHR) {
            if (jqXHR.status === 201) {
                answer.set('id', question.get('id'));
                question.set('answer', answer);

                MOOC.alerts.show(MOOC.alerts.SUCCESS,
                                 MOOC.trans.classroom.answersSent,
                                 MOOC.trans.classroom.answersSentMsg);
            }
        });
    }
});

MOOC.views.questionViews = {};

MOOC.views.Option = Backbone.View.extend({
    types: {
        t: "text",
        c: "checkbox",
        r: "radio"
    },

    render: function () {
        "use strict";
        var optiontype = this.model.get('optiontype'),
            answer = this.model.get('answer'),
            attributes = {
                type: this.types[optiontype],
                id: 'option' + this.model.get('id'),
                style: [
                    'top: ' + this.model.get('y') + 'px;',
                    'left: ' + this.model.get('x') + 'px;',
                    'width: ' + this.model.get('width') + 'px;',
                    'height: ' + this.model.get('height') + 'px;'
                ].join(' ')
            };
        if (optiontype === 'r') {
            attributes.name = 'radio';
        }
        if (answer) {
            if (optiontype === 't') {
                attributes.value = answer;
            } else {
                if (answer === 'True') {
                    attributes.checked = 'checked';
                }
            }
        }
        this.$el.append(this.make('input', attributes));
        return this;
    }
});

MOOC.views.optionViews = {};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015 Memorial Sloan-Kettering Cancer Center.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
 * FOR A PARTICULAR PURPOSE. The software and documentation provided hereunder
 * is on an "as is" basis, and Memorial Sloan-Kettering Cancer Center has no
 * obligations to provide maintenance, support, updates, enhancements or
 * modifications. In no event shall Memorial Sloan-Kettering Cancer Center be
 * liable to any party for direct, indirect, special, incidental or
 * consequential damages, including lost profits, arising out of the use of this
 * software and its documentation, even if Memorial Sloan-Kettering Cancer
 * Center has been advised of the possibility of such damage.
 */

/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
window.OncoprintRenderer = (function() {
	var events = oncoprint_events;
	var utils = oncoprint_utils;
	var RuleSet = oncoprint_RuleSet;

	function OncoprintRenderer(oncoprint, config) {
		this.rule_sets = {};
		this.clipping = true;
		this.oncoprint = oncoprint;
		this.config = config;
		this.upper_padding = utils.ifndef(config.upper_padding, 10);
		this.max_label_length = utils.ifndef(config.max_label_length, 20);

		(function computeLabelAreaWidth(self) {
			var label_font = self.getLabelFont();
			var max_label_width = utils.textWidth((Math.pow(10,self.max_label_length)-1).toString(), label_font);
			var max_percent_altered_width = utils.textWidth('100%', label_font);
			var buffer_width = 20;
			self.label_area_width = max_label_width + buffer_width + max_percent_altered_width;
		})(this);
	};
	OncoprintRenderer.prototype.getTrackGroupSeparation = function() {
		// TODO: configurable
		return 40;
	};
	OncoprintRenderer.prototype.getCellCSSClass = function() {
		return 'oncoprint-cell';
	};
	OncoprintRenderer.prototype.getTrackCellCSSClass = function(track_id) {
		return this.getCellCSSClass()+track_id;
	};
	OncoprintRenderer.prototype.getTrackLabelCSSClass = function(track_id) {
		return 'oncoprint-track-label oncoprint-track-label'+track_id;
	};
	OncoprintRenderer.prototype.getTrackLabelCSSSelector = function(track_id) {
		// TODO: replace with utils.cssClassToSelector
		return "."+this.getTrackLabelCSSClass(track_id).split(" ").join(".");
	};
	OncoprintRenderer.prototype.getTrackCellCtrCSSClass = function(track_id) {
		return 'oncoprint-track-cell-ctr'+track_id;
	};
	OncoprintRenderer.prototype.getLabelFont = function() {
		return this.config.label_font;
	};
	OncoprintRenderer.prototype.setRuleSet = function(track_id, type, params) {
		var new_rule_set = RuleSet.makeRuleSet(type, params);
		this.rule_sets[track_id] = new_rule_set;
		if (new_rule_set.sort_cmp) {
			this.oncoprint.setTrackSortComparator(track_id, new_rule_set.sort_cmp);
		}
	};
	OncoprintRenderer.prototype.useSameRuleSet = function(target_track_id, source_track_id) {
		var rule_set = this.rule_sets[source_track_id];
		this.rule_sets[target_track_id] = rule_set;
		if (rule_set.sort_cmp) {
			this.oncoprint.setTrackSortComparator(target_track_id, rule_set.sort_cmp);
		}
	};
	OncoprintRenderer.prototype.getRuleSet = function(track_id) {
		return this.rule_sets[track_id];
	};
	OncoprintRenderer.prototype.getTrackTops = function() {
		var ret = {};
		var y = this.upper_padding;
		var self = this;
		_.each(this.oncoprint.getTrackGroups(), function(group) {
			_.each(group, function(id) {
				ret[id] = y;
				y+= self.getRenderedTrackHeight(id);
			});
			y += self.getTrackGroupSeparation();
		});
		return ret;
	};
	OncoprintRenderer.prototype.getTrackCellTops = function() {
		return this.track_cell_tops || this.computeTrackCellTops();
	};
	OncoprintRenderer.prototype.computeTrackCellTops = function() {
		var tops = this.getTrackTops();
		var self = this;
		_.each(tops, function(top, id) {
			tops[id] = top + self.oncoprint.getTrackPadding(id);
		});
		this.track_cell_tops = tops;
		return tops;
	};
	OncoprintRenderer.prototype.getTrackLabelTops = function() {
		return this.getTrackCellTops();
	};
	OncoprintRenderer.prototype.getRenderedTrackHeight = function(track_id) {
		return this.oncoprint.getTrackHeight(track_id) + 2*this.oncoprint.getTrackPadding(track_id);
	};
	OncoprintRenderer.prototype.getCellX = function(index) {
		return (typeof index === 'number' ? index*(this.oncoprint.getZoomedCellWidth()+this.oncoprint.getCellPadding()) : -1);
	};
	OncoprintRenderer.prototype.getCellXArray = function(length) {
		var cell_unit = this.oncoprint.getZoomedCellWidth() + this.oncoprint.getCellPadding();
		return _.map(_.range(0,length), function(x) { return x*cell_unit; });
	};
	OncoprintRenderer.prototype.getCellAreaWidth = function() {
		return this.oncoprint.getVisibleIdOrder().length*(this.oncoprint.getZoomedCellWidth() + this.oncoprint.getCellPadding());
	};
	OncoprintRenderer.prototype.getCellAreaHeight = function() {
		var track_tops = this.getTrackTops();
		var track_order = this.oncoprint.getTracks();
		if (track_order.length === 0) {
			return 0;
		} else {
			var last_track = track_order[track_order.length-1];
			return track_tops[last_track] + this.getRenderedTrackHeight(last_track);
		}
	};
	OncoprintRenderer.prototype.getLabelAreaWidth = function() {
		return this.label_area_width;
	};
	OncoprintRenderer.prototype.getLabelAreaHeight = function() {
		return this.getCellAreaHeight();
	};
	OncoprintRenderer.prototype.render = function() {
		throw "not implemented in abstract class";
	}
	return OncoprintRenderer;
})();<commit_msg>fix space issue<commit_after>/*
 * Copyright (c) 2015 Memorial Sloan-Kettering Cancer Center.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
 * FOR A PARTICULAR PURPOSE. The software and documentation provided hereunder
 * is on an "as is" basis, and Memorial Sloan-Kettering Cancer Center has no
 * obligations to provide maintenance, support, updates, enhancements or
 * modifications. In no event shall Memorial Sloan-Kettering Cancer Center be
 * liable to any party for direct, indirect, special, incidental or
 * consequential damages, including lost profits, arising out of the use of this
 * software and its documentation, even if Memorial Sloan-Kettering Cancer
 * Center has been advised of the possibility of such damage.
 */

/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
window.OncoprintRenderer = (function() {
	var events = oncoprint_events;
	var utils = oncoprint_utils;
	var RuleSet = oncoprint_RuleSet;

	function OncoprintRenderer(oncoprint, config) {
		this.rule_sets = {};
		this.clipping = true;
		this.oncoprint = oncoprint;
		this.config = config;
		this.upper_padding = utils.ifndef(config.upper_padding, 10);
		this.max_label_length = utils.ifndef(config.max_label_length, 20);

		(function computeLabelAreaWidth(self) {
			var label_font = self.getLabelFont();
			var max_label_width = utils.textWidth((Math.pow(10,self.max_label_length)-1).toString(), label_font);
			var max_percent_altered_width = utils.textWidth('100%', label_font);
			var buffer_width = 20;
			self.label_area_width = max_label_width + buffer_width + max_percent_altered_width;
		})(this);
	};
	OncoprintRenderer.prototype.getTrackGroupSeparation = function() {
		// TODO: configurable
		return 40;
	};
	OncoprintRenderer.prototype.getCellCSSClass = function() {
		return 'oncoprint-cell';
	};
	OncoprintRenderer.prototype.getTrackCellCSSClass = function(track_id) {
		return this.getCellCSSClass()+track_id;
	};
	OncoprintRenderer.prototype.getTrackLabelCSSClass = function(track_id) {
		return 'oncoprint-track-label oncoprint-track-label'+track_id;
	};
	OncoprintRenderer.prototype.getTrackLabelCSSSelector = function(track_id) {
		// TODO: replace with utils.cssClassToSelector
		return "."+this.getTrackLabelCSSClass(track_id).split(" ").join(".");
	};
	OncoprintRenderer.prototype.getTrackCellCtrCSSClass = function(track_id) {
		return 'oncoprint-track-cell-ctr'+track_id;
	};
	OncoprintRenderer.prototype.getLabelFont = function() {
		return this.config.label_font;
	};
	OncoprintRenderer.prototype.setRuleSet = function(track_id, type, params) {
		var new_rule_set = RuleSet.makeRuleSet(type, params);
		this.rule_sets[track_id] = new_rule_set;
		if (new_rule_set.sort_cmp) {
			this.oncoprint.setTrackSortComparator(track_id, new_rule_set.sort_cmp);
		}
	};
	OncoprintRenderer.prototype.useSameRuleSet = function(target_track_id, source_track_id) {
		var rule_set = this.rule_sets[source_track_id];
		this.rule_sets[target_track_id] = rule_set;
		if (rule_set.sort_cmp) {
			this.oncoprint.setTrackSortComparator(target_track_id, rule_set.sort_cmp);
		}
	};
	OncoprintRenderer.prototype.getRuleSet = function(track_id) {
		return this.rule_sets[track_id];
	};
	OncoprintRenderer.prototype.getTrackTops = function() {
		var ret = {};
		var y = this.upper_padding;
		var self = this;
		_.each(this.oncoprint.getTrackGroups(), function(group) {
			if (group.length === 0) {
				return;
			}
			_.each(group, function(id) {
				ret[id] = y;
				y+= self.getRenderedTrackHeight(id);
			});
			y += self.getTrackGroupSeparation();
		});
		return ret;
	};
	OncoprintRenderer.prototype.getTrackCellTops = function() {
		return this.track_cell_tops || this.computeTrackCellTops();
	};
	OncoprintRenderer.prototype.computeTrackCellTops = function() {
		var tops = this.getTrackTops();
		var self = this;
		_.each(tops, function(top, id) {
			tops[id] = top + self.oncoprint.getTrackPadding(id);
		});
		this.track_cell_tops = tops;
		return tops;
	};
	OncoprintRenderer.prototype.getTrackLabelTops = function() {
		return this.getTrackCellTops();
	};
	OncoprintRenderer.prototype.getRenderedTrackHeight = function(track_id) {
		return this.oncoprint.getTrackHeight(track_id) + 2*this.oncoprint.getTrackPadding(track_id);
	};
	OncoprintRenderer.prototype.getCellX = function(index) {
		return (typeof index === 'number' ? index*(this.oncoprint.getZoomedCellWidth()+this.oncoprint.getCellPadding()) : -1);
	};
	OncoprintRenderer.prototype.getCellXArray = function(length) {
		var cell_unit = this.oncoprint.getZoomedCellWidth() + this.oncoprint.getCellPadding();
		return _.map(_.range(0,length), function(x) { return x*cell_unit; });
	};
	OncoprintRenderer.prototype.getCellAreaWidth = function() {
		return this.oncoprint.getVisibleIdOrder().length*(this.oncoprint.getZoomedCellWidth() + this.oncoprint.getCellPadding());
	};
	OncoprintRenderer.prototype.getCellAreaHeight = function() {
		var track_tops = this.getTrackTops();
		var track_order = this.oncoprint.getTracks();
		if (track_order.length === 0) {
			return 0;
		} else {
			var last_track = track_order[track_order.length-1];
			return track_tops[last_track] + this.getRenderedTrackHeight(last_track);
		}
	};
	OncoprintRenderer.prototype.getLabelAreaWidth = function() {
		return this.label_area_width;
	};
	OncoprintRenderer.prototype.getLabelAreaHeight = function() {
		return this.getCellAreaHeight();
	};
	OncoprintRenderer.prototype.render = function() {
		throw "not implemented in abstract class";
	}
	return OncoprintRenderer;
})();<|endoftext|>


<commit_before>context('Assignment Review', () => {

  beforeEach(() => {
    cy.visit('/course/1/assignment/review/2')
    cy.disableTours();
    cy.server();
    cy.route('GET', '/api/plans/*').as('taskPlan');
  });

  it('loads and views feedback', () => {
    cy.getTestElement('submission-overview-tab').click();
    cy.getTestElement('overview').should('exist');
    cy.getTestElement('student-free-responses').should('exist');
  });

  it('loads and views scores', () => {
    cy.getTestElement('assignment-scores-tab').click()
    cy.getTestElement('scores').should('exist');
  });

  it.skip('can publish scores', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    cy.route('PUT', '/api/tasking_plans/*/grade*').as('publishScores');
    cy.wait('@getGradingTemplates');

    cy.getTestElement('assignment-scores-tab').click();
    cy.getTestElement('publish-scores').click();
    cy.getTestElement('published-scores-toast').should('exist');
  });

  it.skip('can drop questions', () => {
    cy.getTestElement('assignment-scores-tab').click()
    cy.getTestElement('drop-questions-btn').click()
    cy.getTestElement('drop-questions-modal').should('exist')

    cy.getTestElement('drop-question-row').then(([,row]) => {
      row.querySelector('input[type="checkbox"]').click()
      const { questionId } = row.dataset;
      cy.wrap(row.querySelector('input[type="radio"][value="zeroed"]')).should('be.checked')
      cy.getTestElement('save-btn').should('be.disabled')
      cy.getTestElement('cancel-btn').click()
      cy.getTestElement('drop-questions-modal').should('not.exist')
      cy.getTestElement('drop-questions-btn').click()
      cy.get(`[data-question-id=${questionId}] input[type="checkbox"]`).should('be.checked')
    })
  });

  it('can render grading template preview', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    cy.getTestElement('grading-template-card').should('not.exist');
    cy.getTestElement('preview-card-trigger').click();
    cy.getTestElement('grading-template-card').should('exist');
  });

  it('can delete assignment', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    cy.route('DELETE', '/api/plans/2*').as('deletePlan');
    cy.getTestElement('delete-assignment').click();
    cy.getTestElement('confirm-delete-assignment').click();
    cy.wait('@deletePlan');
    cy.location('pathname').should('include', '/course/1/t/month');
  });

  it('can update details', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    cy.getTestElement('edit-assignment').click();
    cy.getTestElement('edit-assignment-name').clear().type('Update');
    cy.getTestElement('confirm-save-assignment').click();
    cy.getTestElement('assignment-name').should('have.text', 'Update');
  });

  it('requires confirmation when changing grading template', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    // Can change
    cy.getTestElement('edit-assignment').click();
    cy.getTestElement('grading-templates').click();
    cy.getTestElement('Second Homework').click();
    cy.getTestElement('confirm-change-template').click();
    cy.getTestElement('selected-grading-template').should('have.text', 'Second Homework');

    // Can cancel
    cy.getTestElement('grading-templates').click();
    cy.getTestElement('Default Homework').click();
    cy.getTestElement('cancel-confirm-change-template').click();
    cy.getTestElement('selected-grading-template').should('have.text', 'Second Homework');
  });

  // TODO: fix this test
  it.skip('only renders grading & questions blocks for homework', () => {
    cy.visit('/course/1/assignment/review/1');
    cy.getTestElement('grading-block').should('not.exist');
    cy.getTestElement('questions-block').should('not.exist');

    cy.visit('/course/1/assignment/review/2');
    cy.server();
    cy.route('GET', '/api/plans/*').as('taskPlan');
    cy.wait('@taskPlan');
    cy.getTestElement('grading-block').should('exist');
    cy.getTestElement('questions-block').should('exist');

    cy.visit('/course/1/assignment/review/4');
    cy.getTestElement('grading-block').should('not.exist');
    cy.getTestElement('questions-block').should('not.exist');

    cy.visit('/course/1/assignment/review/5');
    cy.getTestElement('grading-block').should('not.exist');
    cy.getTestElement('questions-block').should('not.exist');
  });

  it.skip('can edit assigned questions', () => {
    cy.getTestElement('edit-assigned-questions').click();
    cy.location('pathname').should('include', '/course/1/assignment/edit/homework/2/points');
  });

  it('hides overview and scores tabs if not reading or homework', () => {
    // Homework
    cy.getTestElement('submission-overview-tab').should('exist');
    cy.getTestElement('assignment-scores-tab').should('exist');

    // Reading
    cy.visit('/course/1/assignment/review/1');
    cy.getTestElement('submission-overview-tab').should('exist');
    cy.getTestElement('assignment-scores-tab').should('exist');

    // External
    cy.visit('/course/1/assignment/review/3');
    cy.getTestElement('submission-overview-tab').should('not.exist');
    cy.getTestElement('assignment-scores-tab').should('not.exist');

    // Event
    cy.visit('/course/1/assignment/review/4');
    cy.getTestElement('submission-overview-tab').should('not.exist');
    cy.getTestElement('assignment-scores-tab').should('not.exist');
  });

  it('cannot deselect sections', () => {
    cy.getTestElement('edit-assignment').click();
    cy.getTestElement('select-sections').click({ force: true });
    cy.getTestElement('tasking').first().find('[data-icon="check-square"]').first().trigger('mouseover')
    cy.get('[role="tooltip"]').contains('cannot withdraw')
  });

  it('should go directly to the submission overview tab', () => {
    cy.visit('/course/1/assignment/review/2/1?tab=1')
    cy.getTestElement('overview').should('exist');
    cy.getTestElement('student-free-responses').should('exist');
  });

  it('should go directly to the assignment scores tab', () => {
    cy.visit('/course/1/assignment/review/2/1?tab=2')
    cy.getTestElement('scores').should('exist');
  });

  it('should hide the student names', () => {
    cy.visit('/course/1/assignment/review/2/1?tab=1')
    cy.getTestElement('names-toogle-button').should('exist');
    // names are shown first, so button label is "Hide student names"
    cy.getTestElement('names-toogle-button-text').should('have.text', 'Hide student names');
    cy.getTestElement('names-toogle-button').click();
    cy.getTestElement('names-toogle-button-text').should('have.text', 'Show student names');
    // all names should be hidden
    cy.getTestElement('wrq-response-student-name').should(($name) => {
      expect($name).to.contain('Student response')
    })
  });

});
<commit_msg>force click in test<commit_after>context('Assignment Review', () => {

  beforeEach(() => {
    cy.visit('/course/1/assignment/review/2')
    cy.disableTours();
    cy.server();
    cy.route('GET', '/api/plans/*').as('taskPlan');
  });

  it('loads and views feedback', () => {
    cy.getTestElement('submission-overview-tab').click();
    cy.getTestElement('overview').should('exist');
    cy.getTestElement('student-free-responses').should('exist');
  });

  it('loads and views scores', () => {
    cy.getTestElement('assignment-scores-tab').click()
    cy.getTestElement('scores').should('exist');
  });

  it.skip('can publish scores', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    cy.route('PUT', '/api/tasking_plans/*/grade*').as('publishScores');
    cy.wait('@getGradingTemplates');

    cy.getTestElement('assignment-scores-tab').click();
    cy.getTestElement('publish-scores').click();
    cy.getTestElement('published-scores-toast').should('exist');
  });

  it.skip('can drop questions', () => {
    cy.getTestElement('assignment-scores-tab').click()
    cy.getTestElement('drop-questions-btn').click()
    cy.getTestElement('drop-questions-modal').should('exist')

    cy.getTestElement('drop-question-row').then(([,row]) => {
      row.querySelector('input[type="checkbox"]').click()
      const { questionId } = row.dataset;
      cy.wrap(row.querySelector('input[type="radio"][value="zeroed"]')).should('be.checked')
      cy.getTestElement('save-btn').should('be.disabled')
      cy.getTestElement('cancel-btn').click()
      cy.getTestElement('drop-questions-modal').should('not.exist')
      cy.getTestElement('drop-questions-btn').click()
      cy.get(`[data-question-id=${questionId}] input[type="checkbox"]`).should('be.checked')
    })
  });

  it('can render grading template preview', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    cy.getTestElement('grading-template-card').should('not.exist');
    cy.getTestElement('preview-card-trigger').click();
    cy.getTestElement('grading-template-card').should('exist');
  });

  it('can delete assignment', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    cy.route('DELETE', '/api/plans/2*').as('deletePlan');
    cy.getTestElement('delete-assignment').click();
    cy.getTestElement('confirm-delete-assignment').click();
    cy.wait('@deletePlan');
    cy.location('pathname').should('include', '/course/1/t/month');
  });

  it('can update details', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    cy.getTestElement('edit-assignment').click();
    cy.getTestElement('edit-assignment-name').clear().type('Update');
    cy.getTestElement('confirm-save-assignment').click();
    cy.getTestElement('assignment-name').should('have.text', 'Update');
  });

  it('requires confirmation when changing grading template', () => {
    cy.server();
    cy.route('GET', '/api/courses/1/grading_templates*').as('getGradingTemplates');
    // Can change
    cy.getTestElement('edit-assignment').click();
    cy.getTestElement('grading-templates').click();
    cy.getTestElement('Second Homework').click();
    cy.getTestElement('confirm-change-template').click();
    cy.getTestElement('selected-grading-template').should('have.text', 'Second Homework');

    // Can cancel
    cy.getTestElement('grading-templates').click();
    cy.getTestElement('Default Homework').click();
    cy.getTestElement('cancel-confirm-change-template').click();
    cy.getTestElement('selected-grading-template').should('have.text', 'Second Homework');
  });

  // TODO: fix this test
  it.skip('only renders grading & questions blocks for homework', () => {
    cy.visit('/course/1/assignment/review/1');
    cy.getTestElement('grading-block').should('not.exist');
    cy.getTestElement('questions-block').should('not.exist');

    cy.visit('/course/1/assignment/review/2');
    cy.server();
    cy.route('GET', '/api/plans/*').as('taskPlan');
    cy.wait('@taskPlan');
    cy.getTestElement('grading-block').should('exist');
    cy.getTestElement('questions-block').should('exist');

    cy.visit('/course/1/assignment/review/4');
    cy.getTestElement('grading-block').should('not.exist');
    cy.getTestElement('questions-block').should('not.exist');

    cy.visit('/course/1/assignment/review/5');
    cy.getTestElement('grading-block').should('not.exist');
    cy.getTestElement('questions-block').should('not.exist');
  });

  it.skip('can edit assigned questions', () => {
    cy.getTestElement('edit-assigned-questions').click();
    cy.location('pathname').should('include', '/course/1/assignment/edit/homework/2/points');
  });

  it('hides overview and scores tabs if not reading or homework', () => {
    // Homework
    cy.getTestElement('submission-overview-tab').should('exist');
    cy.getTestElement('assignment-scores-tab').should('exist');

    // Reading
    cy.visit('/course/1/assignment/review/1');
    cy.getTestElement('submission-overview-tab').should('exist');
    cy.getTestElement('assignment-scores-tab').should('exist');

    // External
    cy.visit('/course/1/assignment/review/3');
    cy.getTestElement('submission-overview-tab').should('not.exist');
    cy.getTestElement('assignment-scores-tab').should('not.exist');

    // Event
    cy.visit('/course/1/assignment/review/4');
    cy.getTestElement('submission-overview-tab').should('not.exist');
    cy.getTestElement('assignment-scores-tab').should('not.exist');
  });

  it('cannot deselect sections', () => {
    cy.getTestElement('edit-assignment').click();
    cy.getTestElement('select-sections').click({ force: true });
    cy.getTestElement('tasking').first().find('[data-icon="check-square"]').first().trigger('mouseover')
    cy.get('[role="tooltip"]').contains('cannot withdraw')
  });

  it('should go directly to the submission overview tab', () => {
    cy.visit('/course/1/assignment/review/2/1?tab=1')
    cy.getTestElement('overview').should('exist');
    cy.getTestElement('student-free-responses').should('exist');
  });

  it('should go directly to the assignment scores tab', () => {
    cy.visit('/course/1/assignment/review/2/1?tab=2')
    cy.getTestElement('scores').should('exist');
  });

  it.only('should hide the student names', () => {
    cy.visit('/course/1/assignment/review/2/1?tab=1')
    cy.getTestElement('names-toogle-button').should('exist');
    // names are shown first, so button label is "Hide student names"
    cy.getTestElement('names-toogle-button-text').should('have.text', 'Hide student names');
    cy.getTestElement('names-toogle-button').click({ force: true });
    cy.getTestElement('names-toogle-button-text').should('have.text', 'Show student names');
    // all names should be hidden
    cy.getTestElement('wrq-response-student-name').should(($name) => {
      expect($name).to.contain('Student response')
    })
  });

});
<|endoftext|>


<commit_before>import browser from "./browser"
import {domIndex, isEquivalentPosition} from "./dom"
import {hasFocusAndSelection, hasSelection, selectionToDOM} from "./selection"

const observeOptions = {
  childList: true,
  characterData: true,
  characterDataOldValue: true,
  attributes: true,
  attributeOldValue: true,
  subtree: true
}
// IE11 has very broken mutation observers, so we also listen to DOMCharacterDataModified
const useCharData = browser.ie && browser.ie_version <= 11

class SelectionState {
  constructor() {
    this.anchorNode = this.anchorOffset = this.focusNode = this.focusOffset = null
  }

  set(sel) {
    this.anchorNode = sel.anchorNode; this.anchorOffset = sel.anchorOffset
    this.focusNode = sel.focusNode; this.focusOffset = sel.focusOffset
  }

  eq(sel) {
    return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset &&
      sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset
  }
}

export class DOMObserver {
  constructor(view, handleDOMChange) {
    this.view = view
    this.handleDOMChange = handleDOMChange
    this.queue = []
    this.flushingSoon = false
    this.observer = window.MutationObserver &&
      new window.MutationObserver(mutations => {
        for (let i = 0; i < mutations.length; i++) this.queue.push(mutations[i])
        // IE11 will sometimes (on backspacing out a single character
        // text node after a BR node) call the observer callback
        // before actually updating the DOM, which will cause
        // ProseMirror to miss the change (see #930)
        if (browser.ie && browser.ie_version <= 11 && mutations.some(
          m => m.type == "childList" && m.removedNodes.length ||
               m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
          this.flushSoon()
        else
          this.flush()
      })
    this.currentSelection = new SelectionState
    if (useCharData) {
      this.onCharData = e => {
        this.queue.push({target: e.target, type: "characterData", oldValue: e.prevValue})
        this.flushSoon()
      }
    }
    this.onSelectionChange = this.onSelectionChange.bind(this)
    this.suppressingSelectionUpdates = false
  }

  flushSoon() {
    if (!this.flushingSoon) {
      this.flushingSoon = true
      window.setTimeout(() => { this.flushingSoon = false; this.flush() }, 20)
    }
  }

  start() {
    if (this.observer)
      this.observer.observe(this.view.dom, observeOptions)
    if (useCharData)
      this.view.dom.addEventListener("DOMCharacterDataModified", this.onCharData)
    this.connectSelection()
  }

  stop() {
    if (this.observer) {
      let take = this.observer.takeRecords()
      if (take.length) {
        for (let i = 0; i < take.length; i++) this.queue.push(take[i])
        window.setTimeout(() => this.flush(), 20)
      }
      this.observer.disconnect()
    }
    if (useCharData) this.view.dom.removeEventListener("DOMCharacterDataModified", this.onCharData)
    this.disconnectSelection()
  }

  connectSelection() {
    this.view.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange)
  }

  disconnectSelection() {
    this.view.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange)
  }

  suppressSelectionUpdates() {
    this.suppressingSelectionUpdates = true
    setTimeout(() => this.suppressingSelectionUpdates = false, 50)
  }

  onSelectionChange() {
    if (!hasFocusAndSelection(this.view)) return
    if (this.suppressingSelectionUpdates) return selectionToDOM(this.view)
    // Deletions on IE11 fire their events in the wrong order, giving
    // us a selection change event before the DOM changes are
    // reported.
    if (browser.ie && browser.ie_version <= 11 && !this.view.state.selection.empty) {
      let sel = this.view.root.getSelection()
      // Selection.isCollapsed isn't reliable on IE
      if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
        return this.flushSoon()
    }
    this.flush()
  }

  setCurSelection() {
    this.currentSelection.set(this.view.root.getSelection())
  }

  ignoreSelectionChange(sel) {
    if (sel.rangeCount == 0) return true
    let container = sel.getRangeAt(0).commonAncestorContainer
    let desc = this.view.docView.nearestDesc(container)
    return desc && desc.ignoreMutation({type: "selection", target: container.nodeType == 3 ? container.parentNode : container})
  }

  flush() {
    if (!this.view.docView || this.flushingSoon) return
    let mutations = this.observer ? this.observer.takeRecords() : []
    if (this.queue.length) {
      mutations = this.queue.concat(mutations)
      this.queue.length = 0
    }

    let sel = this.view.root.getSelection()
    let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasSelection(this.view) && !this.ignoreSelectionChange(sel)

    let from = -1, to = -1, typeOver = false, added = []
    if (this.view.editable) {
      for (let i = 0; i < mutations.length; i++) {
        let result = this.registerMutation(mutations[i], added)
        if (result) {
          from = from < 0 ? result.from : Math.min(result.from, from)
          to = to < 0 ? result.to : Math.max(result.to, to)
          if (result.typeOver && !this.view.composing) typeOver = true
        }
      }
    }

    if (from > -1 || newSel) {
      if (from > -1) {
        this.view.docView.markDirty(from, to)
        checkCSS(this.view)
      }
      this.handleDOMChange(from, to, typeOver)
      if (this.view.docView.dirty) this.view.updateState(this.view.state)
      else if (!this.currentSelection.eq(sel)) selectionToDOM(this.view)
    }
  }

  registerMutation(mut, added) {
    // Ignore mutations inside nodes that were already noted as inserted
    if (added.indexOf(mut.target) > -1) return null
    let desc = this.view.docView.nearestDesc(mut.target)
    if (mut.type == "attributes" &&
        (desc == this.view.docView || mut.attributeName == "contenteditable" ||
         // Firefox sometimes fires spurious events for null/empty styles
         (mut.attributeName == "style" && !mut.oldValue && !mut.target.getAttribute("style"))))
      return null
    if (!desc || desc.ignoreMutation(mut)) return null

    if (mut.type == "childList") {
      let prev = mut.previousSibling, next = mut.nextSibling
      if (browser.ie && browser.ie_version <= 11 && mut.addedNodes.length) {
        // IE11 gives us incorrect next/prev siblings for some
        // insertions, so if there are added nodes, recompute those
        for (let i = 0; i < mut.addedNodes.length; i++) {
          let {previousSibling, nextSibling} = mut.addedNodes[i]
          if (!previousSibling || Array.prototype.indexOf.call(mut.addedNodes, previousSibling) < 0) prev = previousSibling
          if (!nextSibling || Array.prototype.indexOf.call(mut.addedNodes, nextSibling) < 0) next = nextSibling
        }
      }
      let fromOffset = prev && prev.parentNode == mut.target
          ? domIndex(prev) + 1 : 0
      let from = desc.localPosFromDOM(mut.target, fromOffset, -1)
      let toOffset = next && next.parentNode == mut.target
          ? domIndex(next) : mut.target.childNodes.length
      for (let i = 0; i < mut.addedNodes.length; i++) added.push(mut.addedNodes[i])
      let to = desc.localPosFromDOM(mut.target, toOffset, 1)
      return {from, to}
    } else if (mut.type == "attributes") {
      return {from: desc.posAtStart - desc.border, to: desc.posAtEnd + desc.border}
    } else { // "characterData"
      return {
        from: desc.posAtStart,
        to: desc.posAtEnd,
        // An event was generated for a text change that didn't change
        // any text. Mark the dom change to fall back to assuming the
        // selection was typed over with an identical value if it can't
        // find another change.
        typeOver: mut.target.nodeValue == mut.oldValue
      }
    }
  }
}

let cssChecked = false

function checkCSS(view) {
  if (cssChecked) return
  cssChecked = true
  if (getComputedStyle(view.dom).whiteSpace == "normal")
    console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.")
}
<commit_msg>Work around Firefox BR insertion bug<commit_after>import browser from "./browser"
import {domIndex, isEquivalentPosition} from "./dom"
import {hasFocusAndSelection, hasSelection, selectionToDOM} from "./selection"

const observeOptions = {
  childList: true,
  characterData: true,
  characterDataOldValue: true,
  attributes: true,
  attributeOldValue: true,
  subtree: true
}
// IE11 has very broken mutation observers, so we also listen to DOMCharacterDataModified
const useCharData = browser.ie && browser.ie_version <= 11

class SelectionState {
  constructor() {
    this.anchorNode = this.anchorOffset = this.focusNode = this.focusOffset = null
  }

  set(sel) {
    this.anchorNode = sel.anchorNode; this.anchorOffset = sel.anchorOffset
    this.focusNode = sel.focusNode; this.focusOffset = sel.focusOffset
  }

  eq(sel) {
    return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset &&
      sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset
  }
}

export class DOMObserver {
  constructor(view, handleDOMChange) {
    this.view = view
    this.handleDOMChange = handleDOMChange
    this.queue = []
    this.flushingSoon = false
    this.observer = window.MutationObserver &&
      new window.MutationObserver(mutations => {
        for (let i = 0; i < mutations.length; i++) this.queue.push(mutations[i])
        // IE11 will sometimes (on backspacing out a single character
        // text node after a BR node) call the observer callback
        // before actually updating the DOM, which will cause
        // ProseMirror to miss the change (see #930)
        if (browser.ie && browser.ie_version <= 11 && mutations.some(
          m => m.type == "childList" && m.removedNodes.length ||
               m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
          this.flushSoon()
        else
          this.flush()
      })
    this.currentSelection = new SelectionState
    if (useCharData) {
      this.onCharData = e => {
        this.queue.push({target: e.target, type: "characterData", oldValue: e.prevValue})
        this.flushSoon()
      }
    }
    this.onSelectionChange = this.onSelectionChange.bind(this)
    this.suppressingSelectionUpdates = false
  }

  flushSoon() {
    if (!this.flushingSoon) {
      this.flushingSoon = true
      window.setTimeout(() => { this.flushingSoon = false; this.flush() }, 20)
    }
  }

  start() {
    if (this.observer)
      this.observer.observe(this.view.dom, observeOptions)
    if (useCharData)
      this.view.dom.addEventListener("DOMCharacterDataModified", this.onCharData)
    this.connectSelection()
  }

  stop() {
    if (this.observer) {
      let take = this.observer.takeRecords()
      if (take.length) {
        for (let i = 0; i < take.length; i++) this.queue.push(take[i])
        window.setTimeout(() => this.flush(), 20)
      }
      this.observer.disconnect()
    }
    if (useCharData) this.view.dom.removeEventListener("DOMCharacterDataModified", this.onCharData)
    this.disconnectSelection()
  }

  connectSelection() {
    this.view.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange)
  }

  disconnectSelection() {
    this.view.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange)
  }

  suppressSelectionUpdates() {
    this.suppressingSelectionUpdates = true
    setTimeout(() => this.suppressingSelectionUpdates = false, 50)
  }

  onSelectionChange() {
    if (!hasFocusAndSelection(this.view)) return
    if (this.suppressingSelectionUpdates) return selectionToDOM(this.view)
    // Deletions on IE11 fire their events in the wrong order, giving
    // us a selection change event before the DOM changes are
    // reported.
    if (browser.ie && browser.ie_version <= 11 && !this.view.state.selection.empty) {
      let sel = this.view.root.getSelection()
      // Selection.isCollapsed isn't reliable on IE
      if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
        return this.flushSoon()
    }
    this.flush()
  }

  setCurSelection() {
    this.currentSelection.set(this.view.root.getSelection())
  }

  ignoreSelectionChange(sel) {
    if (sel.rangeCount == 0) return true
    let container = sel.getRangeAt(0).commonAncestorContainer
    let desc = this.view.docView.nearestDesc(container)
    return desc && desc.ignoreMutation({type: "selection", target: container.nodeType == 3 ? container.parentNode : container})
  }

  flush() {
    if (!this.view.docView || this.flushingSoon) return
    let mutations = this.observer ? this.observer.takeRecords() : []
    if (this.queue.length) {
      mutations = this.queue.concat(mutations)
      this.queue.length = 0
    }

    let sel = this.view.root.getSelection()
    let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasSelection(this.view) && !this.ignoreSelectionChange(sel)

    let from = -1, to = -1, typeOver = false, added = []
    if (this.view.editable) {
      for (let i = 0; i < mutations.length; i++) {
        let result = this.registerMutation(mutations[i], added)
        if (result) {
          from = from < 0 ? result.from : Math.min(result.from, from)
          to = to < 0 ? result.to : Math.max(result.to, to)
          if (result.typeOver && !this.view.composing) typeOver = true
        }
      }
    }

    if (browser.gecko && added.length > 1) {
      let brs = added.filter(n => n.nodeName == "BR")
      if (brs.length == 2) {
        let [a, b] = brs
        if (a.parentNode && a.parentNode.parentNode == b.parentNode) b.remove()
        else a.remove()
      }
    }

    if (from > -1 || newSel) {
      if (from > -1) {
        this.view.docView.markDirty(from, to)
        checkCSS(this.view)
      }
      this.handleDOMChange(from, to, typeOver)
      if (this.view.docView.dirty) this.view.updateState(this.view.state)
      else if (!this.currentSelection.eq(sel)) selectionToDOM(this.view)
    }
  }

  registerMutation(mut, added) {
    // Ignore mutations inside nodes that were already noted as inserted
    if (added.indexOf(mut.target) > -1) return null
    let desc = this.view.docView.nearestDesc(mut.target)
    if (mut.type == "attributes" &&
        (desc == this.view.docView || mut.attributeName == "contenteditable" ||
         // Firefox sometimes fires spurious events for null/empty styles
         (mut.attributeName == "style" && !mut.oldValue && !mut.target.getAttribute("style"))))
      return null
    if (!desc || desc.ignoreMutation(mut)) return null

    if (mut.type == "childList") {
      let prev = mut.previousSibling, next = mut.nextSibling
      if (browser.ie && browser.ie_version <= 11 && mut.addedNodes.length) {
        // IE11 gives us incorrect next/prev siblings for some
        // insertions, so if there are added nodes, recompute those
        for (let i = 0; i < mut.addedNodes.length; i++) {
          let {previousSibling, nextSibling} = mut.addedNodes[i]
          if (!previousSibling || Array.prototype.indexOf.call(mut.addedNodes, previousSibling) < 0) prev = previousSibling
          if (!nextSibling || Array.prototype.indexOf.call(mut.addedNodes, nextSibling) < 0) next = nextSibling
        }
      }
      let fromOffset = prev && prev.parentNode == mut.target
          ? domIndex(prev) + 1 : 0
      let from = desc.localPosFromDOM(mut.target, fromOffset, -1)
      let toOffset = next && next.parentNode == mut.target
          ? domIndex(next) : mut.target.childNodes.length
      for (let i = 0; i < mut.addedNodes.length; i++) added.push(mut.addedNodes[i])
      let to = desc.localPosFromDOM(mut.target, toOffset, 1)
      return {from, to}
    } else if (mut.type == "attributes") {
      return {from: desc.posAtStart - desc.border, to: desc.posAtEnd + desc.border}
    } else { // "characterData"
      return {
        from: desc.posAtStart,
        to: desc.posAtEnd,
        // An event was generated for a text change that didn't change
        // any text. Mark the dom change to fall back to assuming the
        // selection was typed over with an identical value if it can't
        // find another change.
        typeOver: mut.target.nodeValue == mut.oldValue
      }
    }
  }
}

let cssChecked = false

function checkCSS(view) {
  if (cssChecked) return
  cssChecked = true
  if (getComputedStyle(view.dom).whiteSpace == "normal")
    console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.")
}
<|endoftext|>


<commit_before>(function () {
  "use strict";

  angular.module('mnSettingsAutoCompactionService', [
    'mnFilters',
    'mnHelper'
  ]).factory('mnSettingsAutoCompactionService', mnSettingsAutoCompactionServiceFactory);

  function mnSettingsAutoCompactionServiceFactory($http, $q, mnBytesToMBFilter, mnMBtoBytesFilter, mnHelper) {
    var mnSettingsAutoCompactionService = {
      prepareSettingsForView: prepareSettingsForView,
      prepareSettingsForSaving: prepareSettingsForSaving,
      getAutoCompaction: getAutoCompaction,
      saveAutoCompaction: saveAutoCompaction,
      prepareErrorsForView: prepareErrorsForView
    };

    return mnSettingsAutoCompactionService;

    function prepareValuesForView(holder) {
      angular.forEach(['size', 'percentage'], function (fieldName) {
        if (holder[fieldName] === "undefined") {
          holder[fieldName] = "";
        } else {
          holder[fieldName + 'Flag'] = true;
          fieldName === "size" && (holder[fieldName] = mnBytesToMBFilter(holder[fieldName]));
        }
      });
    }
    function prepareSettingsForView(settings, isBucketsDetails) {
      var acSettings = settings.autoCompactionSettings;
      prepareValuesForView(acSettings.databaseFragmentationThreshold);
      prepareValuesForView(acSettings.viewFragmentationThreshold);
      if (isBucketsDetails) {
        delete acSettings.indexFragmentationThreshold;
        delete acSettings.indexCircularCompaction;
      } else {
        if (acSettings.indexCircularCompaction) {
          acSettings.indexCircularCompactionFlag = acSettings.indexCompactionMode === "circular";
          if (acSettings.indexCircularCompaction.daysOfWeek == "") {
            acSettings.indexCircularCompactionDaysOfWeek = {};
          } else {
            acSettings.indexCircularCompactionDaysOfWeek = mnHelper.listToCheckboxes(acSettings.indexCircularCompaction.daysOfWeek.split(","));
          }
          acSettings.indexCircularCompaction = acSettings.indexCircularCompaction.interval;
        }
      }
      if (acSettings.indexFragmentationThreshold) {
        prepareValuesForView(acSettings.indexFragmentationThreshold);
      }
      acSettings.allowedTimePeriodFlag = !!acSettings.allowedTimePeriod;
      acSettings.purgeInterval = settings.purgeInterval;
      !acSettings.allowedTimePeriod && (acSettings.allowedTimePeriod = {
        abortOutside: false,
        toMinute: 0,
        toHour: 0,
        fromMinute: 0,
        fromHour: 0
      });
      return acSettings;
    }
    function prepareVluesForSaving(holder) {
      angular.forEach(['size', 'percentage'], function (fieldName) {
        if (!holder[fieldName + 'Flag']) {
          delete holder[fieldName];
        } else {
          fieldName === "size" && (holder[fieldName] = mnMBtoBytesFilter(holder[fieldName]));
        }
      });
    }
    function prepareSettingsForSaving(acSettings) {
      if (!acSettings) {
        return acSettings;
      }

      acSettings = _.clone(acSettings, true);
      if (!acSettings.allowedTimePeriodFlag) {
        delete acSettings.allowedTimePeriod;
      }
      if (acSettings.indexCircularCompaction) {
        acSettings.indexCompactionMode = acSettings.indexCircularCompactionFlag === true ? "circular" : "full";
        acSettings.indexCircularCompaction = {
          daysOfWeek: mnHelper.checkboxesToList(acSettings.indexCircularCompactionDaysOfWeek).join(','),
          interval: acSettings.indexCircularCompaction
        };
        delete acSettings.indexCircularCompactionFlag;
        delete acSettings.indexCircularCompactionDaysOfWeek;
      }
      prepareVluesForSaving(acSettings.databaseFragmentationThreshold);
      prepareVluesForSaving(acSettings.viewFragmentationThreshold);
      if (acSettings.indexFragmentationThreshold) {
        prepareVluesForSaving(acSettings.indexFragmentationThreshold);
        delete acSettings.indexFragmentationThreshold.sizeFlag;
        delete acSettings.indexFragmentationThreshold.percentageFlag;
      }
      delete acSettings.databaseFragmentationThreshold.sizeFlag;
      delete acSettings.viewFragmentationThreshold.percentageFlag;
      delete acSettings.viewFragmentationThreshold.sizeFlag;
      delete acSettings.databaseFragmentationThreshold.percentageFlag;
      delete acSettings.allowedTimePeriodFlag;
      return acSettings;
    }
    function getAutoCompaction(isBucketsDetails) {
      return $http.get('/settings/autoCompaction').then(function (resp) {
        return mnSettingsAutoCompactionService.prepareSettingsForView(resp.data, isBucketsDetails);
      });
    }
    function prepareErrorsForView(errors) {
      angular.forEach(["fromHour", "fromMinute", "toHour", "toMinute"], function (value) {
        if (errors["indexCircularCompaction[interval]["+value+"]"]) {
          errors["indexCircularCompaction["+value+"]"] = errors["indexCircularCompaction[interval]["+value+"]"];
          delete errors["indexCircularCompaction[interval]["+value+"]"];
        }
      });
      angular.forEach(errors, function (value, key) {
        errors[key.replace('[', '_').replace(']', '_')] = value;
      });
      return errors;
    }
    function saveAutoCompaction(autoCompactionSettings, params) {
      return $http({
        method: 'POST',
        url: '/controller/setAutoCompaction',
        params: params || {},
        data: mnSettingsAutoCompactionService.prepareSettingsForSaving(autoCompactionSettings)
      }).then(null, function (resp) {
        resp.data.errors = prepareErrorsForView(resp.data.errors);
        return $q.reject(resp);
      });
    }
  }
})();
<commit_msg>MB-17332: fixed validation of percentage fields in auto-compaction form<commit_after>(function () {
  "use strict";

  angular.module('mnSettingsAutoCompactionService', [
    'mnFilters',
    'mnHelper'
  ]).factory('mnSettingsAutoCompactionService', mnSettingsAutoCompactionServiceFactory);

  function mnSettingsAutoCompactionServiceFactory($http, $q, mnBytesToMBFilter, mnMBtoBytesFilter, mnHelper) {
    var mnSettingsAutoCompactionService = {
      prepareSettingsForView: prepareSettingsForView,
      prepareSettingsForSaving: prepareSettingsForSaving,
      getAutoCompaction: getAutoCompaction,
      saveAutoCompaction: saveAutoCompaction,
      prepareErrorsForView: prepareErrorsForView
    };

    return mnSettingsAutoCompactionService;

    function prepareValuesForView(holder) {
      angular.forEach(['size', 'percentage'], function (fieldName) {
        if (holder[fieldName] === "undefined") {
          holder[fieldName] = "";
        } else {
          holder[fieldName + 'Flag'] = true;
          fieldName === "size" && (holder[fieldName] = mnBytesToMBFilter(holder[fieldName]));
        }
      });
    }
    function prepareSettingsForView(settings, isBucketsDetails) {
      var acSettings = settings.autoCompactionSettings;
      prepareValuesForView(acSettings.databaseFragmentationThreshold);
      prepareValuesForView(acSettings.viewFragmentationThreshold);
      if (isBucketsDetails) {
        delete acSettings.indexFragmentationThreshold;
        delete acSettings.indexCircularCompaction;
      } else {
        if (acSettings.indexCircularCompaction) {
          acSettings.indexCircularCompactionFlag = acSettings.indexCompactionMode === "circular";
          if (acSettings.indexCircularCompaction.daysOfWeek == "") {
            acSettings.indexCircularCompactionDaysOfWeek = {};
          } else {
            acSettings.indexCircularCompactionDaysOfWeek = mnHelper.listToCheckboxes(acSettings.indexCircularCompaction.daysOfWeek.split(","));
          }
          acSettings.indexCircularCompaction = acSettings.indexCircularCompaction.interval;
        }
      }
      if (acSettings.indexFragmentationThreshold) {
        prepareValuesForView(acSettings.indexFragmentationThreshold);
      }
      acSettings.allowedTimePeriodFlag = !!acSettings.allowedTimePeriod;
      acSettings.purgeInterval = settings.purgeInterval;
      !acSettings.allowedTimePeriod && (acSettings.allowedTimePeriod = {
        abortOutside: false,
        toMinute: 0,
        toHour: 0,
        fromMinute: 0,
        fromHour: 0
      });
      return acSettings;
    }
    function prepareVluesForSaving(holder) {
      angular.forEach(['size', 'percentage'], function (fieldName) {
        if (!holder[fieldName + 'Flag']) {
          delete holder[fieldName];
        } else {
          fieldName === "size" && (holder[fieldName] = mnMBtoBytesFilter(holder[fieldName]));
          fieldName === "percentage" && (holder[fieldName] = Number(holder[fieldName]));
        }
      });
    }
    function prepareSettingsForSaving(acSettings) {
      if (!acSettings) {
        return acSettings;
      }

      acSettings = _.clone(acSettings, true);
      if (!acSettings.allowedTimePeriodFlag) {
        delete acSettings.allowedTimePeriod;
      }
      if (acSettings.indexCircularCompaction) {
        acSettings.indexCompactionMode = acSettings.indexCircularCompactionFlag === true ? "circular" : "full";
        acSettings.indexCircularCompaction = {
          daysOfWeek: mnHelper.checkboxesToList(acSettings.indexCircularCompactionDaysOfWeek).join(','),
          interval: acSettings.indexCircularCompaction
        };
        delete acSettings.indexCircularCompactionFlag;
        delete acSettings.indexCircularCompactionDaysOfWeek;
      }
      prepareVluesForSaving(acSettings.databaseFragmentationThreshold);
      prepareVluesForSaving(acSettings.viewFragmentationThreshold);
      if (acSettings.indexFragmentationThreshold) {
        prepareVluesForSaving(acSettings.indexFragmentationThreshold);
        delete acSettings.indexFragmentationThreshold.sizeFlag;
        delete acSettings.indexFragmentationThreshold.percentageFlag;
      }
      delete acSettings.databaseFragmentationThreshold.sizeFlag;
      delete acSettings.viewFragmentationThreshold.percentageFlag;
      delete acSettings.viewFragmentationThreshold.sizeFlag;
      delete acSettings.databaseFragmentationThreshold.percentageFlag;
      delete acSettings.allowedTimePeriodFlag;
      return acSettings;
    }
    function getAutoCompaction(isBucketsDetails) {
      return $http.get('/settings/autoCompaction').then(function (resp) {
        return mnSettingsAutoCompactionService.prepareSettingsForView(resp.data, isBucketsDetails);
      });
    }
    function prepareErrorsForView(errors) {
      angular.forEach(["fromHour", "fromMinute", "toHour", "toMinute"], function (value) {
        if (errors["indexCircularCompaction[interval]["+value+"]"]) {
          errors["indexCircularCompaction["+value+"]"] = errors["indexCircularCompaction[interval]["+value+"]"];
          delete errors["indexCircularCompaction[interval]["+value+"]"];
        }
      });
      angular.forEach(errors, function (value, key) {
        errors[key.replace('[', '_').replace(']', '_')] = value;
      });
      return errors;
    }
    function saveAutoCompaction(autoCompactionSettings, params) {
      return $http({
        method: 'POST',
        url: '/controller/setAutoCompaction',
        params: params || {},
        data: mnSettingsAutoCompactionService.prepareSettingsForSaving(autoCompactionSettings)
      }).then(null, function (resp) {
        resp.data.errors = prepareErrorsForView(resp.data.errors);
        return $q.reject(resp);
      });
    }
  }
})();
<|endoftext|>