<commit_before>const GL_RESERVED_USER_NAMES = [
  'u', 'dashboard', 'projects', 'users', 'help',
  'explore', 'profile', 'public', 'groups', 'abuse_reports'
]
const GL_RESERVED_REPO_NAMES = []
const GL_RESERVED_TYPES = ['raw']
const GL_HEADER = '.navbar-gitlab'
const GL_SIDEBAR = '.sidebar-wrapper'
const GL_TITLE = 'h1.title'
const GL_CONTENT = '.container>.content'
const GL_MAIN_NAV = '.main-nav'

class GitLab extends Adapter {

  constructor(store) {
    super(['turbolinks.js'])

    // GitLab (for now) embeds access token in the page of a logged-in user.
    // Use it to set the token if one isn't available.
    const token = store.get(STORE.TOKEN)
    if (!token) {
      const match = $('head').text().match(/gon.api_token\s*=\s*"(.*?)"/m)
      if (match && match[1]) {
        store.set(STORE.TOKEN, match[1])
      }
    }
  }

  // @override
  init($sidebar) {
    super.init($sidebar)

    // Trigger layout when the GL sidebar is toggled
    $('.toggle-nav-collapse').click(() => {
      setTimeout(() => {
        $(document).trigger(EVENT.LAYOUT_CHANGE)
      }, 10)
    })

    // GitLab disables our submit buttons, re-enable them
    $('.octotree_view_body button[type="submit"]').click((event) => {
      setTimeout(() => {
        $(event.target).prop('disabled', false).removeClass('disabled')
      }, 100)
    })

    // Reuse GitLab styles for inputs
    $('.octotree_view_body input[type="text"], .octotree_view_body textarea')
      .addClass('form-control')

    // GitLab uses Turbolinks to handle page load
    $(document)
      .on('page:fetch', () => $(document).trigger(EVENT.REQ_START))
      .on('page:load', () => {

        // GitLab removes DOM, let's add back
        $sidebar.appendTo('body')

        // Trigger location change since the new page might not a repo page
        $(document).trigger(EVENT.LOC_CHANGE)
        $(document).trigger(EVENT.REQ_END)
      })
  }

  // @override
  getCssClass() {
    return 'octotree_gitlab_sidebar'
  }

  // @override
  getMinWidth() {
    return 220 // just enough to hide the GitLab sidebar
  }

  // @override
  getCreateTokenUrl() {
    return `${location.protocol}//${location.host}/profile/personal_access_tokens`
  }

  // @override
  updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
    const isNewDesign = $('.navbar-gitlab.header-collapsed, .navbar-gitlab.header-expanded').length > 0
    const glSidebarExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded') && $(document).width() > 1199

    if (isNewDesign) {
      const glSidebarWidth = glSidebarExpanded ? 220 : 62
      $(GL_TITLE).css('margin-left',  (!togglerVisible || sidebarVisible) ? '' : 36)
      $('.octotree_toggle').css('right', sidebarVisible ? '' : -(glSidebarWidth + 44))
      $(GL_HEADER).css({'margin-left': sidebarVisible ? sidebarWidth : ''})
      $('.page-with-sidebar').css('padding-left', sidebarVisible ? sidebarWidth : '')
    }
    else {
      $(GL_HEADER).css('z-index', 3)
      $(GL_SIDEBAR).css('z-index', 1)
      $(GL_TITLE).css('margin-left',  sidebarVisible ? '' : 56)
      $('.octotree_toggle').css({
        right: sidebarVisible ? '' : -102,
        top: sidebarVisible ? '' : 0
      })

      // Deals with GitLab 7.5
      $(GL_CONTENT).parent().add(GL_MAIN_NAV).css('margin-left', sidebarVisible ? 230 : '')
    }
  }

  // @override
  getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {

    // 404 page, skip - GitLab doesn't have specific element for Not Found page
    if ($(document).find('title').text() === 'The page you\'re looking for could not be found (404)') {
      return cb()
    }

    // (username)/(reponame)[/(type)]
    const match = window.location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/)
    if (!match) {
      return cb()
    }

    const username = match[1]
    const reponame = match[2]
    const type = match[3]

    // Not a repository, skip
    if (~GL_RESERVED_USER_NAMES.indexOf(username) ||
        ~GL_RESERVED_REPO_NAMES.indexOf(reponame) ||
        ~GL_RESERVED_TYPES.indexOf(type)) {
      return cb()
    }

    // Skip non-code page unless showInNonCodePage is true
    // with GitLab /username/repo is non-code page
    if (!showInNonCodePage &&
      (!match[3] || (match[3] && !~['tree', 'blob'].indexOf(match[3])))) {
      return cb()
    }

    // Get branch by inspecting page, quite fragile so provide multiple fallbacks
    const GL_BRANCH_SEL_1 = '#repository_ref'
    const GL_BRANCH_SEL_2 = '.select2-container.project-refs-select.select2 .select2-chosen'
    // .nav.nav-sidebar is for versions below 8.8
    const GL_BRANCH_SEL_3 = '.nav.nav-sidebar .shortcuts-tree, .nav-links .shortcuts-tree'

    const branch =
      // Code page
      $(GL_BRANCH_SEL_1).val() || $(GL_BRANCH_SEL_2).text() ||
      // Non-code page
      // A space ' ' is a failover to make match() always return an array
      ($(GL_BRANCH_SEL_3).attr('href') || ' ').match(/([^\/]+)/g)[3] ||
      // Assume same with previously
      (currentRepo.username === username && currentRepo.reponame === reponame && currentRepo.branch) ||
      // Default from cache
      this._defaultBranch[username + '/' + reponame]

    const repo = {username: username, reponame: reponame, branch: branch}

    if (repo.branch) {
      cb(null, repo)
    }
    else {
      this._get(null, {token}, (err, data) => {
        if (err) return cb(err)
        repo.branch = this._defaultBranch[username + '/' + reponame] = data.default_branch || 'master'
        cb(null, repo)
      })
    }
  }

  // @override
  selectFile(path) {
    Turbolinks.visit(path)
  }

  // @override
  loadCodeTree(opts, cb) {
    opts.path = opts.node.path
    this._loadCodeTree(opts, (item) => {
      item.sha = item.id
      item.path = item.name
    }, cb)
  }

  // @override
  _getTree(path, opts, cb) {
    this._get(`/tree?path=${path}&ref_name=${opts.encodedBranch}`, opts, cb)
  }

  // @override
  _getSubmodules(tree, opts, cb) {
    const item = tree.filter((item) => /^\.gitmodules$/i.test(item.name))[0]
    if (!item) return cb()

    this._get(`/blobs/${opts.encodedBranch}?filepath=${item.name}`, opts, (err, data) => {
      if (err) return cb(err)
      cb(null, parseGitmodules(data))
    })
  }

  _get(path, opts, cb) {
    const repo = opts.repo
    const host = `${location.protocol}//${location.host}/api/v3`
    const project = $('#search_project_id').val() || $('#project_id').val() || `${repo.username}%2f${repo.reponame}`
    const url = `${host}/projects/${project}/repository${path}&private_token=${opts.token}`
    const cfg = { url, method: 'GET', cache: false }

    $.ajax(cfg)
      .done((data) => cb(null, data))
      .fail((jqXHR) => this._handleError(jqXHR, cb))
  }
}
<commit_msg>Fix Open-button position on GitLab. #319 (#320)<commit_after>const GL_RESERVED_USER_NAMES = [
  'u', 'dashboard', 'projects', 'users', 'help',
  'explore', 'profile', 'public', 'groups', 'abuse_reports'
]
const GL_RESERVED_REPO_NAMES = []
const GL_RESERVED_TYPES = ['raw']
const GL_HEADER = '.navbar-gitlab'
const GL_SIDEBAR = '.sidebar-wrapper'
const GL_TITLE = 'h1.title'
const GL_CONTENT = '.container>.content'
const GL_MAIN_NAV = '.main-nav'

class GitLab extends Adapter {

  constructor(store) {
    super(['turbolinks.js'])

    // GitLab (for now) embeds access token in the page of a logged-in user.
    // Use it to set the token if one isn't available.
    const token = store.get(STORE.TOKEN)
    if (!token) {
      const match = $('head').text().match(/gon.api_token\s*=\s*"(.*?)"/m)
      if (match && match[1]) {
        store.set(STORE.TOKEN, match[1])
      }
    }
  }

  // @override
  init($sidebar) {
    super.init($sidebar)

    // Trigger layout when the GL sidebar is toggled
    $('.toggle-nav-collapse').click(() => {
      setTimeout(() => {
        $(document).trigger(EVENT.LAYOUT_CHANGE)
      }, 10)
    })

    // GitLab disables our submit buttons, re-enable them
    $('.octotree_view_body button[type="submit"]').click((event) => {
      setTimeout(() => {
        $(event.target).prop('disabled', false).removeClass('disabled')
      }, 100)
    })

    // Reuse GitLab styles for inputs
    $('.octotree_view_body input[type="text"], .octotree_view_body textarea')
      .addClass('form-control')

    // GitLab uses Turbolinks to handle page load
    $(document)
      .on('page:fetch', () => $(document).trigger(EVENT.REQ_START))
      .on('page:load', () => {

        // GitLab removes DOM, let's add back
        $sidebar.appendTo('body')

        // Trigger location change since the new page might not a repo page
        $(document).trigger(EVENT.LOC_CHANGE)
        $(document).trigger(EVENT.REQ_END)
      })
  }

  // @override
  getCssClass() {
    return 'octotree_gitlab_sidebar'
  }

  // @override
  getMinWidth() {
    return 220 // just enough to hide the GitLab sidebar
  }

  // @override
  getCreateTokenUrl() {
    return `${location.protocol}//${location.host}/profile/personal_access_tokens`
  }

  // @override
  updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
    const isNewDesign = $('.navbar-gitlab.header-collapsed, .navbar-gitlab.header-expanded').length > 0
    const glSidebarExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded') && $(document).width() > 1199

    if (isNewDesign) {
      const glSidebarWidth = glSidebarExpanded ? 220 : 62
      $(GL_TITLE).css('margin-left',  (!togglerVisible || sidebarVisible) ? '' : 36)
      $('.octotree_toggle').css('right', sidebarVisible ? '' : -(glSidebarWidth + 13))
      $(GL_HEADER).css({'margin-left': sidebarVisible ? sidebarWidth : ''})
      $('.page-with-sidebar').css('padding-left', sidebarVisible ? sidebarWidth : '')
    }
    else {
      $(GL_HEADER).css('z-index', 3)
      $(GL_SIDEBAR).css('z-index', 1)
      $(GL_TITLE).css('margin-left',  sidebarVisible ? '' : 56)
      $('.octotree_toggle').css({
        right: sidebarVisible ? '' : -102,
        top: sidebarVisible ? '' : 0
      })

      // Deals with GitLab 7.5
      $(GL_CONTENT).parent().add(GL_MAIN_NAV).css('margin-left', sidebarVisible ? 230 : '')
    }
  }

  // @override
  getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {

    // 404 page, skip - GitLab doesn't have specific element for Not Found page
    if ($(document).find('title').text() === 'The page you\'re looking for could not be found (404)') {
      return cb()
    }

    // (username)/(reponame)[/(type)]
    const match = window.location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/)
    if (!match) {
      return cb()
    }

    const username = match[1]
    const reponame = match[2]
    const type = match[3]

    // Not a repository, skip
    if (~GL_RESERVED_USER_NAMES.indexOf(username) ||
        ~GL_RESERVED_REPO_NAMES.indexOf(reponame) ||
        ~GL_RESERVED_TYPES.indexOf(type)) {
      return cb()
    }

    // Skip non-code page unless showInNonCodePage is true
    // with GitLab /username/repo is non-code page
    if (!showInNonCodePage &&
      (!match[3] || (match[3] && !~['tree', 'blob'].indexOf(match[3])))) {
      return cb()
    }

    // Get branch by inspecting page, quite fragile so provide multiple fallbacks
    const GL_BRANCH_SEL_1 = '#repository_ref'
    const GL_BRANCH_SEL_2 = '.select2-container.project-refs-select.select2 .select2-chosen'
    // .nav.nav-sidebar is for versions below 8.8
    const GL_BRANCH_SEL_3 = '.nav.nav-sidebar .shortcuts-tree, .nav-links .shortcuts-tree'

    const branch =
      // Code page
      $(GL_BRANCH_SEL_1).val() || $(GL_BRANCH_SEL_2).text() ||
      // Non-code page
      // A space ' ' is a failover to make match() always return an array
      ($(GL_BRANCH_SEL_3).attr('href') || ' ').match(/([^\/]+)/g)[3] ||
      // Assume same with previously
      (currentRepo.username === username && currentRepo.reponame === reponame && currentRepo.branch) ||
      // Default from cache
      this._defaultBranch[username + '/' + reponame]

    const repo = {username: username, reponame: reponame, branch: branch}

    if (repo.branch) {
      cb(null, repo)
    }
    else {
      this._get(null, {token}, (err, data) => {
        if (err) return cb(err)
        repo.branch = this._defaultBranch[username + '/' + reponame] = data.default_branch || 'master'
        cb(null, repo)
      })
    }
  }

  // @override
  selectFile(path) {
    Turbolinks.visit(path)
  }

  // @override
  loadCodeTree(opts, cb) {
    opts.path = opts.node.path
    this._loadCodeTree(opts, (item) => {
      item.sha = item.id
      item.path = item.name
    }, cb)
  }

  // @override
  _getTree(path, opts, cb) {
    this._get(`/tree?path=${path}&ref_name=${opts.encodedBranch}`, opts, cb)
  }

  // @override
  _getSubmodules(tree, opts, cb) {
    const item = tree.filter((item) => /^\.gitmodules$/i.test(item.name))[0]
    if (!item) return cb()

    this._get(`/blobs/${opts.encodedBranch}?filepath=${item.name}`, opts, (err, data) => {
      if (err) return cb(err)
      cb(null, parseGitmodules(data))
    })
  }

  _get(path, opts, cb) {
    const repo = opts.repo
    const host = `${location.protocol}//${location.host}/api/v3`
    const project = $('#search_project_id').val() || $('#project_id').val() || `${repo.username}%2f${repo.reponame}`
    const url = `${host}/projects/${project}/repository${path}&private_token=${opts.token}`
    const cfg = { url, method: 'GET', cache: false }

    $.ajax(cfg)
      .done((data) => cb(null, data))
      .fail((jqXHR) => this._handleError(jqXHR, cb))
  }
}
<|endoftext|>


<commit_before>var renderers = {
    "CAMERA_CONTROL": drawWorld,
    "UNIT_CONTROL": drawWorld,
    "MENU_CONTROL" : drawMenu
}

var specialRenderer;
function SpecialRenderer() {
    function addLayer(key, renderfunc, renderable) {
        renderers[key] = renderfunc.bind(null, renderable);
    }

    function removeLayer(key) {
        delete renderers[key];
    }

    function render() {
        for (var renderer in renderers) {
            if (renderers.hasOwnProperty(renderer)) {
                renderers[renderer]();
            }
        }
    }

    var renderers = {};

    this.addLayer = function(key, renderfunc, renderable) {
        addLayer(key, renderfunc, renderable);
    }

    this.removeLayer = function(key) {
        removeLayer(key);
    }

    this.render = function() {
        render();
    }
}

function initRenderers() {
    specialRenderer = new SpecialRenderer();

    game.selectorCallback = handleSelectorRendering;

    game.pathCallback = handlePathRendering;

    game.movesAvailableCallback = handleMovesRendering;
}

function handleSelectorRendering(selector) {
    specialRenderer.removeLayer("selector");
    sprites.selector.animate = true;
    specialRenderer.addLayer("selector", drawSelector, selector);
}

function handlePathRendering(path) {
    specialRenderer.removeLayer("path");
    specialRenderer.addLayer("path", drawPath, path);
}

function handleMovesRendering(moves) {
    specialRenderer.removeLayer("moves");
    specialRenderer.addLayer("moves", drawMoves, moves);
}

function clearBack() {
    gfx.ctx.fillStyle = "#FFFFFF";
    gfx.ctx.fillRect(0, 0, gfx.width, gfx.height);
}

function drawLine(sx, sy, dx, dy) {
    gfx.ctx.moveTo(sx, sy);
    gfx.ctx.lineTo(dx, dy);
}

function drawMenu() {
    // TODO actually render something for the menu
}

function drawWorld() {
    drawEnvironment(game.world);

    drawUnits(game.world);

    drawGrid(game.world);

    specialRenderer.render();
}

function drawPath(path) {
    var turnTable = Object.freeze({
        LEFTUP: "LEFTUP", RIGHTUP: "RIGHTUP",
        LEFTDOWN: "LEFTDOWN", RIGHTDOWN: "RIGHTDOWN",
		HORIZ: "HORIZ", VERT: "VERT"
    });

    function determineTurn(before, current, after) {
		//  b - c - a
		if (before.position.x + 1 == current.position.x &&
            before.position.y == current.position.y &&
			current.position.x + 1 == after.position.x &&
			current.position.y == after.position.y)
		{
			return turnTable.HORIZ;
		}
		//      b
		//      |
		//      c
		//      |
		//      a
		if (before.position.x == current.position.x &&
            before.position.y + 1 == current.position.y &&
			current.position.x == after.position.x &&
			current.position.y + 1 == after.position.y)
		{
			return turnTable.VERT;
		}
        //      a
        //      |
        //  b - c
        if (before.position.x + 1 == current.position.x &&
            before.position.y == current.position.y &&
            current.position.x == after.position.x &&
            current.position.y - 1 == after.position.y)
        {
            return turnTable.LEFTUP;
        }
        //      b
        //      |
        //      c - a
        if (before.position.x == current.position.x &&
            before.position.y + 1 == current.position.y &&
            current.position.x + 1 == after.position.x &&
            current.position.y == after.position.y)
        {
            return turnTable.RIGHTUP;
        }
        //  b - c
        //      |
        //      a
        if (before.position.x + 1 == current.position.x &&
            before.position.y == current.position.y &&
            current.position.x == after.position.x &&
            current.position.y + 1 == after.position.y)
        {
            return turnTable.LEFTDOWN;
        }
        //      c - a
        //      |
        //      b
        if (before.position.x == current.position.x &&
            before.position.y - 1 == current.position.y &&
            current.position.x + 1 == after.position.x &&
            current.position.y == after.position.y)
        {
            return turnTable.RIGHTDOWN;
        }
    }

    for (var i = 0; i < path.length; i++) {
        if (i == 0) {
            // start path sprite
            gfx.ctx.fillStyle = "#FF0000";
        } else if (i == path.length - 1) {
            // end path sprite
            gfx.ctx.fillStyle = "#00FF00";
        } else {
            var turn1 = determineTurn(path[i - 1], path[i], path[i + 1]);
            var turn2 = determineTurn(path[i + 1], path[i], path[i - 1]);
            var turn = turn1 || turn2;
            switch(turn) {
			    case turnTable.HORIZ:
                    gfx.ctx.fillStyle = "#F0F000";
                    break;
                case turnTable.VERT:
                    gfx.ctx.fillStyle = "#FFFFFF";
                    break;
                case turnTable.LEFTUP:
                    gfx.ctx.fillStyle = "#0F0F00";
                    break;
                case turnTable.RIGHTUP:
                    gfx.ctx.fillStyle = "#0F00F0";
                    break;
                case turnTable.LEFTDOWN:
                    gfx.ctx.fillStyle = "#0F000F";
                    break;
                case turnTable.RIGHTDOWN:
                    gfx.ctx.fillStyle = "#0F0FF0";
                    break;
            }
        }
        var pos = camera.transformToCameraSpace(path[i].position.x, path[i].position.y);
        gfx.ctx.fillRect(pos.cam_x + 25, pos.cam_y + 25, 50, 50);
    }
}

function drawMoves(moves) {
    gfx.ctx.fillStyle = "rgba(0, 0, 150, 0.5)";
    for (var i in moves) {
        var pos = camera.transformToCameraSpace(moves[i].cell.position.x, moves[i].cell.position.y);
        gfx.ctx.fillRect(pos.cam_x, pos.cam_y, 100, 100);
    }
}

function drawEnvironment(world) {
    for (var x = 0; x < world.getWidth(); x++) {
        for (var y = 0; y < world.getHeight(); y++) {
            var current_cell = world.getCell(x, y);
            drawSprite(x, y, current_cell.spriteName)
        }
    }
}

function drawUnits(world) {
    var units = world.getUnits();
    for (var i in units) {
        var position = units[i].pos;
        drawSprite(position['x'], position['y'], units[i].spriteName);
    }
}

function drawSelector(selector) {
    var position = selector.pos;
    drawSprite(position['x'], position['y'], selector.spriteName);
}

function drawSprite(x, y, spriteName) {
    var cam_pos = camera.transformToCameraSpace(x, y);
    if (camera.positionVisible(cam_pos.cam_x, cam_pos.cam_y)) {
        var sprite = assets.spriteManager.getSprite(spriteName)
        var img = assets.get(sprite.url);
        var pos = sprite.getFramePosition();

        var rel_width = sprite.width / assets.spriteManager.minWidth;
        var rel_height = sprite.height / assets.spriteManager.minHeight;
        var cam_size = camera.multZoomFactor(rel_width, rel_height);
        var centered_offset = camera.multZoomFactor(rel_width - 1.0,
                                                    rel_height - 1.0);
        centered_offset.cam_w = centered_offset.cam_w / 2;
        centered_offset.cam_h = centered_offset.cam_h / 2;

        gfx.ctx.drawImage(img,
                          pos['x'], pos['y'],
                          sprite.width, sprite.height,
                          cam_pos.cam_x - centered_offset.cam_w,
                          cam_pos.cam_y - centered_offset.cam_h,
                          cam_size.cam_w, cam_size.cam_h);
    }
}

function drawGrid(world) {
    gfx.ctx.strokeStyle = "#000000";
    gfx.ctx.beginPath();
    for (var x = 0; x <= world.getWidth(); x++) {
        var draw_x = camera.transformToCameraSpace(x, 0).cam_x;
        var top = camera.transformToCameraSpace(x, 0).cam_y;
        var bot = camera.transformToCameraSpace(x, world.getHeight()).cam_y;
        drawLine(draw_x, top, draw_x, bot);
    }

    for (var y = 0; y <= world.getHeight(); y++) {
        var draw_y = camera.transformToCameraSpace(0, y).cam_y;
        var left = camera.transformToCameraSpace(0, y).cam_x;
        var right = camera.transformToCameraSpace(world.getWidth(), y).cam_x;
        drawLine(left, draw_y, right, draw_y);
    }
    gfx.ctx.stroke();
}

function render() {
    clearBack();

    renderers[game.currentState]()
}
<commit_msg>Properly render so far nonexistant attack ranges<commit_after>var renderers = {
    "CAMERA_CONTROL": drawWorld,
    "UNIT_CONTROL": drawWorld,
    "MENU_CONTROL" : drawMenu
}

var specialRenderer;
function SpecialRenderer() {
    function addLayer(key, renderfunc, renderable) {
        renderers[key] = renderfunc.bind(null, renderable);
    }

    function removeLayer(key) {
        delete renderers[key];
    }

    function render() {
        for (var renderer in renderers) {
            if (renderers.hasOwnProperty(renderer)) {
                renderers[renderer]();
            }
        }
    }

    var renderers = {};

    this.addLayer = function(key, renderfunc, renderable) {
        addLayer(key, renderfunc, renderable);
    }

    this.removeLayer = function(key) {
        removeLayer(key);
    }

    this.render = function() {
        render();
    }
}

function initRenderers() {
    specialRenderer = new SpecialRenderer();

    game.selectorCallback = handleSelectorRendering;

    game.pathCallback = handlePathRendering;

    game.movesAvailableCallback = handleMovesRendering;

    game.attacksAvailableCallback = handleAttacksRendering;
}

function handleSelectorRendering(selector) {
    specialRenderer.removeLayer("selector");
    sprites.selector.animate = true;
    specialRenderer.addLayer("selector", drawSelector, selector);
}

function handlePathRendering(path) {
    specialRenderer.removeLayer("path");
    specialRenderer.addLayer("path", drawPath, path);
}

function handleMovesRendering(moves) {
    specialRenderer.removeLayer("moves");
    specialRenderer.addLayer("moves", drawMoves, moves);
}

function handleAttacksRendering(attacks) {
    specialRenderer.removeLayer("attacks");
    specialRenderer.addLayer("attacks", drawAttacks, attacks);
}

function clearBack() {
    gfx.ctx.fillStyle = "#FFFFFF";
    gfx.ctx.fillRect(0, 0, gfx.width, gfx.height);
}

function drawLine(sx, sy, dx, dy) {
    gfx.ctx.moveTo(sx, sy);
    gfx.ctx.lineTo(dx, dy);
}

function drawMenu() {
    // TODO actually render something for the menu
}

function drawWorld() {
    drawEnvironment(game.world);

    drawUnits(game.world);

    drawGrid(game.world);

    specialRenderer.render();
}

function drawPath(path) {
    var turnTable = Object.freeze({
        LEFTUP: "LEFTUP", RIGHTUP: "RIGHTUP",
        LEFTDOWN: "LEFTDOWN", RIGHTDOWN: "RIGHTDOWN",
		HORIZ: "HORIZ", VERT: "VERT"
    });

    function determineTurn(before, current, after) {
		//  b - c - a
		if (before.position.x + 1 == current.position.x &&
            before.position.y == current.position.y &&
			current.position.x + 1 == after.position.x &&
			current.position.y == after.position.y)
		{
			return turnTable.HORIZ;
		}
		//      b
		//      |
		//      c
		//      |
		//      a
		if (before.position.x == current.position.x &&
            before.position.y + 1 == current.position.y &&
			current.position.x == after.position.x &&
			current.position.y + 1 == after.position.y)
		{
			return turnTable.VERT;
		}
        //      a
        //      |
        //  b - c
        if (before.position.x + 1 == current.position.x &&
            before.position.y == current.position.y &&
            current.position.x == after.position.x &&
            current.position.y - 1 == after.position.y)
        {
            return turnTable.LEFTUP;
        }
        //      b
        //      |
        //      c - a
        if (before.position.x == current.position.x &&
            before.position.y + 1 == current.position.y &&
            current.position.x + 1 == after.position.x &&
            current.position.y == after.position.y)
        {
            return turnTable.RIGHTUP;
        }
        //  b - c
        //      |
        //      a
        if (before.position.x + 1 == current.position.x &&
            before.position.y == current.position.y &&
            current.position.x == after.position.x &&
            current.position.y + 1 == after.position.y)
        {
            return turnTable.LEFTDOWN;
        }
        //      c - a
        //      |
        //      b
        if (before.position.x == current.position.x &&
            before.position.y - 1 == current.position.y &&
            current.position.x + 1 == after.position.x &&
            current.position.y == after.position.y)
        {
            return turnTable.RIGHTDOWN;
        }
    }

    for (var i = 0; i < path.length; i++) {
        if (i == 0) {
            // start path sprite
            gfx.ctx.fillStyle = "#FF0000";
        } else if (i == path.length - 1) {
            // end path sprite
            gfx.ctx.fillStyle = "#00FF00";
        } else {
            var turn1 = determineTurn(path[i - 1], path[i], path[i + 1]);
            var turn2 = determineTurn(path[i + 1], path[i], path[i - 1]);
            var turn = turn1 || turn2;
            switch(turn) {
			    case turnTable.HORIZ:
                    gfx.ctx.fillStyle = "#F0F000";
                    break;
                case turnTable.VERT:
                    gfx.ctx.fillStyle = "#FFFFFF";
                    break;
                case turnTable.LEFTUP:
                    gfx.ctx.fillStyle = "#0F0F00";
                    break;
                case turnTable.RIGHTUP:
                    gfx.ctx.fillStyle = "#0F00F0";
                    break;
                case turnTable.LEFTDOWN:
                    gfx.ctx.fillStyle = "#0F000F";
                    break;
                case turnTable.RIGHTDOWN:
                    gfx.ctx.fillStyle = "#0F0FF0";
                    break;
            }
        }
        var pos = camera.transformToCameraSpace(path[i].position.x, path[i].position.y);
        gfx.ctx.fillRect(pos.cam_x + 25, pos.cam_y + 25, 50, 50);
    }
}

function drawMoves(moves) {
    gfx.ctx.fillStyle = "rgba(0, 0, 150, 0.5)";
    for (var i in moves) {
        var pos = camera.transformToCameraSpace(moves[i].cell.position.x, moves[i].cell.position.y);
        gfx.ctx.fillRect(pos.cam_x, pos.cam_y, 100, 100);
    }
}

function drawAttacks(attacks) {
    console.log("attacks and shit");
    gfx.ctx.fillStyle = "rgba(150, 0, 0, 0.5)";
    for (var i in attacks) {
        var pos = camera.transformToCameraSpace(attacks[i].cell.position.x, attacks[i].cell.position.y);
        gfx.ctx.fillRect(pos.cam_x, pos.cam_y, 100, 100);
    }
}

function drawEnvironment(world) {
    for (var x = 0; x < world.getWidth(); x++) {
        for (var y = 0; y < world.getHeight(); y++) {
            var current_cell = world.getCell(x, y);
            drawSprite(x, y, current_cell.spriteName)
        }
    }
}

function drawUnits(world) {
    var units = world.getUnits();
    for (var i in units) {
        var position = units[i].pos;
        drawSprite(position['x'], position['y'], units[i].spriteName);
    }
}

function drawSelector(selector) {
    var position = selector.pos;
    drawSprite(position['x'], position['y'], selector.spriteName);
}

function drawSprite(x, y, spriteName) {
    var cam_pos = camera.transformToCameraSpace(x, y);
    if (camera.positionVisible(cam_pos.cam_x, cam_pos.cam_y)) {
        var sprite = assets.spriteManager.getSprite(spriteName)
        var img = assets.get(sprite.url);
        var pos = sprite.getFramePosition();

        var rel_width = sprite.width / assets.spriteManager.minWidth;
        var rel_height = sprite.height / assets.spriteManager.minHeight;
        var cam_size = camera.multZoomFactor(rel_width, rel_height);
        var centered_offset = camera.multZoomFactor(rel_width - 1.0,
                                                    rel_height - 1.0);
        centered_offset.cam_w = centered_offset.cam_w / 2;
        centered_offset.cam_h = centered_offset.cam_h / 2;

        gfx.ctx.drawImage(img,
                          pos['x'], pos['y'],
                          sprite.width, sprite.height,
                          cam_pos.cam_x - centered_offset.cam_w,
                          cam_pos.cam_y - centered_offset.cam_h,
                          cam_size.cam_w, cam_size.cam_h);
    }
}

function drawGrid(world) {
    gfx.ctx.strokeStyle = "#000000";
    gfx.ctx.beginPath();
    for (var x = 0; x <= world.getWidth(); x++) {
        var draw_x = camera.transformToCameraSpace(x, 0).cam_x;
        var top = camera.transformToCameraSpace(x, 0).cam_y;
        var bot = camera.transformToCameraSpace(x, world.getHeight()).cam_y;
        drawLine(draw_x, top, draw_x, bot);
    }

    for (var y = 0; y <= world.getHeight(); y++) {
        var draw_y = camera.transformToCameraSpace(0, y).cam_y;
        var left = camera.transformToCameraSpace(0, y).cam_x;
        var right = camera.transformToCameraSpace(world.getWidth(), y).cam_x;
        drawLine(left, draw_y, right, draw_y);
    }
    gfx.ctx.stroke();
}

function render() {
    clearBack();

    renderers[game.currentState]()
}
<|endoftext|>


<commit_before>
'use strict';

const Const = require('./const');

class AppIntegrator {

    constructor(params) {
        this._vscode = params.vscode;
        this._gitContentProvider = params.gitContentProvider;
        this._annotateCommand = params.annotateCommand;
        this._switchDiffCommand = params.switchDiffCommand;
        this._takeDiffCommand = params.takeDiffCommand;
    }

    integrate(context) {
        this._registerProviders(context);
        this._registerTextEditorCommands(context);
        this._registerCommands(context);
    }

    _registerProviders(context) {
        const disposable = this._vscode.workspace.registerTextDocumentContentProvider(
                Const.EXTENSION_NAME, this._gitContentProvider);
        context.subscriptions.push(disposable);
    }

    _registerTextEditorCommands(context) {
        [{
            commandName: 'annotate',
            command: this._annotateCommand
        }, {
            commandName: 'switchDiffInCommit',
            command: this._switchDiffCommand
        }].forEach(item => {
            const command = item.command;
            const disposable = this._vscode.commands.registerTextEditorCommand(
                `${Const.EXTENSION_NAME}.${item.commandName}`,
                command.execute.bind(command)
            );
            context.subscriptions.push(disposable);
        });
    }

    _registerCommands(context) {
        const command = this._takeDiffCommand;
        const disposable = this._vscode.commands.registerCommand(
                `${Const.EXTENSION_NAME}.takeDiff`, command.execute.bind(command));
        context.subscriptions.push(disposable);
    }
}

module.exports = AppIntegrator;
<commit_msg>Refactoring AppIntegrator<commit_after>
'use strict';

const Const = require('./const');

class AppIntegrator {

    constructor(params) {
        this._vscode = params.vscode;
        this._gitContentProvider = params.gitContentProvider;
        this._annotateCommand = params.annotateCommand;
        this._switchDiffCommand = params.switchDiffCommand;
        this._takeDiffCommand = params.takeDiffCommand;
    }

    integrate(context) {
        this._registerProviders(context);
        this._registerCommands(context);
    }

    _registerProviders(context) {
        const disposable = this._vscode.workspace.registerTextDocumentContentProvider(
                Const.EXTENSION_NAME, this._gitContentProvider);
        context.subscriptions.push(disposable);
    }

    _registerCommands(context) {
        this._getCommands().forEach(command => {
            const handler = command.handler;
            const registrar = this._getCommandRegistrar(command.type);
            const disposable = this._vscode.commands[registrar](
                `${Const.EXTENSION_NAME}.${command.name}`,
                handler.execute.bind(handler)
            );
            context.subscriptions.push(disposable);
        });
    }

    _getCommands() {
        return [
            {
                name: 'annotate',
                type: 'TEXT_EDITOR',
                handler: this._annotateCommand
            },
            {
                name: 'switchDiffInCommit',
                type: 'TEXT_EDITOR',
                handler: this._switchDiffCommand
            },
            {
                name: 'takeDiff',
                type: 'GENERAL',
                handler: this._takeDiffCommand
            }
        ];
    }

    _getCommandRegistrar(type) {
        switch (type) {
        case 'TEXT_EDITOR':
            return 'registerTextEditorCommand';
        case 'GENERAL':
            return 'registerCommand';
        default:
            throw new Error(`Invalid command type ${type}`);
        }
    }
}

module.exports = AppIntegrator;
<|endoftext|>


<commit_before>const koa = require('koa2');
const bodyparser = require('koa-bodyparser');
const co = require('co');
const handlers = {
    queryUserInfo: require('../handlers/queryUserInfo.js'),
    queryDateInfo: require('../handlers/queryDateInfo.js'),
    queryAssigneeList: require('../handlers/queryAssigneeList.js'),
    queryTaskList: require('../handlers/queryTaskList.js'),

    login: require('../handlers/login.js'),
    logout: require('../handlers/logout.js')
};
const handlersWithAuth = {
    preAddTask: require('../handlers/preAddTask.js'),
    addTask: require('../handlers/addTask.js'),
    updateTask: require('../handlers/updateTask.js'),
    deleteTask: require('../handlers/deleteTask.js'),

    queryLeave: require('../handlers/queryLeave.js'),
    updateLeave: require('../handlers/updateLeave.js'),
    deleteLeave: require('../handlers/deleteLeave.js'),

    adjustAssigneeList: require('../handlers/adjustAssigneeList.js')
};

const app = new koa();

app.keys = ['t-all secret 2017'];

app.use(co.wrap(function *(ctx, next) {
    const start = new Date();
    yield next();
    const ms = new Date() - start;
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
}));

app.use(bodyparser());

// handlers
app.use(co.wrap(function *(ctx, next) {
    const name = ctx.url.replace('/', '').split('?')[0];
    const param = ctx.method === 'POST' ? ctx.request.body :
        (!!ctx.query.reqBody ? JSON.parse(decodeURIComponent(ctx.query.reqBody)) : ctx.query);
    console.log('name: '+name);
    console.log(param);
    if(handlers.hasOwnProperty(name)){
        ctx.response.type = 'application/json';
        const body = {
            "code": 0,
            "data": {}
        }
        try{
            const data = yield handlers[name].call(ctx, param);
            body.code = 0;
            body.data = data;
        }catch(e){
            console.log(e);
            body.code = -1;
        }finally{
            ctx.body = JSON.stringify(body);
        }
    }else if(handlersWithAuth.hasOwnProperty(name)){
        console.log('Authenticating...');
        ctx.response.type = 'application/json';
        const body = {
            "code": 0,
            "data": {}
        }

        try{
            const result = yield handlers.queryUserInfo.call(ctx, param);
            if(!result.userName || !result.userId || !result.adminId || result.adminId !== 'A1'){
                body.code = -101;
                ctx.body = JSON.stringify(body);
                return;
            }
        }catch(e){
            console.log(e);
            body.code = -100;
            ctx.body = JSON.stringify(body);
            return;
        }

        try{
            const data = yield handlersWithAuth[name].call(ctx, param);
            body.code = 0;
            body.data = data;
        }catch(e){
            console.log(e);
        }finally{
            ctx.body = JSON.stringify(body);
        }
    }else{
        ctx.body = 'T-All : 404 - Not Found';
    }
}));

app.listen(3000);
console.log('started');
<commit_msg>[EDIT] 完善请求返回数据<commit_after>const koa = require('koa2');
const bodyparser = require('koa-bodyparser');
const co = require('co');
const handlers = {
    queryUserInfo: require('../handlers/queryUserInfo.js'),
    queryDateInfo: require('../handlers/queryDateInfo.js'),
    queryAssigneeList: require('../handlers/queryAssigneeList.js'),
    queryTaskList: require('../handlers/queryTaskList.js'),

    login: require('../handlers/login.js'),
    logout: require('../handlers/logout.js')
};
const handlersWithAuth = {
    preAddTask: require('../handlers/preAddTask.js'),
    addTask: require('../handlers/addTask.js'),
    updateTask: require('../handlers/updateTask.js'),
    deleteTask: require('../handlers/deleteTask.js'),

    queryLeave: require('../handlers/queryLeave.js'),
    updateLeave: require('../handlers/updateLeave.js'),
    deleteLeave: require('../handlers/deleteLeave.js'),

    adjustAssigneeList: require('../handlers/adjustAssigneeList.js')
};

const app = new koa();

app.keys = ['t-all secret 2017'];

app.use(co.wrap(function *(ctx, next) {
    const start = new Date();
    yield next();
    const ms = new Date() - start;
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
}));

app.use(bodyparser());

// handlers
app.use(co.wrap(function *(ctx, next) {
    const name = ctx.url.replace('/', '').split('?')[0];
    const param = ctx.method === 'POST' ? ctx.request.body :
        (!!ctx.query.reqBody ? JSON.parse(decodeURIComponent(ctx.query.reqBody)) : ctx.query);
    console.log('name: '+name);
    console.log(param);
    if(handlers.hasOwnProperty(name)){
        ctx.response.type = 'application/json';
        const body = {
            "code": 0,
            "data": {}
        }
        try{
            const data = yield handlers[name].call(ctx, param);
            body.code = 0;
            body.data = data;
        }catch(e){
            console.log(e);
            body.code = -1;
        }finally{
            ctx.body = JSON.stringify(body);
        }
    }else if(handlersWithAuth.hasOwnProperty(name)){
        console.log('Authenticating...');
        ctx.response.type = 'application/json';
        const body = {
            "code": 0,
            "data": {}
        }

        try{
            const result = yield handlers.queryUserInfo.call(ctx, param);
            if(!result.userName || !result.userId || !result.adminId || result.adminId !== 'A1'){
                body.code = -101;
                ctx.body = JSON.stringify(body);
                return;
            }
        }catch(e){
            console.log(e);
            body.code = -100;
            ctx.body = JSON.stringify(body);
            return;
        }

        try{
            const data = yield handlersWithAuth[name].call(ctx, param);
            body.code = 0;
            body.data = data;
        }catch(e){
            console.log(e);
            body.code = e.errCode;
            body.msg = e.msg;
        }finally{
            ctx.body = JSON.stringify(body);
        }
    }else{
        ctx.body = 'T-All : 404 - Not Found';
    }
}));

app.listen(3000);
console.log('started');
<|endoftext|>


<commit_before>(function($, exports) {
  if (typeof exports.Dexter == 'undefined') exports.Dexter = {};
  var Dexter = exports.Dexter;

  // view when editing document details (NOT the analysis)
  Dexter.EditDocumentView = function() {
    var self = this;

    self.init = function() {
      if ($('#new-document, #edit-document').length === 0) {
        return;
      }

      // author name autocomplete
      self.$authorWidget = $('.author-widget');
      self.authorHound = new Bloodhound({
        name: 'authors',
        prefetch: {
          url: '/api/authors',
          ttl: 60,
          filter: function(resp) { return resp.authors; },
        },
        sorter: function(a, b) {
          // compare on length, then alphabetically
          if (a.name.length == b.name.length) {
            return a.name.localeCompare(b.name);
          } else {
            return a.name.length - b.name.length;
          }
        },
        datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.name); },
        queryTokenizer: Bloodhound.tokenizers.whitespace
      });
      self.authorHound.initialize();

      var autoset = false;
      self.$authorName = $('#author-name');

      self.$authorName.typeahead({
        highlight: true,
        autoselect: true,
      }, {
        source: self.authorHound.ttAdapter(),
        displayKey: 'name',
      }).on('typeahead:selected', function(e, author, dataset) {
        autoset = true;
        self.setAuthor(author);
      }).on('typeahead:opened', function(e) {
        autoset = false;
      }).on('typeahead:closed', function(e) {
        if (!autoset) {
          // auto-select an exact match if we haven't already
          self.authorHound.get(self.$authorName.typeahead('val'), function(suggestions) {
            var needle = self.$authorName.typeahead('val').toLowerCase();

            for (var i = 0; i < suggestions.length; i++) {
              if (suggestions[i].name.toLowerCase() == needle) {
                // TODO: handle an exact match (eg. try 'sapa')
                self.$authorName.typeahead('val', suggestions[i].name);
                self.$authorName.typeahead('close');
                self.$authorName.trigger('typeahead:selected', [suggestions[i], null]);
                return;
              }
            }

            // new author
            self.setNewAuthor();
          });
        }
      });
    };

    self.setAuthor = function(author) {
      var info = [author.author_type];

      if (author.gender) info.push(author.gender);
      if (author.race) info.push(author.race);

      $('.author-details', self.$authorWidget).removeClass('hidden').text(info.join(', '));
      $('.new-author-details', self.$authorWidget).addClass('hidden');
    };

    self.setNewAuthor = function() {
      // TODO: show the new author form widgets,
      // include type of author, race, gender
      $('.author-details', self.$authorWidget).addClass('hidden');
      $('.new-author-details', self.$authorWidget).removeClass('hidden');
    };
  };

  // view when editing the document analysis
  Dexter.EditDocumentAnalysisView = function() {
    var self = this;

    self.init = function() {
      self.$form = $('form.edit-analysis');
      if (self.$form.length === 0) {
        return;
      }

      // source person name autocomplete
      self.personHound = new Bloodhound({
        name: 'people',
        prefetch: {
          url: '/api/people',
          ttl: 60,
          filter: function(resp) { return resp.people; },
        },
        sorter: function(a, b) {
          // compare on length, then alphabetically
          if (a.name.length == b.name.length) {
            return a.name.localeCompare(b.name);
          } else {
            return a.name.length - b.name.length;
          }
        },
        datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.name); },
        queryTokenizer: Bloodhound.tokenizers.whitespace
      });
      self.personHound.initialize();

      self.newSourceCount = $('.sources tr.new', self.$form).length;

      $('.btn.add-source').on('click', self.addSource);
      $('table.sources', self.$form).
        on('click', '.btn.delete', self.deleteSource).
        on('click', '.btn.undo-delete', self.undoDeleteSource).
        on('change', 'input:radio[name$="-source_type"]', self.toggleSourceType);

      self.newFairnessCount = $('.fairness tr.new', self.$form).length;
      $('table.fairness', self.$form).
        on('change', '.template select', self.addNewFairness).
        on('click', '.btn.delete', self.deleteFairness).
        on('click', '.btn.undo-delete', self.undoDeleteFairness);
    };

    self.addSource = function(e) {
      e.preventDefault();

      var $template = $('table.sources tr.template');
      var $row = $template.clone().insertBefore($template);

      // this row is no longer a template
      $row.removeClass('template').addClass('new');

      self.newSourceCount++;

      // change form field name and 'for' prefixes to be new[ix]
      $('input, select, textarea, label', $row).each(function() {
        var attrs = ['name', 'id', 'for'];

        for (var i = 0; i < attrs.length; i++) {
          var attr = attrs[i];
          var val = $(this).attr(attr);
          if (val) {
            $(this).attr(attr, val.replace('new-', 'new[' + self.newSourceCount + ']-'));
          }
        }
      });

      $('.chosen-select-delayed', $row).chosen();

      self.personTypeaheadEnabled = false;
      self.enablePersonTypeahead($row);
    };

    self.enablePersonTypeahead = function($row) {
      if (!self.personTypeaheadEnabled) {
        $('.person-name input', $row).val('').typeahead({
          highlight: true,
          autoselect: true,
        }, {
          source: self.personHound.ttAdapter(),
          displayKey: 'name',
        });

        self.personTypeaheadEnabled = true;
      }
    };

    self.disablePersonTypeahead = function($row) {
      $('.person-name input', $row).typeahead('destroy').val('');
      self.personTypeaheadEnabled = false;
    };

    // delete button was clicked
    self.deleteSource = function(e) {
      e.preventDefault();

      $row = $(this).closest('tr');
      if ($row.hasClass('new')) {
        // it's new
        $row.remove();
      } else {
        // it's not new
        $row.addClass('deleted');
        $('input[name$="-deleted"]', $row).val('1');
      }
    };

    // undo a source delete
    self.undoDeleteSource = function(e) {
      e.preventDefault();

      $row = $(this).closest('tr');
      $row.removeClass('deleted');
      $('input[name$="-deleted"]', $row).val('0');
    };

    // should we show the unnamed-details area?
    self.toggleSourceType = function(e) {
      var $row = $(this).closest('tr');
      var sourceType = $(this).val();

      if (sourceType == 'unnamed') {
        $('.person-name', $row).hide();
        $('.unnamed-details', $row).show();

      } else {
        $('.person-name', $row).show();
        $('.unnamed-details', $row).hide();

        if (sourceType == 'person') {
          self.enablePersonTypeahead($row);
        } else {
          self.disablePersonTypeahead($row);
        }

        $('.person-name input', $row).focus();
      }
    };

    // when the user starts adding a new fairness, duplicate the row to keep a fresh
    // 'new entry' row, and then rename the elements on this one
    self.addNewFairness = function(e) {
      if ($(this).val() === '') return;

      var $row = $(this).closest('tr');
      var $template = $row.clone().insertAfter($row);
      $('select[type="text"]', $template).val('');

      // this row is no longer a template
      $row.removeClass('template').addClass('new');

      self.newFairnessCount++;

      // change form field name prefixes to be new[ix]
      $('select', $row).each(function() {
        $(this).attr('name', $(this).attr('name').replace('new-', 'new[' + self.newFairnessCount + ']-'));
      });

      $('.chosen-select-delayed', $row).chosen();
    };

    // delete button was clicked
    self.deleteFairness = function(e) {
      e.preventDefault();

      $row = $(this).closest('tr');
      if ($row.hasClass('new')) {
        // it's new
        $row.remove();
      } else {
        // it's not new
        $row.addClass('deleted');
        self.$form.append('<input type="hidden" name="fairness-del[' + $row.data('fairness-id') + ']" value="Y">');
      }
    };

    // undo a fairness delete
    self.undoDeleteFairness = function(e) {
      e.preventDefault();

      $row = $(this).closest('tr');
      $row.removeClass('deleted');
      $('input[name="fairness-del[' + $row.data('fairness-id') + ']"]', self.$form).remove();
    };
  };
})(jQuery, window);

$(function() {
  new Dexter.EditDocumentView().init();
  new Dexter.EditDocumentAnalysisView().init();
});
<commit_msg>Change TTL for typahead search to 10 mins<commit_after>(function($, exports) {
  if (typeof exports.Dexter == 'undefined') exports.Dexter = {};
  var Dexter = exports.Dexter;

  // view when editing document details (NOT the analysis)
  Dexter.EditDocumentView = function() {
    var self = this;

    self.init = function() {
      if ($('#new-document, #edit-document').length === 0) {
        return;
      }

      // author name autocomplete
      self.$authorWidget = $('.author-widget');
      self.authorHound = new Bloodhound({
        name: 'authors',
        prefetch: {
          url: '/api/authors',
          ttl: 600,
          filter: function(resp) { return resp.authors; },
        },
        sorter: function(a, b) {
          // compare on length, then alphabetically
          if (a.name.length == b.name.length) {
            return a.name.localeCompare(b.name);
          } else {
            return a.name.length - b.name.length;
          }
        },
        datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.name); },
        queryTokenizer: Bloodhound.tokenizers.whitespace
      });
      self.authorHound.initialize();

      var autoset = false;
      self.$authorName = $('#author-name');

      self.$authorName.typeahead({
        highlight: true,
        autoselect: true,
      }, {
        source: self.authorHound.ttAdapter(),
        displayKey: 'name',
      }).on('typeahead:selected', function(e, author, dataset) {
        autoset = true;
        self.setAuthor(author);
      }).on('typeahead:opened', function(e) {
        autoset = false;
      }).on('typeahead:closed', function(e) {
        if (!autoset) {
          // auto-select an exact match if we haven't already
          self.authorHound.get(self.$authorName.typeahead('val'), function(suggestions) {
            var needle = self.$authorName.typeahead('val').toLowerCase();

            for (var i = 0; i < suggestions.length; i++) {
              if (suggestions[i].name.toLowerCase() == needle) {
                // TODO: handle an exact match (eg. try 'sapa')
                self.$authorName.typeahead('val', suggestions[i].name);
                self.$authorName.typeahead('close');
                self.$authorName.trigger('typeahead:selected', [suggestions[i], null]);
                return;
              }
            }

            // new author
            self.setNewAuthor();
          });
        }
      });
    };

    self.setAuthor = function(author) {
      var info = [author.author_type];

      if (author.gender) info.push(author.gender);
      if (author.race) info.push(author.race);

      $('.author-details', self.$authorWidget).removeClass('hidden').text(info.join(', '));
      $('.new-author-details', self.$authorWidget).addClass('hidden');
    };

    self.setNewAuthor = function() {
      // TODO: show the new author form widgets,
      // include type of author, race, gender
      $('.author-details', self.$authorWidget).addClass('hidden');
      $('.new-author-details', self.$authorWidget).removeClass('hidden');
    };
  };

  // view when editing the document analysis
  Dexter.EditDocumentAnalysisView = function() {
    var self = this;

    self.init = function() {
      self.$form = $('form.edit-analysis');
      if (self.$form.length === 0) {
        return;
      }

      // source person name autocomplete
      self.personHound = new Bloodhound({
        name: 'people',
        prefetch: {
          url: '/api/people',
          ttl: 600,
          filter: function(resp) { return resp.people; },
        },
        sorter: function(a, b) {
          // compare on length, then alphabetically
          if (a.name.length == b.name.length) {
            return a.name.localeCompare(b.name);
          } else {
            return a.name.length - b.name.length;
          }
        },
        datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.name); },
        queryTokenizer: Bloodhound.tokenizers.whitespace
      });
      self.personHound.initialize();

      self.newSourceCount = $('.sources tr.new', self.$form).length;

      $('.btn.add-source').on('click', self.addSource);
      $('table.sources', self.$form).
        on('click', '.btn.delete', self.deleteSource).
        on('click', '.btn.undo-delete', self.undoDeleteSource).
        on('change', 'input:radio[name$="-source_type"]', self.toggleSourceType);

      self.newFairnessCount = $('.fairness tr.new', self.$form).length;
      $('table.fairness', self.$form).
        on('change', '.template select', self.addNewFairness).
        on('click', '.btn.delete', self.deleteFairness).
        on('click', '.btn.undo-delete', self.undoDeleteFairness);
    };

    self.addSource = function(e) {
      e.preventDefault();

      var $template = $('table.sources tr.template');
      var $row = $template.clone().insertBefore($template);

      // this row is no longer a template
      $row.removeClass('template').addClass('new');

      self.newSourceCount++;

      // change form field name and 'for' prefixes to be new[ix]
      $('input, select, textarea, label', $row).each(function() {
        var attrs = ['name', 'id', 'for'];

        for (var i = 0; i < attrs.length; i++) {
          var attr = attrs[i];
          var val = $(this).attr(attr);
          if (val) {
            $(this).attr(attr, val.replace('new-', 'new[' + self.newSourceCount + ']-'));
          }
        }
      });

      $('.chosen-select-delayed', $row).chosen();

      self.personTypeaheadEnabled = false;
      self.enablePersonTypeahead($row);
    };

    self.enablePersonTypeahead = function($row) {
      if (!self.personTypeaheadEnabled) {
        $('.person-name input', $row).val('').typeahead({
          highlight: true,
          autoselect: true,
        }, {
          source: self.personHound.ttAdapter(),
          displayKey: 'name',
        });

        self.personTypeaheadEnabled = true;
      }
    };

    self.disablePersonTypeahead = function($row) {
      $('.person-name input', $row).typeahead('destroy').val('');
      self.personTypeaheadEnabled = false;
    };

    // delete button was clicked
    self.deleteSource = function(e) {
      e.preventDefault();

      $row = $(this).closest('tr');
      if ($row.hasClass('new')) {
        // it's new
        $row.remove();
      } else {
        // it's not new
        $row.addClass('deleted');
        $('input[name$="-deleted"]', $row).val('1');
      }
    };

    // undo a source delete
    self.undoDeleteSource = function(e) {
      e.preventDefault();

      $row = $(this).closest('tr');
      $row.removeClass('deleted');
      $('input[name$="-deleted"]', $row).val('0');
    };

    // should we show the unnamed-details area?
    self.toggleSourceType = function(e) {
      var $row = $(this).closest('tr');
      var sourceType = $(this).val();

      if (sourceType == 'unnamed') {
        $('.person-name', $row).hide();
        $('.unnamed-details', $row).show();

      } else {
        $('.person-name', $row).show();
        $('.unnamed-details', $row).hide();

        if (sourceType == 'person') {
          self.enablePersonTypeahead($row);
        } else {
          self.disablePersonTypeahead($row);
        }

        $('.person-name input', $row).focus();
      }
    };

    // when the user starts adding a new fairness, duplicate the row to keep a fresh
    // 'new entry' row, and then rename the elements on this one
    self.addNewFairness = function(e) {
      if ($(this).val() === '') return;

      var $row = $(this).closest('tr');
      var $template = $row.clone().insertAfter($row);
      $('select[type="text"]', $template).val('');

      // this row is no longer a template
      $row.removeClass('template').addClass('new');

      self.newFairnessCount++;

      // change form field name prefixes to be new[ix]
      $('select', $row).each(function() {
        $(this).attr('name', $(this).attr('name').replace('new-', 'new[' + self.newFairnessCount + ']-'));
      });

      $('.chosen-select-delayed', $row).chosen();
    };

    // delete button was clicked
    self.deleteFairness = function(e) {
      e.preventDefault();

      $row = $(this).closest('tr');
      if ($row.hasClass('new')) {
        // it's new
        $row.remove();
      } else {
        // it's not new
        $row.addClass('deleted');
        self.$form.append('<input type="hidden" name="fairness-del[' + $row.data('fairness-id') + ']" value="Y">');
      }
    };

    // undo a fairness delete
    self.undoDeleteFairness = function(e) {
      e.preventDefault();

      $row = $(this).closest('tr');
      $row.removeClass('deleted');
      $('input[name="fairness-del[' + $row.data('fairness-id') + ']"]', self.$form).remove();
    };
  };
})(jQuery, window);

$(function() {
  new Dexter.EditDocumentView().init();
  new Dexter.EditDocumentAnalysisView().init();
});
<|endoftext|>


<commit_before>import {linear} from './ease';

export class Tween{
  constructor(startPos = 0, endPos = 0, duration = 0, delay = 0, easingEquation = linear){
    this.paused     = false;
    this.startPos   = startPos;
    this.endPos     = endPos;
    this.duration   = duration;
    this.delay      = delay;
    this.equation   = easingEquation;
    this.currentPos = startPos;
    this.startTime  = 0;
    this.easing     = true;
    this.started    = false;
  }

  reset(){
    this.startTime  = 0;
    this.currentPos = this.startPos;
    this.easing     = true;
    this.started    = false;
  }

  set pos(endPos){
    this.startTime  = 0;
    this.currentPos = this.startPos;
    this.endPos     = endPos;
    this.easing     = true;
  }

  pause(){
    this.paused = true;
  }

  play(){
    this.paused = false;
  }

  ease(now){
    if(this.paused === true) return this.currentPos;
    const duration  = this.duration;
    const endPos    = this.endPos;
    const startPos  = this.startPos;
    const delay     = this.delay;

    if(!now || this.easing === false)
      return endPos;

    if(this.startTime === 0 && now)
      this.startTime = now;

    let diff = now - this.startTime;

    diff = diff - delay;

    if(diff < 0) return startPos;

    if(diff >= duration) {
      this.currentPos = endPos;
      this.easing = false;
      return  this.currentPos;
    }

    const elapsed = diff / this.duration;
    const df = endPos - startPos;

    this.started = true;
    this.currentPos = startPos + df * this.equation(elapsed);

    return  this.currentPos;
  }
}

//STANDALONE TWEEN
export function tween(startValue, endValue, duration, delay, ease, callback){
  return new Promise((resolve) => {
      const tw = new Tween(startValue, endValue, duration, delay, ease);

      function render(){
          const pos = tw.ease(Date.now());
          callback(pos);
          if(tw.easing === false){
             resolve(pos);
             return;
          }

          requestAnimationFrame(render);
      }

      render();
    });
}

export function multiTween(tweens, callback){
  return new Promise((resolve) => {
      let activeTweens = [];

      for(let tw of tweens)
        activeTweens.push(new Tween(...tw));

      const len = activeTweens.length;

      function render(){
        const now = Date.now();
        let rendering = false;
        let values = [];

        for(let i = 0; i < len; i++){
          let tw = activeTweens[i];

          if(tw.easing === true)
            rendering = true;

          values[i] = tw.ease(now);
        }

        callback(values);
        if(rendering){
          requestAnimationFrame(render);
        }else{
          resolve();
        }
      }

      render();
    });
}

export class Lerp{
    constructor(initialValue, speed, threshold = 0.5){
        this.currentPos = initialValue;
        this.startPos   = initialValue;
        this.endPos     = initialValue;
        this.speed      = speed;
        this.threshold  = threshold;
        this.easing     = false;
    }

    finish(){
        this.easing     = false;
        this.currentPos = this.endPos;
    }

    reset(){
        this.currentPos = this.startPos;
        this.easing     = true;
    }

    set pos(endPos){
        this.endPos = endPos;
        this.easing = true;
    }

    set forced(endPos){
        this.endPos     = endPos;
        this.startPos   = endPos;
        this.currentPos = endPos;
        this.easing     = true;
    }

    get pos(){
        if(this.easing === false) return this.currentPos;

        const endPos    = this.endPos;
        const speed     = this.speed;
        const threshold = this.threshold;

        this.currentPos += (endPos - this.currentPos) * speed;

        const direction   = this.currentPos > endPos;
        const offset      = direction ? threshold : -threshold;
        const limit       = this.currentPos - offset;

        if(direction){
            if(limit <= endPos) this.finish();
        }else{
            if(limit >= endPos) this.finish();
        }

        return this.currentPos;
    }
}
<commit_msg>lerp optimization<commit_after>import {linear} from './ease';

export class Tween{
  constructor(startPos = 0, endPos = 0, duration = 0, delay = 0, easingEquation = linear){
    this.paused     = false;
    this.startPos   = startPos;
    this.endPos     = endPos;
    this.duration   = duration;
    this.delay      = delay;
    this.equation   = easingEquation;
    this.currentPos = startPos;
    this.startTime  = 0;
    this.easing     = true;
    this.started    = false;
  }

  reset(){
    this.startTime  = 0;
    this.currentPos = this.startPos;
    this.easing     = true;
    this.started    = false;
  }

  set pos(endPos){
    this.startTime  = 0;
    this.currentPos = this.startPos;
    this.endPos     = endPos;
    this.easing     = true;
  }

  pause(){
    this.paused = true;
  }

  play(){
    this.paused = false;
  }

  ease(now){
    if(this.paused === true) return this.currentPos;
    const duration  = this.duration;
    const endPos    = this.endPos;
    const startPos  = this.startPos;
    const delay     = this.delay;

    if(!now || this.easing === false)
      return endPos;

    if(this.startTime === 0 && now)
      this.startTime = now;

    let diff = now - this.startTime;

    diff = diff - delay;

    if(diff < 0) return startPos;

    if(diff >= duration) {
      this.currentPos = endPos;
      this.easing = false;
      return  this.currentPos;
    }

    const elapsed = diff / this.duration;
    const df = endPos - startPos;

    this.started = true;
    this.currentPos = startPos + df * this.equation(elapsed);

    return  this.currentPos;
  }
}

//STANDALONE TWEEN
export function tween(startValue, endValue, duration, delay, ease, callback){
  return new Promise((resolve) => {
      const tw = new Tween(startValue, endValue, duration, delay, ease);

      function render(){
          const pos = tw.ease(Date.now());
          callback(pos);
          if(tw.easing === false){
             resolve(pos);
             return;
          }

          requestAnimationFrame(render);
      }

      render();
    });
}

export function multiTween(tweens, callback){
  return new Promise((resolve) => {
      let activeTweens = [];

      for(let tw of tweens)
        activeTweens.push(new Tween(...tw));

      const len = activeTweens.length;

      function render(){
        const now = Date.now();
        let rendering = false;
        let values = [];

        for(let i = 0; i < len; i++){
          let tw = activeTweens[i];

          if(tw.easing === true)
            rendering = true;

          values[i] = tw.ease(now);
        }

        callback(values);
        if(rendering){
          requestAnimationFrame(render);
        }else{
          resolve();
        }
      }

      render();
    });
}

export class Lerp{
    constructor(initialValue, speed, threshold = 0.5){
        this.currentPos = initialValue;
        this.startPos   = initialValue;
        this.endPos     = initialValue;
        this.speed      = speed;
        this.threshold  = threshold;
        this.easing     = false;
    }

    finish(){
        this.easing     = false;
        this.currentPos = this.endPos;
    }

    reset(){
        this.currentPos = this.startPos;
        this.easing     = true;
    }

    set pos(endPos){
        if(this.endPos === endPos) return;
        this.endPos = endPos;
        this.easing = true;
    }

    set forced(endPos){
        this.endPos     = endPos;
        this.startPos   = endPos;
        this.currentPos = endPos;
        this.easing     = true;
    }

    get pos(){
        if(this.easing === false) return this.currentPos;

        const endPos    = this.endPos;
        const speed     = this.speed;
        const threshold = this.threshold;

        this.currentPos += (endPos - this.currentPos) * speed;

        const direction   = this.currentPos > endPos;
        const offset      = direction ? threshold : -threshold;
        const limit       = this.currentPos - offset;

        if(direction){
            if(limit <= endPos) this.finish();
        }else{
            if(limit >= endPos) this.finish();
        }

        return this.currentPos;
    }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
/**
 * Simple CLI to simplify common tasks :)
 * @beta
 */
var Hashes = require('../hashes'),
    args = process.argv,
    version = '1.0.4',
    usage, options;

// Usage dialog
usage = [
    'jsHashes ' + version
  , ''
  , 'Usage:'
  , '  hashes [option] [string]'
  , ''
  , 'Options:'
  , '  md5-hex'
  , '  md5-b64'
  , '  sha1-hex'
  , '  sha1-hex'
  , '  sha256-hex'
  , '  sha256-b64'
  , '  sha512-hex'
  , '  sha512-b64'
  , '  rmd160-hex'
  , '  rmd160-b64'
  , '  b64-enc'
  , '  b64-dec'
  , '  crc32'
  , ''
  , 'Help:'
  , '  -h , --help, help'
  , ''
  , 'Current version:'
  , '  -v , --version, version'
  , ''
  , 'Examples:'
  , '  $ hashes sha1-hex "sample text!"'
  , ''
].join('\n');

options = [
   'md5-hex'
  ,'md5-b64'
  ,'sha1-hex'
  ,'sha1-hex'
  ,'sha256-hex'
  ,'sha256-b64'
  ,'sha512-hex'
  ,'sha512-b64'
  ,'rmd160-hex'
  ,'rmd160-b64'
  ,'b64-enc'
  ,'b64-dec'
  ,'crc32'
];

// Exit the process with a message
function die (str) {
  console.log(str);
  process.exit();
}

// process algorithm
function procesAlgorithm() {
    var algorithm = args[0].split('-')[0].toUpperCase(),
        encoding = args[0].split('-')[1],
        string = args.slice(1).join(' '),
        instance, output;

    if (algorithm === 'B64') {
      algorithm = 'Base64';
      encoding = encoding === 'dec' ? 'decode' : 'encode';
    }

    if (Hashes.hasOwnProperty(algorithm)) {
      if (algorithm === 'CRC32') {
        output = Hashes[algorithm](string);
      } else {
        instance = new Hashes[algorithm];
        if (instance.hasOwnProperty(encoding)) {
          output = instance[encoding](string);
        }
      }
    } else {
      output = 'Algorithm not supported. Type help to see the list of available options.'
    }
    return output;
}

if (args[0] && args[0].indexOf('node') !== -1) {
    args = args.slice(2);
}
if (args[0] === '-v' || args[0] === '--version' || args[0] === 'version') {
  die(version);
}
if (args[0] === '-h' || args[0] === '--help' || args[0] === 'help' || args.length < 2 || options.indexOf(args[0]) === -1) {
  die(usage);
}

die(procesAlgorithm());
<commit_msg>allowing sha1-b64<commit_after>#!/usr/bin/env node
/**
 * Simple CLI to simplify common tasks :)
 * @beta
 */
var Hashes = require('../hashes'),
    args = process.argv,
    version = '1.0.4',
    usage, options;

// Usage dialog
usage = [
    'jsHashes ' + version
  , ''
  , 'Usage:'
  , '  hashes [option] [string]'
  , ''
  , 'Options:'
  , '  md5-hex'
  , '  md5-b64'
  , '  sha1-hex'
  , '  sha1-b64'
  , '  sha256-hex'
  , '  sha256-b64'
  , '  sha512-hex'
  , '  sha512-b64'
  , '  rmd160-hex'
  , '  rmd160-b64'
  , '  b64-enc'
  , '  b64-dec'
  , '  crc32'
  , ''
  , 'Help:'
  , '  -h , --help, help'
  , ''
  , 'Current version:'
  , '  -v , --version, version'
  , ''
  , 'Examples:'
  , '  $ hashes sha1-hex "sample text!"'
  , ''
].join('\n');

options = [
   'md5-hex'
  ,'md5-b64'
  ,'sha1-hex'
  ,'sha1-b64'
  ,'sha256-hex'
  ,'sha256-b64'
  ,'sha512-hex'
  ,'sha512-b64'
  ,'rmd160-hex'
  ,'rmd160-b64'
  ,'b64-enc'
  ,'b64-dec'
  ,'crc32'
];

// Exit the process with a message
function die (str) {
  console.log(str);
  process.exit();
}

// process algorithm
function procesAlgorithm() {
    var algorithm = args[0].split('-')[0].toUpperCase(),
        encoding = args[0].split('-')[1],
        string = args.slice(1).join(' '),
        instance, output;

    if (algorithm === 'B64') {
      algorithm = 'Base64';
      encoding = encoding === 'dec' ? 'decode' : 'encode';
    }

    if (Hashes.hasOwnProperty(algorithm)) {
      if (algorithm === 'CRC32') {
        output = Hashes[algorithm](string);
      } else {
        instance = new Hashes[algorithm];
        if (instance.hasOwnProperty(encoding)) {
          output = instance[encoding](string);
        }
      }
    } else {
      output = 'Algorithm not supported. Type help to see the list of available options.'
    }
    return output;
}

if (args[0] && args[0].indexOf('node') !== -1) {
    args = args.slice(2);
}
if (args[0] === '-v' || args[0] === '--version' || args[0] === 'version') {
  die(version);
}
if (args[0] === '-h' || args[0] === '--help' || args[0] === 'help' || args.length < 2 || options.indexOf(args[0]) === -1) {
  die(usage);
}

die(procesAlgorithm());
<|endoftext|>


<commit_before>var conf = require('../conf.js'),
    log = require('../log.js'),
    utils = require('../utils.js');

var C = require('../constants.js');

var errors = require('../errors.js');

var engine = require('engine');

var ResMan = require('../resource_manager.js');

var testAudio = engine.createAudio(),
    oggSupported =  !!(testAudio.canPlayType && testAudio.canPlayType('audio/ogg;').replace(/no/, ''));

var audioExt = oggSupported ? '.ogg' : '.mp3';
var audioType = oggSupported ? 'audio/ogg' : 'audio/mp3';

function getAudioContext() {
    if (engine.isLocal) {
        // we will not be able to load the audio as an ArrayBuffer
        // when we're under file protocol, so we shall have to
        // fall back to <audio> when playing locally.
        return null;
    }

    var AudioContext = global.AudioContext || global.webkitAudioContext;
    if (!AudioContext) {
      return null;
    }

    if (global.anmAudioContext) {
        return global.anmAudioContext;
    }

    try {
      var ctx = new AudioContext();
      return (global.anmAudioContext = ctx);
    } catch (e) {
      return null;
    }
}

var audioContext = getAudioContext();

/**
 * @class anm.Audio
 */
function Audio(url) {
    this.url = url + audioExt;
    this.ready = false;
    this.playing = false;
    this.canPlay = false;
    this.volume = 1;
    this.audio = null;
}
/** @private @method load */
Audio.prototype.load = function(elm, player) {
    var me = this;
    ResMan.loadOrGet(player.id, me.url,
      function(notify_success, notify_error, notify_progress) { // loader
          var url = me.url;
          if (engine.isHttps) {
              url = url.replace('http:', 'https:');
          }

          if (anm.conf.doNotLoadAudio) {
            notify_error('Loading audio is turned off');
            return;
          }

          if (audioContext) {
            // use Web Audio API if possible

            var node = {};

            var decode = function(node, url) {
              try {
                audioContext.decodeAudioData(node.buf, function onSuccess(decodedBuffer) {
                  notify_success(decodedBuffer);
                }, function(err) {
                  if (syncStream(node)) decode(node, url);
                });
              } catch(e) {
                notify_error('Unable to load audio ' + url + ': ' + e.message);
              }
            };

            var loadingDone = function(e) {
              var req = e.target;
              if (req.status == 200) {
                node.buf = req.response;
                node.sync = 0;
                node.retry = 0;
                decode(node);
              } else {
                notify_error('Unable to load audio ' + url + ': ' + req.statusText);
              }
            };

            node.xhr = new XMLHttpRequest();
            node.xhr.open('GET', url, true);
            node.xhr.responseType = 'arraybuffer';
            node.xhr.addEventListener('load', loadingDone, false);
            node.xhr.addEventListener('error', audioErrProxy(url, notify_error), false);
            node.xhr.send();
          } else {
            var el = engine.createAudio();
            el.setAttribute("preload", "auto");

            var progressListener = function(e) {
              var buffered = el.buffered;
              if (buffered.length == 1) {
                  // 0 == HAVE_NOTHING
                  // 1 == HAVE_METADATA
                  // 2 == HAVE_CURRENT_DATA
                  // 3 == HAVE_FUTURE_DATA
                  // 4 == HAVE_ENOUGH_DATA
                  if (el.readyState === 4 || el.readyState === 3) {
                    engine.unsubscribeElementEvents(el,
                        { 'progress': progressAndLoadingListener,
                          'loadedmetadata': loadingListener,
                          'canplay': canPlayListener });
                    notify_success(el);
                    notify_progress(1);
                    return;
                  }

                  if (me.canPlay && window.chrome) {
                    el.volume = 0;
                    el.currentTime = buffered.end(0);
                    el.play();
                    el.pause();
                  }
              } else if (me.canPlay && buffered.length != 1) {
                // will skip preloading since it seems like it will not work properly anyway:
                // it's a workaround for Android-based browsers which
                // will not allow prebuffering until user will explicitly allow it (by touching something)
                notify_success(el);
                notify_progress(1);
              }
            };

            var loadingListener = function(e) {
                var ranges = [];
                for (var i = 0; i < el.buffered.length; i++) {
                    ranges.push([ el.buffered.start(i),
                                  el.buffered.end(i) ]);
                }

                for (i = 0, progress = 0; i < el.buffered.length; i ++) {
                    progress += (1 / el.duration) * (ranges[i][1] - ranges[i][0]);
                }

                notify_progress(progress);
            };

            var progressAndLoadingListener = function(e) {
                progressListener(e); loadingListener(e);
            };

            var canPlayListener = function(e) {
              me.canPlay = true;
              progressListener(e);
            };

            engine.subscribeElementEvents(el,
                { 'progress': progressAndLoadingListener,
                  'loadedmetadata': loadingListener,
                  'canplay': canPlayListener,
                  'error': audioErrProxy(url, notify_error) });

            var addSource = function(audio, url, type) {
                var src = engine.createSource();
                src.addEventListener("error", notify_error, false);
                src.type = type;
                src.src = url;
                audio.appendChild(src);
            };

            try {
              engine.appendToBody(el);
              addSource(el, url, audioType);
            } catch(e) {
                notify_error(e);
            }
          }
      },
      function(audio) { // oncomplete
          me.audio = audio;
          me.ready = true;
          if (player.muted) {
              me.mute();
          }

      },
      function(err) {
          log.error(err ? (err.message || err) : 'Unknown error');
          throw errors.element(err ? err.message : 'Unknown', elm);
      });
};
/** @private @method play */
Audio.prototype.play = function(ltime, duration) {
    if (!this.ready || this.playing) {
      return false;
    }

    this.playing = true;
    var current_time = this.offset + ltime;

    if (audioContext) {
      if (current_time > this.audio.duration) {
        this._audio_is_playing = false;
        return;
      }

      this._source = audioContext.createBufferSource();
      this._source.buffer = this.audio;
      this._gain = audioContext.createGain();
      this._source.connect(this._gain);
      this._gain.connect(audioContext.destination);
      this._gain.gain.value = this.volume;

      if (this._source.play) {
        this._source.play(0, current_time);
      } else if (this._source.start) {
        this._source.start(0, current_time, this._source.buffer.duration - current_time);
      } else {
        this._source.noteGrainOn(0, current_time, this._source.buffer.duration - current_time);
      }
    } else {
      this.audio.currentTime = current_time;
      this.audio.volume = this.volume;
      this.audio.play();
    }
};
/** @private @method stop */
Audio.prototype.stop = function() {
    if (!this.playing) {
        return;
    }
    try {
        if (audioContext) {
            if (this._source.stop) {
                this._source.stop(0);
            } else {
                this._source.noteOff(0);
            }
            this._source = null;
        } else {
            this.audio.pause();
            this.audio.volume = 0;
        }
    } catch (err) {
        // do nothing
    }
    this.playing = false;
};
/** @private @method stopIfNotMaster */
Audio.prototype.stopIfNotMaster = function() {
    if (!this.master) this.stop();
};
/**
 * @method setVolume
 * @chainable
 * @deprecated will be renamed to `.volume()`, will be both getter and setter
 *
 * Change audio volume on the fly
 *
 * @param {Number} volume Volume value
 * @return {anm.Audio}
 */
Audio.prototype.setVolume = function(volume) {
    if (this.muted) {
        this.unmuteVolume = volume;
        return;
    }
    this.volume = volume;
    if (this._gain) {
        this._gain.gain.value = volume;
    } else if (this.audio) {
        this.audio.volume = volume;
    }
    return this;
};
/**
 * @method mute
 *
 * Mute this audio
 */
Audio.prototype.mute = function() {
    if (this.muted) {
        return;
    }
    this.unmuteVolume = this.volume;
    this.setVolume(0);
    this.muted = true;
};
/**
 * @method unmute
 *
 * Unmute this audio
 */
Audio.prototype.unmute = function() {
    if (!this.muted) {
        return;
    }
    this.muted = false;
    this.setVolume(this.unmuteVolume);
};
/**
 * @method toggleMute
 *
 * Toggle mute value of this audio
 */
Audio.prototype.toggleMute = function() {
    if (this.muted) {
        this.unmute();
    } else {
        this.mute();
    }
};
/** @private @method connect */
Audio.prototype.connect = function(element, anim) {
    var me = this;
    element.on(C.X_START, function() {
        me.play.apply(me, arguments);
    });
    element.on(C.X_STOP, function() {
        me.stopIfNotMaster();
    });
    var stop = function() {
        me.stop();
    };
    anim.on(C.A_STOP, stop);
    anim.on(C.A_PAUSE, stop);
};
/**
 * @method clone
 *
 * @return {anm.Audio}
 */
Audio.prototype.clone = function() {
    var clone = new Audio('');
    clone.url = this.url;
    clone.offset = this.offset;
    return clone;
};

// workaround, see http://stackoverflow.com/questions/10365335/decodeaudiodata-returning-a-null-error
function syncStream(node){
  var buf8 = new Uint8Array(node.buf);
  buf8.indexOf = Array.prototype.indexOf;
  var i=node.sync, b=buf8;
  while(1) {
      node.retry++;
      i=b.indexOf(0xFF,i); if(i==-1 || (b[i+1] & 0xE0 == 0xE0 )) break;
      i++;
  }
  if(i!=-1) {
      var tmp=node.buf.slice(i); //carefull there it returns copy
      delete(node.buf); node.buf=null;
      node.buf=tmp;
      node.sync=i;
      return true;
  }
  return false;
}

function audioErrProxy(src, pass_to) {
  return function(err) {
    // e_.MEDIA_ERR_ABORTED=1
    // e_.MEDIA_ERR_NETWORK=2
    // e_.MEDIA_ERR_DECODE=3
    // e_.MEDIA_ERR_SRC_NOT_SUPPORTED=4
    // e_.MEDIA_ERR_ENCRYPTED=5
    pass_to(new Error('Failed to load audio file from ' + src + ' with error code: ' +
                      err.currentTarget.error.code));
  };
}

module.exports = Audio;
<commit_msg>postpone Audio.play when it's not yet ready<commit_after>var conf = require('../conf.js'),
    log = require('../log.js'),
    utils = require('../utils.js');

var C = require('../constants.js');

var errors = require('../errors.js');

var engine = require('engine');

var ResMan = require('../resource_manager.js');

var testAudio = engine.createAudio(),
    oggSupported =  !!(testAudio.canPlayType && testAudio.canPlayType('audio/ogg;').replace(/no/, ''));

var audioExt = oggSupported ? '.ogg' : '.mp3';
var audioType = oggSupported ? 'audio/ogg' : 'audio/mp3';

function getAudioContext() {
    if (engine.isLocal) {
        // we will not be able to load the audio as an ArrayBuffer
        // when we're under file protocol, so we shall have to
        // fall back to <audio> when playing locally.
        return null;
    }

    var AudioContext = global.AudioContext || global.webkitAudioContext;
    if (!AudioContext) {
      return null;
    }

    if (global.anmAudioContext) {
        return global.anmAudioContext;
    }

    try {
      var ctx = new AudioContext();
      return (global.anmAudioContext = ctx);
    } catch (e) {
      return null;
    }
}

var audioContext = getAudioContext();

/**
 * @class anm.Audio
 */
function Audio(url) {
    this.url = url + audioExt;
    this.ready = false;
    this.playing = false;
    this.canPlay = false;
    this.volume = 1;
    this.audio = null;
}
/** @private @method load */
Audio.prototype.load = function(elm, player) {
    var me = this;
    ResMan.loadOrGet(player.id, me.url,
      function(notify_success, notify_error, notify_progress) { // loader
          var url = me.url;
          if (engine.isHttps) {
              url = url.replace('http:', 'https:');
          }

          if (anm.conf.doNotLoadAudio) {
            notify_error('Loading audio is turned off');
            return;
          }

          if (audioContext) {
            // use Web Audio API if possible

            var node = {};

            var decode = function(node, url) {
              try {
                audioContext.decodeAudioData(node.buf, function onSuccess(decodedBuffer) {
                  notify_success(decodedBuffer);
                }, function(err) {
                  if (syncStream(node)) decode(node, url);
                });
              } catch(e) {
                notify_error('Unable to load audio ' + url + ': ' + e.message);
              }
            };

            var loadingDone = function(e) {
              var req = e.target;
              if (req.status == 200) {
                node.buf = req.response;
                node.sync = 0;
                node.retry = 0;
                decode(node);
              } else {
                notify_error('Unable to load audio ' + url + ': ' + req.statusText);
              }
            };

            node.xhr = new XMLHttpRequest();
            node.xhr.open('GET', url, true);
            node.xhr.responseType = 'arraybuffer';
            node.xhr.addEventListener('load', loadingDone, false);
            node.xhr.addEventListener('error', audioErrProxy(url, notify_error), false);
            node.xhr.send();
          } else {
            var el = engine.createAudio();
            el.setAttribute("preload", "auto");

            var progressListener = function(e) {
              var buffered = el.buffered;
              if (buffered.length == 1) {
                  // 0 == HAVE_NOTHING
                  // 1 == HAVE_METADATA
                  // 2 == HAVE_CURRENT_DATA
                  // 3 == HAVE_FUTURE_DATA
                  // 4 == HAVE_ENOUGH_DATA
                  if (el.readyState === 4 || el.readyState === 3) {
                    engine.unsubscribeElementEvents(el,
                        { 'progress': progressAndLoadingListener,
                          'loadedmetadata': loadingListener,
                          'canplay': canPlayListener });
                    notify_success(el);
                    notify_progress(1);
                    return;
                  }

                  if (me.canPlay && window.chrome) {
                    el.volume = 0;
                    el.currentTime = buffered.end(0);
                    el.play();
                    el.pause();
                  }
              } else if (me.canPlay && buffered.length != 1) {
                // will skip preloading since it seems like it will not work properly anyway:
                // it's a workaround for Android-based browsers which
                // will not allow prebuffering until user will explicitly allow it (by touching something)
                notify_success(el);
                notify_progress(1);
              }
            };

            var loadingListener = function(e) {
                var ranges = [];
                for (var i = 0; i < el.buffered.length; i++) {
                    ranges.push([ el.buffered.start(i),
                                  el.buffered.end(i) ]);
                }

                for (i = 0, progress = 0; i < el.buffered.length; i ++) {
                    progress += (1 / el.duration) * (ranges[i][1] - ranges[i][0]);
                }

                notify_progress(progress);
            };

            var progressAndLoadingListener = function(e) {
                progressListener(e); loadingListener(e);
            };

            var canPlayListener = function(e) {
              me.canPlay = true;
              progressListener(e);
            };

            engine.subscribeElementEvents(el,
                { 'progress': progressAndLoadingListener,
                  'loadedmetadata': loadingListener,
                  'canplay': canPlayListener,
                  'error': audioErrProxy(url, notify_error) });

            var addSource = function(audio, url, type) {
                var src = engine.createSource();
                src.addEventListener("error", notify_error, false);
                src.type = type;
                src.src = url;
                audio.appendChild(src);
            };

            try {
              engine.appendToBody(el);
              addSource(el, url, audioType);
            } catch(e) {
                notify_error(e);
            }
          }
      },
      function(audio) { // oncomplete
          me.audio = audio;
          me.ready = true;
          if (me.shouldPlayWhenReady) {
              me.play(me.shouldPlayParams.ltime, me.shouldPlayParams.duration);
              me.shouldPlayWhenReady = false;
          }
          if (player.muted) {
              me.mute();
          }

      },
      function(err) {
          log.error(err ? (err.message || err) : 'Unknown error');
          throw errors.element(err ? err.message : 'Unknown', elm);
      });
};
/** @private @method play */
Audio.prototype.play = function(ltime, duration) {
    if (this.playing) {
      return false;
    }

    if (!this.ready) {
        this.shouldPlayWhenReady = true;
        this.shouldPlayParams = {
            ltime: ltime,
            duration: duration
        };
        return;
    }

    this.playing = true;
    var current_time = this.offset + ltime;

    if (audioContext) {
      if (current_time > this.audio.duration) {
        this._audio_is_playing = false;
        return;
      }

      this._source = audioContext.createBufferSource();
      this._source.buffer = this.audio;
      this._gain = audioContext.createGain();
      this._source.connect(this._gain);
      this._gain.connect(audioContext.destination);
      this._gain.gain.value = this.volume;

      if (this._source.play) {
        this._source.play(0, current_time);
      } else if (this._source.start) {
        this._source.start(0, current_time, this._source.buffer.duration - current_time);
      } else {
        this._source.noteGrainOn(0, current_time, this._source.buffer.duration - current_time);
      }
    } else {
      this.audio.currentTime = current_time;
      this.audio.volume = this.volume;
      this.audio.play();
    }
};
/** @private @method stop */
Audio.prototype.stop = function() {
    if (!this.playing) {
        return;
    }
    try {
        if (audioContext) {
            if (this._source.stop) {
                this._source.stop(0);
            } else {
                this._source.noteOff(0);
            }
            this._source = null;
        } else {
            this.audio.pause();
            this.audio.volume = 0;
        }
    } catch (err) {
        // do nothing
    }
    this.playing = false;
};
/** @private @method stopIfNotMaster */
Audio.prototype.stopIfNotMaster = function() {
    if (!this.master) this.stop();
};
/**
 * @method setVolume
 * @chainable
 * @deprecated will be renamed to `.volume()`, will be both getter and setter
 *
 * Change audio volume on the fly
 *
 * @param {Number} volume Volume value
 * @return {anm.Audio}
 */
Audio.prototype.setVolume = function(volume) {
    if (this.muted) {
        this.unmuteVolume = volume;
        return;
    }
    this.volume = volume;
    if (this._gain) {
        this._gain.gain.value = volume;
    } else if (this.audio) {
        this.audio.volume = volume;
    }
    return this;
};
/**
 * @method mute
 *
 * Mute this audio
 */
Audio.prototype.mute = function() {
    if (this.muted) {
        return;
    }
    this.unmuteVolume = this.volume;
    this.setVolume(0);
    this.muted = true;
};
/**
 * @method unmute
 *
 * Unmute this audio
 */
Audio.prototype.unmute = function() {
    if (!this.muted) {
        return;
    }
    this.muted = false;
    this.setVolume(this.unmuteVolume);
};
/**
 * @method toggleMute
 *
 * Toggle mute value of this audio
 */
Audio.prototype.toggleMute = function() {
    if (this.muted) {
        this.unmute();
    } else {
        this.mute();
    }
};
/** @private @method connect */
Audio.prototype.connect = function(element, anim) {
    var me = this;
    element.on(C.X_START, function() {
        me.play.apply(me, arguments);
    });
    element.on(C.X_STOP, function() {
        me.stopIfNotMaster();
    });
    var stop = function() {
        me.stop();
    };
    anim.on(C.A_STOP, stop);
    anim.on(C.A_PAUSE, stop);
};
/**
 * @method clone
 *
 * @return {anm.Audio}
 */
Audio.prototype.clone = function() {
    var clone = new Audio('');
    clone.url = this.url;
    clone.offset = this.offset;
    return clone;
};

// workaround, see http://stackoverflow.com/questions/10365335/decodeaudiodata-returning-a-null-error
function syncStream(node){
  var buf8 = new Uint8Array(node.buf);
  buf8.indexOf = Array.prototype.indexOf;
  var i=node.sync, b=buf8;
  while(1) {
      node.retry++;
      i=b.indexOf(0xFF,i); if(i==-1 || (b[i+1] & 0xE0 == 0xE0 )) break;
      i++;
  }
  if(i!=-1) {
      var tmp=node.buf.slice(i); //carefull there it returns copy
      delete(node.buf); node.buf=null;
      node.buf=tmp;
      node.sync=i;
      return true;
  }
  return false;
}

function audioErrProxy(src, pass_to) {
  return function(err) {
    // e_.MEDIA_ERR_ABORTED=1
    // e_.MEDIA_ERR_NETWORK=2
    // e_.MEDIA_ERR_DECODE=3
    // e_.MEDIA_ERR_SRC_NOT_SUPPORTED=4
    // e_.MEDIA_ERR_ENCRYPTED=5
    pass_to(new Error('Failed to load audio file from ' + src + ' with error code: ' +
                      err.currentTarget.error.code));
  };
}

module.exports = Audio;
<|endoftext|>