<commit_before>import { throttle } from 'lodash/function';
import data from './data';
import * as utils from '../../utils';
import sound from '../../sound';
import gameApi from '../../lichess/game';
import ground from './ground';
import promotion from './promotion';
import chat from './chat';
import clockCtrl from './clock/clockCtrl';
import i18n from '../../i18n';
import gameStatus from '../../lichess/status';
import correspondenceClockCtrl from './correspondenceClock/correspondenceCtrl';
import session from '../../session';
import socket from '../../socket';
import signals from '../../signals';
import socketHandler from './socketHandler';
import atomic from './atomic';
import backbutton from '../../backbutton';
import helper from '../helper';
import * as xhr from './roundXhr';
import m from 'mithril';

export default function controller(cfg, onFeatured, onTVChannelChange, userTv, onUserTVRedirect) {

  helper.analyticsTrackView('Round');

  this.data = data(cfg);

  this.onTVChannelChange = onTVChannelChange;

  this.firstPly = function() {
    return this.data.steps[0].ply;
  }.bind(this);

  this.lastPly = function() {
    return this.data.steps[this.data.steps.length - 1].ply;
  }.bind(this);

  this.plyStep = function(ply) {
    return this.data.steps[ply - this.firstPly()];
  }.bind(this);

  this.vm = {
    flip: false,
    showingActions: false,
    replayHash: '',
    ply: this.lastPly(),
    moveToSubmit: null
  };

  this.connectSocket = function() {
    socket.createGame(
      this.data.url.socket,
      this.data.player.version,
      socketHandler(this, onFeatured, onUserTVRedirect),
      this.data.url.round,
      userTv
    );
  }.bind(this);

  this.connectSocket();

  // reconnect game socket after a cancelled seek
  signals.seekCanceled.add(this.connectSocket);

  this.stepsHash = function(steps) {
    var h = '';
    for (var i in steps) {
      h += steps[i].san;
    }
    return h;
  };

  this.showActions = function() {
    backbutton.stack.push(this.hideActions);
    this.vm.showingActions = true;
  }.bind(this);

  this.hideActions = function(fromBB) {
    if (fromBB !== 'backbutton' && this.vm.showingActions) backbutton.stack.pop();
    this.vm.showingActions = false;
  }.bind(this);

  this.flip = function() {
    if (this.data.tv) {
      if (m.route.param('flip')) m.route('/tv');
      else m.route('/tv?flip=1');
      return;
    } else if (this.data.player.spectator) {
      m.route('/game/' + this.data.game.id + '/' +
        utils.oppositeColor(this.data.player.color));
      return;
    }
    this.vm.flip = !this.vm.flip;
    this.chessground.set({
      orientation: this.vm.flip ? this.data.opponent.color : this.data.player.color
    });
  }.bind(this);

  this.replaying = function() {
    return this.vm.ply !== this.lastPly();
  }.bind(this);

  this.jump = function(ply) {
    if (ply < this.firstPly() || ply > this.lastPly()) return;
    this.vm.ply = ply;
    var s = this.plyStep(ply);
    var config = {
      fen: s.fen,
      lastMove: s.uci ? [s.uci.substr(0, 2), s.uci.substr(2, 2)] : null,
      check: s.check,
      turnColor: this.vm.ply % 2 === 0 ? 'white' : 'black'
    };
    if (this.replaying()) this.chessground.stop();
    else config.movable = {
      color: gameApi.isPlayerPlaying(this.data) ? this.data.player.color : null,
      dests: gameApi.parsePossibleMoves(this.data.possibleMoves)
    };
    this.chessground.set(config);
  }.bind(this);

  this.jumpNext = function() {
    this.jump(this.vm.ply + 1);
  }.bind(this);

  this.jumpPrev = function() {
    this.jump(this.vm.ply - 1);
  }.bind(this);

  this.jumpFirst = function() {
    this.jump(this.firstPly());
  }.bind(this);

  this.jumpLast = function() {
    this.jump(this.lastPly());
  }.bind(this);

  this.setTitle = function() {
    if (this.data.tv)
      this.title = 'Lichess TV';
    else if (this.data.userTV)
      this.title = this.data.userTV;
    else if (gameStatus.finished(this.data))
      this.title = i18n('gameOver');
    else if (gameStatus.aborted(this.data))
      this.title = i18n('gameAborted');
    else if (gameApi.isPlayerTurn(this.data))
      this.title = i18n('yourTurn');
    else if (gameApi.isOpponentTurn(this.data))
      this.title = i18n('waitingForOpponent');
    else
      this.title = 'lichess.org';
  };
  this.setTitle();

  this.sendMove = function(orig, dest, prom) {
    var move = {
      from: orig,
      to: dest
    };
    if (prom) move.promotion = prom;
    if (this.clock && socket.getAverageLag() !== undefined)
      move.lag = Math.round(socket.getAverageLag());

    if (this.data.pref.submitMove) {
      setTimeout(function() {
        backbutton.stack.push(this.cancelMove);
        this.vm.moveToSubmit = move;
        m.redraw();
      }.bind(this), this.data.pref.animationDuration || 0);
    } else socket.send('move', move, { ackable: true });
  };

  this.cancelMove = function(fromBB) {
    if (fromBB !== 'backbutton') backbutton.stack.pop();
    this.vm.moveToSubmit = null;
    this.jump(this.vm.ply);
  }.bind(this);

  this.submitMove = function(v) {
    if (v) {
      if (this.vm.moveToSubmit)
        socket.send('move', this.vm.moveToSubmit, {
          ackable: true
        });
        this.vm.moveToSubmit = null;
    } else {
      this.cancelMove();
    }
  }.bind(this);

  var userMove = function(orig, dest, meta) {
    if (!promotion.start(this, orig, dest, meta.premove)) this.sendMove(orig, dest);
  }.bind(this);

  var onMove = function(orig, dest, capturedPiece) {
    if (capturedPiece) {
      if (this.data.game.variant.key === 'atomic') {
        atomic.capture(this, dest);
        sound.explosion();
      }
      else sound.capture();
    } else sound.move();
  }.bind(this);

  this.apiMove = function(o) {
    m.startComputation();
    let d = this.data;
    d.game.turns = o.ply;
    d.game.player = o.ply % 2 === 0 ? 'white' : 'black';
    const playedColor = o.ply % 2 === 0 ? 'black' : 'white';
    if (o.status) d.game.status = o.status;
    d[d.player.color === 'white' ? 'player' : 'opponent'].offeringDraw = o.wDraw;
    d[d.player.color === 'black' ? 'player' : 'opponent'].offeringDraw = o.bDraw;
    d.possibleMoves = d.player.color === d.game.player ? o.dests : null;
    this.setTitle();

    if (!this.replaying()) {
      this.vm.ply++;
      this.chessground.apiMove(o.uci.substr(0, 2), o.uci.substr(2, 2));

      if (o.enpassant) {
        let p = o.enpassant;
        let pieces = {};
        pieces[p.key] = null;
        this.chessground.setPieces(pieces);
        if (d.game.variant.key === 'atomic') atomic.enpassant(this, p.key, p.color);
        sound.capture();
      }

      if (o.promotion) ground.promote(this.chessground, o.promotion.key, o.promotion.pieceClass);

      if (o.castle && !this.chessground.data.autoCastle) {
        let c = o.castle;
        let pieces = {};
        pieces[c.king[0]] = null;
        pieces[c.rook[0]] = null;
        pieces[c.king[1]] = {
          role: 'king',
          color: c.color
        };
        pieces[c.rook[1]] = {
          role: 'rook',
          color: c.color
        };
        this.chessground.setPieces(pieces);
      }

      this.chessground.set({
        turnColor: d.game.player,
        movable: {
          dests: gameApi.isPlayerPlaying(d) ? gameApi.parsePossibleMoves(d.possibleMoves) : {}
        },
        check: o.check
      });

      if (playedColor !== d.player.color) {
        // atrocious hack to prevent race condition
        // with explosions and premoves
        // https://github.com/ornicar/lila/issues/343
        if (d.game.variant.key === 'atomic') setTimeout(this.chessground.playPremove, 100);
        else this.chessground.playPremove();
      }
      if (this.data.game.speed === 'correspondence' && session.isConnected()) session.refresh();
    }

    if (o.clock) {
      let c = o.clock;
      if (this.clock) this.clock.update(c.white, c.black);
      else if (this.correspondenceClock) this.correspondenceClock.update(c.white, c.black);
    }

    d.game.threefold = !!o.threefold;
    d.steps.push({
      ply: this.lastPly() + 1,
      fen: o.fen,
      san: o.san,
      uci: o.uci,
      check: o.check
    });
    gameApi.setOnGame(d, playedColor, true);
    m.endComputation();
  }.bind(this);

  this.chessground = ground.make(this.data, cfg.game.fen, userMove, onMove);

  this.clock = this.data.clock ? new clockCtrl(
    this.data.clock,
    this.data.player.spectator ? function() {} : throttle(function() {
      socket.send('outoftime');
    }, 500),
    this.data.player.spectator ? null : this.data.player.color
  ) : false;

  this.isClockRunning = function() {
    return this.data.clock && gameApi.playable(this.data) &&
      ((this.data.game.turns - this.data.game.startedAtTurn) > 1 || this.data.clock.running);
  }.bind(this);

  this.clockTick = function() {
    if (this.isClockRunning()) this.clock.tick(this.data.game.player);
  }.bind(this);

  var makeCorrespondenceClock = function() {
    if (this.data.correspondence && !this.correspondenceClock)
      this.correspondenceClock = new correspondenceClockCtrl(
        this,
        this.data.correspondence,
        () => socket.send('outoftime')
      );
  }.bind(this);
  makeCorrespondenceClock();

  var correspondenceClockTick = function() {
    if (this.correspondenceClock && gameApi.playable(this.data))
      this.correspondenceClock.tick(this.data.game.player);
  }.bind(this);

  var clockIntervId;
  if (this.clock) clockIntervId = setInterval(this.clockTick, 100);
  else if (this.correspondenceClock) clockIntervId = setInterval(correspondenceClockTick, 1000);

  this.chat = (this.data.opponent.ai || this.data.player.spectator) ?
    null : new chat.controller(this);

  this.reload = function(rCfg) {
    if (this.stepsHash(rCfg.steps) !== this.stepsHash(this.data.steps))
      this.vm.ply = rCfg.steps[rCfg.steps.length - 1].ply;
    if (this.chat) this.chat.onReload(rCfg.chat);
    if (this.data.tv) rCfg.tv = this.data.tv;
    if (this.data.userTV) rCfg.userTV = this.data.userTV;
    this.data = data(rCfg);
    makeCorrespondenceClock();
    this.setTitle();
    if (!this.replaying()) ground.reload(this.chessground, this.data, rCfg.game.fen, this.vm.flip);
  }.bind(this);

  window.plugins.insomnia.keepAwake();

  var onResume = function() {
    xhr.reload(this).then(this.reload);
  }.bind(this);

  document.addEventListener('resume', onResume);

  this.onunload = function() {
    socket.destroy();
    clearInterval(clockIntervId);
    if (this.chat) this.chat.onunload();
    document.removeEventListener('resume', onResume);
    window.plugins.insomnia.allowSleepAgain();
  };
}

<commit_msg>Detach signals handler on round unload<commit_after>import { throttle } from 'lodash/function';
import data from './data';
import * as utils from '../../utils';
import sound from '../../sound';
import gameApi from '../../lichess/game';
import ground from './ground';
import promotion from './promotion';
import chat from './chat';
import clockCtrl from './clock/clockCtrl';
import i18n from '../../i18n';
import gameStatus from '../../lichess/status';
import correspondenceClockCtrl from './correspondenceClock/correspondenceCtrl';
import session from '../../session';
import socket from '../../socket';
import signals from '../../signals';
import socketHandler from './socketHandler';
import atomic from './atomic';
import backbutton from '../../backbutton';
import helper from '../helper';
import * as xhr from './roundXhr';
import m from 'mithril';

export default function controller(cfg, onFeatured, onTVChannelChange, userTv, onUserTVRedirect) {

  helper.analyticsTrackView('Round');

  this.data = data(cfg);

  this.onTVChannelChange = onTVChannelChange;

  this.firstPly = function() {
    return this.data.steps[0].ply;
  }.bind(this);

  this.lastPly = function() {
    return this.data.steps[this.data.steps.length - 1].ply;
  }.bind(this);

  this.plyStep = function(ply) {
    return this.data.steps[ply - this.firstPly()];
  }.bind(this);

  this.vm = {
    flip: false,
    showingActions: false,
    replayHash: '',
    ply: this.lastPly(),
    moveToSubmit: null
  };

  var connectSocket = function() {
    socket.createGame(
      this.data.url.socket,
      this.data.player.version,
      socketHandler(this, onFeatured, onUserTVRedirect),
      this.data.url.round,
      userTv
    );
  }.bind(this);

  connectSocket();

  // reconnect game socket after a cancelled seek
  signals.seekCanceled.add(connectSocket);

  this.stepsHash = function(steps) {
    var h = '';
    for (var i in steps) {
      h += steps[i].san;
    }
    return h;
  };

  this.showActions = function() {
    backbutton.stack.push(this.hideActions);
    this.vm.showingActions = true;
  }.bind(this);

  this.hideActions = function(fromBB) {
    if (fromBB !== 'backbutton' && this.vm.showingActions) backbutton.stack.pop();
    this.vm.showingActions = false;
  }.bind(this);

  this.flip = function() {
    if (this.data.tv) {
      if (m.route.param('flip')) m.route('/tv');
      else m.route('/tv?flip=1');
      return;
    } else if (this.data.player.spectator) {
      m.route('/game/' + this.data.game.id + '/' +
        utils.oppositeColor(this.data.player.color));
      return;
    }
    this.vm.flip = !this.vm.flip;
    this.chessground.set({
      orientation: this.vm.flip ? this.data.opponent.color : this.data.player.color
    });
  }.bind(this);

  this.replaying = function() {
    return this.vm.ply !== this.lastPly();
  }.bind(this);

  this.jump = function(ply) {
    if (ply < this.firstPly() || ply > this.lastPly()) return;
    this.vm.ply = ply;
    var s = this.plyStep(ply);
    var config = {
      fen: s.fen,
      lastMove: s.uci ? [s.uci.substr(0, 2), s.uci.substr(2, 2)] : null,
      check: s.check,
      turnColor: this.vm.ply % 2 === 0 ? 'white' : 'black'
    };
    if (this.replaying()) this.chessground.stop();
    else config.movable = {
      color: gameApi.isPlayerPlaying(this.data) ? this.data.player.color : null,
      dests: gameApi.parsePossibleMoves(this.data.possibleMoves)
    };
    this.chessground.set(config);
  }.bind(this);

  this.jumpNext = function() {
    this.jump(this.vm.ply + 1);
  }.bind(this);

  this.jumpPrev = function() {
    this.jump(this.vm.ply - 1);
  }.bind(this);

  this.jumpFirst = function() {
    this.jump(this.firstPly());
  }.bind(this);

  this.jumpLast = function() {
    this.jump(this.lastPly());
  }.bind(this);

  this.setTitle = function() {
    if (this.data.tv)
      this.title = 'Lichess TV';
    else if (this.data.userTV)
      this.title = this.data.userTV;
    else if (gameStatus.finished(this.data))
      this.title = i18n('gameOver');
    else if (gameStatus.aborted(this.data))
      this.title = i18n('gameAborted');
    else if (gameApi.isPlayerTurn(this.data))
      this.title = i18n('yourTurn');
    else if (gameApi.isOpponentTurn(this.data))
      this.title = i18n('waitingForOpponent');
    else
      this.title = 'lichess.org';
  };
  this.setTitle();

  this.sendMove = function(orig, dest, prom) {
    var move = {
      from: orig,
      to: dest
    };
    if (prom) move.promotion = prom;
    if (this.clock && socket.getAverageLag() !== undefined)
      move.lag = Math.round(socket.getAverageLag());

    if (this.data.pref.submitMove) {
      setTimeout(function() {
        backbutton.stack.push(this.cancelMove);
        this.vm.moveToSubmit = move;
        m.redraw();
      }.bind(this), this.data.pref.animationDuration || 0);
    } else socket.send('move', move, { ackable: true });
  };

  this.cancelMove = function(fromBB) {
    if (fromBB !== 'backbutton') backbutton.stack.pop();
    this.vm.moveToSubmit = null;
    this.jump(this.vm.ply);
  }.bind(this);

  this.submitMove = function(v) {
    if (v) {
      if (this.vm.moveToSubmit)
        socket.send('move', this.vm.moveToSubmit, {
          ackable: true
        });
        this.vm.moveToSubmit = null;
    } else {
      this.cancelMove();
    }
  }.bind(this);

  var userMove = function(orig, dest, meta) {
    if (!promotion.start(this, orig, dest, meta.premove)) this.sendMove(orig, dest);
  }.bind(this);

  var onMove = function(orig, dest, capturedPiece) {
    if (capturedPiece) {
      if (this.data.game.variant.key === 'atomic') {
        atomic.capture(this, dest);
        sound.explosion();
      }
      else sound.capture();
    } else sound.move();
  }.bind(this);

  this.apiMove = function(o) {
    m.startComputation();
    let d = this.data;
    d.game.turns = o.ply;
    d.game.player = o.ply % 2 === 0 ? 'white' : 'black';
    const playedColor = o.ply % 2 === 0 ? 'black' : 'white';
    if (o.status) d.game.status = o.status;
    d[d.player.color === 'white' ? 'player' : 'opponent'].offeringDraw = o.wDraw;
    d[d.player.color === 'black' ? 'player' : 'opponent'].offeringDraw = o.bDraw;
    d.possibleMoves = d.player.color === d.game.player ? o.dests : null;
    this.setTitle();

    if (!this.replaying()) {
      this.vm.ply++;
      this.chessground.apiMove(o.uci.substr(0, 2), o.uci.substr(2, 2));

      if (o.enpassant) {
        let p = o.enpassant;
        let pieces = {};
        pieces[p.key] = null;
        this.chessground.setPieces(pieces);
        if (d.game.variant.key === 'atomic') atomic.enpassant(this, p.key, p.color);
        sound.capture();
      }

      if (o.promotion) ground.promote(this.chessground, o.promotion.key, o.promotion.pieceClass);

      if (o.castle && !this.chessground.data.autoCastle) {
        let c = o.castle;
        let pieces = {};
        pieces[c.king[0]] = null;
        pieces[c.rook[0]] = null;
        pieces[c.king[1]] = {
          role: 'king',
          color: c.color
        };
        pieces[c.rook[1]] = {
          role: 'rook',
          color: c.color
        };
        this.chessground.setPieces(pieces);
      }

      this.chessground.set({
        turnColor: d.game.player,
        movable: {
          dests: gameApi.isPlayerPlaying(d) ? gameApi.parsePossibleMoves(d.possibleMoves) : {}
        },
        check: o.check
      });

      if (playedColor !== d.player.color) {
        // atrocious hack to prevent race condition
        // with explosions and premoves
        // https://github.com/ornicar/lila/issues/343
        if (d.game.variant.key === 'atomic') setTimeout(this.chessground.playPremove, 100);
        else this.chessground.playPremove();
      }
      if (this.data.game.speed === 'correspondence' && session.isConnected()) session.refresh();
    }

    if (o.clock) {
      let c = o.clock;
      if (this.clock) this.clock.update(c.white, c.black);
      else if (this.correspondenceClock) this.correspondenceClock.update(c.white, c.black);
    }

    d.game.threefold = !!o.threefold;
    d.steps.push({
      ply: this.lastPly() + 1,
      fen: o.fen,
      san: o.san,
      uci: o.uci,
      check: o.check
    });
    gameApi.setOnGame(d, playedColor, true);
    m.endComputation();
  }.bind(this);

  this.chessground = ground.make(this.data, cfg.game.fen, userMove, onMove);

  this.clock = this.data.clock ? new clockCtrl(
    this.data.clock,
    this.data.player.spectator ? function() {} : throttle(function() {
      socket.send('outoftime');
    }, 500),
    this.data.player.spectator ? null : this.data.player.color
  ) : false;

  this.isClockRunning = function() {
    return this.data.clock && gameApi.playable(this.data) &&
      ((this.data.game.turns - this.data.game.startedAtTurn) > 1 || this.data.clock.running);
  }.bind(this);

  this.clockTick = function() {
    if (this.isClockRunning()) this.clock.tick(this.data.game.player);
  }.bind(this);

  var makeCorrespondenceClock = function() {
    if (this.data.correspondence && !this.correspondenceClock)
      this.correspondenceClock = new correspondenceClockCtrl(
        this,
        this.data.correspondence,
        () => socket.send('outoftime')
      );
  }.bind(this);
  makeCorrespondenceClock();

  var correspondenceClockTick = function() {
    if (this.correspondenceClock && gameApi.playable(this.data))
      this.correspondenceClock.tick(this.data.game.player);
  }.bind(this);

  var clockIntervId;
  if (this.clock) clockIntervId = setInterval(this.clockTick, 100);
  else if (this.correspondenceClock) clockIntervId = setInterval(correspondenceClockTick, 1000);

  this.chat = (this.data.opponent.ai || this.data.player.spectator) ?
    null : new chat.controller(this);

  this.reload = function(rCfg) {
    if (this.stepsHash(rCfg.steps) !== this.stepsHash(this.data.steps))
      this.vm.ply = rCfg.steps[rCfg.steps.length - 1].ply;
    if (this.chat) this.chat.onReload(rCfg.chat);
    if (this.data.tv) rCfg.tv = this.data.tv;
    if (this.data.userTV) rCfg.userTV = this.data.userTV;
    this.data = data(rCfg);
    makeCorrespondenceClock();
    this.setTitle();
    if (!this.replaying()) ground.reload(this.chessground, this.data, rCfg.game.fen, this.vm.flip);
  }.bind(this);

  window.plugins.insomnia.keepAwake();

  var onResume = function() {
    xhr.reload(this).then(this.reload);
  }.bind(this);

  document.addEventListener('resume', onResume);

  this.onunload = function() {
    socket.destroy();
    clearInterval(clockIntervId);
    if (this.chat) this.chat.onunload();
    document.removeEventListener('resume', onResume);
    window.plugins.insomnia.allowSleepAgain();
    signals.seekCanceled.remove(connectSocket);
  };
}

<|endoftext|>


<commit_before>define(['bluebird', 'kb_lib/props', 'kb_lib/messenger', './widget/manager', './Session'], (
    Promise,
    props,
    Messenger,
    WidgetManager,
    Session
) => {
    'use strict';

    class Runtime {
        constructor({ token, username, config }) {
            this.token = token;
            this.username = username;
            this.widgetManager = new WidgetManager({
                baseWidgetConfig: {
                    runtime: this
                }
            });

            this.configDb = new props.Props({ data: config });

            this.pluginPath = '/modules/plugins/auth2-client/iframe_root';
            this.pluginResourcePath = this.pluginPath + '/resources';

            this.messenger = new Messenger();

            this.heartbeatTimer = null;

            this.services = {
                session: new Session({ runtime: this })
            };
        }

        config(path, defaultValue) {
            return this.configDb.getItem(path, defaultValue);
        }

        getConfig(path, defaultValue) {
            return this.config(path, defaultValue);
        }

        service(name) {
            switch (name) {
            case 'session':
                return this.services.session;
            }
        }

        getService(name) {
            return this.service(name);
        }

        send(channel, message, data) {
            this.messenger.send({ channel, message, data });
        }

        receive(channel, message, handler) {
            return this.messenger.receive({ channel, message, handler });
        }

        recv(channel, message, handler) {
            return this.receive(channel, message, handler);
        }

        drop(subscription) {
            this.messenger.unreceive(subscription);
        }

        start() {
            return Promise.try(() => {
                this.heartbeatTimer = window.setInterval(() => {
                    this.send('app', 'heartbeat', { time: new Date().getTime() });
                }, 1000);
                return this.services.session.start();
            });
        }

        stop() {
            return Promise.try(() => {
                window.clearInterval(this.heartbeatTimer);

                return this.services.session.stop();
            });
        }
    }

    return Runtime;
});
<commit_msg>fix case for runtime module call<commit_after>define(['bluebird', 'kb_lib/props', 'kb_lib/messenger', './widget/manager', './session'], (
    Promise,
    props,
    Messenger,
    WidgetManager,
    Session
) => {
    'use strict';

    class Runtime {
        constructor({ token, username, config }) {
            this.token = token;
            this.username = username;
            this.widgetManager = new WidgetManager({
                baseWidgetConfig: {
                    runtime: this
                }
            });

            this.configDb = new props.Props({ data: config });

            this.pluginPath = '/modules/plugins/auth2-client/iframe_root';
            this.pluginResourcePath = this.pluginPath + '/resources';

            this.messenger = new Messenger();

            this.heartbeatTimer = null;

            this.services = {
                session: new Session({ runtime: this })
            };
        }

        config(path, defaultValue) {
            return this.configDb.getItem(path, defaultValue);
        }

        getConfig(path, defaultValue) {
            return this.config(path, defaultValue);
        }

        service(name) {
            switch (name) {
            case 'session':
                return this.services.session;
            }
        }

        getService(name) {
            return this.service(name);
        }

        send(channel, message, data) {
            this.messenger.send({ channel, message, data });
        }

        receive(channel, message, handler) {
            return this.messenger.receive({ channel, message, handler });
        }

        recv(channel, message, handler) {
            return this.receive(channel, message, handler);
        }

        drop(subscription) {
            this.messenger.unreceive(subscription);
        }

        start() {
            return Promise.try(() => {
                this.heartbeatTimer = window.setInterval(() => {
                    this.send('app', 'heartbeat', { time: new Date().getTime() });
                }, 1000);
                return this.services.session.start();
            });
        }

        stop() {
            return Promise.try(() => {
                window.clearInterval(this.heartbeatTimer);

                return this.services.session.stop();
            });
        }
    }

    return Runtime;
});
<|endoftext|>


<commit_before>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright © 2008–2013 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org

    This file is part of Zotero.

    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.

    ***** END LICENSE BLOCK *****
*/

"use strict";

Zotero_Preferences.Sync = {
	init: function () {
		this.updateStorageSettings(null, null, true);

		document.getElementById('sync-password').value = Zotero.Sync.Server.password;
		var pass = Zotero.Sync.Storage.WebDAV.password;
		if (pass) {
			document.getElementById('storage-password').value = pass;
		}
	},

	updateStorageSettings: function (enabled, protocol, skipWarnings) {
		if (enabled === null) {
			enabled = document.getElementById('pref-storage-enabled').value;
		}

		var oldProtocol = document.getElementById('pref-storage-protocol').value;
		if (protocol === null) {
			protocol = oldProtocol;
		}

		var storageSettings = document.getElementById('storage-settings');
		var protocolMenu = document.getElementById('storage-protocol');
		var settings = document.getElementById('storage-webdav-settings');
		var sep = document.getElementById('storage-separator');

		if (!enabled || protocol == 'zotero') {
			settings.hidden = true;
			sep.hidden = false;
		}
		else {
			settings.hidden = false;
			sep.hidden = true;
		}

		var menulists = storageSettings.getElementsByTagName('menulist');
		for each(var menulist in menulists) {
			if (menulist.className == 'storage-personal') {
				menulist.disabled = !enabled;
			}
		}

		if (!skipWarnings) {
			// WARN if going between
		}

		if (oldProtocol == 'zotero' && protocol == 'webdav') {
			var sql = "SELECT COUNT(*) FROM version WHERE schema LIKE 'storage_zfs%'";
			if (Zotero.DB.valueQuery(sql)) {
				var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
										.getService(Components.interfaces.nsIPromptService);
				var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
									+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING)
									+ ps.BUTTON_DELAY_ENABLE;
				var account = Zotero.Sync.Server.username;
				var index = ps.confirmEx(
					null,
					Zotero.getString('zotero.preferences.sync.purgeStorage.title'),
					Zotero.getString('zotero.preferences.sync.purgeStorage.desc'),
					buttonFlags,
					Zotero.getString('zotero.preferences.sync.purgeStorage.confirmButton'),
					Zotero.getString('zotero.preferences.sync.purgeStorage.cancelButton'), null, null, {}
				);

				if (index == 0) {
					var sql = "INSERT OR IGNORE INTO settings VALUES (?,?,?)";
					Zotero.DB.query(sql, ['storage', 'zfsPurge', 'user']);

					Zotero.Sync.Storage.ZFS.purgeDeletedStorageFiles()
					.then(function () {
						ps.alert(
							null,
							Zotero.getString("general.success"),
							"Attachment files from your personal library have been removed from the Zotero servers."
						);
					})
					.catch(function (e) {
						Zotero.debug(e, 1);
						Components.utils.reportError(e);

						ps.alert(
							null,
							Zotero.getString("general.error"),
							"An error occurred. Please try again later."
						);
					});
				}
			}
		}

		var self = this;
		setTimeout(function () {
			self.updateStorageTerms();
		}, 1)
	},

	updateStorageSettingsGroups: function (enabled) {
		var storageSettings = document.getElementById('storage-settings');
		var menulists = storageSettings.getElementsByTagName('menulist');
		for each(var menulist in menulists) {
			if (menulist.className == 'storage-groups') {
				menulist.disabled = !enabled;
			}
		}

		var self = this;
		setTimeout(function () {
			self.updateStorageTerms();
		}, 1)
	},

	updateStorageTerms: function () {
		var terms = document.getElementById('storage-terms');

		var libraryEnabled = document.getElementById('pref-storage-enabled').value;
		var storageProtocol = document.getElementById('pref-storage-protocol').value;
		var groupsEnabled = document.getElementById('pref-group-storage-enabled').value;

		terms.hidden = !((libraryEnabled && storageProtocol == 'zotero') || groupsEnabled);
	},

	unverifyStorageServer: function () {
		Zotero.Prefs.set('sync.storage.verified', false);
		Zotero.Sync.Storage.resetAllSyncStates(null, true, false);
	},

	verifyStorageServer: function () {
		Zotero.debug("Verifying storage");

		var verifyButton = document.getElementById("storage-verify");
		var abortButton = document.getElementById("storage-abort");
		var progressMeter = document.getElementById("storage-progress");
		var urlField = document.getElementById("storage-url");
		var usernameField = document.getElementById("storage-username");
		var passwordField = document.getElementById("storage-password");

		verifyButton.hidden = true;
		abortButton.hidden = false;
		progressMeter.hidden = false;

		var request = null;
		var onDone = false;

		Zotero.Sync.Storage.WebDAV.checkServer()
		// Get the XMLHttpRequest for possible cancelling
		.progress(function (obj) {
			request = obj.xmlhttp;
		})
		.finally(function () {
			verifyButton.hidden = false;
			abortButton.hidden = true;
			progressMeter.hidden = true;
		})
		.spread(function (uri, status) {
			switch (status) {
				case Zotero.Sync.Storage.ERROR_NO_URL:
					onDone = function () {
						urlField.focus();
					};
					break;

				case Zotero.Sync.Storage.ERROR_NO_USERNAME:
					onDone = function () {
						usernameField.focus();
					};
					break;

				case Zotero.Sync.Storage.ERROR_NO_PASSWORD:
				case Zotero.Sync.Storage.ERROR_AUTH_FAILED:
					onDone = function () {
						passwordField.focus();
					};
					break;
			}

			return Zotero.Sync.Storage.WebDAV.checkServerCallback(uri, status, window);
		})
		.then(function (success) {
			if (success) {
				Zotero.debug("WebDAV verification succeeded");

				var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
					.getService(Components.interfaces.nsIPromptService);
				promptService.alert(
					window,
					Zotero.getString('sync.storage.serverConfigurationVerified'),
					Zotero.getString('sync.storage.fileSyncSetUp')
				);
				Zotero.Prefs.set("sync.storage.verified", true);
			}
			else {
				Zotero.debug("WebDAV verification failed");
				if (onDone) {
					setTimeout(function () {
						onDone();
					}, 1);
				}
			}
		})
		.catch(function (e) {
			Zotero.debug("WebDAV verification failed");
			Zotero.debug(e, 1);
			Components.utils.reportError(e);
			Zotero.Utilities.Internal.errorPrompt(Zotero.getString('general.error'), e);

			if (onDone) {
				setTimeout(function () {
					onDone();
				}, 1);
			}
		})
		.done();

		abortButton.onclick = function () {
			if (request) {
				Zotero.debug("Cancelling verification request");
				request.onreadystatechange = undefined;
				request.abort();
				verifyButton.hidden = false;
				abortButton.hidden = true;
				progressMeter.hidden = true;
			}
		}
	},

	handleSyncResetSelect: function (obj) {
		var index = obj.selectedIndex;
		var rows = obj.getElementsByTagName('row');

		for (var i=0; i<rows.length; i++) {
			if (i == index) {
				rows[i].setAttribute('selected', 'true');
			}
			else {
				rows[i].removeAttribute('selected');
			}
		}
	},

	handleSyncReset: function (action) {
		var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
								.getService(Components.interfaces.nsIPromptService);

		if (!Zotero.Sync.Server.enabled) {
			ps.alert(
				null,
				Zotero.getString('general.error'),
				Zotero.getString('zotero.preferences.sync.reset.userInfoMissing',
								document.getElementById('zotero-prefpane-sync')
								.getElementsByTagName('tab')[0].label)
			);
			return;
		}

		var account = Zotero.Sync.Server.username;

		switch (action) {
			case 'restore-from-server':
				var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
									+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)
									+ ps.BUTTON_POS_1_DEFAULT;
				var index = ps.confirmEx(
					null,
					Zotero.getString('general.warning'),
					Zotero.getString('zotero.preferences.sync.reset.restoreFromServer', account),
					buttonFlags,
					Zotero.getString('zotero.preferences.sync.reset.replaceLocalData'),
					null, null, null, {}
				);

				switch (index) {
					case 0:
						// TODO: better error handling

						// Verify username and password
						var callback = function () {
							Zotero.Schema.stopRepositoryTimer();
							Zotero.Sync.Runner.clearSyncTimeout();

							Zotero.DB.skipBackup = true;

							var file = Zotero.getZoteroDirectory();
							file.append('restore-from-server');
							Zotero.File.putContents(file, '');

							var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING);
							var index = ps.confirmEx(
								null,
								Zotero.getString('general.restartRequired'),
								Zotero.getString('zotero.preferences.sync.reset.restartToComplete'),
								buttonFlags,
								Zotero.getString('general.restartNow'),
								null, null, null, {}
							);

							var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
									.getService(Components.interfaces.nsIAppStartup);
							appStartup.quit(Components.interfaces.nsIAppStartup.eRestart | Components.interfaces.nsIAppStartup.eAttemptQuit);
						};

						// TODO: better way of checking for an active session?
						if (Zotero.Sync.Server.sessionIDComponent == 'sessionid=') {
							Zotero.Sync.Server.login()
							.then(function () {
								callback();
							})
							.done();
						}
						else {
							callback();
						}
						break;

					// Cancel
					case 1:
						return;
				}
				break;

			case 'restore-to-server':
				var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
								+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)
								+ ps.BUTTON_POS_1_DEFAULT;
				var index = ps.confirmEx(
					null,
					Zotero.getString('general.warning'),
					Zotero.getString('zotero.preferences.sync.reset.restoreToServer', account),
					buttonFlags,
					Zotero.getString('zotero.preferences.sync.reset.replaceServerData'),
					null, null, null, {}
				);

				switch (index) {
					case 0:
						// TODO: better error handling
						Zotero.Sync.Server.clear(function () {
							Zotero.Sync.Server.sync(/*{

								// TODO: this doesn't work if the pref window is
								closed. fix, perhaps by making original callbacks
								available to the custom callbacks

								onSuccess: function () {
									Zotero.Sync.Runner.setSyncIcon();
									ps.alert(
										null,
										"Restore Completed",
										"Data on the Zotero server has been successfully restored."
									);
								},
								onError: function (msg) {
									// TODO: combine with error dialog for regular syncs
									ps.alert(
										null,
										"Restore Failed",
										"An error occurred uploading your data to the server.\n\n"
											+ "Click the sync error icon in the Zotero toolbar "
											+ "for further information."
									);
									Zotero.Sync.Runner.error(msg);
								}
							}*/);
						});
						break;

					// Cancel
					case 1:
						return;
				}

				break;

			case 'reset-storage-history':
				var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
								+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)
								+ ps.BUTTON_POS_1_DEFAULT;
				var index = ps.confirmEx(
					null,
					Zotero.getString('general.warning'),
					Zotero.getString('zotero.preferences.sync.reset.fileSyncHistory'),
					buttonFlags,
					Zotero.getString('general.reset'),
					null, null, null, {}
				);

				switch (index) {
					case 0:
						Zotero.Sync.Storage.resetAllSyncStates();
						ps.alert(
							null,
							"File Sync History Cleared",
							"The file sync history has been cleared."
						);
						break;

					// Cancel
					case 1:
						return;
				}

				break;

			default:
				throw ("Invalid action '" + action + "' in handleSyncReset()");
		}
	}
};
<commit_msg>Remove another extra function wrapper<commit_after>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright © 2008–2013 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org

    This file is part of Zotero.

    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.

    ***** END LICENSE BLOCK *****
*/

"use strict";

Zotero_Preferences.Sync = {
	init: function () {
		this.updateStorageSettings(null, null, true);

		document.getElementById('sync-password').value = Zotero.Sync.Server.password;
		var pass = Zotero.Sync.Storage.WebDAV.password;
		if (pass) {
			document.getElementById('storage-password').value = pass;
		}
	},

	updateStorageSettings: function (enabled, protocol, skipWarnings) {
		if (enabled === null) {
			enabled = document.getElementById('pref-storage-enabled').value;
		}

		var oldProtocol = document.getElementById('pref-storage-protocol').value;
		if (protocol === null) {
			protocol = oldProtocol;
		}

		var storageSettings = document.getElementById('storage-settings');
		var protocolMenu = document.getElementById('storage-protocol');
		var settings = document.getElementById('storage-webdav-settings');
		var sep = document.getElementById('storage-separator');

		if (!enabled || protocol == 'zotero') {
			settings.hidden = true;
			sep.hidden = false;
		}
		else {
			settings.hidden = false;
			sep.hidden = true;
		}

		var menulists = storageSettings.getElementsByTagName('menulist');
		for each(var menulist in menulists) {
			if (menulist.className == 'storage-personal') {
				menulist.disabled = !enabled;
			}
		}

		if (!skipWarnings) {
			// WARN if going between
		}

		if (oldProtocol == 'zotero' && protocol == 'webdav') {
			var sql = "SELECT COUNT(*) FROM version WHERE schema LIKE 'storage_zfs%'";
			if (Zotero.DB.valueQuery(sql)) {
				var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
										.getService(Components.interfaces.nsIPromptService);
				var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
									+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING)
									+ ps.BUTTON_DELAY_ENABLE;
				var account = Zotero.Sync.Server.username;
				var index = ps.confirmEx(
					null,
					Zotero.getString('zotero.preferences.sync.purgeStorage.title'),
					Zotero.getString('zotero.preferences.sync.purgeStorage.desc'),
					buttonFlags,
					Zotero.getString('zotero.preferences.sync.purgeStorage.confirmButton'),
					Zotero.getString('zotero.preferences.sync.purgeStorage.cancelButton'), null, null, {}
				);

				if (index == 0) {
					var sql = "INSERT OR IGNORE INTO settings VALUES (?,?,?)";
					Zotero.DB.query(sql, ['storage', 'zfsPurge', 'user']);

					Zotero.Sync.Storage.ZFS.purgeDeletedStorageFiles()
					.then(function () {
						ps.alert(
							null,
							Zotero.getString("general.success"),
							"Attachment files from your personal library have been removed from the Zotero servers."
						);
					})
					.catch(function (e) {
						Zotero.debug(e, 1);
						Components.utils.reportError(e);

						ps.alert(
							null,
							Zotero.getString("general.error"),
							"An error occurred. Please try again later."
						);
					});
				}
			}
		}

		var self = this;
		setTimeout(function () {
			self.updateStorageTerms();
		}, 1)
	},

	updateStorageSettingsGroups: function (enabled) {
		var storageSettings = document.getElementById('storage-settings');
		var menulists = storageSettings.getElementsByTagName('menulist');
		for each(var menulist in menulists) {
			if (menulist.className == 'storage-groups') {
				menulist.disabled = !enabled;
			}
		}

		var self = this;
		setTimeout(function () {
			self.updateStorageTerms();
		}, 1)
	},

	updateStorageTerms: function () {
		var terms = document.getElementById('storage-terms');

		var libraryEnabled = document.getElementById('pref-storage-enabled').value;
		var storageProtocol = document.getElementById('pref-storage-protocol').value;
		var groupsEnabled = document.getElementById('pref-group-storage-enabled').value;

		terms.hidden = !((libraryEnabled && storageProtocol == 'zotero') || groupsEnabled);
	},

	unverifyStorageServer: function () {
		Zotero.Prefs.set('sync.storage.verified', false);
		Zotero.Sync.Storage.resetAllSyncStates(null, true, false);
	},

	verifyStorageServer: function () {
		Zotero.debug("Verifying storage");

		var verifyButton = document.getElementById("storage-verify");
		var abortButton = document.getElementById("storage-abort");
		var progressMeter = document.getElementById("storage-progress");
		var urlField = document.getElementById("storage-url");
		var usernameField = document.getElementById("storage-username");
		var passwordField = document.getElementById("storage-password");

		verifyButton.hidden = true;
		abortButton.hidden = false;
		progressMeter.hidden = false;

		var request = null;
		var onDone = false;

		Zotero.Sync.Storage.WebDAV.checkServer()
		// Get the XMLHttpRequest for possible cancelling
		.progress(function (obj) {
			request = obj.xmlhttp;
		})
		.finally(function () {
			verifyButton.hidden = false;
			abortButton.hidden = true;
			progressMeter.hidden = true;
		})
		.spread(function (uri, status) {
			switch (status) {
				case Zotero.Sync.Storage.ERROR_NO_URL:
					onDone = function () {
						urlField.focus();
					};
					break;

				case Zotero.Sync.Storage.ERROR_NO_USERNAME:
					onDone = function () {
						usernameField.focus();
					};
					break;

				case Zotero.Sync.Storage.ERROR_NO_PASSWORD:
				case Zotero.Sync.Storage.ERROR_AUTH_FAILED:
					onDone = function () {
						passwordField.focus();
					};
					break;
			}

			return Zotero.Sync.Storage.WebDAV.checkServerCallback(uri, status, window);
		})
		.then(function (success) {
			if (success) {
				Zotero.debug("WebDAV verification succeeded");

				var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
					.getService(Components.interfaces.nsIPromptService);
				promptService.alert(
					window,
					Zotero.getString('sync.storage.serverConfigurationVerified'),
					Zotero.getString('sync.storage.fileSyncSetUp')
				);
				Zotero.Prefs.set("sync.storage.verified", true);
			}
			else {
				Zotero.debug("WebDAV verification failed");
				if (onDone) {
					setTimeout(function () {
						onDone();
					}, 1);
				}
			}
		})
		.catch(function (e) {
			Zotero.debug("WebDAV verification failed");
			Zotero.debug(e, 1);
			Components.utils.reportError(e);
			Zotero.Utilities.Internal.errorPrompt(Zotero.getString('general.error'), e);

			if (onDone) {
				setTimeout(function () {
					onDone();
				}, 1);
			}
		})
		.done();

		abortButton.onclick = function () {
			if (request) {
				Zotero.debug("Cancelling verification request");
				request.onreadystatechange = undefined;
				request.abort();
				verifyButton.hidden = false;
				abortButton.hidden = true;
				progressMeter.hidden = true;
			}
		}
	},

	handleSyncResetSelect: function (obj) {
		var index = obj.selectedIndex;
		var rows = obj.getElementsByTagName('row');

		for (var i=0; i<rows.length; i++) {
			if (i == index) {
				rows[i].setAttribute('selected', 'true');
			}
			else {
				rows[i].removeAttribute('selected');
			}
		}
	},

	handleSyncReset: function (action) {
		var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
								.getService(Components.interfaces.nsIPromptService);

		if (!Zotero.Sync.Server.enabled) {
			ps.alert(
				null,
				Zotero.getString('general.error'),
				Zotero.getString('zotero.preferences.sync.reset.userInfoMissing',
								document.getElementById('zotero-prefpane-sync')
								.getElementsByTagName('tab')[0].label)
			);
			return;
		}

		var account = Zotero.Sync.Server.username;

		switch (action) {
			case 'restore-from-server':
				var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
									+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)
									+ ps.BUTTON_POS_1_DEFAULT;
				var index = ps.confirmEx(
					null,
					Zotero.getString('general.warning'),
					Zotero.getString('zotero.preferences.sync.reset.restoreFromServer', account),
					buttonFlags,
					Zotero.getString('zotero.preferences.sync.reset.replaceLocalData'),
					null, null, null, {}
				);

				switch (index) {
					case 0:
						// TODO: better error handling

						// Verify username and password
						var callback = function () {
							Zotero.Schema.stopRepositoryTimer();
							Zotero.Sync.Runner.clearSyncTimeout();

							Zotero.DB.skipBackup = true;

							var file = Zotero.getZoteroDirectory();
							file.append('restore-from-server');
							Zotero.File.putContents(file, '');

							var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING);
							var index = ps.confirmEx(
								null,
								Zotero.getString('general.restartRequired'),
								Zotero.getString('zotero.preferences.sync.reset.restartToComplete'),
								buttonFlags,
								Zotero.getString('general.restartNow'),
								null, null, null, {}
							);

							var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
									.getService(Components.interfaces.nsIAppStartup);
							appStartup.quit(Components.interfaces.nsIAppStartup.eRestart | Components.interfaces.nsIAppStartup.eAttemptQuit);
						};

						// TODO: better way of checking for an active session?
						if (Zotero.Sync.Server.sessionIDComponent == 'sessionid=') {
							Zotero.Sync.Server.login()
							.then(callback)
							.done();
						}
						else {
							callback();
						}
						break;

					// Cancel
					case 1:
						return;
				}
				break;

			case 'restore-to-server':
				var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
								+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)
								+ ps.BUTTON_POS_1_DEFAULT;
				var index = ps.confirmEx(
					null,
					Zotero.getString('general.warning'),
					Zotero.getString('zotero.preferences.sync.reset.restoreToServer', account),
					buttonFlags,
					Zotero.getString('zotero.preferences.sync.reset.replaceServerData'),
					null, null, null, {}
				);

				switch (index) {
					case 0:
						// TODO: better error handling
						Zotero.Sync.Server.clear(function () {
							Zotero.Sync.Server.sync(/*{

								// TODO: this doesn't work if the pref window is
								closed. fix, perhaps by making original callbacks
								available to the custom callbacks

								onSuccess: function () {
									Zotero.Sync.Runner.setSyncIcon();
									ps.alert(
										null,
										"Restore Completed",
										"Data on the Zotero server has been successfully restored."
									);
								},
								onError: function (msg) {
									// TODO: combine with error dialog for regular syncs
									ps.alert(
										null,
										"Restore Failed",
										"An error occurred uploading your data to the server.\n\n"
											+ "Click the sync error icon in the Zotero toolbar "
											+ "for further information."
									);
									Zotero.Sync.Runner.error(msg);
								}
							}*/);
						});
						break;

					// Cancel
					case 1:
						return;
				}

				break;

			case 'reset-storage-history':
				var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
								+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)
								+ ps.BUTTON_POS_1_DEFAULT;
				var index = ps.confirmEx(
					null,
					Zotero.getString('general.warning'),
					Zotero.getString('zotero.preferences.sync.reset.fileSyncHistory'),
					buttonFlags,
					Zotero.getString('general.reset'),
					null, null, null, {}
				);

				switch (index) {
					case 0:
						Zotero.Sync.Storage.resetAllSyncStates();
						ps.alert(
							null,
							"File Sync History Cleared",
							"The file sync history has been cleared."
						);
						break;

					// Cancel
					case 1:
						return;
				}

				break;

			default:
				throw ("Invalid action '" + action + "' in handleSyncReset()");
		}
	}
};
<|endoftext|>


<commit_before>import { expect } from 'chai';
import query from '../src/query';

describe('query', () => {
  let target;
  let key;
  let descriptor;
  let decoratorQuery;

  beforeEach(() => {
    target = function() {};
    key = 'user';
    descriptor = function() {};
    descriptor.value = function() {
      return 'Graphite';
    };

    decoratorQuery = query();
  });

  it('should be a function', (done) => {
    decoratorQuery(target, key, descriptor);
    expect(target.prototype.query).to.be.function;
    done();
  });

  context('When query not have params', () => {
    it('should be Query a String and contain user: [User]', (done) => {
      decoratorQuery(target, key, descriptor);
      expect(target.Query).to.have.string;
      expect(target.Query.trim()).eql('user: [User],');
      done();
    });
  });

  context('When query have params', () => {
    it('should be Query a String and contain user(name: String): [User]', (done) => {
      const decoratorQuery = query('name: String');
      decoratorQuery(target, key, descriptor);
      expect(target.Query).to.have.string;
      expect(target.Query.trim()).eql('user(name: String): [User],');
      done();
    });
  });

  it('should be Resolvers a Object', (done) => {
    decoratorQuery(target, key, descriptor);
    expect(target.Resolvers).to.have.object;
    done();
  });

  it('should Resolvers contains object Query', (done) => {
    decoratorQuery(target, key, descriptor);
    expect(target.Resolvers.Query).to.have.object;
    done();
  });

  it('should object Query in the ' + key + ' has a promise', (done) => {
    decoratorQuery(target, key, descriptor);
    expect(target.Resolvers.Query[key]).to.have.function;
    done();
  });

  describe('Promise query', () => {
    describe('Promise success', () => {
      it('should return result', (done) => {
        decoratorQuery(target, key, descriptor);
        target.Resolvers.Query[key]().then(result => {
          expect(result).eql('Graphite');
          done();
        });
      });
    });

    describe('Promise failed', () => {
      it('should return error message', (done) => {
        const descriptorFailed = function() {};
        descriptorFailed.initializer = '';
        decoratorQuery(target, key, descriptorFailed);
        target.Resolvers.Query[key]().catch(error => {
          expect(error.message).to.contain('Decorators query failed.');
          done();
        });
      });
    });
  });
});
<commit_msg>[UPDATE] - change text<commit_after>import { expect } from 'chai';
import query from '../src/query';

describe('query', () => {
  let target;
  let key;
  let descriptor;
  let decoratorQuery;

  beforeEach(() => {
    target = function() {};
    key = 'user';
    descriptor = function() {};
    descriptor.value = function() {
      return 'Graphite';
    };

    decoratorQuery = query();
  });

  it('should be a function', (done) => {
    decoratorQuery(target, key, descriptor);
    expect(target.prototype.query).to.be.function;
    done();
  });

  context('When query not have params', () => {
    it('should be Query a String and contain user: [User]', (done) => {
      decoratorQuery(target, key, descriptor);
      expect(target.Query).to.have.string;
      expect(target.Query.trim()).eql('user: [User],');
      done();
    });
  });

  context('When query have params', () => {
    it('should be Query a String and contain user(name: String): [User]', (done) => {
      const decoratorQuery = query('name: String');
      decoratorQuery(target, key, descriptor);
      expect(target.Query).to.have.string;
      expect(target.Query.trim()).eql('user(name: String): [User],');
      done();
    });
  });

  it('should be Resolvers a Object', (done) => {
    decoratorQuery(target, key, descriptor);
    expect(target.Resolvers).to.have.object;
    done();
  });

  it('should Resolvers contains object Query', (done) => {
    decoratorQuery(target, key, descriptor);
    expect(target.Resolvers.Query).to.have.object;
    done();
  });

  it('should object Query in the ' + key + ' has a promise', (done) => {
    decoratorQuery(target, key, descriptor);
    expect(target.Resolvers.Query[key]).to.have.function;
    done();
  });

  describe('Promise query', () => {
    describe('Promise success', () => {
      it('should return Graphite', (done) => {
        decoratorQuery(target, key, descriptor);
        target.Resolvers.Query[key]().then(result => {
          expect(result).eql('Graphite');
          done();
        });
      });
    });

    describe('Promise failed', () => {
      it('should return error message', (done) => {
        const descriptorFailed = function() {};
        descriptorFailed.initializer = '';
        decoratorQuery(target, key, descriptorFailed);
        target.Resolvers.Query[key]().catch(error => {
          expect(error.message).to.contain('Decorators query failed.');
          done();
        });
      });
    });
  });
});
<|endoftext|>


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

    angular.module('data-prep.services.utils')
        /**
         * @ngdoc object
         * @name data-prep.services.utils.service:apiUrl
         * @description The REST api base url
         */
        .constant('apiUrl', 'http://localhost:8083') // 'http://10.42.10.99:8888')
        /**
         * @ngdoc object
         * @name data-prep.services.utils.service:disableDebug
         * @description Application option. Disable debug mode (ex: in production) for performance
         */
        .constant('disableDebug', false);
})();
<commit_msg>"Oops, someone did it again": reverts utils-constants.js to its original shape.<commit_after>(function() {
    'use strict';

    angular.module('data-prep.services.utils')
        /**
         * @ngdoc object
         * @name data-prep.services.utils.service:apiUrl
         * @description The REST api base url
         */
        .constant('apiUrl', 'http://10.42.10.99:8888')
        /**
         * @ngdoc object
         * @name data-prep.services.utils.service:disableDebug
         * @description Application option. Disable debug mode (ex: in production) for performance
         */
        .constant('disableDebug', false);
})();
<|endoftext|>


<commit_before>(function () {
  function toArray(obj) {
    return Array.prototype.slice.apply(obj);
  }

  function $(selector) {
    return document.querySelector(selector);
  }

  function $$(selector) {
    return toArray(document.querySelectorAll(selector));
  }

  loadSW();

  function loadSW() {
    if ('serviceWorker' in navigator) {
      // Service Workers require HTTPS (http://goo.gl/lq4gCo). `localhost`
      // on a custom port is whitelisted.
      if (location.protocol === 'file:') {
        // Just don't execute the SW code if we're viewing the document.
        return;
      }
      if (location.protocol === 'http:' &&
          (!location.port || location.port === '80')) {
        // Change the protocol to HTTPS if we're running on a real server.
        location.protocol = 'https:';
      }

      if (localStorage.disable_sw) {
        console.log('Service Workers are temporarily disabled');
        navigator.serviceWorker.getRegistration('./sw.js').then(function (sw) {
          if (sw) {
            console.log('Temporarily disabling Service Workers, unregistering…');
            sw.unregister();
          }
        });
        return;
      }

      navigator.serviceWorker.register('./sw.js', {scope: './'}).then(function (sw) {
        if (navigator.serviceWorker.controller) {
          console.log('Page successfully fetched from cache by the Service Worker');
        } else {
          console.log('Page successfully registered by the Service Worker');
        }
      }).catch(function (err) {
        console.error('Service Worker error occurred: ' + err);
      });
    } else {
      console.warn('Service Workers are not supported in your browser');
    }
  }

  // Google Analytics.
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-49796218-16', 'auto');
  ga('send', 'pageview');

  $('.gdc__mozilla').addEventListener('click', function () {
    ga('send', 'event', 'click.header', 'mozilla-logo');
  });

  $('.gdc__logo__link').addEventListener('click', function () {
    ga('send', 'event', 'click.header', 'gdc-logo-link');
  });

  $('.gdc__text__link').addEventListener('click', function () {
    ga('send', 'event', 'click.header', 'gdc-text-link');
  });

  $('.gdc__location').addEventListener('click', function () {
    ga('send', 'event', 'click.header', 'booth-link');
  });

  $$('.company__item').forEach(function (el) {
    el.addEventListener('click', function () {
      // NOTE: Not using dataset for IE compatibility.
      ga('send', 'event', 'click.company',
         el.querySelector('img').getAttribute('alt'));
    });
  });

  $$('.highlight__item').forEach(function (el) {
    el.addEventListener('click', function () {
      ga('send', 'event', 'click.demo',
         el.querySelector('.highlight__item__title').textContent);
    });
  });

  $$('#posts a').forEach(function (el) {
    el.addEventListener('click', function () {
      ga('send', 'event', 'click.post', el.textContent);
    });
  });

  $$('#mozilla a').forEach(function (el) {
    el.addEventListener('click', function () {
      ga('send', 'event', 'click.footer', el.textContent);
    });
  });
})();
<commit_msg>open external links in new tabs<commit_after>(function () {
  function toArray(obj) {
    return Array.prototype.slice.apply(obj);
  }

  function $(selector) {
    return document.querySelector(selector);
  }

  function $$(selector) {
    return toArray(document.querySelectorAll(selector));
  }

  // ServiceWorker for caching requests offline.
  loadSW();

  function loadSW() {
    if ('serviceWorker' in navigator) {
      // Service Workers require HTTPS (http://goo.gl/lq4gCo). `localhost`
      // on a custom port is whitelisted.
      if (location.protocol === 'file:') {
        // Just don't execute the SW code if we're viewing the document.
        return;
      }
      if (location.protocol === 'http:' &&
          (!location.port || location.port === '80')) {
        // Change the protocol to HTTPS if we're running on a real server.
        location.protocol = 'https:';
      }

      if (localStorage.disable_sw) {
        console.log('Service Workers are temporarily disabled');
        navigator.serviceWorker.getRegistration('./sw.js').then(function (sw) {
          if (sw) {
            console.log('Temporarily disabling Service Workers, unregistering…');
            sw.unregister();
          }
        });
        return;
      }

      navigator.serviceWorker.register('./sw.js', {scope: './'}).then(function (sw) {
        if (navigator.serviceWorker.controller) {
          console.log('Page successfully fetched from cache by the Service Worker');
        } else {
          console.log('Page successfully registered by the Service Worker');
        }
      }).catch(function (err) {
        console.error('Service Worker error occurred: ' + err);
      });
    } else {
      console.warn('Service Workers are not supported in your browser');
    }
  }

  // Google Analytics.
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-49796218-16', 'auto');
  ga('send', 'pageview');

  $('.gdc__mozilla').addEventListener('click', function () {
    ga('send', 'event', 'click.header', 'mozilla-logo');
  });

  $('.gdc__logo__link').addEventListener('click', function () {
    ga('send', 'event', 'click.header', 'gdc-logo-link');
  });

  $('.gdc__text__link').addEventListener('click', function () {
    ga('send', 'event', 'click.header', 'gdc-text-link');
  });

  $('.gdc__location').addEventListener('click', function () {
    ga('send', 'event', 'click.header', 'booth-link');
  });

  $$('.company__item').forEach(function (el) {
    el.addEventListener('click', function () {
      // NOTE: Not using dataset for IE compatibility.
      ga('send', 'event', 'click.company',
         el.querySelector('img').getAttribute('alt'));
    });
  });

  $$('.highlight__item').forEach(function (el) {
    el.addEventListener('click', function () {
      ga('send', 'event', 'click.demo',
         el.querySelector('.highlight__item__title').textContent);
    });
  });

  $$('#posts a').forEach(function (el) {
    el.addEventListener('click', function () {
      ga('send', 'event', 'click.post', el.textContent);
    });
  });

  $$('#mozilla a').forEach(function (el) {
    el.addEventListener('click', function () {
      ga('send', 'event', 'click.footer', el.textContent);
    });
  });

  // Open external links in new tabs.
  Array.prototype.slice.call(
    document.querySelectorAll('[href^="//"], [href*="://"]')
  ).forEach(function (link) {
    link.setAttribute('target', '_blank');
  });
})();
<|endoftext|>


<commit_before>import * as messages from "../../../messages";
import * as t from "../../../types";

export var metadata = {
  group: "builtin-advanced"
};

export function ForOfStatement(node, parent, scope, file) {
  var left = node.left;

  if (t.isPattern(left)) {
    // for ({ length: k } in { abc: 3 });

    var temp = scope.generateUidIdentifier("ref");

    node.left = t.variableDeclaration("var", [
      t.variableDeclarator(temp)
    ]);

    t.ensureBlock(node);

    node.body.body.unshift(t.variableDeclaration("var", [
      t.variableDeclarator(left, temp)
    ]));

    return;
  }

  if (!t.isVariableDeclaration(left)) return;

  var pattern = left.declarations[0].id;
  if (!t.isPattern(pattern)) return;

  var key = scope.generateUidIdentifier("ref");
  node.left = t.variableDeclaration(left.kind, [
    t.variableDeclarator(key, null)
  ]);

  var nodes = [];

  var destructuring = new DestructuringTransformer({
    kind: left.kind,
    file: file,
    scope: scope,
    nodes: nodes
  });

  destructuring.init(pattern, key);

  t.ensureBlock(node);

  var block = node.body;
  block.body = nodes.concat(block.body);
}

export { ForOfStatement as ForInStatement };

export function Func/*tion*/(node, parent, scope, file) {
  var nodes = [];

  var hasDestructuring = false;

  node.params = node.params.map(function (pattern, i) {
    if (!t.isPattern(pattern)) return pattern;

    hasDestructuring = true;
    var ref = scope.generateUidIdentifier("ref");
    t.inherits(ref, pattern);

    var destructuring = new DestructuringTransformer({
      blockHoist: node.params.length - i,
      nodes:      nodes,
      scope:      scope,
      file:       file,
      kind:       "let"
    });
    destructuring.init(pattern, ref);

    return ref;
  });

  if (!hasDestructuring) return;

  t.ensureBlock(node);

  var block = node.body;
  block.body = nodes.concat(block.body);
}

export function CatchClause(node, parent, scope, file) {
  var pattern = node.param;
  if (!t.isPattern(pattern)) return;

  var ref = scope.generateUidIdentifier("ref");
  node.param = ref;

  var nodes = [];

  var destructuring = new DestructuringTransformer({
    kind: "let",
    file: file,
    scope: scope,
    nodes: nodes
  });
  destructuring.init(pattern, ref);

  node.body.body = nodes.concat(node.body.body);
}

export function AssignmentExpression(node, parent, scope, file) {
  if (!t.isPattern(node.left)) return;

  var ref = scope.generateUidIdentifierBasedOnNode(node.right, "ref");

  var nodes = [];
  nodes.push(t.variableDeclaration("var", [
    t.variableDeclarator(ref, node.right)
  ]));

  var destructuring = new DestructuringTransformer({
    operator: node.operator,
    file: file,
    scope: scope,
    nodes: nodes
  });

  if (t.isArrayExpression(node.right)) {
    destructuring.arrays[ref.name] = true;
  }

  destructuring.init(node.left, ref);

  nodes.push(t.expressionStatement(ref));

  return nodes;
}

function variableDeclarationHasPattern(node) {
  for (var i = 0; i < node.declarations.length; i++) {
    if (t.isPattern(node.declarations[i].id)) {
      return true;
    }
  }
  return false;
}

export function VariableDeclaration(node, parent, scope, file) {
  if (t.isForInStatement(parent) || t.isForOfStatement(parent)) return;
  if (!variableDeclarationHasPattern(node)) return;

  var nodes = [];
  var declar;

  for (var i = 0; i < node.declarations.length; i++) {
    declar = node.declarations[i];

    var patternId = declar.init;
    var pattern   = declar.id;

    var destructuring = new DestructuringTransformer({
      nodes: nodes,
      scope: scope,
      kind:  node.kind,
      file:  file
    });

    if (t.isPattern(pattern)) {
      destructuring.init(pattern, patternId);

      if (+i !== node.declarations.length - 1) {
        // we aren't the last declarator so let's just make the
        // last transformed node inherit from us
        t.inherits(nodes[nodes.length - 1], declar);
      }
    } else {
      nodes.push(t.inherits(destructuring.buildVariableAssignment(declar.id, declar.init), declar));
    }
  }

  if (!t.isProgram(parent) && !t.isBlockStatement(parent)) {
    // https://github.com/babel/babel/issues/113
    // for (let [x] = [0]; false;) {}

    declar = null;

    for (i = 0; i < nodes.length; i++) {
      node = nodes[i];
      declar = declar || t.variableDeclaration(node.kind, []);

      if (!t.isVariableDeclaration(node) && declar.kind !== node.kind) {
        throw file.errorWithNode(node, messages.get("invalidParentForThisNode"));
      }

      declar.declarations = declar.declarations.concat(node.declarations);
    }

    return declar;
  }

  return nodes;
}

function hasRest(pattern) {
  for (var i = 0; i < pattern.elements.length; i++) {
    if (t.isRestElement(pattern.elements[i])) {
      return true;
    }
  }
  return false;
}

var arrayUnpackVisitor = {
  enter(node, parent, scope, state) {
    if (this.isReferencedIdentifier() && state.bindings[node.name]) {
      state.deopt = true;
      this.stop();
    }
  }
};

class DestructuringTransformer {
  constructor(opts) {
    this.blockHoist = opts.blockHoist;
    this.operator   = opts.operator;
    this.arrays     = {};
    this.nodes      = opts.nodes || [];
    this.scope      = opts.scope;
    this.file       = opts.file;
    this.kind       = opts.kind;
  }

  buildVariableAssignment(id, init) {
    var op = this.operator;
    if (t.isMemberExpression(id)) op = "=";

    var node;

    if (op) {
      node = t.expressionStatement(t.assignmentExpression(op, id, init));
    } else {
      node = t.variableDeclaration(this.kind, [
        t.variableDeclarator(id, init)
      ]);
    }

    node._blockHoist = this.blockHoist;

    return node;
  }

  buildVariableDeclaration(id, init) {
    var declar = t.variableDeclaration("var", [
      t.variableDeclarator(id, init)
    ]);
    declar._blockHoist = this.blockHoist;
    return declar;
  }

  push(id, init) {
    if (t.isObjectPattern(id)) {
      this.pushObjectPattern(id, init);
    } else if (t.isArrayPattern(id)) {
      this.pushArrayPattern(id, init);
    } else if (t.isAssignmentPattern(id)) {
      this.pushAssignmentPattern(id, init);
    } else {
      this.nodes.push(this.buildVariableAssignment(id, init));
    }
  }

  toArray(node, count) {
    if (this.file.isLoose("es6.destructuring") || (t.isIdentifier(node) && this.arrays[node.name])) {
      return node;
    } else {
      return this.scope.toArray(node, count);
    }
  }

  pushAssignmentPattern(pattern, valueRef) {
    // we need to assign the current value of the assignment to avoid evaluating
    // it more than once

    var tempValueRef = this.scope.generateUidIdentifierBasedOnNode(valueRef);

    var declar = t.variableDeclaration("var", [
      t.variableDeclarator(tempValueRef, valueRef)
    ]);
    declar._blockHoist = this.blockHoist;
    this.nodes.push(declar);

    //

    var tempConditional = t.conditionalExpression(
      t.binaryExpression("===", tempValueRef, t.identifier("undefined")),
      pattern.right,
      tempValueRef
    );

    var left = pattern.left;
    if (t.isPattern(left)) {
      this.nodes.push(t.expressionStatement(
        t.assignmentExpression("=", tempValueRef, tempConditional)
      ));
      this.push(left, tempValueRef);
    } else {
      this.nodes.push(this.buildVariableAssignment(left, tempConditional));
    }
  }

  pushObjectSpread(pattern, objRef, spreadProp, spreadPropIndex) {
    // get all the keys that appear in this object before the current spread

    var keys = [];

    for (var i = 0; i < pattern.properties.length; i++) {
      var prop = pattern.properties[i];

      // we've exceeded the index of the spread property to all properties to the
      // right need to be ignored
      if (i >= spreadPropIndex) break;

      // ignore other spread properties
      if (t.isSpreadProperty(prop)) continue;

      var key = prop.key;
      if (t.isIdentifier(key) && !prop.computed) key = t.literal(prop.key.name);
      keys.push(key);
    }

    keys = t.arrayExpression(keys);

    //

    var value = t.callExpression(this.file.addHelper("object-without-properties"), [objRef, keys]);
    this.nodes.push(this.buildVariableAssignment(spreadProp.argument, value));
  }

  pushObjectProperty(prop, propRef) {
    if (t.isLiteral(prop.key)) prop.computed = true;

    var pattern = prop.value;
    var objRef  = t.memberExpression(propRef, prop.key, prop.computed);

    if (t.isPattern(pattern)) {
      this.push(pattern, objRef);
    } else {
      this.nodes.push(this.buildVariableAssignment(pattern, objRef));
    }
  }

  pushObjectPattern(pattern, objRef) {
    // https://github.com/babel/babel/issues/681

    if (!pattern.properties.length) {
      this.nodes.push(t.expressionStatement(
        t.callExpression(this.file.addHelper("object-destructuring-empty"), [objRef])
      ));
    }

    // if we have more than one properties in this pattern and the objectRef is a
    // member expression then we need to assign it to a temporary variable so it's
    // only evaluated once

    if (pattern.properties.length > 1 && t.isMemberExpression(objRef)) {
      var temp = this.scope.generateUidIdentifierBasedOnNode(objRef, this.file);
      this.nodes.push(this.buildVariableDeclaration(temp, objRef));
      objRef = temp;
    }

    //

    for (var i = 0; i < pattern.properties.length; i++) {
      var prop = pattern.properties[i];
      if (t.isSpreadProperty(prop)) {
        this.pushObjectSpread(pattern, objRef, prop, i);
      } else {
        this.pushObjectProperty(prop, objRef);
      }
    }
  }

  canUnpackArrayPattern(pattern, arr) {
    // not an array so there's no way we can deal with this
    if (!t.isArrayExpression(arr)) return false;

    // pattern has less elements than the array and doesn't have a rest so some
    // elements wont be evaluated
    if (pattern.elements.length > arr.elements.length) return;
    if (pattern.elements.length < arr.elements.length && !hasRest(pattern)) return false;

    for (var i = 0; i < pattern.elements.length; i++) {
      var elem = pattern.elements[i];

      // deopt on holes
      if (!elem) return false;

      // deopt on member expressions
      if (t.isMemberExpression(elem)) return false;
    }

    // deopt on reference to left side identifiers
    var bindings = t.getBindingIdentifiers(pattern);
    var state = { deopt: false, bindings };
    this.scope.traverse(arr, arrayUnpackVisitor, state);
    return !state.deopt;
  }

  pushUnpackedArrayPattern(pattern, arr) {
    for (var i = 0; i < pattern.elements.length; i++) {
      var elem = pattern.elements[i];
      if (t.isRestElement(elem)) {
        this.push(elem.argument, t.arrayExpression(arr.elements.slice(i)));
      } else {
        this.push(elem, arr.elements[i]);
      }
    }
  }

  pushArrayPattern(pattern, arrayRef) {
    if (!pattern.elements) return;

    // optimise basic array destructuring of an array expression
    //
    // we can't do this to a pattern of unequal size to it's right hand
    // array expression as then there will be values that wont be evaluated
    //
    // eg: var [a, b] = [1, 2];

    if (this.canUnpackArrayPattern(pattern, arrayRef)) {
      return this.pushUnpackedArrayPattern(pattern, arrayRef);
    }

    // if we have a rest then we need all the elements so don't tell
    // `scope.toArray` to only get a certain amount

    var count = !hasRest(pattern) && pattern.elements.length;

    // so we need to ensure that the `arrayRef` is an array, `scope.toArray` will
    // return a locally bound identifier if it's been inferred to be an array,
    // otherwise it'll be a call to a helper that will ensure it's one

    var toArray = this.toArray(arrayRef, count);

    if (t.isIdentifier(toArray)) {
      // we've been given an identifier so it must have been inferred to be an
      // array
      arrayRef = toArray;
    } else {
      arrayRef = this.scope.generateUidIdentifierBasedOnNode(arrayRef);
      this.arrays[arrayRef.name] = true;
      this.nodes.push(this.buildVariableDeclaration(arrayRef, toArray));
    }

    //

    for (var i = 0; i < pattern.elements.length; i++) {
      var elem = pattern.elements[i];

      // hole
      if (!elem) continue;

      var elemRef;

      if (t.isRestElement(elem)) {
        elemRef = this.toArray(arrayRef);

        if (i > 0) {
          elemRef = t.callExpression(t.memberExpression(elemRef, t.identifier("slice")), [t.literal(i)]);
        }

        // set the element to the rest element argument since we've dealt with it
        // being a rest already
        elem = elem.argument;
      } else {
        elemRef = t.memberExpression(arrayRef, t.literal(i), true);
      }

      this.push(elem, elemRef);
    }
  }

  init(pattern, ref) {
    // trying to destructure a value that we can't evaluate more than once so we
    // need to save it to a variable

    if (!t.isArrayExpression(ref) && !t.isMemberExpression(ref)) {
      var memo = this.scope.maybeGenerateMemoised(ref, true);
      if (memo) {
        this.nodes.push(this.buildVariableDeclaration(memo, ref));
        ref = memo;
      }
    }

    //

    this.push(pattern, ref);

    return this.nodes;
  }
}
<commit_msg>fix destructuring transformer AssignmentExpression to properly handle completion records<commit_after>import * as messages from "../../../messages";
import * as t from "../../../types";

export var metadata = {
  group: "builtin-advanced"
};

export function ForOfStatement(node, parent, scope, file) {
  var left = node.left;

  if (t.isPattern(left)) {
    // for ({ length: k } in { abc: 3 });

    var temp = scope.generateUidIdentifier("ref");

    node.left = t.variableDeclaration("var", [
      t.variableDeclarator(temp)
    ]);

    t.ensureBlock(node);

    node.body.body.unshift(t.variableDeclaration("var", [
      t.variableDeclarator(left, temp)
    ]));

    return;
  }

  if (!t.isVariableDeclaration(left)) return;

  var pattern = left.declarations[0].id;
  if (!t.isPattern(pattern)) return;

  var key = scope.generateUidIdentifier("ref");
  node.left = t.variableDeclaration(left.kind, [
    t.variableDeclarator(key, null)
  ]);

  var nodes = [];

  var destructuring = new DestructuringTransformer({
    kind: left.kind,
    file: file,
    scope: scope,
    nodes: nodes
  });

  destructuring.init(pattern, key);

  t.ensureBlock(node);

  var block = node.body;
  block.body = nodes.concat(block.body);
}

export { ForOfStatement as ForInStatement };

export function Func/*tion*/(node, parent, scope, file) {
  var nodes = [];

  var hasDestructuring = false;

  node.params = node.params.map(function (pattern, i) {
    if (!t.isPattern(pattern)) return pattern;

    hasDestructuring = true;
    var ref = scope.generateUidIdentifier("ref");
    t.inherits(ref, pattern);

    var destructuring = new DestructuringTransformer({
      blockHoist: node.params.length - i,
      nodes:      nodes,
      scope:      scope,
      file:       file,
      kind:       "let"
    });
    destructuring.init(pattern, ref);

    return ref;
  });

  if (!hasDestructuring) return;

  t.ensureBlock(node);

  var block = node.body;
  block.body = nodes.concat(block.body);
}

export function CatchClause(node, parent, scope, file) {
  var pattern = node.param;
  if (!t.isPattern(pattern)) return;

  var ref = scope.generateUidIdentifier("ref");
  node.param = ref;

  var nodes = [];

  var destructuring = new DestructuringTransformer({
    kind: "let",
    file: file,
    scope: scope,
    nodes: nodes
  });
  destructuring.init(pattern, ref);

  node.body.body = nodes.concat(node.body.body);
}

export function AssignmentExpression(node, parent, scope, file) {
  if (!t.isPattern(node.left)) return;

  var nodes = [];

  var destructuring = new DestructuringTransformer({
    operator: node.operator,
    file: file,
    scope: scope,
    nodes: nodes
  });

  var ref;
  if (this.isCompletionRecord() || !this.parentPath.isExpressionStatement()) {
    ref = scope.generateUidIdentifierBasedOnNode(node.right, "ref");;

    nodes.push(t.variableDeclaration("var", [
      t.variableDeclarator(ref, node.right)
    ]));

    if (t.isArrayExpression(node.right)) {
      destructuring.arrays[ref.name] = true;
    }
  }

  destructuring.init(node.left, ref || node.right);

  if (ref) {
    nodes.push(t.expressionStatement(ref));
  }

  return nodes;
}

function variableDeclarationHasPattern(node) {
  for (var i = 0; i < node.declarations.length; i++) {
    if (t.isPattern(node.declarations[i].id)) {
      return true;
    }
  }
  return false;
}

export function VariableDeclaration(node, parent, scope, file) {
  if (t.isForInStatement(parent) || t.isForOfStatement(parent)) return;
  if (!variableDeclarationHasPattern(node)) return;

  var nodes = [];
  var declar;

  for (var i = 0; i < node.declarations.length; i++) {
    declar = node.declarations[i];

    var patternId = declar.init;
    var pattern   = declar.id;

    var destructuring = new DestructuringTransformer({
      nodes: nodes,
      scope: scope,
      kind:  node.kind,
      file:  file
    });

    if (t.isPattern(pattern)) {
      destructuring.init(pattern, patternId);

      if (+i !== node.declarations.length - 1) {
        // we aren't the last declarator so let's just make the
        // last transformed node inherit from us
        t.inherits(nodes[nodes.length - 1], declar);
      }
    } else {
      nodes.push(t.inherits(destructuring.buildVariableAssignment(declar.id, declar.init), declar));
    }
  }

  if (!t.isProgram(parent) && !t.isBlockStatement(parent)) {
    // https://github.com/babel/babel/issues/113
    // for (let [x] = [0]; false;) {}

    declar = null;

    for (i = 0; i < nodes.length; i++) {
      node = nodes[i];
      declar = declar || t.variableDeclaration(node.kind, []);

      if (!t.isVariableDeclaration(node) && declar.kind !== node.kind) {
        throw file.errorWithNode(node, messages.get("invalidParentForThisNode"));
      }

      declar.declarations = declar.declarations.concat(node.declarations);
    }

    return declar;
  }

  return nodes;
}

function hasRest(pattern) {
  for (var i = 0; i < pattern.elements.length; i++) {
    if (t.isRestElement(pattern.elements[i])) {
      return true;
    }
  }
  return false;
}

var arrayUnpackVisitor = {
  enter(node, parent, scope, state) {
    if (this.isReferencedIdentifier() && state.bindings[node.name]) {
      state.deopt = true;
      this.stop();
    }
  }
};

class DestructuringTransformer {
  constructor(opts) {
    this.blockHoist = opts.blockHoist;
    this.operator   = opts.operator;
    this.arrays     = {};
    this.nodes      = opts.nodes || [];
    this.scope      = opts.scope;
    this.file       = opts.file;
    this.kind       = opts.kind;
  }

  buildVariableAssignment(id, init) {
    var op = this.operator;
    if (t.isMemberExpression(id)) op = "=";

    var node;

    if (op) {
      node = t.expressionStatement(t.assignmentExpression(op, id, init));
    } else {
      node = t.variableDeclaration(this.kind, [
        t.variableDeclarator(id, init)
      ]);
    }

    node._blockHoist = this.blockHoist;

    return node;
  }

  buildVariableDeclaration(id, init) {
    var declar = t.variableDeclaration("var", [
      t.variableDeclarator(id, init)
    ]);
    declar._blockHoist = this.blockHoist;
    return declar;
  }

  push(id, init) {
    if (t.isObjectPattern(id)) {
      this.pushObjectPattern(id, init);
    } else if (t.isArrayPattern(id)) {
      this.pushArrayPattern(id, init);
    } else if (t.isAssignmentPattern(id)) {
      this.pushAssignmentPattern(id, init);
    } else {
      this.nodes.push(this.buildVariableAssignment(id, init));
    }
  }

  toArray(node, count) {
    if (this.file.isLoose("es6.destructuring") || (t.isIdentifier(node) && this.arrays[node.name])) {
      return node;
    } else {
      return this.scope.toArray(node, count);
    }
  }

  pushAssignmentPattern(pattern, valueRef) {
    // we need to assign the current value of the assignment to avoid evaluating
    // it more than once

    var tempValueRef = this.scope.generateUidIdentifierBasedOnNode(valueRef);

    var declar = t.variableDeclaration("var", [
      t.variableDeclarator(tempValueRef, valueRef)
    ]);
    declar._blockHoist = this.blockHoist;
    this.nodes.push(declar);

    //

    var tempConditional = t.conditionalExpression(
      t.binaryExpression("===", tempValueRef, t.identifier("undefined")),
      pattern.right,
      tempValueRef
    );

    var left = pattern.left;
    if (t.isPattern(left)) {
      this.nodes.push(t.expressionStatement(
        t.assignmentExpression("=", tempValueRef, tempConditional)
      ));
      this.push(left, tempValueRef);
    } else {
      this.nodes.push(this.buildVariableAssignment(left, tempConditional));
    }
  }

  pushObjectSpread(pattern, objRef, spreadProp, spreadPropIndex) {
    // get all the keys that appear in this object before the current spread

    var keys = [];

    for (var i = 0; i < pattern.properties.length; i++) {
      var prop = pattern.properties[i];

      // we've exceeded the index of the spread property to all properties to the
      // right need to be ignored
      if (i >= spreadPropIndex) break;

      // ignore other spread properties
      if (t.isSpreadProperty(prop)) continue;

      var key = prop.key;
      if (t.isIdentifier(key) && !prop.computed) key = t.literal(prop.key.name);
      keys.push(key);
    }

    keys = t.arrayExpression(keys);

    //

    var value = t.callExpression(this.file.addHelper("object-without-properties"), [objRef, keys]);
    this.nodes.push(this.buildVariableAssignment(spreadProp.argument, value));
  }

  pushObjectProperty(prop, propRef) {
    if (t.isLiteral(prop.key)) prop.computed = true;

    var pattern = prop.value;
    var objRef  = t.memberExpression(propRef, prop.key, prop.computed);

    if (t.isPattern(pattern)) {
      this.push(pattern, objRef);
    } else {
      this.nodes.push(this.buildVariableAssignment(pattern, objRef));
    }
  }

  pushObjectPattern(pattern, objRef) {
    // https://github.com/babel/babel/issues/681

    if (!pattern.properties.length) {
      this.nodes.push(t.expressionStatement(
        t.callExpression(this.file.addHelper("object-destructuring-empty"), [objRef])
      ));
    }

    // if we have more than one properties in this pattern and the objectRef is a
    // member expression then we need to assign it to a temporary variable so it's
    // only evaluated once

    if (pattern.properties.length > 1 && t.isMemberExpression(objRef)) {
      var temp = this.scope.generateUidIdentifierBasedOnNode(objRef, this.file);
      this.nodes.push(this.buildVariableDeclaration(temp, objRef));
      objRef = temp;
    }

    //

    for (var i = 0; i < pattern.properties.length; i++) {
      var prop = pattern.properties[i];
      if (t.isSpreadProperty(prop)) {
        this.pushObjectSpread(pattern, objRef, prop, i);
      } else {
        this.pushObjectProperty(prop, objRef);
      }
    }
  }

  canUnpackArrayPattern(pattern, arr) {
    // not an array so there's no way we can deal with this
    if (!t.isArrayExpression(arr)) return false;

    // pattern has less elements than the array and doesn't have a rest so some
    // elements wont be evaluated
    if (pattern.elements.length > arr.elements.length) return;
    if (pattern.elements.length < arr.elements.length && !hasRest(pattern)) return false;

    for (var i = 0; i < pattern.elements.length; i++) {
      var elem = pattern.elements[i];

      // deopt on holes
      if (!elem) return false;

      // deopt on member expressions
      if (t.isMemberExpression(elem)) return false;
    }

    // deopt on reference to left side identifiers
    var bindings = t.getBindingIdentifiers(pattern);
    var state = { deopt: false, bindings };
    this.scope.traverse(arr, arrayUnpackVisitor, state);
    return !state.deopt;
  }

  pushUnpackedArrayPattern(pattern, arr) {
    for (var i = 0; i < pattern.elements.length; i++) {
      var elem = pattern.elements[i];
      if (t.isRestElement(elem)) {
        this.push(elem.argument, t.arrayExpression(arr.elements.slice(i)));
      } else {
        this.push(elem, arr.elements[i]);
      }
    }
  }

  pushArrayPattern(pattern, arrayRef) {
    if (!pattern.elements) return;

    // optimise basic array destructuring of an array expression
    //
    // we can't do this to a pattern of unequal size to it's right hand
    // array expression as then there will be values that wont be evaluated
    //
    // eg: var [a, b] = [1, 2];

    if (this.canUnpackArrayPattern(pattern, arrayRef)) {
      return this.pushUnpackedArrayPattern(pattern, arrayRef);
    }

    // if we have a rest then we need all the elements so don't tell
    // `scope.toArray` to only get a certain amount

    var count = !hasRest(pattern) && pattern.elements.length;

    // so we need to ensure that the `arrayRef` is an array, `scope.toArray` will
    // return a locally bound identifier if it's been inferred to be an array,
    // otherwise it'll be a call to a helper that will ensure it's one

    var toArray = this.toArray(arrayRef, count);

    if (t.isIdentifier(toArray)) {
      // we've been given an identifier so it must have been inferred to be an
      // array
      arrayRef = toArray;
    } else {
      arrayRef = this.scope.generateUidIdentifierBasedOnNode(arrayRef);
      this.arrays[arrayRef.name] = true;
      this.nodes.push(this.buildVariableDeclaration(arrayRef, toArray));
    }

    //

    for (var i = 0; i < pattern.elements.length; i++) {
      var elem = pattern.elements[i];

      // hole
      if (!elem) continue;

      var elemRef;

      if (t.isRestElement(elem)) {
        elemRef = this.toArray(arrayRef);

        if (i > 0) {
          elemRef = t.callExpression(t.memberExpression(elemRef, t.identifier("slice")), [t.literal(i)]);
        }

        // set the element to the rest element argument since we've dealt with it
        // being a rest already
        elem = elem.argument;
      } else {
        elemRef = t.memberExpression(arrayRef, t.literal(i), true);
      }

      this.push(elem, elemRef);
    }
  }

  init(pattern, ref) {
    // trying to destructure a value that we can't evaluate more than once so we
    // need to save it to a variable

    if (!t.isArrayExpression(ref) && !t.isMemberExpression(ref)) {
      var memo = this.scope.maybeGenerateMemoised(ref, true);
      if (memo) {
        this.nodes.push(this.buildVariableDeclaration(memo, ref));
        ref = memo;
      }
    }

    //

    this.push(pattern, ref);

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


<commit_before>import StoreDetailModule from "./storeDetail";
import StorePickupsModule from "./storePickups/storePickups";
import GroupComponentModule from "../group";

const { module } = angular.mock;

describe("StoreDetail", () => {
  beforeEach(module(StoreDetailModule));
  beforeEach(module(GroupComponentModule));
  beforeEach(module(StorePickupsModule));  // to test redirection
  beforeEach(module(($stateProvider) => {
    $stateProvider
      .state("main", { url: "", abstract: true })
      .state("groupInfo", { url: "g" });
  }));
  beforeEach(module({
    $translate: sinon.stub(),
    translateFilter: (a) => a
  }));
  beforeEach(module(($mdAriaProvider) => {
    $mdAriaProvider.disableWarnings();
  }));

  let $log, $httpBackend, $state;
  beforeEach(inject(($injector) => {
    $log = $injector.get("$log");
    $log.reset();
    $httpBackend = $injector.get("$httpBackend");
    $state = $injector.get("$state");
  }));

  afterEach(() => {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
    $log.assertEmpty();
  });

  describe("Module", () => {
    it("is named storeDetail", () => {
      expect(StoreDetailModule).to.equal("storeDetail");
    });
  });

  describe("Routes", () => {
    it("should load store and group information", () => {
      inject((CurrentGroup, GroupService, Store, $q, CurrentStores, $rootScope, Authentication, $translate) => {
        $translate.returns($q.resolve());
        let groupData = { id: 12, members: [43] };
        let storeData = { id: 25, group: groupData.id };

        $httpBackend.whenGET(`/api/groups/${groupData.id}/`).respond(groupData);
        sinon.stub(CurrentGroup, "set").returns($q.resolve(groupData));
        sinon.stub(Store, "get").returns($q.resolve(storeData));
        sinon.stub(Authentication, "update").returns($q.resolve({ id: 43 }));
        $rootScope.$apply();
        expect(
          $state.go("group.store", { storeId: storeData.id, groupId: groupData.id }).catch((e)=>console.log(e))
        ).to.eventually.be.fulfilled;
        $httpBackend.flush();
        expect($state.current.component).to.equal("storePickups");
        expect(CurrentGroup.set).to.have.been.calledWith(groupData);
        expect(Store.get).to.have.been.calledWith(storeData.id);
        expect(CurrentStores.selected).to.deep.equal(storeData);
      });

    });
  });

  describe("Component", () => {
    let $compile, scope;
    beforeEach(inject(($rootScope, $injector) => {
      $compile = $injector.get("$compile");
      scope = $rootScope.$new();
    }));

    it("compiles component", () => {
      $httpBackend.expectGET("/api/stores/").respond([]);
      $compile("<store-detail></store-detail>")(scope);
      $httpBackend.flush();
    });
  });
});
<commit_msg>remove console.log<commit_after>import StoreDetailModule from "./storeDetail";
import StorePickupsModule from "./storePickups/storePickups";
import GroupComponentModule from "../group";

const { module } = angular.mock;

describe("StoreDetail", () => {
  beforeEach(module(StoreDetailModule));
  beforeEach(module(GroupComponentModule));
  beforeEach(module(StorePickupsModule));  // to test redirection
  beforeEach(module(($stateProvider) => {
    $stateProvider
      .state("main", { url: "", abstract: true })
      .state("groupInfo", { url: "g" });
  }));
  beforeEach(module({
    $translate: sinon.stub(),
    translateFilter: (a) => a
  }));
  beforeEach(module(($mdAriaProvider) => {
    $mdAriaProvider.disableWarnings();
  }));

  let $log, $httpBackend, $state;
  beforeEach(inject(($injector) => {
    $log = $injector.get("$log");
    $log.reset();
    $httpBackend = $injector.get("$httpBackend");
    $state = $injector.get("$state");
  }));

  afterEach(() => {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
    $log.assertEmpty();
  });

  describe("Module", () => {
    it("is named storeDetail", () => {
      expect(StoreDetailModule).to.equal("storeDetail");
    });
  });

  describe("Routes", () => {
    it("should load store and group information", () => {
      inject((CurrentGroup, GroupService, Store, $q, CurrentStores, $rootScope, Authentication, $translate) => {
        $translate.returns($q.resolve());
        let groupData = { id: 12, members: [43] };
        let storeData = { id: 25, group: groupData.id };

        $httpBackend.whenGET(`/api/groups/${groupData.id}/`).respond(groupData);
        sinon.stub(CurrentGroup, "set").returns($q.resolve(groupData));
        sinon.stub(Store, "get").returns($q.resolve(storeData));
        sinon.stub(Authentication, "update").returns($q.resolve({ id: 43 }));
        $rootScope.$apply();
        expect(
          $state.go("group.store", { storeId: storeData.id, groupId: groupData.id })
        ).to.eventually.be.fulfilled;
        $httpBackend.flush();
        expect($state.current.component).to.equal("storePickups");
        expect(CurrentGroup.set).to.have.been.calledWith(groupData);
        expect(Store.get).to.have.been.calledWith(storeData.id);
        expect(CurrentStores.selected).to.deep.equal(storeData);
      });

    });
  });

  describe("Component", () => {
    let $compile, scope;
    beforeEach(inject(($rootScope, $injector) => {
      $compile = $injector.get("$compile");
      scope = $rootScope.$new();
    }));

    it("compiles component", () => {
      $httpBackend.expectGET("/api/stores/").respond([]);
      $compile("<store-detail></store-detail>")(scope);
      $httpBackend.flush();
    });
  });
});
<|endoftext|>