<commit_before>/**
 * @fileOverview Wallet classes.
 */

/**
 * Wallet constructor class.
 * @param {Object} $scope Angular scope.
 * @constructor
 */
define(['./module', 'darkwallet', 'frontend/services', 'util/ng/clipboard', 'util/ng/modals'],
function (controllers, DarkWallet, Services, ClipboardUtils, ModalUtils) {
  'use strict';
  controllers.controller('WalletCtrl',
  ['$scope', '$location' ,'ngProgress', 'toaster', '$modal', '$timeout',
   function($scope, $location, ngProgress, toaster, $modal, $timeout) {
  var pubKey, mpKey, addressIndex;

  // Pointer to service
  var bg = DarkWallet.service();
  $scope.rates = {};

  // Global scope utils
  ModalUtils.registerScope($scope, $modal);
  ClipboardUtils.registerScope($scope);

  // Gui services
  var report = function(msg) {
      if (console) {
        console.log("[WalletCtrl] " + msg);
      }
      toaster.pop('note', "wallet", msg)
  }

  // Gui service, connect to report events on page.
  Services.connect('gui', function(data) {
    console.log('[WalletCtrl] gui bus:', data.type);
    if (data.type == 'balance') {
    }
    if (data.type == 'height') {
        $scope.currentHeight = data.value;
    }
    if (data.type == 'text' || data.type == 'note') {
        toaster.pop('note', 'gui', data.text);
    }
    if (data.type == 'error') {
        toaster.pop('error', data.title || 'gui', data.text);
    }
    if (data.type == 'warning') {
        toaster.pop('warning', 'gui', data.text);
    }
    if (['height', 'update', 'balance'].indexOf(data.type) > -1) {
        if (!$scope.$$phase) {
            $scope.$apply();
        }
    }
  })

  // Obelisk service, connect to get notified on events and connection.
  Services.connect('obelisk', function(data) {
    console.log("[WalletCtrl] obelisk bus:", data.type);
    if (data.type == 'connected') {
        ngProgress.color('green');
        ngProgress.complete();
    }
  })

  // Wallet service, connect to get notified about identity getting loaded.
  Services.connect('wallet', function(data) {
    console.log("[WalletCtrl] wallet bus:", data.type);
    if (data.type == 'ready') {
        // identity is ready here
        loadIdentity(DarkWallet.getIdentity())
    }
    if (data.type == 'ticker') {
        $scope.rates[data.currency] = data['24h_avg'];
    }
  })

  // Check if a route is active
  $scope.isActive = function(route) {
    return route === $location.path();
  }

  // Initialize if empty wallet
  function initializeEmpty() {
      if (Object.keys($scope.addresses).length == 0) {
          // generate 5 addresses for now
          for(var idx=0; idx<5; idx++) {
              $scope.generateAddress(0);
              $scope.generateAddress(1);
          }
      }
  }

  function loadAddresses(identity) {
      /* Load addresses into angular */
      Object.keys(identity.wallet.pubKeys).forEach(function(pubKeyIndex) {
          var walletAddress = identity.wallet.getAddress(pubKeyIndex);
          // Init pockets
          for(var idx=0; idx<identity.wallet.pockets.length; idx++) {
              $scope.initPocket(idx);
          };
          // Regular addresses
          if (walletAddress.index.length > 1) {
              // add to scope
              var pocketIndex = walletAddress.index[0];
              if (!$scope.addresses[pocketIndex]) {
                  $scope.addresses[pocketIndex] = [];
              }
              var addressArray = $scope.addresses[pocketIndex];
              if ($scope.allAddresses.indexOf(walletAddress) == -1) {
                  addressArray.push(walletAddress);
                  $scope.allAddresses.push(walletAddress);
              }
          }
      });
  }

  function loadIdentity(identity) {
      $scope.addresses = {};
      $scope.allAddresses = [];
      // set some links
      $scope.identity = identity;
      $scope.availableIdentities = bg.getKeyRing().availableIdentities;
      $scope.history = identity.history.history;
      // set history update callback
      $scope.totalBalance = identity.wallet.getBalance();
      $scope.selectedCurrency = identity.settings.currency;
      $scope.selectedFiat = identity.settings.fiatCurrency;
      $scope.defaultFee = identity.wallet.fee / 100000000;

      // load addresses into angular
      loadAddresses(identity);

      // initialize if empty wallet
      initializeEmpty();

      // this will connect to obelisk if we're not yet connected
      ngProgress.color('firebrick');
      ngProgress.start();
      if (bg.getClient() && bg.getClient().connected) {
          // If already connected set the progress bar to finish
          // we wait a moment to provide better visual feedback
          $timeout(function() {
              ngProgress.color('green');
              ngProgress.complete();
          }, 500);
      } else {
          // Request connecting to blockchain
          bg.connect();
      }
      console.log("[WalletCtrl] loadIdentity", identity.name);
      // apply scope changes
      if(!$scope.$$phase) {
          $scope.$apply();
      }
  };

  // Initialize pocket structures.
  $scope.initPocket = function(rowIndex) {
      var pocketIndex = rowIndex*2;
      if (!$scope.addresses[pocketIndex]) {
          $scope.addresses[pocketIndex] = [];
      }
      if (!$scope.addresses[pocketIndex+1]) {
          $scope.addresses[pocketIndex+1] = [];
      }
  }

  // scope function to generate (or load from cache) a new address
  $scope.generateAddress = function(isChange, n) {
    if (!isChange) {
        isChange = 0;
    }
    if (!$scope.addresses[isChange]) {
        $scope.addresses[isChange] = [];
    }
    var addressArray = $scope.addresses[isChange];
    if (n === undefined || n === null) {
        n = addressArray.length;
    }
    var walletAddress = $scope.identity.wallet.getAddress([isChange, n]);

    // add to scope
    if ($scope.allAddresses.indexOf(walletAddress) == -1) {
        addressArray.push(walletAddress);
        $scope.allAddresses.push(walletAddress);
    }

    // get history for the new address
    bg.initAddress(walletAddress);
    return walletAddress;
  };

  // get a free change address or a new one
  $scope.getChangeAddress = function() {
    for(var idx=0; $scope.allAddresses.length; idx++) {
        if ($scope.allAddresses[idx].nOutputs == 0 && $scope.allAddresses[idx].index[0]%2 == 1) {
            return $scope.allAddresses[idx];
        }
    }
    return $scope.generateAddress(1);
  }

  // Load identity
  if (bg.getKeyRing().availableIdentities.length && !bg.getIdentity()) {
    bg.loadIdentity(0);
  }
}]);
});
<commit_msg>remove unused code.<commit_after>/**
 * @fileOverview Wallet classes.
 */

/**
 * Wallet constructor class.
 * @param {Object} $scope Angular scope.
 * @constructor
 */
define(['./module', 'darkwallet', 'frontend/services', 'util/ng/clipboard', 'util/ng/modals'],
function (controllers, DarkWallet, Services, ClipboardUtils, ModalUtils) {
  'use strict';
  controllers.controller('WalletCtrl',
  ['$scope', '$location' ,'ngProgress', 'toaster', '$modal', '$timeout',
   function($scope, $location, ngProgress, toaster, $modal, $timeout) {
  var pubKey, mpKey, addressIndex;

  // Pointer to service
  var bg = DarkWallet.service();
  $scope.rates = {};

  // Global scope utils
  ModalUtils.registerScope($scope, $modal);
  ClipboardUtils.registerScope($scope);

  // Gui service, connect to report events on page.
  Services.connect('gui', function(data) {
    console.log('[WalletCtrl] gui bus:', data.type);
    if (data.type == 'balance') {
    }
    if (data.type == 'height') {
        $scope.currentHeight = data.value;
    }
    if (data.type == 'text' || data.type == 'note') {
        toaster.pop('note', 'gui', data.text);
    }
    if (data.type == 'error') {
        toaster.pop('error', data.title || 'gui', data.text);
    }
    if (data.type == 'warning') {
        toaster.pop('warning', 'gui', data.text);
    }
    if (['height', 'update', 'balance'].indexOf(data.type) > -1) {
        if (!$scope.$$phase) {
            $scope.$apply();
        }
    }
  })

  // Obelisk service, connect to get notified on events and connection.
  Services.connect('obelisk', function(data) {
    console.log("[WalletCtrl] obelisk bus:", data.type);
    if (data.type == 'connected') {
        ngProgress.color('green');
        ngProgress.complete();
    }
  })

  // Wallet service, connect to get notified about identity getting loaded.
  Services.connect('wallet', function(data) {
    console.log("[WalletCtrl] wallet bus:", data.type);
    if (data.type == 'ready') {
        // identity is ready here
        loadIdentity(DarkWallet.getIdentity())
    }
    if (data.type == 'ticker') {
        $scope.rates[data.currency] = data['24h_avg'];
    }
  })

  // Check if a route is active
  $scope.isActive = function(route) {
    return route === $location.path();
  }

  // Initialize if empty wallet
  function initializeEmpty() {
      if (Object.keys($scope.addresses).length == 0) {
          // generate 5 addresses for now
          for(var idx=0; idx<5; idx++) {
              $scope.generateAddress(0);
              $scope.generateAddress(1);
          }
      }
  }

  function loadAddresses(identity) {
      /* Load addresses into angular */
      Object.keys(identity.wallet.pubKeys).forEach(function(pubKeyIndex) {
          var walletAddress = identity.wallet.getAddress(pubKeyIndex);
          // Init pockets
          for(var idx=0; idx<identity.wallet.pockets.length; idx++) {
              $scope.initPocket(idx);
          };
          // Regular addresses
          if (walletAddress.index.length > 1) {
              // add to scope
              var pocketIndex = walletAddress.index[0];
              if (!$scope.addresses[pocketIndex]) {
                  $scope.addresses[pocketIndex] = [];
              }
              var addressArray = $scope.addresses[pocketIndex];
              if ($scope.allAddresses.indexOf(walletAddress) == -1) {
                  addressArray.push(walletAddress);
                  $scope.allAddresses.push(walletAddress);
              }
          }
      });
  }

  function loadIdentity(identity) {
      $scope.addresses = {};
      $scope.allAddresses = [];
      // set some links
      $scope.identity = identity;
      $scope.availableIdentities = bg.getKeyRing().availableIdentities;
      $scope.history = identity.history.history;
      // set history update callback
      $scope.totalBalance = identity.wallet.getBalance();
      $scope.selectedCurrency = identity.settings.currency;
      $scope.selectedFiat = identity.settings.fiatCurrency;
      $scope.defaultFee = identity.wallet.fee / 100000000;

      // load addresses into angular
      loadAddresses(identity);

      // initialize if empty wallet
      initializeEmpty();

      // this will connect to obelisk if we're not yet connected
      ngProgress.color('firebrick');
      ngProgress.start();
      if (bg.getClient() && bg.getClient().connected) {
          // If already connected set the progress bar to finish
          // we wait a moment to provide better visual feedback
          $timeout(function() {
              ngProgress.color('green');
              ngProgress.complete();
          }, 500);
      } else {
          // Request connecting to blockchain
          bg.connect();
      }
      console.log("[WalletCtrl] loadIdentity", identity.name);
      // apply scope changes
      if(!$scope.$$phase) {
          $scope.$apply();
      }
  };

  // Initialize pocket structures.
  $scope.initPocket = function(rowIndex) {
      var pocketIndex = rowIndex*2;
      if (!$scope.addresses[pocketIndex]) {
          $scope.addresses[pocketIndex] = [];
      }
      if (!$scope.addresses[pocketIndex+1]) {
          $scope.addresses[pocketIndex+1] = [];
      }
  }

  // scope function to generate (or load from cache) a new address
  $scope.generateAddress = function(isChange, n) {
    if (!isChange) {
        isChange = 0;
    }
    if (!$scope.addresses[isChange]) {
        $scope.addresses[isChange] = [];
    }
    var addressArray = $scope.addresses[isChange];
    if (n === undefined || n === null) {
        n = addressArray.length;
    }
    var walletAddress = $scope.identity.wallet.getAddress([isChange, n]);

    // add to scope
    if ($scope.allAddresses.indexOf(walletAddress) == -1) {
        addressArray.push(walletAddress);
        $scope.allAddresses.push(walletAddress);
    }

    // get history for the new address
    bg.initAddress(walletAddress);
    return walletAddress;
  };

  // get a free change address or a new one
  $scope.getChangeAddress = function() {
    for(var idx=0; $scope.allAddresses.length; idx++) {
        if ($scope.allAddresses[idx].nOutputs == 0 && $scope.allAddresses[idx].index[0]%2 == 1) {
            return $scope.allAddresses[idx];
        }
    }
    return $scope.generateAddress(1);
  }

  // Load identity
  if (bg.getKeyRing().availableIdentities.length && !bg.getIdentity()) {
    bg.loadIdentity(0);
  }
}]);
});
<|endoftext|>


<commit_before>/*
 * grunt-unretina
 * https://github.com/ramonfritsch/grunt-unretina
 *
 * Copyright (c) 2014 Ramon Fritsch
 * Licensed under the MIT license.
 */

'use strict';

var async = require("async");
var gm = require("gm");
var mkdirp = require("mkdirp");
var path = require("path");
var os = require("os");

module.exports = function(grunt) {

  grunt.registerMultiTask('unretina', 'Resizes @2x images to standard resolution', function() {
    var done = this.async();
    var options = this.options({
      overwrite: true,
      quality: 1,
      concurrency: os.cpus().length,
      sufixes: [
        "@2x",
        "-hd"
      ]
    });

    var series = [];

    this.files.forEach(function (f) {
      // Fail for more than one source file per file group.
      if (f.src.length !== 1)
      {
        return grunt.fail.fatal("Can not resize more than one image per destination.\n You need to use a different 'files' format in your Gruntfile.");
      }

      var dirname = path.dirname(f.dest);
      var filepath = f.src[0];
      var name = path.basename(f.dest);

      options.sufixes.forEach(function (sufix) {
        name = name.split(sufix).join("");
      });

      var dest = path.join(dirname, name);

      // Prevent failing if destination directory does not exist.
      if (!grunt.file.isDir(dirname))
      {
        grunt.file.mkdir(dirname);
      }

      if (options.overwrite === false && grunt.file.isFile(f.dest))
      {
        return grunt.log.writeln("Skipping " + filepath + " because destination already exists.\n Set options 'overwrite' to true to enable overwriting of files.");
      }

      series.push(function(callback) {
        gm(filepath)
        .size(function(err, size) {
          if (err) {
            grunt.fatal("Failed to query image dimensions of '" + filepath + "'.\n " + err);
            callback(err);

          } else {
            if (size.width % 2 !== 0 || size.height % 2 !== 0)
            {
              grunt.log.warn("Image " + filepath + " has dimentions not divisible by 2");
              return callback();
            }

            gm(filepath)
            .quality(options.quality * 100)
            .resize("50%", "50%", "!")
            .write(dest, function (err) {
              if (err)
              {
                grunt.fail.warn(err.message);
              }
              else
              {
                grunt.log.ok("Image " + filepath + " resized to " + dest);
              }

              return callback();
            });
          }
        });
      });
    });

    async.parallelLimit(series, options.concurrency, done);
  });
};
<commit_msg>Fixed spelling, fixed overwrite-bug<commit_after>/*
 * grunt-unretina
 * https://github.com/ramonfritsch/grunt-unretina
 *
 * Copyright (c) 2014 Ramon Fritsch
 * Licensed under the MIT license.
 */

'use strict';

var async = require("async");
var gm = require("gm");
var mkdirp = require("mkdirp");
var path = require("path");
var os = require("os");

module.exports = function(grunt) {

  grunt.registerMultiTask('unretina', 'Resizes @2x images to standard resolution', function() {
    var done = this.async();
    var options = this.options({
      overwrite: true,
      quality: 1,
      concurrency: os.cpus().length,
      suffixes: [
        "@2x",
        "-hd"
      ]
    });

    var series = [];

    this.files.forEach(function (f) {
      // Fail for more than one source file per file group.
      if (f.src.length !== 1)
      {
        return grunt.fail.fatal("Can not resize more than one image per destination.\nYou need to use a different 'files' format in your Gruntfile.");
      }

      var dirname = path.dirname(f.dest);
      var filepath = f.src[0];
      var name = path.basename(f.dest);

      options.suffixes.forEach(function (suffix) {
        name = name.split(suffix).join("");
      });

      var dest = path.join(dirname, name);

      // Prevent failing if destination directory does not exist.
      if (!grunt.file.isDir(dirname))
      {
        grunt.file.mkdir(dirname);
      }

      if (options.overwrite === false && grunt.file.isFile(dest))
      {
        return grunt.log.writeln("Skipping " + filepath + " because destination already exists.\nSet options 'overwrite' to true to enable overwriting of files.");
      }

      series.push(function(callback) {
        gm(filepath)
        .size(function(err, size) {
          if (err) {
            grunt.fatal("Failed to query image dimensions of '" + filepath + "'.\n" + err);
            callback(err);

          } else {
            if (!!((size.width|size.height)&1))
            {
              grunt.log.warn("Image " + filepath + " has dimensions not divisible by 2");
              return callback();
            }

            gm(filepath)
            .quality(options.quality * 100)
            .resize("50%", "50%", "!")
            .write(dest, function (err) {
              if (err)
              {
                grunt.fail.warn(err.message);
              }
              else
              {
                grunt.log.ok("Image " + filepath + " resized to " + dest);
              }

              return callback();
            });
          }
        });
      });
    });

    async.parallelLimit(series, options.concurrency, done);
  });
};
<|endoftext|>


<commit_before>/*
Copyright 2016 OpenMarket Ltd

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 React from 'react';
import classNames from 'classnames';

import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';

const AUTH_CACHE_AGE = 5 * 60 * 1000; // 5 minutes

export default class DevicesPanel extends React.Component {
    constructor(props, context) {
        super(props, context);

        this.state = {
            devices: undefined,
            deviceLoadError: undefined,

            selectedDevices: [],
            deleting: false,
        };

        this._unmounted = false;

        this._renderDevice = this._renderDevice.bind(this);
        this._onDeviceSelectionToggled = this._onDeviceSelectionToggled.bind(this);
        this._onDeleteClick = this._onDeleteClick.bind(this);
    }

    componentDidMount() {
        this._loadDevices();
    }

    componentWillUnmount() {
        this._unmounted = true;
    }

    _loadDevices() {
        MatrixClientPeg.get().getDevices().done(
            (resp) => {
                if (this._unmounted) { return; }
                this.setState({devices: resp.devices || []});
            },
            (error) => {
                if (this._unmounted) { return; }
                let errtxt;
                if (error.httpStatus == 404) {
                    // 404 probably means the HS doesn't yet support the API.
                    errtxt = _t("Your home server does not support device management.");
                } else {
                    console.error("Error loading devices:", error);
                    errtxt = _t("Unable to load device list");
                }
                this.setState({deviceLoadError: errtxt});
            },
        );
    }

    /**
     * compare two devices, sorting from most-recently-seen to least-recently-seen
     * (and then, for stability, by device id)
     */
    _deviceCompare(a, b) {
        // return < 0 if a comes before b, > 0 if a comes after b.
        const lastSeenDelta =
              (b.last_seen_ts || 0) - (a.last_seen_ts || 0);

        if (lastSeenDelta !== 0) { return lastSeenDelta; }

        const idA = a.device_id;
        const idB = b.device_id;
        return (idA < idB) ? -1 : (idA > idB) ? 1 : 0;
    }

    _onDeviceSelectionToggled(device) {
        if (this._unmounted) { return; }

        const deviceId = device.device_id;
        this.setState((state, props) => {
            // Make a copy of the selected devices, then add or remove the device
            const selectedDevices = state.selectedDevices.slice();

            const i = selectedDevices.indexOf(deviceId);
            if (i === -1) {
                selectedDevices.push(deviceId);
            } else {
                selectedDevices.splice(i, 1);
            }

            return {selectedDevices};
        });
    }

    _onDeleteClick() {
        if (this.context.authCache.lastUpdate < Date.now() - AUTH_CACHE_AGE) {
            this.context.authCache.auth = null;
        }

        this.setState({
            deleting: true,
        });

        // try with auth cache (which is null, so no interactive auth, to start off)
        this._makeDeleteRequest(this.context.authCache.auth).catch((error) => {
            if (this._unmounted) { return; }
            if (error.httpStatus !== 401 || !error.data || !error.data.flows) {
                // doesn't look like an interactive-auth failure
                throw error;
            }

            // pop up an interactive auth dialog
            const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");

            Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, {
                title: _t("Authentication"),
                matrixClient: MatrixClientPeg.get(),
                authData: error.data,
                makeRequest: this._makeDeleteRequest.bind(this),
            });
        }).catch((e) => {
            console.error("Error deleting devices", e);
            if (this._unmounted) { return; }
        }).finally(() => {
            this.setState({
                deleting: false,
            });
        });
    }

    _makeDeleteRequest(auth) {
        this.context.authCache.auth = auth;
        this.context.authCache.lastUpdate = Date.now();
        return MatrixClientPeg.get().deleteMultipleDevices(this.state.selectedDevices, auth).then(
            () => {
                // Remove the deleted devices from `devices`, reset selection to []
                this.setState({
                    devices: this.state.devices.filter(
                        (d) => !this.state.selectedDevices.includes(d.device_id)
                    ),
                    selectedDevices: [],
                });
            },
        );
    }

    _renderDevice(device) {
        const DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry');
        return <DevicesPanelEntry
            key={device.device_id}
            device={device}
            selected={this.state.selectedDevices.includes(device.device_id)}
            onDeviceToggled={this._onDeviceSelectionToggled}
        />;
    }

    render() {
        const Spinner = sdk.getComponent("elements.Spinner");

        if (this.state.deviceLoadError !== undefined) {
            const classes = classNames(this.props.className, "error");
            return (

                    { this.state.deviceLoadError }

            );
        }

        const devices = this.state.devices;
        if (devices === undefined) {
            // still loading
            const classes = this.props.className;
            return <Spinner className={classes} />;
        }

        devices.sort(this._deviceCompare);

        const deleteButton = this.state.deleting ?
            <Spinner w={22} h={22} /> :

               { _t("Delete %(count)s devices", {count: this.state.selectedDevices.length}) }

;

        const classes = classNames(this.props.className, "mx_DevicesPanel");
        return (

{ _t("Device ID") }

{ _t("Device Name") }

{ _t("Last seen") }

                        { this.state.selectedDevices.length > 0 ? deleteButton : _t('Select devices') }

                { devices.map(this._renderDevice) }

        );
    }
}

DevicesPanel.displayName = 'MemberDeviceInfo';
DevicesPanel.propTypes = {
    className: React.PropTypes.string,
};
DevicesPanel.contextTypes = {
    authCache: React.PropTypes.object,
};
<commit_msg>Remove evil auth cache<commit_after>/*
Copyright 2016 OpenMarket Ltd

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 React from 'react';
import classNames from 'classnames';

import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';

export default class DevicesPanel extends React.Component {
    constructor(props, context) {
        super(props, context);

        this.state = {
            devices: undefined,
            deviceLoadError: undefined,

            selectedDevices: [],
            deleting: false,
        };

        this._unmounted = false;

        this._renderDevice = this._renderDevice.bind(this);
        this._onDeviceSelectionToggled = this._onDeviceSelectionToggled.bind(this);
        this._onDeleteClick = this._onDeleteClick.bind(this);
    }

    componentDidMount() {
        this._loadDevices();
    }

    componentWillUnmount() {
        this._unmounted = true;
    }

    _loadDevices() {
        MatrixClientPeg.get().getDevices().done(
            (resp) => {
                if (this._unmounted) { return; }
                this.setState({devices: resp.devices || []});
            },
            (error) => {
                if (this._unmounted) { return; }
                let errtxt;
                if (error.httpStatus == 404) {
                    // 404 probably means the HS doesn't yet support the API.
                    errtxt = _t("Your home server does not support device management.");
                } else {
                    console.error("Error loading devices:", error);
                    errtxt = _t("Unable to load device list");
                }
                this.setState({deviceLoadError: errtxt});
            },
        );
    }

    /**
     * compare two devices, sorting from most-recently-seen to least-recently-seen
     * (and then, for stability, by device id)
     */
    _deviceCompare(a, b) {
        // return < 0 if a comes before b, > 0 if a comes after b.
        const lastSeenDelta =
              (b.last_seen_ts || 0) - (a.last_seen_ts || 0);

        if (lastSeenDelta !== 0) { return lastSeenDelta; }

        const idA = a.device_id;
        const idB = b.device_id;
        return (idA < idB) ? -1 : (idA > idB) ? 1 : 0;
    }

    _onDeviceSelectionToggled(device) {
        if (this._unmounted) { return; }

        const deviceId = device.device_id;
        this.setState((state, props) => {
            // Make a copy of the selected devices, then add or remove the device
            const selectedDevices = state.selectedDevices.slice();

            const i = selectedDevices.indexOf(deviceId);
            if (i === -1) {
                selectedDevices.push(deviceId);
            } else {
                selectedDevices.splice(i, 1);
            }

            return {selectedDevices};
        });
    }

    _onDeleteClick() {
        this.setState({
            deleting: true,
        });

        this._makeDeleteRequest(null).catch((error) => {
            if (this._unmounted) { return; }
            if (error.httpStatus !== 401 || !error.data || !error.data.flows) {
                // doesn't look like an interactive-auth failure
                throw error;
            }

            // pop up an interactive auth dialog
            const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");

            Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, {
                title: _t("Authentication"),
                matrixClient: MatrixClientPeg.get(),
                authData: error.data,
                makeRequest: this._makeDeleteRequest.bind(this),
            });
        }).catch((e) => {
            console.error("Error deleting devices", e);
            if (this._unmounted) { return; }
        }).finally(() => {
            this.setState({
                deleting: false,
            });
        });
    }

    _makeDeleteRequest(auth) {
        return MatrixClientPeg.get().deleteMultipleDevices(this.state.selectedDevices, auth).then(
            () => {
                // Remove the deleted devices from `devices`, reset selection to []
                this.setState({
                    devices: this.state.devices.filter(
                        (d) => !this.state.selectedDevices.includes(d.device_id),
                    ),
                    selectedDevices: [],
                });
            },
        );
    }

    _renderDevice(device) {
        const DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry');
        return <DevicesPanelEntry
            key={device.device_id}
            device={device}
            selected={this.state.selectedDevices.includes(device.device_id)}
            onDeviceToggled={this._onDeviceSelectionToggled}
        />;
    }

    render() {
        const Spinner = sdk.getComponent("elements.Spinner");

        if (this.state.deviceLoadError !== undefined) {
            const classes = classNames(this.props.className, "error");
            return (

                    { this.state.deviceLoadError }

            );
        }

        const devices = this.state.devices;
        if (devices === undefined) {
            // still loading
            const classes = this.props.className;
            return <Spinner className={classes} />;
        }

        devices.sort(this._deviceCompare);

        const deleteButton = this.state.deleting ?
            <Spinner w={22} h={22} /> :

               { _t("Delete %(count)s devices", {count: this.state.selectedDevices.length}) }

;

        const classes = classNames(this.props.className, "mx_DevicesPanel");
        return (

{ _t("Device ID") }

{ _t("Device Name") }

{ _t("Last seen") }

                        { this.state.selectedDevices.length > 0 ? deleteButton : _t('Select devices') }

                { devices.map(this._renderDevice) }

        );
    }
}

DevicesPanel.displayName = 'MemberDeviceInfo';
DevicesPanel.propTypes = {
    className: React.PropTypes.string,
};
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();

router.get('/', function (req, res, next) {
    res.render('studies', {
        title: 'Studies'
    });
});

router.get('/:study/studyindex', function (req, res, next) {
    res.render('studyindex', {
        title: 'Index',
        study: req.params.study
    });
});

router.get('/:study', function (req, res, next) {
    res.render('study', {
        title: 'Study',
        study: req.params.study
    });
});

router.get('/:study/variables', function (req, res, next) {
    res.render('variables', {
        title: 'Variables',
        study: req.params.study
    });
});

router.get('/:study/responses', function (req, res, next) {
    res.render('responses', {
        title: 'Responses',
        study: req.params.study
    });
});

router.get('/:study/specification', function (req, res, next) {
    res.render('specification', {
        title: 'Specification',
        study: req.params.study
    });
});

router.get('/:study/evaluate', function (req, res, next) {
    res.render('evaluate', {
        title: 'Evaluate',
        study: req.params.study
    });
});

router.get('/:study/summary', function (req, res, next) {
    res.render('summary', {
        title: 'Summary',
        study: req.params.study
    });
});

module.exports = router;
<commit_msg>Cookie redirect router<commit_after>var express = require('express');
var router = express.Router();

var cookieTest = function (req, res, next) {
    console.log("Cookies: ", req.cookies);
    if (Object.keys(req.cookies).indexOf('username') == -1) {
        res.redirect('/');
    } else if (req.cookies.username.length == 0) {
        res.clearCookie('username');
        res.redirect('/');
    } else {
        next();
    }
}

router.get('/*', cookieTest);

router.get('/', function (req, res, next) {
    res.render('studies', {
        title: 'Studies'
    });
});

router.get('/:study/studyindex', function (req, res, next) {
    res.render('studyindex', {
        title: 'Index',
        study: req.params.study
    });
});

router.get('/:study', function (req, res, next) {
    res.render('study', {
        title: 'Study',
        study: req.params.study
    });
});

router.get('/:study/variables', function (req, res, next) {
    res.render('variables', {
        title: 'Variables',
        study: req.params.study
    });
});

router.get('/:study/responses', function (req, res, next) {
    res.render('responses', {
        title: 'Responses',
        study: req.params.study
    });
});

router.get('/:study/specification', function (req, res, next) {
    res.render('specification', {
        title: 'Specification',
        study: req.params.study
    });
});

router.get('/:study/evaluate', function (req, res, next) {
    res.render('evaluate', {
        title: 'Evaluate',
        study: req.params.study
    });
});

router.get('/:study/summary', function (req, res, next) {
    res.render('summary', {
        title: 'Summary',
        study: req.params.study
    });
});

module.exports = router;
<|endoftext|>


<commit_before>/**
 * @fileOverview WalletCtrl angular controller
 */
'use strict';

define(['./module', 'darkwallet', 'frontend/port'],
function (controllers, DarkWallet, Port) {
  'use strict';

  /**
   * Controller
   */
  controllers.controller('WalletCtrl',
  ['$scope', '$location', 'clipboard', 'modals', '$wallet', '$animate', '$tabs',
      function($scope, $location, clipboard, modals, $wallet, $animate, $tabs) {

  // Scope variables
  $scope.rates = {};
  $scope.totalBalance = 0;
  $scope.forms = {readOnlyArray: []};
  $scope.identityName = false;

  $scope.allReadOnly = {};

  // Global scope utils
  $scope.modals = modals;
  $scope.clipboard = clipboard;

  $scope.openWallet = $tabs.openWallet;

  /**
   * Wallet Port
   * Sends notifications about wallet state and identity change
   */
  Port.connectNg('wallet', $scope, function(data) {
      if (data.type == 'ready') {
          var updated = identityLoaded(DarkWallet.getIdentity());
          if(updated && !$scope.$$phase) {
              $scope.$apply();
          }
      }
      else if (data.type == 'rename') {
          $scope.identityName = data.newName;
          $scope.availableIdentities = DarkWallet.getKeyRing().availableIdentities;
      }
      else if (data.type == 'height') {
          $scope.currentHeight = data.value;
      }
      else if (data.type == 'ticker') {
          $scope.rates[data.currency] = data.rate;
      }
      else if (data.type == 'address') {
          var identity = DarkWallet.getIdentity();
          var walletAddress = identity.wallet.getAddress(data.index);
          if (walletAddress) {
              $wallet.addToScope(walletAddress);
          }
      }
  });

  /**
   * Check if a route is active
   */
  $scope.isActive = function(route) {
    return route === $location.path().slice(0, route.length);
  };

  $scope.updateReadOnly = function(identity) {
      $scope.allReadOnly = identity.wallet.pockets.pockets.readonly;
      this.updateReadOnlyPockets(identity);
  };

  $scope.updateReadOnlyPockets = function(identity) {
      $scope.forms.readOnlyArray.splice(0, $scope.forms.readOnlyArray.length);
      var keys = Object.keys($scope.allReadOnly);
      keys.forEach(function(pocketId) {
          $scope.forms.readOnlyArray.push($scope.allReadOnly[pocketId]);
      });
  };

  /**
   * Link given identity to the scope
   */
  function linkIdentity(identity) {
      // Link pockets and funds
      $scope.hdPockets = identity.wallet.pockets.hdPockets;
      $scope.allFunds = identity.wallet.multisig.funds;

      // Sync read only pockets
      $scope.updateReadOnly(identity);

      // set some links
      $scope.availableIdentities = DarkWallet.getKeyRing().availableIdentities;
      $scope.settings = identity.settings;

      // get the balance for the wallet
      var balance = identity.wallet.getBalance();

      $scope.totalBalance = balance.confirmed;
      $scope.totalUnconfirmed = balance.unconfirmed;

      $scope.identityName = identity.name;

      $animate.enabled(identity.settings.animations.enabled);
  }

  $scope.$watch('allReadOnly', function() {
      var identity = DarkWallet.getIdentity();
      if (identity) {
          // not ready yet
          $scope.updateReadOnlyPockets(identity);
      }

  });

  /**
   * Identity loaded, called when a new identity is loaded
   */
  function identityLoaded(identity) {
      if (!identity || $scope.identityName == identity.name) {
          return false;
      }

      if (identity.reseed) {
          $scope.alert = 'reseed';
      }
      $scope.status = DarkWallet.core.servicesStatus;

      // Inform the wallet service
      $wallet.onIdentityLoaded(identity);

      // Link some variables from the identity to scope
      linkIdentity(identity);

      // this will connect to obelisk if we're not yet connected
      if (DarkWallet.getClient() && DarkWallet.getClient().connected) {
          // Already connected, set height
          $scope.currentHeight = DarkWallet.service.wallet.currentHeight;
      } else {
          // Request connecting to blockchain
          setTimeout(function() {
            DarkWallet.core.connect();
          });
      }
      console.log("[WalletCtrl] loadIdentity", identity.name);
      // apply scope changes
      return true;
  }

  /**
   * Click on the title either toggles pocket list or goes to wallet
   */
  $scope.titleClick = function() {
      var currentPath = $location.path();
      if (currentPath.substr(0, 7) === '/wallet') {
          var element = angular.element(document.querySelector('.off-canvas-wrap'));
          element.toggleClass('move-right');
      } else {
          $location.path("/wallet");
      }
  }

  /**
   * Utility function to create iterators
   */
  $scope.range = function(n) {
      if (!n) return [];
      return new Array(n);
  };

  // Load identity
  // If the identity is not immediately loaded then the callback will
  // not be called  and we get it on the Port.
  $wallet.loadIdentity(identityLoaded);

}]);
});
<commit_msg>Set pinned instead of move-right on the sidebar when clicking on the title.<commit_after>/**
 * @fileOverview WalletCtrl angular controller
 */
'use strict';

define(['./module', 'darkwallet', 'frontend/port'],
function (controllers, DarkWallet, Port) {
  'use strict';

  /**
   * Controller
   */
  controllers.controller('WalletCtrl',
  ['$scope', '$location', 'clipboard', 'modals', '$wallet', '$animate', '$tabs',
      function($scope, $location, clipboard, modals, $wallet, $animate, $tabs) {

  // Scope variables
  $scope.rates = {};
  $scope.totalBalance = 0;
  $scope.forms = {readOnlyArray: []};
  $scope.identityName = false;

  $scope.allReadOnly = {};

  // Global scope utils
  $scope.modals = modals;
  $scope.clipboard = clipboard;

  $scope.openWallet = $tabs.openWallet;

  /**
   * Wallet Port
   * Sends notifications about wallet state and identity change
   */
  Port.connectNg('wallet', $scope, function(data) {
      if (data.type == 'ready') {
          var updated = identityLoaded(DarkWallet.getIdentity());
          if(updated && !$scope.$$phase) {
              $scope.$apply();
          }
      }
      else if (data.type == 'rename') {
          $scope.identityName = data.newName;
          $scope.availableIdentities = DarkWallet.getKeyRing().availableIdentities;
      }
      else if (data.type == 'height') {
          $scope.currentHeight = data.value;
      }
      else if (data.type == 'ticker') {
          $scope.rates[data.currency] = data.rate;
      }
      else if (data.type == 'address') {
          var identity = DarkWallet.getIdentity();
          var walletAddress = identity.wallet.getAddress(data.index);
          if (walletAddress) {
              $wallet.addToScope(walletAddress);
          }
      }
  });

  /**
   * Check if a route is active
   */
  $scope.isActive = function(route) {
    return route === $location.path().slice(0, route.length);
  };

  $scope.updateReadOnly = function(identity) {
      $scope.allReadOnly = identity.wallet.pockets.pockets.readonly;
      this.updateReadOnlyPockets(identity);
  };

  $scope.updateReadOnlyPockets = function(identity) {
      $scope.forms.readOnlyArray.splice(0, $scope.forms.readOnlyArray.length);
      var keys = Object.keys($scope.allReadOnly);
      keys.forEach(function(pocketId) {
          $scope.forms.readOnlyArray.push($scope.allReadOnly[pocketId]);
      });
  };

  /**
   * Link given identity to the scope
   */
  function linkIdentity(identity) {
      // Link pockets and funds
      $scope.hdPockets = identity.wallet.pockets.hdPockets;
      $scope.allFunds = identity.wallet.multisig.funds;

      // Sync read only pockets
      $scope.updateReadOnly(identity);

      // set some links
      $scope.availableIdentities = DarkWallet.getKeyRing().availableIdentities;
      $scope.settings = identity.settings;

      // get the balance for the wallet
      var balance = identity.wallet.getBalance();

      $scope.totalBalance = balance.confirmed;
      $scope.totalUnconfirmed = balance.unconfirmed;

      $scope.identityName = identity.name;

      $animate.enabled(identity.settings.animations.enabled);
  }

  $scope.$watch('allReadOnly', function() {
      var identity = DarkWallet.getIdentity();
      if (identity) {
          // not ready yet
          $scope.updateReadOnlyPockets(identity);
      }

  });

  /**
   * Identity loaded, called when a new identity is loaded
   */
  function identityLoaded(identity) {
      if (!identity || $scope.identityName == identity.name) {
          return false;
      }

      if (identity.reseed) {
          $scope.alert = 'reseed';
      }
      $scope.status = DarkWallet.core.servicesStatus;

      // Inform the wallet service
      $wallet.onIdentityLoaded(identity);

      // Link some variables from the identity to scope
      linkIdentity(identity);

      // this will connect to obelisk if we're not yet connected
      if (DarkWallet.getClient() && DarkWallet.getClient().connected) {
          // Already connected, set height
          $scope.currentHeight = DarkWallet.service.wallet.currentHeight;
      } else {
          // Request connecting to blockchain
          setTimeout(function() {
            DarkWallet.core.connect();
          });
      }
      console.log("[WalletCtrl] loadIdentity", identity.name);
      // apply scope changes
      return true;
  }

  /**
   * Click on the title either toggles pocket list or goes to wallet
   */
  $scope.titleClick = function() {
      var currentPath = $location.path();
      if (currentPath.substr(0, 7) === '/wallet') {
          var element = angular.element(document.querySelector('.inner-wrap'));
          element.toggleClass('pinned');
          //var element = angular.element(document.querySelector('.off-canvas-wrap'));
          //element.toggleClass('move-right');
      } else {
          $location.path("/wallet");
      }
  }

  /**
   * Utility function to create iterators
   */
  $scope.range = function(n) {
      if (!n) return [];
      return new Array(n);
  };

  // Load identity
  // If the identity is not immediately loaded then the callback will
  // not be called  and we get it on the Port.
  $wallet.loadIdentity(identityLoaded);

}]);
});
<|endoftext|>


<commit_before>import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Editor } from 'react-draft-wysiwyg';
import cx from 'classnames';
import {
  IconTextBoldMediumOutline,
  IconTextItalicMediumOutline,
  IconListNumeredMediumOutline,
  IconListMediumOutline,
  IconLinkMediumOutline,
} from '@teamleader/ui-icons';

import Box, { pickBoxProps } from '../box';
import ValidationText from '../validationText';

import InlineStylingOptions from './InlineStylingOptions';
import ListStylingOptions from './ListStylingOptions';
import LinkOptions from './LinkOptions';
import { linkDecorator } from './decorators';
import theme from './theme.css';
import translations from './translations';

const toolbar = {
  options: ['inline', 'list', 'link'],
  inline: {
    options: ['bold', 'italic'],
    bold: { icon: <IconTextBoldMediumOutline /> },
    italic: { icon: <IconTextItalicMediumOutline /> },
    component: InlineStylingOptions,
  },
  list: {
    component: ListStylingOptions,
    options: ['unordered', 'ordered'],
    unordered: { icon: <IconListMediumOutline /> },
    ordered: { icon: <IconListNumeredMediumOutline /> },
  },
  link: {
    component: LinkOptions,
    defaultTargetOption: '_self',
    showOpenOptionOnHover: true,
    options: ['link'],
    link: { icon: <IconLinkMediumOutline /> },
  },
};

const customStyleMap = {
  BOLD: {
    fontFamily: 'Inter-Bold',
  },
};

const availableLocales = ['en', 'es', 'it', 'nl', 'fr', 'de'];

const WysiwygEditor = ({
  className,
  error,
  onFocus,
  onBlur,
  success,
  warning,
  helpText,
  width,
  locale = 'en',
  inputClassName,
  ...others
}) => {
  const [isFocused, setIsFocused] = useState(false);
  const [isPlaceholderShown, setIsPlaceholderShown] = useState(true);

  const handleBlur = (event) => {
    setIsFocused(false);
    if (onBlur) {
      onBlur(event);
    }
  };

  const handleFocus = (event) => {
    setIsFocused(true);
    if (onFocus) {
      onFocus(event);
    }
  };

  const handleContentStateChange = ({ blocks: [{ type }] }) => {
    if (type === 'unstyled') {
      setIsPlaceholderShown(true);
      return;
    }
    setIsPlaceholderShown(false);
  };

  const wrapperClassNames = cx(
    theme['wrapper'],
    {
      [theme['has-focus']]: isFocused,
      [theme['has-error']]: error,
      [theme['has-success']]: success,
      [theme['has-warning']]: warning,
      [theme['has-placeholder']]: isPlaceholderShown,
    },
    className,
  );

  const inputClassNames = cx(theme['input'], inputClassName);

  const boxProps = pickBoxProps(others);

  return (
    <Box style={{ width, flex: width && '0 0 auto' }} {...boxProps}>
      <Editor
        toolbar={toolbar}
        wrapperClassName={wrapperClassNames}
        editorClassName={inputClassNames}
        toolbarClassName={theme['toolbar']}
        onFocus={handleFocus}
        onBlur={handleBlur}
        onContentStateChange={handleContentStateChange}
        customStyleMap={customStyleMap}
        customDecorators={[linkDecorator]}
        localization={
          availableLocales.includes(locale)
            ? {
                locale,
                translations: translations[locale],
              }
            : {
                locale: 'en',
                translations: translations['en'],
              }
        }
        {...others}
      />
      <ValidationText error={error} help={helpText} success={success} warning={warning} />
    </Box>
  );
};

WysiwygEditor.propTypes = {
  /** The text string/element to use as error message below the input. */
  error: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
  /** The text string to use as help text below the input. */
  helpText: PropTypes.string,
  /** The text string/element to use as a prefix inside the input field */
  success: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
  /** The text string/element to use as a suffix inside the input field */
  warning: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
  /** A custom width for the editor field */
  width: PropTypes.string,
  /** Locale key for the language you want the editor to be displayed in. */
  locale: PropTypes.string,
  /** Classname for the WysiwygEditor's input element */
  inputClassName: PropTypes.string,
};

export default WysiwygEditor;
<commit_msg>✨ Add `autoFocus` prop for `WysiwygEditor`<commit_after>import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Editor } from 'react-draft-wysiwyg';
import cx from 'classnames';
import {
  IconTextBoldMediumOutline,
  IconTextItalicMediumOutline,
  IconListNumeredMediumOutline,
  IconListMediumOutline,
  IconLinkMediumOutline,
} from '@teamleader/ui-icons';

import Box, { pickBoxProps } from '../box';
import ValidationText from '../validationText';

import InlineStylingOptions from './InlineStylingOptions';
import ListStylingOptions from './ListStylingOptions';
import LinkOptions from './LinkOptions';
import { linkDecorator } from './decorators';
import theme from './theme.css';
import translations from './translations';

const toolbar = {
  options: ['inline', 'list', 'link'],
  inline: {
    options: ['bold', 'italic'],
    bold: { icon: <IconTextBoldMediumOutline /> },
    italic: { icon: <IconTextItalicMediumOutline /> },
    component: InlineStylingOptions,
  },
  list: {
    component: ListStylingOptions,
    options: ['unordered', 'ordered'],
    unordered: { icon: <IconListMediumOutline /> },
    ordered: { icon: <IconListNumeredMediumOutline /> },
  },
  link: {
    component: LinkOptions,
    defaultTargetOption: '_self',
    showOpenOptionOnHover: true,
    options: ['link'],
    link: { icon: <IconLinkMediumOutline /> },
  },
};

const customStyleMap = {
  BOLD: {
    fontFamily: 'Inter-Bold',
  },
};

const availableLocales = ['en', 'es', 'it', 'nl', 'fr', 'de'];

const WysiwygEditor = ({
  className,
  error,
  onFocus,
  onBlur,
  success,
  warning,
  helpText,
  width,
  locale = 'en',
  inputClassName,
  autoFocus,
  ...others
}) => {
  const [isFocused, setIsFocused] = useState(false);
  const [isPlaceholderShown, setIsPlaceholderShown] = useState(true);
  const editorRef = useRef();

  useEffect(() => {
    if (autoFocus) {
      // eslint-disable-next-line no-unused-expressions
      editorRef?.current?.focusEditor();
    }
  }, [autoFocus]);

  const handleBlur = (event) => {
    setIsFocused(false);
    if (onBlur) {
      onBlur(event);
    }
  };

  const handleFocus = (event) => {
    setIsFocused(true);
    if (onFocus) {
      onFocus(event);
    }
  };

  const handleContentStateChange = ({ blocks: [{ type }] }) => {
    if (type === 'unstyled') {
      setIsPlaceholderShown(true);
      return;
    }
    setIsPlaceholderShown(false);
  };

  const wrapperClassNames = cx(
    theme['wrapper'],
    {
      [theme['has-focus']]: isFocused,
      [theme['has-error']]: error,
      [theme['has-success']]: success,
      [theme['has-warning']]: warning,
      [theme['has-placeholder']]: isPlaceholderShown,
    },
    className,
  );

  const inputClassNames = cx(theme['input'], inputClassName);

  const boxProps = pickBoxProps(others);

  return (
    <Box style={{ width, flex: width && '0 0 auto' }} {...boxProps}>
      <Editor
        toolbar={toolbar}
        wrapperClassName={wrapperClassNames}
        editorClassName={inputClassNames}
        toolbarClassName={theme['toolbar']}
        onFocus={handleFocus}
        onBlur={handleBlur}
        onContentStateChange={handleContentStateChange}
        customStyleMap={customStyleMap}
        customDecorators={[linkDecorator]}
        localization={
          availableLocales.includes(locale)
            ? {
                locale,
                translations: translations[locale],
              }
            : {
                locale: 'en',
                translations: translations['en'],
              }
        }
        ref={editorRef}
        {...others}
      />
      <ValidationText error={error} help={helpText} success={success} warning={warning} />
    </Box>
  );
};

WysiwygEditor.propTypes = {
  /** The text string/element to use as error message below the input. */
  error: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
  /** The text string to use as help text below the input. */
  helpText: PropTypes.string,
  /** The text string/element to use as a prefix inside the input field */
  success: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
  /** The text string/element to use as a suffix inside the input field */
  warning: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
  /** A custom width for the editor field */
  width: PropTypes.string,
  /** Locale key for the language you want the editor to be displayed in. */
  locale: PropTypes.string,
  /** Classname for the WysiwygEditor's input element */
  inputClassName: PropTypes.string,
  /** Determines if the editor should be autofocussed on render */
  autoFocus: PropTypes.bool,
};

export default WysiwygEditor;
<|endoftext|>


<commit_before>/*
-----------------------------------------------------------------------------
Copyright (c) 2014-2015 Seth Anderson

This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the
use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be appreciated
but is not required.

2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.
-----------------------------------------------------------------------------
*/

var passport = require("passport"),
	mongoose = require("mongoose"),
	q = require("q"),
	User = require("../model/user.js"),
	Token = require("../model/token.js"),
	authorize = require("../authorization.js").authorize,
	authorizeSessionUser = require("../authorization.js").authorizeSessionUser,
	register = require("../utils/common.js").register,
	login = require("../utils/common.js").login,
	authenticate = require("../utils/common.js").authenticate,
	verifyRecaptcha = require("../utils/common.js").verifyRecaptcha,
	removeDuplicates = require("../utils/common.js").removeDuplicates,
	sanitizeBodyForDB = require("../utils/common.js").sanitizeBodyForDB,
	smtp = require("../utils/smtp.js"),
	config = require("../utils/common.js").config;

module.exports = function(app, prefix){
	app.post(prefix + "/register",
		verifyRecaptcha,
		register,
	function(req, res){
		if(req.isAuthenticated()){
			smtp.sendEmailVerification(app, req.user, req.protocol + '://' + config.domain)
			.then(function(){
				res.status(201).send(req.user._id.toString());
			}).catch(function(err){
				res.status(500).end();
			});
		}else{
			res.status(403).end();
		}
	});

	app.post(prefix + "/login",
		login,
	function(req, res){
		if(req.isAuthenticated()){
			res.status(200).end();
		}else{
			res.status(403).end();
		}
	});

	app.post(prefix + "/logout", function(req, res){
		req.logout();
		res.status(200).end();
	});

	app.get(prefix + "/isloggedin", function(req, res){
		res.send({isLoggedIn: req.isAuthenticated()});
	});

	app.post(prefix + "/resetpassword", function(req, res){
		User.findOne({email: req.body.email}, function(err, doc){
			if(err){
				res.status(400).end();
			}else if(!doc){
				res.status(404).end();
			}else{
				smtp.sendPasswordReset(app, doc, req.protocol + '://' + config.domain)
				.then(function(){
					res.status(200).end();
				}).catch(function(err){
					res.status(400).end();
				});
			}
		});
	});

	app.post(prefix + "/:user/verify",
		authenticate,
		authorizeSessionUser(),
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		User.findById(req.params.user, function(err, doc){
			if(err){
				res.status(500).end();
			}else if(!doc){
				res.status(404).end();
			}else{
				smtp.sendEmailVerification(app, doc, req.protocol + '://' + config.domain)
				.then(function(){
					res.status(200).end();
				}).catch(function(err){
					res.status(400).end();
				});
			}
		});
	});

	app.post(prefix + "/:user/verify/:token", function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		var deferredUser = q.defer(), deferredToken = q.defer(),
			verified = false;
		User.findById(req.params.user, function(err, doc){
			if(err){
				deferredUser.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredUser.reject({reason: "not-found", message: "User not found"});
			}else{
				verified = doc.verified;
				deferredUser.resolve(doc);
			}
		});
		Token.findOne({token: req.params.token}, function(err, doc){
			if(err){
				deferredToken.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredToken.reject({reason: "not-found", message: "Token not found"});
			}else{
				deferredToken.resolve(doc);
			}
		});
		q.all([deferredUser.promise, deferredToken.promise])
		.spread(function(user, token){
			if(verified){
				res.status(200).end();
				return;
			}
			if(token.validate("verify" + user.email)){
				user.verified = true;
				user.modified = Date.now();
				user.save(function(err){
					if(err){
						console.error("Error: Successfully verified token for user \"" + user.email + "\", but failed to update flag");
						res.status(500).end();
					}else{
						user.syncWithDirectory()
						.then(function(){
							res.status(200).end();
						}).catch(function(err){
							console.error("Error: Successfully verified token for user \"" + user.email + "\", but failed to sync with directory");
							res.status(500).end();
						});
					}
				});
			}else{
				res.status(400).end();
			}
		}).catch(function(err){
			if(verified){
				res.status(200).end();
				return;
			}
			if(err.reason === "db-error"){
				res.status(500).end();
			}else if(err.reason === "not-found"){
				res.status(404).end();
			}else{
				res.status(400).end();
			}
		});
	});

	app.get(prefix + "/:user/verified",
		authenticate,
		authorizeSessionUser(),
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}
		// Retrieve and return the "verified" value
		User.findById(req.params.user, function(err, doc){
			if(doc){
				res.status(200).send({verified: doc.verified});
			}else{
				res.status(404).end();
			}
		});
	});

	app.get(prefix,
		authenticate,
		authorize({hasRoles: ["admin"]}),
	function(req, res){
		User.find({}, function(err, docs){
			if(err){
				res.status(500).end();
			}else{
				res.send(docs || []);
			}
		});
	});

	app.get(prefix + "/:user",
		authenticate,
		authorizeSessionUser(),
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}
		User.findById(req.params.user, function(err, doc){
			if(err){
				res.status(500).end();
			}else if(!doc){
				res.status(404).end();
			}else{
				var responseData = {
					email: doc.email,
					verified: doc.verified,
					created: doc.created,
					modified: doc.modified,
					accessed: doc.accessed,
					vip: doc.vip,
					lanInviteDesired: doc.lanInviteDesired,
					firstName: doc.firstName,
					lastName: doc.lastName,
					primaryHandle: doc.primaryHandle,
					tertiaryHandles: doc.tertiaryHandles,
					roles: doc.roles,
					services: doc.services
				};

				if(req.user.hasRole("admin")){
					responseData.__v = doc.__v;
					responseData._id = doc._id;
					responseData.blacklisted = doc.blacklisted;
				}

				res.send(responseData);
			}
		});
	});

	app.post(prefix,
		authenticate,
		authorize({hasRoles: ["admin"]}),
		sanitizeBodyForDB,
	function(req, res){
		if(req.body.roles){
			req.body.roles = removeDuplicates(req.body.roles);
		}
		User.createNew(req.body)
		.then(function(newUser){
			res.status(201)
			.location(prefix + "/" + newUser._id)
			.send({_id: newUser._id});
		}).catch(function(err){
			res.status(400).end();
		});
	});

	app.put(prefix + "/:user",
		authenticate,
		authorizeSessionUser(),
		sanitizeBodyForDB,
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		var deferredExistingUser = q.defer(), deferredEditUser = q.defer();
		User.findOne({email: req.body.email}, function(err, doc){
			if(err){
				deferredExistingUser.reject({reason: "db-error", message: err});
			}else{
				deferredExistingUser.resolve(doc);
			}
		});
		User.findById(req.params.user, function(err, doc){
			if(err){
				deferredEditUser.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredEditUser.reject({reason: "not-found", message: "User not found"});
			}else{
				deferredEditUser.resolve(doc);
			}
		});
		q.all([deferredExistingUser.promise, deferredEditUser.promise])
		.spread(function(existingUser, editUser){
			if(existingUser && existingUser._id.toString() !== editUser._id.toString()){
				return q.reject({reason: "address-in-use", message: "User already using that email address"});
			}
			if(req.body.email !== editUser.email){
				editUser.verified = false;
			}
			editUser.email = req.body.email;
			editUser.firstName = req.body.firstName;
			editUser.lastName = req.body.lastName;
			editUser.lanInviteDesired = req.body.lanInviteDesired;
			editUser.primaryHandle = req.body.primaryHandle;
			editUser.tertiaryHandles = removeDuplicates(req.body.tertiaryHandles);
			editUser.modified = Date.now();

			var onSave = function(err){
				if(err){
					res.status(500).end();
				}else{
					editUser.syncWithDirectory()
					.then(function(){
						res.status(200).end();
					}).catch(function(){
						res.status(500).end();
					});
				}
			};

			req.user.hasRole("admin")
			.then(function(){
				editUser.verified = req.body.verified;
				editUser.vip = req.body.vip;
				editUser.blacklisted = req.body.blacklisted;
				editUser.roles = removeDuplicates(req.body.roles);
				editUser.services = req.body.services;
				editUser.save(onSave);
			}).catch(function(){
				editUser.save(onSave);
			});
		}).catch(function(err){
			if(err.reason === "db-error"){
				res.status(500).end();
			}else if(err.reason === "address-in-use"){
				res.status(409).end();
			}else if(err.reason === "not-found"){
				res.status(404).end();
			}else{
				res.status(400).end();
			}
		});
	});

	app.put(prefix + "/:user/password",
		authenticate,
		authorizeSessionUser(),
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		// Update the user
		User.findById(req.params.user, function(err, doc){
			if(err){
				res.status(400).end();
			}else if(!doc){
				res.status(404).end();
			}else{
				doc.changePassword(req.body.oldPassword, req.body.newPassword)
				.then(function(){
					res.status(200).end();
				}).catch(function(){
					res.status(400).end();
				});
			}
		});
	});

	app.post(prefix + "/:user/password/reset/:token", function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		var deferredUser = q.defer(), deferredToken = q.defer(),
			verified = false;
		User.findById(req.params.user, function(err, doc){
			if(err){
				deferredUser.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredUser.reject({reason: "not-found", message: "User not found"});
			}else{
				verified = doc.verified;
				deferredUser.resolve(doc);
			}
		});
		Token.findOne({token: req.params.token}, function(err, doc){
			if(err){
				deferredToken.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredToken.reject({reason: "not-found", message: "Token not found"});
			}else{
				deferredToken.resolve(doc);
			}
		});
		q.all([deferredUser.promise, deferredToken.promise])
		.spread(function(user, token){
			if(token.validate("passwordreset" + user.email)){
				user.resetPassword(req.body.newPassword)
				.then(function(){
					res.status(200).end();
				}).catch(function(err){
					console.error("Error: Successfully verified token for user \"" + user.email + "\", but failed to reset password");
					res.status(500).end();
				});
			}else{
				res.status(400).end();
			}
		}).catch(function(err){
			if(err.reason === "db-error"){
				res.status(500).end();
			}else if(err.reason === "not-found"){
				res.status(404).end();
			}else{
				res.status(400).end();
			}
		});
	});
}
<commit_msg>Email verification will now remove any recipients with the same email as that user<commit_after>/*
-----------------------------------------------------------------------------
Copyright (c) 2014-2015 Seth Anderson

This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the
use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be appreciated
but is not required.

2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.
-----------------------------------------------------------------------------
*/

var passport = require("passport"),
	mongoose = require("mongoose"),
	q = require("q"),
	User = require("../model/user.js"),
	Token = require("../model/token.js"),
	Recipient = require("../model/recipient.js"),
	authorize = require("../authorization.js").authorize,
	authorizeSessionUser = require("../authorization.js").authorizeSessionUser,
	register = require("../utils/common.js").register,
	login = require("../utils/common.js").login,
	authenticate = require("../utils/common.js").authenticate,
	verifyRecaptcha = require("../utils/common.js").verifyRecaptcha,
	removeDuplicates = require("../utils/common.js").removeDuplicates,
	sanitizeBodyForDB = require("../utils/common.js").sanitizeBodyForDB,
	smtp = require("../utils/smtp.js"),
	config = require("../utils/common.js").config;

module.exports = function(app, prefix){
	app.post(prefix + "/register",
		verifyRecaptcha,
		register,
	function(req, res){
		if(req.isAuthenticated()){
			smtp.sendEmailVerification(app, req.user, req.protocol + '://' + config.domain)
			.then(function(){
				res.status(201).send(req.user._id.toString());
			}).catch(function(err){
				res.status(500).end();
			});
		}else{
			res.status(403).end();
		}
	});

	app.post(prefix + "/login",
		login,
	function(req, res){
		if(req.isAuthenticated()){
			res.status(200).end();
		}else{
			res.status(403).end();
		}
	});

	app.post(prefix + "/logout", function(req, res){
		req.logout();
		res.status(200).end();
	});

	app.get(prefix + "/isloggedin", function(req, res){
		res.send({isLoggedIn: req.isAuthenticated()});
	});

	app.post(prefix + "/resetpassword", function(req, res){
		User.findOne({email: req.body.email}, function(err, doc){
			if(err){
				res.status(400).end();
			}else if(!doc){
				res.status(404).end();
			}else{
				smtp.sendPasswordReset(app, doc, req.protocol + '://' + config.domain)
				.then(function(){
					res.status(200).end();
				}).catch(function(err){
					res.status(400).end();
				});
			}
		});
	});

	app.post(prefix + "/:user/verify",
		authenticate,
		authorizeSessionUser(),
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		User.findById(req.params.user, function(err, doc){
			if(err){
				res.status(500).end();
			}else if(!doc){
				res.status(404).end();
			}else{
				smtp.sendEmailVerification(app, doc, req.protocol + '://' + config.domain)
				.then(function(){
					res.status(200).end();
				}).catch(function(err){
					res.status(400).end();
				});
			}
		});
	});

	app.post(prefix + "/:user/verify/:token", function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		var deferredUser = q.defer(), deferredToken = q.defer(),
			verified = false;
		User.findById(req.params.user, function(err, doc){
			if(err){
				deferredUser.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredUser.reject({reason: "not-found", message: "User not found"});
			}else{
				verified = doc.verified;
				deferredUser.resolve(doc);
			}
		});
		Token.findOne({token: req.params.token}, function(err, doc){
			if(err){
				deferredToken.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredToken.reject({reason: "not-found", message: "Token not found"});
			}else{
				deferredToken.resolve(doc);
			}
		});
		q.all([deferredUser.promise, deferredToken.promise])
		.spread(function(user, token){
			if(verified){
				res.status(200).end();
				return;
			}
			if(token.validate("verify" + user.email)){
				user.verified = true;
				user.modified = Date.now();
				user.save(function(err){
					if(err){
						console.error("Error: Successfully verified token for user \"" + user.email + "\", but failed to update flag");
						res.status(500).end();
					}else{
						Recipient.remove({email: user.email}, function(err, doc){});
						user.syncWithDirectory()
						.then(function(){
							res.status(200).end();
						}).catch(function(err){
							console.error("Error: Successfully verified token for user \"" + user.email + "\", but failed to sync with directory");
							res.status(500).end();
						});
					}
				});
			}else{
				res.status(400).end();
			}
		}).catch(function(err){
			if(verified){
				res.status(200).end();
				return;
			}
			if(err.reason === "db-error"){
				res.status(500).end();
			}else if(err.reason === "not-found"){
				res.status(404).end();
			}else{
				res.status(400).end();
			}
		});
	});

	app.get(prefix + "/:user/verified",
		authenticate,
		authorizeSessionUser(),
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}
		// Retrieve and return the "verified" value
		User.findById(req.params.user, function(err, doc){
			if(doc){
				res.status(200).send({verified: doc.verified});
			}else{
				res.status(404).end();
			}
		});
	});

	app.get(prefix,
		authenticate,
		authorize({hasRoles: ["admin"]}),
	function(req, res){
		User.find({}, function(err, docs){
			if(err){
				res.status(500).end();
			}else{
				res.send(docs || []);
			}
		});
	});

	app.get(prefix + "/:user",
		authenticate,
		authorizeSessionUser(),
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}
		User.findById(req.params.user, function(err, doc){
			if(err){
				res.status(500).end();
			}else if(!doc){
				res.status(404).end();
			}else{
				var responseData = {
					email: doc.email,
					verified: doc.verified,
					created: doc.created,
					modified: doc.modified,
					accessed: doc.accessed,
					vip: doc.vip,
					lanInviteDesired: doc.lanInviteDesired,
					firstName: doc.firstName,
					lastName: doc.lastName,
					primaryHandle: doc.primaryHandle,
					tertiaryHandles: doc.tertiaryHandles,
					roles: doc.roles,
					services: doc.services
				};

				if(req.user.hasRole("admin")){
					responseData.__v = doc.__v;
					responseData._id = doc._id;
					responseData.blacklisted = doc.blacklisted;
				}

				res.send(responseData);
			}
		});
	});

	app.post(prefix,
		authenticate,
		authorize({hasRoles: ["admin"]}),
		sanitizeBodyForDB,
	function(req, res){
		if(req.body.roles){
			req.body.roles = removeDuplicates(req.body.roles);
		}
		User.createNew(req.body)
		.then(function(newUser){
			res.status(201)
			.location(prefix + "/" + newUser._id)
			.send({_id: newUser._id});
		}).catch(function(err){
			res.status(400).end();
		});
	});

	app.put(prefix + "/:user",
		authenticate,
		authorizeSessionUser(),
		sanitizeBodyForDB,
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		var deferredExistingUser = q.defer(), deferredEditUser = q.defer();
		User.findOne({email: req.body.email}, function(err, doc){
			if(err){
				deferredExistingUser.reject({reason: "db-error", message: err});
			}else{
				deferredExistingUser.resolve(doc);
			}
		});
		User.findById(req.params.user, function(err, doc){
			if(err){
				deferredEditUser.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredEditUser.reject({reason: "not-found", message: "User not found"});
			}else{
				deferredEditUser.resolve(doc);
			}
		});
		q.all([deferredExistingUser.promise, deferredEditUser.promise])
		.spread(function(existingUser, editUser){
			if(existingUser && existingUser._id.toString() !== editUser._id.toString()){
				return q.reject({reason: "address-in-use", message: "User already using that email address"});
			}
			if(req.body.email !== editUser.email){
				editUser.verified = false;
			}
			editUser.email = req.body.email;
			editUser.firstName = req.body.firstName;
			editUser.lastName = req.body.lastName;
			editUser.lanInviteDesired = req.body.lanInviteDesired;
			editUser.primaryHandle = req.body.primaryHandle;
			editUser.tertiaryHandles = removeDuplicates(req.body.tertiaryHandles);
			editUser.modified = Date.now();

			var onSave = function(err){
				if(err){
					res.status(500).end();
				}else{
					editUser.syncWithDirectory()
					.then(function(){
						res.status(200).end();
					}).catch(function(){
						res.status(500).end();
					});
				}
			};

			req.user.hasRole("admin")
			.then(function(){
				editUser.verified = req.body.verified;
				editUser.vip = req.body.vip;
				editUser.blacklisted = req.body.blacklisted;
				editUser.roles = removeDuplicates(req.body.roles);
				editUser.services = req.body.services;
				editUser.save(onSave);
			}).catch(function(){
				editUser.save(onSave);
			});
		}).catch(function(err){
			if(err.reason === "db-error"){
				res.status(500).end();
			}else if(err.reason === "address-in-use"){
				res.status(409).end();
			}else if(err.reason === "not-found"){
				res.status(404).end();
			}else{
				res.status(400).end();
			}
		});
	});

	app.put(prefix + "/:user/password",
		authenticate,
		authorizeSessionUser(),
	function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		// Update the user
		User.findById(req.params.user, function(err, doc){
			if(err){
				res.status(400).end();
			}else if(!doc){
				res.status(404).end();
			}else{
				doc.changePassword(req.body.oldPassword, req.body.newPassword)
				.then(function(){
					res.status(200).end();
				}).catch(function(){
					res.status(400).end();
				});
			}
		});
	});

	app.post(prefix + "/:user/password/reset/:token", function(req, res){
		if(!mongoose.Types.ObjectId.isValid(req.params.user)){
			return res.status(404).end();
		}

		var deferredUser = q.defer(), deferredToken = q.defer(),
			verified = false;
		User.findById(req.params.user, function(err, doc){
			if(err){
				deferredUser.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredUser.reject({reason: "not-found", message: "User not found"});
			}else{
				verified = doc.verified;
				deferredUser.resolve(doc);
			}
		});
		Token.findOne({token: req.params.token}, function(err, doc){
			if(err){
				deferredToken.reject({reason: "db-error", message: err});
			}else if(!doc){
				deferredToken.reject({reason: "not-found", message: "Token not found"});
			}else{
				deferredToken.resolve(doc);
			}
		});
		q.all([deferredUser.promise, deferredToken.promise])
		.spread(function(user, token){
			if(token.validate("passwordreset" + user.email)){
				user.resetPassword(req.body.newPassword)
				.then(function(){
					res.status(200).end();
				}).catch(function(err){
					console.error("Error: Successfully verified token for user \"" + user.email + "\", but failed to reset password");
					res.status(500).end();
				});
			}else{
				res.status(400).end();
			}
		}).catch(function(err){
			if(err.reason === "db-error"){
				res.status(500).end();
			}else if(err.reason === "not-found"){
				res.status(404).end();
			}else{
				res.status(400).end();
			}
		});
	});
}
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Tandem is a general instance registry that can be used to track creation/disposal of instances in PhET Simulations.
 * It is used for phetio.js instrumentation for PhET-iO support.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var tandemNamespace = require( 'TANDEM/tandemNamespace' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );

  // text
  var packageString = require( 'text!REPOSITORY/package.json' );

  // constants
  var packageJSON = JSON.parse( packageString );
  var defaultTandemMap = {}; // {string} => {number} Keep track of number of each type of default tandem

  // variables
  var launched = false;

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @param {Object} [options]
   * @constructor
   */
  function Tandem( id, options ) {

    options = _.extend( {
      static: false,
      isDefaultTandem: false
    }, options );

    // @public (read-only)
    this.id = ( id !== undefined ) ? id : '';

    // @private (read-only)
    this.static = options.static;
    this.isDefaultTandem = options.isDefaultTandem;
  }

  var staticInstances = [];

  tandemNamespace.register( 'Tandem', Tandem );

  // Listeners that will be notified when items are registered/deregistered
  var instanceListeners = [];

  inherit( Object, Tandem, {

    // @public (read-only) permit checking whether something is probably a Tandem instance for error checking
    isTandem: true,

    /**
     * Adds an instance of any type.  For example, it could be an axon Property, scenery Node or Sun button.  Each
     * item should only be added to the registry once, but that is not enforced here in Tandem.  For PhET-iO, phet-io.js
     * enforces one entry per ID in phetio.addInstance
     *
     * This is used to register instances with PhET-iO.
     * @param {Object} instance - the instance to add
     * @param {function} type - the PhET-iO type function, such as TString or TProperty(TNumber('volts'))
     * @public
     */
    addInstance: function( instance, type ) {

      if ( phet.chipper.brand === 'phet-io' ) {

        if ( !type ) {
          console.log( 'Missing type declaration for ' + this.id );
        }

        // ifphetio returns a no-op function, so to test whether a valid T wrapper type was passed, we search for the typeName
        assert && assert( type && type.typeName, 'type must be specified and have a typeName' );

        if ( this.static && !launched ) {
          staticInstances.push( { tandem: this, instance: instance, type: type } );
        }
        else {
          for ( var i = 0; i < instanceListeners.length; i++ ) {
            instanceListeners[ i ].addInstance( this.id, instance, type );
          }
        }
      }
    },

    /**
     * Removes an instance from the
     * @param {Object} instance - the instance to remove
     * @public
     */
    removeInstance: function( instance ) {

      // Only active when running as phet-io
      if ( phet.chipper.brand === 'phet-io' ) {
        for ( var i = 0; i < instanceListeners.length; i++ ) {
          instanceListeners[ i ].removeInstance( this.id, instance );
        }
      }
    },

    /**
     * Create a new Tandem by appending the given id
     * @param {string} id
     * @param {Object} [options]
     * @returns {Tandem}
     * @public
     */
    createTandem: function( id, options ) {
      var string = ( this.id.length > 0 ) ? ( this.id + '.' + id ) : id;

      // Any child of something static is also static
      options = _.extend( { static: this.static }, options );

      return new Tandem( string, options );
    },

    /**
     * Creates a group tandem for creating multiple indexed child tandems, such as:
     * sim.screen.model.electron_0
     * sim.screen.model.electron_1
     *
     * In this case, 'sim.screen.model.electron' is the group tandem id.
     *
     * Used for arrays, observable arrays, or when many elements of the same type are created and they do not otherwise
     * have unique identifiers.
     * @param id
     * @returns {GroupTandem}
     */
    createGroupTandem: function( id ) {

      // Unfortunately we must resort to globals here since loading through the namespace would create a cycle
      return new GroupTandem( this.id + '.' + id );
    },

    /**
     * Get the last part of the tandem (after the last .), used in Joist for creating button names dynamically based
     * on screen names
     * @return {string} the tail of the tandem
     */
    get tail() {
      assert && assert( this.id.indexOf( '.' ) >= 0, 'tandem ID does not have a tail' );

      var lastIndexOfDot = this.id.lastIndexOf( '.' );
      var tail = this.id.substring( lastIndexOfDot + 1 );
      assert && assert( tail.length > 0, 'tandem ID did not have a tail' );
      return tail;
    },

    /**
     * When using subtyping, the instance listeners must only be notified once rather than once for every level
     * in the inheritance hierarchy.  When a subtype constructor has a tandem.addInstance call, it should
     * pass a supertype tandem to the parent constructor so that it won't try to register the item twice.
     * @returns {SupertypeTandem}
     */
    createSupertypeTandem: function() {
      return new SupertypeTandem( this.id );
    }
  }, {

    /**
     * In common code (sun, scenery-phet, etc), default tandems are created in the options hash to simplify
     * the logic (instead of being null).
     * @param {string} name - the name of the tandem
     * @returns {Tandem}
     */
    createDefaultTandem: function( name ) {
      if ( !defaultTandemMap.hasOwnProperty( name ) ) {
        defaultTandemMap[ name ] = 0;
      }

      // Name the tandems as derivedProperty0
      // OR: chargesAndFields.derivedProperty0
      var tandem = rootTandem.createTandem( name + '' + defaultTandemMap[ name ], {
        isDefaultTandem: true // for checking in validateTandems
      } );
      defaultTandemMap[ name ]++;
      return tandem;
    },

    /**
     * Adds a listener that will be notified when items are registered/deregistered
     * Listeners have the form
     * {
     *   addInstance(id,instance),
     *   removeInstance(id,instance)
     * }
     * where id is of type {string} and instance is of type {Object}
     *
     * @param {Object} instanceListener - described above
     * @public
     * @static
     */
    addInstanceListener: function( instanceListener ) {
      instanceListeners.push( instanceListener );
    },

    /**
     * Create a tandem based on the name of the running simulation.
     * @returns {Tandem}
     */
    createRootTandem: function() {
      return new Tandem( StringUtils.toCamelCase( packageJSON.name ) );
    },

    /**
     * Create a child of the root tandem.
     * @param {string} name
     * @returns {Tandem}
     */
    createStaticTandem: function( name ) {
      return Tandem.createRootTandem().createTandem( name, { static: true } );
    },

    /**
     * When the simulation is launched, all static instances are registered.
     */
    launch: function() {
      assert && assert( !launched, 'Tandem was launched twice' );
      launched = true;
      while ( staticInstances.length > 0 ) {
        var staticInstance = staticInstances.shift();
        staticInstance.tandem.addInstance( staticInstance.instance, staticInstance.type );
      }
    },

    /**
     * When running as phet-io, User Interface components must be registered with tandem.  This function checks the
     * options to make sure tandem exists.  See https://github.com/phetsims/phet-io/issues/77
     * @param options
     */
    validateOptions: function( options ) {

      // Check to see whether the tandem is "filled in" as opposed to being a default Tandem.createOptionalTandem one.
      if ( phet.chipper.brand === 'phet-io' && phet.chipper.getQueryParameter( 'phet-io.validateTandems' ) !== 'false' ) {
        assert && assert( !options.tandem.isDefaultTandem, 'Default tandem instance cannot be used when running as PhET-iO' );
      }
    }
  } );

  var rootTandem = Tandem.createRootTandem();

  // Tandem checks for listeners added before the Tandem module was loaded.  This is necessary so that phetio.js can
  // receive notifications about items created during static initialization such as Solute.js
  // which is created before Sim.js runs.
  if ( window.tandemPreloadInstanceListeners ) {
    for ( var i = 0; i < window.tandemPreloadInstanceListeners.length; i++ ) {
      Tandem.addInstanceListener( window.tandemPreloadInstanceListeners[ i ] );
    }
  }

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createGroupTandem
   * Declared in the same file to avoid circular reference errors in module loading.
   */
  function GroupTandem( id ) {

    Tandem.call( this, id );

    // @private for generating indices from a pool
    this.groupElementIndex = 0;
  }

  tandemNamespace.register( 'Tandem.GroupTandem', GroupTandem );

  inherit( Tandem, GroupTandem, {

    /**
     * @param [id] {string} optional override, used when loading a state and the tandems must be restored exactly as
     * they were saved
     * @returns {Tandem}
     */
    createSpecificTandem: function( id ) {
      return new Tandem( this.id + '_' + id );
    },

    createNextTandem: function() {
      return new Tandem( this.id + '_' + ( this.groupElementIndex++ ) );
    }
  } );

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createSupertypeTandem
   */
  function SupertypeTandem( id ) {
    Tandem.call( this, id );
  }

  tandemNamespace.register( 'Tandem.SupertypeTandem', SupertypeTandem );

  inherit( Tandem, SupertypeTandem, {

    // @public - Override to make no-op, see createSupertypeTandem
    addInstance: function( instance, type ) {},

    // @public - Override to make no-op, see createSupertypeTandem
    removeInstance: function( instance ) {}
  } );

  return Tandem;
} );

<commit_msg>Check for the existance of a tandem before asserting it is default<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Tandem is a general instance registry that can be used to track creation/disposal of instances in PhET Simulations.
 * It is used for phetio.js instrumentation for PhET-iO support.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var tandemNamespace = require( 'TANDEM/tandemNamespace' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );

  // text
  var packageString = require( 'text!REPOSITORY/package.json' );

  // constants
  var packageJSON = JSON.parse( packageString );
  var defaultTandemMap = {}; // {string} => {number} Keep track of number of each type of default tandem

  // variables
  var launched = false;

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @param {Object} [options]
   * @constructor
   */
  function Tandem( id, options ) {

    options = _.extend( {
      static: false,
      isDefaultTandem: false
    }, options );

    // @public (read-only)
    this.id = ( id !== undefined ) ? id : '';

    // @private (read-only)
    this.static = options.static;
    this.isDefaultTandem = options.isDefaultTandem;
  }

  var staticInstances = [];

  tandemNamespace.register( 'Tandem', Tandem );

  // Listeners that will be notified when items are registered/deregistered
  var instanceListeners = [];

  inherit( Object, Tandem, {

    // @public (read-only) permit checking whether something is probably a Tandem instance for error checking
    isTandem: true,

    /**
     * Adds an instance of any type.  For example, it could be an axon Property, scenery Node or Sun button.  Each
     * item should only be added to the registry once, but that is not enforced here in Tandem.  For PhET-iO, phet-io.js
     * enforces one entry per ID in phetio.addInstance
     *
     * This is used to register instances with PhET-iO.
     * @param {Object} instance - the instance to add
     * @param {function} type - the PhET-iO type function, such as TString or TProperty(TNumber('volts'))
     * @public
     */
    addInstance: function( instance, type ) {

      if ( phet.chipper.brand === 'phet-io' ) {

        if ( !type ) {
          console.log( 'Missing type declaration for ' + this.id );
        }

        // ifphetio returns a no-op function, so to test whether a valid T wrapper type was passed, we search for the typeName
        assert && assert( type && type.typeName, 'type must be specified and have a typeName' );

        if ( this.static && !launched ) {
          staticInstances.push( { tandem: this, instance: instance, type: type } );
        }
        else {
          for ( var i = 0; i < instanceListeners.length; i++ ) {
            instanceListeners[ i ].addInstance( this.id, instance, type );
          }
        }
      }
    },

    /**
     * Removes an instance from the
     * @param {Object} instance - the instance to remove
     * @public
     */
    removeInstance: function( instance ) {

      // Only active when running as phet-io
      if ( phet.chipper.brand === 'phet-io' ) {
        for ( var i = 0; i < instanceListeners.length; i++ ) {
          instanceListeners[ i ].removeInstance( this.id, instance );
        }
      }
    },

    /**
     * Create a new Tandem by appending the given id
     * @param {string} id
     * @param {Object} [options]
     * @returns {Tandem}
     * @public
     */
    createTandem: function( id, options ) {
      var string = ( this.id.length > 0 ) ? ( this.id + '.' + id ) : id;

      // Any child of something static is also static
      options = _.extend( { static: this.static }, options );

      return new Tandem( string, options );
    },

    /**
     * Creates a group tandem for creating multiple indexed child tandems, such as:
     * sim.screen.model.electron_0
     * sim.screen.model.electron_1
     *
     * In this case, 'sim.screen.model.electron' is the group tandem id.
     *
     * Used for arrays, observable arrays, or when many elements of the same type are created and they do not otherwise
     * have unique identifiers.
     * @param id
     * @returns {GroupTandem}
     */
    createGroupTandem: function( id ) {

      // Unfortunately we must resort to globals here since loading through the namespace would create a cycle
      return new GroupTandem( this.id + '.' + id );
    },

    /**
     * Get the last part of the tandem (after the last .), used in Joist for creating button names dynamically based
     * on screen names
     * @return {string} the tail of the tandem
     */
    get tail() {
      assert && assert( this.id.indexOf( '.' ) >= 0, 'tandem ID does not have a tail' );

      var lastIndexOfDot = this.id.lastIndexOf( '.' );
      var tail = this.id.substring( lastIndexOfDot + 1 );
      assert && assert( tail.length > 0, 'tandem ID did not have a tail' );
      return tail;
    },

    /**
     * When using subtyping, the instance listeners must only be notified once rather than once for every level
     * in the inheritance hierarchy.  When a subtype constructor has a tandem.addInstance call, it should
     * pass a supertype tandem to the parent constructor so that it won't try to register the item twice.
     * @returns {SupertypeTandem}
     */
    createSupertypeTandem: function() {
      return new SupertypeTandem( this.id );
    }
  }, {

    /**
     * In common code (sun, scenery-phet, etc), default tandems are created in the options hash to simplify
     * the logic (instead of being null).
     * @param {string} name - the name of the tandem
     * @returns {Tandem}
     */
    createDefaultTandem: function( name ) {
      if ( !defaultTandemMap.hasOwnProperty( name ) ) {
        defaultTandemMap[ name ] = 0;
      }

      // Name the tandems as derivedProperty0
      // OR: chargesAndFields.derivedProperty0
      var tandem = rootTandem.createTandem( name + '' + defaultTandemMap[ name ], {
        isDefaultTandem: true // for checking in validateTandems
      } );
      defaultTandemMap[ name ]++;
      return tandem;
    },

    /**
     * Adds a listener that will be notified when items are registered/deregistered
     * Listeners have the form
     * {
     *   addInstance(id,instance),
     *   removeInstance(id,instance)
     * }
     * where id is of type {string} and instance is of type {Object}
     *
     * @param {Object} instanceListener - described above
     * @public
     * @static
     */
    addInstanceListener: function( instanceListener ) {
      instanceListeners.push( instanceListener );
    },

    /**
     * Create a tandem based on the name of the running simulation.
     * @returns {Tandem}
     */
    createRootTandem: function() {
      return new Tandem( StringUtils.toCamelCase( packageJSON.name ) );
    },

    /**
     * Create a child of the root tandem.
     * @param {string} name
     * @returns {Tandem}
     */
    createStaticTandem: function( name ) {
      return Tandem.createRootTandem().createTandem( name, { static: true } );
    },

    /**
     * When the simulation is launched, all static instances are registered.
     */
    launch: function() {
      assert && assert( !launched, 'Tandem was launched twice' );
      launched = true;
      while ( staticInstances.length > 0 ) {
        var staticInstance = staticInstances.shift();
        staticInstance.tandem.addInstance( staticInstance.instance, staticInstance.type );
      }
    },

    /**
     * When running as phet-io, User Interface components must be registered with tandem.  This function checks the
     * options to make sure tandem exists.  See https://github.com/phetsims/phet-io/issues/77
     * @param options
     */
    validateOptions: function( options ) {

      // Check to see whether the tandem is "filled in" as opposed to being a default Tandem.createOptionalTandem one.
      if ( phet.chipper.brand === 'phet-io' && phet.chipper.getQueryParameter( 'phet-io.validateTandems' ) !== 'false' ) {
        assert && assert( options.tandem, 'tandem should be defined in common code components' );
        assert && assert( !options.tandem.isDefaultTandem, 'Default tandem instance cannot be used when running as PhET-iO' );
      }
    }
  } );

  var rootTandem = Tandem.createRootTandem();

  // Tandem checks for listeners added before the Tandem module was loaded.  This is necessary so that phetio.js can
  // receive notifications about items created during static initialization such as Solute.js
  // which is created before Sim.js runs.
  if ( window.tandemPreloadInstanceListeners ) {
    for ( var i = 0; i < window.tandemPreloadInstanceListeners.length; i++ ) {
      Tandem.addInstanceListener( window.tandemPreloadInstanceListeners[ i ] );
    }
  }

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createGroupTandem
   * Declared in the same file to avoid circular reference errors in module loading.
   */
  function GroupTandem( id ) {

    Tandem.call( this, id );

    // @private for generating indices from a pool
    this.groupElementIndex = 0;
  }

  tandemNamespace.register( 'Tandem.GroupTandem', GroupTandem );

  inherit( Tandem, GroupTandem, {

    /**
     * @param [id] {string} optional override, used when loading a state and the tandems must be restored exactly as
     * they were saved
     * @returns {Tandem}
     */
    createSpecificTandem: function( id ) {
      return new Tandem( this.id + '_' + id );
    },

    createNextTandem: function() {
      return new Tandem( this.id + '_' + ( this.groupElementIndex++ ) );
    }
  } );

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createSupertypeTandem
   */
  function SupertypeTandem( id ) {
    Tandem.call( this, id );
  }

  tandemNamespace.register( 'Tandem.SupertypeTandem', SupertypeTandem );

  inherit( Tandem, SupertypeTandem, {

    // @public - Override to make no-op, see createSupertypeTandem
    addInstance: function( instance, type ) {},

    // @public - Override to make no-op, see createSupertypeTandem
    removeInstance: function( instance ) {}
  } );

  return Tandem;
} );

<|endoftext|>