<commit_before>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Button for toggling timer on and off.
 *
 * @author John Blanco
 */
define( function( require ) {
  'use strict';

  // modules
  var Color = require( 'SCENERY/util/Color' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var SimpleClockIcon = require( 'SCENERY_PHET/SimpleClockIcon' );
  var ToggleButton2 = require( 'SUN/experimental/buttons/ToggleButton2' );

  // Constants
  var X_STROKE_WIDTH = 6;

  /**
   * @param {Property} enabledProperty
   * @param {*} options
   * @constructor
   */
  function TimerToggleButton2( enabledProperty, options ) {

    //TODO scenery-phet#19 this has lots of problems
    // This node's size needs to match the size of SoundToggleButton, so the
    // largest icon used in that node is created as a reference.
    var tempSoundToggleButton = new FontAwesomeNode( 'volume_up' );

    // Create the node that represents the timer being on.
    var clockRadius = tempSoundToggleButton.width * 0.48; // Multiplier tweaked so that size matches.  Need to generalize if used in common code.
    var timerOnNode = new SimpleClockIcon( clockRadius );

    // Create the node that represents the timer being off.
    var timerOffNode = new Node();
    var timerOffNodeBackground = new SimpleClockIcon( clockRadius );
    timerOffNode.addChild( timerOffNodeBackground );
    var xNode = new Shape();
    var xNodeWidth = timerOffNode.width * 0.8;
    xNode.moveTo( 0, 0 );
    xNode.lineTo( xNodeWidth, xNodeWidth );
    xNode.moveTo( 0, xNodeWidth );
    xNode.lineTo( xNodeWidth, 0 );
    timerOffNode.addChild( new Path( xNode,
      {
        stroke: 'red',
        opacity: '0.55',
        lineWidth: X_STROKE_WIDTH,
        lineCap: 'round',
        centerX: timerOffNode.width / 2,
        centerY: timerOffNode.height / 2
      } ) );

    // Create the toggle button.
    ToggleButton2.call( this,
      timerOnNode,
      timerOffNode,
      enabledProperty,
      _.extend( { baseColor: new Color( 255, 242, 2 ) }, options ) ); // TODO: i18n
  }

  return inherit( ToggleButton2, TimerToggleButton2 );
} );<commit_msg>Faded the clock a little when disabled.<commit_after>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Button for toggling timer on and off.
 *
 * @author John Blanco
 */
define( function( require ) {
  'use strict';

  // modules
  var Color = require( 'SCENERY/util/Color' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var SimpleClockIcon = require( 'SCENERY_PHET/SimpleClockIcon' );
  var ToggleButton2 = require( 'SUN/experimental/buttons/ToggleButton2' );

  // Constants
  var X_STROKE_WIDTH = 6;

  /**
   * @param {Property} enabledProperty
   * @param {*} options
   * @constructor
   */
  function TimerToggleButton2( enabledProperty, options ) {

    //TODO scenery-phet#19 this has lots of problems
    // This node's size needs to match the size of SoundToggleButton, so the
    // largest icon used in that node is created as a reference.
    var tempSoundToggleButton = new FontAwesomeNode( 'volume_up' );

    // Create the node that represents the timer being on.
    var clockRadius = tempSoundToggleButton.width * 0.48; // Multiplier tweaked so that size matches.  Need to generalize if used in common code.
    var timerOnNode = new SimpleClockIcon( clockRadius );

    // Create the node that represents the timer being off.
    var timerOffNode = new Node();
    var timerOffNodeBackground = new SimpleClockIcon( clockRadius, { opacity: 0.8 } );
    timerOffNode.addChild( timerOffNodeBackground );
    var xNode = new Shape();
    var xNodeWidth = timerOffNode.width * 0.8;
    xNode.moveTo( 0, 0 );
    xNode.lineTo( xNodeWidth, xNodeWidth );
    xNode.moveTo( 0, xNodeWidth );
    xNode.lineTo( xNodeWidth, 0 );
    timerOffNode.addChild( new Path( xNode,
      {
        stroke: 'red',
        opacity: '0.55',
        lineWidth: X_STROKE_WIDTH,
        lineCap: 'round',
        centerX: timerOffNode.width / 2,
        centerY: timerOffNode.height / 2
      } ) );

    // Create the toggle button.
    ToggleButton2.call( this,
      timerOnNode,
      timerOffNode,
      enabledProperty,
      _.extend( { baseColor: new Color( 255, 242, 2 ) }, options ) ); // TODO: i18n
  }

  return inherit( ToggleButton2, TimerToggleButton2 );
} );<|endoftext|>


<commit_before>'use strict';

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2015 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var _ = require('underscore');
var flatten = require('internal').flatten;
var arangodb = require('org/arangodb');
var console = require('console');
var qb = require('aqb');
var db = arangodb.db;
var queueCache = {};
var jobCache = {};
var jobTypeCache = {};

function resetQueueControl() {
  try {
    global.KEY_SET("queue-control", "skip", 0);
  } catch (e) {}
}

function getQueue(key) {
  var dbName = db._name();
  if (!queueCache[dbName]) {
    queueCache[dbName] = {};
  }
  if (!queueCache[dbName][key]) {
    if (!db._queues.exists(key)) {
      throw new Error('Queue does not exist: ' + key);
    }
    queueCache[dbName][key] = new Queue(key);
  }
  return queueCache[dbName][key];
}

function createQueue(key, maxWorkers) {
  try {
    db._queues.save({_key: key, maxWorkers: maxWorkers || 1});
  } catch (err) {
    if (!err instanceof arangodb.ArangoError ||
        err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
      throw err;
    }
    if (maxWorkers) {
      db._queues.update(key, {maxWorkers: maxWorkers});
    }
  }
  resetQueueControl();
  var dbName = db._name();
  if (!queueCache[dbName]) {
    queueCache[dbName] = {};
  }
  queueCache[dbName][key] = new Queue(key);
  return queueCache[dbName][key];
}

function deleteQueue(key) {
  var result = false;
  db._executeTransaction({
    collections: {
      read: ['_queues'],
      write: ['_queues']
    },
    action: function () {
      if (db._queues.exists(key)) {
        db._queues.remove(key);
        result = true;
      }
    }
  });
  if (result) {
    resetQueueControl();
  }
  return result;
}

function registerJobType(type, opts) {
  if (typeof opts === 'function') {
    opts = {execute: opts};
  }
  if (typeof opts.execute !== 'function') {
    throw new Error('Must provide a function to execute!');
  }
  if (opts.schema && typeof opts.schema.validate !== 'function') {
    throw new Error('Schema must be a joi schema!');
  }
  var cfg = _.extend({maxFailures: 0}, opts);

  // _jobTypes are database-specific
  var dbName = db._name();
  if (!jobTypeCache[dbName]) {
    jobTypeCache[dbName] = {};
  }
  jobTypeCache[dbName][type] = cfg;
}

function getJobs(queue, status, type) {
  var query = qb.for('job').in('_jobs');
  var vars = {};

  if (queue !== undefined) {
    query = query.filter(qb.ref('@queue').eq('job.queue'));
    vars.queue = queue;
  }

  if (status !== undefined) {
    query = query.filter(qb.ref('@status').eq('job.status'));
    vars.status = status;
  }

  if (type !== undefined) {
    query = query.filter(qb.ref('@type').eq('job.type'));
    vars.type = type;
  }

  return db._createStatement({
    query: query.sort('job.delayUntil', 'ASC').return('job._id'),
    bindVars: vars
  }).execute().toArray();
}

function Job(id) {
  Object.defineProperty(this, 'id', {
    value: id,
    writable: false,
    configurable: false,
    enumerable: true
  });
  _.each(['data', 'status', 'type', 'failures'], function (key) {
    Object.defineProperty(this, key, {
      get: function () {
        var value = db._jobs.document(this.id)[key];
        return (value && typeof value === 'object') ? Object.freeze(value) : value;
      },
      configurable: false,
      enumerable: true
    });
  }, this);
}

_.extend(Job.prototype, {
  abort: function () {
    var self = this;
    db._executeTransaction({
      collections: {
        read: ['_jobs'],
        write: ['_jobs']
      },
      action: function () {
        var job = db._jobs.document(self.id);
        if (job.status !== 'completed') {
          job.failures.push(flatten(new Error('Job aborted.')));
          db._jobs.update(job, {
            status: 'failed',
            modified: Date.now(),
            failures: job.failures
          });
        }
      }
    });
  },
  reset: function () {
    db._jobs.update(this.id, {
      status: 'pending'
    });
    resetQueueControl();
  }
});

function Queue(name) {
  Object.defineProperty(this, 'name', {
    get: function () {
      return name;
    },
    configurable: false,
    enumerable: true
  });
}

_.extend(Queue.prototype, {
  push: function (name, data, opts) {
    var type, result, now;
    if (typeof name !== 'string') {
      throw new Error('Must pass a job type!');
    }
    if (!opts) {
      opts = {};
    }
    // _jobTypes are database-specific
    var dbName = db._name();
    if (!jobTypeCache[dbName]) {
      jobTypeCache[dbName] = {};
    }
    type = jobTypeCache[dbName][name];

    if (type !== undefined) {
      if (type.schema) {
        result = type.schema.validate(data);
        if (result.error) {
          throw result.error;
        }
        data = result.value;
      }
      if (type.preprocess) {
        data = type.preprocess(data);
      }
    } else if (opts.allowUnknown) {
      console.warn('Unknown job type: ' + name);
    } else {
      throw new Error('Unknown job type: ' + name);
    }
    resetQueueControl();
    now = Date.now();
    return db._jobs.save({
      status: 'pending',
      queue: this.name,
      type: name,
      failures: [],
      data: data,
      created: now,
      modified: now,
      maxFailures: opts.maxFailures === Infinity ? -1 : opts.maxFailures,
      backOff: typeof opts.backOff === 'function' ? opts.backOff.toString() : opts.backOff,
      delayUntil: opts.delayUntil || now,
      onSuccess: opts.success ? opts.success.toString() : null,
      onFailure: opts.failure ? opts.failure.toString() : null
    })._id;
  },
  get: function (id) {
    if (typeof id !== 'string') {
      throw new Error('Invalid job id');
    }
    if (!id.match(/^_jobs\//)) {
      id = '_jobs/' + id;
    }
    // jobs are database-specific
    var dbName = db._name();
    if (!jobCache[dbName]) {
      jobCache[dbName] = {};
    }
    if (!jobCache[dbName][id]) {
      jobCache[dbName][id] = new Job(id);
    }
    return jobCache[dbName][id];
  },
  delete: function (id) {
    return db._executeTransaction({
      collections: {
        read: ['_jobs'],
        write: ['_jobs']
      },
      action: function () {
        try {
          db._jobs.remove(id);
          resetQueueControl();
          return true;
        } catch (err) {
          return false;
        }
      }
    });
  },
  pending: function (jobType) {
    return getJobs(this.name, 'pending', jobType);
  },
  complete: function (jobType) {
    return getJobs(this.name, 'complete', jobType);
  },
  failed: function (jobType) {
    return getJobs(this.name, 'failed', jobType);
  },
  progress: function (jobType) {
    return getJobs(this.name, 'progress', jobType);
  },
  all: function (jobType) {
    return getJobs(this.name, undefined, jobType);
  }
});

createQueue('default');

module.exports = {
  _jobTypes: jobTypeCache,
  _clearCache: resetQueueControl,
  get: getQueue,
  create: createQueue,
  delete: deleteQueue,
  registerJobType: registerJobType
};

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:
<commit_msg>Style.<commit_after>'use strict';

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2015 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var _ = require('underscore');
var flatten = require('internal').flatten;
var arangodb = require('org/arangodb');
var console = require('console');
var qb = require('aqb');
var db = arangodb.db;
var queueCache = {};
var jobCache = {};
var jobTypeCache = {};

function resetQueueControl() {
  try {
    global.KEY_SET("queue-control", "skip", 0);
  } catch (e) {}
}

function getQueue(key) {
  var dbName = db._name();
  if (!queueCache[dbName]) {
    queueCache[dbName] = {};
  }
  if (!queueCache[dbName][key]) {
    if (!db._queues.exists(key)) {
      throw new Error('Queue does not exist: ' + key);
    }
    queueCache[dbName][key] = new Queue(key);
  }
  return queueCache[dbName][key];
}

function createQueue(key, maxWorkers) {
  try {
    db._queues.save({_key: key, maxWorkers: maxWorkers || 1});
  } catch (err) {
    if (!err instanceof arangodb.ArangoError ||
        err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
      throw err;
    }
    if (maxWorkers) {
      db._queues.update(key, {maxWorkers: maxWorkers});
    }
  }
  resetQueueControl();
  var dbName = db._name();
  if (!queueCache[dbName]) {
    queueCache[dbName] = {};
  }
  queueCache[dbName][key] = new Queue(key);
  return queueCache[dbName][key];
}

function deleteQueue(key) {
  var result = false;
  db._executeTransaction({
    collections: {
      read: ['_queues'],
      write: ['_queues']
    },
    action: function () {
      if (db._queues.exists(key)) {
        db._queues.remove(key);
        result = true;
      }
    }
  });
  if (result) {
    resetQueueControl();
  }
  return result;
}

function registerJobType(type, opts) {
  if (typeof opts === 'function') {
    opts = {execute: opts};
  }
  if (typeof opts.execute !== 'function') {
    throw new Error('Must provide a function to execute!');
  }
  if (opts.schema && typeof opts.schema.validate !== 'function') {
    throw new Error('Schema must be a joi schema!');
  }
  var cfg = _.extend({maxFailures: 0}, opts);

  // _jobTypes are database-specific
  var dbName = db._name();
  if (!jobTypeCache[dbName]) {
    jobTypeCache[dbName] = {};
  }
  jobTypeCache[dbName][type] = cfg;
}

function getJobs(queue, status, type) {
  var query = qb.for('job').in('_jobs');
  var vars = {};

  if (queue !== undefined) {
    query = query.filter(qb.ref('@queue').eq('job.queue'));
    vars.queue = queue;
  }

  if (status !== undefined) {
    query = query.filter(qb.ref('@status').eq('job.status'));
    vars.status = status;
  }

  if (type !== undefined) {
    query = query.filter(qb.ref('@type').eq('job.type'));
    vars.type = type;
  }

  return db._createStatement({
    query: query.sort('job.delayUntil', 'ASC').return('job._id'),
    bindVars: vars
  }).execute().toArray();
}

function Job(id) {
  Object.defineProperty(this, 'id', {
    value: id,
    writable: false,
    configurable: false,
    enumerable: true
  });
  _.each(['data', 'status', 'type', 'failures'], function (key) {
    Object.defineProperty(this, key, {
      get: function () {
        var value = db._jobs.document(this.id)[key];
        return (value && typeof value === 'object') ? Object.freeze(value) : value;
      },
      configurable: false,
      enumerable: true
    });
  }, this);
}

_.extend(Job.prototype, {
  abort: function () {
    var self = this;
    db._executeTransaction({
      collections: {
        read: ['_jobs'],
        write: ['_jobs']
      },
      action: function () {
        var job = db._jobs.document(self.id);
        if (job.status !== 'completed') {
          job.failures.push(flatten(new Error('Job aborted.')));
          db._jobs.update(job, {
            status: 'failed',
            modified: Date.now(),
            failures: job.failures
          });
        }
      }
    });
  },
  reset: function () {
    db._jobs.update(this.id, {
      status: 'pending'
    });
    resetQueueControl();
  }
});

function Queue(name) {
  Object.defineProperty(this, 'name', {
    get: function () {
      return name;
    },
    configurable: false,
    enumerable: true
  });
}

_.extend(Queue.prototype, {
  push: function (name, data, opts) {
    if (typeof name !== 'string') {
      throw new Error('Must pass a job type!');
    }
    if (!opts) {
      opts = {};
    }
    // _jobTypes are database-specific
    var dbName = db._name();
    if (!jobTypeCache[dbName]) {
      jobTypeCache[dbName] = {};
    }
    var type = jobTypeCache[dbName][name];

    if (type !== undefined) {
      if (type.schema) {
        var result = type.schema.validate(data);
        if (result.error) {
          throw result.error;
        }
        data = result.value;
      }
      if (type.preprocess) {
        data = type.preprocess(data);
      }
    } else if (opts.allowUnknown) {
      console.warn('Unknown job type: ' + name);
    } else {
      throw new Error('Unknown job type: ' + name);
    }
    resetQueueControl();
    var now = Date.now();
    return db._jobs.save({
      status: 'pending',
      queue: this.name,
      type: name,
      failures: [],
      data: data,
      created: now,
      modified: now,
      maxFailures: opts.maxFailures === Infinity ? -1 : opts.maxFailures,
      backOff: typeof opts.backOff === 'function' ? opts.backOff.toString() : opts.backOff,
      delayUntil: opts.delayUntil || now,
      onSuccess: opts.success ? opts.success.toString() : null,
      onFailure: opts.failure ? opts.failure.toString() : null
    })._id;
  },
  get: function (id) {
    if (typeof id !== 'string') {
      throw new Error('Invalid job id');
    }
    if (!id.match(/^_jobs\//)) {
      id = '_jobs/' + id;
    }
    // jobs are database-specific
    var dbName = db._name();
    if (!jobCache[dbName]) {
      jobCache[dbName] = {};
    }
    if (!jobCache[dbName][id]) {
      jobCache[dbName][id] = new Job(id);
    }
    return jobCache[dbName][id];
  },
  delete: function (id) {
    return db._executeTransaction({
      collections: {
        read: ['_jobs'],
        write: ['_jobs']
      },
      action: function () {
        try {
          db._jobs.remove(id);
          resetQueueControl();
          return true;
        } catch (err) {
          return false;
        }
      }
    });
  },
  pending: function (jobType) {
    return getJobs(this.name, 'pending', jobType);
  },
  complete: function (jobType) {
    return getJobs(this.name, 'complete', jobType);
  },
  failed: function (jobType) {
    return getJobs(this.name, 'failed', jobType);
  },
  progress: function (jobType) {
    return getJobs(this.name, 'progress', jobType);
  },
  all: function (jobType) {
    return getJobs(this.name, undefined, jobType);
  }
});

createQueue('default');

module.exports = {
  _jobTypes: jobTypeCache,
  _clearCache: resetQueueControl,
  get: getQueue,
  create: createQueue,
  delete: deleteQueue,
  registerJobType: registerJobType
};

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:
<|endoftext|>


<commit_before>/*jslint es5: true, indent: 2, nomen: true, maxlen: 120 */
/*global module, require */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var QUEUE_MANAGER_PERIOD = 1000, // in ms
  _ = require('underscore'),
  tasks = require('org/arangodb/tasks'),
  arangodb = require('org/arangodb'),
  db = arangodb.db,
  queueMap,
  queues,
  Queue,
  worker;

queueMap = Object.create(null);

queues = {
  _jobTypes: Object.create(null),
  get: function (key) {
    'use strict';
    if (!db._queues.exists(key)) {
      throw new Error('Queue does not exist: ' + key);
    }
    if (!queueMap[key]) {
      queueMap[key] = new Queue(key);
    }
    return queueMap[key];
  },
  create: function (key, maxWorkers) {
    'use strict';
    try {
      db._queues.save({_key: key, maxWorkers: maxWorkers || 1});
    } catch (err) {
      if (!err instanceof arangodb.ArangoError ||
          err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
        throw err;
      }
      if (maxWorkers) {
        db._queues.update(key, {maxWorkers: maxWorkers});
      }
    }
    if (!queueMap[key]) {
      queueMap[key] = new Queue(key);
    }
    return queueMap[key];
  },
  destroy: function (key) {
    'use strict';
    var result = false;
    db._executeTransaction({
      collections: {
        read: ['_queues'],
        write: ['_queues']
      },
      action: function () {
        if (db._queues.exists(key)) {
          db._queues.delete(key);
          result = true;
        }
      }
    });
    return result;
  },
  registerJobType: function (type, opts) {
    'use strict';
    if (typeof opts === 'function') {
      opts = {execute: opts};
    }
    if (typeof opts.execute !== 'function') {
      throw new Error('Must provide a function to execute!');
    }
    var cfg = _.extend({maxFailures: 0}, opts);
    if (cfg.maxFailures < 0) {
      cfg.maxFailures = Infinity;
    }
    queues._jobTypes[type] = cfg;
  }
};

Queue = function Queue(name) {
  'use strict';
  Object.defineProperty(this, 'name', {
    get: function () {
      return name;
    },
    set: function (value) {
      throw new Error('name is not mutable');
    },
    configurable: false,
    enumerable: true
  });
};

_.extend(Queue.prototype, {
  push: function (type, data) {
    'use strict';
    if (typeof type !== 'string') {
      throw new Error('Must pass a job type!');
    }
    db._jobs.save({
      status: 'pending',
      queue: this.name,
      type: type,
      failures: 0,
      data: data
    });
    return db._jobs.byExample({
      status: 'pending',
      queue: this.name
    }).count();
  }
});

queues._worker = {
  work: function (job) {
    'use strict';
    var db = require('org/arangodb').db,
      queues = require('org/arangodb/foxx').queues,
      console = require('console'),
      cfg = queues._jobTypes[job.type],
      failed = false;

    if (!cfg) {
      console.warn('Unknown job type for job ' + job._key + ':', job.type);
      db._jobs.update(job._key, {status: 'pending'});
      return;
    }

    try {
      cfg.execute(job.data);
    } catch (err) {
      console.error('Job ' + job._key + ' failed:', err);
      failed = true;
    }
    db._jobs.update(job._key, failed ? {
      failures: job.failures + 1,
      status: (job.failures + 1 > cfg.maxFailures) ? 'failed' : 'pending'
    } : {status: 'complete'});
  }
};

queues._manager = {
  manage: function () {
    'use strict';
    var _ = require('underscore'),
      db = require('org/arangodb').db,
      queues = require('org/arangodb/foxx').queues,
      tasks = require('org/arangodb/tasks');

    db._executeTransaction({
      collections: {
        read: ['_queues', '_jobs'],
        write: ['_jobs']
      },
      action: function () {
        db._queues.all().toArray().forEach(function (queue) {
          var numBusy = db._jobs.byExample({
            queue: queue._key,
            status: 'progress'
          }).count();
          if (numBusy >= queue.maxWorkers) {
            return;
          }
          db._jobs.byExample({
            queue: queue._key,
            status: 'pending'
          }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (doc) {
            db._jobs.update(doc, {status: 'progress'});
            tasks.register({
              command: queues._worker.work,
              params: _.extend({}, doc, {status: 'progress'}),
              offset: 0
            });
          });
        });
      }
    });
  },
  run: function () {
    'use strict';
    db._jobs.updateByExample({status: 'progress'}, {status: 'pending'});
    tasks.register({command: queues._manager.manage, period: QUEUE_MANAGER_PERIOD / 1000});
  }
};

queues.create('default', 1);

module.exports = queues;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:
<commit_msg>Renamed queues.destroy => queues.delete.<commit_after>/*jslint es5: true, indent: 2, nomen: true, maxlen: 120 */
/*global module, require */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var QUEUE_MANAGER_PERIOD = 1000, // in ms
  _ = require('underscore'),
  tasks = require('org/arangodb/tasks'),
  arangodb = require('org/arangodb'),
  db = arangodb.db,
  queueMap,
  queues,
  Queue,
  worker;

queueMap = Object.create(null);

queues = {
  _jobTypes: Object.create(null),
  get: function (key) {
    'use strict';
    if (!db._queues.exists(key)) {
      throw new Error('Queue does not exist: ' + key);
    }
    if (!queueMap[key]) {
      queueMap[key] = new Queue(key);
    }
    return queueMap[key];
  },
  create: function (key, maxWorkers) {
    'use strict';
    try {
      db._queues.save({_key: key, maxWorkers: maxWorkers || 1});
    } catch (err) {
      if (!err instanceof arangodb.ArangoError ||
          err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
        throw err;
      }
      if (maxWorkers) {
        db._queues.update(key, {maxWorkers: maxWorkers});
      }
    }
    if (!queueMap[key]) {
      queueMap[key] = new Queue(key);
    }
    return queueMap[key];
  },
  delete: function (key) {
    'use strict';
    var result = false;
    db._executeTransaction({
      collections: {
        read: ['_queues'],
        write: ['_queues']
      },
      action: function () {
        if (db._queues.exists(key)) {
          db._queues.delete(key);
          result = true;
        }
      }
    });
    return result;
  },
  registerJobType: function (type, opts) {
    'use strict';
    if (typeof opts === 'function') {
      opts = {execute: opts};
    }
    if (typeof opts.execute !== 'function') {
      throw new Error('Must provide a function to execute!');
    }
    var cfg = _.extend({maxFailures: 0}, opts);
    if (cfg.maxFailures < 0) {
      cfg.maxFailures = Infinity;
    }
    queues._jobTypes[type] = cfg;
  }
};

Queue = function Queue(name) {
  'use strict';
  Object.defineProperty(this, 'name', {
    get: function () {
      return name;
    },
    set: function (value) {
      throw new Error('name is not mutable');
    },
    configurable: false,
    enumerable: true
  });
};

_.extend(Queue.prototype, {
  push: function (type, data) {
    'use strict';
    if (typeof type !== 'string') {
      throw new Error('Must pass a job type!');
    }
    db._jobs.save({
      status: 'pending',
      queue: this.name,
      type: type,
      failures: 0,
      data: data
    });
    return db._jobs.byExample({
      status: 'pending',
      queue: this.name
    }).count();
  }
});

queues._worker = {
  work: function (job) {
    'use strict';
    var db = require('org/arangodb').db,
      queues = require('org/arangodb/foxx').queues,
      console = require('console'),
      cfg = queues._jobTypes[job.type],
      failed = false;

    if (!cfg) {
      console.warn('Unknown job type for job ' + job._key + ':', job.type);
      db._jobs.update(job._key, {status: 'pending'});
      return;
    }

    try {
      cfg.execute(job.data);
    } catch (err) {
      console.error('Job ' + job._key + ' failed:', err);
      failed = true;
    }
    db._jobs.update(job._key, failed ? {
      failures: job.failures + 1,
      status: (job.failures + 1 > cfg.maxFailures) ? 'failed' : 'pending'
    } : {status: 'complete'});
  }
};

queues._manager = {
  manage: function () {
    'use strict';
    var _ = require('underscore'),
      db = require('org/arangodb').db,
      queues = require('org/arangodb/foxx').queues,
      tasks = require('org/arangodb/tasks');

    db._executeTransaction({
      collections: {
        read: ['_queues', '_jobs'],
        write: ['_jobs']
      },
      action: function () {
        db._queues.all().toArray().forEach(function (queue) {
          var numBusy = db._jobs.byExample({
            queue: queue._key,
            status: 'progress'
          }).count();
          if (numBusy >= queue.maxWorkers) {
            return;
          }
          db._jobs.byExample({
            queue: queue._key,
            status: 'pending'
          }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (doc) {
            db._jobs.update(doc, {status: 'progress'});
            tasks.register({
              command: queues._worker.work,
              params: _.extend({}, doc, {status: 'progress'}),
              offset: 0
            });
          });
        });
      }
    });
  },
  run: function () {
    'use strict';
    db._jobs.updateByExample({status: 'progress'}, {status: 'pending'});
    tasks.register({command: queues._manager.manage, period: QUEUE_MANAGER_PERIOD / 1000});
  }
};

queues.create('default', 1);

module.exports = queues;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:
<|endoftext|>


<commit_before>import Modal from 'flarum/components/Modal';
import DiscussionPage from 'flarum/components/DiscussionPage';
import Button from 'flarum/components/Button';
import highlight from 'flarum/helpers/highlight';
import classList from 'flarum/utils/classList';
import extractText from 'flarum/utils/extractText';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import Stream from 'flarum/utils/Stream';

import tagLabel from '../../common/helpers/tagLabel';
import tagIcon from '../../common/helpers/tagIcon';
import sortTags from '../../common/utils/sortTags';

export default class TagDiscussionModal extends Modal {
  oninit(vnode) {
    super.oninit(vnode);

    this.tags = app.store.all('tags');

    if (this.attrs.discussion) {
      this.tags = this.tags.filter(tag => tag.canAddToDiscussion() || this.attrs.discussion.tags().indexOf(tag) !== -1);
    } else {
      this.tags = this.tags.filter(tag => tag.canStartDiscussion());
    }

    this.tags = sortTags(this.tags);

    this.selected = [];
    this.filter = Stream('');
    this.index = this.tags[0].id();
    this.focused = false;

    if (this.attrs.selectedTags) {
      this.attrs.selectedTags.map(this.addTag.bind(this));
    } else if (this.attrs.discussion) {
      this.attrs.discussion.tags().map(this.addTag.bind(this));
    }

    this.minPrimary = app.forum.attribute('minPrimaryTags');
    this.maxPrimary = app.forum.attribute('maxPrimaryTags');
    this.minSecondary = app.forum.attribute('minSecondaryTags');
    this.maxSecondary = app.forum.attribute('maxSecondaryTags');

    this.navigator = new KeyboardNavigatable();
    this.navigator
      .onUp(() => this.setIndex(this.getCurrentNumericIndex() - 1, true))
      .onDown(() => this.setIndex(this.getCurrentNumericIndex() + 1, true))
      .onSelect(this.select.bind(this))
      .onRemove(() => this.selected.splice(this.selected.length - 1, 1));
  }

  primaryCount() {
    return this.selected.filter(tag => tag.isPrimary()).length;
  }

  secondaryCount() {
    return this.selected.filter(tag => !tag.isPrimary()).length;
  }

  /**
   * Add the given tag to the list of selected tags.
   *
   * @param {Tag} tag
   */
  addTag(tag) {
    if (!tag.canStartDiscussion()) return;

    // If this tag has a parent, we'll also need to add the parent tag to the
    // selected list if it's not already in there.
    const parent = tag.parent();
    if (parent && this.selected.indexOf(parent) === -1) {
      this.selected.push(parent);
    }

    if (this.selected.indexOf(tag) === -1) {
      this.selected.push(tag);
    }
  }

  /**
   * Remove the given tag from the list of selected tags.
   *
   * @param {Tag} tag
   */
  removeTag(tag) {
    const index = this.selected.indexOf(tag);
    if (index !== -1) {
      this.selected.splice(index, 1);

      // Look through the list of selected tags for any tags which have the tag
      // we just removed as their parent. We'll need to remove them too.
      this.selected
        .filter(selected => selected.parent() === tag)
        .forEach(this.removeTag.bind(this));
    }
  }

  className() {
    return 'TagDiscussionModal';
  }

  title() {
    return this.attrs.discussion
      ? app.translator.trans('flarum-tags.forum.choose_tags.edit_title', {title: {this.attrs.discussion.title()}})
      : app.translator.trans('flarum-tags.forum.choose_tags.title');
  }

  getInstruction(primaryCount, secondaryCount) {
    if (primaryCount < this.minPrimary) {
      const remaining = this.minPrimary - primaryCount;
      return app.translator.transChoice('flarum-tags.forum.choose_tags.choose_primary_placeholder', remaining, {count: remaining});
    } else if (secondaryCount < this.minSecondary) {
      const remaining = this.minSecondary - secondaryCount;
      return app.translator.transChoice('flarum-tags.forum.choose_tags.choose_secondary_placeholder', remaining, {count: remaining});
    }

    return '';
  }

  content() {
    let tags = this.tags;
    const filter = this.filter().toLowerCase();
    const primaryCount = this.primaryCount();
    const secondaryCount = this.secondaryCount();

    // Filter out all child tags whose parents have not been selected. This
    // makes it impossible to select a child if its parent hasn't been selected.
    tags = tags.filter(tag => {
      const parent = tag.parent();
      return parent === false || this.selected.indexOf(parent) !== -1;
    });

    // If the number of selected primary/secondary tags is at the maximum, then
    // we'll filter out all other tags of that type.
    if (primaryCount >= this.maxPrimary) {
      tags = tags.filter(tag => !tag.isPrimary() || this.selected.indexOf(tag) !== -1);
    }

    if (secondaryCount >= this.maxSecondary) {
      tags = tags.filter(tag => tag.isPrimary() || this.selected.indexOf(tag) !== -1);
    }

    // If the user has entered text in the filter input, then filter by tags
    // whose name matches what they've entered.
    if (filter) {
      tags = tags.filter(tag => tag.name().substr(0, filter.length).toLowerCase() === filter);
    }

    if (tags.indexOf(this.index) === -1) this.index = tags[0];

    const inputWidth = Math.max(extractText(this.getInstruction(primaryCount, secondaryCount)).length, this.filter().length);

    return [

 this.$('.TagsInput input').focus()}
            >

                {this.selected.map(tag =>
                   {
                    this.removeTag(tag);
                    this.onready();
                  }}>
                    {tagLabel(tag)}

                )}

              <input className="FormControl"
                placeholder={extractText(this.getInstruction(primaryCount, secondaryCount))}
                bidi={this.filter}
                style={{ width: inputWidth + 'ch' }}
                onkeydown={this.navigator.navigate.bind(this.navigator)}
                onfocus={() => this.focused = true}
                onblur={() => this.focused = false}/>

            <Button type="submit" className="Button Button--primary" disabled={!this.meetsRequirements(primaryCount, secondaryCount)} icon="fas fa-check">
              {app.translator.trans('flarum-tags.forum.choose_tags.submit_button')}
            </Button>

,

          {tags
            .filter(tag => filter || !tag.parent() || this.selected.indexOf(tag.parent()) !== -1)
            .map(tag => (

 this.index = tag}
                onclick={this.toggleTag.bind(this, tag)}
              >
                {tagIcon(tag)}

                  {highlight(tag.name(), filter)}

                {tag.description()
                  ? (

                      {tag.description()}

                  ) : ''}

            ))}

    ];
  }

  meetsRequirements(primaryCount, secondaryCount) {
    if (app.forum.attribute('canBypassTagCounts')) {
      return true;
    }

    return primaryCount >= this.minPrimary && secondaryCount >= this.minSecondary;
  }

  toggleTag(tag) {
    const index = this.selected.indexOf(tag);

    if (index !== -1) {
      this.removeTag(tag);
    } else {
      this.addTag(tag);
    }

    if (this.filter()) {
      this.filter('');
      this.index = this.tags[0];
    }

    this.onready();
  }

  select(e) {
    // Ctrl + Enter submits the selection, just Enter completes the current entry
    if (e.metaKey || e.ctrlKey || this.selected.indexOf(this.index) !== -1) {
      if (this.selected.length) {
        this.$('form').submit();
      }
    } else {
      this.getItem(this.index)[0].dispatchEvent(new Event('click'));
    }
  }

  selectableItems() {
    return this.$('.TagDiscussionModal-list > li');
  }

  getCurrentNumericIndex() {
    return this.selectableItems().index(
      this.getItem(this.index)
    );
  }

  getItem(index) {
    return this.selectableItems().filter(`[data-index="${index.id()}"]`);
  }

  setIndex(index, scrollToItem) {
    const $items = this.selectableItems();
    const $dropdown = $items.parent();

    if (index < 0) {
      index = $items.length - 1;
    } else if (index >= $items.length) {
      index = 0;
    }

    const $item = $items.eq(index);

    this.index = app.store.getById('tags', $item.attr('data-index'));

    m.redraw();

    if (scrollToItem) {
      const dropdownScroll = $dropdown.scrollTop();
      const dropdownTop = $dropdown.offset().top;
      const dropdownBottom = dropdownTop + $dropdown.outerHeight();
      const itemTop = $item.offset().top;
      const itemBottom = itemTop + $item.outerHeight();

      let scrollTop;
      if (itemTop < dropdownTop) {
        scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);
      } else if (itemBottom > dropdownBottom) {
        scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);
      }

      if (typeof scrollTop !== 'undefined') {
        $dropdown.stop(true).animate({scrollTop}, 100);
      }
    }
  }

  onsubmit(e) {
    e.preventDefault();

    const discussion = this.attrs.discussion;
    const tags = this.selected;

    if (discussion) {
      discussion.save({relationships: {tags}})
        .then(() => {
          if (app.current.matches(DiscussionPage)) {
            app.current.get('stream').update();
          }
          m.redraw();
        });
    }

    if (this.attrs.onsubmit) this.attrs.onsubmit(tags);

    this.hide();
  }
}
<commit_msg>Fix submitting TagDiscussionModal via enter key<commit_after>import Modal from 'flarum/components/Modal';
import DiscussionPage from 'flarum/components/DiscussionPage';
import Button from 'flarum/components/Button';
import highlight from 'flarum/helpers/highlight';
import classList from 'flarum/utils/classList';
import extractText from 'flarum/utils/extractText';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import Stream from 'flarum/utils/Stream';

import tagLabel from '../../common/helpers/tagLabel';
import tagIcon from '../../common/helpers/tagIcon';
import sortTags from '../../common/utils/sortTags';

export default class TagDiscussionModal extends Modal {
  oninit(vnode) {
    super.oninit(vnode);

    this.tags = app.store.all('tags');

    if (this.attrs.discussion) {
      this.tags = this.tags.filter(tag => tag.canAddToDiscussion() || this.attrs.discussion.tags().indexOf(tag) !== -1);
    } else {
      this.tags = this.tags.filter(tag => tag.canStartDiscussion());
    }

    this.tags = sortTags(this.tags);

    this.selected = [];
    this.filter = Stream('');
    this.index = this.tags[0].id();
    this.focused = false;

    if (this.attrs.selectedTags) {
      this.attrs.selectedTags.map(this.addTag.bind(this));
    } else if (this.attrs.discussion) {
      this.attrs.discussion.tags().map(this.addTag.bind(this));
    }

    this.minPrimary = app.forum.attribute('minPrimaryTags');
    this.maxPrimary = app.forum.attribute('maxPrimaryTags');
    this.minSecondary = app.forum.attribute('minSecondaryTags');
    this.maxSecondary = app.forum.attribute('maxSecondaryTags');

    this.navigator = new KeyboardNavigatable();
    this.navigator
      .onUp(() => this.setIndex(this.getCurrentNumericIndex() - 1, true))
      .onDown(() => this.setIndex(this.getCurrentNumericIndex() + 1, true))
      .onSelect(this.select.bind(this))
      .onRemove(() => this.selected.splice(this.selected.length - 1, 1));
  }

  primaryCount() {
    return this.selected.filter(tag => tag.isPrimary()).length;
  }

  secondaryCount() {
    return this.selected.filter(tag => !tag.isPrimary()).length;
  }

  /**
   * Add the given tag to the list of selected tags.
   *
   * @param {Tag} tag
   */
  addTag(tag) {
    if (!tag.canStartDiscussion()) return;

    // If this tag has a parent, we'll also need to add the parent tag to the
    // selected list if it's not already in there.
    const parent = tag.parent();
    if (parent && this.selected.indexOf(parent) === -1) {
      this.selected.push(parent);
    }

    if (this.selected.indexOf(tag) === -1) {
      this.selected.push(tag);
    }
  }

  /**
   * Remove the given tag from the list of selected tags.
   *
   * @param {Tag} tag
   */
  removeTag(tag) {
    const index = this.selected.indexOf(tag);
    if (index !== -1) {
      this.selected.splice(index, 1);

      // Look through the list of selected tags for any tags which have the tag
      // we just removed as their parent. We'll need to remove them too.
      this.selected
        .filter(selected => selected.parent() === tag)
        .forEach(this.removeTag.bind(this));
    }
  }

  className() {
    return 'TagDiscussionModal';
  }

  title() {
    return this.attrs.discussion
      ? app.translator.trans('flarum-tags.forum.choose_tags.edit_title', {title: {this.attrs.discussion.title()}})
      : app.translator.trans('flarum-tags.forum.choose_tags.title');
  }

  getInstruction(primaryCount, secondaryCount) {
    if (primaryCount < this.minPrimary) {
      const remaining = this.minPrimary - primaryCount;
      return app.translator.transChoice('flarum-tags.forum.choose_tags.choose_primary_placeholder', remaining, {count: remaining});
    } else if (secondaryCount < this.minSecondary) {
      const remaining = this.minSecondary - secondaryCount;
      return app.translator.transChoice('flarum-tags.forum.choose_tags.choose_secondary_placeholder', remaining, {count: remaining});
    }

    return '';
  }

  content() {
    let tags = this.tags;
    const filter = this.filter().toLowerCase();
    const primaryCount = this.primaryCount();
    const secondaryCount = this.secondaryCount();

    // Filter out all child tags whose parents have not been selected. This
    // makes it impossible to select a child if its parent hasn't been selected.
    tags = tags.filter(tag => {
      const parent = tag.parent();
      return parent === false || this.selected.indexOf(parent) !== -1;
    });

    // If the number of selected primary/secondary tags is at the maximum, then
    // we'll filter out all other tags of that type.
    if (primaryCount >= this.maxPrimary) {
      tags = tags.filter(tag => !tag.isPrimary() || this.selected.indexOf(tag) !== -1);
    }

    if (secondaryCount >= this.maxSecondary) {
      tags = tags.filter(tag => tag.isPrimary() || this.selected.indexOf(tag) !== -1);
    }

    // If the user has entered text in the filter input, then filter by tags
    // whose name matches what they've entered.
    if (filter) {
      tags = tags.filter(tag => tag.name().substr(0, filter.length).toLowerCase() === filter);
    }

    if (tags.indexOf(this.index) === -1) this.index = tags[0];

    const inputWidth = Math.max(extractText(this.getInstruction(primaryCount, secondaryCount)).length, this.filter().length);

    return [

 this.$('.TagsInput input').focus()}
            >

                {this.selected.map(tag =>
                   {
                    this.removeTag(tag);
                    this.onready();
                  }}>
                    {tagLabel(tag)}

                )}

              <input className="FormControl"
                placeholder={extractText(this.getInstruction(primaryCount, secondaryCount))}
                bidi={this.filter}
                style={{ width: inputWidth + 'ch' }}
                onkeydown={this.navigator.navigate.bind(this.navigator)}
                onfocus={() => this.focused = true}
                onblur={() => this.focused = false}/>

            <Button type="submit" className="Button Button--primary" disabled={!this.meetsRequirements(primaryCount, secondaryCount)} icon="fas fa-check">
              {app.translator.trans('flarum-tags.forum.choose_tags.submit_button')}
            </Button>

,

          {tags
            .filter(tag => filter || !tag.parent() || this.selected.indexOf(tag.parent()) !== -1)
            .map(tag => (

 this.index = tag}
                onclick={this.toggleTag.bind(this, tag)}
              >
                {tagIcon(tag)}

                  {highlight(tag.name(), filter)}

                {tag.description()
                  ? (

                      {tag.description()}

                  ) : ''}

            ))}

    ];
  }

  meetsRequirements(primaryCount, secondaryCount) {
    if (app.forum.attribute('canBypassTagCounts')) {
      return true;
    }

    return primaryCount >= this.minPrimary && secondaryCount >= this.minSecondary;
  }

  toggleTag(tag) {
    const index = this.selected.indexOf(tag);

    if (index !== -1) {
      this.removeTag(tag);
    } else {
      this.addTag(tag);
    }

    if (this.filter()) {
      this.filter('');
      this.index = this.tags[0];
    }

    this.onready();
  }

  select(e) {
    // Ctrl + Enter submits the selection, just Enter completes the current entry
    if (e.metaKey || e.ctrlKey || this.selected.indexOf(this.index) !== -1) {
      if (this.selected.length) {
        // The DOM submit method doesn't emit a `submit event, so we
        // simulate a manual submission so our `onsubmit` logic is run.
        this.$('button[type="submit"]').click();
      }
    } else {
      this.getItem(this.index)[0].dispatchEvent(new Event('click'));
    }
  }

  selectableItems() {
    return this.$('.TagDiscussionModal-list > li');
  }

  getCurrentNumericIndex() {
    return this.selectableItems().index(
      this.getItem(this.index)
    );
  }

  getItem(index) {
    return this.selectableItems().filter(`[data-index="${index.id()}"]`);
  }

  setIndex(index, scrollToItem) {
    const $items = this.selectableItems();
    const $dropdown = $items.parent();

    if (index < 0) {
      index = $items.length - 1;
    } else if (index >= $items.length) {
      index = 0;
    }

    const $item = $items.eq(index);

    this.index = app.store.getById('tags', $item.attr('data-index'));

    m.redraw();

    if (scrollToItem) {
      const dropdownScroll = $dropdown.scrollTop();
      const dropdownTop = $dropdown.offset().top;
      const dropdownBottom = dropdownTop + $dropdown.outerHeight();
      const itemTop = $item.offset().top;
      const itemBottom = itemTop + $item.outerHeight();

      let scrollTop;
      if (itemTop < dropdownTop) {
        scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);
      } else if (itemBottom > dropdownBottom) {
        scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);
      }

      if (typeof scrollTop !== 'undefined') {
        $dropdown.stop(true).animate({scrollTop}, 100);
      }
    }
  }

  onsubmit(e) {
    e.preventDefault();

    const discussion = this.attrs.discussion;
    const tags = this.selected;

    if (discussion) {
      discussion.save({relationships: {tags}})
        .then(() => {
          if (app.current.matches(DiscussionPage)) {
            app.current.get('stream').update();
          }
          m.redraw();
        });
    }

    if (this.attrs.onsubmit) this.attrs.onsubmit(tags);

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


<commit_before>module.exports = function(grunt) {

  var path = require('path');

  var msBuild = 'C:/Windows/Microsoft.NET/Framework/v4.0.30319/MSBuild.exe ';
  var msBuildOptions = ' /p:Configuration=Release /verbosity:minimal ';

  var breezeJsDir = '../../../Breeze.js/';

  var jsBuildDir  = breezeJsDir + 'build/';
  var jsSrcDir    = breezeJsDir + 'src/';
  var tsSrcDir    = breezeJsDir + 'typescript/';
  var qDir        = breezeJsDir + 'test/vendor/';
  var metadataDir = breezeJsDir + 'docs/metadata/';

  var serverNetDir = '../../../Breeze.server.net/';

  var tempDir = '../_temp/';

  var tempPaths = [
     'bin','obj', 'packages','*_Resharper*','*.suo','*.temp.*'
  ];

  var nugetPackageNames = [
     'Breeze.WebApi',
	   'Breeze.WebApi2.EF6',
     'Breeze.WebApi2.NH',
	   'Breeze.Client',
	   'Breeze.Server.WebApi2',
     'Breeze.Server.ContextProvider.EF6',
     'Breeze.Server.ContextProvider.NH',
     'Breeze.Server.ContextProvider'
	];

  var breezeDlls = [
    'Breeze.WebApi',
    'Breeze.WebApi.EF',
    'Breeze.WebApi.NH',
    'Breeze.ContextProvider',
    'Breeze.ContextProvider.EF6',
    'Breeze.ContextProvider.NH',
    'Breeze.WebApi2'
  ];

  var sampleSolutionFileNames = grunt.file.expand('../../**/*.sln');

  var sampleDirs = grunt.file.expand(['../../net/*/', '../../no-server/*/', '../../node/*/']);
  grunt.log.writeln('SampleDirs ');
  grunt.log.writeln('---------- ');
  sampleDirs.forEach(function(fn) {
     grunt.log.writeln( '    ' + fn);
  });

  sampleDirs.forEach(function(dir) {
     grunt.log.writeln('Dir: ' + dir);
     grunt.log.writeln('Relative: ' + path.relative('../../', dir));
  });

  var versionNum = getBreezeVersion();
  var zipFileName = '../breeze-runtime-' + versionNum + '.zip';
  var zipPlusFileName = '../breeze-runtime-plus-' + versionNum + '.zip';

  grunt.log.writeln('zipName: ' + zipPlusFileName);
  grunt.file.write(tempDir + 'version.txt', 'Version: ' + versionNum);
  grunt.log.writeln('localAppData: ' + process.env.LOCALAPPDATA);
  grunt.log.writeln('SolutionFileNames ');
  grunt.log.writeln('----------------- ');
  sampleSolutionFileNames.forEach(function(fn) {
     grunt.log.writeln( '    ' + fn);
  });

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    nugetSolutionUpdate: {
      samples: {
        solutionFileNames: sampleSolutionFileNames
      }
    },

    clean: {
      options: {
        // uncomment to test
        // "no-write": true,
        force: true,
      },

      samples:  join( ['../../**/'], tempPaths),
    },

	  msBuild: {
      samples: {
        msBuildOptions: msBuildOptions,
        solutionFileNames: sampleSolutionFileNames,
      },
    },

    copy: {
      preZip: {
        files: [
          { expand: true, cwd: jsBuildDir, src: ['breeze*.js'], dest: tempDir },
          { expand: true, cwd: jsSrcDir, src: ['b??_breeze.**js'], dest: tempDir + 'Scripts/Adapters/',
            rename: function(dest, src) {
              return dest + 'breeze' + src.substring(src.indexOf('.'));
            }
          },
          { expand: true, cwd: qDir , src: ['q.**js'], dest: tempDir + 'Scripts' },
          { expand: true, cwd: tsSrcDir, src: ['breeze.d.ts'], dest: tempDir },
          { expand: true, cwd: metadataDir, src: ['*.*'], dest: tempDir },

          { expand: true, cwd: serverNetDir + 'Breeze.WebApi', src: ['Breeze.WebApi.dll'], dest: tempDir + 'Server'},
          { expand: true, cwd: serverNetDir + 'Breeze.WebApi.EF', src: ['Breeze.WebApi.EF.dll'], dest: tempDir + 'Server'},
          { expand: true, cwd: serverNetDir + 'Breeze.WebApi.NH', src: ['Breeze.WebApi.NH.dll'], dest: tempDir + 'Server'},

          { expand: true, cwd: serverNetDir + 'Breeze.WebApi2', src: ['Breeze.WebApi2.dll'], dest: tempDir + 'Server'},
          { expand: true, cwd: serverNetDir + 'Breeze.ContextProvider', src: ['Breeze.ContextProvider.dll'], dest: tempDir + 'Server'},
          { expand: true, cwd: serverNetDir + 'ContextProvider.EF6', src: ['Breeze.ContextProvider.EF6.dll'], dest: tempDir + 'Server'},
          { expand: true, cwd: serverNetDir + 'Breeze.ContextProvider.NH', src: ['Breeze.ContextProvider.NH.dll'], dest: tempDir + 'Server'},

          { expand: true, cwd: serverNetDir + 'Nuget.builds', src: ['readme.txt'], dest: tempDir },

        ]
      },
      samples: {
        files: sampleDirs.map(function(dir) {
            return buildSampleCopyCmd(dir, tempDir);
          })
      },
    },

    compress: {
      base: {
        options: { archive:  zipFileName, mode: 'zip', level: 9 },
        files: [
          { expand: true, cwd: tempDir, src: [ '**/**', '!Samples/**/*' ], dest: '/' }
        ]
      },
      baseWithSamples: {
        options: { archive:  zipPlusFileName, mode: 'zip', level: 9  },
        files: [
          { expand: true, dot: true, cwd: tempDir, src: [ '**/*' ], dest: '/' }
        ]
      }
    },

  });

  grunt.loadNpmTasks('grunt-exec');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-compress');

  grunt.registerMultiTask('nugetSolutionUpdate', 'nuget update', function( ) {
    // dynamically build the exec tasks
    var that = this;

    this.data.solutionFileNames.forEach(function(solutionFileName) {
      execNugetInstall(solutionFileName, that.data);
      execNugetUpdate(solutionFileName, nugetPackageNames, that.data);
    });
  });

  grunt.registerMultiTask('msBuild', 'Execute MsBuild', function( ) {
    // dynamically build the exec tasks
    grunt.log.writeln('msBuildOptions: ' + this.data.msBuildOptions);
    var that = this;

    this.data.solutionFileNames.forEach(function(solutionFileName) {
      execMsBuild(solutionFileName, that.data);
    });

  });

  // for debugging file patterns
  grunt.registerMultiTask('listFiles', 'List files', function() {
    grunt.log.writeln('target: ' + this.target);

    this.files.forEach(function(fileGroup) {
      fileGroup.src.forEach(function(fileName) {
        grunt.log.writeln('file: ' + fileName);
      });
    });
  });

  grunt.registerTask('buildRelease',
   [ 'nugetSolutionUpdate:samples', 'clean:samples', 'msBuild:samples']);
  grunt.registerTask('packageRelease',
   [ 'clean:samples', 'copy:samples', 'compress']);

  grunt.registerTask('default', ['buildRelease', 'packageRelease']);

  function getBreezeVersion() {
     var versionFile = grunt.file.read( jsSrcDir + '_head.jsfrag');
     var regex = /\s+version:\s*"(\d.\d\d*.?\d*)"/
     var matches = regex.exec(versionFile);

     if (matches == null) {
        throw new Error('Version number not found');
     }
     // matches[0] is entire version string - [1] is just the capturing group.
     var versionNum = matches[1];
     grunt.log.writeln('version: ' + versionNum);
     return versionNum;
  }

  function join(a1, a2) {
    var result = [];
    a1.forEach(function(a1Item) {
      a2.forEach(function(a2Item) {
        result.push(a1Item + '**/' + a2Item);
      });
    });
    return result;
  }

  function buildSampleCopyCmd(srcRoot, destRoot) {
    // var baseName = path.basename(srcRoot);
    var destPath = path.relative('../../', srcRoot);
    var files = ['**/*', '**/.nuget/*'];

    var cmd = {
      expand: true,
      cwd: srcRoot ,
      src: files,
      dest: destRoot + destPath,
    }
    return cmd;
  }

  function execNugetInstall(solutionFileName, config ) {

    var solutionDir = path.dirname(solutionFileName);
    var packagesDir = solutionDir + '/packages';

    var configFileNames = grunt.file.expand(solutionDir + '/**/packages.config');
    configFileNames.forEach(function(fn) {
      grunt.log.writeln('Preparing nuget install for file: ' + fn);
      var cmd = 'nuget install ' + fn + ' -OutputDirectory ' + packagesDir;
      // grunt.log.writeln('cmd: ' + cmd);
      runExec('nugetInstall', {
        cmd: cmd
      });
    });
  }

  function execNugetUpdate(solutionFileName, nugetPackageNames, config) {
    var baseCmd = 'nuget update ' + solutionFileName +  ' -FileConflictAction Ignore -Id ';

    nugetPackageNames.forEach(function(npn) {
      grunt.log.writeln('cmd: ' + baseCmd + npn);
      runExec('nugetUpdate', {
        cmd: baseCmd + npn
      });
    });
  }

  function execMsBuild(solutionFileName, config ) {
    grunt.log.writeln('Executing solution build for: ' + solutionFileName);

    var cwd = path.dirname(solutionFileName);
    var baseName = path.basename(solutionFileName);
    var rootCmd = msBuild + '"' + baseName +'"' + config.msBuildOptions + ' /t:'

    runExec('msBuildClean', {
      cwd: cwd,
      cmd: rootCmd + 'Clean'
    });
    runExec('msBuildRebuild', {
      cwd: cwd,
      cmd: rootCmd + 'Rebuild'
    });

  }

  var index = 0;

  function runExec(name, config) {
    var name = name+'-'+index++;
    grunt.config('exec.' + name, config);
    grunt.task.run('exec:' + name);
  }

  function log(err, stdout, stderr, cb) {
    if (err) {
      grunt.log.write(err);
      grunt.log.write(stderr);
      throw new Error("Failed");
    }

    grunt.log.write(stdout);

    cb();
  }

};<commit_msg>more grunt work<commit_after>module.exports = function(grunt) {

  var path = require('path');

  var msBuild = 'C:/Windows/Microsoft.NET/Framework/v4.0.30319/MSBuild.exe ';
  var msBuildOptions = ' /p:Configuration=Release /verbosity:minimal ';

  var breezeJsDir = '../../../Breeze.js/';

  var jsBuildDir  = breezeJsDir + 'build/';
  var jsSrcDir    = breezeJsDir + 'src/';
  var tsSrcDir    = breezeJsDir + 'typescript/';
  var qDir        = breezeJsDir + 'test/vendor/';
  var metadataDir = breezeJsDir + 'docs/metadata/';

  var serverNetDir = '../../../Breeze.server.net/';

  var tempDir = '../_temp/';

  var tempPaths = [
     'bin','obj', 'packages','*_Resharper*','*.suo','*.temp.*'
  ];

  var nugetPackageNames = [
     'Breeze.WebApi',
	   'Breeze.WebApi2.EF6',
     'Breeze.WebApi2.NH',
	   'Breeze.Client',
	   'Breeze.Server.WebApi2',
     'Breeze.Server.ContextProvider.EF6',
     'Breeze.Server.ContextProvider.NH',
     'Breeze.Server.ContextProvider'
	];

  var breezeDlls = [
    'Breeze.WebApi',
    'Breeze.WebApi.EF',
    'Breeze.WebApi.NH',
    'Breeze.ContextProvider',
    'Breeze.ContextProvider.EF6',
    'Breeze.ContextProvider.NH',
    'Breeze.WebApi2'
  ];

  var sampleSolutionFileNames = grunt.file.expand('../../**/*.sln');

  var sampleDirs = grunt.file.expand(['../../net/*/', '../../no-server/*/', '../../node/*/']);
  grunt.log.writeln('SampleDirs ');
  grunt.log.writeln('---------- ');
  sampleDirs.forEach(function(fn) {
     grunt.log.writeln( '    ' + fn);
  });

  sampleDirs.forEach(function(dir) {
     grunt.log.writeln('Dir: ' + dir);
     grunt.log.writeln('Relative: ' + path.relative('../../', dir));
  });

  var versionNum = getBreezeVersion();
  var zipFileName = '../breeze-runtime-' + versionNum + '.zip';
  var zipPlusFileName = '../breeze-runtime-plus-' + versionNum + '.zip';

  grunt.log.writeln('zipName: ' + zipPlusFileName);
  grunt.file.write(tempDir + 'version.txt', 'Version: ' + versionNum);
  grunt.log.writeln('localAppData: ' + process.env.LOCALAPPDATA);
  grunt.log.writeln('SolutionFileNames ');
  grunt.log.writeln('----------------- ');
  sampleSolutionFileNames.forEach(function(fn) {
     grunt.log.writeln( '    ' + fn);
  });

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    nugetSolutionUpdate: {
      samples: {
        solutionFileNames: sampleSolutionFileNames
      }
    },

    clean: {
      options: {
        // uncomment to test
        // "no-write": true,
        force: true,
      },
      tempDir: tempDir,
      samples:  join( ['../../**/'], tempPaths),
    },

	  msBuild: {
      samples: {
        msBuildOptions: msBuildOptions,
        solutionFileNames: sampleSolutionFileNames,
      },
    },

    copy: {
      jsClient: {
        files: [
          { expand: true, cwd: jsBuildDir, src: ['breeze*.js'], dest: tempDir + 'Scripts'},
          { expand: true, cwd: jsSrcDir, src: ['b??_breeze.**js'], dest: tempDir + 'Scripts/Adapters/',
            rename: function(dest, src) {
              return dest + 'breeze' + src.substring(src.indexOf('.'));
            }
          },
          { expand: true, cwd: qDir , src: ['q.**js'], dest: tempDir + 'Scripts' },
          { expand: true, cwd: tsSrcDir, src: ['breeze.d.ts'], dest: tempDir + 'Typescript'},
          { expand: true, cwd: metadataDir, src: ['*.*'], dest: tempDir + 'MetadataSchema' },
        ]
      },
      netDlls: {
        files: [
          { expand: true, cwd: serverNetDir + 'Breeze.WebApi', src: ['Breeze.WebApi.dll'], dest: tempDir + 'NetDlls'},
          { expand: true, cwd: serverNetDir + 'Breeze.WebApi.EF', src: ['Breeze.WebApi.EF.dll'], dest: tempDir + 'NetDlls'},
          { expand: true, cwd: serverNetDir + 'Breeze.WebApi.NH', src: ['Breeze.WebApi.NH.dll'], dest: tempDir + 'NetDlls'},

          { expand: true, cwd: serverNetDir + 'Breeze.WebApi2', src: ['Breeze.WebApi2.dll'], dest: tempDir + 'NetDlls'},
          { expand: true, cwd: serverNetDir + 'Breeze.ContextProvider', src: ['Breeze.ContextProvider.dll'], dest: tempDir + 'NetDlls'},
          { expand: true, cwd: serverNetDir + 'ContextProvider.EF6', src: ['Breeze.ContextProvider.EF6.dll'], dest: tempDir + 'NetDlls'},
          { expand: true, cwd: serverNetDir + 'Breeze.ContextProvider.NH', src: ['Breeze.ContextProvider.NH.dll'], dest: tempDir + 'NetDlls'},
        ]
      },

      samples: {
        files: sampleDirs.map(function(dir) {
            return buildSampleCopyCmd(dir, tempDir);
          })
      },

      readMe: {
        files: [{ expand: true, cwd: serverNetDir + 'Nuget.builds', src: ['readme.txt'], dest: tempDir + 'NetDlls'}],
      }
    },

    compress: {
      base: {
        options: { archive:  zipFileName, mode: 'zip', level: 9 },
        files: [
          { expand: true, cwd: tempDir, src: [ '**/**', '!Samples/**/*' ], dest: '/' }
        ]
      },
      baseWithSamples: {
        options: { archive:  zipPlusFileName, mode: 'zip', level: 9  },
        files: [
          { expand: true, dot: true, cwd: tempDir, src: [ '**/*' ], dest: '/' }
        ]
      }
    },

  });

  grunt.loadNpmTasks('grunt-exec');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-compress');

  grunt.registerMultiTask('nugetSolutionUpdate', 'nuget update', function( ) {
    // dynamically build the exec tasks
    var that = this;

    this.data.solutionFileNames.forEach(function(solutionFileName) {
      execNugetInstall(solutionFileName, that.data);
      execNugetUpdate(solutionFileName, nugetPackageNames, that.data);
    });
  });

  grunt.registerMultiTask('msBuild', 'Execute MsBuild', function( ) {
    // dynamically build the exec tasks
    grunt.log.writeln('msBuildOptions: ' + this.data.msBuildOptions);
    var that = this;

    this.data.solutionFileNames.forEach(function(solutionFileName) {
      execMsBuild(solutionFileName, that.data);
    });

  });

  // for debugging file patterns
  grunt.registerMultiTask('listFiles', 'List files', function() {
    grunt.log.writeln('target: ' + this.target);

    this.files.forEach(function(fileGroup) {
      fileGroup.src.forEach(function(fileName) {
        grunt.log.writeln('file: ' + fileName);
      });
    });
  });

  grunt.registerTask('buildRelease',
   [ 'nugetSolutionUpdate:samples', 'msBuild:samples']);
  grunt.registerTask('packageRelease',
   [ 'clean', 'copy', 'compress']);

  grunt.registerTask('default', ['buildRelease', 'packageRelease']);

  function getBreezeVersion() {
     var versionFile = grunt.file.read( jsSrcDir + '_head.jsfrag');
     var regex = /\s+version:\s*"(\d.\d\d*.?\d*)"/
     var matches = regex.exec(versionFile);

     if (matches == null) {
        throw new Error('Version number not found');
     }
     // matches[0] is entire version string - [1] is just the capturing group.
     var versionNum = matches[1];
     grunt.log.writeln('version: ' + versionNum);
     return versionNum;
  }

  function join(a1, a2) {
    var result = [];
    a1.forEach(function(a1Item) {
      a2.forEach(function(a2Item) {
        result.push(a1Item + '**/' + a2Item);
      });
    });
    return result;
  }

  function buildSampleCopyCmd(srcRoot, destRoot) {
    // var baseName = path.basename(srcRoot);
    var destPath = path.relative('../../', srcRoot);
    var files = ['**/*', '**/.nuget/*'];

    var cmd = {
      expand: true,
      cwd: srcRoot ,
      src: files,
      dest: destRoot + "Samples/" + destPath,
    }
    return cmd;
  }

  function execNugetInstall(solutionFileName, config ) {

    var solutionDir = path.dirname(solutionFileName);
    var packagesDir = solutionDir + '/packages';

    var configFileNames = grunt.file.expand(solutionDir + '/**/packages.config');
    configFileNames.forEach(function(fn) {
      grunt.log.writeln('Preparing nuget install for file: ' + fn);
      var cmd = 'nuget install ' + fn + ' -OutputDirectory ' + packagesDir;
      // grunt.log.writeln('cmd: ' + cmd);
      runExec('nugetInstall', {
        cmd: cmd
      });
    });
  }

  function execNugetUpdate(solutionFileName, nugetPackageNames, config) {
    var baseCmd = 'nuget update ' + solutionFileName +  ' -FileConflictAction Ignore -Id ';

    nugetPackageNames.forEach(function(npn) {
      grunt.log.writeln('cmd: ' + baseCmd + npn);
      runExec('nugetUpdate', {
        cmd: baseCmd + npn
      });
    });
  }

  function execMsBuild(solutionFileName, config ) {
    grunt.log.writeln('Executing solution build for: ' + solutionFileName);

    var cwd = path.dirname(solutionFileName);
    var baseName = path.basename(solutionFileName);
    var rootCmd = msBuild + '"' + baseName +'"' + config.msBuildOptions + ' /t:'

    runExec('msBuildClean', {
      cwd: cwd,
      cmd: rootCmd + 'Clean'
    });
    runExec('msBuildRebuild', {
      cwd: cwd,
      cmd: rootCmd + 'Rebuild'
    });

  }

  var index = 0;

  function runExec(name, config) {
    var name = name+'-'+index++;
    grunt.config('exec.' + name, config);
    grunt.task.run('exec:' + name);
  }

  function log(err, stdout, stderr, cb) {
    if (err) {
      grunt.log.write(err);
      grunt.log.write(stderr);
      throw new Error("Failed");
    }

    grunt.log.write(stdout);

    cb();
  }

};<|endoftext|>


<commit_before>// ES10 - 1: Object - fromEntries
// To do: make all tests pass, leave the assert lines unchanged!
// Follow the hints of the failure messages!

describe('`Object.fromEntries()` converts key-value pairs into an object', () => {
  it('it is a static function defined on `Object`', () => {
    //// const fn = Object.entries;
    const fn = Object.fromEntries;
    assert(Object.is(Object.fromEntries, fn));
  });
  describe('the key-value pairs might be given as', () => {
    it('a simple array, with (at least) two values', () => {
      const keyValuePair = ['one', 'two'];
      assert.deepEqual(Object.fromEntries([keyValuePair]), {one: 'two'});
    });
    it('a simple array, with multiple key-value pairs', () => {
      const keyValuePair1 = ['one', 'two'];
      const keyValuePair2 = ['three', 'four'];
      assert.deepEqual(Object.fromEntries([keyValuePair1, keyValuePair2]), {one: 'two', three: 'four'});
    });
    it('a simple array, with duplicate keys override their predecessor', () => {
      const keyValuePair1 = ['one', 'two'];
      //// const keyValuePair2 = ['ONE', 'four'];
      const keyValuePair2 = ['one', 'four'];
      assert.deepEqual(Object.fromEntries([keyValuePair1, keyValuePair2]), {one: 'four'});
    });
    it('an object, with properties `0` and `1`', () => {
      const keyValuePair = {0: 'key', 1: 'value'};
      assert.deepEqual(Object.fromEntries([keyValuePair]), {key: 'value'});
    });
    it('an object, without a property `0` results in an object using `undefined` as key', () => {
      const keyValuePair = {key: 'key', 1: 'value'};
      assert.deepEqual(Object.fromEntries([keyValuePair]), {[undefined]: 'value'});
    });
    it('many objects, with the same property `0` override each other`s values', () => {
      const pair1 = {0: 'key', 1: 'value 1'};
      //// const pair2 = {0: 'key1', 1: 'value 2'};
      const pair2 = {0: 'key', 1: 'value 2'};
      assert.deepEqual(Object.fromEntries([pair1, pair2]), {key: 'value 2'});
    });
    it('a `Map`, which maps naturally to object key+value', () => {
      const map = new Map([['key', 'value']]);
      map.set('key1', 'value1');
      //// const object = Object.entries(map);
      const object = Object.fromEntries(map);
      assert.deepEqual(object, {key: 'value', key1: 'value1'});
    });
    it('an empty `Map`, results in an empty object', () => {
      //// const object = Object.fromEntries(new Map([[]]));
      const object = Object.fromEntries(new Map());
      assert.deepEqual(object, {});
    });
    it('a `Set`, used with `set.entires()`, returns an object with key=value', () => {
      const set = new Set();
      ////
      set.add('value0');
      set.add('value1');
      assert.deepEqual(Object.fromEntries(set.entries()), {value0: 'value0', value1: 'value1'});
    });
  });
  describe('the 1st parameter', () => {
    describe('must be iterable', () => {
      it('like an array', () => {
        assert.doesNotThrow(() => Object.fromEntries([]));
      });
      it('a self-made iterable', () => {
        let cnt = -1;
        const iterable = {
          [Symbol.iterator]() {
            return {
              next: () => {
                cnt++;
                if (cnt === 0) return {value: ['a', 42], done: false};
                if (cnt === 1) return {done: true};
              }
            }
          }
        };
        assert.deepEqual(Object.fromEntries(iterable), {a: 42});
      });
    });
    describe('each entry must have a property `0` and `1` for key+value', () => {
      it('like an object with these explicit properties', () => {
        assert.deepEqual(Object.fromEntries([{0: 'key', 1: 'value'}]), {key: 'value'});
      });
      it('if any (or both) are missing, `undefined` is used', () => {
        //// const obj = {0: 'key', 10: 'no key', 20: 'no value'};
        const obj = {};
        assert.deepEqual(Object.fromEntries([obj]), {[undefined]: undefined});
      });
      it('if an empty array is given, both are undefined too', () => {
        //// const emptyArray = [1, 2];
        const emptyArray = [];
        assert.deepEqual(Object.fromEntries([emptyArray]), {[undefined]: undefined});
      });
      it('toString-ables can be keys', () => {
        //// const s = new class { toString() { return 'value'; }  };
        const s = new class { toString() { return 'key'; }  };
        assert.deepEqual(Object.fromEntries([[s, 42]]), {key: 42});
      });
      it('iterables (like a `Map`) are NOT expected, and not used', () => {
        const map = new Map([['key', 'value']]);
        assert.deepEqual(Object.fromEntries([map]), {[undefined]: undefined});
        //// map.key = 23;
        map[0] = 23;
        assert.deepEqual(Object.fromEntries([map]), {23: undefined});
      });
    });
    describe('throws a TypeError', () => {
      it('if missing', () => {
        //// const fn = () => Object.fromEntries('');
        const fn = () => Object.fromEntries();
        assert.throws(fn, TypeError);
      });
      it('if `undefined` (which is not an iterable)', () => {
        assert.throws(() => { Object.fromEntries(undefined); }, TypeError);
      });
      it('if `null` (which is not an iterable)', () => {
        assert.throws(() => { Object.fromEntries(undefined); }, TypeError);
      });
      it('if boolean (which is not an iterable)', () => {
        assert.throws(() => { Object.fromEntries(true); }, TypeError);
      });
      it('if a Symbol (which is not an iterable)', () => {
        assert.throws(() => { Object.fromEntries(Symbol.for('some')); }, TypeError);
      });
    });
  });
  describe('more use cases/learnings, are', () => {
    it('can be used to map data', () => {
      const people = [{name: 'Alex', age: 21}, {name: 'Anna', age: 21}];
      //// const peoplesAge = Object.entries(people.map(({name, age}) => [name, age]));
      const peoplesAge = Object.fromEntries(people.map(({name, age}) => [name, age]));
      assert.deepEqual({Alex: 21, Anna:21}, peoplesAge);
    });
    it('an empty string, returns an empty object', () => {
      //// const emptyString = ' ';
      const emptyString = '';
      assert.deepEqual(Object.fromEntries(emptyString), {});
    });
    it('a single-space string, throws', () => {
      assert.throws(() => Object.fromEntries(' '), TypeError);
    });
    describe('not symetric to `Object.entries()`', () => {
      it('allows Symbols as keys, while `Object.entries()` does not report them', () => {
        const sym = Symbol();
        const fromEntries = Object.fromEntries([[sym, 42]]);
        assert.deepEqual(fromEntries, {[sym]: 42});
        // while
        assert.deepEqual(Object.entries(fromEntries), []);
      });
    });
  });
});
<commit_msg>Now its a kata.<commit_after>// ES10 - 1: Object - fromEntries
// To do: make all tests pass, leave the assert lines unchanged!
// Follow the hints of the failure messages!

describe('`Object.fromEntries()` converts key-value pairs into an object', () => {
  it('it is a static function defined on `Object`', () => {
    //// const fn = Object.entries;
    const fn = Object.fromEntries;
    assert(Object.is(Object.fromEntries, fn));
  });
  describe('the key-value pairs might be given as', () => {
    it('a simple array, with (at least) two values', () => {
      //// const keyValuePair = ['zero', 'one', 'two'];
      const keyValuePair = ['one', 'two'];
      assert.deepEqual(Object.fromEntries([keyValuePair]), {one: 'two'});
    });
    it('a simple array, with multiple key-value pairs', () => {
      const keyValuePair1 = ['one', 'two'];
      //// const keyValuePair2 = ['3', 'Four'];
      const keyValuePair2 = ['three', 'four'];
      assert.deepEqual(Object.fromEntries([keyValuePair1, keyValuePair2]), {one: 'two', three: 'four'});
    });
    it('a simple array, with duplicate keys override their predecessor', () => {
      const keyValuePair1 = ['one', 'two'];
      //// const keyValuePair2 = ['ONE', 'four'];
      const keyValuePair2 = ['one', 'four'];
      assert.deepEqual(Object.fromEntries([keyValuePair1, keyValuePair2]), {one: 'four'});
    });
    it('an object, with properties `0` and `1`', () => {
      //// const keyValuePair = {key: 'key', 1: 'value'};
      const keyValuePair = {0: 'key', 1: 'value'};
      assert.deepEqual(Object.fromEntries([keyValuePair]), {key: 'value'});
    });
    it('an object, without a property `0` results in an object using `undefined` as key', () => {
      //// const keyValuePair = {0: 'key', 1: 'value'};
      const keyValuePair = {key: 'key', 1: 'value'};
      assert.deepEqual(Object.fromEntries([keyValuePair]), {[undefined]: 'value'});
    });
    it('many objects, with the same property `0` override each other`s values', () => {
      const pair1 = {0: 'key', 1: 'value 1'};
      //// const pair2 = {0: 'key1', 1: 'value 2'};
      const pair2 = {0: 'key', 1: 'value 2'};
      assert.deepEqual(Object.fromEntries([pair1, pair2]), {key: 'value 2'});
    });
    it('a `Map`, which maps naturally to object key+value', () => {
      const map = new Map([['key', 'value']]);
      map.set('key1', 'value1');
      //// const object = Object.entries(map);
      const object = Object.fromEntries(map);
      assert.deepEqual(object, {key: 'value', key1: 'value1'});
    });
    it('an empty `Map`, results in an empty object', () => {
      //// const object = Object.fromEntries(new Map([[]]));
      const object = Object.fromEntries(new Map());
      assert.deepEqual(object, {});
    });
    it('a `Set`, used with `set.entires()`, returns an object with key=value', () => {
      const set = new Set();
      ////
      set.add('value0');
      set.add('value1');
      assert.deepEqual(Object.fromEntries(set.entries()), {value0: 'value0', value1: 'value1'});
    });
  });
  describe('the 1st parameter', () => {
    describe('must be iterable', () => {
      it('like an array', () => {
        //// const anArray = ' ';
        const anArray = [];
        assert.doesNotThrow(() => Object.fromEntries(anArray));
      });
      it('a self-made iterable', () => {
        let cnt = -1;
        const iterable = {
          [Symbol.iterator]() {
            return {
              next: () => {
                cnt++;
                //// if (cnt === 1) return {value: ['key', 42], done: false};
                if (cnt === 0) return {value: ['key', 42], done: false};
                //// if (cnt === 0) return {done: true};
                if (cnt === 1) return {done: true};
              }
            }
          }
        };
        assert.deepEqual(Object.fromEntries(iterable), {key: 42});
      });
    });
    describe('each entry must have a property `0` and `1` for key+value', () => {
      it('like an object with these explicit properties', () => {
        //// const obj = {'00': 'key', 11: 'value'};
        const obj = {0: 'key', 1: 'value'};
        assert.deepEqual(Object.fromEntries([obj]), {key: 'value'});
      });
      it('if any (or both) are missing, `undefined` is used', () => {
        //// const obj = {0: 'key', 10: 'no key', 20: 'no value'};
        const obj = {};
        assert.deepEqual(Object.fromEntries([obj]), {[undefined]: undefined});
      });
      it('if an empty array is given, both are undefined too', () => {
        //// const emptyArray = [1, 2];
        const emptyArray = [];
        assert.deepEqual(Object.fromEntries([emptyArray]), {[undefined]: undefined});
      });
      it('toString-ables can be keys', () => {
        //// const s = new class { toString() { return 'value'; }  };
        const s = new class { toString() { return 'key'; }  };
        assert.deepEqual(Object.fromEntries([[s, 42]]), {key: 42});
      });
      it('iterables (like a `Map`) are NOT expected, and not used', () => {
        const map = new Map([['key', 'value']]);
        assert.deepEqual(Object.fromEntries([map]), {[undefined]: undefined});
        //// map.key = 23;
        map[0] = 23;
        assert.deepEqual(Object.fromEntries([map]), {23: undefined});
      });
    });
    describe('throws a TypeError', () => {
      it('if missing', () => {
        //// const fn = () => Object.fromEntries('');
        const fn = () => Object.fromEntries();
        assert.throws(fn, TypeError);
      });
      it('if `undefined` (which is not an iterable)', () => {
        //// const fn = () => { Object.fromEntries(''); };
        const fn = () => { Object.fromEntries(undefined); };
        assert.throws(fn, TypeError);
      });
      it('if `null` (which is not an iterable)', () => {
        //// const nullValue = '';
        const nullValue = null;
        assert.throws(() => { Object.fromEntries(nullValue); }, TypeError);
      });
      it('if boolean (which is not an iterable)', () => {
        //// const aBool = [];
        const aBool = true;
        assert.throws(() => { Object.fromEntries(aBool); }, TypeError);
      });
      it('if a Symbol (which is not an iterable)', () => {
        //// const fn = () => { Object.fromEntries(SyMbOl('some')); };
        const fn = () => { Object.fromEntries(Symbol('some')); };
        assert.throws(fn, TypeError);
      });
    });
  });
  describe('more use cases/learnings, are', () => {
    it('can be used to map data', () => {
      const people = [{name: 'Alex', age: 21}, {name: 'Anna', age: 21}];
      //// const peoplesAge = Object.entries(people.map(({name, age}) => [name, age]));
      const peoplesAge = Object.fromEntries(people.map(({name, age}) => [name, age]));
      assert.deepEqual({Alex: 21, Anna:21}, peoplesAge);
    });
    it('an empty string, returns an empty object', () => {
      //// const emptyString = ' ';
      const emptyString = '';
      assert.deepEqual(Object.fromEntries(emptyString), {});
    });
    it('a single-space string, throws', () => {
      //// const singleSpace = '';
      const singleSpace = ' ';
      assert.throws(() => Object.fromEntries(singleSpace), TypeError);
    });
    describe('not symetric to `Object.entries()`', () => {
      it('allows Symbols as keys, while `Object.entries()` does not report them', () => {
        const sym = Symbol();
        const fromEntries = Object.fromEntries([[sym, 42]]);
        //// const entries = [sym, 42];
        const entries = [];

        assert.deepEqual(fromEntries, {[sym]: 42});
        assert.deepEqual(Object.entries(fromEntries), entries);
      });
    });
  });
});
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

import {checkAccess} from "./authentication";
import Backbone from "backbone";
import _ from "lodash";

var beforeUnloads = {};

export default Backbone.Router.extend({
  routes: {},

  beforeUnload: function (name, fn) {
    beforeUnloads[name] = fn;
  },

  removeBeforeUnload: function (name) {
    delete beforeUnloads[name];
  },

  navigate: function (fragment, options) {
    let continueNav = true;
    const msg = _.find(_.map(beforeUnloads, function (fn) { return fn(); }), function (beforeReturn) {
      if (beforeReturn) { return true; }
    });

    if (msg) {
      continueNav = window.confirm(msg);
    }

    if (options && options.redirect) {
      return window.location = fragment;
    }

    if (continueNav) {
      Backbone.Router.prototype.navigate(fragment, options);
    }
  },

  addModuleRouteObject: function (RouteObject) {
    const that = this;
    const routeUrls = RouteObject.prototype.getRouteUrls();

    routeUrls.forEach(route => {
      this.route(route, route.toString(), (...args) => {
        const roles = RouteObject.prototype.getRouteRoles(route);

        checkAccess(roles).then(() => {
          if (!that.activeRouteObject || !that.activeRouteObject.hasRoute(route)) {
            that.activeRouteObject = new RouteObject(route, args);
          }

          const routeObject = that.activeRouteObject;
          const component = routeObject.routeCallback(route, args);
          that.currentRouteOptions = {
            selectedHeader: this.activeRouteObject.selectedHeader,
            component,
            roles,
            route: route.toString()
          };
          that.trigger('new-component', this.currentRouteOptions);
        }, () => {/* do nothing on reject*/ });
      });
    });
  },

  setModuleRoutes: function (addons) {
    _.each(addons, function (module) {
      if (module) {
        module.initialize();
        // This is pure routes the addon provides
        if (module.RouteObjects) {
          _.each(module.RouteObjects, this.addModuleRouteObject, this);
        }
      }
    }, this);
  },

  initialize: function (addons) {
    this.addons = addons;
    // NOTE: This must be below creation of the layout
    // FauxtonAPI header links and others depend on existence of the layout
    this.setModuleRoutes(addons);

    this.lastPages = [];
    //keep last pages visited in Fauxton
    Backbone.history.on('route', function () {
      this.lastPages.push(Backbone.history.fragment);
      if (this.lastPages.length > 2) {
        this.lastPages.shift();
      }
    }, this);
  }
});
<commit_msg>Updates page title to match current route (#1108)<commit_after>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

import {checkAccess} from "./authentication";
import Backbone from "backbone";
import _ from "lodash";

var beforeUnloads = {};

export default Backbone.Router.extend({
  routes: {},
  originalPageTitle: window.document.title,

  beforeUnload: function (name, fn) {
    beforeUnloads[name] = fn;
  },

  removeBeforeUnload: function (name) {
    delete beforeUnloads[name];
  },

  navigate: function (fragment, options) {
    let continueNav = true;
    const msg = _.find(_.map(beforeUnloads, function (fn) { return fn(); }), function (beforeReturn) {
      if (beforeReturn) { return true; }
    });

    if (msg) {
      continueNav = window.confirm(msg);
    }

    if (options && options.redirect) {
      return window.location = fragment;
    }

    if (continueNav) {
      Backbone.Router.prototype.navigate(fragment, options);
      this.updateWindowTitle(fragment);
    }
  },

  updateWindowTitle: function(fragment) {
    if (fragment.startsWith('#/')) {
      window.document.title = this.originalPageTitle + ' - ' + fragment.substring(2);
    } else if (fragment.startsWith('/') || fragment.startsWith('#')) {
      window.document.title = this.originalPageTitle + ' - ' + fragment.substring(1);
    } else {
      window.document.title = this.originalPageTitle + ' - ' + fragment;
    }
  },

  addModuleRouteObject: function (RouteObject) {
    const that = this;
    const routeUrls = RouteObject.prototype.getRouteUrls();

    routeUrls.forEach(route => {
      this.route(route, route.toString(), (...args) => {
        const roles = RouteObject.prototype.getRouteRoles(route);

        checkAccess(roles).then(() => {
          if (!that.activeRouteObject || !that.activeRouteObject.hasRoute(route)) {
            that.activeRouteObject = new RouteObject(route, args);
          }

          const routeObject = that.activeRouteObject;
          const component = routeObject.routeCallback(route, args);
          that.currentRouteOptions = {
            selectedHeader: this.activeRouteObject.selectedHeader,
            component,
            roles,
            route: route.toString()
          };
          that.trigger('new-component', this.currentRouteOptions);
        }, () => {/* do nothing on reject*/ });
      });
    });
  },

  setModuleRoutes: function (addons) {
    _.each(addons, function (module) {
      if (module) {
        module.initialize();
        // This is pure routes the addon provides
        if (module.RouteObjects) {
          _.each(module.RouteObjects, this.addModuleRouteObject, this);
        }
      }
    }, this);
  },

  initialize: function (addons) {
    this.addons = addons;
    // NOTE: This must be below creation of the layout
    // FauxtonAPI header links and others depend on existence of the layout
    this.setModuleRoutes(addons);

    this.lastPages = [];
    //keep last pages visited in Fauxton
    Backbone.history.on('route', function () {
      this.lastPages.push(Backbone.history.fragment);
      if (this.lastPages.length > 2) {
        this.lastPages.shift();
      }
    }, this);
  }
});
<|endoftext|>


<commit_before>import io from 'socket.io-client';
import store from '../store';
import {Map} from 'immutable';
import {addChannel, addUserToChannel} from '../actions/channels';
import {addMessage, loadChannelHistory} from '../actions/messages';
import {setUserInfo, joinUser} from 'actions/users';
import {init, initUser} from '../actions/local';
import {SC} from '../../constants';

export function socketClient(type = null, socketData) {
  const socket = io();

  if (type === 'SOCKET_INIT') {
    socket.on(SC.ADD_MESSAGE, (data) => {
      store.dispatch(addMessage(Map(data)));
    });

    socket.on(SC.ADD_CHANNEL, (data) => {
      store.dispatch(addChannel(Map({id: data.id, name: data.name, joined: false})));
    });

    socket.on(SC.JOIN_USER, (data) => {
      store.dispatch(joinUser(data));
    });

    socket.on(SC.INIT, (data) => {
      store.dispatch(init(data));
    });

    socket.on(SC.SIGN_IN, (data) => {
      store.dispatch(initUser(data));
    });

    socket.on(SC.JOIN_TO_CHANNEL, (data) => {
      store.dispatch(addUserToChannel(data));
    });

    socket.on(SC.CHANGE_USER_INFO, (data) => {
      store.dispatch(setUserInfo(data));
    });

    socket.on(SC.SET_CHANNEL_HISTORY, (data) => {
      store.dispatch(loadChannelHistory(data));
    });

    socket.on(SC.ADD_DIRECT_CHANNEL, (data) => {
      store.dispatch(addChannel(Map({
        id: data.id,
        name: data.name,
        users: data.users,
        isDirect: data.isDirect,
      })));
    });

    socket.on('error', () => {
      store.history.pushState(null, '/login');
    });
  } else if (type) {
    socket.emit(type, socketData);
  }
}
<commit_msg>feat: reload page on session id not found<commit_after>import io from 'socket.io-client';
import store from '../store';
import {Map} from 'immutable';
import {addChannel, addUserToChannel} from '../actions/channels';
import {addMessage, loadChannelHistory} from '../actions/messages';
import {setUserInfo, joinUser} from 'actions/users';
import {init, initUser, logOut} from '../actions/local';
import {SC} from '../../constants';

export function socketClient(type = null, socketData) {
  const socket = io();

  if (type === 'SOCKET_INIT') {
    socket.on(SC.ADD_MESSAGE, (data) => {
      store.dispatch(addMessage(Map(data)));
    });

    socket.on(SC.ADD_CHANNEL, (data) => {
      store.dispatch(addChannel(Map({id: data.id, name: data.name, joined: false})));
    });

    socket.on(SC.JOIN_USER, (data) => {
      store.dispatch(joinUser(data));
    });

    socket.on(SC.INIT, (data) => {
      store.dispatch(init(data));
    });

    socket.on(SC.SIGN_IN, (data) => {
      store.dispatch(initUser(data));
    });

    socket.on(SC.JOIN_TO_CHANNEL, (data) => {
      store.dispatch(addUserToChannel(data));
    });

    socket.on(SC.CHANGE_USER_INFO, (data) => {
      store.dispatch(setUserInfo(data));
    });

    socket.on(SC.SET_CHANNEL_HISTORY, (data) => {
      store.dispatch(loadChannelHistory(data));
    });

    socket.on(SC.ADD_DIRECT_CHANNEL, (data) => {
      store.dispatch(addChannel(Map({
        id: data.id,
        name: data.name,
        users: data.users,
        isDirect: data.isDirect,
      })));
    });

    socket.on('error', () => {
      store.dispatch(logOut());
    });
  } else if (type) {
    socket.emit(type, socketData);
  }
}
<|endoftext|>