<commit_before>(function ($) {

module("View - Map");

var GeoJSONFixture = {
  getDataset: function() {
    var fields = [
        {id: 'id'},
        {id: 'x'},
        {id: 'y'},
        {id: 'z'},
        {id: 'geom'}
      ];
    var records = [
      {id: 0, x: 1, y: 2, z: 3, geom: '{"type":"Point","coordinates":[13.40,52.35]}'},
      {id: 1, x: 2, y: 4, z: 6, geom: {type:"Point",coordinates:[13.40,52.35]}},
      {id: 2, x: 3, y: 6, z: 9, geom: {type:"LineString",coordinates:[[100.0, 0.0],[101.0, 1.0]]}}
    ];
    var dataset = new recline.Model.Dataset({
      records: records,
      fields: fields
    });
    return dataset;
  }
};

test('basics', function () {
  var dataset = Fixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  assertPresent('.editor-field-type', view.elSidebar);

  // Check that the Leaflet map was set up
  assertPresent('.leaflet-container',view.el);

  ok(view.map instanceof L.Map);
  ok(view.features instanceof L.GeoJSON);

  view.remove();
});

test('_setupGeometryField', function () {
  var dataset = Fixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  view.render();
  var exp = {
    geomField: null,
    lonField: 'lon',
    latField: 'lat',
    autoZoom: true,
    cluster: false
  };
  deepEqual(view.state.toJSON(), exp);
  deepEqual(view.menu.state.toJSON(), exp);
});

test('Lat/Lon geom fields', function () {
  var dataset = Fixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  // Not really needed but fire query to test that resetting works!
  dataset.query();

  // Check that all markers were created
  equal(_getFeaturesCount(view.features),6);

  // Delete a record
  view.model.records.remove(view.model.records.get('1'));
  equal(_getFeaturesCount(view.features),5);

  // Add a new one
  view.model.records.add({id: 7, x: 7, y: 14, z: 21, country: 'KX', label: 'seventh', lat:13.23, lon:23.56}),
  equal(_getFeaturesCount(view.features),6);

  view.remove();
});

test('GeoJSON geom field', function () {
  var dataset = GeoJSONFixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  // Check that all features were created
  equal(_getFeaturesCount(view.features),3);

  // Delete a record
  view.model.records.remove(view.model.records.get('2'));
  equal(_getFeaturesCount(view.features),2);

  // Add it back
  view.model.records.add({id: 2, x: 3, y: 6, z: 9, geom: {type:"LineString",coordinates:[[100.0, 0.0],[101.0, 1.0]]}}),
  equal(_getFeaturesCount(view.features),3);

  view.remove();
});

test('_getGeometryFromRecord non-GeoJSON', function () {
  var test = [
    [{ lon: 47, lat: 53}, [47,53]],
    ["53.3,47.32", [47.32, 53.3]],
    ["53.3, 47.32", [47.32, 53.3]],
    ["(53.3,47.32)", [47.32, 53.3]],
    [[53.3,47.32], [53.3, 47.32]],
    ["53.3 N, 113.5 W", [53.3, -113.5]],
    ["53° 18' N, 113° 30' W", [53.3, -113.5]]
    ["22°54′30″S 43°11′47″W", [-22.983, -43.3139]]
  ];
  var view = new recline.View.Map({
    model: new recline.Model.Dataset({
      records: [{a: 1}]
    }),
    state: {
      geomField: 'location'
    }
  });
  _.each(test, function(item) {
    var record = new recline.Model.Record({location: item[0]});
    var out = view._getGeometryFromRecord(record);
    deepEqual(out.coordinates, item[1]);
  });
});

test('many markers and clustering', function () {
  var data = [];
  for (var i = 0; i<1000; i++) {
    data.push({ id: i, lon: 13+3*i, lat: 52+i/10});
  }
  var fields = [
    {id: 'id'},
    {id: 'lat'},
    {id: 'lon'}
  ];

  var dataset = new recline.Model.Dataset({records: data, fields: fields});
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  dataset.query();

  // this whole test looks a bit odd now
  // we used to turn on clustering automatically at a certain level but we do not any more
  equal(view.state.get('cluster'), false);

  view.state.set({cluster: true});
  equal(view.state.get('cluster'), true);

  view.remove();
});

test('Popup', function () {
  var dataset = GeoJSONFixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  var marker = view.$el.find('.leaflet-marker-icon').first();

  assertPresent(marker);

  _.values(view.features._layers)[0].fire('click');

  var popup = view.$el.find('.leaflet-popup-content');

  assertPresent(popup);

  var text = popup.text();
  ok((text.indexOf('geom') === -1))
  _.each(view.model.fields.toJSON(),function(field){
    if (field.id != 'geom'){
      ok((text.indexOf(field.id) !== -1))
    }
  });

  view.remove();
});

test('Popup - Custom', function (assert) {
  var dataset = GeoJSONFixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.infobox = function(record) {
    var html = Mustache.render('
{{x}}
y: {{y}}', record.toJSON());
    return html;
  };
  view.render();

  var marker = view.$el.find('.leaflet-marker-icon').first();
  _.values(view.features._layers)[0].fire('click');
  var popup = view.$el.find('.leaflet-popup-content');

  assertPresent(popup);

  var text = popup.html();
  assert.htmlEqual(text, '
1
y: 2');

  view.remove();
});

test('geoJsonLayerOptions', function () {
  var dataset = GeoJSONFixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.geoJsonLayerOptions.point
  view.geoJsonLayerOptions.pointToLayer = function(feature, latlng) {
    var marker = new L.CircleMarker(latlng, { radius: 8 } );
    marker.bindPopup(feature.properties.popupContent);
    return marker;
  }
  view.render();

  // TODO: test it somehow?
  expect(0);

  view.remove();
});

test('MapMenu', function () {
  var dataset = Fixture.getDataset();
  var controls = new recline.View.MapMenu({
    model: dataset,
    state: {}
  });
  assertPresent('.editor-field-type', controls.el);
});

var _getFeaturesCount = function(features){
  var cnt = 0;
  _.each(features._layers, function(layer) {
    cnt++;
  });
  return cnt;
}

})(this.jQuery);
<commit_msg>Corrected syntax of new mapping test cases<commit_after>(function ($) {

module("View - Map");

var GeoJSONFixture = {
  getDataset: function() {
    var fields = [
        {id: 'id'},
        {id: 'x'},
        {id: 'y'},
        {id: 'z'},
        {id: 'geom'}
      ];
    var records = [
      {id: 0, x: 1, y: 2, z: 3, geom: '{"type":"Point","coordinates":[13.40,52.35]}'},
      {id: 1, x: 2, y: 4, z: 6, geom: {type:"Point",coordinates:[13.40,52.35]}},
      {id: 2, x: 3, y: 6, z: 9, geom: {type:"LineString",coordinates:[[100.0, 0.0],[101.0, 1.0]]}}
    ];
    var dataset = new recline.Model.Dataset({
      records: records,
      fields: fields
    });
    return dataset;
  }
};

test('basics', function () {
  var dataset = Fixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  assertPresent('.editor-field-type', view.elSidebar);

  // Check that the Leaflet map was set up
  assertPresent('.leaflet-container',view.el);

  ok(view.map instanceof L.Map);
  ok(view.features instanceof L.GeoJSON);

  view.remove();
});

test('_setupGeometryField', function () {
  var dataset = Fixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  view.render();
  var exp = {
    geomField: null,
    lonField: 'lon',
    latField: 'lat',
    autoZoom: true,
    cluster: false
  };
  deepEqual(view.state.toJSON(), exp);
  deepEqual(view.menu.state.toJSON(), exp);
});

test('Lat/Lon geom fields', function () {
  var dataset = Fixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  // Not really needed but fire query to test that resetting works!
  dataset.query();

  // Check that all markers were created
  equal(_getFeaturesCount(view.features),6);

  // Delete a record
  view.model.records.remove(view.model.records.get('1'));
  equal(_getFeaturesCount(view.features),5);

  // Add a new one
  view.model.records.add({id: 7, x: 7, y: 14, z: 21, country: 'KX', label: 'seventh', lat:13.23, lon:23.56}),
  equal(_getFeaturesCount(view.features),6);

  view.remove();
});

test('GeoJSON geom field', function () {
  var dataset = GeoJSONFixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  // Check that all features were created
  equal(_getFeaturesCount(view.features),3);

  // Delete a record
  view.model.records.remove(view.model.records.get('2'));
  equal(_getFeaturesCount(view.features),2);

  // Add it back
  view.model.records.add({id: 2, x: 3, y: 6, z: 9, geom: {type:"LineString",coordinates:[[100.0, 0.0],[101.0, 1.0]]}}),
  equal(_getFeaturesCount(view.features),3);

  view.remove();
});

test('_getGeometryFromRecord non-GeoJSON', function () {
  var test = [
    [{ lon: 47, lat: 53}, [47,53]],
    [{ lon: -47, lat: 53}, [-47,53]],
    ["53.3,47.32", [47.32, 53.3]],
    ["53.3, 47.32", [47.32, 53.3]],
    ["(53.3,47.32)", [47.32, 53.3]],
    [[53.3,47.32], [53.3, 47.32]],
    ["53.3 N, 113.5 W", [-113.5, 53.3]],
    ["53° 18' N, 113° 30' W", [-113.5, 53.3 ]],
    ["22°45′90″S, 43°15′45″W", [-43.2625, -22.775]]
  ];
  var view = new recline.View.Map({
    model: new recline.Model.Dataset({
      records: [{a: 1}]
    }),
    state: {
      geomField: 'location'
    }
  });
  _.each(test, function(item) {
    var record = new recline.Model.Record({location: item[0]});
    var out = view._getGeometryFromRecord(record);
    deepEqual(out.coordinates, item[1]);
  });
});

test('many markers and clustering', function () {
  var data = [];
  for (var i = 0; i<1000; i++) {
    data.push({ id: i, lon: 13+3*i, lat: 52+i/10});
  }
  var fields = [
    {id: 'id'},
    {id: 'lat'},
    {id: 'lon'}
  ];

  var dataset = new recline.Model.Dataset({records: data, fields: fields});
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  dataset.query();

  // this whole test looks a bit odd now
  // we used to turn on clustering automatically at a certain level but we do not any more
  equal(view.state.get('cluster'), false);

  view.state.set({cluster: true});
  equal(view.state.get('cluster'), true);

  view.remove();
});

test('Popup', function () {
  var dataset = GeoJSONFixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.render();

  var marker = view.$el.find('.leaflet-marker-icon').first();

  assertPresent(marker);

  _.values(view.features._layers)[0].fire('click');

  var popup = view.$el.find('.leaflet-popup-content');

  assertPresent(popup);

  var text = popup.text();
  ok((text.indexOf('geom') === -1))
  _.each(view.model.fields.toJSON(),function(field){
    if (field.id != 'geom'){
      ok((text.indexOf(field.id) !== -1))
    }
  });

  view.remove();
});

test('Popup - Custom', function (assert) {
  var dataset = GeoJSONFixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.infobox = function(record) {
    var html = Mustache.render('
{{x}}
y: {{y}}', record.toJSON());
    return html;
  };
  view.render();

  var marker = view.$el.find('.leaflet-marker-icon').first();
  _.values(view.features._layers)[0].fire('click');
  var popup = view.$el.find('.leaflet-popup-content');

  assertPresent(popup);

  var text = popup.html();
  assert.htmlEqual(text, '
1
y: 2');

  view.remove();
});

test('geoJsonLayerOptions', function () {
  var dataset = GeoJSONFixture.getDataset();
  var view = new recline.View.Map({
    model: dataset
  });
  $('.fixtures').append(view.el);
  view.geoJsonLayerOptions.point
  view.geoJsonLayerOptions.pointToLayer = function(feature, latlng) {
    var marker = new L.CircleMarker(latlng, { radius: 8 } );
    marker.bindPopup(feature.properties.popupContent);
    return marker;
  }
  view.render();

  // TODO: test it somehow?
  expect(0);

  view.remove();
});

test('MapMenu', function () {
  var dataset = Fixture.getDataset();
  var controls = new recline.View.MapMenu({
    model: dataset,
    state: {}
  });
  assertPresent('.editor-field-type', controls.el);
});

var _getFeaturesCount = function(features){
  var cnt = 0;
  _.each(features._layers, function(layer) {
    cnt++;
  });
  return cnt;
}

})(this.jQuery);
<|endoftext|>


<commit_before>'use strict';

const test = require('tape');
const warnings = require('../lib/warnings.js');
const stdout = require('test-console').stdout;

const expected = ['========= WARNING WHITE-LISTED LICENSES ==========\n',
    'name: test2 , version: 1.2 , licenses: Bogus\n',
    '========= WARNING WHITE-LISTED LICENSES ==========\n'];

test('Should report the correct warning.', (t) => {
  t.plan(1);
  const project = {
    name: 'testProject',
    licenses: {
      license: [
       {name: 'test1', version: '1.0', license: 'MIT', file: '...'},
       {name: 'test2', version: '1.2', license: 'Bogus', file: '...'},
       {name: 'test2', version: '1.2', license: 'ASL 1.1', file: '...'}
      ]
    }
  };

  const whitelist = [{'name': 'ASL 1.1'}];
  const log = stdout.inspectSync(() => warnings.print(require('../lib/whitelist.js')(whitelist).check(project),
                 'WHITE-LISTED'));
  t.deepEqual(log, expected);
  t.end();
});
<commit_msg>test: fix lint issue in warnings-test<commit_after>'use strict';

const test = require('tape');
const warnings = require('../lib/warnings.js');
const stdout = require('test-console').stdout;

const expected = ['========= WARNING WHITE-LISTED LICENSES ==========\n',
  'name: test2 , version: 1.2 , licenses: Bogus\n',
  '========= WARNING WHITE-LISTED LICENSES ==========\n'];

test('Should report the correct warning.', (t) => {
  t.plan(1);
  const project = {
    name: 'testProject',
    licenses: {
      license: [
       {name: 'test1', version: '1.0', license: 'MIT', file: '...'},
       {name: 'test2', version: '1.2', license: 'Bogus', file: '...'},
       {name: 'test2', version: '1.2', license: 'ASL 1.1', file: '...'}
      ]
    }
  };

  const whitelist = [{'name': 'ASL 1.1'}];
  const log = stdout.inspectSync(() => warnings.print(require('../lib/whitelist.js')(whitelist).check(project),
                 'WHITE-LISTED'));
  t.deepEqual(log, expected);
  t.end();
});
<|endoftext|>


<commit_before>const fs = require('fs-extra')
const {Fixture} = require('./helper')
const {EventMatcher} = require('./matcher')

describe('watching a directory', function () {
  let fixture

  beforeEach(async function () {
    fixture = new Fixture()
    await fixture.before()
    await fixture.log()
  })

  afterEach(async function () {
    await fixture.after(this.currentTest)
  })

  it('begins receiving events within that directory', async function () {
    const matcher = new EventMatcher(fixture)
    await matcher.watch([], {})

    const filePath = fixture.watchPath('file.txt')
    await fs.writeFile(filePath, 'indeed')

    await until('an event arrives', matcher.allEvents({path: filePath}))
  })

  it('can watch multiple directories at once and dispatch events appropriately', async function () {
    const watchDirA = fixture.watchPath('dir_a')
    const watchDirB = fixture.watchPath('dir_b')
    await Promise.all(
      [watchDirA, watchDirB].map(subdir => fs.mkdir(subdir))
    )

    const matcherA = new EventMatcher(fixture)
    await matcherA.watch(['dir_a'], {})

    const matcherB = new EventMatcher(fixture)
    await matcherB.watch(['dir_b'], {})

    const fileA = fixture.watchPath('dir_a', 'a.txt')
    await fs.writeFile(fileA, 'file a')
    await until('watcher A picks up its event', matcherA.allEvents({path: fileA}))

    const fileB = fixture.watchPath('dir_b', 'b.txt')
    await fs.writeFile(fileB, 'file b')
    await until('watcher B picks up its event', matcherB.allEvents({path: fileB}))

    // Assert that the streams weren't crossed
    assert.isTrue(matcherA.noEvents({path: fileB}))
    assert.isTrue(matcherB.noEvents({path: fileA}))
  })

  it('watches subdirectories recursively', async function () {
    const subdir0 = fixture.watchPath('subdir0')
    const subdir1 = fixture.watchPath('subdir1')
    await Promise.all(
      [subdir0, subdir1].map(subdir => fs.mkdir(subdir))
    )

    const matcher = new EventMatcher(fixture)
    await matcher.watch([], {})

    const rootFile = fixture.watchPath('root.txt')
    await fs.writeFile(rootFile, 'root')

    const file0 = fixture.watchPath('subdir0', '0.txt')
    await fs.writeFile(file0, 'file 0')

    const file1 = fixture.watchPath('subdir1', '1.txt')
    await fs.writeFile(file1, 'file 1')

    await until('all three events arrive', matcher.allEvents(
      {path: rootFile},
      {path: file0},
      {path: file1}
    ))
  })

  it('watches newly created subdirectories', async function () {
    const matcher = new EventMatcher(fixture)
    await matcher.watch([], {})

    const subdir = fixture.watchPath('subdir')
    const file0 = fixture.watchPath('subdir', 'file-0.txt')

    await fs.mkdir(subdir)
    await until('the subdirectory creation event arrives', matcher.allEvents({path: subdir}))

    await fs.writeFile(file0, 'file 0')
    await until('the modification event arrives', matcher.allEvents({path: file0}))
  })

  it('watches directories renamed within a watch root', async function () {
    const externalDir = fixture.fixturePath('outside')
    const externalSubdir = fixture.fixturePath('outside', 'directory')
    const externalFile = fixture.fixturePath('outside', 'directory', 'file.txt')

    const internalDir = fixture.watchPath('inside')
    const internalFile = fixture.watchPath('inside', 'directory', 'file.txt')

    await fs.mkdirs(externalSubdir)
    await fs.writeFile(externalFile, 'contents')

    const matcher = new EventMatcher(fixture)
    await matcher.watch([], {})

    await fs.rename(externalDir, internalDir)
    await until('creation event arrives', matcher.allEvents({path: internalDir}))

    await fs.writeFile(internalFile, 'changed')

    await until('modification event arrives', matcher.allEvents({path: internalFile}))
  })

  })
})
<commit_msg>Unit test to catch the re-used watch descriptor issue<commit_after>const fs = require('fs-extra')
const {Fixture} = require('./helper')
const {EventMatcher} = require('./matcher')

describe('watching a directory', function () {
  let fixture

  beforeEach(async function () {
    fixture = new Fixture()
    await fixture.before()
    await fixture.log()
  })

  afterEach(async function () {
    await fixture.after(this.currentTest)
  })

  it('begins receiving events within that directory', async function () {
    const matcher = new EventMatcher(fixture)
    await matcher.watch([], {})

    const filePath = fixture.watchPath('file.txt')
    await fs.writeFile(filePath, 'indeed')

    await until('an event arrives', matcher.allEvents({path: filePath}))
  })

  it('can watch multiple directories at once and dispatch events appropriately', async function () {
    const watchDirA = fixture.watchPath('dir_a')
    const watchDirB = fixture.watchPath('dir_b')
    await Promise.all(
      [watchDirA, watchDirB].map(subdir => fs.mkdir(subdir))
    )

    const matcherA = new EventMatcher(fixture)
    await matcherA.watch(['dir_a'], {})

    const matcherB = new EventMatcher(fixture)
    await matcherB.watch(['dir_b'], {})

    const fileA = fixture.watchPath('dir_a', 'a.txt')
    await fs.writeFile(fileA, 'file a')
    await until('watcher A picks up its event', matcherA.allEvents({path: fileA}))

    const fileB = fixture.watchPath('dir_b', 'b.txt')
    await fs.writeFile(fileB, 'file b')
    await until('watcher B picks up its event', matcherB.allEvents({path: fileB}))

    // Assert that the streams weren't crossed
    assert.isTrue(matcherA.noEvents({path: fileB}))
    assert.isTrue(matcherB.noEvents({path: fileA}))
  })

  it('watches subdirectories recursively', async function () {
    const subdir0 = fixture.watchPath('subdir0')
    const subdir1 = fixture.watchPath('subdir1')
    await Promise.all(
      [subdir0, subdir1].map(subdir => fs.mkdir(subdir))
    )

    const matcher = new EventMatcher(fixture)
    await matcher.watch([], {})

    const rootFile = fixture.watchPath('root.txt')
    await fs.writeFile(rootFile, 'root')

    const file0 = fixture.watchPath('subdir0', '0.txt')
    await fs.writeFile(file0, 'file 0')

    const file1 = fixture.watchPath('subdir1', '1.txt')
    await fs.writeFile(file1, 'file 1')

    await until('all three events arrive', matcher.allEvents(
      {path: rootFile},
      {path: file0},
      {path: file1}
    ))
  })

  it('watches newly created subdirectories', async function () {
    const matcher = new EventMatcher(fixture)
    await matcher.watch([], {})

    const subdir = fixture.watchPath('subdir')
    const file0 = fixture.watchPath('subdir', 'file-0.txt')

    await fs.mkdir(subdir)
    await until('the subdirectory creation event arrives', matcher.allEvents({path: subdir}))

    await fs.writeFile(file0, 'file 0')
    await until('the modification event arrives', matcher.allEvents({path: file0}))
  })

  it('watches directories renamed within a watch root', async function () {
    const externalDir = fixture.fixturePath('outside')
    const externalSubdir = fixture.fixturePath('outside', 'directory')
    const externalFile = fixture.fixturePath('outside', 'directory', 'file.txt')

    const internalDir = fixture.watchPath('inside')
    const internalFile = fixture.watchPath('inside', 'directory', 'file.txt')

    await fs.mkdirs(externalSubdir)
    await fs.writeFile(externalFile, 'contents')

    const matcher = new EventMatcher(fixture)
    await matcher.watch([], {})

    await fs.rename(externalDir, internalDir)
    await until('creation event arrives', matcher.allEvents({path: internalDir}))

    await fs.writeFile(internalFile, 'changed')

    await until('modification event arrives', matcher.allEvents({path: internalFile}))
  })

  it('can watch a directory nested within an already-watched directory', async function () {
    const rootFile = fixture.watchPath('root-file.txt')
    const subDir = fixture.watchPath('subdir')
    const subFile = fixture.watchPath('subdir', 'sub-file.txt')

    await fs.mkdir(subDir)
    await fs.writeFile(rootFile, 'root\n')
    await fs.writeFile(subFile, 'sub\n')

    const parent = new EventMatcher(fixture)
    await parent.watch([], {})

    const child = new EventMatcher(fixture)
    const nw = await child.watch(['subdir'], {})

    await fs.appendFile(rootFile, 'change 0\n')
    await fs.appendFile(subFile, 'change 0\n')

    await until('parent events arrive', parent.allEvents(
      {path: rootFile},
      {path: subFile}
    ))
    await until('child events arrive', parent.allEvents(
      {path: subFile}
    ))

    await nw.stop()
    parent.reset()

    await fs.appendFile(rootFile, 'change 1\n')
    await fs.appendFile(subFile, 'change 1\n')

    await until('parent events arrive', parent.allEvents(
      {path: rootFile},
      {path: subFile}
    ))
  })
})
<|endoftext|>


<commit_before>var parser = require("./parser.jison");

var buildExpression = function(expression) {
    return _.map(expression, function(ex, i) {
        var prev = i > 0 ? expression[i-1] : null;

        return buildGroup(ex, prev);
    });
};

var makeSpan = function(className, children) {
    var span = document.createElement("span");
    span.className = className;

    if (_.isArray(children)) {
        _.each(children, function(v) {
            span.appendChild(v);
        });
    } else if (children) {
        span.appendChild(children);
    }

    return span;
};

var buildGroup = function(group, prev) {
    if (group.type === "ord") {
        return makeSpan("mord", mathit(group.value));
    } else if (group.type === "bin") {
        var className = "mbin";
        if (prev == null || prev.type === "bin" || prev.type === "open") {
            group.type = "ord";
            className = "mord";
        }
        return makeSpan(className, textit(group.value));
    } else if (group.type === "sup") {
        var sup = makeSpan("msup", buildExpression(group.value.sup));
        return makeSpan("mord", buildExpression(group.value.base).concat(sup));
    } else if (group.type === "sub") {
        var sub = makeSpan("msub", buildExpression(group.value.sub, sub));
        return makeSpan("mord", buildExpression(group.value.base, elem).concat(sub));
    } else if (group.type === "supsub") {
        var sup = makeSpan("msup", buildExpression(group.value.sup, sup));
        var sub = makeSpan("msub", buildExpression(group.value.sub, sub));

        var supsub = makeSpan("msupsub", [sup, sub]);

        return makeSpan("mord", buildExpression(group.value.base, elem).concat(supsub));
    } else if (group.type === "open") {
        return makeSpan("mopen", textit(group.value));
    } else if (group.type === "close") {
        return makeSpan("mclose", textit(group.value));
    } else if (group.type === "frac") {
        var numer = makeSpan("mfracnum", buildExpression(group.value.numer, numer));
        var mid = makeSpan("mfracmid", makeSpan());
        var denom = makeSpan("mfracden", buildExpression(group.value.denom, denom));

        return makeSpan("mord mfrac", [numer, mid, denom]);
    } else {
        console.log("Unknown type:", group.type);
    }
};

var charLookup = {
    "*": "\u2217",
    "-": "\u2212",
    "cdot": "\u22C5",
    "lvert": "|",
    "rvert": "|",
    "pm": "\u00b1",
    "div": "\u00f7"
};

var textit = function(value) {
    if (value in charLookup) {
        value = charLookup[value];
    }
    return document.createTextNode(value);
};

var mathit = function(value) {
    var text = textit(value);

    if (/[a-zA-Z]/.test(value)) {
        return makeSpan("mathit", text);
    } else {
        return text;
    }
};

var clearNode = function(node) {
    if ("textContent" in node) {
        node.textContent = "";
    } else {
        node.innerText = "";
    }
};

var process = function(toParse, baseElem) {
    var tree = parser.parse(toParse);
    clearNode(baseElem);
    _.each(buildExpression(tree), function(elem) {
        baseElem.appendChild(elem);
    });
};

module.exports = {
    process: process
};
<commit_msg>Don't make undefined classNames<commit_after>var parser = require("./parser.jison");

var buildExpression = function(expression) {
    return _.map(expression, function(ex, i) {
        var prev = i > 0 ? expression[i-1] : null;

        return buildGroup(ex, prev);
    });
};

var makeSpan = function(className, children) {
    var span = document.createElement("span");
    span.className = className || "";

    if (_.isArray(children)) {
        _.each(children, function(v) {
            span.appendChild(v);
        });
    } else if (children) {
        span.appendChild(children);
    }

    return span;
};

var buildGroup = function(group, prev) {
    if (group.type === "ord") {
        return makeSpan("mord", mathit(group.value));
    } else if (group.type === "bin") {
        var className = "mbin";
        if (prev == null || prev.type === "bin" || prev.type === "open") {
            group.type = "ord";
            className = "mord";
        }
        return makeSpan(className, textit(group.value));
    } else if (group.type === "sup") {
        var sup = makeSpan("msup", buildExpression(group.value.sup));
        return makeSpan("mord", buildExpression(group.value.base).concat(sup));
    } else if (group.type === "sub") {
        var sub = makeSpan("msub", buildExpression(group.value.sub, sub));
        return makeSpan("mord", buildExpression(group.value.base, elem).concat(sub));
    } else if (group.type === "supsub") {
        var sup = makeSpan("msup", buildExpression(group.value.sup, sup));
        var sub = makeSpan("msub", buildExpression(group.value.sub, sub));

        var supsub = makeSpan("msupsub", [sup, sub]);

        return makeSpan("mord", buildExpression(group.value.base, elem).concat(supsub));
    } else if (group.type === "open") {
        return makeSpan("mopen", textit(group.value));
    } else if (group.type === "close") {
        return makeSpan("mclose", textit(group.value));
    } else if (group.type === "frac") {
        var numer = makeSpan("mfracnum", buildExpression(group.value.numer, numer));
        var mid = makeSpan("mfracmid", makeSpan());
        var denom = makeSpan("mfracden", buildExpression(group.value.denom, denom));

        return makeSpan("mord mfrac", [numer, mid, denom]);
    } else {
        console.log("Unknown type:", group.type);
    }
};

var charLookup = {
    "*": "\u2217",
    "-": "\u2212",
    "cdot": "\u22C5",
    "lvert": "|",
    "rvert": "|",
    "pm": "\u00b1",
    "div": "\u00f7"
};

var textit = function(value) {
    if (value in charLookup) {
        value = charLookup[value];
    }
    return document.createTextNode(value);
};

var mathit = function(value) {
    var text = textit(value);

    if (/[a-zA-Z]/.test(value)) {
        return makeSpan("mathit", text);
    } else {
        return text;
    }
};

var clearNode = function(node) {
    if ("textContent" in node) {
        node.textContent = "";
    } else {
        node.innerText = "";
    }
};

var process = function(toParse, baseElem) {
    var tree = parser.parse(toParse);
    clearNode(baseElem);
    _.each(buildExpression(tree), function(elem) {
        baseElem.appendChild(elem);
    });
};

module.exports = {
    process: process
};
<|endoftext|>


<commit_before>/**
 * @author aleeper / http://adamleeper.com/
 * @author mrdoob / http://mrdoob.com/
 * @author gero3 / https://github.com/gero3
 *
 * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
 *
 * Supports both binary and ASCII encoded files, with automatic detection of type.
 *
 * Limitations:
 * 	Binary decoding ignores header. There doesn't seem to be much of a use for it.
 * 	There is perhaps some question as to how valid it is to always assume little-endian-ness.
 * 	ASCII decoding assumes file is UTF-8. Seems to work for the examples...
 *
 * Usage:
 * 	var loader = new THREE.STLLoader();
 * 	loader.addEventListener( 'load', function ( event ) {
 *
 * 		var geometry = event.content;
 * 		scene.add( new THREE.Mesh( geometry ) );
 *
 * 	} );
 * 	loader.load( './models/stl/slotted_disk.stl' );
 */

THREE.STLLoader = function () {};

THREE.STLLoader.prototype = {

	constructor: THREE.STLLoader

};

THREE.STLLoader.prototype.load = function (url, callback) {

	var scope = this;

	var xhr = new XMLHttpRequest();

	function onloaded( event ) {

		if ( event.target.status === 200 || event.target.status === 0 ) {

				var geometry = scope.parse( event.target.response || event.target.responseText );

				geometry.name = url.replace(/^.*[\\\/]/, '').replace(/\..+$/, '');

				scope.dispatchEvent( { type: 'load', content: geometry } );

				if ( callback ) callback( geometry );

		} else {

			scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']',
				response: event.target.responseText } );

		}

	}

	xhr.addEventListener( 'load', onloaded, false );

	xhr.addEventListener( 'progress', function ( event ) {

		scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );

	}, false );

	xhr.addEventListener( 'error', function () {

		scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );

	}, false );

	xhr.overrideMimeType('text/plain; charset=x-user-defined');
	xhr.open( 'GET', url, true );
	xhr.responseType = "arraybuffer";
	xhr.send( null );

};

THREE.STLLoader.prototype.parse = function (data) {

	var isBinary = function () {

		var expect, face_size, n_faces, reader;
		reader = new DataView( binData );
		face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8);
		n_faces = reader.getUint32(80,true);
		expect = 80 + (32 / 8) + (n_faces * face_size);
		return expect === reader.byteLength;

	};

	var binData = this.ensureBinary( data );

	return geometry = isBinary()
		? this.parseBinary( binData )
		: this.parseASCII( this.ensureString( data ) );
};

THREE.STLLoader.prototype.parseBinary = function (data) {

	var face, geometry, n_faces, reader, length, normal, i, dataOffset, faceLength, start, vertexstart;

	reader = new DataView( data );
	n_faces = reader.getUint32(80,true);
	geometry = new THREE.Geometry();
	dataOffset = 84;
	faceLength = 12 * 4 + 2;

	for (face = 0; face < n_faces; face++) {

		start = dataOffset + face * faceLength;
		normal = new THREE.Vector3(
			reader.getFloat32(start,true),
			reader.getFloat32(start + 4,true),
			reader.getFloat32(start + 8,true)
		);

		for (i = 1; i <= 3; i++) {

			vertexstart = start + i * 12;
			geometry.vertices.push(
				new THREE.Vector3(
					reader.getFloat32(vertexstart,true),
					reader.getFloat32(vertexstart +4,true),
					reader.getFloat32(vertexstart + 8,true)
				)
			);

		}

		length = geometry.vertices.length;
		geometry.faces.push(new THREE.Face3(length - 3, length - 2, length - 1, normal));

	}

	geometry.computeCentroids();
	geometry.computeBoundingSphere();

	return geometry;

};

THREE.STLLoader.prototype.parseASCII = function (data) {

	var geometry, length, normal, patternFace, patternNormal, patternVertex, result, text;
	geometry = new THREE.Geometry();
	patternFace = /facet([\s\S]*?)endfacet/g;

	while (((result = patternFace.exec(data)) != null)) {

		text = result[0];
		patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;

		while (((result = patternNormal.exec(text)) != null)) {

			normal = new THREE.Vector3(parseFloat(result[1]), parseFloat(result[3]), parseFloat(result[5]));

		}

		patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;

		while (((result = patternVertex.exec(text)) != null)) {

			geometry.vertices.push(new THREE.Vector3(parseFloat(result[1]), parseFloat(result[3]), parseFloat(result[5])));

		}

		length = geometry.vertices.length;
		geometry.faces.push(new THREE.Face3(length - 3, length - 2, length - 1, normal));

	}

	geometry.computeCentroids();
	geometry.computeBoundingBox();
	geometry.computeBoundingSphere();

	return geometry;

};

THREE.STLLoader.prototype.ensureString = function (buf) {

	if (typeof buf !== "string"){
		var array_buffer = new Uint8Array(buf);
		var str = '';
		for(var i = 0; i < buf.byteLength; i++) {
			str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian
		}
		return str;
	} else {
		return buf;
	}

};

THREE.STLLoader.prototype.ensureBinary = function (buf) {

	if (typeof buf === "string"){
		var array_buffer = new Uint8Array(buf.length);
		for(var i = 0; i < buf.length; i++) {
			array_buffer[i] = buf.charCodeAt(i) & 0xff; // implicitly assumes little-endian
		}
		return array_buffer.buffer || array_buffer;
	} else {
		return buf;
	}

};

THREE.EventDispatcher.prototype.apply( THREE.STLLoader.prototype );

if ( typeof DataView === 'undefined'){

	DataView = function(buffer, byteOffset, byteLength){

		this.buffer = buffer;
		this.byteOffset = byteOffset || 0;
		this.byteLength = byteLength || buffer.byteLength || buffer.length;
		this._isString = typeof buffer === "string";

	}

	DataView.prototype = {

		_getCharCodes:function(buffer,start,length){
			start = start || 0;
			length = length || buffer.length;
			var end = start + length;
			var codes = [];
			for (var i = start; i < end; i++) {
				codes.push(buffer.charCodeAt(i) & 0xff);
			}
			return codes;
		},

		_getBytes: function (length, byteOffset, littleEndian) {

			var result;

			// Handle the lack of endianness
			if (littleEndian === undefined) {

				littleEndian = this._littleEndian;

			}

			// Handle the lack of byteOffset
			if (byteOffset === undefined) {

				byteOffset = this.byteOffset;

			} else {

				byteOffset = this.byteOffset + byteOffset;

			}

			if (length === undefined) {

				length = this.byteLength - byteOffset;

			}

			// Error Checking
			if (typeof byteOffset !== 'number') {

				throw new TypeError('DataView byteOffset is not a number');

			}

			if (length < 0 || byteOffset + length > this.byteLength) {

				throw new Error('DataView length or (byteOffset+length) value is out of bounds');

			}

			if (this.isString){

				result = this._getCharCodes(this.buffer, byteOffset, byteOffset + length);

			} else {

				result = this.buffer.slice(byteOffset, byteOffset + length);

			}

			if (!littleEndian && length > 1) {

				if (!(result instanceof Array)) {

					result = Array.prototype.slice.call(result);

				}

				result.reverse();
			}

			return result;

		},

		// Compatibility functions on a String Buffer

		getFloat64: function (byteOffset, littleEndian) {

			var b = this._getBytes(8, byteOffset, littleEndian),

				sign = 1 - (2 * (b[7] >> 7)),
				exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1),

			// Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead
				mantissa = ((b[6] & 0x0f) * Math.pow(2, 48)) + (b[5] * Math.pow(2, 40)) + (b[4] * Math.pow(2, 32)) +
							(b[3] * Math.pow(2, 24)) + (b[2] * Math.pow(2, 16)) + (b[1] * Math.pow(2, 8)) + b[0];

			if (exponent === 1024) {
				if (mantissa !== 0) {
					return NaN;
				} else {
					return sign * Infinity;
				}
			}

			if (exponent === -1023) { // Denormalized
				return sign * mantissa * Math.pow(2, -1022 - 52);
			}

			return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent);

		},

		getFloat32: function (byteOffset, littleEndian) {

			var b = this._getBytes(4, byteOffset, littleEndian),

				sign = 1 - (2 * (b[3] >> 7)),
				exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127,
				mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0];

			if (exponent === 128) {
				if (mantissa !== 0) {
					return NaN;
				} else {
					return sign * Infinity;
				}
			}

			if (exponent === -127) { // Denormalized
				return sign * mantissa * Math.pow(2, -126 - 23);
			}

			return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent);
		},

		getInt32: function (byteOffset, littleEndian) {
			var b = this._getBytes(4, byteOffset, littleEndian);
			return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
		},

		getUint32: function (byteOffset, littleEndian) {
			return this.getInt32(byteOffset, littleEndian) >>> 0;
		},

		getInt16: function (byteOffset, littleEndian) {
			return (this.getUint16(byteOffset, littleEndian) << 16) >> 16;
		},

		getUint16: function (byteOffset, littleEndian) {
			var b = this._getBytes(2, byteOffset, littleEndian);
			return (b[1] << 8) | b[0];
		},

		getInt8: function (byteOffset) {
			return (this.getUint8(byteOffset) << 24) >> 24;
		},

		getUint8: function (byteOffset) {
			return this._getBytes(1, byteOffset)[0];
		}

	 };

}
<commit_msg>Clean up changes made to STLLoader.js<commit_after>/**
 * @author aleeper / http://adamleeper.com/
 * @author mrdoob / http://mrdoob.com/
 * @author gero3 / https://github.com/gero3
 *
 * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
 *
 * Supports both binary and ASCII encoded files, with automatic detection of type.
 *
 * Limitations:
 * 	Binary decoding ignores header. There doesn't seem to be much of a use for it.
 * 	There is perhaps some question as to how valid it is to always assume little-endian-ness.
 * 	ASCII decoding assumes file is UTF-8. Seems to work for the examples...
 *
 * Usage:
 * 	var loader = new THREE.STLLoader();
 * 	loader.addEventListener( 'load', function ( event ) {
 *
 * 		var geometry = event.content;
 * 		scene.add( new THREE.Mesh( geometry ) );
 *
 * 	} );
 * 	loader.load( './models/stl/slotted_disk.stl' );
 */

THREE.STLLoader = function () {};

THREE.STLLoader.prototype = {

	constructor: THREE.STLLoader

};

THREE.STLLoader.prototype.load = function (url, callback) {

	var scope = this;

	var xhr = new XMLHttpRequest();

	function onloaded( event ) {

		if ( event.target.status === 200 || event.target.status === 0 ) {

				var geometry = scope.parse( event.target.response || event.target.responseText );

				geometry.name = url.replace(/^.*[\\\/]/, '').replace(/\..+$/, '');

				scope.dispatchEvent( { type: 'load', content: geometry } );

				if ( callback ) callback( geometry );

		} else {

			scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']',
				response: event.target.responseText } );

		}

	}

	xhr.addEventListener( 'load', onloaded, false );

	xhr.addEventListener( 'progress', function ( event ) {

		scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );

	}, false );

	xhr.addEventListener( 'error', function () {

		scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );

	}, false );

	xhr.overrideMimeType('text/plain; charset=x-user-defined');
	xhr.open( 'GET', url, true );
	xhr.responseType = "arraybuffer";
	xhr.send( null );

};

THREE.STLLoader.prototype.parse = function (data) {

	var isBinary = function () {

		var expect, face_size, n_faces, reader;
		reader = new DataView( binData );
		face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8);
		n_faces = reader.getUint32(80,true);
		expect = 80 + (32 / 8) + (n_faces * face_size);
		return expect === reader.byteLength;

	};

	var binData = this.ensureBinary( data );

	return isBinary()
		? this.parseBinary( binData )
		: this.parseASCII( this.ensureString( data ) );

};

THREE.STLLoader.prototype.parseBinary = function (data) {

	var face, geometry, n_faces, reader, length, normal, i, dataOffset, faceLength, start, vertexstart;

	reader = new DataView( data );
	n_faces = reader.getUint32(80,true);
	geometry = new THREE.Geometry();
	dataOffset = 84;
	faceLength = 12 * 4 + 2;

	for (face = 0; face < n_faces; face++) {

		start = dataOffset + face * faceLength;
		normal = new THREE.Vector3(
			reader.getFloat32(start,true),
			reader.getFloat32(start + 4,true),
			reader.getFloat32(start + 8,true)
		);

		for (i = 1; i <= 3; i++) {

			vertexstart = start + i * 12;
			geometry.vertices.push(
				new THREE.Vector3(
					reader.getFloat32(vertexstart,true),
					reader.getFloat32(vertexstart +4,true),
					reader.getFloat32(vertexstart + 8,true)
				)
			);

		}

		length = geometry.vertices.length;
		geometry.faces.push(new THREE.Face3(length - 3, length - 2, length - 1, normal));

	}

	geometry.computeCentroids();
	geometry.computeBoundingSphere();

	return geometry;

};

THREE.STLLoader.prototype.parseASCII = function (data) {

	var geometry, length, normal, patternFace, patternNormal, patternVertex, result, text;
	geometry = new THREE.Geometry();
	patternFace = /facet([\s\S]*?)endfacet/g;

	while (((result = patternFace.exec(data)) != null)) {

		text = result[0];
		patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;

		while (((result = patternNormal.exec(text)) != null)) {

			normal = new THREE.Vector3(parseFloat(result[1]), parseFloat(result[3]), parseFloat(result[5]));

		}

		patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;

		while (((result = patternVertex.exec(text)) != null)) {

			geometry.vertices.push(new THREE.Vector3(parseFloat(result[1]), parseFloat(result[3]), parseFloat(result[5])));

		}

		length = geometry.vertices.length;
		geometry.faces.push(new THREE.Face3(length - 3, length - 2, length - 1, normal));

	}

	geometry.computeCentroids();
	geometry.computeBoundingBox();
	geometry.computeBoundingSphere();

	return geometry;

};

THREE.STLLoader.prototype.ensureString = function (buf) {

	if (typeof buf !== "string"){
		var array_buffer = new Uint8Array(buf);
		var str = '';
		for(var i = 0; i < buf.byteLength; i++) {
			str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian
		}
		return str;
	} else {
		return buf;
	}

};

THREE.STLLoader.prototype.ensureBinary = function (buf) {

	if (typeof buf === "string"){
		var array_buffer = new Uint8Array(buf.length);
		for(var i = 0; i < buf.length; i++) {
			array_buffer[i] = buf.charCodeAt(i) & 0xff; // implicitly assumes little-endian
		}
		return array_buffer.buffer || array_buffer;
	} else {
		return buf;
	}

};

THREE.EventDispatcher.prototype.apply( THREE.STLLoader.prototype );

if ( typeof DataView === 'undefined'){

	DataView = function(buffer, byteOffset, byteLength){

		this.buffer = buffer;
		this.byteOffset = byteOffset || 0;
		this.byteLength = byteLength || buffer.byteLength || buffer.length;
		this._isString = typeof buffer === "string";

	}

	DataView.prototype = {

		_getCharCodes:function(buffer,start,length){
			start = start || 0;
			length = length || buffer.length;
			var end = start + length;
			var codes = [];
			for (var i = start; i < end; i++) {
				codes.push(buffer.charCodeAt(i) & 0xff);
			}
			return codes;
		},

		_getBytes: function (length, byteOffset, littleEndian) {

			var result;

			// Handle the lack of endianness
			if (littleEndian === undefined) {

				littleEndian = this._littleEndian;

			}

			// Handle the lack of byteOffset
			if (byteOffset === undefined) {

				byteOffset = this.byteOffset;

			} else {

				byteOffset = this.byteOffset + byteOffset;

			}

			if (length === undefined) {

				length = this.byteLength - byteOffset;

			}

			// Error Checking
			if (typeof byteOffset !== 'number') {

				throw new TypeError('DataView byteOffset is not a number');

			}

			if (length < 0 || byteOffset + length > this.byteLength) {

				throw new Error('DataView length or (byteOffset+length) value is out of bounds');

			}

			if (this.isString){

				result = this._getCharCodes(this.buffer, byteOffset, byteOffset + length);

			} else {

				result = this.buffer.slice(byteOffset, byteOffset + length);

			}

			if (!littleEndian && length > 1) {

				if (!(result instanceof Array)) {

					result = Array.prototype.slice.call(result);

				}

				result.reverse();
			}

			return result;

		},

		// Compatibility functions on a String Buffer

		getFloat64: function (byteOffset, littleEndian) {

			var b = this._getBytes(8, byteOffset, littleEndian),

				sign = 1 - (2 * (b[7] >> 7)),
				exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1),

			// Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead
				mantissa = ((b[6] & 0x0f) * Math.pow(2, 48)) + (b[5] * Math.pow(2, 40)) + (b[4] * Math.pow(2, 32)) +
							(b[3] * Math.pow(2, 24)) + (b[2] * Math.pow(2, 16)) + (b[1] * Math.pow(2, 8)) + b[0];

			if (exponent === 1024) {
				if (mantissa !== 0) {
					return NaN;
				} else {
					return sign * Infinity;
				}
			}

			if (exponent === -1023) { // Denormalized
				return sign * mantissa * Math.pow(2, -1022 - 52);
			}

			return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent);

		},

		getFloat32: function (byteOffset, littleEndian) {

			var b = this._getBytes(4, byteOffset, littleEndian),

				sign = 1 - (2 * (b[3] >> 7)),
				exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127,
				mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0];

			if (exponent === 128) {
				if (mantissa !== 0) {
					return NaN;
				} else {
					return sign * Infinity;
				}
			}

			if (exponent === -127) { // Denormalized
				return sign * mantissa * Math.pow(2, -126 - 23);
			}

			return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent);
		},

		getInt32: function (byteOffset, littleEndian) {
			var b = this._getBytes(4, byteOffset, littleEndian);
			return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
		},

		getUint32: function (byteOffset, littleEndian) {
			return this.getInt32(byteOffset, littleEndian) >>> 0;
		},

		getInt16: function (byteOffset, littleEndian) {
			return (this.getUint16(byteOffset, littleEndian) << 16) >> 16;
		},

		getUint16: function (byteOffset, littleEndian) {
			var b = this._getBytes(2, byteOffset, littleEndian);
			return (b[1] << 8) | b[0];
		},

		getInt8: function (byteOffset) {
			return (this.getUint8(byteOffset) << 24) >> 24;
		},

		getUint8: function (byteOffset) {
			return this._getBytes(1, byteOffset)[0];
		}

	 };

}
<|endoftext|>


<commit_before>/*
 * Toggle widget
 * https://github.com/filamentgroup/toggle
 * Copyright (c) 2013 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

    // Defaults
    var pluginName = "collapsible";
    // overrideable defaults
    var defaults = {
        pluginClass: pluginName,
        collapsedClass: pluginName + "-collapsed",
        headerClass: pluginName + "-header",
        contentClass: pluginName + "-content",
        instructions: "Interact to toggle content",
        collapsed: true
    };

    // plugin constructor
    function Plugin(element, options) {
        this.element = $( element );
        var self = this,
            dataOptions = {};

        // Allow data-attr option setting
        if( this.element.is( "[data-config]" ) ){
            $.each( defaults, function( option ) {

                var value = self.element.attr( "data-" + option.replace( /[A-Z]/g, function( c ) {
                                return "-" + c.toLowerCase();
                            }));

                if ( value !== undefined ) {
                    if( value === "true" || value === "false" ){
                        dataOptions[ option ] = value === "true";
                    }
                    else {
                        dataOptions[ option ] = value;
                    }
                }
            });
        }

        this.options = $.extend( {}, defaults, dataOptions, options );
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }

    Plugin.prototype = {
        init: function () {
            this.header = this.element.children().eq( 0 );
            this.content = this.header.next();
            this._addAttributes();
            this._bindEvents();
        },

        _addAttributes: function(){
            this.element.addClass( this.options.pluginClass );

            this.header.addClass( this.options.headerClass );

            this.header.attr( "title", this.options.instructions );

            this.header.attr( "role", "button" );

            this.header.attr( "aria-expanded", "true" );

            this.header.attr( "tabindex", "0" );

            this.content.addClass( this.options.contentClass );
        },

        _bindEvents: function(){
            var self = this;

            this.element
                .on( "expand", this.expand )
                .on( "collapse", this.collapse )
                .on( "toggle", this.toggle );

            this.header
                .on( "mouseup", function(){
                    self.element.trigger( "toggle" );
                })
                .on( "keyup", function( e ){
                    if( e.which === 13 || e.which === 32 ){
                        self.element.trigger( "toggle" );
                    }
                });

            if( this.options.collapsed ){
                this.collapse();
            }
        },

        collapsed: false,

        expand: function () {
            var self = $( this ).data( "plugin_" + pluginName ) || this;
            self.element.removeClass( self.options.collapsedClass );
            self.collapsed = false;
            self.header.attr( "aria-expanded", "true" );
        },

        collapse: function() {
            var self = $( this ).data( "plugin_" + pluginName ) || this;
            self.element.addClass( self.options.collapsedClass );
            self.collapsed = true;
            self.header.attr( "aria-expanded", "false" );
        },

        toggle: function(){
            var self = $( this ).data( "plugin_" + pluginName );
            self.element.trigger( self.collapsed ? "expand" : "collapse" );
        }
    };

    // lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[ pluginName ] = function (options) {
        return this.each(function () {
            if ( !$( this ).data( "plugin_" + pluginName ) ) {
                $( this ).data( "plugin_" + pluginName, new Plugin( this, options ));
            }
        });
    };

    // Simple auto-init by selector that runs when the dom is ready. Use if desirable.
    $(function(){
        $( "." + pluginName )[ pluginName ]();
    });

})(jQuery, window, document);
<commit_msg>fixes #4<commit_after>/*
 * Toggle widget
 * https://github.com/filamentgroup/toggle
 * Copyright (c) 2013 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

    // Defaults
    var pluginName = "collapsible";
    // overrideable defaults
    var defaults = {
        pluginClass: pluginName,
        collapsedClass: pluginName + "-collapsed",
        headerClass: pluginName + "-header",
        contentClass: pluginName + "-content",
        instructions: false,
        collapsed: true
    };

    // plugin constructor
    function Plugin(element, options) {
        this.element = $( element );
        var self = this,
            dataOptions = {};

        // Allow data-attr option setting
        if( this.element.is( "[data-config]" ) ){
            $.each( defaults, function( option ) {

                var value = self.element.attr( "data-" + option.replace( /[A-Z]/g, function( c ) {
                                return "-" + c.toLowerCase();
                            }));

                if ( value !== undefined ) {
                    if( value === "true" || value === "false" ){
                        dataOptions[ option ] = value === "true";
                    }
                    else {
                        dataOptions[ option ] = value;
                    }
                }
            });
        }

        this.options = $.extend( {}, defaults, dataOptions, options );
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }

    Plugin.prototype = {
        init: function () {
            this.header = this.element.children().eq( 0 );
            this.content = this.header.next();
            this._addAttributes();
            this._bindEvents();
        },

        _addAttributes: function(){
            this.element.addClass( this.options.pluginClass );

            this.header.addClass( this.options.headerClass );

            if( this.options.instructions ){
                this.header.attr( "title", this.options.instructions );
            }

            this.header.attr( "role", "button" );

            this.header.attr( "aria-expanded", "true" );

            this.header.attr( "tabindex", "0" );

            this.content.addClass( this.options.contentClass );
        },

        _bindEvents: function(){
            var self = this;

            this.element
                .on( "expand", this.expand )
                .on( "collapse", this.collapse )
                .on( "toggle", this.toggle );

            this.header
                .on( "mouseup", function(){
                    self.element.trigger( "toggle" );
                })
                .on( "keyup", function( e ){
                    if( e.which === 13 || e.which === 32 ){
                        self.element.trigger( "toggle" );
                    }
                });

            if( this.options.collapsed ){
                this.collapse();
            }
        },

        collapsed: false,

        expand: function () {
            var self = $( this ).data( "plugin_" + pluginName ) || this;
            self.element.removeClass( self.options.collapsedClass );
            self.collapsed = false;
            self.header.attr( "aria-expanded", "true" );
        },

        collapse: function() {
            var self = $( this ).data( "plugin_" + pluginName ) || this;
            self.element.addClass( self.options.collapsedClass );
            self.collapsed = true;
            self.header.attr( "aria-expanded", "false" );
        },

        toggle: function(){
            var self = $( this ).data( "plugin_" + pluginName );
            self.element.trigger( self.collapsed ? "expand" : "collapse" );
        }
    };

    // lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[ pluginName ] = function (options) {
        return this.each(function () {
            if ( !$( this ).data( "plugin_" + pluginName ) ) {
                $( this ).data( "plugin_" + pluginName, new Plugin( this, options ));
            }
        });
    };

    // Simple auto-init by selector that runs when the dom is ready. Use if desirable.
    $(function(){
        $( "." + pluginName )[ pluginName ]();
    });

})(jQuery, window, document);
<|endoftext|>


<commit_before>const {memoize} = require('./functions')

module.exports = {minBy, maxBy}

function minBy (selector) {
  selector = memoize(selector)
  return (currentMinItem, item) => {
    return selector(item) < selector(currentMinItem)
      ? item
      : currentMinItem
  }
}

function maxBy (selector) {
  selector = memoize(selector)
  return (currentMinItem, item) => {
    return selector(item) > selector(currentMinItem)
      ? item
      : currentMinItem
  }
}
<commit_msg>docs: docs for maxBy/minBy<commit_after>const {memoize} = require('./functions')

module.exports = {minBy, maxBy}

/**
 * @param {function} selector function by which to order values
 * @returns reducer to find the minimum
 * @example [1, 2, 3].reduce(minBy(() => 1 / x)) === 3
 */
function minBy (selector) {
  selector = memoize(selector)
  return (currentMinItem, item) => {
    return selector(item) < selector(currentMinItem)
      ? item
      : currentMinItem
  }
}

/**
 * @param {function} selector function by which to order values
 * @returns reducer to find the maximum
 * @example [1, 2, 3].reduce(minBy(() => 1 / x)) === 1
 */
function maxBy (selector) {
  selector = memoize(selector)
  return (currentMinItem, item) => {
    return selector(item) > selector(currentMinItem)
      ? item
      : currentMinItem
  }
}
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var Q = require('q');
var MongoClient = require('mongodb').MongoClient;

var host = 'brie.cshl.edu'
  , port = 27017
  , dbName = 'search'
  , dbVersion = '56'
  , rootMongoUrl = 'mongodb://' + host + ':' + port + '/' + dbName + dbVersion
  , databasePromise = Q.ninvoke(MongoClient, "connect", rootMongoUrl);

function Collections(collections) {
  // copy all the properties to this object
  _.assign(this, collections);

  // add mongoCollection function to each.
  _.forOwn(collections, function (collection) {
    collection.mongoCollection = function() {
      return databasePromise.then(function (db) {
        return db.collection(collection.collectionName);
      }).catch(function(err) {
        console.log(err);
      });
    }.bind(this)
  }.bind(this));
}

Collections.prototype.closeMongoDatabase = function () {
  databasePromise.then(function (db) {
    db.close();
  });
};

Collections.prototype.getMongoConfig = function () {
  return {
    host: host,
    port: port,
    version: dbVersion,
    db: dbName + dbVersion
  };
};

var collections = new Collections({
  genes: {
    collectionName: 'genes',
    description: 'gramene genes'
  },
  genetrees: {
    collectionName: 'genetree',
    description: 'compara gene trees'
  },
  GO: {
    collectionName: 'GO',
    description: 'gene ontology terms'
  },
  PO: {
    collectionName: 'PO',
    description: 'plant ontology terms'
  },
  taxonomy: {
    collectionName: 'taxonomy',
    description: 'NCBI taxonomy (pruned to cover gramene species)'
  },
  domains: {
    collectionName: 'domains',
    description: 'intepro domains'
  },
  pathways: {
    collectionName: 'pathways',
    description: 'plant reactome pathways and reactions'
  },
  maps: {
    collectionName: 'maps',
    description: 'maps genomes, genetic maps, and physical maps'
  },
  experiments: {
    collectionName: 'experiments',
    description: 'EBI Atlas experiments'
  },
  assays: {
    collectionName: 'assays',
    description: 'EBI Atlas assays'
  },
  expression: {
    collectionName: 'expression',
    description: 'EBI Atlas expression data'
  }
});

module.exports = collections;
<commit_msg>release56a<commit_after>'use strict';

var _ = require('lodash');
var Q = require('q');
var MongoClient = require('mongodb').MongoClient;

var host = 'brie.cshl.edu'
  , port = 27017
  , dbName = 'search'
  , dbVersion = '56'
  , rootMongoUrl = 'mongodb://' + host + ':' + port + '/' + dbName + dbVersion
  , databasePromise = Q.ninvoke(MongoClient, "connect", rootMongoUrl);

function Collections(collections) {
  // copy all the properties to this object
  _.assign(this, collections);

  // add mongoCollection function to each.
  _.forOwn(collections, function (collection) {
    collection.mongoCollection = function() {
      return databasePromise.then(function (db) {
        return db.collection(collection.collectionName);
      }).catch(function(err) {
        console.log(err);
      });
    }.bind(this)
  }.bind(this));
}

Collections.prototype.closeMongoDatabase = function () {
  databasePromise.then(function (db) {
    db.close();
  });
};

Collections.prototype.getMongoConfig = function () {
  return {
    host: host,
    port: port,
    version: dbVersion,
    db: dbName + dbVersion
  };
};

var collections = new Collections({
  genes: {
    collectionName: 'genes_a',
    description: 'gramene genes'
  },
  genetrees: {
    collectionName: 'genetree_a',
    description: 'compara gene trees'
  },
  GO: {
    collectionName: 'GO',
    description: 'gene ontology terms'
  },
  PO: {
    collectionName: 'PO',
    description: 'plant ontology terms'
  },
  taxonomy: {
    collectionName: 'taxonomy',
    description: 'NCBI taxonomy (pruned to cover gramene species)'
  },
  domains: {
    collectionName: 'domains',
    description: 'intepro domains'
  },
  pathways: {
    collectionName: 'pathways',
    description: 'plant reactome pathways and reactions'
  },
  maps: {
    collectionName: 'maps',
    description: 'maps genomes, genetic maps, and physical maps'
  },
  experiments: {
    collectionName: 'experiments',
    description: 'EBI Atlas experiments'
  },
  assays: {
    collectionName: 'assays',
    description: 'EBI Atlas assays'
  },
  expression: {
    collectionName: 'expression',
    description: 'EBI Atlas expression data'
  }
});

module.exports = collections;
<|endoftext|>


<commit_before>
/*
 *  Guacamole - Clientless Remote Desktop
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Guacamole namespace
var Guacamole = Guacamole || {};

/**
 * Abstract ordered drawing surface. Each Layer contains a canvas element and
 * provides simple drawing instructions for drawing to that canvas element,
 * however unlike the canvas element itself, drawing operations on a Layer are
 * guaranteed to run in order, even if such an operation must wait for an image
 * to load before completing.
 *
 * @constructor
 *
 * @param {Number} width The width of the Layer, in pixels. The canvas element
 *                       backing this Layer will be given this width.
 *
 * @param {Number} height The height of the Layer, in pixels. The canvas element
 *                        backing this Layer will be given this height.
 */
Guacamole.Layer = function(width, height) {

    // Reference to this Layer
    var layer = this;

    // Off-screen buffer (canvas element) and corresponding
    // context.
    var display = document.createElement("canvas");
    var displayContext = display.getContext("2d");

    /**
     * Returns the canvas element backing this Layer.
     * @returns {Element} The canvas element backing this Layer.
     */
    this.getCanvas = function() {
        return display;
    };

    /**
     * Resizes the canvas element backing this Layer without testing the
     * new size. This function should only be used internally.
     *
     * @private
     * @param {Number} newWidth The new width to assign to this Layer.
     * @param {Number} newHeight The new height to assign to this Layer.
     */
    function resize(newWidth, newHeight) {
        display.style.position = "absolute";
        display.style.left = "0px";
        display.style.top = "0px";

        display.width = newWidth;
        display.height = newHeight;

        width = newWidth;
        height = newHeight;
    }

    /**
     * Changes the size of this Layer to the given width and height. Resizing
     * is only attempted if the new size provided is actually different from
     * the current size.
     *
     * @param {Number} newWidth The new width to assign to this Layer.
     * @param {Number} newHeight The new height to assign to this Layer.
     */
    this.resize = function(newWidth, newHeight) {
        if (newWidth != width || newHeight != height)
            resize(newWidth, newHeight);
    };

    /**
     * Given the X and Y coordinates of the upper-left corner of a rectangle
     * and the rectangle's width and height, resize the backing canvas element
     * as necessary to ensure that the rectangle fits within the canvas
     * element's coordinate space. This function will only make the canvas
     * larger. If the rectangle already fits within the canvas element's
     * coordinate space, the canvas is left unchanged.
     *
     * @private
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to fit.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to fit.
     * @param {Number} w The width of the the rectangle to fit.
     * @param {Number} h The height of the the rectangle to fit.
     */
    function fitRect(x, y, w, h) {

        // Calculate bounds
        var opBoundX = w + x;
        var opBoundY = h + y;

        // Determine max width
        var resizeWidth;
        if (opBoundX > width)
            resizeWidth = opBoundX;
        else
            resizeWidth = width;

        // Determine max height
        var resizeHeight;
        if (opBoundY > height)
            resizeHeight = opBoundY;
        else
            resizeHeight = height;

        // Resize if necessary
        if (resizeWidth != width || resizeHeight != height)
            resize(resizeWidth, resizeHeight);

    }

    // Initialize canvas dimensions
    resize(width, height);

    /**
     * Set to true if this Layer should resize itself to accomodate the
     * dimensions of any drawing operation, and false (the default) otherwise.
     *
     * Note that setting this property takes effect immediately, and thus may
     * take effect on operations that were started in the past but have not
     * yet completed. If you wish the setting of this flag to only modify
     * future operations, you will need to make the setting of this flag an
     * operation with sync().
     *
     * @example
     * // Set autosize to true for all future operations
     * layer.sync(function() {
     *     layer.autosize = true;
     * });
     *
     * @type Boolean
     * @default false
     */
    this.autosize = false;

    var updates = new Array();

    function Update(updateHandler) {

        this.setHandler = function(handler) {
            updateHandler = handler;
        };

        this.hasHandler = function() {
            return updateHandler != null;
        };

        this.handle = function() {
            updateHandler();
        };

    }

    function reserveJob(handler) {

        // If no pending updates, just call (if available) and exit
        if (layer.isReady() && handler != null) {
            handler();
            return null;
        }

        // If updates are pending/executing, schedule a pending update
        // and return a reference to it.
        var update = new Update(handler);
        updates.push(update);
        return update;

    }

    function handlePendingUpdates() {

        // Draw all pending updates.
        var update;
        while ((update = updates[0]) != null && update.hasHandler()) {
            update.handle();
            updates.shift();
        }

    }

    /**
     * Returns whether this Layer is ready. A Layer is ready if it has no
     * pending operations and no operations in-progress.
     *
     * @returns {Boolean} true if this Layer is ready, false otherwise.
     */
    this.isReady = function() {
        return updates.length == 0;
    };

    /**
     * Draws the specified image at the given coordinates. The image specified
     * must already be loaded.
     *
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     * @param {Image} image The image to draw. Note that this is an Image
     *                      object - not a URL.
     */
    this.drawImage = function(x, y, image) {
        reserveJob(function() {
            if (autosize != 0) fitRect(x, y, image.width, image.height);
            displayContext.drawImage(image, x, y);
        });
    };

    /**
     * Draws the image at the specified URL at the given coordinates. The image
     * will be loaded automatically, and this and any future operations will
     * wait for the image to finish loading.
     *
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     * @param {String} url The URL of the image to draw.
     */
    this.draw = function(x, y, url) {
        var update = reserveJob(null);

        var image = new Image();
        image.onload = function() {

            update.setHandler(function() {
                if (autosize != 0) fitRect(x, y, image.width, image.height);
                displayContext.drawImage(image, x, y);
            });

            // As this update originally had no handler and may have blocked
            // other updates, handle any blocked updates.
            handlePendingUpdates();

        };
        image.src = url;

    };

    /**
     * Run an arbitrary function as soon as currently pending operations
     * are complete.
     *
     * @param {function} handler The function to call once all currently
     *                           pending operations are complete.
     */
    this.sync = function(handler) {
        reserveJob(handler);
    };

    /**
     * Copy a rectangle of image data from one Layer to this Layer. This
     * operation will copy exactly the image data that will be drawn once all
     * operations of the source Layer that were pending at the time this
     * function was called are complete. This operation will not alter the
     * size of the source Layer even if its autosize property is set to true.
     *
     * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
     * @param {Number} srcx The X coordinate of the upper-left corner of the
     *                      rectangle within the source Layer's coordinate
     *                      space to copy data from.
     * @param {Number} srcy The Y coordinate of the upper-left corner of the
     *                      rectangle within the source Layer's coordinate
     *                      space to copy data from.
     * @param {Number} srcw The width of the rectangle within the source Layer's
     *                      coordinate space to copy data from.
     * @param {Number} srch The height of the rectangle within the source
     *                      Layer's coordinate space to copy data from.
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     */
    this.copyRect = function(srcLayer, srcx, srcy, srcw, srch, x, y) {

        function doCopyRect() {
            if (autosize != 0) fitRect(x, y, srcw, srch);
            displayContext.drawImage(srcLayer, srcx, srcy, srcw, srch, x, y, srcw, srch);
        }

        // If we ARE the source layer, no need to sync.
        // Syncing would result in deadlock.
        if (layer === srcLayer)
            reserveJob(doCopyRect);

        // Otherwise synchronize copy operation with source layer
        else {
            var update = reserveJob(null);
            srcLayer.sync(function() {

                update.setHandler(doCopyRect);

                // As this update originally had no handler and may have blocked
                // other updates, handle any blocked updates.
                handlePendingUpdates();

            });
        }

    };

    this.clearRect = function(x, y, w, h) {
        reserveJob(function() {
            if (autosize != 0) fitRect(x, y, w, h);
            displayContext.clearRect(x, y, w, h);
        });
    };

    this.filter = function(filter) {
        reserveJob(function() {
            var imageData = displayContext.getImageData(0, 0, width, height);
            filter(imageData.data, width, height);
            displayContext.putImageData(imageData, 0, 0);
        });
    };

    var compositeOperation = {
     /* 0x0 NOT IMPLEMENTED */
        0x1: "destination-in",
        0x2: "destination-out",
     /* 0x3 NOT IMPLEMENTED */
        0x4: "source-in",
     /* 0x5 NOT IMPLEMENTED */
        0x6: "source-atop",
     /* 0x7 NOT IMPLEMENTED */
        0x8: "source-out",
        0x9: "destination-atop",
        0xA: "xor",
        0xB: "destination-over",
        0xC: "copy",
     /* 0xD NOT IMPLEMENTED */
        0xE: "source-over",
        0xF: "lighter"
    };

    this.setChannelMask = function(mask) {
        reserveJob(function() {
            displayContext.globalCompositeOperation = compositeOperation[mask];
        });
    };

}

<commit_msg>More JSDoc, renamed Update to Task, fixed wording.<commit_after>
/*
 *  Guacamole - Clientless Remote Desktop
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Guacamole namespace
var Guacamole = Guacamole || {};

/**
 * Abstract ordered drawing surface. Each Layer contains a canvas element and
 * provides simple drawing instructions for drawing to that canvas element,
 * however unlike the canvas element itself, drawing operations on a Layer are
 * guaranteed to run in order, even if such an operation must wait for an image
 * to load before completing.
 *
 * @constructor
 *
 * @param {Number} width The width of the Layer, in pixels. The canvas element
 *                       backing this Layer will be given this width.
 *
 * @param {Number} height The height of the Layer, in pixels. The canvas element
 *                        backing this Layer will be given this height.
 */
Guacamole.Layer = function(width, height) {

    /**
     * Reference to this Layer.
     * @private
     */
    var layer = this;

    /**
     * The canvas element backing this Layer.
     * @private
     */
    var display = document.createElement("canvas");

    /**
     * The 2D display context of the canvas element backing this Layer.
     * @private
     */
    var displayContext = display.getContext("2d");

    var tasks = new Array();

    var compositeOperation = {
     /* 0x0 NOT IMPLEMENTED */
        0x1: "destination-in",
        0x2: "destination-out",
     /* 0x3 NOT IMPLEMENTED */
        0x4: "source-in",
     /* 0x5 NOT IMPLEMENTED */
        0x6: "source-atop",
     /* 0x7 NOT IMPLEMENTED */
        0x8: "source-out",
        0x9: "destination-atop",
        0xA: "xor",
        0xB: "destination-over",
        0xC: "copy",
     /* 0xD NOT IMPLEMENTED */
        0xE: "source-over",
        0xF: "lighter"
    };

    /**
     * Returns the canvas element backing this Layer.
     * @returns {Element} The canvas element backing this Layer.
     */
    this.getCanvas = function() {
        return display;
    };

    /**
     * Resizes the canvas element backing this Layer without testing the
     * new size. This function should only be used internally.
     *
     * @private
     * @param {Number} newWidth The new width to assign to this Layer.
     * @param {Number} newHeight The new height to assign to this Layer.
     */
    function resize(newWidth, newHeight) {
        display.style.position = "absolute";
        display.style.left = "0px";
        display.style.top = "0px";

        display.width = newWidth;
        display.height = newHeight;

        width = newWidth;
        height = newHeight;
    }

    /**
     * Changes the size of this Layer to the given width and height. Resizing
     * is only attempted if the new size provided is actually different from
     * the current size.
     *
     * @param {Number} newWidth The new width to assign to this Layer.
     * @param {Number} newHeight The new height to assign to this Layer.
     */
    this.resize = function(newWidth, newHeight) {
        if (newWidth != width || newHeight != height)
            resize(newWidth, newHeight);
    };

    /**
     * Given the X and Y coordinates of the upper-left corner of a rectangle
     * and the rectangle's width and height, resize the backing canvas element
     * as necessary to ensure that the rectangle fits within the canvas
     * element's coordinate space. This function will only make the canvas
     * larger. If the rectangle already fits within the canvas element's
     * coordinate space, the canvas is left unchanged.
     *
     * @private
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to fit.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to fit.
     * @param {Number} w The width of the the rectangle to fit.
     * @param {Number} h The height of the the rectangle to fit.
     */
    function fitRect(x, y, w, h) {

        // Calculate bounds
        var opBoundX = w + x;
        var opBoundY = h + y;

        // Determine max width
        var resizeWidth;
        if (opBoundX > width)
            resizeWidth = opBoundX;
        else
            resizeWidth = width;

        // Determine max height
        var resizeHeight;
        if (opBoundY > height)
            resizeHeight = opBoundY;
        else
            resizeHeight = height;

        // Resize if necessary
        if (resizeWidth != width || resizeHeight != height)
            resize(resizeWidth, resizeHeight);

    }

    /**
     * Set to true if this Layer should resize itself to accomodate the
     * dimensions of any drawing operation, and false (the default) otherwise.
     *
     * Note that setting this property takes effect immediately, and thus may
     * take effect on operations that were started in the past but have not
     * yet completed. If you wish the setting of this flag to only modify
     * future operations, you will need to make the setting of this flag an
     * operation with sync().
     *
     * @example
     * // Set autosize to true for all future operations
     * layer.sync(function() {
     *     layer.autosize = true;
     * });
     *
     * @type Boolean
     * @default false
     */
    this.autosize = false;

    /**
     * A container for an task handler. Each operation which must be ordered
     * is associated with a Task that goes into a task queue. Tasks in this
     * queue are executed in order once their handlers are set, while Tasks
     * without handlers block themselves and any following Tasks from running.
     *
     * @constructor
     * @private
     * @param {function} taskHandler The function to call when this task
     *                               runs, if any.
     */
    function Task(taskHandler) {

        /**
         * The handler this Task is associated with, if any.
         *
         * @type function
         */
        this.handler = taskHandler;

    }

    function scheduleTask(handler) {

        // If no pending tasks, just call (if available) and exit
        if (layer.isReady() && handler != null) {
            handler();
            return null;
        }

        // If tasks are pending/executing, schedule a pending task
        // and return a reference to it.
        var task = new Task(handler);
        tasks.push(task);
        return task;

    }

    function handlePendingTasks() {

        // Draw all pending tasks.
        var task;
        while ((task = tasks[0]) != null && task.handler) {
            task.handler();
            tasks.shift();
        }

    }

    /**
     * Returns whether this Layer is ready. A Layer is ready if it has no
     * pending operations and no operations in-progress.
     *
     * @returns {Boolean} true if this Layer is ready, false otherwise.
     */
    this.isReady = function() {
        return tasks.length == 0;
    };

    /**
     * Draws the specified image at the given coordinates. The image specified
     * must already be loaded.
     *
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     * @param {Image} image The image to draw. Note that this is an Image
     *                      object - not a URL.
     */
    this.drawImage = function(x, y, image) {
        scheduleTask(function() {
            if (autosize != 0) fitRect(x, y, image.width, image.height);
            displayContext.drawImage(image, x, y);
        });
    };

    /**
     * Draws the image at the specified URL at the given coordinates. The image
     * will be loaded automatically, and this and any future operations will
     * wait for the image to finish loading.
     *
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     * @param {String} url The URL of the image to draw.
     */
    this.draw = function(x, y, url) {
        var task = scheduleTask(null);

        var image = new Image();
        image.onload = function() {

            task.handler = function() {
                if (autosize != 0) fitRect(x, y, image.width, image.height);
                displayContext.drawImage(image, x, y);
            };

            // As this task originally had no handler and may have blocked
            // other tasks, handle any blocked tasks.
            handlePendingTasks();

        };
        image.src = url;

    };

    /**
     * Run an arbitrary function as soon as currently pending operations
     * are complete.
     *
     * @param {function} handler The function to call once all currently
     *                           pending operations are complete.
     */
    this.sync = function(handler) {
        scheduleTask(handler);
    };

    /**
     * Copy a rectangle of image data from one Layer to this Layer. This
     * operation will copy exactly the image data that will be drawn once all
     * operations of the source Layer that were pending at the time this
     * function was called are complete. This operation will not alter the
     * size of the source Layer even if its autosize property is set to true.
     *
     * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
     * @param {Number} srcx The X coordinate of the upper-left corner of the
     *                      rectangle within the source Layer's coordinate
     *                      space to copy data from.
     * @param {Number} srcy The Y coordinate of the upper-left corner of the
     *                      rectangle within the source Layer's coordinate
     *                      space to copy data from.
     * @param {Number} srcw The width of the rectangle within the source Layer's
     *                      coordinate space to copy data from.
     * @param {Number} srch The height of the rectangle within the source
     *                      Layer's coordinate space to copy data from.
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     */
    this.copyRect = function(srcLayer, srcx, srcy, srcw, srch, x, y) {

        function doCopyRect() {
            if (autosize != 0) fitRect(x, y, srcw, srch);
            displayContext.drawImage(srcLayer, srcx, srcy, srcw, srch, x, y, srcw, srch);
        }

        // If we ARE the source layer, no need to sync.
        // Syncing would result in deadlock.
        if (layer === srcLayer)
            scheduleTask(doCopyRect);

        // Otherwise synchronize copy operation with source layer
        else {
            var task = scheduleTask(null);
            srcLayer.sync(function() {

                task.handler = doCopyRect;

                // As this task originally had no handler and may have blocked
                // other tasks, handle any blocked tasks.
                handlePendingTasks();

            });
        }

    };

    this.clearRect = function(x, y, w, h) {
        scheduleTask(function() {
            if (autosize != 0) fitRect(x, y, w, h);
            displayContext.clearRect(x, y, w, h);
        });
    };

    this.filter = function(filter) {
        scheduleTask(function() {
            var imageData = displayContext.getImageData(0, 0, width, height);
            filter(imageData.data, width, height);
            displayContext.putImageData(imageData, 0, 0);
        });
    };

    this.setChannelMask = function(mask) {
        scheduleTask(function() {
            displayContext.globalCompositeOperation = compositeOperation[mask];
        });
    };

    // Initialize canvas dimensions
    resize(width, height);

}

<|endoftext|>