<commit_before>'use strict'

const { test } = require('tap')
const memory = require('aedes-persistence')
const { setup, connect, noError } = require('./helper')
const aedes = require('../')

function willConnect (s, opts, connected) {
  opts = opts || {}
  opts.will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  return connect(s, opts, connected)
}

test('delivers a will', function (t) {
  t.plan(4)

  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(setup(),
    opts,
    function () {
      s.conn.destroy()
    }
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    cb()
  })
})

test('calling close two times should not deliver two wills', function (t) {
  t.plan(4)

  const opts = {}
  const broker = aedes()
  t.tearDown(broker.close.bind(broker))

  broker.on('client', function (client) {
    client.close()
    client.close()
  })

  broker.mq.on('mywill', onWill)

  // willConnect populates opts with a will
  willConnect(setup(broker), opts)

  function onWill (packet, cb) {
    broker.mq.removeListener('mywill', onWill)
    broker.mq.on('mywill', t.fail.bind(t))
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    cb()
  }
})

test('delivers old will in case of a crash', function (t) {
  t.plan(6)

  const persistence = memory()
  const will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  persistence.broker = {
    id: 'anotherBroker'
  }

  persistence.putWill({
    id: 'myClientId42'
  }, will, function (err) {
    t.error(err, 'no error')

    const interval = 10 // ms, so that the will check happens fast!
    const broker = aedes({
      persistence: persistence,
      heartbeatInterval: interval
    })
    t.tearDown(broker.close.bind(broker))

    const start = Date.now()

    broker.mq.on('mywill', check)

    function check (packet, cb) {
      broker.mq.removeListener('mywill', check)
      t.ok(Date.now() - start >= 3 * interval, 'the will needs to be emitted after 3 heartbeats')
      t.equal(packet.topic, will.topic, 'topic matches')
      t.deepEqual(packet.payload, will.payload, 'payload matches')
      t.equal(packet.qos, will.qos, 'qos matches')
      t.equal(packet.retain, will.retain, 'retain matches')
      broker.mq.on('mywill', function (packet) {
        t.fail('the will must be delivered only once')
      })
      cb()
    }
  })
})

test('delete old broker', function (t) {
  t.plan(1)

  var heartbeatInterval = 100
  const broker = aedes({
    heartbeatInterval: heartbeatInterval
  })
  t.tearDown(broker.close.bind(broker))

  var brokerId = 'dummyBroker'

  broker.brokers[brokerId] = Date.now() - heartbeatInterval * 3.5

  setTimeout(() => {
    t.equal(broker.brokers[brokerId], undefined, 'Broker deleted')
  }, heartbeatInterval * 4)
})

test('store the will in the persistence', function (t) {
  t.plan(5)

  const opts = {
    clientId: 'abcde'
  }

  // willConnect populates opts with a will
  const s = willConnect(setup(), opts)
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('client', function () {
    // this is connack
    s.broker.persistence.getWill({
      id: opts.clientId
    }, function (err, packet) {
      t.error(err, 'no error')
      t.deepEqual(packet.topic, opts.will.topic, 'will topic matches')
      t.deepEqual(packet.payload, opts.will.payload, 'will payload matches')
      t.deepEqual(packet.qos, opts.will.qos, 'will qos matches')
      t.deepEqual(packet.retain, opts.will.retain, 'will retain matches')
    })
  })
})

test('delete the will in the persistence after publish', function (t) {
  t.plan(2)

  const opts = {
    clientId: 'abcde'
  }

  const broker = aedes()
  t.tearDown(broker.close.bind(broker))

  broker.on('client', function (client) {
    setImmediate(function () {
      client.close()
    })
  })

  broker.mq.on('mywill', check)

  // willConnect populates opts with a will
  willConnect(setup(broker), opts)

  function check (packet, cb) {
    broker.mq.removeListener('mywill', check)
    setImmediate(function () {
      broker.persistence.getWill({
        id: opts.clientId
      }, function (err, p) {
        t.error(err, 'no error')
        t.notOk(p, 'packet is empty')
      })
    })
    cb()
  }
})

test('delivers a will with authorization', function (t) {
  t.plan(6)

  let authorized = false
  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(
    setup(aedes({
      authorizePublish: (client, packet, callback) => {
        authorized = true
        callback(null)
      }
    })),
    opts,
    function () {
      s.conn.destroy()
    }
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('clientDisconnect', function (client) {
    t.equal(client.connected, false)
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    t.equal(authorized, true, 'authorization called')
    cb()
  })
})

test('delivers a will waits for authorization', function (t) {
  t.plan(6)

  let authorized = false
  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(
    setup(aedes({
      authorizePublish: (client, packet, callback) => {
        authorized = true
        setImmediate(() => { callback(null) })
      }
    })),
    opts,
    function () {
      s.conn.destroy()
    }
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('clientDisconnect', function () {
    t.pass('client is disconnected')
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    t.equal(authorized, true, 'authorization called')
    cb()
  })
})

test('does not deliver a will without authorization', function (t) {
  t.plan(1)

  let authorized = false
  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(
    setup(aedes({
      authorizePublish: (username, packet, callback) => {
        authorized = true
        callback(new Error())
      }
    })),
    opts,
    function () {
      s.conn.destroy()
    }
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('clientDisconnect', function () {
    t.equal(authorized, true, 'authorization called')
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail('received will without authorization')
    cb()
  })
})

test('does not deliver a will without authentication', function (t) {
  t.plan(1)

  let authenticated = false
  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(
    setup(aedes({
      authenticate: (client, username, password, callback) => {
        authenticated = true
        callback(new Error(), false)
      }
    })),
    opts
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('clientError', function () {
    t.equal(authenticated, true, 'authentication called')
    t.end()
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail('received will without authentication')
    cb()
  })
})

test('does not deliver will if broker is closed during authentication', function (t) {
  t.plan(0)

  const opts = { keepalive: 1 }

  const broker = aedes({
    authenticate: function (client, username, password, callback) {
      setTimeout(function () {
        callback(null, true)
      }, 3000)
      broker.close()
    }
  })

  broker.on('keepaliveTimeout', function () {
    t.fail('keepalive timer shoud not be set')
  })

  broker.mq.on('mywill', function (packet, cb) {
    t.fail('Received will when it was not expected')
    cb()
  })

  willConnect(setup(broker), opts)
})

// [MQTT-3.14.4-3]
test('does not deliver will when client sends a DISCONNECT', function (t) {
  t.plan(0)

  const broker = aedes()
  t.tearDown(broker.close.bind(broker))

  const s = noError(willConnect(setup(broker), {}, function () {
    s.inStream.end({
      cmd: 'disconnect'
    })
  }), t)

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail(packet)
    cb()
  })
})

test('does not store multiple will with same clientid', function (t) {
  t.plan(4)

  const opts = { clientId: 'abcde' }

  const broker = aedes()

  var s = noError(willConnect(setup(broker), opts, function () {
    // gracefully close client so no will is sent
    s.inStream.end({
      cmd: 'disconnect'
    })
  }), t)

  broker.on('clientDisconnect', function (client) {
    // reconnect same client with will
    s = willConnect(setup(broker), opts, function () {
      // check that there are not 2 will messages for the same clientid
      s.broker.persistence.delWill({ id: opts.clientId }, function (err, packet) {
        t.error(err, 'no error')
        t.equal(packet.clientId, opts.clientId, 'will packet found')
        s.broker.persistence.delWill({ id: opts.clientId }, function (err, packet) {
          t.error(err, 'no error')
          t.equal(!!packet, false, 'no duplicated packets')
          broker.close()
        })
      })
    })
  })
})
<commit_msg>don't deliver will test<commit_after>'use strict'

const { test } = require('tap')
const memory = require('aedes-persistence')
const { setup, connect, noError } = require('./helper')
const aedes = require('../')

function willConnect (s, opts, connected) {
  opts = opts || {}
  opts.will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  return connect(s, opts, connected)
}

test('delivers a will', function (t) {
  t.plan(4)

  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(setup(),
    opts,
    function () {
      s.conn.destroy()
    }
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    cb()
  })
})

test('calling close two times should not deliver two wills', function (t) {
  t.plan(4)

  const opts = {}
  const broker = aedes()
  t.tearDown(broker.close.bind(broker))

  broker.on('client', function (client) {
    client.close()
    client.close()
  })

  broker.mq.on('mywill', onWill)

  // willConnect populates opts with a will
  willConnect(setup(broker), opts)

  function onWill (packet, cb) {
    broker.mq.removeListener('mywill', onWill)
    broker.mq.on('mywill', t.fail.bind(t))
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    cb()
  }
})

test('delivers old will in case of a crash', function (t) {
  t.plan(6)

  const persistence = memory()
  const will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  persistence.broker = {
    id: 'anotherBroker'
  }

  persistence.putWill({
    id: 'myClientId42'
  }, will, function (err) {
    t.error(err, 'no error')

    const interval = 10 // ms, so that the will check happens fast!
    const broker = aedes({
      persistence: persistence,
      heartbeatInterval: interval
    })
    t.tearDown(broker.close.bind(broker))

    const start = Date.now()

    broker.mq.on('mywill', check)

    function check (packet, cb) {
      broker.mq.removeListener('mywill', check)
      t.ok(Date.now() - start >= 3 * interval, 'the will needs to be emitted after 3 heartbeats')
      t.equal(packet.topic, will.topic, 'topic matches')
      t.deepEqual(packet.payload, will.payload, 'payload matches')
      t.equal(packet.qos, will.qos, 'qos matches')
      t.equal(packet.retain, will.retain, 'retain matches')
      broker.mq.on('mywill', function (packet) {
        t.fail('the will must be delivered only once')
      })
      cb()
    }
  })
})

test('delete old broker', function (t) {
  t.plan(1)

  var heartbeatInterval = 100
  const broker = aedes({
    heartbeatInterval: heartbeatInterval
  })
  t.tearDown(broker.close.bind(broker))

  var brokerId = 'dummyBroker'

  broker.brokers[brokerId] = Date.now() - heartbeatInterval * 3.5

  setTimeout(() => {
    t.equal(broker.brokers[brokerId], undefined, 'Broker deleted')
  }, heartbeatInterval * 4)
})

test('store the will in the persistence', function (t) {
  t.plan(5)

  const opts = {
    clientId: 'abcde'
  }

  // willConnect populates opts with a will
  const s = willConnect(setup(), opts)
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('client', function () {
    // this is connack
    s.broker.persistence.getWill({
      id: opts.clientId
    }, function (err, packet) {
      t.error(err, 'no error')
      t.deepEqual(packet.topic, opts.will.topic, 'will topic matches')
      t.deepEqual(packet.payload, opts.will.payload, 'will payload matches')
      t.deepEqual(packet.qos, opts.will.qos, 'will qos matches')
      t.deepEqual(packet.retain, opts.will.retain, 'will retain matches')
    })
  })
})

test('delete the will in the persistence after publish', function (t) {
  t.plan(2)

  const opts = {
    clientId: 'abcde'
  }

  const broker = aedes()
  t.tearDown(broker.close.bind(broker))

  broker.on('client', function (client) {
    setImmediate(function () {
      client.close()
    })
  })

  broker.mq.on('mywill', check)

  // willConnect populates opts with a will
  willConnect(setup(broker), opts)

  function check (packet, cb) {
    broker.mq.removeListener('mywill', check)
    setImmediate(function () {
      broker.persistence.getWill({
        id: opts.clientId
      }, function (err, p) {
        t.error(err, 'no error')
        t.notOk(p, 'packet is empty')
      })
    })
    cb()
  }
})

test('delivers a will with authorization', function (t) {
  t.plan(6)

  let authorized = false
  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(
    setup(aedes({
      authorizePublish: (client, packet, callback) => {
        authorized = true
        callback(null)
      }
    })),
    opts,
    function () {
      s.conn.destroy()
    }
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('clientDisconnect', function (client) {
    t.equal(client.connected, false)
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    t.equal(authorized, true, 'authorization called')
    cb()
  })
})

test('delivers a will waits for authorization', function (t) {
  t.plan(6)

  let authorized = false
  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(
    setup(aedes({
      authorizePublish: (client, packet, callback) => {
        authorized = true
        setImmediate(() => { callback(null) })
      }
    })),
    opts,
    function () {
      s.conn.destroy()
    }
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('clientDisconnect', function () {
    t.pass('client is disconnected')
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    t.equal(authorized, true, 'authorization called')
    cb()
  })
})

test('does not deliver a will without authorization', function (t) {
  t.plan(1)

  let authorized = false
  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(
    setup(aedes({
      authorizePublish: (username, packet, callback) => {
        authorized = true
        callback(new Error())
      }
    })),
    opts,
    function () {
      s.conn.destroy()
    }
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('clientDisconnect', function () {
    t.equal(authorized, true, 'authorization called')
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail('received will without authorization')
    cb()
  })
})

test('does not deliver a will without authentication', function (t) {
  t.plan(1)

  let authenticated = false
  const opts = {}
  // willConnect populates opts with a will
  const s = willConnect(
    setup(aedes({
      authenticate: (client, username, password, callback) => {
        authenticated = true
        callback(new Error(), false)
      }
    })),
    opts
  )
  t.tearDown(s.broker.close.bind(s.broker))

  s.broker.on('clientError', function () {
    t.equal(authenticated, true, 'authentication called')
    t.end()
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail('received will without authentication')
    cb()
  })
})

test('does not deliver will if broker is closed during authentication', function (t) {
  t.plan(0)

  const opts = { keepalive: 1 }

  const broker = aedes({
    authenticate: function (client, username, password, callback) {
      setTimeout(function () {
        callback(null, true)
      }, 3000)
      broker.close()
    }
  })

  broker.on('keepaliveTimeout', function () {
    t.fail('keepalive timer shoud not be set')
  })

  broker.mq.on('mywill', function (packet, cb) {
    t.fail('Received will when it was not expected')
    cb()
  })

  willConnect(setup(broker), opts)
})

// [MQTT-3.14.4-3]
test('does not deliver will when client sends a DISCONNECT', function (t) {
  t.plan(0)

  const broker = aedes()
  t.tearDown(broker.close.bind(broker))

  const s = noError(willConnect(setup(broker), {}, function () {
    s.inStream.end({
      cmd: 'disconnect'
    })
  }), t)

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail(packet)
    cb()
  })
})

test('does not store multiple will with same clientid', function (t) {
  t.plan(4)

  const opts = { clientId: 'abcde' }

  const broker = aedes()

  var s = noError(willConnect(setup(broker), opts, function () {
    // gracefully close client so no will is sent
    s.inStream.end({
      cmd: 'disconnect'
    })
  }), t)

  broker.on('clientDisconnect', function (client) {
    // reconnect same client with will
    s = willConnect(setup(broker), opts, function () {
      // check that there are not 2 will messages for the same clientid
      s.broker.persistence.delWill({ id: opts.clientId }, function (err, packet) {
        t.error(err, 'no error')
        t.equal(packet.clientId, opts.clientId, 'will packet found')
        s.broker.persistence.delWill({ id: opts.clientId }, function (err, packet) {
          t.error(err, 'no error')
          t.equal(!!packet, false, 'no duplicated packets')
          broker.close()
        })
      })
    })
  })
})

test('don\'t delivers a will', function (t) {
  const persistence = memory()
  const will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  var oldBroker = 'broker1'

  persistence.broker = {
    id: oldBroker
  }

  persistence.putWill({
    id: 'myClientId42'
  }, will, function (err) {
    t.error(err, 'no error')

    const opts = {
      persistence: persistence,
      heartbeatInterval: 10
    }

    var count = 0

    const broker = aedes(opts)
    t.tearDown(broker.close.bind(broker))

    broker.mq.on('mywill', function (packet, cb) {
      t.fail('Will received')
      cb()
    })

    broker.mq.on('$SYS/+/heartbeat', function () {
      // update old broker heartbeat to simulate it is alive
      broker.brokers[oldBroker] = Date.now()
      t.pass('Heartbeat received')

      if (++count === 5) t.end()
    })
  })
})
<|endoftext|>


<commit_before>define([
  'd3',
  'base/object',
  'base/class',
], function(d3, object, Class) {

  var Tool = Class.extend({

    init: function(testing) {
      //console.log("TOOL!!! "+testing);
    }

  });

  return Tool;

  var template = function(core, options) {
    this.id = (options && options.name) || 'id-undefined';
    this.selector = (options && options.selector) || 'body';

    this.state = {};
    this.language = 'dev';

    // SVG builder
    this.wrapperName = 'vizabi-' + this.id;

    this.container = d3.select(this.selector).append('div')
      .attr('id', this.wrapperName)
      .classed('vizabi', true);

    this.svg = this.container.append('svg');

    // Manager instances, obtained from the core
    this.managers = {
      events: core.getInstance('events'),
      data: core.getInstance('data'),
      layout: core.getInstance('layout'),
      i18n: core.getInstance('i18n')
    };

    // Sets up Managers
    this.managers.layout.setProperties({
      div: this.container,    // d3 object
      svg: this.svg,          // d3 object
      schema: 'desktop',
      defaultMeasures: { width: 900, height: 500 }
    });

    var _this = this;

    this.managers.events.bind('resize', function() {
      _this.managers.layout.update();
    });
  };

  template.prototype = {
    start: function() {
      // returns self
      return this;
    },

    // Ignore
    stop: function() {
      this.managers.events.trigger('vizabi:halt');
      return this;
    },

    // Improve
    destroy: function() {
      d3.select('#' + this.wrapperName).remove();
    },

    getSVG: function() {
      return this.svg;
    },

    getState: function() {
      return this.state;
    },

    setState: function(state) {
      extend(this.state, state);
      this.managers.events.trigger('change:state', this.state);
      return this;
    },

    setLanguage: function(lang) {
      if (!lang) return;  // let it fail silently?
      this.managers.i18n.setLanguage(lang, function(lang) {
        this.managers.events.trigger('change:language', lang);
      });
      return this;
    },

    getLanguage: function() {
      if (this.language) return this.language;
    },

    getLayout: function() {
      return this.layout;
    },

    setLayout: function(layout) {
      if (!layout) return this;   // let it fail silently.
      this.layout = layout;
      this.managers.layout.set(layout);
      return this;
    },

    getInstance: function(name) {
      if (!name) return this.managers;
      if (this.managers[name]) return this.managers[name];
      return null;
    }
  };

  return template;
});
<commit_msg>add init function for the tool<commit_after>define([
  'd3',
  'base/object',
  'base/class',
], function(d3, object, Class) {

  var Tool = Class.extend({
    init: function(core, options) {
      this.name = options.name;
      this.state = {};

      this.template = "tools/tool";
      this.placeholder = d3.select(options.placeholder); // Markup to define where a tool is going to be rendered.
      this.element = undefined; // Element which embodies the tool
      this.widgets = {};
      this.layout_profiles = {
        "default": {
            timeslider: true,
            title: true,
            button: true
        }
      };
    }
  });

  return Tool;

  var template = function(core, options) {
    this.id = (options && options.name) || 'id-undefined';
    this.selector = (options && options.selector) || 'body';

    this.state = {};
    this.language = 'dev';

    // SVG builder
    this.wrapperName = 'vizabi-' + this.id;

    this.container = d3.select(this.selector).append('div')
      .attr('id', this.wrapperName)
      .classed('vizabi', true);

    this.svg = this.container.append('svg');

    // Manager instances, obtained from the core
    this.managers = {
      events: core.getInstance('events'),
      data: core.getInstance('data'),
      layout: core.getInstance('layout'),
      i18n: core.getInstance('i18n')
    };

    // Sets up Managers
    this.managers.layout.setProperties({
      div: this.container,    // d3 object
      svg: this.svg,          // d3 object
      schema: 'desktop',
      defaultMeasures: { width: 900, height: 500 }
    });

    var _this = this;

    this.managers.events.bind('resize', function() {
      _this.managers.layout.update();
    });
  };

  template.prototype = {
    start: function() {
      // returns self
      return this;
    },

    // Ignore
    stop: function() {
      this.managers.events.trigger('vizabi:halt');
      return this;
    },

    // Improve
    destroy: function() {
      d3.select('#' + this.wrapperName).remove();
    },

    getSVG: function() {
      return this.svg;
    },

    getState: function() {
      return this.state;
    },

    setState: function(state) {
      extend(this.state, state);
      this.managers.events.trigger('change:state', this.state);
      return this;
    },

    setLanguage: function(lang) {
      if (!lang) return;  // let it fail silently?
      this.managers.i18n.setLanguage(lang, function(lang) {
        this.managers.events.trigger('change:language', lang);
      });
      return this;
    },

    getLanguage: function() {
      if (this.language) return this.language;
    },

    getLayout: function() {
      return this.layout;
    },

    setLayout: function(layout) {
      if (!layout) return this;   // let it fail silently.
      this.layout = layout;
      this.managers.layout.set(layout);
      return this;
    },

    getInstance: function(name) {
      if (!name) return this.managers;
      if (this.managers[name]) return this.managers[name];
      return null;
    }
  };

  return template;
});
<|endoftext|>


<commit_before>/* jshint -W097 */
"use strict";

function sourceToArray(source) {
  var arr = new Array(source.length),
    i;
  for (i = 0; i < source.length; i += 1) {
    arr[i] = source[i];
  }
  return arr;
}

function Begin(section) {
  this.section = section;
  this.type    = Begin;
}

Begin.prototype.toString = function () {
  return '';
};

Begin.prototype.length = function () {
  return this.section.length;
};

function BeginMethodDef(method, i, defPart) {
  this.method  = method;
  this.i       = i;
  this.defPart = defPart;
  this.type    = Begin;
}

BeginMethodDef.prototype.length = function () {
  return this.defPart.length;
};

BeginMethodDef.prototype.toString = function () {
  let tags = "MethodDeclaration",
    id = "m:" + this.method.sourceSection.sourceId +
      ":" + this.method.sourceSection.id +
      ":" + this.i;
  return '';
};

function End(section, length) {
  this.section = section;
  this.len     = length;
  this.type    = End;
}

End.prototype.toString = function () {
  return '';
};

End.prototype.length = function () {
  return this.len;
};

function Annotation(char) {
  this.char   = char;
  this.before = [];
  this.after  = [];
}

Annotation.prototype.toString = function() {
  this.before.sort(function (a, b) {
    if (a.type !== b.type) {
      if (a.type === Begin) {
        return -1;
      } else {
        return 1;
      }
    }

    if (a.length() === b.length()) {
      return 0;
    }

    if (a.length() < b.length()) {
      return (a.type === Begin) ? 1 : -1;
    } else {
      return (a.type === Begin) ? -1 : 1;
    }
  });

  var result = this.before.join("");
  result += this.char;
  result += this.after.join("");
  return result;
};

function arrayToString(arr) {
  var result = "";
  for (var i = 0; i < arr.length; i++) {
    result += arr[i].toString();
  }
  return result;
}

function nodeFromTemplate(tplId) {
  var tpl = document.getElementById(tplId),
    result = tpl.cloneNode(true);
  result.removeAttribute("id");
  return result;
}

function createLineNumbers(cnt) {
  var result = "1",
    i;
  for (i = 2; i <= cnt; i += 1) {
    result = result + "\n" + i + "";
  }
  return result;
}

function ensureItIsAnnotation(arr, idx) {
  if (!(arr[idx] instanceof Annotation)) {
    arr[idx] = new Annotation(arr[idx]);
  }
  return arr[idx];
}

function annotateArray(arr, sourceId, sections, methods) {
  // adding all source sections
  for (var sId in sections) {
    var s = sections[sId];
    if (s.sourceId === sourceId) {
      var start = ensureItIsAnnotation(arr, s.firstIndex),
            end = ensureItIsAnnotation(arr, s.firstIndex + s.length);
      start.before.push(new Begin(s));
      end.before.push(new End(s, s.length));
    }
  }

  // adding method definitions
  for (let k in methods) {
    let meth = methods[k];
    if (meth.sourceSection.sourceId !== sourceId) {
      continue;
    }

    for (let i in meth.definition) {
      let defPart = meth.definition[i],
        start = ensureItIsAnnotation(arr, defPart.firstIndex),
        end = ensureItIsAnnotation(arr, defPart.firstIndex + defPart.length);
      start.before.push(new BeginMethodDef(meth, i, defPart));
      end.before.push(new End(meth, defPart.length));
    }
  }
}

function countNumberOfLines(str) {
  var cnt = 1;
  for (var i = 0; i < str.length; i++) {
    if (str[i] === "\n") {
      cnt += 1;
    }
  }
  return cnt;
}

function enableEventualSendClicks(fileNode) {
  var sendOperator = fileNode.find(".EventualMessageSend");
  sendOperator.attr({
    "data-toggle"    : "popover",
    "data-trigger"   : "click hover",
    "title"          : "Message Breakpoint",
    "data-html"      : "true",
    "data-placement" : "auto top"
  });

  sendOperator.attr("data-content", function() {
    let content = nodeFromTemplate("hover-menu");
    // capture the source section id, and store it on the buttons
    $(content).find("button").attr("data-ss-id", this.id);
    return $(content).html();
  });
  sendOperator.popover();

  $(document).on("click", ".bp-rcv", function (e) {
    e.stopImmediatePropagation();
    ctrl.onToggleSendBreakpoint(e.currentTarget.attributes["data-ss-id"].value, "receiver");
  });

  $(document).on("click", ".bp-send", function (e) {
    e.stopImmediatePropagation();
    ctrl.onToggleSendBreakpoint(e.currentTarget.attributes["data-ss-id"].value, "sender");
  });
}

function enableMethodBreakpointHover(fileNode) {
  let methDecls = fileNode.find(".MethodDeclaration");
  methDecls.attr({
    "data-toggle"   : "popover",
    "data-trigger"  : "click hover",
    "title"         : "Breakpoints",
    "animation"     : "false",
    "data-html"     : "true",
    "data-placement": "auto top" });

  methDecls.attr("data-content", function () {
    let content = nodeFromTemplate("method-breakpoints");
    return $(content).html();
  });

  methDecls.popover();

  $(document).on("click", ".bp-async-rcv", function () { dbgLog("bp-async-rcv"); });
}

function showSource(s, sections, methods) {
  var tabListEntry = document.getElementById(s.id),
    aElem = document.getElementById("a" + s.id);
  if (tabListEntry) {
    if (aElem.innerText !== s.name) {
      $(tabListEntry).remove();
      $(aElem).remove();
      tabListEntry = null;
      aElem = null;
    } else {
      return; // source is already there, so, I think, we don't need to update it
    }
  }

  var annotationArray = sourceToArray(s.sourceText);
  annotateArray(annotationArray, s.id, sections, methods);

  tabListEntry = nodeFromTemplate("tab-list-entry");

  if (aElem === null) {
    // create the tab "header/handle"
    aElem = $(tabListEntry).find("a");
    aElem.attr("href", "#" + s.id);
    aElem.attr("id", "a" + s.id);
    aElem.text(s.name);
    $("#tabs").append(tabListEntry);
  }

  // create tab pane
  var newFileElement = nodeFromTemplate("file");
  newFileElement.setAttribute("id", s.id);
  newFileElement.getElementsByClassName("line-numbers")[0].innerHTML = createLineNumbers(countNumberOfLines(s.sourceText));
  var fileNode = newFileElement.getElementsByClassName("source-file")[0];
  fileNode.innerHTML = arrayToString(annotationArray);

  // enable clicking on EventualSendNodes
  enableEventualSendClicks($(fileNode));
  enableMethodBreakpointHover($(fileNode));

  var files = document.getElementById("files");
  files.appendChild(newFileElement);
}

/**
 * The HTML View, which realizes all access to the DOM for displaying
 * data and reacting to events.
 * @constructor
 */
function View() {
  this.currentSectionId = null;
  this.currentDomNode   = null;
  this.debuggerButtonJQNodes = null;
}

View.prototype.onConnect = function () {
  $("#dbg-connect-btn").html("Connected");
};

View.prototype.onClose = function () {
  $("#dbg-connect-btn").html("Reconnect");
};

View.prototype.displaySources = function (msg) {
  var sId;
  for (sId in msg.sources) {
    showSource(msg.sources[sId], msg.sections, msg.methods);
  }
  $('.nav-tabs a[href="#' + sId + '"]').tab('show');
};

function showVar(name, value, list) {
  var entry = nodeFromTemplate("frame-state-tpl");
  var t = $(entry).find("th");
  t.html(name);
  t = $(entry).find("td");
  t.html(value);
  list.appendChild(entry);
}

function showFrame(frame, i, list) {
  var stackEntry = frame.methodName;
  if (frame.sourceSection) {
    stackEntry += ":" + frame.sourceSection.line + ":" + frame.sourceSection.column;
  }
  var entry = nodeFromTemplate("stack-frame-tpl");
  entry.setAttribute("id", "frame-" + i);

  var tds = $(entry).find("td");
  tds[0].innerHTML = stackEntry;
  list.appendChild(entry);
}

View.prototype.displaySuspendEvent = function (data, getSourceAndMethods) {
  var list = document.getElementById("stack-frames");
  while (list.lastChild) {
    list.removeChild(list.lastChild);
  }

  for (var i = 0; i < data.stack.length; i++) {
    showFrame(data.stack[i], i, list);
  }

  list = document.getElementById("frame-state");
  while (list.lastChild) {
    list.removeChild(list.lastChild);
  }

  showVar("Arguments", data.topFrame.arguments.join(", "), list);

  for (var varName in data.topFrame.slots) {
    showVar(varName, data.topFrame.slots[varName], list);
  }

  // update the source sections for the sourceId
  if (data.sections) {
    var pane = document.getElementById(data.sourceId);
    var sourceFile = $(pane).find(".source-file");

    // remove all spans
    sourceFile.find("span").replaceWith($(".html"));

    // apply new spans
    var result = getSourceAndMethods(data.sourceId),
      source   = result[0],
      methods  = result[1];

    var annotationArray = sourceToArray(source.sourceText);
    annotateArray(annotationArray, source.id, data.sections, methods);
    sourceFile.html(arrayToString(annotationArray));

    // enable clicking on EventualSendNodes
    enableEventualSendClicks(sourceFile);
    enableMethodBreakpointHover(sourceFile);
  }

  // highlight current node
  var ssId = data.stack[0].sourceSection.id;
  var sourceId = data.stack[0].sourceSection.sourceId;
  var ss = document.getElementById(ssId);
  $(ss).addClass("DbgCurrentNode");

  this.currentDomNode   = ss;
  this.currentSectionId = ssId;
  this.showSourceById(sourceId);

  // scroll to the statement
  $('html, body').animate({
    scrollTop: $(ss).offset().top
  }, 300);
};

View.prototype.showSourceById = function (sourceId) {
  if (this.getActiveSourceId() !== sourceId) {
    $(document.getElementById("a" + sourceId)).tab('show');
  }
};

View.prototype.getActiveSourceId = function () {
  return $(".tab-pane.active").attr("id");
};

View.prototype.ensureBreakpointListEntry = function (breakpoint) {
  if (breakpoint.checkbox !== null) {
    return;
  }

  var bpId = "bp:" + breakpoint.source.id + ":" + breakpoint.getId();
  var entry = nodeFromTemplate("breakpoint-tpl");
  entry.setAttribute("id", bpId);

  var tds = $(entry).find("td");
  tds[0].innerHTML = breakpoint.source.name;
  tds[1].innerHTML = breakpoint.getId();

  breakpoint.checkbox = $(entry).find("input");
  breakpoint.checkbox.attr("id", bpId + "chk");

  var list = document.getElementById("breakpoint-list");
  list.appendChild(entry);
};

View.prototype.updateBreakpoint = function (breakpoint, highlightElem,
                                            highlightClass) {
  this.ensureBreakpointListEntry(breakpoint);
  var enabled = breakpoint.isEnabled();

  breakpoint.checkbox.prop('checked', enabled);
  if (enabled) {
    highlightElem.addClass(highlightClass);
  } else {
    highlightElem.removeClass(highlightClass);
  }
};

View.prototype.updateLineBreakpoint = function (bp) {
  var lineNumSpan = $(bp.lineNumSpan);
  this.updateBreakpoint(bp, lineNumSpan, "breakpoint-active");
};

View.prototype.updateSendBreakpoint = function (bp) {
  var bpSpan = $("#" + bp.sectionId);
  this.updateBreakpoint(bp, bpSpan, "send-breakpoint-active");
};

View.prototype.lazyFindDebuggerButtons = function () {
  if (!this.debuggerButtonJQNodes) {
    this.debuggerButtonJQNodes = {};
    this.debuggerButtonJQNodes.resume = $(document.getElementById("dbg-btn-resume"));
    this.debuggerButtonJQNodes.pause  = $(document.getElementById("dbg-btn-pause"));
    this.debuggerButtonJQNodes.stop   = $(document.getElementById("dbg-btn-stop"));

    this.debuggerButtonJQNodes.stepInto = $(document.getElementById("dbg-btn-step-into"));
    this.debuggerButtonJQNodes.stepOver = $(document.getElementById("dbg-btn-step-over"));
    this.debuggerButtonJQNodes.return   = $(document.getElementById("dbg-btn-return"));
  }
};

View.prototype.switchDebuggerToSuspendedState = function () {
  this.lazyFindDebuggerButtons();

  this.debuggerButtonJQNodes.resume.removeClass("disabled");
  this.debuggerButtonJQNodes.pause.addClass("disabled");
  this.debuggerButtonJQNodes.stop.removeClass("disabled");

  this.debuggerButtonJQNodes.stepInto.removeClass("disabled");
  this.debuggerButtonJQNodes.stepOver.removeClass("disabled");
  this.debuggerButtonJQNodes.return.removeClass("disabled");
};

View.prototype.switchDebuggerToResumedState = function () {
  this.lazyFindDebuggerButtons();

  this.debuggerButtonJQNodes.resume.addClass("disabled");
  this.debuggerButtonJQNodes.pause.removeClass("disabled");
  this.debuggerButtonJQNodes.stop.removeClass("disabled");

  this.debuggerButtonJQNodes.stepInto.addClass("disabled");
  this.debuggerButtonJQNodes.stepOver.addClass("disabled");
  this.debuggerButtonJQNodes.return.addClass("disabled");
};

View.prototype.onContinueExecution = function () {
  this.switchDebuggerToResumedState();
  $(this.currentDomNode).removeClass("DbgCurrentNode");
};
<commit_msg>Added helper to encode and decode method decl breakpoints<commit_after>/* jshint -W097 */
"use strict";

function sourceToArray(source) {
  var arr = new Array(source.length),
    i;
  for (i = 0; i < source.length; i += 1) {
    arr[i] = source[i];
  }
  return arr;
}

function methodDeclIdToString(sourceId, sectionId, idx) {
  return "m:" + sourceId + ":" + sectionId + ":" + idx;
}

function methodDeclIdToObj(id) {
  let arr = id.split(":");
  return {
    "sourceId"  : arr[1],
    "sectionId" : arr[2],
    "idx" : arr[3]
  };
}

function Begin(section) {
  this.section = section;
  this.type    = Begin;
}

Begin.prototype.toString = function () {
  return '';
};

Begin.prototype.length = function () {
  return this.section.length;
};

function BeginMethodDef(method, i, defPart) {
  this.method  = method;
  this.i       = i;
  this.defPart = defPart;
  this.type    = Begin;
}

BeginMethodDef.prototype.length = function () {
  return this.defPart.length;
};

BeginMethodDef.prototype.toString = function () {
  let tags = "MethodDeclaration",
    id = methodDeclIdToString(this.method.sourceSection.sourceId,
      this.method.sourceSection.id, this.i);
  return '';
};

function End(section, length) {
  this.section = section;
  this.len     = length;
  this.type    = End;
}

End.prototype.toString = function () {
  return '';
};

End.prototype.length = function () {
  return this.len;
};

function Annotation(char) {
  this.char   = char;
  this.before = [];
  this.after  = [];
}

Annotation.prototype.toString = function() {
  this.before.sort(function (a, b) {
    if (a.type !== b.type) {
      if (a.type === Begin) {
        return -1;
      } else {
        return 1;
      }
    }

    if (a.length() === b.length()) {
      return 0;
    }

    if (a.length() < b.length()) {
      return (a.type === Begin) ? 1 : -1;
    } else {
      return (a.type === Begin) ? -1 : 1;
    }
  });

  var result = this.before.join("");
  result += this.char;
  result += this.after.join("");
  return result;
};

function arrayToString(arr) {
  var result = "";
  for (var i = 0; i < arr.length; i++) {
    result += arr[i].toString();
  }
  return result;
}

function nodeFromTemplate(tplId) {
  var tpl = document.getElementById(tplId),
    result = tpl.cloneNode(true);
  result.removeAttribute("id");
  return result;
}

function createLineNumbers(cnt) {
  var result = "1",
    i;
  for (i = 2; i <= cnt; i += 1) {
    result = result + "\n" + i + "";
  }
  return result;
}

function ensureItIsAnnotation(arr, idx) {
  if (!(arr[idx] instanceof Annotation)) {
    arr[idx] = new Annotation(arr[idx]);
  }
  return arr[idx];
}

function annotateArray(arr, sourceId, sections, methods) {
  // adding all source sections
  for (var sId in sections) {
    var s = sections[sId];
    if (s.sourceId === sourceId) {
      var start = ensureItIsAnnotation(arr, s.firstIndex),
            end = ensureItIsAnnotation(arr, s.firstIndex + s.length);
      start.before.push(new Begin(s));
      end.before.push(new End(s, s.length));
    }
  }

  // adding method definitions
  for (let k in methods) {
    let meth = methods[k];
    if (meth.sourceSection.sourceId !== sourceId) {
      continue;
    }

    for (let i in meth.definition) {
      let defPart = meth.definition[i],
        start = ensureItIsAnnotation(arr, defPart.firstIndex),
        end = ensureItIsAnnotation(arr, defPart.firstIndex + defPart.length);
      start.before.push(new BeginMethodDef(meth, i, defPart));
      end.before.push(new End(meth, defPart.length));
    }
  }
}

function countNumberOfLines(str) {
  var cnt = 1;
  for (var i = 0; i < str.length; i++) {
    if (str[i] === "\n") {
      cnt += 1;
    }
  }
  return cnt;
}

function enableEventualSendClicks(fileNode) {
  var sendOperator = fileNode.find(".EventualMessageSend");
  sendOperator.attr({
    "data-toggle"    : "popover",
    "data-trigger"   : "click hover",
    "title"          : "Message Breakpoint",
    "data-html"      : "true",
    "data-placement" : "auto top"
  });

  sendOperator.attr("data-content", function() {
    let content = nodeFromTemplate("hover-menu");
    // capture the source section id, and store it on the buttons
    $(content).find("button").attr("data-ss-id", this.id);
    return $(content).html();
  });
  sendOperator.popover();

  $(document).on("click", ".bp-rcv", function (e) {
    e.stopImmediatePropagation();
    ctrl.onToggleSendBreakpoint(e.currentTarget.attributes["data-ss-id"].value, "receiver");
  });

  $(document).on("click", ".bp-send", function (e) {
    e.stopImmediatePropagation();
    ctrl.onToggleSendBreakpoint(e.currentTarget.attributes["data-ss-id"].value, "sender");
  });
}

function enableMethodBreakpointHover(fileNode) {
  let methDecls = fileNode.find(".MethodDeclaration");
  methDecls.attr({
    "data-toggle"   : "popover",
    "data-trigger"  : "click hover",
    "title"         : "Breakpoints",
    "animation"     : "false",
    "data-html"     : "true",
    "data-placement": "auto top" });

  methDecls.attr("data-content", function () {
    let content = nodeFromTemplate("method-breakpoints");
    return $(content).html();
  });

  methDecls.popover();

  $(document).on("click", ".bp-async-rcv", function () { dbgLog("bp-async-rcv"); });
}

function showSource(s, sections, methods) {
  var tabListEntry = document.getElementById(s.id),
    aElem = document.getElementById("a" + s.id);
  if (tabListEntry) {
    if (aElem.innerText !== s.name) {
      $(tabListEntry).remove();
      $(aElem).remove();
      tabListEntry = null;
      aElem = null;
    } else {
      return; // source is already there, so, I think, we don't need to update it
    }
  }

  var annotationArray = sourceToArray(s.sourceText);
  annotateArray(annotationArray, s.id, sections, methods);

  tabListEntry = nodeFromTemplate("tab-list-entry");

  if (aElem === null) {
    // create the tab "header/handle"
    aElem = $(tabListEntry).find("a");
    aElem.attr("href", "#" + s.id);
    aElem.attr("id", "a" + s.id);
    aElem.text(s.name);
    $("#tabs").append(tabListEntry);
  }

  // create tab pane
  var newFileElement = nodeFromTemplate("file");
  newFileElement.setAttribute("id", s.id);
  newFileElement.getElementsByClassName("line-numbers")[0].innerHTML = createLineNumbers(countNumberOfLines(s.sourceText));
  var fileNode = newFileElement.getElementsByClassName("source-file")[0];
  fileNode.innerHTML = arrayToString(annotationArray);

  // enable clicking on EventualSendNodes
  enableEventualSendClicks($(fileNode));
  enableMethodBreakpointHover($(fileNode));

  var files = document.getElementById("files");
  files.appendChild(newFileElement);
}

/**
 * The HTML View, which realizes all access to the DOM for displaying
 * data and reacting to events.
 * @constructor
 */
function View() {
  this.currentSectionId = null;
  this.currentDomNode   = null;
  this.debuggerButtonJQNodes = null;
}

View.prototype.onConnect = function () {
  $("#dbg-connect-btn").html("Connected");
};

View.prototype.onClose = function () {
  $("#dbg-connect-btn").html("Reconnect");
};

View.prototype.displaySources = function (msg) {
  var sId;
  for (sId in msg.sources) {
    showSource(msg.sources[sId], msg.sections, msg.methods);
  }
  $('.nav-tabs a[href="#' + sId + '"]').tab('show');
};

function showVar(name, value, list) {
  var entry = nodeFromTemplate("frame-state-tpl");
  var t = $(entry).find("th");
  t.html(name);
  t = $(entry).find("td");
  t.html(value);
  list.appendChild(entry);
}

function showFrame(frame, i, list) {
  var stackEntry = frame.methodName;
  if (frame.sourceSection) {
    stackEntry += ":" + frame.sourceSection.line + ":" + frame.sourceSection.column;
  }
  var entry = nodeFromTemplate("stack-frame-tpl");
  entry.setAttribute("id", "frame-" + i);

  var tds = $(entry).find("td");
  tds[0].innerHTML = stackEntry;
  list.appendChild(entry);
}

View.prototype.displaySuspendEvent = function (data, getSourceAndMethods) {
  var list = document.getElementById("stack-frames");
  while (list.lastChild) {
    list.removeChild(list.lastChild);
  }

  for (var i = 0; i < data.stack.length; i++) {
    showFrame(data.stack[i], i, list);
  }

  list = document.getElementById("frame-state");
  while (list.lastChild) {
    list.removeChild(list.lastChild);
  }

  showVar("Arguments", data.topFrame.arguments.join(", "), list);

  for (var varName in data.topFrame.slots) {
    showVar(varName, data.topFrame.slots[varName], list);
  }

  // update the source sections for the sourceId
  if (data.sections) {
    var pane = document.getElementById(data.sourceId);
    var sourceFile = $(pane).find(".source-file");

    // remove all spans
    sourceFile.find("span").replaceWith($(".html"));

    // apply new spans
    var result = getSourceAndMethods(data.sourceId),
      source   = result[0],
      methods  = result[1];

    var annotationArray = sourceToArray(source.sourceText);
    annotateArray(annotationArray, source.id, data.sections, methods);
    sourceFile.html(arrayToString(annotationArray));

    // enable clicking on EventualSendNodes
    enableEventualSendClicks(sourceFile);
    enableMethodBreakpointHover(sourceFile);
  }

  // highlight current node
  var ssId = data.stack[0].sourceSection.id;
  var sourceId = data.stack[0].sourceSection.sourceId;
  var ss = document.getElementById(ssId);
  $(ss).addClass("DbgCurrentNode");

  this.currentDomNode   = ss;
  this.currentSectionId = ssId;
  this.showSourceById(sourceId);

  // scroll to the statement
  $('html, body').animate({
    scrollTop: $(ss).offset().top
  }, 300);
};

View.prototype.showSourceById = function (sourceId) {
  if (this.getActiveSourceId() !== sourceId) {
    $(document.getElementById("a" + sourceId)).tab('show');
  }
};

View.prototype.getActiveSourceId = function () {
  return $(".tab-pane.active").attr("id");
};

View.prototype.ensureBreakpointListEntry = function (breakpoint) {
  if (breakpoint.checkbox !== null) {
    return;
  }

  var bpId = "bp:" + breakpoint.source.id + ":" + breakpoint.getId();
  var entry = nodeFromTemplate("breakpoint-tpl");
  entry.setAttribute("id", bpId);

  var tds = $(entry).find("td");
  tds[0].innerHTML = breakpoint.source.name;
  tds[1].innerHTML = breakpoint.getId();

  breakpoint.checkbox = $(entry).find("input");
  breakpoint.checkbox.attr("id", bpId + "chk");

  var list = document.getElementById("breakpoint-list");
  list.appendChild(entry);
};

View.prototype.updateBreakpoint = function (breakpoint, highlightElem,
                                            highlightClass) {
  this.ensureBreakpointListEntry(breakpoint);
  var enabled = breakpoint.isEnabled();

  breakpoint.checkbox.prop('checked', enabled);
  if (enabled) {
    highlightElem.addClass(highlightClass);
  } else {
    highlightElem.removeClass(highlightClass);
  }
};

View.prototype.updateLineBreakpoint = function (bp) {
  var lineNumSpan = $(bp.lineNumSpan);
  this.updateBreakpoint(bp, lineNumSpan, "breakpoint-active");
};

View.prototype.updateSendBreakpoint = function (bp) {
  var bpSpan = $("#" + bp.sectionId);
  this.updateBreakpoint(bp, bpSpan, "send-breakpoint-active");
};

View.prototype.lazyFindDebuggerButtons = function () {
  if (!this.debuggerButtonJQNodes) {
    this.debuggerButtonJQNodes = {};
    this.debuggerButtonJQNodes.resume = $(document.getElementById("dbg-btn-resume"));
    this.debuggerButtonJQNodes.pause  = $(document.getElementById("dbg-btn-pause"));
    this.debuggerButtonJQNodes.stop   = $(document.getElementById("dbg-btn-stop"));

    this.debuggerButtonJQNodes.stepInto = $(document.getElementById("dbg-btn-step-into"));
    this.debuggerButtonJQNodes.stepOver = $(document.getElementById("dbg-btn-step-over"));
    this.debuggerButtonJQNodes.return   = $(document.getElementById("dbg-btn-return"));
  }
};

View.prototype.switchDebuggerToSuspendedState = function () {
  this.lazyFindDebuggerButtons();

  this.debuggerButtonJQNodes.resume.removeClass("disabled");
  this.debuggerButtonJQNodes.pause.addClass("disabled");
  this.debuggerButtonJQNodes.stop.removeClass("disabled");

  this.debuggerButtonJQNodes.stepInto.removeClass("disabled");
  this.debuggerButtonJQNodes.stepOver.removeClass("disabled");
  this.debuggerButtonJQNodes.return.removeClass("disabled");
};

View.prototype.switchDebuggerToResumedState = function () {
  this.lazyFindDebuggerButtons();

  this.debuggerButtonJQNodes.resume.addClass("disabled");
  this.debuggerButtonJQNodes.pause.removeClass("disabled");
  this.debuggerButtonJQNodes.stop.removeClass("disabled");

  this.debuggerButtonJQNodes.stepInto.addClass("disabled");
  this.debuggerButtonJQNodes.stepOver.addClass("disabled");
  this.debuggerButtonJQNodes.return.addClass("disabled");
};

View.prototype.onContinueExecution = function () {
  this.switchDebuggerToResumedState();
  $(this.currentDomNode).removeClass("DbgCurrentNode");
};
<|endoftext|>


<commit_before>var f_ = require('../index.js');

var TaskList = require('./TaskList');

var f_config = {
	functionFlow: ['getSource', 'writeSource', 'notify'],  // REQUIRED
	resetOnRetryAll: true,

	toLog: ['all'],

	desc: 'Test taskList',

	maxRetries: {
		all: 2
	}
};

TaskList = f_.augment(TaskList, f_config);

var runSingle = function (){
	var taskListInstance = new TaskList();
	taskListInstance = f_.setup(taskListInstance);

	taskListInstance.start();
}();

var runMultiple = function (){
	var startTime = Date.now();

	for(var i=0; i<10000; i+=1){
		var taskListInstance = new TaskList();
		taskListInstance = f_.setup(taskListInstance);

		taskListInstance.f_desc = taskListInstance.f_desc + ' #' + i;

		taskListInstance.start();
	}
	var endTime = Date.now(),
			timeTaken = endTime - startTime;

	console.log(timeTaken);
};
<commit_msg>Comment about start method not being present in functionFlow<commit_after>var f_ = require('../index.js');

var TaskList = require('./TaskList');

var f_config = {

	/**
	 * `start` method is not given here, since we call it manualy
	 * This is just a matter of personal taste, I do not like auto starts!
	 */
	functionFlow: ['getSource', 'writeSource', 'notify'],  // REQUIRED
	resetOnRetryAll: true,

	toLog: ['all'],

	desc: 'Test taskList',

	maxRetries: {
		all: 2
	}
};

TaskList = f_.augment(TaskList, f_config);

var runSingle = function (){
	var taskListInstance = new TaskList();
	taskListInstance = f_.setup(taskListInstance);

	taskListInstance.start();
}();

var runMultiple = function (){
	var startTime = Date.now();

	for(var i=0; i<10000; i+=1){
		var taskListInstance = new TaskList();
		taskListInstance = f_.setup(taskListInstance);

		taskListInstance.f_desc = taskListInstance.f_desc + ' #' + i;

		taskListInstance.start();
	}
	var endTime = Date.now(),
			timeTaken = endTime - startTime;

	console.log(timeTaken);
};
<|endoftext|>


<commit_before>function screens_platform() {
    var platform = "browser";
    if (typeof module !== 'undefined' && this && this.module !== module) {
        platform = global.platform || "server";
    } else if (typeof importScripts !== 'undefined') {
        platform = "client";
    }
    return platform;
}

function screens_create_proxy(id) {
    /* Create component proxy */
    var component_obj = new Proxy(() => {
        return {};
    }, {
            get: function (object, property) {
                if (Reflect.has(object, property)) {
                    return Reflect.get(object, property);
                } else if (property in screens) {
                    return screens[property];
                } else {
                    var proxy = Reflect.get(object, "proxy");
                    if (proxy && proxy.get && proxy.get.enabled) {
                        return proxy.get(object, property);
                    }
                }
                return undefined;
            },
            apply: function (object, thisArg, argumentsList) {
                var proxy = Reflect.get(object, "proxy");
                if (proxy && proxy.apply) {
                    return proxy.apply.apply(thisArg, argumentsList);
                }
            }
        });
    component_obj.proxy = {};
    component_obj.id = id;
    return component_obj;
}

function screens_setup(package_name, component_name, child_name, node) {
    var children = [];
    var id = package_name + "." + component_name;
    var component_id = id;
    if (child_name) {
        node = node[child_name];
        id += "." + child_name;
    } else {
        node = screens(id);
        for (var key in node) {
            children.push(key);
        }
    }
    if(node && node.id) {
        return [];
    }
    /* Create component proxy */
    var component_obj = screens_create_proxy(id);
    if (child_name) {
        screens[package_name][component_name][child_name] = component_obj;
        component_obj.upper = screens[package_name][component_name];
    } else {
        screens[package_name][component_name] = component_obj;
    }
    if (typeof node !== "function") {
        throw "Component " + id + " cannot be loaded stack: " + new Error().stack;
    }
    var platform = node(component_obj, child_name);
    var init = null;
    if (platform && screens.platform !== platform) {
        console.log(screens.platform + ": remote component: " + id + " = " + platform);
        component_obj = screens_create_proxy(id);
        component_obj.proxy.apply = function (object, thisArg, argumentsList) {
            return function () {
                var args = Array.prototype.slice.call(argumentsList);
                args.unshift(id);
                return component_obj.core.message["send_" + platform].apply(null, args);
            };
        };
        component_obj.proxy.get = function (object, property) {
            return function () {
                var args = Array.prototype.slice.call(arguments);
                args.unshift(id + "." + property);
                return component_obj.core.message["send_" + platform].apply(null, args);
            };
        };
        if (child_name) {
            screens[package_name][component_name][child_name] = component_obj;
            component_obj.upper = screens[package_name][component_name];
        } else {
            screens[package_name][component_name] = component_obj;
        }
    }
    else {
        init = { callback: component_obj.init, args: [component_obj] };
    }
    component_obj.require = platform;
    if (component_obj.proxy && component_obj.proxy.get) {
        component_obj.proxy.get.enabled = true;
    }
    console.log(screens.platform + ": Loaded " + id);
    screens.components.push(id);
    /* Load child components */
    var initializers = children.map(function (child) {
        return screens_setup(package_name, component_name, child, node);
    });
    if (init) {
        initializers.unshift(init);
        initializers = initializers.reduce((a, b) => a.concat(b), []);
    }
    return initializers;
}

async function screens_init(items) {
    for (item of items) {
        var initializers = item.initializers;
        if (initializers) {
            console.log(screens.platform + ": initializing: " + item.package_name + "." + item.component_name);
            do {
                var init = initializers.shift();
                if (init && init.callback) {
                    try {
                        var promise = init.callback.apply(null, init.args);
                        if (promise && promise.then) {
                            await promise;
                        }
                    }
                    catch (err) {
                        var message = err.message || err;
                        console.error(screens.platform + ": Failed to initialise component: " + item.package_name + "." + item.component_name + " with error: " + message + " stack: " + err.stack);
                    }
                }
            } while (init);
            console.log(screens.platform + ": initialized: " + item.package_name + "." + item.component_name);
        }
    }
}

function screens_import(path, optional) {
    if(screens.imports[path]) {
        return true;
    }
    screens.imports.push(path);
    if (screens.platform === "server" || screens.platform === "service") {
        require(path);
    }
    else if (screens.platform === "client") {
        try {
            importScripts(path);
        }
        catch(e) {
            console.error("Failure in importing: " + path);
            throw e;
        }
    }
    else if (screens.platform === "browser") {
        var isScript = path.includes(".js");
        var isStylesheet = path.includes(".css");
        var tagName = "";
        if(isScript) {
            tagName = "script";
        }
        else if(isStylesheet) {
            tagName = "link";
        }
        var items = document.getElementsByTagName(tagName);
        for (var i = items.length; i--;) {
            var target = items[i].src || items[i].href;
            if (target.includes(path)) {
                return items[i];
            }
        }
        return new Promise((resolve, reject) => {
            var ref = items[0];
            var parentNode = null;
            if(ref) {
                parentNode = ref.parentNode;
            }
            else {
                parentNode = document.getElementsByTagName("head")[0];
            }
            var item = document.createElement(tagName);
            if(isScript) {
                item.src = path;
                item.async = true;
            }
            if(isStylesheet) {
                item.href = path;
                item.type = "text/css";
                item.rel = "stylesheet";
                item.media = "screen,print";
            }
            item.onload = () => {
                resolve(item);
            };
            item.onerror = () => {
                me.error("Failure in stylesheet: " + path);
                reject();
            };
            parentNode.appendChild(item);
        });
    }
}

function screens_load(items, state) {
    if (items && items.length) {
        if (screens.platform === "server" || screens.platform === "service") {
            for (var item of items) {
                if (state === "import") {
                    if (item.package_name in screens && item.component_name in screens[item.package_name]) {
                        continue;
                    }
                    var path = "../code/" + item.package_name + "/" + item.package_name + "_" + item.component_name;
                    console.log(screens.platform + ": Loading " + path);
                    item.promises = [screens_import(path)];
                }
                else if (state === "setup") {
                    item.initializers = screens_setup(item.package_name, item.component_name);
                }
            }
        }
        else {
            var first = true;
            var paths = items.map((item) => {
                if (state === "import") {
                    if (item.package_name in screens && item.component_name in screens[item.package_name]) {
                        return null;
                    }
                }
                var path = item.package_name + "_" + item.component_name;
                if (first) {
                    path = "/packages/code/" + item.package_name + "/" + path;
                    first = false;
                }
                return path + ".js";
            });
            paths = paths.filter(Boolean);
            if (paths.length) {
                var path = paths.join(",") + "?platform=" + screens.platform;
                if (state === "import") {
                    console.log(screens.platform + ": Loading " + path);
                    var promises = items[0].promises = [];
                    promises.push(screens_import(path));
                    var firstItem = items[0];
                    if (firstItem.component_name === "*" && screens.platform === "browser") {
                        promises.push(screens_import(path.replace(/\.js/gi, ".css"), true));
                    }
                }
                else if (state === "setup") {
                    var firstItem = items[0];
                    if (firstItem.component_name === "*") {
                        items = Object.keys(screens[firstItem.package_name]).map((component_name) => {
                            return {
                                package_name: firstItem.package_name,
                                component_name: component_name
                            };
                        });
                    }
                    items.map((item) => {
                        item.initializers = screens_setup(item.package_name, item.component_name);
                    });
                }
            }
        }
    }
    return items;
}

async function screens_include(packages) {
    if (typeof packages === "string" && packages) {
        var names = packages.split(".");
        var package_name = names[0];
        var component_name = names[1];
        packages = {};
        packages[package_name] = [component_name];
    }
    var collection = {};
    for (var package_name in packages) {
        var components = packages[package_name];
        var items = [];
        if (!(package_name in screens)) {
            screens[package_name] = {};
        }
        components.forEach((component_name) => {
            var item = {
                package_name: package_name,
                component_name: component_name
            };
            items.push(item);
        });
        items = screens_load(items, "import");
        collection[package_name] = items;
    }
    var promises = [];
    for (package_name in packages) {
        for(var item of collection[package_name]) {
            if(item.promises) {
                promises.push(...item.promises);
            }
        }
    }
    if(promises) {
        await Promise.all(promises);
    }
    for (package_name in packages) {
        collection[package_name] = screens_load(collection[package_name], "setup");
    }
    for (package_name in packages) {
        console.log(screens.platform + ": initializing package: " + package_name);
        await screens_init(collection[package_name]);
    }
}

var screens = new Proxy(() => {
    return {};
}, {
        apply: function (object, thisArg, argumentsList) {
            return argumentsList[0].split('.').reduce((parent, name) => {
                return parent[name];
            }, screens);
        }
    });

Object.assign(screens, {
    components: [],
    imports: [],
    id: "package",
    platform: screens_platform(),
    include: screens_include,
    import: screens_import
});

var platform = screens_platform();
if (platform === "server" || platform === "service") {
    global.screens = screens;
    global.__json__ = {};
    global.__html__ = {};
}

if(platform === "browser" || platform === "client") {
    var module = screens;
}
<commit_msg>better error logging<commit_after>function screens_platform() {
    var platform = "browser";
    if (typeof module !== 'undefined' && this && this.module !== module) {
        platform = global.platform || "server";
    } else if (typeof importScripts !== 'undefined') {
        platform = "client";
    }
    return platform;
}

function screens_create_proxy(id) {
    /* Create component proxy */
    var component_obj = new Proxy(() => {
        return {};
    }, {
            get: function (object, property) {
                if (Reflect.has(object, property)) {
                    return Reflect.get(object, property);
                } else if (property in screens) {
                    return screens[property];
                } else {
                    var proxy = Reflect.get(object, "proxy");
                    if (proxy && proxy.get && proxy.get.enabled) {
                        return proxy.get(object, property);
                    }
                }
                return undefined;
            },
            apply: function (object, thisArg, argumentsList) {
                var proxy = Reflect.get(object, "proxy");
                if (proxy && proxy.apply) {
                    return proxy.apply.apply(thisArg, argumentsList);
                }
            }
        });
    component_obj.proxy = {};
    component_obj.id = id;
    return component_obj;
}

function screens_setup(package_name, component_name, child_name, node) {
    var children = [];
    var id = package_name + "." + component_name;
    var component_id = id;
    if (child_name) {
        node = node[child_name];
        id += "." + child_name;
    } else {
        node = screens(id);
        for (var key in node) {
            children.push(key);
        }
    }
    if(node && node.id) {
        return [];
    }
    /* Create component proxy */
    var component_obj = screens_create_proxy(id);
    if (child_name) {
        screens[package_name][component_name][child_name] = component_obj;
        component_obj.upper = screens[package_name][component_name];
    } else {
        screens[package_name][component_name] = component_obj;
    }
    if (typeof node !== "function") {
        throw "Component " + id + " cannot be loaded stack: " + new Error().stack;
    }
    var platform = node(component_obj, child_name);
    var init = null;
    if (platform && screens.platform !== platform) {
        console.log(screens.platform + ": remote component: " + id + " = " + platform);
        component_obj = screens_create_proxy(id);
        component_obj.proxy.apply = function (object, thisArg, argumentsList) {
            return function () {
                var args = Array.prototype.slice.call(argumentsList);
                args.unshift(id);
                return component_obj.core.message["send_" + platform].apply(null, args);
            };
        };
        component_obj.proxy.get = function (object, property) {
            return function () {
                var args = Array.prototype.slice.call(arguments);
                args.unshift(id + "." + property);
                return component_obj.core.message["send_" + platform].apply(null, args);
            };
        };
        if (child_name) {
            screens[package_name][component_name][child_name] = component_obj;
            component_obj.upper = screens[package_name][component_name];
        } else {
            screens[package_name][component_name] = component_obj;
        }
    }
    else {
        init = { callback: component_obj.init, args: [component_obj] };
    }
    component_obj.require = platform;
    if (component_obj.proxy && component_obj.proxy.get) {
        component_obj.proxy.get.enabled = true;
    }
    console.log(screens.platform + ": Loaded " + id);
    screens.components.push(id);
    /* Load child components */
    var initializers = children.map(function (child) {
        return screens_setup(package_name, component_name, child, node);
    });
    if (init) {
        initializers.unshift(init);
        initializers = initializers.reduce((a, b) => a.concat(b), []);
    }
    return initializers;
}

async function screens_init(items) {
    for (item of items) {
        var initializers = item.initializers;
        if (initializers) {
            console.log(screens.platform + ": initializing: " + item.package_name + "." + item.component_name);
            do {
                var init = initializers.shift();
                if (init && init.callback) {
                    try {
                        var promise = init.callback.apply(null, init.args);
                        if (promise && promise.then) {
                            await promise;
                        }
                    }
                    catch (err) {
                        console.error(screens.platform + ": Failed to initialise component: " + item.package_name + "." + item.component_name + " with error: " + JSON.stringify(err));
                    }
                }
            } while (init);
            console.log(screens.platform + ": initialized: " + item.package_name + "." + item.component_name);
        }
    }
}

function screens_import(path, optional) {
    if(screens.imports[path]) {
        return true;
    }
    screens.imports.push(path);
    if (screens.platform === "server" || screens.platform === "service") {
        require(path);
    }
    else if (screens.platform === "client") {
        try {
            importScripts(path);
        }
        catch(e) {
            console.error("Failure in importing: " + path);
            throw e;
        }
    }
    else if (screens.platform === "browser") {
        var isScript = path.includes(".js");
        var isStylesheet = path.includes(".css");
        var tagName = "";
        if(isScript) {
            tagName = "script";
        }
        else if(isStylesheet) {
            tagName = "link";
        }
        var items = document.getElementsByTagName(tagName);
        for (var i = items.length; i--;) {
            var target = items[i].src || items[i].href;
            if (target.includes(path)) {
                return items[i];
            }
        }
        return new Promise((resolve, reject) => {
            var ref = items[0];
            var parentNode = null;
            if(ref) {
                parentNode = ref.parentNode;
            }
            else {
                parentNode = document.getElementsByTagName("head")[0];
            }
            var item = document.createElement(tagName);
            if(isScript) {
                item.src = path;
                item.async = true;
            }
            if(isStylesheet) {
                item.href = path;
                item.type = "text/css";
                item.rel = "stylesheet";
                item.media = "screen,print";
            }
            item.onload = () => {
                resolve(item);
            };
            item.onerror = () => {
                me.error("Failure in stylesheet: " + path);
                reject();
            };
            parentNode.appendChild(item);
        });
    }
}

function screens_load(items, state) {
    if (items && items.length) {
        if (screens.platform === "server" || screens.platform === "service") {
            for (var item of items) {
                if (state === "import") {
                    if (item.package_name in screens && item.component_name in screens[item.package_name]) {
                        continue;
                    }
                    var path = "../code/" + item.package_name + "/" + item.package_name + "_" + item.component_name;
                    console.log(screens.platform + ": Loading " + path);
                    item.promises = [screens_import(path)];
                }
                else if (state === "setup") {
                    item.initializers = screens_setup(item.package_name, item.component_name);
                }
            }
        }
        else {
            var first = true;
            var paths = items.map((item) => {
                if (state === "import") {
                    if (item.package_name in screens && item.component_name in screens[item.package_name]) {
                        return null;
                    }
                }
                var path = item.package_name + "_" + item.component_name;
                if (first) {
                    path = "/packages/code/" + item.package_name + "/" + path;
                    first = false;
                }
                return path + ".js";
            });
            paths = paths.filter(Boolean);
            if (paths.length) {
                var path = paths.join(",") + "?platform=" + screens.platform;
                if (state === "import") {
                    console.log(screens.platform + ": Loading " + path);
                    var promises = items[0].promises = [];
                    promises.push(screens_import(path));
                    var firstItem = items[0];
                    if (firstItem.component_name === "*" && screens.platform === "browser") {
                        promises.push(screens_import(path.replace(/\.js/gi, ".css"), true));
                    }
                }
                else if (state === "setup") {
                    var firstItem = items[0];
                    if (firstItem.component_name === "*") {
                        items = Object.keys(screens[firstItem.package_name]).map((component_name) => {
                            return {
                                package_name: firstItem.package_name,
                                component_name: component_name
                            };
                        });
                    }
                    items.map((item) => {
                        item.initializers = screens_setup(item.package_name, item.component_name);
                    });
                }
            }
        }
    }
    return items;
}

async function screens_include(packages) {
    if (typeof packages === "string" && packages) {
        var names = packages.split(".");
        var package_name = names[0];
        var component_name = names[1];
        packages = {};
        packages[package_name] = [component_name];
    }
    var collection = {};
    for (var package_name in packages) {
        var components = packages[package_name];
        var items = [];
        if (!(package_name in screens)) {
            screens[package_name] = {};
        }
        components.forEach((component_name) => {
            var item = {
                package_name: package_name,
                component_name: component_name
            };
            items.push(item);
        });
        items = screens_load(items, "import");
        collection[package_name] = items;
    }
    var promises = [];
    for (package_name in packages) {
        for(var item of collection[package_name]) {
            if(item.promises) {
                promises.push(...item.promises);
            }
        }
    }
    if(promises) {
        await Promise.all(promises);
    }
    for (package_name in packages) {
        collection[package_name] = screens_load(collection[package_name], "setup");
    }
    for (package_name in packages) {
        console.log(screens.platform + ": initializing package: " + package_name);
        await screens_init(collection[package_name]);
    }
}

var screens = new Proxy(() => {
    return {};
}, {
        apply: function (object, thisArg, argumentsList) {
            return argumentsList[0].split('.').reduce((parent, name) => {
                return parent[name];
            }, screens);
        }
    });

Object.assign(screens, {
    components: [],
    imports: [],
    id: "package",
    platform: screens_platform(),
    include: screens_include,
    import: screens_import
});

var platform = screens_platform();
if (platform === "server" || platform === "service") {
    global.screens = screens;
    global.__json__ = {};
    global.__html__ = {};
}

if(platform === "browser" || platform === "client") {
    var module = screens;
}
<|endoftext|>


<commit_before><commit_msg>Add tetrinimo model to tetrinimo.js<commit_after>var Tetrinimo = function(args) {
  this.element = args.element;
  var shape = args.shape;

  this.blocks = shape.map(function(block) {
    return {x: new Number(block.x), y: new Number(block.y)};
  });

  this.row = 0;
  this.col = 5;

  for(var block in this.blocks) {
    this.blocks[block].y += this.row;
    this.blocks[block].x += this.col;
  }
}
Tetrinimo.prototype.raise = function() {
  this.row--;
  for(var block in this.blocks) {
    this.blocks[block].y--;
  }
};
Tetrinimo.prototype.drop = function() {
  this.row++;
  for(var block in this.blocks) {
    this.blocks[block].y++;
  }
};
var directToInt = {
  'left': -1,
  'right': 1
};
Tetrinimo.prototype.slide = function(direction) {
  this.blocks.forEach(function(block) {
    block.x += directToInt[direction];
  });
};
<|endoftext|>


<commit_before>(function main() {
    'use strict';

    var Simulation = function() {
        var canvas,
        context,
        self = this,
        width,
        height,
        focalLength = 300,
        objectDistance = 800,
        objectHeight = 50,
        imageDistance = 1 / (1 / focalLength - 1 / objectDistance),
        imageHeight = -objectHeight * imageDistance / objectDistance;

        var draw = function() {
            var centerX = Math.floor(width / 2),
            centerY = Math.floor(height / 2),
            angle = Math.asin(100 / focalLength),
            arrowHeight,
            arrowWidth = 6;

            // Draw dashed center line
            context.setLineDash([20, 20]);
            context.lineDashOffset = 10;
            // Draw in two segments so dashes are centered
            context.beginPath();
            context.moveTo(centerX, centerY);
            context.lineTo(0, centerY);
            context.stroke();
            context.beginPath();
            context.moveTo(centerX, centerY);
            context.lineTo(width, centerY);
            context.stroke();
            context.setLineDash([1, 0]);

            // Draw lens
            context.lineWidth = 2;
            context.strokeStyle = '#666';
            context.beginPath();
            context.arc(centerX - 2 * focalLength + focalLength * 2 * (1 - Math.cos(angle)), centerY, focalLength * 2, -angle, angle, false);
            context.stroke();
            context.beginPath();
            context.arc(centerX + 2 * focalLength - focalLength * 2 * (1 - Math.cos(angle)), centerY, focalLength * 2, Math.PI - angle, Math.PI + angle, false);
            context.stroke();
            context.strokeStyle = '#000';

            // Draw focal points
            context.beginPath();
            context.arc(centerX, centerY, 5, 0, 2 * Math.PI, false);
            context.fill();
            context.beginPath();
            context.arc(centerX - focalLength, centerY, 5, 0, 2 * Math.PI, false);
            context.fill();
            context.beginPath();
            context.arc(centerX + focalLength, centerY, 5, 0, 2 * Math.PI, false);
            context.fill();

            // Draw object
            arrowHeight = 8 * Math.sign(objectHeight);
            context.lineCap = 'butt';
            context.lineWidth = 4;

            // Object body
            context.beginPath();
            context.moveTo(centerX - objectDistance, centerY);
            context.lineTo(centerX - objectDistance, centerY - objectHeight + arrowHeight);
            context.stroke();

            // Arrowhead
            context.lineWidth = 1;
            context.beginPath();
            context.moveTo(centerX - objectDistance + arrowWidth, centerY - objectHeight + arrowHeight);
            context.lineTo(centerX - objectDistance - arrowWidth, centerY - objectHeight + arrowHeight);
            context.lineTo(centerX - objectDistance, centerY - objectHeight);
            context.lineTo(centerX - objectDistance + arrowWidth, centerY - objectHeight + arrowHeight);
            context.fill();

            // Draw image
            arrowHeight = 8 * Math.sign(imageHeight);
            context.lineWidth = 4;

            // Image body
            context.beginPath();
            context.moveTo(centerX + imageDistance, centerY);
            context.lineTo(centerX + imageDistance, centerY - imageHeight + arrowHeight);
            context.stroke();

            // Arrowhead
            context.lineWidth = 1;
            context.beginPath();
            context.moveTo(centerX + imageDistance + arrowWidth, centerY - imageHeight + arrowHeight);
            context.lineTo(centerX + imageDistance - arrowWidth, centerY - imageHeight + arrowHeight);
            context.lineTo(centerX + imageDistance, centerY - imageHeight);
            context.lineTo(centerX + imageDistance + arrowWidth, centerY - imageHeight + arrowHeight);
            context.fill();
        };

        this.resize = function() {
            width = window.innerWidth;
            height = window.innerHeight;
            canvas.width = width;
            canvas.height = height;
            draw();
        };

        this.init = function() {
            canvas = document.getElementById('main-canvas');
            if (canvas.getContext) {
                context = canvas.getContext('2d');
                self.resize();
            }
        };
    };

    var sim = new Simulation();

    document.addEventListener('DOMContentLoaded', sim.init, false);
    window.addEventListener('resize', sim.resize, false);
}());
<commit_msg>Refactor: draw arrows in separate function<commit_after>(function main() {
    'use strict';

    var Simulation = function() {
        var canvas,
        context,
        self = this,
        width,
        height,
        focalLength = 300,
        objectDistance = 800,
        objectHeight = 50,
        imageDistance = 1 / (1 / focalLength - 1 / objectDistance),
        imageHeight = -objectHeight * imageDistance / objectDistance;

        // Draw a vertical arrow. Positive length is downward.
        var drawArrow = function(startX, startY, length) {
            var arrowWidth = 6,
            arrowLength = length > 0 ? 8 : -8;

            context.lineCap = 'butt';
            context.lineWidth = 4;

            // Arrow body
            context.beginPath();
            context.moveTo(startX, startY);
            context.lineTo(startX, startY + length - arrowLength);
            context.stroke();

            // Arrowhead
            context.lineWidth = 1;
            context.beginPath();
            context.moveTo(startX + arrowWidth, startY + length - arrowLength);
            context.lineTo(startX - arrowWidth, startY + length - arrowLength);
            context.lineTo(startX, startY + length);
            context.lineTo(startX + arrowWidth, startY + length - arrowLength);
            context.fill();
        };

        var draw = function() {
            var centerX = Math.floor(width / 2),
            centerY = Math.floor(height / 2),
            angle = Math.asin(100 / focalLength);

            // Draw dashed center line
            context.setLineDash([20, 20]);
            context.lineDashOffset = 10;
            // Draw in two segments so dashes are centered
            context.beginPath();
            context.moveTo(centerX, centerY);
            context.lineTo(0, centerY);
            context.stroke();
            context.beginPath();
            context.moveTo(centerX, centerY);
            context.lineTo(width, centerY);
            context.stroke();
            context.setLineDash([1, 0]);

            // Draw lens
            context.lineWidth = 2;
            context.lineJoin = 'round';
            context.strokeStyle = '#666';
            context.beginPath();
            context.arc(centerX - 2 * focalLength * Math.cos(angle), centerY, focalLength * 2, -angle, angle, false);
            context.arc(centerX + 2 * focalLength * Math.cos(angle), centerY, focalLength * 2, Math.PI - angle, Math.PI + angle, false);
            context.closePath();
            context.stroke();
            context.strokeStyle = '#000';

            // Draw focal points
            context.beginPath();
            context.arc(centerX, centerY, 5, 0, 2 * Math.PI, false);
            context.fill();
            context.beginPath();
            context.arc(centerX - focalLength, centerY, 5, 0, 2 * Math.PI, false);
            context.fill();
            context.beginPath();
            context.arc(centerX + focalLength, centerY, 5, 0, 2 * Math.PI, false);
            context.fill();

            // Draw object and image.
            drawArrow(centerX - objectDistance, centerY, -objectHeight);
            drawArrow(centerX + imageDistance, centerY, -imageHeight);
        };

        this.resize = function() {
            width = window.innerWidth;
            height = window.innerHeight;
            canvas.width = width;
            canvas.height = height;
            draw();
        };

        this.init = function() {
            canvas = document.getElementById('main-canvas');
            if (canvas.getContext) {
                context = canvas.getContext('2d');
                self.resize();
            }
        };
    };

    var sim = new Simulation();

    document.addEventListener('DOMContentLoaded', sim.init, false);
    window.addEventListener('resize', sim.resize, false);
}());
<|endoftext|>