<commit_before>'use strict';

var isCopy      = require('es5-ext/lib/Array/prototype/is-copy')
  , d           = require('es5-ext/lib/Object/descriptor')
  , makeEl      = require('dom-ext/lib/Document/prototype/make-element')
  , Db          = require('dbjs')
  , DOMInput    = require('./_controls/input')
  , DOMRadio    = require('./_controls/radio')
  , DOMCheckbox = require('./_controls/checkbox')

  , isArray = Array.isArray
  , BooleanType = Db.Boolean
  , getValue =
	Object.getOwnPropertyDescriptor(DOMCheckbox.prototype, 'value').get
  , getName = Object.getOwnPropertyDescriptor(DOMInput.prototype, 'name').get
  , Radio, Checkbox, arrResult = ['0', '1'].sort(), getLabel;

getLabel = function (name, options, ns) {
	name += 'Label';
	if (options[name]) return options[name];
	options = options.dbOptions;
	if (options[name]) return options['_' + name];
	return ns['_' + name];
};

Radio = function (document, ns/*, options*/) {
	var tOption, fOption, options = Object(arguments[2]), reverse;
	DOMRadio.call(this, document, ns, options);
	tOption = this.createOption('1', getLabel('true', options, ns));
	fOption = this.createOption('0', getLabel('false', options, ns));

	reverse = Number(options.order) < 0;
	this.dom.appendChild(reverse ? fOption : tOption);
	this.dom.appendChild(document.createTextNode(' '));
	this.dom.appendChild(reverse ? tOption : fOption);
};
Radio.prototype = Object.create(DOMRadio.prototype, { constructor: d(Radio) });

Checkbox = function (document, ns/*, options*/) {
	DOMCheckbox.apply(this, arguments);
};
Checkbox.prototype = Object.create(DOMCheckbox.prototype, {
	constructor: d(Checkbox),
	inputValue: d.gs(function () { return this.control.checked ? '1' : '0'; }),
	_render: d(function () {
		var el = makeEl.bind(this.document);
		this.controls = {};
		this.dom = el('span', this.control = el('input',
			{ type: 'checkbox', value: '1' }), this.hidden = el('input',
				{ type: 'hidden', value: '0' }));
	}),
	name: d.gs(getName, function (name) {
		this._name = name;
		name = this.name;
		if (name) {
			this.control.setAttribute('name', name);
			this.hidden.setAttribute('name', name);
		} else {
			this.control.removeAttribute('name');
			this.hidden.removeAttribute('name');
		}
	}),
	value: d.gs(getValue, function (value) {
		var old = this.inputValue, nu = this.ns.toInputValue(value);
		if (nu == null) nu = '0';
		if (nu !== this._value) {
			if (nu !== '1') this.control.removeAttribute('checked');
			else this.control.setAttribute('checked', 'checked');
		}

		if (nu !== old) this.control.checked = (nu === '1');
		this.onChange();
	}),
	setCheckedValue: d(function () { throw new Error("Not supported"); })
});

module.exports = Object.defineProperties(BooleanType, {
	fromInputValue: d(function (value) {
		if (value == null) return undefined;
		if (value === '0') return false;
		if (value === '1') return true;
		if (isArray(value) && isCopy.call(value.sort(), arrResult)) {
			return true;
		}
		return null;
	}),
	toInputValue: d(function (value) {
		if (value == null) return null;
		else return value.valueOf() ? '1' : '0';
	}),
	DOMRadio: d(Radio),
	DOMCheckbox: d(Checkbox),
	toDOMInput: d(function (document/*, options*/) {
		var options = Object(arguments[1]);
		if (options.multiple) {
			return new this.DOMMultipleInput(document, this, options);
		}
		if (options.type === 'checkbox') {
			return new this.DOMCheckbox(document, this, options);
		} else {
			return new this.DOMRadio(document, this, options);
		}
	})
});
<commit_msg>Fix default boolean checkbox value<commit_after>'use strict';

var isCopy      = require('es5-ext/lib/Array/prototype/is-copy')
  , d           = require('es5-ext/lib/Object/descriptor')
  , makeEl      = require('dom-ext/lib/Document/prototype/make-element')
  , Db          = require('dbjs')
  , DOMInput    = require('./_controls/input')
  , DOMRadio    = require('./_controls/radio')
  , DOMCheckbox = require('./_controls/checkbox')

  , isArray = Array.isArray
  , BooleanType = Db.Boolean
  , getValue =
	Object.getOwnPropertyDescriptor(DOMCheckbox.prototype, 'value').get
  , getName = Object.getOwnPropertyDescriptor(DOMInput.prototype, 'name').get
  , Radio, Checkbox, arrResult = ['0', '1'].sort(), getLabel;

getLabel = function (name, options, ns) {
	name += 'Label';
	if (options[name]) return options[name];
	options = options.dbOptions;
	if (options[name]) return options['_' + name];
	return ns['_' + name];
};

Radio = function (document, ns/*, options*/) {
	var tOption, fOption, options = Object(arguments[2]), reverse;
	DOMRadio.call(this, document, ns, options);
	tOption = this.createOption('1', getLabel('true', options, ns));
	fOption = this.createOption('0', getLabel('false', options, ns));

	reverse = Number(options.order) < 0;
	this.dom.appendChild(reverse ? fOption : tOption);
	this.dom.appendChild(document.createTextNode(' '));
	this.dom.appendChild(reverse ? tOption : fOption);
};
Radio.prototype = Object.create(DOMRadio.prototype, { constructor: d(Radio) });

Checkbox = function (document, ns/*, options*/) {
	DOMCheckbox.apply(this, arguments);
};
Checkbox.prototype = Object.create(DOMCheckbox.prototype, {
	constructor: d(Checkbox),
	_value: d('0'),
	inputValue: d.gs(function () { return this.control.checked ? '1' : '0'; }),
	_render: d(function () {
		var el = makeEl.bind(this.document);
		this.controls = {};
		this.dom = el('span', this.control = el('input',
			{ type: 'checkbox', value: '1' }), this.hidden = el('input',
				{ type: 'hidden', value: '0' }));
	}),
	name: d.gs(getName, function (name) {
		this._name = name;
		name = this.name;
		if (name) {
			this.control.setAttribute('name', name);
			this.hidden.setAttribute('name', name);
		} else {
			this.control.removeAttribute('name');
			this.hidden.removeAttribute('name');
		}
	}),
	value: d.gs(getValue, function (value) {
		var old = this.inputValue, nu = this.ns.toInputValue(value);
		if (nu == null) nu = '0';
		if (nu !== this._value) {
			if (nu !== '1') this.control.removeAttribute('checked');
			else this.control.setAttribute('checked', 'checked');
		}

		if (nu !== old) this.control.checked = (nu === '1');
		this.onChange();
	}),
	setCheckedValue: d(function () { throw new Error("Not supported"); })
});

module.exports = Object.defineProperties(BooleanType, {
	fromInputValue: d(function (value) {
		if (value == null) return undefined;
		if (value === '0') return false;
		if (value === '1') return true;
		if (isArray(value) && isCopy.call(value.sort(), arrResult)) {
			return true;
		}
		return null;
	}),
	toInputValue: d(function (value) {
		if (value == null) return null;
		else return value.valueOf() ? '1' : '0';
	}),
	DOMRadio: d(Radio),
	DOMCheckbox: d(Checkbox),
	toDOMInput: d(function (document/*, options*/) {
		var options = Object(arguments[1]);
		if (options.multiple) {
			return new this.DOMMultipleInput(document, this, options);
		}
		if (options.type === 'checkbox') {
			return new this.DOMCheckbox(document, this, options);
		} else {
			return new this.DOMRadio(document, this, options);
		}
	})
});
<|endoftext|>


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

/**
 * Button for a single screen in the navigation bar, shows the text and the navigation bar icon.
 *
 * @author Sam Reid
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonListener = require( 'SUN/buttons/ButtonListener' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var HighlightNode = require( 'JOIST/HighlightNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var PushButtonModel = require( 'SUN/buttons/PushButtonModel' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // constants
  var HIGHLIGHT_SPACING = 4;

  /**
   * Create a nav bar.  Layout assumes all of the screen widths are the same.
   * @param {Property.<string>} navigationBarFillProperty - the color of the navbar, as a string.
   * @param {Property.<number>} screenIndexProperty
   * @param {Array.<Screen>} screens - all of the available sim content screens (excluding the home screen)
   * @param {Screen} screen
   * @param {number} navBarHeight
   * @param {Object} [options]
   * @constructor
   */
  function NavigationBarScreenButton( navigationBarFillProperty, screenIndexProperty, screens, screen, navBarHeight, options ) {

    options = _.extend( {
      cursor: 'pointer',
      focusable: true,
      textDescription: screen.name + ' Screen: Button',
      tandem: null,
      maxButtonWidth: null // {number|null} the maximum width of the button, causes text and/or icon to be scaled down if necessary
    }, options );

    Node.call( this );

    // icon,
    var icon = new Node( {
      children: [ screen.navigationBarIcon ], // wrap in case this icon is used in multiple place (eg, home screen and navbar)
      maxHeight: 0.625 * navBarHeight
    } );

    // Is this button's screen selected?
    var selectedProperty = new DerivedProperty( [ screenIndexProperty ], function( screenIndex ) {
      return screenIndex === screens.indexOf( screen );
    } );

    // create the button model, needs to be public so that together wrappers can hook up to it if needed
    this.buttonModel = new PushButtonModel( {
      listener: function() {
        screenIndexProperty.value = screens.indexOf( screen );
      }
    } );
    this.addInputListener( new ButtonListener( this.buttonModel ) );

    options.tandem && options.tandem.addInstance( this );

    var text = new Text( screen.name, {
      font: new PhetFont( 10 )
    } );

    var box = new VBox( {
      children: [ icon, text ],
      pickable: false,
      spacing: Math.max( 0, 12 - text.height ), // see https://github.com/phetsims/joist/issues/143
      usesOpacity: true // hint, since we change its opacity
    } );

    // add a transparent overlay for input handling and to size touchArea/mouseArea
    var overlay = new Rectangle( 0, 0, box.width, box.height, { center: box.center } );

    // highlights
    var highlightWidth = overlay.width + ( 2 * HIGHLIGHT_SPACING );
    var brightenHighlight = new HighlightNode( highlightWidth, overlay.height, {
      center: box.center,
      fill: 'white'
    } );
    var darkenHighlight = new HighlightNode( highlightWidth, overlay.height, {
      center: box.center,
      fill: 'black'
    } );

    this.addChild( box );
    this.addChild( overlay );
    this.addChild( brightenHighlight );
    this.addChild( darkenHighlight );

    // manage interaction feedback
    Property.multilink(
      [ selectedProperty, this.buttonModel.downProperty, this.buttonModel.overProperty, navigationBarFillProperty ],
      function update( selected, down, over, navigationBarFill ) {

        var useDarkenHighlights = ( navigationBarFill !== 'black' );

        // Color match yellow with the PhET Logo
        var selectedTextColor = useDarkenHighlights ? 'black' : '#f2e916';
        var unselectedTextColor = useDarkenHighlights ? 'gray' : 'white';

        text.fill = selected ? selectedTextColor : unselectedTextColor;
        box.opacity = selected ? 1.0 : ( down ? 0.65 : 0.5 );
        brightenHighlight.visible = !useDarkenHighlights && ( over || down );
        darkenHighlight.visible = useDarkenHighlights && ( over || down );
      } );

    // Constrain text and icon width, if necessary
    if ( options.maxButtonWidth && ( this.width > options.maxButtonWidth ) ) {

      text.maxWidth = icon.maxWidth = options.maxButtonWidth - ( this.width - box.width );

      // adjust the overlay
      overlay.setRect( 0, 0, box.width, overlay.height );
      overlay.center = box.center;

      // adjust the highlights
      brightenHighlight.spacing = darkenHighlight.spacing = overlay.width + ( 2 * HIGHLIGHT_SPACING );
      brightenHighlight.center = darkenHighlight.center = box.center;

      assert && assert( Util.toFixed( this.width, 0 ) === Util.toFixed( options.maxButtonWidth, 0 ) );
    }

    this.mutate( _.omit( options, 'tandem' ) );
  }

  return inherit( Node, NavigationBarScreenButton );
} );
<commit_msg>#241 constrain box (icon + text) to height of navbar<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Button for a single screen in the navigation bar, shows the text and the navigation bar icon.
 *
 * @author Sam Reid
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonListener = require( 'SUN/buttons/ButtonListener' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var HighlightNode = require( 'JOIST/HighlightNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var PushButtonModel = require( 'SUN/buttons/PushButtonModel' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // constants
  var HIGHLIGHT_SPACING = 4;

  /**
   * Create a nav bar.  Layout assumes all of the screen widths are the same.
   * @param {Property.<string>} navigationBarFillProperty - the color of the navbar, as a string.
   * @param {Property.<number>} screenIndexProperty
   * @param {Array.<Screen>} screens - all of the available sim content screens (excluding the home screen)
   * @param {Screen} screen
   * @param {number} navBarHeight
   * @param {Object} [options]
   * @constructor
   */
  function NavigationBarScreenButton( navigationBarFillProperty, screenIndexProperty, screens, screen, navBarHeight, options ) {

    options = _.extend( {
      cursor: 'pointer',
      focusable: true,
      textDescription: screen.name + ' Screen: Button',
      tandem: null,
      maxButtonWidth: null // {number|null} the maximum width of the button, causes text and/or icon to be scaled down if necessary
    }, options );

    Node.call( this );

    // icon,
    var icon = new Node( {
      children: [ screen.navigationBarIcon ], // wrap in case this icon is used in multiple place (eg, home screen and navbar)
      maxHeight: 0.625 * navBarHeight
    } );

    // Is this button's screen selected?
    var selectedProperty = new DerivedProperty( [ screenIndexProperty ], function( screenIndex ) {
      return screenIndex === screens.indexOf( screen );
    } );

    // create the button model, needs to be public so that together wrappers can hook up to it if needed
    this.buttonModel = new PushButtonModel( {
      listener: function() {
        screenIndexProperty.value = screens.indexOf( screen );
      }
    } );
    this.addInputListener( new ButtonListener( this.buttonModel ) );

    options.tandem && options.tandem.addInstance( this );

    var text = new Text( screen.name, {
      font: new PhetFont( 10 )
    } );

    var box = new VBox( {
      children: [ icon, text ],
      pickable: false,
      spacing: Math.max( 0, 12 - text.height ), // see https://github.com/phetsims/joist/issues/143
      usesOpacity: true, // hint, since we change its opacity
      maxHeight: navBarHeight
    } );

    // add a transparent overlay for input handling and to size touchArea/mouseArea
    var overlay = new Rectangle( 0, 0, box.width, box.height, { center: box.center } );

    // highlights
    var highlightWidth = overlay.width + ( 2 * HIGHLIGHT_SPACING );
    var brightenHighlight = new HighlightNode( highlightWidth, overlay.height, {
      center: box.center,
      fill: 'white'
    } );
    var darkenHighlight = new HighlightNode( highlightWidth, overlay.height, {
      center: box.center,
      fill: 'black'
    } );

    this.addChild( box );
    this.addChild( overlay );
    this.addChild( brightenHighlight );
    this.addChild( darkenHighlight );

    // manage interaction feedback
    Property.multilink(
      [ selectedProperty, this.buttonModel.downProperty, this.buttonModel.overProperty, navigationBarFillProperty ],
      function update( selected, down, over, navigationBarFill ) {

        var useDarkenHighlights = ( navigationBarFill !== 'black' );

        // Color match yellow with the PhET Logo
        var selectedTextColor = useDarkenHighlights ? 'black' : '#f2e916';
        var unselectedTextColor = useDarkenHighlights ? 'gray' : 'white';

        text.fill = selected ? selectedTextColor : unselectedTextColor;
        box.opacity = selected ? 1.0 : ( down ? 0.65 : 0.5 );
        brightenHighlight.visible = !useDarkenHighlights && ( over || down );
        darkenHighlight.visible = useDarkenHighlights && ( over || down );
      } );

    // Constrain text and icon width, if necessary
    if ( options.maxButtonWidth && ( this.width > options.maxButtonWidth ) ) {

      text.maxWidth = icon.maxWidth = options.maxButtonWidth - ( this.width - box.width );

      // adjust the overlay
      overlay.setRect( 0, 0, box.width, overlay.height );
      overlay.center = box.center;

      // adjust the highlights
      brightenHighlight.spacing = darkenHighlight.spacing = overlay.width + ( 2 * HIGHLIGHT_SPACING );
      brightenHighlight.center = darkenHighlight.center = box.center;

      assert && assert( Util.toFixed( this.width, 0 ) === Util.toFixed( options.maxButtonWidth, 0 ) );
    }

    this.mutate( _.omit( options, 'tandem' ) );
  }

  return inherit( Node, NavigationBarScreenButton );
} );
<|endoftext|>


<commit_before>"use strict";

const deps = require("./dev/deps.json");
const fs = require("fs");
const prompt = require("prompt");
const sh = require("shellcmd");
const vendorCss = deps.style;
const vendorJs = deps.script;

// Message
console.log("\nChoose extra packages to install:\n");

// Start the prompt
prompt.start();

const schema = {
	properties: require("vendor.js")
};

prompt.get(schema, (err, input) => {
	if (input["Angular Strap"].match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Angular Strap...");

		sh("mkdir app/extension/ui/");
		sh("cp -a node_modules/rectangular/install/extra/extension/ui/bootstrap/. app/extension/ui/");
		sh("npm i --save bootstrap angular-strap");
	}
	else {
		delete vendorJs["angular-strap"];
		delete vendorCss.bootstrap;
	}

	if (input["Angular Material"].match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Angular Material...");

		!fs.existsSync("app/extension/ui") && sh("mkdir app/extension/ui/");

		sh("cp -a node_modules/rectangular/install/extra/extension/ui/material-design/. app/extension/ui/");
		sh("npm i --save angular-material");
	}
	else {
		delete vendorJs["angular-material"];
		delete vendorCss["angular-material"];
	}

	if (input.Bluebird.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Bluebird...");

		sh("mkdir app/extension/promise/");
		sh("cp -a node_modules/rectangular/install/extra/extension/promise/. app/extension/promise/");
		sh("npm i --save bluebird");
	}

	if (input.MomentJS.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing MomentJS...");

		sh("mkdir app/extension/date/");
		sh("cp -a node_modules/rectangular/install/extra/extension/date/. app/extension/date/");
		sh("npm i --save moment && npm i --save angular-moment");
	}
	else {
		delete vendorJs.moment;
	}

	if (input.D3.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Lodash...");

		sh("mkdir app/extension/chart/");
		sh("cp -a node_modules/rectangular/install/extra/extension/chart/. app/extension/chart/");
		sh("npm i --save d3");
	}

	if (input.Lodash.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Lodash...");

		sh("mkdir app/extension/_/");
		sh("cp -a node_modules/rectangular/install/extra/extension/_/. app/extension/_/");
		sh("npm i --save lodash");
	}
	else if (input.Restangular.match(/^(?:No|no|N|n)$/)) {
		delete vendorJs.lodash;
	}

	if (input.Teleprint.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Teleprint...");

		sh("mkdir app/extension/print/");
		sh("cp -a node_modules/rectangular/install/extra/extension/print/. app/extension/print/");
		sh("npm i --save teleprint");
	}

	if (input["Angular Translate"].match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Angular Translate...");

		sh("mkdir app/extension/locale/");
		sh("cp -a node_modules/rectangular/install/extra/extension/locale/. app/extension/locale/");
		sh("npm i --save angular-translate");
	}
	else {
		delete vendorJs["angular-translate"];
	}

	if (input.Restangular.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Restangular...");

		sh("mkdir app/extension/rest/");
		sh("cp -a node_modules/rectangular/install/extra/extension/rest/. app/extension/rest/");
		sh("npm i --save lodash restangular");
	}
	else {
		delete vendorJs.restangular;
	}

	if (input.SocketIO.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing SocketIO...");

		sh("mkdir app/extension/upload/");
		sh("cp -a node_modules/rectangular/install/extra/extension/socket/. app/extension/socket/");
		sh("npm i --save socket.io");
	}
	else {
		delete vendorJs["socket.io"];;
	}

	if (input["NG File Upload"].match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing NG File Upload...");

		sh("mkdir app/extension/upload/");
		sh("cp -a node_modules/rectangular/install/extra/extension/upload/. app/extension/upload/");
		sh("npm i --save ng-file-upload");
	}
	else {
		delete vendorJs["ng-file-upload"];;
	}

	// Update paths
	deps.script = vendorJs;
	deps.style = vendorCss;

	fs.writeFile(__dirname + "/dev/deps.json", JSON.stringify(deps));
});
<commit_msg>Bugfix<commit_after>"use strict";

const deps = require("./dev/deps.json");
const fs = require("fs");
const prompt = require("prompt");
const sh = require("shellcmd");
const vendorCss = deps.style;
const vendorJs = deps.script;

// Message
console.log("\nChoose extra packages to install:\n");

// Start the prompt
prompt.start();

const schema = {
	properties: require(__dirname + "/vendor.js")
};

prompt.get(schema, (err, input) => {
	if (input["Angular Strap"].match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Angular Strap...");

		sh("mkdir app/extension/ui/");
		sh("cp -a node_modules/rectangular/install/extra/extension/ui/bootstrap/. app/extension/ui/");
		sh("npm i --save bootstrap angular-strap");
	}
	else {
		delete vendorJs["angular-strap"];
		delete vendorCss.bootstrap;
	}

	if (input["Angular Material"].match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Angular Material...");

		!fs.existsSync("app/extension/ui") && sh("mkdir app/extension/ui/");

		sh("cp -a node_modules/rectangular/install/extra/extension/ui/material-design/. app/extension/ui/");
		sh("npm i --save angular-material");
	}
	else {
		delete vendorJs["angular-material"];
		delete vendorCss["angular-material"];
	}

	if (input.Bluebird.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Bluebird...");

		sh("mkdir app/extension/promise/");
		sh("cp -a node_modules/rectangular/install/extra/extension/promise/. app/extension/promise/");
		sh("npm i --save bluebird");
	}

	if (input.MomentJS.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing MomentJS...");

		sh("mkdir app/extension/date/");
		sh("cp -a node_modules/rectangular/install/extra/extension/date/. app/extension/date/");
		sh("npm i --save moment && npm i --save angular-moment");
	}
	else {
		delete vendorJs.moment;
	}

	if (input.D3.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Lodash...");

		sh("mkdir app/extension/chart/");
		sh("cp -a node_modules/rectangular/install/extra/extension/chart/. app/extension/chart/");
		sh("npm i --save d3");
	}

	if (input.Lodash.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Lodash...");

		sh("mkdir app/extension/_/");
		sh("cp -a node_modules/rectangular/install/extra/extension/_/. app/extension/_/");
		sh("npm i --save lodash");
	}
	else if (input.Restangular.match(/^(?:No|no|N|n)$/)) {
		delete vendorJs.lodash;
	}

	if (input.Teleprint.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Teleprint...");

		sh("mkdir app/extension/print/");
		sh("cp -a node_modules/rectangular/install/extra/extension/print/. app/extension/print/");
		sh("npm i --save teleprint");
	}

	if (input["Angular Translate"].match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Angular Translate...");

		sh("mkdir app/extension/locale/");
		sh("cp -a node_modules/rectangular/install/extra/extension/locale/. app/extension/locale/");
		sh("npm i --save angular-translate");
	}
	else {
		delete vendorJs["angular-translate"];
	}

	if (input.Restangular.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing Restangular...");

		sh("mkdir app/extension/rest/");
		sh("cp -a node_modules/rectangular/install/extra/extension/rest/. app/extension/rest/");
		sh("npm i --save lodash restangular");
	}
	else {
		delete vendorJs.restangular;
	}

	if (input.SocketIO.match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing SocketIO...");

		sh("mkdir app/extension/upload/");
		sh("cp -a node_modules/rectangular/install/extra/extension/socket/. app/extension/socket/");
		sh("npm i --save socket.io");
	}
	else {
		delete vendorJs["socket.io"];;
	}

	if (input["NG File Upload"].match(/^(?:Yes|yes|Y|y)$/)) {
		console.log("");
		console.log("Installing NG File Upload...");

		sh("mkdir app/extension/upload/");
		sh("cp -a node_modules/rectangular/install/extra/extension/upload/. app/extension/upload/");
		sh("npm i --save ng-file-upload");
	}
	else {
		delete vendorJs["ng-file-upload"];;
	}

	// Update paths
	deps.script = vendorJs;
	deps.style = vendorCss;

	fs.writeFile(__dirname + "/dev/deps.json", JSON.stringify(deps));
});
<|endoftext|>


<commit_before>/**
 * Nextcloud - passman
 *
 * @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
 * @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

(function () {
	'use strict';

	/**
	 * @ngdoc service
	 * @name passmanApp.ShareService
	 * @description
	 * # ShareService
	 * Service in the passmanApp.
	 */
	angular.module('passmanApp')
		.service('ShareService', ['$http', 'VaultService', 'EncryptService', 'CredentialService', function ($http, VaultService, EncryptService, CredentialService) {
			// Setup sjcl random engine to max paranoia level and start collecting data
			var paranoia_level = 10;
			/** global: sjcl */
			sjcl.random.setDefaultParanoia(paranoia_level);
			sjcl.random.startCollectors();

			return {
				search: function (string) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/search');
					return $http.post(queryUrl, {search: string}).then(function (response) {
						if (response.data) {
							return response.data;
						} else {
							return response;
						}
					});
				},
				shareWithUser: function (credential, target_user_data) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/share');
					return $http.post(queryUrl,
						{
							item_id: credential.credential_id,
							item_guid: credential.guid,
							permissions: target_user_data.accessLevel,
							vaults: target_user_data.vaults,
						}
					);
				},
				getVaultsByUser: function (userId) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/vaults/' + userId);
					return $http.get(queryUrl, {search: userId}).then(function (response) {
						if (response.data) {
							for (var i = 0; i < response.data.length; i++) {
								/** global: forge */
								response.data[i].public_sharing_key = forge.pki.publicKeyFromPem(response.data[i].public_sharing_key);
							}
							return response.data;
						} else {
							return response;
						}
					});
				},
				getPendingRequests: function () {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/pending');
					return $http.get(queryUrl).then(function (response) {
						if (response.data) {
							return response.data;
						}
					});
				},
				saveSharingRequest: function (request, crypted_shared_key) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/save');
					return $http.post(queryUrl, {
						item_guid: request.item_guid,
						target_vault_guid: request.target_vault_guid,
						final_shared_key: crypted_shared_key
					}).then(function (response) {
						return response.data;
					});
				},
				declineSharingRequest: function (request) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/decline/' + request.req_id);
					return $http.delete(queryUrl).then(function (response) {
						return response.data;
					});
				},
				unshareCredential: function (credential) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid);
					return $http.delete(queryUrl).then(function (response) {
						return response.data;
					});
				},

				unshareCredentialFromUser: function (credential, user_id) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/' + user_id);
					return $http.delete(queryUrl).then(function (response) {
						return response.data;
					});
				},

				createPublicSharedCredential: function (shareObj) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/public');
					return $http.post(queryUrl, shareObj).then(function (response) {
						return response.data;
					});
				},
				getPublicSharedCredential: function (credential_guid) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential_guid + '/public');
					return $http.get(queryUrl).then(function (response) {
							if (response.data) {
								return response;
							} else {
								return response;
							}
						},
						function (result) {
							return result;
						});
				},
				getSharedCredentialACL: function (credential) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/acl');
					return $http.get(queryUrl).then(function (response) {
							if (response.data) {
								return response.data;
							} else {
								return response;
							}
						},
						function (result) {
							return result;
						});
				},
				updateCredentialAcl: function (credential, acl) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/acl');
					return $http.patch(queryUrl, acl).then(function (response) {
						return response.data;
					});
				},
				getCredendialsSharedWithUs: function (vault_guid) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/vault/' + vault_guid + '/get');
					return $http.get(queryUrl).then(function (response) {
						if (response.data) {
							return response.data;
						}
					});
				},
				downloadSharedFile: function (credential, file) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/file/' + file.guid);
					return $http.get(queryUrl).then(function (response) {
						if (response.data) {
							return response.data;
						}
					});
				},
				encryptSharedCredential: function (credential, sharedKey) {
					var _credential = angular.copy(credential);
					_credential.shared_key = EncryptService.encryptString(sharedKey);
					var encrypted_fields = CredentialService.getEncryptedFields();
					for (var i = 0; i < encrypted_fields.length; i++) {
						var field = encrypted_fields[i];
						var fieldValue = angular.copy(credential[field]);
						_credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue), sharedKey);
					}
					return _credential;
				},
				decryptSharedCredential: function (credential, sharedKey) {

					var _credential = angular.copy(credential);
					var encrypted_fields = CredentialService.getEncryptedFields();
					for (var i = 0; i < encrypted_fields.length; i++) {

						var field = encrypted_fields[i];
						var fieldValue = angular.copy(_credential[field]);
						var field_decrypted_value;
						if (_credential.hasOwnProperty(field)) {
							try {
								field_decrypted_value = EncryptService.decryptString(fieldValue, sharedKey);
							} catch (e) {
								if(field === 'compromised'){
									//old shares do not have compromised set, so we need to make sure that it will be set for version 2.3.0
									//credentials from 2.3.0 onwards have compromised already set, and don't need to worry about that.
									//if compromised is not the issue, break and throw an error
									field_decrypted_value=0;
								}else{
									throw e;
								}
							}
							try {
								_credential[field] = JSON.parse(field_decrypted_value);
							} catch (e) {
								console.warn('Field' + field + ' in ' + _credential.label + ' could not be parsed! Value:' + fieldValue);
								throw e;
							}
						}
					}
					return _credential;
				},

				generateRSAKeys: function (key_length) {
					/** global: C_Promise */
					var p = new C_Promise(function () {
						/** global: forge */
						var state = forge.pki.rsa.createKeyPairGenerationState(key_length, 0x10001);
						var step = function () {
							// run for 100 ms
							/** global: forge */
							if (!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
								if (state.p !== null) {
									// progress(50);
									this.call_progress(50);
								}
								else {
									// progress(0);
									this.call_progress(0);
								}
								setTimeout(step.bind(this), 1);
							}
							else {
								// callback(state.keys);
								this.call_then(state.keys);
							}
						};
						setTimeout(step.bind(this), 100);
					});
					return p;
				},
				generateSharedKey: function (size) {
					size = size || 20;
					/** global: C_Promise */
					return new C_Promise(function () {
						var t = this;
						/** global: CRYPTO */
						CRYPTO.PASSWORD.generate(size,
							function (pass) {
								t.call_then(pass);
							},
							function (progress) {
								t.call_progress(progress);
							}
						);
					});
				},
				rsaKeyPairToPEM: function (keypair) {
					return {
						'publicKey': forge.pki.publicKeyToPem(keypair.publicKey),
						'privateKey': forge.pki.privateKeyToPem(keypair.privateKey)
					};
				},
				getSharingKeys: function () {
					var vault = VaultService.getActiveVault();
					return {
						'private_sharing_key': EncryptService.decryptString(angular.copy(vault.private_sharing_key)),
						'public_sharing_key': vault.public_sharing_key
					};
				},
				rsaPrivateKeyFromPEM: function (private_pem) {
					/** global: forge */
					return forge.pki.privateKeyFromPem(private_pem);
				},
				rsaPublicKeyFromPEM: function (public_pem) {
					/** global: forge */
					return forge.pki.publicKeyFromPem(public_pem);
				},
				/**
				 * Cyphers an array of string in a non-blocking way
				 * @param vaults[]    An array of vaults with the processed public keys
				 * @param string    The string to cypher
				 */
				cypherRSAStringWithPublicKeyBulkAsync: function (vaults, string) {
					var workload = function () {
						if (this.current_index < this.vaults.length > 0 && this.vaults.length > 0) {
							var _vault = angular.copy(this.vaults[this.current_index]);
							/** global: forge */
							_vault.key = forge.util.encode64(
								_vault.public_sharing_key.encrypt(this.string)
							);
							this.data.push(
								_vault
							);
							this.current_index++;

							this.call_progress(this.current_index);
							setTimeout(workload.bind(this), 1);
						}
						else {
							this.call_then(this.data);
						}
					};
					/** global: C_Promise */
					return new C_Promise(function () {
						this.data = [];
						this.vaults = vaults;
						this.string = string;
						this.current_index = 0;

						setTimeout(workload.bind(this), 0);
					});
				}
			};
		}]);
}());<commit_msg>Add null-check to further improve errorchecking for missing compromisedfield for pre 2.3.0 shares<commit_after>/**
 * Nextcloud - passman
 *
 * @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
 * @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

(function () {
	'use strict';

	/**
	 * @ngdoc service
	 * @name passmanApp.ShareService
	 * @description
	 * # ShareService
	 * Service in the passmanApp.
	 */
	angular.module('passmanApp')
		.service('ShareService', ['$http', 'VaultService', 'EncryptService', 'CredentialService', function ($http, VaultService, EncryptService, CredentialService) {
			// Setup sjcl random engine to max paranoia level and start collecting data
			var paranoia_level = 10;
			/** global: sjcl */
			sjcl.random.setDefaultParanoia(paranoia_level);
			sjcl.random.startCollectors();

			return {
				search: function (string) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/search');
					return $http.post(queryUrl, {search: string}).then(function (response) {
						if (response.data) {
							return response.data;
						} else {
							return response;
						}
					});
				},
				shareWithUser: function (credential, target_user_data) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/share');
					return $http.post(queryUrl,
						{
							item_id: credential.credential_id,
							item_guid: credential.guid,
							permissions: target_user_data.accessLevel,
							vaults: target_user_data.vaults,
						}
					);
				},
				getVaultsByUser: function (userId) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/vaults/' + userId);
					return $http.get(queryUrl, {search: userId}).then(function (response) {
						if (response.data) {
							for (var i = 0; i < response.data.length; i++) {
								/** global: forge */
								response.data[i].public_sharing_key = forge.pki.publicKeyFromPem(response.data[i].public_sharing_key);
							}
							return response.data;
						} else {
							return response;
						}
					});
				},
				getPendingRequests: function () {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/pending');
					return $http.get(queryUrl).then(function (response) {
						if (response.data) {
							return response.data;
						}
					});
				},
				saveSharingRequest: function (request, crypted_shared_key) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/save');
					return $http.post(queryUrl, {
						item_guid: request.item_guid,
						target_vault_guid: request.target_vault_guid,
						final_shared_key: crypted_shared_key
					}).then(function (response) {
						return response.data;
					});
				},
				declineSharingRequest: function (request) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/decline/' + request.req_id);
					return $http.delete(queryUrl).then(function (response) {
						return response.data;
					});
				},
				unshareCredential: function (credential) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid);
					return $http.delete(queryUrl).then(function (response) {
						return response.data;
					});
				},

				unshareCredentialFromUser: function (credential, user_id) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/' + user_id);
					return $http.delete(queryUrl).then(function (response) {
						return response.data;
					});
				},

				createPublicSharedCredential: function (shareObj) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/public');
					return $http.post(queryUrl, shareObj).then(function (response) {
						return response.data;
					});
				},
				getPublicSharedCredential: function (credential_guid) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential_guid + '/public');
					return $http.get(queryUrl).then(function (response) {
							if (response.data) {
								return response;
							} else {
								return response;
							}
						},
						function (result) {
							return result;
						});
				},
				getSharedCredentialACL: function (credential) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/acl');
					return $http.get(queryUrl).then(function (response) {
							if (response.data) {
								return response.data;
							} else {
								return response;
							}
						},
						function (result) {
							return result;
						});
				},
				updateCredentialAcl: function (credential, acl) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/acl');
					return $http.patch(queryUrl, acl).then(function (response) {
						return response.data;
					});
				},
				getCredendialsSharedWithUs: function (vault_guid) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/vault/' + vault_guid + '/get');
					return $http.get(queryUrl).then(function (response) {
						if (response.data) {
							return response.data;
						}
					});
				},
				downloadSharedFile: function (credential, file) {
					var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/file/' + file.guid);
					return $http.get(queryUrl).then(function (response) {
						if (response.data) {
							return response.data;
						}
					});
				},
				encryptSharedCredential: function (credential, sharedKey) {
					var _credential = angular.copy(credential);
					_credential.shared_key = EncryptService.encryptString(sharedKey);
					var encrypted_fields = CredentialService.getEncryptedFields();
					for (var i = 0; i < encrypted_fields.length; i++) {
						var field = encrypted_fields[i];
						var fieldValue = angular.copy(credential[field]);
						_credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue), sharedKey);
					}
					return _credential;
				},
				decryptSharedCredential: function (credential, sharedKey) {

					var _credential = angular.copy(credential);
					var encrypted_fields = CredentialService.getEncryptedFields();
					for (var i = 0; i < encrypted_fields.length; i++) {

						var field = encrypted_fields[i];
						var fieldValue = angular.copy(_credential[field]);
						var field_decrypted_value;
						if (_credential.hasOwnProperty(field)) {
							try {
								field_decrypted_value = EncryptService.decryptString(fieldValue, sharedKey);
							} catch (e) {
								if(field === 'compromised' && fieldValue === null){
									//old shares do not have compromised set, so we need to make sure that it will be set for version 2.3.0
									//credentials from 2.3.0 onwards have compromised already set, and don't need to worry about that.
									//if compromised is not the issue, break and throw an error
									field_decrypted_value=0;
								}else{
									throw e;
								}
							}
							try {
								_credential[field] = JSON.parse(field_decrypted_value);
							} catch (e) {
								console.warn('Field' + field + ' in ' + _credential.label + ' could not be parsed! Value:' + fieldValue);
								throw e;
							}
						}
					}
					return _credential;
				},

				generateRSAKeys: function (key_length) {
					/** global: C_Promise */
					var p = new C_Promise(function () {
						/** global: forge */
						var state = forge.pki.rsa.createKeyPairGenerationState(key_length, 0x10001);
						var step = function () {
							// run for 100 ms
							/** global: forge */
							if (!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
								if (state.p !== null) {
									// progress(50);
									this.call_progress(50);
								}
								else {
									// progress(0);
									this.call_progress(0);
								}
								setTimeout(step.bind(this), 1);
							}
							else {
								// callback(state.keys);
								this.call_then(state.keys);
							}
						};
						setTimeout(step.bind(this), 100);
					});
					return p;
				},
				generateSharedKey: function (size) {
					size = size || 20;
					/** global: C_Promise */
					return new C_Promise(function () {
						var t = this;
						/** global: CRYPTO */
						CRYPTO.PASSWORD.generate(size,
							function (pass) {
								t.call_then(pass);
							},
							function (progress) {
								t.call_progress(progress);
							}
						);
					});
				},
				rsaKeyPairToPEM: function (keypair) {
					return {
						'publicKey': forge.pki.publicKeyToPem(keypair.publicKey),
						'privateKey': forge.pki.privateKeyToPem(keypair.privateKey)
					};
				},
				getSharingKeys: function () {
					var vault = VaultService.getActiveVault();
					return {
						'private_sharing_key': EncryptService.decryptString(angular.copy(vault.private_sharing_key)),
						'public_sharing_key': vault.public_sharing_key
					};
				},
				rsaPrivateKeyFromPEM: function (private_pem) {
					/** global: forge */
					return forge.pki.privateKeyFromPem(private_pem);
				},
				rsaPublicKeyFromPEM: function (public_pem) {
					/** global: forge */
					return forge.pki.publicKeyFromPem(public_pem);
				},
				/**
				 * Cyphers an array of string in a non-blocking way
				 * @param vaults[]    An array of vaults with the processed public keys
				 * @param string    The string to cypher
				 */
				cypherRSAStringWithPublicKeyBulkAsync: function (vaults, string) {
					var workload = function () {
						if (this.current_index < this.vaults.length > 0 && this.vaults.length > 0) {
							var _vault = angular.copy(this.vaults[this.current_index]);
							/** global: forge */
							_vault.key = forge.util.encode64(
								_vault.public_sharing_key.encrypt(this.string)
							);
							this.data.push(
								_vault
							);
							this.current_index++;

							this.call_progress(this.current_index);
							setTimeout(workload.bind(this), 1);
						}
						else {
							this.call_then(this.data);
						}
					};
					/** global: C_Promise */
					return new C_Promise(function () {
						this.data = [];
						this.vaults = vaults;
						this.string = string;
						this.current_index = 0;

						setTimeout(workload.bind(this), 0);
					});
				}
			};
		}]);
}());<|endoftext|>


<commit_before>/** @jsx React.DOM */
'use strict';

var Lazy = require('lazy.js'),
  React = require('react/addons'),
  ReactIntlMixin = require('react-intl'),
  saveAs = require('filesaver.js');

var BaremeChart = require('./svg/bareme-chart'),
  BaremeSettings = require('./bareme-settings'),
  helpers = require('../../helpers'),
  ReformSelector = require('./reform-selector'),
  SendFeedbackButton = require('../send-feedback-button'),
  VariablesTree = require('./variables-tree'),
  VisualizationSelect = require('./visualization-select');

var obj = helpers.obj;

var BaremeVisualization = React.createClass({
  mixins: [ReactIntlMixin],
  propTypes: {
    chartAspectRatio: React.PropTypes.number.isRequired,
    collapsedVariables: React.PropTypes.object,
    columns: React.PropTypes.object.isRequired,
    defaultProps: React.PropTypes.object.isRequired,
    diffMode: React.PropTypes.bool,
    displayBisectrix: React.PropTypes.bool,
    displaySettings: React.PropTypes.bool,
    downloadAttribution: React.PropTypes.string,
    formatNumber: React.PropTypes.func.isRequired,
    isChartFullWidth: React.PropTypes.bool,
    labelsFontSize: React.PropTypes.number,
    maxHeightRatio: React.PropTypes.number.isRequired,
    noColorFill: React.PropTypes.string.isRequired,
    onDownload: React.PropTypes.func.isRequired,
    onReformChange: React.PropTypes.func.isRequired,
    onSettingsChange: React.PropTypes.func.isRequired,
    onVisualizationChange: React.PropTypes.func.isRequired,
    reformName: React.PropTypes.string,
    testCase: React.PropTypes.object.isRequired,
    variablesTree: React.PropTypes.object.isRequired,
    visualizationSlug: React.PropTypes.string.isRequired,
    xAxisVariableCode: React.PropTypes.string.isRequired,
    xMaxValue: React.PropTypes.number.isRequired,
    xMinValue: React.PropTypes.number.isRequired,
  },
  componentDidMount: function() {
    window.onresize = this.handleWidthChange;
    this.handleWidthChange();
  },
  componentDidUpdate: function(prevProps) {
    if (prevProps.isChartFullWidth !== this.props.isChartFullWidth) {
      this.handleWidthChange();
    }
  },
  componentWillUnmount: function() {
    window.onresize = null;
  },
  formatHint: function(variables) {
    var variableName;
    if (this.state.activeVariableCode) {
      var variable = Lazy(variables).find({code: this.state.activeVariableCode});
      variableName = variable.name;
    } else {
      variableName = this.getIntlMessage('hoverOverChart');
    }
    return variableName;
  },
  getDefaultProps: function() {
    return {
      chartAspectRatio: 4/3,
      collapsedVariables: {},
      maxHeightRatio: 2/3,
      noColorFill: 'gray',
    };
  },
  getInitialState: function() {
    return {
      activeVariableCode: null,
      chartContainerWidth: null,
    };
  },
  getVariables: function() {
    var diffValues = values => {
      var referenceValues = values.slice(0, values.length / 2),
        reformValues = values.slice(values.length / 2, values.length);
      return reformValues.map((value, index) => value - referenceValues[index]);
    };
    var processNode = (variable, baseValues, depth, hidden) => {
      var newVariables = [];
      var isCollapsed = this.isCollapsed(variable);
      if (variable.children) {
        var childrenVariables = [];
        var childBaseValues = baseValues;
        Lazy(variable.children).each(child => {
          var childVariables = processNode(child, childBaseValues, depth + 1, hidden || isCollapsed);
          childrenVariables = childrenVariables.concat(childVariables);
          var values = this.props.diffMode ? diffValues(child.values) : child.values.slice(sliceStart, sliceEnd);
          childBaseValues = Lazy(childBaseValues).zip(values).map(pair => Lazy(pair).sum()).toArray();
        });
        newVariables = newVariables.concat(childrenVariables);
      }
      var values = this.props.diffMode ? diffValues(variable.values) : variable.values.slice(sliceStart, sliceEnd);
      var hasValue = Lazy(values).any(value => value !== 0);
      if (! hidden && hasValue) {
        var newVariableSequence = Lazy(variable).omit(['children']);
        var hasChildren = !! variable.children;
        newVariableSequence = newVariableSequence.assign({
          baseValues: baseValues,
          depth: depth,
          hasChildren: hasChildren,
          isCollapsed: isCollapsed,
          isSubtotal: hasChildren && depth > 0,
          values: values,
        });
        var newVariable = newVariableSequence.toObject();
        newVariables.push(newVariable);
      }
      return newVariables;
    };
    var values = this.props.variablesTree.values;
    var valuesLength = this.props.variablesTree.values.length;
    if ( ! this.props.diffMode) {
      var sliceStart = this.props.reform ? valuesLength / 2 : 0;
      var sliceEnd = this.props.reform ? valuesLength : valuesLength / 2;
    }
    var initBaseValues = Lazy.repeat(0, values.length / 2).toArray();
    var variables = processNode(this.props.variablesTree, initBaseValues, 0, false);
    return variables;
  },
  handleChartDownload: function() {
    var newProps = Lazy(this.refs.chart.props).omit(['ref']).assign({
      attribution: this.props.downloadAttribution,
      height: null,
      width: 800,
    }).toObject();
    var svgString = React.renderComponentToStaticMarkup(BaremeChart(newProps));
    saveAs(
      new Blob([svgString], {type: "image/svg+xml"}),
      this.formatMessage(this.getIntlMessage('baremeFilename'), {extension: 'svg'})
    );
  },
  handleVariableHover: function(variable) {
    this.setState({activeVariableCode: variable ? variable.code : null});
  },
  handleVariableToggle: function (variable) {
    this.props.onSettingsChange({
      collapsedVariables: obj(variable.code, ! this.props.collapsedVariables[variable.code]),
    });
  },
  handleWidthChange: function() {
    var chartContainerDOMNode = this.refs.chartContainer.getDOMNode();
    var width = chartContainerDOMNode.offsetWidth;
    var height = this.props.height || width / this.props.chartAspectRatio,
      maxHeight = window.innerHeight * this.props.maxHeightRatio;
    if (height > maxHeight) {
      height = maxHeight;
      width = height * this.props.chartAspectRatio;
    }
    this.setState({chartContainerWidth: width});
  },
  isCollapsed: function(variable) {
    return variable.code in this.props.collapsedVariables && this.props.collapsedVariables[variable.code];
  },
  render: function() {
    var variables = this.getVariables();
    return (

            <ReformSelector
              diffMode={this.props.diffMode}
              onChange={this.props.onReformChange}
              reformName={this.props.reformName}
            />

              <SendFeedbackButton testCase={this.props.testCase} />

                  <VisualizationSelect
                    onChange={this.props.onVisualizationChange}
                    value={this.props.visualizationSlug}
                  />

                      <input
                        checked={this.props.isChartFullWidth}
                        onChange={event => this.props.onSettingsChange({isChartFullWidth: event.target.checked})}
                        type='checkbox'
                      />
                      {' ' + this.getIntlMessage('fullWidth')}

                {
                  this.state.chartContainerWidth && (
                    <BaremeChart
                      activeVariableCode={this.state.activeVariableCode}
                      displayBisectrix={this.props.displayBisectrix}
                      formatNumber={this.props.formatNumber}
                      onVariableHover={this.handleVariableHover}
                      ref='chart'
                      variables={variables}
                      width={this.state.chartContainerWidth - 15 * 2 /* Substract Bootstrap panel left and right paddings. */}
                      xAxisLabel={
                        this.props.xAxisVariableCode in this.props.columns ?
                          this.props.columns[this.props.xAxisVariableCode].label :
                          ''
                      }
                      xMaxValue={this.props.xMaxValue}
                      xMinValue={this.props.xMinValue}
                    />
                  )
                }

                {variables && this.formatHint(variables)}

                <BaremeSettings
                  columns={this.props.columns}
                  defaultProps={this.props.defaultProps}
                  displayBisectrix={this.props.displayBisectrix}
                  displaySettings={this.props.displaySettings}
                  onSettingsChange={this.props.onSettingsChange}
                  xAxisVariableCode={this.props.xAxisVariableCode}
                  xMaxValue={this.props.xMaxValue}
                  xMinValue={this.props.xMinValue}
                />

              {this.getIntlMessage('variablesDecomposition')}

              <VariablesTree
                activeVariableCode={this.state.activeVariableCode}
                displayVariablesColors={true}
                displayVariablesValues={false}
                formatNumber={this.props.formatNumber}
                negativeColor={this.props.negativeColor}
                noColorFill={this.props.noColorFill}
                onVariableHover={this.handleVariableHover}
                onVariableToggle={this.handleVariableToggle}
                positiveColor={this.props.positiveColor}
                variableHeightByCode={{revdisp: 5}}
                variables={variables}
              />

              {this.getIntlMessage('dataExport')}

                  {this.getIntlMessage('testCase')}
                  <button className='btn btn-default btn-sm' onClick={() => this.props.onDownload('testCase', 'json')}>
                    JSON
                  </button>

                  {this.getIntlMessage('simulationResult')}
                  <button
                    className='btn btn-default btn-sm'
                    onClick={() => this.props.onDownload('simulationResult', 'json')}
                    style={{marginRight: '1em'}}>
                    JSON
                  </button>
                  <button
                    className='btn btn-default btn-sm'
                    onClick={() => this.props.onDownload('simulationResult', 'csv')}>
                    CSV
                  </button>

                  {this.getIntlMessage('chart')}
                  <button className='btn btn-default btn-sm' onClick={this.handleChartDownload}>SVG</button>

    );
  },
});

module.exports = BaremeVisualization;
<commit_msg>Fix prop name<commit_after>/** @jsx React.DOM */
'use strict';

var Lazy = require('lazy.js'),
  React = require('react/addons'),
  ReactIntlMixin = require('react-intl'),
  saveAs = require('filesaver.js');

var BaremeChart = require('./svg/bareme-chart'),
  BaremeSettings = require('./bareme-settings'),
  helpers = require('../../helpers'),
  ReformSelector = require('./reform-selector'),
  SendFeedbackButton = require('../send-feedback-button'),
  VariablesTree = require('./variables-tree'),
  VisualizationSelect = require('./visualization-select');

var obj = helpers.obj;

var BaremeVisualization = React.createClass({
  mixins: [ReactIntlMixin],
  propTypes: {
    chartAspectRatio: React.PropTypes.number.isRequired,
    collapsedVariables: React.PropTypes.object,
    columns: React.PropTypes.object.isRequired,
    defaultProps: React.PropTypes.object.isRequired,
    diffMode: React.PropTypes.bool,
    displayBisectrix: React.PropTypes.bool,
    displaySettings: React.PropTypes.bool,
    downloadAttribution: React.PropTypes.string,
    formatNumber: React.PropTypes.func.isRequired,
    isChartFullWidth: React.PropTypes.bool,
    labelsFontSize: React.PropTypes.number,
    maxHeightRatio: React.PropTypes.number.isRequired,
    noColorFill: React.PropTypes.string.isRequired,
    onDownload: React.PropTypes.func.isRequired,
    onReformChange: React.PropTypes.func.isRequired,
    onSettingsChange: React.PropTypes.func.isRequired,
    onVisualizationChange: React.PropTypes.func.isRequired,
    reformName: React.PropTypes.string,
    testCase: React.PropTypes.object.isRequired,
    variablesTree: React.PropTypes.object.isRequired,
    visualizationSlug: React.PropTypes.string.isRequired,
    xAxisVariableCode: React.PropTypes.string.isRequired,
    xMaxValue: React.PropTypes.number.isRequired,
    xMinValue: React.PropTypes.number.isRequired,
  },
  componentDidMount: function() {
    window.onresize = this.handleWidthChange;
    this.handleWidthChange();
  },
  componentDidUpdate: function(prevProps) {
    if (prevProps.isChartFullWidth !== this.props.isChartFullWidth) {
      this.handleWidthChange();
    }
  },
  componentWillUnmount: function() {
    window.onresize = null;
  },
  formatHint: function(variables) {
    var variableName;
    if (this.state.activeVariableCode) {
      var variable = Lazy(variables).find({code: this.state.activeVariableCode});
      variableName = variable.name;
    } else {
      variableName = this.getIntlMessage('hoverOverChart');
    }
    return variableName;
  },
  getDefaultProps: function() {
    return {
      chartAspectRatio: 4/3,
      collapsedVariables: {},
      maxHeightRatio: 2/3,
      noColorFill: 'gray',
    };
  },
  getInitialState: function() {
    return {
      activeVariableCode: null,
      chartContainerWidth: null,
    };
  },
  getVariables: function() {
    var diffValues = values => {
      var referenceValues = values.slice(0, values.length / 2),
        reformValues = values.slice(values.length / 2, values.length);
      return reformValues.map((value, index) => value - referenceValues[index]);
    };
    var processNode = (variable, baseValues, depth, hidden) => {
      var newVariables = [];
      var isCollapsed = this.isCollapsed(variable);
      if (variable.children) {
        var childrenVariables = [];
        var childBaseValues = baseValues;
        Lazy(variable.children).each(child => {
          var childVariables = processNode(child, childBaseValues, depth + 1, hidden || isCollapsed);
          childrenVariables = childrenVariables.concat(childVariables);
          var values = this.props.diffMode ? diffValues(child.values) : child.values.slice(sliceStart, sliceEnd);
          childBaseValues = Lazy(childBaseValues).zip(values).map(pair => Lazy(pair).sum()).toArray();
        });
        newVariables = newVariables.concat(childrenVariables);
      }
      var values = this.props.diffMode ? diffValues(variable.values) : variable.values.slice(sliceStart, sliceEnd);
      var hasValue = Lazy(values).any(value => value !== 0);
      if (! hidden && hasValue) {
        var newVariableSequence = Lazy(variable).omit(['children']);
        var hasChildren = !! variable.children;
        newVariableSequence = newVariableSequence.assign({
          baseValues: baseValues,
          depth: depth,
          hasChildren: hasChildren,
          isCollapsed: isCollapsed,
          isSubtotal: hasChildren && depth > 0,
          values: values,
        });
        var newVariable = newVariableSequence.toObject();
        newVariables.push(newVariable);
      }
      return newVariables;
    };
    var values = this.props.variablesTree.values;
    var valuesLength = this.props.variablesTree.values.length;
    if ( ! this.props.diffMode) {
      var sliceStart = this.props.reformName ? valuesLength / 2 : 0;
      var sliceEnd = this.props.reformName ? valuesLength : valuesLength / 2;
    }
    var initBaseValues = Lazy.repeat(0, values.length / 2).toArray();
    var variables = processNode(this.props.variablesTree, initBaseValues, 0, false);
    return variables;
  },
  handleChartDownload: function() {
    var newProps = Lazy(this.refs.chart.props).omit(['ref']).assign({
      attribution: this.props.downloadAttribution,
      height: null,
      width: 800,
    }).toObject();
    var svgString = React.renderComponentToStaticMarkup(BaremeChart(newProps));
    saveAs(
      new Blob([svgString], {type: "image/svg+xml"}),
      this.formatMessage(this.getIntlMessage('baremeFilename'), {extension: 'svg'})
    );
  },
  handleVariableHover: function(variable) {
    this.setState({activeVariableCode: variable ? variable.code : null});
  },
  handleVariableToggle: function (variable) {
    this.props.onSettingsChange({
      collapsedVariables: obj(variable.code, ! this.props.collapsedVariables[variable.code]),
    });
  },
  handleWidthChange: function() {
    var chartContainerDOMNode = this.refs.chartContainer.getDOMNode();
    var width = chartContainerDOMNode.offsetWidth;
    var height = this.props.height || width / this.props.chartAspectRatio,
      maxHeight = window.innerHeight * this.props.maxHeightRatio;
    if (height > maxHeight) {
      height = maxHeight;
      width = height * this.props.chartAspectRatio;
    }
    this.setState({chartContainerWidth: width});
  },
  isCollapsed: function(variable) {
    return variable.code in this.props.collapsedVariables && this.props.collapsedVariables[variable.code];
  },
  render: function() {
    var variables = this.getVariables();
    return (

            <ReformSelector
              diffMode={this.props.diffMode}
              onChange={this.props.onReformChange}
              reformName={this.props.reformName}
            />

              <SendFeedbackButton testCase={this.props.testCase} />

                  <VisualizationSelect
                    onChange={this.props.onVisualizationChange}
                    value={this.props.visualizationSlug}
                  />

                      <input
                        checked={this.props.isChartFullWidth}
                        onChange={event => this.props.onSettingsChange({isChartFullWidth: event.target.checked})}
                        type='checkbox'
                      />
                      {' ' + this.getIntlMessage('fullWidth')}

                {
                  this.state.chartContainerWidth && (
                    <BaremeChart
                      activeVariableCode={this.state.activeVariableCode}
                      displayBisectrix={this.props.displayBisectrix}
                      formatNumber={this.props.formatNumber}
                      onVariableHover={this.handleVariableHover}
                      ref='chart'
                      variables={variables}
                      width={this.state.chartContainerWidth - 15 * 2 /* Substract Bootstrap panel left and right paddings. */}
                      xAxisLabel={
                        this.props.xAxisVariableCode in this.props.columns ?
                          this.props.columns[this.props.xAxisVariableCode].label :
                          ''
                      }
                      xMaxValue={this.props.xMaxValue}
                      xMinValue={this.props.xMinValue}
                    />
                  )
                }

                {variables && this.formatHint(variables)}

                <BaremeSettings
                  columns={this.props.columns}
                  defaultProps={this.props.defaultProps}
                  displayBisectrix={this.props.displayBisectrix}
                  displaySettings={this.props.displaySettings}
                  onSettingsChange={this.props.onSettingsChange}
                  xAxisVariableCode={this.props.xAxisVariableCode}
                  xMaxValue={this.props.xMaxValue}
                  xMinValue={this.props.xMinValue}
                />

              {this.getIntlMessage('variablesDecomposition')}

              <VariablesTree
                activeVariableCode={this.state.activeVariableCode}
                displayVariablesColors={true}
                displayVariablesValues={false}
                formatNumber={this.props.formatNumber}
                negativeColor={this.props.negativeColor}
                noColorFill={this.props.noColorFill}
                onVariableHover={this.handleVariableHover}
                onVariableToggle={this.handleVariableToggle}
                positiveColor={this.props.positiveColor}
                variableHeightByCode={{revdisp: 5}}
                variables={variables}
              />

              {this.getIntlMessage('dataExport')}

                  {this.getIntlMessage('testCase')}
                  <button className='btn btn-default btn-sm' onClick={() => this.props.onDownload('testCase', 'json')}>
                    JSON
                  </button>

                  {this.getIntlMessage('simulationResult')}
                  <button
                    className='btn btn-default btn-sm'
                    onClick={() => this.props.onDownload('simulationResult', 'json')}
                    style={{marginRight: '1em'}}>
                    JSON
                  </button>
                  <button
                    className='btn btn-default btn-sm'
                    onClick={() => this.props.onDownload('simulationResult', 'csv')}>
                    CSV
                  </button>

                  {this.getIntlMessage('chart')}
                  <button className='btn btn-default btn-sm' onClick={this.handleChartDownload}>SVG</button>

    );
  },
});

module.exports = BaremeVisualization;
<|endoftext|>


<commit_before>define([
    'knockout',
    'text!./tags-modal.html',
    'components/Component',
    'utils/CommonUtils',
    'utils/AutoBind',
    'utils/DatatableUtils',
    'services/AuthAPI',
    'less!./tags-modal.less',
    'databindings',
], function (
    ko,
    view,
    Component,
    commonUtils,
    AutoBind,
    datatableUtils,
    authApi
) {
    class TagsModal extends AutoBind(Component) {
        constructor(params) {
            super(params);

            this.isModalShown = params.isModalShown;
            this.getTagsList = params.tagsList;
            this.allTagsList = ko.observableArray();
            this.assignedTagsList = ko.observableArray();
            this.tagGroupsList = ko.observableArray();
            this.currentTagGroup = ko.observable();
            this.tagsInGroupList = ko.observableArray();

            this.showCreateCustomTag = ko.observable(false);
            this.newCustomTagName = ko.observable();
            this.newCustomTagGroup = ko.observable();
            this.groupsForCustomTags = ko.observableArray();

            this.isLoading = ko.observable(true);

            this.assignTagFn = params.assignTagFn;
            this.unassignTagFn = params.unassignTagFn;
            this.createNewTagFn = params.createNewTagFn;
            this.loadAvailableTagsFn = params.loadAvailableTagsFn;
            this.checkAssignPermissionFn = params.checkAssignPermissionFn;
            this.checkUnassignPermissionFn = params.checkUnassignPermissionFn;

            this.tableOptions = commonUtils.getTableOptions('XS');

            this.tagGroupsColumns = [
                {
                    title: ko.i18n('columns.name', 'Name'),
                    width: '100px',
                    render: (s, p, d) => {
                        return `<span class="cell-tag-name" data-bind="title: '${d.name}'">${d.name}</span>`;
                    }
                },
                {
                    title: ko.i18n('columns.description', 'Description'),
                    width: '465px',
                    render: (s, p, d) => {
                        return `<span class="cell-group-description" data-bind="title: '${d.description}'">${d.description}</span>`;
                    }
                },
                {
                    title: ko.i18n('columns.type', 'Type'),
                    width: '80px',
                    render: (s, p, d) => {
                        d.typeText = d.type === 'CUSTOM'
                            ? ko.i18n('components.tags.typeCustom', 'Free-form')
                            : ko.i18n('components.tags.typeSystem', 'System');
                        return `<span class="cell-tag-type" data-bind="text: typeText, title: typeText"></span>`;
                    }
                },
                {
                    title: ko.i18n('columns.action', 'Action'),
                    sortable: false,
                    width: '80px',
                    render: (s, p, d) => {
                        d.showIncludedTagsTable = () => this.showIncludedTagsTable(d);
                        return `<a data-bind="css: '${this.classes('action-link')}', click: showIncludedTagsTable, text: ko.i18n('components.tags.showTags', 'Show tags')" class="cell-tag-action"></a>`
                    }
                }
            ];

            this.tagsInGroupColumns = [
                {
                    title: ko.i18n('columns.name', 'Name'),
                    width: '100px',
                    render: (s, p, d) => {
                        return `<span class="cell-tag-name" data-bind="title: '${d.name}'">${d.name}</span>`;
                    }
                },
                {
                    title: ko.i18n('columns.created', 'Created'),
                    width: '120px',
                    render: (s, p, d) => {
                        const dateTime = datatableUtils.getDateFieldFormatter('createdDate')(s, p, d);
                        return `<span class="cell-tag-created" data-bind="title: '${dateTime}'">${dateTime}</span>`;
                    },
                },
                {
                    title: ko.i18n('columns.author', 'Author'),
                    width: '100px',
                    render: (s, p, d) => {
                        const author = datatableUtils.getCreatedByFormatter('System')(s, p, d);
                        return `<span class="cell-tag-author" data-bind="title: '${author}'">${author}</span>`;
                    },
                },
                {
                    title: ko.i18n('columns.description', 'Description'),
                    width: '225px',
                    render: (s, p, d) => {
                        const desc = d.description || '-';
                        return `<span class="cell-tag-description" data-bind="title: '${desc}'">${desc}</span>`;
                    }
                },
                {
                    title: ko.i18n('columns.usageCount', 'Usage count'),
                    width: '90px',
                    data: 'count'
                },
                {
                    title: '',
                    width: '16px',
                    sortable: false,
                    render: (s, p, d) => {
                        if (d.assigned) {
                            return `<span class="fa fa-tag"></span>`;
                        }
                    }
                },
                {
                    title: ko.i18n('columns.action', 'Action'),
                    width: '80px',
                    sortable: false,
                    render: (s, p, d) => {
                        if (d.permissionProtected && !this.checkAssignPermissionFn(d)) {
                            return `<span data-bind="text: ko.i18n('components.tags.notPermitted', 'Not permitted')" class="cell-tag-action"></span>`;
                        }
                        if (!d.assigned) {
                            d.assign = () => this.assignTag(d);
                            return `<a data-bind="css: '${this.classes('action-link')}', click: assign, text: ko.i18n('components.tags.assign', 'Assign')" class="cell-tag-action"></a>`
                        } else {
                            d.unassign = () => this.unassignTag(d);
                            return `<a data-bind="css: '${this.classes('action-link')}', click: unassign, text: ko.i18n('components.tags.unassign', 'Unassign')" class="cell-tag-action assigned"></a>`
                        }
                    }
                }
            ];

            this.isModalShown.subscribe(open => {
                if (!open) {
                    this.allTagsList([]);
                    this.tagsInGroupList([]);
                    return;
                }
                setTimeout(async () => {
                    try {
                        this.isLoading(true);
                        this.assignedTagsList(this.getTagsList());
                        await this.loadAvailableTags();
                        this.isLoading(false);
                    } catch (ex) {
                        console.log(ex);
                    }
                }, 0);
            });
        }

        async loadAvailableTags() {
            const res = await this.loadAvailableTagsFn();
            this.allTagsList(res.filter(t => t.groups && t.groups.length > 0));          // tags without main groups
            this.tagGroupsList(res.filter(t => !t.groups || t.groups.length === 0)       // main tag groups
                .sort((t1, t2) => t1.id - t2.id));
            this.groupsForCustomTags(this.tagGroupsList().filter(tg => tg.allowCustom));
        }

        async assignTag(tag) {
            try {
                let allowAssign = true;

                // non-multi-selection group check
                const nonMultiSelectedTagGroups = tag.groups.filter(tg => !tg.multiSelection);
                if (nonMultiSelectedTagGroups.length > 0) {
                    nonMultiSelectedTagGroups.forEach(ntg => {
                        this.assignedTagsList().filter(t => t.groups.filter(tg => tg.id === ntg.id).length > 0)
                            .forEach(t => {
                                if (t.permissionProtected && !this.checkUnassignPermissionFn(t)) {
                                    allowAssign = false;
                                    alert(ko.i18nformat('components.tags.cannotUnassignProtectedTagWarning', 'Cannot unassign protected tag: <%=tagName%>', {tagName: t.name})());
                                    return;
                                }
                                if (!confirm(ko.i18nformat('components.tags.reassignConfirm', 'The maximum number of assigned tags in the tag group "<%=tagGroup%>" is <%=maxNumber%>. The tag "<%=tagName%>" will be unassigned. Proceed?', {tagGroup: ntg.name, maxNumber: 1, tagName: t.name})())) {
                                    allowAssign = false;
                                    return;
                                }
                                this.unassignTag(t);
                            });
                    });
                }

                if (!allowAssign) {
                    return;
                }

                await this.assignTagFn(tag);
                tag.assigned = true;
                this.assignedTagsList.unshift(tag);
                if (tag.groups.filter(tg => tg.id === this.currentTagGroup().id).length > 0) {
                    this.tagsInGroupList.valueHasMutated();
                }
            } catch (ex) {
                console.log(ex);
            }
        }

        async unassignTag(tag) {
            try {
                await this.unassignTagFn(tag);
                this.assignedTagsList.remove(t => t.id === tag.id);
                ko.utils.arrayForEach(this.tagsInGroupList(), (t) => {
                    if (t.id === tag.id) {
                        t.assigned = false;
                    }
                });
                this.tagsInGroupList.valueHasMutated();
            } catch (ex) {
                console.log(ex);
            }
        }

        async createNewCustomTag() {
            const existingTag = this.exists(this.newCustomTagName());
            if(existingTag) {
                alert(ko.i18nformat('components.tags.tabs.tagNameExistsWarning', 'Tag <%=tagName%> already exists.', {tagName: this.newCustomTagName()})());
                return;
            }

            const tagGroup = this.groupsForCustomTags().filter(tg => tg.id === this.newCustomTagGroup())[0];
            const newTag = {
                name: this.newCustomTagName(),
                groups: [tagGroup]
            };
            try {
                const savedTagRes = await this.createNewTagFn(newTag);
                const savedTag = savedTagRes.data;
                await this.assignTag(savedTag);
                this.allTagsList().unshift(savedTag);
                if (this.newCustomTagGroup() === this.currentTagGroup().id) {
                    this.tagsInGroupList().unshift(savedTag);
                    this.sortByAssigned(this.tagsInGroupList);
                    this.tagsInGroupList.valueHasMutated();
                }
                this.newCustomTagName('');
            } catch (ex) {
                console.log(ex);
            }
        }

        exists(tagName) {
            return this.allTagsList().find(t => t.name === tagName);
        }

        showIncludedTagsTable(tagGroup) {
            const tags = this.allTagsList().filter(t => t.groups.filter(tg => tg.id === tagGroup.id).length > 0);
            tags.forEach(t => t.assigned = this.assignedTagsList().filter(at => at.id === t.id).length > 0);
            this.sortByAssigned(tags);
            this.tagsInGroupList(tags);
            this.currentTagGroup(tagGroup);
        }

        sortByAssigned(list) {
            list.sort((t1, t2) => t1.assigned === t2.assigned ? 0 : t1.assigned ? -1 : 1);
        }
    }

    return commonUtils.build('tags-modal', TagsModal, view);
});
<commit_msg> Tags table rendering error #2782 (#2784)<commit_after>define([
    'knockout',
    'text!./tags-modal.html',
    'components/Component',
    'utils/CommonUtils',
    'utils/AutoBind',
    'utils/DatatableUtils',
    'services/AuthAPI',
    'less!./tags-modal.less',
    'databindings',
], function (
    ko,
    view,
    Component,
    commonUtils,
    AutoBind,
    datatableUtils,
    authApi
) {
    class TagsModal extends AutoBind(Component) {
        constructor(params) {
            super(params);

            this.isModalShown = params.isModalShown;
            this.getTagsList = params.tagsList;
            this.allTagsList = ko.observableArray();
            this.assignedTagsList = ko.observableArray();
            this.tagGroupsList = ko.observableArray();
            this.currentTagGroup = ko.observable();
            this.tagsInGroupList = ko.observableArray();

            this.showCreateCustomTag = ko.observable(false);
            this.newCustomTagName = ko.observable();
            this.newCustomTagGroup = ko.observable();
            this.groupsForCustomTags = ko.observableArray();

            this.isLoading = ko.observable(true);

            this.assignTagFn = params.assignTagFn;
            this.unassignTagFn = params.unassignTagFn;
            this.createNewTagFn = params.createNewTagFn;
            this.loadAvailableTagsFn = params.loadAvailableTagsFn;
            this.checkAssignPermissionFn = params.checkAssignPermissionFn;
            this.checkUnassignPermissionFn = params.checkUnassignPermissionFn;

            this.tableOptions = commonUtils.getTableOptions('XS');

            this.tagGroupsColumns = [
                {
                    title: ko.i18n('columns.name', 'Name'),
                    width: '100px',
                    render: (s, p, d) => {
                        return `<span class="cell-tag-name" data-bind="title: name">${d.name}</span>`;
                    }
                },
                {
                    title: ko.i18n('columns.description', 'Description'),
                    width: '465px',
                    render: (s, p, d) => {
                        return `<span class="cell-group-description" data-bind="title: '${d.description}'">${d.description}</span>`;
                    }
                },
                {
                    title: ko.i18n('columns.type', 'Type'),
                    width: '80px',
                    render: (s, p, d) => {
                        d.typeText = d.type === 'CUSTOM'
                            ? ko.i18n('components.tags.typeCustom', 'Free-form')
                            : ko.i18n('components.tags.typeSystem', 'System');
                        return `<span class="cell-tag-type" data-bind="text: typeText, title: typeText"></span>`;
                    }
                },
                {
                    title: ko.i18n('columns.action', 'Action'),
                    sortable: false,
                    width: '80px',
                    render: (s, p, d) => {
                        d.showIncludedTagsTable = () => this.showIncludedTagsTable(d);
                        return `<a data-bind="css: '${this.classes('action-link')}', click: showIncludedTagsTable, text: ko.i18n('components.tags.showTags', 'Show tags')" class="cell-tag-action"></a>`
                    }
                }
            ];

            this.tagsInGroupColumns = [
                {
                    title: ko.i18n('columns.name', 'Name'),
                    width: '100px',
                    render: (s, p, d) => {
                        return `<span class="cell-tag-name" data-bind="title: name">${d.name}</span>`;
                    }
                },
                {
                    title: ko.i18n('columns.created', 'Created'),
                    width: '120px',
                    render: (s, p, d) => {
                        const dateTime = datatableUtils.getDateFieldFormatter('createdDate')(s, p, d);
                        return `<span class="cell-tag-created" data-bind="title: '${dateTime}'">${dateTime}</span>`;
                    },
                },
                {
                    title: ko.i18n('columns.author', 'Author'),
                    width: '100px',
                    render: (s, p, d) => {
                        const author = datatableUtils.getCreatedByFormatter('System')(s, p, d);
                        return `<span class="cell-tag-author" data-bind="title: '${author}'">${author}</span>`;
                    },
                },
                {
                    title: ko.i18n('columns.description', 'Description'),
                    width: '225px',
                    render: (s, p, d) => {
                        const desc = d.description || '-';
                        return `<span class="cell-tag-description" data-bind="title: '${desc}'">${desc}</span>`;
                    }
                },
                {
                    title: ko.i18n('columns.usageCount', 'Usage count'),
                    width: '90px',
                    data: 'count'
                },
                {
                    title: '',
                    width: '16px',
                    sortable: false,
                    render: (s, p, d) => {
                        if (d.assigned) {
                            return `<span class="fa fa-tag"></span>`;
                        }
                    }
                },
                {
                    title: ko.i18n('columns.action', 'Action'),
                    width: '80px',
                    sortable: false,
                    render: (s, p, d) => {
                        if (d.permissionProtected && !this.checkAssignPermissionFn(d)) {
                            return `<span data-bind="text: ko.i18n('components.tags.notPermitted', 'Not permitted')" class="cell-tag-action"></span>`;
                        }
                        if (!d.assigned) {
                            d.assign = () => this.assignTag(d);
                            return `<a data-bind="css: '${this.classes('action-link')}', click: assign, text: ko.i18n('components.tags.assign', 'Assign')" class="cell-tag-action"></a>`
                        } else {
                            d.unassign = () => this.unassignTag(d);
                            return `<a data-bind="css: '${this.classes('action-link')}', click: unassign, text: ko.i18n('components.tags.unassign', 'Unassign')" class="cell-tag-action assigned"></a>`
                        }
                    }
                }
            ];

            this.isModalShown.subscribe(open => {
                if (!open) {
                    this.allTagsList([]);
                    this.tagsInGroupList([]);
                    return;
                }
                setTimeout(async () => {
                    try {
                        this.isLoading(true);
                        this.assignedTagsList(this.getTagsList());
                        await this.loadAvailableTags();
                        this.isLoading(false);
                    } catch (ex) {
                        console.log(ex);
                    }
                }, 0);
            });
        }

        async loadAvailableTags() {
            const res = await this.loadAvailableTagsFn();
            this.allTagsList(res.filter(t => t.groups && t.groups.length > 0));          // tags without main groups
            this.tagGroupsList(res.filter(t => !t.groups || t.groups.length === 0)       // main tag groups
                .sort((t1, t2) => t1.id - t2.id));
            this.groupsForCustomTags(this.tagGroupsList().filter(tg => tg.allowCustom));
        }

        async assignTag(tag) {
            try {
                let allowAssign = true;

                // non-multi-selection group check
                const nonMultiSelectedTagGroups = tag.groups.filter(tg => !tg.multiSelection);
                if (nonMultiSelectedTagGroups.length > 0) {
                    nonMultiSelectedTagGroups.forEach(ntg => {
                        this.assignedTagsList().filter(t => t.groups.filter(tg => tg.id === ntg.id).length > 0)
                            .forEach(t => {
                                if (t.permissionProtected && !this.checkUnassignPermissionFn(t)) {
                                    allowAssign = false;
                                    alert(ko.i18nformat('components.tags.cannotUnassignProtectedTagWarning', 'Cannot unassign protected tag: <%=tagName%>', {tagName: t.name})());
                                    return;
                                }
                                if (!confirm(ko.i18nformat('components.tags.reassignConfirm', 'The maximum number of assigned tags in the tag group "<%=tagGroup%>" is <%=maxNumber%>. The tag "<%=tagName%>" will be unassigned. Proceed?', {tagGroup: ntg.name, maxNumber: 1, tagName: t.name})())) {
                                    allowAssign = false;
                                    return;
                                }
                                this.unassignTag(t);
                            });
                    });
                }

                if (!allowAssign) {
                    return;
                }

                await this.assignTagFn(tag);
                tag.assigned = true;
                this.assignedTagsList.unshift(tag);
                if (tag.groups.filter(tg => tg.id === this.currentTagGroup().id).length > 0) {
                    this.tagsInGroupList.valueHasMutated();
                }
            } catch (ex) {
                console.log(ex);
            }
        }

        async unassignTag(tag) {
            try {
                await this.unassignTagFn(tag);
                this.assignedTagsList.remove(t => t.id === tag.id);
                ko.utils.arrayForEach(this.tagsInGroupList(), (t) => {
                    if (t.id === tag.id) {
                        t.assigned = false;
                    }
                });
                this.tagsInGroupList.valueHasMutated();
            } catch (ex) {
                console.log(ex);
            }
        }

        async createNewCustomTag() {
            const existingTag = this.exists(this.newCustomTagName());
            if(existingTag) {
                alert(ko.i18nformat('components.tags.tabs.tagNameExistsWarning', 'Tag <%=tagName%> already exists.', {tagName: this.newCustomTagName()})());
                return;
            }

            const tagGroup = this.groupsForCustomTags().filter(tg => tg.id === this.newCustomTagGroup())[0];
            const newTag = {
                name: this.newCustomTagName(),
                groups: [tagGroup]
            };
            try {
                const savedTagRes = await this.createNewTagFn(newTag);
                const savedTag = savedTagRes.data;
                await this.assignTag(savedTag);
                this.allTagsList().unshift(savedTag);
                if (this.newCustomTagGroup() === this.currentTagGroup().id) {
                    this.tagsInGroupList().unshift(savedTag);
                    this.sortByAssigned(this.tagsInGroupList);
                    this.tagsInGroupList.valueHasMutated();
                }
                this.newCustomTagName('');
            } catch (ex) {
                console.log(ex);
            }
        }

        exists(tagName) {
            return this.allTagsList().find(t => t.name === tagName);
        }

        showIncludedTagsTable(tagGroup) {
            const tags = this.allTagsList().filter(t => t.groups.filter(tg => tg.id === tagGroup.id).length > 0);
            tags.forEach(t => t.assigned = this.assignedTagsList().filter(at => at.id === t.id).length > 0);
            this.sortByAssigned(tags);
            this.tagsInGroupList(tags);
            this.currentTagGroup(tagGroup);
        }

        sortByAssigned(list) {
            list.sort((t1, t2) => t1.assigned === t2.assigned ? 0 : t1.assigned ? -1 : 1);
        }
    }

    return commonUtils.build('tags-modal', TagsModal, view);
});
<|endoftext|>