<commit_before>"use strict";

const { synchronize } = require("misc");
const { ServiceMgr } = require("service");
const { notification } = require("typelib");
const singleton = require("singleton");
const Specification = require("./types/specification");
const Collector = require("./types/collector");
const Baseline = require("./types/baseline");

class Control {
    constructor() {
        synchronize(this, "update");
        synchronize(this, "generate");
    }

    // TODO: Maybe we need some sort of common lock around generate and update

    async start() {
        const mb = ServiceMgr.instance.msgBus;

        notification.on("specification.created", async (specification) => {
            for (const data of specification.collectors) {
                await Collector.createFromSpecificationData(specification._id, data);
            }
        });

        notification.on("specification.updated", async (specification) => {
            const requested = await Collector.stopAllByBaselineName(specification._id);

            for (const data of specification.collectors) {
                await Collector.createFromSpecificationData(specification._id, data, requested);
            }
        });

        notification.on("specification.removed", async (specification) => {
            await Collector.stopAllByBaselineName(specification._id);
        });

        // TODO: Handle specification update which adds new and remove

        notification.on("collector.updated", async (collector) => {
            if (collector.previousState === collector.state) {
                return;
            }

            if (collector.state === Collector.STATES.USED || collector.state === Collector.STATES.COMPLETED) {
                const specification = await Specification.findOne({ _id: collector.baseline });

                if (specification) {
                    const data = specification.collectors.filter((data) => data.name === collector.name)[0];

                    if (data) {
                        await Collector.createFromSpecificationData(specification._id, data);
                    }
                }
            } else if (collector.state === Collector.STATES.READY || collector.state === Collector.STATES.COMPLETED) {
                await this.generate(collector.baseline, false);
            }
        });

        notification.on("specification.request", async (specification) => {
            await this.generate(specification._id, true);
        });

        mb.on("data", async (event) => {
            await this.update(event);
        });
    }

    async update(event) {
        if (event.event === "created" || event.event === "updated") {
            const collectors = await Collector.findMatching(event);

            for (const collector of collectors) {
                await collector.update(event);
            }
        }
    }

    async generate(baselineName, request = false) {
        const collectors = await Collector.findLatest(baselineName);

        if (collectors.length === 0) {
            // TODO: Print error?
            return;
        }

        const readyCollectors = collectors.filter((collector) => collector.state === Collector.STATES.READY || collector.state === Collector.STATES.COMPLETED);

        if (readyCollectors.length !== collectors.length) {
            // Baseline is not ready

            if (request) {
                for (const collector of collectors) {
                    if (!collector.requested) {
                        collector.requested = new Date();
                        await collector.save();
                    }
                }
            }

            return;
        }

        if (request || collectors[0].requested) {
            for (const collector of collectors) {
                await collector.setState(Collector.STATES.USED);
            }

            const baseline = new Baseline({
                name: baselineName,
                content: collectors.map((collector) => ({
                    _ref: true,
                    name: collector.name,
                    type: collector.collectType,
                    id: collector.ids
                }))
            });

            await baseline.save();
        }
    }

    async dispose() {
        notification.removeAllListeners("specification.created");
        notification.removeAllListeners("collector.updated");
        notification.removeAllListeners("specification.request");
    }
}

module.exports = singleton(Control);
<commit_msg>BaselineGen: Bugfix to generate baseline when status is completed (#140)<commit_after>"use strict";

const { synchronize } = require("misc");
const { ServiceMgr } = require("service");
const { notification } = require("typelib");
const singleton = require("singleton");
const Specification = require("./types/specification");
const Collector = require("./types/collector");
const Baseline = require("./types/baseline");

class Control {
    constructor() {
        synchronize(this, "update");
        synchronize(this, "generate");
    }

    // TODO: Maybe we need some sort of common lock around generate and update

    async start() {
        const mb = ServiceMgr.instance.msgBus;

        notification.on("specification.created", async (specification) => {
            for (const data of specification.collectors) {
                await Collector.createFromSpecificationData(specification._id, data);
            }
        });

        notification.on("specification.updated", async (specification) => {
            const requested = await Collector.stopAllByBaselineName(specification._id);

            for (const data of specification.collectors) {
                await Collector.createFromSpecificationData(specification._id, data, requested);
            }
        });

        notification.on("specification.removed", async (specification) => {
            await Collector.stopAllByBaselineName(specification._id);
        });

        // TODO: Handle specification update which adds new and remove

        notification.on("collector.updated", async (collector) => {
            if (collector.previousState === collector.state) {
                return;
            }

            if (collector.state === Collector.STATES.USED || collector.state === Collector.STATES.COMPLETED) {
                const specification = await Specification.findOne({ _id: collector.baseline });

                if (specification) {
                    const data = specification.collectors.filter((data) => data.name === collector.name)[0];

                    if (data) {
                        await Collector.createFromSpecificationData(specification._id, data);
                    }
                }
            }

            if (collector.state === Collector.STATES.READY || collector.state === Collector.STATES.COMPLETED) {
                await this.generate(collector.baseline, false);
            }
        });

        notification.on("specification.request", async (specification) => {
            await this.generate(specification._id, true);
        });

        mb.on("data", async (event) => {
            await this.update(event);
        });
    }

    async update(event) {
        if (event.event === "created" || event.event === "updated") {
            const collectors = await Collector.findMatching(event);

            for (const collector of collectors) {
                await collector.update(event);
            }
        }
    }

    async generate(baselineName, request = false) {
        const collectors = await Collector.findLatest(baselineName);

        if (collectors.length === 0) {
            // TODO: Print error?
            return;
        }

        const readyCollectors = collectors.filter((collector) => collector.state === Collector.STATES.READY || collector.state === Collector.STATES.COMPLETED);

        if (readyCollectors.length !== collectors.length) {
            // Baseline is not ready

            if (request) {
                for (const collector of collectors) {
                    if (!collector.requested) {
                        collector.requested = new Date();
                        await collector.save();
                    }
                }
            }

            return;
        }

        if (request || collectors[0].requested) {
            for (const collector of collectors) {
                await collector.setState(Collector.STATES.USED);
            }

            const baseline = new Baseline({
                name: baselineName,
                content: collectors.map((collector) => ({
                    _ref: true,
                    name: collector.name,
                    type: collector.collectType,
                    id: collector.ids
                }))
            });

            await baseline.save();
        }
    }

    async dispose() {
        notification.removeAllListeners("specification.created");
        notification.removeAllListeners("collector.updated");
        notification.removeAllListeners("specification.request");
    }
}

module.exports = singleton(Control);
<|endoftext|>


<commit_before>import React from 'react';

import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';

export default {
  descriptions: {
    // lv15
    [SPELLS.BESTOW_FAITH_TALENT.id]: <SpellLink id={SPELLS.BESTOW_FAITH_TALENT.id} /> is the default choice for most encounters in raids. The 5-second delay requires foresight, but this ability gives great healing for a very low mana cost.,
    [SPELLS.LIGHTS_HAMMER_TALENT.id]: Unfortunately Light's Hammer isn't as good of a choice as it may seem. Even if you can use it on cooldown and it doesn't overheal Bestow Faith will outperform it in all situations. Primarily due to its low mana cost and beacon transfer(s). For this reason it is not recommended to take Light's Hammer.,
    [SPELLS.CRUSADERS_MIGHT_TALENT.id]: Unfortunately Crusader's Might is usually only viable with a unique playstyle that does not work well with the T19 4 set bonus. For this reason it is not recommended to take Crusader's Might.,
    // lv30
    [SPELLS.CAVALIER_TALENT.id]: You can take Cavalier if most of the raid is clumped together and you won't need Rule of Law, or if you have a specific need for two charges of Divine Steed. It's important to understand that two charges doesn't actually mean you get twice as many uses of Divine Steed. If you use both charges at once, only one will recharge at a time meaning it will take 90 seconds for you to have both charges up again.,
    [SPELLS.UNBREAKABLE_SPIRIT_TALENT.id]: Unbreakable Spirit is almost never a valid choice.,
    [SPELLS.RULE_OF_LAW_TALENT.id]: Rule of Law is the default choice for raiding. Because the cooldown is so short, you should try to use it with Light of Dawn as often as you can. Rule of Law is also extremely helpful for healing targets if they happen to be out of range and need healing e.g.: running out with a debuff.,
    // lv45
    [SPELLS.FIST_OF_JUSTICE_TALENT_SHARED.id]: The talent selected in this tier usually has no impact in raids.,
    [SPELLS.REPENTANCE_TALENT.id]: The talent selected in this tier usually has no impact in raids.,
    [SPELLS.BLINDING_LIGHT_TALENT.id]: The talent selected in this tier usually has no impact in raids.,
    // lv60
    [SPELLS.DEVOTION_AURA_TALENT.id]: Devotion Aura and Aura of Mercy are both situationally good. Devotion Aura is the best choice to mitigate damage from any heavy-hitting boss mechanic. If you won't need the damage reduction from Devotion Aura, then you should take Aura of Mercy as it will result in more overall healing.,
    [SPELLS.AURA_OF_SACRIFICE_TALENT.id]: Aura of Sacrifice works as a decent raid cooldown with Aura Mastery. It is best to use it during Avenging Wrath and/or Holy Avenger. For this reason it is more situationally useful as not all encounters work out such that you can line up all your cool downs at once, and have it not be wasted.,
    [SPELLS.AURA_OF_MERCY_TALENT.id]: Devotion Aura and Aura of Mercy are both situationally good. Devotion Aura is the best choice to mitigate damage from any heavy-hitting boss mechanic. If you won't need the damage reduction from Devotion Aura, then you should take Aura of Mercy as it will result in more overall healing.,
    // lv75
    [SPELLS.DIVINE_PURPOSE_TALENT_HOLY.id]: Divine Purpose provides a small chance for a free Holy Shock or Light of Dawn, which saves mana by not having to cast something that consumes mana. The downside to Divine Purpose is that it is not very reliable, unlike Holy Avenger. This tier is mostly preference.,
    [SPELLS.HOLY_AVENGER_TALENT.id]: Holy Avenger provides a lot of on-demand healing. The downside to Holy Avenger is that is requires fairly heavy mana usage to be effective. This tier is mostly preference.,
    [SPELLS.HOLY_PRISM_TALENT.id]: Holy Prism provides you another AoE heal similar to Light of Dawn. It doesn't heal for a lot while it's mana cost is fairly high, so it is usually not recommended to pick this talent. This tier is mostly preference.,
    // lv90
    [SPELLS.FERVENT_MARTYR_TALENT.id]: https://www.youtube.com/watch?v=WWaLxFIVX1s,
    [SPELLS.SANCTIFIED_WRATH_TALENT.id]: Only take Sanctified Wrath if a fight really requires burst healing, otherwise Judgment of Light is always the better choice (especially with Chain of Thrayn).,
    [SPELLS.JUDGMENT_OF_LIGHT_TALENT.id]: Judgment of Light is the default choice in this tier as it does a lot of consistent healing.,
    // lv100
    [SPELLS.BEACON_OF_FAITH_TALENT.id]: Beacon of Faith is excellent when both tanks are taking damage; specifically if there are a lot of adds that the off-tank has to pick up, or on any fight where the boss cleaves onto both tanks. In the Nighthold this is the best default choice.,
    [SPELLS.BEACON_OF_THE_LIGHTBRINGER_TALENT.id]: Beacon of the Lightbringer only provides a single beacon so requires you to manage swapping your Beacon onto the tank that is taking damage. The upside is that it increases your mastery effectiveness and the healing of Light of Dawn. In the Nighthold there is rarely a good reason to pick this talent over Beacon of Faith.,
    [SPELLS.BEACON_OF_VIRTUE_TALENT.id]: Beacon of Virtue is rarely a good choice in raids. It requires you to use a Global Cooldown to apply to random targets and costs a considerable amount of mana.,
  },
  attribution: Parts of the descriptions for talents came from the Holy Paladin Wowhead guide by Pelinal.,
};
<commit_msg>Update Holy Paladin T15 talent descriptions<commit_after>import React from 'react';

import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';

export default {
  descriptions: {
    // lv15
    [SPELLS.BESTOW_FAITH_TALENT.id]: <SpellLink id={SPELLS.BESTOW_FAITH_TALENT.id} /> is the default choice for most encounters in raids. The 5-second delay requires foresight, but this ability gives great healing for a very low mana cost. With patch 7.2.5 <SpellLink id={SPELLS.LIGHTS_HAMMER_TALENT.id} /> is a slightly better talent and could see some use. ,
    [SPELLS.LIGHTS_HAMMER_TALENT.id]: <SpellLink id={SPELLS.LIGHTS_HAMMER_TALENT.id} /> could be used on encounters where a large number of stacked raid members will sustain damage. It is quite poor on fights where the raid must spread out or move out of the active area.,
    [SPELLS.CRUSADERS_MIGHT_TALENT.id]: <SpellLink id={SPELLS.CRUSADERS_MIGHT_TALENT.id} /> has uses in dungeons where you can spend a good amount of time using <SpellLink id={SPELLS.CRUSADER_STRIKE.id} /> to deal damage. <SpellLink id={SPELLS.BESTOW_FAITH_TALENT.id} /> is also perfectly fine for Dungeons and works really well with <SpellLink id={SPELLS.BEACON_OF_VIRTUE_TALENT.id} />.,
    // lv30
    [SPELLS.CAVALIER_TALENT.id]: You can take Cavalier if most of the raid is clumped together and you won't need Rule of Law, or if you have a specific need for two charges of Divine Steed. It's important to understand that two charges doesn't actually mean you get twice as many uses of Divine Steed. If you use both charges at once, only one will recharge at a time meaning it will take 90 seconds for you to have both charges up again.,
    [SPELLS.UNBREAKABLE_SPIRIT_TALENT.id]: Unbreakable Spirit is almost never a valid choice.,
    [SPELLS.RULE_OF_LAW_TALENT.id]: Rule of Law is the default choice for raiding. Because the cooldown is so short, you should try to use it with Light of Dawn as often as you can. Rule of Law is also extremely helpful for healing targets if they happen to be out of range and need healing e.g.: running out with a debuff.,
    // lv45
    [SPELLS.FIST_OF_JUSTICE_TALENT_SHARED.id]: The talent selected in this tier usually has no impact in raids.,
    [SPELLS.REPENTANCE_TALENT.id]: The talent selected in this tier usually has no impact in raids.,
    [SPELLS.BLINDING_LIGHT_TALENT.id]: The talent selected in this tier usually has no impact in raids.,
    // lv60
    [SPELLS.DEVOTION_AURA_TALENT.id]: Devotion Aura and Aura of Mercy are both situationally good. Devotion Aura is the best choice to mitigate damage from any heavy-hitting boss mechanic. If you won't need the damage reduction from Devotion Aura, then you should take Aura of Mercy as it will result in more overall healing.,
    [SPELLS.AURA_OF_SACRIFICE_TALENT.id]: Aura of Sacrifice works as a decent raid cooldown with Aura Mastery. It is best to use it during Avenging Wrath and/or Holy Avenger. For this reason it is more situationally useful as not all encounters work out such that you can line up all your cool downs at once, and have it not be wasted.,
    [SPELLS.AURA_OF_MERCY_TALENT.id]: Devotion Aura and Aura of Mercy are both situationally good. Devotion Aura is the best choice to mitigate damage from any heavy-hitting boss mechanic. If you won't need the damage reduction from Devotion Aura, then you should take Aura of Mercy as it will result in more overall healing.,
    // lv75
    [SPELLS.DIVINE_PURPOSE_TALENT_HOLY.id]: Divine Purpose provides a small chance for a free Holy Shock or Light of Dawn, which saves mana by not having to cast something that consumes mana. The downside to Divine Purpose is that it is not very reliable, unlike Holy Avenger. This tier is mostly preference.,
    [SPELLS.HOLY_AVENGER_TALENT.id]: Holy Avenger provides a lot of on-demand healing. The downside to Holy Avenger is that is requires fairly heavy mana usage to be effective. This tier is mostly preference.,
    [SPELLS.HOLY_PRISM_TALENT.id]: Holy Prism provides you another AoE heal similar to Light of Dawn. It doesn't heal for a lot while it's mana cost is fairly high, so it is usually not recommended to pick this talent. This tier is mostly preference.,
    // lv90
    [SPELLS.FERVENT_MARTYR_TALENT.id]: https://www.youtube.com/watch?v=WWaLxFIVX1s,
    [SPELLS.SANCTIFIED_WRATH_TALENT.id]: Only take Sanctified Wrath if a fight really requires burst healing, otherwise Judgment of Light is always the better choice (especially with Chain of Thrayn).,
    [SPELLS.JUDGMENT_OF_LIGHT_TALENT.id]: Judgment of Light is the default choice in this tier as it does a lot of consistent healing.,
    // lv100
    [SPELLS.BEACON_OF_FAITH_TALENT.id]: Beacon of Faith is excellent when both tanks are taking damage; specifically if there are a lot of adds that the off-tank has to pick up, or on any fight where the boss cleaves onto both tanks. In the Nighthold this is the best default choice.,
    [SPELLS.BEACON_OF_THE_LIGHTBRINGER_TALENT.id]: Beacon of the Lightbringer only provides a single beacon so requires you to manage swapping your Beacon onto the tank that is taking damage. The upside is that it increases your mastery effectiveness and the healing of Light of Dawn. In the Nighthold there is rarely a good reason to pick this talent over Beacon of Faith.,
    [SPELLS.BEACON_OF_VIRTUE_TALENT.id]: Beacon of Virtue is rarely a good choice in raids. It requires you to use a Global Cooldown to apply to random targets and costs a considerable amount of mana.,
  },
  attribution: Parts of the descriptions for talents came from the Holy Paladin Wowhead guide by Pelinal.,
};
<|endoftext|>


<commit_before>
mw.IA =
{
  css:function(str)
  {
    var obj = document.createElement('style');
    obj.setAttribute('type', 'text/css');
    if (obj.styleSheet)
      obj.styleSheet.cssText = str; //MSIE
    else
      obj.appendChild(document.createTextNode(str)); // other browsers

    var headobj = document.getElementsByTagName("head")[0];
    headobj.appendChild(obj);
  },

  // try to parse the identifier from the video and make the lower right icon
  // then go to the item's /details/ page
  detailsLink:function()
  {
    return (typeof(location.pathname)!='undefined'  &&
            location.pathname.length>0  &&
            location.pathname.match(/^\/details\//) ?
            '/details/'+location.pathname.replace(/^\/details\/([^\/]+).*$/, '$1')
            : '');
  },

  embedUrl:function()
  {
    return ('http://www.archive.org/embed/' +
            mw.IA.detailsLink().replace(/\/details\//,''));
  },

playingClipNumMW:0,

// Set up so that:
//   - when "click to play" clicked, resize video window and playlist
//   - we advance to the next clip (when 2+ present)
newEmbedPlayerMW:function(arg)
{
  var player = $('#mwplayer');
  if (!player)
    return;

  mw.log('newEmbedPlayerMW()');
  player.bind('ended', mw.IA.onDoneMW);
  player.unbind('play').bind('play', mw.IA.firstplayMW);
},

resizeMW:function()
{
  var player = $('#mwplayer');

  $('#flowplayerdiv')[0].style.width  = IAPlay.FLASH_WIDTH;
  $('#flowplayerdiv')[0].style.height = IAPlay.VIDEO_HEIGHT;

  $('#flowplayerplaylist')[0].style.width  = IAPlay.FLASH_WIDTH;

  var jplay = player[0];
  IAD.log('IA ' + jplay.getWidth() + 'x' + jplay.getHeight());

  jplay.resizePlayer({'width':  IAPlay.FLASH_WIDTH,
        'height': IAPlay.VIDEO_HEIGHT},true);
},

firstplayMW:function()
{
  if (typeof(mw.IA.MWsetup)!='undefined')
    return;
  mw.IA.MWsetup = true;

  mw.log('firstplayMW()');
  mw.IA.resizeMW();
},

playClipMW:function(idx, id, mp4, ogv)
{
  mw.IA.playingClipNumMW = idx;
  mw.log('IAplay: '+mp4+'('+idx+')');

  // set things up so we can update the "playing triangle"
  IAPlay.flowplayerplaylist = $('#flowplayerplaylist')[0];
  IAPlay.indicateIsPlaying(idx);

  mw.ready(function(){

      var player = $('#mwplayer'); //
<video ...>

      if (!player)
        return;

      player.embedPlayer(
        { 'autoplay' : true, 'autoPlay' : true,
            'sources' : [
              { 'src' : '/download/'+id+'/'+mp4 },
              { 'src' : '/download/'+id+'/'+ogv }
              ]
            }
        );
    });

  return false;
},

onDoneMW:function(event, onDoneActionObject )
{
  mw.IA.playingClipNumMW++;

  var plist = $('#flowplayerplaylist')[0].getElementsByTagName('tr');
  mw.log(plist);
  var row=plist[mw.IA.playingClipNumMW];
  if (typeof(row)=='undefined')
    return;

  var js=row.getAttribute('onClick');
  //alert('HIYA xxxx tracey '+mw.IA.playingClipNumMW + ' ==> ' + js);

  var parts=js.split("'");
  var id=parts[1];
  var mp4=parts[3];
  var ogv=parts[5];

  mw.IA.playClipMW(mw.IA.playingClipNumMW, id, mp4, ogv);
},

  setup:function()
  {
    mw.IA.css(".archive-icon {\n\
background-image:url('http://www.archive.org/images/logo-mw.png') !important;\n\
background-repeat:no-repeat;\n\
display: block;\n\
height: 12px;\n\
width: 12px;\n\
margin-top: -6px !important;\n\
margin-left: 3px !important;\n\
}\n\
\n\
div.control-bar { -moz-border-radius:6px; -webkit-border-radius:6px; -khtml-border-radius:6px; border-radius:6px; }\n\
\n\
");

    var det = mw.IA.detailsLink();

    if (det == ''  &&  typeof(document.getElementsByTagName)!='undefined')
    {
      var els = document.getElementsByTagName('object');
      if (els  &&  els.length)
      {
        var i=0;
        for (i=0; i < els.length; i++)
        {
          var el = els[i];
          if (typeof(el.data)!='undefined')
          {
            var mat = el.data.match(/\.archive.org\/download\/([^\/]+)/);
            if (typeof(mat)!='undefined'  &&  mat  &&  mat.length>1)
            {
              det = '/details/' + mat[1]; //xxx not working yet for embed codes!!
              break;
            }
          }
        }
      }
    }

    //var mods = mw.getConfig('enabledModules');
    //mods.push('InternetArchiveSupport');

    mw.setConfig( {
        // We want our video player to attribute something...
        "EmbedPlayer.AttributionButton" : true,

        // 'enabledModules' : mods,

        //"EmbedPlayer.NativeControlsMobileSafari" : true, //xxx

        // Our attribution button
        'EmbedPlayer.AttributionButton' : {
          'title' : 'Internet Archive',
          'href' : 'http://www.archive.org'+det,
          'class' : 'archive-icon'
        }
      });

    //alert(mw.getConfig('enabledModules'));
    //mw.load('mw.InternetArchiveSupport', function() { alert('loada'); });
  }
}

mw.IA.setup();
<commit_msg>moving more of IA JS code into its mwEmbed module<commit_after>
mw.IA =
{
  css:function(str)
  {
    var obj = document.createElement('style');
    obj.setAttribute('type', 'text/css');
    if (obj.styleSheet)
      obj.styleSheet.cssText = str; //MSIE
    else
      obj.appendChild(document.createTextNode(str)); // other browsers

    var headobj = document.getElementsByTagName("head")[0];
    headobj.appendChild(obj);
  },

  // try to parse the identifier from the video and make the lower right icon
  // then go to the item's /details/ page
  detailsLink:function()
  {
    return (typeof(location.pathname)!='undefined'  &&
            location.pathname.length>0  &&
            location.pathname.match(/^\/details\//) ?
            '/details/'+location.pathname.replace(/^\/details\/([^\/]+).*$/, '$1')
            : '');
  },

  embedUrl:function()
  {
    return ('http://www.archive.org/embed/' +
            mw.IA.detailsLink().replace(/\/details\//,''));
  },

playingClipNumMW:0,

// Set up so that:
//   - when "click to play" clicked, resize video window and playlist
//   - we advance to the next clip (when 2+ present)
newEmbedPlayerMW:function(arg)
{
  var player = $('#mwplayer');
  if (!player)
    return;

  mw.log('newEmbedPlayerMW()');
  player.bind('ended', mw.IA.onDoneMW);
  player.unbind('play').bind('play', mw.IA.firstplayMW);
},

resizeMW:function()
{
  var player = $('#mwplayer');

  $('#flowplayerdiv')[0].style.width  = IAPlay.FLASH_WIDTH;
  $('#flowplayerdiv')[0].style.height = IAPlay.VIDEO_HEIGHT;

  $('#flowplayerplaylist')[0].style.width  = IAPlay.FLASH_WIDTH;

  var jplay = player[0];
  IAD.log('IA ' + jplay.getWidth() + 'x' + jplay.getHeight());

  jplay.resizePlayer({'width':  IAPlay.FLASH_WIDTH,
        'height': IAPlay.VIDEO_HEIGHT},true);
},

firstplayMW:function()
{
  if (typeof(mw.IA.MWsetup)!='undefined')
    return;
  mw.IA.MWsetup = true;

  mw.log('firstplayMW()');
  mw.IA.resizeMW();
},

playClipMW:function(idx, id, mp4, ogv)
{
  mw.IA.playingClipNumMW = idx;
  mw.log('IAplay: '+mp4+'('+idx+')');

  // set things up so we can update the "playing triangle"
  IAPlay.flowplayerplaylist = $('#flowplayerplaylist')[0];
  IAPlay.indicateIsPlaying(idx);

  mw.ready(function(){

      var player = $('#mwplayer'); //
<video ...>

      if (!player)
        return;

      player.embedPlayer(
        { 'autoplay' : true, 'autoPlay' : true,
            'sources' : [
              { 'src' : '/download/'+id+'/'+mp4 },
              { 'src' : '/download/'+id+'/'+ogv }
              ]
            }
        );
    });

  return false;
},

onDoneMW:function(event, onDoneActionObject )
{
  mw.IA.playingClipNumMW++;

  var plist = $('#flowplayerplaylist')[0].getElementsByTagName('tr');
  mw.log(plist);
  var row=plist[mw.IA.playingClipNumMW];
  if (typeof(row)=='undefined')
    return;

  var js=row.getAttribute('onClick');
  //alert('HIYA xxxx tracey '+mw.IA.playingClipNumMW + ' ==> ' + js);

  var parts=js.split("'");
  var id=parts[1];
  var mp4=parts[3];
  var ogv=parts[5];

  mw.IA.playClipMW(mw.IA.playingClipNumMW, id, mp4, ogv);
},

  setup:function()
  {
    mw.IA.css(".archive-icon {\n\
background-image:url('http://www.archive.org/images/logo-mw.png') !important;\n\
background-repeat:no-repeat;\n\
display: block;\n\
height: 12px;\n\
width: 12px;\n\
margin-top: -6px !important;\n\
margin-left: 3px !important;\n\
}\n\
\n\
div.control-bar { -moz-border-radius:6px; -webkit-border-radius:6px; -khtml-border-radius:6px; border-radius:6px; }\n\
\n\
");

    var det = mw.IA.detailsLink();

    if (det == ''  &&  typeof(document.getElementsByTagName)!='undefined')
    {
      var els = document.getElementsByTagName('object');
      if (els  &&  els.length)
      {
        var i=0;
        for (i=0; i < els.length; i++)
        {
          var el = els[i];
          if (typeof(el.data)!='undefined')
          {
            var mat = el.data.match(/\.archive.org\/download\/([^\/]+)/);
            if (typeof(mat)!='undefined'  &&  mat  &&  mat.length>1)
            {
              det = '/details/' + mat[1]; //xxx not working yet for embed codes!!
              break;
            }
          }
        }
      }
    }

    //var mods = mw.getConfig('enabledModules');
    //mods.push('InternetArchiveSupport');

    mw.setConfig( {
        // We want our video player to attribute something...
        "EmbedPlayer.AttributionButton" : true,

        // 'enabledModules' : mods,

        //"EmbedPlayer.NativeControlsMobileSafari" : true, //xxx

        // Our attribution button
        'EmbedPlayer.AttributionButton' : {
          'title' : 'Internet Archive',
          'href' : 'http://www.archive.org'+det,
          'class' : 'archive-icon'
        }
      });

    //alert(mw.getConfig('enabledModules'));
    //mw.load('mw.InternetArchiveSupport', function() { alert('loada'); });

    // NOTE: keep this outside "mw.ready()" so that "click-to-play" does indeed
    // cause the newEmbedPlayerMW() call
    $( mw ).bind('newEmbedPlayerEvent', mw.IA.newEmbedPlayerMW);

    mw.ready(function(){
        mw.log("IAPlayer says mw.ready()");

        var star = (IAPlay.arg('start') ? parseFloat(IAPlay.arg('start')) : 0);
        if (star)
        {
          mw.IA.resizeMW();
          var jplay = $('#mwplayer').get(0);
          var dura = jplay.duration;
          IAD.log(star+'s of '+dura+'s');

          jplay.currentTime = star;
          jplay.play();
        }
      });
  }
};

mw.IA.setup();
<|endoftext|>


<commit_before>var utils = require('../utils');

var countColor = function(count) {
  return "blue";
};

var font = function(size) {
  var pxSize = size;
  return "bold " + pxSize + "px Arial";
};

var dirtOffsets = [
  { x: 0.6, y: 0.2 },
  { x: 0.3, y: 0.6 },
  { x: 0.7, y: 0.7 }];

var starOffsets = [
  { x: -0.13333333333333333, y: -0.8666666666666667 },
  { x: 0.06666666666666667,  y: -0.3333333333333333 },
  { x: 0.26666666666666666,  y: -0.6666666666666666 },
  { x: 0.26666666666666666,  y: -0.2 },
  { x: 0.6666666666666666,   y: 0.26666666666666666 },
  { x: 0.13333333333333333,  y: 0.06666666666666667 },
  { x: 0.4666666666666667,   y: 0.7333333333333333 },
  { x: -0.13333333333333333, y: 0.13333333333333333 },
  { x: -0.4666666666666667,  y: 0.6 },
  { x: -0.4,                 y: 0 },
  { x: -0.7333333333333333,  y: -0.4666666666666667 },
  { x: -0.4,                 y: -0.3333333333333333 } ];

var flagRects = [
  {
    color: 'red',
    x: -0.4666666666666667,
    y: -0.7333333333333333,
    width: 0.2,
    height: 0.2
  },
  {
    color: 'black',
    x: -0.06666666666666667,
    y: -0.7333333333333333,
    width: 0.06666666666666667,
    height: 0.6666666666666666
  },
  {
    color: 'black',
    x: -0.4,
    y: 0.6,
    width: 0.43333333333333335,
    height: 0.06666666666666667
  } ];

var FieldLocationArtist = function(options) {
  utils.requireOptions(options, 'context', 'metrics', 'field');

  this.context = options.context;
  this.field = options.field;
  this.size = options.metrics.locationSize;
};

FieldLocationArtist.prototype.draw = function(location, x, y) {
  //this._drawBorder(x, y);

  if (location.detonated) {
    this._drawStar(x, y);
    return;
  }

  if (location.flagged) {
    this._drawFlag(x, y);
    return;
  }

  if (!location.dug) {

    this._drawDirt(x, y);

  } else {

    var mineCount = this.field.countMines(x, y);
    if (mineCount > 0) {
      this._drawCount(x, y, mineCount);
    }

  }

};

FieldLocationArtist.prototype._drawDirt = function(x, y) {
  var self = this;
  dirtOffsets.forEach(function(offset) {
    self._drawDirtBox(x, y, offset);
  });
};

FieldLocationArtist.prototype._drawDirtBox = function(x, y, offset) {
  var a = x * this.size + offset.x * this.size,
      b = y * this.size + offset.y * this.size,
      dirtSize = this.size * 0.1,
      ctx = this.context;

  ctx.beginPath();
  ctx.rect(a, b, dirtSize, dirtSize);
  ctx.fillStyle = 'black';
  ctx.fill();
};

FieldLocationArtist.prototype._drawCount = function(x, y, count) {
  var center = utils.squareCenter(x * this.size, y * this.size, this.size);
  var ctx = this.context;

  ctx.fillStyle    = countColor(count);
  ctx.font         = font(this.size);
  ctx.textAlign    = 'center';
  ctx.textBaseline = 'middle';

  ctx.fillText(count, center.x, center.y);
};

FieldLocationArtist.prototype._drawStar = function(x, y) {

  var ctx = this.context;
  var center = utils.squareCenter(x * this.size, y * this.size, this.size);

  ctx.beginPath();
  var pt = this._starOffsetToPoint(center, 0);
  ctx.moveTo(pt.x, pt.y);
  console.log(pt);

  for(var i = 1; i < starOffsets.length; i++) {
    pt = this._starOffsetToPoint(center, i);
    ctx.lineTo(pt.x, pt.y);
    console.log(pt);
  }

  ctx.closePath();
  ctx.fillStyle = 'yellow';
  ctx.fill();
  ctx.stroke();

  ctx.beginPath();
  var r = (1/15)*(this.size);
  ctx.arc(center.x, center.y, r, 0, 2*Math.PI, false);
  ctx.fillStyle='red';
  ctx.fill();
  ctx.stroke();
};

FieldLocationArtist.prototype._starOffsetToPoint = function(center, starPointIndex) {
  return {
    x: center.x + starOffsets[starPointIndex].x * (this.size / 2),
    y: center.y + starOffsets[starPointIndex].y * (this.size / 2)
  };
};

FieldLocationArtist.prototype._drawFlag = function(x, y) {
  var center = utils.squareCenter(x * this.size, y * this.size, this.size, this.size);
  var self = this;

  flagRects.forEach(function(r) {
    self._fillRect(
      r.color,
      center.x + r.x * (self.size/ 2),
      center.y + r.y * (self.size / 2),
      r.width * self.size,
      r.height * self.size);
  });
};

FieldLocationArtist.prototype._fillRect = function(color, x, y, w, h) {
  var ctx = this.context;
  ctx.beginPath();
  ctx.rect(x, y, w, h);
  ctx.fillStyle = color;
  ctx.fill();
};

FieldLocationArtist.prototype._drawBorder = function(x, y) {
  var ctx = this.context;
  ctx.beginPath();
  ctx.rect(x * this.size, y * this.size, this.size, this.size);
  ctx.lineWidth = 1;
  ctx.stroke();
};

module.exports = FieldLocationArtist;
<commit_msg>remove console log<commit_after>var utils = require('../utils');

var countColor = function(count) {
  return "blue";
};

var font = function(size) {
  var pxSize = size;
  return "bold " + pxSize + "px Arial";
};

var dirtOffsets = [
  { x: 0.6, y: 0.2 },
  { x: 0.3, y: 0.6 },
  { x: 0.7, y: 0.7 }];

var starOffsets = [
  { x: -0.13333333333333333, y: -0.8666666666666667 },
  { x: 0.06666666666666667,  y: -0.3333333333333333 },
  { x: 0.26666666666666666,  y: -0.6666666666666666 },
  { x: 0.26666666666666666,  y: -0.2 },
  { x: 0.6666666666666666,   y: 0.26666666666666666 },
  { x: 0.13333333333333333,  y: 0.06666666666666667 },
  { x: 0.4666666666666667,   y: 0.7333333333333333 },
  { x: -0.13333333333333333, y: 0.13333333333333333 },
  { x: -0.4666666666666667,  y: 0.6 },
  { x: -0.4,                 y: 0 },
  { x: -0.7333333333333333,  y: -0.4666666666666667 },
  { x: -0.4,                 y: -0.3333333333333333 } ];

var flagRects = [
  {
    color: 'red',
    x: -0.4666666666666667,
    y: -0.7333333333333333,
    width: 0.2,
    height: 0.2
  },
  {
    color: 'black',
    x: -0.06666666666666667,
    y: -0.7333333333333333,
    width: 0.06666666666666667,
    height: 0.6666666666666666
  },
  {
    color: 'black',
    x: -0.4,
    y: 0.6,
    width: 0.43333333333333335,
    height: 0.06666666666666667
  } ];

var FieldLocationArtist = function(options) {
  utils.requireOptions(options, 'context', 'metrics', 'field');

  this.context = options.context;
  this.field = options.field;
  this.size = options.metrics.locationSize;
};

FieldLocationArtist.prototype.draw = function(location, x, y) {
  //this._drawBorder(x, y);

  if (location.detonated) {
    this._drawStar(x, y);
    return;
  }

  if (location.flagged) {
    this._drawFlag(x, y);
    return;
  }

  if (!location.dug) {

    this._drawDirt(x, y);

  } else {

    var mineCount = this.field.countMines(x, y);
    if (mineCount > 0) {
      this._drawCount(x, y, mineCount);
    }

  }

};

FieldLocationArtist.prototype._drawDirt = function(x, y) {
  var self = this;
  dirtOffsets.forEach(function(offset) {
    self._drawDirtBox(x, y, offset);
  });
};

FieldLocationArtist.prototype._drawDirtBox = function(x, y, offset) {
  var a = x * this.size + offset.x * this.size,
      b = y * this.size + offset.y * this.size,
      dirtSize = this.size * 0.1,
      ctx = this.context;

  ctx.beginPath();
  ctx.rect(a, b, dirtSize, dirtSize);
  ctx.fillStyle = 'black';
  ctx.fill();
};

FieldLocationArtist.prototype._drawCount = function(x, y, count) {
  var center = utils.squareCenter(x * this.size, y * this.size, this.size);
  var ctx = this.context;

  ctx.fillStyle    = countColor(count);
  ctx.font         = font(this.size);
  ctx.textAlign    = 'center';
  ctx.textBaseline = 'middle';

  ctx.fillText(count, center.x, center.y);
};

FieldLocationArtist.prototype._drawStar = function(x, y) {

  var ctx = this.context;
  var center = utils.squareCenter(x * this.size, y * this.size, this.size);

  ctx.beginPath();
  var pt = this._starOffsetToPoint(center, 0);
  ctx.moveTo(pt.x, pt.y);

  for(var i = 1; i < starOffsets.length; i++) {
    pt = this._starOffsetToPoint(center, i);
    ctx.lineTo(pt.x, pt.y);
    console.log(pt);
  }

  ctx.closePath();
  ctx.fillStyle = 'yellow';
  ctx.fill();
  ctx.stroke();

  //center
  ctx.beginPath();
  var r = (1/15)*(this.size);
  ctx.arc(center.x, center.y, r, 0, 2*Math.PI, false);
  ctx.fillStyle='red';
  ctx.fill();
  ctx.stroke();
};

FieldLocationArtist.prototype._starOffsetToPoint = function(center, starPointIndex) {
  return {
    x: center.x + starOffsets[starPointIndex].x * (this.size / 2),
    y: center.y + starOffsets[starPointIndex].y * (this.size / 2)
  };
};

FieldLocationArtist.prototype._drawFlag = function(x, y) {
  var center = utils.squareCenter(x * this.size, y * this.size, this.size, this.size);
  var self = this;

  flagRects.forEach(function(r) {
    self._fillRect(
      r.color,
      center.x + r.x * (self.size/ 2),
      center.y + r.y * (self.size / 2),
      r.width * self.size,
      r.height * self.size);
  });
};

FieldLocationArtist.prototype._fillRect = function(color, x, y, w, h) {
  var ctx = this.context;
  ctx.beginPath();
  ctx.rect(x, y, w, h);
  ctx.fillStyle = color;
  ctx.fill();
};

FieldLocationArtist.prototype._drawBorder = function(x, y) {
  var ctx = this.context;
  ctx.beginPath();
  ctx.rect(x * this.size, y * this.size, this.size, this.size);
  ctx.lineWidth = 1;
  ctx.stroke();
};

module.exports = FieldLocationArtist;
<|endoftext|>


<commit_before>'use strict';

angular.module('core').controller('HeaderController', [
  '$rootScope',
  '$scope',
  '$state',
  'Authentication',
  'Menus',
  'Socket',
  'NotifsService',
  'NotifsApi',
  'Storages',
  'Constants',
  function ($rootScope, $scope, $state, Authentication, Menus, Socket, Notifs, NotifsApi, Storages, Constants) {
    console.log('HeaderController');
    // Expose view variables
    $scope.$state = $state;
    // $scope.authentication = Authentication;
    // $scope.isAdmin = _.contains($scope.authentication.user.roles, 'admin');

    // Get the topbar menu
    $scope.menu = Menus.getMenu('topbar');

    // Nghe sự kiện login thành công để load menu
    $rootScope.$on('loginSuccess', () => {
      console.log('loginSuccess');
      init();
    });
    // Nghe sự kiện update Notif để load notifs
    $rootScope.$on('changeNotif', () => {
      // if ($scope.authentication.user) {
      if ($scope.user) {
        loadNotifs(10);
        loadUncheckNotifs();
      }
    });
    // Nghe sự kiện chuyển state để đóng menu collapse
    $scope.$on('$stateChangeSuccess', function () {
      if (angular.element('body').hasClass('aside-menu-show')) {
        angular.element('body').removeClass('aside-menu-show');
      }
    });

    init();

    function init() {
      console.log($scope.user);
      if ($scope.user) {
        // $scope.authentication = Authentication;
        // $scope.isAdmin = _.contains($scope.authentication.user.roles, 'admin');
        loadNotifs(10);
        loadUncheckNotifs();
        initSocket();
      }
    }

    function initSocket() {
      if (!Socket.socket) {
        Socket.connect();
      }
      Socket.on('notifs', res => {
        loadNotifs(10);
        loadUncheckNotifs();
      });
      Socket.on('activity', res => {
        res.time = moment().format();
        let activitys = JSON.parse(Storages.get_session(Constants.storages.activitys, JSON.stringify([])));
        activitys.push(res);
        Storages.set_session(Constants.storages.activitys, JSON.stringify(activitys));
        $rootScope.$emit('activity');
      });
      $scope.$on('$destroy', function () {
        Socket.removeListener('activity');
      });
    }

    function loadNotifs(limit) {
      return new Promise((resolve, reject) => {
        NotifsApi.findNotifs(limit)
          .then(res => {
            $scope.notifs = res.data || [];
            return resolve(res.data);
          })
          .catch(err => {
            alert(err + '');
            return reject(err);
          });
      });
    }

    function loadUncheckNotifs() {
      return new Promise((resolve, reject) => {
        NotifsApi.countUnchecks()
          .then(res => {
            $scope.uncheckNotifs = res.data || 0;
            $rootScope.$emit('updateNotif', $scope.uncheckNotifs);
            return resolve(res.data);
          })
          .catch(err => {
            console.log(err + '');
            return reject();
          });
      });
    }
    $scope.mark_all_read = () => {
      NotifsApi.markAllRead()
        .then(res => {
          loadNotifs(10);
          loadUncheckNotifs();
        });
    };
    $scope.search_key = '';
    $scope.search = () => {
      if ($scope.search_key !== '') {
        $state.go('search', { key: $scope.search_key, in: 'title' });
      }
    };

  }
]);
<commit_msg>Commited from HaNhatLenh at Sat, Aug 19, 2017  9:24:23 PM<commit_after>'use strict';

angular.module('core').controller('HeaderController', [
  '$rootScope',
  '$scope',
  '$state',
  'Authentication',
  'Menus',
  'Socket',
  'NotifsService',
  'NotifsApi',
  'Storages',
  'Constants',
  function ($rootScope, $scope, $state, Authentication, Menus, Socket, Notifs, NotifsApi, Storages, Constants) {
    console.log('HeaderController');
    // Expose view variables
    $scope.$state = $state;
    // $scope.authentication = Authentication;
    // $scope.isAdmin = _.contains($scope.authentication.user.roles, 'admin');

    // Get the topbar menu
    $scope.menu = Menus.getMenu('topbar');

    // Nghe sự kiện login thành công để load menu
    $rootScope.$on('loginSuccess', () => {
      console.log('loginSuccess');
      init();
    });
    // Nghe sự kiện update Notif để load notifs
    $rootScope.$on('changeNotif', () => {
      // if ($scope.authentication.user) {
      if ($scope.user) {
        loadNotifs(10);
        loadUncheckNotifs();
      }
    });
    // Nghe sự kiện chuyển state để đóng menu collapse
    $scope.$on('$stateChangeSuccess', function () {
      if (angular.element('body').hasClass('aside-menu-show')) {
        angular.element('body').removeClass('aside-menu-show');
      }
    });

    //init();

    function init() {
      console.log($scope.user);
      if ($scope.user) {
        // $scope.authentication = Authentication;
        // $scope.isAdmin = _.contains($scope.authentication.user.roles, 'admin');
        loadNotifs(10);
        loadUncheckNotifs();
        initSocket();
      }
    }

    function initSocket() {
      if (!Socket.socket) {
        Socket.connect();
      }
      Socket.on('notifs', res => {
        loadNotifs(10);
        loadUncheckNotifs();
      });
      Socket.on('activity', res => {
        res.time = moment().format();
        let activitys = JSON.parse(Storages.get_session(Constants.storages.activitys, JSON.stringify([])));
        activitys.push(res);
        Storages.set_session(Constants.storages.activitys, JSON.stringify(activitys));
        $rootScope.$emit('activity');
      });
      $scope.$on('$destroy', function () {
        Socket.removeListener('activity');
      });
    }

    function loadNotifs(limit) {
      return new Promise((resolve, reject) => {
        NotifsApi.findNotifs(limit)
          .then(res => {
            $scope.notifs = res.data || [];
            return resolve(res.data);
          })
          .catch(err => {
            alert(err + '');
            return reject(err);
          });
      });
    }

    function loadUncheckNotifs() {
      return new Promise((resolve, reject) => {
        NotifsApi.countUnchecks()
          .then(res => {
            $scope.uncheckNotifs = res.data || 0;
            $rootScope.$emit('updateNotif', $scope.uncheckNotifs);
            return resolve(res.data);
          })
          .catch(err => {
            console.log(err + '');
            return reject();
          });
      });
    }
    $scope.mark_all_read = () => {
      NotifsApi.markAllRead()
        .then(res => {
          loadNotifs(10);
          loadUncheckNotifs();
        });
    };
    $scope.search_key = '';
    $scope.search = () => {
      if ($scope.search_key !== '') {
        $state.go('search', { key: $scope.search_key, in: 'title' });
      }
    };

  }
]);
<|endoftext|>


<commit_before>
kff.FocusBlurBinder = kff.createClass(
{
	extend: kff.EventBinder
},
/** @lends kff.FocusBlurBinder.prototype */
{
	/**
	 * Two-way data binder for focus/blur event.
	 * Sets model atrribute to true when element gets focus or to false when it looses focus.
	 * Also triggers focus/blur event on attribute change.
	 * Values are passed throught eventual parsers/formatters of course.
	 *
	 * @constructs
	 * @augments kff.EventBinder
	 * @param {Object} options Options object
	 */
	constructor: function(options)
	{
		if(options.eventNames.length === 0)	options.eventNames = ['focus blur'];
		kff.EventBinder.call(this, options);
	},

	triggerEvent: function(event)
	{
		this.updateModel(this.$element.is(':focus'));
	},

	refresh: function()
	{
		if(this.value)
		{
			if(!this.$element.is(':focus')) this.$element.trigger('focus');
		}
		else
		{
			if(this.$element.is(':focus')) this.$element.trigger('blur');
		}
	}
});

kff.BindingView.registerBinder('focusblur', kff.FocusBlurBinder);<commit_msg>refactor(kff.FocusBlurBinder): use direct DOM access to focused element instead of jQuery method<commit_after>
kff.FocusBlurBinder = kff.createClass(
{
	extend: kff.EventBinder
},
/** @lends kff.FocusBlurBinder.prototype */
{
	/**
	 * Two-way data binder for focus/blur event.
	 * Sets model atrribute to true when element gets focus or to false when it looses focus.
	 * Also triggers focus/blur event on attribute change.
	 * Values are passed throught eventual parsers/formatters of course.
	 *
	 * @constructs
	 * @augments kff.EventBinder
	 * @param {Object} options Options object
	 */
	constructor: function(options)
	{
		if(options.eventNames.length === 0)	options.eventNames = ['focus blur'];
		kff.EventBinder.call(this, options);
	},

	triggerEvent: function(event)
	{
		this.updateModel(document.activeElement === this.$element[0]);
	},

	refresh: function()
	{
		if(this.value)
		{
			if(document.activeElement !== this.$element[0]) this.$element[0].focus();
		}
		else
		{
			if(document.activeElement === this.$element[0]) this.$element[0].blur();
		}
	}
});

kff.BindingView.registerBinder('focusblur', kff.FocusBlurBinder);<|endoftext|>


<commit_before>define(function(require) {

    var SeriesModel = require('../../model/Series');
    var Tree = require('../../data/Tree');
    var zrUtil = require('zrender/core/util');
    var Model = require('../../model/Model');
    var formatUtil = require('../../util/format');
    var helper = require('./helper');
    var encodeHTML = formatUtil.encodeHTML;
    var addCommas = formatUtil.addCommas;

    return SeriesModel.extend({

        type: 'series.treemap',

        layoutMode: 'box',

        dependencies: ['grid', 'polar'],

        /**
         * @type {module:echarts/data/Tree~Node}
         */
        _viewRoot: null,

        defaultOption: {
            // Disable progressive rendering
            progressive: 0,
            hoverLayerThreshold: Infinity,
            // center: ['50%', '50%'],          // not supported in ec3.
            // size: ['80%', '80%'],            // deprecated, compatible with ec2.
            left: 'center',
            top: 'middle',
            right: null,
            bottom: null,
            width: '80%',
            height: '80%',
            sort: true,                         // Can be null or false or true
                                                // (order by desc default, asc not supported yet (strange effect))
            clipWindow: 'origin',               // Size of clipped window when zooming. 'origin' or 'fullscreen'
            squareRatio: 0.5 * (1 + Math.sqrt(5)), // golden ratio
            leafDepth: null,                    // Nodes on depth from root are regarded as leaves.
                                                // Count from zero (zero represents only view root).
            drillDownIcon: '▶',                 // Use html character temporarily because it is complicated
                                                // to align specialized icon. ▷▶❒❐▼✚

            zoomToNodeRatio: 0.32 * 0.32,       // Be effective when using zoomToNode. Specify the proportion of the
                                                // target node area in the view area.
            roam: true,                         // true, false, 'scale' or 'zoom', 'move'.
            nodeClick: 'zoomToNode',            // Leaf node click behaviour: 'zoomToNode', 'link', false.
                                                // If leafDepth is set and clicking a node which has children but
                                                // be on left depth, the behaviour would be changing root. Otherwise
                                                // use behavious defined above.
            animation: true,
            animationDurationUpdate: 900,
            animationEasing: 'quinticInOut',
            breadcrumb: {
                show: true,
                height: 22,
                left: 'center',
                top: 'bottom',
                // right
                // bottom
                emptyItemWidth: 25,             // Width of empty node.
                itemStyle: {
                    normal: {
                        color: 'rgba(0,0,0,0.7)', //'#5793f3',
                        borderColor: 'rgba(255,255,255,0.7)',
                        borderWidth: 1,
                        shadowColor: 'rgba(150,150,150,1)',
                        shadowBlur: 3,
                        shadowOffsetX: 0,
                        shadowOffsetY: 0,
                        textStyle: {
                            color: '#fff'
                        }
                    },
                    emphasis: {
                        textStyle: {}
                    }
                }
            },
            label: {
                normal: {
                    show: true,
                    position: 'inside', // Can be [5, '5%'] or position stirng like 'insideTopLeft', ...
                    textStyle: {
                        color: '#fff',
                        ellipsis: true
                    }
                }
            },
            itemStyle: {
                normal: {
                    color: null,            // Can be 'none' if not necessary.
                    colorAlpha: null,       // Can be 'none' if not necessary.
                    colorSaturation: null,  // Can be 'none' if not necessary.
                    borderWidth: 0,
                    gapWidth: 0,
                    borderColor: '#fff',
                    borderColorSaturation: null // If specified, borderColor will be ineffective, and the
                                                // border color is evaluated by color of current node and
                                                // borderColorSaturation.
                },
                emphasis: {

                }
            },

            visualDimension: 0,                 // Can be 0, 1, 2, 3.
            visualMin: null,
            visualMax: null,

            color: [],                  // + treemapSeries.color should not be modified. Please only modified
                                        // level[n].color (if necessary).
                                        // + Specify color list of each level. level[0].color would be global
                                        // color list if not specified. (see method `setDefault`).
                                        // + But set as a empty array to forbid fetch color from global palette
                                        // when using nodeModel.get('color'), otherwise nodes on deep level
                                        // will always has color palette set and are not able to inherit color
                                        // from parent node.
                                        // + TreemapSeries.color can not be set as 'none', otherwise effect
                                        // legend color fetching (see seriesColor.js).
            colorAlpha: null,           // Array. Specify color alpha range of each level, like [0.2, 0.8]
            colorSaturation: null,      // Array. Specify color saturation of each level, like [0.2, 0.5]
            colorMappingBy: 'index',    // 'value' or 'index' or 'id'.
            visibleMin: 10,             // If area less than this threshold (unit: pixel^2), node will not
                                        // be rendered. Only works when sort is 'asc' or 'desc'.
            childrenVisibleMin: null,   // If area of a node less than this threshold (unit: pixel^2),
                                        // grandchildren will not show.
                                        // Why grandchildren? If not grandchildren but children,
                                        // some siblings show children and some not,
                                        // the appearance may be mess and not consistent,
            levels: []                  // Each item: {
                                        //     visibleMin, itemStyle, visualDimension, label
                                        // }
            // data: {
            //      value: [],
            //      children: [],
            //      link: 'http://xxx.xxx.xxx',
            //      target: 'blank' or 'self'
            // }
        },

        /**
         * @override
         */
        getInitialData: function (option, ecModel) {
            var rootName = option.name;
            rootName == null && (rootName = option.name);

            // Create a virtual root.
            var root = {name: rootName, children: option.data};

            completeTreeValue(root);

            var levels = option.levels || [];

            levels = option.levels = setDefault(levels, ecModel);

            // Make sure always a new tree is created when setOption,
            // in TreemapView, we check whether oldTree === newTree
            // to choose mappings approach among old shapes and new shapes.
            return Tree.createTree(root, this, levels).data;
        },

        optionUpdated: function () {
            this.resetViewRoot();
        },

        /**
         * @override
         * @param {number} dataIndex
         * @param {boolean} [mutipleSeries=false]
         */
        formatTooltip: function (dataIndex) {
            var data = this.getData();
            var value = this.getRawValue(dataIndex);
            var formattedValue = zrUtil.isArray(value)
                ? addCommas(value[0]) : addCommas(value);
            var name = data.getName(dataIndex);

            return encodeHTML(name + ': ' + formattedValue);
        },

        /**
         * Add tree path to tooltip param
         *
         * @override
         * @param {number} dataIndex
         * @return {Object}
         */
        getDataParams: function (dataIndex) {
            var params = SeriesModel.prototype.getDataParams.apply(this, arguments);

            var node = this.getData().tree.getNodeByDataIndex(dataIndex);
            params.treePathInfo = helper.wrapTreePathInfo(node, this);

            return params;
        },

        /**
         * @public
         * @param {Object} layoutInfo {
         *                                x: containerGroup x
         *                                y: containerGroup y
         *                                width: containerGroup width
         *                                height: containerGroup height
         *                            }
         */
        setLayoutInfo: function (layoutInfo) {
            /**
             * @readOnly
             * @type {Object}
             */
            this.layoutInfo = this.layoutInfo || {};
            zrUtil.extend(this.layoutInfo, layoutInfo);
        },

        /**
         * @param  {string} id
         * @return {number} index
         */
        mapIdToIndex: function (id) {
            // A feature is implemented:
            // index is monotone increasing with the sequence of
            // input id at the first time.
            // This feature can make sure that each data item and its
            // mapped color have the same index between data list and
            // color list at the beginning, which is useful for user
            // to adjust data-color mapping.

            /**
             * @private
             * @type {Object}
             */
            var idIndexMap = this._idIndexMap;

            if (!idIndexMap) {
                idIndexMap = this._idIndexMap = zrUtil.createHashMap();
                /**
                 * @private
                 * @type {number}
                 */
                this._idIndexMapCount = 0;
            }

            var index = idIndexMap.get(id);
            if (index == null) {
                idIndexMap.set(id, index = this._idIndexMapCount++);
            }

            return index;
        },

        getViewRoot: function () {
            return this._viewRoot;
        },

        /**
         * @param {module:echarts/data/Tree~Node} [viewRoot]
         */
        resetViewRoot: function (viewRoot) {
            viewRoot
                ? (this._viewRoot = viewRoot)
                : (viewRoot = this._viewRoot);

            var root = this.getData().tree.root;

            if (!viewRoot
                || (viewRoot !== root && !root.contains(viewRoot))
            ) {
                this._viewRoot = root;
            }
        }
    });

    /**
     * @param {Object} dataNode
     */
    function completeTreeValue(dataNode) {
        // Postorder travel tree.
        // If value of none-leaf node is not set,
        // calculate it by suming up the value of all children.
        var sum = 0;

        zrUtil.each(dataNode.children, function (child) {

            completeTreeValue(child);

            var childValue = child.value;
            zrUtil.isArray(childValue) && (childValue = childValue[0]);

            sum += childValue;
        });

        var thisValue = dataNode.value;
        if (zrUtil.isArray(thisValue)) {
            thisValue = thisValue[0];
        }

        if (thisValue == null || isNaN(thisValue)) {
            thisValue = sum;
        }
        // Value should not less than 0.
        if (thisValue < 0) {
            thisValue = 0;
        }

        zrUtil.isArray(dataNode.value)
            ? (dataNode.value[0] = thisValue)
            : (dataNode.value = thisValue);
    }

    /**
     * set default to level configuration
     */
    function setDefault(levels, ecModel) {
        var globalColorList = ecModel.get('color');

        if (!globalColorList) {
            return;
        }

        levels = levels || [];
        var hasColorDefine;
        zrUtil.each(levels, function (levelDefine) {
            var model = new Model(levelDefine);
            var modelColor = model.get('color');

            if (model.get('itemStyle.normal.color')
                || (modelColor && modelColor !== 'none')
            ) {
                hasColorDefine = true;
            }
        });

        if (!hasColorDefine) {
            var level0 = levels[0] || (levels[0] = {});
            level0.color = globalColorList.slice();
        }

        return levels;
    }

});<commit_msg>tweak.<commit_after>define(function(require) {

    var SeriesModel = require('../../model/Series');
    var Tree = require('../../data/Tree');
    var zrUtil = require('zrender/core/util');
    var Model = require('../../model/Model');
    var formatUtil = require('../../util/format');
    var helper = require('./helper');
    var encodeHTML = formatUtil.encodeHTML;
    var addCommas = formatUtil.addCommas;

    return SeriesModel.extend({

        type: 'series.treemap',

        layoutMode: 'box',

        dependencies: ['grid', 'polar'],

        /**
         * @type {module:echarts/data/Tree~Node}
         */
        _viewRoot: null,

        defaultOption: {
            // Disable progressive rendering
            progressive: 0,
            hoverLayerThreshold: Infinity,
            // center: ['50%', '50%'],          // not supported in ec3.
            // size: ['80%', '80%'],            // deprecated, compatible with ec2.
            left: 'center',
            top: 'middle',
            right: null,
            bottom: null,
            width: '80%',
            height: '80%',
            sort: true,                         // Can be null or false or true
                                                // (order by desc default, asc not supported yet (strange effect))
            clipWindow: 'origin',               // Size of clipped window when zooming. 'origin' or 'fullscreen'
            squareRatio: 0.5 * (1 + Math.sqrt(5)), // golden ratio
            leafDepth: null,                    // Nodes on depth from root are regarded as leaves.
                                                // Count from zero (zero represents only view root).
            drillDownIcon: '▶',                 // Use html character temporarily because it is complicated
                                                // to align specialized icon. ▷▶❒❐▼✚

            zoomToNodeRatio: 0.32 * 0.32,       // Be effective when using zoomToNode. Specify the proportion of the
                                                // target node area in the view area.
            roam: true,                         // true, false, 'scale' or 'zoom', 'move'.
            nodeClick: 'zoomToNode',            // Leaf node click behaviour: 'zoomToNode', 'link', false.
                                                // If leafDepth is set and clicking a node which has children but
                                                // be on left depth, the behaviour would be changing root. Otherwise
                                                // use behavious defined above.
            animation: true,
            animationDurationUpdate: 900,
            animationEasing: 'quinticInOut',
            breadcrumb: {
                show: true,
                height: 22,
                left: 'center',
                top: 'bottom',
                // right
                // bottom
                emptyItemWidth: 25,             // Width of empty node.
                itemStyle: {
                    normal: {
                        color: 'rgba(0,0,0,0.7)', //'#5793f3',
                        borderColor: 'rgba(255,255,255,0.7)',
                        borderWidth: 1,
                        shadowColor: 'rgba(150,150,150,1)',
                        shadowBlur: 3,
                        shadowOffsetX: 0,
                        shadowOffsetY: 0,
                        textStyle: {
                            color: '#fff'
                        }
                    },
                    emphasis: {
                        textStyle: {}
                    }
                }
            },
            label: {
                normal: {
                    show: true,
                    position: 'inside', // Can be [5, '5%'] or position stirng like 'insideTopLeft', ...
                    textStyle: {
                        color: '#fff',
                        ellipsis: true
                    }
                }
            },
            itemStyle: {
                normal: {
                    color: null,            // Can be 'none' if not necessary.
                    colorAlpha: null,       // Can be 'none' if not necessary.
                    colorSaturation: null,  // Can be 'none' if not necessary.
                    borderWidth: 0,
                    gapWidth: 0,
                    borderColor: '#fff',
                    borderColorSaturation: null // If specified, borderColor will be ineffective, and the
                                                // border color is evaluated by color of current node and
                                                // borderColorSaturation.
                },
                emphasis: {

                }
            },

            visualDimension: 0,                 // Can be 0, 1, 2, 3.
            visualMin: null,
            visualMax: null,

            color: [],                  // + treemapSeries.color should not be modified. Please only modified
                                        // level[n].color (if necessary).
                                        // + Specify color list of each level. level[0].color would be global
                                        // color list if not specified. (see method `setDefault`).
                                        // + But set as a empty array to forbid fetch color from global palette
                                        // when using nodeModel.get('color'), otherwise nodes on deep level
                                        // will always has color palette set and are not able to inherit color
                                        // from parent node.
                                        // + TreemapSeries.color can not be set as 'none', otherwise effect
                                        // legend color fetching (see seriesColor.js).
            colorAlpha: null,           // Array. Specify color alpha range of each level, like [0.2, 0.8]
            colorSaturation: null,      // Array. Specify color saturation of each level, like [0.2, 0.5]
            colorMappingBy: 'index',    // 'value' or 'index' or 'id'.
            visibleMin: 10,             // If area less than this threshold (unit: pixel^2), node will not
                                        // be rendered. Only works when sort is 'asc' or 'desc'.
            childrenVisibleMin: null,   // If area of a node less than this threshold (unit: pixel^2),
                                        // grandchildren will not show.
                                        // Why grandchildren? If not grandchildren but children,
                                        // some siblings show children and some not,
                                        // the appearance may be mess and not consistent,
            levels: []                  // Each item: {
                                        //     visibleMin, itemStyle, visualDimension, label
                                        // }
            // data: {
            //      value: [],
            //      children: [],
            //      link: 'http://xxx.xxx.xxx',
            //      target: 'blank' or 'self'
            // }
        },

        /**
         * @override
         */
        getInitialData: function (option, ecModel) {
            // Create a virtual root.
            var root = {name: option.name, children: option.data};

            completeTreeValue(root);

            var levels = option.levels || [];

            levels = option.levels = setDefault(levels, ecModel);

            // Make sure always a new tree is created when setOption,
            // in TreemapView, we check whether oldTree === newTree
            // to choose mappings approach among old shapes and new shapes.
            return Tree.createTree(root, this, levels).data;
        },

        optionUpdated: function () {
            this.resetViewRoot();
        },

        /**
         * @override
         * @param {number} dataIndex
         * @param {boolean} [mutipleSeries=false]
         */
        formatTooltip: function (dataIndex) {
            var data = this.getData();
            var value = this.getRawValue(dataIndex);
            var formattedValue = zrUtil.isArray(value)
                ? addCommas(value[0]) : addCommas(value);
            var name = data.getName(dataIndex);

            return encodeHTML(name + ': ' + formattedValue);
        },

        /**
         * Add tree path to tooltip param
         *
         * @override
         * @param {number} dataIndex
         * @return {Object}
         */
        getDataParams: function (dataIndex) {
            var params = SeriesModel.prototype.getDataParams.apply(this, arguments);

            var node = this.getData().tree.getNodeByDataIndex(dataIndex);
            params.treePathInfo = helper.wrapTreePathInfo(node, this);

            return params;
        },

        /**
         * @public
         * @param {Object} layoutInfo {
         *                                x: containerGroup x
         *                                y: containerGroup y
         *                                width: containerGroup width
         *                                height: containerGroup height
         *                            }
         */
        setLayoutInfo: function (layoutInfo) {
            /**
             * @readOnly
             * @type {Object}
             */
            this.layoutInfo = this.layoutInfo || {};
            zrUtil.extend(this.layoutInfo, layoutInfo);
        },

        /**
         * @param  {string} id
         * @return {number} index
         */
        mapIdToIndex: function (id) {
            // A feature is implemented:
            // index is monotone increasing with the sequence of
            // input id at the first time.
            // This feature can make sure that each data item and its
            // mapped color have the same index between data list and
            // color list at the beginning, which is useful for user
            // to adjust data-color mapping.

            /**
             * @private
             * @type {Object}
             */
            var idIndexMap = this._idIndexMap;

            if (!idIndexMap) {
                idIndexMap = this._idIndexMap = zrUtil.createHashMap();
                /**
                 * @private
                 * @type {number}
                 */
                this._idIndexMapCount = 0;
            }

            var index = idIndexMap.get(id);
            if (index == null) {
                idIndexMap.set(id, index = this._idIndexMapCount++);
            }

            return index;
        },

        getViewRoot: function () {
            return this._viewRoot;
        },

        /**
         * @param {module:echarts/data/Tree~Node} [viewRoot]
         */
        resetViewRoot: function (viewRoot) {
            viewRoot
                ? (this._viewRoot = viewRoot)
                : (viewRoot = this._viewRoot);

            var root = this.getData().tree.root;

            if (!viewRoot
                || (viewRoot !== root && !root.contains(viewRoot))
            ) {
                this._viewRoot = root;
            }
        }
    });

    /**
     * @param {Object} dataNode
     */
    function completeTreeValue(dataNode) {
        // Postorder travel tree.
        // If value of none-leaf node is not set,
        // calculate it by suming up the value of all children.
        var sum = 0;

        zrUtil.each(dataNode.children, function (child) {

            completeTreeValue(child);

            var childValue = child.value;
            zrUtil.isArray(childValue) && (childValue = childValue[0]);

            sum += childValue;
        });

        var thisValue = dataNode.value;
        if (zrUtil.isArray(thisValue)) {
            thisValue = thisValue[0];
        }

        if (thisValue == null || isNaN(thisValue)) {
            thisValue = sum;
        }
        // Value should not less than 0.
        if (thisValue < 0) {
            thisValue = 0;
        }

        zrUtil.isArray(dataNode.value)
            ? (dataNode.value[0] = thisValue)
            : (dataNode.value = thisValue);
    }

    /**
     * set default to level configuration
     */
    function setDefault(levels, ecModel) {
        var globalColorList = ecModel.get('color');

        if (!globalColorList) {
            return;
        }

        levels = levels || [];
        var hasColorDefine;
        zrUtil.each(levels, function (levelDefine) {
            var model = new Model(levelDefine);
            var modelColor = model.get('color');

            if (model.get('itemStyle.normal.color')
                || (modelColor && modelColor !== 'none')
            ) {
                hasColorDefine = true;
            }
        });

        if (!hasColorDefine) {
            var level0 = levels[0] || (levels[0] = {});
            level0.color = globalColorList.slice();
        }

        return levels;
    }

});<|endoftext|>


<commit_before>'use strict'

var Header = require('./Header')
var Footer = require('./Footer')
var Body = require('./Body')

var units = require('../utils/units')

var $ = window.jQuery
var getComputedStyle = window.getComputedStyle

module.exports = HeaderFooterFactory

HeaderFooterFactory.prototype = {
  resetLastActiveSection: resetLastActiveSection,
  updateLastActiveSection: updateLastActiveSection,
  loadElement: loadElement,
  insertHeader: insertHeader,
  insertBody: insertBody,
  insertFooter: insertFooter,
  removeHeader: removeHeader,
  removeFooter: removeFooter,
  hasHeader: hasHeader,
  hasBody: hasBody,
  hasFooter: hasFooter,
  focusToEndOfBody: focusToEndOfBody,
  forceCursorToAllowedLocation: forceCursorToAllowedLocation,
  getActiveSection: getActiveSection,
  reload: reload,
  forceBodyMinHeigh: forceBodyMinHeigh,
  removeAnyOuterElement: removeAnyOuterElement
}

/**
 * HeaderFactory class. The aim of this class is to manage the document header and footer.
 * @constructor
 * @param {Editor} editor The current editor
 * @property {Editor} _editor The current editor
 * @property {Boolean} _hasHeader Tell if the document has a header or not
 * @property {Boolean} _hasBody Tell if the document has a body or not
 * @property {Boolean} _hasFooter Tell if the document has a fooer or not
 * @property {Header} header The current header if exists
 * @property {Footer} footer The current footer if exists
 */
function HeaderFooterFactory (editor, menuItemsList) {
  this._editor = editor
  this._hasHeader = false
  this._hasBody = false
  this._hasFooter = false
  this._menuItemsList = menuItemsList

  this.resetLastActiveSection()
}

/**
 * Reset the last active section
 * @method
 * @returns {undefined}
 */
function resetLastActiveSection () {
  this.lastActiveSection = null
}

/**
 * Update HeaderFooterFactory#lastActiveSection getting the HeaderFooterFactory#getActiveSection() returned value
 * @method
 * @returns {HeadFoot} the updated last active section
 */
function updateLastActiveSection () {
  this.lastActiveSection = this.getActiveSection()
  return this.lastActiveSection
}

/**
 * Load an existing header or footer depending of its nature, from its DOM element.
 * @method
 * @param {DOMElement} element
 * @throws {Error} when the given element is not a header, footer neither a body element.
 * @returns void
 */
function loadElement (element) {
  var $el = $(element)
  if ($el.attr('data-headfoot-header')) {
    this._hasHeader = true
    this.header = new Header(this._editor, this._editor.getBody(), element)
  } else if ($el.attr('data-headfoot-footer')) {
    this._hasFooter = true
    this.footer = new Footer(this._editor, this._editor.getBody(), element)
  } else if ($el.attr('data-headfoot-body')) {
    this._hasBody = true
    this.body = new Body(this._editor, this._editor.getBody(), element, this.hasHeader(), this.hasFooter(), this.header)
  } else throw new Error('This element is not a header, footer neither a body element.')
}

/**
 * Insert a new header
 * @method
 * @returns void
 */
function insertHeader () {
  this.header = new Header(this._editor, this._editor.getBody())
  this._hasHeader = true
  this.header.enterNode()
}

/**
 * Insert a new body
 * @method
 * @returns void
 */
function insertBody () {
  this.body = new Body(this._editor, this._editor.getBody(), this._hasHeader, this._hasFooter, this.header)
  this._hasBody = true
  this.body.enterNode()
}

/**
 * Insert a new footer
 * @method
 * @returns void
 */
function insertFooter () {
  this.footer = new Footer(this._editor, this._editor.getBody())
  this._hasFooter = true
  this.footer.enterNode()
}

/**
 * Remove the current header
 * @method
 * @returns void
 */
function removeHeader () {
  // the header can be removed only if it exists
  if (!this.hasHeader()) throw new Error('No header available to remove')

  $(this.header.node).remove()
  this.header = null
  this._hasHeader = false
}

/**
 * Insert a new footer
 * @method
 * @returns void
 */
function removeFooter () {
  // the footer can be removed only if it exists
  if (!this.hasFooter()) throw new Error('No footer available to remove')

  $(this.footer.node).remove()
  this.footer = null
  this._hasFooter = false
}

/**
 * Check if the document has a header or not
 * @method
 * @returns {Boolean} true if the document has a header, false if not
 */
function hasHeader () {
  return this._hasHeader
}

/**
 * Check if the document has a body or not
 * @method
 * @returns {Boolean} true if the document has a body, false if not
 */
function hasBody () {
  return this._hasBody
}

/**
 * Check if the document has a footer or not
 * @method
 * @returns {Boolean} true if the document has a footer, false if not
 */
function hasFooter () {
  return this._hasFooter
}

function focusToEndOfBody () {
  var $body = $(this.body.node)
  var lastBodyChild = $body.children().last()[0]
  this.body.enterNode()
  this._editor.selection.setCursorLocation(lastBodyChild, lastBodyChild.childNodes.length)
}

function forceCursorToAllowedLocation (node, parents) {
  if (this.hasBody()) {
    if (!parents) {
      var $node = $(node)
      var allparents = $node.parents()
      parents = allparents.slice(0, -2)
    }

    var lastParent = parents[parents.length - 1]
    var allowedLocations = [this.body.node]

    if (this.hasHeader()) {
      allowedLocations.push(this.header.node)
    }
    if (this.hasFooter()) {
      allowedLocations.push(this.footer.node)
    }

    if (!~allowedLocations.indexOf(lastParent) && !~allowedLocations.indexOf(node)) {
      this.focusToEndOfBody()
    }
  }
}

function getActiveSection () {
  return [this.header, this.body, this.footer]
  .reduce(function (prev, section) {
    if (prev) {
      return prev
    } else {
      if (section && section.isActive) {
        return section
      }
    }
  }, null)
}

/**
 * Helper function. Do the reload of headers and footers
 * @method
 * @returns {undefined}
 */
function reload () {
  var that = this
  var editor = this._editor
  var $headFootElmts = $('*[data-headfoot]', editor.getDoc())
  var $bodyElmt = $('*[data-headfoot-body]', editor.getDoc())
  var hasBody = !!$bodyElmt.length
  var $allElmts = null

  // init starting states
  this._menuItemsList.insertHeader.show()
  this._menuItemsList.insertFooter.show()
  this._menuItemsList.removeHeader.hide()
  this._menuItemsList.removeFooter.hide()

  // set another state and load elements if a header or a footer exists
  $headFootElmts.each(function (i, el) {
    var $el = $(el)

    // Configure menu items
    if ($el.attr('data-headfoot-header')) {
      that._menuItemsList.insertHeader.hide()
      that._menuItemsList.removeHeader.show()
    } else if ($el.attr('data-headfoot-body')) {
      // @TODO something ?
    } else if ($el.attr('data-headfoot-footer')) {
      that._menuItemsList.insertFooter.hide()
      that._menuItemsList.removeFooter.show()
    }

    // Configure the headfoot DOM section
    that.loadElement(el)
  })

  // Create the body section if it doesn't exist yet
  // and populate it with the previous body tag children elements
  if (!hasBody) {
    $allElmts = $(editor.getBody()).children()
    that.insertBody()
    var $body = $(that.body.node)
    $body.empty()
    $allElmts.each(function (i, el) {
      var $el = $(el)
      if (!$el.attr('data-headfoot')) {
        $body.append($el)
      }
    })
  }
  // editor.fire('SetContent', {set: true})
}

/**
 * Make sure the body minimum height is correct, depending the margins, header and footer height.
 * NodeChange event handler.
 * @function
 * @inner
 * @returns void
 */
function forceBodyMinHeigh () {
  var bodyTag = {}
  var bodySection = {}
  var headerSection = {}
  var footerSection = {}
  var pageHeight

  bodySection.node = this.body.node
  bodySection.height = this.body.node.offsetHeight
  bodySection.style = window.getComputedStyle(bodySection.node)

  if (this.hasHeader()) {
    headerSection.node = this.header.node
    headerSection.height = this.header.node.offsetHeight
    headerSection.style = window.getComputedStyle(headerSection.node)
  } else {
    headerSection.node = null
    headerSection.height = 0
    headerSection.style = window.getComputedStyle(document.createElement('bogusElement'))
  }

  if (this.hasFooter()) {
    footerSection.node = this.footer.node
    footerSection.height = this.footer.node.offsetHeight
    footerSection.style = window.getComputedStyle(footerSection.node)
  } else {
    footerSection.node = null
    footerSection.height = 0
    footerSection.style = window.getComputedStyle(document.createElement('bogusElement'))
  }

  bodyTag.node = this._editor.getBody()
  bodyTag.height = units.getValueFromStyle(getComputedStyle(this._editor.getBody()).minHeight)
  bodyTag.style = getComputedStyle(bodyTag.node)
  bodyTag.paddingTop = units.getValueFromStyle(bodyTag.style.paddingTop)
  bodyTag.paddingBottom = units.getValueFromStyle(bodyTag.style.paddingBottom)

  pageHeight = bodyTag.height - bodyTag.paddingTop - bodyTag.paddingBottom - headerSection.height - footerSection.height
  $(bodySection.node).css({ minHeight: pageHeight })
}

/**
 * Remove any element located out of the allowed sections.
 * @method
 * @returns void
 */
function removeAnyOuterElement () {
  var that = this
  var $body = $(this._editor.getBody())
  $body.children().each(function (i) {
    var allowedRootNodes = [that.body.node]
    if (that.hasHeader()) {
      allowedRootNodes.push(that.header.node)
    }
    if (that.hasFooter()) {
      allowedRootNodes.push(that.footer.node)
    }
    if (!~allowedRootNodes.indexOf(this) && !this.getAttribute('data-mce-bogus')) {
      console.error('Removing the following element because it is out of the allowed sections')
      console.log('Removed element:', this)
      $(this).remove()
    }
  })
}
<commit_msg>wrapping headerFooterFactory#loadElement() in a set of try/catch blocks<commit_after>'use strict'

var Header = require('./Header')
var Footer = require('./Footer')
var Body = require('./Body')

var units = require('../utils/units')

var $ = window.jQuery
var getComputedStyle = window.getComputedStyle

module.exports = HeaderFooterFactory

HeaderFooterFactory.prototype = {
  resetLastActiveSection: resetLastActiveSection,
  updateLastActiveSection: updateLastActiveSection,
  loadElement: loadElement,
  insertHeader: insertHeader,
  insertBody: insertBody,
  insertFooter: insertFooter,
  removeHeader: removeHeader,
  removeFooter: removeFooter,
  hasHeader: hasHeader,
  hasBody: hasBody,
  hasFooter: hasFooter,
  focusToEndOfBody: focusToEndOfBody,
  forceCursorToAllowedLocation: forceCursorToAllowedLocation,
  getActiveSection: getActiveSection,
  reload: reload,
  forceBodyMinHeigh: forceBodyMinHeigh,
  removeAnyOuterElement: removeAnyOuterElement
}

/**
 * HeaderFactory class. The aim of this class is to manage the document header and footer.
 * @constructor
 * @param {Editor} editor The current editor
 * @property {Editor} _editor The current editor
 * @property {Boolean} _hasHeader Tell if the document has a header or not
 * @property {Boolean} _hasBody Tell if the document has a body or not
 * @property {Boolean} _hasFooter Tell if the document has a fooer or not
 * @property {Header} header The current header if exists
 * @property {Footer} footer The current footer if exists
 */
function HeaderFooterFactory (editor, menuItemsList) {
  this._editor = editor
  this._hasHeader = false
  this._hasBody = false
  this._hasFooter = false
  this._menuItemsList = menuItemsList

  this.resetLastActiveSection()
}

/**
 * Reset the last active section
 * @method
 * @returns {undefined}
 */
function resetLastActiveSection () {
  this.lastActiveSection = null
}

/**
 * Update HeaderFooterFactory#lastActiveSection getting the HeaderFooterFactory#getActiveSection() returned value
 * @method
 * @returns {HeadFoot} the updated last active section
 */
function updateLastActiveSection () {
  this.lastActiveSection = this.getActiveSection()
  return this.lastActiveSection
}

/**
 * Load an existing header or footer depending of its nature, from its DOM element.
 * @method
 * @param {DOMElement} element
 * @throws {Error} when the given element is not a header, footer neither a body element.
 * @returns void
 */
function loadElement (element) {
  var $el = $(element)
  if ($el.attr('data-headfoot-header')) {
    this._hasHeader = true
    this.header = new Header(this._editor, this._editor.getBody(), element)
  } else if ($el.attr('data-headfoot-footer')) {
    this._hasFooter = true
    this.footer = new Footer(this._editor, this._editor.getBody(), element)
  } else if ($el.attr('data-headfoot-body')) {
    this._hasBody = true
    this.body = new Body(this._editor, this._editor.getBody(), element, this.hasHeader(), this.hasFooter(), this.header)
  } else throw new Error('This element is not a header, footer neither a body element.')
}

/**
 * Insert a new header
 * @method
 * @returns void
 */
function insertHeader () {
  this.header = new Header(this._editor, this._editor.getBody())
  this._hasHeader = true
  this.header.enterNode()
}

/**
 * Insert a new body
 * @method
 * @returns void
 */
function insertBody () {
  this.body = new Body(this._editor, this._editor.getBody(), this._hasHeader, this._hasFooter, this.header)
  this._hasBody = true
  this.body.enterNode()
}

/**
 * Insert a new footer
 * @method
 * @returns void
 */
function insertFooter () {
  this.footer = new Footer(this._editor, this._editor.getBody())
  this._hasFooter = true
  this.footer.enterNode()
}

/**
 * Remove the current header
 * @method
 * @returns void
 */
function removeHeader () {
  // the header can be removed only if it exists
  if (!this.hasHeader()) throw new Error('No header available to remove')

  $(this.header.node).remove()
  this.header = null
  this._hasHeader = false
}

/**
 * Insert a new footer
 * @method
 * @returns void
 */
function removeFooter () {
  // the footer can be removed only if it exists
  if (!this.hasFooter()) throw new Error('No footer available to remove')

  $(this.footer.node).remove()
  this.footer = null
  this._hasFooter = false
}

/**
 * Check if the document has a header or not
 * @method
 * @returns {Boolean} true if the document has a header, false if not
 */
function hasHeader () {
  return this._hasHeader
}

/**
 * Check if the document has a body or not
 * @method
 * @returns {Boolean} true if the document has a body, false if not
 */
function hasBody () {
  return this._hasBody
}

/**
 * Check if the document has a footer or not
 * @method
 * @returns {Boolean} true if the document has a footer, false if not
 */
function hasFooter () {
  return this._hasFooter
}

function focusToEndOfBody () {
  var $body = $(this.body.node)
  var lastBodyChild = $body.children().last()[0]
  this.body.enterNode()
  this._editor.selection.setCursorLocation(lastBodyChild, lastBodyChild.childNodes.length)
}

function forceCursorToAllowedLocation (node, parents) {
  if (this.hasBody()) {
    if (!parents) {
      var $node = $(node)
      var allparents = $node.parents()
      parents = allparents.slice(0, -2)
    }

    var lastParent = parents[parents.length - 1]
    var allowedLocations = [this.body.node]

    if (this.hasHeader()) {
      allowedLocations.push(this.header.node)
    }
    if (this.hasFooter()) {
      allowedLocations.push(this.footer.node)
    }

    if (!~allowedLocations.indexOf(lastParent) && !~allowedLocations.indexOf(node)) {
      this.focusToEndOfBody()
    }
  }
}

function getActiveSection () {
  return [this.header, this.body, this.footer]
  .reduce(function (prev, section) {
    if (prev) {
      return prev
    } else {
      if (section && section.isActive) {
        return section
      }
    }
  }, null)
}

/**
 * Helper function. Do the reload of headers and footers
 * @method
 * @returns {undefined}
 */
function reload () {
  var that = this
  var editor = this._editor
  var $headFootElmts = $('*[data-headfoot]', editor.getDoc())
  var $bodyElmt = $('*[data-headfoot-body]', editor.getDoc())
  var hasBody = !!$bodyElmt.length
  var $allElmts = null

  // init starting states
  this._menuItemsList.insertHeader.show()
  this._menuItemsList.insertFooter.show()
  this._menuItemsList.removeHeader.hide()
  this._menuItemsList.removeFooter.hide()

  // set another state and load elements if a header or a footer exists
  $headFootElmts.each(function (i, el) {
    var $el = $(el)

    // Configure menu items
    if ($el.attr('data-headfoot-header')) {
      that._menuItemsList.insertHeader.hide()
      that._menuItemsList.removeHeader.show()
    } else if ($el.attr('data-headfoot-body')) {
      // @TODO something ?
    } else if ($el.attr('data-headfoot-footer')) {
      that._menuItemsList.insertFooter.hide()
      that._menuItemsList.removeFooter.show()
    }

    try {
      // Configure the headfoot DOM section
      that.loadElement(el)
    } catch (e) {
      console.error('headerFooterFactory reloading failed for the following element.')
      console.log('Failed element (continuing...):', el)
    }
  })

  // Create the body section if it doesn't exist yet
  // and populate it with the previous body tag children elements
  if (!hasBody) {
    $allElmts = $(editor.getBody()).children()
    that.insertBody()
    var $body = $(that.body.node)
    $body.empty()
    $allElmts.each(function (i, el) {
      var $el = $(el)
      if (!$el.attr('data-headfoot')) {
        $body.append($el)
      }
    })
  }
  // editor.fire('SetContent', {set: true})
}

/**
 * Make sure the body minimum height is correct, depending the margins, header and footer height.
 * NodeChange event handler.
 * @function
 * @inner
 * @returns void
 */
function forceBodyMinHeigh () {
  var bodyTag = {}
  var bodySection = {}
  var headerSection = {}
  var footerSection = {}
  var pageHeight

  bodySection.node = this.body.node
  bodySection.height = this.body.node.offsetHeight
  bodySection.style = window.getComputedStyle(bodySection.node)

  if (this.hasHeader()) {
    headerSection.node = this.header.node
    headerSection.height = this.header.node.offsetHeight
    headerSection.style = window.getComputedStyle(headerSection.node)
  } else {
    headerSection.node = null
    headerSection.height = 0
    headerSection.style = window.getComputedStyle(document.createElement('bogusElement'))
  }

  if (this.hasFooter()) {
    footerSection.node = this.footer.node
    footerSection.height = this.footer.node.offsetHeight
    footerSection.style = window.getComputedStyle(footerSection.node)
  } else {
    footerSection.node = null
    footerSection.height = 0
    footerSection.style = window.getComputedStyle(document.createElement('bogusElement'))
  }

  bodyTag.node = this._editor.getBody()
  bodyTag.height = units.getValueFromStyle(getComputedStyle(this._editor.getBody()).minHeight)
  bodyTag.style = getComputedStyle(bodyTag.node)
  bodyTag.paddingTop = units.getValueFromStyle(bodyTag.style.paddingTop)
  bodyTag.paddingBottom = units.getValueFromStyle(bodyTag.style.paddingBottom)

  pageHeight = bodyTag.height - bodyTag.paddingTop - bodyTag.paddingBottom - headerSection.height - footerSection.height
  $(bodySection.node).css({ minHeight: pageHeight })
}

/**
 * Remove any element located out of the allowed sections.
 * @method
 * @returns void
 */
function removeAnyOuterElement () {
  var that = this
  var $body = $(this._editor.getBody())
  $body.children().each(function (i) {
    var allowedRootNodes = [that.body.node]
    if (that.hasHeader()) {
      allowedRootNodes.push(that.header.node)
    }
    if (that.hasFooter()) {
      allowedRootNodes.push(that.footer.node)
    }
    if (!~allowedRootNodes.indexOf(this) && !this.getAttribute('data-mce-bogus')) {
      console.error('Removing the following element because it is out of the allowed sections')
      console.log('Removed element:', this)
      $(this).remove()
    }
  })
}
<|endoftext|>