<commit_before>'use strict';

var util              =  require('util')
  , stream            =  require('stream')
  , Transform         =  stream.Transform
  , Readable          =  stream.Readable
  , EventEmitter      =  require('events').EventEmitter
  , log               =  require('valuepack-core/util/log')
  , fetchGithubUpdate =  require('./fetch-github-update')
  , batchGithubRepos  =  require('./batch-github-repos')
  ;

// Logins
util.inherits(ArrayReadable, Readable);

function ArrayReadable (items, opts) {
  opts = opts || {};
  opts.objectMode = true;
  Readable.call(this, opts);
  this.items = items;
}

ArrayReadable.prototype._read = function () {
  this.push(this.items.pop());
}

// Repos
util.inherits(LoginToReposTransform, Transform);

function LoginToReposTransform (db, opts) {
  opts = opts || {};
  opts.objectMode = true;
  Transform.call(this, opts);

  this.db = db;
}

LoginToReposTransform.prototype._transform = function (login, encoding, cb) {
  var self = this;

  log.info(__filename, 'fetching github update for', login);
  fetchGithubUpdate(this.db, login, onupdate);
  function onupdate (err, res) {
    if (err) {
      log.error(__filename, err)
      log.error(__filename, 'while fetching ', login);
      // drop this login
    } else {
      self.push(res);
    }
    cb();
  }
}

// Batch
util.inherits(ReposToPutsTransform, Transform);

function ReposToPutsTransform (db, opts) {
  opts = opts || {};
  opts.objectMode = true;
  Transform.call(this, opts);

  this.db = db;
}

ReposToPutsTransform.prototype._transform = function (res, encoding, cb) {
  var self = this;
  var login = res.username;

  function pushPut(put) { self.push(put) }

  log.verbose(__filename, 'batching structured repo data for', login);

  batchGithubRepos(this.db, res, login, onbatch);
  function onbatch (err, res) {
    if (err) {
      log.error(__filename, err)
      log.error(__filename, 'while batching ', login);
      // drop this response
    } else {
      log.info(__filename, 'batched', login);
      log.info(__filename, 'state', res.info);
      res.batch.forEach(pushPut);
    }
    cb();
  }
}

// Chunks
util.inherits(PutsToSizedBatchesTransform, Transform);

function PutsToSizedBatchesTransform (opts, limit) {
  opts = opts || {};

  this.limit = limit || 4 * 1024 * 1024; // 4MB by default
  this.size = 0;
  this.batch = []

  opts.objectMode = true;
  opts.highWaterMark = this.limit * 2;
  Transform.call(this, opts);
}

PutsToSizedBatchesTransform.prototype.getByteLength = function (put) {
  var limit = this.limit;

  function getLen (put) {
    var len = put.length
    if (len) return len
    try {
      return JSON.stringify(put).length
    } catch (e) {
      // if we can't determine length properly assume that it is big
      return limit
    }
  }
  return getLen(put.key) + getLen(put.value);
}

PutsToSizedBatchesTransform.prototype.pushBatch = function () {
  log.verbose(__filename, 'pushing batch with %d puts and size %d', this.batch.length, this.size);
  var batch = this.batch;
  this.push(batch);
  this.size = 0;
  this.batch = [];
}

PutsToSizedBatchesTransform.prototype._transform = function (put, encoding, cb) {
  var len = this.getByteLength(put);

  if ((this.size + len) >= this.limit) {
    // if single obj is bigger than limit
    if (this.size === 0) {
      this.batch.push(put);
      this.pushBatch();
    } else {
      // queue current batch and batch obj again to have it added to next batch
      this.pushBatch();
      // TODO:
      //setImmediate(this._transform.bind(this, put));
    }
  } else {
    this.batch.push(put);
    this.size += len;
  }
  cb();
}

PutsToSizedBatchesTransform.prototype._flush = function (cb) {
  if (this.batch.length) this.pushBatch();
  cb();
}

var go = module.exports = function (db, logins) {
  var events = new EventEmitter()

  var loginStream    =  new ArrayReadable(logins);
  var reposTransform =  new LoginToReposTransform(db);
  var putsTransform  =  new ReposToPutsTransform(db);
  var batchTransform =  new PutsToSizedBatchesTransform();
  var inflight       =  false;

  var batchStream = loginStream
    .pipe(reposTransform)
    .pipe(putsTransform)
    .pipe(batchTransform)

  batchStream.once('readable', onreadable);

  function onreadable () {
    // avoid multi entry
    if (inflight) return;

    inflight = true;

    if (!batchStream.readable) {
      inflight = false;
      return batchStream.once('readable', onreadable);
    }

    var batch = batchStream.read();

    if (!batch) {
      inflight = false;
      return batchStream.once('readable', onreadable);
    }

    db.batch(batch, function (err) {
      if (err) log.error(__filename, err);
      log.info(__filename, 'stored batch with length', batch.length);
      inflight = false;
      setImmediate(onreadable);
    });
  }

  // TODO: emit some
  return events;
};

// Test
if (!module.parent) {
  log.level = 'silly';

  var tap = require('tap-stream')
  var leveldb = require('valuepack-core/mine/leveldb')
  leveldb.open(function (err, db) {
    if (err) return console.error(err);
    var logins         =  ['thlorenz', 'danielmoore'];
    go(db, logins);
  });
}
<commit_msg>just streaming puts directly to avoid memory spikes and have reasonably fast writes<commit_after>'use strict';

var util              =  require('util')
  , stream            =  require('stream')
  , Transform         =  stream.Transform
  , Readable          =  stream.Readable
  , EventEmitter      =  require('events').EventEmitter
  , log               =  require('valuepack-core/util/log')
  , fetchGithubUpdate =  require('./fetch-github-update')
  , batchGithubRepos  =  require('./batch-github-repos')
  ;

// Logins
util.inherits(ArrayReadable, Readable);

function ArrayReadable (items, opts) {
  opts = opts || {};
  opts.objectMode = true;
  Readable.call(this, opts);
  this.items = items;
}

ArrayReadable.prototype._read = function () {
  this.push(this.items.pop());
}

// Repos
util.inherits(LoginToReposTransform, Transform);

function LoginToReposTransform (db, opts) {
  opts = opts || {};
  opts.objectMode = true;
  Transform.call(this, opts);

  this.db = db;
}

LoginToReposTransform.prototype._transform = function (login, encoding, cb) {
  var self = this;

  log.info(__filename, 'fetching github update for', login);
  fetchGithubUpdate(this.db, login, onupdate);
  function onupdate (err, res) {
    if (err) {
      log.error(__filename, err)
      log.error(__filename, 'while fetching ', login);
      // drop this login
    } else {
      self.push(res);
    }
    cb();
  }
}

// Batch
util.inherits(ReposToPutsTransform, Transform);

function ReposToPutsTransform (db, opts) {
  opts = opts || {};
  opts.objectMode = true;
  Transform.call(this, opts);

  this.db = db;
}

ReposToPutsTransform.prototype._transform = function (res, encoding, cb) {
  var self = this;
  var login = res.username;

  function pushPut(put) { self.push(put) }

  log.verbose(__filename, 'batching structured repo data for', login);

  batchGithubRepos(this.db, res, login, onbatch);
  function onbatch (err, res) {
    if (err) {
      log.error(__filename, err)
      log.error(__filename, 'while batching ', login);
      // drop this response
    } else {
      log.info(__filename, 'batched', login);
      log.info(__filename, 'state', res.info);
      res.batch.forEach(pushPut);
    }
    cb();
  }
}

// Chunks
util.inherits(PutsToSizedBatchesTransform, Transform);

function PutsToSizedBatchesTransform (opts, limit) {
  opts = opts || {};

  this.limit = limit || 4 * 1024 * 1024; // 4MB by default
  this.size = 0;
  this.batch = []

  opts.objectMode = true;
  opts.highWaterMark = this.limit * 2;
  Transform.call(this, opts);
}

PutsToSizedBatchesTransform.prototype.getByteLength = function (put) {
  var limit = this.limit;

  function getLen (put) {
    var len = put.length
    if (len) return len
    try {
      return JSON.stringify(put).length
    } catch (e) {
      // if we can't determine length properly assume that it is big
      return limit
    }
  }
  return getLen(put.key) + getLen(put.value);
}

PutsToSizedBatchesTransform.prototype.pushBatch = function () {
  log.verbose(__filename, 'pushing batch with %d puts and size %d', this.batch.length, this.size);
  var batch = this.batch;
  this.push(batch);
  this.size = 0;
  this.batch = [];
}

PutsToSizedBatchesTransform.prototype._transform = function (put, encoding, cb) {
  var len = this.getByteLength(put);

  if ((this.size + len) >= this.limit) {
    // if single obj is bigger than limit
    if (this.size === 0) {
      this.batch.push(put);
      this.pushBatch();
    } else {
      // queue current batch and batch obj again to have it added to next batch
      this.pushBatch();
      // TODO:
      //setImmediate(this._transform.bind(this, put));
    }
  } else {
    this.batch.push(put);
    this.size += len;
    log.verbose(__filename, 'size: %d, limit: %d, limit - size: %d', this.size, this.limit, this.limit - this.size);
  }
  cb();
}

PutsToSizedBatchesTransform.prototype._flush = function (cb) {
  if (this.batch.length) this.pushBatch();
  cb();
}

var go = module.exports = function (db, logins) {
  var events = new EventEmitter()

  var loginStream    =  new ArrayReadable(logins);
  var reposTransform =  new LoginToReposTransform(db);
  var putsTransform  =  new ReposToPutsTransform(db);
  var batchTransform =  new PutsToSizedBatchesTransform();
  var inflight       =  false;

  var batchStream = loginStream
    .pipe(reposTransform)
    .pipe(putsTransform)
    .pipe(db.createWriteStream());

 /*  This manual batching is a lot slower and takes a lot more memory than just piping the puts
  *  into a writeStream like I'm doing now.
  *  The batch sizes are optimized to be just below the writeBufferSize (see PutsToSizedBatchesTransform), which should be optimal
  *  for batching puts.
  *  I'm not sure why a batch (which is optimized) is behaving worse than writeStream which itself
  *  batches under the hood anyways, but in this case much smaller batches.
  *
    .pipe(batchTransform)

  batchStream.once('readable', onreadable);

  function onreadable () {
    // avoid multi entry
    if (inflight) return;

    inflight = true;

    if (!batchStream.readable) {
      inflight = false;
      return batchStream.once('readable', onreadable);
    }

    var batch = batchStream.read();

    if (!batch) {
      inflight = false;
      return batchStream.once('readable', onreadable);
    }

    db.batch(batch, function (err) {
      if (err) log.error(__filename, err);
      log.info(__filename, 'stored batch with length', batch.length);
      inflight = false;
      setImmediate(onreadable);
    });
  }*/

  // TODO: emit some
  return events;
};

// Test
if (!module.parent) {
  log.level = 'silly';

  var tap = require('tap-stream')
  var leveldb = require('valuepack-core/mine/leveldb')
  leveldb.open(function (err, db) {
    if (err) return console.error(err);
    var logins         =  ['thlorenz', 'danielmoore'];
    go(db, logins);
  });
}
<|endoftext|>


<commit_before>if (typeof define !== 'function') { var define = require('amdefine')(module);}

define([
    './compiler.js',
    './sprite.js',
    './ui.js',
    './utils.js'
    ], function(compiler, sprite, ui, utils) {

var spr_editor = $('#sprite-editor')[0];

var loader = new ui.SpriteLoader(spr_editor);

var ide = {
    files_opened: [],
    init: function(){
        ide.codemirror = CodeMirror.fromTextArea($("#asm")[0], {
            lineNumbers: true,
            matchBrackets: true,
            useCPP: true,
            mode: "text/x-asm"
            });
        ide.codemirror.on("change", scheduleUpdate);

        utils.on_write_end = function (filename, url){
            add_status_msg('Build success', '' + filename + ' file is now available', 'success');
        };

    },
    filename: null,
    load_file: function(file){
        $.get(file, function(data) {
            var regex = /([a-z\d\-\/]+\/)([a-z\d\-\d]+\.(asm|s))/;
            var m  = regex.exec(file);
            if (m) {
                ide.files_opened = []; //
                utils.path = m[1];
                ide.filename = m[2];
                ide.codemirror.setValue(data);
                build();
                for (var f in ide.files_opened){
                    if (ide.files_opened[f].match(/\.chr$/)){
                        loader.load(ide.files_opened[f]);
                        break;
                    }
                }
            }
        });
    },
    compiler_open_file: function(file){
        ide.files_opened.push(file);
        if (loader.file == file){
            return String.fromCharCode.apply(undefined, loader.sprites);
        } else {
            return compiler.open_file(file);
        }
    }
};

ide.init();

function add_status_msg(name, msg, type){
    var div = $('
');
    div.addClass('alert');
    div.addClass('alert-'+type);
    div.append("" + name + " " + msg);
    $('#status').append(div);
}

function build(){
    clearTimeout(_idleTimer);
    $('#status').empty();
    try {
        var data = compiler.nes_compiler(ide.codemirror.getValue());
        var regex = /([a-z\-\d]+)(\.asm|\.s)$/;
        var m = regex.exec(ide.filename);
        if (m){
            utils.write_file(m[1]+'.nes', data);
        } else {
            console.log('regex error');
        }
        _nes.loadRom(data);
        _nes.start();
        add_status_msg('Succefully compiled','OK', 'success');
    } catch (e){
        for (var err in e){
            add_status_msg(e[err].type,e[err].message, 'error');
        }
    }
}

var _idleTimer = null;

function scheduleUpdate(){
    if (_idleTimer !== null){
        clearTimeout(_idleTimer);
    }
    _idleTimer = setTimeout(build, 3000);
}

var _nes;

function emulatorUI () {
    var parent = this;
    var UI = function(nes) {
        this.nes = nes;
        _nes = nes;
        this.screen = $('#nes-screen')[0];
        this.zoomed = false;

        this.canvasContext = this.screen.getContext('2d');
        this.canvasImageData = this.canvasContext.getImageData(0, 0, 256, 240);
        this.resetCanvas();
        /*
        TODO: by enabling it. The source editor stops working
        $(document).
            bind('keydown', function(evt) {
                _nes.keyboard.keyDown(evt);
            }).
            bind('keyup', function(evt) {
                _nes.keyboard.keyUp(evt);
            }).
            bind('keypress', function(evt) {
                _nes.keyboard.keyPress(evt);
            });
        */
        this.dynamicaudio = new DynamicAudio({
            swf: nes.opts.swfPath+'dynamicaudio.swf'
        });
    };

    UI.prototype = {
        resetCanvas: function() {
            this.canvasContext.fillStyle = 'black';
            this.canvasContext.fillRect(0, 0, 256, 240);
            for (var i = 3; i < this.canvasImageData.data.length-3; i += 4) {
                this.canvasImageData.data[i] = 0xFF;
            }
        },
        enable: function() {
        },
        updateStatus: function(s) {
        },
        writeAudio: function(samples) {
            return this.dynamicaudio.writeInt(samples);
        },
        writeFrame: function(buffer, prevBuffer) {
            var imageData = this.canvasImageData.data;
            var pixel, i, j;

            for (i=0; i<256*240; i++) {
                pixel = buffer[i];

                if (pixel != prevBuffer[i]) {
                    j = i*4;
                    imageData[j] = pixel & 0xFF;
                    imageData[j+1] = (pixel >> 8) & 0xFF;
                    imageData[j+2] = (pixel >> 16) & 0xFF;
                    prevBuffer[i] = pixel;
                }
            }
            this.canvasContext.putImageData(this.canvasImageData, 0, 0);
        }
    };
    return UI;
}

$(function() {
    new JSNES({
        'swfPath': '',
        'ui': emulatorUI()
    });
});

// window resize

function onResize() {
    var tab_content = $('.tab-content')[0];
    var left = tab_content.offsetLeft;
    var top = tab_content.offsetTop;
    var width = document.documentElement.clientWidth - left;
    var height = document.documentElement.clientHeight - top;
    tab_content.style.width = width + "px";
    tab_content.style.height = height + "px";
}

window.onresize = onResize;
onResize();

//sprite editor

var sprites = sprite.load_sprites('example/scrolling/mario.chr');

var options = {
    sprites: sprites,
    palette: [0x22,0x16,0x27,0x18],
    sprite_x: 8,
    sprite_y: 16
};

var pixel_editor = new ui.PixelEditor(spr_editor, 165, 0, options);
var selector = new ui.SpriteSelector(spr_editor, 440, 0, options);
var palette = new ui.Palette(spr_editor, 0 , 325, options);
var color_picker = new ui.ColorPicker(spr_editor, 165,270);
var preview = new ui.Preview(spr_editor, 0, 0, options);

pixel_editor.addColorChangeListener(palette);
palette.addColorChangeListener(selector);
palette.addColorChangeListener(preview);
palette.addColorChangeListener(pixel_editor);
color_picker.addColorChangeListener(palette);
selector.addPreviousPageButton("fast_backward.png", 440, 315);
selector.addNextPageButton("fast_forward.png", 475, 315);

//TODO selector sprite one by one
//TODO selector sprite 8 by 8

selector.addSpriteChangedListener(preview);
preview.addSpriteChangedListener(pixel_editor);
pixel_editor.addRedrawListener(preview);
pixel_editor.addRedrawListener(selector);

loader.addRedrawListener(selector);
loader.addRedrawListener(preview);
loader.addRedrawListener(pixel_editor);
loader.updater = build;
loader.addUpdateCompileButton("check.png", 510, 315);

function getCursorPosition(canvas, event) {
    var totalOffsetX = 0;
    var totalOffsetY = 0;
    var canvasX = 0;
    var canvasY = 0;

    var element = canvas;

    do {
        totalOffsetX += element.offsetLeft;
        totalOffsetY += element.offsetTop;
        element = element.offsetParent;
    }
    while (element !== null);

    canvasX = event.pageX - totalOffsetX;
    canvasY = event.pageY - totalOffsetY;

    canvasX = Math.round( canvasX * (canvas.width / canvas.offsetWidth) );
    canvasY = Math.round( canvasY * (canvas.height / canvas.offsetHeight) );

    return {x:canvasX, y:canvasY};
}

$('#sprite-editor').click(
    function(e) {
        var canvas = $(this)[0];
        var pos = getCursorPosition(canvas, e);
        var widgets = [pixel_editor, palette, preview, color_picker, selector, loader];
        for (var w in widgets){
            if (widgets[w].was_clicked(pos.x, pos.y)){
                widgets[w].click(pos.x, pos.y);
                break;
            }
        }
    }
);

//Bootstrap

$('#tabs li:eq(0) a').tab('show');
$('.dropdown-toggle').dropdown();

var AppRouter = Backbone.Router.extend({

  routes: {
    "example/:path/:file": "example",
    "source": "source",
    "sprites": "sprites"
  },

  example: function(path, file) {
    $('#tabs li:eq(0) a').tab('show');
    ide.load_file('example/'+path+'/'+file);
  },
  source: function() {
    $('#tabs li:eq(0) a').tab('show');
  },
  sprites: function(){
    $('#tabs li:eq(1) a').tab('show');
  },
  about: function(){
    $('#tabs li:eq(2) a').tab('show');
  }
});

var app_router = new AppRouter();

app_router.on('route:defaultRoute', function(actions) {
    alert(actions);
});

Backbone.history.start();

});
<commit_msg>fixed bug on ide's spriteeditor #27<commit_after>if (typeof define !== 'function') { var define = require('amdefine')(module);}

define([
    './compiler.js',
    './sprite.js',
    './ui.js',
    './utils.js'
    ], function(compiler, sprite, ui, utils) {

var spr_editor = $('#sprite-editor')[0];

var loader = new ui.SpriteLoader(spr_editor);

var ide = {
    files_opened: [],
    init: function(){
        ide.codemirror = CodeMirror.fromTextArea($("#asm")[0], {
            lineNumbers: true,
            matchBrackets: true,
            useCPP: true,
            mode: "text/x-asm"
            });
        ide.codemirror.on("change", scheduleUpdate);

        utils.default_open_file = utils.open_file;
        utils.open_file = ide.utils_open_file;

        utils.on_write_end = function (filename, url){
            add_status_msg('Build success', '' + filename + ' file is now available', 'success');
        };

    },
    filename: null,
    load_file: function(file){
        $.get(file, function(data) {
            var regex = /([a-z\d\-\/]+\/)([a-z\d\-\d]+\.(asm|s))/;
            var m  = regex.exec(file);
            if (m) {
                ide.files_opened = []; //
                utils.path = m[1];
                ide.filename = m[2];
                ide.codemirror.setValue(data);
                build();
                for (var f in ide.files_opened){
                    if (ide.files_opened[f].match(/\.chr$/)){
                        loader.load(ide.files_opened[f]);
                        break;
                    }
                }
            }
        });
    },
    utils_open_file: function(file){
        ide.files_opened.push(file);
        if (loader.file == file){
            return String.fromCharCode.apply(undefined, loader.sprites);
        } else {
            return utils.default_open_file(file);
        }
    }
};

ide.init();

function add_status_msg(name, msg, type){
    var div = $('
');
    div.addClass('alert');
    div.addClass('alert-'+type);
    div.append("" + name + " " + msg);
    $('#status').append(div);
}

function build(){
    clearTimeout(_idleTimer);
    $('#status').empty();
    try {
        var data = compiler.nes_compiler(ide.codemirror.getValue());
        var regex = /([a-z\-\d]+)(\.asm|\.s)$/;
        var m = regex.exec(ide.filename);
        if (m){
            utils.write_file(m[1]+'.nes', data);
        } else {
            console.log('regex error');
        }
        _nes.loadRom(data);
        _nes.start();
        add_status_msg('Succefully compiled','OK', 'success');
    } catch (e){
        for (var err in e){
            add_status_msg(e[err].type,e[err].message, 'error');
        }
    }
}

var _idleTimer = null;

function scheduleUpdate(){
    if (_idleTimer !== null){
        clearTimeout(_idleTimer);
    }
    _idleTimer = setTimeout(build, 3000);
}

var _nes;

function emulatorUI () {
    var parent = this;
    var UI = function(nes) {
        this.nes = nes;
        _nes = nes;
        this.screen = $('#nes-screen')[0];
        this.zoomed = false;

        this.canvasContext = this.screen.getContext('2d');
        this.canvasImageData = this.canvasContext.getImageData(0, 0, 256, 240);
        this.resetCanvas();
        /*
        TODO: by enabling it. The source editor stops working
        $(document).
            bind('keydown', function(evt) {
                _nes.keyboard.keyDown(evt);
            }).
            bind('keyup', function(evt) {
                _nes.keyboard.keyUp(evt);
            }).
            bind('keypress', function(evt) {
                _nes.keyboard.keyPress(evt);
            });
        */
        this.dynamicaudio = new DynamicAudio({
            swf: nes.opts.swfPath+'dynamicaudio.swf'
        });
    };

    UI.prototype = {
        resetCanvas: function() {
            this.canvasContext.fillStyle = 'black';
            this.canvasContext.fillRect(0, 0, 256, 240);
            for (var i = 3; i < this.canvasImageData.data.length-3; i += 4) {
                this.canvasImageData.data[i] = 0xFF;
            }
        },
        enable: function() {
        },
        updateStatus: function(s) {
        },
        writeAudio: function(samples) {
            return this.dynamicaudio.writeInt(samples);
        },
        writeFrame: function(buffer, prevBuffer) {
            var imageData = this.canvasImageData.data;
            var pixel, i, j;

            for (i=0; i<256*240; i++) {
                pixel = buffer[i];

                if (pixel != prevBuffer[i]) {
                    j = i*4;
                    imageData[j] = pixel & 0xFF;
                    imageData[j+1] = (pixel >> 8) & 0xFF;
                    imageData[j+2] = (pixel >> 16) & 0xFF;
                    prevBuffer[i] = pixel;
                }
            }
            this.canvasContext.putImageData(this.canvasImageData, 0, 0);
        }
    };
    return UI;
}

$(function() {
    new JSNES({
        'swfPath': '',
        'ui': emulatorUI()
    });
});

// window resize

function onResize() {
    var tab_content = $('.tab-content')[0];
    var left = tab_content.offsetLeft;
    var top = tab_content.offsetTop;
    var width = document.documentElement.clientWidth - left;
    var height = document.documentElement.clientHeight - top;
    tab_content.style.width = width + "px";
    tab_content.style.height = height + "px";
}

window.onresize = onResize;
onResize();

//sprite editor

var sprites = sprite.load_sprites('example/scrolling/mario.chr');

var options = {
    sprites: sprites,
    palette: [0x22,0x16,0x27,0x18],
    sprite_x: 8,
    sprite_y: 16
};

var pixel_editor = new ui.PixelEditor(spr_editor, 165, 0, options);
var selector = new ui.SpriteSelector(spr_editor, 440, 0, options);
var palette = new ui.Palette(spr_editor, 0 , 325, options);
var color_picker = new ui.ColorPicker(spr_editor, 165,270);
var preview = new ui.Preview(spr_editor, 0, 0, options);

pixel_editor.addColorChangeListener(palette);
palette.addColorChangeListener(selector);
palette.addColorChangeListener(preview);
palette.addColorChangeListener(pixel_editor);
color_picker.addColorChangeListener(palette);
selector.addPreviousPageButton("fast_backward.png", 440, 315);
selector.addNextPageButton("fast_forward.png", 475, 315);

//TODO selector sprite one by one
//TODO selector sprite 8 by 8

selector.addSpriteChangedListener(preview);
preview.addSpriteChangedListener(pixel_editor);
pixel_editor.addRedrawListener(preview);
pixel_editor.addRedrawListener(selector);

loader.addRedrawListener(selector);
loader.addRedrawListener(preview);
loader.addRedrawListener(pixel_editor);
loader.updater = build;
loader.addUpdateCompileButton("check.png", 510, 315);

function getCursorPosition(canvas, event) {
    var totalOffsetX = 0;
    var totalOffsetY = 0;
    var canvasX = 0;
    var canvasY = 0;

    var element = canvas;

    do {
        totalOffsetX += element.offsetLeft;
        totalOffsetY += element.offsetTop;
        element = element.offsetParent;
    }
    while (element !== null);

    canvasX = event.pageX - totalOffsetX;
    canvasY = event.pageY - totalOffsetY;

    canvasX = Math.round( canvasX * (canvas.width / canvas.offsetWidth) );
    canvasY = Math.round( canvasY * (canvas.height / canvas.offsetHeight) );

    return {x:canvasX, y:canvasY};
}

$('#sprite-editor').click(
    function(e) {
        var canvas = $(this)[0];
        var pos = getCursorPosition(canvas, e);
        var widgets = [pixel_editor, palette, preview, color_picker, selector, loader];
        for (var w in widgets){
            if (widgets[w].was_clicked(pos.x, pos.y)){
                widgets[w].click(pos.x, pos.y);
                break;
            }
        }
    }
);

//Bootstrap

$('#tabs li:eq(0) a').tab('show');
$('.dropdown-toggle').dropdown();

var AppRouter = Backbone.Router.extend({

  routes: {
    "example/:path/:file": "example",
    "source": "source",
    "sprites": "sprites"
  },

  example: function(path, file) {
    $('#tabs li:eq(0) a').tab('show');
    ide.load_file('example/'+path+'/'+file);
  },
  source: function() {
    $('#tabs li:eq(0) a').tab('show');
  },
  sprites: function(){
    $('#tabs li:eq(1) a').tab('show');
  },
  about: function(){
    $('#tabs li:eq(2) a').tab('show');
  }
});

var app_router = new AppRouter();

app_router.on('route:defaultRoute', function(actions) {
    alert(actions);
});

Backbone.history.start();

});
<|endoftext|>


<commit_before>// Eval Template Modify JS for Utilities
// @author lovemore.nalube@uct.ac.za

var evalTemplateUtils = (function() {

    //Configurable private variables
    var canDebug = typeof window.console.firebug !== "undefined",
            canDebugLevels = "info,debug,warn,error", //Comma delimitated set of the debug levels to show. Select from info,debug,warn,error
            entityTemplateItemURL = "/direct/eval-templateitem/:ID:.xml",
            messgeBundlePath = "/direct/eval-resources/message-bundle.json",

    // Dont configure these vars
            templateId = 0,
            pagesLoadedByFBwithJs = [],
            messageBundle = {},
            closedGroups = [];     // keep track of groups a user closes: EVALSYS-825

    function resizeFrame(updown, height) {
        try {
            var thisHeight = typeof height === "undefined" ? 280 : Number(height) + 40,
                frame = parent.document.getElementById(window.name),
                clientH = "";

            if (frame) {
                if (updown === -1) {
                    clientH = document.body.clientHeight;
                }
                else {
                    clientH = document.body.clientHeight + thisHeight; //increasing the height
                }
                $(frame).height(clientH);
            }
        } catch(e) {
            evalTemplateUtils.debug.error("Frame resize did not work. Error: %o", e, e);
        }
    }

    // Format bundle path for user locale
    var loadMessageBundle = function(){
        $.ajax({
            url: messgeBundlePath,
            global: false,
            cache: true,
            dataType : "json",
            success: function(messageBundleJSON){
                messageBundle = messageBundleJSON.data;
            }
        });
    };

    //public data
    return {
        entity:{
            getTemplateItemURL: function(id) {
                return entityTemplateItemURL.replace(":ID:", id);
            }
        },
        //keep frame size before grow
        frameSize: 0,
        //keep iframe scroll height
        frameScrollHeight: 0,
        frameGrow: function(height) {
            resizeFrame(1, height);
        },
        frameShrink: function(height) {
            resizeFrame(-1, height);
        },
        debug: {
            info : function() {
            },
            debug : function() {
            },
            warn : function() {
            },
            error : function() {
            },
            group : function() {
            },
            groupEnd : function() {
            },
            time : function() {
            },
            timeEnd : function() {
            }
        },
        vars: {
            groupableItems : [],
            isIE : false
        },
        init: function() {
            // Debugging. Acts on Firebug console methods described at http://getfirebug.com/console.html
            if (canDebug && typeof console !== "undefined") {
                if (typeof console.info !== "undefined" && canDebugLevels.search(/info/i) !== -1) {
                    evalTemplateUtils.debug.info = console.info;
                }
                if (typeof console.debug !== "undefined" && canDebugLevels.search(/debug/i) !== -1) {
                    evalTemplateUtils.debug.debug = console.debug;
                }
                if (typeof console.warn !== "undefined" && canDebugLevels.search(/warn/i) !== -1) {
                    evalTemplateUtils.debug.warn = console.warn;
                }
                if (typeof console.error !== "undefined" && canDebugLevels.search(/error/i) !== -1) {
                    evalTemplateUtils.debug.error = console.error;
                }
                if (typeof console.group !== "undefined") {
                    evalTemplateUtils.debug.group = console.group;
                }
                if (typeof console.groupEnd !== "undefined") {
                    evalTemplateUtils.debug.groupEnd = console.groupEnd;
                }
                if (typeof console.time !== "undefined") {
                    evalTemplateUtils.debug.time = console.time;
                }
                if (typeof console.timeEnd !== "undefined") {
                    evalTemplateUtils.debug.timeEnd = console.timeEnd;
                }
            }
            //browser check
            evalTemplateUtils.vars.isIE = $.browser.msie;

            // Message Locale bundle loader
            loadMessageBundle();

            templateId = $('input[name=templateId]:hidden').val();
        },
        pages: {
                modify_item_page: "modify_item",
                modify_template_page: "modify_template",
                modify_block_page: "modify_block",
                choose_existing_page: "choose_existing_items",
                choose_expert_page: "choose_expert_category",
                eb_save_order: "/direct/eval-templateitem/template-items-reorder",
                eb_block_edit: "/direct/eval-templateitem/modify-block-items",
                preview_item_page: "preview_item"
            },
        getPageType: function(url){
            evalTemplateUtils.debug.group("Getting the page type/name");
            var pageType = undefined,
                    i = 0,
                    regExp = null;
            pagesLoadedByFBwithJs = [ evalTemplateUtils.pages.modify_item_page, evalTemplateUtils.pages.choose_expert_page,
                evalTemplateUtils.pages.modify_template_page, evalTemplateUtils.pages.modify_block_page,
                evalTemplateUtils.pages.choose_existing_page, evalTemplateUtils.pages.preview_item_page];
            evalTemplateUtils.debug.debug("Pages supported are %s", pagesLoadedByFBwithJs.toString());
            for ( i in pagesLoadedByFBwithJs){
                if ( typeof pageType == "undefined" ){
                    regExp = new RegExp(pagesLoadedByFBwithJs[i]);
                    evalTemplateUtils.debug.debug("Checking url against page filter %s and regExp %o", pagesLoadedByFBwithJs[i], regExp);
                    pageType =  ( url.search(regExp) !== -1 ) ? pagesLoadedByFBwithJs[i] : undefined ;
                    evalTemplateUtils.debug.debug("Page type is %s", pageType);
                }
            }
            evalTemplateUtils.debug.info("Page type found as: %s", pageType);
            evalTemplateUtils.debug.groupEnd();
            return pageType;
        },
        //retrieve the message strings for key
        messageLocator: function(key, params){
            return fluid.messageLocator( messageBundle )([key], params);
        },

        // Remember the groups a user closes: EVALSYS-825
        closedGroup : {
            add : function(groupId){
                if( groupId ){
                    if ( $.inArray(groupId, closedGroups) === -1){
                        closedGroups.push(groupId);
                        evalTemplateUtils.debug.info("Closed %s", groupId);
                    }
                }
                evalTemplateUtils.debug.warn("closedGroups %o", closedGroups);
            },
            remove : function(groupId){
                if( groupId && $.inArray(groupId, closedGroups) > -1){
                    var index;
                    for ( var i in closedGroups){
                        if ( closedGroups[i] === groupId ){
                            index = i;
                        }
                    }
                    closedGroups.splice(index, 1);
                    evalTemplateUtils.debug.info("Opened %s", groupId);
                }
                evalTemplateUtils.debug.warn("closedGroups %o", closedGroups);
            },
            get : closedGroups
        },

        // DOM values retrieval functions
        getTemplateId:  function(){return templateId;},
        getTemplateItemId: function(that){
            return that.parents('.itemRow').find('input[name=template-item-id]:hidden').val();
        },
        getItemClassification: function(that){
            return that.parents('.itemRow').find('input[name=item-classification]:hidden').val();
        }

        };

})($);

//init evalUtils
evalTemplateUtils.init();<commit_msg>NOJIRA Switch off JS debugging based on firebug detection.<commit_after>// Eval Template Modify JS for Utilities
// @author lovemore.nalube@uct.ac.za

var evalTemplateUtils = (function() {

    //Configurable private variables
    var canDebug = false,
            canDebugLevels = "info,debug,warn,error", //Comma delimitated set of the debug levels to show. Select from info,debug,warn,error
            entityTemplateItemURL = "/direct/eval-templateitem/:ID:.xml",
            messgeBundlePath = "/direct/eval-resources/message-bundle.json",

    // Dont configure these vars
            templateId = 0,
            pagesLoadedByFBwithJs = [],
            messageBundle = {},
            closedGroups = [];     // keep track of groups a user closes: EVALSYS-825

    function resizeFrame(updown, height) {
        try {
            var thisHeight = typeof height === "undefined" ? 280 : Number(height) + 40,
                frame = parent.document.getElementById(window.name),
                clientH = "";

            if (frame) {
                if (updown === -1) {
                    clientH = document.body.clientHeight;
                }
                else {
                    clientH = document.body.clientHeight + thisHeight; //increasing the height
                }
                $(frame).height(clientH);
            }
        } catch(e) {
            evalTemplateUtils.debug.error("Frame resize did not work. Error: %o", e, e);
        }
    }

    // Format bundle path for user locale
    var loadMessageBundle = function(){
        $.ajax({
            url: messgeBundlePath,
            global: false,
            cache: true,
            dataType : "json",
            success: function(messageBundleJSON){
                messageBundle = messageBundleJSON.data;
            }
        });
    };

    //public data
    return {
        entity:{
            getTemplateItemURL: function(id) {
                return entityTemplateItemURL.replace(":ID:", id);
            }
        },
        //keep frame size before grow
        frameSize: 0,
        //keep iframe scroll height
        frameScrollHeight: 0,
        frameGrow: function(height) {
            resizeFrame(1, height);
        },
        frameShrink: function(height) {
            resizeFrame(-1, height);
        },
        debug: {
            info : function() {
            },
            debug : function() {
            },
            warn : function() {
            },
            error : function() {
            },
            group : function() {
            },
            groupEnd : function() {
            },
            time : function() {
            },
            timeEnd : function() {
            }
        },
        vars: {
            groupableItems : [],
            isIE : false
        },
        init: function() {
            // Debugging. Acts on Firebug console methods described at http://getfirebug.com/console.html
            if (canDebug && typeof console !== "undefined") {
                if (typeof console.info !== "undefined" && canDebugLevels.search(/info/i) !== -1) {
                    evalTemplateUtils.debug.info = console.info;
                }
                if (typeof console.debug !== "undefined" && canDebugLevels.search(/debug/i) !== -1) {
                    evalTemplateUtils.debug.debug = console.debug;
                }
                if (typeof console.warn !== "undefined" && canDebugLevels.search(/warn/i) !== -1) {
                    evalTemplateUtils.debug.warn = console.warn;
                }
                if (typeof console.error !== "undefined" && canDebugLevels.search(/error/i) !== -1) {
                    evalTemplateUtils.debug.error = console.error;
                }
                if (typeof console.group !== "undefined") {
                    evalTemplateUtils.debug.group = console.group;
                }
                if (typeof console.groupEnd !== "undefined") {
                    evalTemplateUtils.debug.groupEnd = console.groupEnd;
                }
                if (typeof console.time !== "undefined") {
                    evalTemplateUtils.debug.time = console.time;
                }
                if (typeof console.timeEnd !== "undefined") {
                    evalTemplateUtils.debug.timeEnd = console.timeEnd;
                }
            }
            //browser check
            evalTemplateUtils.vars.isIE = $.browser.msie;

            // Message Locale bundle loader
            loadMessageBundle();

            templateId = $('input[name=templateId]:hidden').val();
        },
        pages: {
                modify_item_page: "modify_item",
                modify_template_page: "modify_template",
                modify_block_page: "modify_block",
                choose_existing_page: "choose_existing_items",
                choose_expert_page: "choose_expert_category",
                eb_save_order: "/direct/eval-templateitem/template-items-reorder",
                eb_block_edit: "/direct/eval-templateitem/modify-block-items",
                preview_item_page: "preview_item"
            },
        getPageType: function(url){
            evalTemplateUtils.debug.group("Getting the page type/name");
            var pageType = undefined,
                    i = 0,
                    regExp = null;
            pagesLoadedByFBwithJs = [ evalTemplateUtils.pages.modify_item_page, evalTemplateUtils.pages.choose_expert_page,
                evalTemplateUtils.pages.modify_template_page, evalTemplateUtils.pages.modify_block_page,
                evalTemplateUtils.pages.choose_existing_page, evalTemplateUtils.pages.preview_item_page];
            evalTemplateUtils.debug.debug("Pages supported are %s", pagesLoadedByFBwithJs.toString());
            for ( i in pagesLoadedByFBwithJs){
                if ( typeof pageType == "undefined" ){
                    regExp = new RegExp(pagesLoadedByFBwithJs[i]);
                    evalTemplateUtils.debug.debug("Checking url against page filter %s and regExp %o", pagesLoadedByFBwithJs[i], regExp);
                    pageType =  ( url.search(regExp) !== -1 ) ? pagesLoadedByFBwithJs[i] : undefined ;
                    evalTemplateUtils.debug.debug("Page type is %s", pageType);
                }
            }
            evalTemplateUtils.debug.info("Page type found as: %s", pageType);
            evalTemplateUtils.debug.groupEnd();
            return pageType;
        },
        //retrieve the message strings for key
        messageLocator: function(key, params){
            return fluid.messageLocator( messageBundle )([key], params);
        },

        // Remember the groups a user closes: EVALSYS-825
        closedGroup : {
            add : function(groupId){
                if( groupId ){
                    if ( $.inArray(groupId, closedGroups) === -1){
                        closedGroups.push(groupId);
                        evalTemplateUtils.debug.info("Closed %s", groupId);
                    }
                }
                evalTemplateUtils.debug.warn("closedGroups %o", closedGroups);
            },
            remove : function(groupId){
                if( groupId && $.inArray(groupId, closedGroups) > -1){
                    var index;
                    for ( var i in closedGroups){
                        if ( closedGroups[i] === groupId ){
                            index = i;
                        }
                    }
                    closedGroups.splice(index, 1);
                    evalTemplateUtils.debug.info("Opened %s", groupId);
                }
                evalTemplateUtils.debug.warn("closedGroups %o", closedGroups);
            },
            get : closedGroups
        },

        // DOM values retrieval functions
        getTemplateId:  function(){return templateId;},
        getTemplateItemId: function(that){
            return that.parents('.itemRow').find('input[name=template-item-id]:hidden').val();
        },
        getItemClassification: function(that){
            return that.parents('.itemRow').find('input[name=item-classification]:hidden').val();
        }

        };

})($);

//init evalUtils
evalTemplateUtils.init();
<|endoftext|>


<commit_before>// Could break if GitHub changes its markup
const context = document.querySelector('#org_your_repos') ? 'org' : 'user'
const menuItems = {
  user: [
    'Watched repositories --',
    'Code',
    'Releases',
    'Conversations',
    'Following --',
    'Open source',
    'Stars and follows',
    'Forks',
    'You --',
    'Starred and followed by',
    'Forked by'
  ],
  org: [
    'Code',
    'Releases',
    'Conversations',
    'Administration'
  ]
}

const events = [
  // Code
  'git-branch', 'push', 'gollum',
  // Releases
  'release', 'tag',
  // Conversations
  'issues_closed', 'issues_labeled', 'issues_opened', 'issues_reopened', 'commit_comment', 'issues_comment',
  // Open source
  'create', 'public', 'repo',
  // Stars and follows / Starred and followed by
  'watch_started', 'follow',
  // Forks / Forked by
  'fork',
  // Administration
  'team_add', 'member_add'
]

init()
updateClasses()
if (context === 'user') specifyTimelineEvents()

document.addEventListener('change', function (evt) {
  if (evt.target.classList.contains('js-dashboard-filter-checkbox')) {
    updateClasses()
    rememberPreference()
  }
})

document.addEventListener('click', function (evt) {
  if (evt.shiftKey && evt.target.classList.contains('js-dashboard-filter-label')) {
    for (const checkbox of document.querySelectorAll('.js-dashboard-filter-checkbox')) {
      if (checkbox === evt.target) continue
      checkbox.checked = false
    }
  }
})

function updateClasses() {
  const target = document.querySelector('#dashboard')
  for (const checkbox of document.querySelectorAll('.js-dashboard-filter-checkbox')) {
    target.classList.toggle(`show_${checkbox.id}`, checkbox.checked)
  }
}

function init () {
  const details = document.createElement('details')
  details.classList.add('position-relative', 'js-dropdown-details', 'details-overlay')
  details.style.userSelect = 'none'
  const summary = document.createElement('summary')
  summary.classList.add('btn', 'btn-sm')
  summary.innerText = 'Filter'
  const container = document.createElement('div')
  container.classList.add('dropdown-menu', 'dropdown-menu-se', 'f5')
  container.style.width = '260px'

  for (const key of menuItems[context]) {
    const isHeader = key.split(/ --$/)
    if (isHeader.length > 1) {
      const header = document.createElement('div')
      header.textContent = isHeader[0]
      header.classList.add('dropdown-header')
      container.appendChild(header)
      continue
    }
    const id = key.toLowerCase().replace(/\s/g, '_').replace(/\/_/g, '')
    const input = document.createElement('input')
    input.type = 'checkbox'
    input.id = id
    input.className = 'position-absolute my-2 ml-3 js-dashboard-filter-checkbox'

    const label = document.createElement('label')
    label.className = 'pl-6 dropdown-item js-dashboard-filter-label'
    label.innerText = key
    label.htmlFor = id

    container.appendChild(input)
    container.appendChild(label)
  }
  details.appendChild(summary)
  details.appendChild(container)

  const newDashboard = document.querySelector('.page-responsive [data-src*="/dashboard/recent-activity"]')
  if (newDashboard) {
    newDashboard.after(details)
  } else {
    // org or user condition
    summary.classList.remove('btn-sm')
    details.classList.add(context === 'org' ? 'mt-3' : 'mt-5')
    document.querySelector('.news').prepend(details)
  }
  applyPreference()
}

function rememberPreference () {
  const preference = JSON.parse(localStorage.getItem(`dashboard:select:${context}`) || '{}')
  for (const box of document.querySelectorAll('.js-dashboard-filter-checkbox')) {
    preference[box.id] = box.checked
  }

  localStorage.setItem(`dashboard:select:${context}`, JSON.stringify(preference))
}

function applyPreference () {
  const preference = JSON.parse(localStorage.getItem(`dashboard:select:${context}`) || '{}')

  for (const box of document.querySelectorAll('.js-dashboard-filter-checkbox')) {
    box.checked = (typeof preference[box.id] === 'boolean') ? preference[box.id] : true
  }
}

function specifyTimelineEvents() {
  const dashboard = document.querySelector('#dashboard .news')
  if (!dashboard) return
  const observer = new MutationObserver(addMoreSpecificIdentifiers)
  observer.observe(dashboard, {subtree: true, childList: true})
}

async function getFollowingList() {
  console.log('Dashboard extension: getting list of people you follow from localStorage')
  const following = localStorage.getItem('dashboard:following')
  if (!following || (following && (new Date().getTime() - new Date(JSON.parse(following).updatedAt))/1000 > 24*60*60)) {
    const results = await fetchFollowing()
    const following = {
      updatedAt: (new Date()).getTime(),
      following: results
    }
    localStorage.setItem('dashboard:following', JSON.stringify(following))
    return results
  } else {
    return JSON.parse(following).following
  }
}

async function fetchFollowing() {
  console.log('Dashboard extension: updating list of people you follow from GitHub API (once every 24h)')
  return new Promise(async function(resolve) {
    let following = []
    const user = document.querySelector('.HeaderNavlink.name img').alt.slice(1)
    const endpoint = `https://api.github.com/users/${user}/following`
    let page = 1
    while (page > 0) {
      const res = await fetch(`${endpoint}?page=${page}`)
      const people = await res.json()
      following = following.concat(people)
      if (people.length === 30) {
        page++
      } else {
        page = 0
        resolve(following.map(o => o.login))
      }
    }
  })
}

// Could break if GitHub changes its markup
async function addMoreSpecificIdentifiers(list) {
  const following = await getFollowingList()
  for (const record of list) {
    if (!record.target.classList.contains('news')) continue
    for (const eventItem of record.addedNodes) {
      if (!(eventItem instanceof HTMLElement)) continue
      // eventItem should have only one className
      if (events.indexOf(eventItem.className) < 0) continue
      // First link is actor. I know this is not great.
      const actor = eventItem.querySelector('a')
      if (!actor) continue
      const fromFollowedPeople= following.indexOf(actor.pathname.slice(1)) >= 0
      eventItem.classList.add(fromFollowedPeople ? 'by_followed_people' : 'by_internet')
    }
  }
}
<commit_msg>Fix multiple actors with one being someone user follows<commit_after>// Could break if GitHub changes its markup
const context = document.querySelector('#org_your_repos') ? 'org' : 'user'
const menuItems = {
  user: [
    'Watched repositories --',
    'Code',
    'Releases',
    'Conversations',
    'Following --',
    'Open source',
    'Stars and follows',
    'Forks',
    'You --',
    'Starred and followed by',
    'Forked by'
  ],
  org: [
    'Code',
    'Releases',
    'Conversations',
    'Administration'
  ]
}

const events = [
  // Code
  'git-branch', 'push', 'gollum',
  // Releases
  'release', 'tag',
  // Conversations
  'issues_closed', 'issues_labeled', 'issues_opened', 'issues_reopened', 'commit_comment', 'issues_comment',
  // Open source
  'create', 'public', 'repo',
  // Stars and follows / Starred and followed by
  'watch_started', 'follow',
  // Forks / Forked by
  'fork',
  // Administration
  'team_add', 'member_add'
]

init()
updateClasses()
if (context === 'user') specifyTimelineEvents()

document.addEventListener('change', function (evt) {
  if (evt.target.classList.contains('js-dashboard-filter-checkbox')) {
    updateClasses()
    rememberPreference()
  }
})

document.addEventListener('click', function (evt) {
  if (evt.shiftKey && evt.target.classList.contains('js-dashboard-filter-label')) {
    for (const checkbox of document.querySelectorAll('.js-dashboard-filter-checkbox')) {
      if (checkbox === evt.target) continue
      checkbox.checked = false
    }
  }
})

function updateClasses() {
  const target = document.querySelector('#dashboard')
  for (const checkbox of document.querySelectorAll('.js-dashboard-filter-checkbox')) {
    target.classList.toggle(`show_${checkbox.id}`, checkbox.checked)
  }
}

function init () {
  const details = document.createElement('details')
  details.classList.add('position-relative', 'js-dropdown-details', 'details-overlay')
  details.style.userSelect = 'none'
  const summary = document.createElement('summary')
  summary.classList.add('btn', 'btn-sm')
  summary.innerText = 'Filter'
  const container = document.createElement('div')
  container.classList.add('dropdown-menu', 'dropdown-menu-se', 'f5')
  container.style.width = '260px'

  for (const key of menuItems[context]) {
    const isHeader = key.split(/ --$/)
    if (isHeader.length > 1) {
      const header = document.createElement('div')
      header.textContent = isHeader[0]
      header.classList.add('dropdown-header')
      container.appendChild(header)
      continue
    }
    const id = key.toLowerCase().replace(/\s/g, '_').replace(/\/_/g, '')
    const input = document.createElement('input')
    input.type = 'checkbox'
    input.id = id
    input.className = 'position-absolute my-2 ml-3 js-dashboard-filter-checkbox'

    const label = document.createElement('label')
    label.className = 'pl-6 dropdown-item js-dashboard-filter-label'
    label.innerText = key
    label.htmlFor = id

    container.appendChild(input)
    container.appendChild(label)
  }
  details.appendChild(summary)
  details.appendChild(container)

  const newDashboard = document.querySelector('.page-responsive [data-src*="/dashboard/recent-activity"]')
  if (newDashboard) {
    newDashboard.after(details)
  } else {
    // org or user condition
    summary.classList.remove('btn-sm')
    details.classList.add(context === 'org' ? 'mt-3' : 'mt-5')
    document.querySelector('.news').prepend(details)
  }
  applyPreference()
}

function rememberPreference () {
  const preference = JSON.parse(localStorage.getItem(`dashboard:select:${context}`) || '{}')
  for (const box of document.querySelectorAll('.js-dashboard-filter-checkbox')) {
    preference[box.id] = box.checked
  }

  localStorage.setItem(`dashboard:select:${context}`, JSON.stringify(preference))
}

function applyPreference () {
  const preference = JSON.parse(localStorage.getItem(`dashboard:select:${context}`) || '{}')

  for (const box of document.querySelectorAll('.js-dashboard-filter-checkbox')) {
    box.checked = (typeof preference[box.id] === 'boolean') ? preference[box.id] : true
  }
}

function specifyTimelineEvents() {
  const dashboard = document.querySelector('#dashboard .news')
  if (!dashboard) return
  const observer = new MutationObserver(addMoreSpecificIdentifiers)
  observer.observe(dashboard, {subtree: true, childList: true})
}

async function getFollowingList() {
  console.log('Dashboard extension: getting list of people you follow from localStorage')
  const following = localStorage.getItem('dashboard:following')
  if (!following || (following && (new Date().getTime() - new Date(JSON.parse(following).updatedAt))/1000 > 24*60*60)) {
    const results = await fetchFollowing()
    const following = {
      updatedAt: (new Date()).getTime(),
      following: results
    }
    localStorage.setItem('dashboard:following', JSON.stringify(following))
    return results
  } else {
    return JSON.parse(following).following
  }
}

async function fetchFollowing() {
  console.log('Dashboard extension: updating list of people you follow from GitHub API (once every 24h)')
  return new Promise(async function(resolve) {
    let following = []
    const user = document.querySelector('.HeaderNavlink.name img').alt.slice(1)
    const endpoint = `https://api.github.com/users/${user}/following`
    let page = 1
    while (page > 0) {
      const res = await fetch(`${endpoint}?page=${page}`)
      const people = await res.json()
      following = following.concat(people)
      if (people.length === 30) {
        page++
      } else {
        page = 0
        resolve(following.map(o => o.login))
      }
    }
  })
}

// Could break if GitHub changes its markup
async function addMoreSpecificIdentifiers(list) {
  const following = await getFollowingList()
  for (const record of list) {
    if (!record.target.classList.contains('news')) continue
    for (const eventItem of record.addedNodes) {
      if (!(eventItem instanceof HTMLElement)) continue
      // eventItem should have only one className
      if (events.indexOf(eventItem.className) < 0) continue

      // Check if any links are to one of the followed people
      const fromFollowedPeople = Array.from(eventItem.querySelectorAll('a')).some(function(maybeActor) {
        return following.indexOf(maybeActor.pathname.slice(1)) >= 0
      })
      eventItem.classList.add(fromFollowedPeople ? 'by_followed_people' : 'by_internet')
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

var Fs = require('fs');

/**
 * @class MyServer
 * @extends Server
 * */
var MyServer = /** @type Server */ require('../../Framework').extend({
    //  Не триггерить события во время бенчмарка
    // для чистоты эксперимета
    emit: function () {}
});

var app = new MyServer();
var sock = 'benchmark/framework.sock';
var Http = require('http');
var path = require('../lib/path');

app.unit({
    path: '_',
    deps: ['a'],
    data: function (track) {
        track.send();
    }
});

app.unit({
    path: 'a',
    deps: ['b'],
    data: function (track, errors, result, done) {
        done(null, 'a');
    }
});

app.unit({
    path: 'b',
    deps: [],
    data: function (track, errors, result, done) {
        done(null, 'b');
    }
});

app.route('GET /page/<pageName>/', '_');

/*eslint no-sync: 0*/
try {
    Fs.unlinkSync(sock);
} catch (ex) {}

if ( 'function' === typeof app.ready ) {
    app.ready();
}

Http.createServer(app.getHandler()).listen(sock);

module.exports = function (done) {
    Http.request({
        method: 'GET',
        socketPath: sock,
        path: path
    }, done).end();
};
<commit_msg>bench-fix: починил бенчмарк<commit_after>'use strict';

var Fs = require('fs');
var inherit = require('inherit');
/**
 * @class MyServer
 * @extends Server
 * */
var MyServer = /** @type Server */ inherit(require('../../Framework'), {
    //  Не триггерить события во время бенчмарка
    // для чистоты эксперимета
    emit: function () {}
});

var app = new MyServer();
var sock = 'benchmark/framework.sock';
var Http = require('http');
var path = require('../lib/path');

app.unit({
    path: '_',
    deps: ['a'],
    data: function (track) {
        track.send();
    }
});

app.unit({
    path: 'a',
    deps: ['b'],
    data: function (track, errors, result, done) {
        done(null, 'a');
    }
});

app.unit({
    path: 'b',
    deps: [],
    data: function (track, errors, result, done) {
        done(null, 'b');
    }
});

app.route('GET /page/<pageName>/', '_');

/*eslint no-sync: 0*/
try {
    Fs.unlinkSync(sock);
} catch (ex) {}

if ( 'function' === typeof app.ready ) {
    app.ready();
}

Http.createServer(app.getHandler()).listen(sock);

module.exports = function (done) {
    Http.request({
        method: 'GET',
        socketPath: sock,
        path: path
    }, done).end();
};
<|endoftext|>


<commit_before>import React from 'react'
import GenericForm from 'app/components/genericform'
import rpc from 'app/rpc'
import Error from 'app/components/error'
import Flash from 'app/flash'
import {set_modal} from 'app/utils/store'
import i18n from 'app/utils/i18n'
import QueryServiceSelect from 'app/containers/board/queryserviceselect'
import Widget from 'app/containers/board/widget'
import {map_get} from 'app/utils'
import {MarkdownPreview} from 'react-marked-markdown'

class EditWidget extends React.Component{
  constructor(props){
    super(props)
    const config = map_get(this.props, ["widget", "config"], {})
    this.state = {
      widget: undefined,
      extractors: config.__extractors__ || [],
      config: config,
      postconfig: {},
      postconfig_timer: undefined,
      errors: [],
    }
    this.delayConfigUpdate()
  }
  handleSaveChanges(){
    const state=this.state
    const props=this.props
    const data={
      uuid: props.widget.uuid,
      widget: props.widget.widget,
      project: this.props.project,
      config: {...state.config, "__extractors__": state.extractors},
    }
    this.props.saveWidget(data)
  }
  setFormData(config){
    this.delayConfigUpdate()
    this.setState({config})
  }
  delayConfigUpdate(){
    let postconfig_timer = this.state.postconfig_timer
    if (postconfig_timer)
      clearTimeout(postconfig_timer)
    postconfig_timer = setTimeout( () => {
        this.fakeWidgetExtract()
        this.setState({postconfig_timer: undefined})
      }, 300 )
    this.setState({postconfig_timer})
  }
  fakeWidgetExtract(){
    // fake do as dashboard.widget.extract, to show at widget preview
    let postconfig = {}
    let context = {}
    for (const ext of this.state.extractors){
      context[ext.id]={ extractor: ext.extractor, service: ext.service, config: ext.config }
    }
    context["__vars__"] = this.props.vars
    const config = this.state.config

    for (const p of map_get(this.props, ["template","params"], [])){
      let value = config[p.name]
      if (p.type=="query"){
        value = rpc.call("query.query", {query: value, context}).then( res => {
          let postconfig = {...this.state.postconfig}
          postconfig[p.name] = res
          // console.log("Set postconfig: [%o] = %o ", p.name, res)
          this.setState({postconfig})
        }).catch( e => {
          // console.error("Error getting postconfig: ", value, e)
          let postconfig = {...this.state.postconfig}
          postconfig[p.name] = {error: e}
          let errors = this.state.errors.concat(e)
          this.setState({postconfig, errors})
        })
        postconfig[p.name] = {loading: true}
      }
      else
        postconfig[p.name] = value
    }

    this.setState({postconfig, errors: []})
  }
  hasQuery(){
    return this.props.template && this.props.template.params && this.props.template.params.find( t => t.type == "query" ) != undefined
  }
  updateQueryParams(params){
    if (!params)
      return
    return params.map( p => {
      if (p.type=='query'){
        return {...p, type: "textarea"} // TODO data for autocomplete and so on.
      }
      return p
    })
  }
  handleSetExtractors(extractors){
    this.setState({ extractors })
    this.delayConfigUpdate()
  }
  render(){
    const template = this.props.template

    if (template=="not-found"){
      return (

          <Error>
            {i18n("Could not load information about this widget. Maybe the plugin was deleted?\n\nTry to install it again, or remove the widget.")}
          </Error>

      )
    }

    const widget = this.props.widget || {}
    const state = this.state

    let layout={x:0, y:0, h: 2, w: 2, minW: 1, minH: 1, maxW: 20, maxH: 20}
    if (typeof(template.hints) == "object"){
      layout={...layout, ...template.hints}
    }
    if (widget.ui)
      layout = {...layout, ...widget.ui}
    layout.h = Math.max(Math.min(layout.h, layout.maxH), layout.minH)
    layout.w = Math.max(Math.min(layout.w, layout.maxW), layout.minW)
    const wwidth = layout.w*140
    const wheight = (layout.h*163)
    console.log(layout)

    return (

                <Widget
                  key={widget.uuid}
                  widget={widget.widget}
                  config={state.postconfig}
                  uuid={widget.uuid}
                  project={this.props.project}
                  layout={layout}
                  />

            {state.errors.map( e => (
              <MarkdownPreview key={e} className="ui red bold text" value={String(e)}/>
            ))}

{template.name}

            <MarkdownPreview value={template.description}/>

              {state.error ? (

{i18n("Error")}

{state.error}

              ) : (

{widget.description}

                  {this.hasQuery() && (

                      <QueryServiceSelect
                        extractors={state.extractors}
                        onSetExtractors={this.handleSetExtractors.bind(this)}
                        />

                  )}

                  <GenericForm fields={this.updateQueryParams(template.params)} data={state.config} updateForm={this.setFormData.bind(this)}/>
                  <button className="ui button teal" style={{marginTop:20}} onClick={this.handleSaveChanges.bind(this)}>
                    {this.props.saveLabel || i18n("Update widget")}
                  </button>

              )}

    )
  }
}

export default EditWidget
<commit_msg>Cleanup dev logging<commit_after>import React from 'react'
import GenericForm from 'app/components/genericform'
import rpc from 'app/rpc'
import Error from 'app/components/error'
import Flash from 'app/flash'
import {set_modal} from 'app/utils/store'
import i18n from 'app/utils/i18n'
import QueryServiceSelect from 'app/containers/board/queryserviceselect'
import Widget from 'app/containers/board/widget'
import {map_get} from 'app/utils'
import {MarkdownPreview} from 'react-marked-markdown'

class EditWidget extends React.Component{
  constructor(props){
    super(props)
    const config = map_get(this.props, ["widget", "config"], {})
    this.state = {
      widget: undefined,
      extractors: config.__extractors__ || [],
      config: config,
      postconfig: {},
      postconfig_timer: undefined,
      errors: [],
    }
    this.delayConfigUpdate()
  }
  handleSaveChanges(){
    const state=this.state
    const props=this.props
    const data={
      uuid: props.widget.uuid,
      widget: props.widget.widget,
      project: this.props.project,
      config: {...state.config, "__extractors__": state.extractors},
    }
    this.props.saveWidget(data)
  }
  setFormData(config){
    this.delayConfigUpdate()
    this.setState({config})
  }
  delayConfigUpdate(){
    let postconfig_timer = this.state.postconfig_timer
    if (postconfig_timer)
      clearTimeout(postconfig_timer)
    postconfig_timer = setTimeout( () => {
        this.fakeWidgetExtract()
        this.setState({postconfig_timer: undefined})
      }, 300 )
    this.setState({postconfig_timer})
  }
  fakeWidgetExtract(){
    // fake do as dashboard.widget.extract, to show at widget preview
    let postconfig = {}
    let context = {}
    for (const ext of this.state.extractors){
      context[ext.id]={ extractor: ext.extractor, service: ext.service, config: ext.config }
    }
    context["__vars__"] = this.props.vars
    const config = this.state.config

    for (const p of map_get(this.props, ["template","params"], [])){
      let value = config[p.name]
      if (p.type=="query"){
        value = rpc.call("query.query", {query: value, context}).then( res => {
          let postconfig = {...this.state.postconfig}
          postconfig[p.name] = res
          // console.log("Set postconfig: [%o] = %o ", p.name, res)
          this.setState({postconfig})
        }).catch( e => {
          // console.error("Error getting postconfig: ", value, e)
          let postconfig = {...this.state.postconfig}
          postconfig[p.name] = {error: e}
          let errors = this.state.errors.concat(e)
          this.setState({postconfig, errors})
        })
        postconfig[p.name] = {loading: true}
      }
      else
        postconfig[p.name] = value
    }

    this.setState({postconfig, errors: []})
  }
  hasQuery(){
    return this.props.template && this.props.template.params && this.props.template.params.find( t => t.type == "query" ) != undefined
  }
  updateQueryParams(params){
    if (!params)
      return
    return params.map( p => {
      if (p.type=='query'){
        return {...p, type: "textarea"} // TODO data for autocomplete and so on.
      }
      return p
    })
  }
  handleSetExtractors(extractors){
    this.setState({ extractors })
    this.delayConfigUpdate()
  }
  render(){
    const template = this.props.template

    if (template=="not-found"){
      return (

          <Error>
            {i18n("Could not load information about this widget. Maybe the plugin was deleted?\n\nTry to install it again, or remove the widget.")}
          </Error>

      )
    }

    const widget = this.props.widget || {}
    const state = this.state

    let layout={x:0, y:0, h: 2, w: 2, minW: 1, minH: 1, maxW: 20, maxH: 20}
    if (typeof(template.hints) == "object"){
      layout={...layout, ...template.hints}
    }
    if (widget.ui)
      layout = {...layout, ...widget.ui}
    layout.h = Math.max(Math.min(layout.h, layout.maxH), layout.minH)
    layout.w = Math.max(Math.min(layout.w, layout.maxW), layout.minW)
    const wwidth = layout.w*140
    const wheight = (layout.h*163)

    return (

                <Widget
                  key={widget.uuid}
                  widget={widget.widget}
                  config={state.postconfig}
                  uuid={widget.uuid}
                  project={this.props.project}
                  layout={layout}
                  />

            {state.errors.map( e => (
              <MarkdownPreview key={e} className="ui red bold text" value={String(e)}/>
            ))}

{template.name}

            <MarkdownPreview value={template.description}/>

              {state.error ? (

{i18n("Error")}

{state.error}

              ) : (

{widget.description}

                  {this.hasQuery() && (

                      <QueryServiceSelect
                        extractors={state.extractors}
                        onSetExtractors={this.handleSetExtractors.bind(this)}
                        />

                  )}

                  <GenericForm fields={this.updateQueryParams(template.params)} data={state.config} updateForm={this.setFormData.bind(this)}/>
                  <button className="ui button teal" style={{marginTop:20}} onClick={this.handleSaveChanges.bind(this)}>
                    {this.props.saveLabel || i18n("Update widget")}
                  </button>

              )}

    )
  }
}

export default EditWidget
<|endoftext|>


<commit_before>define(['angular',
  'lodash',
  'jquery',
  'moment',
  'require',
  'app/core/config',
],
function (angular, _, $, moment, require, config) {
  'use strict';

  var module = angular.module('grafana.controllers');

  module.controller('ShareModalCtrl', function($scope, $rootScope, $location, $timeout, timeSrv, templateSrv, linkSrv) {

    $scope.options = { forCurrent: true, includeTemplateVars: true, theme: 'current' };
    $scope.editor = { index: $scope.tabIndex || 0};

    $scope.init = function() {
      $scope.modeSharePanel = $scope.panel ? true : false;

      $scope.tabs = [{title: 'Link', src: 'shareLink.html'}];

      if ($scope.modeSharePanel) {
        $scope.modalTitle = 'Share Panel';
        $scope.tabs.push({title: 'Embed', src: 'shareEmbed.html'});
      } else {
        $scope.modalTitle = 'Share';
      }

      if (!$scope.dashboard.meta.isSnapshot) {
        $scope.tabs.push({title: 'Snapshot', src: 'shareSnapshot.html'});
      }

      if (!$scope.dashboard.meta.isSnapshot && !$scope.modeSharePanel) {
        $scope.tabs.push({title: 'Export', src: 'shareExport.html'});
      }

      $scope.buildUrl();
    };

    $scope.buildUrl = function() {
      var baseUrl = $location.absUrl();
      var queryStart = baseUrl.indexOf('?');

      if (queryStart !== -1) {
        baseUrl = baseUrl.substring(0, queryStart);
      }

      var params = angular.copy($location.search());

      var range = timeSrv.timeRange();
      params.from = range.from.valueOf();
      params.to = range.to.valueOf();

      if ($scope.options.includeTemplateVars) {
        templateSrv.fillVariableValuesForUrl(params);
      }

      if (!$scope.options.forCurrent) {
        delete params.from;
        delete params.to;
      }

      if ($scope.options.theme !== 'current') {
        params.theme = $scope.options.theme;
      }

      if ($scope.modeSharePanel) {
        params.panelId = $scope.panel.id;
        params.fullscreen = true;
      } else {
        delete params.panelId;
        delete params.fullscreen;
      }

      $scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);

      var soloUrl = $scope.shareUrl;
      soloUrl = soloUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
      soloUrl = soloUrl.replace("&fullscreen", "").replace("&edit", "");

      $scope.iframeHtml = '';

      $scope.imageUrl = soloUrl.replace(config.appSubUrl + '/dashboard-solo/', config.appSubUrl + '/render/dashboard-solo/');
      $scope.imageUrl += '&width=1000';
      $scope.imageUrl += '&height=500';
      $scope.imageUrl += '&tz=UTC' + encodeURIComponent(moment().format("Z"));
    };

  });

  module.directive('clipboardButton',function() {
    return function(scope, elem) {
      require(['vendor/clipboard/dist/clipboard'], function(Clipboard) {
        scope.clipboard = new Clipboard(elem[0]);
      });

      scope.$on('$destroy', function() {
        if (scope.clipboard) {
          scope.clipboard.destroy();
        }
      });
    };
  });

});
<commit_msg>sharing: set orgId as query parameter when sharing a dashboard<commit_after>define(['angular',
  'lodash',
  'jquery',
  'moment',
  'require',
  'app/core/config',
],
function (angular, _, $, moment, require, config) {
  'use strict';

  var module = angular.module('grafana.controllers');

  module.controller('ShareModalCtrl', function($scope, $rootScope, $location, $timeout, timeSrv, templateSrv, linkSrv) {

    $scope.options = { forCurrent: true, includeTemplateVars: true, theme: 'current' };
    $scope.editor = { index: $scope.tabIndex || 0};

    $scope.init = function() {
      $scope.modeSharePanel = $scope.panel ? true : false;

      $scope.tabs = [{title: 'Link', src: 'shareLink.html'}];

      if ($scope.modeSharePanel) {
        $scope.modalTitle = 'Share Panel';
        $scope.tabs.push({title: 'Embed', src: 'shareEmbed.html'});
      } else {
        $scope.modalTitle = 'Share';
      }

      if (!$scope.dashboard.meta.isSnapshot) {
        $scope.tabs.push({title: 'Snapshot', src: 'shareSnapshot.html'});
      }

      if (!$scope.dashboard.meta.isSnapshot && !$scope.modeSharePanel) {
        $scope.tabs.push({title: 'Export', src: 'shareExport.html'});
      }

      $scope.buildUrl();
    };

    $scope.buildUrl = function() {
      var baseUrl = $location.absUrl();
      var queryStart = baseUrl.indexOf('?');

      if (queryStart !== -1) {
        baseUrl = baseUrl.substring(0, queryStart);
      }

      var params = angular.copy($location.search());

      var range = timeSrv.timeRange();
      params.from = range.from.valueOf();
      params.to = range.to.valueOf();
      params.orgId = config.bootData.user.orgId;

      if ($scope.options.includeTemplateVars) {
        templateSrv.fillVariableValuesForUrl(params);
      }

      if (!$scope.options.forCurrent) {
        delete params.from;
        delete params.to;
      }

      if ($scope.options.theme !== 'current') {
        params.theme = $scope.options.theme;
      }

      if ($scope.modeSharePanel) {
        params.panelId = $scope.panel.id;
        params.fullscreen = true;
      } else {
        delete params.panelId;
        delete params.fullscreen;
      }

      $scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);

      var soloUrl = $scope.shareUrl;
      soloUrl = soloUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
      soloUrl = soloUrl.replace("&fullscreen", "").replace("&edit", "");

      $scope.iframeHtml = '';

      $scope.imageUrl = soloUrl.replace(config.appSubUrl + '/dashboard-solo/', config.appSubUrl + '/render/dashboard-solo/');
      $scope.imageUrl += '&width=1000';
      $scope.imageUrl += '&height=500';
      $scope.imageUrl += '&tz=UTC' + encodeURIComponent(moment().format("Z"));
    };

  });

  module.directive('clipboardButton',function() {
    return function(scope, elem) {
      require(['vendor/clipboard/dist/clipboard'], function(Clipboard) {
        scope.clipboard = new Clipboard(elem[0]);
      });

      scope.$on('$destroy', function() {
        if (scope.clipboard) {
          scope.clipboard.destroy();
        }
      });
    };
  });

});
<|endoftext|>


<commit_before>// jazz.js

var chord = require('./chord');
var note = require('./note');
var interval = require('./interval');
var _ = require('underscore');

// Map for roman numerals
var romanNumeral = [null, 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII'];

// Map for Mehegan qualities
var meheganQuality = [null, 'M', 'm', 'm', 'M', 'x', 'm', 'ø'];

var tonicChords = [
  'IM', 'Im', 'Ix', 'Iø',
  'IIIM', 'IIIm', 'IIIx', 'IIIø',
  'bIIIM', 'bIIIm', 'bIIIx', 'bIIIø',
  'VIM', 'VIm', 'VIx', 'VIø',
  'bVIM', 'bVIm', 'bVIx', 'bVIø',
];
var subdominantChords = [
  'IIM', 'IIm', 'IIx', 'IIø',
  'IVM', 'IVm', 'IVx', 'IVø',
  'VIM', 'VIm', 'VIx', 'VIø',
  'bVIM', 'bVIm', 'bVIx', 'bVIø'
];
var dominantChords = ['IIIx', 'bIIIx', 'Vx', 'VIIx', 'bVIIx'];

// Given a chord, return the Mehegan quality
var getQuality = function (ch) {
  if (ch.hasInterval('M3')) {
    return ch.hasInterval('m7') ? 'x' : 'M';
  }

  if (ch.hasInterval('m3')) {
    if (ch.hasInterval('dim5')) {
      return ch.hasInterval('m7') ? 'ø' : 'o';
    }

    return 'm';
  }

  return null;
};

// Given a key and a note, return a roman numeral representing the note
var getNumeral = function (key, n) {
  var int;
  var numeral;

  try {
    int = key.getInterval(n);
  } catch (err) {
    int = key.getInterval(n.clean());
  }

  // Although dim7, for example, is a valid interval, we don't want a bbVII in our symbol
  if (!interval.isPerfect(int.number) && int.quality === 'dim') {
    int = interval.create(int.number - 1, 'M');
  }

  numeral = romanNumeral[int.number];

  if (int.quality === 'm' || int.quality === 'dim') numeral = 'b' + numeral;
  if (int.quality === 'aug') numeral = '#' + numeral;

  return numeral;
};

// Given a roman numeral and an alteration (b or #), return the number of half steps
var getHalfSteps = function (numeral, alteration) {
  var halfSteps = interval.parse(_.indexOf(romanNumeral, numeral).toString()).halfSteps();

  if (alteration === 'b') halfSteps -= 1;
  if (alteration === '#') halfSteps += 1;

  return halfSteps;
};

// Given a roman numeral, infer the Mehegan chord qualitiy
var inferQuality = function (numeral) {
  return meheganQuality[_.indexOf(romanNumeral, numeral)];
}

// A symbol that is used in the jazz automaton.  Each symbol can be thought of as a roman numeral
// representing a chord using the Mehegan system.
var Symbol = function (interval, quality, numeral) {
  // The number of half-steps between the key and the chord
  // e.g. I => 0, bIV => 6
  this.interval = interval;

  // Chord quality: M, m, x, o, ø
  this.quality = quality;
  if (!_.contains(['M', 'm', 'x', 'o', 'ø'], quality)) {
    throw new Error('Invalid chord quality');
  }

  // Roman numeral representing chord
  this.numeral = numeral;

  this.toString = function () {
    return this.numeral + this.quality;
  };
}

// Create a symbol given a key and a chord
var symbolFromChord = function (key, ch) {
  key = note.create(key);
  ch = chord.create(ch);

  return new Symbol(key.getHalfSteps(ch.root), getQuality(ch), getNumeral(key, ch.root));
};

// Create a symbol given a Mehegan chord
var symbolFromMehegan = function (mehegan) {
  var matches = mehegan.match(/([b#]?)([iIvV]+)([mMxoø]?)/);
  var alteration;
  var numeral;
  var quality;

  if (!matches) {
    throw new Error('Invalid Mehegan symbol');
  }

  alteration = matches[1];
  numeral = matches[2].toUpperCase();
  quality = matches[3];

  if (!quality) quality = inferQuality(numeral);

  return new Symbol(getHalfSteps(numeral, alteration), quality, alteration + numeral);
};

// Test for equality with other symbols
Symbol.prototype.eq = function (symbol) {
  return this.interval === symbol.interval && this.quality === symbol.quality;
};

// Return the corresponding interval, e.g., bIII -> m3
Symbol.prototype.toInterval = function () {
  var matches = this.numeral.match(/([b#]?)([iIvV]+)/);
  var alteration = matches[1];
  var numeral = matches[2];
  var intervalNumber = _.indexOf(romanNumeral, numeral);

  var intervalQuality = interval.isPerfect(intervalNumber) ? 'P' : 'M';

  if (alteration === 'b') {
    intervalQuality = interval.isPerfect(intervalNumber) ? 'dim' : 'm';
  }
  if (alteration === '#') {
    intervalQuality = 'aug';
  }

  return interval.create(intervalNumber, intervalQuality);
};

var JzState = function (name) {
  this.name = name;
  this.transitions = [];
  this.sources = [];
};

JzState.prototype.addTransition = function (symbol, state) {
  this.transitions.push({symbol: symbol, state: state});
  state.sources.push({symbol: symbol, state: this});
};

JzState.prototype.addSource = function (symbol, state) {
  this.sources.push({symbol: symbol, state: state});
  state.transitions.push({symbol: symbol, state: this});
};

JzState.prototype.hasTransition = function (symbol, state) {
  return _.some(this.transitions, function (transition) {
    return transition.symbol.eq(symbol) && transition.state === state;
  });
};

JzState.prototype.hasSource = function (symbol, state) {
  return _.some(this.sources, function (source) {
    return source.symbol.eq(symbol) && source.state === state;
  });
};

JzState.prototype.getTransitionStatesBySymbol = function (symbol) {
  return _.chain(this.transitions)
    .filter(function (transition) {
      return transition.symbol.eq(symbol);
    })
    .pluck('state')
    .value();
};

JzState.prototype.getSourceStatesBySymbol = function (symbol) {
  return _.chain(this.sources)
    .filter(function (source) {
      return source.symbol.eq(symbol);
    })
    .pluck('state')
    .value();
};

var JzA = function () {
  this.states = [];
};

JzA.prototype.addState = function (name) {
  var state = new JzState(name);
  this.states.push(state);
  return state;
};

JzA.prototype.addTransition = function (symbol, from, to) {
  from.addTransition(symbol, to);
};

JzA.prototype.getTransitions = function () {
  return _.chain(this.states)
  .map(function (state) {
    return _.map(state.transitions, function (transition) {
      return {symbol: transition.symbol, from: state, to: transition.state};
    });
  })
  .flatten()
  .value();
};

JzA.prototype.getTransitionsBySymbol = function (symbol) {
  return _.filter(this.getTransitions(), function (t) {
    return t.symbol.eq(symbol);
  })
};

JzA.prototype.getStatesByName = function (name) {
  return _.filter(this.states, function (state) {
    return state.name.toLowerCase() === name.toLowerCase();
  });
};

JzA.prototype.getStateByName = function (name) {
  return _.first(this.getStatesByName(name));
};

JzA.prototype.analyze = function (symbols) {
  // We are going to keep track of all possible pathways through the automaton
  // Since any state can be an "initial" state, start by finding all transitions for the first symbol
  var pathways = _.chain(this.getTransitionsBySymbol(_.first(symbols)))
    .map(function (transition) {
      return transition.to;
    })
    .uniq() // Eliminate duplicate first states
    .map(function (state) { // Do this after the elimination because [x] !== [x]
      return [state];
    })
    .value();
  var oldPathways;
  var nextStates;

  // With each symbol, we will construct a new list of valid pathways and then iterate
  // With each iteration, some pathways will be eliminated and others may split into multiple possibilities
  _.each(_.rest(symbols), function (symbol) {
    oldPathways = pathways;
    pathways = [];

    _.each(oldPathways, function (pathway) {
      nextStates = _.last(pathway).getTransitionStatesBySymbol(symbol);

      // If we're at a dead end, don't add this pathway
      // Otherwise, add all possible pathways we can take
      if (nextStates.length) {
        _.each(nextStates, function (nextState) {
          pathways.push(pathway.concat([nextState]));
        });
      }
    });
  });

  return pathways;
};

JzA.prototype.validate = function (symbols) {
  return this.analyze(symbols).length > 0;
};

var addPrimitiveChords = function (tonic, subdominant, dominant) {
  _.each(tonicChords, function (sym) {
    dominant.addTransition(symbolFromMehegan(sym), tonic);
    tonic.addTransition(symbolFromMehegan(sym), tonic);
  });

  _.each(subdominantChords, function (sym) {
    tonic.addTransition(symbolFromMehegan(sym), subdominant);
    subdominant.addTransition(symbolFromMehegan(sym), subdominant);
  });

  _.each(dominantChords, function (sym) {
    subdominant.addTransition(symbolFromMehegan(sym), dominant);
    dominant.addTransition(symbolFromMehegan(sym), dominant);
  });
};

var constructDefaultJzA = function (jza) {
  var tonic = jza.addState('Tonic');
  var subdominant = jza.addState('Subdominant');
  var dominant = jza.addState('Dominant');

  addPrimitiveChords(tonic, subdominant, dominant);

  return jza;
};

module.exports.symbolFromChord = symbolFromChord;
module.exports.symbolFromMehegan = symbolFromMehegan;
module.exports.symbolsFromChord = _.partial(_.map, _, symbolFromChord);
module.exports.symbolsFromMehegan = _.partial(_.map, _, symbolFromMehegan);

module.exports.jza = function (type) {
  var jza = new JzA();

  if (type !== 'empty') {
    constructDefaultJzA(jza);
  }

  return jza;
};
<commit_msg>Added missing semicolons<commit_after>// jazz.js

var chord = require('./chord');
var note = require('./note');
var interval = require('./interval');
var _ = require('underscore');

// Map for roman numerals
var romanNumeral = [null, 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII'];

// Map for Mehegan qualities
var meheganQuality = [null, 'M', 'm', 'm', 'M', 'x', 'm', 'ø'];

var tonicChords = [
  'IM', 'Im', 'Ix', 'Iø',
  'IIIM', 'IIIm', 'IIIx', 'IIIø',
  'bIIIM', 'bIIIm', 'bIIIx', 'bIIIø',
  'VIM', 'VIm', 'VIx', 'VIø',
  'bVIM', 'bVIm', 'bVIx', 'bVIø',
];
var subdominantChords = [
  'IIM', 'IIm', 'IIx', 'IIø',
  'IVM', 'IVm', 'IVx', 'IVø',
  'VIM', 'VIm', 'VIx', 'VIø',
  'bVIM', 'bVIm', 'bVIx', 'bVIø'
];
var dominantChords = ['IIIx', 'bIIIx', 'Vx', 'VIIx', 'bVIIx'];

// Given a chord, return the Mehegan quality
var getQuality = function (ch) {
  if (ch.hasInterval('M3')) {
    return ch.hasInterval('m7') ? 'x' : 'M';
  }

  if (ch.hasInterval('m3')) {
    if (ch.hasInterval('dim5')) {
      return ch.hasInterval('m7') ? 'ø' : 'o';
    }

    return 'm';
  }

  return null;
};

// Given a key and a note, return a roman numeral representing the note
var getNumeral = function (key, n) {
  var int;
  var numeral;

  try {
    int = key.getInterval(n);
  } catch (err) {
    int = key.getInterval(n.clean());
  }

  // Although dim7, for example, is a valid interval, we don't want a bbVII in our symbol
  if (!interval.isPerfect(int.number) && int.quality === 'dim') {
    int = interval.create(int.number - 1, 'M');
  }

  numeral = romanNumeral[int.number];

  if (int.quality === 'm' || int.quality === 'dim') numeral = 'b' + numeral;
  if (int.quality === 'aug') numeral = '#' + numeral;

  return numeral;
};

// Given a roman numeral and an alteration (b or #), return the number of half steps
var getHalfSteps = function (numeral, alteration) {
  var halfSteps = interval.parse(_.indexOf(romanNumeral, numeral).toString()).halfSteps();

  if (alteration === 'b') halfSteps -= 1;
  if (alteration === '#') halfSteps += 1;

  return halfSteps;
};

// Given a roman numeral, infer the Mehegan chord qualitiy
var inferQuality = function (numeral) {
  return meheganQuality[_.indexOf(romanNumeral, numeral)];
};

// A symbol that is used in the jazz automaton.  Each symbol can be thought of as a roman numeral
// representing a chord using the Mehegan system.
var Symbol = function (interval, quality, numeral) {
  // The number of half-steps between the key and the chord
  // e.g. I => 0, bIV => 6
  this.interval = interval;

  // Chord quality: M, m, x, o, ø
  this.quality = quality;
  if (!_.contains(['M', 'm', 'x', 'o', 'ø'], quality)) {
    throw new Error('Invalid chord quality');
  }

  // Roman numeral representing chord
  this.numeral = numeral;

  this.toString = function () {
    return this.numeral + this.quality;
  };
};

// Create a symbol given a key and a chord
var symbolFromChord = function (key, ch) {
  key = note.create(key);
  ch = chord.create(ch);

  return new Symbol(key.getHalfSteps(ch.root), getQuality(ch), getNumeral(key, ch.root));
};

// Create a symbol given a Mehegan chord
var symbolFromMehegan = function (mehegan) {
  var matches = mehegan.match(/([b#]?)([iIvV]+)([mMxoø]?)/);
  var alteration;
  var numeral;
  var quality;

  if (!matches) {
    throw new Error('Invalid Mehegan symbol');
  }

  alteration = matches[1];
  numeral = matches[2].toUpperCase();
  quality = matches[3];

  if (!quality) quality = inferQuality(numeral);

  return new Symbol(getHalfSteps(numeral, alteration), quality, alteration + numeral);
};

// Test for equality with other symbols
Symbol.prototype.eq = function (symbol) {
  return this.interval === symbol.interval && this.quality === symbol.quality;
};

// Return the corresponding interval, e.g., bIII -> m3
Symbol.prototype.toInterval = function () {
  var matches = this.numeral.match(/([b#]?)([iIvV]+)/);
  var alteration = matches[1];
  var numeral = matches[2];
  var intervalNumber = _.indexOf(romanNumeral, numeral);

  var intervalQuality = interval.isPerfect(intervalNumber) ? 'P' : 'M';

  if (alteration === 'b') {
    intervalQuality = interval.isPerfect(intervalNumber) ? 'dim' : 'm';
  }
  if (alteration === '#') {
    intervalQuality = 'aug';
  }

  return interval.create(intervalNumber, intervalQuality);
};

var JzState = function (name) {
  this.name = name;
  this.transitions = [];
  this.sources = [];
};

JzState.prototype.addTransition = function (symbol, state) {
  this.transitions.push({symbol: symbol, state: state});
  state.sources.push({symbol: symbol, state: this});
};

JzState.prototype.addSource = function (symbol, state) {
  this.sources.push({symbol: symbol, state: state});
  state.transitions.push({symbol: symbol, state: this});
};

JzState.prototype.hasTransition = function (symbol, state) {
  return _.some(this.transitions, function (transition) {
    return transition.symbol.eq(symbol) && transition.state === state;
  });
};

JzState.prototype.hasSource = function (symbol, state) {
  return _.some(this.sources, function (source) {
    return source.symbol.eq(symbol) && source.state === state;
  });
};

JzState.prototype.getTransitionStatesBySymbol = function (symbol) {
  return _.chain(this.transitions)
    .filter(function (transition) {
      return transition.symbol.eq(symbol);
    })
    .pluck('state')
    .value();
};

JzState.prototype.getSourceStatesBySymbol = function (symbol) {
  return _.chain(this.sources)
    .filter(function (source) {
      return source.symbol.eq(symbol);
    })
    .pluck('state')
    .value();
};

var JzA = function () {
  this.states = [];
};

JzA.prototype.addState = function (name) {
  var state = new JzState(name);
  this.states.push(state);
  return state;
};

JzA.prototype.addTransition = function (symbol, from, to) {
  from.addTransition(symbol, to);
};

JzA.prototype.getTransitions = function () {
  return _.chain(this.states)
  .map(function (state) {
    return _.map(state.transitions, function (transition) {
      return {symbol: transition.symbol, from: state, to: transition.state};
    });
  })
  .flatten()
  .value();
};

JzA.prototype.getTransitionsBySymbol = function (symbol) {
  return _.filter(this.getTransitions(), function (t) {
    return t.symbol.eq(symbol);
  });
};

JzA.prototype.getStatesByName = function (name) {
  return _.filter(this.states, function (state) {
    return state.name.toLowerCase() === name.toLowerCase();
  });
};

JzA.prototype.getStateByName = function (name) {
  return _.first(this.getStatesByName(name));
};

JzA.prototype.analyze = function (symbols) {
  // We are going to keep track of all possible pathways through the automaton
  // Since any state can be an "initial" state, start by finding all transitions for the first symbol
  var pathways = _.chain(this.getTransitionsBySymbol(_.first(symbols)))
    .map(function (transition) {
      return transition.to;
    })
    .uniq() // Eliminate duplicate first states
    .map(function (state) { // Do this after the elimination because [x] !== [x]
      return [state];
    })
    .value();
  var oldPathways;
  var nextStates;

  // With each symbol, we will construct a new list of valid pathways and then iterate
  // With each iteration, some pathways will be eliminated and others may split into multiple possibilities
  _.each(_.rest(symbols), function (symbol) {
    oldPathways = pathways;
    pathways = [];

    _.each(oldPathways, function (pathway) {
      nextStates = _.last(pathway).getTransitionStatesBySymbol(symbol);

      // If we're at a dead end, don't add this pathway
      // Otherwise, add all possible pathways we can take
      if (nextStates.length) {
        _.each(nextStates, function (nextState) {
          pathways.push(pathway.concat([nextState]));
        });
      }
    });
  });

  return pathways;
};

JzA.prototype.validate = function (symbols) {
  return this.analyze(symbols).length > 0;
};

var addPrimitiveChords = function (tonic, subdominant, dominant) {
  _.each(tonicChords, function (sym) {
    dominant.addTransition(symbolFromMehegan(sym), tonic);
    tonic.addTransition(symbolFromMehegan(sym), tonic);
  });

  _.each(subdominantChords, function (sym) {
    tonic.addTransition(symbolFromMehegan(sym), subdominant);
    subdominant.addTransition(symbolFromMehegan(sym), subdominant);
  });

  _.each(dominantChords, function (sym) {
    subdominant.addTransition(symbolFromMehegan(sym), dominant);
    dominant.addTransition(symbolFromMehegan(sym), dominant);
  });
};

var constructDefaultJzA = function (jza) {
  var tonic = jza.addState('Tonic');
  var subdominant = jza.addState('Subdominant');
  var dominant = jza.addState('Dominant');

  addPrimitiveChords(tonic, subdominant, dominant);

  return jza;
};

module.exports.symbolFromChord = symbolFromChord;
module.exports.symbolFromMehegan = symbolFromMehegan;
module.exports.symbolsFromChord = _.partial(_.map, _, symbolFromChord);
module.exports.symbolsFromMehegan = _.partial(_.map, _, symbolFromMehegan);

module.exports.jza = function (type) {
  var jza = new JzA();

  if (type !== 'empty') {
    constructDefaultJzA(jza);
  }

  return jza;
};
<|endoftext|>